* [RFC] Inter-processor Mailboxes Drivers
From: Meador Inge @ 2011-02-11 21:19 UTC (permalink / raw)
To: linuxppc-dev, linux-arm-kernel
Cc: openmcapi-dev, Blanchard, Hollis, Hiroshi DOYU
Hi All,
I am currently working on building AMP systems using OpenMCAPI
(https://bitbucket.org/hollisb/openmcapi/wiki/Home) as the
inter-processor communication mechanism. With OpenMCAPI we, of course,
need a way to send messages to various cores. On some Freescale PPC
platforms (e.g. P1022DS, MPC8572DS), we have been using message
registers to do this work. Recently, I was looking at the OMAP4
mailboxes to gear up for moving into ARM based platforms.
With that, I noticed 'arch/arm/plat-omap/mailbox.c'. This is very
specific to the OMAP4 boards. I am looking at designing a new set of
drivers to expose a mailbox service to userspace that will be used
for inter-processor communication. This would entail the traditional
generic/specific driver split:
1. Hardware specific bits somewhere under '.../arch/*'. Drivers
for the MPIC message registers on Power and OMAP4 mailboxes, for
example.
2. A higher level driver under '.../drivers/mailbox/*'. That the
pieces in (1) would register with. This piece would expose the
main kernel API.
3. Userspace interfaces for accessing the mailboxes. A
'/dev/mailbox1', '/dev/mailbox2', etc... mapping, for example.
Now I have the following questions:
1. Do others see value in this?
2. Does something like this already exist?
3. Is someone else already working on this?
Any feedback will be greatly appreciated.
--
Meador Inge | meador_inge AT mentor.com
Mentor Embedded | http://www.mentor.com/embedded-software
^ permalink raw reply
* Re: Question on setting up external interrupt for P1020 (MPC8xxx) with device tree
From: Scott Wood @ 2011-02-11 21:56 UTC (permalink / raw)
To: Kenny Ho; +Cc: linuxppc-dev
In-Reply-To: <92C536451C241244A14F3001819BF6688A503B@bnott05.belairnetworks.com>
On Fri, 11 Feb 2011 14:07:26 -0500
Kenny Ho <kho@belairnetworks.com> wrote:
> Hi,
>
>
>
> I am trying to write a device driver that uses an external interrupt
> (one of the 16 irq lines) for the Freescale P1020 processor and I hope
> some of you can help. Am I suppose to setup a separate node in the dts
> and look for that node in my driver to setup the interrupt?
Ideally, yes. It's good to describe your device with a node even if it
doesn't have interrupts.
> Or does the > dts interrupt declaration belong to the interrupt-controller
> declaration? I noticed the mpc8xxx_gpio driver defined its own irq_chip
> but I thought that may be because each gpio pin can trigger an
> interrupt.
Right, it's because gpio is an interrupt controller, not just a user of one.
> Do I need to define my own irq_chip structure for my device
> even if I am using one of the hard irq lines?
No. If the device's node describes the interrupt correctly, you can use
irq_of_parse_and_map() on the device node to get a virtual irq that you can
pass to request_irq().
-Scott
^ permalink raw reply
* RE: Question on setting up external interrupt for P1020 (MPC8xxx) with device tree
From: Kenny Ho @ 2011-02-11 22:15 UTC (permalink / raw)
To: Scott Wood; +Cc: linuxppc-dev
In-Reply-To: <20110211155644.6799f6b6@udp111988uds>
Thanks Scott.
Do you know the logic of the hwirq <-> virq mapping? When I was digging
into the GPIO interrupt, the hwirq seems to be the same as the virq
(both are 47 which is 16 (external interrupt) + 31 (internal interrupt).
If I want to use external interrupt 7 and 8, should the virq also be 7
and 8? For some reason I am getting 41 for virq... may be I am still
doing something wrong.
This is the node definition I created:
rpcl {
compatible =3D "ba,rpcl";
interrupts =3D <0x7 2 0x8 2>;
interrupt-parent =3D <&mpic>;
};
Kenny
-----Original Message-----
From: Scott Wood [mailto:scottwood@freescale.com]=20
Sent: Friday, February 11, 2011 4:57 PM
To: Kenny Ho
Cc: linuxppc-dev@lists.ozlabs.org
Subject: Re: Question on setting up external interrupt for P1020
(MPC8xxx) with device tree
On Fri, 11 Feb 2011 14:07:26 -0500
Kenny Ho <kho@belairnetworks.com> wrote:
> Hi,
>=20
> =20
>=20
> I am trying to write a device driver that uses an external interrupt
> (one of the 16 irq lines) for the Freescale P1020 processor and I hope
> some of you can help. Am I suppose to setup a separate node in the
dts
> and look for that node in my driver to setup the interrupt?=20
Ideally, yes. It's good to describe your device with a node even if it
doesn't have interrupts.
> Or does the > dts interrupt declaration belong to the
interrupt-controller
> declaration? I noticed the mpc8xxx_gpio driver defined its own
irq_chip
> but I thought that may be because each gpio pin can trigger an
> interrupt.
Right, it's because gpio is an interrupt controller, not just a user of
one.
> Do I need to define my own irq_chip structure for my device
> even if I am using one of the hard irq lines? =20
No. If the device's node describes the interrupt correctly, you can use
irq_of_parse_and_map() on the device node to get a virtual irq that you
can
pass to request_irq().
-Scott
^ permalink raw reply
* Re: Question on setting up external interrupt for P1020 (MPC8xxx) with device tree
From: Scott Wood @ 2011-02-11 22:19 UTC (permalink / raw)
To: Kenny Ho; +Cc: linuxppc-dev
In-Reply-To: <92C536451C241244A14F3001819BF6688A503C@bnott05.belairnetworks.com>
On Fri, 11 Feb 2011 17:15:47 -0500
Kenny Ho <kho@belairnetworks.com> wrote:
> Thanks Scott.
>
> Do you know the logic of the hwirq <-> virq mapping?
It's dynamic.
> When I was digging into the GPIO interrupt, the hwirq seems to be the same as the virq
The allocator tries that if it's available, but if that virq is already
taken, or is in the legacy ISA region under 16, you'll get a different
number.
> (both are 47 which is 16 (external interrupt) + 31 (internal interrupt).
> If I want to use external interrupt 7 and 8, should the virq also be 7
> and 8?
No.
> For some reason I am getting 41 for virq... may be I am still
> doing something wrong.
It's correct. Pass what you receive into request_irq().
-Scott
^ permalink raw reply
* [PATCH RFCv7 0/2] CARMA Board Support
From: Ira W. Snyder @ 2011-02-11 23:34 UTC (permalink / raw)
To: linuxppc-dev; +Cc: dmitry.torokhov, linux-kernel, Ira W. Snyder
Hello everyone,
This is the seventh posting of these drivers, taking into account comments
from earlier postings. I've made sure that the drivers both pass checkpatch
without any errors or warnings. I would appreciate as much review as you
can offer, so that these can get into the next merge cycle. They've been
sitting outside mainline for far too long.
RFCv6 -> RFCv7:
- reference count private data structure (to support unbind)
- use #defines instead of hex values for registers
- keep lines <=80 characters
RFCv5 -> RFCv6:
- change locking in several functions
- use list_move_tail() to simplify code
- remove unused helper functions
RFCv4 -> RFCv5:
- remove unecessary locking per review comments
- do not clobber return values from *_interruptible()
- explicitly track buffer DMA mapping
- use #defines instead of raw hex addresses
- change enable sysfs attribute to root-writeable only
RFCv3 -> RFCv4:
- updates for DATA-FPGA version 2
RFCv2 -> RFCv3:
- use miscdevice framework (removing the carma class)
- add bitfile readback capability to the programmer
RFCv1 -> RFCv2:
- change comments to kerneldoc format
- Kconfig improvements
- use the videobuf_dma_sg API in the programmer
- updates for Freescale DMAEngine DMA_SLAVE API changes
KNOWN ISSUES:
- untested with a setup that can generate interrupts (will get access soon)
- does not handle runtime "unbind"
Information about the CARMA board:
The CARMA board is essentially an MPC8349EA MDS reference design with a
1GHz ADC and 4 high powered data processing FPGAs connected to the local
bus. It is all packed into a compact PCI form factor. It is used at the
Owens Valley Radio Observatory as the main component in the correlator
system.
For board information, see:
http://www.mmarray.org/~dwh/carma_board/index.html
For DATA-FPGA register layout, see:
http://www.mmarray.org/memos/carma_memo46.pdf
These drivers are the necessary pieces to get the data processing FPGAs
working and producing data. Despite the fact that the hardware is custom
and we are the only users, I'd still like to get the drivers upstream.
Several people have suggested that this is possible.
Some further patches will be forthcoming. I have a driver for the LED
subsystem and the PPS subsystem. The LED register layout is expected to
change soon, so I won't post the driver until that is finished. The PPS
driver will be posted seperately from this patch series; it is very
generic.
Thanks to everyone who has provided comments on earlier versions!
Ira W. Snyder (2):
misc: add CARMA DATA-FPGA Access Driver
misc: add CARMA DATA-FPGA Programmer support
drivers/misc/Kconfig | 1 +
drivers/misc/Makefile | 1 +
drivers/misc/carma/Kconfig | 18 +
drivers/misc/carma/Makefile | 2 +
drivers/misc/carma/carma-fpga-program.c | 1141 ++++++++++++++++++++++++
drivers/misc/carma/carma-fpga.c | 1433 +++++++++++++++++++++++++++++++
6 files changed, 2596 insertions(+), 0 deletions(-)
create mode 100644 drivers/misc/carma/Kconfig
create mode 100644 drivers/misc/carma/Makefile
create mode 100644 drivers/misc/carma/carma-fpga-program.c
create mode 100644 drivers/misc/carma/carma-fpga.c
--
1.7.3.4
^ permalink raw reply
* [PATCH RFCv7 2/2] misc: add CARMA DATA-FPGA Programmer support
From: Ira W. Snyder @ 2011-02-11 23:34 UTC (permalink / raw)
To: linuxppc-dev; +Cc: dmitry.torokhov, linux-kernel, Ira W. Snyder
In-Reply-To: <1297467270-29576-1-git-send-email-iws@ovro.caltech.edu>
This adds support for programming the data processing FPGAs on the OVRO
CARMA board. These FPGAs have a special programming sequence that
requires that we program the Freescale DMA engine, which is only
available inside the kernel.
Signed-off-by: Ira W. Snyder <iws@ovro.caltech.edu>
---
drivers/misc/carma/Kconfig | 9 +
drivers/misc/carma/Makefile | 1 +
drivers/misc/carma/carma-fpga-program.c | 1141 +++++++++++++++++++++++++++++++
3 files changed, 1151 insertions(+), 0 deletions(-)
create mode 100644 drivers/misc/carma/carma-fpga-program.c
diff --git a/drivers/misc/carma/Kconfig b/drivers/misc/carma/Kconfig
index 4be183f..e57a9d3 100644
--- a/drivers/misc/carma/Kconfig
+++ b/drivers/misc/carma/Kconfig
@@ -7,3 +7,12 @@ config CARMA_FPGA
Say Y here to include support for communicating with the data
processing FPGAs on the OVRO CARMA board.
+config CARMA_FPGA_PROGRAM
+ tristate "CARMA DATA-FPGA Programmer"
+ depends on FSL_SOC && PPC_83xx && MEDIA_SUPPORT && HAS_DMA && FSL_DMA
+ select VIDEOBUF_DMA_SG
+ default n
+ help
+ Say Y here to include support for programming the data processing
+ FPGAs on the OVRO CARMA board.
+
diff --git a/drivers/misc/carma/Makefile b/drivers/misc/carma/Makefile
index 0b69fa7..ff36ac2 100644
--- a/drivers/misc/carma/Makefile
+++ b/drivers/misc/carma/Makefile
@@ -1 +1,2 @@
obj-$(CONFIG_CARMA_FPGA) += carma-fpga.o
+obj-$(CONFIG_CARMA_FPGA_PROGRAM) += carma-fpga-program.o
diff --git a/drivers/misc/carma/carma-fpga-program.c b/drivers/misc/carma/carma-fpga-program.c
new file mode 100644
index 0000000..7ce6065
--- /dev/null
+++ b/drivers/misc/carma/carma-fpga-program.c
@@ -0,0 +1,1141 @@
+/*
+ * CARMA Board DATA-FPGA Programmer
+ *
+ * Copyright (c) 2009-2011 Ira W. Snyder <iws@ovro.caltech.edu>
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ */
+
+#include <linux/dma-mapping.h>
+#include <linux/of_platform.h>
+#include <linux/completion.h>
+#include <linux/miscdevice.h>
+#include <linux/dmaengine.h>
+#include <linux/interrupt.h>
+#include <linux/highmem.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/delay.h>
+#include <linux/init.h>
+#include <linux/leds.h>
+#include <linux/slab.h>
+#include <linux/kref.h>
+#include <linux/fs.h>
+#include <linux/io.h>
+
+#include <media/videobuf-dma-sg.h>
+
+/* MPC8349EMDS specific get_immrbase() */
+#include <sysdev/fsl_soc.h>
+
+static const char drv_name[] = "carma-fpga-program";
+
+/*
+ * Firmware images are always this exact size
+ *
+ * 12849552 bytes for a CARMA Digitizer Board (EP2S90 FPGAs)
+ * 18662880 bytes for a CARMA Correlator Board (EP2S130 FPGAs)
+ */
+#define FW_SIZE_EP2S90 12849552
+#define FW_SIZE_EP2S130 18662880
+
+struct fpga_dev {
+ struct miscdevice miscdev;
+
+ /* Reference count */
+ struct kref ref;
+
+ /* Device Registers */
+ struct device *dev;
+ void __iomem *regs;
+ void __iomem *immr;
+
+ /* Freescale DMA Device */
+ struct dma_chan *chan;
+
+ /* Interrupts */
+ int irq, status;
+ struct completion completion;
+
+ /* FPGA Bitfile */
+ struct mutex lock;
+
+ struct videobuf_dmabuf vb;
+ bool vb_allocated;
+
+ /* max size and written bytes */
+ size_t fw_size;
+ size_t bytes;
+};
+
+/*
+ * FPGA Bitfile Helpers
+ */
+
+/**
+ * fpga_drop_firmware_data() - drop the bitfile image from memory
+ * @priv: the driver's private data structure
+ *
+ * LOCKING: must hold priv->lock
+ */
+static void fpga_drop_firmware_data(struct fpga_dev *priv)
+{
+ videobuf_dma_free(&priv->vb);
+ priv->vb_allocated = false;
+ priv->bytes = 0;
+}
+
+/*
+ * Private Data Reference Count
+ */
+
+static void fpga_dev_remove(struct kref *ref)
+{
+ struct fpga_dev *priv = container_of(ref, struct fpga_dev, ref);
+
+ /* free any firmware image that was not programmed */
+ fpga_drop_firmware_data(priv);
+
+ mutex_destroy(&priv->lock);
+ kfree(priv);
+}
+
+/*
+ * LED Trigger (could be a seperate module)
+ */
+
+/*
+ * NOTE: this whole thing does have the problem that whenever the led's are
+ * NOTE: first set to use the fpga trigger, they could be in the wrong state
+ */
+
+DEFINE_LED_TRIGGER(ledtrig_fpga);
+
+static void ledtrig_fpga_programmed(bool enabled)
+{
+ if (enabled)
+ led_trigger_event(ledtrig_fpga, LED_FULL);
+ else
+ led_trigger_event(ledtrig_fpga, LED_OFF);
+}
+
+/*
+ * FPGA Register Helpers
+ */
+
+/* Register Definitions */
+#define FPGA_CONFIG_CONTROL 0x40
+#define FPGA_CONFIG_STATUS 0x44
+#define FPGA_CONFIG_FIFO_SIZE 0x48
+#define FPGA_CONFIG_FIFO_USED 0x4C
+#define FPGA_CONFIG_TOTAL_BYTE_COUNT 0x50
+#define FPGA_CONFIG_CUR_BYTE_COUNT 0x54
+
+#define FPGA_FIFO_ADDRESS 0x3000
+
+static int fpga_fifo_size(void __iomem *regs)
+{
+ return ioread32be(regs + FPGA_CONFIG_FIFO_SIZE);
+}
+
+#define CFG_STATUS_ERR_MASK 0xfffe
+
+static int fpga_config_error(void __iomem *regs)
+{
+ return ioread32be(regs + FPGA_CONFIG_STATUS) & CFG_STATUS_ERR_MASK;
+}
+
+static int fpga_fifo_empty(void __iomem *regs)
+{
+ return ioread32be(regs + FPGA_CONFIG_FIFO_USED) == 0;
+}
+
+static void fpga_fifo_write(void __iomem *regs, u32 val)
+{
+ iowrite32be(val, regs + FPGA_FIFO_ADDRESS);
+}
+
+static void fpga_set_byte_count(void __iomem *regs, u32 count)
+{
+ iowrite32be(count, regs + FPGA_CONFIG_TOTAL_BYTE_COUNT);
+}
+
+#define CFG_CTL_ENABLE (1 << 0)
+#define CFG_CTL_RESET (1 << 1)
+#define CFG_CTL_DMA (1 << 2)
+
+static void fpga_programmer_enable(struct fpga_dev *priv, bool dma)
+{
+ u32 val;
+
+ val = (dma) ? (CFG_CTL_ENABLE | CFG_CTL_DMA) : CFG_CTL_ENABLE;
+ iowrite32be(val, priv->regs + FPGA_CONFIG_CONTROL);
+}
+
+static void fpga_programmer_disable(struct fpga_dev *priv)
+{
+ iowrite32be(0x0, priv->regs + FPGA_CONFIG_CONTROL);
+}
+
+static void fpga_dump_registers(struct fpga_dev *priv)
+{
+ u32 control, status, size, used, total, curr;
+
+ /* good status: do nothing */
+ if (priv->status == 0)
+ return;
+
+ /* Dump all status registers */
+ control = ioread32be(priv->regs + FPGA_CONFIG_CONTROL);
+ status = ioread32be(priv->regs + FPGA_CONFIG_STATUS);
+ size = ioread32be(priv->regs + FPGA_CONFIG_FIFO_SIZE);
+ used = ioread32be(priv->regs + FPGA_CONFIG_FIFO_USED);
+ total = ioread32be(priv->regs + FPGA_CONFIG_TOTAL_BYTE_COUNT);
+ curr = ioread32be(priv->regs + FPGA_CONFIG_CUR_BYTE_COUNT);
+
+ dev_err(priv->dev, "Configuration failed, dumping status registers\n");
+ dev_err(priv->dev, "Control: 0x%.8x\n", control);
+ dev_err(priv->dev, "Status: 0x%.8x\n", status);
+ dev_err(priv->dev, "FIFO Size: 0x%.8x\n", size);
+ dev_err(priv->dev, "FIFO Used: 0x%.8x\n", used);
+ dev_err(priv->dev, "FIFO Total: 0x%.8x\n", total);
+ dev_err(priv->dev, "FIFO Curr: 0x%.8x\n", curr);
+}
+
+/*
+ * FPGA Power Supply Code
+ */
+
+#define CTL_PWR_CONTROL 0x2006
+#define CTL_PWR_STATUS 0x200A
+#define CTL_PWR_FAIL 0x200B
+
+#define PWR_CONTROL_ENABLE 0x01
+
+#define PWR_STATUS_ERROR_MASK 0x10
+#define PWR_STATUS_GOOD 0x0f
+
+/*
+ * Determine if the FPGA power is good for all supplies
+ */
+static bool fpga_power_good(struct fpga_dev *priv)
+{
+ u8 val;
+
+ val = ioread8(priv->regs + CTL_PWR_STATUS);
+ if (val & PWR_STATUS_ERROR_MASK)
+ return false;
+
+ return val == PWR_STATUS_GOOD;
+}
+
+/*
+ * Disable the FPGA power supplies
+ */
+static void fpga_disable_power_supplies(struct fpga_dev *priv)
+{
+ unsigned long start;
+ u8 val;
+
+ iowrite8(0x0, priv->regs + CTL_PWR_CONTROL);
+
+ /*
+ * Wait 500ms for the power rails to discharge
+ *
+ * Without this delay, the CTL-CPLD state machine can get into a
+ * state where it is waiting for the power-goods to assert, but they
+ * never do. This only happens when enabling and disabling the
+ * power sequencer very rapidly.
+ *
+ * The loop below will also wait for the power goods to de-assert,
+ * but testing has shown that they are always disabled by the time
+ * the sleep completes. However, omitting the sleep and only waiting
+ * for the power-goods to de-assert was not sufficient to ensure
+ * that the power sequencer would not wedge itself.
+ */
+ msleep(500);
+
+ start = jiffies;
+ while (time_before(jiffies, start + HZ)) {
+ val = ioread8(priv->regs + CTL_PWR_STATUS);
+ if (!(val & PWR_STATUS_GOOD))
+ break;
+
+ usleep_range(5000, 10000);
+ }
+
+ val = ioread8(priv->regs + CTL_PWR_STATUS);
+ if (val & PWR_STATUS_GOOD) {
+ dev_err(priv->dev, "power disable failed: "
+ "power goods: status 0x%.2x\n", val);
+ }
+
+ if (val & PWR_STATUS_ERROR_MASK) {
+ dev_err(priv->dev, "power disable failed: "
+ "alarm bit set: status 0x%.2x\n", val);
+ }
+}
+
+/**
+ * fpga_enable_power_supplies() - enable the DATA-FPGA power supplies
+ * @priv: the driver's private data structure
+ *
+ * Enable the DATA-FPGA power supplies, waiting up to 1 second for
+ * them to enable successfully.
+ *
+ * Returns 0 on success, -ERRNO otherwise
+ */
+static int fpga_enable_power_supplies(struct fpga_dev *priv)
+{
+ unsigned long start = jiffies;
+
+ if (fpga_power_good(priv)) {
+ dev_dbg(priv->dev, "power was already good\n");
+ return 0;
+ }
+
+ iowrite8(PWR_CONTROL_ENABLE, priv->regs + CTL_PWR_CONTROL);
+ while (time_before(jiffies, start + HZ)) {
+ if (fpga_power_good(priv))
+ return 0;
+
+ usleep_range(5000, 10000);
+ }
+
+ return fpga_power_good(priv) ? 0 : -ETIMEDOUT;
+}
+
+/*
+ * Determine if the FPGA power supplies are all enabled
+ */
+static bool fpga_power_enabled(struct fpga_dev *priv)
+{
+ u8 val;
+
+ val = ioread8(priv->regs + CTL_PWR_CONTROL);
+ if (val & PWR_CONTROL_ENABLE)
+ return true;
+
+ return false;
+}
+
+/*
+ * Determine if the FPGA's are programmed and running correctly
+ */
+static bool fpga_running(struct fpga_dev *priv)
+{
+ if (!fpga_power_good(priv))
+ return false;
+
+ /* Check the config done bit */
+ return ioread32be(priv->regs + FPGA_CONFIG_STATUS) & (1 << 18);
+}
+
+/*
+ * FPGA Programming Code
+ */
+
+/**
+ * fpga_program_block() - put a block of data into the programmer's FIFO
+ * @priv: the driver's private data structure
+ * @buf: the data to program
+ * @count: the length of data to program (must be a multiple of 4 bytes)
+ *
+ * Returns 0 on success, -ERRNO otherwise
+ */
+static int fpga_program_block(struct fpga_dev *priv, void *buf, size_t count)
+{
+ u32 *data = buf;
+ int size = fpga_fifo_size(priv->regs);
+ int i, len;
+ unsigned long timeout;
+
+ /* enforce correct data length for the FIFO */
+ BUG_ON(count % 4 != 0);
+
+ while (count > 0) {
+
+ /* Get the size of the block to write (maximum is FIFO_SIZE) */
+ len = min_t(size_t, count, size);
+ timeout = jiffies + HZ / 4;
+
+ /* Write the block */
+ for (i = 0; i < len / 4; i++)
+ fpga_fifo_write(priv->regs, data[i]);
+
+ /* Update the amounts left */
+ count -= len;
+ data += len / 4;
+
+ /* Wait for the fifo to empty */
+ while (true) {
+
+ if (fpga_fifo_empty(priv->regs)) {
+ break;
+ } else {
+ dev_dbg(priv->dev, "Fifo not empty\n");
+ cpu_relax();
+ }
+
+ if (fpga_config_error(priv->regs)) {
+ dev_err(priv->dev, "Error detected\n");
+ return -EIO;
+ }
+
+ if (time_after(jiffies, timeout)) {
+ dev_err(priv->dev, "Fifo drain timeout\n");
+ return -ETIMEDOUT;
+ }
+
+ usleep_range(5000, 10000);
+ }
+ }
+
+ return 0;
+}
+
+/**
+ * fpga_program_cpu() - program the DATA-FPGA's using the CPU
+ * @priv: the driver's private data structure
+ *
+ * This is useful when the DMA programming method fails. It is possible to
+ * wedge the Freescale DMA controller such that the DMA programming method
+ * always fails. This method has always succeeded.
+ *
+ * Returns 0 on success, -ERRNO otherwise
+ */
+static noinline int fpga_program_cpu(struct fpga_dev *priv)
+{
+ int ret;
+
+ /* Disable the programmer */
+ fpga_programmer_disable(priv);
+
+ /* Set the total byte count */
+ fpga_set_byte_count(priv->regs, priv->bytes);
+ dev_dbg(priv->dev, "total byte count %u bytes\n", priv->bytes);
+
+ /* Enable the controller for programming */
+ fpga_programmer_enable(priv, false);
+ dev_dbg(priv->dev, "enabled the controller\n");
+
+ /* Write each chunk of the FPGA bitfile to FPGA programmer */
+ ret = fpga_program_block(priv, priv->vb.vaddr, priv->bytes);
+ if (ret)
+ goto out_disable_controller;
+
+ /* Wait for the interrupt handler to signal that programming finished */
+ ret = wait_for_completion_timeout(&priv->completion, 2 * HZ);
+ if (!ret) {
+ dev_err(priv->dev, "Timed out waiting for completion\n");
+ ret = -ETIMEDOUT;
+ goto out_disable_controller;
+ }
+
+ /* Retrieve the status from the interrupt handler */
+ ret = priv->status;
+
+out_disable_controller:
+ fpga_programmer_disable(priv);
+ return ret;
+}
+
+#define FIFO_DMA_ADDRESS 0xf0003000
+#define FIFO_MAX_LEN 4096
+
+/**
+ * fpga_program_dma() - program the DATA-FPGA's using the DMA engine
+ * @priv: the driver's private data structure
+ *
+ * Program the DATA-FPGA's using the Freescale DMA engine. This requires that
+ * the engine is programmed such that the hardware DMA request lines can
+ * control the entire DMA transaction. The system controller FPGA then
+ * completely offloads the programming from the CPU.
+ *
+ * Returns 0 on success, -ERRNO otherwise
+ */
+static noinline int fpga_program_dma(struct fpga_dev *priv)
+{
+ struct videobuf_dmabuf *vb = &priv->vb;
+ struct dma_chan *chan = priv->chan;
+ struct dma_async_tx_descriptor *tx;
+ size_t num_pages, len, avail = 0;
+ struct dma_slave_config config;
+ struct scatterlist *sg;
+ struct sg_table table;
+ dma_cookie_t cookie;
+ int ret, i;
+
+ /* Disable the programmer */
+ fpga_programmer_disable(priv);
+
+ /* Allocate a scatterlist for the DMA destination */
+ num_pages = DIV_ROUND_UP(priv->bytes, FIFO_MAX_LEN);
+ ret = sg_alloc_table(&table, num_pages, GFP_KERNEL);
+ if (ret) {
+ dev_err(priv->dev, "Unable to allocate dst scatterlist\n");
+ ret = -ENOMEM;
+ goto out_return;
+ }
+
+ /*
+ * This is an ugly hack
+ *
+ * We fill in a scatterlist as if it were mapped for DMA. This is
+ * necessary because there exists no better structure for this
+ * inside the kernel code.
+ *
+ * As an added bonus, we can use the DMAEngine API for all of this,
+ * rather than inventing another extremely similar API.
+ */
+ avail = priv->bytes;
+ for_each_sg(table.sgl, sg, num_pages, i) {
+ len = min_t(size_t, avail, FIFO_MAX_LEN);
+ sg_dma_address(sg) = FIFO_DMA_ADDRESS;
+ sg_dma_len(sg) = len;
+
+ avail -= len;
+ }
+
+ /* Map the buffer for DMA */
+ ret = videobuf_dma_map(priv->dev, &priv->vb);
+ if (ret) {
+ dev_err(priv->dev, "Unable to map buffer for DMA\n");
+ goto out_free_table;
+ }
+
+ /*
+ * Configure the DMA channel to transfer FIFO_SIZE / 2 bytes per
+ * transaction, and then put it under external control
+ */
+ memset(&config, 0, sizeof(config));
+ config.direction = DMA_TO_DEVICE;
+ config.dst_addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES;
+ config.dst_maxburst = fpga_fifo_size(priv->regs) / 2 / 4;
+ ret = chan->device->device_control(chan, DMA_SLAVE_CONFIG,
+ (unsigned long)&config);
+ if (ret) {
+ dev_err(priv->dev, "DMA slave configuration failed\n");
+ goto out_dma_unmap;
+ }
+
+ ret = chan->device->device_control(chan, FSLDMA_EXTERNAL_START, 1);
+ if (ret) {
+ dev_err(priv->dev, "DMA external control setup failed\n");
+ goto out_dma_unmap;
+ }
+
+ /* setup and submit the DMA transaction */
+ tx = chan->device->device_prep_dma_sg(chan,
+ table.sgl, num_pages,
+ vb->sglist, vb->sglen, 0);
+ if (!tx) {
+ dev_err(priv->dev, "Unable to prep DMA transaction\n");
+ ret = -ENOMEM;
+ goto out_dma_unmap;
+ }
+
+ cookie = tx->tx_submit(tx);
+ if (dma_submit_error(cookie)) {
+ dev_err(priv->dev, "Unable to submit DMA transaction\n");
+ ret = -ENOMEM;
+ goto out_dma_unmap;
+ }
+
+ dma_async_memcpy_issue_pending(chan);
+
+ /* Set the total byte count */
+ fpga_set_byte_count(priv->regs, priv->bytes);
+ dev_dbg(priv->dev, "total byte count %u bytes\n", priv->bytes);
+
+ /* Enable the controller for DMA programming */
+ fpga_programmer_enable(priv, true);
+ dev_dbg(priv->dev, "enabled the controller\n");
+
+ /* Wait for the interrupt handler to signal that programming finished */
+ ret = wait_for_completion_timeout(&priv->completion, 2 * HZ);
+ if (!ret) {
+ dev_err(priv->dev, "Timed out waiting for completion\n");
+ ret = -ETIMEDOUT;
+ goto out_disable_controller;
+ }
+
+ /* Retrieve the status from the interrupt handler */
+ ret = priv->status;
+
+out_disable_controller:
+ fpga_programmer_disable(priv);
+out_dma_unmap:
+ videobuf_dma_unmap(priv->dev, vb);
+out_free_table:
+ sg_free_table(&table);
+out_return:
+ return ret;
+}
+
+/*
+ * Interrupt Handling
+ */
+
+static irqreturn_t fpga_irq(int irq, void *dev_id)
+{
+ struct fpga_dev *priv = dev_id;
+
+ /* Save the status */
+ priv->status = fpga_config_error(priv->regs) ? -EIO : 0;
+ dev_dbg(priv->dev, "INTERRUPT status %d\n", priv->status);
+ fpga_dump_registers(priv);
+
+ /* Disabling the programmer clears the interrupt */
+ fpga_programmer_disable(priv);
+
+ /* Notify any waiters */
+ complete(&priv->completion);
+
+ return IRQ_HANDLED;
+}
+
+/*
+ * SYSFS Helpers
+ */
+
+/**
+ * fpga_do_stop() - deconfigure (reset) the DATA-FPGA's
+ * @priv: the driver's private data structure
+ *
+ * LOCKING: must hold priv->lock
+ */
+static int fpga_do_stop(struct fpga_dev *priv)
+{
+ u32 val;
+
+ /* Set the led to unprogrammed */
+ ledtrig_fpga_programmed(false);
+
+ /* Pulse the config line to reset the FPGA's */
+ val = CFG_CTL_ENABLE | CFG_CTL_RESET;
+ iowrite32be(val, priv->regs + FPGA_CONFIG_CONTROL);
+ iowrite32be(0x0, priv->regs + FPGA_CONFIG_CONTROL);
+
+ return 0;
+}
+
+static noinline int fpga_do_program(struct fpga_dev *priv)
+{
+ int ret;
+
+ if (priv->bytes != priv->fw_size) {
+ dev_err(priv->dev, "Incorrect bitfile size: got %zu bytes, "
+ "should be %zu bytes\n",
+ priv->bytes, priv->fw_size);
+ return -EINVAL;
+ }
+
+ if (!fpga_power_enabled(priv)) {
+ dev_err(priv->dev, "Power not enabled\n");
+ return -EINVAL;
+ }
+
+ if (!fpga_power_good(priv)) {
+ dev_err(priv->dev, "Power not good\n");
+ return -EINVAL;
+ }
+
+ /* Set the LED to unprogrammed */
+ ledtrig_fpga_programmed(false);
+
+ /* Try to program the FPGA's using DMA */
+ ret = fpga_program_dma(priv);
+
+ /* If DMA failed or doesn't exist, try with CPU */
+ if (ret) {
+ dev_warn(priv->dev, "Falling back to CPU programming\n");
+ ret = fpga_program_cpu(priv);
+ }
+
+ if (ret) {
+ dev_err(priv->dev, "Unable to program FPGA's\n");
+ return ret;
+ }
+
+ /* Drop the firmware bitfile from memory */
+ fpga_drop_firmware_data(priv);
+
+ dev_dbg(priv->dev, "FPGA programming successful\n");
+ ledtrig_fpga_programmed(true);
+
+ return 0;
+}
+
+/*
+ * File Operations
+ */
+
+static int fpga_open(struct inode *inode, struct file *filp)
+{
+ /*
+ * The miscdevice layer puts our struct miscdevice into the
+ * filp->private_data field. We use this to find our private
+ * data and then overwrite it with our own private structure.
+ */
+ struct fpga_dev *priv = container_of(filp->private_data,
+ struct fpga_dev, miscdev);
+ unsigned int nr_pages;
+ int ret;
+
+ /* We only allow one process at a time */
+ ret = mutex_lock_interruptible(&priv->lock);
+ if (ret)
+ return ret;
+
+ filp->private_data = priv;
+ kref_get(&priv->ref);
+
+ /* Truncation: drop any existing data */
+ if (filp->f_flags & O_TRUNC)
+ priv->bytes = 0;
+
+ /* Check if we have already allocated a buffer */
+ if (priv->vb_allocated)
+ return 0;
+
+ /* Allocate a buffer to hold enough data for the bitfile */
+ nr_pages = DIV_ROUND_UP(priv->fw_size, PAGE_SIZE);
+ ret = videobuf_dma_init_kernel(&priv->vb, DMA_TO_DEVICE, nr_pages);
+ if (ret) {
+ dev_err(priv->dev, "unable to allocate data buffer\n");
+ mutex_unlock(&priv->lock);
+ kref_put(&priv->ref, fpga_dev_remove);
+ return ret;
+ }
+
+ priv->vb_allocated = true;
+ return 0;
+}
+
+static int fpga_release(struct inode *inode, struct file *filp)
+{
+ struct fpga_dev *priv = filp->private_data;
+
+ mutex_unlock(&priv->lock);
+ kref_put(&priv->ref, fpga_dev_remove);
+ return 0;
+}
+
+static ssize_t fpga_write(struct file *filp, const char __user *buf,
+ size_t count, loff_t *f_pos)
+{
+ struct fpga_dev *priv = filp->private_data;
+
+ /* FPGA bitfiles have an exact size: disallow anything else */
+ if (priv->bytes >= priv->fw_size)
+ return -ENOSPC;
+
+ count = min_t(size_t, priv->fw_size - priv->bytes, count);
+ if (copy_from_user(priv->vb.vaddr + priv->bytes, buf, count))
+ return -EFAULT;
+
+ priv->bytes += count;
+ return count;
+}
+
+static ssize_t fpga_read(struct file *filp, char __user *buf, size_t count,
+ loff_t *f_pos)
+{
+ struct fpga_dev *priv = filp->private_data;
+
+ count = min_t(size_t, priv->bytes - *f_pos, count);
+ if (copy_to_user(buf, priv->vb.vaddr + *f_pos, count))
+ return -EFAULT;
+
+ *f_pos += count;
+ return count;
+}
+
+static loff_t fpga_llseek(struct file *filp, loff_t offset, int origin)
+{
+ struct fpga_dev *priv = filp->private_data;
+ loff_t newpos;
+
+ /* only read-only opens are allowed to seek */
+ if ((filp->f_flags & O_ACCMODE) != O_RDONLY)
+ return -EINVAL;
+
+ switch (origin) {
+ case SEEK_SET: /* seek relative to the beginning of the file */
+ newpos = offset;
+ break;
+ case SEEK_CUR: /* seek relative to current position in the file */
+ newpos = filp->f_pos + offset;
+ break;
+ case SEEK_END: /* seek relative to the end of the file */
+ newpos = priv->fw_size - offset;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ /* check for sanity */
+ if (newpos > priv->fw_size)
+ return -EINVAL;
+
+ filp->f_pos = newpos;
+ return newpos;
+}
+
+static const struct file_operations fpga_fops = {
+ .open = fpga_open,
+ .release = fpga_release,
+ .write = fpga_write,
+ .read = fpga_read,
+ .llseek = fpga_llseek,
+};
+
+/*
+ * Device Attributes
+ */
+
+static ssize_t pfail_show(struct device *dev, struct device_attribute *attr,
+ char *buf)
+{
+ struct fpga_dev *priv = dev_get_drvdata(dev);
+ u8 val;
+
+ val = ioread8(priv->regs + CTL_PWR_FAIL);
+ return snprintf(buf, PAGE_SIZE, "0x%.2x\n", val);
+}
+
+static ssize_t pgood_show(struct device *dev, struct device_attribute *attr,
+ char *buf)
+{
+ struct fpga_dev *priv = dev_get_drvdata(dev);
+ return snprintf(buf, PAGE_SIZE, "%d\n", fpga_power_good(priv));
+}
+
+static ssize_t penable_show(struct device *dev, struct device_attribute *attr,
+ char *buf)
+{
+ struct fpga_dev *priv = dev_get_drvdata(dev);
+ return snprintf(buf, PAGE_SIZE, "%d\n", fpga_power_enabled(priv));
+}
+
+static ssize_t penable_store(struct device *dev, struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct fpga_dev *priv = dev_get_drvdata(dev);
+ unsigned long val;
+ int ret;
+
+ if (strict_strtoul(buf, 0, &val))
+ return -EINVAL;
+
+ if (val) {
+ ret = fpga_enable_power_supplies(priv);
+ if (ret)
+ return ret;
+ } else {
+ fpga_do_stop(priv);
+ fpga_disable_power_supplies(priv);
+ }
+
+ return count;
+}
+
+static ssize_t program_show(struct device *dev, struct device_attribute *attr,
+ char *buf)
+{
+ struct fpga_dev *priv = dev_get_drvdata(dev);
+ return snprintf(buf, PAGE_SIZE, "%d\n", fpga_running(priv));
+}
+
+static ssize_t program_store(struct device *dev, struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct fpga_dev *priv = dev_get_drvdata(dev);
+ unsigned long val;
+ int ret;
+
+ if (strict_strtoul(buf, 0, &val))
+ return -EINVAL;
+
+ /* We can't have an image writer and be programming simultaneously */
+ if (mutex_lock_interruptible(&priv->lock))
+ return -ERESTARTSYS;
+
+ /* Program or Reset the FPGA's */
+ ret = val ? fpga_do_program(priv) : fpga_do_stop(priv);
+ if (ret)
+ goto out_unlock;
+
+ /* Success */
+ ret = count;
+
+out_unlock:
+ mutex_unlock(&priv->lock);
+ return ret;
+}
+
+static DEVICE_ATTR(power_fail, S_IRUGO, pfail_show, NULL);
+static DEVICE_ATTR(power_good, S_IRUGO, pgood_show, NULL);
+static DEVICE_ATTR(power_enable, S_IRUGO | S_IWUSR,
+ penable_show, penable_store);
+
+static DEVICE_ATTR(program, S_IRUGO | S_IWUSR,
+ program_show, program_store);
+
+static struct attribute *fpga_attributes[] = {
+ &dev_attr_power_fail.attr,
+ &dev_attr_power_good.attr,
+ &dev_attr_power_enable.attr,
+ &dev_attr_program.attr,
+ NULL,
+};
+
+static const struct attribute_group fpga_attr_group = {
+ .attrs = fpga_attributes,
+};
+
+/*
+ * OpenFirmware Device Subsystem
+ */
+
+#define SYS_REG_VERSION 0x00
+#define SYS_REG_GEOGRAPHIC 0x10
+
+static bool dma_filter(struct dma_chan *chan, void *data)
+{
+ /*
+ * DMA Channel #0 is the only acceptable device
+ *
+ * This probably won't survive an unload/load cycle of the Freescale
+ * DMAEngine driver, but that won't be a problem
+ */
+ return chan->chan_id == 0 && chan->device->dev_id == 0;
+}
+
+static int fpga_of_remove(struct platform_device *op)
+{
+ struct fpga_dev *priv = dev_get_drvdata(&op->dev);
+ struct device *this_device = priv->miscdev.this_device;
+
+ sysfs_remove_group(&this_device->kobj, &fpga_attr_group);
+ misc_deregister(&priv->miscdev);
+
+ free_irq(priv->irq, priv);
+ irq_dispose_mapping(priv->irq);
+
+ /* make sure the power supplies are off */
+ fpga_disable_power_supplies(priv);
+
+ /* unmap registers */
+ iounmap(priv->immr);
+ iounmap(priv->regs);
+
+ dma_release_channel(priv->chan);
+
+ /* drop our reference to the private data structure */
+ kref_put(&priv->ref, fpga_dev_remove);
+ return 0;
+}
+
+/* CTL-CPLD Version Register */
+#define CTL_CPLD_VERSION 0x2000
+
+static int fpga_of_probe(struct platform_device *op,
+ const struct of_device_id *match)
+{
+ struct device_node *of_node = op->dev.of_node;
+ struct device *this_device;
+ struct fpga_dev *priv;
+ dma_cap_mask_t mask;
+ u32 ver;
+ int ret;
+
+ /* Allocate private data */
+ priv = kzalloc(sizeof(*priv), GFP_KERNEL);
+ if (!priv) {
+ dev_err(&op->dev, "Unable to allocate private data\n");
+ ret = -ENOMEM;
+ goto out_return;
+ }
+
+ /* Setup the miscdevice */
+ priv->miscdev.minor = MISC_DYNAMIC_MINOR;
+ priv->miscdev.name = drv_name;
+ priv->miscdev.fops = &fpga_fops;
+
+ kref_init(&priv->ref);
+
+ dev_set_drvdata(&op->dev, priv);
+ priv->dev = &op->dev;
+ mutex_init(&priv->lock);
+ init_completion(&priv->completion);
+ videobuf_dma_init(&priv->vb);
+
+ dev_set_drvdata(priv->dev, priv);
+ dma_cap_zero(mask);
+ dma_cap_set(DMA_MEMCPY, mask);
+ dma_cap_set(DMA_INTERRUPT, mask);
+ dma_cap_set(DMA_SLAVE, mask);
+ dma_cap_set(DMA_SG, mask);
+
+ /* Get control of DMA channel #0 */
+ priv->chan = dma_request_channel(mask, dma_filter, NULL);
+ if (!priv->chan) {
+ dev_err(&op->dev, "Unable to acquire DMA channel #0\n");
+ ret = -ENODEV;
+ goto out_free_priv;
+ }
+
+ /* Remap the registers for use */
+ priv->regs = of_iomap(of_node, 0);
+ if (!priv->regs) {
+ dev_err(&op->dev, "Unable to ioremap registers\n");
+ ret = -ENOMEM;
+ goto out_dma_release_channel;
+ }
+
+ /* Remap the IMMR for use */
+ priv->immr = ioremap(get_immrbase(), 0x100000);
+ if (!priv->immr) {
+ dev_err(&op->dev, "Unable to ioremap IMMR\n");
+ ret = -ENOMEM;
+ goto out_unmap_regs;
+ }
+
+ /*
+ * Check that external DMA is configured
+ *
+ * U-Boot does this for us, but we should check it and bail out if
+ * there is a problem. Failing to have this register setup correctly
+ * will cause the DMA controller to transfer a single cacheline
+ * worth of data, then wedge itself.
+ */
+ if ((ioread32be(priv->immr + 0x114) & 0xE00) != 0xE00) {
+ dev_err(&op->dev, "External DMA control not configured\n");
+ ret = -ENODEV;
+ goto out_unmap_immr;
+ }
+
+ /*
+ * Check the CTL-CPLD version
+ *
+ * This driver uses the CTL-CPLD DATA-FPGA power sequencer, and we
+ * don't want to run on any version of the CTL-CPLD that does not use
+ * a compatible register layout.
+ *
+ * v2: changed register layout, added power sequencer
+ * v3: added glitch filter on the i2c overcurrent/overtemp outputs
+ */
+ ver = ioread8(priv->regs + CTL_CPLD_VERSION);
+ if (ver != 0x02 && ver != 0x03) {
+ dev_err(&op->dev, "CTL-CPLD is not version 0x02 or 0x03!\n");
+ ret = -ENODEV;
+ goto out_unmap_immr;
+ }
+
+ /* Set the exact size that the firmware image should be */
+ ver = ioread32be(priv->regs + SYS_REG_VERSION);
+ priv->fw_size = (ver & (1 << 18)) ? FW_SIZE_EP2S130 : FW_SIZE_EP2S90;
+
+ /* Find the correct IRQ number */
+ priv->irq = irq_of_parse_and_map(of_node, 0);
+ if (priv->irq == NO_IRQ) {
+ dev_err(&op->dev, "Unable to find IRQ line\n");
+ ret = -ENODEV;
+ goto out_unmap_immr;
+ }
+
+ /* Request the IRQ */
+ ret = request_irq(priv->irq, fpga_irq, IRQF_SHARED, drv_name, priv);
+ if (ret) {
+ dev_err(&op->dev, "Unable to request IRQ %d\n", priv->irq);
+ ret = -ENODEV;
+ goto out_irq_dispose_mapping;
+ }
+
+ /* Reset and stop the FPGA's, just in case */
+ fpga_do_stop(priv);
+
+ /* Register the miscdevice */
+ ret = misc_register(&priv->miscdev);
+ if (ret) {
+ dev_err(&op->dev, "Unable to register miscdevice\n");
+ goto out_free_irq;
+ }
+
+ /* Create the sysfs files */
+ this_device = priv->miscdev.this_device;
+ dev_set_drvdata(this_device, priv);
+ ret = sysfs_create_group(&this_device->kobj, &fpga_attr_group);
+ if (ret) {
+ dev_err(&op->dev, "Unable to create sysfs files\n");
+ goto out_misc_deregister;
+ }
+
+ dev_info(priv->dev, "CARMA FPGA Programmer: %s rev%s with %s FPGAs\n",
+ (ver & (1 << 17)) ? "Correlator" : "Digitizer",
+ (ver & (1 << 16)) ? "B" : "A",
+ (ver & (1 << 18)) ? "EP2S130" : "EP2S90");
+
+ return 0;
+
+out_misc_deregister:
+ misc_deregister(&priv->miscdev);
+out_free_irq:
+ free_irq(priv->irq, priv);
+out_irq_dispose_mapping:
+ irq_dispose_mapping(priv->irq);
+out_unmap_immr:
+ iounmap(priv->immr);
+out_unmap_regs:
+ iounmap(priv->regs);
+out_dma_release_channel:
+ dma_release_channel(priv->chan);
+out_free_priv:
+ kref_put(&priv->ref, fpga_dev_remove);
+out_return:
+ return ret;
+}
+
+static struct of_device_id fpga_of_match[] = {
+ { .compatible = "carma,fpga-programmer", },
+ {},
+};
+
+static struct of_platform_driver fpga_of_driver = {
+ .probe = fpga_of_probe,
+ .remove = fpga_of_remove,
+ .driver = {
+ .name = drv_name,
+ .of_match_table = fpga_of_match,
+ .owner = THIS_MODULE,
+ },
+};
+
+/*
+ * Module Init / Exit
+ */
+
+static int __init fpga_init(void)
+{
+ led_trigger_register_simple("fpga", &ledtrig_fpga);
+ return of_register_platform_driver(&fpga_of_driver);
+}
+
+static void __exit fpga_exit(void)
+{
+ of_unregister_platform_driver(&fpga_of_driver);
+ led_trigger_unregister_simple(ledtrig_fpga);
+}
+
+MODULE_AUTHOR("Ira W. Snyder <iws@ovro.caltech.edu>");
+MODULE_DESCRIPTION("CARMA Board DATA-FPGA Programmer");
+MODULE_LICENSE("GPL");
+
+module_init(fpga_init);
+module_exit(fpga_exit);
--
1.7.3.4
^ permalink raw reply related
* [PATCH RFCv7 1/2] misc: add CARMA DATA-FPGA Access Driver
From: Ira W. Snyder @ 2011-02-11 23:34 UTC (permalink / raw)
To: linuxppc-dev; +Cc: dmitry.torokhov, linux-kernel, Ira W. Snyder
In-Reply-To: <1297467270-29576-1-git-send-email-iws@ovro.caltech.edu>
This driver allows userspace to access the data processing FPGAs on the
OVRO CARMA board. It has two modes of operation:
1) random access
This allows users to poke any DATA-FPGA registers by using mmap to map
the address region directly into their memory map.
2) correlation dumping
When correlating, the DATA-FPGA's have special requirements for getting
the data out of their memory before the next correlation. This nominally
happens at 64Hz (every 15.625ms). If the data is not dumped before the
next correlation, data is lost.
The data dumping driver handles buffering up to 1 second worth of
correlation data from the FPGAs. This lowers the realtime scheduling
requirements for the userspace process reading the device.
Signed-off-by: Ira W. Snyder <iws@ovro.caltech.edu>
---
drivers/misc/Kconfig | 1 +
drivers/misc/Makefile | 1 +
drivers/misc/carma/Kconfig | 9 +
drivers/misc/carma/Makefile | 1 +
drivers/misc/carma/carma-fpga.c | 1433 +++++++++++++++++++++++++++++++++++++++
5 files changed, 1445 insertions(+), 0 deletions(-)
create mode 100644 drivers/misc/carma/Kconfig
create mode 100644 drivers/misc/carma/Makefile
create mode 100644 drivers/misc/carma/carma-fpga.c
diff --git a/drivers/misc/Kconfig b/drivers/misc/Kconfig
index cc8e49d..93cf1e6 100644
--- a/drivers/misc/Kconfig
+++ b/drivers/misc/Kconfig
@@ -457,5 +457,6 @@ source "drivers/misc/eeprom/Kconfig"
source "drivers/misc/cb710/Kconfig"
source "drivers/misc/iwmc3200top/Kconfig"
source "drivers/misc/ti-st/Kconfig"
+source "drivers/misc/carma/Kconfig"
endif # MISC_DEVICES
diff --git a/drivers/misc/Makefile b/drivers/misc/Makefile
index 98009cc..2c1610e 100644
--- a/drivers/misc/Makefile
+++ b/drivers/misc/Makefile
@@ -42,3 +42,4 @@ obj-$(CONFIG_ARM_CHARLCD) += arm-charlcd.o
obj-$(CONFIG_PCH_PHUB) += pch_phub.o
obj-y += ti-st/
obj-$(CONFIG_AB8500_PWM) += ab8500-pwm.o
+obj-y += carma/
diff --git a/drivers/misc/carma/Kconfig b/drivers/misc/carma/Kconfig
new file mode 100644
index 0000000..4be183f
--- /dev/null
+++ b/drivers/misc/carma/Kconfig
@@ -0,0 +1,9 @@
+config CARMA_FPGA
+ tristate "CARMA DATA-FPGA Access Driver"
+ depends on FSL_SOC && PPC_83xx && MEDIA_SUPPORT && HAS_DMA && FSL_DMA
+ select VIDEOBUF_DMA_SG
+ default n
+ help
+ Say Y here to include support for communicating with the data
+ processing FPGAs on the OVRO CARMA board.
+
diff --git a/drivers/misc/carma/Makefile b/drivers/misc/carma/Makefile
new file mode 100644
index 0000000..0b69fa7
--- /dev/null
+++ b/drivers/misc/carma/Makefile
@@ -0,0 +1 @@
+obj-$(CONFIG_CARMA_FPGA) += carma-fpga.o
diff --git a/drivers/misc/carma/carma-fpga.c b/drivers/misc/carma/carma-fpga.c
new file mode 100644
index 0000000..3965821
--- /dev/null
+++ b/drivers/misc/carma/carma-fpga.c
@@ -0,0 +1,1433 @@
+/*
+ * CARMA DATA-FPGA Access Driver
+ *
+ * Copyright (c) 2009-2011 Ira W. Snyder <iws@ovro.caltech.edu>
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ */
+
+/*
+ * FPGA Memory Dump Format
+ *
+ * FPGA #0 control registers (32 x 32-bit words)
+ * FPGA #1 control registers (32 x 32-bit words)
+ * FPGA #2 control registers (32 x 32-bit words)
+ * FPGA #3 control registers (32 x 32-bit words)
+ * SYSFPGA control registers (32 x 32-bit words)
+ * FPGA #0 correlation array (NUM_CORL0 correlation blocks)
+ * FPGA #1 correlation array (NUM_CORL1 correlation blocks)
+ * FPGA #2 correlation array (NUM_CORL2 correlation blocks)
+ * FPGA #3 correlation array (NUM_CORL3 correlation blocks)
+ *
+ * Each correlation array consists of:
+ *
+ * Correlation Data (2 x NUM_LAGSn x 32-bit words)
+ * Pipeline Metadata (2 x NUM_METAn x 32-bit words)
+ * Quantization Counters (2 x NUM_QCNTn x 32-bit words)
+ *
+ * The NUM_CORLn, NUM_LAGSn, NUM_METAn, and NUM_QCNTn values come from
+ * the FPGA configuration registers. They do not change once the FPGA's
+ * have been programmed, they only change on re-programming.
+ */
+
+/*
+ * Basic Description:
+ *
+ * This driver is used to capture correlation spectra off of the four data
+ * processing FPGAs. The FPGAs are often reprogrammed at runtime, therefore
+ * this driver supports dynamic enable/disable of capture while the device
+ * remains open.
+ *
+ * The nominal capture rate is 64Hz (every 15.625ms). To facilitate this fast
+ * capture rate, all buffers are pre-allocated to avoid any potentially long
+ * running memory allocations while capturing.
+ *
+ * There are two lists and one pointer which are used to keep track of the
+ * different states of data buffers.
+ *
+ * 1) free list
+ * This list holds all empty data buffers which are ready to receive data.
+ *
+ * 2) inflight pointer
+ * This pointer holds the currently inflight data buffer. This buffer is having
+ * data copied into it by the DMA engine.
+ *
+ * 3) used list
+ * This list holds data buffers which have been filled, and are waiting to be
+ * read by userspace.
+ *
+ * All buffers start life on the free list, then move successively to the
+ * inflight pointer, and then to the used list. After they have been read by
+ * userspace, they are moved back to the free list. The cycle repeats as long
+ * as necessary.
+ *
+ * It should be noted that all buffers are mapped and ready for DMA when they
+ * are on any of the three lists. They are only unmapped when they are in the
+ * process of being read by userspace.
+ */
+
+/*
+ * Notes on the IRQ masking scheme:
+ *
+ * The IRQ masking scheme here is different than most other hardware. The only
+ * way for the DATA-FPGAs to detect if the kernel has taken too long to copy
+ * the data is if the status registers are not cleared before the next
+ * correlation data dump is ready.
+ *
+ * The interrupt line is connected to the status registers, such that when they
+ * are cleared, the interrupt is de-asserted. Therein lies our problem. We need
+ * to schedule a long-running DMA operation and return from the interrupt
+ * handler quickly, but we cannot clear the status registers.
+ *
+ * To handle this, the system controller FPGA has the capability to connect the
+ * interrupt line to a user-controlled GPIO pin. This pin is driven high
+ * (unasserted) and left that way. To mask the interrupt, we change the
+ * interrupt source to the GPIO pin. Tada, we hid the interrupt. :)
+ */
+
+#include <linux/of_platform.h>
+#include <linux/dma-mapping.h>
+#include <linux/miscdevice.h>
+#include <linux/interrupt.h>
+#include <linux/dmaengine.h>
+#include <linux/seq_file.h>
+#include <linux/highmem.h>
+#include <linux/debugfs.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/poll.h>
+#include <linux/init.h>
+#include <linux/slab.h>
+#include <linux/kref.h>
+#include <linux/io.h>
+
+#include <media/videobuf-dma-sg.h>
+
+/* system controller registers */
+#define SYS_IRQ_SOURCE_CTL 0x24
+#define SYS_IRQ_OUTPUT_EN 0x28
+#define SYS_IRQ_OUTPUT_DATA 0x2C
+#define SYS_IRQ_INPUT_DATA 0x30
+#define SYS_FPGA_CONFIG_STATUS 0x44
+
+/* GPIO IRQ line assignment */
+#define IRQ_CORL_DONE 0x10
+
+/* FPGA registers */
+#define MMAP_REG_VERSION 0x00
+#define MMAP_REG_CORL_CONF1 0x08
+#define MMAP_REG_CORL_CONF2 0x0C
+#define MMAP_REG_STATUS 0x48
+
+#define SYS_FPGA_BLOCK 0xF0000000
+
+#define DATA_FPGA_START 0x400000
+#define DATA_FPGA_SIZE 0x80000
+
+static const char drv_name[] = "carma-fpga";
+
+#define NUM_FPGA 4
+
+#define MIN_DATA_BUFS 8
+#define MAX_DATA_BUFS 64
+
+struct fpga_info {
+ unsigned int num_lag_ram;
+ unsigned int blk_size;
+};
+
+struct data_buf {
+ struct list_head entry;
+ struct videobuf_dmabuf vb;
+ size_t size;
+};
+
+struct fpga_device {
+ /* character device */
+ struct miscdevice miscdev;
+ struct device *dev;
+ struct mutex mutex;
+
+ /* reference count */
+ struct kref ref;
+
+ /* FPGA registers and information */
+ struct fpga_info info[NUM_FPGA];
+ void __iomem *regs;
+ int irq;
+
+ /* FPGA Physical Address/Size Information */
+ resource_size_t phys_addr;
+ size_t phys_size;
+
+ /* DMA structures */
+ struct sg_table corl_table;
+ unsigned int corl_nents;
+ struct dma_chan *chan;
+
+ /* Protection for all members below */
+ spinlock_t lock;
+
+ /* Device enable/disable flag */
+ bool enabled;
+
+ /* Correlation data buffers */
+ wait_queue_head_t wait;
+ struct list_head free;
+ struct list_head used;
+ struct data_buf *inflight;
+
+ /* Information about data buffers */
+ unsigned int num_dropped;
+ unsigned int num_buffers;
+ size_t bufsize;
+ struct dentry *dbg_entry;
+};
+
+struct fpga_reader {
+ struct fpga_device *priv;
+ struct data_buf *buf;
+ off_t buf_start;
+};
+
+static void fpga_device_release(struct kref *ref)
+{
+ struct fpga_device *priv = container_of(ref, struct fpga_device, ref);
+
+ /* the last reader has exited, cleanup the last bits */
+ mutex_destroy(&priv->mutex);
+ kfree(priv);
+}
+
+/*
+ * Data Buffer Allocation Helpers
+ */
+
+/**
+ * data_free_buffer() - free a single data buffer and all allocated memory
+ * @buf: the buffer to free
+ *
+ * This will free all of the pages allocated to the given data buffer, and
+ * then free the structure itself
+ */
+static void data_free_buffer(struct data_buf *buf)
+{
+ /* It is ok to free a NULL buffer */
+ if (!buf)
+ return;
+
+ /* free all memory */
+ videobuf_dma_free(&buf->vb);
+ kfree(buf);
+}
+
+/**
+ * data_alloc_buffer() - allocate and fill a data buffer with pages
+ * @bytes: the number of bytes required
+ *
+ * This allocates all space needed for a data buffer. It must be mapped before
+ * use in a DMA transaction using videobuf_dma_map().
+ *
+ * Returns NULL on failure
+ */
+static struct data_buf *data_alloc_buffer(const size_t bytes)
+{
+ unsigned int nr_pages;
+ struct data_buf *buf;
+ int ret;
+
+ /* calculate the number of pages necessary */
+ nr_pages = DIV_ROUND_UP(bytes, PAGE_SIZE);
+
+ /* allocate the buffer structure */
+ buf = kzalloc(sizeof(*buf), GFP_KERNEL);
+ if (!buf)
+ goto out_return;
+
+ /* initialize internal fields */
+ INIT_LIST_HEAD(&buf->entry);
+ buf->size = bytes;
+
+ /* allocate the videobuf */
+ videobuf_dma_init(&buf->vb);
+ ret = videobuf_dma_init_kernel(&buf->vb, DMA_FROM_DEVICE, nr_pages);
+ if (ret)
+ goto out_free_buf;
+
+ return buf;
+
+out_free_buf:
+ kfree(buf);
+out_return:
+ return NULL;
+}
+
+/**
+ * data_free_buffers() - free all allocated buffers
+ * @priv: the driver's private data structure
+ *
+ * Free all buffers allocated by the driver (except those currently in the
+ * process of being read by userspace).
+ *
+ * LOCKING: must hold dev->mutex
+ * CONTEXT: user
+ */
+static void data_free_buffers(struct fpga_device *priv)
+{
+ struct data_buf *buf, *tmp;
+
+ /* the device should be stopped, no DMA in progress */
+ BUG_ON(priv->inflight != NULL);
+
+ list_for_each_entry_safe(buf, tmp, &priv->free, entry) {
+ list_del_init(&buf->entry);
+ videobuf_dma_unmap(priv->dev, &buf->vb);
+ data_free_buffer(buf);
+ }
+
+ list_for_each_entry_safe(buf, tmp, &priv->used, entry) {
+ list_del_init(&buf->entry);
+ videobuf_dma_unmap(priv->dev, &buf->vb);
+ data_free_buffer(buf);
+ }
+
+ priv->num_buffers = 0;
+ priv->bufsize = 0;
+}
+
+/**
+ * data_alloc_buffers() - allocate 1 seconds worth of data buffers
+ * @priv: the driver's private data structure
+ *
+ * Allocate enough buffers for a whole second worth of data
+ *
+ * This routine will attempt to degrade nicely by succeeding even if a full
+ * second worth of data buffers could not be allocated, as long as a minimum
+ * number were allocated. In this case, it will print a message to the kernel
+ * log.
+ *
+ * The device must not be modifying any lists when this is called.
+ *
+ * CONTEXT: user
+ * LOCKING: must hold dev->mutex
+ *
+ * Returns 0 on success, -ERRNO otherwise
+ */
+static int data_alloc_buffers(struct fpga_device *priv)
+{
+ struct data_buf *buf;
+ int i, ret;
+
+ for (i = 0; i < MAX_DATA_BUFS; i++) {
+
+ /* allocate a buffer */
+ buf = data_alloc_buffer(priv->bufsize);
+ if (!buf)
+ break;
+
+ /* map it for DMA */
+ ret = videobuf_dma_map(priv->dev, &buf->vb);
+ if (ret) {
+ data_free_buffer(buf);
+ break;
+ }
+
+ /* add it to the list of free buffers */
+ list_add_tail(&buf->entry, &priv->free);
+ priv->num_buffers++;
+ }
+
+ /* Make sure we allocated the minimum required number of buffers */
+ if (priv->num_buffers < MIN_DATA_BUFS) {
+ dev_err(priv->dev, "Unable to allocate enough data buffers\n");
+ data_free_buffers(priv);
+ return -ENOMEM;
+ }
+
+ /* Warn if we are running in a degraded state, but do not fail */
+ if (priv->num_buffers < MAX_DATA_BUFS) {
+ dev_warn(priv->dev,
+ "Unable to allocate %d buffers, using %d buffers instead\n",
+ MAX_DATA_BUFS, i);
+ }
+
+ return 0;
+}
+
+/*
+ * DMA Operations Helpers
+ */
+
+/**
+ * fpga_start_addr() - get the physical address a DATA-FPGA
+ * @priv: the driver's private data structure
+ * @fpga: the DATA-FPGA number (zero based)
+ */
+static dma_addr_t fpga_start_addr(struct fpga_device *priv, unsigned int fpga)
+{
+ return priv->phys_addr + 0x400000 + (0x80000 * fpga);
+}
+
+/**
+ * fpga_block_addr() - get the physical address of a correlation data block
+ * @priv: the driver's private data structure
+ * @fpga: the DATA-FPGA number (zero based)
+ * @blknum: the correlation block number (zero based)
+ */
+static dma_addr_t fpga_block_addr(struct fpga_device *priv, unsigned int fpga,
+ unsigned int blknum)
+{
+ return fpga_start_addr(priv, fpga) + (0x10000 * (1 + blknum));
+}
+
+#define REG_BLOCK_SIZE (32 * 4)
+
+/**
+ * data_setup_corl_table() - create the scatterlist for correlation dumps
+ * @priv: the driver's private data structure
+ *
+ * Create the scatterlist for transferring a correlation dump from the
+ * DATA FPGAs. This structure will be reused for each buffer than needs
+ * to be filled with correlation data.
+ *
+ * Returns 0 on success, -ERRNO otherwise
+ */
+static int data_setup_corl_table(struct fpga_device *priv)
+{
+ struct sg_table *table = &priv->corl_table;
+ struct scatterlist *sg;
+ struct fpga_info *info;
+ int i, j, ret;
+
+ /* Calculate the number of entries needed */
+ priv->corl_nents = (1 + NUM_FPGA) * REG_BLOCK_SIZE;
+ for (i = 0; i < NUM_FPGA; i++)
+ priv->corl_nents += priv->info[i].num_lag_ram;
+
+ /* Allocate the scatterlist table */
+ ret = sg_alloc_table(table, priv->corl_nents, GFP_KERNEL);
+ if (ret) {
+ dev_err(priv->dev, "unable to allocate DMA table\n");
+ return ret;
+ }
+
+ /* Add the DATA FPGA registers to the scatterlist */
+ sg = table->sgl;
+ for (i = 0; i < NUM_FPGA; i++) {
+ sg_dma_address(sg) = fpga_start_addr(priv, i);
+ sg_dma_len(sg) = REG_BLOCK_SIZE;
+ sg = sg_next(sg);
+ }
+
+ /* Add the SYS-FPGA registers to the scatterlist */
+ sg_dma_address(sg) = SYS_FPGA_BLOCK;
+ sg_dma_len(sg) = REG_BLOCK_SIZE;
+ sg = sg_next(sg);
+
+ /* Add the FPGA correlation data blocks to the scatterlist */
+ for (i = 0; i < NUM_FPGA; i++) {
+ info = &priv->info[i];
+ for (j = 0; j < info->num_lag_ram; j++) {
+ sg_dma_address(sg) = fpga_block_addr(priv, i, j);
+ sg_dma_len(sg) = info->blk_size;
+ sg = sg_next(sg);
+ }
+ }
+
+ /*
+ * All physical addresses and lengths are present in the structure
+ * now. It can be reused for every FPGA DATA interrupt
+ */
+ return 0;
+}
+
+/*
+ * FPGA Register Access Helpers
+ */
+
+static void fpga_write_reg(struct fpga_device *priv, unsigned int fpga,
+ unsigned int reg, u32 val)
+{
+ const int fpga_start = DATA_FPGA_START + (fpga * DATA_FPGA_SIZE);
+ iowrite32be(val, priv->regs + fpga_start + reg);
+}
+
+static u32 fpga_read_reg(struct fpga_device *priv, unsigned int fpga,
+ unsigned int reg)
+{
+ const int fpga_start = DATA_FPGA_START + (fpga * DATA_FPGA_SIZE);
+ return ioread32be(priv->regs + fpga_start + reg);
+}
+
+/**
+ * data_calculate_bufsize() - calculate the data buffer size required
+ * @priv: the driver's private data structure
+ *
+ * Calculate the total buffer size needed to hold a single block
+ * of correlation data
+ *
+ * CONTEXT: user
+ *
+ * Returns 0 on success, -ERRNO otherwise
+ */
+static int data_calculate_bufsize(struct fpga_device *priv)
+{
+ u32 num_corl, num_lags, num_meta, num_qcnt, num_pack;
+ u32 conf1, conf2, version;
+ u32 num_lag_ram, blk_size;
+ int i;
+
+ /* Each buffer starts with the 5 FPGA register areas */
+ priv->bufsize = (1 + NUM_FPGA) * REG_BLOCK_SIZE;
+
+ /* Read and store the configuration data for each FPGA */
+ for (i = 0; i < NUM_FPGA; i++) {
+ version = fpga_read_reg(priv, i, MMAP_REG_VERSION);
+ conf1 = fpga_read_reg(priv, i, MMAP_REG_CORL_CONF1);
+ conf2 = fpga_read_reg(priv, i, MMAP_REG_CORL_CONF2);
+
+ /* minor version 2 and later */
+ if ((version & 0x000000FF) >= 2) {
+ num_corl = (conf1 & 0x000000F0) >> 4;
+ num_pack = (conf1 & 0x00000F00) >> 8;
+ num_lags = (conf1 & 0x00FFF000) >> 12;
+ num_meta = (conf1 & 0x7F000000) >> 24;
+ num_qcnt = (conf2 & 0x00000FFF) >> 0;
+ } else {
+ num_corl = (conf1 & 0x000000F0) >> 4;
+ num_pack = 1; /* implied */
+ num_lags = (conf1 & 0x000FFF00) >> 8;
+ num_meta = (conf1 & 0x7FF00000) >> 20;
+ num_qcnt = (conf2 & 0x00000FFF) >> 0;
+ }
+
+ num_lag_ram = (num_corl + num_pack - 1) / num_pack;
+ blk_size = ((num_pack * num_lags) + num_meta + num_qcnt) * 8;
+
+ priv->info[i].num_lag_ram = num_lag_ram;
+ priv->info[i].blk_size = blk_size;
+ priv->bufsize += num_lag_ram * blk_size;
+
+ dev_dbg(priv->dev, "FPGA %d NUM_CORL: %d\n", i, num_corl);
+ dev_dbg(priv->dev, "FPGA %d NUM_PACK: %d\n", i, num_pack);
+ dev_dbg(priv->dev, "FPGA %d NUM_LAGS: %d\n", i, num_lags);
+ dev_dbg(priv->dev, "FPGA %d NUM_META: %d\n", i, num_meta);
+ dev_dbg(priv->dev, "FPGA %d NUM_QCNT: %d\n", i, num_qcnt);
+ dev_dbg(priv->dev, "FPGA %d BLK_SIZE: %d\n", i, blk_size);
+ }
+
+ dev_dbg(priv->dev, "TOTAL BUFFER SIZE: %zu bytes\n", priv->bufsize);
+ return 0;
+}
+
+/*
+ * Interrupt Handling
+ */
+
+/**
+ * data_disable_interrupts() - stop the device from generating interrupts
+ * @priv: the driver's private data structure
+ *
+ * Hide interrupts by switching to GPIO interrupt source
+ *
+ * LOCKING: must hold dev->lock
+ */
+static void data_disable_interrupts(struct fpga_device *priv)
+{
+ /* hide the interrupt by switching the IRQ driver to GPIO */
+ iowrite32be(0x2F, priv->regs + SYS_IRQ_SOURCE_CTL);
+}
+
+/**
+ * data_enable_interrupts() - allow the device to generate interrupts
+ * @priv: the driver's private data structure
+ *
+ * Unhide interrupts by switching to the FPGA interrupt source. At the
+ * same time, clear the DATA-FPGA status registers.
+ *
+ * LOCKING: must hold dev->lock
+ */
+static void data_enable_interrupts(struct fpga_device *priv)
+{
+ /* clear the actual FPGA corl_done interrupt */
+ fpga_write_reg(priv, 0, MMAP_REG_STATUS, 0x0);
+ fpga_write_reg(priv, 1, MMAP_REG_STATUS, 0x0);
+ fpga_write_reg(priv, 2, MMAP_REG_STATUS, 0x0);
+ fpga_write_reg(priv, 3, MMAP_REG_STATUS, 0x0);
+
+ /* flush the writes */
+ fpga_read_reg(priv, 0, MMAP_REG_STATUS);
+
+ /* switch back to the external interrupt source */
+ iowrite32be(0x3F, priv->regs + SYS_IRQ_SOURCE_CTL);
+}
+
+/**
+ * data_dma_cb() - DMAEngine callback for DMA completion
+ * @data: the driver's private data structure
+ *
+ * Complete a DMA transfer from the DATA-FPGA's
+ *
+ * This is called via the DMA callback mechanism, and will handle moving the
+ * completed DMA transaction to the used list, and then wake any processes
+ * waiting for new data
+ *
+ * CONTEXT: any, softirq expected
+ */
+static void data_dma_cb(void *data)
+{
+ struct fpga_device *priv = data;
+ unsigned long flags;
+
+ spin_lock_irqsave(&priv->lock, flags);
+
+ /* If there is no inflight buffer, we've got a bug */
+ BUG_ON(priv->inflight == NULL);
+
+ /* Move the inflight buffer onto the used list */
+ list_move_tail(&priv->inflight->entry, &priv->used);
+ priv->inflight = NULL;
+
+ /* clear the FPGA status and re-enable interrupts */
+ data_enable_interrupts(priv);
+
+ spin_unlock_irqrestore(&priv->lock, flags);
+
+ /*
+ * We've changed both the inflight and used lists, so we need
+ * to wake up any processes that are blocking for those events
+ */
+ wake_up(&priv->wait);
+}
+
+/**
+ * data_submit_dma() - prepare and submit the required DMA to fill a buffer
+ * @priv: the driver's private data structure
+ * @buf: the data buffer
+ *
+ * Prepare and submit the necessary DMA transactions to fill a correlation
+ * data buffer.
+ *
+ * LOCKING: must hold dev->lock
+ * CONTEXT: hardirq only
+ *
+ * Returns 0 on success, -ERRNO otherwise
+ */
+static int data_submit_dma(struct fpga_device *priv, struct data_buf *buf)
+{
+ struct scatterlist *dst_sg, *src_sg;
+ unsigned int dst_nents, src_nents;
+ struct dma_chan *chan = priv->chan;
+ struct dma_async_tx_descriptor *tx;
+ dma_cookie_t cookie;
+ dma_addr_t dst, src;
+
+ dst_sg = buf->vb.sglist;
+ dst_nents = buf->vb.sglen;
+
+ src_sg = priv->corl_table.sgl;
+ src_nents = priv->corl_nents;
+
+ /*
+ * All buffers passed to this function should be ready and mapped
+ * for DMA already. Therefore, we don't need to do anything except
+ * submit it to the Freescale DMA Engine for processing
+ */
+
+ /* setup the scatterlist to scatterlist transfer */
+ tx = chan->device->device_prep_dma_sg(chan,
+ dst_sg, dst_nents,
+ src_sg, src_nents,
+ 0);
+ if (!tx) {
+ dev_err(priv->dev, "unable to prep scatterlist DMA\n");
+ return -ENOMEM;
+ }
+
+ /* submit the transaction to the DMA controller */
+ cookie = tx->tx_submit(tx);
+ if (dma_submit_error(cookie)) {
+ dev_err(priv->dev, "unable to submit scatterlist DMA\n");
+ return -ENOMEM;
+ }
+
+ /* Prepare the re-read of the SYS-FPGA block */
+ dst = sg_dma_address(dst_sg) + (NUM_FPGA * REG_BLOCK_SIZE);
+ src = SYS_FPGA_BLOCK;
+ tx = chan->device->device_prep_dma_memcpy(chan, dst, src,
+ REG_BLOCK_SIZE,
+ DMA_PREP_INTERRUPT);
+ if (!tx) {
+ dev_err(priv->dev, "unable to prep SYS-FPGA DMA\n");
+ return -ENOMEM;
+ }
+
+ /* Setup the callback */
+ tx->callback = data_dma_cb;
+ tx->callback_param = priv;
+
+ /* submit the transaction to the DMA controller */
+ cookie = tx->tx_submit(tx);
+ if (dma_submit_error(cookie)) {
+ dev_err(priv->dev, "unable to submit SYS-FPGA DMA\n");
+ return -ENOMEM;
+ }
+
+ return 0;
+}
+
+#define CORL_DONE 0x1
+#define CORL_ERR 0x2
+
+static irqreturn_t data_irq(int irq, void *dev_id)
+{
+ struct fpga_device *priv = dev_id;
+ bool submitted = false;
+ struct data_buf *buf;
+ u32 status;
+ int i;
+
+ /* detect spurious interrupts via FPGA status */
+ for (i = 0; i < 4; i++) {
+ status = fpga_read_reg(priv, i, MMAP_REG_STATUS);
+ if (!(status & (CORL_DONE | CORL_ERR))) {
+ dev_err(priv->dev, "spurious irq detected (FPGA)\n");
+ return IRQ_NONE;
+ }
+ }
+
+ /* detect spurious interrupts via raw IRQ pin readback */
+ status = ioread32be(priv->regs + SYS_IRQ_INPUT_DATA);
+ if (status & IRQ_CORL_DONE) {
+ dev_err(priv->dev, "spurious irq detected (IRQ)\n");
+ return IRQ_NONE;
+ }
+
+ spin_lock(&priv->lock);
+
+ /* hide the interrupt by switching the IRQ driver to GPIO */
+ data_disable_interrupts(priv);
+
+ /* If there are no free buffers, drop this data */
+ if (list_empty(&priv->free)) {
+ priv->num_dropped++;
+ goto out;
+ }
+
+ buf = list_first_entry(&priv->free, struct data_buf, entry);
+ list_del_init(&buf->entry);
+ BUG_ON(buf->size != priv->bufsize);
+
+ /* Submit a DMA transfer to get the correlation data */
+ if (data_submit_dma(priv, buf)) {
+ dev_err(priv->dev, "Unable to setup DMA transfer\n");
+ list_move_tail(&buf->entry, &priv->free);
+ goto out;
+ }
+
+ /* Save the buffer for the DMA callback */
+ priv->inflight = buf;
+ submitted = true;
+
+ /* Start the DMA Engine */
+ dma_async_memcpy_issue_pending(priv->chan);
+
+out:
+ /* If no DMA was submitted, re-enable interrupts */
+ if (!submitted)
+ data_enable_interrupts(priv);
+
+ spin_unlock(&priv->lock);
+ return IRQ_HANDLED;
+}
+
+/*
+ * Realtime Device Enable Helpers
+ */
+
+/**
+ * data_device_enable() - enable the device for buffered dumping
+ * @priv: the driver's private data structure
+ *
+ * Enable the device for buffered dumping. Allocates buffers and hooks up
+ * the interrupt handler. When this finishes, data will come pouring in.
+ *
+ * LOCKING: must hold dev->mutex
+ * CONTEXT: user context only
+ *
+ * Returns 0 on success, -ERRNO otherwise
+ */
+static int data_device_enable(struct fpga_device *priv)
+{
+ u32 val;
+ int ret;
+
+ /* multiple enables are safe: they do nothing */
+ if (priv->enabled)
+ return 0;
+
+ /* check that the FPGAs are programmed */
+ val = ioread32be(priv->regs + SYS_FPGA_CONFIG_STATUS);
+ if (!(val & (1 << 18))) {
+ dev_err(priv->dev, "DATA-FPGAs are not enabled\n");
+ return -ENODATA;
+ }
+
+ /* read the FPGAs to calculate the buffer size */
+ ret = data_calculate_bufsize(priv);
+ if (ret) {
+ dev_err(priv->dev, "unable to calculate buffer size\n");
+ goto out_error;
+ }
+
+ /* allocate the correlation data buffers */
+ ret = data_alloc_buffers(priv);
+ if (ret) {
+ dev_err(priv->dev, "unable to allocate buffers\n");
+ goto out_error;
+ }
+
+ /* setup the source scatterlist for dumping correlation data */
+ ret = data_setup_corl_table(priv);
+ if (ret) {
+ dev_err(priv->dev, "unable to setup correlation DMA table\n");
+ goto out_error;
+ }
+
+ /* hookup the irq handler */
+ ret = request_irq(priv->irq, data_irq, IRQF_SHARED, drv_name, priv);
+ if (ret) {
+ dev_err(priv->dev, "unable to request IRQ handler\n");
+ goto out_error;
+ }
+
+ /* switch to the external FPGA IRQ line */
+ data_enable_interrupts(priv);
+
+ /* success, we're enabled */
+ priv->enabled = true;
+ return 0;
+
+out_error:
+ sg_free_table(&priv->corl_table);
+ priv->corl_nents = 0;
+
+ data_free_buffers(priv);
+ return ret;
+}
+
+/**
+ * data_device_disable() - disable the device for buffered dumping
+ * @priv: the driver's private data structure
+ *
+ * Disable the device for buffered dumping. Stops new DMA transactions from
+ * being generated, waits for all outstanding DMA to complete, and then frees
+ * all buffers.
+ *
+ * LOCKING: must hold dev->mutex
+ * CONTEXT: user only
+ *
+ * Returns 0 on success, -ERRNO otherwise
+ */
+static int data_device_disable(struct fpga_device *priv)
+{
+ int ret;
+
+ /* allow multiple disable */
+ if (!priv->enabled)
+ return 0;
+
+ /* switch to the internal GPIO IRQ line */
+ data_disable_interrupts(priv);
+
+ /* unhook the irq handler */
+ free_irq(priv->irq, priv);
+
+ /*
+ * wait for all outstanding DMA to complete
+ *
+ * Device interrupts are disabled, therefore another buffer cannot
+ * be marked inflight.
+ */
+ ret = wait_event_interruptible(priv->wait, priv->inflight == NULL);
+ if (ret)
+ return ret;
+
+ /* free the correlation table */
+ sg_free_table(&priv->corl_table);
+ priv->corl_nents = 0;
+
+ /*
+ * We are taking the spinlock not to protect priv->enabled, but instead
+ * to make sure that there are no readers in the process of altering
+ * the free or used lists while we are setting this flag.
+ */
+ spin_lock_irq(&priv->lock);
+ priv->enabled = false;
+ spin_unlock_irq(&priv->lock);
+
+ /* free all buffers: the free and used lists are not being changed */
+ data_free_buffers(priv);
+ return 0;
+}
+
+/*
+ * DEBUGFS Interface
+ */
+#ifdef CONFIG_DEBUG_FS
+
+/*
+ * Count the number of entries in the given list
+ */
+static unsigned int list_num_entries(struct list_head *list)
+{
+ struct list_head *entry;
+ unsigned int ret = 0;
+
+ list_for_each(entry, list)
+ ret++;
+
+ return ret;
+}
+
+static int data_debug_show(struct seq_file *f, void *offset)
+{
+ struct fpga_device *priv = f->private;
+ int ret;
+
+ /*
+ * Lock the mutex first, so that we get an accurate value for enable
+ * Lock the spinlock next, to get accurate list counts
+ */
+ ret = mutex_lock_interruptible(&priv->mutex);
+ if (ret)
+ return ret;
+
+ spin_lock_irq(&priv->lock);
+
+ seq_printf(f, "enabled: %d\n", priv->enabled);
+ seq_printf(f, "bufsize: %d\n", priv->bufsize);
+ seq_printf(f, "num_buffers: %d\n", priv->num_buffers);
+ seq_printf(f, "num_free: %d\n", list_num_entries(&priv->free));
+ seq_printf(f, "inflight: %d\n", priv->inflight != NULL);
+ seq_printf(f, "num_used: %d\n", list_num_entries(&priv->used));
+ seq_printf(f, "num_dropped: %d\n", priv->num_dropped);
+
+ spin_unlock_irq(&priv->lock);
+ mutex_unlock(&priv->mutex);
+ return 0;
+}
+
+static int data_debug_open(struct inode *inode, struct file *file)
+{
+ return single_open(file, data_debug_show, inode->i_private);
+}
+
+static const struct file_operations data_debug_fops = {
+ .owner = THIS_MODULE,
+ .open = data_debug_open,
+ .read = seq_read,
+ .llseek = seq_lseek,
+ .release = single_release,
+};
+
+static int data_debugfs_init(struct fpga_device *priv)
+{
+ priv->dbg_entry = debugfs_create_file(drv_name, S_IRUGO, NULL, priv,
+ &data_debug_fops);
+ if (IS_ERR(priv->dbg_entry))
+ return PTR_ERR(priv->dbg_entry);
+
+ return 0;
+}
+
+static void data_debugfs_exit(struct fpga_device *priv)
+{
+ debugfs_remove(priv->dbg_entry);
+}
+
+#else
+
+static inline int data_debugfs_init(struct fpga_device *priv)
+{
+ return 0;
+}
+
+static inline void data_debugfs_exit(struct fpga_device *priv)
+{
+}
+
+#endif /* CONFIG_DEBUG_FS */
+
+/*
+ * SYSFS Attributes
+ */
+
+static ssize_t data_en_show(struct device *dev, struct device_attribute *attr,
+ char *buf)
+{
+ struct fpga_device *priv = dev_get_drvdata(dev);
+ return snprintf(buf, PAGE_SIZE, "%u\n", priv->enabled);
+}
+
+static ssize_t data_en_set(struct device *dev, struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct fpga_device *priv = dev_get_drvdata(dev);
+ unsigned long enable;
+ int ret;
+
+ ret = strict_strtoul(buf, 0, &enable);
+ if (ret) {
+ dev_err(priv->dev, "unable to parse enable input\n");
+ return -EINVAL;
+ }
+
+ ret = mutex_lock_interruptible(&priv->mutex);
+ if (ret)
+ return ret;
+
+ if (enable)
+ ret = data_device_enable(priv);
+ else
+ ret = data_device_disable(priv);
+
+ if (ret) {
+ dev_err(priv->dev, "device %s failed\n",
+ enable ? "enable" : "disable");
+ count = ret;
+ goto out_unlock;
+ }
+
+out_unlock:
+ mutex_unlock(&priv->mutex);
+ return count;
+}
+
+static DEVICE_ATTR(enable, S_IWUSR | S_IRUGO, data_en_show, data_en_set);
+
+static struct attribute *data_sysfs_attrs[] = {
+ &dev_attr_enable.attr,
+ NULL,
+};
+
+static const struct attribute_group rt_sysfs_attr_group = {
+ .attrs = data_sysfs_attrs,
+};
+
+/*
+ * FPGA Realtime Data Character Device
+ */
+
+static int data_open(struct inode *inode, struct file *filp)
+{
+ /*
+ * The miscdevice layer puts our struct miscdevice into the
+ * filp->private_data field. We use this to find our private
+ * data and then overwrite it with our own private structure.
+ */
+ struct fpga_device *priv = container_of(filp->private_data,
+ struct fpga_device, miscdev);
+ struct fpga_reader *reader;
+ int ret;
+
+ /* allocate private data */
+ reader = kzalloc(sizeof(*reader), GFP_KERNEL);
+ if (!reader)
+ return -ENOMEM;
+
+ reader->priv = priv;
+ reader->buf = NULL;
+
+ filp->private_data = reader;
+ ret = nonseekable_open(inode, filp);
+ if (ret) {
+ dev_err(priv->dev, "nonseekable-open failed\n");
+ kfree(reader);
+ return ret;
+ }
+
+ /*
+ * success, increase the reference count of the private data structure
+ * so that it doesn't disappear if the device is unbound
+ */
+ kref_get(&priv->ref);
+ return 0;
+}
+
+static int data_release(struct inode *inode, struct file *filp)
+{
+ struct fpga_reader *reader = filp->private_data;
+ struct fpga_device *priv = reader->priv;
+
+ /* free the per-reader structure */
+ data_free_buffer(reader->buf);
+ kfree(reader);
+ filp->private_data = NULL;
+
+ /* decrement our reference count to the private data */
+ kref_put(&priv->ref, fpga_device_release);
+ return 0;
+}
+
+static ssize_t data_read(struct file *filp, char __user *ubuf, size_t count,
+ loff_t *f_pos)
+{
+ struct fpga_reader *reader = filp->private_data;
+ struct fpga_device *priv = reader->priv;
+ struct list_head *used = &priv->used;
+ struct data_buf *dbuf;
+ size_t avail;
+ void *data;
+ int ret;
+
+ /* check if we already have a partial buffer */
+ if (reader->buf) {
+ dbuf = reader->buf;
+ goto have_buffer;
+ }
+
+ spin_lock_irq(&priv->lock);
+
+ /* Block until there is at least one buffer on the used list */
+ while (list_empty(used)) {
+ spin_unlock_irq(&priv->lock);
+
+ if (filp->f_flags & O_NONBLOCK)
+ return -EAGAIN;
+
+ ret = wait_event_interruptible(priv->wait, !list_empty(used));
+ if (ret)
+ return ret;
+
+ spin_lock_irq(&priv->lock);
+ }
+
+ /* Grab the first buffer off of the used list */
+ dbuf = list_first_entry(used, struct data_buf, entry);
+ list_del_init(&dbuf->entry);
+
+ spin_unlock_irq(&priv->lock);
+
+ /* Buffers are always mapped: unmap it */
+ videobuf_dma_unmap(priv->dev, &dbuf->vb);
+
+ /* save the buffer for later */
+ reader->buf = dbuf;
+ reader->buf_start = 0;
+
+have_buffer:
+ /* Get the number of bytes available */
+ avail = dbuf->size - reader->buf_start;
+ data = dbuf->vb.vaddr + reader->buf_start;
+
+ /* Get the number of bytes we can transfer */
+ count = min(count, avail);
+
+ /* Copy the data to the userspace buffer */
+ if (copy_to_user(ubuf, data, count))
+ return -EFAULT;
+
+ /* Update the amount of available space */
+ avail -= count;
+
+ /*
+ * If there is still some data available, save the buffer for the
+ * next userspace call to read() and return
+ */
+ if (avail > 0) {
+ reader->buf_start += count;
+ reader->buf = dbuf;
+ return count;
+ }
+
+ /*
+ * Get the buffer ready to be reused for DMA
+ *
+ * If it fails, we pretend that the read never happed and return
+ * -EFAULT to userspace. The read will be retried.
+ */
+ ret = videobuf_dma_map(priv->dev, &dbuf->vb);
+ if (ret) {
+ dev_err(priv->dev, "unable to remap buffer for DMA\n");
+ return -EFAULT;
+ }
+
+ /* Lock against concurrent enable/disable */
+ spin_lock_irq(&priv->lock);
+
+ /* the reader is finished with this buffer */
+ reader->buf = NULL;
+
+ /*
+ * One of two things has happened, the device is disabled, or the
+ * device has been reconfigured underneath us. In either case, we
+ * should just throw away the buffer.
+ */
+ if (!priv->enabled || dbuf->size != priv->bufsize) {
+ videobuf_dma_unmap(priv->dev, &dbuf->vb);
+ data_free_buffer(dbuf);
+ goto out_unlock;
+ }
+
+ /* The buffer is safe to reuse, so add it back to the free list */
+ list_add_tail(&dbuf->entry, &priv->free);
+
+out_unlock:
+ spin_unlock_irq(&priv->lock);
+ return count;
+}
+
+static unsigned int data_poll(struct file *filp, struct poll_table_struct *tbl)
+{
+ struct fpga_reader *reader = filp->private_data;
+ struct fpga_device *priv = reader->priv;
+ unsigned int mask = 0;
+
+ poll_wait(filp, &priv->wait, tbl);
+
+ if (!list_empty(&priv->used))
+ mask |= POLLIN | POLLRDNORM;
+
+ return mask;
+}
+
+static int data_mmap(struct file *filp, struct vm_area_struct *vma)
+{
+ struct fpga_reader *reader = filp->private_data;
+ struct fpga_device *priv = reader->priv;
+ unsigned long offset, vsize, psize, addr;
+
+ /* VMA properties */
+ offset = vma->vm_pgoff << PAGE_SHIFT;
+ vsize = vma->vm_end - vma->vm_start;
+ psize = priv->phys_size - offset;
+ addr = (priv->phys_addr + offset) >> PAGE_SHIFT;
+
+ /* Check against the FPGA region's physical memory size */
+ if (vsize > psize) {
+ dev_err(priv->dev, "requested mmap mapping too large\n");
+ return -EINVAL;
+ }
+
+ /* IO memory (stop cacheing) */
+ vma->vm_flags |= VM_IO | VM_RESERVED;
+ vma->vm_page_prot = pgprot_noncached(vma->vm_page_prot);
+
+ return io_remap_pfn_range(vma, vma->vm_start, addr, vsize,
+ vma->vm_page_prot);
+}
+
+static const struct file_operations data_fops = {
+ .owner = THIS_MODULE,
+ .open = data_open,
+ .release = data_release,
+ .read = data_read,
+ .poll = data_poll,
+ .mmap = data_mmap,
+ .llseek = no_llseek,
+};
+
+/*
+ * OpenFirmware Device Subsystem
+ */
+
+static bool dma_filter(struct dma_chan *chan, void *data)
+{
+ /*
+ * DMA Channel #0 is used for the FPGA Programmer, so ignore it
+ *
+ * This probably won't survive an unload/load cycle of the Freescale
+ * DMAEngine driver, but that won't be a problem
+ */
+ if (chan->chan_id == 0 && chan->device->dev_id == 0)
+ return false;
+
+ return true;
+}
+
+static int data_of_probe(struct platform_device *op,
+ const struct of_device_id *match)
+{
+ struct device_node *of_node = op->dev.of_node;
+ struct device *this_device;
+ struct fpga_device *priv;
+ struct resource res;
+ dma_cap_mask_t mask;
+ int ret;
+
+ /* Allocate private data */
+ priv = kzalloc(sizeof(*priv), GFP_KERNEL);
+ if (!priv) {
+ dev_err(&op->dev, "Unable to allocate device private data\n");
+ ret = -ENOMEM;
+ goto out_return;
+ }
+
+ dev_set_drvdata(&op->dev, priv);
+ priv->dev = &op->dev;
+ kref_init(&priv->ref);
+ mutex_init(&priv->mutex);
+
+ dev_set_drvdata(priv->dev, priv);
+ spin_lock_init(&priv->lock);
+ INIT_LIST_HEAD(&priv->free);
+ INIT_LIST_HEAD(&priv->used);
+ init_waitqueue_head(&priv->wait);
+
+ /* Setup the misc device */
+ priv->miscdev.minor = MISC_DYNAMIC_MINOR;
+ priv->miscdev.name = drv_name;
+ priv->miscdev.fops = &data_fops;
+
+ /* Get the physical address of the FPGA registers */
+ ret = of_address_to_resource(of_node, 0, &res);
+ if (ret) {
+ dev_err(&op->dev, "Unable to find FPGA physical address\n");
+ ret = -ENODEV;
+ goto out_free_priv;
+ }
+
+ priv->phys_addr = res.start;
+ priv->phys_size = resource_size(&res);
+
+ /* ioremap the registers for use */
+ priv->regs = of_iomap(of_node, 0);
+ if (!priv->regs) {
+ dev_err(&op->dev, "Unable to ioremap registers\n");
+ ret = -ENOMEM;
+ goto out_free_priv;
+ }
+
+ dma_cap_zero(mask);
+ dma_cap_set(DMA_MEMCPY, mask);
+ dma_cap_set(DMA_INTERRUPT, mask);
+ dma_cap_set(DMA_SLAVE, mask);
+ dma_cap_set(DMA_SG, mask);
+
+ /* Request a DMA channel */
+ priv->chan = dma_request_channel(mask, dma_filter, NULL);
+ if (!priv->chan) {
+ dev_err(&op->dev, "Unable to request DMA channel\n");
+ ret = -ENODEV;
+ goto out_unmap_regs;
+ }
+
+ /* Find the correct IRQ number */
+ priv->irq = irq_of_parse_and_map(of_node, 0);
+ if (priv->irq == NO_IRQ) {
+ dev_err(&op->dev, "Unable to find IRQ line\n");
+ ret = -ENODEV;
+ goto out_release_dma;
+ }
+
+ /* Drive the GPIO for FPGA IRQ high (no interrupt) */
+ iowrite32be(IRQ_CORL_DONE, priv->regs + SYS_IRQ_OUTPUT_DATA);
+
+ /* Register the miscdevice */
+ ret = misc_register(&priv->miscdev);
+ if (ret) {
+ dev_err(&op->dev, "Unable to register miscdevice\n");
+ goto out_irq_dispose_mapping;
+ }
+
+ /* Create the debugfs files */
+ ret = data_debugfs_init(priv);
+ if (ret) {
+ dev_err(&op->dev, "Unable to create debugfs files\n");
+ goto out_misc_deregister;
+ }
+
+ /* Create the sysfs files */
+ this_device = priv->miscdev.this_device;
+ dev_set_drvdata(this_device, priv);
+ ret = sysfs_create_group(&this_device->kobj, &rt_sysfs_attr_group);
+ if (ret) {
+ dev_err(&op->dev, "Unable to create sysfs files\n");
+ goto out_data_debugfs_exit;
+ }
+
+ dev_info(&op->dev, "CARMA FPGA Realtime Data Driver Loaded\n");
+ return 0;
+
+out_data_debugfs_exit:
+ data_debugfs_exit(priv);
+out_misc_deregister:
+ misc_deregister(&priv->miscdev);
+out_irq_dispose_mapping:
+ irq_dispose_mapping(priv->irq);
+out_release_dma:
+ dma_release_channel(priv->chan);
+out_unmap_regs:
+ iounmap(priv->regs);
+out_free_priv:
+ kref_put(&priv->ref, fpga_device_release);
+out_return:
+ return ret;
+}
+
+static int data_of_remove(struct platform_device *op)
+{
+ struct fpga_device *priv = dev_get_drvdata(&op->dev);
+ struct device *this_device = priv->miscdev.this_device;
+
+ /* remove all sysfs files, now the device cannot be re-enabled */
+ sysfs_remove_group(&this_device->kobj, &rt_sysfs_attr_group);
+
+ /* remove all debugfs files */
+ data_debugfs_exit(priv);
+
+ /* disable the device from generating data */
+ data_device_disable(priv);
+
+ /* remove the character device to stop new readers from appearing */
+ misc_deregister(&priv->miscdev);
+
+ /* cleanup everything not needed by readers */
+ irq_dispose_mapping(priv->irq);
+ dma_release_channel(priv->chan);
+ iounmap(priv->regs);
+
+ /* release our reference */
+ kref_put(&priv->ref, fpga_device_release);
+ return 0;
+}
+
+static struct of_device_id data_of_match[] = {
+ { .compatible = "carma,carma-fpga", },
+ {},
+};
+
+static struct of_platform_driver data_of_driver = {
+ .probe = data_of_probe,
+ .remove = data_of_remove,
+ .driver = {
+ .name = drv_name,
+ .of_match_table = data_of_match,
+ .owner = THIS_MODULE,
+ },
+};
+
+/*
+ * Module Init / Exit
+ */
+
+static int __init data_init(void)
+{
+ return of_register_platform_driver(&data_of_driver);
+}
+
+static void __exit data_exit(void)
+{
+ of_unregister_platform_driver(&data_of_driver);
+}
+
+MODULE_AUTHOR("Ira W. Snyder <iws@ovro.caltech.edu>");
+MODULE_DESCRIPTION("CARMA DATA-FPGA Access Driver");
+MODULE_LICENSE("GPL");
+
+module_init(data_init);
+module_exit(data_exit);
--
1.7.3.4
^ permalink raw reply related
* Re: [RFC] Inter-processor Mailboxes Drivers
From: Sundar @ 2011-02-12 6:28 UTC (permalink / raw)
To: Meador Inge
Cc: Linus WALLEIJ, Hiroshi DOYU, Blanchard, Hollis, openmcapi-dev,
linuxppc-dev, linux-arm-kernel
In-Reply-To: <4D55A7F7.5090700@mentor.com>
Hi,
On Sat, Feb 12, 2011 at 2:49 AM, Meador Inge <meador_inge@mentor.com> wrote=
:
>
> =A0 =A01. Hardware specific bits somewhere under '.../arch/*'. =A0Drivers
> =A0 =A0 =A0 for the MPIC message registers on Power and OMAP4 mailboxes, =
for
> =A0 =A0 =A0 example.
Yes; this can help.
> =A0 =A02. A higher level driver under '.../drivers/mailbox/*'. =A0That th=
e
> =A0 =A0 =A0 pieces in (1) would register with. =A0This piece would expose=
the
> =A0 =A0 =A0 main kernel API.
A lot of mailboxes are too platform specific with regards to the communicat=
ion
with the main CPU and probably it depends on the mailbox too; you can
find polling and
interrupt supported mailbox support at times on the same platform.
APIs should probably be
generic enough to be able to operate in any context.
> Now I have the following questions:
>
> =A0 =A01. Do others see value in this?
At least I would like this; I wanted to generalize such mailbox IPCs
right from the
day when I was working on one, but coudnt really work on that.
> =A0 =A02. Does something like this already exist?
Not generic as you say; but apart from the OMAP platforms,
you could refer to arch/arm/mach-ux500/prcmu for a mailbox based
IPC on the U8500 platform.
> =A0 =A03. Is someone else already working on this?
Not sure of that too :), but I am CCing Linus W, the maintainer
of U8500 if he thinks it is a good idea to come up with a mailbox IPC
framework
Cheers!
--=20
---------
The views expressed in this email are personal and do not necessarily
echo my employers.
^ permalink raw reply
* arch/powerpc/kernel/cacheinfo.c: Possible null pointer dereference
From: David Binderman @ 2011-02-12 10:08 UTC (permalink / raw)
To: benh, paulus; +Cc: linuxppc-dev
[-- Attachment #1: Type: text/plain, Size: 538 bytes --]
Hello there,
I just tried out cppcheck-1.47 on the linux-2.6.38-rc4 source code.
It said
[arch/powerpc/kernel/cacheinfo.c:380]: (error) Possible null pointer dereference: cache - otherwise it is redundant to check if cache is null at line 382
The source code is
WARN_ONCE(cache && cache->level != level,
"cache level mismatch on lookup (got %d, expected %d)\n",
cache->level, level);
if (!cache)
I agree with cppcheck. Suggest code rework.
Regards
David Binderman
[-- Attachment #2: Type: text/html, Size: 913 bytes --]
^ permalink raw reply
* [git pull] Documentation changes and an SPI bug fix
From: Grant Likely @ 2011-02-13 15:15 UTC (permalink / raw)
To: Linux Kernel Mailing List, Linus Torvalds
Cc: spi-devel-general, devicetree-discuss, Guennadi Liakhovetski,
linuxppc-dev
In-Reply-To: <AANLkTikM0bZbRfSPeXjf4NVn_AAHm1w7FWRKYvoucdHj@mail.gmail.com>
[-- Attachment #1: Type: text/plain, Size: 9451 bytes --]
Hi Linus,
A set of mostly documentation changes, but I also had an spi bug fix that I
needed to send to you, so I've sent it all in one pull req.
g.
The following changes since commit d2478521afc20227658a10a8c5c2bf1a2aa615b3:
char/ipmi: fix OOPS caused by pnp_unregister_driver on unregistered driver
(2011-02-10 18:00:21 -0800)
are available in the git repository at:
git://git.secretlab.ca/git/linux-2.6 spi/merge
Grant Likely (5):
dt: Move device tree documentation out of powerpc directory
dt: Remove obsolete description of powerpc boot interface
dt: add documentation of ARM dt boot interface
MAINTAINERS: Add entry for GPIO subsystem
Merge branch 'devicetree/merge' into spi/merge
Guennadi Liakhovetski (1):
spi/spi_sh_msiof: fix wrong address calculation, which leads to an Oops
Paul Bolle (1):
devicetree-discuss is moderated for non-subscribers
Documentation/arm/Booting | 33 +++-
.../bindings/ata/fsl-sata.txt} | 0
.../bindings}/eeprom.txt | 0
.../fsl => devicetree/bindings/gpio}/8xxx_gpio.txt | 0
.../bindings}/gpio/gpio.txt | 0
.../bindings}/gpio/led.txt | 0
.../bindings/i2c/fsl-i2c.txt} | 0
.../bindings}/marvell.txt | 0
.../bindings/mmc/fsl-esdhc.txt} | 0
.../bindings/mmc}/mmc-spi-slot.txt | 0
.../bindings/mtd/fsl-upm-nand.txt} | 0
.../bindings/mtd}/mtd-physmap.txt | 0
.../bindings/net/can/mpc5xxx-mscan.txt} | 0
.../bindings/net}/can/sja1000.txt | 0
.../bindings/net/fsl-tsec-phy.txt} | 0
.../bindings/net/mdio-gpio.txt} | 0
.../bindings/net}/phy.txt | 0
.../bindings/pci}/83xx-512x-pci.txt | 0
.../bindings/powerpc}/4xx/cpm.txt | 0
.../bindings/powerpc}/4xx/emac.txt | 0
.../bindings/powerpc}/4xx/ndfc.txt | 0
.../bindings/powerpc}/4xx/ppc440spe-adma.txt | 0
.../bindings/powerpc}/4xx/reboot.txt | 0
.../bindings/powerpc}/fsl/board.txt | 0
.../bindings/powerpc}/fsl/cpm_qe/cpm.txt | 0
.../bindings/powerpc}/fsl/cpm_qe/cpm/brg.txt | 0
.../bindings/powerpc}/fsl/cpm_qe/cpm/i2c.txt | 0
.../bindings/powerpc}/fsl/cpm_qe/cpm/pic.txt | 0
.../bindings/powerpc}/fsl/cpm_qe/cpm/usb.txt | 0
.../bindings/powerpc}/fsl/cpm_qe/gpio.txt | 0
.../bindings/powerpc}/fsl/cpm_qe/network.txt | 0
.../bindings/powerpc}/fsl/cpm_qe/qe.txt | 0
.../bindings/powerpc}/fsl/cpm_qe/qe/firmware.txt | 0
.../bindings/powerpc}/fsl/cpm_qe/qe/par_io.txt | 0
.../bindings/powerpc}/fsl/cpm_qe/qe/pincfg.txt | 0
.../bindings/powerpc}/fsl/cpm_qe/qe/ucc.txt | 0
.../bindings/powerpc}/fsl/cpm_qe/qe/usb.txt | 0
.../bindings/powerpc}/fsl/cpm_qe/serial.txt | 0
.../bindings/powerpc}/fsl/diu.txt | 0
.../bindings/powerpc}/fsl/dma.txt | 0
.../bindings/powerpc/fsl}/ecm.txt | 0
.../bindings/powerpc}/fsl/gtm.txt | 0
.../bindings/powerpc}/fsl/guts.txt | 0
.../bindings/powerpc}/fsl/lbc.txt | 0
.../bindings/powerpc}/fsl/mcm.txt | 0
.../bindings/powerpc}/fsl/mcu-mpc8349emitx.txt | 0
.../bindings/powerpc}/fsl/mpc5121-psc.txt | 0
.../bindings/powerpc}/fsl/mpc5200.txt | 0
.../bindings/powerpc}/fsl/mpic.txt | 0
.../bindings/powerpc}/fsl/msi-pic.txt | 0
.../bindings/powerpc}/fsl/pmc.txt | 0
.../bindings/powerpc}/fsl/sec.txt | 0
.../bindings/powerpc}/fsl/ssi.txt | 0
.../bindings/powerpc}/nintendo/gamecube.txt | 0
.../bindings/powerpc}/nintendo/wii.txt | 0
.../bindings/spi/fsl-spi.txt} | 0
.../bindings/spi}/spi-bus.txt | 0
.../bindings/usb/fsl-usb.txt} | 0
.../bindings/usb}/usb-ehci.txt | 0
.../bindings}/xilinx.txt | 0
.../{powerpc => devicetree}/booting-without-of.txt | 193
+++++++++-----------
MAINTAINERS | 11 +-
drivers/spi/spi_sh_msiof.c | 6 +-
63 files changed, 131 insertions(+), 112 deletions(-)
rename Documentation/{powerpc/dts-bindings/fsl/sata.txt =>
devicetree/bindings/ata/fsl-sata.txt} (100%)
rename Documentation/{powerpc/dts-bindings =>
devicetree/bindings}/eeprom.txt (100%)
rename Documentation/{powerpc/dts-bindings/fsl =>
devicetree/bindings/gpio}/8xxx_gpio.txt (100%)
rename Documentation/{powerpc/dts-bindings =>
devicetree/bindings}/gpio/gpio.txt (100%)
rename Documentation/{powerpc/dts-bindings =>
devicetree/bindings}/gpio/led.txt (100%)
rename Documentation/{powerpc/dts-bindings/fsl/i2c.txt =>
devicetree/bindings/i2c/fsl-i2c.txt} (100%)
rename Documentation/{powerpc/dts-bindings =>
devicetree/bindings}/marvell.txt (100%)
rename Documentation/{powerpc/dts-bindings/fsl/esdhc.txt =>
devicetree/bindings/mmc/fsl-esdhc.txt} (100%)
rename Documentation/{powerpc/dts-bindings =>
devicetree/bindings/mmc}/mmc-spi-slot.txt (100%)
rename Documentation/{powerpc/dts-bindings/fsl/upm-nand.txt =>
devicetree/bindings/mtd/fsl-upm-nand.txt} (100%)
rename Documentation/{powerpc/dts-bindings =>
devicetree/bindings/mtd}/mtd-physmap.txt (100%)
rename Documentation/{powerpc/dts-bindings/fsl/can.txt =>
devicetree/bindings/net/can/mpc5xxx-mscan.txt} (100%)
rename Documentation/{powerpc/dts-bindings =>
devicetree/bindings/net}/can/sja1000.txt (100%)
rename Documentation/{powerpc/dts-bindings/fsl/tsec.txt =>
devicetree/bindings/net/fsl-tsec-phy.txt} (100%)
rename Documentation/{powerpc/dts-bindings/gpio/mdio.txt =>
devicetree/bindings/net/mdio-gpio.txt} (100%)
rename Documentation/{powerpc/dts-bindings =>
devicetree/bindings/net}/phy.txt (100%)
rename Documentation/{powerpc/dts-bindings/fsl =>
devicetree/bindings/pci}/83xx-512x-pci.txt (100%)
rename Documentation/{powerpc/dts-bindings =>
devicetree/bindings/powerpc}/4xx/cpm.txt (100%)
rename Documentation/{powerpc/dts-bindings =>
devicetree/bindings/powerpc}/4xx/emac.txt (100%)
rename Documentation/{powerpc/dts-bindings =>
devicetree/bindings/powerpc}/4xx/ndfc.txt (100%)
rename Documentation/{powerpc/dts-bindings =>
devicetree/bindings/powerpc}/4xx/ppc440spe-adma.txt (100%)
rename Documentation/{powerpc/dts-bindings =>
devicetree/bindings/powerpc}/4xx/reboot.txt (100%)
rename Documentation/{powerpc/dts-bindings =>
devicetree/bindings/powerpc}/fsl/board.txt (100%)
rename Documentation/{powerpc/dts-bindings =>
devicetree/bindings/powerpc}/fsl/cpm_qe/cpm.txt (100%)
rename Documentation/{powerpc/dts-bindings =>
devicetree/bindings/powerpc}/fsl/cpm_qe/cpm/brg.txt (100%)
rename Documentation/{powerpc/dts-bindings =>
devicetree/bindings/powerpc}/fsl/cpm_qe/cpm/i2c.txt (100%)
rename Documentation/{powerpc/dts-bindings =>
devicetree/bindings/powerpc}/fsl/cpm_qe/cpm/pic.txt (100%)
rename Documentation/{powerpc/dts-bindings =>
devicetree/bindings/powerpc}/fsl/cpm_qe/cpm/usb.txt (100%)
rename Documentation/{powerpc/dts-bindings =>
devicetree/bindings/powerpc}/fsl/cpm_qe/gpio.txt (100%)
rename Documentation/{powerpc/dts-bindings =>
devicetree/bindings/powerpc}/fsl/cpm_qe/network.txt (100%)
rename Documentation/{powerpc/dts-bindings =>
devicetree/bindings/powerpc}/fsl/cpm_qe/qe.txt (100%)
rename Documentation/{powerpc/dts-bindings =>
devicetree/bindings/powerpc}/fsl/cpm_qe/qe/firmware.txt (100%)
rename Documentation/{powerpc/dts-bindings =>
devicetree/bindings/powerpc}/fsl/cpm_qe/qe/par_io.txt (100%)
rename Documentation/{powerpc/dts-bindings =>
devicetree/bindings/powerpc}/fsl/cpm_qe/qe/pincfg.txt (100%)
rename Documentation/{powerpc/dts-bindings =>
devicetree/bindings/powerpc}/fsl/cpm_qe/qe/ucc.txt (100%)
rename Documentation/{powerpc/dts-bindings =>
devicetree/bindings/powerpc}/fsl/cpm_qe/qe/usb.txt (100%)
rename Documentation/{powerpc/dts-bindings =>
devicetree/bindings/powerpc}/fsl/cpm_qe/serial.txt (100%)
rename Documentation/{powerpc/dts-bindings =>
devicetree/bindings/powerpc}/fsl/diu.txt (100%)
rename Documentation/{powerpc/dts-bindings =>
devicetree/bindings/powerpc}/fsl/dma.txt (100%)
rename Documentation/{powerpc/dts-bindings =>
devicetree/bindings/powerpc/fsl}/ecm.txt (100%)
rename Documentation/{powerpc/dts-bindings =>
devicetree/bindings/powerpc}/fsl/gtm.txt (100%)
rename Documentation/{powerpc/dts-bindings =>
devicetree/bindings/powerpc}/fsl/guts.txt (100%)
rename Documentation/{powerpc/dts-bindings =>
devicetree/bindings/powerpc}/fsl/lbc.txt (100%)
rename Documentation/{powerpc/dts-bindings =>
devicetree/bindings/powerpc}/fsl/mcm.txt (100%)
rename Documentation/{powerpc/dts-bindings =>
devicetree/bindings/powerpc}/fsl/mcu-mpc8349emitx.txt (100%)
rename Documentation/{powerpc/dts-bindings =>
devicetree/bindings/powerpc}/fsl/mpc5121-psc.txt (100%)
rename Documentation/{powerpc/dts-bindings =>
devicetree/bindings/powerpc}/fsl/mpc5200.txt (100%)
rename Documentation/{powerpc/dts-bindings =>
devicetree/bindings/powerpc}/fsl/mpic.txt (100%)
rename Documentation/{powerpc/dts-bindings =>
devicetree/bindings/powerpc}/fsl/msi-pic.txt (100%)
rename Documentation/{powerpc/dts-bindings =>
devicetree/bindings/powerpc}/fsl/pmc.txt (100%)
rename Documentation/{powerpc/dts-bindings =>
devicetree/bindings/powerpc}/fsl/sec.txt (100%)
rename Documentation/{powerpc/dts-bindings =>
devicetree/bindings/powerpc}/fsl/ssi.txt (100%)
rename Documentation/{powerpc/dts-bindings =>
devicetree/bindings/powerpc}/nintendo/gamecube.txt (100%)
rename Documentation/{powerpc/dts-bindings =>
devicetree/bindings/powerpc}/nintendo/wii.txt (100%)
rename Documentation/{powerpc/dts-bindings/fsl/spi.txt =>
devicetree/bindings/spi/fsl-spi.txt} (100%)
rename Documentation/{powerpc/dts-bindings =>
devicetree/bindings/spi}/spi-bus.txt (100%)
rename Documentation/{powerpc/dts-bindings/fsl/usb.txt =>
devicetree/bindings/usb/fsl-usb.txt} (100%)
rename Documentation/{powerpc/dts-bindings =>
devicetree/bindings/usb}/usb-ehci.txt (100%)
rename Documentation/{powerpc/dts-bindings =>
devicetree/bindings}/xilinx.txt (100%)
rename Documentation/{powerpc => devicetree}/booting-without-of.txt (90%)
--
Grant Likely, B.Sc., P.Eng.
Secret Lab Technologies Ltd.
[-- Attachment #2: Type: text/html, Size: 10304 bytes --]
^ permalink raw reply
* Re: [RFC] Inter-processor Mailboxes Drivers
From: Linus Walleij @ 2011-02-13 21:16 UTC (permalink / raw)
To: Sundar
Cc: Meador Inge, Hiroshi DOYU, Blanchard, Hollis, openmcapi-dev,
linuxppc-dev, linux-arm-kernel
In-Reply-To: <AANLkTikP0P9S1VgiU_yT53f-kZq0JMLBJoVWigAy8YWB@mail.gmail.com>
2011/2/12 Sundar <sunder.svit@gmail.com>:
> At least I would like this; I wanted to generalize such mailbox IPCs
> right from the day when I was working on one, but coudnt really
> work on that.
>
>> =A0 =A02. Does something like this already exist?
>
> Not generic as you say; but apart from the OMAP platforms,
> you could refer to arch/arm/mach-ux500/prcmu for a mailbox based
> IPC on the U8500 platform.
We also have this thing:
arch/arm/mach-ux500/mbox-db5500.c
It's another mailbox driver, this one talks to the modem in the
U5500 (basically a physical transport for the CAIF protocol).
(For the U8500 I think modem IPC is instead handled with
a high-speed hardware FIFO, a bit different.)
>> =A0 =A03. Is someone else already working on this?
>
> Not sure of that too :), but I am CCing Linus W, the maintainer
> of U8500 if he thinks it is a good idea to come up with a mailbox IPC
> framework
I don't know too much about the subject actually, I've not been
deeply into any such code. I don't think anyone is working
on something general from ST-Ericsson or Linaro.
Recently I saw that Texas Instruments are posting a "hardware
spinlock" framework though, this would be on a related tone,
but I think it's for shared data structures (control path) rather
than buffer passing (data path). I'm guessing this works like
that one CPU gets to spin waiting for another one to release
the lock.
Given that we may have a framework for hardware spinlock
and that we don't want to stockpile drivers into arch/*
or drivers/misc/* I would say it's intuitively a good idea,
but the question is what data types you would pass in?
In arch/arm/mach-ux500/include/mach/mbox-db5500.h
we have a struct like this:
/**
* struct mbox - Mailbox instance struct
* @list: Linked list head.
* @pdev: Pointer to device struct.
* @cb: Callback function. Will be called
* when new data is received.
* @client_data: Clients private data. Will be sent back
* in the callback function.
* @virtbase_peer: Virtual address for outgoing mailbox.
* @virtbase_local: Virtual address for incoming mailbox.
* @buffer: Then internal queue for outgoing messages.
* @name: Name of this mailbox.
* @buffer_available: Completion variable to achieve "blocking send".
* This variable will be signaled when there is
* internal buffer space available.
* @client_blocked: To keep track if any client is currently
* blocked.
* @lock: Spinlock to protect this mailbox instance.
* @write_index: Index in internal buffer to write to.
* @read_index: Index in internal buffer to read from.
* @allocated: Indicates whether this particular mailbox
* id has been allocated by someone.
*/
struct mbox {
struct list_head list;
struct platform_device *pdev;
mbox_recv_cb_t *cb;
void *client_data;
void __iomem *virtbase_peer;
void __iomem *virtbase_local;
u32 buffer[MBOX_BUF_SIZE];
char name[MBOX_NAME_SIZE];
struct completion buffer_available;
u8 client_blocked;
spinlock_t lock;
u8 write_index;
u8 read_index;
bool allocated;
};
Compare OMAPs mailboxes in
arch/arm/plat-omap/include/plat/mailbox.h:
typedef u32 mbox_msg_t;
truct omap_mbox_ops {
omap_mbox_type_t type;
int (*startup)(struct omap_mbox *mbox);
void (*shutdown)(struct omap_mbox *mbox);
/* fifo */
mbox_msg_t (*fifo_read)(struct omap_mbox *mbox);
void (*fifo_write)(struct omap_mbox *mbox, mbox_msg_t ms=
g);
int (*fifo_empty)(struct omap_mbox *mbox);
int (*fifo_full)(struct omap_mbox *mbox);
/* irq */
void (*enable_irq)(struct omap_mbox *mbox,
omap_mbox_irq_t irq);
void (*disable_irq)(struct omap_mbox *mbox,
omap_mbox_irq_t irq);
void (*ack_irq)(struct omap_mbox *mbox, omap_mbox_irq_t =
irq);
int (*is_irq)(struct omap_mbox *mbox, omap_mbox_irq_t i=
rq);
/* ctx */
void (*save_ctx)(struct omap_mbox *mbox);
void (*restore_ctx)(struct omap_mbox *mbox);
};
struct omap_mbox_queue {
spinlock_t lock;
struct kfifo fifo;
struct work_struct work;
struct tasklet_struct tasklet;
struct omap_mbox *mbox;
bool full;
};
struct omap_mbox {
char *name;
unsigned int irq;
struct omap_mbox_queue *txq, *rxq;
struct omap_mbox_ops *ops;
struct device *dev;
void *priv;
int use_count;
struct blocking_notifier_head notifier;
};
Some of this may be generalized? I dunno, they look quite
different but maybe queueing etc can actually be made general
enough to form a framework.
Yours,
Linus Walleij
^ permalink raw reply
* Re: [RFC] Inter-processor Mailboxes Drivers
From: Linus Walleij @ 2011-02-13 21:24 UTC (permalink / raw)
To: Meador Inge
Cc: openmcapi-dev, Blanchard, Hollis, Hiroshi DOYU, linuxppc-dev,
linux-arm-kernel
In-Reply-To: <4D55A7F7.5090700@mentor.com>
2011/2/11 Meador Inge <meador_inge@mentor.com>:
>=A0This would entail the traditional
> generic/specific driver split:
>
> =A0 =A01. Hardware specific bits somewhere under '.../arch/*'. =A0Drivers
> =A0 =A0 =A0 for the MPIC message registers on Power and OMAP4 mailboxes, =
for
> =A0 =A0 =A0 example.
Having any drivers under arch/* is no good tradition IMO.
Better move the whole shebang down to drivers/mailbox so
that the subsystem maintainer get the complete overview
of her/his driver family.
> =A0 =A02. A higher level driver under '.../drivers/mailbox/*'. =A0That th=
e
> =A0 =A0 =A0 pieces in (1) would register with. =A0This piece would expose=
the
> =A0 =A0 =A0 main kernel API.
Cool...
> =A0 =A03. Userspace interfaces for accessing the mailboxes. =A0A
> =A0 =A0 =A0 '/dev/mailbox1', '/dev/mailbox2', etc... mapping, for example=
.
What kind of business does userspace have with directly using
mailboxes? Enlighten me so I get it... in our system these are
used by protocols, such as net/caif/* thru drivers/net/caif/*, and
we have similar kernelspace functionality for Phonet.
CAIF and Phonet on the other hand, have custom openings
down to the thing that exists on the other end of the mailbox.
Most of these systems tend to talk some funny protocol that
is often better handled by the kernel than by any userspace.
So is this for the situation when you have no intermediate
protocol between your userpace and the other CPU's
subsystem? Or are you thinking about handling that
protocol in userspace? That is generally not such a good idea
for efficiency reasons.
Yoursm
Linus Walleij
^ permalink raw reply
* Re: arch/powerpc/kernel/cacheinfo.c: Possible null pointer dereference
From: Michael Ellerman @ 2011-02-13 23:24 UTC (permalink / raw)
To: David Binderman; +Cc: paulus, linuxppc-dev
In-Reply-To: <BLU154-w1013E8541813DF8B7564CD9CEE0@phx.gbl>
[-- Attachment #1: Type: text/plain, Size: 861 bytes --]
On Sat, 2011-02-12 at 10:08 +0000, David Binderman wrote:
> Hello there,
>
> I just tried out cppcheck-1.47 on the linux-2.6.38-rc4 source code.
>
> It said
>
> [arch/powerpc/kernel/cacheinfo.c:380]: (error) Possible null pointer
> dereference: cache - otherwise it is redundant to check if cache is
> null at line 382
>
> The source code is
>
> WARN_ONCE(cache && cache->level != level,
> "cache level mismatch on lookup (got %d, expected %d)\n",
> cache->level, level);
>
> if (!cache)
>
> I agree with cppcheck. Suggest code rework.
It's wrong.
The code is essentially:
if (cache && cache->level != level)
printf("cache level mismatch on lookup (got %d, expected %d)\n", cache->level, level);
if (!cache)
cache = cache_do_one_devnode(node, level);
Which looks fine to me?
cheers
[-- Attachment #2: This is a digitally signed message part --]
[-- Type: application/pgp-signature, Size: 198 bytes --]
^ permalink raw reply
* Re: Resources used by driver ?
From: Michael Ellerman @ 2011-02-13 23:38 UTC (permalink / raw)
To: Guillaume Dargaud; +Cc: LinuxPPC-dev
In-Reply-To: <201102101108.47647.dargaud@lpsc.in2p3.fr>
[-- Attachment #1: Type: text/plain, Size: 983 bytes --]
On Thu, 2011-02-10 at 11:08 +0100, Guillaume Dargaud wrote:
> Thanks to the advice I received here, I now have my first 'real' Linux driver
> up and running and am now at the optimization stage.
>
> Is it possible to see the resources consumed by a module ? They don't show in
> 'ps/top' but I'm sure there are ways to see how much memory it uses (to check
> for memory leaks) and how much CPU it takes. How ?
Hi Guillaume,
It's actually not that easy to measure the resources used by a driver.
In general your driver code only runs on behalf of another process, and
so that process is where the cpu usage will be accounted.
One exception is if your driver spawns a kernel thread for something, in
that case you can see it with top.
For memory usage, you can monitor /proc/slabinfo and see if anything is
growing every time you access your driver. Another option is to enable
CONFIG_DEBUG_KMEMLEAK, which will try and detect real leaks for you.
cheers
[-- Attachment #2: This is a digitally signed message part --]
[-- Type: application/pgp-signature, Size: 198 bytes --]
^ permalink raw reply
* [git pull] Documentation changes and an SPI bug fix
From: Grant Likely @ 2011-02-14 3:46 UTC (permalink / raw)
To: Linux Kernel Mailing List, Linus Torvalds
Cc: spi-devel-general, devicetree-discuss, Guennadi Liakhovetski,
linuxppc-dev
In-Reply-To: <AANLkTikpR4191DrgyodXV-Qx=r8mjXziknzJ8tJpgBt9@mail.gmail.com>
[Resend: Android insisted on sending HTML, so it was rejected by LKML]
Hi Linus,
A set of mostly documentation changes, but I also had an spi bug fix
that I needed to send to you, so I've sent it all in one pull req.
g.
The following changes since commit d2478521afc20227658a10a8c5c2bf1a2aa615b3:
char/ipmi: fix OOPS caused by pnp_unregister_driver on unregistered
driver (2011-02-10 18:00:21 -0800)
are available in the git repository at:
git://git.secretlab.ca/git/linux-2.6 spi/merge
Grant Likely (5):
dt: Move device tree documentation out of powerpc directory
dt: Remove obsolete description of powerpc boot interface
dt: add documentation of ARM dt boot interface
MAINTAINERS: Add entry for GPIO subsystem
Merge branch 'devicetree/merge' into spi/merge
Guennadi Liakhovetski (1):
spi/spi_sh_msiof: fix wrong address calculation, which leads to an Oops
Paul Bolle (1):
devicetree-discuss is moderated for non-subscribers
Documentation/arm/Booting | 33 +++-
.../bindings/ata/fsl-sata.txt} | 0
.../bindings}/eeprom.txt | 0
.../fsl => devicetree/bindings/gpio}/8xxx_gpio.txt | 0
.../bindings}/gpio/gpio.txt | 0
.../bindings}/gpio/led.txt | 0
.../bindings/i2c/fsl-i2c.txt} | 0
.../bindings}/marvell.txt | 0
.../bindings/mmc/fsl-esdhc.txt} | 0
.../bindings/mmc}/mmc-spi-slot.txt | 0
.../bindings/mtd/fsl-upm-nand.txt} | 0
.../bindings/mtd}/mtd-physmap.txt | 0
.../bindings/net/can/mpc5xxx-mscan.txt} | 0
.../bindings/net}/can/sja1000.txt | 0
.../bindings/net/fsl-tsec-phy.txt} | 0
.../bindings/net/mdio-gpio.txt} | 0
.../bindings/net}/phy.txt | 0
.../bindings/pci}/83xx-512x-pci.txt | 0
.../bindings/powerpc}/4xx/cpm.txt | 0
.../bindings/powerpc}/4xx/emac.txt | 0
.../bindings/powerpc}/4xx/ndfc.txt | 0
.../bindings/powerpc}/4xx/ppc440spe-adma.txt | 0
.../bindings/powerpc}/4xx/reboot.txt | 0
.../bindings/powerpc}/fsl/board.txt | 0
.../bindings/powerpc}/fsl/cpm_qe/cpm.txt | 0
.../bindings/powerpc}/fsl/cpm_qe/cpm/brg.txt | 0
.../bindings/powerpc}/fsl/cpm_qe/cpm/i2c.txt | 0
.../bindings/powerpc}/fsl/cpm_qe/cpm/pic.txt | 0
.../bindings/powerpc}/fsl/cpm_qe/cpm/usb.txt | 0
.../bindings/powerpc}/fsl/cpm_qe/gpio.txt | 0
.../bindings/powerpc}/fsl/cpm_qe/network.txt | 0
.../bindings/powerpc}/fsl/cpm_qe/qe.txt | 0
.../bindings/powerpc}/fsl/cpm_qe/qe/firmware.txt | 0
.../bindings/powerpc}/fsl/cpm_qe/qe/par_io.txt | 0
.../bindings/powerpc}/fsl/cpm_qe/qe/pincfg.txt | 0
.../bindings/powerpc}/fsl/cpm_qe/qe/ucc.txt | 0
.../bindings/powerpc}/fsl/cpm_qe/qe/usb.txt | 0
.../bindings/powerpc}/fsl/cpm_qe/serial.txt | 0
.../bindings/powerpc}/fsl/diu.txt | 0
.../bindings/powerpc}/fsl/dma.txt | 0
.../bindings/powerpc/fsl}/ecm.txt | 0
.../bindings/powerpc}/fsl/gtm.txt | 0
.../bindings/powerpc}/fsl/guts.txt | 0
.../bindings/powerpc}/fsl/lbc.txt | 0
.../bindings/powerpc}/fsl/mcm.txt | 0
.../bindings/powerpc}/fsl/mcu-mpc8349emitx.txt | 0
.../bindings/powerpc}/fsl/mpc5121-psc.txt | 0
.../bindings/powerpc}/fsl/mpc5200.txt | 0
.../bindings/powerpc}/fsl/mpic.txt | 0
.../bindings/powerpc}/fsl/msi-pic.txt | 0
.../bindings/powerpc}/fsl/pmc.txt | 0
.../bindings/powerpc}/fsl/sec.txt | 0
.../bindings/powerpc}/fsl/ssi.txt | 0
.../bindings/powerpc}/nintendo/gamecube.txt | 0
.../bindings/powerpc}/nintendo/wii.txt | 0
.../bindings/spi/fsl-spi.txt} | 0
.../bindings/spi}/spi-bus.txt | 0
.../bindings/usb/fsl-usb.txt} | 0
.../bindings/usb}/usb-ehci.txt | 0
.../bindings}/xilinx.txt | 0
.../{powerpc => devicetree}/booting-without-of.txt | 193 +++++++++-----------
MAINTAINERS | 11 +-
drivers/spi/spi_sh_msiof.c | 6 +-
63 files changed, 131 insertions(+), 112 deletions(-)
rename Documentation/{powerpc/dts-bindings/fsl/sata.txt =>
devicetree/bindings/ata/fsl-sata.txt} (100%)
rename Documentation/{powerpc/dts-bindings =>
devicetree/bindings}/eeprom.txt (100%)
rename Documentation/{powerpc/dts-bindings/fsl =>
devicetree/bindings/gpio}/8xxx_gpio.txt (100%)
rename Documentation/{powerpc/dts-bindings =>
devicetree/bindings}/gpio/gpio.txt (100%)
rename Documentation/{powerpc/dts-bindings =>
devicetree/bindings}/gpio/led.txt (100%)
rename Documentation/{powerpc/dts-bindings/fsl/i2c.txt =>
devicetree/bindings/i2c/fsl-i2c.txt} (100%)
rename Documentation/{powerpc/dts-bindings =>
devicetree/bindings}/marvell.txt (100%)
rename Documentation/{powerpc/dts-bindings/fsl/esdhc.txt =>
devicetree/bindings/mmc/fsl-esdhc.txt} (100%)
rename Documentation/{powerpc/dts-bindings =>
devicetree/bindings/mmc}/mmc-spi-slot.txt (100%)
rename Documentation/{powerpc/dts-bindings/fsl/upm-nand.txt =>
devicetree/bindings/mtd/fsl-upm-nand.txt} (100%)
rename Documentation/{powerpc/dts-bindings =>
devicetree/bindings/mtd}/mtd-physmap.txt (100%)
rename Documentation/{powerpc/dts-bindings/fsl/can.txt =>
devicetree/bindings/net/can/mpc5xxx-mscan.txt} (100%)
rename Documentation/{powerpc/dts-bindings =>
devicetree/bindings/net}/can/sja1000.txt (100%)
rename Documentation/{powerpc/dts-bindings/fsl/tsec.txt =>
devicetree/bindings/net/fsl-tsec-phy.txt} (100%)
rename Documentation/{powerpc/dts-bindings/gpio/mdio.txt =>
devicetree/bindings/net/mdio-gpio.txt} (100%)
rename Documentation/{powerpc/dts-bindings =>
devicetree/bindings/net}/phy.txt (100%)
rename Documentation/{powerpc/dts-bindings/fsl =>
devicetree/bindings/pci}/83xx-512x-pci.txt (100%)
rename Documentation/{powerpc/dts-bindings =>
devicetree/bindings/powerpc}/4xx/cpm.txt (100%)
rename Documentation/{powerpc/dts-bindings =>
devicetree/bindings/powerpc}/4xx/emac.txt (100%)
rename Documentation/{powerpc/dts-bindings =>
devicetree/bindings/powerpc}/4xx/ndfc.txt (100%)
rename Documentation/{powerpc/dts-bindings =>
devicetree/bindings/powerpc}/4xx/ppc440spe-adma.txt (100%)
rename Documentation/{powerpc/dts-bindings =>
devicetree/bindings/powerpc}/4xx/reboot.txt (100%)
rename Documentation/{powerpc/dts-bindings =>
devicetree/bindings/powerpc}/fsl/board.txt (100%)
rename Documentation/{powerpc/dts-bindings =>
devicetree/bindings/powerpc}/fsl/cpm_qe/cpm.txt (100%)
rename Documentation/{powerpc/dts-bindings =>
devicetree/bindings/powerpc}/fsl/cpm_qe/cpm/brg.txt (100%)
rename Documentation/{powerpc/dts-bindings =>
devicetree/bindings/powerpc}/fsl/cpm_qe/cpm/i2c.txt (100%)
rename Documentation/{powerpc/dts-bindings =>
devicetree/bindings/powerpc}/fsl/cpm_qe/cpm/pic.txt (100%)
rename Documentation/{powerpc/dts-bindings =>
devicetree/bindings/powerpc}/fsl/cpm_qe/cpm/usb.txt (100%)
rename Documentation/{powerpc/dts-bindings =>
devicetree/bindings/powerpc}/fsl/cpm_qe/gpio.txt (100%)
rename Documentation/{powerpc/dts-bindings =>
devicetree/bindings/powerpc}/fsl/cpm_qe/network.txt (100%)
rename Documentation/{powerpc/dts-bindings =>
devicetree/bindings/powerpc}/fsl/cpm_qe/qe.txt (100%)
rename Documentation/{powerpc/dts-bindings =>
devicetree/bindings/powerpc}/fsl/cpm_qe/qe/firmware.txt (100%)
rename Documentation/{powerpc/dts-bindings =>
devicetree/bindings/powerpc}/fsl/cpm_qe/qe/par_io.txt (100%)
rename Documentation/{powerpc/dts-bindings =>
devicetree/bindings/powerpc}/fsl/cpm_qe/qe/pincfg.txt (100%)
rename Documentation/{powerpc/dts-bindings =>
devicetree/bindings/powerpc}/fsl/cpm_qe/qe/ucc.txt (100%)
rename Documentation/{powerpc/dts-bindings =>
devicetree/bindings/powerpc}/fsl/cpm_qe/qe/usb.txt (100%)
rename Documentation/{powerpc/dts-bindings =>
devicetree/bindings/powerpc}/fsl/cpm_qe/serial.txt (100%)
rename Documentation/{powerpc/dts-bindings =>
devicetree/bindings/powerpc}/fsl/diu.txt (100%)
rename Documentation/{powerpc/dts-bindings =>
devicetree/bindings/powerpc}/fsl/dma.txt (100%)
rename Documentation/{powerpc/dts-bindings =>
devicetree/bindings/powerpc/fsl}/ecm.txt (100%)
rename Documentation/{powerpc/dts-bindings =>
devicetree/bindings/powerpc}/fsl/gtm.txt (100%)
rename Documentation/{powerpc/dts-bindings =>
devicetree/bindings/powerpc}/fsl/guts.txt (100%)
rename Documentation/{powerpc/dts-bindings =>
devicetree/bindings/powerpc}/fsl/lbc.txt (100%)
rename Documentation/{powerpc/dts-bindings =>
devicetree/bindings/powerpc}/fsl/mcm.txt (100%)
rename Documentation/{powerpc/dts-bindings =>
devicetree/bindings/powerpc}/fsl/mcu-mpc8349emitx.txt (100%)
rename Documentation/{powerpc/dts-bindings =>
devicetree/bindings/powerpc}/fsl/mpc5121-psc.txt (100%)
rename Documentation/{powerpc/dts-bindings =>
devicetree/bindings/powerpc}/fsl/mpc5200.txt (100%)
rename Documentation/{powerpc/dts-bindings =>
devicetree/bindings/powerpc}/fsl/mpic.txt (100%)
rename Documentation/{powerpc/dts-bindings =>
devicetree/bindings/powerpc}/fsl/msi-pic.txt (100%)
rename Documentation/{powerpc/dts-bindings =>
devicetree/bindings/powerpc}/fsl/pmc.txt (100%)
rename Documentation/{powerpc/dts-bindings =>
devicetree/bindings/powerpc}/fsl/sec.txt (100%)
rename Documentation/{powerpc/dts-bindings =>
devicetree/bindings/powerpc}/fsl/ssi.txt (100%)
rename Documentation/{powerpc/dts-bindings =>
devicetree/bindings/powerpc}/nintendo/gamecube.txt (100%)
rename Documentation/{powerpc/dts-bindings =>
devicetree/bindings/powerpc}/nintendo/wii.txt (100%)
rename Documentation/{powerpc/dts-bindings/fsl/spi.txt =>
devicetree/bindings/spi/fsl-spi.txt} (100%)
rename Documentation/{powerpc/dts-bindings =>
devicetree/bindings/spi}/spi-bus.txt (100%)
rename Documentation/{powerpc/dts-bindings/fsl/usb.txt =>
devicetree/bindings/usb/fsl-usb.txt} (100%)
rename Documentation/{powerpc/dts-bindings =>
devicetree/bindings/usb}/usb-ehci.txt (100%)
rename Documentation/{powerpc/dts-bindings =>
devicetree/bindings}/xilinx.txt (100%)
rename Documentation/{powerpc => devicetree}/booting-without-of.txt (90%)
--
Grant Likely, B.Sc., P.Eng.
Secret Lab Technologies Ltd.
--
Grant Likely, B.Sc., P.Eng.
Secret Lab Technologies Ltd.
^ permalink raw reply
* [PATCH v2 3/3] tcm ibmvscsis driver
From: FUJITA Tomonori @ 2011-02-14 3:48 UTC (permalink / raw)
To: linux-scsi; +Cc: brking, linuxppc-dev, nab
This is the second version of tcm ibmvscsis driver. You can find the
first version at:
http://marc.info/?t=129734085600004&r=1&w=2
The changes are:
- send VIOSRP_MAD_NOT_SUPPORTED for unknown mad type requests.
- fix inquiry typo
- sends task management response (for now, 'NOT SUPPORTED').
- remove dead code.
=
From: FUJITA Tomonori <fujita.tomonori@lab.ntt.co.jp>
Subject: [PATCH v2 3/3] tcm ibmvscsis driver
This replaces ibmvstgt driver that uses the old target framework.
Signed-off-by: FUJITA Tomonori <fujita.tomonori@lab.ntt.co.jp>
---
drivers/scsi/ibmvscsi/Makefile | 4 +-
drivers/scsi/ibmvscsi/ibmvscsis.c | 1760 +++++++++++++++++++++++++++++++++++++
2 files changed, 1763 insertions(+), 1 deletions(-)
create mode 100644 drivers/scsi/ibmvscsi/ibmvscsis.c
diff --git a/drivers/scsi/ibmvscsi/Makefile b/drivers/scsi/ibmvscsi/Makefile
index a423d96..a615ea5 100644
--- a/drivers/scsi/ibmvscsi/Makefile
+++ b/drivers/scsi/ibmvscsi/Makefile
@@ -1,8 +1,10 @@
+EXTRA_CFLAGS += -I$(srctree)/drivers/target/
+
obj-$(CONFIG_SCSI_IBMVSCSI) += ibmvscsic.o
ibmvscsic-y += ibmvscsi.o
ibmvscsic-$(CONFIG_PPC_ISERIES) += iseries_vscsi.o
ibmvscsic-$(CONFIG_PPC_PSERIES) += rpa_vscsi.o
-obj-$(CONFIG_SCSI_IBMVSCSIS) += ibmvstgt.o
+obj-$(CONFIG_SCSI_IBMVSCSIS) += ibmvscsis.o
obj-$(CONFIG_SCSI_IBMVFC) += ibmvfc.o
diff --git a/drivers/scsi/ibmvscsi/ibmvscsis.c b/drivers/scsi/ibmvscsi/ibmvscsis.c
new file mode 100644
index 0000000..591cedb
--- /dev/null
+++ b/drivers/scsi/ibmvscsi/ibmvscsis.c
@@ -0,0 +1,1760 @@
+/*
+ * IBM eServer i/pSeries Virtual SCSI Target Driver
+ * Copyright (C) 2003-2005 Dave Boutcher (boutcher@us.ibm.com) IBM Corp.
+ * Santiago Leon (santil@us.ibm.com) IBM Corp.
+ * Linda Xie (lxie@us.ibm.com) IBM Corp.
+ *
+ * Copyright (C) 2005-2011 FUJITA Tomonori <tomof@acm.org>
+ * Copyright (C) 2010 Nicholas A. Bellinger <nab@kernel.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ * USA
+ */
+#include <linux/slab.h>
+#include <linux/kthread.h>
+#include <linux/types.h>
+#include <linux/list.h>
+#include <linux/types.h>
+#include <linux/string.h>
+#include <linux/ctype.h>
+#include <linux/utsname.h>
+#include <asm/unaligned.h>
+#include <scsi/scsi.h>
+#include <scsi/scsi_host.h>
+#include <scsi/scsi_device.h>
+#include <scsi/scsi_cmnd.h>
+#include <scsi/scsi_tcq.h>
+#include <scsi/libsrp.h>
+#include <generated/utsrelease.h>
+
+#include <target/target_core_base.h>
+#include <target/target_core_transport.h>
+#include <target/target_core_fabric_ops.h>
+#include <target/target_core_fabric_lib.h>
+#include <target/target_core_fabric_configfs.h>
+#include <target/target_core_device.h>
+#include <target/target_core_tpg.h>
+#include <target/target_core_configfs.h>
+
+#include <asm/hvcall.h>
+#include <asm/iommu.h>
+#include <asm/prom.h>
+#include <asm/vio.h>
+
+#include "ibmvscsi.h"
+#include "viosrp.h"
+
+#define IBMVSCSIS_VERSION "v0.1"
+#define IBMVSCSIS_NAMELEN 32
+
+#define INITIAL_SRP_LIMIT 16
+#define DEFAULT_MAX_SECTORS 256
+
+/*
+ * Hypervisor calls.
+ */
+#define h_copy_rdma(l, sa, sb, da, db) \
+ plpar_hcall_norets(H_COPY_RDMA, l, sa, sb, da, db)
+#define h_send_crq(ua, l, h) \
+ plpar_hcall_norets(H_SEND_CRQ, ua, l, h)
+#define h_reg_crq(ua, tok, sz)\
+ plpar_hcall_norets(H_REG_CRQ, ua, tok, sz);
+#define h_free_crq(ua) \
+ plpar_hcall_norets(H_FREE_CRQ, ua);
+
+#define GETTARGET(x) ((int)((((u64)(x)) >> 56) & 0x003f))
+#define GETBUS(x) ((int)((((u64)(x)) >> 53) & 0x0007))
+#define GETLUN(x) ((int)((((u64)(x)) >> 48) & 0x001f))
+
+/*
+ * These are fixed for the system and come from the Open Firmware device tree.
+ * We just store them here to save getting them every time.
+ */
+static char system_id[64] = "";
+static char partition_name[97] = "UNKNOWN";
+static unsigned int partition_number = -1;
+
+static LIST_HEAD(tpg_list);
+static DEFINE_SPINLOCK(tpg_lock);
+
+struct ibmvscsis_adapter {
+ struct vio_dev *dma_dev;
+ struct list_head siblings;
+
+ struct crq_queue crq_queue;
+
+ struct work_struct crq_work;
+
+ unsigned long liobn;
+ unsigned long riobn;
+
+ /* todo: remove */
+ struct srp_target srpt;
+
+ /* SRP port target portal group tag for TCM */
+ unsigned long tport_tpgt;
+
+ /* Returned by ibmvscsis_make_tpg() */
+ struct se_portal_group se_tpg;
+
+ struct se_session *se_sess;
+
+
+ /* SCSI protocol the tport is providing */
+ u8 tport_proto_id;
+ /* Binary World Wide unique Port Name for SRP Target port */
+ u64 tport_wwpn;
+ /* ASCII formatted WWPN for SRP Target port */
+ char tport_name[IBMVSCSIS_NAMELEN];
+ /* Returned by ibmvscsis_make_tport() */
+ struct se_wwn tport_wwn;
+};
+
+struct ibmvscsis_cmnd {
+ /* Used for libsrp processing callbacks */
+ struct scsi_cmnd sc;
+ /* Used for TCM Core operations */
+ struct se_cmd se_cmd;
+ /* Sense buffer that will be mapped into outgoing status */
+ unsigned char sense_buf[TRANSPORT_SENSE_BUFFER];
+};
+
+static int ibmvscsis_check_true(struct se_portal_group *se_tpg)
+{
+ return 1;
+}
+
+static int ibmvscsis_check_false(struct se_portal_group *se_tpg)
+{
+ return 0;
+}
+
+static char *ibmvscsis_get_fabric_name(void)
+{
+ return "ibmvscsis";
+}
+
+static u8 ibmvscsis_get_fabric_proto_ident(struct se_portal_group *se_tpg)
+{
+ return 4;
+}
+
+static char *ibmvscsis_get_fabric_wwn(struct se_portal_group *se_tpg)
+{
+ struct ibmvscsis_adapter *adapter =
+ container_of(se_tpg, struct ibmvscsis_adapter, se_tpg);
+
+ return adapter->tport_name;
+}
+
+static u16 ibmvscsis_get_tag(struct se_portal_group *se_tpg)
+{
+ struct ibmvscsis_adapter *adapter =
+ container_of(se_tpg, struct ibmvscsis_adapter, se_tpg);
+ return adapter->tport_tpgt;
+}
+
+static u32 ibmvscsis_get_default_depth(struct se_portal_group *se_tpg)
+{
+ return 1;
+}
+
+/* we don't care about the transport id since we never use pr. */
+static u32 ibmvscsis_get_pr_transport_id(struct se_portal_group *se_tpg,
+ struct se_node_acl *se_nacl,
+ struct t10_pr_registration *pr_reg,
+ int *format_code,
+ unsigned char *buf)
+{
+ return 24;
+}
+
+static u32 ibmvscsis_get_pr_transport_id_len(struct se_portal_group *se_tpg,
+ struct se_node_acl *se_nacl,
+ struct t10_pr_registration *pr_reg,
+ int *format_code)
+{
+ return 24;
+}
+
+static char *ibmvscsis_parse_pr_out_transport_id(struct se_portal_group *se_tpg,
+ const char *buf,
+ u32 *out_tid_len,
+ char **port_nexus_ptr)
+{
+ return NULL;
+}
+
+struct ibmvscsis_nacl {
+ /* Binary World Wide unique Port Name for SRP Initiator port */
+ u64 iport_wwpn;
+ /* ASCII formatted WWPN for Sas Initiator port */
+ char iport_name[IBMVSCSIS_NAMELEN];
+ /* Returned by ibmvscsis_make_nodeacl() */
+ struct se_node_acl se_node_acl;
+};
+
+static struct se_node_acl *ibmvscsis_alloc_fabric_acl(struct se_portal_group *se_tpg)
+{
+ struct ibmvscsis_nacl *nacl;
+
+ nacl = kzalloc(sizeof(struct ibmvscsis_nacl), GFP_KERNEL);
+ if (!(nacl)) {
+ printk(KERN_ERR "Unable to alocate struct ibmvscsis_nacl\n");
+ return NULL;
+ }
+
+ return &nacl->se_node_acl;
+}
+
+static void ibmvscsis_release_fabric_acl(struct se_portal_group *se_tpg,
+ struct se_node_acl *se_nacl)
+{
+ struct ibmvscsis_nacl *nacl = container_of(se_nacl,
+ struct ibmvscsis_nacl, se_node_acl);
+ kfree(nacl);
+}
+
+static u32 ibmvscsis_tpg_get_inst_index(struct se_portal_group *se_tpg)
+{
+ return 1;
+}
+
+static void ibmvscsis_release_cmd(struct se_cmd *se_cmd)
+{
+ struct ibmvscsis_cmnd *cmd =
+ container_of(se_cmd, struct ibmvscsis_cmnd, se_cmd);
+ kfree(cmd);
+ return;
+}
+
+static int ibmvscsis_shutdown_session(struct se_session *se_sess)
+{
+ return 0;
+}
+
+static void ibmvscsis_close_session(struct se_session *se_sess)
+{
+ return;
+}
+
+static void ibmvscsis_stop_session(struct se_session *se_sess,
+ int sess_sleep , int conn_sleep)
+{
+ return;
+}
+
+static void ibmvscsis_reset_nexus(struct se_session *se_sess)
+{
+ return;
+}
+
+static int ibmvscsis_sess_logged_in(struct se_session *se_sess)
+{
+ return 0;
+}
+
+static u32 ibmvscsis_sess_get_index(struct se_session *se_sess)
+{
+ return 0;
+}
+
+static int ibmvscsis_write_pending_status(struct se_cmd *se_cmd)
+{
+ return 0;
+}
+
+static void ibmvscsis_set_default_node_attrs(struct se_node_acl *nacl)
+{
+ return;
+}
+
+static u32 ibmvscsis_get_task_tag(struct se_cmd *se_cmd)
+{
+ return 0;
+}
+
+static int ibmvscsis_get_cmd_state(struct se_cmd *se_cmd)
+{
+ return 0;
+}
+
+static void ibmvscsis_new_cmd_failure(struct se_cmd *se_cmd)
+{
+ return;
+}
+
+static int ibmvscsis_queue_tm_rsp(struct se_cmd *se_cmd)
+{
+ return 0;
+}
+
+static u16 ibmvscsis_set_fabric_sense_len(struct se_cmd *se_cmd,
+ u32 sense_length)
+{
+ return 0;
+}
+
+static u16 ibmvscsis_get_fabric_sense_len(void)
+{
+ return 0;
+}
+
+static int ibmvscsis_is_state_remove(struct se_cmd *se_cmd)
+{
+ return 0;
+}
+
+static u64 make_lun(unsigned int bus, unsigned int target, unsigned int lun);
+
+static u64 ibmvscsis_pack_lun(unsigned int lun)
+{
+ return make_lun(0, lun & 0x003f, 0);
+}
+
+/* Local pointer to allocated TCM configfs fabric module */
+static struct target_fabric_configfs *ibmvscsis_fabric_configfs;
+
+static struct se_portal_group *ibmvscsis_make_tpg(struct se_wwn *wwn,
+ struct config_group *group,
+ const char *name)
+{
+ struct ibmvscsis_adapter *adapter =
+ container_of(wwn, struct ibmvscsis_adapter, tport_wwn);
+ struct se_node_acl *acl;
+ int ret;
+ char *dname = (char *)dev_name(&adapter->dma_dev->dev);
+
+ if (strncmp(name, "tpgt_1", 6))
+ return ERR_PTR(-EINVAL);
+
+ ret = core_tpg_register(&ibmvscsis_fabric_configfs->tf_ops, wwn,
+ &adapter->se_tpg, (void *)adapter,
+ TRANSPORT_TPG_TYPE_NORMAL);
+ if (ret)
+ return ERR_PTR(-ENOMEM);
+
+ adapter->se_sess = transport_init_session();
+ if (!adapter->se_sess) {
+ core_tpg_deregister(&adapter->se_tpg);
+ return ERR_PTR(-ENOMEM);
+ }
+
+ acl = core_tpg_check_initiator_node_acl(&adapter->se_tpg, dname);
+ if (!acl) {
+ transport_free_session(adapter->se_sess);
+ adapter->se_sess = NULL;
+ return ERR_PTR(-ENOMEM);
+ }
+ adapter->se_sess->se_node_acl = acl;
+
+ transport_register_session(&adapter->se_tpg,
+ adapter->se_sess->se_node_acl,
+ adapter->se_sess, adapter);
+
+ return &adapter->se_tpg;
+}
+
+static void ibmvscsis_drop_tpg(struct se_portal_group *se_tpg)
+{
+ struct ibmvscsis_adapter *adapter =
+ container_of(se_tpg, struct ibmvscsis_adapter, se_tpg);
+ unsigned long flags;
+
+
+ transport_deregister_session_configfs(adapter->se_sess);
+ transport_free_session(adapter->se_sess);
+ core_tpg_deregister(se_tpg);
+
+ spin_lock_irqsave(&tpg_lock, flags);
+ adapter->se_sess = NULL;
+ spin_unlock_irqrestore(&tpg_lock, flags);
+}
+
+static struct se_wwn *ibmvscsis_make_tport(struct target_fabric_configfs *tf,
+ struct config_group *group,
+ const char *name)
+{
+ struct ibmvscsis_adapter *adapter;
+ unsigned long tpgt, flags;
+
+ if (strict_strtoul(name, 10, &tpgt))
+ return NULL;
+
+ spin_lock_irqsave(&tpg_lock, flags);
+ list_for_each_entry(adapter, &tpg_list, siblings) {
+ if (tpgt == adapter->tport_tpgt)
+ goto found;
+ }
+
+ spin_unlock_irqrestore(&tpg_lock, flags);
+ return NULL;
+found:
+ spin_unlock_irqrestore(&tpg_lock, flags);
+
+ return &adapter->tport_wwn;
+}
+
+static void ibmvscsis_drop_tport(struct se_wwn *wwn)
+{
+}
+
+static ssize_t ibmvscsis_wwn_show_attr_version(struct target_fabric_configfs *tf,
+ char *page)
+{
+ return sprintf(page, "IBMVSCSIS fabric module %s on %s/%s"
+ "on "UTS_RELEASE"\n", IBMVSCSIS_VERSION, utsname()->sysname,
+ utsname()->machine);
+}
+
+TF_WWN_ATTR_RO(ibmvscsis, version);
+
+static struct configfs_attribute *ibmvscsis_wwn_attrs[] = {
+ &ibmvscsis_wwn_version.attr,
+ NULL,
+};
+
+static int ibmvscsis_write_pending(struct se_cmd *se_cmd);
+static int ibmvscsis_queue_data_in(struct se_cmd *se_cmd);
+static int ibmvscsis_queue_status(struct se_cmd *se_cmd);
+static int ibmvscsis_new_cmd_map(struct se_cmd *se_cmd);
+static void ibmvscsis_check_stop_free(struct se_cmd *se_cmd);
+
+static struct target_core_fabric_ops ibmvscsis_ops = {
+ .task_sg_chaining = 1,
+ .get_fabric_name = ibmvscsis_get_fabric_name,
+ .get_fabric_proto_ident = ibmvscsis_get_fabric_proto_ident,
+ .tpg_get_wwn = ibmvscsis_get_fabric_wwn,
+ .tpg_get_tag = ibmvscsis_get_tag,
+ .tpg_get_default_depth = ibmvscsis_get_default_depth,
+ .tpg_get_pr_transport_id = ibmvscsis_get_pr_transport_id,
+ .tpg_get_pr_transport_id_len = ibmvscsis_get_pr_transport_id_len,
+ .tpg_parse_pr_out_transport_id = ibmvscsis_parse_pr_out_transport_id,
+ .tpg_check_demo_mode = ibmvscsis_check_true,
+ .tpg_check_demo_mode_cache = ibmvscsis_check_true,
+ .tpg_check_demo_mode_write_protect = ibmvscsis_check_false,
+ .tpg_check_prod_mode_write_protect = ibmvscsis_check_false,
+ .tpg_alloc_fabric_acl = ibmvscsis_alloc_fabric_acl,
+ .tpg_release_fabric_acl = ibmvscsis_release_fabric_acl,
+ .tpg_get_inst_index = ibmvscsis_tpg_get_inst_index,
+ .new_cmd_map = ibmvscsis_new_cmd_map,
+ .check_stop_free = ibmvscsis_check_stop_free,
+ .release_cmd_to_pool = ibmvscsis_release_cmd,
+ .release_cmd_direct = ibmvscsis_release_cmd,
+ .shutdown_session = ibmvscsis_shutdown_session,
+ .close_session = ibmvscsis_close_session,
+ .stop_session = ibmvscsis_stop_session,
+ .fall_back_to_erl0 = ibmvscsis_reset_nexus,
+ .sess_logged_in = ibmvscsis_sess_logged_in,
+ .sess_get_index = ibmvscsis_sess_get_index,
+ .sess_get_initiator_sid = NULL,
+ .write_pending = ibmvscsis_write_pending,
+ .write_pending_status = ibmvscsis_write_pending_status,
+ .set_default_node_attributes = ibmvscsis_set_default_node_attrs,
+ .get_task_tag = ibmvscsis_get_task_tag,
+ .get_cmd_state = ibmvscsis_get_cmd_state,
+ .new_cmd_failure = ibmvscsis_new_cmd_failure,
+ .queue_data_in = ibmvscsis_queue_data_in,
+ .queue_status = ibmvscsis_queue_status,
+ .queue_tm_rsp = ibmvscsis_queue_tm_rsp,
+ .get_fabric_sense_len = ibmvscsis_get_fabric_sense_len,
+ .set_fabric_sense_len = ibmvscsis_set_fabric_sense_len,
+ .is_state_remove = ibmvscsis_is_state_remove,
+ .pack_lun = ibmvscsis_pack_lun,
+ .fabric_make_wwn = ibmvscsis_make_tport,
+ .fabric_drop_wwn = ibmvscsis_drop_tport,
+ .fabric_make_tpg = ibmvscsis_make_tpg,
+ .fabric_drop_tpg = ibmvscsis_drop_tpg,
+ .fabric_post_link = NULL,
+ .fabric_pre_unlink = NULL,
+ .fabric_make_np = NULL,
+ .fabric_drop_np = NULL,
+ .fabric_make_nodeacl = NULL,
+ .fabric_drop_nodeacl = NULL,
+};
+
+static inline union viosrp_iu *vio_iu(struct iu_entry *iue)
+{
+ return (union viosrp_iu *)(iue->sbuf->buf);
+}
+
+static int send_iu(struct iu_entry *iue, u64 length, u8 format)
+{
+ struct srp_target *target = iue->target;
+ struct ibmvscsis_adapter *adapter = target->ldata;
+ long rc, rc1;
+ union {
+ struct viosrp_crq cooked;
+ u64 raw[2];
+ } crq;
+
+ /* First copy the SRP */
+ rc = h_copy_rdma(length, adapter->liobn, iue->sbuf->dma,
+ adapter->riobn, iue->remote_token);
+
+ if (rc)
+ printk(KERN_ERR "Error %ld transferring data\n", rc);
+
+ crq.cooked.valid = 0x80;
+ crq.cooked.format = format;
+ crq.cooked.reserved = 0x00;
+ crq.cooked.timeout = 0x00;
+ crq.cooked.IU_length = length;
+ crq.cooked.IU_data_ptr = vio_iu(iue)->srp.rsp.tag;
+
+ if (rc == 0)
+ crq.cooked.status = 0x99; /* Just needs to be non-zero */
+ else
+ crq.cooked.status = 0x00;
+
+ rc1 = h_send_crq(adapter->dma_dev->unit_address, crq.raw[0],
+ crq.raw[1]);
+ if (rc1) {
+ printk(KERN_ERR "%ld sending response\n", rc1);
+ return rc1;
+ }
+
+ return rc;
+}
+
+#define SRP_RSP_SENSE_DATA_LEN 18
+
+static int send_rsp(struct iu_entry *iue, struct scsi_cmnd *sc,
+ unsigned char status, unsigned char asc)
+{
+ union viosrp_iu *iu = vio_iu(iue);
+ uint64_t tag = iu->srp.rsp.tag;
+
+ /* If the linked bit is on and status is good */
+ if (test_bit(V_LINKED, &iue->flags) && (status == NO_SENSE))
+ status = 0x10;
+
+ memset(iu, 0, sizeof(struct srp_rsp));
+ iu->srp.rsp.opcode = SRP_RSP;
+ iu->srp.rsp.req_lim_delta = 1;
+ iu->srp.rsp.tag = tag;
+
+ if (test_bit(V_DIOVER, &iue->flags))
+ iu->srp.rsp.flags |= SRP_RSP_FLAG_DIOVER;
+
+ iu->srp.rsp.data_in_res_cnt = 0;
+ iu->srp.rsp.data_out_res_cnt = 0;
+
+ iu->srp.rsp.flags &= ~SRP_RSP_FLAG_RSPVALID;
+
+ iu->srp.rsp.resp_data_len = 0;
+ iu->srp.rsp.status = status;
+ if (status) {
+ uint8_t *sense = iu->srp.rsp.data;
+
+ if (sc) {
+ iu->srp.rsp.flags |= SRP_RSP_FLAG_SNSVALID;
+ iu->srp.rsp.sense_data_len = SCSI_SENSE_BUFFERSIZE;
+ memcpy(sense, sc->sense_buffer, SCSI_SENSE_BUFFERSIZE);
+ } else {
+ iu->srp.rsp.status = SAM_STAT_CHECK_CONDITION;
+ iu->srp.rsp.flags |= SRP_RSP_FLAG_SNSVALID;
+ iu->srp.rsp.sense_data_len = SRP_RSP_SENSE_DATA_LEN;
+
+ /* Valid bit and 'current errors' */
+ sense[0] = (0x1 << 7 | 0x70);
+ /* Sense key */
+ sense[2] = status;
+ /* Additional sense length */
+ sense[7] = 0xa; /* 10 bytes */
+ /* Additional sense code */
+ sense[12] = asc;
+ }
+ }
+
+ send_iu(iue, sizeof(iu->srp.rsp) + SRP_RSP_SENSE_DATA_LEN,
+ VIOSRP_SRP_FORMAT);
+
+ return 0;
+}
+
+static int send_adapter_info(struct iu_entry *iue,
+ dma_addr_t remote_buffer, u16 length)
+{
+ struct srp_target *target = iue->target;
+ struct ibmvscsis_adapter *adapter = target->ldata;
+ dma_addr_t data_token;
+ struct mad_adapter_info_data *info;
+ int err;
+
+ info = dma_alloc_coherent(&adapter->dma_dev->dev, sizeof(*info),
+ &data_token, GFP_KERNEL);
+ if (!info) {
+ printk(KERN_ERR "bad dma_alloc_coherent %p\n", target);
+ return 1;
+ }
+
+ /* Get remote info */
+ err = h_copy_rdma(sizeof(*info), adapter->riobn, remote_buffer,
+ adapter->liobn, data_token);
+ if (err == H_SUCCESS) {
+ printk(KERN_INFO "Client connect: %s (%d)\n",
+ info->partition_name, info->partition_number);
+ }
+
+ memset(info, 0, sizeof(*info));
+
+ strcpy(info->srp_version, "16.a");
+ strncpy(info->partition_name, partition_name,
+ sizeof(info->partition_name));
+ info->partition_number = partition_number;
+ info->mad_version = 1;
+ info->os_type = 2;
+ info->port_max_txu[0] = DEFAULT_MAX_SECTORS << 9;
+
+ /* Send our info to remote */
+ err = h_copy_rdma(sizeof(*info), adapter->liobn, data_token,
+ adapter->riobn, remote_buffer);
+
+ dma_free_coherent(&adapter->dma_dev->dev, sizeof(*info), info,
+ data_token);
+ if (err != H_SUCCESS) {
+ printk(KERN_INFO "Error sending adapter info %d\n", err);
+ return 1;
+ }
+
+ return 0;
+}
+
+static int process_mad_iu(struct iu_entry *iue)
+{
+ union viosrp_iu *iu = vio_iu(iue);
+ struct viosrp_adapter_info *info;
+ struct viosrp_host_config *conf;
+
+ switch (iu->mad.empty_iu.common.type) {
+ case VIOSRP_EMPTY_IU_TYPE:
+ printk(KERN_ERR "%s\n", "Unsupported EMPTY MAD IU");
+ break;
+ case VIOSRP_ERROR_LOG_TYPE:
+ printk(KERN_ERR "%s\n", "Unsupported ERROR LOG MAD IU");
+ iu->mad.error_log.common.status = 1;
+ send_iu(iue, sizeof(iu->mad.error_log), VIOSRP_MAD_FORMAT);
+ break;
+ case VIOSRP_ADAPTER_INFO_TYPE:
+ info = &iu->mad.adapter_info;
+ info->common.status = send_adapter_info(iue, info->buffer,
+ info->common.length);
+ send_iu(iue, sizeof(*info), VIOSRP_MAD_FORMAT);
+ break;
+ case VIOSRP_HOST_CONFIG_TYPE:
+ conf = &iu->mad.host_config;
+ conf->common.status = 1;
+ send_iu(iue, sizeof(*conf), VIOSRP_MAD_FORMAT);
+ break;
+ default:
+ printk(KERN_ERR "Unknown type %u\n", iu->srp.rsp.opcode);
+ iu->mad.empty_iu.common.status = VIOSRP_MAD_NOT_SUPPORTED;
+ send_iu(iue, sizeof(iu->mad), VIOSRP_MAD_FORMAT);
+ break;
+ }
+
+ return 1;
+}
+
+static void process_login(struct iu_entry *iue)
+{
+ union viosrp_iu *iu = vio_iu(iue);
+ struct srp_login_rsp *rsp = &iu->srp.login_rsp;
+ u64 tag = iu->srp.rsp.tag;
+
+ /*
+ * TODO handle case that requested size is wrong and buffer
+ * format is wrong
+ */
+ memset(iu, 0, sizeof(struct srp_login_rsp));
+ rsp->opcode = SRP_LOGIN_RSP;
+ rsp->req_lim_delta = INITIAL_SRP_LIMIT;
+ rsp->tag = tag;
+ rsp->max_it_iu_len = sizeof(union srp_iu);
+ rsp->max_ti_iu_len = sizeof(union srp_iu);
+ /* direct and indirect */
+ rsp->buf_fmt = SRP_BUF_FORMAT_DIRECT | SRP_BUF_FORMAT_INDIRECT;
+
+ send_iu(iue, sizeof(*rsp), VIOSRP_SRP_FORMAT);
+}
+
+static void process_tsk_mgmt(struct iu_entry *iue)
+{
+ union viosrp_iu *iu = vio_iu(iue);
+ uint64_t tag = iu->srp.rsp.tag;
+ uint8_t *resp_data = iu->srp.rsp.data;
+
+ memset(iu, 0, sizeof(struct srp_rsp));
+ iu->srp.rsp.opcode = SRP_RSP;
+ iu->srp.rsp.req_lim_delta = 1;
+ iu->srp.rsp.tag = tag;
+
+ iu->srp.rsp.data_in_res_cnt = 0;
+ iu->srp.rsp.data_out_res_cnt = 0;
+
+ iu->srp.rsp.flags &= ~SRP_RSP_FLAG_RSPVALID;
+
+ iu->srp.rsp.resp_data_len = 4;
+ /* TASK MANAGEMENT FUNCTION NOT SUPPORTED for now */
+ resp_data[3] = 4;
+
+ send_iu(iue, sizeof(iu->srp.rsp) + iu->srp.rsp.resp_data_len,
+ VIOSRP_SRP_FORMAT);
+}
+
+static int process_srp_iu(struct iu_entry *iue)
+{
+ union viosrp_iu *iu = vio_iu(iue);
+ struct srp_target *target = iue->target;
+ int done = 1;
+ u8 opcode = iu->srp.rsp.opcode;
+ unsigned long flags;
+
+ switch (opcode) {
+ case SRP_LOGIN_REQ:
+ process_login(iue);
+ break;
+ case SRP_TSK_MGMT:
+ process_tsk_mgmt(iue);
+ break;
+ case SRP_CMD:
+ spin_lock_irqsave(&target->lock, flags);
+ list_add_tail(&iue->ilist, &target->cmd_queue);
+ spin_unlock_irqrestore(&target->lock, flags);
+ done = 0;
+ break;
+ case SRP_LOGIN_RSP:
+ case SRP_I_LOGOUT:
+ case SRP_T_LOGOUT:
+ case SRP_RSP:
+ case SRP_CRED_REQ:
+ case SRP_CRED_RSP:
+ case SRP_AER_REQ:
+ case SRP_AER_RSP:
+ printk(KERN_ERR "Unsupported type %u\n", opcode);
+ break;
+ default:
+ printk(KERN_ERR "Unknown type %u\n", opcode);
+ }
+
+ return done;
+}
+
+static void process_iu(struct viosrp_crq *crq,
+ struct ibmvscsis_adapter *adapter)
+{
+ struct iu_entry *iue;
+ long err;
+ int done = 1;
+
+ iue = srp_iu_get(&adapter->srpt);
+ if (!iue) {
+ printk(KERN_ERR "Error getting IU from pool\n");
+ return;
+ }
+
+ iue->remote_token = crq->IU_data_ptr;
+
+ err = h_copy_rdma(crq->IU_length, adapter->riobn,
+ iue->remote_token, adapter->liobn, iue->sbuf->dma);
+
+ if (err != H_SUCCESS) {
+ printk(KERN_ERR "%ld transferring data error %p\n", err, iue);
+ goto out;
+ }
+
+ if (crq->format == VIOSRP_MAD_FORMAT)
+ done = process_mad_iu(iue);
+ else
+ done = process_srp_iu(iue);
+out:
+ if (done)
+ srp_iu_put(iue);
+}
+
+static void process_crq(struct viosrp_crq *crq,
+ struct ibmvscsis_adapter *adapter)
+{
+ switch (crq->valid) {
+ case 0xC0:
+ /* initialization */
+ switch (crq->format) {
+ case 0x01:
+ h_send_crq(adapter->dma_dev->unit_address,
+ 0xC002000000000000, 0);
+ break;
+ case 0x02:
+ break;
+ default:
+ printk(KERN_ERR "Unknown format %u\n", crq->format);
+ }
+ break;
+ case 0xFF:
+ /* transport event */
+ break;
+ case 0x80:
+ /* real payload */
+ switch (crq->format) {
+ case VIOSRP_SRP_FORMAT:
+ case VIOSRP_MAD_FORMAT:
+ process_iu(crq, adapter);
+ break;
+ case VIOSRP_OS400_FORMAT:
+ case VIOSRP_AIX_FORMAT:
+ case VIOSRP_LINUX_FORMAT:
+ case VIOSRP_INLINE_FORMAT:
+ printk(KERN_ERR "Unsupported format %u\n", crq->format);
+ break;
+ default:
+ printk(KERN_ERR "Unknown format %u\n", crq->format);
+ }
+ break;
+ default:
+ printk(KERN_ERR "unknown message type 0x%02x!?\n", crq->valid);
+ }
+}
+
+static inline struct viosrp_crq *next_crq(struct crq_queue *queue)
+{
+ struct viosrp_crq *crq;
+ unsigned long flags;
+
+ spin_lock_irqsave(&queue->lock, flags);
+ crq = &queue->msgs[queue->cur];
+ if (crq->valid & 0x80) {
+ if (++queue->cur == queue->size)
+ queue->cur = 0;
+ } else
+ crq = NULL;
+ spin_unlock_irqrestore(&queue->lock, flags);
+
+ return crq;
+}
+
+static int tcm_queuecommand(struct ibmvscsis_adapter *adapter,
+ struct ibmvscsis_cmnd *vsc,
+ struct srp_cmd *cmd)
+{
+ struct se_cmd *se_cmd;
+ int attr;
+ int data_len;
+ int ret;
+
+ switch (cmd->task_attr) {
+ case SRP_SIMPLE_TASK:
+ attr = MSG_SIMPLE_TAG;
+ break;
+ case SRP_ORDERED_TASK:
+ attr = MSG_ORDERED_TAG;
+ break;
+ case SRP_HEAD_TASK:
+ attr = MSG_HEAD_TAG;
+ break;
+ default:
+ printk(KERN_WARNING "Task attribute %d not supported\n",
+ cmd->task_attr);
+ attr = MSG_SIMPLE_TAG;
+ }
+
+ data_len = srp_data_length(cmd, srp_cmd_direction(cmd));
+
+ se_cmd = &vsc->se_cmd;
+
+ transport_init_se_cmd(se_cmd,
+ adapter->se_tpg.se_tpg_tfo,
+ adapter->se_sess, data_len,
+ srp_cmd_direction(cmd),
+ attr, vsc->sense_buf);
+
+ ret = transport_get_lun_for_cmd(se_cmd, NULL, cmd->lun);
+ if (ret) {
+ printk(KERN_ERR "invalid lun %u\n", GETLUN(cmd->lun));
+ transport_send_check_condition_and_sense(se_cmd,
+ se_cmd->scsi_sense_reason,
+ 0);
+ return ret;
+ }
+
+ transport_device_setup_cmd(se_cmd);
+ transport_generic_handle_cdb_map(se_cmd);
+
+ return 0;
+}
+
+static int ibmvscsis_new_cmd_map(struct se_cmd *se_cmd)
+{
+ struct ibmvscsis_cmnd *cmd =
+ container_of(se_cmd, struct ibmvscsis_cmnd, se_cmd);
+ struct scsi_cmnd *sc = &cmd->sc;
+ struct iu_entry *iue = (struct iu_entry *)sc->SCp.ptr;
+ struct srp_cmd *scmd = iue->sbuf->buf;
+ int ret;
+
+ /*
+ * Allocate the necessary tasks to complete the received CDB+data
+ */
+ ret = transport_generic_allocate_tasks(se_cmd, scmd->cdb);
+ if (ret == -1) {
+ /* Out of Resources */
+ return PYX_TRANSPORT_LU_COMM_FAILURE;
+ } else if (ret == -2) {
+ /*
+ * Handle case for SAM_STAT_RESERVATION_CONFLICT
+ */
+ if (se_cmd->se_cmd_flags & SCF_SCSI_RESERVATION_CONFLICT)
+ return PYX_TRANSPORT_RESERVATION_CONFLICT;
+ /*
+ * Otherwise, return SAM_STAT_CHECK_CONDITION and return
+ * sense data
+ */
+ return PYX_TRANSPORT_USE_SENSE_REASON;
+ }
+
+ return 0;
+}
+
+static void ibmvscsis_check_stop_free(struct se_cmd *se_cmd)
+{
+ if (se_cmd->se_tmr_req)
+ return;
+ transport_generic_free_cmd(se_cmd, 0, 1, 0);
+}
+
+static u64 scsi_lun_to_int(u64 lun)
+{
+ if (GETBUS(lun) || GETLUN(lun))
+ return ~0UL;
+ else
+ return GETTARGET(lun);
+}
+
+struct inquiry_data {
+ u8 qual_type;
+ u8 rmb_reserve;
+ u8 version;
+ u8 aerc_naca_hisup_format;
+ u8 addl_len;
+ u8 sccs_reserved;
+ u8 bque_encserv_vs_multip_mchngr_reserved;
+ u8 reladr_reserved_linked_cmdqueue_vs;
+ char vendor[8];
+ char product[16];
+ char revision[4];
+ char vendor_specific[20];
+ char reserved1[2];
+ char version_descriptor[16];
+ char reserved2[22];
+ char unique[158];
+};
+
+static u64 make_lun(unsigned int bus, unsigned int target, unsigned int lun)
+{
+ u16 result = (0x8000 |
+ ((target & 0x003f) << 8) |
+ ((bus & 0x0007) << 5) |
+ (lun & 0x001f));
+ return ((u64) result) << 48;
+}
+
+static int ibmvscsis_inquiry(struct ibmvscsis_adapter *adapter,
+ struct srp_cmd *cmd, char *data)
+{
+ struct se_portal_group *se_tpg = &adapter->se_tpg;
+ struct inquiry_data *id = (struct inquiry_data *)data;
+ u64 unpacked_lun, lun = cmd->lun;
+ u8 *cdb = cmd->cdb;
+ int len;
+
+ if (!data)
+ printk(KERN_INFO "%s %d: oomu\n", __func__, __LINE__);
+
+ if (((cdb[1] & 0x3) == 0x3) || (!(cdb[1] & 0x3) && cdb[2])) {
+ printk(KERN_INFO "%s %d: invalid req\n", __func__, __LINE__);
+ return 0;
+ }
+
+ if (cdb[1] & 0x3)
+ printk(KERN_INFO "%s %d: needs the normal path\n",
+ __func__, __LINE__);
+ else {
+ id->qual_type = TYPE_DISK;
+ id->rmb_reserve = 0x00;
+ id->version = 0x84; /* ISO/IE */
+ id->aerc_naca_hisup_format = 0x22; /* naca & fmt 0x02 */
+ id->addl_len = sizeof(*id) - 4;
+ id->bque_encserv_vs_multip_mchngr_reserved = 0x00;
+ id->reladr_reserved_linked_cmdqueue_vs = 0x02; /* CMDQ */
+ memcpy(id->vendor, "IBM ", 8);
+ /*
+ * Don't even ask about the next bit. AIX uses
+ * hardcoded device naming to recognize device types
+ * and their client won't work unless we use VOPTA and
+ * VDASD.
+ */
+ if (id->qual_type == TYPE_ROM)
+ memcpy(id->product, "VOPTA blkdev ", 16);
+ else
+ memcpy(id->product, "VDASD blkdev ", 16);
+
+ memcpy(id->revision, "0001", 4);
+
+ snprintf(id->unique, sizeof(id->unique),
+ "IBM-VSCSI-%s-P%d-%x-%d-%d-%d\n",
+ system_id,
+ partition_number,
+ adapter->dma_dev->unit_address,
+ GETBUS(lun),
+ GETTARGET(lun),
+ GETLUN(lun));
+ }
+
+ len = min_t(int, sizeof(*id), cdb[4]);
+
+ unpacked_lun = scsi_lun_to_int(cmd->lun);
+
+ spin_lock(&se_tpg->tpg_lun_lock);
+
+ if (unpacked_lun < TRANSPORT_MAX_LUNS_PER_TPG &&
+ se_tpg->tpg_lun_list[unpacked_lun].lun_status ==
+ TRANSPORT_LUN_STATUS_ACTIVE)
+ ;
+ else
+ data[0] = TYPE_NO_LUN;
+
+ spin_unlock(&se_tpg->tpg_lun_lock);
+
+ return len;
+}
+
+static int ibmvscsis_mode_sense(struct ibmvscsis_adapter *adapter,
+ struct srp_cmd *cmd, char *mode)
+{
+ int bytes;
+ struct se_portal_group *se_tpg = &adapter->se_tpg;
+ u64 unpacked_lun;
+ struct se_lun *lun;
+ u32 blocks;
+
+ unpacked_lun = scsi_lun_to_int(cmd->lun);
+
+ spin_lock(&se_tpg->tpg_lun_lock);
+
+ lun = &se_tpg->tpg_lun_list[unpacked_lun];
+
+ blocks = TRANSPORT(lun->lun_se_dev)->get_blocks(lun->lun_se_dev);
+
+ spin_unlock(&se_tpg->tpg_lun_lock);
+
+ switch (cmd->cdb[2]) {
+ case 0:
+ case 0x3f:
+ mode[1] = 0x00; /* Default medium */
+ /* if (iue->req.vd->b.ro) */
+ if (0)
+ mode[2] = 0x80; /* device specific */
+ else
+ mode[2] = 0x00; /* device specific */
+
+ /* note the DPOFUA bit is set to zero! */
+ mode[3] = 0x08; /* block descriptor length */
+ *((u32 *) &mode[4]) = blocks - 1;
+ *((u32 *) &mode[8]) = 512;
+ bytes = mode[0] = 12; /* length */
+ break;
+
+ case 0x08: /* Cache page */
+ mode[1] = 0x00; /* Default medium */
+ if (0)
+ mode[2] = 0x80; /* device specific */
+ else
+ mode[2] = 0x00; /* device specific */
+
+ /* note the DPOFUA bit is set to zero! */
+ mode[3] = 0x08; /* block descriptor length */
+ *((u32 *) &mode[4]) = blocks - 1;
+ *((u32 *) &mode[8]) = 512;
+
+ /* Cache page */
+ mode[12] = 0x08; /* page */
+ mode[13] = 0x12; /* page length */
+ mode[14] = 0x01; /* no cache (0x04 for read/write cache) */
+
+ bytes = mode[0] = 12 + mode[13]; /* length */
+ break;
+ }
+
+ return bytes;
+}
+
+static int ibmvscsis_report_luns(struct ibmvscsis_adapter *adapter,
+ struct srp_cmd *cmd, u64 *data)
+{
+ u64 lun;
+ struct se_portal_group *se_tpg = &adapter->se_tpg;
+ int i, idx;
+ int alen, oalen, nr_luns, rbuflen = 4096;
+
+ alen = get_unaligned_be32(&cmd->cdb[6]);
+
+ alen &= ~(8 - 1);
+ oalen = alen;
+
+ if (cmd->lun) {
+ nr_luns = 1;
+ goto done;
+ }
+
+ alen -= 8;
+ rbuflen -= 8; /* FIXME */
+ idx = 2;
+ nr_luns = 1;
+
+ spin_lock(&se_tpg->tpg_lun_lock);
+ for (i = 0; i < 255; i++) {
+ if (se_tpg->tpg_lun_list[i].lun_status !=
+ TRANSPORT_LUN_STATUS_ACTIVE)
+ continue;
+
+ lun = make_lun(0, i & 0x003f, 0);
+ data[idx++] = cpu_to_be64(lun);
+ alen -= 8;
+ if (!alen)
+ break;
+ rbuflen -= 8;
+ if (!rbuflen)
+ break;
+
+ nr_luns++;
+ }
+ spin_unlock(&se_tpg->tpg_lun_lock);
+done:
+ put_unaligned_be32(nr_luns * 8, data);
+ return min(oalen, nr_luns * 8 + 8);
+}
+
+static int ibmvscsis_rdma(struct scsi_cmnd *sc, struct scatterlist *sg, int nsg,
+ struct srp_direct_buf *md, int nmd,
+ enum dma_data_direction dir, unsigned int rest)
+{
+ struct iu_entry *iue = (struct iu_entry *) sc->SCp.ptr;
+ struct srp_target *target = iue->target;
+ struct ibmvscsis_adapter *adapter = target->ldata;
+ dma_addr_t token;
+ long err;
+ unsigned int done = 0;
+ int i, sidx, soff;
+
+ sidx = soff = 0;
+ token = sg_dma_address(sg + sidx);
+
+ for (i = 0; i < nmd && rest; i++) {
+ unsigned int mdone, mlen;
+
+ mlen = min(rest, md[i].len);
+ for (mdone = 0; mlen;) {
+ int slen = min(sg_dma_len(sg + sidx) - soff, mlen);
+
+ if (dir == DMA_TO_DEVICE)
+ err = h_copy_rdma(slen,
+ adapter->riobn,
+ md[i].va + mdone,
+ adapter->liobn,
+ token + soff);
+ else
+ err = h_copy_rdma(slen,
+ adapter->liobn,
+ token + soff,
+ adapter->riobn,
+ md[i].va + mdone);
+
+ if (err != H_SUCCESS) {
+ printk(KERN_ERR "rdma error %d %d %ld\n",
+ dir, slen, err);
+ return -EIO;
+ }
+
+ mlen -= slen;
+ mdone += slen;
+ soff += slen;
+ done += slen;
+
+ if (soff == sg_dma_len(sg + sidx)) {
+ sidx++;
+ soff = 0;
+ token = sg_dma_address(sg + sidx);
+
+ if (sidx > nsg) {
+ printk(KERN_ERR "out of sg %p %d %d\n",
+ iue, sidx, nsg);
+ return -EIO;
+ }
+ }
+ };
+
+ rest -= mlen;
+ }
+ return 0;
+}
+
+static int ibmvscsis_cmd_done(struct scsi_cmnd *sc)
+{
+ unsigned long flags;
+ struct iu_entry *iue = (struct iu_entry *) sc->SCp.ptr;
+ struct srp_target *target = iue->target;
+ int err = 0;
+
+ if (scsi_sg_count(sc))
+ err = srp_transfer_data(sc, &vio_iu(iue)->srp.cmd,
+ ibmvscsis_rdma, 1, 1);
+
+ spin_lock_irqsave(&target->lock, flags);
+ list_del(&iue->ilist);
+ spin_unlock_irqrestore(&target->lock, flags);
+
+ if (err || sc->result != SAM_STAT_GOOD) {
+ printk(KERN_ERR "operation failed %p %d %x\n",
+ iue, sc->result, vio_iu(iue)->srp.cmd.cdb[0]);
+ send_rsp(iue, sc, HARDWARE_ERROR, 0x00);
+ } else
+ send_rsp(iue, sc, NO_SENSE, 0x00);
+
+ /* done(sc); */
+ srp_iu_put(iue);
+ return 0;
+}
+
+struct ibmvscsis_cmd {
+ /* Used for libsrp processing callbacks */
+ struct scsi_cmnd sc;
+ /* Used for TCM Core operations */
+ struct se_cmd se_cmd;
+ /* Sense buffer that will be mapped into outgoing status */
+ unsigned char sense_buf[TRANSPORT_SENSE_BUFFER];
+};
+
+static int ibmvscsis_write_pending(struct se_cmd *se_cmd)
+{
+ struct ibmvscsis_cmnd *cmd = container_of(se_cmd,
+ struct ibmvscsis_cmnd, se_cmd);
+ struct scsi_cmnd *sc = &cmd->sc;
+ struct iu_entry *iue = (struct iu_entry *) sc->SCp.ptr;
+ int ret;
+
+ sc->sdb.length = se_cmd->data_length;
+
+ if ((se_cmd->se_cmd_flags & SCF_SCSI_DATA_SG_IO_CDB) ||
+ (se_cmd->se_cmd_flags & SCF_SCSI_CONTROL_SG_IO_CDB)) {
+ transport_do_task_sg_chain(se_cmd);
+
+ sc->sdb.table.nents = T_TASK(se_cmd)->t_tasks_sg_chained_no;
+ sc->sdb.table.sgl = T_TASK(se_cmd)->t_tasks_sg_chained;
+ } else if (se_cmd->se_cmd_flags & SCF_SCSI_CONTROL_NONSG_IO_CDB) {
+ /*
+ * Use T_TASK(se_cmd)->t_tasks_sg_bounce for control CDBs
+ * using a contigious buffer
+ */
+ sg_init_table(&T_TASK(se_cmd)->t_tasks_sg_bounce, 1);
+ sg_set_buf(&T_TASK(se_cmd)->t_tasks_sg_bounce,
+ T_TASK(se_cmd)->t_task_buf, se_cmd->data_length);
+
+ sc->sdb.table.nents = 1;
+ sc->sdb.table.sgl = &T_TASK(se_cmd)->t_tasks_sg_bounce;
+ }
+
+ ret = srp_transfer_data(sc, &vio_iu(iue)->srp.cmd,
+ ibmvscsis_rdma, 1, 1);
+ if (ret) {
+ printk(KERN_ERR "srp_transfer_data() failed: %d\n", ret);
+ return PYX_TRANSPORT_LU_COMM_FAILURE;
+ }
+ /*
+ * We now tell TCM to add this WRITE CDB directly into the TCM storage
+ * object execution queue.
+ */
+ transport_generic_process_write(se_cmd);
+ return 0;
+}
+
+static int ibmvscsis_queue_data_in(struct se_cmd *se_cmd)
+{
+ struct ibmvscsis_cmnd *cmd = container_of(se_cmd,
+ struct ibmvscsis_cmnd, se_cmd);
+ struct scsi_cmnd *sc = &cmd->sc;
+ /*
+ * Check for overflow residual count
+ */
+ if (se_cmd->se_cmd_flags & SCF_OVERFLOW_BIT)
+ scsi_set_resid(sc, se_cmd->residual_count);
+
+ sc->sdb.length = se_cmd->data_length;
+
+ /*
+ * Setup the struct se_task->task_sg[] chained SG list
+ */
+ if ((se_cmd->se_cmd_flags & SCF_SCSI_DATA_SG_IO_CDB) ||
+ (se_cmd->se_cmd_flags & SCF_SCSI_CONTROL_SG_IO_CDB)) {
+ transport_do_task_sg_chain(se_cmd);
+
+ sc->sdb.table.nents = T_TASK(se_cmd)->t_tasks_sg_chained_no;
+ sc->sdb.table.sgl = T_TASK(se_cmd)->t_tasks_sg_chained;
+ } else if (se_cmd->se_cmd_flags & SCF_SCSI_CONTROL_NONSG_IO_CDB) {
+ /*
+ * Use T_TASK(se_cmd)->t_tasks_sg_bounce for control CDBs
+ * using a contigious buffer
+ */
+ sg_init_table(&T_TASK(se_cmd)->t_tasks_sg_bounce, 1);
+ sg_set_buf(&T_TASK(se_cmd)->t_tasks_sg_bounce,
+ T_TASK(se_cmd)->t_task_buf, se_cmd->data_length);
+
+ sc->sdb.table.nents = 1;
+ sc->sdb.table.sgl = &T_TASK(se_cmd)->t_tasks_sg_bounce;
+ }
+ /*
+ * This will call srp_transfer_data() and post the response
+ * to VIO via libsrp.
+ */
+ ibmvscsis_cmd_done(sc);
+ return 0;
+}
+
+static int ibmvscsis_queue_status(struct se_cmd *se_cmd)
+{
+ struct ibmvscsis_cmnd *cmd = container_of(se_cmd,
+ struct ibmvscsis_cmnd, se_cmd);
+ struct scsi_cmnd *sc = &cmd->sc;
+ /*
+ * Copy any generated SENSE data into sc->sense_buffer and
+ * set the appropiate sc->result to be translated by
+ * ibmvscsis_cmd_done()
+ */
+ if (se_cmd->sense_buffer &&
+ ((se_cmd->se_cmd_flags & SCF_TRANSPORT_TASK_SENSE) ||
+ (se_cmd->se_cmd_flags & SCF_EMULATED_TASK_SENSE))) {
+ memcpy((void *)sc->sense_buffer, (void *)se_cmd->sense_buffer,
+ SCSI_SENSE_BUFFERSIZE);
+ sc->result = host_byte(DID_OK) | driver_byte(DRIVER_SENSE) |
+ SAM_STAT_CHECK_CONDITION;
+ } else
+ sc->result = host_byte(DID_OK) | se_cmd->scsi_status;
+ /*
+ * Finally post the response to VIO via libsrp.
+ */
+ ibmvscsis_cmd_done(sc);
+ return 0;
+}
+
+static int ibmvscsis_queuecommand(struct ibmvscsis_adapter *adapter,
+ struct iu_entry *iue)
+{
+ int data_len;
+ struct srp_cmd *cmd = iue->sbuf->buf;
+ struct scsi_cmnd *sc;
+ struct page *pg;
+ struct ibmvscsis_cmnd *vsc;
+
+ data_len = srp_data_length(cmd, srp_cmd_direction(cmd));
+
+ vsc = kzalloc(sizeof(*vsc), GFP_KERNEL);
+ sc = &vsc->sc;
+ sc->sense_buffer = vsc->sense_buf;
+ sc->cmnd = cmd->cdb;
+ sc->SCp.ptr = (char *)iue;
+
+ switch (cmd->cdb[0]) {
+ case INQUIRY:
+ sg_alloc_table(&sc->sdb.table, 1, GFP_KERNEL);
+ pg = alloc_page(GFP_KERNEL|__GFP_ZERO);
+ sc->sdb.length = ibmvscsis_inquiry(adapter, cmd,
+ page_address(pg));
+ sg_set_page(sc->sdb.table.sgl, pg, sc->sdb.length, 0);
+ ibmvscsis_cmd_done(sc);
+ sg_free_table(&sc->sdb.table);
+ __free_page(pg);
+ kfree(vsc);
+ break;
+ case REPORT_LUNS:
+ sg_alloc_table(&sc->sdb.table, 1, GFP_KERNEL);
+ pg = alloc_page(GFP_KERNEL|__GFP_ZERO);
+ sc->sdb.length = ibmvscsis_report_luns(adapter, cmd,
+ page_address(pg));
+ sg_set_page(sc->sdb.table.sgl, pg, sc->sdb.length, 0);
+ ibmvscsis_cmd_done(sc);
+ sg_free_table(&sc->sdb.table);
+ __free_page(pg);
+ kfree(vsc);
+ break;
+ case MODE_SENSE:
+ /* fixme: needs to use tcm */
+ sg_alloc_table(&sc->sdb.table, 1, GFP_KERNEL);
+ pg = alloc_page(GFP_KERNEL|__GFP_ZERO);
+ sc->sdb.length = ibmvscsis_mode_sense(adapter,
+ cmd, page_address(pg));
+ sg_set_page(sc->sdb.table.sgl, pg, sc->sdb.length, 0);
+ ibmvscsis_cmd_done(sc);
+ sg_free_table(&sc->sdb.table);
+ __free_page(pg);
+ kfree(vsc);
+ break;
+ default:
+ tcm_queuecommand(adapter, vsc, cmd);
+ break;
+ }
+
+ return 0;
+}
+
+static void handle_cmd_queue(struct ibmvscsis_adapter *adapter)
+{
+ struct srp_target *target = &adapter->srpt;
+ struct iu_entry *iue;
+ struct srp_cmd *cmd;
+ unsigned long flags;
+ int err;
+
+retry:
+ spin_lock_irqsave(&target->lock, flags);
+
+ list_for_each_entry(iue, &target->cmd_queue, ilist) {
+ if (!test_and_set_bit(V_FLYING, &iue->flags)) {
+ spin_unlock_irqrestore(&target->lock, flags);
+ err = ibmvscsis_queuecommand(adapter, iue);
+ if (err) {
+ printk(KERN_ERR "cannot queue cmd %p %d\n",
+ cmd, err);
+ srp_iu_put(iue);
+ }
+ goto retry;
+ }
+ }
+
+ spin_unlock_irqrestore(&target->lock, flags);
+}
+
+static void handle_crq(struct work_struct *work)
+{
+ struct ibmvscsis_adapter *adapter =
+ container_of(work, struct ibmvscsis_adapter, crq_work);
+ struct viosrp_crq *crq;
+ int done = 0;
+
+ while (!done) {
+ while ((crq = next_crq(&adapter->crq_queue)) != NULL) {
+ process_crq(crq, adapter);
+ crq->valid = 0x00;
+ }
+
+ vio_enable_interrupts(adapter->dma_dev);
+
+ crq = next_crq(&adapter->crq_queue);
+ if (crq) {
+ vio_disable_interrupts(adapter->dma_dev);
+ process_crq(crq, adapter);
+ crq->valid = 0x00;
+ } else
+ done = 1;
+ }
+
+ handle_cmd_queue(adapter);
+}
+
+static irqreturn_t ibmvscsis_interrupt(int dummy, void *data)
+{
+ struct ibmvscsis_adapter *adapter = data;
+
+ vio_disable_interrupts(adapter->dma_dev);
+ schedule_work(&adapter->crq_work);
+
+ return IRQ_HANDLED;
+}
+
+static int crq_queue_create(struct crq_queue *queue,
+ struct ibmvscsis_adapter *adapter)
+{
+ int err;
+ struct vio_dev *vdev = adapter->dma_dev;
+
+ queue->msgs = (struct viosrp_crq *)get_zeroed_page(GFP_KERNEL);
+ if (!queue->msgs)
+ goto malloc_failed;
+ queue->size = PAGE_SIZE / sizeof(*queue->msgs);
+
+ queue->msg_token = dma_map_single(&vdev->dev, queue->msgs,
+ queue->size * sizeof(*queue->msgs),
+ DMA_BIDIRECTIONAL);
+
+ if (dma_mapping_error(&vdev->dev, queue->msg_token))
+ goto map_failed;
+
+ err = h_reg_crq(vdev->unit_address, queue->msg_token,
+ PAGE_SIZE);
+
+ /* If the adapter was left active for some reason (like kexec)
+ * try freeing and re-registering
+ */
+ if (err == H_RESOURCE) {
+ do {
+ err = h_free_crq(vdev->unit_address);
+ } while (err == H_BUSY || H_IS_LONG_BUSY(err));
+
+ err = h_reg_crq(vdev->unit_address, queue->msg_token,
+ PAGE_SIZE);
+ }
+
+ if (err != H_SUCCESS && err != 2) {
+ printk(KERN_ERR "Error 0x%x opening virtual adapter\n", err);
+ goto reg_crq_failed;
+ }
+
+ err = request_irq(vdev->irq, &ibmvscsis_interrupt,
+ IRQF_DISABLED, "ibmvscsis", adapter);
+ if (err)
+ goto req_irq_failed;
+
+ vio_enable_interrupts(vdev);
+
+ h_send_crq(vdev->unit_address, 0xC001000000000000, 0);
+
+ queue->cur = 0;
+ spin_lock_init(&queue->lock);
+
+ return 0;
+
+req_irq_failed:
+ do {
+ err = h_free_crq(vdev->unit_address);
+ } while (err == H_BUSY || H_IS_LONG_BUSY(err));
+
+reg_crq_failed:
+ dma_unmap_single(&vdev->dev, queue->msg_token,
+ queue->size * sizeof(*queue->msgs), DMA_BIDIRECTIONAL);
+map_failed:
+ free_page((unsigned long) queue->msgs);
+
+malloc_failed:
+ return -ENOMEM;
+}
+
+static void crq_queue_destroy(struct ibmvscsis_adapter *adapter)
+{
+ struct crq_queue *queue = &adapter->crq_queue;
+ int err;
+
+ free_irq(adapter->dma_dev->irq, adapter);
+ flush_work_sync(&adapter->crq_work);
+ do {
+ err = h_free_crq(adapter->dma_dev->unit_address);
+ } while (err == H_BUSY || H_IS_LONG_BUSY(err));
+
+ dma_unmap_single(&adapter->dma_dev->dev, queue->msg_token,
+ queue->size * sizeof(*queue->msgs), DMA_BIDIRECTIONAL);
+
+ free_page((unsigned long)queue->msgs);
+}
+
+static int ibmvscsis_probe(struct vio_dev *dev, const struct vio_device_id *id)
+{
+ unsigned int *dma, dma_size;
+ unsigned long flags;
+ int ret;
+ struct ibmvscsis_adapter *adapter;
+
+ adapter = kzalloc(sizeof(*adapter), GFP_KERNEL);
+ if (!adapter)
+ return -ENOMEM;
+
+ adapter->dma_dev = dev;
+
+ dma = (unsigned int *)vio_get_attribute(dev, "ibm,my-dma-window",
+ &dma_size);
+ if (!dma || dma_size != 40) {
+ printk(KERN_ERR "Couldn't get window property %d\n", dma_size);
+ kfree(adapter);
+ return -EIO;
+ }
+
+ adapter->liobn = dma[0];
+ adapter->riobn = dma[5];
+ ret = strict_strtoul(dev_name(&dev->dev), 10, &adapter->tport_tpgt);
+
+ spin_lock_irqsave(&tpg_lock, flags);
+ list_add(&adapter->siblings, &tpg_list);
+ spin_unlock_irqrestore(&tpg_lock, flags);
+
+ INIT_WORK(&adapter->crq_work, handle_crq);
+
+ dev_set_drvdata(&dev->dev, adapter);
+
+ ret = srp_target_alloc(&adapter->srpt, &dev->dev, INITIAL_SRP_LIMIT,
+ SRP_MAX_IU_LEN);
+
+ adapter->srpt.ldata = adapter;
+
+ ret = crq_queue_create(&adapter->crq_queue, adapter);
+
+ return 0;
+}
+
+static int ibmvscsis_remove(struct vio_dev *dev)
+{
+ struct ibmvscsis_adapter *adapter = dev_get_drvdata(&dev->dev);
+ unsigned long flags;
+
+ spin_lock_irqsave(&tpg_lock, flags);
+ list_del(&adapter->siblings);
+ spin_unlock_irqrestore(&tpg_lock, flags);
+
+ crq_queue_destroy(adapter);
+
+ srp_target_free(&adapter->srpt);
+
+ kfree(adapter);
+ return 0;
+}
+
+static struct vio_device_id ibmvscsis_device_table[] __devinitdata = {
+ {"v-scsi-host", "IBM,v-scsi-host"},
+ {"", ""}
+};
+
+MODULE_DEVICE_TABLE(vio, ibmvscsis_device_table);
+
+static struct vio_driver ibmvscsis_driver = {
+ .id_table = ibmvscsis_device_table,
+ .probe = ibmvscsis_probe,
+ .remove = ibmvscsis_remove,
+ .driver = {
+ .name = "ibmvscsis",
+ .owner = THIS_MODULE,
+ }
+};
+
+static int get_system_info(void)
+{
+ struct device_node *rootdn;
+ const char *id, *model, *name;
+ const unsigned int *num;
+
+ rootdn = of_find_node_by_path("/");
+ if (!rootdn)
+ return -ENOENT;
+
+ model = of_get_property(rootdn, "model", NULL);
+ id = of_get_property(rootdn, "system-id", NULL);
+ if (model && id)
+ snprintf(system_id, sizeof(system_id), "%s-%s", model, id);
+
+ name = of_get_property(rootdn, "ibm,partition-name", NULL);
+ if (name)
+ strncpy(partition_name, name, sizeof(partition_name));
+
+ num = of_get_property(rootdn, "ibm,partition-no", NULL);
+ if (num)
+ partition_number = *num;
+
+ of_node_put(rootdn);
+ return 0;
+}
+
+static int ibmvscsis_register_configfs(void)
+{
+ struct target_fabric_configfs *fabric;
+ int ret;
+
+ printk(KERN_INFO "IBMVSCSIS fabric module %s on %s/%s"
+ " on "UTS_RELEASE"\n", IBMVSCSIS_VERSION, utsname()->sysname,
+ utsname()->machine);
+ /*
+ * Register the top level struct config_item_type with TCM core
+ */
+ fabric = target_fabric_configfs_init(THIS_MODULE, "ibmvscsis");
+ if (!(fabric)) {
+ printk(KERN_ERR "target_fabric_configfs_init() failed\n");
+ return -ENOMEM;
+ }
+ /*
+ * Setup fabric->tf_ops from our local ibmvscsis_ops
+ */
+ fabric->tf_ops = ibmvscsis_ops;
+ /*
+ * Setup default attribute lists for various fabric->tf_cit_tmpl
+ */
+ TF_CIT_TMPL(fabric)->tfc_wwn_cit.ct_attrs = ibmvscsis_wwn_attrs;
+ TF_CIT_TMPL(fabric)->tfc_tpg_base_cit.ct_attrs = NULL;
+ TF_CIT_TMPL(fabric)->tfc_tpg_attrib_cit.ct_attrs = NULL;
+ TF_CIT_TMPL(fabric)->tfc_tpg_param_cit.ct_attrs = NULL;
+ TF_CIT_TMPL(fabric)->tfc_tpg_np_base_cit.ct_attrs = NULL;
+ TF_CIT_TMPL(fabric)->tfc_tpg_nacl_base_cit.ct_attrs = NULL;
+ TF_CIT_TMPL(fabric)->tfc_tpg_nacl_attrib_cit.ct_attrs = NULL;
+ TF_CIT_TMPL(fabric)->tfc_tpg_nacl_auth_cit.ct_attrs = NULL;
+ TF_CIT_TMPL(fabric)->tfc_tpg_nacl_param_cit.ct_attrs = NULL;
+ /*
+ * Register the fabric for use within TCM
+ */
+ ret = target_fabric_configfs_register(fabric);
+ if (ret < 0) {
+ printk(KERN_ERR "target_fabric_configfs_register() failed"
+ " for IBMVSCSIS\n");
+ target_fabric_configfs_deregister(fabric);
+ return ret;
+ }
+ /*
+ * Setup our local pointer to *fabric
+ */
+ ibmvscsis_fabric_configfs = fabric;
+ printk(KERN_INFO "IBMVSCSIS[0] - Set fabric -> ibmvscsis_fabric_configfs\n");
+ return 0;
+};
+
+static void ibmvscsis_deregister_configfs(void)
+{
+ if (!(ibmvscsis_fabric_configfs))
+ return;
+
+ target_fabric_configfs_deregister(ibmvscsis_fabric_configfs);
+ ibmvscsis_fabric_configfs = NULL;
+ printk(KERN_INFO "IBMVSCSIS[0] - Cleared ibmvscsis_fabric_configfs\n");
+};
+
+static int __init ibmvscsis_init(void)
+{
+ int ret;
+
+ ret = get_system_info();
+ if (ret)
+ return ret;
+
+ ret = vio_register_driver(&ibmvscsis_driver);
+ if (ret)
+ return ret;
+
+ ret = ibmvscsis_register_configfs();
+ if (ret < 0)
+ return ret;
+
+ return 0;
+};
+
+static void ibmvscsis_exit(void)
+{
+ vio_unregister_driver(&ibmvscsis_driver);
+ ibmvscsis_deregister_configfs();
+};
+
+MODULE_DESCRIPTION("IBMVSCSIS series fabric driver");
+MODULE_AUTHOR("FUJITA Tomonori");
+MODULE_LICENSE("GPL");
+module_init(ibmvscsis_init);
+module_exit(ibmvscsis_exit);
--
1.7.2.3
^ permalink raw reply related
* Re: [RFC] Inter-processor Mailboxes Drivers
From: Hiroshi DOYU @ 2011-02-14 7:32 UTC (permalink / raw)
To: linus.walleij
Cc: meador_inge, Hollis_Blanchard, sunder.svit, openmcapi-dev,
linuxppc-dev, linux-arm-kernel
In-Reply-To: <AANLkTikoExk30pqdcHXCA2K_oJB=VJ5_AnCkoYUrEt3X@mail.gmail.com>
From: ext Linus Walleij <linus.walleij@linaro.org>
Subject: Re: [RFC] Inter-processor Mailboxes Drivers
Date: Sun, 13 Feb 2011 22:16:12 +0100
> 2011/2/12 Sundar <sunder.svit@gmail.com>:
> =
>> At least I would like this; I wanted to generalize such mailbox IPCs=
>> right from the day when I was working on one, but coudnt really
>> work on that.
>>
>>> =A0 =A02. Does something like this already exist?
>>
>> Not generic as you say; but apart from the OMAP platforms,
>> you could refer to arch/arm/mach-ux500/prcmu for a mailbox based
>> IPC on the U8500 platform.
> =
> We also have this thing:
> arch/arm/mach-ux500/mbox-db5500.c
> =
> It's another mailbox driver, this one talks to the modem in the
> U5500 (basically a physical transport for the CAIF protocol).
> (For the U8500 I think modem IPC is instead handled with
> a high-speed hardware FIFO, a bit different.)
> =
>>> =A0 =A03. Is someone else already working on this?
>>
>> Not sure of that too :), but I am CCing Linus W, the maintainer
>> of U8500 if he thinks it is a good idea to come up with a mailbox IP=
C
>> framework
> =
> I don't know too much about the subject actually, I've not been
> deeply into any such code. I don't think anyone is working
> on something general from ST-Ericsson or Linaro.
> =
> Recently I saw that Texas Instruments are posting a "hardware
> spinlock" framework though, this would be on a related tone,
> but I think it's for shared data structures (control path) rather
> than buffer passing (data path). I'm guessing this works like
> that one CPU gets to spin waiting for another one to release
> the lock.
> =
> Given that we may have a framework for hardware spinlock
> and that we don't want to stockpile drivers into arch/*
> or drivers/misc/* I would say it's intuitively a good idea,
> but the question is what data types you would pass in?
> In arch/arm/mach-ux500/include/mach/mbox-db5500.h
> we have a struct like this:
> =
> /**
> * struct mbox - Mailbox instance struct
> * @list: Linked list head.
> * @pdev: Pointer to device struct.
> * @cb: Callback function. Will be called
> * when new data is received.
> * @client_data: Clients private data. Will be sent back
> * in the callback function.
> * @virtbase_peer: Virtual address for outgoing mailbox.
> * @virtbase_local: Virtual address for incoming mailbox.
> * @buffer: Then internal queue for outgoing messages.
> * @name: Name of this mailbox.
> * @buffer_available: Completion variable to achieve "blocking send=
".
> * This variable will be signaled when there is
> * internal buffer space available.
> * @client_blocked: To keep track if any client is currently
> * blocked.
> * @lock: Spinlock to protect this mailbox instance.
> * @write_index: Index in internal buffer to write to.
> * @read_index: Index in internal buffer to read from.
> * @allocated: Indicates whether this particular mailbox
> * id has been allocated by someone.
> */
> struct mbox {
> struct list_head list;
> struct platform_device *pdev;
> mbox_recv_cb_t *cb;
> void *client_data;
> void __iomem *virtbase_peer;
> void __iomem *virtbase_local;
> u32 buffer[MBOX_BUF_SIZE];
> char name[MBOX_NAME_SIZE];
> struct completion buffer_available;
> u8 client_blocked;
> spinlock_t lock;
> u8 write_index;
> u8 read_index;
> bool allocated;
> };
> =
> Compare OMAPs mailboxes in
> arch/arm/plat-omap/include/plat/mailbox.h:
> =
> typedef u32 mbox_msg_t;
> =
> truct omap_mbox_ops {
> omap_mbox_type_t type;
> int (*startup)(struct omap_mbox *mbox);
> void (*shutdown)(struct omap_mbox *mbox);
> /* fifo */
> mbox_msg_t (*fifo_read)(struct omap_mbox *mbox);
> void (*fifo_write)(struct omap_mbox *mbox, mbox_ms=
g_t msg);
> int (*fifo_empty)(struct omap_mbox *mbox);
> int (*fifo_full)(struct omap_mbox *mbox);
> /* irq */
> void (*enable_irq)(struct omap_mbox *mbox,
> omap_mbox_irq_t irq);=
> void (*disable_irq)(struct omap_mbox *mbox,
> omap_mbox_irq_t irq);=
> void (*ack_irq)(struct omap_mbox *mbox, omap_mbox_=
irq_t irq);
> int (*is_irq)(struct omap_mbox *mbox, omap_mbox_i=
rq_t irq);
> /* ctx */
> void (*save_ctx)(struct omap_mbox *mbox);
> void (*restore_ctx)(struct omap_mbox *mbox);
> };
> =
> struct omap_mbox_queue {
> spinlock_t lock;
> struct kfifo fifo;
> struct work_struct work;
> struct tasklet_struct tasklet;
> struct omap_mbox *mbox;
> bool full;
> };
> =
> struct omap_mbox {
> char *name;
> unsigned int irq;
> struct omap_mbox_queue *txq, *rxq;
> struct omap_mbox_ops *ops;
> struct device *dev;
> void *priv;
> int use_count;
> struct blocking_notifier_head notifier;
> };
> =
> Some of this may be generalized? I dunno, they look quite
> different but maybe queueing etc can actually be made general
> enough to form a framework.
OMAP mailbox is the interrupt driven 32bit unit H/W FIFO to other
cores.
"struct omap_mbox_ops" was provided mainly to absorb the difference
between OMAP1 and OMAP2+ from H/W POV. So generally the layer could
be:
-----------------------
character device driver
-----------------------
generic mailbox driver
-----------------------
H/W registration
-----------------------
In OMAP case, in addition to the above, it could be exceptionally:
-----------------------
character device driver
-----------------------
generic mailbox driver
-----------------------
H/W registration
-----------------------
OMAP 1 | OMAP2+
-----------------------
So "character device driver"(interface) and "generic mailbox
driver"(queuing) may be able to abstructed/generalized.
^ permalink raw reply
* Re: [RFC] Inter-processor Mailboxes Drivers
From: Linus Walleij @ 2011-02-14 8:39 UTC (permalink / raw)
To: Hiroshi DOYU
Cc: meador_inge, Hollis_Blanchard, sunder.svit, openmcapi-dev,
linuxppc-dev, linux-arm-kernel
In-Reply-To: <20110214.093257.58795266617213863.Hiroshi.DOYU@nokia.com>
On Mon, Feb 14, 2011 at 8:32 AM, Hiroshi DOYU <Hiroshi.DOYU@nokia.com> wrote:
> OMAP mailbox is the interrupt driven 32bit unit H/W FIFO to other
> cores.
How is it used? Is it a low-traffic (like single 32bit words etc) signal
control-path link while the actual high-throughput data-path is done
with shared memory? (That is how the db5500 mbox works anyways.)
Linus Walleij
^ permalink raw reply
* Re: [RFC] Inter-processor Mailboxes Drivers
From: Hiroshi DOYU @ 2011-02-14 7:21 UTC (permalink / raw)
To: meador_inge
Cc: openmcapi-dev, Hollis_Blanchard, linuxppc-dev, linux-arm-kernel
In-Reply-To: <4D55A7F7.5090700@mentor.com>
Hi Meador,
From: ext Meador Inge <meador_inge@mentor.com>
Subject: [RFC] Inter-processor Mailboxes Drivers
Date: Fri, 11 Feb 2011 15:19:51 -0600
> Hi All,
>
> I am currently working on building AMP systems using OpenMCAPI
> (https://bitbucket.org/hollisb/openmcapi/wiki/Home) as the
> inter-processor communication mechanism. With OpenMCAPI we, of
> course,
> need a way to send messages to various cores. On some Freescale PPC
> platforms (e.g. P1022DS, MPC8572DS), we have been using message
> registers to do this work. Recently, I was looking at the OMAP4
> mailboxes to gear up for moving into ARM based platforms.
>
> With that, I noticed 'arch/arm/plat-omap/mailbox.c'. This is very
> specific to the OMAP4 boards. I am looking at designing a new set of
> drivers to expose a mailbox service to userspace that will be used
> for inter-processor communication. This would entail the traditional
> generic/specific driver split:
>
> 1. Hardware specific bits somewhere under '.../arch/*'. Drivers
> for the MPIC message registers on Power and OMAP4 mailboxes, for
> example.
> 2. A higher level driver under '.../drivers/mailbox/*'. That the
> pieces in (1) would register with. This piece would expose the
> main kernel API.
> 3. Userspace interfaces for accessing the mailboxes. A
> '/dev/mailbox1', '/dev/mailbox2', etc... mapping, for example.
>
> Now I have the following questions:
>
> 1. Do others see value in this?
Yes.
I discussed this with TI(Hari) long time ago, but it didn't proceed
with some reason, not techinical one.
> 2. Does something like this already exist?
> 3. Is someone else already working on this?
>
> Any feedback will be greatly appreciated.
Now the basic concpet can be devided into 3 parts, (1)HW dependent,
(2) generic driver, (3) character device interface. I guess that it
might be good to have one _pseudo_ H/W instance, then easier for
verification without real H/W and firmwares, across multiplatforms.
^ permalink raw reply
* RE: [PATCH V8 03/10] USB/ppc4xx: Add Synopsys DWC OTG Core InterfaceLayer
From: David Laight @ 2011-02-14 8:53 UTC (permalink / raw)
To: Tirumala Marri, Greg KH, Alexander Gordeev
Cc: linuxppc-dev, linux-usb, Mark Miesfeld, Fushen Chen
In-Reply-To: <4f99d54a42d4f13663c88b0b081f047b@mail.gmail.com>
=20
> > Sorry, I don't understand that. I think u32 is always 32bit=20
> > 4byte on all archs. Right?
>=20
> Yes.
>=20
> Use an unsigned long if you want to hold a pointer correctly on all
> arches.
Although that is true for many systems (and probably all ppc Linux)
it isn't necessarily true (eg 64 bit Microsoft Windows).
C99 inttypes.h should define uintptr_t as an unsigned integer
type that is large enough to hold a (data) pointer.
I'm not sure if this is defined for the Linux kernel.
David
^ permalink raw reply
* Re: [RFC] Inter-processor Mailboxes Drivers
From: Hiroshi DOYU @ 2011-02-14 8:55 UTC (permalink / raw)
To: linus.walleij
Cc: meador_inge, Hollis_Blanchard, sunder.svit, openmcapi-dev,
linuxppc-dev, linux-arm-kernel
In-Reply-To: <AANLkTimnHHhYKnvUgsMo_jsKaYdY+XuABZ+FV0p25JLi@mail.gmail.com>
From: ext Linus Walleij <linus.walleij@linaro.org>
Subject: Re: [RFC] Inter-processor Mailboxes Drivers
Date: Mon, 14 Feb 2011 09:39:32 +0100
> On Mon, Feb 14, 2011 at 8:32 AM, Hiroshi DOYU <Hiroshi.DOYU@nokia.com> wrote:
>
>> OMAP mailbox is the interrupt driven 32bit unit H/W FIFO to other
>> cores.
>
> How is it used? Is it a low-traffic (like single 32bit words etc) signal
> control-path link while the actual high-throughput data-path is done
> with shared memory? (That is how the db5500 mbox works anyways.)
Yes, maybe quite similar.
maibox is not single 32 bit but is 32 bit x 4(or 8?) slots fifo, IIRC,
and mainly it is used as notification between cores. And big amount of
data is transftered with sahred memory, which has been mapped onto the
virtual address space of the other side of core, in advance.
For example, typical usage of DSP, mp3 decoding,
1, ARM maps 2 shared memory area(input/output) onto DSP virtual
address space.
2, ARM fills mp3 data in input buffer.
3, ARM notifies DSP that data is ready in input buffer.
4, DSP decodes input data and put output data on output buffer.
5, DSP notifies that output buffer is ready.
Roughly something like the above. DSP S/W is multi-tasking, though.
Does db5500 use IOMMU for mapping shared memories?
^ permalink raw reply
* Re: [RFC] Inter-processor Mailboxes Drivers
From: Linus Walleij @ 2011-02-14 9:00 UTC (permalink / raw)
To: Hiroshi DOYU
Cc: meador_inge, Hollis_Blanchard, sunder.svit, openmcapi-dev,
linuxppc-dev, linux-arm-kernel
In-Reply-To: <20110214.105553.296482686007701595.Hiroshi.DOYU@nokia.com>
On Mon, Feb 14, 2011 at 9:55 AM, Hiroshi DOYU <Hiroshi.DOYU@nokia.com> wrote:
> Does db5500 use IOMMU for mapping shared memories?
Nope, it's a fixed physical allocation from the modem side
of the world.
Yours,
Linus Walleij
^ permalink raw reply
* Re: [RFC] Inter-processor Mailboxes Drivers
From: Hiroshi DOYU @ 2011-02-14 9:06 UTC (permalink / raw)
To: meador_inge, linus.walleij
Cc: openmcapi-dev, sunder.svit, Hollis_Blanchard, linuxppc-dev,
linux-arm-kernel
In-Reply-To: <20110214.105553.296482686007701595.Hiroshi.DOYU@nokia.com>
From: Hiroshi DOYU <Hiroshi.DOYU@nokia.com>
Subject: Re: [RFC] Inter-processor Mailboxes Drivers
Date: Mon, 14 Feb 2011 10:55:53 +0200 (EET)
> From: ext Linus Walleij <linus.walleij@linaro.org>
> Subject: Re: [RFC] Inter-processor Mailboxes Drivers
> Date: Mon, 14 Feb 2011 09:39:32 +0100
>
>> On Mon, Feb 14, 2011 at 8:32 AM, Hiroshi DOYU <Hiroshi.DOYU@nokia.com> wrote:
>>
>>> OMAP mailbox is the interrupt driven 32bit unit H/W FIFO to other
>>> cores.
>>
>> How is it used? Is it a low-traffic (like single 32bit words etc) signal
>> control-path link while the actual high-throughput data-path is done
>> with shared memory? (That is how the db5500 mbox works anyways.)
>
> Yes, maybe quite similar.
>
> maibox is not single 32 bit but is 32 bit x 4(or 8?) slots fifo, IIRC,
> and mainly it is used as notification between cores. And big amount of
> data is transftered with sahred memory, which has been mapped onto the
> virtual address space of the other side of core, in advance.
>
> For example, typical usage of DSP, mp3 decoding,
>
> 1, ARM maps 2 shared memory area(input/output) onto DSP virtual
> address space.
> 2, ARM fills mp3 data in input buffer.
> 3, ARM notifies DSP that data is ready in input buffer.
> 4, DSP decodes input data and put output data on output buffer.
> 5, DSP notifies that output buffer is ready.
>
> Roughly something like the above. DSP S/W is multi-tasking, though.
Here, ARM side process talks to DSP site process in their own way, and
also there are another cores, talking with their own protocols. So I
think that at least, protocol part should be pluggable anyway although
it doesn't have to be always in userland.
^ permalink raw reply
* Re: [PATCH v2 3/3] tcm ibmvscsis driver
From: Nicholas A. Bellinger @ 2011-02-14 9:12 UTC (permalink / raw)
To: FUJITA Tomonori; +Cc: brking, linuxppc-dev, linux-scsi
In-Reply-To: <20110214124821T.fujita.tomonori@lab.ntt.co.jp>
On Mon, 2011-02-14 at 12:48 +0900, FUJITA Tomonori wrote:
> This is the second version of tcm ibmvscsis driver. You can find the
> first version at:
>
> http://marc.info/?t=129734085600004&r=1&w=2
>
> The changes are:
>
> - send VIOSRP_MAD_NOT_SUPPORTED for unknown mad type requests.
> - fix inquiry typo
> - sends task management response (for now, 'NOT SUPPORTED').
> - remove dead code.
>
Your rev2 patch has been merged into lio-core-2.6.git/tcm_ibmvscsis
currently @ .38-rc4.
Thanks!
--nab
^ permalink raw reply
* Re: [RFC] Inter-processor Mailboxes Drivers
From: Jamie Iles @ 2011-02-14 10:01 UTC (permalink / raw)
To: Meador Inge
Cc: openmcapi-dev, Blanchard, Hollis, Hiroshi DOYU, linuxppc-dev,
linux-arm-kernel
In-Reply-To: <4D55A7F7.5090700@mentor.com>
On Fri, Feb 11, 2011 at 03:19:51PM -0600, Meador Inge wrote:
> 1. Hardware specific bits somewhere under '.../arch/*'. Drivers
> for the MPIC message registers on Power and OMAP4 mailboxes, for
> example.
> 2. A higher level driver under '.../drivers/mailbox/*'. That the
> pieces in (1) would register with. This piece would expose the
> main kernel API.
> 3. Userspace interfaces for accessing the mailboxes. A
> '/dev/mailbox1', '/dev/mailbox2', etc... mapping, for example.
How about using virtio for all of this and having the mailbox as a
notification/message passing driver for the virtio backend? There are
already virtio console and network drivers that could be useful for the
userspace part of it. drivers/virtio/virtio_ring.c might be a good
starting point if you thought there was some mileage in this approach.
Jamie
^ permalink raw reply
page: next (older) | prev (newer) | latest
- recent:[subjects (threaded)|topics (new)|topics (active)]
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox