rapidio: add RapidIO channelized messaging driver
authorAlexandre Bounine <alexandre.bounine@idt.com>
Tue, 2 Aug 2016 21:06:25 +0000 (14:06 -0700)
committerLinus Torvalds <torvalds@linux-foundation.org>
Tue, 2 Aug 2016 23:35:31 +0000 (19:35 -0400)
Add channelized messaging driver to support native RapidIO messaging
exchange between multiple senders/recipients on devices that use kernel
RapidIO subsystem services.

This device driver is the result of collaboration within the RapidIO.org
Software Task Group (STG) between Texas Instruments, Prodrive
Technologies, Nokia Networks, BAE and IDT.  Additional input was
received from other members of RapidIO.org.

The objective was to create a character mode driver interface which
exposes messaging capabilities of RapidIO endpoint devices (mports)
directly to applications, in a manner that allows the numerous and
varied RapidIO implementations to interoperate.

This char mode device driver allows user-space applications to setup
messaging communication channels using single shared RapidIO messaging
mailbox.

By default this driver uses RapidIO MBOX_1 (MBOX_0 is reserved for use by
RIONET Ethernet emulation driver).

[weiyj.lk@gmail.com: rapidio/rio_cm: fix return value check in riocm_init()]
Link: http://lkml.kernel.org/r/1469198221-21970-1-git-send-email-alexandre.bounine@idt.com
Link: http://lkml.kernel.org/r/1468952862-18056-1-git-send-email-alexandre.bounine@idt.com
Signed-off-by: Alexandre Bounine <alexandre.bounine@idt.com>
Tested-by: Barry Wood <barry.wood@idt.com>
Cc: Matt Porter <mporter@kernel.crashing.org>
Cc: Aurelien Jacquiot <a-jacquiot@ti.com>
Cc: Andre van Herk <andre.van.herk@prodrive-technologies.com>
Cc: Barry Wood <barry.wood@idt.com>
Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
Documentation/rapidio/rio_cm.txt [new file with mode: 0644]
drivers/rapidio/Kconfig
drivers/rapidio/Makefile
drivers/rapidio/rio_cm.c [new file with mode: 0644]
include/uapi/linux/Kbuild
include/uapi/linux/rio_cm_cdev.h [new file with mode: 0644]

diff --git a/Documentation/rapidio/rio_cm.txt b/Documentation/rapidio/rio_cm.txt
new file mode 100644 (file)
index 0000000..27aa401
--- /dev/null
@@ -0,0 +1,119 @@
+RapidIO subsystem Channelized Messaging character device driver (rio_cm.c)
+==========================================================================
+
+Version History:
+----------------
+  1.0.0 - Initial driver release.
+
+==========================================================================
+
+I. Overview
+
+This device driver is the result of collaboration within the RapidIO.org
+Software Task Group (STG) between Texas Instruments, Prodrive Technologies,
+Nokia Networks, BAE and IDT.  Additional input was received from other members
+of RapidIO.org.
+
+The objective was to create a character mode driver interface which exposes
+messaging capabilities of RapidIO endpoint devices (mports) directly
+to applications, in a manner that allows the numerous and varied RapidIO
+implementations to interoperate.
+
+This driver (RIO_CM) provides to user-space applications shared access to
+RapidIO mailbox messaging resources.
+
+RapidIO specification (Part 2) defines that endpoint devices may have up to four
+messaging mailboxes in case of multi-packet message (up to 4KB) and
+up to 64 mailboxes if single-packet messages (up to 256 B) are used. In addition
+to protocol definition limitations, a particular hardware implementation can
+have reduced number of messaging mailboxes.  RapidIO aware applications must
+therefore share the messaging resources of a RapidIO endpoint.
+
+Main purpose of this device driver is to provide RapidIO mailbox messaging
+capability to large number of user-space processes by introducing socket-like
+operations using a single messaging mailbox.  This allows applications to
+use the limited RapidIO messaging hardware resources efficiently.
+
+Most of device driver's operations are supported through 'ioctl' system calls.
+
+When loaded this device driver creates a single file system node named rio_cm
+in /dev directory common for all registered RapidIO mport devices.
+
+Following ioctl commands are available to user-space applications:
+
+- RIO_CM_MPORT_GET_LIST : Returns to caller list of local mport devices that
+    support messaging operations (number of entries up to RIO_MAX_MPORTS).
+    Each list entry is combination of mport's index in the system and RapidIO
+    destination ID assigned to the port.
+- RIO_CM_EP_GET_LIST_SIZE : Returns number of messaging capable remote endpoints
+    in a RapidIO network associated with the specified mport device.
+- RIO_CM_EP_GET_LIST : Returns list of RapidIO destination IDs for messaging
+    capable remote endpoints (peers) available in a RapidIO network associated
+    with the specified mport device.
+- RIO_CM_CHAN_CREATE : Creates RapidIO message exchange channel data structure
+    with channel ID assigned automatically or as requested by a caller.
+- RIO_CM_CHAN_BIND : Binds the specified channel data structure to the specified
+    mport device.
+- RIO_CM_CHAN_LISTEN : Enables listening for connection requests on the specified
+    channel.
+- RIO_CM_CHAN_ACCEPT : Accepts a connection request from peer on the specified
+    channel. If wait timeout for this request is specified by a caller it is
+    a blocking call. If timeout set to 0 this is non-blocking call - ioctl
+    handler checks for a pending connection request and if one is not available
+    exits with -EGAIN error status immediately.
+- RIO_CM_CHAN_CONNECT : Sends a connection request to a remote peer/channel.
+- RIO_CM_CHAN_SEND : Sends a data message through the specified channel.
+    The handler for this request assumes that message buffer specified by
+    a caller includes the reserved space for a packet header required by
+    this driver.
+- RIO_CM_CHAN_RECEIVE : Receives a data message through a connected channel.
+    If the channel does not have an incoming message ready to return this ioctl
+    handler will wait for new message until timeout specified by a caller
+    expires. If timeout value is set to 0, ioctl handler uses a default value
+    defined by MAX_SCHEDULE_TIMEOUT.
+- RIO_CM_CHAN_CLOSE : Closes a specified channel and frees associated buffers.
+    If the specified channel is in the CONNECTED state, sends close notification
+    to the remote peer.
+
+The ioctl command codes and corresponding data structures intended for use by
+user-space applications are defined in 'include/uapi/linux/rio_cm_cdev.h'.
+
+II. Hardware Compatibility
+
+This device driver uses standard interfaces defined by kernel RapidIO subsystem
+and therefore it can be used with any mport device driver registered by RapidIO
+subsystem with limitations set by available mport HW implementation of messaging
+mailboxes.
+
+III. Module parameters
+
+- 'dbg_level' - This parameter allows to control amount of debug information
+        generated by this device driver. This parameter is formed by set of
+        bit masks that correspond to the specific functional block.
+        For mask definitions see 'drivers/rapidio/devices/rio_cm.c'
+        This parameter can be changed dynamically.
+        Use CONFIG_RAPIDIO_DEBUG=y to enable debug output at the top level.
+
+- 'cmbox' - Number of RapidIO mailbox to use (default value is 1).
+        This parameter allows to set messaging mailbox number that will be used
+        within entire RapidIO network. It can be used when default mailbox is
+        used by other device drivers or is not supported by some nodes in the
+        RapidIO network.
+
+- 'chstart' - Start channel number for dynamic assignment. Default value - 256.
+        Allows to exclude channel numbers below this parameter from dynamic
+        allocation to avoid conflicts with software components that use
+        reserved predefined channel numbers.
+
+IV. Known problems
+
+  None.
+
+V. User-space Applications and API Library
+
+Messaging API library and applications that use this device driver are available
+from RapidIO.org.
+
+VI. TODO List
+
+- Add support for system notification messages (reserved channel 0).
index b5a10d3c92c71820f44c68e056537b78b212d500..d6d2f20c45977aecf8f13c2306ab97384dd8e257 100644 (file)
@@ -67,6 +67,15 @@ config RAPIDIO_ENUM_BASIC
 
 endchoice
 
+config RAPIDIO_CHMAN
+       tristate "RapidIO Channelized Messaging driver"
+       depends on RAPIDIO
+       help
+         This option includes RapidIO channelized messaging driver which
+         provides socket-like interface to allow sharing of single RapidIO
+         messaging mailbox between multiple user-space applications.
+         See "Documentation/rapidio/rio_cm.txt" for driver description.
+
 config RAPIDIO_MPORT_CDEV
        tristate "RapidIO /dev mport device driver"
        depends on RAPIDIO
index 6271ada6993fb21f4c7d493784eec94c58742044..74dcea45ad498dc30a5f946f31e3c7adb2647ad9 100644 (file)
@@ -5,6 +5,7 @@ obj-$(CONFIG_RAPIDIO) += rapidio.o
 rapidio-y := rio.o rio-access.o rio-driver.o rio-sysfs.o
 
 obj-$(CONFIG_RAPIDIO_ENUM_BASIC) += rio-scan.o
+obj-$(CONFIG_RAPIDIO_CHMAN)    += rio_cm.o
 
 obj-$(CONFIG_RAPIDIO)          += switches/
 obj-$(CONFIG_RAPIDIO)          += devices/
diff --git a/drivers/rapidio/rio_cm.c b/drivers/rapidio/rio_cm.c
new file mode 100644 (file)
index 0000000..cecc15a
--- /dev/null
@@ -0,0 +1,2366 @@
+/*
+ * rio_cm - RapidIO Channelized Messaging Driver
+ *
+ * Copyright 2013-2016 Integrated Device Technology, Inc.
+ * Copyright (c) 2015, Prodrive Technologies
+ * Copyright (c) 2015, RapidIO Trade Association
+ *
+ * This program is free software; you can redistribute  it and/or modify it
+ * under  the terms of  the GNU General  Public License as published by the
+ * Free Software Foundation;  either version 2 of the  License, or (at your
+ * option) any later version.
+ *
+ * THIS PROGRAM IS DISTRIBUTED IN THE HOPE THAT IT WILL BE USEFUL,
+ * BUT WITHOUT ANY WARRANTY; WITHOUT EVEN THE IMPLIED WARRANTY OF
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE.  SEE THE
+ * GNU GENERAL PUBLIC LICENSE FOR MORE DETAILS.
+ */
+
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/dma-mapping.h>
+#include <linux/delay.h>
+#include <linux/sched.h>
+#include <linux/rio.h>
+#include <linux/rio_drv.h>
+#include <linux/slab.h>
+#include <linux/idr.h>
+#include <linux/interrupt.h>
+#include <linux/cdev.h>
+#include <linux/fs.h>
+#include <linux/poll.h>
+#include <linux/reboot.h>
+#include <linux/bitops.h>
+#include <linux/printk.h>
+#include <linux/rio_cm_cdev.h>
+
+#define DRV_NAME        "rio_cm"
+#define DRV_VERSION     "1.0.0"
+#define DRV_AUTHOR      "Alexandre Bounine <alexandre.bounine@idt.com>"
+#define DRV_DESC        "RapidIO Channelized Messaging Driver"
+#define DEV_NAME       "rio_cm"
+
+/* Debug output filtering masks */
+enum {
+       DBG_NONE        = 0,
+       DBG_INIT        = BIT(0), /* driver init */
+       DBG_EXIT        = BIT(1), /* driver exit */
+       DBG_MPORT       = BIT(2), /* mport add/remove */
+       DBG_RDEV        = BIT(3), /* RapidIO device add/remove */
+       DBG_CHOP        = BIT(4), /* channel operations */
+       DBG_WAIT        = BIT(5), /* waiting for events */
+       DBG_TX          = BIT(6), /* message TX */
+       DBG_TX_EVENT    = BIT(7), /* message TX event */
+       DBG_RX_DATA     = BIT(8), /* inbound data messages */
+       DBG_RX_CMD      = BIT(9), /* inbound REQ/ACK/NACK messages */
+       DBG_ALL         = ~0,
+};
+
+#ifdef DEBUG
+#define riocm_debug(level, fmt, arg...) \
+       do { \
+               if (DBG_##level & dbg_level) \
+                       pr_debug(DRV_NAME ": %s " fmt "\n", \
+                               __func__, ##arg); \
+       } while (0)
+#else
+#define riocm_debug(level, fmt, arg...) \
+               no_printk(KERN_DEBUG pr_fmt(DRV_NAME fmt "\n"), ##arg)
+#endif
+
+#define riocm_warn(fmt, arg...) \
+       pr_warn(DRV_NAME ": %s WARNING " fmt "\n", __func__, ##arg)
+
+#define riocm_error(fmt, arg...) \
+       pr_err(DRV_NAME ": %s ERROR " fmt "\n", __func__, ##arg)
+
+
+static int cmbox = 1;
+module_param(cmbox, int, S_IRUGO);
+MODULE_PARM_DESC(cmbox, "RapidIO Mailbox number (default 1)");
+
+static int chstart = 256;
+module_param(chstart, int, S_IRUGO);
+MODULE_PARM_DESC(chstart,
+                "Start channel number for dynamic allocation (default 256)");
+
+#ifdef DEBUG
+static u32 dbg_level = DBG_NONE;
+module_param(dbg_level, uint, S_IWUSR | S_IRUGO);
+MODULE_PARM_DESC(dbg_level, "Debugging output level (default 0 = none)");
+#endif
+
+MODULE_AUTHOR(DRV_AUTHOR);
+MODULE_DESCRIPTION(DRV_DESC);
+MODULE_LICENSE("GPL");
+MODULE_VERSION(DRV_VERSION);
+
+#define RIOCM_TX_RING_SIZE     128
+#define RIOCM_RX_RING_SIZE     128
+#define RIOCM_CONNECT_TO       3 /* connect response TO (in sec) */
+
+#define RIOCM_MAX_CHNUM                0xffff /* Use full range of u16 field */
+#define RIOCM_CHNUM_AUTO       0
+#define RIOCM_MAX_EP_COUNT     0x10000 /* Max number of endpoints */
+
+enum rio_cm_state {
+       RIO_CM_IDLE,
+       RIO_CM_CONNECT,
+       RIO_CM_CONNECTED,
+       RIO_CM_DISCONNECT,
+       RIO_CM_CHAN_BOUND,
+       RIO_CM_LISTEN,
+       RIO_CM_DESTROYING,
+};
+
+enum rio_cm_pkt_type {
+       RIO_CM_SYS      = 0xaa,
+       RIO_CM_CHAN     = 0x55,
+};
+
+enum rio_cm_chop {
+       CM_CONN_REQ,
+       CM_CONN_ACK,
+       CM_CONN_CLOSE,
+       CM_DATA_MSG,
+};
+
+struct rio_ch_base_bhdr {
+       u32 src_id;
+       u32 dst_id;
+#define RIO_HDR_LETTER_MASK 0xffff0000
+#define RIO_HDR_MBOX_MASK   0x0000ffff
+       u8  src_mbox;
+       u8  dst_mbox;
+       u8  type;
+} __attribute__((__packed__));
+
+struct rio_ch_chan_hdr {
+       struct rio_ch_base_bhdr bhdr;
+       u8 ch_op;
+       u16 dst_ch;
+       u16 src_ch;
+       u16 msg_len;
+       u16 rsrvd;
+} __attribute__((__packed__));
+
+struct tx_req {
+       struct list_head node;
+       struct rio_dev   *rdev;
+       void             *buffer;
+       size_t           len;
+};
+
+struct cm_dev {
+       struct list_head        list;
+       struct rio_mport        *mport;
+       void                    *rx_buf[RIOCM_RX_RING_SIZE];
+       int                     rx_slots;
+       struct mutex            rx_lock;
+
+       void                    *tx_buf[RIOCM_TX_RING_SIZE];
+       int                     tx_slot;
+       int                     tx_cnt;
+       int                     tx_ack_slot;
+       struct list_head        tx_reqs;
+       spinlock_t              tx_lock;
+
+       struct list_head        peers;
+       u32                     npeers;
+       struct workqueue_struct *rx_wq;
+       struct work_struct      rx_work;
+};
+
+struct chan_rx_ring {
+       void    *buf[RIOCM_RX_RING_SIZE];
+       int     head;
+       int     tail;
+       int     count;
+
+       /* Tracking RX buffers reported to upper level */
+       void    *inuse[RIOCM_RX_RING_SIZE];
+       int     inuse_cnt;
+};
+
+struct rio_channel {
+       u16                     id;     /* local channel ID */
+       struct kref             ref;    /* channel refcount */
+       struct file             *filp;
+       struct cm_dev           *cmdev; /* associated CM device object */
+       struct rio_dev          *rdev;  /* remote RapidIO device */
+       enum rio_cm_state       state;
+       int                     error;
+       spinlock_t              lock;
+       void                    *context;
+       u32                     loc_destid;     /* local destID */
+       u32                     rem_destid;     /* remote destID */
+       u16                     rem_channel;    /* remote channel ID */
+       struct list_head        accept_queue;
+       struct list_head        ch_node;
+       struct completion       comp;
+       struct completion       comp_close;
+       struct chan_rx_ring     rx_ring;
+};
+
+struct cm_peer {
+       struct list_head node;
+       struct rio_dev *rdev;
+};
+
+struct rio_cm_work {
+       struct work_struct work;
+       struct cm_dev *cm;
+       void *data;
+};
+
+struct conn_req {
+       struct list_head node;
+       u32 destid;     /* requester destID */
+       u16 chan;       /* requester channel ID */
+       struct cm_dev *cmdev;
+};
+
+/*
+ * A channel_dev structure represents a CM_CDEV
+ * @cdev       Character device
+ * @dev                Associated device object
+ */
+struct channel_dev {
+       struct cdev     cdev;
+       struct device   *dev;
+};
+
+static struct rio_channel *riocm_ch_alloc(u16 ch_num);
+static void riocm_ch_free(struct kref *ref);
+static int riocm_post_send(struct cm_dev *cm, struct rio_dev *rdev,
+                          void *buffer, size_t len);
+static int riocm_ch_close(struct rio_channel *ch);
+
+static DEFINE_SPINLOCK(idr_lock);
+static DEFINE_IDR(ch_idr);
+
+static LIST_HEAD(cm_dev_list);
+static DECLARE_RWSEM(rdev_sem);
+
+static struct class *dev_class;
+static unsigned int dev_major;
+static unsigned int dev_minor_base;
+static dev_t dev_number;
+static struct channel_dev riocm_cdev;
+
+#define is_msg_capable(src_ops, dst_ops)                       \
+                       ((src_ops & RIO_SRC_OPS_DATA_MSG) &&    \
+                        (dst_ops & RIO_DST_OPS_DATA_MSG))
+#define dev_cm_capable(dev) \
+       is_msg_capable(dev->src_ops, dev->dst_ops)
+
+static int riocm_cmp(struct rio_channel *ch, enum rio_cm_state cmp)
+{
+       int ret;
+
+       spin_lock_bh(&ch->lock);
+       ret = (ch->state == cmp);
+       spin_unlock_bh(&ch->lock);
+       return ret;
+}
+
+static int riocm_cmp_exch(struct rio_channel *ch,
+                          enum rio_cm_state cmp, enum rio_cm_state exch)
+{
+       int ret;
+
+       spin_lock_bh(&ch->lock);
+       ret = (ch->state == cmp);
+       if (ret)
+               ch->state = exch;
+       spin_unlock_bh(&ch->lock);
+       return ret;
+}
+
+static enum rio_cm_state riocm_exch(struct rio_channel *ch,
+                                   enum rio_cm_state exch)
+{
+       enum rio_cm_state old;
+
+       spin_lock_bh(&ch->lock);
+       old = ch->state;
+       ch->state = exch;
+       spin_unlock_bh(&ch->lock);
+       return old;
+}
+
+static struct rio_channel *riocm_get_channel(u16 nr)
+{
+       struct rio_channel *ch;
+
+       spin_lock_bh(&idr_lock);
+       ch = idr_find(&ch_idr, nr);
+       if (ch)
+               kref_get(&ch->ref);
+       spin_unlock_bh(&idr_lock);
+       return ch;
+}
+
+static void riocm_put_channel(struct rio_channel *ch)
+{
+       kref_put(&ch->ref, riocm_ch_free);
+}
+
+static void *riocm_rx_get_msg(struct cm_dev *cm)
+{
+       void *msg;
+       int i;
+
+       msg = rio_get_inb_message(cm->mport, cmbox);
+       if (msg) {
+               for (i = 0; i < RIOCM_RX_RING_SIZE; i++) {
+                       if (cm->rx_buf[i] == msg) {
+                               cm->rx_buf[i] = NULL;
+                               cm->rx_slots++;
+                               break;
+                       }
+               }
+
+               if (i == RIOCM_RX_RING_SIZE)
+                       riocm_warn("no record for buffer 0x%p", msg);
+       }
+
+       return msg;
+}
+
+/*
+ * riocm_rx_fill - fills a ring of receive buffers for given cm device
+ * @cm: cm_dev object
+ * @nent: max number of entries to fill
+ *
+ * Returns: none
+ */
+static void riocm_rx_fill(struct cm_dev *cm, int nent)
+{
+       int i;
+
+       if (cm->rx_slots == 0)
+               return;
+
+       for (i = 0; i < RIOCM_RX_RING_SIZE && cm->rx_slots && nent; i++) {
+               if (cm->rx_buf[i] == NULL) {
+                       cm->rx_buf[i] = kmalloc(RIO_MAX_MSG_SIZE, GFP_KERNEL);
+                       if (cm->rx_buf[i] == NULL)
+                               break;
+                       rio_add_inb_buffer(cm->mport, cmbox, cm->rx_buf[i]);
+                       cm->rx_slots--;
+                       nent--;
+               }
+       }
+}
+
+/*
+ * riocm_rx_free - frees all receive buffers associated with given cm device
+ * @cm: cm_dev object
+ *
+ * Returns: none
+ */
+static void riocm_rx_free(struct cm_dev *cm)
+{
+       int i;
+
+       for (i = 0; i < RIOCM_RX_RING_SIZE; i++) {
+               if (cm->rx_buf[i] != NULL) {
+                       kfree(cm->rx_buf[i]);
+                       cm->rx_buf[i] = NULL;
+               }
+       }
+}
+
+/*
+ * riocm_req_handler - connection request handler
+ * @cm: cm_dev object
+ * @req_data: pointer to the request packet
+ *
+ * Returns: 0 if success, or
+ *          -EINVAL if channel is not in correct state,
+ *          -ENODEV if cannot find a channel with specified ID,
+ *          -ENOMEM if unable to allocate memory to store the request
+ */
+static int riocm_req_handler(struct cm_dev *cm, void *req_data)
+{
+       struct rio_channel *ch;
+       struct conn_req *req;
+       struct rio_ch_chan_hdr *hh = req_data;
+       u16 chnum;
+
+       chnum = ntohs(hh->dst_ch);
+
+       ch = riocm_get_channel(chnum);
+
+       if (!ch)
+               return -ENODEV;
+
+       if (ch->state != RIO_CM_LISTEN) {
+               riocm_debug(RX_CMD, "channel %d is not in listen state", chnum);
+               riocm_put_channel(ch);
+               return -EINVAL;
+       }
+
+       req = kzalloc(sizeof(*req), GFP_KERNEL);
+       if (!req) {
+               riocm_put_channel(ch);
+               return -ENOMEM;
+       }
+
+       req->destid = ntohl(hh->bhdr.src_id);
+       req->chan = ntohs(hh->src_ch);
+       req->cmdev = cm;
+
+       spin_lock_bh(&ch->lock);
+       list_add_tail(&req->node, &ch->accept_queue);
+       spin_unlock_bh(&ch->lock);
+       complete(&ch->comp);
+       riocm_put_channel(ch);
+
+       return 0;
+}
+
+/*
+ * riocm_resp_handler - response to connection request handler
+ * @resp_data: pointer to the response packet
+ *
+ * Returns: 0 if success, or
+ *          -EINVAL if channel is not in correct state,
+ *          -ENODEV if cannot find a channel with specified ID,
+ */
+static int riocm_resp_handler(void *resp_data)
+{
+       struct rio_channel *ch;
+       struct rio_ch_chan_hdr *hh = resp_data;
+       u16 chnum;
+
+       chnum = ntohs(hh->dst_ch);
+       ch = riocm_get_channel(chnum);
+       if (!ch)
+               return -ENODEV;
+
+       if (ch->state != RIO_CM_CONNECT) {
+               riocm_put_channel(ch);
+               return -EINVAL;
+       }
+
+       riocm_exch(ch, RIO_CM_CONNECTED);
+       ch->rem_channel = ntohs(hh->src_ch);
+       complete(&ch->comp);
+       riocm_put_channel(ch);
+
+       return 0;
+}
+
+/*
+ * riocm_close_handler - channel close request handler
+ * @req_data: pointer to the request packet
+ *
+ * Returns: 0 if success, or
+ *          -ENODEV if cannot find a channel with specified ID,
+ *            + error codes returned by riocm_ch_close.
+ */
+static int riocm_close_handler(void *data)
+{
+       struct rio_channel *ch;
+       struct rio_ch_chan_hdr *hh = data;
+       int ret;
+
+       riocm_debug(RX_CMD, "for ch=%d", ntohs(hh->dst_ch));
+
+       spin_lock_bh(&idr_lock);
+       ch = idr_find(&ch_idr, ntohs(hh->dst_ch));
+       if (!ch) {
+               spin_unlock_bh(&idr_lock);
+               return -ENODEV;
+       }
+       idr_remove(&ch_idr, ch->id);
+       spin_unlock_bh(&idr_lock);
+
+       riocm_exch(ch, RIO_CM_DISCONNECT);
+
+       ret = riocm_ch_close(ch);
+       if (ret)
+               riocm_debug(RX_CMD, "riocm_ch_close() returned %d", ret);
+
+       return 0;
+}
+
+/*
+ * rio_cm_handler - function that services request (non-data) packets
+ * @cm: cm_dev object
+ * @data: pointer to the packet
+ */
+static void rio_cm_handler(struct cm_dev *cm, void *data)
+{
+       struct rio_ch_chan_hdr *hdr;
+
+       if (!rio_mport_is_running(cm->mport))
+               goto out;
+
+       hdr = data;
+
+       riocm_debug(RX_CMD, "OP=%x for ch=%d from %d",
+                   hdr->ch_op, ntohs(hdr->dst_ch), ntohs(hdr->src_ch));
+
+       switch (hdr->ch_op) {
+       case CM_CONN_REQ:
+               riocm_req_handler(cm, data);
+               break;
+       case CM_CONN_ACK:
+               riocm_resp_handler(data);
+               break;
+       case CM_CONN_CLOSE:
+               riocm_close_handler(data);
+               break;
+       default:
+               riocm_error("Invalid packet header");
+               break;
+       }
+out:
+       kfree(data);
+}
+
+/*
+ * rio_rx_data_handler - received data packet handler
+ * @cm: cm_dev object
+ * @buf: data packet
+ *
+ * Returns: 0 if success, or
+ *          -ENODEV if cannot find a channel with specified ID,
+ *          -EIO if channel is not in CONNECTED state,
+ *          -ENOMEM if channel RX queue is full (packet discarded)
+ */
+static int rio_rx_data_handler(struct cm_dev *cm, void *buf)
+{
+       struct rio_ch_chan_hdr *hdr;
+       struct rio_channel *ch;
+
+       hdr = buf;
+
+       riocm_debug(RX_DATA, "for ch=%d", ntohs(hdr->dst_ch));
+
+       ch = riocm_get_channel(ntohs(hdr->dst_ch));
+       if (!ch) {
+               /* Discard data message for non-existing channel */
+               kfree(buf);
+               return -ENODEV;
+       }
+
+       /* Place pointer to the buffer into channel's RX queue */
+       spin_lock(&ch->lock);
+
+       if (ch->state != RIO_CM_CONNECTED) {
+               /* Channel is not ready to receive data, discard a packet */
+               riocm_debug(RX_DATA, "ch=%d is in wrong state=%d",
+                           ch->id, ch->state);
+               spin_unlock(&ch->lock);
+               kfree(buf);
+               riocm_put_channel(ch);
+               return -EIO;
+       }
+
+       if (ch->rx_ring.count == RIOCM_RX_RING_SIZE) {
+               /* If RX ring is full, discard a packet */
+               riocm_debug(RX_DATA, "ch=%d is full", ch->id);
+               spin_unlock(&ch->lock);
+               kfree(buf);
+               riocm_put_channel(ch);
+               return -ENOMEM;
+       }
+
+       ch->rx_ring.buf[ch->rx_ring.head] = buf;
+       ch->rx_ring.head++;
+       ch->rx_ring.count++;
+       ch->rx_ring.head %= RIOCM_RX_RING_SIZE;
+
+       complete(&ch->comp);
+
+       spin_unlock(&ch->lock);
+       riocm_put_channel(ch);
+
+       return 0;
+}
+
+/*
+ * rio_ibmsg_handler - inbound message packet handler
+ */
+static void rio_ibmsg_handler(struct work_struct *work)
+{
+       struct cm_dev *cm = container_of(work, struct cm_dev, rx_work);
+       void *data;
+       struct rio_ch_chan_hdr *hdr;
+
+       if (!rio_mport_is_running(cm->mport))
+               return;
+
+       while (1) {
+               mutex_lock(&cm->rx_lock);
+               data = riocm_rx_get_msg(cm);
+               if (data)
+                       riocm_rx_fill(cm, 1);
+               mutex_unlock(&cm->rx_lock);
+
+               if (data == NULL)
+                       break;
+
+               hdr = data;
+
+               if (hdr->bhdr.type != RIO_CM_CHAN) {
+                       /* For now simply discard packets other than channel */
+                       riocm_error("Unsupported TYPE code (0x%x). Msg dropped",
+                                   hdr->bhdr.type);
+                       kfree(data);
+                       continue;
+               }
+
+               /* Process a channel message */
+               if (hdr->ch_op == CM_DATA_MSG)
+                       rio_rx_data_handler(cm, data);
+               else
+                       rio_cm_handler(cm, data);
+       }
+}
+
+static void riocm_inb_msg_event(struct rio_mport *mport, void *dev_id,
+                               int mbox, int slot)
+{
+       struct cm_dev *cm = dev_id;
+
+       if (rio_mport_is_running(cm->mport) && !work_pending(&cm->rx_work))
+               queue_work(cm->rx_wq, &cm->rx_work);
+}
+
+/*
+ * rio_txcq_handler - TX completion handler
+ * @cm: cm_dev object
+ * @slot: TX queue slot
+ *
+ * TX completion handler also ensures that pending request packets are placed
+ * into transmit queue as soon as a free slot becomes available. This is done
+ * to give higher priority to request packets during high intensity data flow.
+ */
+static void rio_txcq_handler(struct cm_dev *cm, int slot)
+{
+       int ack_slot;
+
+       /* ATTN: Add TX completion notification if/when direct buffer
+        * transfer is implemented. At this moment only correct tracking
+        * of tx_count is important.
+        */
+       riocm_debug(TX_EVENT, "for mport_%d slot %d tx_cnt %d",
+                   cm->mport->id, slot, cm->tx_cnt);
+
+       spin_lock(&cm->tx_lock);
+       ack_slot = cm->tx_ack_slot;
+
+       if (ack_slot == slot)
+               riocm_debug(TX_EVENT, "slot == ack_slot");
+
+       while (cm->tx_cnt && ((ack_slot != slot) ||
+              (cm->tx_cnt == RIOCM_TX_RING_SIZE))) {
+
+               cm->tx_buf[ack_slot] = NULL;
+               ++ack_slot;
+               ack_slot &= (RIOCM_TX_RING_SIZE - 1);
+               cm->tx_cnt--;
+       }
+
+       if (cm->tx_cnt < 0 || cm->tx_cnt > RIOCM_TX_RING_SIZE)
+               riocm_error("tx_cnt %d out of sync", cm->tx_cnt);
+
+       WARN_ON((cm->tx_cnt < 0) || (cm->tx_cnt > RIOCM_TX_RING_SIZE));
+
+       cm->tx_ack_slot = ack_slot;
+
+       /*
+        * If there are pending requests, insert them into transmit queue
+        */
+       if (!list_empty(&cm->tx_reqs) && (cm->tx_cnt < RIOCM_TX_RING_SIZE)) {
+               struct tx_req *req, *_req;
+               int rc;
+
+               list_for_each_entry_safe(req, _req, &cm->tx_reqs, node) {
+                       list_del(&req->node);
+                       cm->tx_buf[cm->tx_slot] = req->buffer;
+                       rc = rio_add_outb_message(cm->mport, req->rdev, cmbox,
+                                                 req->buffer, req->len);
+                       kfree(req->buffer);
+                       kfree(req);
+
+                       ++cm->tx_cnt;
+                       ++cm->tx_slot;
+                       cm->tx_slot &= (RIOCM_TX_RING_SIZE - 1);
+                       if (cm->tx_cnt == RIOCM_TX_RING_SIZE)
+                               break;
+               }
+       }
+
+       spin_unlock(&cm->tx_lock);
+}
+
+static void riocm_outb_msg_event(struct rio_mport *mport, void *dev_id,
+                                int mbox, int slot)
+{
+       struct cm_dev *cm = dev_id;
+
+       if (cm && rio_mport_is_running(cm->mport))
+               rio_txcq_handler(cm, slot);
+}
+
+static int riocm_queue_req(struct cm_dev *cm, struct rio_dev *rdev,
+                          void *buffer, size_t len)
+{
+       unsigned long flags;
+       struct tx_req *treq;
+
+       treq = kzalloc(sizeof(*treq), GFP_KERNEL);
+       if (treq == NULL)
+               return -ENOMEM;
+
+       treq->rdev = rdev;
+       treq->buffer = buffer;
+       treq->len = len;
+
+       spin_lock_irqsave(&cm->tx_lock, flags);
+       list_add_tail(&treq->node, &cm->tx_reqs);
+       spin_unlock_irqrestore(&cm->tx_lock, flags);
+       return 0;
+}
+
+/*
+ * riocm_post_send - helper function that places packet into msg TX queue
+ * @cm: cm_dev object
+ * @rdev: target RapidIO device object (required by outbound msg interface)
+ * @buffer: pointer to a packet buffer to send
+ * @len: length of data to transfer
+ * @req: request priority flag
+ *
+ * Returns: 0 if success, or error code otherwise.
+ */
+static int riocm_post_send(struct cm_dev *cm, struct rio_dev *rdev,
+                          void *buffer, size_t len)
+{
+       int rc;
+       unsigned long flags;
+
+       spin_lock_irqsave(&cm->tx_lock, flags);
+
+       if (cm->mport == NULL) {
+               rc = -ENODEV;
+               goto err_out;
+       }
+
+       if (cm->tx_cnt == RIOCM_TX_RING_SIZE) {
+               riocm_debug(TX, "Tx Queue is full");
+               rc = -EBUSY;
+               goto err_out;
+       }
+
+       cm->tx_buf[cm->tx_slot] = buffer;
+       rc = rio_add_outb_message(cm->mport, rdev, cmbox, buffer, len);
+
+       riocm_debug(TX, "Add buf@%p destid=%x tx_slot=%d tx_cnt=%d",
+                buffer, rdev->destid, cm->tx_slot, cm->tx_cnt);
+
+       ++cm->tx_cnt;
+       ++cm->tx_slot;
+       cm->tx_slot &= (RIOCM_TX_RING_SIZE - 1);
+
+err_out:
+       spin_unlock_irqrestore(&cm->tx_lock, flags);
+       return rc;
+}
+
+/*
+ * riocm_ch_send - sends a data packet to a remote device
+ * @ch_id: local channel ID
+ * @buf: pointer to a data buffer to send (including CM header)
+ * @len: length of data to transfer (including CM header)
+ *
+ * ATTN: ASSUMES THAT THE HEADER SPACE IS RESERVED PART OF THE DATA PACKET
+ *
+ * Returns: 0 if success, or
+ *          -EINVAL if one or more input parameters is/are not valid,
+ *          -ENODEV if cannot find a channel with specified ID,
+ *          -EAGAIN if a channel is not in CONNECTED state,
+ *         + error codes returned by HW send routine.
+ */
+static int riocm_ch_send(u16 ch_id, void *buf, int len)
+{
+       struct rio_channel *ch;
+       struct rio_ch_chan_hdr *hdr;
+       int ret;
+
+       if (buf == NULL || ch_id == 0 || len == 0 || len > RIO_MAX_MSG_SIZE)
+               return -EINVAL;
+
+       ch = riocm_get_channel(ch_id);
+       if (!ch) {
+               riocm_error("%s(%d) ch_%d not found", current->comm,
+                           task_pid_nr(current), ch_id);
+               return -ENODEV;
+       }
+
+       if (!riocm_cmp(ch, RIO_CM_CONNECTED)) {
+               ret = -EAGAIN;
+               goto err_out;
+       }
+
+       /*
+        * Fill buffer header section with corresponding channel data
+        */
+       hdr = buf;
+
+       hdr->bhdr.src_id = htonl(ch->loc_destid);
+       hdr->bhdr.dst_id = htonl(ch->rem_destid);
+       hdr->bhdr.src_mbox = cmbox;
+       hdr->bhdr.dst_mbox = cmbox;
+       hdr->bhdr.type = RIO_CM_CHAN;
+       hdr->ch_op = CM_DATA_MSG;
+       hdr->dst_ch = htons(ch->rem_channel);
+       hdr->src_ch = htons(ch->id);
+       hdr->msg_len = htons((u16)len);
+
+       /* ATTN: the function call below relies on the fact that underlying
+        * HW-specific add_outb_message() routine copies TX data into its own
+        * internal transfer buffer (true for all RIONET compatible mport
+        * drivers). Must be reviewed if mport driver uses the buffer directly.
+        */
+
+       ret = riocm_post_send(ch->cmdev, ch->rdev, buf, len);
+       if (ret)
+               riocm_debug(TX, "ch %d send_err=%d", ch->id, ret);
+err_out:
+       riocm_put_channel(ch);
+       return ret;
+}
+
+static int riocm_ch_free_rxbuf(struct rio_channel *ch, void *buf)
+{
+       int i, ret = -EINVAL;
+
+       spin_lock_bh(&ch->lock);
+
+       for (i = 0; i < RIOCM_RX_RING_SIZE; i++) {
+               if (ch->rx_ring.inuse[i] == buf) {
+                       ch->rx_ring.inuse[i] = NULL;
+                       ch->rx_ring.inuse_cnt--;
+                       ret = 0;
+                       break;
+               }
+       }
+
+       spin_unlock_bh(&ch->lock);
+
+       if (!ret)
+               kfree(buf);
+
+       return ret;
+}
+
+/*
+ * riocm_ch_receive - fetch a data packet received for the specified channel
+ * @ch: local channel ID
+ * @buf: pointer to a packet buffer
+ * @timeout: timeout to wait for incoming packet (in jiffies)
+ *
+ * Returns: 0 and valid buffer pointer if success, or NULL pointer and one of:
+ *          -EAGAIN if a channel is not in CONNECTED state,
+ *          -ENOMEM if in-use tracking queue is full,
+ *          -ETIME if wait timeout expired,
+ *         -EINTR if wait was interrupted.
+ */
+static int riocm_ch_receive(struct rio_channel *ch, void **buf, long timeout)
+{
+       void *rxmsg = NULL;
+       int i, ret = 0;
+       long wret;
+
+       if (!riocm_cmp(ch, RIO_CM_CONNECTED)) {
+               ret = -EAGAIN;
+               goto out;
+       }
+
+       if (ch->rx_ring.inuse_cnt == RIOCM_RX_RING_SIZE) {
+               /* If we do not have entries to track buffers given to upper
+                * layer, reject request.
+                */
+               ret = -ENOMEM;
+               goto out;
+       }
+
+       wret = wait_for_completion_interruptible_timeout(&ch->comp, timeout);
+
+       riocm_debug(WAIT, "wait on %d returned %ld", ch->id, wret);
+
+       if (!wret)
+               ret = -ETIME;
+       else if (wret == -ERESTARTSYS)
+               ret = -EINTR;
+       else
+               ret = riocm_cmp(ch, RIO_CM_CONNECTED) ? 0 : -ECONNRESET;
+
+       if (ret)
+               goto out;
+
+       spin_lock_bh(&ch->lock);
+
+       rxmsg = ch->rx_ring.buf[ch->rx_ring.tail];
+       ch->rx_ring.buf[ch->rx_ring.tail] = NULL;
+       ch->rx_ring.count--;
+       ch->rx_ring.tail++;
+       ch->rx_ring.tail %= RIOCM_RX_RING_SIZE;
+       ret = -ENOMEM;
+
+       for (i = 0; i < RIOCM_RX_RING_SIZE; i++) {
+               if (ch->rx_ring.inuse[i] == NULL) {
+                       ch->rx_ring.inuse[i] = rxmsg;
+                       ch->rx_ring.inuse_cnt++;
+                       ret = 0;
+                       break;
+               }
+       }
+
+       if (ret) {
+               /* We have no entry to store pending message: drop it */
+               kfree(rxmsg);
+               rxmsg = NULL;
+       }
+
+       spin_unlock_bh(&ch->lock);
+out:
+       *buf = rxmsg;
+       return ret;
+}
+
+/*
+ * riocm_ch_connect - sends a connect request to a remote device
+ * @loc_ch: local channel ID
+ * @cm: CM device to send connect request
+ * @peer: target RapidIO device
+ * @rem_ch: remote channel ID
+ *
+ * Returns: 0 if success, or
+ *          -EINVAL if the channel is not in IDLE state,
+ *          -EAGAIN if no connection request available immediately,
+ *          -ETIME if ACK response timeout expired,
+ *          -EINTR if wait for response was interrupted.
+ */
+static int riocm_ch_connect(u16 loc_ch, struct cm_dev *cm,
+                           struct cm_peer *peer, u16 rem_ch)
+{
+       struct rio_channel *ch = NULL;
+       struct rio_ch_chan_hdr *hdr;
+       int ret;
+       long wret;
+
+       ch = riocm_get_channel(loc_ch);
+       if (!ch)
+               return -ENODEV;
+
+       if (!riocm_cmp_exch(ch, RIO_CM_IDLE, RIO_CM_CONNECT)) {
+               ret = -EINVAL;
+               goto conn_done;
+       }
+
+       ch->cmdev = cm;
+       ch->rdev = peer->rdev;
+       ch->context = NULL;
+       ch->loc_destid = cm->mport->host_deviceid;
+       ch->rem_channel = rem_ch;
+
+       /*
+        * Send connect request to the remote RapidIO device
+        */
+
+       hdr = kzalloc(sizeof(*hdr), GFP_KERNEL);
+       if (hdr == NULL) {
+               ret = -ENOMEM;
+               goto conn_done;
+       }
+
+       hdr->bhdr.src_id = htonl(ch->loc_destid);
+       hdr->bhdr.dst_id = htonl(peer->rdev->destid);
+       hdr->bhdr.src_mbox = cmbox;
+       hdr->bhdr.dst_mbox = cmbox;
+       hdr->bhdr.type = RIO_CM_CHAN;
+       hdr->ch_op = CM_CONN_REQ;
+       hdr->dst_ch = htons(rem_ch);
+       hdr->src_ch = htons(loc_ch);
+
+       /* ATTN: the function call below relies on the fact that underlying
+        * HW-specific add_outb_message() routine copies TX data into its
+        * internal transfer buffer. Must be reviewed if mport driver uses
+        * this buffer directly.
+        */
+       ret = riocm_post_send(cm, peer->rdev, hdr, sizeof(*hdr));
+
+       if (ret != -EBUSY) {
+               kfree(hdr);
+       } else {
+               ret = riocm_queue_req(cm, peer->rdev, hdr, sizeof(*hdr));
+               if (ret)
+                       kfree(hdr);
+       }
+
+       if (ret) {
+               riocm_cmp_exch(ch, RIO_CM_CONNECT, RIO_CM_IDLE);
+               goto conn_done;
+       }
+
+       /* Wait for connect response from the remote device */
+       wret = wait_for_completion_interruptible_timeout(&ch->comp,
+                                                        RIOCM_CONNECT_TO * HZ);
+       riocm_debug(WAIT, "wait on %d returns %ld", ch->id, wret);
+
+       if (!wret)
+               ret = -ETIME;
+       else if (wret == -ERESTARTSYS)
+               ret = -EINTR;
+       else
+               ret = riocm_cmp(ch, RIO_CM_CONNECTED) ? 0 : -1;
+
+conn_done:
+       riocm_put_channel(ch);
+       return ret;
+}
+
+static int riocm_send_ack(struct rio_channel *ch)
+{
+       struct rio_ch_chan_hdr *hdr;
+       int ret;
+
+       hdr = kzalloc(sizeof(*hdr), GFP_KERNEL);
+       if (hdr == NULL)
+               return -ENOMEM;
+
+       hdr->bhdr.src_id = htonl(ch->loc_destid);
+       hdr->bhdr.dst_id = htonl(ch->rem_destid);
+       hdr->dst_ch = htons(ch->rem_channel);
+       hdr->src_ch = htons(ch->id);
+       hdr->bhdr.src_mbox = cmbox;
+       hdr->bhdr.dst_mbox = cmbox;
+       hdr->bhdr.type = RIO_CM_CHAN;
+       hdr->ch_op = CM_CONN_ACK;
+
+       /* ATTN: the function call below relies on the fact that underlying
+        * add_outb_message() routine copies TX data into its internal transfer
+        * buffer. Review if switching to direct buffer version.
+        */
+       ret = riocm_post_send(ch->cmdev, ch->rdev, hdr, sizeof(*hdr));
+
+       if (ret == -EBUSY && !riocm_queue_req(ch->cmdev,
+                                             ch->rdev, hdr, sizeof(*hdr)))
+               return 0;
+       kfree(hdr);
+
+       if (ret)
+               riocm_error("send ACK to ch_%d on %s failed (ret=%d)",
+                           ch->id, rio_name(ch->rdev), ret);
+       return ret;
+}
+
+/*
+ * riocm_ch_accept - accept incoming connection request
+ * @ch_id: channel ID
+ * @new_ch_id: local mport device
+ * @timeout: wait timeout (if 0 non-blocking call, do not wait if connection
+ *           request is not available).
+ *
+ * Returns: pointer to new channel struct if success, or error-valued pointer:
+ *          -ENODEV - cannot find specified channel or mport,
+ *          -EINVAL - the channel is not in IDLE state,
+ *          -EAGAIN - no connection request available immediately (timeout=0),
+ *          -ENOMEM - unable to allocate new channel,
+ *          -ETIME - wait timeout expired,
+ *          -EINTR - wait was interrupted.
+ */
+static struct rio_channel *riocm_ch_accept(u16 ch_id, u16 *new_ch_id,
+                                          long timeout)
+{
+       struct rio_channel *ch = NULL;
+       struct rio_channel *new_ch = NULL;
+       struct conn_req *req;
+       struct cm_peer *peer;
+       int found = 0;
+       int err = 0;
+       long wret;
+
+       ch = riocm_get_channel(ch_id);
+       if (!ch)
+               return ERR_PTR(-EINVAL);
+
+       if (!riocm_cmp(ch, RIO_CM_LISTEN)) {
+               err = -EINVAL;
+               goto err_put;
+       }
+
+       /* Don't sleep if this is a non blocking call */
+       if (!timeout) {
+               if (!try_wait_for_completion(&ch->comp)) {
+                       err = -EAGAIN;
+                       goto err_put;
+               }
+       } else {
+               riocm_debug(WAIT, "on %d", ch->id);
+
+               wret = wait_for_completion_interruptible_timeout(&ch->comp,
+                                                                timeout);
+               if (!wret) {
+                       err = -ETIME;
+                       goto err_put;
+               } else if (wret == -ERESTARTSYS) {
+                       err = -EINTR;
+                       goto err_put;
+               }
+       }
+
+       spin_lock_bh(&ch->lock);
+
+       if (ch->state != RIO_CM_LISTEN) {
+               err = -ECANCELED;
+       } else if (list_empty(&ch->accept_queue)) {
+               riocm_debug(WAIT, "on %d accept_queue is empty on completion",
+                           ch->id);
+               err = -EIO;
+       }
+
+       spin_unlock_bh(&ch->lock);
+
+       if (err) {
+               riocm_debug(WAIT, "on %d returns %d", ch->id, err);
+               goto err_put;
+       }
+
+       /* Create new channel for this connection */
+       new_ch = riocm_ch_alloc(RIOCM_CHNUM_AUTO);
+
+       if (IS_ERR(new_ch)) {
+               riocm_error("failed to get channel for new req (%ld)",
+                       PTR_ERR(new_ch));
+               err = -ENOMEM;
+               goto err_put;
+       }
+
+       spin_lock_bh(&ch->lock);
+
+       req = list_first_entry(&ch->accept_queue, struct conn_req, node);
+       list_del(&req->node);
+       new_ch->cmdev = ch->cmdev;
+       new_ch->loc_destid = ch->loc_destid;
+       new_ch->rem_destid = req->destid;
+       new_ch->rem_channel = req->chan;
+
+       spin_unlock_bh(&ch->lock);
+       riocm_put_channel(ch);
+       kfree(req);
+
+       down_read(&rdev_sem);
+       /* Find requester's device object */
+       list_for_each_entry(peer, &new_ch->cmdev->peers, node) {
+               if (peer->rdev->destid == new_ch->rem_destid) {
+                       riocm_debug(RX_CMD, "found matching device(%s)",
+                                   rio_name(peer->rdev));
+                       found = 1;
+                       break;
+               }
+       }
+       up_read(&rdev_sem);
+
+       if (!found) {
+               /* If peer device object not found, simply ignore the request */
+               err = -ENODEV;
+               goto err_nodev;
+       }
+
+       new_ch->rdev = peer->rdev;
+       new_ch->state = RIO_CM_CONNECTED;
+       spin_lock_init(&new_ch->lock);
+
+       /* Acknowledge the connection request. */
+       riocm_send_ack(new_ch);
+
+       *new_ch_id = new_ch->id;
+       return new_ch;
+err_put:
+       riocm_put_channel(ch);
+err_nodev:
+       if (new_ch) {
+               spin_lock_bh(&idr_lock);
+               idr_remove(&ch_idr, new_ch->id);
+               spin_unlock_bh(&idr_lock);
+               riocm_put_channel(new_ch);
+       }
+       *new_ch_id = 0;
+       return ERR_PTR(err);
+}
+
+/*
+ * riocm_ch_listen - puts a channel into LISTEN state
+ * @ch_id: channel ID
+ *
+ * Returns: 0 if success, or
+ *          -EINVAL if the specified channel does not exists or
+ *                  is not in CHAN_BOUND state.
+ */
+static int riocm_ch_listen(u16 ch_id)
+{
+       struct rio_channel *ch = NULL;
+       int ret = 0;
+
+       riocm_debug(CHOP, "(ch_%d)", ch_id);
+
+       ch = riocm_get_channel(ch_id);
+       if (!ch || !riocm_cmp_exch(ch, RIO_CM_CHAN_BOUND, RIO_CM_LISTEN))
+               ret = -EINVAL;
+       riocm_put_channel(ch);
+       return ret;
+}
+
+/*
+ * riocm_ch_bind - associate a channel object and an mport device
+ * @ch_id: channel ID
+ * @mport_id: local mport device ID
+ * @context: pointer to the additional caller's context
+ *
+ * Returns: 0 if success, or
+ *          -ENODEV if cannot find specified mport,
+ *          -EINVAL if the specified channel does not exist or
+ *                  is not in IDLE state.
+ */
+static int riocm_ch_bind(u16 ch_id, u8 mport_id, void *context)
+{
+       struct rio_channel *ch = NULL;
+       struct cm_dev *cm;
+       int rc = -ENODEV;
+
+       riocm_debug(CHOP, "ch_%d to mport_%d", ch_id, mport_id);
+
+       /* Find matching cm_dev object */
+       down_read(&rdev_sem);
+       list_for_each_entry(cm, &cm_dev_list, list) {
+               if ((cm->mport->id == mport_id) &&
+                    rio_mport_is_running(cm->mport)) {
+                       rc = 0;
+                       break;
+               }
+       }
+
+       if (rc)
+               goto exit;
+
+       ch = riocm_get_channel(ch_id);
+       if (!ch) {
+               rc = -EINVAL;
+               goto exit;
+       }
+
+       spin_lock_bh(&ch->lock);
+       if (ch->state != RIO_CM_IDLE) {
+               spin_unlock_bh(&ch->lock);
+               rc = -EINVAL;
+               goto err_put;
+       }
+
+       ch->cmdev = cm;
+       ch->loc_destid = cm->mport->host_deviceid;
+       ch->context = context;
+       ch->state = RIO_CM_CHAN_BOUND;
+       spin_unlock_bh(&ch->lock);
+err_put:
+       riocm_put_channel(ch);
+exit:
+       up_read(&rdev_sem);
+       return rc;
+}
+
+/*
+ * riocm_ch_alloc - channel object allocation helper routine
+ * @ch_num: channel ID (1 ... RIOCM_MAX_CHNUM, 0 = automatic)
+ *
+ * Return value: pointer to newly created channel object,
+ *               or error-valued pointer
+ */
+static struct rio_channel *riocm_ch_alloc(u16 ch_num)
+{
+       int id;
+       int start, end;
+       struct rio_channel *ch;
+
+       ch = kzalloc(sizeof(*ch), GFP_KERNEL);
+       if (!ch)
+               return ERR_PTR(-ENOMEM);
+
+       if (ch_num) {
+               /* If requested, try to obtain the specified channel ID */
+               start = ch_num;
+               end = ch_num + 1;
+       } else {
+               /* Obtain channel ID from the dynamic allocation range */
+               start = chstart;
+               end = RIOCM_MAX_CHNUM + 1;
+       }
+
+       idr_preload(GFP_KERNEL);
+       spin_lock_bh(&idr_lock);
+       id = idr_alloc_cyclic(&ch_idr, ch, start, end, GFP_NOWAIT);
+       spin_unlock_bh(&idr_lock);
+       idr_preload_end();
+
+       if (id < 0) {
+               kfree(ch);
+               return ERR_PTR(id == -ENOSPC ? -EBUSY : id);
+       }
+
+       ch->id = (u16)id;
+       ch->state = RIO_CM_IDLE;
+       spin_lock_init(&ch->lock);
+       INIT_LIST_HEAD(&ch->accept_queue);
+       INIT_LIST_HEAD(&ch->ch_node);
+       init_completion(&ch->comp);
+       init_completion(&ch->comp_close);
+       kref_init(&ch->ref);
+       ch->rx_ring.head = 0;
+       ch->rx_ring.tail = 0;
+       ch->rx_ring.count = 0;
+       ch->rx_ring.inuse_cnt = 0;
+
+       return ch;
+}
+
+/*
+ * riocm_ch_create - creates a new channel object and allocates ID for it
+ * @ch_num: channel ID (1 ... RIOCM_MAX_CHNUM, 0 = automatic)
+ *
+ * Allocates and initializes a new channel object. If the parameter ch_num > 0
+ * and is within the valid range, riocm_ch_create tries to allocate the
+ * specified ID for the new channel. If ch_num = 0, channel ID will be assigned
+ * automatically from the range (chstart ... RIOCM_MAX_CHNUM).
+ * Module parameter 'chstart' defines start of an ID range available for dynamic
+ * allocation. Range below 'chstart' is reserved for pre-defined ID numbers.
+ * Available channel numbers are limited by 16-bit size of channel numbers used
+ * in the packet header.
+ *
+ * Return value: PTR to rio_channel structure if successful (with channel number
+ *               updated via pointer) or error-valued pointer if error.
+ */
+static struct rio_channel *riocm_ch_create(u16 *ch_num)
+{
+       struct rio_channel *ch = NULL;
+
+       ch = riocm_ch_alloc(*ch_num);
+
+       if (IS_ERR(ch))
+               riocm_debug(CHOP, "Failed to allocate channel %d (err=%ld)",
+                           *ch_num, PTR_ERR(ch));
+       else
+               *ch_num = ch->id;
+
+       return ch;
+}
+
+/*
+ * riocm_ch_free - channel object release routine
+ * @ref: pointer to a channel's kref structure
+ */
+static void riocm_ch_free(struct kref *ref)
+{
+       struct rio_channel *ch = container_of(ref, struct rio_channel, ref);
+       int i;
+
+       riocm_debug(CHOP, "(ch_%d)", ch->id);
+
+       if (ch->rx_ring.inuse_cnt) {
+               for (i = 0;
+                    i < RIOCM_RX_RING_SIZE && ch->rx_ring.inuse_cnt; i++) {
+                       if (ch->rx_ring.inuse[i] != NULL) {
+                               kfree(ch->rx_ring.inuse[i]);
+                               ch->rx_ring.inuse_cnt--;
+                       }
+               }
+       }
+
+       if (ch->rx_ring.count)
+               for (i = 0; i < RIOCM_RX_RING_SIZE && ch->rx_ring.count; i++) {
+                       if (ch->rx_ring.buf[i] != NULL) {
+                               kfree(ch->rx_ring.buf[i]);
+                               ch->rx_ring.count--;
+                       }
+               }
+
+       complete(&ch->comp_close);
+}
+
+static int riocm_send_close(struct rio_channel *ch)
+{
+       struct rio_ch_chan_hdr *hdr;
+       int ret;
+
+       /*
+        * Send CH_CLOSE notification to the remote RapidIO device
+        */
+
+       hdr = kzalloc(sizeof(*hdr), GFP_KERNEL);
+       if (hdr == NULL)
+               return -ENOMEM;
+
+       hdr->bhdr.src_id = htonl(ch->loc_destid);
+       hdr->bhdr.dst_id = htonl(ch->rem_destid);
+       hdr->bhdr.src_mbox = cmbox;
+       hdr->bhdr.dst_mbox = cmbox;
+       hdr->bhdr.type = RIO_CM_CHAN;
+       hdr->ch_op = CM_CONN_CLOSE;
+       hdr->dst_ch = htons(ch->rem_channel);
+       hdr->src_ch = htons(ch->id);
+
+       /* ATTN: the function call below relies on the fact that underlying
+        * add_outb_message() routine copies TX data into its internal transfer
+        * buffer. Needs to be reviewed if switched to direct buffer mode.
+        */
+       ret = riocm_post_send(ch->cmdev, ch->rdev, hdr, sizeof(*hdr));
+
+       if (ret == -EBUSY && !riocm_queue_req(ch->cmdev, ch->rdev,
+                                             hdr, sizeof(*hdr)))
+               return 0;
+       kfree(hdr);
+
+       if (ret)
+               riocm_error("ch(%d) send CLOSE failed (ret=%d)", ch->id, ret);
+
+       return ret;
+}
+
+/*
+ * riocm_ch_close - closes a channel object with specified ID (by local request)
+ * @ch: channel to be closed
+ */
+static int riocm_ch_close(struct rio_channel *ch)
+{
+       unsigned long tmo = msecs_to_jiffies(3000);
+       enum rio_cm_state state;
+       long wret;
+       int ret = 0;
+
+       riocm_debug(CHOP, "ch_%d by %s(%d)",
+                   ch->id, current->comm, task_pid_nr(current));
+
+       state = riocm_exch(ch, RIO_CM_DESTROYING);
+       if (state == RIO_CM_CONNECTED)
+               riocm_send_close(ch);
+
+       complete_all(&ch->comp);
+
+       riocm_put_channel(ch);
+       wret = wait_for_completion_interruptible_timeout(&ch->comp_close, tmo);
+
+       riocm_debug(WAIT, "wait on %d returns %ld", ch->id, wret);
+
+       if (wret == 0) {
+               /* Timeout on wait occurred */
+               riocm_debug(CHOP, "%s(%d) timed out waiting for ch %d",
+                      current->comm, task_pid_nr(current), ch->id);
+               ret = -ETIMEDOUT;
+       } else if (wret == -ERESTARTSYS) {
+               /* Wait_for_completion was interrupted by a signal */
+               riocm_debug(CHOP, "%s(%d) wait for ch %d was interrupted",
+                       current->comm, task_pid_nr(current), ch->id);
+               ret = -EINTR;
+       }
+
+       if (!ret) {
+               riocm_debug(CHOP, "ch_%d resources released", ch->id);
+               kfree(ch);
+       } else {
+               riocm_debug(CHOP, "failed to release ch_%d resources", ch->id);
+       }
+
+       return ret;
+}
+
+/*
+ * riocm_cdev_open() - Open character device
+ */
+static int riocm_cdev_open(struct inode *inode, struct file *filp)
+{
+       riocm_debug(INIT, "by %s(%d) filp=%p ",
+                   current->comm, task_pid_nr(current), filp);
+
+       if (list_empty(&cm_dev_list))
+               return -ENODEV;
+
+       return 0;
+}
+
+/*
+ * riocm_cdev_release() - Release character device
+ */
+static int riocm_cdev_release(struct inode *inode, struct file *filp)
+{
+       struct rio_channel *ch, *_c;
+       unsigned int i;
+       LIST_HEAD(list);
+
+       riocm_debug(EXIT, "by %s(%d) filp=%p",
+                   current->comm, task_pid_nr(current), filp);
+
+       /* Check if there are channels associated with this file descriptor */
+       spin_lock_bh(&idr_lock);
+       idr_for_each_entry(&ch_idr, ch, i) {
+               if (ch && ch->filp == filp) {
+                       riocm_debug(EXIT, "ch_%d not released by %s(%d)",
+                                   ch->id, current->comm,
+                                   task_pid_nr(current));
+                       idr_remove(&ch_idr, ch->id);
+                       list_add(&ch->ch_node, &list);
+               }
+       }
+       spin_unlock_bh(&idr_lock);
+
+       if (!list_empty(&list)) {
+               list_for_each_entry_safe(ch, _c, &list, ch_node) {
+                       list_del(&ch->ch_node);
+                       riocm_ch_close(ch);
+               }
+       }
+
+       return 0;
+}
+
+/*
+ * cm_ep_get_list_size() - Reports number of endpoints in the network
+ */
+static int cm_ep_get_list_size(void __user *arg)
+{
+       u32 __user *p = arg;
+       u32 mport_id;
+       u32 count = 0;
+       struct cm_dev *cm;
+
+       if (get_user(mport_id, p))
+               return -EFAULT;
+       if (mport_id >= RIO_MAX_MPORTS)
+               return -EINVAL;
+
+       /* Find a matching cm_dev object */
+       down_read(&rdev_sem);
+       list_for_each_entry(cm, &cm_dev_list, list) {
+               if (cm->mport->id == mport_id) {
+                       count = cm->npeers;
+                       up_read(&rdev_sem);
+                       if (copy_to_user(arg, &count, sizeof(u32)))
+                               return -EFAULT;
+                       return 0;
+               }
+       }
+       up_read(&rdev_sem);
+
+       return -ENODEV;
+}
+
+/*
+ * cm_ep_get_list() - Returns list of attached endpoints
+ */
+static int cm_ep_get_list(void __user *arg)
+{
+       struct cm_dev *cm;
+       struct cm_peer *peer;
+       u32 info[2];
+       void *buf;
+       u32 nent;
+       u32 *entry_ptr;
+       u32 i = 0;
+       int ret = 0;
+
+       if (copy_from_user(&info, arg, sizeof(info)))
+               return -EFAULT;
+
+       if (info[1] >= RIO_MAX_MPORTS || info[0] > RIOCM_MAX_EP_COUNT)
+               return -EINVAL;
+
+       /* Find a matching cm_dev object */
+       down_read(&rdev_sem);
+       list_for_each_entry(cm, &cm_dev_list, list)
+               if (cm->mport->id == (u8)info[1])
+                       goto found;
+
+       up_read(&rdev_sem);
+       return -ENODEV;
+
+found:
+       nent = min(info[0], cm->npeers);
+       buf = kcalloc(nent + 2, sizeof(u32), GFP_KERNEL);
+       if (!buf) {
+               up_read(&rdev_sem);
+               return -ENOMEM;
+       }
+
+       entry_ptr = (u32 *)((uintptr_t)buf + 2*sizeof(u32));
+
+       list_for_each_entry(peer, &cm->peers, node) {
+               *entry_ptr = (u32)peer->rdev->destid;
+               entry_ptr++;
+               if (++i == nent)
+                       break;
+       }
+       up_read(&rdev_sem);
+
+       ((u32 *)buf)[0] = i; /* report an updated number of entries */
+       ((u32 *)buf)[1] = info[1]; /* put back an mport ID */
+       if (copy_to_user(arg, buf, sizeof(u32) * (info[0] + 2)))
+               ret = -EFAULT;
+
+       kfree(buf);
+       return ret;
+}
+
+/*
+ * cm_mport_get_list() - Returns list of available local mport devices
+ */
+static int cm_mport_get_list(void __user *arg)
+{
+       int ret = 0;
+       u32 entries;
+       void *buf;
+       struct cm_dev *cm;
+       u32 *entry_ptr;
+       int count = 0;
+
+       if (copy_from_user(&entries, arg, sizeof(entries)))
+               return -EFAULT;
+       if (entries == 0 || entries > RIO_MAX_MPORTS)
+               return -EINVAL;
+       buf = kcalloc(entries + 1, sizeof(u32), GFP_KERNEL);
+       if (!buf)
+               return -ENOMEM;
+
+       /* Scan all registered cm_dev objects */
+       entry_ptr = (u32 *)((uintptr_t)buf + sizeof(u32));
+       down_read(&rdev_sem);
+       list_for_each_entry(cm, &cm_dev_list, list) {
+               if (count++ < entries) {
+                       *entry_ptr = (cm->mport->id << 16) |
+                                     cm->mport->host_deviceid;
+                       entry_ptr++;
+               }
+       }
+       up_read(&rdev_sem);
+
+       *((u32 *)buf) = count; /* report a real number of entries */
+       if (copy_to_user(arg, buf, sizeof(u32) * (count + 1)))
+               ret = -EFAULT;
+
+       kfree(buf);
+       return ret;
+}
+
+/*
+ * cm_chan_create() - Create a message exchange channel
+ */
+static int cm_chan_create(struct file *filp, void __user *arg)
+{
+       u16 __user *p = arg;
+       u16 ch_num;
+       struct rio_channel *ch;
+
+       if (get_user(ch_num, p))
+               return -EFAULT;
+
+       riocm_debug(CHOP, "ch_%d requested by %s(%d)",
+                   ch_num, current->comm, task_pid_nr(current));
+       ch = riocm_ch_create(&ch_num);
+       if (IS_ERR(ch))
+               return PTR_ERR(ch);
+
+       ch->filp = filp;
+       riocm_debug(CHOP, "ch_%d created by %s(%d)",
+                   ch_num, current->comm, task_pid_nr(current));
+       return put_user(ch_num, p);
+}
+
+/*
+ * cm_chan_close() - Close channel
+ * @filp:      Pointer to file object
+ * @arg:       Channel to close
+ */
+static int cm_chan_close(struct file *filp, void __user *arg)
+{
+       u16 __user *p = arg;
+       u16 ch_num;
+       struct rio_channel *ch;
+
+       if (get_user(ch_num, p))
+               return -EFAULT;
+
+       riocm_debug(CHOP, "ch_%d by %s(%d)",
+                   ch_num, current->comm, task_pid_nr(current));
+
+       spin_lock_bh(&idr_lock);
+       ch = idr_find(&ch_idr, ch_num);
+       if (!ch) {
+               spin_unlock_bh(&idr_lock);
+               return 0;
+       }
+       if (ch->filp != filp) {
+               spin_unlock_bh(&idr_lock);
+               return -EINVAL;
+       }
+       idr_remove(&ch_idr, ch->id);
+       spin_unlock_bh(&idr_lock);
+
+       return riocm_ch_close(ch);
+}
+
+/*
+ * cm_chan_bind() - Bind channel
+ * @arg:       Channel number
+ */
+static int cm_chan_bind(void __user *arg)
+{
+       struct rio_cm_channel chan;
+
+       if (copy_from_user(&chan, arg, sizeof(chan)))
+               return -EFAULT;
+       if (chan.mport_id >= RIO_MAX_MPORTS)
+               return -EINVAL;
+
+       return riocm_ch_bind(chan.id, chan.mport_id, NULL);
+}
+
+/*
+ * cm_chan_listen() - Listen on channel
+ * @arg:       Channel number
+ */
+static int cm_chan_listen(void __user *arg)
+{
+       u16 __user *p = arg;
+       u16 ch_num;
+
+       if (get_user(ch_num, p))
+               return -EFAULT;
+
+       return riocm_ch_listen(ch_num);
+}
+
+/*
+ * cm_chan_accept() - Accept incoming connection
+ * @filp:      Pointer to file object
+ * @arg:       Channel number
+ */
+static int cm_chan_accept(struct file *filp, void __user *arg)
+{
+       struct rio_cm_accept param;
+       long accept_to;
+       struct rio_channel *ch;
+
+       if (copy_from_user(&param, arg, sizeof(param)))
+               return -EFAULT;
+
+       riocm_debug(CHOP, "on ch_%d by %s(%d)",
+                   param.ch_num, current->comm, task_pid_nr(current));
+
+       accept_to = param.wait_to ?
+                       msecs_to_jiffies(param.wait_to) : 0;
+
+       ch = riocm_ch_accept(param.ch_num, &param.ch_num, accept_to);
+       if (IS_ERR(ch))
+               return PTR_ERR(ch);
+       ch->filp = filp;
+
+       riocm_debug(CHOP, "new ch_%d for %s(%d)",
+                   ch->id, current->comm, task_pid_nr(current));
+
+       if (copy_to_user(arg, &param, sizeof(param)))
+               return -EFAULT;
+       return 0;
+}
+
+/*
+ * cm_chan_connect() - Connect on channel
+ * @arg:       Channel information
+ */
+static int cm_chan_connect(void __user *arg)
+{
+       struct rio_cm_channel chan;
+       struct cm_dev *cm;
+       struct cm_peer *peer;
+       int ret = -ENODEV;
+
+       if (copy_from_user(&chan, arg, sizeof(chan)))
+               return -EFAULT;
+       if (chan.mport_id >= RIO_MAX_MPORTS)
+               return -EINVAL;
+
+       down_read(&rdev_sem);
+
+       /* Find matching cm_dev object */
+       list_for_each_entry(cm, &cm_dev_list, list) {
+               if (cm->mport->id == chan.mport_id) {
+                       ret = 0;
+                       break;
+               }
+       }
+
+       if (ret)
+               goto err_out;
+
+       if (chan.remote_destid >= RIO_ANY_DESTID(cm->mport->sys_size)) {
+               ret = -EINVAL;
+               goto err_out;
+       }
+
+       /* Find corresponding RapidIO endpoint device object */
+       ret = -ENODEV;
+
+       list_for_each_entry(peer, &cm->peers, node) {
+               if (peer->rdev->destid == chan.remote_destid) {
+                       ret = 0;
+                       break;
+               }
+       }
+
+       if (ret)
+               goto err_out;
+
+       up_read(&rdev_sem);
+
+       return riocm_ch_connect(chan.id, cm, peer, chan.remote_channel);
+err_out:
+       up_read(&rdev_sem);
+       return ret;
+}
+
+/*
+ * cm_chan_msg_send() - Send a message through channel
+ * @arg:       Outbound message information
+ */
+static int cm_chan_msg_send(void __user *arg)
+{
+       struct rio_cm_msg msg;
+       void *buf;
+       int ret = 0;
+
+       if (copy_from_user(&msg, arg, sizeof(msg)))
+               return -EFAULT;
+       if (msg.size > RIO_MAX_MSG_SIZE)
+               return -EINVAL;
+
+       buf = kmalloc(msg.size, GFP_KERNEL);
+       if (!buf)
+               return -ENOMEM;
+
+       if (copy_from_user(buf, (void __user *)(uintptr_t)msg.msg, msg.size)) {
+               ret = -EFAULT;
+               goto out;
+       }
+
+       ret = riocm_ch_send(msg.ch_num, buf, msg.size);
+out:
+       kfree(buf);
+       return ret;
+}
+
+/*
+ * cm_chan_msg_rcv() - Receive a message through channel
+ * @arg:       Inbound message information
+ */
+static int cm_chan_msg_rcv(void __user *arg)
+{
+       struct rio_cm_msg msg;
+       struct rio_channel *ch;
+       void *buf;
+       long rxto;
+       int ret = 0, msg_size;
+
+       if (copy_from_user(&msg, arg, sizeof(msg)))
+               return -EFAULT;
+
+       if (msg.ch_num == 0 || msg.size == 0)
+               return -EINVAL;
+
+       ch = riocm_get_channel(msg.ch_num);
+       if (!ch)
+               return -ENODEV;
+
+       rxto = msg.rxto ? msecs_to_jiffies(msg.rxto) : MAX_SCHEDULE_TIMEOUT;
+
+       ret = riocm_ch_receive(ch, &buf, rxto);
+       if (ret)
+               goto out;
+
+       msg_size = min(msg.size, (u16)(RIO_MAX_MSG_SIZE));
+
+       if (copy_to_user((void __user *)(uintptr_t)msg.msg, buf, msg_size))
+               ret = -EFAULT;
+
+       riocm_ch_free_rxbuf(ch, buf);
+out:
+       riocm_put_channel(ch);
+       return ret;
+}
+
+/*
+ * riocm_cdev_ioctl() - IOCTL requests handler
+ */
+static long
+riocm_cdev_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
+{
+       switch (cmd) {
+       case RIO_CM_EP_GET_LIST_SIZE:
+               return cm_ep_get_list_size((void __user *)arg);
+       case RIO_CM_EP_GET_LIST:
+               return cm_ep_get_list((void __user *)arg);
+       case RIO_CM_CHAN_CREATE:
+               return cm_chan_create(filp, (void __user *)arg);
+       case RIO_CM_CHAN_CLOSE:
+               return cm_chan_close(filp, (void __user *)arg);
+       case RIO_CM_CHAN_BIND:
+               return cm_chan_bind((void __user *)arg);
+       case RIO_CM_CHAN_LISTEN:
+               return cm_chan_listen((void __user *)arg);
+       case RIO_CM_CHAN_ACCEPT:
+               return cm_chan_accept(filp, (void __user *)arg);
+       case RIO_CM_CHAN_CONNECT:
+               return cm_chan_connect((void __user *)arg);
+       case RIO_CM_CHAN_SEND:
+               return cm_chan_msg_send((void __user *)arg);
+       case RIO_CM_CHAN_RECEIVE:
+               return cm_chan_msg_rcv((void __user *)arg);
+       case RIO_CM_MPORT_GET_LIST:
+               return cm_mport_get_list((void __user *)arg);
+       default:
+               break;
+       }
+
+       return -EINVAL;
+}
+
+static const struct file_operations riocm_cdev_fops = {
+       .owner          = THIS_MODULE,
+       .open           = riocm_cdev_open,
+       .release        = riocm_cdev_release,
+       .unlocked_ioctl = riocm_cdev_ioctl,
+};
+
+/*
+ * riocm_add_dev - add new remote RapidIO device into channel management core
+ * @dev: device object associated with RapidIO device
+ * @sif: subsystem interface
+ *
+ * Adds the specified RapidIO device (if applicable) into peers list of
+ * the corresponding channel management device (cm_dev).
+ */
+static int riocm_add_dev(struct device *dev, struct subsys_interface *sif)
+{
+       struct cm_peer *peer;
+       struct rio_dev *rdev = to_rio_dev(dev);
+       struct cm_dev *cm;
+
+       /* Check if the remote device has capabilities required to support CM */
+       if (!dev_cm_capable(rdev))
+               return 0;
+
+       riocm_debug(RDEV, "(%s)", rio_name(rdev));
+
+       peer = kmalloc(sizeof(*peer), GFP_KERNEL);
+       if (!peer)
+               return -ENOMEM;
+
+       /* Find a corresponding cm_dev object */
+       down_write(&rdev_sem);
+       list_for_each_entry(cm, &cm_dev_list, list) {
+               if (cm->mport == rdev->net->hport)
+                       goto found;
+       }
+
+       up_write(&rdev_sem);
+       kfree(peer);
+       return -ENODEV;
+
+found:
+       peer->rdev = rdev;
+       list_add_tail(&peer->node, &cm->peers);
+       cm->npeers++;
+
+       up_write(&rdev_sem);
+       return 0;
+}
+
+/*
+ * riocm_remove_dev - remove remote RapidIO device from channel management core
+ * @dev: device object associated with RapidIO device
+ * @sif: subsystem interface
+ *
+ * Removes the specified RapidIO device (if applicable) from peers list of
+ * the corresponding channel management device (cm_dev).
+ */
+static void riocm_remove_dev(struct device *dev, struct subsys_interface *sif)
+{
+       struct rio_dev *rdev = to_rio_dev(dev);
+       struct cm_dev *cm;
+       struct cm_peer *peer;
+       struct rio_channel *ch, *_c;
+       unsigned int i;
+       bool found = false;
+       LIST_HEAD(list);
+
+       /* Check if the remote device has capabilities required to support CM */
+       if (!dev_cm_capable(rdev))
+               return;
+
+       riocm_debug(RDEV, "(%s)", rio_name(rdev));
+
+       /* Find matching cm_dev object */
+       down_write(&rdev_sem);
+       list_for_each_entry(cm, &cm_dev_list, list) {
+               if (cm->mport == rdev->net->hport) {
+                       found = true;
+                       break;
+               }
+       }
+
+       if (!found) {
+               up_write(&rdev_sem);
+               return;
+       }
+
+       /* Remove remote device from the list of peers */
+       found = false;
+       list_for_each_entry(peer, &cm->peers, node) {
+               if (peer->rdev == rdev) {
+                       riocm_debug(RDEV, "removing peer %s", rio_name(rdev));
+                       found = true;
+                       list_del(&peer->node);
+                       cm->npeers--;
+                       kfree(peer);
+                       break;
+               }
+       }
+
+       up_write(&rdev_sem);
+
+       if (!found)
+               return;
+
+       /*
+        * Release channels associated with this peer
+        */
+
+       spin_lock_bh(&idr_lock);
+       idr_for_each_entry(&ch_idr, ch, i) {
+               if (ch && ch->rdev == rdev) {
+                       if (atomic_read(&rdev->state) != RIO_DEVICE_SHUTDOWN)
+                               riocm_exch(ch, RIO_CM_DISCONNECT);
+                       idr_remove(&ch_idr, ch->id);
+                       list_add(&ch->ch_node, &list);
+               }
+       }
+       spin_unlock_bh(&idr_lock);
+
+       if (!list_empty(&list)) {
+               list_for_each_entry_safe(ch, _c, &list, ch_node) {
+                       list_del(&ch->ch_node);
+                       riocm_ch_close(ch);
+               }
+       }
+}
+
+/*
+ * riocm_cdev_add() - Create rio_cm char device
+ * @devno: device number assigned to device (MAJ + MIN)
+ */
+static int riocm_cdev_add(dev_t devno)
+{
+       int ret;
+
+       cdev_init(&riocm_cdev.cdev, &riocm_cdev_fops);
+       riocm_cdev.cdev.owner = THIS_MODULE;
+       ret = cdev_add(&riocm_cdev.cdev, devno, 1);
+       if (ret < 0) {
+               riocm_error("Cannot register a device with error %d", ret);
+               return ret;
+       }
+
+       riocm_cdev.dev = device_create(dev_class, NULL, devno, NULL, DEV_NAME);
+       if (IS_ERR(riocm_cdev.dev)) {
+               cdev_del(&riocm_cdev.cdev);
+               return PTR_ERR(riocm_cdev.dev);
+       }
+
+       riocm_debug(MPORT, "Added %s cdev(%d:%d)",
+                   DEV_NAME, MAJOR(devno), MINOR(devno));
+
+       return 0;
+}
+
+/*
+ * riocm_add_mport - add new local mport device into channel management core
+ * @dev: device object associated with mport
+ * @class_intf: class interface
+ *
+ * When a new mport device is added, CM immediately reserves inbound and
+ * outbound RapidIO mailboxes that will be used.
+ */
+static int riocm_add_mport(struct device *dev,
+                          struct class_interface *class_intf)
+{
+       int rc;
+       int i;
+       struct cm_dev *cm;
+       struct rio_mport *mport = to_rio_mport(dev);
+
+       riocm_debug(MPORT, "add mport %s", mport->name);
+
+       cm = kzalloc(sizeof(*cm), GFP_KERNEL);
+       if (!cm)
+               return -ENOMEM;
+
+       cm->mport = mport;
+
+       rc = rio_request_outb_mbox(mport, cm, cmbox,
+                                  RIOCM_TX_RING_SIZE, riocm_outb_msg_event);
+       if (rc) {
+               riocm_error("failed to allocate OBMBOX_%d on %s",
+                           cmbox, mport->name);
+               kfree(cm);
+               return -ENODEV;
+       }
+
+       rc = rio_request_inb_mbox(mport, cm, cmbox,
+                                 RIOCM_RX_RING_SIZE, riocm_inb_msg_event);
+       if (rc) {
+               riocm_error("failed to allocate IBMBOX_%d on %s",
+                           cmbox, mport->name);
+               rio_release_outb_mbox(mport, cmbox);
+               kfree(cm);
+               return -ENODEV;
+       }
+
+       /*
+        * Allocate and register inbound messaging buffers to be ready
+        * to receive channel and system management requests
+        */
+       for (i = 0; i < RIOCM_RX_RING_SIZE; i++)
+               cm->rx_buf[i] = NULL;
+
+       cm->rx_slots = RIOCM_RX_RING_SIZE;
+       mutex_init(&cm->rx_lock);
+       riocm_rx_fill(cm, RIOCM_RX_RING_SIZE);
+       cm->rx_wq = create_workqueue(DRV_NAME "/rxq");
+       INIT_WORK(&cm->rx_work, rio_ibmsg_handler);
+
+       cm->tx_slot = 0;
+       cm->tx_cnt = 0;
+       cm->tx_ack_slot = 0;
+       spin_lock_init(&cm->tx_lock);
+
+       INIT_LIST_HEAD(&cm->peers);
+       cm->npeers = 0;
+       INIT_LIST_HEAD(&cm->tx_reqs);
+
+       down_write(&rdev_sem);
+       list_add_tail(&cm->list, &cm_dev_list);
+       up_write(&rdev_sem);
+
+       return 0;
+}
+
+/*
+ * riocm_remove_mport - remove local mport device from channel management core
+ * @dev: device object associated with mport
+ * @class_intf: class interface
+ *
+ * Removes a local mport device from the list of registered devices that provide
+ * channel management services. Returns an error if the specified mport is not
+ * registered with the CM core.
+ */
+static void riocm_remove_mport(struct device *dev,
+                              struct class_interface *class_intf)
+{
+       struct rio_mport *mport = to_rio_mport(dev);
+       struct cm_dev *cm;
+       struct cm_peer *peer, *temp;
+       struct rio_channel *ch, *_c;
+       unsigned int i;
+       bool found = false;
+       LIST_HEAD(list);
+
+       riocm_debug(MPORT, "%s", mport->name);
+
+       /* Find a matching cm_dev object */
+       down_write(&rdev_sem);
+       list_for_each_entry(cm, &cm_dev_list, list) {
+               if (cm->mport == mport) {
+                       list_del(&cm->list);
+                       found = true;
+                       break;
+               }
+       }
+       up_write(&rdev_sem);
+       if (!found)
+               return;
+
+       flush_workqueue(cm->rx_wq);
+       destroy_workqueue(cm->rx_wq);
+
+       /* Release channels bound to this mport */
+       spin_lock_bh(&idr_lock);
+       idr_for_each_entry(&ch_idr, ch, i) {
+               if (ch->cmdev == cm) {
+                       riocm_debug(RDEV, "%s drop ch_%d",
+                                   mport->name, ch->id);
+                       idr_remove(&ch_idr, ch->id);
+                       list_add(&ch->ch_node, &list);
+               }
+       }
+       spin_unlock_bh(&idr_lock);
+
+       if (!list_empty(&list)) {
+               list_for_each_entry_safe(ch, _c, &list, ch_node) {
+                       list_del(&ch->ch_node);
+                       riocm_ch_close(ch);
+               }
+       }
+
+       rio_release_inb_mbox(mport, cmbox);
+       rio_release_outb_mbox(mport, cmbox);
+
+       /* Remove and free peer entries */
+       if (!list_empty(&cm->peers))
+               riocm_debug(RDEV, "ATTN: peer list not empty");
+       list_for_each_entry_safe(peer, temp, &cm->peers, node) {
+               riocm_debug(RDEV, "removing peer %s", rio_name(peer->rdev));
+               list_del(&peer->node);
+               kfree(peer);
+       }
+
+       riocm_rx_free(cm);
+       kfree(cm);
+       riocm_debug(MPORT, "%s done", mport->name);
+}
+
+static int rio_cm_shutdown(struct notifier_block *nb, unsigned long code,
+       void *unused)
+{
+       struct rio_channel *ch;
+       unsigned int i;
+
+       riocm_debug(EXIT, ".");
+
+       spin_lock_bh(&idr_lock);
+       idr_for_each_entry(&ch_idr, ch, i) {
+               riocm_debug(EXIT, "close ch %d", ch->id);
+               if (ch->state == RIO_CM_CONNECTED)
+                       riocm_send_close(ch);
+       }
+       spin_unlock_bh(&idr_lock);
+
+       return NOTIFY_DONE;
+}
+
+/*
+ * riocm_interface handles addition/removal of remote RapidIO devices
+ */
+static struct subsys_interface riocm_interface = {
+       .name           = "rio_cm",
+       .subsys         = &rio_bus_type,
+       .add_dev        = riocm_add_dev,
+       .remove_dev     = riocm_remove_dev,
+};
+
+/*
+ * rio_mport_interface handles addition/removal local mport devices
+ */
+static struct class_interface rio_mport_interface __refdata = {
+       .class = &rio_mport_class,
+       .add_dev = riocm_add_mport,
+       .remove_dev = riocm_remove_mport,
+};
+
+static struct notifier_block rio_cm_notifier = {
+       .notifier_call = rio_cm_shutdown,
+};
+
+static int __init riocm_init(void)
+{
+       int ret;
+
+       /* Create device class needed by udev */
+       dev_class = class_create(THIS_MODULE, DRV_NAME);
+       if (IS_ERR(dev_class)) {
+               riocm_error("Cannot create " DRV_NAME " class");
+               return PTR_ERR(dev_class);
+       }
+
+       ret = alloc_chrdev_region(&dev_number, 0, 1, DRV_NAME);
+       if (ret) {
+               class_destroy(dev_class);
+               return ret;
+       }
+
+       dev_major = MAJOR(dev_number);
+       dev_minor_base = MINOR(dev_number);
+       riocm_debug(INIT, "Registered class with %d major", dev_major);
+
+       /*
+        * Register as rapidio_port class interface to get notifications about
+        * mport additions and removals.
+        */
+       ret = class_interface_register(&rio_mport_interface);
+       if (ret) {
+               riocm_error("class_interface_register error: %d", ret);
+               goto err_reg;
+       }
+
+       /*
+        * Register as RapidIO bus interface to get notifications about
+        * addition/removal of remote RapidIO devices.
+        */
+       ret = subsys_interface_register(&riocm_interface);
+       if (ret) {
+               riocm_error("subsys_interface_register error: %d", ret);
+               goto err_cl;
+       }
+
+       ret = register_reboot_notifier(&rio_cm_notifier);
+       if (ret) {
+               riocm_error("failed to register reboot notifier (err=%d)", ret);
+               goto err_sif;
+       }
+
+       ret = riocm_cdev_add(dev_number);
+       if (ret) {
+               unregister_reboot_notifier(&rio_cm_notifier);
+               ret = -ENODEV;
+               goto err_sif;
+       }
+
+       return 0;
+err_sif:
+       subsys_interface_unregister(&riocm_interface);
+err_cl:
+       class_interface_unregister(&rio_mport_interface);
+err_reg:
+       unregister_chrdev_region(dev_number, 1);
+       class_destroy(dev_class);
+       return ret;
+}
+
+static void __exit riocm_exit(void)
+{
+       riocm_debug(EXIT, "enter");
+       unregister_reboot_notifier(&rio_cm_notifier);
+       subsys_interface_unregister(&riocm_interface);
+       class_interface_unregister(&rio_mport_interface);
+       idr_destroy(&ch_idr);
+
+       device_unregister(riocm_cdev.dev);
+       cdev_del(&(riocm_cdev.cdev));
+
+       class_destroy(dev_class);
+       unregister_chrdev_region(dev_number, 1);
+}
+
+late_initcall(riocm_init);
+module_exit(riocm_exit);
index 6d4e92ccdc914460b28f9b07aaa65406676f912b..c44747c0796a26e08ad6c847cf2ef0b4e75c578c 100644 (file)
@@ -357,6 +357,7 @@ header-y += reiserfs_fs.h
 header-y += reiserfs_xattr.h
 header-y += resource.h
 header-y += rfkill.h
+header-y += rio_cm_cdev.h
 header-y += rio_mport_cdev.h
 header-y += romfs_fs.h
 header-y += rose.h
diff --git a/include/uapi/linux/rio_cm_cdev.h b/include/uapi/linux/rio_cm_cdev.h
new file mode 100644 (file)
index 0000000..6edb900
--- /dev/null
@@ -0,0 +1,78 @@
+/*
+ * Copyright (c) 2015, Integrated Device Technology Inc.
+ * Copyright (c) 2015, Prodrive Technologies
+ * Copyright (c) 2015, RapidIO Trade Association
+ * All rights reserved.
+ *
+ * This software is available to you under a choice of one of two licenses.
+ * You may choose to be licensed under the terms of the GNU General Public
+ * License(GPL) Version 2, or the BSD-3 Clause license below:
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * 3. Neither the name of the copyright holder nor the names of its contributors
+ * may be used to endorse or promote products derived from this software without
+ * specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef _RIO_CM_CDEV_H_
+#define _RIO_CM_CDEV_H_
+
+#include <linux/types.h>
+
+struct rio_cm_channel {
+       __u16 id;
+       __u16 remote_channel;
+       __u16 remote_destid;
+       __u8 mport_id;
+};
+
+struct rio_cm_msg {
+       __u16 ch_num;
+       __u16 size;
+       __u32 rxto;     /* receive timeout in mSec. 0 = blocking */
+       __u64 msg;
+};
+
+struct rio_cm_accept {
+       __u16 ch_num;
+       __u16 pad0;
+       __u32 wait_to;  /* accept timeout in mSec. 0 = blocking */
+};
+
+/* RapidIO Channelized Messaging Driver IOCTLs */
+#define RIO_CM_IOC_MAGIC       'c'
+
+#define RIO_CM_EP_GET_LIST_SIZE        _IOWR(RIO_CM_IOC_MAGIC, 1, __u32)
+#define RIO_CM_EP_GET_LIST     _IOWR(RIO_CM_IOC_MAGIC, 2, __u32)
+#define RIO_CM_CHAN_CREATE     _IOWR(RIO_CM_IOC_MAGIC, 3, __u16)
+#define RIO_CM_CHAN_CLOSE      _IOW(RIO_CM_IOC_MAGIC, 4, __u16)
+#define RIO_CM_CHAN_BIND       _IOW(RIO_CM_IOC_MAGIC, 5, struct rio_cm_channel)
+#define RIO_CM_CHAN_LISTEN     _IOW(RIO_CM_IOC_MAGIC, 6, __u16)
+#define RIO_CM_CHAN_ACCEPT     _IOWR(RIO_CM_IOC_MAGIC, 7, struct rio_cm_accept)
+#define RIO_CM_CHAN_CONNECT    _IOW(RIO_CM_IOC_MAGIC, 8, struct rio_cm_channel)
+#define RIO_CM_CHAN_SEND       _IOW(RIO_CM_IOC_MAGIC, 9, struct rio_cm_msg)
+#define RIO_CM_CHAN_RECEIVE    _IOWR(RIO_CM_IOC_MAGIC, 10, struct rio_cm_msg)
+#define RIO_CM_MPORT_GET_LIST  _IOWR(RIO_CM_IOC_MAGIC, 11, __u32)
+
+#endif /* _RIO_CM_CDEV_H_ */