All of lore.kernel.org
 help / color / mirror / Atom feed
* [PATCHv4 00/11] XSHM: Shared Memory Driver for ST-E Thor M7400 LTE modem
@ 2011-12-16 10:49 Sjur Brændeland
  2011-12-16 10:49 ` [PATCHv4 01/11] xshm: Shared Memory layout for ST-E M7400 driver Sjur Brændeland
                   ` (10 more replies)
  0 siblings, 11 replies; 12+ messages in thread
From: Sjur Brændeland @ 2011-12-16 10:49 UTC (permalink / raw)
  To: linux-kernel; +Cc: Arnd Bergmann, Linus Walleij, sjurbren, Sjur Brændeland

PATCHv4 Changes:
~~~~~~~~~~~~~~~
o Added missing file: drivers/xshm/xshm_bus.c to patchset.
o Fixed module-unload issues (request_firmware, bad kfree when removing device)

Remaining Bugs/Questions:
o Open-timeout-open sequence fails for character device.
o netlink configuration data and fw-image should be better sanity tested.
o Review comments: use of character device/tty, use of device list in xshm_chr,
  memory address as module parameter.


PATCHv3 Changes:
~~~~~~~~~~~~~~~~
o Move xshm_ipctoc.h from include/linux/xshm to drivers/xshm.
o Remove #pragma pack.
o Reduce use of global variables by introducing struct xshm_modem
  in xshm_boot.c and struct xshm_parent in xshm_dev.c.
o Remove most of the device lists, and use *_for_each_* instead.
  In xshm_boot.c an array of configuration entries is kept,
  instead of device instances. (Device list is in use in xshm_chr.c)
o request_firmware is implemented instead of home-brewed solution.
o Minor cleanup of debug macros.
o Added dependency on CONFIG_NET, issue reported by Randy Dunlap.
o Dropped __DATE__ macro, issue reported by Michal Marek.


Introduction:
~~~~~~~~~~~~~
This patch-set introduces the Shared Memory Driver for ST-Ericsson's
Thor M7400 LTE modem.
The shared memory model is implemented for Chip-to-chip and
uses a reserved memory area and a number of bi-directional
channels. Each channel has it's own designated data area where payload
data is copied into.

Two different channel types are defined, one stream channel which is
implemented as a traditional ring-buffer, and a packet channel which
is a ring-buffer of fix sized buffers where each buffer contains
an array of CAIF frames.

The notification of read and write index updates are handled in a
separate driver called c2c_genio. This driver will be contributed separately,
but the API is included in this patch-set.

The channel configuration is stored in shared memory, each channel
has a designated area in shared memory for configuration data,
read and write indexes and a data area.

Configuration
~~~~~~~~~~~~~~~

        IPC-TOC
        +--------------------+	   Index Area
        | Channel Descr 0    | -----> +------------+
	|                    | -+     |	Read Index |
	|--------------------|	|     |------------|
	| Channel Descr 1    | 	|     |	Write Index|
	|                    |	|     +------------+
	|--------------------|	|   Data Area
	|       ...    	     |	|     +------------+
	|                    |	+---> |            |
	+--------------------+	      |	       	   |
				      |	       	   |
				      +------------+

A IPC-TOC (table of content) is used for holding configuration data
for the channels (struct xshm_ipctoc). It contains an array of
channel descriptors (struct xshm_ipctoc_channel). The channel
descriptors points out the data area and the location of
read and write indexes.

The configuration is provided from user-space using gen-netlink.
The gen-netlink format is defined in xshm_netlink.h and and handled
in xshm_boot.c

Packet data
~~~~~~~~~~~
The packet channel is set up to minimize interrupts needed to
transfer a packet and to allow efficient DMA operations on the modem.

Ring Buffer Indexes:
    +------------+
  +-| Read Index |
  | |------------|
  | | Write Index|------------------------+
  | +------------+   		 	  |
  |    		    			  |
  V    	  				  |
Buffer-0:				  V Buffer-1:
 +----------------------------------------+---------------+---
 |ofs,len|ofs,len| ....| frm-0|frm-1| ... | ofs,len | ....|...
 +----------------------------------------+---------------+--
   |      |              ^      ^
   +---------------------+      |
	  +---------------------+

Packet data is organized in a channel containing a number of fixed-
size buffers. The channel has a read and write pointer to a buffer in a normal
ring-buffer fashion.

Each buffer holds an array of CAIF-frames, and starts with an descriptor array
containing pointers to the data frames in the buffer. The descriptor array
contains offset and length of each frame.

The packet device (caif_xshm.c) is implemented as a network interface of
type ARPHRD_CAIF.

Stream data
~~~~~~~~~~~
The driver for the stream channel is implemented as a character device
interface to user space. The character device implements non-blocking open
and non-blocking IO in general. The character device is implementing
a traditional circular buffer directly in the shared memory region for
the channel.


Driver model
~~~~~~~~~~~~~~
A XSHM bus is implemented, the example below shows one device
of each type (stream and packet):

/sys/bus/xshm
|-- devices
|   |-- xshm0 -> ../../../devices/xshm/xshm0
|   `-- xshm1 -> ../../../devices/xshm/xshm1
|-- drivers
|   |-- caif_xshm
|   |   |-- bind
|   |   |-- module -> ../../../../module/caif_xshm
|   |   |-- uevent
|   |   |-- unbind
|   |   `-- xshm1 -> ../../../../devices/xshm/xshm1
|   `-- xshm_chr
|       |-- bind
|       |-- module -> ../../../../module/xshm_chr
|       |-- uevent
|       |-- unbind
|       `-- xshm0 -> ../../../../devices/xshm/xshm0
|-- drivers_autoprobe
|-- drivers_probe
`-- uevent


/sys/devices/xshm/
|-- bootimg
|-- caif_ready
|-- ipc_ready
|-- uevent
|-- xshm0
|   |-- driver -> ../../../bus/xshm/drivers/xshm_chr
|   |-- misc
|   |   `-- xshm0
|   |       |-- dev
|   |       |-- device -> ../../../xshm0
|   |       |-- subsystem -> ../../../../../class/misc
|   |       `-- uevent
|   |-- subsystem -> ../../../bus/xshm
|   `-- uevent
`-- xshm1
    |-- driver -> ../../../bus/xshm/drivers/caif_xshm
    |-- subsystem -> ../../../bus/xshm
    `-- uevent


Review comments and feedback is welcome.

Regards,
Sjur Brændeland



Sjur Brændeland (11):
  xshm: Shared Memory layout for ST-E M7400 driver.
  xshm: Channel config definitions for ST-E M7400 driver.
  xshm: Configuration for XSHM Channel an devices.
  xshm: geni/geno driver interface.
  xshm: genio dummy driver
  xshm: Add XSHM device bus.
  xshm: Add xshm device implementation
  xshm: XSHM Configuration data handling
  xshm: Character device for XSHM channel access.
  xshm: Makefile and Kconfig for M7400 Shared Memory Drivers
  caif-xshm: Add CAIF driver for Shared memory for M7400

 drivers/Kconfig                   |    2 +
 drivers/Makefile                  |    1 +
 drivers/net/caif/Kconfig          |   10 +
 drivers/net/caif/Makefile         |    1 +
 drivers/net/caif/caif_xshm.c      |  915 +++++++++++++++++++++++++++
 drivers/xshm/Kconfig              |   61 ++
 drivers/xshm/Makefile             |    5 +
 drivers/xshm/dummy_c2c_genio.c    |   76 +++
 drivers/xshm/xshm_boot.c          | 1150 +++++++++++++++++++++++++++++++++
 drivers/xshm/xshm_bus.c           |  113 ++++
 drivers/xshm/xshm_chr.c           | 1262 +++++++++++++++++++++++++++++++++++++
 drivers/xshm/xshm_dev.c           |  546 ++++++++++++++++
 drivers/xshm/xshm_ipctoc.h        |  153 +++++
 include/linux/Kbuild              |    1 +
 include/linux/c2c_genio.h         |  195 ++++++
 include/linux/xshm/Kbuild         |    1 +
 include/linux/xshm/xshm_dev.h     |  245 +++++++
 include/linux/xshm/xshm_netlink.h |   95 +++
 18 files changed, 4832 insertions(+), 0 deletions(-)
 create mode 100644 drivers/net/caif/caif_xshm.c
 create mode 100644 drivers/xshm/Kconfig
 create mode 100644 drivers/xshm/Makefile
 create mode 100644 drivers/xshm/dummy_c2c_genio.c
 create mode 100644 drivers/xshm/xshm_boot.c
 create mode 100644 drivers/xshm/xshm_bus.c
 create mode 100644 drivers/xshm/xshm_chr.c
 create mode 100644 drivers/xshm/xshm_dev.c
 create mode 100644 drivers/xshm/xshm_ipctoc.h
 create mode 100644 include/linux/c2c_genio.h
 create mode 100644 include/linux/xshm/Kbuild
 create mode 100644 include/linux/xshm/xshm_dev.h
 create mode 100644 include/linux/xshm/xshm_netlink.h


^ permalink raw reply	[flat|nested] 12+ messages in thread

* [PATCHv4 01/11] xshm: Shared Memory layout for ST-E M7400 driver.
  2011-12-16 10:49 [PATCHv4 00/11] XSHM: Shared Memory Driver for ST-E Thor M7400 LTE modem Sjur Brændeland
@ 2011-12-16 10:49 ` Sjur Brændeland
  2011-12-16 10:49 ` [PATCHv4 02/11] xshm: Channel config definitions " Sjur Brændeland
                   ` (9 subsequent siblings)
  10 siblings, 0 replies; 12+ messages in thread
From: Sjur Brændeland @ 2011-12-16 10:49 UTC (permalink / raw)
  To: linux-kernel; +Cc: Arnd Bergmann, Linus Walleij, sjurbren, Sjur Brændeland

Add structures defining the channel configuration and the
shared memory layout used for ST-Ericsson M7400.

These data structures are shared between the Linux host and the
M7400 modem.

Signed-off-by: Sjur Brændeland <sjur.brandeland@stericsson.com>
---
 drivers/xshm/xshm_ipctoc.h |  153 ++++++++++++++++++++++++++++++++++++++++++++
 1 files changed, 153 insertions(+), 0 deletions(-)
 create mode 100644 drivers/xshm/xshm_ipctoc.h

diff --git a/drivers/xshm/xshm_ipctoc.h b/drivers/xshm/xshm_ipctoc.h
new file mode 100644
index 0000000..b604bc5
--- /dev/null
+++ b/drivers/xshm/xshm_ipctoc.h
@@ -0,0 +1,153 @@
+/*
+ * Copyright (C) ST-Ericsson AB 2011
+ * Author: Sjur Brændeland / sjur.brandeland@stericsson.com
+ * License terms: GNU General Public License (GPL) version 2
+ */
+
+#ifndef XSHM_TOC
+#define XSHM_TOC
+
+/**
+ * DOC: XSHM Shared Memory Layout
+ *
+ * XSHM defines a set of structures describing the memory layout used
+ * for the Shared Memory IPC. In short &toc_entry points out &ipc_toc,
+ * which points out the &xshm_ipctoc_channel. &xshm_ipctoc_channel defines
+ * the channels used to communicate between host and external device (modem).
+ *
+ *  &xshm_ipctoc_channel can be used in packet-mode or stream-mode,
+ *  and points out &xshm_bufidx holding information about cirular
+ *  buffers, andtheir read/write indices etc.
+ */
+
+struct _xshm_offsets {
+	__le32 rx;
+	__le32 tx;
+};
+
+/**
+ * struct xshm_ipctoc - Table Of Content definition for IPC.
+ *
+ * @magic:	Magic shall always be set to Ascii coded string "TC" (2 bytes)
+ * @version:	Main version of the TOC header.
+ * @subver:	Sub version of the TOC header.
+ * @channel_offsets: Offset to both rx and tx direction must be set.
+ *			The array must be terminated by a zero value.
+ *
+ * This struct is stored at the start of the External Shared memory, and
+ * serves as a extended table of contents defining the channel configurations
+ * for the external shared memory protocol between a modem and host.
+ *
+ * This extended table of content (ipctoc) is written to a predefine memory
+ * location and the modem will read this ipctoc during start-up and use this
+ * for setting up the IPC channels and it's buffers.
+ *
+ */
+
+struct xshm_ipctoc {
+	__u8 magic[2];
+	__u8 version;
+	__u8 subver;
+	struct _xshm_offsets channel_offsets[8];
+};
+
+#define XSHM_IPCTOC_MAGIC1 'T'
+#define XSHM_IPCTOC_MAGIC2 'C'
+
+/**
+ * struct xshm_ipctoc_channel - Channel descriptor for External Shared memory.
+ *
+ * @offset: Relative address to channel data area.
+ * @size: Total size of a SHM channel area partition.
+ * @mtu: Maximum Transfer Unit for packets in a buffer (packet mode).
+ * @packets: Maximum Number of packets in a buffer (packet mode).
+ * @mode: Mode of channel: Packet mode=1, Stream mode (shm_channel_mode = 2).
+ * @buffers: Number of buffers for the channel.
+ * @ipc: Offset to IPC message location (of type struct xshm_bufidx).
+ * @read_bit: GENI/O bit used to indicate update of the read pointer for
+ *	this channel (at offset ipc).
+ * @write_bit: GENI/O bit used to indicate update of the write pointer for
+ *	this channel (at offset ipc).
+ * @alignment: Protocol specific options for the protocol,
+ *	e.g. packet alignment.
+ *
+ * This struct defines the channel configuration for a single direction.
+ *
+ * This structure is pointed out by the &xshm_toc and is written by
+ * host during start-up and read by modem at firmware boot.
+ *
+ */
+
+struct xshm_ipctoc_channel {
+	__le32 offset;
+	__le32 size;
+/* private: */
+	__u8 unused[3];
+/* public: */
+	__u8 mode;
+	__le32 buffers;
+	__le32 ipc;
+	__le16 write_bit;
+	__le16 read_bit;
+	__u16 mtu;
+	__u8 packets;
+	__u8 alignment;
+};
+
+/**
+ * struct xshm_bufidx - Indices's for a uni-directional xshm channel.
+ *
+ * @read_index: Specify the read index for a channel. This field can
+ *	have value in range of [0.. xshm_ipctoc_channel.buffers -1].
+ *	In stream mode - this is the read index in the ringbuffer.
+ *	In packet mode - this index will at any time refer to the next
+ *	buffer available for read.
+ *
+ * @write_index: Specify the write index for a channel.
+ *	This field can have value in range of [0.. buffers -1].
+ *	In stream mode - this is the write index in the ringbuffer.
+ *	In packet mode - this index will at any time refer to the next
+ *	buffer available for write.
+ *
+ * @size: The actual number of bytes for a buffer at each index.
+ *	  This array has xshm_ipctoc_channel.buffers slots, one for each buffer.
+ *	  The size is updated every time data is written to the buffer.
+ *
+ * @state: The state of the channel, 0 - Closed, 1 - Open
+ *
+ *
+ * This structure contains data for the ring-buffer used in packet and stream
+ * mode, for the external shared memory protocol.
+ * Note that the read_buf_index and the write_buf_index
+ * refer to two different channels. So for a ring buffer used to communicate
+ * from modem, the modem will update the write_buf_index while Linux host
+ * will update read_buf_index.
+ */
+struct xshm_bufidx {
+	__le32 state;
+	__le32 read_index;
+	__le32 write_index;
+	__le32 size[0];
+};
+
+/** struct toc_entry - Points out the boot imiages
+ *
+ * @start: Offset counting from start of memory area to the image data.
+ * @size:  Size of the images in bytes.
+ * @flags: Use 0 if no flags are in use.
+ * @entry: Where to jump to start exeuting. Only applicable
+ *		when using SDRAM. Set to 0xffffffff if unused.
+ * @load_addr: Location in SDRAM to move image. Set to 0xffffffff if
+ *		not applicable.
+ * @name: Name of image.
+ */
+struct toc_entry {
+	__le32 start;
+	__le32 size;
+	__le32 flags;
+	__le32 entry_point;
+	__le32 load_addr;
+	char name[12];
+};
+
+#endif
-- 
1.7.1


^ permalink raw reply related	[flat|nested] 12+ messages in thread

* [PATCHv4 02/11] xshm: Channel config definitions for ST-E M7400 driver.
  2011-12-16 10:49 [PATCHv4 00/11] XSHM: Shared Memory Driver for ST-E Thor M7400 LTE modem Sjur Brændeland
  2011-12-16 10:49 ` [PATCHv4 01/11] xshm: Shared Memory layout for ST-E M7400 driver Sjur Brændeland
@ 2011-12-16 10:49 ` Sjur Brændeland
  2011-12-16 10:49 ` [PATCHv4 03/11] xshm: Configuration for XSHM Channel an devices Sjur Brændeland
                   ` (8 subsequent siblings)
  10 siblings, 0 replies; 12+ messages in thread
From: Sjur Brændeland @ 2011-12-16 10:49 UTC (permalink / raw)
  To: linux-kernel; +Cc: Arnd Bergmann, Linus Walleij, sjurbren, Sjur Brændeland

Netlink parameters used for configuring channels from user space.

Signed-off-by: Sjur Brændeland <sjur.brandeland@stericsson.com>
---
 include/linux/Kbuild              |    1 +
 include/linux/xshm/Kbuild         |    1 +
 include/linux/xshm/xshm_netlink.h |   95 +++++++++++++++++++++++++++++++++++++
 3 files changed, 97 insertions(+), 0 deletions(-)
 create mode 100644 include/linux/xshm/Kbuild
 create mode 100644 include/linux/xshm/xshm_netlink.h

diff --git a/include/linux/Kbuild b/include/linux/Kbuild
index 619b565..bd2db5c 100644
--- a/include/linux/Kbuild
+++ b/include/linux/Kbuild
@@ -17,6 +17,7 @@ header-y += netfilter_bridge/
 header-y += netfilter_ipv4/
 header-y += netfilter_ipv6/
 header-y += usb/
+header-y += xshm/
 header-y += wimax/
 
 objhdr-y += version.h
diff --git a/include/linux/xshm/Kbuild b/include/linux/xshm/Kbuild
new file mode 100644
index 0000000..4315a21
--- /dev/null
+++ b/include/linux/xshm/Kbuild
@@ -0,0 +1 @@
+header-y += xshm_netlink.h
diff --git a/include/linux/xshm/xshm_netlink.h b/include/linux/xshm/xshm_netlink.h
new file mode 100644
index 0000000..e785ad2
--- /dev/null
+++ b/include/linux/xshm/xshm_netlink.h
@@ -0,0 +1,95 @@
+/*
+ * Copyright (C) ST-Ericsson AB 2011
+ * Author: Sjur Brændeland / sjur.brandeland@stericsson.com
+ * License terms: GNU General Public License (GPL) version 2
+ */
+
+#ifndef XSHM_NL_H_
+#define XSHM_NL_H_
+
+#define XSHM_PROTO_VERSION 1
+#define XSHM_PROTO_SUB_VERSION 0
+#define XSHM_NETLINK_VERSION 1
+/**
+ * enum XSHM_COMMANDS - Attributes used for configuring XSHM device.
+ *
+ * @XSHM_C_ADD_STREAM_CHANNEL: Adds a Stream Channel.
+ * This will cause an instance of XSHM-CHR to be created.
+ * @XSHM_C_ADD_PACKET_CHANNEL: Adds a Packet Channel.
+ * This will cause an instance of CAIF-SHM to be created.
+ * @XSHM_C_COMMIT: formats and write Channel configuration data to
+ *	Shared Memory.
+ * @XSHM_C_SET_ADDR: Writes the TOC address to GENO register.
+ * @XSHM_C_REGISTER:  Initiates registration of the channel devices.
+ *	This will cause xshm - character devices or
+ *	CAIF network instances to be created.
+ * @XSHM_C_RESET:  Reset the configuration data and removes the
+ *	platform devices and their associated channel configuration.
+ *	ipc_ready and caif_ready is set to false.
+ *
+ * A normal sequence of events is: [XSHM_C_RESET], [XSHM_C_ADD_X_CHANNEL],
+ *	XSHM_C_COMMIT, XSHM_C_REGISTER, XSHM_C_SET_ADDR.
+ */
+enum XSHM_COMMANDS {
+	XSHM_C_ADD_STREAM_CHANNEL = 1,
+	XSHM_C_ADD_PACKET_CHANNEL,
+	XSHM_C_RESET,
+	XSHM_C_SET_ADDR,
+	XSHM_C_COMMIT,
+	XSHM_C_REGISTER,
+	__XSHM_C_VERIFY,
+	__XSHM_C_MAX
+};
+
+/**
+ * enum XSHM_ATTRIBUTES - Attributes used for configuring XSHM device.
+ * @XSHM_A_VERSION: Version of XSHM netlink protocol. Type NLA_U8
+ * @XSHM_A_SUB_VERSION: Sub-version of XSHM netlink protocol. Type NLA_U8
+ * @XSHM_A_NAME: Name of the channel, max 15 characters. Type NLA_NUL_STRING
+ * @XSHM_A_EXCL_GROUP: Devices may be part of a group. Devices from the
+ *	same group are allowed to be open simultaneously,
+ *	but devices from different groups cannot be opened
+ *	at the same time. Type NLA_U8.
+ * @XSHM_A_RX_CHANNEL: The RX direction attributes. Type NLA_NESTED.
+ *	Each channel may contain the attributes - XSHM_A_CHANNEL_SIZE,
+ *	XSHM_A_CHANNEL_BUFFERS, XSHM_A_ALIGNMENT, XSHM_A_MTU.
+ *
+ * @XSHM_A_TX_CHANNEL: The TX direction attributes. Type NLA_NESTED.
+ *
+ * @XSHM_A_CHANNEL_SIZE: Size of the data area for a channel. Specified
+ *	for RX, TX. Type NLA_U32,
+ * @XSHM_A_CHANNEL_BUFFERS: Numer of buffers for a packet channel.
+ *	This attribute is only used for packet channels.  Specified for RX, TX.
+ *	Type NLA_U32,
+ * @XSHM_A_ALIGNMENT: Alignment for each packet in a buffer. This attribute
+ *	 is only used for packet channels.  Specified for RX, TX.Type NLA_U8,
+ * @XSHM_A_MTU: Maximum Transfer Unit for packets in a buffer.
+ *	This is only appplicable for packet channels.
+ *	Specified for RX, TX.Type NLA_U16,
+ * @XSHM_A_PACKETS: Maximum number of packets in a buffer. Type NLA_U8
+ * @XSHM_A_PRIORITY: Priority of the channel, legal range is 0-7 where
+ *	0 is lowest priority. Type NLA_U8.
+ * @XSHM_A_LATENCY: Latency for channel, value:0 means low latency
+ *	 and low bandwidth,
+ *	 value 1 means high latency and high bandwidth. Type NLA_U8.
+ */
+enum XSHM_ATTRIBUTES {
+	__XSHM_A_FLAGS = 1,		/* Test flags: NLA_U32 */
+	XSHM_A_VERSION,
+	XSHM_A_SUB_VERSION,
+	XSHM_A_NAME,
+	XSHM_A_EXCL_GROUP,
+	XSHM_A_RX_CHANNEL,
+	XSHM_A_TX_CHANNEL,
+	XSHM_A_CHANNEL_SIZE,
+	XSHM_A_CHANNEL_BUFFERS,
+	XSHM_A_ALIGNMENT,
+	XSHM_A_MTU,
+	XSHM_A_PACKETS,
+	XSHM_A_PRIORITY,
+	XSHM_A_LATENCY,
+	__XSHM_A_MAX,
+};
+#define XSHM_A_MAX (__XSHM_A_MAX - 1)
+
+#endif /* XSHM_NL_H_ */
-- 
1.7.1


^ permalink raw reply related	[flat|nested] 12+ messages in thread

* [PATCHv4 03/11] xshm: Configuration for XSHM Channel an devices.
  2011-12-16 10:49 [PATCHv4 00/11] XSHM: Shared Memory Driver for ST-E Thor M7400 LTE modem Sjur Brændeland
  2011-12-16 10:49 ` [PATCHv4 01/11] xshm: Shared Memory layout for ST-E M7400 driver Sjur Brændeland
  2011-12-16 10:49 ` [PATCHv4 02/11] xshm: Channel config definitions " Sjur Brændeland
@ 2011-12-16 10:49 ` Sjur Brændeland
  2011-12-16 10:49 ` [PATCHv4 04/11] xshm: geni/geno driver interface Sjur Brændeland
                   ` (7 subsequent siblings)
  10 siblings, 0 replies; 12+ messages in thread
From: Sjur Brændeland @ 2011-12-16 10:49 UTC (permalink / raw)
  To: linux-kernel; +Cc: Arnd Bergmann, Linus Walleij, sjurbren, Sjur Brændeland

This patch defines the configuration data needed to operate
on a channel. Each channel is represented as a xshm device
containing the necessary configuration data to operate on the channel.

Signed-off-by: Sjur Brændeland <sjur.brandeland@stericsson.com>
---
 include/linux/xshm/xshm_dev.h |  245 +++++++++++++++++++++++++++++++++++++++++
 1 files changed, 245 insertions(+), 0 deletions(-)
 create mode 100644 include/linux/xshm/xshm_dev.h

diff --git a/include/linux/xshm/xshm_dev.h b/include/linux/xshm/xshm_dev.h
new file mode 100644
index 0000000..231d6d1
--- /dev/null
+++ b/include/linux/xshm/xshm_dev.h
@@ -0,0 +1,245 @@
+/*
+ * Copyright (C) ST-Ericsson AB 2011
+ * Author: Sjur Brændeland / sjur.brandeland@stericsson.com
+ * License terms: GNU General Public License (GPL) version 2
+ */
+
+#ifndef XSHM_DEV_H_
+#define XSHM_DEV_H_
+
+#include <linux/device.h>
+#include <linux/firmware.h>
+
+#define XSHM_NAMESZ 16
+#define XSHM_MAX_CHANNELS 7
+#define XSHM_PACKET_MODE 0x1
+#define XSHM_STREAM_MODE 0x2
+#define XSHM_LOOP_MODE	 0x4
+#define XSHM_PAIR_MODE	 0x8
+#define XSHM_MODE_MASK	 0x3
+
+/**
+ * struct xshm_udchannel - Unidirectional channel for xshm driver.
+ *
+ * @addr: Base address of the channel, address must be
+ *		  a kernel logical address.
+ * @buffers: The number of buffers in the channel.
+ * @ch_size: The size of data area for the channel in one direction.
+ * @xfer_bit: GENI/O bit used when sending data (write pointer move)
+ * @xfer_done_bit: GENI/O bit used to indicate avilable buffers
+ *	(read pointer move).
+ * @alignment: Alignment used in payload protocol.
+ * @mtu: Maxium Transfer Unit used for packet in a buffer (Packet mode).
+ * @packets: Maxium number of packets in a buffer (Packet mode).
+ * @state: State of the device 0 - Closed, 1 - Open
+ * @read: Specify the read index for a channel. In packed mode
+ *	this index will at any time refer to the next buffer available for read.
+ *	In stream mode, this will be the read index in the ring-buffer.
+ * @write: Specify the write index for a channel. In packed mode
+ *	this index will at any time refer to the next buffer available for
+ *	write. In stream mode, this will be the write index in the ring-buffer.
+ * @buf_size: In packet mode, this array contains the size of each buffer.
+ *	In stream mode this is unused.
+ *
+ * This external shared memory channel configuration is exported from the
+ * xshm device. It gives the xshm driver the necessary information for
+ * running the shared memory protocol between modem and host.
+ *
+ * Note that two instances of this configuration is needed in order to run a
+ * bi-directional channel.
+ */
+struct xshm_udchannel {
+	void *addr;
+	u32 buffers;
+	u32 ch_size;
+	u8 xfer_done_bit;
+	u8 xfer_bit;
+	u32 mtu;
+	u32 alignment;
+	u32 packets;
+	__le32 *state;
+	__le32 *read;
+	__le32 *write;
+	__le32 *buf_size;
+/* private: */
+	struct kobject kobj; /* kobj must be located at the end */
+};
+
+/**
+ * struct xshm_channel - Channel definition for xshm driver.
+ * @rx: Configuration for RX channel
+ * @tx: Configuration for TX channel
+ * @excl_group: Only channels with the same group ID can be open simultaneously.
+ * @mode: Configuring type of channel PACKET(1), STREAM(2)
+ * @name: Name of interface.
+ * @priority: Priority of the channel.
+ * @latency: Latency of the channel.
+ */
+struct xshm_channel {
+	struct xshm_udchannel rx, tx;
+	u32 excl_group;
+	u32 mode;
+	char name[XSHM_NAMESZ];
+	u32 priority;
+	u32 latency;
+};
+
+#define XSHM_OPEN   1
+#define XSHM_CLOSED 0
+
+enum xshm_dev_state {
+	XSHM_DEV_CLOSED = 0,
+	XSHM_DEV_OPENING,
+	XSHM_DEV_OPEN,
+	XSHM_DEV_ACTIVE,
+};
+
+/**
+ * struct xshm_dev - Device definition for xshm device.
+ *
+ * @dev: Reference to device
+ * @cfg: Configuration for the Channel
+ * @state: State of the device: Closed - No user space client is using it,
+ *	Open - Open but no payload queued, Active - Payload queued on device.
+ *
+ * @open: The driver calls open() when channel is taken into use.
+ *	This function will fail if channel configuration is inconsistent,
+ *	or upon resource conflicts with other channels.
+ *
+ * @open_cb: The device calls open_cb() when is ready for use.
+ *
+ * @close: Called by the driver when a channel is no longer in use.
+ *
+ * @close_cb: The device calls close_cb() to notify about remote side closure.
+ *
+ * @ipc_tx_release_cb: This callback is triggered by the modem when a
+ *	transmit operation has completed and the buffer can be reused.
+ *	This function must be set by the driver upon device registration.
+ *	The "more" flag is set if ipc_rx_cb() call is coming immediately
+ *	after this call to ipc_tx_release_cb().
+ *
+ * @ipc_rx_cb: The driver gets this callback when the modem sends a buffer
+ *	from the modem. The driver must call ipc_rx_release()
+ *	to make the buffer available again when the received buffer has been
+ *	processed.
+ *	This function pointer must be set by the driver upon device
+ *	registration.
+ *
+ * @ipc_rx_release: Called by the driver when a RX operation has completed
+ *	and that the rx-buffer is released.
+ *
+ * @ipc_tx: Called by the driver when a TX buffer shall be sent to the modem.
+ *
+ * @driver_data: pointer to driver specific data.
+ *
+ * When communicating between two systems (e.g. modem and host),
+ * external shared memory can bused (e.g. C2C or DPRAM).
+ *
+ * This structure is used by the xshm device representing the
+ * External Shared Memory.
+ *
+ * The this structure contains configuration data for the xshm device and
+ * functions pointers for IPC communication between Linux host and modem.
+ * The external shared memory protocol memory e.g. C2C or DPRAM
+ * together is a IPC mechanism for transporting small commands such as
+ * Mailbox or GENI/O.
+ *
+ * This data structure is initiated by the xshm device, except
+ * for the functions ipc_rx_cb() and ipc_tx_release_cb(). They must be set by
+ * the xshm-driver when device is registering.
+ */
+
+struct xshm_dev {
+	struct device dev;
+	struct xshm_channel cfg;
+	enum xshm_dev_state state;
+	int (*open)(struct xshm_dev *dev);
+	void (*close)(struct xshm_dev *dev);
+	int (*ipc_rx_release)(struct xshm_dev *dev, bool more);
+	int (*ipc_tx)(struct xshm_dev *dev);
+	int (*open_cb)(void *drv);
+	void (*close_cb)(void *drv);
+	int (*ipc_rx_cb)(void *drv);
+	int (*ipc_tx_release_cb)(void *drv);
+	void *driver_data;
+	/* private: */
+	struct list_head node;
+	void *priv;
+};
+
+/**
+ * xshm_driver - operations for a xshm I/O driver
+ * @driver: underlying device driver (populate name and owner).
+ * @mode: Type of channel for driver: PACKET(1), STREAM(2)
+ * @probe: the function to call when a device is found.  Returns 0 or -errno.
+ * @remove: the function when a device is removed.
+ */
+struct xshm_driver {
+	struct device_driver driver;
+	u32 mode;
+	int (*probe)(struct xshm_dev *dev);
+	void (*remove)(struct xshm_dev *dev);
+};
+
+/**
+ * xshm_register_driver() - Register an xshm driver.
+ * @driver: XSHM driver instance
+ */
+int xshm_register_driver(struct xshm_driver *driver);
+
+/**
+ * xshm_unregister_driver() - Unregister an xshm driver.
+ * @driver: XSHM driver instance
+ */
+void xshm_unregister_driver(struct xshm_driver *driver);
+
+/**
+ * xshm_register_device() - Register an xshm device.
+ * @dev: XSHM device instance
+ */
+int xshm_register_device(struct xshm_dev *dev);
+
+/**
+ * xshm_unregister_device() - Unregister an xshm device.
+ * @dev: XSHM device instance
+ */
+void xshm_unregister_device(struct xshm_dev *dev);
+
+/**
+ * xshm_foreach_dev - device iterator.
+ * @data: data for the callback.
+ * @fn: function to be called for each device.
+ *
+ * Iterate over xshm bus's list of devices, and call @fn for each,
+ * passing it @data.
+ */
+void xshm_foreach_dev(void fn(struct xshm_dev*, void *data), void *data);
+
+/**
+ * xshm_register_devices() - Register an array of xshm devices.
+ * @devs: XSHM devices to register
+ * @devices: Number of devices in array
+ */
+int xshm_register_devices(struct xshm_channel *channel[], int channels);
+
+/**
+ * xshm_reset() - XSHM unregister and remove all XSHM devices
+ */
+void xshm_reset(void);
+
+/**
+ * genio_ipc_ready_cb() - Callback for CAIF_READY notification.
+ */
+void genio_ipc_ready_cb(void);
+
+/**
+ * xshm_request_firmware() - Request firmware from user-space.
+ * @context: Context returned in cb() function.
+ * @img_name: Name of the firmware image to load.
+ * @fw_avilable: Callback function called when firmware is avilable.
+ */
+int xshm_request_firmware(void *context, struct module *mod,
+		const char *img_name,
+		void (*fw_avilable)(const struct firmware *fw, void *ctx));
+
+#endif
-- 
1.7.1


^ permalink raw reply related	[flat|nested] 12+ messages in thread

* [PATCHv4 04/11] xshm: geni/geno driver interface.
  2011-12-16 10:49 [PATCHv4 00/11] XSHM: Shared Memory Driver for ST-E Thor M7400 LTE modem Sjur Brændeland
                   ` (2 preceding siblings ...)
  2011-12-16 10:49 ` [PATCHv4 03/11] xshm: Configuration for XSHM Channel an devices Sjur Brændeland
@ 2011-12-16 10:49 ` Sjur Brændeland
  2011-12-16 10:49 ` [PATCHv4 05/11] xshm: genio dummy driver Sjur Brændeland
                   ` (6 subsequent siblings)
  10 siblings, 0 replies; 12+ messages in thread
From: Sjur Brændeland @ 2011-12-16 10:49 UTC (permalink / raw)
  To: linux-kernel; +Cc: Arnd Bergmann, Linus Walleij, sjurbren, Sjur Brændeland

The Shared Memory driver is using two paired registers called
GENI and GENO in for triggering events/interrupts on modem and Linux host.

It's primary use is notification about read and write indexes changes
on each channel. But GENI/GENO are also used for communicating the
address of the shared memory upon modem boot and to notify about start-up
event such as when IPC channels and CAIF is ready for use.

Signed-off-by: Sjur Brændeland <sjur.brandeland@stericsson.com>
---
 include/linux/c2c_genio.h |  195 +++++++++++++++++++++++++++++++++++++++++++++
 1 files changed, 195 insertions(+), 0 deletions(-)
 create mode 100644 include/linux/c2c_genio.h

diff --git a/include/linux/c2c_genio.h b/include/linux/c2c_genio.h
new file mode 100644
index 0000000..d356472
--- /dev/null
+++ b/include/linux/c2c_genio.h
@@ -0,0 +1,195 @@
+/*
+ * Copyright (C) ST-Ericsson AB 2011
+ * Author: Sjur Brændeland / sjur.brandeland@stericsson.com
+ *
+ * License terms: GNU General Public License (GPL) version 2
+ */
+
+#ifndef __INC_GENIO_H
+#define __INC_GENIO_H
+#include <linux/types.h>
+
+/**
+ * DOC: C2C GENI/GENO interface.
+ *
+ * This API defines the API for the C2C driver and the GENI/GENO registers.
+ */
+
+/**
+ * enum GENIO_BITS - Definition of special GENIO BITS.
+ * @READY_FOR_IPC_BIT:  Remote side is ready for IPC.
+ * This GENI/GENO bit is triggered when ring-buffer protocol
+ * is enabled from the remote end (modem)
+ *
+ * @READY_FOR_CAIF_BIT: Remote side is ready for CAIF.
+ * This GENI/GENO bit is triggered when CAIF protocol
+ * is enabled from the remote end (modem)
+ */
+enum GENIO_BITS {
+	READY_FOR_CAIF_BIT = 28,
+	READY_FOR_IPC_BIT = 29
+};
+
+/**
+ * genio_subscribe - Subscribe for notifications on bit-change of GENI/O bits.
+ *
+ * @bit:	The GENI/O bit where we want to be called back when it changes.
+ *
+ * @bit_set_cb:	Callback function to be called when the requested GENI/O bit
+ *		is set by the external device (modem).
+ *
+ * @data: Client data to be provided in the callback function.
+ *
+ * Install a callback function for a GENI/O bit. Returns negative upon error.
+ *
+ * The genio driver is expected to handle the 4-state handshake for geni/geno
+ * update, for the bit this function subscribe to. This function may block,
+ * and cannot be called from IRQ context.
+ *
+ * Returns zero on success, and negative upon error.
+ *
+ * Precondition: This function is called after genio_set_shm_addr() and
+ * genio_bit_alloc(). @bit must be defined as as getter in genio_bit_alloc().
+ *
+ * Callback context:
+ *		The "bit_set_cb" callback must be called from the
+ *		IRQ context. The callback function is not allowed to block
+ *		or spend much CPU time in the callback, It must defer
+ *		work to soft-IRQ or work queues.
+ */
+int genio_subscribe(int bit, void (*bit_set_cb)(void *data), void *data);
+
+/**
+ * genio_unsubscribe - Unsubscribe for callback  GENI/O bit.
+ * @bit:       The GENI/O bit to we want release.
+ *
+ * This function may block. It returns zero on success, and negative upon error.
+ *
+ * Precondition: @bit must be defined as as getter in genio_bit_alloc().
+ */
+int genio_unsubscribe(int bit);
+
+/**
+ * genio_bit_alloc - Allocate the usage of GENI/O bits.
+ *
+ * @setter_mask:	Bit-mask defining the bits that can be set by
+ *			genio_set_bit()
+ * @getter_mask:	Bit-mask defining the bits that can be subscribed by
+ *			genio_subscribe().
+ *
+ * The @getter_mask defines the bit for RX direction, i.e. bits that can
+ * be subscribed by the function genio_subscribe().
+ * The @setter_mask defines the bit for TX direction, i.e. bits that can
+ * be set by the function genio_set_bit().
+ * This function may block.
+ *
+ * Returns zero on success, and negative upon error.
+ *
+ * Precondition:
+ * This function cannot be called before ipc_ready_cb() has been called,
+ * and must be called prior to any call on genio_subscribe() or genio_set_bit().
+ *
+ */
+int genio_bit_alloc(u32 setter_mask, u32 getter_mask);
+
+/**
+ * genio_set_shm_addr - Inform remote device about the shared memory address.
+ * @addr:		The shared memory address.
+ * @ipc_ready_cb:	The callback function indicating IPC is ready.
+ *
+ * Description:
+ * Implements the setting of shared memory address involving the
+ * READY_FOR_IPC GENIO bits, and READY_FOR_IPC handshaking.
+ * When handshaking is done ipc_ready_cb is called.
+ * The usage of this bit is during start/restart.
+ * This function may block.
+ *
+ * When the ipc_ready_cb() with @ready=%true, Stream channels can be opened.
+ *
+ * Sequence:
+ * (1) Write the address to the GENO register,
+ *
+ * (2) wait for interrupt on IPC_READY (set to one in GENI register)
+ *
+ * (3) write zero to the GENO register
+ *
+ * (4) wait for interrupt on IPC_READY (set to zero)
+ *
+ * (5) call the callback function for IPC_READY
+ *
+ * Returns zero on success, and negative upon error.
+ *
+ * Precondition:
+ * This function must be called initially upon start, or after remote device
+ * has been reset.
+ */
+int genio_set_shm_addr(u32 addr, void (*ipc_ready_cb) (void));
+
+/**
+ * genio_subscribe_caif_ready - Notify that CAIF channels can be
+ * opened.
+ *
+ * @caif_ready_cb: is called with @ready = %true in the start up sequence,
+ * when the remote side is ready for CAIF enumeration. Upon reset,
+ * caif_ready_cb() will be called with @ready = %false.
+ *
+ * The %READY_FOR_CAIF_BIT is set to %one as long as the
+ * modem is able to run CAIF traffic. Upon modem restart/crash it will
+ * be set back to %zero.
+ * This function may block.
+ *
+ * Returns zero on success, and negative upon error.
+ */
+int genio_subscribe_caif_ready(void (*caif_ready_cb) (bool ready));
+
+/**
+ * genio_register_errhandler - Register an error handler.
+ *
+ * @errhandler: error handler called from driver upon severe errors
+ *		that requires reset of the remote device.
+ */
+void genio_register_errhandler(void (*errhandler)(int errno));
+
+/**
+ * genio_reset() - Reset the C2C driver
+ *
+ * Reset the C2C Driver due to remote device restart.
+ * This shall reset state back to initial state, and should only
+ * be used when remote device (modem) has reset.
+ *
+ * All settings, subscriptions and state information in the driver must
+ * be reset. GENIO client must do all subscriptions again.
+ * This function may block.
+ *
+ * Returns zero on success, and negative upon error.
+ */
+int genio_reset(void);
+
+/**
+ * genio_set_bit() -	Set a single GENI/O bit.
+ *
+ * @bit:	The GENI/O bit to set
+ *
+ * This function is used to signal over GENI/GENO the driver must
+ * perform the 4-state protocol to signal the change to remote device.
+ * This function is non-blocking, and can be called from Soft-IRQ context.
+ *
+ * Returns zero on success, and negative upon error.
+ *
+ * Precondition: @bit must be defined as as setter in genio_bit_alloc().
+ */
+int genio_set_bit(int bit);
+
+/**
+ * genio_power_req() -	Request power on on remote C2C block.
+ *
+ * @state:	1 - power-on request , 0 - power-off request
+ *
+ * This function will request power on of remote C2C block.
+ * This function is non-blocking, and can be called from Soft-IRQ context.
+ *
+ * Returns zero on success, and negative upon error.
+ */
+int genio_power_req(int state);
+
+#endif /*INC_GENIO_H*/
-- 
1.7.1


^ permalink raw reply related	[flat|nested] 12+ messages in thread

* [PATCHv4 05/11] xshm: genio dummy driver
  2011-12-16 10:49 [PATCHv4 00/11] XSHM: Shared Memory Driver for ST-E Thor M7400 LTE modem Sjur Brændeland
                   ` (3 preceding siblings ...)
  2011-12-16 10:49 ` [PATCHv4 04/11] xshm: geni/geno driver interface Sjur Brændeland
@ 2011-12-16 10:49 ` Sjur Brændeland
  2011-12-16 10:49 ` [PATCHv4 06/11] xshm: Add XSHM device bus Sjur Brændeland
                   ` (5 subsequent siblings)
  10 siblings, 0 replies; 12+ messages in thread
From: Sjur Brændeland @ 2011-12-16 10:49 UTC (permalink / raw)
  To: linux-kernel; +Cc: Arnd Bergmann, Linus Walleij, sjurbren, Sjur Brændeland

Dummy driver for genio. A proper driver for Nova A9540 will be
contributed at a later stage.

Signed-off-by: Sjur Brændeland <sjur.brandeland@stericsson.com>
---
 drivers/xshm/dummy_c2c_genio.c |   76 ++++++++++++++++++++++++++++++++++++++++
 1 files changed, 76 insertions(+), 0 deletions(-)
 create mode 100644 drivers/xshm/dummy_c2c_genio.c

diff --git a/drivers/xshm/dummy_c2c_genio.c b/drivers/xshm/dummy_c2c_genio.c
new file mode 100644
index 0000000..9b95aa3
--- /dev/null
+++ b/drivers/xshm/dummy_c2c_genio.c
@@ -0,0 +1,76 @@
+/*
+ * Copyright (C) ST-Ericsson AB 2011
+ * Author:	Sjur Brændeland / sjur.brandeland@stericsson.com
+ * License terms: GNU General Public License (GPL) version 2
+ */
+
+/* This is a dummy implementation of GENIO */
+
+#include <linux/module.h>
+#include <linux/c2c_genio.h>
+
+int genio_subscribe(int bit, void (*bit_set_cb)(void *data), void *data)
+{
+	return 0;
+}
+EXPORT_SYMBOL(genio_subscribe);
+
+int genio_unsubscribe(int bit)
+{
+	return 0;
+}
+EXPORT_SYMBOL(genio_unsubscribe);
+
+int genio_set_bit(int bit)
+{
+	return 0;
+}
+EXPORT_SYMBOL(genio_set_bit);
+
+int genio_reset(void)
+{
+	return 0;
+}
+EXPORT_SYMBOL(genio_reset);
+
+int genio_subscribe_caif_ready(void (*caif_ready_cb) (bool ready))
+{
+	return 0;
+}
+EXPORT_SYMBOL(genio_subscribe_caif_ready);
+
+int genio_set_shm_addr(u32 addr, void (*ipc_ready_cb) (void))
+{
+	return 0;
+}
+EXPORT_SYMBOL(genio_set_shm_addr);
+
+int genio_bit_alloc(u32 setter_mask, u32 getter_mask)
+{
+	return 0;
+}
+EXPORT_SYMBOL(genio_bit_alloc);
+
+void genio_register_errhandler(void (*errhandler)(int errno))
+{
+}
+EXPORT_SYMBOL(genio_register_errhandler);
+
+int genio_power_req(int state)
+{
+	return 0;
+}
+EXPORT_SYMBOL(genio_power_req);
+
+static int __init genio_init(void)
+{
+	return 0;
+}
+
+static void __exit genio_exit(void)
+{
+}
+module_init(genio_init);
+module_exit(genio_exit);
+
+MODULE_LICENSE("GPL");
-- 
1.7.1


^ permalink raw reply related	[flat|nested] 12+ messages in thread

* [PATCHv4 06/11] xshm: Add XSHM device bus.
  2011-12-16 10:49 [PATCHv4 00/11] XSHM: Shared Memory Driver for ST-E Thor M7400 LTE modem Sjur Brændeland
                   ` (4 preceding siblings ...)
  2011-12-16 10:49 ` [PATCHv4 05/11] xshm: genio dummy driver Sjur Brændeland
@ 2011-12-16 10:49 ` Sjur Brændeland
  2011-12-16 10:49 ` [PATCHv4 07/11] xshm: Add xshm device implementation Sjur Brændeland
                   ` (4 subsequent siblings)
  10 siblings, 0 replies; 12+ messages in thread
From: Sjur Brændeland @ 2011-12-16 10:49 UTC (permalink / raw)
  To: linux-kernel; +Cc: Arnd Bergmann, Linus Walleij, sjurbren, Sjur Brændeland

Add a simple xshm device bus, providing device and driver registration
and a simple iterator. Device mode (packet or stream) is used
for matching device and driver.

Signed-off-by: Sjur Brændeland <sjur.brandeland@stericsson.com>
---
 drivers/xshm/xshm_bus.c |  113 +++++++++++++++++++++++++++++++++++++++++++++++
 1 files changed, 113 insertions(+), 0 deletions(-)
 create mode 100644 drivers/xshm/xshm_bus.c

diff --git a/drivers/xshm/xshm_bus.c b/drivers/xshm/xshm_bus.c
new file mode 100644
index 0000000..d6410a6
--- /dev/null
+++ b/drivers/xshm/xshm_bus.c
@@ -0,0 +1,113 @@
+/*
+ * Copyright (C) ST-Ericsson AB 2011
+ * Author: Sjur Brændeland / sjur.brandeland@stericsson.com
+ * License terms: GNU General Public License (GPL) version 2
+ */
+
+#include <linux/spinlock.h>
+#include <linux/module.h>
+#include <linux/xshm/xshm_dev.h>
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Sjur Brændland <sjur.brandeland@stericsson.com>");
+
+static int xshm_dev_match(struct device *_dv, struct device_driver *_dr)
+{
+	struct xshm_dev *dev = container_of(_dv, struct xshm_dev, dev);
+	struct xshm_driver *drv = container_of(_dr, struct xshm_driver, driver);
+
+	return drv->mode ==  (drv->mode & dev->cfg.mode);
+}
+
+static int xshm_dev_probe(struct device *_d)
+{
+	struct xshm_dev *dev = container_of(_d, struct xshm_dev, dev);
+	struct xshm_driver *drv = container_of(dev->dev.driver,
+						 struct xshm_driver, driver);
+	return drv->probe(dev);
+}
+
+static int xshm_dev_remove(struct device *_d)
+{
+	struct xshm_dev *dev = container_of(_d, struct xshm_dev, dev);
+	struct xshm_driver *drv = container_of(dev->dev.driver,
+						 struct xshm_driver, driver);
+	drv->remove(dev);
+	return 0;
+}
+
+static struct bus_type xshm_bus = {
+	.name  = "xshm",
+	.match = xshm_dev_match,
+	.probe = xshm_dev_probe,
+	.remove = xshm_dev_remove,
+};
+
+struct xshm_iter_data {
+	void *data;
+	void (*fn)(struct xshm_dev *, void *);
+};
+
+int xshm_iter(struct device *_dev, void *data)
+{
+	struct xshm_dev *dev = container_of(_dev, struct xshm_dev, dev);
+	struct xshm_iter_data *iter_data = data;
+
+	iter_data->fn(dev, iter_data->data);
+	return 0;
+}
+
+void xshm_foreach_dev(void fn(struct xshm_dev *, void *), void *data)
+{
+	struct xshm_iter_data iter = {
+		.data = data,
+		.fn = fn
+	};
+
+	bus_for_each_dev(&xshm_bus, NULL, &iter, xshm_iter);
+}
+EXPORT_SYMBOL_GPL(xshm_foreach_dev);
+
+int xshm_register_driver(struct xshm_driver *driver)
+{
+	driver->driver.bus = &xshm_bus;
+	return driver_register(&driver->driver);
+}
+EXPORT_SYMBOL_GPL(xshm_register_driver);
+
+void xshm_unregister_driver(struct xshm_driver *driver)
+{
+	driver_unregister(&driver->driver);
+}
+EXPORT_SYMBOL_GPL(xshm_unregister_driver);
+
+int xshm_register_device(struct xshm_dev *dev)
+{
+	int err;
+
+	dev->dev.bus = &xshm_bus;
+	err = device_register(&dev->dev);
+	return err;
+}
+EXPORT_SYMBOL_GPL(xshm_register_device);
+
+void xshm_unregister_device(struct xshm_dev *dev)
+{
+	device_unregister(&dev->dev);
+}
+EXPORT_SYMBOL_GPL(xshm_unregister_device);
+
+static int xshm_bus_init(void)
+{
+	if (bus_register(&xshm_bus) != 0)
+		panic("xshm bus registration failed");
+	return 0;
+}
+
+static void __exit xshm_bus_exit(void)
+{
+	bus_unregister(&xshm_bus);
+}
+
+core_initcall(xshm_bus_init);
+module_exit(xshm_bus_exit);
-- 
1.7.1


^ permalink raw reply related	[flat|nested] 12+ messages in thread

* [PATCHv4 07/11] xshm: Add xshm device implementation
  2011-12-16 10:49 [PATCHv4 00/11] XSHM: Shared Memory Driver for ST-E Thor M7400 LTE modem Sjur Brændeland
                   ` (5 preceding siblings ...)
  2011-12-16 10:49 ` [PATCHv4 06/11] xshm: Add XSHM device bus Sjur Brændeland
@ 2011-12-16 10:49 ` Sjur Brændeland
  2011-12-16 10:49 ` [PATCHv4 08/11] xshm: XSHM Configuration data handling Sjur Brændeland
                   ` (3 subsequent siblings)
  10 siblings, 0 replies; 12+ messages in thread
From: Sjur Brændeland @ 2011-12-16 10:49 UTC (permalink / raw)
  To: linux-kernel; +Cc: Arnd Bergmann, Linus Walleij, sjurbren, Sjur Brændeland

This patch implements the XSHM device.
- Channel management such as open/close.
- Modem start-up synchronization events.
- C2C power request indications, requesting power-on when transmit is ongoing,
  and power-off upon inactivity timeout.

Signed-off-by: Sjur Brændeland <sjur.brandeland@stericsson.com>
---
 drivers/xshm/xshm_dev.c |  546 +++++++++++++++++++++++++++++++++++++++++++++++
 1 files changed, 546 insertions(+), 0 deletions(-)
 create mode 100644 drivers/xshm/xshm_dev.c

diff --git a/drivers/xshm/xshm_dev.c b/drivers/xshm/xshm_dev.c
new file mode 100644
index 0000000..bfd771c
--- /dev/null
+++ b/drivers/xshm/xshm_dev.c
@@ -0,0 +1,546 @@
+/*
+ * Copyright (C) ST-Ericsson AB 2011
+ * Author: Sjur Brændeland / sjur.brandeland@stericsson.com
+ * License terms: GNU General Public License (GPL) version 2
+ */
+
+#define pr_fmt(fmt) KBUILD_MODNAME ":%s:" fmt, __func__
+#include <linux/version.h>
+#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <linux/netdevice.h>
+#include <linux/firmware.h>
+#include <linux/c2c_genio.h>
+#include <linux/xshm/xshm_dev.h>
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Sjur Brændland <sjur.brandeland@stericsson.com>");
+
+static int xshm_inactivity_timeout = 1000;
+module_param(xshm_inactivity_timeout, int, S_IRUGO | S_IWUSR);
+MODULE_PARM_DESC(xshm_inactivity_timeout, "Inactivity timeout, ms.");
+
+struct xshm_parent {
+	struct device dev;
+	bool ready_for_ipc;
+	bool ready_for_caif;
+	spinlock_t timer_lock;
+	int inactivity_timeout;
+	struct timer_list inactivity_timer;
+	bool power_on;
+};
+
+struct xshm_parent *parent_dev;
+
+#define xdev_dbg(dev, fmt, arg...) dev_dbg(&dev->dev, pr_fmt(fmt), ##arg)
+#define xdev_vdbg(dev, fmt, arg...) dev_vdbg(&dev->dev, pr_fmt(fmt), ##arg)
+#define pr_xshmstate(dev, str, arg...)					\
+	xdev_vdbg(dev, str "STATE: %s txch:%s(%p) rxch:%s(%p)\n", ##arg,\
+			dev->state == XSHM_DEV_OPEN ? "open" : "close", \
+			*dev->cfg.tx.state == cpu_to_le32(XSHM_OPEN) ?	\
+			"open" : "close",				\
+			dev->cfg.tx.state,				\
+			*dev->cfg.rx.state == cpu_to_le32(XSHM_OPEN) ?	\
+			"open" : "close",				\
+			dev->cfg.rx.state)
+
+static void inactivity_tout(unsigned long arg)
+{
+	unsigned long flags;
+	pr_devel("enter\n");
+	spin_lock_irqsave(&parent_dev->timer_lock, flags);
+	/*
+	 * This is paranoia, but if timer is reactivated
+	 * before this tout function is scheduled,
+	 * we just ignore this timeout.
+	 */
+	if (timer_pending(&parent_dev->inactivity_timer))
+		goto out;
+
+	if (parent_dev->power_on) {
+		pr_devel("genio power req(off)\n");
+		genio_power_req(false);
+		parent_dev->power_on = false;
+	}
+out:
+	spin_unlock_irqrestore(&parent_dev->timer_lock, flags);
+}
+
+static void activity(void)
+{
+	unsigned long flags;
+	pr_devel("enter\n");
+	spin_lock_irqsave(&parent_dev->timer_lock, flags);
+	if (!parent_dev->power_on) {
+		pr_devel("genio power req(on)\n");
+		genio_power_req(true);
+		parent_dev->power_on = true;
+	}
+	mod_timer(&parent_dev->inactivity_timer,
+			jiffies + parent_dev->inactivity_timeout);
+	spin_unlock_irqrestore(&parent_dev->timer_lock, flags);
+}
+
+static void reset_activity_tout(void)
+{
+	unsigned long flags;
+	pr_devel("enter\n");
+	spin_lock_irqsave(&parent_dev->timer_lock, flags);
+	if (parent_dev->power_on) {
+		genio_power_req(false);
+		parent_dev->power_on = false;
+	}
+	del_timer_sync(&parent_dev->inactivity_timer);
+	spin_unlock_irqrestore(&parent_dev->timer_lock, flags);
+}
+
+static int xshmdev_ipc_tx(struct xshm_dev *dev)
+{
+	xdev_vdbg(dev, "call genio_set_bit(%d)\n", dev->cfg.tx.xfer_bit);
+	activity();
+	return genio_set_bit(dev->cfg.tx.xfer_bit);
+}
+
+static int xshmdev_ipc_rx_release(struct xshm_dev *dev, bool more)
+{
+	xdev_vdbg(dev, "call genio_set_bit(%d)\n", dev->cfg.tx.xfer_bit);
+	activity();
+	return genio_set_bit(dev->cfg.rx.xfer_done_bit);
+}
+
+static int do_open(struct xshm_dev *dev)
+{
+	int err;
+
+	pr_xshmstate(dev, "enter");
+	err = dev->open_cb(dev->driver_data);
+	if (err < 0) {
+		xdev_dbg(dev, "Error - open_cb failed\n");
+
+		/* Make sure ring-buffer is empty i RX and TX direction */
+		*dev->cfg.rx.read = *dev->cfg.rx.write;
+		*dev->cfg.tx.write = *dev->cfg.tx.read;
+		*dev->cfg.tx.state = cpu_to_le32(XSHM_CLOSED);
+		xdev_vdbg(dev, "set state = XSHM_DEV_CLOSED\n");
+		dev->state = XSHM_DEV_CLOSED;
+		return err;
+	}
+
+	/* Check is we already have any data in the pipe */
+	if (*dev->cfg.rx.write != *dev->cfg.rx.read) {
+		xdev_vdbg(dev, "Received data during opening\n");
+		dev->ipc_rx_cb(dev->driver_data);
+	}
+
+	return err;
+}
+
+static void genio_rx_cb(void *data)
+{
+	struct xshm_dev *dev = data;
+
+	pr_xshmstate(dev, "Enter");
+
+	if (likely(dev->state == XSHM_DEV_OPEN)) {
+		if (unlikely(!parent_dev->ready_for_ipc)) {
+			xdev_vdbg(dev, "ready_for_ipc is not yet set\n");
+			return;
+		}
+
+		if (dev->ipc_rx_cb) {
+			int err = dev->ipc_rx_cb(dev->driver_data);
+			if (unlikely(err < 0))
+				goto remote_close;
+		}
+
+	} else if (*dev->cfg.rx.state == cpu_to_le32(XSHM_OPEN)) {
+		pr_xshmstate(dev, "");
+		dev->state = XSHM_DEV_OPEN;
+		if (!parent_dev->ready_for_ipc) {
+			xdev_vdbg(dev, "ready_for_ipc is not yet set\n");
+			return;
+		}
+		if (do_open(dev) < 0)
+			goto open_fail;
+	}
+	return;
+open_fail:
+	pr_xshmstate(dev, "exit open failed");
+	/* Make sure ring-buffer is empty i RX and TX direction */
+	*dev->cfg.rx.read = *dev->cfg.rx.write;
+	*dev->cfg.tx.write = *dev->cfg.tx.read;
+remote_close:
+	*dev->cfg.tx.state = cpu_to_le32(XSHM_CLOSED);
+	dev->state = XSHM_DEV_CLOSED;
+	dev->close_cb(dev->driver_data);
+}
+
+static void genio_tx_release_cb(void *data)
+{
+	struct xshm_dev *dev = data;
+
+	pr_xshmstate(dev, "Enter");
+	if (!parent_dev->ready_for_ipc) {
+		xdev_vdbg(dev, "not ready_for_ipc\n");
+		return;
+	}
+	if (dev->ipc_tx_release_cb)
+		dev->ipc_tx_release_cb(dev->driver_data);
+}
+
+struct xshm_xgroup {
+	bool prohibit;
+	u32 group;
+};
+
+static void check_exclgroup(struct xshm_dev *dev, void *data)
+{
+	struct xshm_xgroup *x = data;
+	if (dev->state == XSHM_DEV_OPEN &&
+		dev->cfg.excl_group != x->group) {
+		x->prohibit = true;
+		xdev_dbg(dev, "Exclusive group "
+				"prohibits device open\n");
+	}
+}
+
+static int xshmdev_open(struct xshm_dev *dev)
+{
+	int err = -EINVAL;
+	struct xshm_xgroup x = {
+		.prohibit = false,
+		.group = dev->cfg.excl_group
+	};
+
+
+	pr_xshmstate(dev, "Enter");
+	if (WARN_ON(dev->ipc_rx_cb == NULL) ||
+			WARN_ON(dev->ipc_tx_release_cb == NULL) ||
+			WARN_ON(dev->open_cb == NULL) ||
+			WARN_ON(dev->close_cb == NULL))
+		goto err;
+
+	xshm_foreach_dev(check_exclgroup, &x);
+	if (x.prohibit) {
+		xdev_dbg(dev, "Exclusive group prohibits device open\n");
+		err = -EPERM;
+		goto err;
+	}
+
+	xdev_vdbg(dev, "call genio_subscribe(%d)\n", dev->cfg.rx.xfer_bit);
+	err = genio_subscribe(dev->cfg.rx.xfer_bit, genio_rx_cb, dev);
+	if (err)
+		goto err;
+
+	xdev_vdbg(dev, "call genio_subscribe(%d)\n", dev->cfg.tx.xfer_done_bit);
+	err = genio_subscribe(dev->cfg.tx.xfer_done_bit,
+			genio_tx_release_cb, dev);
+	if (err)
+		goto err;
+
+	/* Indicate that our side is open and ready for action */
+	*dev->cfg.rx.read = *dev->cfg.rx.write;
+	*dev->cfg.tx.write = *dev->cfg.tx.read;
+	*dev->cfg.tx.state = cpu_to_le32(XSHM_OPEN);
+
+	if (parent_dev->ready_for_ipc)
+		err = xshmdev_ipc_tx(dev);
+
+	if (err < 0) {
+		xdev_dbg(dev, "can't update geno\n");
+		goto err;
+	}
+	/* If other side is ready as well we're ready to role */
+	if (*dev->cfg.rx.state == cpu_to_le32(XSHM_OPEN) &&
+			parent_dev->ready_for_ipc) {
+
+		if (do_open(dev) < 0)
+			goto err;
+		dev->state = XSHM_DEV_OPEN;
+	}
+
+	return 0;
+err:
+	pr_xshmstate(dev, "exit error");
+	*dev->cfg.rx.read = *dev->cfg.rx.write;
+	*dev->cfg.tx.write = *dev->cfg.tx.read;
+	*dev->cfg.tx.state = cpu_to_le32(XSHM_CLOSED);
+	return err;
+}
+
+static void xshmdev_close(struct xshm_dev *dev)
+{
+	pr_xshmstate(dev, "enter");
+
+	dev->state = XSHM_DEV_CLOSED;
+	*dev->cfg.rx.read = *dev->cfg.rx.write;
+	*dev->cfg.tx.state = cpu_to_le32(XSHM_CLOSED);
+	xshmdev_ipc_tx(dev);
+	if (dev->close_cb)
+		dev->close_cb(dev->driver_data);
+
+	xdev_vdbg(dev, "call genio_unsubscribe(%d)\n", dev->cfg.rx.xfer_bit);
+	genio_unsubscribe(dev->cfg.rx.xfer_bit);
+	xdev_vdbg(dev, "call genio_unsubscribe(%d)\n",
+			dev->cfg.tx.xfer_done_bit);
+	genio_unsubscribe(dev->cfg.tx.xfer_done_bit);
+}
+
+static void xshmdev_release(struct device *dev)
+{
+	struct xshm_dev *xshm = container_of(dev, struct xshm_dev, dev);
+	kfree(xshm);
+}
+
+int xshm_register_devices(struct xshm_channel *channel[], int channels)
+{
+
+	int i, err;
+	struct xshm_dev *dev;
+
+	for (i = 0; i < channels; i++) {
+		dev = kzalloc(sizeof(*dev), GFP_KERNEL);
+		if (dev == NULL)
+			return -ENOMEM;
+		dev_set_name(&dev->dev, "xshm%u", i);
+		dev->dev.release = xshmdev_release;
+
+		dev->state = XSHM_DEV_CLOSED;
+		dev->open = xshmdev_open;
+		dev->close = xshmdev_close;
+		dev->ipc_rx_release = xshmdev_ipc_rx_release;
+		dev->ipc_tx = xshmdev_ipc_tx;
+		xdev_vdbg(dev, "register XSHM device %s\n",
+				dev_name(&dev->dev));
+		dev->dev.parent = &parent_dev->dev;
+		dev->cfg = *channel[i];
+
+		err = xshm_register_device(dev);
+		if (err) {
+			xdev_dbg(dev, "registration failed (%d)\n", err);
+			return err;
+		}
+	}
+	return 0;
+}
+EXPORT_SYMBOL(xshm_register_devices);
+
+/* sysfs: ipc_ready file */
+static ssize_t caif_ready_show(struct device *dev,
+				struct device_attribute *attr, char *buf)
+{
+	return sprintf(buf, "%d\n", parent_dev->ready_for_caif);
+}
+
+static DEVICE_ATTR(caif_ready, S_IRUSR | S_IRUGO, caif_ready_show, NULL);
+
+/* sysfs: notification on change of caif_ready to user space */
+void xshm_caif_ready(void)
+{
+	sysfs_notify(&parent_dev->dev.kobj, NULL,
+			dev_attr_caif_ready.attr.name);
+}
+
+/* sysfs: ipc_ready file */
+static ssize_t ipc_ready_show(struct device *dev, struct device_attribute *attr,
+				char *buf)
+{
+	return sprintf(buf, "%d\n", parent_dev->ready_for_ipc);
+}
+
+static DEVICE_ATTR(ipc_ready, S_IRUSR | S_IRUGO, ipc_ready_show, NULL);
+
+/* sysfs: notification on change of ipc_ready to user space */
+void xshm_ipc_ready(void)
+{
+	sysfs_notify(&parent_dev->dev.kobj, NULL, dev_attr_ipc_ready.attr.name);
+}
+
+static void genio_caif_ready_cb(bool ready)
+{
+	pr_devel("enter\n");
+	/* Set global variable ready_for_caif true */
+	if (parent_dev->ready_for_caif != ready) {
+		parent_dev->ready_for_caif = ready;
+		xshm_caif_ready();
+	}
+}
+
+static void genio_errhandler(int errno)
+{
+	/* Fake CAIF_READY low to trigger modem restart */
+	pr_warn("Driver reported error:%d\n", errno);
+	parent_dev->ready_for_caif = 0;
+	xshm_caif_ready();
+}
+
+struct xshm_bits {
+	u32 setter;
+	u32 getter;
+};
+
+static void collect_bits(struct xshm_dev *dev, void *data)
+{
+	struct xshm_bits *bits = data;
+	bits->setter |= 1 << dev->cfg.tx.xfer_bit;
+	bits->setter |= 1 << dev->cfg.rx.xfer_done_bit;
+	bits->getter |= 1 << dev->cfg.rx.xfer_bit;
+	bits->getter |= 1 << dev->cfg.tx.xfer_done_bit;
+}
+
+static void handle_open(struct xshm_dev *dev, void *data)
+{
+		if (dev->cfg.rx.state != NULL && dev->cfg.tx.state != NULL &&
+				*dev->cfg.rx.state == cpu_to_le32(XSHM_OPEN) &&
+				*dev->cfg.tx.state == cpu_to_le32(XSHM_OPEN)) {
+			dev->state = XSHM_DEV_OPEN;
+			do_open(dev);
+		}
+}
+
+void genio_ipc_ready_cb(void)
+{
+	int err;
+	struct xshm_bits bits = {0, 0};
+
+	pr_devel("enter\n");
+	/* Set global variable ready_for_ipc true */
+#ifdef DEBUG
+	/*
+	 * In real life ready_for_ipc doesn't change, but it's
+	 * convenient for testing.
+	 */
+	parent_dev->ready_for_ipc = !parent_dev->ready_for_ipc;
+#else
+	parent_dev->ready_for_ipc = true;
+#endif
+
+	xshm_ipc_ready();
+
+	genio_register_errhandler(genio_errhandler);
+
+	pr_devel("call genio_subscribe_caif_ready()\n");
+	err = genio_subscribe_caif_ready(genio_caif_ready_cb);
+	if (err < 0)
+		pr_debug("genio_subscribe_caif_ready failed:%d\n", err);
+
+	/* Collect the bit-mask for GENIO bits */
+	xshm_foreach_dev(collect_bits, &bits);
+	pr_devel("call genio_bit_alloc(%x,%x)\n", bits.setter, bits.getter);
+	err = genio_bit_alloc(bits.setter, bits.getter);
+	if (err < 0)
+		pr_debug("genio_bit_alloc failed:%d\n", err);
+	xshm_foreach_dev(handle_open, NULL);
+}
+EXPORT_SYMBOL(genio_ipc_ready_cb);
+
+int xshm_request_firmware(void *context, struct module *mod,
+		const char *img_name,
+		void (*cb)(const struct firmware *, void *))
+{
+	pr_debug("firmware request\n");
+	return request_firmware_nowait(mod,
+			FW_ACTION_NOHOTPLUG,
+			img_name,
+			&parent_dev->dev,
+			GFP_KERNEL,
+			context,
+			cb);
+}
+EXPORT_SYMBOL(xshm_request_firmware);
+
+static void parent_release(struct device *dev)
+{
+	struct xshm_parent *parent = container_of(dev, struct xshm_parent, dev);
+	WARN_ON(parent_dev != parent);
+	kfree(parent);
+	parent_dev = NULL;
+}
+
+static int __init xshm_init(void)
+{
+	int err;
+
+	pr_devel("Initializing\n");
+
+	parent_dev = kzalloc(sizeof(*parent_dev), GFP_KERNEL);
+	if (parent_dev  == NULL)
+		return -ENOMEM;
+
+	dev_set_name(&parent_dev->dev, "xshm");
+	parent_dev->dev.release = parent_release;
+
+	/* Pre-calculate inactivity timeout. */
+	if (xshm_inactivity_timeout != -1) {
+		parent_dev->inactivity_timeout =
+				xshm_inactivity_timeout * HZ / 1000;
+		if (parent_dev->inactivity_timeout == 0)
+			parent_dev->inactivity_timeout = 1;
+		else if (parent_dev->inactivity_timeout > NEXT_TIMER_MAX_DELTA)
+			parent_dev->inactivity_timeout = NEXT_TIMER_MAX_DELTA;
+	} else
+		parent_dev->inactivity_timeout = NEXT_TIMER_MAX_DELTA;
+
+	spin_lock_init(&parent_dev->timer_lock);
+	init_timer(&parent_dev->inactivity_timer);
+	parent_dev->inactivity_timer.data = 0L;
+	parent_dev->inactivity_timer.function = inactivity_tout;
+
+	err = device_register(&parent_dev->dev);
+	if (err)
+		goto err;
+
+	err = device_create_file(&parent_dev->dev, &dev_attr_ipc_ready);
+	if (err)
+		goto err_unreg;
+	err = device_create_file(&parent_dev->dev, &dev_attr_caif_ready);
+	if (err)
+		goto err_unreg;
+
+	return err;
+err_unreg:
+	pr_debug("initialization failed\n");
+	device_unregister(&parent_dev->dev);
+err:
+	return err;
+}
+
+static void handle_close(struct xshm_dev *dev, void *data)
+{
+	if (dev->close_cb)
+		dev->close_cb(dev->driver_data);
+}
+
+void close_devices(void)
+{
+	xshm_foreach_dev(handle_close, NULL);
+}
+
+static void handle_reset(struct xshm_dev *dev, void *data)
+{
+	if (dev->close_cb)
+		dev->close_cb(dev->driver_data);
+	xshm_unregister_device(dev);
+}
+
+void xshm_reset(void)
+{
+	parent_dev->ready_for_ipc = false;
+	parent_dev->ready_for_caif = false;
+	xshm_foreach_dev(handle_reset, NULL);
+	reset_activity_tout();
+	genio_reset();
+}
+EXPORT_SYMBOL(xshm_reset);
+
+static void __exit xshm_exit(void)
+{
+	xshm_reset();
+	device_unregister(&parent_dev->dev);
+	genio_unsubscribe(READY_FOR_IPC_BIT);
+	genio_unsubscribe(READY_FOR_CAIF_BIT);
+}
+
+module_init(xshm_init);
+module_exit(xshm_exit);
-- 
1.7.1


^ permalink raw reply related	[flat|nested] 12+ messages in thread

* [PATCHv4 08/11] xshm: XSHM Configuration data handling
  2011-12-16 10:49 [PATCHv4 00/11] XSHM: Shared Memory Driver for ST-E Thor M7400 LTE modem Sjur Brændeland
                   ` (6 preceding siblings ...)
  2011-12-16 10:49 ` [PATCHv4 07/11] xshm: Add xshm device implementation Sjur Brændeland
@ 2011-12-16 10:49 ` Sjur Brændeland
  2011-12-16 10:49 ` [PATCHv4 09/11] xshm: Character device for XSHM channel access Sjur Brændeland
                   ` (2 subsequent siblings)
  10 siblings, 0 replies; 12+ messages in thread
From: Sjur Brændeland @ 2011-12-16 10:49 UTC (permalink / raw)
  To: linux-kernel; +Cc: Arnd Bergmann, Linus Walleij, sjurbren, Sjur Brændeland

This patch implements the configuration handling:
- reception of gen-netlink requests from user-space.
- calculating address of where to put information in shared memory, i.e
  the offset of: IPC-TOC, channel-descriptors, read/write indexes for each
  directional channel, and data area of each channel.
- formatting of the shared memory area, so modem can read out configuration
  data.
- Creation of configuration data given to the XSHM drivers: xshm_chr and
  caif_xshm.

Signed-off-by: Sjur Brændeland <sjur.brandeland@stericsson.com>
---
 drivers/xshm/xshm_boot.c | 1150 ++++++++++++++++++++++++++++++++++++++++++++++
 1 files changed, 1150 insertions(+), 0 deletions(-)
 create mode 100644 drivers/xshm/xshm_boot.c

diff --git a/drivers/xshm/xshm_boot.c b/drivers/xshm/xshm_boot.c
new file mode 100644
index 0000000..5e61d53
--- /dev/null
+++ b/drivers/xshm/xshm_boot.c
@@ -0,0 +1,1150 @@
+/*
+ * Copyright (C) ST-Ericsson AB 2011
+ * Author:	Sjur Brændeland / sjur.brandeland@stericsson.com
+ * License terms: GNU General Public License (GPL) version 2
+ */
+
+#define pr_fmt(fmt) KBUILD_MODNAME ": %s :" fmt, __func__
+#include <linux/version.h>
+#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/kobject.h>
+#include <linux/string.h>
+#include <linux/sysfs.h>
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/slab.h>
+#include <linux/ctype.h>
+#include <linux/crc-ccitt.h>
+#include <linux/kdev_t.h>
+#include <net/netlink.h>
+#include <net/genetlink.h>
+#include "xshm_ipctoc.h"
+#include <linux/xshm/xshm_dev.h>
+#include <linux/xshm/xshm_netlink.h>
+#include <linux/c2c_genio.h>
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Sjur Brændland <sjur.brandeland@stericsson.com>");
+
+#define XSHM_VERSION	0x1
+#define XSHM_SUBVER	0x0
+#define TOC_SZ		512
+#define IMG_MAX_SZ	65536
+#define XSHM_ALIGNMT	sizeof(u32)
+#define XSHM_MIN_CHSZ 3
+#define XSHM_PAYL_ALIGN max(32, L1_CACHE_BYTES)
+#define STE_BOOTIMG_NAME "ste-bootimg.bin"
+
+#define GET_OFFSET(base, ptr) (((u8 *)(ptr)) - ((u8 *)(base)))
+#define OFFS2PTR(base, offs) ((void *) ((u8 *)base + offs))
+#define LEOFFS2PTR(base, offs) ((void *) ((u8 *)base + le32_to_cpu(offs)))
+
+/* Structure use in debug mode for integrity checking */
+struct ipctoc_hash {
+	u16 img_hash;
+	u16 ch_hash;
+	u16 ch_size;
+};
+
+struct xshm_modem {
+	bool config_error;
+	bool commited;
+	bool registered;
+	bool addr_set;
+	u32 modem_bootimg_size;
+	void *shm_start;
+	u32 channels;
+	struct xshm_channel *channel[XSHM_MAX_CHANNELS + 1];
+	struct xshm_ipctoc *ipctoc;
+	bool gennetl_reg;
+};
+
+static unsigned long xshm_start;
+module_param(xshm_start, ulong, S_IRUGO | S_IWUSR);
+MODULE_PARM_DESC(xshm_start, "Address for memory shared by host/modem.");
+
+static unsigned long xshm_c2c_bootaddr;
+module_param(xshm_c2c_bootaddr, ulong, S_IRUGO | S_IWUSR);
+MODULE_PARM_DESC(xshm_c2c_bootaddr,
+			"Address given to modem (through GENI register)");
+
+static long xshm_size;
+module_param(xshm_size, long, S_IRUGO | S_IWUSR);
+MODULE_PARM_DESC(xshm_size, "Size of SHM area");
+
+#ifdef DEBUG
+
+/* In debug mode we pad around all payload area in order to detect overwrite */
+#define MAGIC_PAD_LEN 32
+#define MAGIC_PAD 0xbc
+
+/* Verify a magic-pad area */
+static inline bool padok(void *mag)
+{
+	u32 *p = mag, v = 0xbcbcbcbc;
+	int i;
+
+	for (i = 0; i < 8; i++)
+		if (*p++ != v)
+			return false;
+	return true;
+}
+
+/* Insert a magic-pad area */
+static inline void add_magic_pad(u32 *offset, void *base)
+{
+	if (*offset < xshm_size)
+		memset(base + *offset, MAGIC_PAD, MAGIC_PAD_LEN);
+	*offset += MAGIC_PAD_LEN;
+}
+
+/* Abuse the pad area to create a checksum of the ipc-toc and descriptors */
+static inline void store_checksum(struct xshm_modem *modem, u32 size)
+{
+	struct ipctoc_hash *hash = (void *)modem->ipctoc;
+	--hash;
+	hash->img_hash =
+		crc_ccitt(0xffff, (u8 *) modem->shm_start,
+				modem->modem_bootimg_size);
+	hash->ch_hash = crc_ccitt(0xffff, (u8 *) modem->ipctoc, size);
+	hash->ch_size = size;
+}
+
+/* Verify that shm config has not been accidently tampered. */
+static inline bool ok_checksum(struct xshm_modem *modem,
+				struct xshm_ipctoc *ipctoc)
+{
+	struct ipctoc_hash *hash = (void *) ipctoc;
+	u16 new_hash, new_imghash;
+	int i;
+	u8 *p;
+
+	if (!modem->commited)
+		return false;
+
+	for (i = 0; i < modem->channels; i++) {
+		struct xshm_ipctoc_channel *ch;
+
+		ch = LEOFFS2PTR(modem->shm_start,
+				ipctoc->channel_offsets[i].rx);
+		p = LEOFFS2PTR(modem->shm_start, ch->ipc);
+		if (!padok(p - MAGIC_PAD_LEN))
+			return false;
+		p = LEOFFS2PTR(modem->shm_start, ch->offset);
+		if (!padok(p - MAGIC_PAD_LEN))
+			return false;
+		ch = LEOFFS2PTR(modem->shm_start,
+				ipctoc->channel_offsets[i].tx);
+		p = LEOFFS2PTR(modem->shm_start, ch->ipc);
+		if (!padok(p - MAGIC_PAD_LEN))
+			return false;
+		p = LEOFFS2PTR(modem->shm_start, ch->offset);
+		if (!padok(p - MAGIC_PAD_LEN))
+			return false;
+	}
+
+	--hash;
+	new_hash = crc_ccitt(0xffff, (u8 *) ipctoc, hash->ch_size);
+	new_imghash =
+		crc_ccitt(0xffff, (u8 *) modem->shm_start,
+				modem->modem_bootimg_size);
+	pr_debug("Hash result:size:%d chksm:%u/%u img:%u/%u\n",
+			hash->ch_size, hash->ch_hash, new_hash,
+			hash->img_hash, new_imghash);
+	return hash->ch_hash == new_hash && hash->img_hash == new_imghash;
+}
+
+static inline void init_data(void *shm_start, u32 offset,
+				int ch, u32 size)
+{
+	memset((u8 *)shm_start + offset, ch + 1, size);
+}
+#else
+#define MAGIC_PAD_LEN 0
+static inline void add_magic_pad(u32 *offset, void *base)
+{
+}
+static inline void store_checksum(struct xshm_modem *modem, u32 size)
+{
+}
+static inline bool ok_checksum(struct xshm_modem *modem, void *ipctoc)
+{
+	return true;
+}
+static inline void init_data(struct xshm_modem *modem, u32 offs, int ch,
+				u32 size)
+{
+}
+#endif
+
+/* write_to_shm - Write SHM Channel descriptors to SHM.
+ *
+ * Based on the configuration data channel configuration
+ * is written to the shared memory area.
+ * This is the data layout:
+ *
+ * +------------+  <---- xshm_start
+ * |	TOC	|
+ * +------------+
+ * | Boot IMG	|
+ * +------------+ <---- rw_start
+ * | RW Data	|
+ * +------------+
+ * | RW Buf idx |
+ * +------------+ <---- ipctoc
+ * | IPC TOC	|
+ * +------------+
+ * | RW Ch Decr |
+ * +------------+ <---- ro_start
+ * | RO Ch Decr |
+ * +------------+
+ * | RO Buf idx |
+ * +------------+
+ * | RO Data	|
+ * +------------+
+ */
+
+static int write_to_shm(struct xshm_modem *modem)
+{
+	int i, pri, bitno;
+	u32 offset, ro_start, rw_start, ipctoc_offs, ipcro_offs;
+	bool found;
+	struct xshm_ipctoc_channel *ch;
+	struct toc_entry *toc_entry;
+	struct xshm_bufidx *bix;
+
+	/*
+	 * Find where to put IPC-TOC by adding up
+	 * the size of Payload buffers pluss buf-indices
+	 */
+	ipctoc_offs = ALIGN(modem->modem_bootimg_size, XSHM_PAYL_ALIGN);
+	rw_start = ipctoc_offs;
+	for (i = 0; i < modem->channels; i++) {
+		int n = modem->channel[i]->tx.buffers;
+		ipctoc_offs += MAGIC_PAD_LEN;
+		ipctoc_offs += offsetof(struct xshm_bufidx, size[n + 2]);
+		ipctoc_offs = ALIGN(ipctoc_offs, XSHM_PAYL_ALIGN);
+		ipctoc_offs += MAGIC_PAD_LEN;
+		ipctoc_offs += modem->channel[i]->tx.ch_size;
+		ipctoc_offs = ALIGN(ipctoc_offs, XSHM_PAYL_ALIGN);
+	}
+	add_magic_pad(&ipctoc_offs, modem->shm_start);
+	pr_debug("IPC toc @ %08x\n", ipctoc_offs);
+
+	/*
+	 * Allocate the IPC-TOC and, initiatlize it.
+	 * The IPC toc will be located after the RW Data and
+	 * buffer indices.
+	 */
+	offset = ipctoc_offs;
+	modem->ipctoc = OFFS2PTR(modem->shm_start, ipctoc_offs);
+	modem->ipctoc->magic[0] = XSHM_IPCTOC_MAGIC1;
+	modem->ipctoc->magic[1] = XSHM_IPCTOC_MAGIC2;
+	modem->ipctoc->version = XSHM_VERSION;
+	modem->ipctoc->subver = XSHM_SUBVER;
+	memset(modem->ipctoc->channel_offsets, 0,
+		sizeof(modem->ipctoc->channel_offsets));
+
+	/* Find start of first channel descriptor */
+	offset += sizeof(struct xshm_ipctoc);
+
+	/*
+	 * Allocate the location for the RW Channel descriptors.
+	 * It will be located after the IPC-TOC.
+	 */
+	offset = ALIGN(offset, XSHM_ALIGNMT);
+	for (i = 0; i < modem->channels; i++) {
+		pr_debug("Channel descriptor %d RW @ 0x%08x\n", i, offset);
+		modem->ipctoc->channel_offsets[i].tx = cpu_to_le32(offset);
+		offset += sizeof(struct xshm_ipctoc_channel);
+		offset = ALIGN(offset, XSHM_ALIGNMT);
+		if (offset > xshm_size)
+			goto badsize;
+	}
+	ro_start = offset;
+
+	/*
+	 * Allocate the location for the RO Channel descriptors.
+	 * It will be located after the RW Channels.
+	 */
+	for (i = 0; i < modem->channels; i++) {
+		pr_debug("Channel descriptor %d RO @ 0x%08x\n", i, offset);
+		modem->ipctoc->channel_offsets[i].rx = cpu_to_le32(offset);
+		offset += sizeof(struct xshm_ipctoc_channel);
+		offset = ALIGN(offset, XSHM_ALIGNMT);
+		if (offset > xshm_size)
+			goto badsize;
+	}
+
+	/*
+	 * Allocate the location for the RO Buffer Indices.
+	 * It will be located after the RO Channels.
+	 */
+	offset = ALIGN(offset, XSHM_PAYL_ALIGN);
+	ipcro_offs = offset;
+	for (i = 0; i < modem->channels; i++) {
+		int n = modem->channel[i]->rx.buffers;
+		ch = LEOFFS2PTR(modem->shm_start,
+				modem->ipctoc->channel_offsets[i].rx);
+		add_magic_pad(&offset, modem->shm_start);
+		ch->ipc = cpu_to_le32(offset);
+
+		bix = OFFS2PTR(modem->shm_start, offset);
+		bix->read_index = cpu_to_le32(0);
+		bix->write_index = cpu_to_le32(0);
+		bix->state = cpu_to_le32(XSHM_CLOSED);
+		bix->size[0] = cpu_to_le32(0);
+
+		pr_debug("IPC RO[%d] @: 0x%08x\n",  i, offset);
+		offset += offsetof(struct xshm_bufidx, size[n + 2]);
+		offset = ALIGN(offset, XSHM_PAYL_ALIGN);
+		if (offset > xshm_size)
+			goto badsize;
+	}
+
+	/*
+	 * Allocate RO Data Area. This will located after
+	 * the RO Buffer Indices at the end of the Share Memory
+	 * area.
+	 */
+	offset = ALIGN(offset, XSHM_PAYL_ALIGN);
+	for (i = 0; i < modem->channels; i++) {
+		u8 align;
+		u32 size;
+		ch = LEOFFS2PTR(modem->shm_start,
+				modem->ipctoc->channel_offsets[i].rx);
+		add_magic_pad(&offset, modem->shm_start);
+		ch->offset = cpu_to_le32(offset);
+
+		BUILD_BUG_ON(sizeof(ch->mode) != 1);
+		ch->mode = modem->channel[i]->mode & XSHM_MODE_MASK;
+		ch->buffers = cpu_to_le32(modem->channel[i]->rx.buffers);
+		align = rounddown_pow_of_two(modem->channel[i]->rx.alignment);
+		ch->alignment = align;
+		ch->packets = modem->channel[i]->rx.packets;
+		ch->mtu = modem->channel[i]->rx.mtu;
+		size = modem->channel[i]->tx.ch_size;
+		if (modem->channel[i]->mode & XSHM_PACKET_MODE) {
+			u32 buf_size;
+			buf_size = size / modem->channel[i]->tx.buffers;
+			buf_size = rounddown(buf_size, align);
+			size = buf_size * modem->channel[i]->tx.buffers;
+		}
+		pr_debug("Buffer area RO for Channel[%d] at: 0x%08x size:%d\n",
+				i, offset, size);
+		ch->size = cpu_to_le32(size);
+
+		init_data(modem->shm_start, offset, i,
+				modem->channel[i]->rx.ch_size);
+		offset += modem->channel[i]->rx.ch_size;
+		offset = ALIGN(offset, XSHM_PAYL_ALIGN);
+		if (offset > xshm_size)
+			goto badsize;
+	}
+
+	/*
+	 * Allocate RW Data Area. This will located in the beginning
+	 * just after the Modem Boot Image.
+	 */
+	offset = rw_start;
+	for (i = 0; i < modem->channels; i++) {
+		u8 align;
+		u32 size;
+		ch = LEOFFS2PTR(modem->shm_start,
+				modem->ipctoc->channel_offsets[i].tx);
+		add_magic_pad(&offset, modem->shm_start);
+		ch->offset = cpu_to_le32(offset);
+		init_data(modem->shm_start, offset, i,
+				modem->channel[i]->tx.ch_size);
+		ch->mode = modem->channel[i]->mode &
+				XSHM_MODE_MASK;
+		ch->buffers = cpu_to_le32(modem->channel[i]->tx.buffers);
+		align = rounddown_pow_of_two(modem->channel[i]->rx.alignment);
+		ch->alignment = align;
+		ch->packets = modem->channel[i]->rx.packets;
+		ch->mtu = modem->channel[i]->rx.mtu;
+		size = modem->channel[i]->tx.ch_size;
+		if (modem->channel[i]->mode & XSHM_PACKET_MODE) {
+			u32 buf_size;
+			buf_size = size / modem->channel[i]->tx.buffers;
+			buf_size = rounddown(buf_size, align);
+			size = buf_size * modem->channel[i]->tx.buffers;
+		}
+		ch->size = cpu_to_le32(size);
+		pr_debug("Buffer area RW for Channel[%d] at: 0x%08x size:%d\n",
+				i, offset, size);
+		offset += modem->channel[i]->tx.ch_size;
+		offset = ALIGN(offset, XSHM_PAYL_ALIGN);
+		if (offset > ro_start)
+			goto badsize;
+	}
+
+	/*
+	 * Allocate RW IPC Area. This will located after RW data area,
+	 * just before the IPC-TOC.
+	 */
+	offset = ALIGN(offset, XSHM_PAYL_ALIGN);
+	for (i = 0; i < modem->channels; i++) {
+		int n = modem->channel[i]->tx.buffers;
+		ch = LEOFFS2PTR(modem->shm_start,
+				modem->ipctoc->channel_offsets[i].tx);
+		add_magic_pad(&offset, modem->shm_start);
+		ch->ipc = cpu_to_le32(offset);
+		bix = OFFS2PTR(modem->shm_start, offset);
+		bix->read_index = cpu_to_le32(0);
+		bix->write_index = cpu_to_le32(0);
+		bix->state = cpu_to_le32(XSHM_CLOSED);
+		bix->size[0] = cpu_to_le32(0);
+
+		pr_debug("IPC RW[%d] @: 0x%08x\n",  i, offset);
+		offset += offsetof(struct xshm_bufidx, size[n + 2]);
+		offset = ALIGN(offset, XSHM_PAYL_ALIGN);
+		if (offset > xshm_size)
+			goto badsize;
+	}
+
+	/* Allocate genio bits for each channel according to priority*/
+	bitno = 0;
+	for (pri = 0; pri < 8; pri++) {
+		for (i = 0; i < modem->channels; i++) {
+			if (modem->channel[i]->priority == pri) {
+				ch = LEOFFS2PTR(modem->shm_start,
+					modem->ipctoc->channel_offsets[i].tx);
+				ch->write_bit = cpu_to_le16(bitno * 4);
+				ch->read_bit = cpu_to_le16(bitno * 4 + 2);
+				ch = LEOFFS2PTR(modem->shm_start,
+					modem->ipctoc->channel_offsets[i].rx);
+				ch->write_bit = cpu_to_le16(bitno * 4 + 1);
+				ch->read_bit = cpu_to_le16(bitno * 4 + 3);
+				bitno++;
+			}
+		}
+	}
+
+	/*
+	 * The Master TOC points out the boot images for the modem,
+	 * Use the first avilable entry in the toc to write the pointer,
+	 * to the IPC-TOC defined above.
+	 */
+	found = false;
+	for (toc_entry = modem->shm_start, i = 0; i < 16; i++, toc_entry++)
+		if (toc_entry->start == cpu_to_le32(0xffffffff)) {
+			pr_debug("IPCTOC address written into Master TOC"
+					" @ 0x%08x\n", i * 32);
+			toc_entry->start =
+				cpu_to_le32(GET_OFFSET(modem->shm_start,
+							modem->ipctoc));
+			toc_entry->size = cpu_to_le32(0);
+			toc_entry->flags = cpu_to_le32(0);
+			toc_entry->entry_point = cpu_to_le32(0);
+			toc_entry->load_addr = cpu_to_le32(0xffffffff);
+			memset(toc_entry->name, 0, sizeof(toc_entry->name));
+			sprintf(toc_entry->name, "ipc-toc");
+			found = true;
+			break;
+		}
+	if (!found) {
+		pr_debug("Cannot insert IPC-TOC in toc\n");
+		goto bad_config;
+	}
+
+	store_checksum(modem, ipcro_offs - ipctoc_offs);
+
+	return 0;
+
+badsize:
+	pr_debug("IPCTOC not enough space offset (size:0x%lx offset:0x%x\n",
+			xshm_size, offset);
+	return -ENOSPC;
+
+bad_config:
+	pr_debug("IPCTOC bad configuration data\n");
+	return -EINVAL;
+}
+
+static int xshm_verify_config(struct xshm_modem *modem,
+				struct xshm_channel *xcfg)
+{
+	int j;
+
+	if ((xcfg->mode & XSHM_MODE_MASK) != XSHM_PACKET_MODE &&
+			(xcfg->mode & XSHM_MODE_MASK) != XSHM_STREAM_MODE) {
+		pr_debug("Bad config:"
+				"channel mode must be set\n");
+		return -EINVAL;
+	}
+	if (xcfg->mode & XSHM_PACKET_MODE && xcfg->rx.buffers < 2) {
+		pr_debug("Bad config:minimum 2 buffers "
+				"must be set for packet mode\n");
+		return -EINVAL;
+	}
+
+	if (xcfg->rx.ch_size < XSHM_MIN_CHSZ) {
+		pr_debug("Bad config:"
+				"Channel size must be larger than %d\n",
+				XSHM_MIN_CHSZ);
+		return -EINVAL;
+	}
+
+	if (xcfg->mode & XSHM_PACKET_MODE) {
+		if (xcfg->tx.buffers < 2) {
+			pr_debug("Bad config:"
+				"buffers must be minimum 2 packet mode\n");
+			return -EINVAL;
+		}
+		if (xcfg->tx.packets < 1) {
+			pr_debug("Bad config:"
+				"packets must be set for packet mode\n");
+			return -EINVAL;
+		}
+	}
+
+	if (xcfg->tx.ch_size < XSHM_MIN_CHSZ) {
+		pr_debug("Bad config:"
+				"Channel size must be larger than %d\n",
+				XSHM_MIN_CHSZ);
+		return -EINVAL;
+	}
+
+	if (xcfg->name[0] == '\0') {
+		pr_debug("Channel must be named\n");
+		return -EINVAL;
+	}
+	for (j = 0; j < modem->channels; j++) {
+		struct xshm_channel *xcfg2 = modem->channel[j];
+		if (xcfg != xcfg2 && strcmp(xcfg->name, xcfg2->name) == 0) {
+			pr_debug("Channels has same name:%s\n",
+					 xcfg->name);
+			return -EINVAL;
+		}
+	}
+	return 0;
+}
+
+static int verify_config(struct xshm_modem *modem)
+{
+	int i;
+
+	if (modem->channels == 0) {
+		pr_debug("Bad config: minimum one channel must be defined\n");
+		return -EINVAL;
+	}
+	for (i = 0; i < modem->channels; i++) {
+		int err = xshm_verify_config(modem, modem->channel[i]);
+		if (err)
+			return err;
+	}
+	return 0;
+}
+
+/*
+ * Create Configuration data for the xshm devices.
+ */
+static void create_devs(struct xshm_modem *modem)
+{
+	int i;
+
+	for (i = 0; i < modem->channels; i++) {
+		struct xshm_bufidx *buf_rx, *buf_tx;
+		struct xshm_ipctoc_channel *ch_rx, *ch_tx;
+		struct xshm_channel *xcfg = modem->channel[i];
+		ch_rx = LEOFFS2PTR(modem->shm_start,
+				modem->ipctoc->channel_offsets[i].rx);
+		buf_rx = LEOFFS2PTR(modem->shm_start, ch_rx->ipc);
+		ch_tx = LEOFFS2PTR(modem->shm_start,
+				modem->ipctoc->channel_offsets[i].tx);
+		buf_tx = LEOFFS2PTR(modem->shm_start, ch_tx->ipc);
+
+		/*
+		 * Due to restricted read-only access
+		 * we swap positions for read/write
+		 * pointers.
+		 */
+		xcfg->tx.write = &buf_tx->write_index;
+		xcfg->tx.read = &buf_rx->read_index;
+
+		xcfg->rx.write = &buf_rx->write_index;
+		xcfg->rx.read = &buf_tx->read_index;
+
+		xcfg->rx.addr = LEOFFS2PTR(modem->shm_start, ch_rx->offset);
+		xcfg->tx.addr = LEOFFS2PTR(modem->shm_start, ch_tx->offset);
+		xcfg->rx.state = &buf_rx->state;
+		xcfg->tx.state = &buf_tx->state;
+		xcfg->tx.buf_size = buf_tx->size;
+		xcfg->rx.buf_size = buf_rx->size;
+
+		xcfg->rx.xfer_bit = le16_to_cpu(ch_rx->write_bit);
+		xcfg->tx.xfer_bit = le16_to_cpu(ch_tx->write_bit);
+		xcfg->rx.xfer_done_bit = le16_to_cpu(ch_rx->read_bit);
+		xcfg->tx.xfer_done_bit = le16_to_cpu(ch_tx->read_bit);
+
+		if (xcfg->mode & XSHM_PAIR_MODE) {
+			struct xshm_channel *pair;
+			pr_debug("Channel[%d] is in PAIR mode\n", i);
+			if (i < 1) {
+				pr_debug("No channel to pair with\n");
+				continue;
+			}
+			/* Cross couple rx/tx on the pair */
+			pair = modem->channel[i - 1];
+
+			/* Copy everything but the kobj which is at the end */
+			memcpy(&xcfg->tx, &pair->rx,
+					offsetof(struct xshm_udchannel, kobj));
+			memcpy(&xcfg->rx, &pair->tx,
+					offsetof(struct xshm_udchannel, kobj));
+		} else if (xcfg->mode & XSHM_LOOP_MODE) {
+			pr_debug("Channel[%d] is in LOOP mode\n", i);
+			/*
+			 * Connect rx/tx in a pair. Copy everything,
+			 * but the kobj which is at the end
+			 */
+			memcpy(&xcfg->tx, &xcfg->rx,
+					offsetof(struct xshm_udchannel, kobj));
+		}
+
+		pr_devel("RX[%d] wi:%p ri:%p\n", i, xcfg->rx.read,
+				xcfg->rx.write);
+		pr_devel("TX[%d] wi:%p ri:%p\n", i, xcfg->tx.read,
+				xcfg->tx.write);
+	}
+}
+
+struct xshm_modem *modem;
+
+struct xshm_modem *get_modem(struct sk_buff *skb, struct genl_info *info)
+{
+	return modem;
+}
+
+static int do_commit(struct xshm_modem *modem)
+{
+	int err;
+
+	if (modem->config_error) {
+		pr_devel("config error detected\n");
+		return -EINVAL;
+	}
+
+	if (modem->commited) {
+		pr_devel("already commited\n");
+		modem->config_error = true;
+		return -EINVAL;
+	}
+	err = verify_config(modem);
+	if (err) {
+		pr_devel("bad config\n");
+		modem->config_error = true;
+		return err;
+	}
+	err = write_to_shm(modem);
+	if (err) {
+		pr_devel("writei to SHM failed\n");
+		modem->config_error = true;
+		return err;
+	}
+	modem->commited = true;
+	create_devs(modem);
+	return 0;
+}
+
+static int do_register(struct xshm_modem *modem)
+{
+	int err;
+
+	if (!modem->commited || modem->registered || modem->config_error) {
+		pr_devel("bad sequence of requests\n");
+		modem->config_error = true;
+		return -EINVAL;
+	}
+
+	err = verify_config(modem);
+	if (err) {
+		modem->config_error = true;
+		pr_devel("bad config\n");
+		return err;
+	}
+	modem->registered = true;
+
+	xshm_register_devices(modem->channel, modem->channels);
+
+	return 0;
+}
+
+static void do_reset(struct xshm_modem *modem)
+{
+	int i;
+
+	xshm_reset();
+	modem->config_error = false;
+	modem->registered = false;
+	modem->commited = false;
+	modem->addr_set = false;
+	modem->modem_bootimg_size = TOC_SZ;
+	for (i = 0; i < modem->channels; i++) {
+		kfree(modem->channel[i]);
+		modem->channel[i] = NULL;
+	}
+	modem->channels = 0;
+}
+
+static int do_set_addr(struct xshm_modem *modem)
+{
+	int err;
+	if (!modem->commited || modem->addr_set || modem->config_error) {
+		pr_devel("bad sequence of requests\n");
+		modem->config_error = true;
+		return -EINVAL;
+	}
+	err = verify_config(modem);
+	if (err) {
+		modem->config_error = true;
+		pr_devel("bad config\n");
+		return err;
+	}
+	modem->addr_set = true;
+	return genio_set_shm_addr(xshm_c2c_bootaddr, genio_ipc_ready_cb);
+}
+
+static int copy_name(const char *src, char *d, size_t count)
+{
+	const char *s, *end = src + count;
+	for (s = src; *s && s < end; s++, d++)
+		if (*s == '\0' || *s == '\n')
+			break;
+		else if (!isalnum(*s)) {
+			pr_debug("Illegal chr:'%c' in name:'%s'\n", *s, src);
+			return -EINVAL;
+		} else if (s - src >= XSHM_NAMESZ - 1) {
+			pr_debug("Name '%s'too long\n", src);
+			return -EINVAL;
+		} else
+			*d = *s;
+	*d = '\0';
+
+	return count;
+}
+
+/* XSHM Generic NETLINK family */
+static struct genl_family xshm_gnl_family = {
+	.id = GENL_ID_GENERATE,
+	.hdrsize = 0,
+	.name = "XSHM",
+	.version = XSHM_PROTO_VERSION,
+	.maxattr = XSHM_A_MAX,
+};
+
+/* XSHM Netlink attribute policy */
+static const struct nla_policy xshm_genl_policy[XSHM_A_MAX + 1] = {
+	[XSHM_A_VERSION] = { .type = NLA_U8 },
+	[XSHM_A_SUB_VERSION] = { .type = NLA_U8 },
+	[__XSHM_A_FLAGS] = { .type = NLA_U32 },
+	[XSHM_A_NAME] = { .type = NLA_NUL_STRING, .len = XSHM_NAMESZ},
+	[XSHM_A_RX_CHANNEL] = { .type = NLA_NESTED },
+	[XSHM_A_TX_CHANNEL] = { .type = NLA_NESTED },
+	[XSHM_A_PRIORITY] = { .type = NLA_U8 },
+	[XSHM_A_LATENCY] = { .type = NLA_U8 },
+};
+
+/* Policy for uni-directional attributes for stream */
+static const struct nla_policy stream_policy[XSHM_A_MAX + 1] = {
+	[XSHM_A_CHANNEL_SIZE] = { .type = NLA_U32 },
+};
+
+/* Policy for uni-directional attributes for packet */
+static const struct nla_policy packet_policy[XSHM_A_MAX + 1] = {
+	[XSHM_A_CHANNEL_SIZE] = { .type = NLA_U32 },
+	[XSHM_A_CHANNEL_BUFFERS] = { .type = NLA_U32 },
+	[XSHM_A_MTU] = { .type = NLA_U16 },
+	[XSHM_A_ALIGNMENT] = { .type = NLA_U8 },
+	[XSHM_A_PACKETS] = { .type = NLA_U8 },
+};
+
+static int xshm_add_udchannel(struct xshm_udchannel *chn, int attr,
+			struct genl_info *info, struct nla_policy const *policy)
+{
+	struct nlattr *nla;
+	int nla_rem;
+
+	if (!info->attrs[attr])
+		return -EINVAL;
+
+	if (nla_validate_nested(info->attrs[attr],
+					XSHM_A_MAX,
+					policy) != 0) {
+		pr_info("Invalid RX channel attributes\n");
+		return -EINVAL;
+	}
+
+	nla_for_each_nested(nla, info->attrs[attr], nla_rem) {
+
+		if (nla_type(nla) == XSHM_A_CHANNEL_SIZE)
+			chn->ch_size = nla_get_u32(nla);
+
+		if (nla_type(nla) == XSHM_A_CHANNEL_BUFFERS)
+			chn->buffers = nla_get_u32(nla);
+
+		if (nla_type(nla) == XSHM_A_MTU)
+			chn->mtu = nla_get_u16(nla);
+
+		if (nla_type(nla) == XSHM_A_PACKETS)
+			chn->packets = nla_get_u8(nla);
+
+		if (nla_type(nla) == XSHM_A_ALIGNMENT) {
+			chn->alignment = nla_get_u8(nla);
+			chn->alignment = rounddown_pow_of_two(chn->alignment);
+		}
+
+	}
+	return 0;
+}
+
+static int xshm_add_channel(struct xshm_channel *cfg, struct genl_info *info,
+			int mode)
+{
+	int len, err;
+	struct nla_policy const *policy;
+	char name[XSHM_NAMESZ];
+
+	policy = (mode == XSHM_PACKET_MODE) ? packet_policy : stream_policy;
+
+	if (info->attrs[XSHM_A_VERSION]) {
+		u8 version;
+		u8 sub_version;
+
+		version = nla_get_u8(info->attrs[XSHM_A_VERSION]);
+		if (!info->attrs[XSHM_A_SUB_VERSION])
+			return -EINVAL;
+		sub_version = nla_get_u8(info->attrs[XSHM_A_SUB_VERSION]);
+		if (version != 1 || sub_version != 0) {
+			pr_info("Bad version or sub_version\n");
+			return -EINVAL;
+		}
+	}
+
+	if (!info->attrs[XSHM_A_NAME]) {
+		pr_debug("Name not specified\n");
+		return -EINVAL;
+	}
+
+	len = nla_strlcpy(name, info->attrs[XSHM_A_NAME],
+			XSHM_NAMESZ);
+
+	if (len > XSHM_NAMESZ)
+		return -EINVAL;
+
+	err = copy_name(name, cfg->name, sizeof(name));
+	if (err < 0)
+		return err;
+
+	cfg->excl_group = 1;
+	if (info->attrs[XSHM_A_EXCL_GROUP])
+		cfg->excl_group = nla_get_u8(info->attrs[XSHM_A_EXCL_GROUP]);
+
+	err = xshm_add_udchannel(&cfg->rx, XSHM_A_RX_CHANNEL, info, policy);
+
+	if (err)
+		return err;
+	err = xshm_add_udchannel(&cfg->tx, XSHM_A_TX_CHANNEL, info, policy);
+
+	if (err)
+		return err;
+
+	if (info->attrs[XSHM_A_PRIORITY]) {
+		cfg->priority = nla_get_u8(info->attrs[XSHM_A_PRIORITY]);
+		/* silently fixup bad value */
+		if (cfg->priority > 7)
+			cfg->priority = 0;
+	}
+
+	if (info->attrs[XSHM_A_LATENCY])
+		cfg->latency = nla_get_u8(info->attrs[XSHM_A_LATENCY]);
+
+	if (info->attrs[__XSHM_A_FLAGS])
+		cfg->mode |= nla_get_u32(info->attrs[__XSHM_A_FLAGS]);
+
+
+	return 0;
+}
+
+static int do_reply(struct genl_info *info, int result)
+{
+	struct sk_buff *msg;
+	int err;
+	void *reply;
+
+	msg = nlmsg_new(NLMSG_DEFAULT_SIZE, GFP_KERNEL);
+	if (msg == NULL)
+		return -ENOMEM;
+
+	reply = genlmsg_put_reply(msg, info, &xshm_gnl_family, 0, result);
+	if (reply == NULL) {
+		kfree_skb(msg);
+		return -EMSGSIZE;
+	}
+
+	genlmsg_end(msg, reply);
+	err = genlmsg_reply(msg, info);
+	return err;
+}
+
+static int xshm_add_ch(struct sk_buff *skb, struct genl_info *info, int mode)
+{
+	int err;
+	struct xshm_channel *cfg;
+	struct xshm_modem *modem = get_modem(skb, info);
+	if (!modem)
+		return -EINVAL;
+
+	if (modem->channels + 1 > XSHM_MAX_CHANNELS) {
+		pr_debug("Too many channels added\n");
+		return -EINVAL;
+	}
+
+
+	cfg = kzalloc(sizeof(*cfg), GFP_KERNEL);
+	if (cfg == NULL)
+		return -ENOMEM;
+
+	cfg->mode = mode;
+	err = xshm_add_channel(cfg, info, mode);
+	if (err)
+		goto error;
+
+	modem->channel[modem->channels] = cfg;
+	++modem->channels;
+
+	err = xshm_verify_config(modem, cfg);
+	if (err)
+		goto error;
+
+	err = do_reply(info, 0);
+	if (err)
+		goto error;
+	return err;
+
+error:
+	--modem->channels;
+	kfree(cfg);
+	return err;
+}
+
+static int xshm_add_packet_ch(struct sk_buff *skb, struct genl_info *info)
+{
+	return xshm_add_ch(skb, info, XSHM_PACKET_MODE);
+}
+
+static int xshm_add_stream_ch(struct sk_buff *skb, struct genl_info *info)
+{
+	return xshm_add_ch(skb, info, XSHM_STREAM_MODE);
+}
+
+
+static int xshm_c_commit(struct sk_buff *skb, struct genl_info *info)
+{
+	struct xshm_modem *modem = get_modem(skb, info);
+	int err;
+	if (!modem)
+		return -EINVAL;
+
+	err = do_commit(modem);
+	if (!err)
+		do_reply(info, 0);
+	return err;
+}
+
+static int xshm_c_register(struct sk_buff *skb, struct genl_info *info)
+{
+	int err;
+	struct xshm_modem *modem = get_modem(skb, info);
+	if (!modem)
+		return -EINVAL;
+
+	err = do_register(modem);
+	if (!err)
+		do_reply(info, 0);
+	return err;
+}
+
+static int xshm_c_set_addr(struct sk_buff *skb, struct genl_info *info)
+{
+	int err;
+	struct xshm_modem *modem = get_modem(skb, info);
+	if (!modem)
+		return -EINVAL;
+
+	err = do_set_addr(modem);
+	if (!err)
+		do_reply(info, 0);
+	return err;
+}
+
+static int xshm_c_reset(struct sk_buff *skb, struct genl_info *info)
+{
+	struct xshm_modem *modem = get_modem(skb, info);
+	if (!modem)
+		return -EINVAL;
+
+	do_reset(modem);
+	do_reply(info, 0);
+	return 0;
+}
+
+static int xshm_c_verify(struct sk_buff *skb, struct genl_info *info)
+{
+	int err;
+	struct xshm_modem *modem = get_modem(skb, info);
+	if (!modem)
+		return -EINVAL;
+
+	err = verify_config(modem);
+	if (!err)
+		do_reply(info, 0);
+	return err;
+}
+
+static struct genl_ops xshm_genl_ops[] = {
+	{
+	.cmd = XSHM_C_ADD_STREAM_CHANNEL,
+	.flags = GENL_ADMIN_PERM,
+	.policy = xshm_genl_policy,
+	.doit = xshm_add_stream_ch,
+	.dumpit = NULL,
+	},
+	{
+	.cmd = XSHM_C_ADD_PACKET_CHANNEL,
+	.flags = GENL_ADMIN_PERM,
+	.policy = xshm_genl_policy,
+	.doit = xshm_add_packet_ch,
+	.dumpit = NULL,
+	},
+	{
+	.cmd = XSHM_C_COMMIT,
+	.flags = GENL_ADMIN_PERM,
+	.doit = xshm_c_commit,
+	.dumpit = NULL,
+	},
+	{
+	.cmd = XSHM_C_REGISTER,
+	.flags = GENL_ADMIN_PERM,
+	.doit = xshm_c_register,
+	.dumpit = NULL,
+	},
+	{
+	.cmd = XSHM_C_SET_ADDR,
+	.flags = GENL_ADMIN_PERM,
+	.doit = xshm_c_set_addr,
+	.dumpit = NULL,
+	},
+	{
+	.cmd = XSHM_C_RESET,
+	.flags = GENL_ADMIN_PERM,
+	.doit = xshm_c_reset,
+	.dumpit = NULL,
+	},
+	{
+	.cmd = __XSHM_C_VERIFY,
+	.flags = GENL_ADMIN_PERM,
+	.doit = xshm_c_verify,
+	.dumpit = NULL,
+	},
+
+};
+
+
+static void xshm_firmware(const struct firmware *fw, void *context)
+{
+	struct xshm_modem *modem = context;
+
+	if (fw == NULL) {
+		pr_warn("No boot-img provided\n");
+		return;
+	}
+
+	if (modem->commited) {
+		pr_warn("Received firmware too late in modem boot\n");
+		return;
+	}
+
+	pr_debug("recevied firmware\n");
+	memcpy(modem->shm_start, fw->data, fw->size);
+}
+
+/* Initialize boot handling and create sysfs entries*/
+int __init xshm_boot_init(void)
+{
+	int err = -EINVAL;
+	bool xshm_fake = false;
+
+	modem = kzalloc(sizeof(struct xshm_modem), GFP_KERNEL);
+	if (modem == NULL)
+		return -ENOMEM;
+
+	/* Negative xshm_size indicates module test without real SHM */
+	if (xshm_size < 0) {
+		xshm_fake = true;
+		xshm_size = abs(xshm_size);
+	}
+
+	if (xshm_size < TOC_SZ)
+		goto bad_config;
+
+	if (xshm_fake) {
+		modem->shm_start = kzalloc(xshm_size, GFP_KERNEL);
+		err = -ENOMEM;
+		if (!modem->shm_start)
+			goto error;
+		xshm_start = (unsigned long) modem->shm_start;
+		memset(modem->shm_start, 0xaa, xshm_size);
+	} else {
+		if (xshm_start == 0)
+			goto bad_config;
+		modem->shm_start = ioremap(xshm_start, xshm_size);
+	}
+
+	/* Initiate the Master TOC to 0xff for the first 512 bytes */
+	if (xshm_size > TOC_SZ)
+		memset(modem->shm_start, 0xff, TOC_SZ);
+
+	modem->modem_bootimg_size = TOC_SZ;
+
+	pr_debug("Boot image addr: %p size:%d\n", modem->shm_start,
+			modem->modem_bootimg_size);
+
+	err = genl_register_family_with_ops(&xshm_gnl_family,
+		xshm_genl_ops, ARRAY_SIZE(xshm_genl_ops));
+	if (err)
+		goto error;
+
+	modem->gennetl_reg = 1;
+
+	err = xshm_request_firmware(modem, THIS_MODULE,
+			STE_BOOTIMG_NAME, xshm_firmware);
+	if (err)
+		goto error;
+
+	return err;
+error:
+	pr_debug("initialization failed\n");
+
+	if (xshm_fake)
+		kfree(modem->shm_start);
+	return err;
+bad_config:
+	pr_err("Bad module configuration:"
+			" xshm_base_address:%lu xshm_size:%lu err:%d\n",
+			xshm_start, xshm_size, err);
+	/* Buildin module should not return error */
+	return -EINVAL;
+}
+
+void __exit xshm_boot_exit(void)
+{
+
+	if (modem->gennetl_reg)
+		genl_unregister_family(&xshm_gnl_family);
+	modem->gennetl_reg = 0;
+}
+module_init(xshm_boot_init);
+module_exit(xshm_boot_exit);
-- 
1.7.1


^ permalink raw reply related	[flat|nested] 12+ messages in thread

* [PATCHv4 09/11] xshm: Character device for XSHM channel access.
  2011-12-16 10:49 [PATCHv4 00/11] XSHM: Shared Memory Driver for ST-E Thor M7400 LTE modem Sjur Brændeland
                   ` (7 preceding siblings ...)
  2011-12-16 10:49 ` [PATCHv4 08/11] xshm: XSHM Configuration data handling Sjur Brændeland
@ 2011-12-16 10:49 ` Sjur Brændeland
  2011-12-16 10:49 ` [PATCHv4 10/11] xshm: Makefile and Kconfig for M7400 Shared Memory Drivers Sjur Brændeland
  2011-12-16 10:49 ` [PATCHv4 11/11] caif-xshm: Add CAIF driver for Shared memory for M7400 Sjur Brændeland
  10 siblings, 0 replies; 12+ messages in thread
From: Sjur Brændeland @ 2011-12-16 10:49 UTC (permalink / raw)
  To: linux-kernel; +Cc: Arnd Bergmann, Linus Walleij, sjurbren, Sjur Brændeland

This patch introduces a character interface to the XSHM channels.
A misc device is created, providing asynchronous IO and management.

The character device implements ring-buffer handling, copying
data directly from the Shared Memory area into user buffers.

Signed-off-by: Sjur Brændeland <sjur.brandeland@stericsson.com>
---
 drivers/xshm/xshm_chr.c | 1262 +++++++++++++++++++++++++++++++++++++++++++++++
 1 files changed, 1262 insertions(+), 0 deletions(-)
 create mode 100644 drivers/xshm/xshm_chr.c

diff --git a/drivers/xshm/xshm_chr.c b/drivers/xshm/xshm_chr.c
new file mode 100644
index 0000000..6951a90
--- /dev/null
+++ b/drivers/xshm/xshm_chr.c
@@ -0,0 +1,1262 @@
+/*
+ * Copyright (C) ST-Ericsson AB 2011
+ * Author:	Per Sigmond / Per.Sigmond@stericsson.com
+ *		Sjur Brændeland / sjur.brandeland@stericsson.com
+ * License terms: GNU General Public License (GPL) version 2
+ */
+
+#define pr_fmt(fmt) KBUILD_MODNAME ":%s :" fmt, __func__
+#include <linux/kernel.h>
+#include <linux/fs.h>
+#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/miscdevice.h>
+#include <linux/sched.h>
+#include <linux/spinlock.h>
+#include <linux/mutex.h>
+#include <linux/list.h>
+#include <linux/wait.h>
+#include <linux/poll.h>
+#include <linux/slab.h>
+#include <linux/err.h>
+#include <linux/uaccess.h>
+#include <asm/uaccess.h>
+#include <asm/atomic.h>
+#include <linux/xshm/xshm_dev.h>
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Sjur Brændland <sjur.brandeland@stericsson.com>");
+
+static LIST_HEAD(xshmchr_chrdev_list);
+static spinlock_t list_lock;
+
+#define xdev_dbg(dev, fmt, arg...) \
+		dev_dbg(dev->misc.this_device, pr_fmt(fmt), ##arg)
+
+#define xdev_vdbg(dev, fmt, arg...) \
+		dev_vdbg(dev->misc.this_device, pr_fmt(fmt), ##arg)
+
+#define pr_xchrstate(dev, str, arg...)			\
+	xdev_vdbg(dev, str " State: %s %s\n", ##arg,	\
+		STATE_IS_PENDING(dev) ? "pending" : "", \
+		STATE_IS_OPEN(dev) ? "open" : "close")
+
+#define OPEN_TOUT			(25 * HZ)
+#define CONN_STATE_OPEN_BIT		0
+#define CONN_STATE_PENDING_BIT		1
+#define CONN_REMOTE_TEARDOWN_BIT	2
+#define CONN_EOF_BIT			4
+
+#define STATE_IS_OPEN(dev) test_bit(CONN_STATE_OPEN_BIT, \
+					(void *) &(dev)->conn_state)
+#define STATE_IS_REMOTE_TEARDOWN(dev) test_bit(CONN_REMOTE_TEARDOWN_BIT, \
+					(void *) &(dev)->conn_state)
+#define STATE_IS_PENDING(dev) test_bit(CONN_STATE_PENDING_BIT, \
+					(void *) &(dev)->conn_state)
+#define SET_STATE_OPEN(dev) (set_bit(CONN_STATE_OPEN_BIT,	\
+			(void *) &(dev)->conn_state), \
+			pr_devel("SET_STATE_OPEN:%d\n", dev->conn_state))
+#define SET_STATE_CLOSED(dev) (clear_bit(CONN_STATE_OPEN_BIT,	\
+			(void *) &(dev)->conn_state), \
+			pr_devel("SET_STATE_CLOSED:%d\n", dev->conn_state))
+#define SET_PENDING_ON(dev) (set_bit(CONN_STATE_PENDING_BIT,	\
+			(void *) &(dev)->conn_state), \
+			pr_devel("SET_PENDING_ON:%d\n", dev->conn_state))
+#define SET_PENDING_OFF(dev) (clear_bit(CONN_STATE_PENDING_BIT, \
+			(void *) &(dev)->conn_state), \
+			pr_devel("SET_PENDING_OFF:%d\n", dev->conn_state))
+#define SET_REMOTE_TEARDOWN(dev) (set_bit(CONN_REMOTE_TEARDOWN_BIT,	\
+			(void *) &(dev)->conn_state), \
+			pr_devel("SET_REMOTE_TEARDOWN:%d\n", dev->conn_state))
+#define CLEAR_REMOTE_TEARDOWN(dev) (clear_bit(CONN_REMOTE_TEARDOWN_BIT, \
+			(void *) &(dev)->conn_state), \
+			pr_devel("CLEAR_REMOTE_TEARDOWN:%d\n", dev->conn_state))
+#define SET_EOF(dev) (set_bit(CONN_EOF_BIT,	\
+			(void *) &(dev)->conn_state), \
+			pr_devel("SET_EOF:%d\n", dev->conn_state))
+#define CLEAR_EOF(dev) (clear_bit(CONN_EOF_BIT, \
+			(void *) &(dev)->conn_state), \
+			pr_devel("CLEAR_EOF:%d\n", dev->conn_state))
+#define STATE_IS_EOF(dev) test_bit(CONN_EOF_BIT, \
+					(void *) &(dev)->conn_state)
+
+#define CHR_READ_FLAG 0x01
+#define CHR_WRITE_FLAG 0x02
+
+#ifdef CONFIG_DEBUG_FS
+static struct dentry *debugfsdir;
+#include <linux/debugfs.h>
+#define	dbfs_atomic_inc(a) atomic_inc(a)
+#define	dbfs_atomic_add(v, a) atomic_add_return(v, a)
+#else
+#define	dbfs_atomic_inc(a) 0
+#define	dbfs_atomic_add(v, a) 0
+#endif
+
+struct ringbuf {
+	__le32 *ri;	/* Pointer to read-index in shared memory.*/
+	__le32 *wi;	/* Pointer to write-index in shared memory */
+	unsigned int size;/* Size of buffer */
+	void *data;	/* Buffer data in shared memory */
+};
+
+struct xshmchr_char_dev {
+	struct xshm_dev *xshm;
+	struct kref kref;
+	struct ringbuf rx, tx;
+	u32 conn_state;
+	char name[256];
+	struct miscdevice misc;
+	int file_mode;
+
+	/* Access to this struct and below layers */
+	struct mutex mutex;
+	wait_queue_head_t mgmt_wq;
+	/* List of misc test devices */
+	struct list_head list_field;
+#ifdef CONFIG_DEBUG_FS
+	struct dentry *debugfs_device_dir;
+	atomic_t num_open;
+	atomic_t num_close;
+	atomic_t num_read;
+	atomic_t num_read_block;
+	atomic_t num_read_bytes;
+
+	atomic_t num_write;
+	atomic_t num_write_block;
+	atomic_t num_write_bytes;
+
+	atomic_t num_init;
+	atomic_t num_init_resp;
+	atomic_t num_deinit;
+	atomic_t num_deinit_resp;
+	atomic_t num_remote_teardown_ind;
+
+#endif
+};
+
+static void xshm_release(struct kref *kref)
+{
+	struct xshmchr_char_dev *dev;
+	dev = container_of(kref, struct xshmchr_char_dev, kref);
+	xdev_vdbg(dev, "Freeing device\n");
+	kfree(dev);
+}
+
+static void xshmchr_get(struct xshmchr_char_dev *dev)
+{
+	kref_get(&dev->kref);
+}
+
+static void xshmchr_put(struct xshmchr_char_dev *dev)
+{
+	kref_put(&dev->kref, xshm_release);
+}
+
+static inline unsigned int ringbuf_empty(struct ringbuf *rb)
+{
+	return *rb->wi == *rb->ri;
+}
+
+static inline unsigned int ringbuf_full(struct ringbuf *rb)
+{
+	return (le32_to_cpu(*rb->wi) + 1) % rb->size == le32_to_cpu(*rb->ri);
+}
+
+static int insert_ringbuf(struct ringbuf *rb, const char __user *from,
+		u32 len)
+{
+	u32 wi = le32_to_cpu(*rb->wi);
+	u32 ri = le32_to_cpu(*rb->ri);
+	u32 cpylen, cpylen2 = 0, notcpy;
+
+	pr_devel("insert: wi:%d ri:%d len:%d\n", wi, ri, len);
+	if (wi >= ri) {
+		len = min(len, rb->size - 1 - wi + ri);
+		cpylen = min(rb->size, wi + len) - wi;
+
+		/* Write is ahead of read, copy 'cpylen' data from 'wi' */
+		notcpy = copy_from_user(rb->data + wi, from, cpylen);
+		if (cpylen > 0 && notcpy == cpylen)
+			return -EIO;
+
+		if (cpylen < len && notcpy == 0) {
+			cpylen2 = min(ri - 1 , len - cpylen);
+
+			/* We have wrapped copy 'cpylen2' from start */
+			notcpy = copy_from_user(rb->data, from + cpylen,
+					cpylen2);
+		}
+	} else {
+		cpylen = min(ri - 1 - wi , len);
+
+		/* Read is ahead of write, copy from wi to (ri - 1) */
+		notcpy = copy_from_user(rb->data + wi, from, cpylen);
+		if (cpylen > 0 && notcpy == cpylen)
+			return -EIO;
+
+	}
+	/* Do write barrier before updating index */
+	smp_wmb();
+	*rb->wi = cpu_to_le32((wi + cpylen + cpylen2 - notcpy) % rb->size);
+	pr_devel("write ringbuf: wi: %d->%d l:%d\n",
+			wi, le32_to_cpu(*rb->wi), cpylen + cpylen2);
+	return cpylen + cpylen2 - notcpy;
+}
+
+static int extract_ringbuf(struct ringbuf *rb, void __user *to, u32 len)
+{
+	u32 wi = le32_to_cpu(*rb->wi);
+	u32 ri = le32_to_cpu(*rb->ri);
+	u32 cpylen = 0, cpylen2 = 0, notcpy;
+
+	pr_devel("extract: wi:%d ri:%d len:%d\n", wi, ri, len);
+	if (ri <= wi) {
+		len = min(wi - ri, len);
+
+		/* Read is ahead of write, copy 'len' data from 'ri' */
+		notcpy = copy_to_user(to, rb->data + ri, len);
+		if (len > 0 && notcpy == len)
+			return -EIO;
+
+		/* Do write barrier before updating index */
+		smp_wmb();
+		*rb->ri = cpu_to_le32(ri + len - notcpy);
+		pr_devel("read ringbuf: ri: %d->%d len:%d\n",
+			ri, le32_to_cpu(*rb->ri), len - notcpy);
+
+		return len - notcpy;
+	} else {
+		/* wr >= ri */
+		cpylen = min(rb->size - ri, len);
+
+		/* Write is ahead, copy 'cpylen' data from ri until end */
+		notcpy = copy_to_user(to, rb->data + ri, cpylen);
+		if (cpylen > 0 && notcpy == cpylen)
+			return -EIO;
+		if (cpylen < len && notcpy == 0) {
+			cpylen2 = min(wi , len - cpylen);
+			/* we have wrapped copy from [0 .. cpylen2] */
+			notcpy = copy_to_user(to + cpylen, rb->data, cpylen2);
+		}
+		/* Do write barrier before updating index */
+		smp_wmb();
+
+		*rb->ri = cpu_to_le32((ri + cpylen + cpylen2 - notcpy)
+						% rb->size);
+		pr_devel("read ringbuf: ri: %d->%d cpylen:%d\n",
+			ri, le32_to_cpu(*rb->ri), cpylen + cpylen2 - notcpy);
+
+		return cpylen + cpylen2 - notcpy;
+	}
+}
+
+static void drain_ringbuf(struct xshmchr_char_dev *dev)
+{
+	/* Empty the ringbuf. */
+	*dev->xshm->cfg.rx.read = *dev->xshm->cfg.rx.write;
+	*dev->xshm->cfg.tx.write = *dev->xshm->cfg.tx.read;
+}
+
+static int open_cb(void *drv)
+{
+	struct xshmchr_char_dev *dev = drv;
+
+	pr_xchrstate(dev, "enter");
+	dbfs_atomic_inc(&dev->num_init_resp);
+	/* Signal reader that data is available. */
+	WARN_ON(!STATE_IS_OPEN(dev));
+	SET_PENDING_OFF(dev);
+	wake_up_interruptible_all(&dev->mgmt_wq);
+	pr_xchrstate(dev, "exit");
+	return 0;
+}
+
+static void close_cb(void *drv)
+{
+	struct xshmchr_char_dev *dev = drv;
+
+	pr_xchrstate(dev, "enter");
+	dbfs_atomic_inc(&dev->num_remote_teardown_ind);
+	if (STATE_IS_PENDING(dev) && !STATE_IS_OPEN(dev)) {
+		/* Normal close sequence */
+		SET_PENDING_OFF(dev);
+		CLEAR_REMOTE_TEARDOWN(dev);
+		SET_EOF(dev);
+		drain_ringbuf(dev);
+		dev->file_mode = 0;
+	} else {
+		/* Remote teardown, close should be called from user-space */
+		SET_REMOTE_TEARDOWN(dev);
+		SET_PENDING_OFF(dev);
+	}
+
+	wake_up_interruptible_all(&dev->mgmt_wq);
+	pr_xchrstate(dev, "exit");
+}
+
+static int ipc_rx_cb(void *drv)
+{
+	struct xshmchr_char_dev *dev = drv;
+
+	xdev_vdbg(dev, "enter\n");
+
+	if (unlikely(*dev->xshm->cfg.rx.state == cpu_to_le32(XSHM_CLOSED)))
+		return -ESHUTDOWN;
+
+	/*
+	 * Performance could perhaps be improved by having a WAIT
+	 * flag, similar to SOCK_ASYNC_WAITDATA, and only do wake up
+	 * when it's actually needed.
+	 */
+	wake_up_interruptible_all(&dev->mgmt_wq);
+	return 0;
+}
+
+static int ipc_tx_release_cb(void *drv)
+{
+	struct xshmchr_char_dev *dev = drv;
+
+	xdev_vdbg(dev, "enter\n");
+	wake_up_interruptible_all(&dev->mgmt_wq);
+	return 0;
+}
+
+/* Device Read function called from Linux kernel */
+static ssize_t xshmchr_chrread(struct file *filp, char __user *buf,
+	 size_t count, loff_t *f_pos)
+{
+	unsigned int len = 0;
+	int result;
+	struct xshmchr_char_dev *dev = filp->private_data;
+	ssize_t ret = -EIO;
+
+	if (dev == NULL) {
+		xdev_dbg(dev, "private_data not set!\n");
+		return -EBADFD;
+	}
+
+	/* I want to be alone on dev (except status and queue) */
+	if (mutex_lock_interruptible(&dev->mutex)) {
+		xdev_dbg(dev, "mutex_lock_interruptible got signalled\n");
+		return -ERESTARTSYS;
+	}
+	xshmchr_get(dev);
+
+	if (!STATE_IS_OPEN(dev)) {
+		/* Device is closed or closing. */
+		if (!STATE_IS_PENDING(dev)) {
+			xdev_dbg(dev, "device is closed (by remote)\n");
+			ret = -ECONNRESET;
+		} else {
+			xdev_dbg(dev, "device is closing...\n");
+			ret = -EBADF;
+		}
+		goto read_error;
+	}
+
+	dbfs_atomic_inc(&dev->num_read);
+
+	/* Device is open or opening. */
+	if (STATE_IS_PENDING(dev)) {
+		xdev_vdbg(dev, "device is opening...\n");
+
+		dbfs_atomic_inc(&dev->num_read_block);
+		if (filp->f_flags & O_NONBLOCK) {
+			/* We can't block. */
+			xdev_dbg(dev, "exit: state pending and O_NONBLOCK\n");
+			ret = -EAGAIN;
+			goto read_error;
+		}
+
+		/*
+		 * To reach here client must do blocking open,
+		 * and start read() before open completes. This is
+		 * quite quirky, but let's handle it anyway.
+		 */
+		result =
+		    wait_event_interruptible(dev->mgmt_wq,
+					!STATE_IS_PENDING(dev) ||
+				    STATE_IS_REMOTE_TEARDOWN(dev));
+
+		if (result == -ERESTARTSYS) {
+			xdev_dbg(dev, "wait_event_interruptible"
+				 " woken by a signal (1)\n");
+			ret = -ERESTARTSYS;
+			goto read_error;
+		}
+		if (STATE_IS_REMOTE_TEARDOWN(dev)) {
+			xdev_dbg(dev,
+				"received remote_shutdown indication (1)\n");
+			ret = -ESHUTDOWN;
+			goto read_error;
+		}
+	}
+
+	/* Block if we don't have any received buffers.
+	 * The queue has its own lock.
+	 */
+	while (ringbuf_empty(&dev->rx)) {
+
+		if (filp->f_flags & O_NONBLOCK) {
+			xdev_vdbg(dev, "exit: O_NONBLOCK\n");
+			ret = -EAGAIN;
+			goto read_error;
+		}
+
+		/* Let writers in. */
+		mutex_unlock(&dev->mutex);
+
+		xdev_vdbg(dev, "wait for data\n");
+		/* Block reader until data arrives or device is closed. */
+		if (wait_event_interruptible(dev->mgmt_wq,
+				!ringbuf_empty(&dev->rx)
+				|| STATE_IS_REMOTE_TEARDOWN(dev)
+				|| !STATE_IS_OPEN(dev)) == -ERESTARTSYS) {
+			xdev_vdbg(dev, "event_interruptible woken by "
+				 "a signal, signal_pending(current) = %d\n",
+				signal_pending(current));
+			return -ERESTARTSYS;
+		}
+
+		xdev_vdbg(dev, "wakeup readq\n");
+
+		if (STATE_IS_REMOTE_TEARDOWN(dev) && ringbuf_empty(&dev->rx)) {
+			if (!STATE_IS_EOF(dev)) {
+				xdev_dbg(dev, "First EOF OK\n");
+				SET_EOF(dev);
+				ret = 0;
+				goto error_nolock;
+			}
+			xdev_dbg(dev, "2'nd EOF - remote_shutdown\n");
+			ret = -ECONNRESET;
+			goto error_nolock;
+		}
+
+		/* I want to be alone on dev (except status and queue). */
+		if (mutex_lock_interruptible(&dev->mutex)) {
+			xdev_dbg(dev, "mutex_lock_interruptible"
+					" got signalled\n");
+			return -ERESTARTSYS;
+		}
+
+		if (!STATE_IS_OPEN(dev)) {
+			/* Someone closed the link, report error. */
+			xdev_dbg(dev, "remote end shutdown!\n");
+			ret = -EBADF;
+			goto read_error;
+		}
+	}
+
+	xdev_vdbg(dev, "copy data\n");
+	len = extract_ringbuf(&dev->rx, buf, count);
+	if (len <= 0) {
+		xdev_dbg(dev, "Extracting from ringbuf failed\n");
+		ret = -EINVAL;
+		goto read_error;
+	}
+
+	/* Signal to modem that data is read from ringbuf */
+
+	dev->xshm->ipc_rx_release(dev->xshm, false);
+
+	dbfs_atomic_add(len, &dev->num_read_bytes);
+	/* Let the others in. */
+	mutex_unlock(&dev->mutex);
+	xshmchr_put(dev);
+	return len;
+
+read_error:
+	mutex_unlock(&dev->mutex);
+error_nolock:
+	xshmchr_put(dev);
+	return ret;
+}
+
+/* Device write function called from Linux kernel (misc device) */
+static ssize_t xshmchr_chrwrite(struct file *filp, const char __user *buf,
+		      size_t count, loff_t *f_pos)
+{
+	struct xshmchr_char_dev *dev = filp->private_data;
+	ssize_t ret = -EIO;
+	int result;
+	uint len = 0;
+
+	if (dev == NULL) {
+		xdev_dbg(dev, "private_data not set!\n");
+		ret = -EBADFD;
+		goto write_error_no_unlock;
+	}
+
+	pr_xchrstate(dev, "enter");
+
+	/* I want to be alone on dev (except status and queue). */
+	if (mutex_lock_interruptible(&dev->mutex)) {
+		xdev_dbg(dev, "mutex_lock_interruptible got signalled\n");
+		ret = -ERESTARTSYS;
+		goto write_error_no_unlock;
+	}
+	xshmchr_get(dev);
+
+	dbfs_atomic_inc(&dev->num_write);
+	if (!STATE_IS_OPEN(dev)) {
+		/* Device is closed or closing. */
+		if (!STATE_IS_PENDING(dev)) {
+			xdev_dbg(dev, "device is closed (by remote)\n");
+			ret = -EPIPE;
+		} else {
+			xdev_dbg(dev, "device is closing...\n");
+			ret = -EBADF;
+		}
+		goto write_error;
+	}
+
+	/* Device is open or opening. */
+	if (STATE_IS_PENDING(dev)) {
+		xdev_dbg(dev, "device is opening...\n");
+
+		dbfs_atomic_inc(&dev->num_write_block);
+		if (filp->f_flags & O_NONBLOCK) {
+			/* We can't block */
+			xdev_dbg(dev, "exit: state pending and O_NONBLOCK\n");
+			ret = -EAGAIN;
+			goto write_error;
+		}
+
+		/* Blocking mode; state is pending and we need to wait
+		 * for its conclusion. (Shutdown_ind set pending off.)
+		 */
+		result =
+		    wait_event_interruptible(dev->mgmt_wq,
+					!STATE_IS_PENDING(dev) ||
+					STATE_IS_REMOTE_TEARDOWN(dev));
+		if (result == -ERESTARTSYS) {
+			xdev_dbg(dev, "wait_event_interruptible"
+				 " woken by a signal (1)\n");
+			ret = -ERESTARTSYS;
+			goto write_error;
+		}
+	}
+	if (STATE_IS_REMOTE_TEARDOWN(dev)) {
+		xdev_dbg(dev, "received remote_shutdown indication\n");
+		ret = -EPIPE;
+		goto write_error;
+	}
+
+	while (ringbuf_full(&dev->tx)) {
+		/* Flow is off. Check non-block flag. */
+		if (filp->f_flags & O_NONBLOCK) {
+			xdev_dbg(dev, "exit: O_NONBLOCK and tx flow off");
+			ret = -EAGAIN;
+			goto write_error;
+		}
+
+		/* Let readers in. */
+		mutex_unlock(&dev->mutex);
+
+		xdev_vdbg(dev, "wait for write space\n");
+		/* Wait until flow is on or device is closed. */
+		if (wait_event_interruptible(dev->mgmt_wq,
+					!ringbuf_full(&dev->tx)
+					|| !STATE_IS_OPEN(dev)
+					|| STATE_IS_REMOTE_TEARDOWN(dev)
+					) == -ERESTARTSYS) {
+			xdev_dbg(dev, "wait_event_interruptible"
+				 " woken by a signal (1)\n");
+			ret = -ERESTARTSYS;
+			goto write_error_no_unlock;
+		}
+
+		/* I want to be alone on dev (except status and queue). */
+		if (mutex_lock_interruptible(&dev->mutex)) {
+			xdev_dbg(dev, "mutex_lock_interruptible "
+					"got signalled\n");
+			ret = -ERESTARTSYS;
+			goto write_error_no_unlock;
+		}
+
+		xdev_vdbg(dev, "wakeup got write space\n");
+		if (!STATE_IS_OPEN(dev)) {
+			/* Someone closed the link, report error. */
+			xdev_dbg(dev, "remote end shutdown!\n");
+			ret = -EPIPE;
+			goto write_error;
+		}
+		if (STATE_IS_REMOTE_TEARDOWN(dev)) {
+			xdev_dbg(dev, "received remote_shutdown indication\n");
+			ret = -ESHUTDOWN;
+			goto write_error;
+		}
+	}
+	len = insert_ringbuf(&dev->tx, buf, count);
+	xdev_vdbg(dev, "inserted %d bytes\n", len);
+	if (len <= 0) {
+		xdev_dbg(dev, "transmit failed, error = %d\n",
+				(int) ret);
+		goto write_error;
+	}
+
+	dbfs_atomic_add(len, &dev->num_write_bytes);
+
+	/* Signal to modem that data is put in ringbuf */
+	dev->xshm->ipc_tx(dev->xshm);
+
+	mutex_unlock(&dev->mutex);
+	xshmchr_put(dev);
+	return len;
+
+write_error:
+	mutex_unlock(&dev->mutex);
+write_error_no_unlock:
+	xshmchr_put(dev);
+	return ret;
+}
+
+static unsigned int xshmchr_chrpoll(struct file *filp, poll_table *waittab)
+{
+	struct xshmchr_char_dev *dev = filp->private_data;
+	unsigned int mask = 0;
+
+	if (dev == NULL) {
+		xdev_dbg(dev, "private_data not set!\n");
+		return -EBADFD;
+	}
+
+	/* I want to be alone on dev (except status and queue). */
+	if (mutex_lock_interruptible(&dev->mutex)) {
+		xdev_dbg(dev, "mutex_lock_interruptible got signalled\n");
+		mask |= POLLERR;
+		goto out_unlocked;
+	}
+	xshmchr_get(dev);
+
+	if (STATE_IS_REMOTE_TEARDOWN(dev)) {
+		xdev_dbg(dev, "not open\n");
+		mask |= POLLRDHUP | POLLHUP;
+		goto out;
+	}
+
+	xdev_vdbg(dev, "poll wait\n");
+	poll_wait(filp, &dev->mgmt_wq, waittab);
+
+	if (STATE_IS_OPEN(dev) && STATE_IS_PENDING(dev))
+		goto out;
+
+	if (!ringbuf_empty(&dev->rx))
+		mask |= (POLLIN | POLLRDNORM);
+
+	if (!ringbuf_full(&dev->tx))
+		mask |= (POLLOUT | POLLWRNORM);
+
+out:
+	mutex_unlock(&dev->mutex);
+out_unlocked:
+	xdev_vdbg(dev, "poll return mask=0x%04x\n", mask);
+	xshmchr_put(dev);
+	return mask;
+}
+
+/* Usage:
+ * minor >= 0 : find from minor
+ * minor < 0 and name == name : find from name
+ * minor < 0 and name == NULL : get first
+ */
+
+static struct xshmchr_char_dev *find_device(int minor, char *name,
+					 int remove_from_list)
+{
+	struct list_head *list_node;
+	struct list_head *n;
+	struct xshmchr_char_dev *dev = NULL;
+	struct xshmchr_char_dev *tmp;
+	spin_lock(&list_lock);
+	pr_devel("start looping\n");
+	list_for_each_safe(list_node, n, &xshmchr_chrdev_list) {
+		tmp = list_entry(list_node, struct xshmchr_char_dev,
+				list_field);
+		if (minor >= 0) {	/* find from minor */
+			if (tmp->misc.minor == minor)
+				dev = tmp;
+
+		} else if (name) {	/* find from name */
+			if (!strncmp(tmp->name, name, sizeof(tmp->name)))
+				dev = tmp;
+		} else {	/* take first */
+			dev = tmp;
+		}
+
+		if (dev) {
+			xdev_vdbg(dev, "match %d, %s\n",
+				      minor, name);
+			if (remove_from_list)
+				list_del(list_node);
+			break;
+		}
+	}
+	spin_unlock(&list_lock);
+	return dev;
+}
+
+static int xshmchr_chropen(struct inode *inode, struct file *filp)
+{
+	struct xshmchr_char_dev *dev = NULL;
+	int result = -1;
+	int minor = iminor(inode);
+	int mode = 0;
+	int ret = -EIO;
+
+	dev = find_device(minor, NULL, 0);
+	pr_xchrstate(dev, "enter");
+
+	if (dev == NULL) {
+		xdev_dbg(dev, "Could not find device\n");
+		return -EBADF;
+	}
+
+	/* I want to be alone on dev (except status and queue). */
+	if (mutex_lock_interruptible(&dev->mutex)) {
+		xdev_dbg(dev, "mutex_lock_interruptible got signalled\n");
+		return -ERESTARTSYS;
+	}
+
+	xshmchr_get(dev);
+	dbfs_atomic_inc(&dev->num_open);
+	filp->private_data = dev;
+
+	switch (filp->f_flags & O_ACCMODE) {
+	case O_RDONLY:
+		mode = CHR_READ_FLAG;
+		break;
+	case O_WRONLY:
+		mode = CHR_WRITE_FLAG;
+		break;
+	case O_RDWR:
+		mode = CHR_READ_FLAG | CHR_WRITE_FLAG;
+		break;
+	}
+
+	/* If device is not open, make sure device is in fully closed state. */
+	if (!STATE_IS_OPEN(dev)) {
+		/* Has link close response been received
+		 * (if we ever sent it)?
+		 */
+		if (STATE_IS_PENDING(dev)) {
+			/* Still waiting for close response from remote.
+			 * If opened non-blocking, report "would block".
+			 */
+			if (filp->f_flags & O_NONBLOCK) {
+				xdev_vdbg(dev,
+					"exit: O_NONBLOCK && close pending\n");
+				ret = -EAGAIN;
+				goto open_error;
+			}
+
+			xdev_vdbg(dev, "WAIT for close response from remote\n");
+
+			/*
+			 * Blocking mode; close is pending and we need to wait
+			 * for its conclusion. However modem may be dead,
+			 * or resureccted and alive waiting for
+			 * an open ack.
+			 * It's hard to get this rigth - if state is
+			 * pending. We have missed a state update,
+			 * let's just wait for ack, and then proceede
+			 * with watever state we have.
+			 */
+			result =
+			    wait_event_interruptible_timeout(dev->mgmt_wq,
+					    !STATE_IS_PENDING(dev) ||
+					    STATE_IS_REMOTE_TEARDOWN(dev),
+					    OPEN_TOUT);
+
+			if (result == -ERESTARTSYS) {
+				xdev_dbg(dev, "wait_event_interruptible"
+					" woken by a signal (1)\n");
+				ret = -ERESTARTSYS;
+				goto open_error;
+			}
+
+			if (result == 0) {
+				SET_PENDING_OFF(dev);
+				pr_xchrstate(dev,
+				    "Timeout -pending close -Clear pending");
+			} else
+				pr_xchrstate(dev, "wakeup (wait for close)");
+		}
+	}
+
+	/* Device is now either closed, pending open or open */
+	if (STATE_IS_OPEN(dev) && !STATE_IS_PENDING(dev)) {
+		/* Open */
+		xdev_vdbg(dev, "Device is already opened (dev=%p) check"
+				"access f_flags = 0x%x file_mode = 0x%x\n",
+				dev, mode, dev->file_mode);
+
+		if (mode & dev->file_mode) {
+			xdev_vdbg(dev, "Access mode already in use 0x%x\n",
+					mode);
+			ret = -EBUSY;
+			goto open_error;
+		}
+	} else {
+
+		/* We are closed or pending open.
+		 * If closed:	    send link setup
+		 * If pending open: link setup already sent (we could have been
+		 *		    interrupted by a signal last time)
+		 */
+		if (!STATE_IS_OPEN(dev)) {
+			/* First opening of file; do connect */
+
+			SET_STATE_OPEN(dev);
+			SET_PENDING_ON(dev);
+			CLEAR_EOF(dev);
+			/* Send "open" by resetting indexes */
+			result = dev->xshm->open(dev->xshm);
+
+			if (result < 0) {
+				xdev_dbg(dev, "can't open channel\n");
+				ret = -EIO;
+				SET_STATE_CLOSED(dev);
+				SET_PENDING_OFF(dev);
+				goto open_error;
+			}
+			dbfs_atomic_inc(&dev->num_init);
+		}
+
+		/* If opened non-blocking, report "success".
+		 */
+		if (filp->f_flags & O_NONBLOCK) {
+			xdev_vdbg(dev, "EXIT: O_NONBLOCK success\n");
+			ret = 0;
+			goto open_success;
+		}
+
+		xdev_vdbg(dev, "WAIT for connect response\n");
+		/*
+		 * misc_open holds a global mutex anyway so there is no
+		 * reason to release our own while waiting
+		 */
+		result =
+		    wait_event_interruptible_timeout(dev->mgmt_wq,
+				    !STATE_IS_PENDING(dev) ||
+				    STATE_IS_REMOTE_TEARDOWN(dev),
+				    OPEN_TOUT);
+		if (result == 0) {
+			xdev_dbg(dev, "wait_event_interruptible timeout (1)\n");
+			ret = -ETIMEDOUT;
+			goto open_error;
+		}
+		if (result == -ERESTARTSYS) {
+			xdev_dbg(dev, "wait_event_interruptible woken by sig");
+			ret = -ERESTARTSYS;
+			goto open_error;
+		}
+		if (STATE_IS_REMOTE_TEARDOWN(dev)) {
+			xdev_dbg(dev, "received remote_shutdown indication\n");
+			ret = -ESHUTDOWN;
+			goto open_error;
+		}
+
+		pr_xchrstate(dev, "wakeup (wait for open)");
+		if (!STATE_IS_OPEN(dev)) {
+			/* Lower layers said "no". */
+			xdev_dbg(dev, "xshmchr_chropen: Closed received\n");
+			ret = -EPIPE;
+			goto open_error;
+		}
+
+		xdev_vdbg(dev, "connect received\n");
+	}
+open_success:
+	/* Open is OK. */
+	dev->file_mode |= mode;
+
+	xdev_vdbg(dev, "file mode = %x\n", dev->file_mode);
+	pr_xchrstate(dev, "exit");
+
+	mutex_unlock(&dev->mutex);
+	xshmchr_put(dev);
+	return 0;
+
+open_error:
+	dev->xshm->close(dev->xshm);
+	SET_STATE_CLOSED(dev);
+	SET_PENDING_OFF(dev);
+	mutex_unlock(&dev->mutex);
+	xshmchr_put(dev);
+	return ret;
+}
+
+static int xshmchr_chrrelease(struct inode *inode, struct file *filp)
+{
+	struct xshmchr_char_dev *dev = NULL;
+	int minor = iminor(inode);
+	int mode = 0;
+
+
+	dev = find_device(minor, NULL, 0);
+	if (dev == NULL) {
+		xdev_dbg(dev, "Could not find device\n");
+		return -EBADF;
+	}
+
+	pr_xchrstate(dev, "enter");
+
+	/* I want to be alone on dev (except status queue). */
+	if (mutex_lock_interruptible(&dev->mutex)) {
+		xdev_dbg(dev, "mutex_lock_interruptible got signalled\n");
+		return -ERESTARTSYS;
+	}
+
+	xshmchr_get(dev);
+	dbfs_atomic_inc(&dev->num_close);
+
+	/* Is the device open? */
+	if (!STATE_IS_OPEN(dev)) {
+		xdev_vdbg(dev, "Device not open (dev=%p)\n",
+			      dev);
+		mutex_unlock(&dev->mutex);
+		xshmchr_put(dev);
+		return 0;
+	}
+
+	switch (filp->f_flags & O_ACCMODE) {
+	case O_RDONLY:
+		mode = CHR_READ_FLAG;
+		break;
+	case O_WRONLY:
+		mode = CHR_WRITE_FLAG;
+		break;
+	case O_RDWR:
+		mode = CHR_READ_FLAG | CHR_WRITE_FLAG;
+		break;
+	}
+
+	dev->file_mode &= ~mode;
+	if (dev->file_mode) {
+		xdev_vdbg(dev, "Device is kept open by someone else, "
+			 " don't close. XSHMCHR connection - file_mode = %x\n",
+			 dev->file_mode);
+		mutex_unlock(&dev->mutex);
+		xshmchr_put(dev);
+		return 0;
+	}
+
+	/* IS_CLOSED have double meaning:
+	 * 1) Spontanous Remote Shutdown Request.
+	 * 2) Ack on a channel teardown(disconnect)
+	 * Must clear bit, in case we previously received
+	 * a remote shudown request.
+	 */
+
+	SET_STATE_CLOSED(dev);
+	SET_PENDING_ON(dev);
+	CLEAR_REMOTE_TEARDOWN(dev);
+	SET_EOF(dev);
+
+	dev->xshm->close(dev->xshm);
+
+	dbfs_atomic_inc(&dev->num_deinit);
+
+	/* Empty the ringbuf */
+	drain_ringbuf(dev);
+	dev->file_mode = 0;
+
+	mutex_unlock(&dev->mutex);
+	pr_xchrstate(dev, "exit");
+	xshmchr_put(dev);
+	return 0;
+}
+
+static const struct file_operations xshmchr_chrfops = {
+	.owner = THIS_MODULE,
+	.read = xshmchr_chrread,
+	.write = xshmchr_chrwrite,
+	.open = xshmchr_chropen,
+	.release = xshmchr_chrrelease,
+	.poll = xshmchr_chrpoll,
+};
+
+static int cfshm_probe(struct xshm_dev *xshm)
+
+{
+	struct xshmchr_char_dev *dev = NULL;
+	int result;
+
+	pr_devel("cfshm_probe called\n");
+	if (xshm == NULL)
+		return 0;
+
+	/* Allocate device */
+	dev = kmalloc(sizeof(*dev), GFP_KERNEL);
+
+	if (!dev) {
+		pr_err("kmalloc failed.\n");
+		return -ENOMEM;
+	}
+
+	memset(dev, 0, sizeof(*dev));
+	kref_init(&dev->kref);
+
+	dev->xshm = xshm;
+	mutex_init(&dev->mutex);
+	init_waitqueue_head(&dev->mgmt_wq);
+
+	/* Fill in some information concerning the misc device. */
+	dev->misc.minor = MISC_DYNAMIC_MINOR;
+	if (strlen(xshm->cfg.name) == 0) {
+		xdev_dbg(dev, "XSHM device does not have a name\n");
+		return -EINVAL;
+	}
+	sprintf(dev->name, "%s", xshm->cfg.name);
+	dev->misc.name = dev->name;
+	dev->misc.fops = &xshmchr_chrfops;
+
+	dev->tx.ri = xshm->cfg.tx.read;
+	dev->tx.wi = xshm->cfg.tx.write;
+	dev->tx.data = xshm->cfg.tx.addr;
+	dev->tx.size = xshm->cfg.tx.ch_size - 1;
+
+	dev->rx.ri = xshm->cfg.rx.read;
+	dev->rx.wi = xshm->cfg.rx.write;
+	dev->rx.data = xshm->cfg.rx.addr;
+	dev->rx.size = xshm->cfg.rx.ch_size - 1;
+	if (dev->rx.size < 2 || dev->tx.size < 2) {
+		dev->rx.size = 0;
+		dev->tx.size = 0;
+		xdev_dbg(dev, "dev:error - channel size too small\n");
+		return -EINVAL;
+	}
+	dev->xshm->ipc_rx_cb = ipc_rx_cb;
+	dev->xshm->ipc_tx_release_cb = ipc_tx_release_cb;
+	dev->xshm->open_cb = open_cb;
+	dev->xshm->close_cb = close_cb;
+	dev->xshm->driver_data = dev;
+
+	mutex_lock(&dev->mutex);
+
+	/* Register the device. */
+	dev->misc.parent = &xshm->dev;
+	pr_devel("register dev:%s chr=%s(%s) dev=%p\n",
+			dev_name(&dev->xshm->dev),
+			dev->name, xshm->cfg.name, dev);
+	result = misc_register(&dev->misc);
+
+	/* Lock in order to try to stop someone from opening the device
+	 * too early. The misc device has its own lock. We cannot take our
+	 * lock until misc_register() is finished, because in open() the
+	 * locks are taken in this order (misc first and then dev).
+	 * So anyone managing to open the device between the misc_register
+	 * and the mutex_lock will get a "device not found" error. Don't
+	 * think it can be avoided.
+	 */
+
+	if (result < 0) {
+		pr_warn("error - %d, can't register misc\n",
+				result);
+		mutex_unlock(&dev->mutex);
+		goto err_failed;
+	}
+
+	xdev_vdbg(dev, "Registered dev with name=%s minor=%d, dev=%p\n",
+			dev->misc.name, dev->misc.minor, dev->misc.this_device);
+
+	SET_STATE_CLOSED(dev);
+	SET_PENDING_OFF(dev);
+	CLEAR_REMOTE_TEARDOWN(dev);
+	CLEAR_EOF(dev);
+
+	/* Add the device. */
+	spin_lock(&list_lock);
+	list_add(&dev->list_field, &xshmchr_chrdev_list);
+	spin_unlock(&list_lock);
+
+#ifdef CONFIG_DEBUG_FS
+	if (debugfsdir != NULL) {
+		dev->debugfs_device_dir =
+		    debugfs_create_dir(dev->misc.name, debugfsdir);
+		debugfs_create_u32("conn_state", S_IRUSR | S_IWUSR,
+				   dev->debugfs_device_dir, &dev->conn_state);
+		debugfs_create_u32("num_open", S_IRUSR | S_IWUSR,
+				   dev->debugfs_device_dir,
+				   (u32 *) &dev->num_open);
+		debugfs_create_u32("num_close", S_IRUSR | S_IWUSR,
+				   dev->debugfs_device_dir,
+				   (u32 *) &dev->num_close);
+		debugfs_create_u32("num_init", S_IRUSR | S_IWUSR,
+				   dev->debugfs_device_dir,
+				   (u32 *) &dev->num_init);
+		debugfs_create_u32("num_init_resp", S_IRUSR | S_IWUSR,
+				   dev->debugfs_device_dir,
+				   (u32 *) &dev->num_init_resp);
+		debugfs_create_u32("num_deinit", S_IRUSR | S_IWUSR,
+				   dev->debugfs_device_dir,
+				   (u32 *) &dev->num_deinit);
+		debugfs_create_u32("num_deinit_resp", S_IRUSR | S_IWUSR,
+				   dev->debugfs_device_dir,
+				   (u32 *) &dev->num_deinit_resp);
+		debugfs_create_u32("num_remote_teardown_ind",
+				   S_IRUSR | S_IWUSR, dev->debugfs_device_dir,
+				   (u32 *) &dev->num_remote_teardown_ind);
+		debugfs_create_u32("num_read",
+				   S_IRUSR | S_IWUSR, dev->debugfs_device_dir,
+				   (u32 *) &dev->num_read);
+		debugfs_create_u32("num_read_block",
+				   S_IRUSR | S_IWUSR, dev->debugfs_device_dir,
+				   (u32 *) &dev->num_read_block);
+		debugfs_create_u32("num_read_bytes",
+				   S_IRUSR | S_IWUSR, dev->debugfs_device_dir,
+				   (u32 *) &dev->num_read_bytes);
+		debugfs_create_u32("num_write",
+				   S_IRUSR | S_IWUSR, dev->debugfs_device_dir,
+				   (u32 *) &dev->num_write);
+		debugfs_create_u32("num_write_block",
+				   S_IRUSR | S_IWUSR, dev->debugfs_device_dir,
+				   (u32 *) &dev->num_write_block);
+		debugfs_create_u32("num_write_bytes",
+				   S_IRUSR | S_IWUSR, dev->debugfs_device_dir,
+				   (u32 *) &dev->num_write_bytes);
+
+		debugfs_create_u32("rx_write_index",
+				   S_IRUSR | S_IWUSR, dev->debugfs_device_dir,
+				(u32 *) dev->xshm->cfg.rx.write);
+		debugfs_create_u32("rx_read_index",
+				   S_IRUSR | S_IWUSR, dev->debugfs_device_dir,
+				(u32 *) dev->xshm->cfg.rx.read);
+		debugfs_create_u32("rx_state",
+				   S_IRUSR | S_IWUSR, dev->debugfs_device_dir,
+				(u32 *) dev->xshm->cfg.rx.state);
+		debugfs_create_u32("tx_write_index",
+				   S_IRUSR | S_IWUSR, dev->debugfs_device_dir,
+				(u32 *) dev->xshm->cfg.tx.write);
+		debugfs_create_u32("tx_read_index",
+				   S_IRUSR | S_IWUSR, dev->debugfs_device_dir,
+				(u32 *) dev->xshm->cfg.tx.read);
+		debugfs_create_u32("tx_state",
+				   S_IRUSR | S_IWUSR, dev->debugfs_device_dir,
+				(u32 *) dev->xshm->cfg.tx.state);
+
+	}
+#endif
+	mutex_unlock(&dev->mutex);
+	return 0;
+err_failed:
+	xshmchr_put(dev);
+	return result;
+}
+
+static int chrdev_remove(struct xshmchr_char_dev *dev)
+{
+	if (!dev)
+		return -EBADF;
+
+	if (STATE_IS_OPEN(dev)) {
+		xdev_dbg(dev, "Device is opened "
+			 "(dev=%p) file_mode = 0x%x\n",
+			 dev, dev->file_mode);
+		SET_STATE_CLOSED(dev);
+		SET_PENDING_OFF(dev);
+		wake_up_interruptible_all(&dev->mgmt_wq);
+	}
+
+	if (mutex_lock_interruptible(&dev->mutex)) {
+		xdev_dbg(dev, "mutex_lock_interruptible got signalled\n");
+		xshmchr_put(dev);
+		return -ERESTARTSYS;
+	}
+
+	drain_ringbuf(dev);
+
+	misc_deregister(&dev->misc);
+
+	/* Remove from list. */
+	list_del(&dev->list_field);
+
+#ifdef CONFIG_DEBUG_FS
+	if (dev->debugfs_device_dir != NULL)
+		debugfs_remove_recursive(dev->debugfs_device_dir);
+#endif
+
+	mutex_unlock(&dev->mutex);
+	xshmchr_put(dev);
+	return 0;
+}
+
+static void cfshm_remove(struct xshm_dev *xshm)
+{
+	int err;
+
+	pr_devel("unregister pdev:%s chr=%s xshm=%p\n",
+			dev_name(&xshm->dev),
+			xshm->cfg.name, xshm);
+
+	err = chrdev_remove(xshm->driver_data);
+	if (err)
+		pr_debug("removing char-dev:%s failed.%d\n",
+					xshm->cfg.name, err);
+
+	xshm->ipc_rx_cb = NULL;
+	xshm->ipc_tx_release_cb = NULL;
+	xshm->open_cb = NULL;
+	xshm->close_cb = NULL;
+	xshm->driver_data = NULL;
+}
+
+static struct xshm_driver cfshm_drv = {
+	.mode = XSHM_STREAM_MODE,
+	.probe = cfshm_probe,
+	.remove = cfshm_remove,
+	.driver = {
+		.name = KBUILD_MODNAME,
+		.owner = THIS_MODULE,
+	},
+};
+
+
+static int __init xshmchr_chrinit_module(void)
+{
+	int err;
+	pr_devel("xshm init\n");
+	spin_lock_init(&list_lock);
+
+	/* Register xshm driver. */
+	err = xshm_register_driver(&cfshm_drv);
+	if (err) {
+		printk(KERN_ERR "Could not register SHM driver: %d.\n",
+			err);
+		goto err_dev_register;
+	}
+
+#ifdef CONFIG_DEBUG_FS
+	debugfsdir = debugfs_create_dir("xshm_chr", NULL);
+#endif
+
+ err_dev_register:
+	return err;
+
+}
+
+static void __exit xshmchr_chrexit_module(void)
+{
+	int result;
+	struct xshmchr_char_dev *dev = NULL;
+
+	/* Unregister xshm driver. */
+	xshm_unregister_driver(&cfshm_drv);
+
+	do {
+		/* Remove any device (the first in the list). */
+		dev = find_device(-1, NULL, 0);
+		result = chrdev_remove(dev);
+	} while (result == 0);
+
+#ifdef CONFIG_DEBUG_FS
+	if (debugfsdir != NULL)
+		debugfs_remove_recursive(debugfsdir);
+#endif
+
+}
+
+module_init(xshmchr_chrinit_module);
+module_exit(xshmchr_chrexit_module);
-- 
1.7.1


^ permalink raw reply related	[flat|nested] 12+ messages in thread

* [PATCHv4 10/11] xshm: Makefile and Kconfig for M7400 Shared Memory Drivers
  2011-12-16 10:49 [PATCHv4 00/11] XSHM: Shared Memory Driver for ST-E Thor M7400 LTE modem Sjur Brændeland
                   ` (8 preceding siblings ...)
  2011-12-16 10:49 ` [PATCHv4 09/11] xshm: Character device for XSHM channel access Sjur Brændeland
@ 2011-12-16 10:49 ` Sjur Brændeland
  2011-12-16 10:49 ` [PATCHv4 11/11] caif-xshm: Add CAIF driver for Shared memory for M7400 Sjur Brændeland
  10 siblings, 0 replies; 12+ messages in thread
From: Sjur Brændeland @ 2011-12-16 10:49 UTC (permalink / raw)
  To: linux-kernel; +Cc: Arnd Bergmann, Linus Walleij, sjurbren, Sjur Brændeland

CONFIG_XSHM should be selected from architectures supporting
C2C, this will enable the XSHM configuration.

Signed-off-by: Sjur Brændeland <sjur.brandeland@stericsson.com>
---
 drivers/Kconfig       |    2 +
 drivers/Makefile      |    1 +
 drivers/xshm/Kconfig  |   61 +++++++++++++++++++++++++++++++++++++++++++++++++
 drivers/xshm/Makefile |    5 ++++
 4 files changed, 69 insertions(+), 0 deletions(-)
 create mode 100644 drivers/xshm/Kconfig
 create mode 100644 drivers/xshm/Makefile

diff --git a/drivers/Kconfig b/drivers/Kconfig
index b5e6f24..c91a13c 100644
--- a/drivers/Kconfig
+++ b/drivers/Kconfig
@@ -136,4 +136,6 @@ source "drivers/hv/Kconfig"
 
 source "drivers/devfreq/Kconfig"
 
+source "drivers/xshm/Kconfig"
+
 endmenu
diff --git a/drivers/Makefile b/drivers/Makefile
index 1b31421..eedd8e5 100644
--- a/drivers/Makefile
+++ b/drivers/Makefile
@@ -120,6 +120,7 @@ obj-$(CONFIG_VLYNQ)		+= vlynq/
 obj-$(CONFIG_STAGING)		+= staging/
 obj-y				+= platform/
 obj-y				+= ieee802154/
+obj-$(CONFIG_XSHM)		+= xshm/
 #common clk code
 obj-y				+= clk/
 
diff --git a/drivers/xshm/Kconfig b/drivers/xshm/Kconfig
new file mode 100644
index 0000000..0993346
--- /dev/null
+++ b/drivers/xshm/Kconfig
@@ -0,0 +1,61 @@
+#
+# XSHM Framework
+#
+
+config XSHM
+	tristate
+	default n
+	depends on EXPERIMENTAL
+
+if XSHM && NET
+
+menu "XSHM drivers and interfaces"
+
+config DUMMY_C2C
+	tristate "Dummy C2C GENIO driver"
+	depends on !C2C
+	default XSHM
+	---help---
+	Say "Y" or "M" if you want to compile a dummy C2C driver.
+	Use this if you want to compile without the real
+	C2C driver available.
+
+config XSHM_BUS
+	tristate
+	default XSHM_DEV
+
+config XSHM_BOOT
+	tristate
+	depends on XSHM_DEV
+	default XSHM_DEV
+
+config XSHM_DEV
+	tristate "XSHM Channels for External Shared Memory (XSHM)"
+	depends on DUMMY_C2C || C2C
+	select XSHM_BUS
+	select XSHM_BOOT
+	default XSHM
+	---help---
+	Say "Y" to use External Shared Memory (XSHM) IPC mechanism.
+	XSHM is an IPC protocol used to talk to external device such
+	as modem over a shared memory (e.g. Chip to Chip).
+	Only say "M" here if you want to test XSHM and need to load
+	and unload its module.
+	If unsure say N.
+
+config XSHM_CHR
+	tristate "Character device for External Shared Memory (XSHM)"
+	depends on XSHM_DEV
+	default XSHM_DEV
+	---help---
+	Say "Y" to use a character device for the External Shared
+	Memory (XSHM) IPC mechanism. XSHM is an IPC protocol used to
+	talk to external device such as modem over a shared memory
+	(e.g. Chip to Chip).
+	Only say "M" here if you want to test XSHM and need to load
+	and unload its module.
+	If unsure say N.
+
+endmenu
+
+endif
diff --git a/drivers/xshm/Makefile b/drivers/xshm/Makefile
new file mode 100644
index 0000000..001cd79
--- /dev/null
+++ b/drivers/xshm/Makefile
@@ -0,0 +1,5 @@
+obj-$(CONFIG_XSHM_BUS) += xshm_bus.o
+obj-$(CONFIG_XSHM_BOOT) += xshm_boot.o
+obj-$(CONFIG_XSHM_DEV) += xshm_dev.o
+obj-$(CONFIG_XSHM_CHR) +=  xshm_chr.o
+obj-$(CONFIG_DUMMY_C2C) +=  dummy_c2c_genio.o
-- 
1.7.1


^ permalink raw reply related	[flat|nested] 12+ messages in thread

* [PATCHv4 11/11] caif-xshm: Add CAIF driver for Shared memory for M7400
  2011-12-16 10:49 [PATCHv4 00/11] XSHM: Shared Memory Driver for ST-E Thor M7400 LTE modem Sjur Brændeland
                   ` (9 preceding siblings ...)
  2011-12-16 10:49 ` [PATCHv4 10/11] xshm: Makefile and Kconfig for M7400 Shared Memory Drivers Sjur Brændeland
@ 2011-12-16 10:49 ` Sjur Brændeland
  10 siblings, 0 replies; 12+ messages in thread
From: Sjur Brændeland @ 2011-12-16 10:49 UTC (permalink / raw)
  To: linux-kernel
  Cc: Arnd Bergmann, Linus Walleij, sjurbren, Sjur Brændeland,
	David S. Miller

This patch introduces a caif shared memory link layer driver
for ST-Ericsson's Thor M7400 LTE modem.

M7400 uses a ring-buffer in shared memory for transporting data from the modem.
Each ring-buffer element contains an array of caif frames. caif_xshm calls
napi_schedule() when receiving notification about incoming data.
The napi-poll function copies data from the ring-buffer to SKBs until
ring-buffer is empty, or quota is exceeded.

If transmit ring-buffer is full, it also uses napi for scheduling transmission
of queued transmit buffer.

cc: David S. Miller <davem@davemloft.net>
Acked-by: David S. Miller <davem@davemloft.net>
Signed-off-by: Sjur Brændeland <sjur.brandeland@stericsson.com>
---
 drivers/net/caif/Kconfig     |   10 +
 drivers/net/caif/Makefile    |    1 +
 drivers/net/caif/caif_xshm.c |  915 ++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 926 insertions(+), 0 deletions(-)
 create mode 100644 drivers/net/caif/caif_xshm.c

diff --git a/drivers/net/caif/Kconfig b/drivers/net/caif/Kconfig
index abf4d7a..54364de 100644
--- a/drivers/net/caif/Kconfig
+++ b/drivers/net/caif/Kconfig
@@ -47,3 +47,13 @@ config CAIF_HSI
        The caif low level driver for CAIF over HSI.
        Be aware that if you enable this then you also need to
        enable a low-level HSI driver.
+
+config CAIF_XSHM
+	tristate "CAIF external memory protocol driver"
+	depends on XSHM_BUS && CAIF
+	default n
+	---help---
+	Say "Y" if you want to support CAIF over External Shared Memory (XSHM)
+	IPC mechanism (e.g. over Chip to Chip). Only say M here if you want to
+	test CAIF over XSHM and need to load and unload its module.
+	If unsure say N.
diff --git a/drivers/net/caif/Makefile b/drivers/net/caif/Makefile
index 91dff86..9310b24 100644
--- a/drivers/net/caif/Makefile
+++ b/drivers/net/caif/Makefile
@@ -10,6 +10,7 @@ obj-$(CONFIG_CAIF_SPI_SLAVE) += cfspi_slave.o
 # Shared memory
 caif_shm-objs := caif_shmcore.o caif_shm_u5500.o
 obj-$(CONFIG_CAIF_SHM) += caif_shm.o
+obj-$(CONFIG_CAIF_XSHM) += caif_xshm.o
 
 # HSI interface
 obj-$(CONFIG_CAIF_HSI) += caif_hsi.o
diff --git a/drivers/net/caif/caif_xshm.c b/drivers/net/caif/caif_xshm.c
new file mode 100644
index 0000000..21ff087
--- /dev/null
+++ b/drivers/net/caif/caif_xshm.c
@@ -0,0 +1,915 @@
+/*
+ * Copyright (C) ST-Ericsson AB 2011
+ * Contact: Sjur Brændeland / sjur.brandeland@stericsson.com
+ * Authors: Sjur Brændeland / sjur.brandeland@stericsson.com
+ *	   Daniel Martensson / daniel.martensson@stericsson.com
+ * License terms: GNU General Public License (GPL) version 2
+ */
+
+#define pr_fmt(fmt) KBUILD_MODNAME ": %s() :" fmt, __func__
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/spinlock.h>
+#include <linux/sched.h>
+#include <linux/list.h>
+#include <linux/netdevice.h>
+#include <linux/if_arp.h>
+#include <linux/xshm/xshm_dev.h>
+#include <net/rtnetlink.h>
+#include <net/caif/caif_device.h>
+#include <net/caif/caif_layer.h>
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Daniel Martensson <daniel.martensson@stericsson.com>");
+MODULE_AUTHOR("Sjur Brændeland <sjur.brandeland@stericsson.com>");
+MODULE_DESCRIPTION("CAIF SHM driver");
+
+#define CONNECT_TIMEOUT (3 * HZ)
+#define CAIF_NEEDED_HEADROOM	32
+#define CAIF_FLOW_ON		1
+#define CAIF_FLOW_OFF		0
+
+#define LOW_XOFF_WATERMARK	50
+#define HIGH_XOFF_WATERMARK	70
+#define STUFF_MARK		30
+
+struct ringbuf {
+	__le32	*rip;
+	__le32	*wip;
+	u32	size;
+	__le32	*bufsize;
+};
+
+struct shm_pck_desc {
+	/* Offset from start of channel to CAIF frame. */
+	u32 offset;
+	u32 length;
+} __packed;
+
+struct shm_caif_frm {
+	/* Number of bytes of padding before the CAIF frame. */
+	u8 hdr_ofs;
+} __packed;
+
+#define SHM_HDR_LEN sizeof(struct shm_caif_frm)
+
+struct shmbuffer {
+/* Static part: */
+	u8 *addr;
+	u32 index;
+	u32 len;
+/* Dynamic part: */
+	u32 frames;
+	/* Offset from start of buffer to CAIF frame. */
+	u32 frm_ofs;
+};
+
+enum CFSHM_STATE {
+	CFSHM_CLOSED = 1,
+	CFSHM_OPENING,
+	CFSHM_OPEN
+};
+
+struct cfshm {
+	/* caif_dev_common must always be first in the structure*/
+	struct caif_dev_common cfdev;
+	struct xshm_dev *xshm;
+	struct napi_struct napi;
+	struct ringbuf tx;
+	struct sk_buff_head sk_qhead;
+	spinlock_t lock;
+	struct ringbuf rx;
+	u8 *rx_ringbuf;
+	u32 rx_frms_pr_buf;
+	u32 rx_alignment;
+	struct shmbuffer **rx_bufs;
+	struct net_device *ndev;
+
+	u32 tx_frms_pr_buf;
+	u32 tx_alignment;
+	struct shmbuffer **tx_bufs;
+	u8 *tx_ringbuf;
+	u32 tx_flow_on;
+	u32 high_xoff_water;
+	u32 low_xoff_water;
+	u32 stuff_mark;
+	atomic_t dbg_smp_rxactive;
+	enum CFSHM_STATE state;
+	wait_queue_head_t netmgmt_wq;
+};
+
+static unsigned int ringbuf_used(struct ringbuf *rb)
+{
+	if (le32_to_cpu(*rb->wip) >= le32_to_cpu(*rb->rip))
+		return le32_to_cpu(*rb->wip) - le32_to_cpu(*rb->rip);
+	else
+		return rb->size - le32_to_cpu(*rb->rip) + le32_to_cpu(*rb->wip);
+}
+
+static int ringbuf_get_writepos(struct ringbuf *rb)
+{
+	if ((le32_to_cpu(*rb->wip) + 1) % rb->size == le32_to_cpu(*rb->rip))
+		return -1;
+	else
+		return le32_to_cpu(*rb->wip);
+}
+
+static int ringbuf_get_readpos(struct ringbuf *rb)
+{
+
+	if (le32_to_cpu(*rb->wip) == le32_to_cpu(*rb->rip))
+		return -1;
+	else
+		return le32_to_cpu(*rb->rip);
+}
+
+static int ringbuf_upd_writeptr(struct ringbuf *rb)
+{
+	if (!WARN_ON((le32_to_cpu(*rb->wip) + 1) % rb->size ==
+					le32_to_cpu(*rb->rip))) {
+
+		*rb->wip = cpu_to_le32((le32_to_cpu(*rb->wip) + 1) % rb->size);
+		/* Do write barrier before updating index */
+		smp_wmb();
+	}
+	return le32_to_cpu(*rb->wip);
+}
+
+static void ringbuf_upd_readptr(struct ringbuf *rb)
+{
+	if (!WARN_ON(le32_to_cpu(*rb->wip) == le32_to_cpu(*rb->rip))) {
+		*rb->rip = cpu_to_le32((le32_to_cpu(*rb->rip) + 1) % rb->size);
+		/* Do write barrier before updating index */
+		smp_wmb();
+	}
+}
+
+
+
+static struct shmbuffer *get_rx_buf(struct cfshm *cfshm)
+{
+	struct shmbuffer *pbuf = NULL;
+	int idx = ringbuf_get_readpos(&cfshm->rx);
+
+	if (idx < 0)
+		goto out;
+	pbuf = cfshm->rx_bufs[idx];
+out:
+	return pbuf;
+}
+
+static struct shmbuffer *new_rx_buf(struct cfshm *cfshm)
+{
+	struct shmbuffer *pbuf = get_rx_buf(cfshm);
+
+	WARN_ON(!spin_is_locked(&cfshm->lock));
+	if (pbuf)
+		pbuf->frames = 0;
+
+	return pbuf;
+}
+
+static struct shmbuffer *get_tx_buf(struct cfshm *cfshm)
+{
+	int idx = ringbuf_get_writepos(&cfshm->tx);
+
+	if (idx < 0)
+		return NULL;
+	return cfshm->tx_bufs[idx];
+}
+
+inline struct shmbuffer *tx_bump_buf(struct cfshm *cfshm,
+			struct shmbuffer *pbuf)
+{
+	u32 desc_size;
+	struct shmbuffer *newpbuf = pbuf;
+
+	WARN_ON(!spin_is_locked(&cfshm->lock));
+	if (pbuf) {
+		cfshm->xshm->cfg.tx.buf_size[pbuf->index] =
+			cpu_to_le32(pbuf->frm_ofs);
+		ringbuf_upd_writeptr(&cfshm->tx);
+		newpbuf = get_tx_buf(cfshm);
+		/* Reset buffer parameters. */
+		desc_size = (cfshm->tx_frms_pr_buf + 1) *
+			sizeof(struct shm_pck_desc);
+		pbuf->frm_ofs = desc_size + (desc_size % cfshm->rx_alignment);
+		pbuf->frames = 0;
+
+	}
+	return newpbuf;
+}
+
+static struct shmbuffer *shm_rx_func(struct cfshm *cfshm, int quota)
+{
+	struct shmbuffer *pbuf;
+	struct sk_buff *skb;
+	int ret;
+	unsigned long flags;
+
+	pbuf = get_rx_buf(cfshm);
+	while (pbuf) {
+		/* Retrieve pointer to start of the packet descriptor area. */
+		struct shm_pck_desc *pck_desc =
+			((struct shm_pck_desc *) pbuf->addr) + pbuf->frames;
+		u32 offset;
+
+		/* Loop until descriptor contains zero offset */
+		while ((offset = pck_desc->offset)) {
+			unsigned int caif_len;
+			struct shm_caif_frm *frm;
+			u32 length = pck_desc->length;
+			u8 hdr_ofs;
+			frm = (struct shm_caif_frm *)(pbuf->addr + offset);
+			hdr_ofs = frm->hdr_ofs;
+			caif_len =
+				length - SHM_HDR_LEN -
+				hdr_ofs;
+
+			pr_devel("copy data buf:%d frm:%d offs:%d @%x len:%d\n",
+					pbuf->index, pbuf->frames, offset,
+					(u32) (SHM_HDR_LEN + hdr_ofs + offset +
+						pbuf->addr - cfshm->rx_ringbuf),
+					length);
+
+			/* Check whether number of frames is below limit */
+			if (pbuf->frames > cfshm->rx_frms_pr_buf) {
+				pr_warn("Too many frames in buffer.\n");
+				++cfshm->ndev->stats.rx_frame_errors;
+				goto desc_err;
+			}
+
+			/* Check whether offset is below low limits */
+			if (pbuf->addr + offset
+					<= (u8 *)(pck_desc + 1)) {
+				pr_warn("Offset in desc. below buffer area.\n");
+				++cfshm->ndev->stats.rx_frame_errors;
+				goto desc_err;
+			}
+
+			/* Check whether offset above upper limit */
+			if (offset + length > pbuf->len) {
+				pr_warn("Offset outside buffer area:\n");
+				++cfshm->ndev->stats.rx_frame_errors;
+				goto desc_err;
+			}
+
+			skb = netdev_alloc_skb(cfshm->ndev,
+							caif_len + 1);
+			if (skb == NULL) {
+				pr_debug("Couldn't allocate SKB\n");
+				++cfshm->ndev->stats.rx_dropped;
+				goto out;
+			}
+
+			memcpy(skb_put(skb, caif_len),
+					SHM_HDR_LEN + hdr_ofs +
+					offset + pbuf->addr,
+					caif_len);
+
+			skb->protocol = htons(ETH_P_CAIF);
+			skb_reset_mac_header(skb);
+			skb->dev = cfshm->ndev;
+
+			/* Push received packet up the stack. */
+			ret = netif_receive_skb(skb);
+
+			if (!ret) {
+				cfshm->ndev->stats.rx_packets++;
+				cfshm->ndev->stats.rx_bytes +=
+					length;
+			} else
+				++cfshm->ndev->stats.rx_dropped;
+			/* Move to next packet descriptor. */
+			pck_desc++;
+
+			pbuf->frames++;
+			if (--quota <= 0) {
+				pr_devel("Quota exeeded (pbuf:%p)\n", pbuf);
+				goto out;
+			}
+		}
+desc_err:
+		pbuf->frames = 0;
+
+		spin_lock_irqsave(&cfshm->lock, flags);
+		ringbuf_upd_readptr(&cfshm->rx);
+		pbuf = new_rx_buf(cfshm);
+		spin_unlock_irqrestore(&cfshm->lock, flags);
+
+	}
+	cfshm->xshm->ipc_rx_release(cfshm->xshm, false);
+out:
+	return pbuf;
+}
+
+static int insert_skb_in_buf(struct cfshm *cfshm, struct sk_buff *skb,
+					struct shmbuffer *pbuf)
+{
+	struct shm_pck_desc *pck_desc;
+	unsigned int frmlen;
+	struct shm_caif_frm *frm;
+	u8 hdr_ofs;
+	struct caif_payload_info *info = (struct caif_payload_info *)&skb->cb;
+
+	WARN_ON(!spin_is_locked(&cfshm->lock));
+
+	if (unlikely(pbuf->frames >= cfshm->tx_frms_pr_buf)) {
+		pr_devel("-ENOSPC exeeded frames: %d >= %d\n",
+				pbuf->frames, cfshm->tx_frms_pr_buf);
+		return -ENOSPC;
+	}
+
+	/*
+	 * Align the address of the entire CAIF frame (incl padding),
+	 * so the modem can do efficient DMA of this frame
+	 * FIXME: Alignment is power of to, so it could use binary ops.
+	 */
+	pbuf->frm_ofs = roundup(pbuf->frm_ofs, cfshm->tx_alignment);
+
+
+	/* Make the payload (IP packet) inside the frame aligned */
+	hdr_ofs = (unsigned long) &pbuf->frm_ofs;
+	hdr_ofs = roundup(hdr_ofs + SHM_HDR_LEN + info->hdr_len,
+			cfshm->tx_alignment);
+
+	frm = (struct shm_caif_frm *)
+		(pbuf->addr + pbuf->frm_ofs);
+
+	frmlen = SHM_HDR_LEN + hdr_ofs + skb->len;
+
+	/*
+	 * Verify that packet, header and additional padding
+	 * can fit within the buffer frame area.
+	 */
+	if (pbuf->len < pbuf->frm_ofs + frmlen) {
+		pr_devel("-ENOSPC exeeded offset %d < %d\n",
+				pbuf->len, pbuf->frm_ofs + frmlen);
+		return -ENOSPC;
+	}
+
+	/* Copy in CAIF frame. */
+	frm->hdr_ofs = hdr_ofs;
+	skb_copy_bits(skb, 0, pbuf->addr +
+			pbuf->frm_ofs + SHM_HDR_LEN +
+			hdr_ofs, skb->len);
+
+	pr_devel("copy data buf:%d frm:%d offs:%d @%d len:%d\n",
+			pbuf->index, pbuf->frames,
+			pbuf->frm_ofs,
+			(u32) (pbuf->addr + pbuf->frm_ofs +
+				SHM_HDR_LEN + hdr_ofs - cfshm->tx_ringbuf),
+			skb->len);
+
+	cfshm->ndev->stats.tx_packets++;
+	cfshm->ndev->stats.tx_bytes += frmlen;
+	/* Fill in the shared memory packet descriptor area. */
+	pck_desc = (struct shm_pck_desc *) (pbuf->addr);
+	/* Forward to current frame. */
+	pck_desc += pbuf->frames;
+	pck_desc->offset = pbuf->frm_ofs;
+	pck_desc->length = frmlen;
+	/* Terminate packet descriptor area. */
+	pck_desc++;
+	pck_desc->offset = 0;
+	pck_desc->length = 0;
+	/* Update buffer parameters. */
+	pbuf->frames++;
+	pbuf->frm_ofs += frmlen;
+
+	return 0;
+}
+
+static struct shmbuffer *queue_to_ringbuf(struct cfshm *cfshm, int *new_bufs)
+{
+	struct shmbuffer *pbuf;
+	struct sk_buff *skb;
+	int err;
+
+	WARN_ON(!spin_is_locked(&cfshm->lock));
+
+	pbuf = get_tx_buf(cfshm);
+	while (pbuf != NULL) {
+		skb = skb_peek(&cfshm->sk_qhead);
+		if (skb == NULL)
+			break;
+		err = insert_skb_in_buf(cfshm, skb, pbuf);
+		if (unlikely(err == -ENOSPC)) {
+			pr_devel("No more space in buffer\n");
+			++(*new_bufs);
+			pbuf = tx_bump_buf(cfshm, pbuf);
+			continue;
+		}
+		skb = skb_dequeue(&cfshm->sk_qhead);
+		/* We're always in NET_*_SOFTIRQ */
+		dev_kfree_skb(skb);
+	}
+	return pbuf;
+}
+
+static int shm_netdev_open(struct net_device *netdev)
+{
+	struct cfshm *cfshm = netdev_priv(netdev);
+	int ret, err = 0;
+
+	cfshm->state = CFSHM_OPENING;
+	if (cfshm->xshm != NULL && cfshm->xshm->open != NULL)
+		err = cfshm->xshm->open(cfshm->xshm);
+	if (err)
+		goto error;
+
+	rtnl_unlock();  /* Release RTNL lock during connect wait */
+	ret = wait_event_interruptible_timeout(cfshm->netmgmt_wq,
+			cfshm->state != CFSHM_OPENING,
+			CONNECT_TIMEOUT);
+	rtnl_lock();
+
+	if (ret == 0) {
+		pr_debug("connect timeout\n");
+		err = -ETIMEDOUT;
+		goto error;
+	}
+
+	if (cfshm->state !=  CFSHM_OPEN) {
+		pr_debug("connect failed\n");
+		err = -ECONNREFUSED;
+		goto error;
+	}
+
+	napi_enable(&cfshm->napi);
+	return 0;
+error:
+	if (cfshm->xshm != NULL && cfshm->xshm->close != NULL)
+		cfshm->xshm->close(cfshm->xshm);
+	return err;
+}
+
+static int shm_netdev_close(struct net_device *netdev)
+{
+	struct cfshm *cfshm = netdev_priv(netdev);
+
+	napi_disable(&cfshm->napi);
+
+	if (cfshm->xshm != NULL && cfshm->xshm->close != NULL)
+		cfshm->xshm->close(cfshm->xshm);
+
+	return 0;
+}
+
+static int open_cb(void *drv)
+{
+	struct cfshm *cfshm = drv;
+
+	cfshm->state = CFSHM_OPEN;
+	netif_carrier_on(cfshm->ndev);
+	wake_up_interruptible(&cfshm->netmgmt_wq);
+	return 0;
+}
+
+static void close_cb(void *drv)
+{
+	struct cfshm *cfshm = drv;
+
+	cfshm->state = CFSHM_CLOSED;
+	netif_carrier_off(cfshm->ndev);
+	wake_up_interruptible(&cfshm->netmgmt_wq);
+}
+
+static int caif_shmdrv_rx_cb(void *drv)
+{
+	struct cfshm *cfshm = drv;
+
+	if (unlikely(*cfshm->xshm->cfg.rx.state == cpu_to_le32(XSHM_CLOSED)))
+		return -ESHUTDOWN;
+
+	napi_schedule(&cfshm->napi);
+	return 0;
+}
+
+static int send_pending_txbufs(struct cfshm *cfshm, int usedbufs)
+{
+	unsigned long flags;
+
+	/* Send the started buffer if used buffers are low enough */
+	WARN_ON(!spin_is_locked(&cfshm->lock));
+	if (likely(usedbufs < cfshm->stuff_mark)) {
+		struct shmbuffer *pbuf = get_tx_buf(cfshm);
+		if (unlikely(pbuf->frames > 0)) {
+			if (spin_trylock_irqsave(&cfshm->lock, flags)) {
+				WARN_ON(!spin_is_locked(&cfshm->lock));
+				pbuf = get_tx_buf(cfshm);
+				tx_bump_buf(cfshm, pbuf);
+				spin_unlock_irqrestore(&cfshm->lock, flags);
+				cfshm->xshm->ipc_tx(cfshm->xshm);
+				return 0;
+			} else {
+				return -EBUSY;
+			}
+		}
+	}
+	return 0;
+}
+
+static int caif_shmdrv_tx_release_cb(void *drv)
+{
+	struct cfshm *cfshm = drv;
+	int usedbufs;
+
+	usedbufs = ringbuf_used(&cfshm->tx);
+
+	/* Send flow-on if we have sent flow-off and get below low-water */
+	if (usedbufs <= cfshm->low_xoff_water && !cfshm->tx_flow_on) {
+		pr_debug("Flow on\n");
+		cfshm->tx_flow_on = true;
+		cfshm->cfdev.flowctrl(cfshm->ndev, CAIF_FLOW_ON);
+	}
+
+	/* If ringbuf is full, schedule NAPI to start sending */
+	if (skb_peek(&cfshm->sk_qhead) != NULL) {
+		pr_debug("Schedule NAPI to empty queue\n");
+		napi_schedule(&cfshm->napi);
+		return 0;
+	}
+
+	/* Send the started buffer if used buffers are low enough */
+	if (usedbufs < cfshm->stuff_mark) {
+		struct shmbuffer *pbuf = get_tx_buf(cfshm);
+		if (pbuf != NULL && pbuf->frames > 0)
+			napi_schedule(&cfshm->napi);
+	}
+	return 0;
+}
+
+static int shm_rx_poll(struct napi_struct *napi, int quota)
+{
+	struct cfshm *cfshm = container_of(napi, struct cfshm, napi);
+	int new_bufs;
+	struct shmbuffer *pbuf;
+	int usedbufs;
+	unsigned long flags;
+
+	/* Simply return if rx_poll is already called on other CPU */
+	if (atomic_read(&cfshm->dbg_smp_rxactive) > 0)
+		return quota;
+
+	WARN_ON(atomic_inc_return(&cfshm->dbg_smp_rxactive) > 1);
+
+	pbuf = shm_rx_func(cfshm, quota);
+
+	usedbufs = ringbuf_used(&cfshm->tx);
+
+	if (spin_trylock_irqsave(&cfshm->lock, flags)) {
+
+		/* Check if we're below "Stuff" limit, and send pending data */
+		send_pending_txbufs(cfshm, usedbufs);
+
+		/* Check if we have queued packets */
+		if (unlikely(skb_peek(&cfshm->sk_qhead) != NULL)) {
+			struct shmbuffer *txbuf;
+			WARN_ON(!spin_is_locked(&cfshm->lock));
+			pr_debug("Try to empty tx-queue\n");
+			new_bufs = 0;
+			txbuf = queue_to_ringbuf(cfshm, &new_bufs);
+
+			/* Bump out if we are configured with few buffers */
+			if (txbuf && cfshm->xshm->cfg.tx.buffers < 3) {
+				tx_bump_buf(cfshm, txbuf);
+
+				spin_unlock_irqrestore(&cfshm->lock, flags);
+				cfshm->xshm->ipc_tx(cfshm->xshm);
+				goto txdone;
+			}
+		}
+		spin_unlock_irqrestore(&cfshm->lock, flags);
+	}
+txdone:
+
+	if (pbuf == NULL)
+		napi_complete(&cfshm->napi);
+
+	atomic_dec(&cfshm->dbg_smp_rxactive);
+	return 0;
+}
+
+static int shm_netdev_tx(struct sk_buff *skb, struct net_device *shm_netdev)
+{
+	struct shmbuffer *pbuf = NULL;
+	int usedbufs;
+	int new_bufs = 0;
+	struct cfshm *cfshm = netdev_priv(shm_netdev);
+	unsigned long flags;
+
+	/*
+	 * If we have packets in queue, keep queueing to avoid
+	 * out-of-order delivery
+	 */
+	spin_lock_irqsave(&cfshm->lock, flags);
+
+	skb_queue_tail(&cfshm->sk_qhead, skb);
+	pbuf = queue_to_ringbuf(cfshm, &new_bufs);
+
+	usedbufs = ringbuf_used(&cfshm->tx);
+
+	if (usedbufs > cfshm->high_xoff_water && cfshm->tx_flow_on) {
+		pr_debug("Flow off\n");
+		cfshm->tx_flow_on = false;
+		spin_unlock_irqrestore(&cfshm->lock, flags);
+		cfshm->cfdev.flowctrl(cfshm->ndev, CAIF_FLOW_OFF);
+		return 0;
+	}
+
+	/* Check if we should accumulate more packets */
+	if (new_bufs == 0 && usedbufs > cfshm->stuff_mark) {
+		spin_unlock_irqrestore(&cfshm->lock, flags);
+		return 0;
+	}
+	tx_bump_buf(cfshm, pbuf);
+	spin_unlock_irqrestore(&cfshm->lock, flags);
+	cfshm->xshm->ipc_tx(cfshm->xshm);
+	return 0;
+}
+
+static const struct net_device_ops netdev_ops = {
+	.ndo_open = shm_netdev_open,
+	.ndo_stop = shm_netdev_close,
+	.ndo_start_xmit = shm_netdev_tx,
+};
+
+static void shm_netdev_setup(struct net_device *pshm_netdev)
+{
+	struct cfshm *cfshm;
+
+	cfshm = netdev_priv(pshm_netdev);
+	pshm_netdev->netdev_ops = &netdev_ops;
+	pshm_netdev->type = ARPHRD_CAIF;
+	pshm_netdev->hard_header_len = CAIF_NEEDED_HEADROOM;
+	pshm_netdev->tx_queue_len = 0;
+	pshm_netdev->destructor = free_netdev;
+
+	/* Initialize structures in a clean state. */
+	memset(cfshm, 0, sizeof(struct cfshm));
+}
+
+static void deinit_bufs(struct cfshm *cfshm)
+{
+	int j;
+
+	if (cfshm == NULL)
+		return;
+
+	for (j = 0; j < cfshm->xshm->cfg.rx.buffers; j++)
+		kfree(cfshm->rx_bufs[j]);
+	kfree(cfshm->rx_bufs);
+
+	for (j = 0; j < cfshm->xshm->cfg.tx.buffers; j++)
+		kfree(cfshm->tx_bufs[j]);
+	kfree(cfshm->tx_bufs);
+}
+
+static int cfshm_probe(struct xshm_dev *xshm)
+{
+	int err, j;
+	struct cfshm *cfshm = NULL;
+	struct net_device *netdev;
+	u32 buf_size;
+	unsigned long flags;
+
+	if (xshm == NULL)
+		return -EINVAL;
+	if (xshm->cfg.tx.addr == NULL || xshm->cfg.rx.addr == NULL) {
+		pr_debug("Shared Memory are not configured\n");
+		return -EINVAL;
+	}
+
+	if (xshm->cfg.tx.ch_size / xshm->cfg.tx.buffers <
+			xshm->cfg.tx.packets * sizeof(struct shm_pck_desc) +
+				xshm->cfg.tx.mtu) {
+		pr_warn("Bad packet TX-channel size");
+		return -EINVAL;
+	}
+
+	if (xshm->cfg.rx.ch_size / xshm->cfg.rx.buffers <
+			sizeof(struct shm_pck_desc) + xshm->cfg.rx.mtu) {
+		pr_warn("Bad packet RX-channel size");
+		return -EINVAL;
+	}
+
+	if (xshm->cfg.rx.buffers < 2 || xshm->cfg.tx.buffers < 2) {
+		pr_warn("Too few buffers in channel");
+		return -EINVAL;
+	}
+
+	err = -ENOMEM;
+	netdev = alloc_netdev(sizeof(struct cfshm), xshm->cfg.name,
+			shm_netdev_setup);
+
+	if (netdev == NULL)
+		goto error;
+
+	cfshm = netdev_priv(netdev);
+	cfshm->state = CFSHM_CLOSED;
+	init_waitqueue_head(&cfshm->netmgmt_wq);
+
+	cfshm->xshm = xshm;
+	xshm->driver_data = cfshm;
+	cfshm->ndev = netdev;
+	netdev->mtu = xshm->cfg.tx.mtu;
+	cfshm->high_xoff_water =
+		(xshm->cfg.rx.buffers * HIGH_XOFF_WATERMARK) / 100;
+	cfshm->low_xoff_water =
+		(xshm->cfg.rx.buffers * LOW_XOFF_WATERMARK) / 100;
+	cfshm->stuff_mark = (xshm->cfg.rx.buffers * STUFF_MARK) / 100;
+
+	cfshm->tx_frms_pr_buf = xshm->cfg.tx.packets;
+	cfshm->rx_frms_pr_buf = xshm->cfg.rx.packets;
+	cfshm->rx_alignment = xshm->cfg.rx.alignment;
+	cfshm->tx_alignment = xshm->cfg.tx.alignment;
+
+	if (xshm->cfg.latency)
+		cfshm->cfdev.link_select = CAIF_LINK_LOW_LATENCY;
+	else
+		cfshm->cfdev.link_select = CAIF_LINK_HIGH_BANDW;
+
+	cfshm->tx.rip = xshm->cfg.tx.read;
+	cfshm->tx.wip = xshm->cfg.tx.write;
+	cfshm->tx.bufsize = xshm->cfg.tx.buf_size;
+	cfshm->tx.size = xshm->cfg.tx.buffers;
+
+	cfshm->rx.rip = xshm->cfg.rx.read;
+	cfshm->rx.wip = xshm->cfg.rx.write;
+	cfshm->rx.bufsize = xshm->cfg.rx.buf_size;
+	cfshm->rx.size = xshm->cfg.rx.buffers;
+	pr_devel("RX ri:%d wi:%d size:%d\n",
+		le32_to_cpu(*cfshm->rx.rip),
+			le32_to_cpu(*cfshm->rx.wip), cfshm->rx.size);
+	pr_devel("TX ri:%d wi:%d size:%d\n",
+		le32_to_cpu(*cfshm->tx.rip),
+			le32_to_cpu(*cfshm->tx.wip), cfshm->rx.size);
+	pr_devel("frms_pr_buf:%d %d\n", cfshm->rx_frms_pr_buf,
+			cfshm->tx_frms_pr_buf);
+
+	spin_lock_init(&cfshm->lock);
+	netif_carrier_off(netdev);
+	skb_queue_head_init(&cfshm->sk_qhead);
+
+	pr_devel("SHM DEVICE[%p] PROBED BY DRIVER, NEW SHM DRIVER"
+			" INSTANCE AT cfshm =0x%p\n",
+			cfshm->xshm, cfshm);
+
+	cfshm->tx_ringbuf = xshm->cfg.tx.addr;
+	cfshm->rx_ringbuf = xshm->cfg.rx.addr;
+
+	pr_devel("TX-BASE:%p RX-BASE:%p\n",
+			cfshm->tx_ringbuf,
+			cfshm->rx_ringbuf);
+
+	cfshm->tx_bufs = kzalloc(sizeof(struct shmbuffer *) *
+			xshm->cfg.tx.buffers, GFP_KERNEL);
+	if (cfshm->tx_bufs == NULL)
+		goto error;
+	buf_size = xshm->cfg.tx.ch_size / xshm->cfg.tx.buffers;
+
+	pr_devel("TX: buffers:%d buf_size:%d frms:%d mtu:%d\n",
+			xshm->cfg.tx.buffers, buf_size,
+			cfshm->tx_frms_pr_buf, netdev->mtu);
+
+	for (j = 0; j < xshm->cfg.tx.buffers; j++) {
+		u32 desc_size;
+		struct shmbuffer *tx_buf =
+				kzalloc(sizeof(struct shmbuffer), GFP_KERNEL);
+
+		if (tx_buf == NULL) {
+			pr_warn("ERROR, Could not"
+					" allocate dynamic mem. for tx_buf, "
+					" Bailing out ...\n");
+			goto error;
+		}
+
+		tx_buf->index = j;
+
+		tx_buf->addr = cfshm->tx_ringbuf + (buf_size * j);
+		tx_buf->len = buf_size;
+		tx_buf->frames = 0;
+		desc_size = (cfshm->tx_frms_pr_buf + 1) *
+				sizeof(struct shm_pck_desc);
+
+		tx_buf->frm_ofs = desc_size + (desc_size % cfshm->tx_alignment);
+
+		cfshm->tx_bufs[j] = tx_buf;
+
+		pr_devel("tx_buf[%d] addr:%p len:%d\n",
+				tx_buf->index,
+				tx_buf->addr,
+				tx_buf->len);
+	}
+
+	cfshm->rx_bufs = kzalloc(sizeof(struct shmbuffer *) *
+				xshm->cfg.rx.buffers, GFP_KERNEL);
+	if (cfshm->rx_bufs == NULL)
+		goto error;
+	buf_size = xshm->cfg.tx.ch_size / xshm->cfg.tx.buffers;
+	pr_devel("RX: buffers:%d buf_size:%d frms:%d mtu:%d\n",
+			xshm->cfg.rx.buffers, buf_size,
+			cfshm->rx_frms_pr_buf, netdev->mtu);
+
+	for (j = 0; j < xshm->cfg.rx.buffers; j++) {
+		struct shmbuffer *rx_buf =
+				kzalloc(sizeof(struct shmbuffer), GFP_KERNEL);
+
+		if (rx_buf == NULL) {
+			pr_warn("ERROR, Could not"
+					" allocate dynamic mem.for rx_buf, "
+					" Bailing out ...\n");
+			goto error;
+		}
+
+		rx_buf->index = j;
+
+		rx_buf->addr = cfshm->rx_ringbuf + (buf_size * j);
+		rx_buf->len = buf_size;
+		cfshm->rx_bufs[j] = rx_buf;
+		pr_devel("rx_buf[%d] addr:%p len:%d\n",
+				rx_buf->index,
+				rx_buf->addr,
+				rx_buf->len);
+	}
+
+	cfshm->tx_flow_on = 1;
+	cfshm->xshm->ipc_rx_cb = caif_shmdrv_rx_cb;
+	cfshm->xshm->ipc_tx_release_cb = caif_shmdrv_tx_release_cb;
+	cfshm->xshm->open_cb = open_cb;
+	cfshm->xshm->close_cb = close_cb;
+
+	spin_lock_irqsave(&cfshm->lock, flags);
+	get_tx_buf(cfshm);
+	new_rx_buf(cfshm);
+	spin_unlock_irqrestore(&cfshm->lock, flags);
+
+	netif_napi_add(netdev, &cfshm->napi, shm_rx_poll,
+			2 * cfshm->rx_frms_pr_buf);
+
+	netdev->dev.parent = &xshm->dev;
+	err = register_netdev(netdev);
+	if (err) {
+		pr_warn("ERROR[%d], SHM could not, "
+			"register with NW FRMWK Bailing out ...\n", err);
+		goto error;
+	}
+	return err;
+error:
+	deinit_bufs(cfshm);
+	free_netdev(netdev);
+	return err;
+}
+
+static void cfshm_remove(struct xshm_dev *xshm)
+{
+	struct cfshm *cfshm;
+
+	if (xshm == NULL || xshm->driver_data == NULL)
+		return;
+
+	cfshm = xshm->driver_data;
+	deinit_bufs(cfshm);
+	unregister_netdev(cfshm->ndev);
+
+	xshm->ipc_rx_cb = NULL;
+	xshm->ipc_tx_release_cb = NULL;
+	xshm->open_cb = NULL;
+	xshm->close_cb = NULL;
+	xshm->driver_data = NULL;
+}
+
+static struct xshm_driver cfshm_drv = {
+	.mode = XSHM_PACKET_MODE,
+	.probe = cfshm_probe,
+	.remove = cfshm_remove,
+	.driver = {
+		.name = KBUILD_MODNAME,
+		.owner = THIS_MODULE,
+	},
+};
+
+static void __exit cfshm_exit_module(void)
+{
+	xshm_unregister_driver(&cfshm_drv);
+}
+
+static int __init cfshm_init_module(void)
+{
+	int err;
+
+	err = xshm_register_driver(&cfshm_drv);
+	if (err) {
+		printk(KERN_ERR "Could not register XSHM driver: %d.\n",
+			err);
+		goto err_dev_register;
+	}
+	return err;
+
+ err_dev_register:
+	return err;
+}
+
+module_init(cfshm_init_module);
+module_exit(cfshm_exit_module);
-- 
1.7.1


^ permalink raw reply related	[flat|nested] 12+ messages in thread

end of thread, other threads:[~2011-12-16 10:51 UTC | newest]

Thread overview: 12+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2011-12-16 10:49 [PATCHv4 00/11] XSHM: Shared Memory Driver for ST-E Thor M7400 LTE modem Sjur Brændeland
2011-12-16 10:49 ` [PATCHv4 01/11] xshm: Shared Memory layout for ST-E M7400 driver Sjur Brændeland
2011-12-16 10:49 ` [PATCHv4 02/11] xshm: Channel config definitions " Sjur Brændeland
2011-12-16 10:49 ` [PATCHv4 03/11] xshm: Configuration for XSHM Channel an devices Sjur Brændeland
2011-12-16 10:49 ` [PATCHv4 04/11] xshm: geni/geno driver interface Sjur Brændeland
2011-12-16 10:49 ` [PATCHv4 05/11] xshm: genio dummy driver Sjur Brændeland
2011-12-16 10:49 ` [PATCHv4 06/11] xshm: Add XSHM device bus Sjur Brændeland
2011-12-16 10:49 ` [PATCHv4 07/11] xshm: Add xshm device implementation Sjur Brændeland
2011-12-16 10:49 ` [PATCHv4 08/11] xshm: XSHM Configuration data handling Sjur Brændeland
2011-12-16 10:49 ` [PATCHv4 09/11] xshm: Character device for XSHM channel access Sjur Brændeland
2011-12-16 10:49 ` [PATCHv4 10/11] xshm: Makefile and Kconfig for M7400 Shared Memory Drivers Sjur Brændeland
2011-12-16 10:49 ` [PATCHv4 11/11] caif-xshm: Add CAIF driver for Shared memory for M7400 Sjur Brændeland

This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.