aboutsummaryrefslogtreecommitdiffstats
path: root/drivers/staging/ozwpan
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/staging/ozwpan')
-rw-r--r--drivers/staging/ozwpan/Kbuild19
-rw-r--r--drivers/staging/ozwpan/Kconfig9
-rw-r--r--drivers/staging/ozwpan/README25
-rw-r--r--drivers/staging/ozwpan/TODO12
-rw-r--r--drivers/staging/ozwpan/ozappif.h46
-rw-r--r--drivers/staging/ozwpan/ozcdev.c521
-rw-r--r--drivers/staging/ozwpan/ozcdev.h18
-rw-r--r--drivers/staging/ozwpan/ozconfig.h27
-rw-r--r--drivers/staging/ozwpan/ozeltbuf.c339
-rw-r--r--drivers/staging/ozwpan/ozeltbuf.h70
-rw-r--r--drivers/staging/ozwpan/ozevent.c116
-rw-r--r--drivers/staging/ozwpan/ozevent.h31
-rw-r--r--drivers/staging/ozwpan/ozeventdef.h47
-rw-r--r--drivers/staging/ozwpan/ozhcd.c2256
-rw-r--r--drivers/staging/ozwpan/ozhcd.h15
-rw-r--r--drivers/staging/ozwpan/ozmain.c58
-rw-r--r--drivers/staging/ozwpan/ozpd.c832
-rw-r--r--drivers/staging/ozwpan/ozpd.h121
-rw-r--r--drivers/staging/ozwpan/ozproto.c957
-rw-r--r--drivers/staging/ozwpan/ozproto.h69
-rw-r--r--drivers/staging/ozwpan/ozprotocol.h372
-rw-r--r--drivers/staging/ozwpan/oztrace.c36
-rw-r--r--drivers/staging/ozwpan/oztrace.h35
-rw-r--r--drivers/staging/ozwpan/ozurbparanoia.c53
-rw-r--r--drivers/staging/ozwpan/ozurbparanoia.h19
-rw-r--r--drivers/staging/ozwpan/ozusbif.h43
-rw-r--r--drivers/staging/ozwpan/ozusbsvc.c245
-rw-r--r--drivers/staging/ozwpan/ozusbsvc.h32
-rw-r--r--drivers/staging/ozwpan/ozusbsvc1.c437
29 files changed, 6860 insertions, 0 deletions
diff --git a/drivers/staging/ozwpan/Kbuild b/drivers/staging/ozwpan/Kbuild
new file mode 100644
index 000000000000..6cc84cb3f0a6
--- /dev/null
+++ b/drivers/staging/ozwpan/Kbuild
@@ -0,0 +1,19 @@
+# -----------------------------------------------------------------------------
+# Copyright (c) 2011 Ozmo Inc
+# Released under the GNU General Public License Version 2 (GPLv2).
+# -----------------------------------------------------------------------------
+obj-$(CONFIG_USB_WPAN_HCD) += ozwpan.o
+ozwpan-y := \
+ ozmain.o \
+ ozpd.o \
+ ozusbsvc.o \
+ ozusbsvc1.o \
+ ozhcd.o \
+ ozeltbuf.o \
+ ozproto.o \
+ ozcdev.o \
+ ozurbparanoia.o \
+ oztrace.o \
+ ozevent.o
+
+
diff --git a/drivers/staging/ozwpan/Kconfig b/drivers/staging/ozwpan/Kconfig
new file mode 100644
index 000000000000..7904caec546a
--- /dev/null
+++ b/drivers/staging/ozwpan/Kconfig
@@ -0,0 +1,9 @@
+config USB_WPAN_HCD
+ tristate "USB over WiFi Host Controller"
+ depends on USB && NET
+ help
+ A driver for USB Host Controllers that are compatible with
+ Ozmo Devices USB over WiFi technology.
+
+ To compile this driver a module, choose M here: the module
+ will be called "ozwpan".
diff --git a/drivers/staging/ozwpan/README b/drivers/staging/ozwpan/README
new file mode 100644
index 000000000000..bb1a69b94541
--- /dev/null
+++ b/drivers/staging/ozwpan/README
@@ -0,0 +1,25 @@
+OZWPAN USB Host Controller Driver
+---------------------------------
+This driver is a USB HCD driver that does not have an associated a physical
+device but instead uses Wi-Fi to communicate with the wireless peripheral.
+The USB requests are converted into a layer 2 network protocol and transmitted
+on the network using an ethertype (0x892e) regestered to Ozmo Device Inc.
+This driver is compatible with existing wireless devices that use Ozmo Devices
+technology.
+
+To operate the driver must be bound to a suitable network interface. This can
+be done when the module is loaded (specifying the name of the network interface
+as a paramter - e.g. 'insmod ozwpan g_net_dev=go0') or can be bound after
+loading using an ioctl call. See the ozappif.h file and the ioctls
+OZ_IOCTL_ADD_BINDING and OZ_IOCTL_REMOVE_BINDING.
+
+The devices connect to the host use Wi-Fi Direct so a network card that supports
+Wi-Fi direct is required. A recent version (0.8.x or later) version of the
+wpa_supplicant can be used to setup the network interface to create a persistent
+autonomous group (for older pre-WFD peripherals) or put in a listen state to
+allow group negotiation to occur for more recent devices that support WFD.
+
+The protocol used over the network does not directly mimic the USB bus
+transactions as this would be rather busy and inefficient. Instead the chapter 9
+requests are converted into a request/response pair of messages. (See
+ozprotocol.h for data structures used in the protocol).
diff --git a/drivers/staging/ozwpan/TODO b/drivers/staging/ozwpan/TODO
new file mode 100644
index 000000000000..f7a9c122f596
--- /dev/null
+++ b/drivers/staging/ozwpan/TODO
@@ -0,0 +1,12 @@
+TODO:
+ - review user mode interface and determine if ioctls can be replaced
+ with something better. correctly export data structures to user mode
+ if ioctls are still required and allocate ioctl numbers from
+ ioctl-number.txt.
+ - check USB HCD implementation is complete and correct.
+ - remove any debug and trace code.
+ - code review by USB developer community.
+ - testing with as many devices as possible.
+
+Please send any patches for this driver to Chris Kelly <ckelly@ozmodevices.com>
+and Greg Kroah-Hartman <gregkh@linuxfoundation.org>.
diff --git a/drivers/staging/ozwpan/ozappif.h b/drivers/staging/ozwpan/ozappif.h
new file mode 100644
index 000000000000..af0273293872
--- /dev/null
+++ b/drivers/staging/ozwpan/ozappif.h
@@ -0,0 +1,46 @@
+/* -----------------------------------------------------------------------------
+ * Copyright (c) 2011 Ozmo Inc
+ * Released under the GNU General Public License Version 2 (GPLv2).
+ * -----------------------------------------------------------------------------
+ */
+#ifndef _OZAPPIF_H
+#define _OZAPPIF_H
+
+#include "ozeventdef.h"
+
+#define OZ_IOCTL_MAGIC 0xf4
+
+struct oz_mac_addr {
+ unsigned char a[6];
+};
+
+#define OZ_MAX_PDS 8
+
+struct oz_pd_list {
+ int count;
+ struct oz_mac_addr addr[OZ_MAX_PDS];
+};
+
+#define OZ_MAX_BINDING_LEN 32
+
+struct oz_binding_info {
+ char name[OZ_MAX_BINDING_LEN];
+};
+
+struct oz_test {
+ int action;
+};
+
+#define OZ_IOCTL_GET_PD_LIST _IOR(OZ_IOCTL_MAGIC, 0, struct oz_pd_list)
+#define OZ_IOCTL_SET_ACTIVE_PD _IOW(OZ_IOCTL_MAGIC, 1, struct oz_mac_addr)
+#define OZ_IOCTL_GET_ACTIVE_PD _IOR(OZ_IOCTL_MAGIC, 2, struct oz_mac_addr)
+#define OZ_IOCTL_CLEAR_EVENTS _IO(OZ_IOCTL_MAGIC, 3)
+#define OZ_IOCTL_GET_EVENTS _IOR(OZ_IOCTL_MAGIC, 4, struct oz_evtlist)
+#define OZ_IOCTL_ADD_BINDING _IOW(OZ_IOCTL_MAGIC, 5, struct oz_binding_info)
+#define OZ_IOCTL_TEST _IOWR(OZ_IOCTL_MAGIC, 6, struct oz_test)
+#define OZ_IOCTL_SET_EVENT_MASK _IOW(OZ_IOCTL_MAGIC, 7, unsigned long)
+#define OZ_IOCTL_REMOVE_BINDING _IOW(OZ_IOCTL_MAGIC, 8, struct oz_binding_info)
+#define OZ_IOCTL_MAX 9
+
+
+#endif /* _OZAPPIF_H */
diff --git a/drivers/staging/ozwpan/ozcdev.c b/drivers/staging/ozwpan/ozcdev.c
new file mode 100644
index 000000000000..1c380d687963
--- /dev/null
+++ b/drivers/staging/ozwpan/ozcdev.c
@@ -0,0 +1,521 @@
+/* -----------------------------------------------------------------------------
+ * Copyright (c) 2011 Ozmo Inc
+ * Released under the GNU General Public License Version 2 (GPLv2).
+ * -----------------------------------------------------------------------------
+ */
+#include <linux/module.h>
+#include <linux/fs.h>
+#include <linux/cdev.h>
+#include <linux/uaccess.h>
+#include <linux/netdevice.h>
+#include <linux/poll.h>
+#include <linux/sched.h>
+#include "ozconfig.h"
+#include "ozprotocol.h"
+#include "oztrace.h"
+#include "ozappif.h"
+#include "ozeltbuf.h"
+#include "ozpd.h"
+#include "ozproto.h"
+#include "ozevent.h"
+/*------------------------------------------------------------------------------
+ */
+#define OZ_RD_BUF_SZ 256
+struct oz_cdev {
+ dev_t devnum;
+ struct cdev cdev;
+ wait_queue_head_t rdq;
+ spinlock_t lock;
+ u8 active_addr[ETH_ALEN];
+ struct oz_pd *active_pd;
+};
+
+/* Per PD context for the serial service stored in the PD. */
+struct oz_serial_ctx {
+ atomic_t ref_count;
+ u8 tx_seq_num;
+ u8 rx_seq_num;
+ u8 rd_buf[OZ_RD_BUF_SZ];
+ int rd_in;
+ int rd_out;
+};
+/*------------------------------------------------------------------------------
+ */
+int g_taction;
+/*------------------------------------------------------------------------------
+ */
+static struct oz_cdev g_cdev;
+/*------------------------------------------------------------------------------
+ * Context: process and softirq
+ */
+static struct oz_serial_ctx *oz_cdev_claim_ctx(struct oz_pd *pd)
+{
+ struct oz_serial_ctx *ctx;
+ spin_lock_bh(&pd->app_lock[OZ_APPID_SERIAL-1]);
+ ctx = (struct oz_serial_ctx *)pd->app_ctx[OZ_APPID_SERIAL-1];
+ if (ctx)
+ atomic_inc(&ctx->ref_count);
+ spin_unlock_bh(&pd->app_lock[OZ_APPID_SERIAL-1]);
+ return ctx;
+}
+/*------------------------------------------------------------------------------
+ * Context: softirq or process
+ */
+static void oz_cdev_release_ctx(struct oz_serial_ctx *ctx)
+{
+ if (atomic_dec_and_test(&ctx->ref_count)) {
+ oz_trace("Dealloc serial context.\n");
+ kfree(ctx);
+ }
+}
+/*------------------------------------------------------------------------------
+ * Context: process
+ */
+int oz_cdev_open(struct inode *inode, struct file *filp)
+{
+ struct oz_cdev *dev;
+ oz_trace("oz_cdev_open()\n");
+ oz_trace("major = %d minor = %d\n", imajor(inode), iminor(inode));
+ dev = container_of(inode->i_cdev, struct oz_cdev, cdev);
+ filp->private_data = dev;
+ return 0;
+}
+/*------------------------------------------------------------------------------
+ * Context: process
+ */
+int oz_cdev_release(struct inode *inode, struct file *filp)
+{
+ oz_trace("oz_cdev_release()\n");
+ return 0;
+}
+/*------------------------------------------------------------------------------
+ * Context: process
+ */
+ssize_t oz_cdev_read(struct file *filp, char __user *buf, size_t count,
+ loff_t *fpos)
+{
+ int n;
+ int ix;
+
+ struct oz_pd *pd;
+ struct oz_serial_ctx *ctx = 0;
+
+ spin_lock_bh(&g_cdev.lock);
+ pd = g_cdev.active_pd;
+ if (pd)
+ oz_pd_get(pd);
+ spin_unlock_bh(&g_cdev.lock);
+ if (pd == 0)
+ return -1;
+ ctx = oz_cdev_claim_ctx(pd);
+ if (ctx == 0)
+ goto out2;
+ n = ctx->rd_in - ctx->rd_out;
+ if (n < 0)
+ n += OZ_RD_BUF_SZ;
+ if (count > n)
+ count = n;
+ ix = ctx->rd_out;
+ n = OZ_RD_BUF_SZ - ix;
+ if (n > count)
+ n = count;
+ if (copy_to_user(buf, &ctx->rd_buf[ix], n)) {
+ count = 0;
+ goto out1;
+ }
+ ix += n;
+ if (ix == OZ_RD_BUF_SZ)
+ ix = 0;
+ if (n < count) {
+ if (copy_to_user(&buf[n], ctx->rd_buf, count-n)) {
+ count = 0;
+ goto out1;
+ }
+ ix = count-n;
+ }
+ ctx->rd_out = ix;
+out1:
+ oz_cdev_release_ctx(ctx);
+out2:
+ oz_pd_put(pd);
+ return count;
+}
+/*------------------------------------------------------------------------------
+ * Context: process
+ */
+ssize_t oz_cdev_write(struct file *filp, const char __user *buf, size_t count,
+ loff_t *fpos)
+{
+ struct oz_pd *pd;
+ struct oz_elt_buf *eb;
+ struct oz_elt_info *ei = 0;
+ struct oz_elt *elt;
+ struct oz_app_hdr *app_hdr;
+ struct oz_serial_ctx *ctx;
+
+ spin_lock_bh(&g_cdev.lock);
+ pd = g_cdev.active_pd;
+ if (pd)
+ oz_pd_get(pd);
+ spin_unlock_bh(&g_cdev.lock);
+ if (pd == 0)
+ return -1;
+ eb = &pd->elt_buff;
+ ei = oz_elt_info_alloc(eb);
+ if (ei == 0) {
+ count = 0;
+ goto out;
+ }
+ elt = (struct oz_elt *)ei->data;
+ app_hdr = (struct oz_app_hdr *)(elt+1);
+ elt->length = sizeof(struct oz_app_hdr) + count;
+ elt->type = OZ_ELT_APP_DATA;
+ ei->app_id = OZ_APPID_SERIAL;
+ ei->length = elt->length + sizeof(struct oz_elt);
+ app_hdr->app_id = OZ_APPID_SERIAL;
+ if (copy_from_user(app_hdr+1, buf, count))
+ goto out;
+ spin_lock_bh(&pd->app_lock[OZ_APPID_USB-1]);
+ ctx = (struct oz_serial_ctx *)pd->app_ctx[OZ_APPID_SERIAL-1];
+ if (ctx) {
+ app_hdr->elt_seq_num = ctx->tx_seq_num++;
+ if (ctx->tx_seq_num == 0)
+ ctx->tx_seq_num = 1;
+ spin_lock(&eb->lock);
+ if (oz_queue_elt_info(eb, 0, 0, ei) == 0)
+ ei = 0;
+ spin_unlock(&eb->lock);
+ }
+ spin_unlock_bh(&pd->app_lock[OZ_APPID_USB-1]);
+out:
+ if (ei) {
+ count = 0;
+ spin_lock_bh(&eb->lock);
+ oz_elt_info_free(eb, ei);
+ spin_unlock_bh(&eb->lock);
+ }
+ oz_pd_put(pd);
+ return count;
+}
+/*------------------------------------------------------------------------------
+ * Context: process
+ */
+static int oz_set_active_pd(u8 *addr)
+{
+ int rc = 0;
+ struct oz_pd *pd;
+ struct oz_pd *old_pd;
+ pd = oz_pd_find(addr);
+ if (pd) {
+ spin_lock_bh(&g_cdev.lock);
+ memcpy(g_cdev.active_addr, addr, ETH_ALEN);
+ old_pd = g_cdev.active_pd;
+ g_cdev.active_pd = pd;
+ spin_unlock_bh(&g_cdev.lock);
+ if (old_pd)
+ oz_pd_put(old_pd);
+ } else {
+ if (!memcmp(addr, "\0\0\0\0\0\0", sizeof(addr))) {
+ spin_lock_bh(&g_cdev.lock);
+ pd = g_cdev.active_pd;
+ g_cdev.active_pd = 0;
+ memset(g_cdev.active_addr, 0,
+ sizeof(g_cdev.active_addr));
+ spin_unlock_bh(&g_cdev.lock);
+ if (pd)
+ oz_pd_put(pd);
+ } else {
+ rc = -1;
+ }
+ }
+ return rc;
+}
+/*------------------------------------------------------------------------------
+ * Context: process
+ */
+long oz_cdev_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
+{
+ int rc = 0;
+ if (_IOC_TYPE(cmd) != OZ_IOCTL_MAGIC)
+ return -ENOTTY;
+ if (_IOC_NR(cmd) > OZ_IOCTL_MAX)
+ return -ENOTTY;
+ if (_IOC_DIR(cmd) & _IOC_READ)
+ rc = !access_ok(VERIFY_WRITE, (void __user *)arg,
+ _IOC_SIZE(cmd));
+ else if (_IOC_DIR(cmd) & _IOC_WRITE)
+ rc = !access_ok(VERIFY_READ, (void __user *)arg,
+ _IOC_SIZE(cmd));
+ if (rc)
+ return -EFAULT;
+ switch (cmd) {
+ case OZ_IOCTL_GET_PD_LIST: {
+ struct oz_pd_list list;
+ oz_trace("OZ_IOCTL_GET_PD_LIST\n");
+ list.count = oz_get_pd_list(list.addr, OZ_MAX_PDS);
+ if (copy_to_user((void __user *)arg, &list,
+ sizeof(list)))
+ return -EFAULT;
+ }
+ break;
+ case OZ_IOCTL_SET_ACTIVE_PD: {
+ u8 addr[ETH_ALEN];
+ oz_trace("OZ_IOCTL_SET_ACTIVE_PD\n");
+ if (copy_from_user(addr, (void __user *)arg, ETH_ALEN))
+ return -EFAULT;
+ rc = oz_set_active_pd(addr);
+ }
+ break;
+ case OZ_IOCTL_GET_ACTIVE_PD: {
+ u8 addr[ETH_ALEN];
+ oz_trace("OZ_IOCTL_GET_ACTIVE_PD\n");
+ spin_lock_bh(&g_cdev.lock);
+ memcpy(addr, g_cdev.active_addr, ETH_ALEN);
+ spin_unlock_bh(&g_cdev.lock);
+ if (copy_to_user((void __user *)arg, addr, ETH_ALEN))
+ return -EFAULT;
+ }
+ break;
+#ifdef WANT_EVENT_TRACE
+ case OZ_IOCTL_CLEAR_EVENTS:
+ oz_events_clear();
+ break;
+ case OZ_IOCTL_GET_EVENTS:
+ rc = oz_events_copy((void __user *)arg);
+ break;
+ case OZ_IOCTL_SET_EVENT_MASK:
+ if (copy_from_user(&g_evt_mask, (void __user *)arg,
+ sizeof(unsigned long))) {
+ return -EFAULT;
+ }
+ break;
+#endif /* WANT_EVENT_TRACE */
+ case OZ_IOCTL_ADD_BINDING:
+ case OZ_IOCTL_REMOVE_BINDING: {
+ struct oz_binding_info b;
+ if (copy_from_user(&b, (void __user *)arg,
+ sizeof(struct oz_binding_info))) {
+ return -EFAULT;
+ }
+ /* Make sure name is null terminated. */
+ b.name[OZ_MAX_BINDING_LEN-1] = 0;
+ if (cmd == OZ_IOCTL_ADD_BINDING)
+ oz_binding_add(b.name);
+ else
+ oz_binding_remove(b.name);
+ }
+ break;
+ }
+ return rc;
+}
+/*------------------------------------------------------------------------------
+ * Context: process
+ */
+unsigned int oz_cdev_poll(struct file *filp, poll_table *wait)
+{
+ unsigned int ret = 0;
+ struct oz_cdev *dev = filp->private_data;
+ oz_trace("Poll called wait = %p\n", wait);
+ spin_lock_bh(&dev->lock);
+ if (dev->active_pd) {
+ struct oz_serial_ctx *ctx = oz_cdev_claim_ctx(dev->active_pd);
+ if (ctx) {
+ if (ctx->rd_in != ctx->rd_out)
+ ret |= POLLIN | POLLRDNORM;
+ oz_cdev_release_ctx(ctx);
+ }
+ }
+ spin_unlock_bh(&dev->lock);
+ if (wait)
+ poll_wait(filp, &dev->rdq, wait);
+ return ret;
+}
+/*------------------------------------------------------------------------------
+ */
+const struct file_operations oz_fops = {
+ .owner = THIS_MODULE,
+ .open = oz_cdev_open,
+ .release = oz_cdev_release,
+ .read = oz_cdev_read,
+ .write = oz_cdev_write,
+ .unlocked_ioctl = oz_cdev_ioctl,
+ .poll = oz_cdev_poll
+};
+/*------------------------------------------------------------------------------
+ * Context: process
+ */
+int oz_cdev_register(void)
+{
+ int err;
+ memset(&g_cdev, 0, sizeof(g_cdev));
+ err = alloc_chrdev_region(&g_cdev.devnum, 0, 1, "ozwpan");
+ if (err < 0)
+ return err;
+ oz_trace("Alloc dev number %d:%d\n", MAJOR(g_cdev.devnum),
+ MINOR(g_cdev.devnum));
+ cdev_init(&g_cdev.cdev, &oz_fops);
+ g_cdev.cdev.owner = THIS_MODULE;
+ g_cdev.cdev.ops = &oz_fops;
+ spin_lock_init(&g_cdev.lock);
+ init_waitqueue_head(&g_cdev.rdq);
+ err = cdev_add(&g_cdev.cdev, g_cdev.devnum, 1);
+ return 0;
+}
+/*------------------------------------------------------------------------------
+ * Context: process
+ */
+int oz_cdev_deregister(void)
+{
+ cdev_del(&g_cdev.cdev);
+ unregister_chrdev_region(g_cdev.devnum, 1);
+ return 0;
+}
+/*------------------------------------------------------------------------------
+ * Context: process
+ */
+int oz_cdev_init(void)
+{
+ oz_event_log(OZ_EVT_SERVICE, 1, OZ_APPID_SERIAL, 0, 0);
+ oz_app_enable(OZ_APPID_SERIAL, 1);
+ return 0;
+}
+/*------------------------------------------------------------------------------
+ * Context: process
+ */
+void oz_cdev_term(void)
+{
+ oz_event_log(OZ_EVT_SERVICE, 2, OZ_APPID_SERIAL, 0, 0);
+ oz_app_enable(OZ_APPID_SERIAL, 0);
+}
+/*------------------------------------------------------------------------------
+ * Context: softirq-serialized
+ */
+int oz_cdev_start(struct oz_pd *pd, int resume)
+{
+ struct oz_serial_ctx *ctx;
+ struct oz_serial_ctx *old_ctx = 0;
+ oz_event_log(OZ_EVT_SERVICE, 3, OZ_APPID_SERIAL, 0, resume);
+ if (resume) {
+ oz_trace("Serial service resumed.\n");
+ return 0;
+ }
+ ctx = kzalloc(sizeof(struct oz_serial_ctx), GFP_ATOMIC);
+ if (ctx == 0)
+ return -ENOMEM;
+ atomic_set(&ctx->ref_count, 1);
+ ctx->tx_seq_num = 1;
+ spin_lock_bh(&pd->app_lock[OZ_APPID_SERIAL-1]);
+ old_ctx = pd->app_ctx[OZ_APPID_SERIAL-1];
+ if (old_ctx) {
+ spin_unlock_bh(&pd->app_lock[OZ_APPID_SERIAL-1]);
+ kfree(ctx);
+ } else {
+ pd->app_ctx[OZ_APPID_SERIAL-1] = ctx;
+ spin_unlock_bh(&pd->app_lock[OZ_APPID_SERIAL-1]);
+ }
+ spin_lock(&g_cdev.lock);
+ if ((g_cdev.active_pd == 0) &&
+ (memcmp(pd->mac_addr, g_cdev.active_addr, ETH_ALEN) == 0)) {
+ oz_pd_get(pd);
+ g_cdev.active_pd = pd;
+ oz_trace("Active PD arrived.\n");
+ }
+ spin_unlock(&g_cdev.lock);
+ oz_trace("Serial service started.\n");
+ return 0;
+}
+/*------------------------------------------------------------------------------
+ * Context: softirq or process
+ */
+void oz_cdev_stop(struct oz_pd *pd, int pause)
+{
+ struct oz_serial_ctx *ctx;
+ oz_event_log(OZ_EVT_SERVICE, 4, OZ_APPID_SERIAL, 0, pause);
+ if (pause) {
+ oz_trace("Serial service paused.\n");
+ return;
+ }
+ spin_lock_bh(&pd->app_lock[OZ_APPID_SERIAL-1]);
+ ctx = (struct oz_serial_ctx *)pd->app_ctx[OZ_APPID_SERIAL-1];
+ pd->app_ctx[OZ_APPID_SERIAL-1] = 0;
+ spin_unlock_bh(&pd->app_lock[OZ_APPID_SERIAL-1]);
+ if (ctx)
+ oz_cdev_release_ctx(ctx);
+ spin_lock(&g_cdev.lock);
+ if (pd == g_cdev.active_pd)
+ g_cdev.active_pd = 0;
+ else
+ pd = 0;
+ spin_unlock(&g_cdev.lock);
+ if (pd) {
+ oz_pd_put(pd);
+ oz_trace("Active PD departed.\n");
+ }
+ oz_trace("Serial service stopped.\n");
+}
+/*------------------------------------------------------------------------------
+ * Context: softirq-serialized
+ */
+void oz_cdev_rx(struct oz_pd *pd, struct oz_elt *elt)
+{
+ struct oz_serial_ctx *ctx;
+ struct oz_app_hdr *app_hdr;
+ u8 *data;
+ int len;
+ int space;
+ int copy_sz;
+ int ix;
+
+ ctx = oz_cdev_claim_ctx(pd);
+ if (ctx == 0) {
+ oz_trace("Cannot claim serial context.\n");
+ return;
+ }
+
+ app_hdr = (struct oz_app_hdr *)(elt+1);
+ /* If sequence number is non-zero then check it is not a duplicate.
+ */
+ if (app_hdr->elt_seq_num != 0) {
+ if (((ctx->rx_seq_num - app_hdr->elt_seq_num) & 0x80) == 0) {
+ /* Reject duplicate element. */
+ oz_trace("Duplicate element:%02x %02x\n",
+ app_hdr->elt_seq_num, ctx->rx_seq_num);
+ goto out;
+ }
+ }
+ ctx->rx_seq_num = app_hdr->elt_seq_num;
+ len = elt->length - sizeof(struct oz_app_hdr);
+ data = ((u8 *)(elt+1)) + sizeof(struct oz_app_hdr);
+ if (len <= 0)
+ goto out;
+ space = ctx->rd_out - ctx->rd_in - 1;
+ if (space < 0)
+ space += OZ_RD_BUF_SZ;
+ if (len > space) {
+ oz_trace("Not enough space:%d %d\n", len, space);
+ len = space;
+ }
+ ix = ctx->rd_in;
+ copy_sz = OZ_RD_BUF_SZ - ix;
+ if (copy_sz > len)
+ copy_sz = len;
+ memcpy(&ctx->rd_buf[ix], data, copy_sz);
+ len -= copy_sz;
+ ix += copy_sz;
+ if (ix == OZ_RD_BUF_SZ)
+ ix = 0;
+ if (len) {
+ memcpy(ctx->rd_buf, data+copy_sz, len);
+ ix = len;
+ }
+ ctx->rd_in = ix;
+ wake_up(&g_cdev.rdq);
+out:
+ oz_cdev_release_ctx(ctx);
+}
+/*------------------------------------------------------------------------------
+ * Context: softirq
+ */
+void oz_cdev_heartbeat(struct oz_pd *pd)
+{
+}
diff --git a/drivers/staging/ozwpan/ozcdev.h b/drivers/staging/ozwpan/ozcdev.h
new file mode 100644
index 000000000000..698014bb8d72
--- /dev/null
+++ b/drivers/staging/ozwpan/ozcdev.h
@@ -0,0 +1,18 @@
+/* -----------------------------------------------------------------------------
+ * Copyright (c) 2011 Ozmo Inc
+ * Released under the GNU General Public License Version 2 (GPLv2).
+ * -----------------------------------------------------------------------------
+ */
+#ifndef _OZCDEV_H
+#define _OZCDEV_H
+
+int oz_cdev_register(void);
+int oz_cdev_deregister(void);
+int oz_cdev_init(void);
+void oz_cdev_term(void);
+int oz_cdev_start(struct oz_pd *pd, int resume);
+void oz_cdev_stop(struct oz_pd *pd, int pause);
+void oz_cdev_rx(struct oz_pd *pd, struct oz_elt *elt);
+void oz_cdev_heartbeat(struct oz_pd *pd);
+
+#endif /* _OZCDEV_H */
diff --git a/drivers/staging/ozwpan/ozconfig.h b/drivers/staging/ozwpan/ozconfig.h
new file mode 100644
index 000000000000..43e6373a009c
--- /dev/null
+++ b/drivers/staging/ozwpan/ozconfig.h
@@ -0,0 +1,27 @@
+/* -----------------------------------------------------------------------------
+ * Copyright (c) 2011 Ozmo Inc
+ * Released under the GNU General Public License Version 2 (GPLv2).
+ * ---------------------------------------------------------------------------*/
+#ifndef _OZCONFIG_H
+#define _OZCONFIG_H
+
+/* #define WANT_TRACE */
+#ifdef WANT_TRACE
+#define WANT_VERBOSE_TRACE
+#endif /* #ifdef WANT_TRACE */
+/* #define WANT_URB_PARANOIA */
+
+/* #define WANT_PRE_2_6_39 */
+#define WANT_EVENT_TRACE
+
+/* These defines determine what verbose trace is displayed. */
+#ifdef WANT_VERBOSE_TRACE
+/* #define WANT_TRACE_STREAM */
+/* #define WANT_TRACE_URB */
+/* #define WANT_TRACE_CTRL_DETAIL */
+#define WANT_TRACE_HUB
+/* #define WANT_TRACE_RX_FRAMES */
+/* #define WANT_TRACE_TX_FRAMES */
+#endif /* WANT_VERBOSE_TRACE */
+
+#endif /* _OZCONFIG_H */
diff --git a/drivers/staging/ozwpan/ozeltbuf.c b/drivers/staging/ozwpan/ozeltbuf.c
new file mode 100644
index 000000000000..988f522475d9
--- /dev/null
+++ b/drivers/staging/ozwpan/ozeltbuf.c
@@ -0,0 +1,339 @@
+/* -----------------------------------------------------------------------------
+ * Copyright (c) 2011 Ozmo Inc
+ * Released under the GNU General Public License Version 2 (GPLv2).
+ * -----------------------------------------------------------------------------
+ */
+#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/netdevice.h>
+#include "ozconfig.h"
+#include "ozprotocol.h"
+#include "ozeltbuf.h"
+#include "ozpd.h"
+#include "oztrace.h"
+/*------------------------------------------------------------------------------
+ */
+#define OZ_ELT_INFO_MAGIC_USED 0x35791057
+#define OZ_ELT_INFO_MAGIC_FREE 0x78940102
+/*------------------------------------------------------------------------------
+ * Context: softirq-serialized
+ */
+int oz_elt_buf_init(struct oz_elt_buf *buf)
+{
+ memset(buf, 0, sizeof(struct oz_elt_buf));
+ INIT_LIST_HEAD(&buf->stream_list);
+ INIT_LIST_HEAD(&buf->order_list);
+ INIT_LIST_HEAD(&buf->isoc_list);
+ buf->max_free_elts = 32;
+ spin_lock_init(&buf->lock);
+ return 0;
+}
+/*------------------------------------------------------------------------------
+ * Context: softirq or process
+ */
+void oz_elt_buf_term(struct oz_elt_buf *buf)
+{
+ struct list_head *e;
+ int i;
+ /* Free any elements in the order or isoc lists. */
+ for (i = 0; i < 2; i++) {
+ struct list_head *list;
+ if (i)
+ list = &buf->order_list;
+ else
+ list = &buf->isoc_list;
+ e = list->next;
+ while (e != list) {
+ struct oz_elt_info *ei =
+ container_of(e, struct oz_elt_info, link_order);
+ e = e->next;
+ kfree(ei);
+ }
+ }
+ /* Free any elelment in the pool. */
+ while (buf->elt_pool) {
+ struct oz_elt_info *ei =
+ container_of(buf->elt_pool, struct oz_elt_info, link);
+ buf->elt_pool = buf->elt_pool->next;
+ kfree(ei);
+ }
+ buf->free_elts = 0;
+}
+/*------------------------------------------------------------------------------
+ * Context: softirq or process
+ */
+struct oz_elt_info *oz_elt_info_alloc(struct oz_elt_buf *buf)
+{
+ struct oz_elt_info *ei = 0;
+ spin_lock_bh(&buf->lock);
+ if (buf->free_elts && buf->elt_pool) {
+ ei = container_of(buf->elt_pool, struct oz_elt_info, link);
+ buf->elt_pool = ei->link.next;
+ buf->free_elts--;
+ spin_unlock_bh(&buf->lock);
+ if (ei->magic != OZ_ELT_INFO_MAGIC_FREE) {
+ oz_trace("oz_elt_info_alloc: ei with bad magic: 0x%x\n",
+ ei->magic);
+ }
+ } else {
+ spin_unlock_bh(&buf->lock);
+ ei = kmalloc(sizeof(struct oz_elt_info), GFP_ATOMIC);
+ }
+ if (ei) {
+ ei->flags = 0;
+ ei->app_id = 0;
+ ei->callback = 0;
+ ei->context = 0;
+ ei->stream = 0;
+ ei->magic = OZ_ELT_INFO_MAGIC_USED;
+ INIT_LIST_HEAD(&ei->link);
+ INIT_LIST_HEAD(&ei->link_order);
+ }
+ return ei;
+}
+/*------------------------------------------------------------------------------
+ * Precondition: oz_elt_buf.lock must be held.
+ * Context: softirq or process
+ */
+void oz_elt_info_free(struct oz_elt_buf *buf, struct oz_elt_info *ei)
+{
+ if (ei) {
+ if (ei->magic == OZ_ELT_INFO_MAGIC_USED) {
+ buf->free_elts++;
+ ei->link.next = buf->elt_pool;
+ buf->elt_pool = &ei->link;
+ ei->magic = OZ_ELT_INFO_MAGIC_FREE;
+ } else {
+ oz_trace("oz_elt_info_free: bad magic ei: %p"
+ " magic: 0x%x\n",
+ ei, ei->magic);
+ }
+ }
+}
+/*------------------------------------------------------------------------------
+ * Context: softirq
+ */
+void oz_elt_info_free_chain(struct oz_elt_buf *buf, struct list_head *list)
+{
+ struct list_head *e;
+ e = list->next;
+ spin_lock_bh(&buf->lock);
+ while (e != list) {
+ struct oz_elt_info *ei;
+ ei = container_of(e, struct oz_elt_info, link);
+ e = e->next;
+ oz_elt_info_free(buf, ei);
+ }
+ spin_unlock_bh(&buf->lock);
+}
+/*------------------------------------------------------------------------------
+ */
+int oz_elt_stream_create(struct oz_elt_buf *buf, u8 id, int max_buf_count)
+{
+ struct oz_elt_stream *st;
+
+ oz_trace("oz_elt_stream_create(0x%x)\n", id);
+
+ st = kzalloc(sizeof(struct oz_elt_stream), GFP_ATOMIC | __GFP_ZERO);
+ if (st == 0)
+ return -ENOMEM;
+ atomic_set(&st->ref_count, 1);
+ st->id = id;
+ st->max_buf_count = max_buf_count;
+ INIT_LIST_HEAD(&st->elt_list);
+ spin_lock_bh(&buf->lock);
+ list_add_tail(&st->link, &buf->stream_list);
+ spin_unlock_bh(&buf->lock);
+ return 0;
+}
+/*------------------------------------------------------------------------------
+ */
+int oz_elt_stream_delete(struct oz_elt_buf *buf, u8 id)
+{
+ struct list_head *e;
+ struct oz_elt_stream *st;
+ oz_trace("oz_elt_stream_delete(0x%x)\n", id);
+ spin_lock_bh(&buf->lock);
+ e = buf->stream_list.next;
+ while (e != &buf->stream_list) {
+ st = container_of(e, struct oz_elt_stream, link);
+ if (st->id == id) {
+ list_del(e);
+ break;
+ }
+ st = 0;
+ }
+ if (!st) {
+ spin_unlock_bh(&buf->lock);
+ return -1;
+ }
+ e = st->elt_list.next;
+ while (e != &st->elt_list) {
+ struct oz_elt_info *ei =
+ container_of(e, struct oz_elt_info, link);
+ e = e->next;
+ list_del_init(&ei->link);
+ list_del_init(&ei->link_order);
+ st->buf_count -= ei->length;
+ oz_trace2(OZ_TRACE_STREAM, "Stream down: %d %d %d\n",
+ st->buf_count,
+ ei->length, atomic_read(&st->ref_count));
+ oz_elt_stream_put(st);
+ oz_elt_info_free(buf, ei);
+ }
+ spin_unlock_bh(&buf->lock);
+ oz_elt_stream_put(st);
+ return 0;
+}
+/*------------------------------------------------------------------------------
+ */
+void oz_elt_stream_get(struct oz_elt_stream *st)
+{
+ atomic_inc(&st->ref_count);
+}
+/*------------------------------------------------------------------------------
+ */
+void oz_elt_stream_put(struct oz_elt_stream *st)
+{
+ if (atomic_dec_and_test(&st->ref_count)) {
+ oz_trace("Stream destroyed\n");
+ kfree(st);
+ }
+}
+/*------------------------------------------------------------------------------
+ * Precondition: Element buffer lock must be held.
+ * If this function fails the caller is responsible for deallocating the elt
+ * info structure.
+ */
+int oz_queue_elt_info(struct oz_elt_buf *buf, u8 isoc, u8 id,
+ struct oz_elt_info *ei)
+{
+ struct oz_elt_stream *st = 0;
+ struct list_head *e;
+ if (id) {
+ list_for_each(e, &buf->stream_list) {
+ st = container_of(e, struct oz_elt_stream, link);
+ if (st->id == id)
+ break;
+ }
+ if (e == &buf->stream_list) {
+ /* Stream specified but stream not known so fail.
+ * Caller deallocates element info. */
+ return -1;
+ }
+ }
+ if (st) {
+ /* If this is an ISOC fixed element that needs a frame number
+ * then insert that now. Earlier we stored the unit count in
+ * this field.
+ */
+ struct oz_isoc_fixed *body = (struct oz_isoc_fixed *)
+ &ei->data[sizeof(struct oz_elt)];
+ if ((body->app_id == OZ_APPID_USB) && (body->type
+ == OZ_USB_ENDPOINT_DATA) &&
+ (body->format == OZ_DATA_F_ISOC_FIXED)) {
+ u8 unit_count = body->frame_number;
+ body->frame_number = st->frame_number;
+ st->frame_number += unit_count;
+ }
+ /* Claim stream and update accounts */
+ oz_elt_stream_get(st);
+ ei->stream = st;
+ st->buf_count += ei->length;
+ /* Add to list in stream. */
+ list_add_tail(&ei->link, &st->elt_list);
+ oz_trace2(OZ_TRACE_STREAM, "Stream up: %d %d\n",
+ st->buf_count, ei->length);
+ /* Check if we have too much buffered for this stream. If so
+ * start dropping elements until we are back in bounds.
+ */
+ while ((st->buf_count > st->max_buf_count) &&
+ !list_empty(&st->elt_list)) {
+ struct oz_elt_info *ei2 =
+ list_first_entry(&st->elt_list,
+ struct oz_elt_info, link);
+ list_del_init(&ei2->link);
+ list_del_init(&ei2->link_order);
+ st->buf_count -= ei2->length;
+ oz_elt_info_free(buf, ei2);
+ oz_elt_stream_put(st);
+ }
+ }
+ list_add_tail(&ei->link_order, isoc ?
+ &buf->isoc_list : &buf->order_list);
+ return 0;
+}
+/*------------------------------------------------------------------------------
+ */
+int oz_select_elts_for_tx(struct oz_elt_buf *buf, u8 isoc, unsigned *len,
+ unsigned max_len, struct list_head *list)
+{
+ int count = 0;
+ struct list_head *e;
+ struct list_head *el;
+ struct oz_elt_info *ei;
+ spin_lock_bh(&buf->lock);
+ if (isoc)
+ el = &buf->isoc_list;
+ else
+ el = &buf->order_list;
+ e = el->next;
+ while (e != el) {
+ struct oz_app_hdr *app_hdr;
+ ei = container_of(e, struct oz_elt_info, link_order);
+ e = e->next;
+ if ((*len + ei->length) <= max_len) {
+ app_hdr = (struct oz_app_hdr *)
+ &ei->data[sizeof(struct oz_elt)];
+ app_hdr->elt_seq_num = buf->tx_seq_num[ei->app_id]++;
+ if (buf->tx_seq_num[ei->app_id] == 0)
+ buf->tx_seq_num[ei->app_id] = 1;
+ *len += ei->length;
+ list_del(&ei->link);
+ list_del(&ei->link_order);
+ if (ei->stream) {
+ ei->stream->buf_count -= ei->length;
+ oz_trace2(OZ_TRACE_STREAM,
+ "Stream down: %d %d\n",
+ ei->stream->buf_count, ei->length);
+ oz_elt_stream_put(ei->stream);
+ ei->stream = 0;
+ }
+ INIT_LIST_HEAD(&ei->link_order);
+ list_add_tail(&ei->link, list);
+ count++;
+ } else {
+ break;
+ }
+ }
+ spin_unlock_bh(&buf->lock);
+ return count;
+}
+/*------------------------------------------------------------------------------
+ */
+int oz_are_elts_available(struct oz_elt_buf *buf)
+{
+ return buf->order_list.next != &buf->order_list;
+}
+/*------------------------------------------------------------------------------
+ */
+void oz_trim_elt_pool(struct oz_elt_buf *buf)
+{
+ struct list_head *free = 0;
+ struct list_head *e;
+ spin_lock_bh(&buf->lock);
+ while (buf->free_elts > buf->max_free_elts) {
+ e = buf->elt_pool;
+ buf->elt_pool = e->next;
+ e->next = free;
+ free = e;
+ buf->free_elts--;
+ }
+ spin_unlock_bh(&buf->lock);
+ while (free) {
+ struct oz_elt_info *ei =
+ container_of(free, struct oz_elt_info, link);
+ free = free->next;
+ kfree(ei);
+ }
+}
diff --git a/drivers/staging/ozwpan/ozeltbuf.h b/drivers/staging/ozwpan/ozeltbuf.h
new file mode 100644
index 000000000000..03c12f57b9bb
--- /dev/null
+++ b/drivers/staging/ozwpan/ozeltbuf.h
@@ -0,0 +1,70 @@
+/* -----------------------------------------------------------------------------
+ * Copyright (c) 2011 Ozmo Inc
+ * Released under the GNU General Public License Version 2 (GPLv2).
+ * -----------------------------------------------------------------------------
+ */
+#ifndef _OZELTBUF_H
+#define _OZELTBUF_H
+
+#include "ozprotocol.h"
+
+/*-----------------------------------------------------------------------------
+ */
+struct oz_pd;
+typedef void (*oz_elt_callback_t)(struct oz_pd *pd, long context);
+
+struct oz_elt_stream {
+ struct list_head link;
+ struct list_head elt_list;
+ atomic_t ref_count;
+ unsigned buf_count;
+ unsigned max_buf_count;
+ u8 frame_number;
+ u8 id;
+};
+
+#define OZ_MAX_ELT_PAYLOAD 255
+struct oz_elt_info {
+ struct list_head link;
+ struct list_head link_order;
+ u8 flags;
+ u8 app_id;
+ oz_elt_callback_t callback;
+ long context;
+ struct oz_elt_stream *stream;
+ u8 data[sizeof(struct oz_elt) + OZ_MAX_ELT_PAYLOAD];
+ int length;
+ unsigned magic;
+};
+/* Flags values */
+#define OZ_EI_F_MARKED 0x1
+
+struct oz_elt_buf {
+ spinlock_t lock;
+ struct list_head stream_list;
+ struct list_head order_list;
+ struct list_head isoc_list;
+ struct list_head *elt_pool;
+ int free_elts;
+ int max_free_elts;
+ u8 tx_seq_num[OZ_NB_APPS];
+};
+
+int oz_elt_buf_init(struct oz_elt_buf *buf);
+void oz_elt_buf_term(struct oz_elt_buf *buf);
+struct oz_elt_info *oz_elt_info_alloc(struct oz_elt_buf *buf);
+void oz_elt_info_free(struct oz_elt_buf *buf, struct oz_elt_info *ei);
+void oz_elt_info_free_chain(struct oz_elt_buf *buf, struct list_head *list);
+int oz_elt_stream_create(struct oz_elt_buf *buf, u8 id, int max_buf_count);
+int oz_elt_stream_delete(struct oz_elt_buf *buf, u8 id);
+void oz_elt_stream_get(struct oz_elt_stream *st);
+void oz_elt_stream_put(struct oz_elt_stream *st);
+int oz_queue_elt_info(struct oz_elt_buf *buf, u8 isoc, u8 id,
+ struct oz_elt_info *ei);
+int oz_select_elts_for_tx(struct oz_elt_buf *buf, u8 isoc, unsigned *len,
+ unsigned max_len, struct list_head *list);
+int oz_are_elts_available(struct oz_elt_buf *buf);
+void oz_trim_elt_pool(struct oz_elt_buf *buf);
+
+#endif /* _OZELTBUF_H */
+
diff --git a/drivers/staging/ozwpan/ozevent.c b/drivers/staging/ozwpan/ozevent.c
new file mode 100644
index 000000000000..73703d3e96bd
--- /dev/null
+++ b/drivers/staging/ozwpan/ozevent.c
@@ -0,0 +1,116 @@
+/* -----------------------------------------------------------------------------
+ * Copyright (c) 2011 Ozmo Inc
+ * Released under the GNU General Public License Version 2 (GPLv2).
+ * -----------------------------------------------------------------------------
+ */
+#include "ozconfig.h"
+#ifdef WANT_EVENT_TRACE
+#include <linux/jiffies.h>
+#include <linux/uaccess.h>
+#include "oztrace.h"
+#include "ozevent.h"
+/*------------------------------------------------------------------------------
+ */
+unsigned long g_evt_mask = 0xffffffff;
+/*------------------------------------------------------------------------------
+ */
+#define OZ_MAX_EVTS 2048 /* Must be power of 2 */
+DEFINE_SPINLOCK(g_eventlock);
+static int g_evt_in;
+static int g_evt_out;
+static int g_missed_events;
+static struct oz_event g_events[OZ_MAX_EVTS];
+/*------------------------------------------------------------------------------
+ * Context: process
+ */
+void oz_event_init(void)
+{
+ oz_trace("Event tracing initialized\n");
+ g_evt_in = g_evt_out = 0;
+ g_missed_events = 0;
+}
+/*------------------------------------------------------------------------------
+ * Context: process
+ */
+void oz_event_term(void)
+{
+ oz_trace("Event tracing terminated\n");
+}
+/*------------------------------------------------------------------------------
+ * Context: any
+ */
+void oz_event_log2(u8 evt, u8 ctx1, u16 ctx2, void *ctx3, unsigned ctx4)
+{
+ unsigned long irqstate;
+ int ix;
+ spin_lock_irqsave(&g_eventlock, irqstate);
+ ix = (g_evt_in + 1) & (OZ_MAX_EVTS - 1);
+ if (ix != g_evt_out) {
+ struct oz_event *e = &g_events[g_evt_in];
+ e->jiffies = jiffies;
+ e->evt = evt;
+ e->ctx1 = ctx1;
+ e->ctx2 = ctx2;
+ e->ctx3 = ctx3;
+ e->ctx4 = ctx4;
+ g_evt_in = ix;
+ } else {
+ g_missed_events++;
+ }
+ spin_unlock_irqrestore(&g_eventlock, irqstate);
+}
+/*------------------------------------------------------------------------------
+ * Context: process
+ */
+int oz_events_copy(struct oz_evtlist __user *lst)
+{
+ int first;
+ int ix;
+ struct hdr {
+ int count;
+ int missed;
+ } hdr;
+ ix = g_evt_out;
+ hdr.count = g_evt_in - ix;
+ if (hdr.count < 0)
+ hdr.count += OZ_MAX_EVTS;
+ if (hdr.count > OZ_EVT_LIST_SZ)
+ hdr.count = OZ_EVT_LIST_SZ;
+ hdr.missed = g_missed_events;
+ g_missed_events = 0;
+ if (copy_to_user((void __user *)lst, &hdr, sizeof(hdr)))
+ return -EFAULT;
+ first = OZ_MAX_EVTS - ix;
+ if (first > hdr.count)
+ first = hdr.count;
+ if (first) {
+ int sz = first*sizeof(struct oz_event);
+ void __user *p = (void __user *)lst->evts;
+ if (copy_to_user(p, &g_events[ix], sz))
+ return -EFAULT;
+ if (hdr.count > first) {
+ p = (void __user *)&lst->evts[first];
+ sz = (hdr.count-first)*sizeof(struct oz_event);
+ if (copy_to_user(p, g_events, sz))
+ return -EFAULT;
+ }
+ }
+ ix += hdr.count;
+ if (ix >= OZ_MAX_EVTS)
+ ix -= OZ_MAX_EVTS;
+ g_evt_out = ix;
+ return 0;
+}
+/*------------------------------------------------------------------------------
+ * Context: process
+ */
+void oz_events_clear(void)
+{
+ unsigned long irqstate;
+ spin_lock_irqsave(&g_eventlock, irqstate);
+ g_evt_in = g_evt_out = 0;
+ g_missed_events = 0;
+ spin_unlock_irqrestore(&g_eventlock, irqstate);
+}
+#endif /* WANT_EVENT_TRACE */
+
diff --git a/drivers/staging/ozwpan/ozevent.h b/drivers/staging/ozwpan/ozevent.h
new file mode 100644
index 000000000000..f033d014c6f3
--- /dev/null
+++ b/drivers/staging/ozwpan/ozevent.h
@@ -0,0 +1,31 @@
+/* -----------------------------------------------------------------------------
+ * Copyright (c) 2011 Ozmo Inc
+ * Released under the GNU General Public License Version 2 (GPLv2).
+ * -----------------------------------------------------------------------------
+ */
+#ifndef _OZEVENT_H
+#define _OZEVENT_H
+#include "ozconfig.h"
+#include "ozeventdef.h"
+
+#ifdef WANT_EVENT_TRACE
+extern unsigned long g_evt_mask;
+void oz_event_init(void);
+void oz_event_term(void);
+void oz_event_log2(u8 evt, u8 ctx1, u16 ctx2, void *ctx3, unsigned ctx4);
+#define oz_event_log(__evt, __ctx1, __ctx2, __ctx3, __ctx4) \
+ do { \
+ if ((1<<(__evt)) & g_evt_mask) \
+ oz_event_log2(__evt, __ctx1, __ctx2, __ctx3, __ctx4); \
+ } while (0)
+int oz_events_copy(struct oz_evtlist __user *lst);
+void oz_events_clear(void);
+#else
+#define oz_event_init()
+#define oz_event_term()
+#define oz_event_log(__evt, __ctx1, __ctx2, __ctx3, __ctx4)
+#define oz_events_copy(__lst)
+#define oz_events_clear()
+#endif /* WANT_EVENT_TRACE */
+
+#endif /* _OZEVENT_H */
diff --git a/drivers/staging/ozwpan/ozeventdef.h b/drivers/staging/ozwpan/ozeventdef.h
new file mode 100644
index 000000000000..a880288bab11
--- /dev/null
+++ b/drivers/staging/ozwpan/ozeventdef.h
@@ -0,0 +1,47 @@
+/* -----------------------------------------------------------------------------
+ * Copyright (c) 2011 Ozmo Inc
+ * Released under the GNU General Public License Version 2 (GPLv2).
+ * -----------------------------------------------------------------------------
+ */
+#ifndef _OZEVENTDEF_H
+#define _OZEVENTDEF_H
+
+#define OZ_EVT_RX_FRAME 0
+#define OZ_EVT_RX_PROCESS 1
+#define OZ_EVT_TX_FRAME 2
+#define OZ_EVT_TX_ISOC 3
+#define OZ_EVT_URB_SUBMIT 4
+#define OZ_EVT_URB_DONE 5
+#define OZ_EVT_URB_CANCEL 6
+#define OZ_EVT_CTRL_REQ 7
+#define OZ_EVT_CTRL_CNF 8
+#define OZ_EVT_CTRL_LOCAL 9
+#define OZ_EVT_CONNECT_REQ 10
+#define OZ_EVT_CONNECT_RSP 11
+#define OZ_EVT_EP_CREDIT 12
+#define OZ_EVT_EP_BUFFERING 13
+#define OZ_EVT_TX_ISOC_DONE 14
+#define OZ_EVT_TX_ISOC_DROP 15
+#define OZ_EVT_TIMER_CTRL 16
+#define OZ_EVT_TIMER 17
+#define OZ_EVT_PD_STATE 18
+#define OZ_EVT_SERVICE 19
+#define OZ_EVT_DEBUG 20
+
+struct oz_event {
+ unsigned long jiffies;
+ unsigned char evt;
+ unsigned char ctx1;
+ unsigned short ctx2;
+ void *ctx3;
+ unsigned ctx4;
+};
+
+#define OZ_EVT_LIST_SZ 64
+struct oz_evtlist {
+ int count;
+ int missed;
+ struct oz_event evts[OZ_EVT_LIST_SZ];
+};
+
+#endif /* _OZEVENTDEF_H */
diff --git a/drivers/staging/ozwpan/ozhcd.c b/drivers/staging/ozwpan/ozhcd.c
new file mode 100644
index 000000000000..750b14eb505e
--- /dev/null
+++ b/drivers/staging/ozwpan/ozhcd.c
@@ -0,0 +1,2256 @@
+/* -----------------------------------------------------------------------------
+ * Copyright (c) 2011 Ozmo Inc
+ * Released under the GNU General Public License Version 2 (GPLv2).
+ *
+ * This file provides the implementation of a USB host controller device that
+ * does not have any associated hardware. Instead the virtual device is
+ * connected to the WiFi network and emulates the operation of a USB hcd by
+ * receiving and sending network frames.
+ * Note:
+ * We take great pains to reduce the amount of code where interrupts need to be
+ * disabled and in this respect we are different from standard HCD's. In
+ * particular we don't want in_irq() code bleeding over to the protocol side of
+ * the driver.
+ * The troublesome functions are the urb enqueue and dequeue functions both of
+ * which can be called in_irq(). So for these functions we put the urbs into a
+ * queue and request a tasklet to process them. This means that a spinlock with
+ * interrupts disabled must be held for insertion and removal but most code is
+ * is in tasklet or soft irq context. The lock that protects this list is called
+ * the tasklet lock and serves the purpose of the 'HCD lock' which must be held
+ * when calling the following functions.
+ * usb_hcd_link_urb_to_ep()
+ * usb_hcd_unlink_urb_from_ep()
+ * usb_hcd_flush_endpoint()
+ * usb_hcd_check_unlink_urb()
+ * -----------------------------------------------------------------------------
+ */
+#include <linux/platform_device.h>
+#include <linux/usb.h>
+#include <linux/jiffies.h>
+#include <linux/slab.h>
+#include <linux/export.h>
+#include "linux/usb/hcd.h"
+#include <asm/unaligned.h>
+#include "ozconfig.h"
+#include "ozusbif.h"
+#include "oztrace.h"
+#include "ozurbparanoia.h"
+#include "ozevent.h"
+/*------------------------------------------------------------------------------
+ * Number of units of buffering to capture for an isochronous IN endpoint before
+ * allowing data to be indicated up.
+ */
+#define OZ_IN_BUFFERING_UNITS 50
+/* Name of our platform device.
+ */
+#define OZ_PLAT_DEV_NAME "ozwpan"
+/* Maximum number of free urb links that can be kept in the pool.
+ */
+#define OZ_MAX_LINK_POOL_SIZE 16
+/* Get endpoint object from the containing link.
+ */
+#define ep_from_link(__e) container_of((__e), struct oz_endpoint, link)
+/*------------------------------------------------------------------------------
+ * Used to link urbs together and also store some status information for each
+ * urb.
+ * A cache of these are kept in a pool to reduce number of calls to kmalloc.
+ */
+struct oz_urb_link {
+ struct list_head link;
+ struct urb *urb;
+ struct oz_port *port;
+ u8 req_id;
+ u8 ep_num;
+ unsigned long submit_jiffies;
+};
+
+/* Holds state information about a USB endpoint.
+ */
+struct oz_endpoint {
+ struct list_head urb_list; /* List of oz_urb_link items. */
+ struct list_head link; /* For isoc ep, links in to isoc
+ lists of oz_port. */
+ unsigned long last_jiffies;
+ int credit;
+ int credit_ceiling;
+ u8 ep_num;
+ u8 attrib;
+ u8 *buffer;
+ int buffer_size;
+ int in_ix;
+ int out_ix;
+ int buffered_units;
+ unsigned flags;
+ int start_frame;
+};
+/* Bits in the flags field. */
+#define OZ_F_EP_BUFFERING 0x1
+#define OZ_F_EP_HAVE_STREAM 0x2
+
+/* Holds state information about a USB interface.
+ */
+struct oz_interface {
+ unsigned ep_mask;
+ u8 alt;
+};
+
+/* Holds state information about an hcd port.
+ */
+#define OZ_NB_ENDPOINTS 16
+struct oz_port {
+ unsigned flags;
+ unsigned status;
+ void *hpd;
+ struct oz_hcd *ozhcd;
+ spinlock_t port_lock;
+ u8 bus_addr;
+ u8 next_req_id;
+ u8 config_num;
+ int num_iface;
+ struct oz_interface *iface;
+ struct oz_endpoint *out_ep[OZ_NB_ENDPOINTS];
+ struct oz_endpoint *in_ep[OZ_NB_ENDPOINTS];
+ struct list_head isoc_out_ep;
+ struct list_head isoc_in_ep;
+};
+#define OZ_PORT_F_PRESENT 0x1
+#define OZ_PORT_F_CHANGED 0x2
+#define OZ_PORT_F_DYING 0x4
+
+/* Data structure in the private context area of struct usb_hcd.
+ */
+#define OZ_NB_PORTS 8
+struct oz_hcd {
+ spinlock_t hcd_lock;
+ struct list_head urb_pending_list;
+ struct list_head urb_cancel_list;
+ struct list_head orphanage;
+ int conn_port; /* Port that is currently connecting, -1 if none.*/
+ struct oz_port ports[OZ_NB_PORTS];
+ uint flags;
+ struct usb_hcd *hcd;
+};
+/* Bits in flags field.
+ */
+#define OZ_HDC_F_SUSPENDED 0x1
+
+/*------------------------------------------------------------------------------
+ * Static function prototypes.
+ */
+static int oz_hcd_start(struct usb_hcd *hcd);
+static void oz_hcd_stop(struct usb_hcd *hcd);
+static void oz_hcd_shutdown(struct usb_hcd *hcd);
+static int oz_hcd_urb_enqueue(struct usb_hcd *hcd, struct urb *urb,
+ gfp_t mem_flags);
+static int oz_hcd_urb_dequeue(struct usb_hcd *hcd, struct urb *urb, int status);
+static void oz_hcd_endpoint_disable(struct usb_hcd *hcd,
+ struct usb_host_endpoint *ep);
+static void oz_hcd_endpoint_reset(struct usb_hcd *hcd,
+ struct usb_host_endpoint *ep);
+static int oz_hcd_get_frame_number(struct usb_hcd *hcd);
+static int oz_hcd_hub_status_data(struct usb_hcd *hcd, char *buf);
+static int oz_hcd_hub_control(struct usb_hcd *hcd, u16 req_type, u16 wvalue,
+ u16 windex, char *buf, u16 wlength);
+static int oz_hcd_bus_suspend(struct usb_hcd *hcd);
+static int oz_hcd_bus_resume(struct usb_hcd *hcd);
+static int oz_plat_probe(struct platform_device *dev);
+static int oz_plat_remove(struct platform_device *dev);
+static void oz_plat_shutdown(struct platform_device *dev);
+static int oz_plat_suspend(struct platform_device *dev, pm_message_t msg);
+static int oz_plat_resume(struct platform_device *dev);
+static void oz_urb_process_tasklet(unsigned long unused);
+static int oz_build_endpoints_for_config(struct usb_hcd *hcd,
+ struct oz_port *port, struct usb_host_config *config,
+ gfp_t mem_flags);
+static void oz_clean_endpoints_for_config(struct usb_hcd *hcd,
+ struct oz_port *port);
+static int oz_build_endpoints_for_interface(struct usb_hcd *hcd,
+ struct oz_port *port,
+ struct usb_host_interface *intf, gfp_t mem_flags);
+static void oz_clean_endpoints_for_interface(struct usb_hcd *hcd,
+ struct oz_port *port, int if_ix);
+static void oz_process_ep0_urb(struct oz_hcd *ozhcd, struct urb *urb,
+ gfp_t mem_flags);
+static struct oz_urb_link *oz_remove_urb(struct oz_endpoint *ep,
+ struct urb *urb);
+static void oz_hcd_clear_orphanage(struct oz_hcd *ozhcd, int status);
+/*------------------------------------------------------------------------------
+ * Static external variables.
+ */
+static struct platform_device *g_plat_dev;
+static struct oz_hcd *g_ozhcd;
+static DEFINE_SPINLOCK(g_hcdlock); /* Guards g_ozhcd. */
+static const char g_hcd_name[] = "Ozmo WPAN";
+static struct list_head *g_link_pool;
+static int g_link_pool_size;
+static DEFINE_SPINLOCK(g_link_lock);
+static DEFINE_SPINLOCK(g_tasklet_lock);
+static struct tasklet_struct g_urb_process_tasklet;
+static struct tasklet_struct g_urb_cancel_tasklet;
+static atomic_t g_pending_urbs = ATOMIC_INIT(0);
+static const struct hc_driver g_oz_hc_drv = {
+ .description = g_hcd_name,
+ .product_desc = "Ozmo Devices WPAN",
+ .hcd_priv_size = sizeof(struct oz_hcd),
+ .flags = HCD_USB11,
+ .start = oz_hcd_start,
+ .stop = oz_hcd_stop,
+ .shutdown = oz_hcd_shutdown,
+ .urb_enqueue = oz_hcd_urb_enqueue,
+ .urb_dequeue = oz_hcd_urb_dequeue,
+ .endpoint_disable = oz_hcd_endpoint_disable,
+ .endpoint_reset = oz_hcd_endpoint_reset,
+ .get_frame_number = oz_hcd_get_frame_number,
+ .hub_status_data = oz_hcd_hub_status_data,
+ .hub_control = oz_hcd_hub_control,
+ .bus_suspend = oz_hcd_bus_suspend,
+ .bus_resume = oz_hcd_bus_resume,
+};
+
+static struct platform_driver g_oz_plat_drv = {
+ .probe = oz_plat_probe,
+ .remove = oz_plat_remove,
+ .shutdown = oz_plat_shutdown,
+ .suspend = oz_plat_suspend,
+ .resume = oz_plat_resume,
+ .driver = {
+ .name = OZ_PLAT_DEV_NAME,
+ .owner = THIS_MODULE,
+ },
+};
+/*------------------------------------------------------------------------------
+ * Gets our private context area (which is of type struct oz_hcd) from the
+ * usb_hcd structure.
+ * Context: any
+ */
+static inline struct oz_hcd *oz_hcd_private(struct usb_hcd *hcd)
+{
+ return (struct oz_hcd *)hcd->hcd_priv;
+}
+/*------------------------------------------------------------------------------
+ * Searches list of ports to find the index of the one with a specified USB
+ * bus address. If none of the ports has the bus address then the connection
+ * port is returned, if there is one or -1 otherwise.
+ * Context: any
+ */
+static int oz_get_port_from_addr(struct oz_hcd *ozhcd, u8 bus_addr)
+{
+ int i;
+ for (i = 0; i < OZ_NB_PORTS; i++) {
+ if (ozhcd->ports[i].bus_addr == bus_addr)
+ return i;
+ }
+ return ozhcd->conn_port;
+}
+/*------------------------------------------------------------------------------
+ * Allocates an urb link, first trying the pool but going to heap if empty.
+ * Context: any
+ */
+static struct oz_urb_link *oz_alloc_urb_link(void)
+{
+ struct oz_urb_link *urbl = 0;
+ unsigned long irq_state;
+ spin_lock_irqsave(&g_link_lock, irq_state);
+ if (g_link_pool) {
+ urbl = container_of(g_link_pool, struct oz_urb_link, link);
+ g_link_pool = urbl->link.next;
+ --g_link_pool_size;
+ }
+ spin_unlock_irqrestore(&g_link_lock, irq_state);
+ if (urbl == 0)
+ urbl = kmalloc(sizeof(struct oz_urb_link), GFP_ATOMIC);
+ return urbl;
+}
+/*------------------------------------------------------------------------------
+ * Frees an urb link by putting it in the pool if there is enough space or
+ * deallocating it to heap otherwise.
+ * Context: any
+ */
+static void oz_free_urb_link(struct oz_urb_link *urbl)
+{
+ if (urbl) {
+ unsigned long irq_state;
+ spin_lock_irqsave(&g_link_lock, irq_state);
+ if (g_link_pool_size < OZ_MAX_LINK_POOL_SIZE) {
+ urbl->link.next = g_link_pool;
+ g_link_pool = &urbl->link;
+ urbl = 0;
+ g_link_pool_size++;
+ }
+ spin_unlock_irqrestore(&g_link_lock, irq_state);
+ if (urbl)
+ kfree(urbl);
+ }
+}
+/*------------------------------------------------------------------------------
+ * Deallocates all the urb links in the pool.
+ * Context: unknown
+ */
+static void oz_empty_link_pool(void)
+{
+ struct list_head *e;
+ unsigned long irq_state;
+ spin_lock_irqsave(&g_link_lock, irq_state);
+ e = g_link_pool;
+ g_link_pool = 0;
+ g_link_pool_size = 0;
+ spin_unlock_irqrestore(&g_link_lock, irq_state);
+ while (e) {
+ struct oz_urb_link *urbl =
+ container_of(e, struct oz_urb_link, link);
+ e = e->next;
+ kfree(urbl);
+ }
+}
+/*------------------------------------------------------------------------------
+ * Allocates endpoint structure and optionally a buffer. If a buffer is
+ * allocated it immediately follows the endpoint structure.
+ * Context: softirq
+ */
+static struct oz_endpoint *oz_ep_alloc(gfp_t mem_flags, int buffer_size)
+{
+ struct oz_endpoint *ep =
+ kzalloc(sizeof(struct oz_endpoint)+buffer_size, mem_flags);
+ if (ep) {
+ INIT_LIST_HEAD(&ep->urb_list);
+ INIT_LIST_HEAD(&ep->link);
+ ep->credit = -1;
+ if (buffer_size) {
+ ep->buffer_size = buffer_size;
+ ep->buffer = (u8 *)(ep+1);
+ }
+ }
+ return ep;
+}
+/*------------------------------------------------------------------------------
+ * Pre-condition: Must be called with g_tasklet_lock held and interrupts
+ * disabled.
+ * Context: softirq or process
+ */
+struct oz_urb_link *oz_uncancel_urb(struct oz_hcd *ozhcd, struct urb *urb)
+{
+ struct oz_urb_link *urbl;
+ struct list_head *e;
+ list_for_each(e, &ozhcd->urb_cancel_list) {
+ urbl = container_of(e, struct oz_urb_link, link);
+ if (urb == urbl->urb) {
+ list_del_init(e);
+ return urbl;
+ }
+ }
+ return 0;
+}
+/*------------------------------------------------------------------------------
+ * This is called when we have finished processing an urb. It unlinks it from
+ * the ep and returns it to the core.
+ * Context: softirq or process
+ */
+static void oz_complete_urb(struct usb_hcd *hcd, struct urb *urb,
+ int status, unsigned long submit_jiffies)
+{
+ struct oz_hcd *ozhcd = oz_hcd_private(hcd);
+ unsigned long irq_state;
+ struct oz_urb_link *cancel_urbl = 0;
+ spin_lock_irqsave(&g_tasklet_lock, irq_state);
+ usb_hcd_unlink_urb_from_ep(hcd, urb);
+ /* Clear hcpriv which will prevent it being put in the cancel list
+ * in the event that an attempt is made to cancel it.
+ */
+ urb->hcpriv = 0;
+ /* Walk the cancel list in case the urb is already sitting there.
+ * Since we process the cancel list in a tasklet rather than in
+ * the dequeue function this could happen.
+ */
+ cancel_urbl = oz_uncancel_urb(ozhcd, urb);
+ /* Note: we release lock but do not enable local irqs.
+ * It appears that usb_hcd_giveback_urb() expects irqs to be disabled,
+ * or at least other host controllers disable interrupts at this point
+ * so we do the same. We must, however, release the lock otherwise a
+ * deadlock will occur if an urb is submitted to our driver in the urb
+ * completion function. Because we disable interrupts it is possible
+ * that the urb_enqueue function can be called with them disabled.
+ */
+ spin_unlock(&g_tasklet_lock);
+ if (oz_forget_urb(urb)) {
+ oz_trace("OZWPAN: ERROR Unknown URB %p\n", urb);
+ } else {
+ static unsigned long last_time;
+ atomic_dec(&g_pending_urbs);
+ oz_trace2(OZ_TRACE_URB,
+ "%lu: giveback_urb(%p,%x) %lu %lu pending:%d\n",
+ jiffies, urb, status, jiffies-submit_jiffies,
+ jiffies-last_time, atomic_read(&g_pending_urbs));
+ last_time = jiffies;
+ oz_event_log(OZ_EVT_URB_DONE, 0, 0, urb, status);
+ usb_hcd_giveback_urb(hcd, urb, status);
+ }
+ spin_lock(&g_tasklet_lock);
+ spin_unlock_irqrestore(&g_tasklet_lock, irq_state);
+ if (cancel_urbl)
+ oz_free_urb_link(cancel_urbl);
+}
+/*------------------------------------------------------------------------------
+ * Deallocates an endpoint including deallocating any associated stream and
+ * returning any queued urbs to the core.
+ * Context: softirq
+ */
+static void oz_ep_free(struct oz_port *port, struct oz_endpoint *ep)
+{
+ oz_trace("oz_ep_free()\n");
+ if (port) {
+ struct list_head list;
+ struct oz_hcd *ozhcd = port->ozhcd;
+ INIT_LIST_HEAD(&list);
+ if (ep->flags & OZ_F_EP_HAVE_STREAM)
+ oz_usb_stream_delete(port->hpd, ep->ep_num);
+ /* Transfer URBs to the orphanage while we hold the lock. */
+ spin_lock_bh(&ozhcd->hcd_lock);
+ /* Note: this works even if ep->urb_list is empty.*/
+ list_replace_init(&ep->urb_list, &list);
+ /* Put the URBs in the orphanage. */
+ list_splice_tail(&list, &ozhcd->orphanage);
+ spin_unlock_bh(&ozhcd->hcd_lock);
+ }
+ oz_trace("Freeing endpoint memory\n");
+ kfree(ep);
+}
+/*------------------------------------------------------------------------------
+ * Context: softirq
+ */
+static int oz_enqueue_ep_urb(struct oz_port *port, u8 ep_addr, int in_dir,
+ struct urb *urb, u8 req_id)
+{
+ struct oz_urb_link *urbl;
+ struct oz_endpoint *ep;
+ int err = 0;
+ if (ep_addr >= OZ_NB_ENDPOINTS) {
+ oz_trace("Invalid endpoint number in oz_enqueue_ep_urb().\n");
+ return -EINVAL;
+ }
+ urbl = oz_alloc_urb_link();
+ if (!urbl)
+ return -ENOMEM;
+ urbl->submit_jiffies = jiffies;
+ urbl->urb = urb;
+ urbl->req_id = req_id;
+ urbl->ep_num = ep_addr;
+ /* Hold lock while we insert the URB into the list within the
+ * endpoint structure.
+ */
+ spin_lock_bh(&port->ozhcd->hcd_lock);
+ /* If the urb has been unlinked while out of any list then
+ * complete it now.
+ */
+ if (urb->unlinked) {
+ spin_unlock_bh(&port->ozhcd->hcd_lock);
+ oz_trace("urb %p unlinked so complete immediately\n", urb);
+ oz_complete_urb(port->ozhcd->hcd, urb, 0, 0);
+ oz_free_urb_link(urbl);
+ return 0;
+ }
+ if (in_dir)
+ ep = port->in_ep[ep_addr];
+ else
+ ep = port->out_ep[ep_addr];
+ if (ep && port->hpd) {
+ list_add_tail(&urbl->link, &ep->urb_list);
+ if (!in_dir && ep_addr && (ep->credit < 0)) {
+ ep->last_jiffies = jiffies;
+ ep->credit = 0;
+ oz_event_log(OZ_EVT_EP_CREDIT, ep->ep_num,
+ 0, 0, ep->credit);
+ }
+ } else {
+ err = -EPIPE;
+ }
+ spin_unlock_bh(&port->ozhcd->hcd_lock);
+ if (err)
+ oz_free_urb_link(urbl);
+ return err;
+}
+/*------------------------------------------------------------------------------
+ * Removes an urb from the queue in the endpoint.
+ * Returns 0 if it is found and -EIDRM otherwise.
+ * Context: softirq
+ */
+static int oz_dequeue_ep_urb(struct oz_port *port, u8 ep_addr, int in_dir,
+ struct urb *urb)
+{
+ struct oz_urb_link *urbl = 0;
+ struct oz_endpoint *ep;
+ spin_lock_bh(&port->ozhcd->hcd_lock);
+ if (in_dir)
+ ep = port->in_ep[ep_addr];
+ else
+ ep = port->out_ep[ep_addr];
+ if (ep) {
+ struct list_head *e;
+ list_for_each(e, &ep->urb_list) {
+ urbl = container_of(e, struct oz_urb_link, link);
+ if (urbl->urb == urb) {
+ list_del_init(e);
+ break;
+ }
+ urbl = 0;
+ }
+ }
+ spin_unlock_bh(&port->ozhcd->hcd_lock);
+ if (urbl)
+ oz_free_urb_link(urbl);
+ return urbl ? 0 : -EIDRM;
+}
+/*------------------------------------------------------------------------------
+ * Finds an urb given its request id.
+ * Context: softirq
+ */
+static struct urb *oz_find_urb_by_id(struct oz_port *port, int ep_ix,
+ u8 req_id)
+{
+ struct oz_hcd *ozhcd = port->ozhcd;
+ struct urb *urb = 0;
+ struct oz_urb_link *urbl = 0;
+ struct oz_endpoint *ep;
+
+ spin_lock_bh(&ozhcd->hcd_lock);
+ ep = port->out_ep[ep_ix];
+ if (ep) {
+ struct list_head *e;
+ list_for_each(e, &ep->urb_list) {
+ urbl = container_of(e, struct oz_urb_link, link);
+ if (urbl->req_id == req_id) {
+ urb = urbl->urb;
+ list_del_init(e);
+ break;
+ }
+ }
+ }
+ spin_unlock_bh(&ozhcd->hcd_lock);
+ /* If urb is non-zero then we we must have an urb link to delete.
+ */
+ if (urb)
+ oz_free_urb_link(urbl);
+ return urb;
+}
+/*------------------------------------------------------------------------------
+ * Pre-condition: Port lock must be held.
+ * Context: softirq
+ */
+static void oz_acquire_port(struct oz_port *port, void *hpd)
+{
+ INIT_LIST_HEAD(&port->isoc_out_ep);
+ INIT_LIST_HEAD(&port->isoc_in_ep);
+ port->flags |= OZ_PORT_F_PRESENT | OZ_PORT_F_CHANGED;
+ port->status |= USB_PORT_STAT_CONNECTION |
+ (USB_PORT_STAT_C_CONNECTION << 16);
+ oz_usb_get(hpd);
+ port->hpd = hpd;
+}
+/*------------------------------------------------------------------------------
+ * Context: softirq
+ */
+static struct oz_hcd *oz_hcd_claim(void)
+{
+ struct oz_hcd *ozhcd;
+ spin_lock_bh(&g_hcdlock);
+ ozhcd = g_ozhcd;
+ if (ozhcd)
+ usb_get_hcd(ozhcd->hcd);
+ spin_unlock_bh(&g_hcdlock);
+ return ozhcd;
+}
+/*------------------------------------------------------------------------------
+ * Context: softirq
+ */
+static inline void oz_hcd_put(struct oz_hcd *ozhcd)
+{
+ if (ozhcd)
+ usb_put_hcd(ozhcd->hcd);
+}
+/*------------------------------------------------------------------------------
+ * This is called by the protocol handler to notify that a PD has arrived.
+ * We allocate a port to associate with the PD and create a structure for
+ * endpoint 0. This port is made the connection port.
+ * In the event that one of the other port is already a connection port then
+ * we fail.
+ * TODO We should be able to do better than fail and should be able remember
+ * that this port needs configuring and make it the connection port once the
+ * current connection port has been assigned an address. Collisions here are
+ * probably very rare indeed.
+ * Context: softirq
+ */
+void *oz_hcd_pd_arrived(void *hpd)
+{
+ int i;
+ void *hport = 0;
+ struct oz_hcd *ozhcd = 0;
+ struct oz_endpoint *ep;
+ oz_trace("oz_hcd_pd_arrived()\n");
+ ozhcd = oz_hcd_claim();
+ if (ozhcd == 0)
+ return 0;
+ /* Allocate an endpoint object in advance (before holding hcd lock) to
+ * use for out endpoint 0.
+ */
+ ep = oz_ep_alloc(GFP_ATOMIC, 0);
+ spin_lock_bh(&ozhcd->hcd_lock);
+ if (ozhcd->conn_port >= 0) {
+ spin_unlock_bh(&ozhcd->hcd_lock);
+ oz_trace("conn_port >= 0\n");
+ goto out;
+ }
+ for (i = 0; i < OZ_NB_PORTS; i++) {
+ struct oz_port *port = &ozhcd->ports[i];
+ spin_lock(&port->port_lock);
+ if ((port->flags & OZ_PORT_F_PRESENT) == 0) {
+ oz_acquire_port(port, hpd);
+ spin_unlock(&port->port_lock);
+ break;
+ }
+ spin_unlock(&port->port_lock);
+ }
+ if (i < OZ_NB_PORTS) {
+ oz_trace("Setting conn_port = %d\n", i);
+ ozhcd->conn_port = i;
+ /* Attach out endpoint 0.
+ */
+ ozhcd->ports[i].out_ep[0] = ep;
+ ep = 0;
+ hport = &ozhcd->ports[i];
+ spin_unlock_bh(&ozhcd->hcd_lock);
+ if (ozhcd->flags & OZ_HDC_F_SUSPENDED) {
+ oz_trace("Resuming root hub\n");
+ usb_hcd_resume_root_hub(ozhcd->hcd);
+ }
+ usb_hcd_poll_rh_status(ozhcd->hcd);
+ } else {
+ spin_unlock_bh(&ozhcd->hcd_lock);
+ }
+out:
+ if (ep) /* ep is non-null if not used. */
+ oz_ep_free(0, ep);
+ oz_hcd_put(ozhcd);
+ return hport;
+}
+/*------------------------------------------------------------------------------
+ * This is called by the protocol handler to notify that the PD has gone away.
+ * We need to deallocate all resources and then request that the root hub is
+ * polled. We release the reference we hold on the PD.
+ * Context: softirq
+ */
+void oz_hcd_pd_departed(void *hport)
+{
+ struct oz_port *port = (struct oz_port *)hport;
+ struct oz_hcd *ozhcd;
+ void *hpd;
+ struct oz_endpoint *ep = 0;
+
+ oz_trace("oz_hcd_pd_departed()\n");
+ if (port == 0) {
+ oz_trace("oz_hcd_pd_departed() port = 0\n");
+ return;
+ }
+ ozhcd = port->ozhcd;
+ if (ozhcd == 0)
+ return;
+ /* Check if this is the connection port - if so clear it.
+ */
+ spin_lock_bh(&ozhcd->hcd_lock);
+ if ((ozhcd->conn_port >= 0) &&
+ (port == &ozhcd->ports[ozhcd->conn_port])) {
+ oz_trace("Clearing conn_port\n");
+ ozhcd->conn_port = -1;
+ }
+ spin_lock(&port->port_lock);
+ port->flags |= OZ_PORT_F_DYING;
+ spin_unlock(&port->port_lock);
+ spin_unlock_bh(&ozhcd->hcd_lock);
+
+ oz_clean_endpoints_for_config(ozhcd->hcd, port);
+ spin_lock_bh(&port->port_lock);
+ hpd = port->hpd;
+ port->hpd = 0;
+ port->bus_addr = 0xff;
+ port->flags &= ~(OZ_PORT_F_PRESENT | OZ_PORT_F_DYING);
+ port->flags |= OZ_PORT_F_CHANGED;
+ port->status &= ~USB_PORT_STAT_CONNECTION;
+ port->status |= (USB_PORT_STAT_C_CONNECTION << 16);
+ /* If there is an endpont 0 then clear the pointer while we hold
+ * the spinlock be we deallocate it after releasing the lock.
+ */
+ if (port->out_ep[0]) {
+ ep = port->out_ep[0];
+ port->out_ep[0] = 0;
+ }
+ spin_unlock_bh(&port->port_lock);
+ if (ep)
+ oz_ep_free(port, ep);
+ usb_hcd_poll_rh_status(ozhcd->hcd);
+ oz_usb_put(hpd);
+}
+/*------------------------------------------------------------------------------
+ * Context: softirq
+ */
+void oz_hcd_pd_reset(void *hpd, void *hport)
+{
+ /* Cleanup the current configuration and report reset to the core.
+ */
+ struct oz_port *port = (struct oz_port *)hport;
+ struct oz_hcd *ozhcd = port->ozhcd;
+ oz_trace("PD Reset\n");
+ spin_lock_bh(&port->port_lock);
+ port->flags |= OZ_PORT_F_CHANGED;
+ port->status |= USB_PORT_STAT_RESET;
+ port->status |= (USB_PORT_STAT_C_RESET << 16);
+ spin_unlock_bh(&port->port_lock);
+ oz_clean_endpoints_for_config(ozhcd->hcd, port);
+ usb_hcd_poll_rh_status(ozhcd->hcd);
+}
+/*------------------------------------------------------------------------------
+ * Context: softirq
+ */
+void oz_hcd_get_desc_cnf(void *hport, u8 req_id, int status, u8 *desc,
+ int length, int offset, int total_size)
+{
+ struct oz_port *port = (struct oz_port *)hport;
+ struct urb *urb;
+ int err = 0;
+
+ oz_event_log(OZ_EVT_CTRL_CNF, 0, req_id, 0, status);
+ oz_trace("oz_hcd_get_desc_cnf length = %d offs = %d tot_size = %d\n",
+ length, offset, total_size);
+ urb = oz_find_urb_by_id(port, 0, req_id);
+ if (!urb)
+ return;
+ if (status == 0) {
+ int copy_len;
+ int required_size = urb->transfer_buffer_length;
+ if (required_size > total_size)
+ required_size = total_size;
+ copy_len = required_size-offset;
+ if (length <= copy_len)
+ copy_len = length;
+ memcpy(urb->transfer_buffer+offset, desc, copy_len);
+ offset += copy_len;
+ if (offset < required_size) {
+ struct usb_ctrlrequest *setup =
+ (struct usb_ctrlrequest *)urb->setup_packet;
+ unsigned wvalue = le16_to_cpu(setup->wValue);
+ if (oz_enqueue_ep_urb(port, 0, 0, urb, req_id))
+ err = -ENOMEM;
+ else if (oz_usb_get_desc_req(port->hpd, req_id,
+ setup->bRequestType, (u8)(wvalue>>8),
+ (u8)wvalue, setup->wIndex, offset,
+ required_size-offset)) {
+ oz_dequeue_ep_urb(port, 0, 0, urb);
+ err = -ENOMEM;
+ }
+ if (err == 0)
+ return;
+ }
+ }
+ urb->actual_length = total_size;
+ oz_complete_urb(port->ozhcd->hcd, urb, 0, 0);
+}
+/*------------------------------------------------------------------------------
+ * Context: softirq
+ */
+#ifdef WANT_TRACE
+static void oz_display_conf_type(u8 t)
+{
+ switch (t) {
+ case USB_REQ_GET_STATUS:
+ oz_trace("USB_REQ_GET_STATUS - cnf\n");
+ break;
+ case USB_REQ_CLEAR_FEATURE:
+ oz_trace("USB_REQ_CLEAR_FEATURE - cnf\n");
+ break;
+ case USB_REQ_SET_FEATURE:
+ oz_trace("USB_REQ_SET_FEATURE - cnf\n");
+ break;
+ case USB_REQ_SET_ADDRESS:
+ oz_trace("USB_REQ_SET_ADDRESS - cnf\n");
+ break;
+ case USB_REQ_GET_DESCRIPTOR:
+ oz_trace("USB_REQ_GET_DESCRIPTOR - cnf\n");
+ break;
+ case USB_REQ_SET_DESCRIPTOR:
+ oz_trace("USB_REQ_SET_DESCRIPTOR - cnf\n");
+ break;
+ case USB_REQ_GET_CONFIGURATION:
+ oz_trace("USB_REQ_GET_CONFIGURATION - cnf\n");
+ break;
+ case USB_REQ_SET_CONFIGURATION:
+ oz_trace("USB_REQ_SET_CONFIGURATION - cnf\n");
+ break;
+ case USB_REQ_GET_INTERFACE:
+ oz_trace("USB_REQ_GET_INTERFACE - cnf\n");
+ break;
+ case USB_REQ_SET_INTERFACE:
+ oz_trace("USB_REQ_SET_INTERFACE - cnf\n");
+ break;
+ case USB_REQ_SYNCH_FRAME:
+ oz_trace("USB_REQ_SYNCH_FRAME - cnf\n");
+ break;
+ }
+}
+#else
+#define oz_display_conf_type(__x)
+#endif /* WANT_TRACE */
+/*------------------------------------------------------------------------------
+ * Context: softirq
+ */
+static void oz_hcd_complete_set_config(struct oz_port *port, struct urb *urb,
+ u8 rcode, u8 config_num)
+{
+ int rc = 0;
+ struct usb_hcd *hcd = port->ozhcd->hcd;
+ if (rcode == 0) {
+ port->config_num = config_num;
+ oz_clean_endpoints_for_config(hcd, port);
+ if (oz_build_endpoints_for_config(hcd, port,
+ &urb->dev->config[port->config_num-1], GFP_ATOMIC)) {
+ rc = -ENOMEM;
+ }
+ } else {
+ rc = -ENOMEM;
+ }
+ oz_complete_urb(hcd, urb, rc, 0);
+}
+/*------------------------------------------------------------------------------
+ * Context: softirq
+ */
+static void oz_hcd_complete_set_interface(struct oz_port *port, struct urb *urb,
+ u8 rcode, u8 if_num, u8 alt)
+{
+ struct usb_hcd *hcd = port->ozhcd->hcd;
+ int rc = 0;
+ if (rcode == 0) {
+ struct usb_host_config *config;
+ struct usb_host_interface *intf;
+ oz_trace("Set interface %d alt %d\n", if_num, alt);
+ oz_clean_endpoints_for_interface(hcd, port, if_num);
+ config = &urb->dev->config[port->config_num-1];
+ intf = &config->intf_cache[if_num]->altsetting[alt];
+ if (oz_build_endpoints_for_interface(hcd, port, intf,
+ GFP_ATOMIC))
+ rc = -ENOMEM;
+ else
+ port->iface[if_num].alt = alt;
+ } else {
+ rc = -ENOMEM;
+ }
+ oz_complete_urb(hcd, urb, rc, 0);
+}
+/*------------------------------------------------------------------------------
+ * Context: softirq
+ */
+void oz_hcd_control_cnf(void *hport, u8 req_id, u8 rcode, u8 *data,
+ int data_len)
+{
+ struct oz_port *port = (struct oz_port *)hport;
+ struct urb *urb;
+ struct usb_ctrlrequest *setup;
+ struct usb_hcd *hcd = port->ozhcd->hcd;
+ unsigned windex;
+ unsigned wvalue;
+
+ oz_event_log(OZ_EVT_CTRL_CNF, 0, req_id, 0, rcode);
+ oz_trace("oz_hcd_control_cnf rcode=%u len=%d\n", rcode, data_len);
+ urb = oz_find_urb_by_id(port, 0, req_id);
+ if (!urb) {
+ oz_trace("URB not found\n");
+ return;
+ }
+ setup = (struct usb_ctrlrequest *)urb->setup_packet;
+ windex = le16_to_cpu(setup->wIndex);
+ wvalue = le16_to_cpu(setup->wValue);
+ if ((setup->bRequestType & USB_TYPE_MASK) == USB_TYPE_STANDARD) {
+ /* Standard requests */
+ oz_display_conf_type(setup->bRequest);
+ switch (setup->bRequest) {
+ case USB_REQ_SET_CONFIGURATION:
+ oz_hcd_complete_set_config(port, urb, rcode,
+ (u8)wvalue);
+ break;
+ case USB_REQ_SET_INTERFACE:
+ oz_hcd_complete_set_interface(port, urb, rcode,
+ (u8)windex, (u8)wvalue);
+ break;
+ default:
+ oz_complete_urb(hcd, urb, 0, 0);
+ }
+
+ } else {
+ int copy_len;
+ oz_trace("VENDOR-CLASS - cnf\n");
+ if (data_len <= urb->transfer_buffer_length)
+ copy_len = data_len;
+ else
+ copy_len = urb->transfer_buffer_length;
+ if (copy_len)
+ memcpy(urb->transfer_buffer, data, copy_len);
+ urb->actual_length = copy_len;
+ oz_complete_urb(hcd, urb, 0, 0);
+ }
+}
+/*------------------------------------------------------------------------------
+ * Context: softirq-serialized
+ */
+static int oz_hcd_buffer_data(struct oz_endpoint *ep, u8 *data, int data_len)
+{
+ int space;
+ int copy_len;
+ if (!ep->buffer)
+ return -1;
+ space = ep->out_ix-ep->in_ix-1;
+ if (space < 0)
+ space += ep->buffer_size;
+ if (space < (data_len+1)) {
+ oz_trace("Buffer full\n");
+ return -1;
+ }
+ ep->buffer[ep->in_ix] = (u8)data_len;
+ if (++ep->in_ix == ep->buffer_size)
+ ep->in_ix = 0;
+ copy_len = ep->buffer_size - ep->in_ix;
+ if (copy_len > data_len)
+ copy_len = data_len;
+ memcpy(&ep->buffer[ep->in_ix], data, copy_len);
+
+ if (copy_len < data_len) {
+ memcpy(ep->buffer, data+copy_len, data_len-copy_len);
+ ep->in_ix = data_len-copy_len;
+ } else {
+ ep->in_ix += copy_len;
+ }
+ if (ep->in_ix == ep->buffer_size)
+ ep->in_ix = 0;
+ ep->buffered_units++;
+ return 0;
+}
+/*------------------------------------------------------------------------------
+ * Context: softirq-serialized
+ */
+void oz_hcd_data_ind(void *hport, u8 endpoint, u8 *data, int data_len)
+{
+ struct oz_port *port = (struct oz_port *)hport;
+ struct oz_endpoint *ep;
+ struct oz_hcd *ozhcd = port->ozhcd;
+ spin_lock_bh(&ozhcd->hcd_lock);
+ ep = port->in_ep[endpoint & USB_ENDPOINT_NUMBER_MASK];
+ if (ep == 0)
+ goto done;
+ switch (ep->attrib & USB_ENDPOINT_XFERTYPE_MASK) {
+ case USB_ENDPOINT_XFER_INT:
+ case USB_ENDPOINT_XFER_BULK:
+ if (!list_empty(&ep->urb_list)) {
+ struct oz_urb_link *urbl =
+ list_first_entry(&ep->urb_list,
+ struct oz_urb_link, link);
+ struct urb *urb;
+ int copy_len;
+ list_del_init(&urbl->link);
+ spin_unlock_bh(&ozhcd->hcd_lock);
+ urb = urbl->urb;
+ oz_free_urb_link(urbl);
+ if (data_len <= urb->transfer_buffer_length)
+ copy_len = data_len;
+ else
+ copy_len = urb->transfer_buffer_length;
+ memcpy(urb->transfer_buffer, data, copy_len);
+ urb->actual_length = copy_len;
+ oz_complete_urb(port->ozhcd->hcd, urb, 0, 0);
+ return;
+ }
+ break;
+ case USB_ENDPOINT_XFER_ISOC:
+ oz_hcd_buffer_data(ep, data, data_len);
+ break;
+ }
+done:
+ spin_unlock_bh(&ozhcd->hcd_lock);
+}
+/*------------------------------------------------------------------------------
+ * Context: unknown
+ */
+static inline int oz_usb_get_frame_number(void)
+{
+ return jiffies_to_msecs(get_jiffies_64());
+}
+/*------------------------------------------------------------------------------
+ * Context: softirq
+ */
+int oz_hcd_heartbeat(void *hport)
+{
+ int rc = 0;
+ struct oz_port *port = (struct oz_port *)hport;
+ struct oz_hcd *ozhcd = port->ozhcd;
+ struct oz_urb_link *urbl;
+ struct list_head xfr_list;
+ struct list_head *e;
+ struct list_head *n;
+ struct urb *urb;
+ struct oz_endpoint *ep;
+ unsigned long now = jiffies;
+ INIT_LIST_HEAD(&xfr_list);
+ /* Check the OUT isoc endpoints to see if any URB data can be sent.
+ */
+ spin_lock_bh(&ozhcd->hcd_lock);
+ list_for_each(e, &port->isoc_out_ep) {
+ ep = ep_from_link(e);
+ if (ep->credit < 0)
+ continue;
+ ep->credit += (now - ep->last_jiffies);
+ if (ep->credit > ep->credit_ceiling)
+ ep->credit = ep->credit_ceiling;
+ oz_event_log(OZ_EVT_EP_CREDIT, ep->ep_num, 0, 0, ep->credit);
+ ep->last_jiffies = now;
+ while (ep->credit && !list_empty(&ep->urb_list)) {
+ urbl = list_first_entry(&ep->urb_list,
+ struct oz_urb_link, link);
+ urb = urbl->urb;
+ if (ep->credit < urb->number_of_packets)
+ break;
+ ep->credit -= urb->number_of_packets;
+ oz_event_log(OZ_EVT_EP_CREDIT, ep->ep_num, 0, 0,
+ ep->credit);
+ list_del(&urbl->link);
+ list_add_tail(&urbl->link, &xfr_list);
+ }
+ }
+ spin_unlock_bh(&ozhcd->hcd_lock);
+ /* Send to PD and complete URBs.
+ */
+ list_for_each_safe(e, n, &xfr_list) {
+ unsigned long t;
+ urbl = container_of(e, struct oz_urb_link, link);
+ urb = urbl->urb;
+ t = urbl->submit_jiffies;
+ list_del_init(e);
+ urb->error_count = 0;
+ urb->start_frame = oz_usb_get_frame_number();
+ oz_usb_send_isoc(port->hpd, urbl->ep_num, urb);
+ oz_free_urb_link(urbl);
+ oz_complete_urb(port->ozhcd->hcd, urb, 0, t);
+ }
+ /* Check the IN isoc endpoints to see if any URBs can be completed.
+ */
+ spin_lock_bh(&ozhcd->hcd_lock);
+ list_for_each(e, &port->isoc_in_ep) {
+ struct oz_endpoint *ep = ep_from_link(e);
+ if (ep->flags & OZ_F_EP_BUFFERING) {
+ if (ep->buffered_units * OZ_IN_BUFFERING_UNITS) {
+ ep->flags &= ~OZ_F_EP_BUFFERING;
+ ep->credit = 0;
+ oz_event_log(OZ_EVT_EP_CREDIT,
+ ep->ep_num | USB_DIR_IN,
+ 0, 0, ep->credit);
+ ep->last_jiffies = now;
+ ep->start_frame = 0;
+ oz_event_log(OZ_EVT_EP_BUFFERING,
+ ep->ep_num | USB_DIR_IN, 0, 0, 0);
+ }
+ continue;
+ }
+ ep->credit += (now - ep->last_jiffies);
+ oz_event_log(OZ_EVT_EP_CREDIT, ep->ep_num | USB_DIR_IN,
+ 0, 0, ep->credit);
+ ep->last_jiffies = now;
+ while (!list_empty(&ep->urb_list)) {
+ struct oz_urb_link *urbl =
+ list_first_entry(&ep->urb_list,
+ struct oz_urb_link, link);
+ struct urb *urb = urbl->urb;
+ int len = 0;
+ int copy_len;
+ int i;
+ if (ep->credit < urb->number_of_packets)
+ break;
+ if (ep->buffered_units < urb->number_of_packets)
+ break;
+ urb->actual_length = 0;
+ for (i = 0; i < urb->number_of_packets; i++) {
+ len = ep->buffer[ep->out_ix];
+ if (++ep->out_ix == ep->buffer_size)
+ ep->out_ix = 0;
+ copy_len = ep->buffer_size - ep->out_ix;
+ if (copy_len > len)
+ copy_len = len;
+ memcpy(urb->transfer_buffer,
+ &ep->buffer[ep->out_ix], copy_len);
+ if (copy_len < len) {
+ memcpy(urb->transfer_buffer+copy_len,
+ ep->buffer, len-copy_len);
+ ep->out_ix = len-copy_len;
+ } else
+ ep->out_ix += copy_len;
+ if (ep->out_ix == ep->buffer_size)
+ ep->out_ix = 0;
+ urb->iso_frame_desc[i].offset =
+ urb->actual_length;
+ urb->actual_length += len;
+ urb->iso_frame_desc[i].actual_length = len;
+ urb->iso_frame_desc[i].status = 0;
+ }
+ ep->buffered_units -= urb->number_of_packets;
+ urb->error_count = 0;
+ urb->start_frame = ep->start_frame;
+ ep->start_frame += urb->number_of_packets;
+ list_del(&urbl->link);
+ list_add_tail(&urbl->link, &xfr_list);
+ ep->credit -= urb->number_of_packets;
+ oz_event_log(OZ_EVT_EP_CREDIT, ep->ep_num | USB_DIR_IN,
+ 0, 0, ep->credit);
+ }
+ }
+ if (!list_empty(&port->isoc_out_ep) || !list_empty(&port->isoc_in_ep))
+ rc = 1;
+ spin_unlock_bh(&ozhcd->hcd_lock);
+ /* Complete the filled URBs.
+ */
+ list_for_each_safe(e, n, &xfr_list) {
+ urbl = container_of(e, struct oz_urb_link, link);
+ urb = urbl->urb;
+ list_del_init(e);
+ oz_free_urb_link(urbl);
+ oz_complete_urb(port->ozhcd->hcd, urb, 0, 0);
+ }
+ /* Check if there are any ep0 requests that have timed out.
+ * If so resent to PD.
+ */
+ ep = port->out_ep[0];
+ if (ep) {
+ struct list_head *e;
+ struct list_head *n;
+ spin_lock_bh(&ozhcd->hcd_lock);
+ list_for_each_safe(e, n, &ep->urb_list) {
+ urbl = container_of(e, struct oz_urb_link, link);
+ if (time_after(now, urbl->submit_jiffies+HZ/2)) {
+ oz_trace("%ld: Request 0x%p timeout\n",
+ now, urbl->urb);
+ urbl->submit_jiffies = now;
+ list_del(e);
+ list_add_tail(e, &xfr_list);
+ }
+ }
+ if (!list_empty(&ep->urb_list))
+ rc = 1;
+ spin_unlock_bh(&ozhcd->hcd_lock);
+ e = xfr_list.next;
+ while (e != &xfr_list) {
+ urbl = container_of(e, struct oz_urb_link, link);
+ e = e->next;
+ oz_trace("Resending request to PD.\n");
+ oz_process_ep0_urb(ozhcd, urbl->urb, GFP_ATOMIC);
+ oz_free_urb_link(urbl);
+ }
+ }
+ return rc;
+}
+/*------------------------------------------------------------------------------
+ * Context: softirq
+ */
+static int oz_build_endpoints_for_interface(struct usb_hcd *hcd,
+ struct oz_port *port,
+ struct usb_host_interface *intf, gfp_t mem_flags)
+{
+ struct oz_hcd *ozhcd = port->ozhcd;
+ int i;
+ int if_ix = intf->desc.bInterfaceNumber;
+ int request_heartbeat = 0;
+ oz_trace("interface[%d] = %p\n", if_ix, intf);
+ for (i = 0; i < intf->desc.bNumEndpoints; i++) {
+ struct usb_host_endpoint *hep = &intf->endpoint[i];
+ u8 ep_addr = hep->desc.bEndpointAddress;
+ u8 ep_num = ep_addr & USB_ENDPOINT_NUMBER_MASK;
+ struct oz_endpoint *ep;
+ int buffer_size = 0;
+
+ oz_trace("%d bEndpointAddress = %x\n", i, ep_addr);
+ if ((ep_addr & USB_ENDPOINT_DIR_MASK) &&
+ ((hep->desc.bmAttributes & USB_ENDPOINT_XFERTYPE_MASK)
+ == USB_ENDPOINT_XFER_ISOC)) {
+ buffer_size = 24*1024;
+ }
+
+ ep = oz_ep_alloc(mem_flags, buffer_size);
+ if (!ep) {
+ oz_clean_endpoints_for_interface(hcd, port, if_ix);
+ return -ENOMEM;
+ }
+ ep->attrib = hep->desc.bmAttributes;
+ ep->ep_num = ep_num;
+ if ((ep->attrib & USB_ENDPOINT_XFERTYPE_MASK)
+ == USB_ENDPOINT_XFER_ISOC) {
+ oz_trace("wMaxPacketSize = %d\n",
+ hep->desc.wMaxPacketSize);
+ ep->credit_ceiling = 200;
+ if (ep_addr & USB_ENDPOINT_DIR_MASK) {
+ ep->flags |= OZ_F_EP_BUFFERING;
+ oz_event_log(OZ_EVT_EP_BUFFERING,
+ ep->ep_num | USB_DIR_IN, 1, 0, 0);
+ } else {
+ ep->flags |= OZ_F_EP_HAVE_STREAM;
+ if (oz_usb_stream_create(port->hpd, ep_num))
+ ep->flags &= ~OZ_F_EP_HAVE_STREAM;
+ }
+ }
+ spin_lock_bh(&ozhcd->hcd_lock);
+ if (ep_addr & USB_ENDPOINT_DIR_MASK) {
+ port->in_ep[ep_num] = ep;
+ port->iface[if_ix].ep_mask |=
+ (1<<(ep_num+OZ_NB_ENDPOINTS));
+ if ((ep->attrib & USB_ENDPOINT_XFERTYPE_MASK)
+ == USB_ENDPOINT_XFER_ISOC) {
+ list_add_tail(&ep->link, &port->isoc_in_ep);
+ request_heartbeat = 1;
+ }
+ } else {
+ port->out_ep[ep_num] = ep;
+ port->iface[if_ix].ep_mask |= (1<<ep_num);
+ if ((ep->attrib & USB_ENDPOINT_XFERTYPE_MASK)
+ == USB_ENDPOINT_XFER_ISOC) {
+ list_add_tail(&ep->link, &port->isoc_out_ep);
+ request_heartbeat = 1;
+ }
+ }
+ spin_unlock_bh(&ozhcd->hcd_lock);
+ if (request_heartbeat && port->hpd)
+ oz_usb_request_heartbeat(port->hpd);
+ }
+ return 0;
+}
+/*------------------------------------------------------------------------------
+ * Context: softirq
+ */
+static void oz_clean_endpoints_for_interface(struct usb_hcd *hcd,
+ struct oz_port *port, int if_ix)
+{
+ struct oz_hcd *ozhcd = port->ozhcd;
+ unsigned mask;
+ int i;
+ struct list_head ep_list;
+
+ oz_trace("Deleting endpoints for interface %d\n", if_ix);
+ if (if_ix >= port->num_iface)
+ return;
+ INIT_LIST_HEAD(&ep_list);
+ spin_lock_bh(&ozhcd->hcd_lock);
+ mask = port->iface[if_ix].ep_mask;
+ port->iface[if_ix].ep_mask = 0;
+ for (i = 0; i < OZ_NB_ENDPOINTS; i++) {
+ struct list_head *e;
+ /* Gather OUT endpoints.
+ */
+ if ((mask & (1<<i)) && port->out_ep[i]) {
+ e = &port->out_ep[i]->link;
+ port->out_ep[i] = 0;
+ /* Remove from isoc list if present.
+ */
+ list_del(e);
+ list_add_tail(e, &ep_list);
+ }
+ /* Gather IN endpoints.
+ */
+ if ((mask & (1<<(i+OZ_NB_ENDPOINTS))) && port->in_ep[i]) {
+ e = &port->in_ep[i]->link;
+ port->in_ep[i] = 0;
+ list_del(e);
+ list_add_tail(e, &ep_list);
+ }
+ }
+ spin_unlock_bh(&ozhcd->hcd_lock);
+ while (!list_empty(&ep_list)) {
+ struct oz_endpoint *ep =
+ list_first_entry(&ep_list, struct oz_endpoint, link);
+ list_del_init(&ep->link);
+ oz_ep_free(port, ep);
+ }
+}
+/*------------------------------------------------------------------------------
+ * Context: softirq
+ */
+static int oz_build_endpoints_for_config(struct usb_hcd *hcd,
+ struct oz_port *port, struct usb_host_config *config,
+ gfp_t mem_flags)
+{
+ struct oz_hcd *ozhcd = port->ozhcd;
+ int i;
+ int num_iface = config->desc.bNumInterfaces;
+ if (num_iface) {
+ struct oz_interface *iface;
+
+ iface = kmalloc(num_iface*sizeof(struct oz_interface),
+ mem_flags | __GFP_ZERO);
+ if (!iface)
+ return -ENOMEM;
+ spin_lock_bh(&ozhcd->hcd_lock);
+ port->iface = iface;
+ port->num_iface = num_iface;
+ spin_unlock_bh(&ozhcd->hcd_lock);
+ }
+ for (i = 0; i < num_iface; i++) {
+ struct usb_host_interface *intf =
+ &config->intf_cache[i]->altsetting[0];
+ if (oz_build_endpoints_for_interface(hcd, port, intf,
+ mem_flags))
+ goto fail;
+ }
+ return 0;
+fail:
+ oz_clean_endpoints_for_config(hcd, port);
+ return -1;
+}
+/*------------------------------------------------------------------------------
+ * Context: softirq
+ */
+static void oz_clean_endpoints_for_config(struct usb_hcd *hcd,
+ struct oz_port *port)
+{
+ struct oz_hcd *ozhcd = port->ozhcd;
+ int i;
+ oz_trace("Deleting endpoints for configuration.\n");
+ for (i = 0; i < port->num_iface; i++)
+ oz_clean_endpoints_for_interface(hcd, port, i);
+ spin_lock_bh(&ozhcd->hcd_lock);
+ if (port->iface) {
+ oz_trace("Freeing interfaces object.\n");
+ kfree(port->iface);
+ port->iface = 0;
+ }
+ port->num_iface = 0;
+ spin_unlock_bh(&ozhcd->hcd_lock);
+}
+/*------------------------------------------------------------------------------
+ * Context: tasklet
+ */
+static void *oz_claim_hpd(struct oz_port *port)
+{
+ void *hpd = 0;
+ struct oz_hcd *ozhcd = port->ozhcd;
+ spin_lock_bh(&ozhcd->hcd_lock);
+ hpd = port->hpd;
+ if (hpd)
+ oz_usb_get(hpd);
+ spin_unlock_bh(&ozhcd->hcd_lock);
+ return hpd;
+}
+/*------------------------------------------------------------------------------
+ * Context: tasklet
+ */
+static void oz_process_ep0_urb(struct oz_hcd *ozhcd, struct urb *urb,
+ gfp_t mem_flags)
+{
+ struct usb_ctrlrequest *setup;
+ unsigned windex;
+ unsigned wvalue;
+ unsigned wlength;
+ void *hpd = 0;
+ u8 req_id;
+ int rc = 0;
+ unsigned complete = 0;
+
+ int port_ix = -1;
+ struct oz_port *port = 0;
+
+ oz_trace2(OZ_TRACE_URB, "%lu: oz_process_ep0_urb(%p)\n", jiffies, urb);
+ port_ix = oz_get_port_from_addr(ozhcd, urb->dev->devnum);
+ if (port_ix < 0) {
+ rc = -EPIPE;
+ goto out;
+ }
+ port = &ozhcd->ports[port_ix];
+ if (((port->flags & OZ_PORT_F_PRESENT) == 0)
+ || (port->flags & OZ_PORT_F_DYING)) {
+ oz_trace("Refusing URB port_ix = %d devnum = %d\n",
+ port_ix, urb->dev->devnum);
+ rc = -EPIPE;
+ goto out;
+ }
+ /* Store port in private context data.
+ */
+ urb->hcpriv = port;
+ setup = (struct usb_ctrlrequest *)urb->setup_packet;
+ windex = le16_to_cpu(setup->wIndex);
+ wvalue = le16_to_cpu(setup->wValue);
+ wlength = le16_to_cpu(setup->wLength);
+ oz_trace2(OZ_TRACE_CTRL_DETAIL, "bRequestType = %x\n",
+ setup->bRequestType);
+ oz_trace2(OZ_TRACE_CTRL_DETAIL, "bRequest = %x\n", setup->bRequest);
+ oz_trace2(OZ_TRACE_CTRL_DETAIL, "wValue = %x\n", wvalue);
+ oz_trace2(OZ_TRACE_CTRL_DETAIL, "wIndex = %x\n", windex);
+ oz_trace2(OZ_TRACE_CTRL_DETAIL, "wLength = %x\n", wlength);
+
+ req_id = port->next_req_id++;
+ hpd = oz_claim_hpd(port);
+ if (hpd == 0) {
+ oz_trace("Cannot claim port\n");
+ rc = -EPIPE;
+ goto out;
+ }
+
+ if ((setup->bRequestType & USB_TYPE_MASK) == USB_TYPE_STANDARD) {
+ /* Standard requests
+ */
+ switch (setup->bRequest) {
+ case USB_REQ_GET_DESCRIPTOR:
+ oz_trace("USB_REQ_GET_DESCRIPTOR - req\n");
+ break;
+ case USB_REQ_SET_ADDRESS:
+ oz_event_log(OZ_EVT_CTRL_LOCAL, setup->bRequest,
+ 0, 0, setup->bRequestType);
+ oz_trace("USB_REQ_SET_ADDRESS - req\n");
+ oz_trace("Port %d address is 0x%x\n", ozhcd->conn_port,
+ (u8)le16_to_cpu(setup->wValue));
+ spin_lock_bh(&ozhcd->hcd_lock);
+ if (ozhcd->conn_port >= 0) {
+ ozhcd->ports[ozhcd->conn_port].bus_addr =
+ (u8)le16_to_cpu(setup->wValue);
+ oz_trace("Clearing conn_port\n");
+ ozhcd->conn_port = -1;
+ }
+ spin_unlock_bh(&ozhcd->hcd_lock);
+ complete = 1;
+ break;
+ case USB_REQ_SET_CONFIGURATION:
+ oz_trace("USB_REQ_SET_CONFIGURATION - req\n");
+ break;
+ case USB_REQ_GET_CONFIGURATION:
+ /* We short curcuit this case and reply directly since
+ * we have the selected configuration number cached.
+ */
+ oz_event_log(OZ_EVT_CTRL_LOCAL, setup->bRequest, 0, 0,
+ setup->bRequestType);
+ oz_trace("USB_REQ_GET_CONFIGURATION - reply now\n");
+ if (urb->transfer_buffer_length >= 1) {
+ urb->actual_length = 1;
+ *((u8 *)urb->transfer_buffer) =
+ port->config_num;
+ complete = 1;
+ } else {
+ rc = -EPIPE;
+ }
+ break;
+ case USB_REQ_GET_INTERFACE:
+ /* We short curcuit this case and reply directly since
+ * we have the selected interface alternative cached.
+ */
+ oz_event_log(OZ_EVT_CTRL_LOCAL, setup->bRequest, 0, 0,
+ setup->bRequestType);
+ oz_trace("USB_REQ_GET_INTERFACE - reply now\n");
+ if (urb->transfer_buffer_length >= 1) {
+ urb->actual_length = 1;
+ *((u8 *)urb->transfer_buffer) =
+ port->iface[(u8)windex].alt;
+ oz_trace("interface = %d alt = %d\n",
+ windex, port->iface[(u8)windex].alt);
+ complete = 1;
+ } else {
+ rc = -EPIPE;
+ }
+ break;
+ case USB_REQ_SET_INTERFACE:
+ oz_trace("USB_REQ_SET_INTERFACE - req\n");
+ break;
+ }
+ }
+ if (!rc && !complete) {
+ int data_len = 0;
+ if ((setup->bRequestType & USB_DIR_IN) == 0)
+ data_len = wlength;
+ if (oz_usb_control_req(port->hpd, req_id, setup,
+ urb->transfer_buffer, data_len)) {
+ rc = -ENOMEM;
+ } else {
+ /* Note: we are queuing the request after we have
+ * submitted it to be tranmitted. If the request were
+ * to complete before we queued it then it would not
+ * be found in the queue. It seems impossible for
+ * this to happen but if it did the request would
+ * be resubmitted so the problem would hopefully
+ * resolve itself. Putting the request into the
+ * queue before it has been sent is worse since the
+ * urb could be cancelled while we are using it
+ * to build the request.
+ */
+ if (oz_enqueue_ep_urb(port, 0, 0, urb, req_id))
+ rc = -ENOMEM;
+ }
+ }
+ oz_usb_put(hpd);
+out:
+ if (rc || complete) {
+ oz_trace("Completing request locally\n");
+ oz_complete_urb(ozhcd->hcd, urb, rc, 0);
+ } else {
+ oz_usb_request_heartbeat(port->hpd);
+ }
+}
+/*------------------------------------------------------------------------------
+ * Context: tasklet
+ */
+static int oz_urb_process(struct oz_hcd *ozhcd, struct urb *urb)
+{
+ int rc = 0;
+ struct oz_port *port = urb->hcpriv;
+ u8 ep_addr;
+ /* When we are paranoid we keep a list of urbs which we check against
+ * before handing one back. This is just for debugging during
+ * development and should be turned off in the released driver.
+ */
+ oz_remember_urb(urb);
+ /* Check buffer is valid.
+ */
+ if (!urb->transfer_buffer && urb->transfer_buffer_length)
+ return -EINVAL;
+ /* Check if there is a device at the port - refuse if not.
+ */
+ if ((port->flags & OZ_PORT_F_PRESENT) == 0)
+ return -EPIPE;
+ ep_addr = usb_pipeendpoint(urb->pipe);
+ if (ep_addr) {
+ /* If the request is not for EP0 then queue it.
+ */
+ if (oz_enqueue_ep_urb(port, ep_addr, usb_pipein(urb->pipe),
+ urb, 0))
+ rc = -EPIPE;
+ } else {
+ oz_process_ep0_urb(ozhcd, urb, GFP_ATOMIC);
+ }
+ return rc;
+}
+/*------------------------------------------------------------------------------
+ * Context: tasklet
+ */
+static void oz_urb_process_tasklet(unsigned long unused)
+{
+ unsigned long irq_state;
+ struct urb *urb;
+ struct oz_hcd *ozhcd = oz_hcd_claim();
+ int rc = 0;
+ if (ozhcd == 0)
+ return;
+ /* This is called from a tasklet so is in softirq context but the urb
+ * list is filled from any context so we need to lock
+ * appropriately while removing urbs.
+ */
+ spin_lock_irqsave(&g_tasklet_lock, irq_state);
+ while (!list_empty(&ozhcd->urb_pending_list)) {
+ struct oz_urb_link *urbl =
+ list_first_entry(&ozhcd->urb_pending_list,
+ struct oz_urb_link, link);
+ list_del_init(&urbl->link);
+ spin_unlock_irqrestore(&g_tasklet_lock, irq_state);
+ urb = urbl->urb;
+ oz_free_urb_link(urbl);
+ rc = oz_urb_process(ozhcd, urb);
+ if (rc)
+ oz_complete_urb(ozhcd->hcd, urb, rc, 0);
+ spin_lock_irqsave(&g_tasklet_lock, irq_state);
+ }
+ spin_unlock_irqrestore(&g_tasklet_lock, irq_state);
+ oz_hcd_put(ozhcd);
+}
+/*------------------------------------------------------------------------------
+ * This function searches for the urb in any of the lists it could be in.
+ * If it is found it is removed from the list and completed. If the urb is
+ * being processed then it won't be in a list so won't be found. However, the
+ * call to usb_hcd_check_unlink_urb() will set the value of the unlinked field
+ * to a non-zero value. When an attempt is made to put the urb back in a list
+ * the unlinked field will be checked and the urb will then be completed.
+ * Context: tasklet
+ */
+static void oz_urb_cancel(struct oz_port *port, u8 ep_num, struct urb *urb)
+{
+ struct oz_urb_link *urbl = 0;
+ struct list_head *e;
+ struct oz_hcd *ozhcd;
+ unsigned long irq_state;
+ u8 ix;
+ if (port == 0) {
+ oz_trace("ERRORERROR: oz_urb_cancel(%p) port is null\n", urb);
+ return;
+ }
+ ozhcd = port->ozhcd;
+ if (ozhcd == 0) {
+ oz_trace("ERRORERROR: oz_urb_cancel(%p) ozhcd is null\n", urb);
+ return;
+ }
+
+ /* Look in the tasklet queue.
+ */
+ spin_lock_irqsave(&g_tasklet_lock, irq_state);
+ list_for_each(e, &ozhcd->urb_cancel_list) {
+ urbl = container_of(e, struct oz_urb_link, link);
+ if (urb == urbl->urb) {
+ list_del_init(e);
+ spin_unlock_irqrestore(&g_tasklet_lock, irq_state);
+ goto out2;
+ }
+ }
+ spin_unlock_irqrestore(&g_tasklet_lock, irq_state);
+ urbl = 0;
+
+ /* Look in the orphanage.
+ */
+ spin_lock_irqsave(&ozhcd->hcd_lock, irq_state);
+ list_for_each(e, &ozhcd->orphanage) {
+ urbl = container_of(e, struct oz_urb_link, link);
+ if (urbl->urb == urb) {
+ list_del(e);
+ oz_trace("Found urb in orphanage\n");
+ goto out;
+ }
+ }
+ ix = (ep_num & 0xf);
+ urbl = 0;
+ if ((ep_num & USB_DIR_IN) && ix)
+ urbl = oz_remove_urb(port->in_ep[ix], urb);
+ else
+ urbl = oz_remove_urb(port->out_ep[ix], urb);
+out:
+ spin_unlock_irqrestore(&ozhcd->hcd_lock, irq_state);
+out2:
+ if (urbl) {
+ urb->actual_length = 0;
+ oz_free_urb_link(urbl);
+ oz_complete_urb(ozhcd->hcd, urb, -EPIPE, 0);
+ }
+}
+/*------------------------------------------------------------------------------
+ * Context: tasklet
+ */
+static void oz_urb_cancel_tasklet(unsigned long unused)
+{
+ unsigned long irq_state;
+ struct urb *urb;
+ struct oz_hcd *ozhcd = oz_hcd_claim();
+ if (ozhcd == 0)
+ return;
+ spin_lock_irqsave(&g_tasklet_lock, irq_state);
+ while (!list_empty(&ozhcd->urb_cancel_list)) {
+ struct oz_urb_link *urbl =
+ list_first_entry(&ozhcd->urb_cancel_list,
+ struct oz_urb_link, link);
+ list_del_init(&urbl->link);
+ spin_unlock_irqrestore(&g_tasklet_lock, irq_state);
+ urb = urbl->urb;
+ if (urb->unlinked)
+ oz_urb_cancel(urbl->port, urbl->ep_num, urb);
+ oz_free_urb_link(urbl);
+ spin_lock_irqsave(&g_tasklet_lock, irq_state);
+ }
+ spin_unlock_irqrestore(&g_tasklet_lock, irq_state);
+ oz_hcd_put(ozhcd);
+}
+/*------------------------------------------------------------------------------
+ * Context: unknown
+ */
+static void oz_hcd_clear_orphanage(struct oz_hcd *ozhcd, int status)
+{
+ if (ozhcd) {
+ struct oz_urb_link *urbl;
+ while (!list_empty(&ozhcd->orphanage)) {
+ urbl = list_first_entry(&ozhcd->orphanage,
+ struct oz_urb_link, link);
+ list_del(&urbl->link);
+ oz_complete_urb(ozhcd->hcd, urbl->urb, status, 0);
+ oz_free_urb_link(urbl);
+ }
+ }
+}
+/*------------------------------------------------------------------------------
+ * Context: unknown
+ */
+static int oz_hcd_start(struct usb_hcd *hcd)
+{
+ oz_trace("oz_hcd_start()\n");
+ hcd->power_budget = 200;
+ hcd->state = HC_STATE_RUNNING;
+ hcd->uses_new_polling = 1;
+ return 0;
+}
+/*------------------------------------------------------------------------------
+ * Context: unknown
+ */
+static void oz_hcd_stop(struct usb_hcd *hcd)
+{
+ oz_trace("oz_hcd_stop()\n");
+}
+/*------------------------------------------------------------------------------
+ * Context: unknown
+ */
+static void oz_hcd_shutdown(struct usb_hcd *hcd)
+{
+ oz_trace("oz_hcd_shutdown()\n");
+}
+/*------------------------------------------------------------------------------
+ * Context: any
+ */
+#ifdef WANT_EVENT_TRACE
+static u8 oz_get_irq_ctx(void)
+{
+ u8 irq_info = 0;
+ if (in_interrupt())
+ irq_info |= 1;
+ if (in_irq())
+ irq_info |= 2;
+ return irq_info;
+}
+#endif /* WANT_EVENT_TRACE */
+/*------------------------------------------------------------------------------
+ * Called to queue an urb for the device.
+ * This function should return a non-zero error code if it fails the urb but
+ * should not call usb_hcd_giveback_urb().
+ * Context: any
+ */
+static int oz_hcd_urb_enqueue(struct usb_hcd *hcd, struct urb *urb,
+ gfp_t mem_flags)
+{
+ struct oz_hcd *ozhcd = oz_hcd_private(hcd);
+ int rc = 0;
+ int port_ix;
+ struct oz_port *port;
+ unsigned long irq_state;
+ struct oz_urb_link *urbl;
+ oz_trace2(OZ_TRACE_URB, "%lu: oz_hcd_urb_enqueue(%p)\n",
+ jiffies, urb);
+ oz_event_log(OZ_EVT_URB_SUBMIT, oz_get_irq_ctx(),
+ (u16)urb->number_of_packets, urb, urb->pipe);
+ if (unlikely(ozhcd == 0)) {
+ oz_trace2(OZ_TRACE_URB, "%lu: Refused urb(%p) not ozhcd.\n",
+ jiffies, urb);
+ return -EPIPE;
+ }
+ if (unlikely(hcd->state != HC_STATE_RUNNING)) {
+ oz_trace2(OZ_TRACE_URB, "%lu: Refused urb(%p) not running.\n",
+ jiffies, urb);
+ return -EPIPE;
+ }
+ port_ix = oz_get_port_from_addr(ozhcd, urb->dev->devnum);
+ if (port_ix < 0)
+ return -EPIPE;
+ port = &ozhcd->ports[port_ix];
+ if (port == 0)
+ return -EPIPE;
+ if ((port->flags & OZ_PORT_F_PRESENT) == 0) {
+ oz_trace("Refusing URB port_ix = %d devnum = %d\n",
+ port_ix, urb->dev->devnum);
+ return -EPIPE;
+ }
+ urb->hcpriv = port;
+ /* Put request in queue for processing by tasklet.
+ */
+ urbl = oz_alloc_urb_link();
+ if (unlikely(urbl == 0))
+ return -ENOMEM;
+ urbl->urb = urb;
+ spin_lock_irqsave(&g_tasklet_lock, irq_state);
+ rc = usb_hcd_link_urb_to_ep(hcd, urb);
+ if (unlikely(rc)) {
+ spin_unlock_irqrestore(&g_tasklet_lock, irq_state);
+ oz_free_urb_link(urbl);
+ return rc;
+ }
+ list_add_tail(&urbl->link, &ozhcd->urb_pending_list);
+ spin_unlock_irqrestore(&g_tasklet_lock, irq_state);
+ tasklet_schedule(&g_urb_process_tasklet);
+ atomic_inc(&g_pending_urbs);
+ return 0;
+}
+/*------------------------------------------------------------------------------
+ * Context: tasklet
+ */
+static struct oz_urb_link *oz_remove_urb(struct oz_endpoint *ep,
+ struct urb *urb)
+{
+ struct oz_urb_link *urbl = 0;
+ struct list_head *e;
+ if (unlikely(ep == 0))
+ return 0;
+ list_for_each(e, &ep->urb_list) {
+ urbl = container_of(e, struct oz_urb_link, link);
+ if (urbl->urb == urb) {
+ list_del_init(e);
+ if (usb_pipeisoc(urb->pipe)) {
+ ep->credit -= urb->number_of_packets;
+ if (ep->credit < 0)
+ ep->credit = 0;
+ oz_event_log(OZ_EVT_EP_CREDIT,
+ usb_pipein(urb->pipe) ?
+ (ep->ep_num | USB_DIR_IN) : ep->ep_num,
+ 0, 0, ep->credit);
+ }
+ return urbl;
+ }
+ }
+ return 0;
+}
+/*------------------------------------------------------------------------------
+ * Called to dequeue a previously submitted urb for the device.
+ * Context: any
+ */
+static int oz_hcd_urb_dequeue(struct usb_hcd *hcd, struct urb *urb, int status)
+{
+ struct oz_hcd *ozhcd = oz_hcd_private(hcd);
+ struct oz_urb_link *urbl = 0;
+ int rc;
+ unsigned long irq_state;
+ oz_trace2(OZ_TRACE_URB, "%lu: oz_hcd_urb_dequeue(%p)\n", jiffies, urb);
+ urbl = oz_alloc_urb_link();
+ if (unlikely(urbl == 0))
+ return -ENOMEM;
+ spin_lock_irqsave(&g_tasklet_lock, irq_state);
+ /* The following function checks the urb is still in the queue
+ * maintained by the core and that the unlinked field is zero.
+ * If both are true the function sets the unlinked field and returns
+ * zero. Otherwise it returns an error.
+ */
+ rc = usb_hcd_check_unlink_urb(hcd, urb, status);
+ /* We have to check we haven't completed the urb or are about
+ * to complete it. When we do we set hcpriv to 0 so if this has
+ * already happened we don't put the urb in the cancel queue.
+ */
+ if ((rc == 0) && urb->hcpriv) {
+ urbl->urb = urb;
+ urbl->port = (struct oz_port *)urb->hcpriv;
+ urbl->ep_num = usb_pipeendpoint(urb->pipe);
+ if (usb_pipein(urb->pipe))
+ urbl->ep_num |= USB_DIR_IN;
+ list_add_tail(&urbl->link, &ozhcd->urb_cancel_list);
+ spin_unlock_irqrestore(&g_tasklet_lock, irq_state);
+ tasklet_schedule(&g_urb_cancel_tasklet);
+ } else {
+ spin_unlock_irqrestore(&g_tasklet_lock, irq_state);
+ oz_free_urb_link(urbl);
+ }
+ return rc;
+}
+/*------------------------------------------------------------------------------
+ * Context: unknown
+ */
+static void oz_hcd_endpoint_disable(struct usb_hcd *hcd,
+ struct usb_host_endpoint *ep)
+{
+ oz_trace("oz_hcd_endpoint_disable\n");
+}
+/*------------------------------------------------------------------------------
+ * Context: unknown
+ */
+static void oz_hcd_endpoint_reset(struct usb_hcd *hcd,
+ struct usb_host_endpoint *ep)
+{
+ oz_trace("oz_hcd_endpoint_reset\n");
+}
+/*------------------------------------------------------------------------------
+ * Context: unknown
+ */
+static int oz_hcd_get_frame_number(struct usb_hcd *hcd)
+{
+ oz_trace("oz_hcd_get_frame_number\n");
+ return oz_usb_get_frame_number();
+}
+/*------------------------------------------------------------------------------
+ * Context: softirq
+ * This is called as a consquence of us calling usb_hcd_poll_rh_status() and we
+ * always do that in softirq context.
+ */
+static int oz_hcd_hub_status_data(struct usb_hcd *hcd, char *buf)
+{
+ struct oz_hcd *ozhcd = oz_hcd_private(hcd);
+ int i;
+
+ oz_trace2(OZ_TRACE_HUB, "oz_hcd_hub_status_data()\n");
+ buf[0] = 0;
+
+ spin_lock_bh(&ozhcd->hcd_lock);
+ for (i = 0; i < OZ_NB_PORTS; i++) {
+ if (ozhcd->ports[i].flags & OZ_PORT_F_CHANGED) {
+ oz_trace2(OZ_TRACE_HUB, "Port %d changed\n", i);
+ ozhcd->ports[i].flags &= ~OZ_PORT_F_CHANGED;
+ buf[0] |= 1<<(i+1);
+ }
+ }
+ spin_unlock_bh(&ozhcd->hcd_lock);
+ return buf[0] ? 1 : 0;
+}
+/*------------------------------------------------------------------------------
+ * Context: process
+ */
+static void oz_get_hub_descriptor(struct usb_hcd *hcd,
+ struct usb_hub_descriptor *desc)
+{
+ oz_trace2(OZ_TRACE_HUB, "GetHubDescriptor\n");
+ memset(desc, 0, sizeof(*desc));
+ desc->bDescriptorType = 0x29;
+ desc->bDescLength = 9;
+ desc->wHubCharacteristics = (__force __u16)
+ __constant_cpu_to_le16(0x0001);
+ desc->bNbrPorts = OZ_NB_PORTS;
+}
+/*------------------------------------------------------------------------------
+ * Context: process
+ */
+static int oz_set_port_feature(struct usb_hcd *hcd, u16 wvalue, u16 windex)
+{
+ struct oz_port *port;
+ int err = 0;
+ u8 port_id = (u8)windex;
+ struct oz_hcd *ozhcd = oz_hcd_private(hcd);
+ unsigned set_bits = 0;
+ unsigned clear_bits = 0;
+ oz_trace2(OZ_TRACE_HUB, "SetPortFeature\n");
+ if ((port_id < 1) || (port_id > OZ_NB_PORTS))
+ return -EPIPE;
+ port = &ozhcd->ports[port_id-1];
+ switch (wvalue) {
+ case USB_PORT_FEAT_CONNECTION:
+ oz_trace2(OZ_TRACE_HUB, "USB_PORT_FEAT_CONNECTION\n");
+ break;
+ case USB_PORT_FEAT_ENABLE:
+ oz_trace2(OZ_TRACE_HUB, "USB_PORT_FEAT_ENABLE\n");
+ break;
+ case USB_PORT_FEAT_SUSPEND:
+ oz_trace2(OZ_TRACE_HUB, "USB_PORT_FEAT_SUSPEND\n");
+ break;
+ case USB_PORT_FEAT_OVER_CURRENT:
+ oz_trace2(OZ_TRACE_HUB, "USB_PORT_FEAT_OVER_CURRENT\n");
+ break;
+ case USB_PORT_FEAT_RESET:
+ oz_trace2(OZ_TRACE_HUB, "USB_PORT_FEAT_RESET\n");
+ set_bits = USB_PORT_STAT_ENABLE | (USB_PORT_STAT_C_RESET<<16);
+ clear_bits = USB_PORT_STAT_RESET;
+ ozhcd->ports[port_id-1].bus_addr = 0;
+ break;
+ case USB_PORT_FEAT_POWER:
+ oz_trace2(OZ_TRACE_HUB, "USB_PORT_FEAT_POWER\n");
+ set_bits |= USB_PORT_STAT_POWER;
+ break;
+ case USB_PORT_FEAT_LOWSPEED:
+ oz_trace2(OZ_TRACE_HUB, "USB_PORT_FEAT_LOWSPEED\n");
+ break;
+ case USB_PORT_FEAT_C_CONNECTION:
+ oz_trace2(OZ_TRACE_HUB, "USB_PORT_FEAT_C_CONNECTION\n");
+ break;
+ case USB_PORT_FEAT_C_ENABLE:
+ oz_trace2(OZ_TRACE_HUB, "USB_PORT_FEAT_C_ENABLE\n");
+ break;
+ case USB_PORT_FEAT_C_SUSPEND:
+ oz_trace2(OZ_TRACE_HUB, "USB_PORT_FEAT_C_SUSPEND\n");
+ break;
+ case USB_PORT_FEAT_C_OVER_CURRENT:
+ oz_trace2(OZ_TRACE_HUB, "USB_PORT_FEAT_C_OVER_CURRENT\n");
+ break;
+ case USB_PORT_FEAT_C_RESET:
+ oz_trace2(OZ_TRACE_HUB, "USB_PORT_FEAT_C_RESET\n");
+ break;
+ case USB_PORT_FEAT_TEST:
+ oz_trace2(OZ_TRACE_HUB, "USB_PORT_FEAT_TEST\n");
+ break;
+ case USB_PORT_FEAT_INDICATOR:
+ oz_trace2(OZ_TRACE_HUB, "USB_PORT_FEAT_INDICATOR\n");
+ break;
+ default:
+ oz_trace2(OZ_TRACE_HUB, "Other %d\n", wvalue);
+ break;
+ }
+ if (set_bits || clear_bits) {
+ spin_lock_bh(&port->port_lock);
+ port->status &= ~clear_bits;
+ port->status |= set_bits;
+ spin_unlock_bh(&port->port_lock);
+ }
+ oz_trace2(OZ_TRACE_HUB, "Port[%d] status = 0x%x\n", port_id,
+ port->status);
+ return err;
+}
+/*------------------------------------------------------------------------------
+ * Context: process
+ */
+static int oz_clear_port_feature(struct usb_hcd *hcd, u16 wvalue, u16 windex)
+{
+ struct oz_port *port;
+ int err = 0;
+ u8 port_id = (u8)windex;
+ struct oz_hcd *ozhcd = oz_hcd_private(hcd);
+ unsigned clear_bits = 0;
+ oz_trace2(OZ_TRACE_HUB, "ClearPortFeature\n");
+ if ((port_id < 1) || (port_id > OZ_NB_PORTS))
+ return -EPIPE;
+ port = &ozhcd->ports[port_id-1];
+ switch (wvalue) {
+ case USB_PORT_FEAT_CONNECTION:
+ oz_trace2(OZ_TRACE_HUB, "USB_PORT_FEAT_CONNECTION\n");
+ break;
+ case USB_PORT_FEAT_ENABLE:
+ oz_trace2(OZ_TRACE_HUB, "USB_PORT_FEAT_ENABLE\n");
+ clear_bits = USB_PORT_STAT_ENABLE;
+ break;
+ case USB_PORT_FEAT_SUSPEND:
+ oz_trace2(OZ_TRACE_HUB, "USB_PORT_FEAT_SUSPEND\n");
+ break;
+ case USB_PORT_FEAT_OVER_CURRENT:
+ oz_trace2(OZ_TRACE_HUB, "USB_PORT_FEAT_OVER_CURRENT\n");
+ break;
+ case USB_PORT_FEAT_RESET:
+ oz_trace2(OZ_TRACE_HUB, "USB_PORT_FEAT_RESET\n");
+ break;
+ case USB_PORT_FEAT_POWER:
+ oz_trace2(OZ_TRACE_HUB, "USB_PORT_FEAT_POWER\n");
+ clear_bits |= USB_PORT_STAT_POWER;
+ break;
+ case USB_PORT_FEAT_LOWSPEED:
+ oz_trace2(OZ_TRACE_HUB, "USB_PORT_FEAT_LOWSPEED\n");
+ break;
+ case USB_PORT_FEAT_C_CONNECTION:
+ oz_trace2(OZ_TRACE_HUB, "USB_PORT_FEAT_C_CONNECTION\n");
+ clear_bits = (USB_PORT_STAT_C_CONNECTION << 16);
+ break;
+ case USB_PORT_FEAT_C_ENABLE:
+ oz_trace2(OZ_TRACE_HUB, "USB_PORT_FEAT_C_ENABLE\n");
+ clear_bits = (USB_PORT_STAT_C_ENABLE << 16);
+ break;
+ case USB_PORT_FEAT_C_SUSPEND:
+ oz_trace2(OZ_TRACE_HUB, "USB_PORT_FEAT_C_SUSPEND\n");
+ break;
+ case USB_PORT_FEAT_C_OVER_CURRENT:
+ oz_trace2(OZ_TRACE_HUB, "USB_PORT_FEAT_C_OVER_CURRENT\n");
+ break;
+ case USB_PORT_FEAT_C_RESET:
+ oz_trace2(OZ_TRACE_HUB, "USB_PORT_FEAT_C_RESET\n");
+ clear_bits = (USB_PORT_FEAT_C_RESET << 16);
+ break;
+ case USB_PORT_FEAT_TEST:
+ oz_trace2(OZ_TRACE_HUB, "USB_PORT_FEAT_TEST\n");
+ break;
+ case USB_PORT_FEAT_INDICATOR:
+ oz_trace2(OZ_TRACE_HUB, "USB_PORT_FEAT_INDICATOR\n");
+ break;
+ default:
+ oz_trace2(OZ_TRACE_HUB, "Other %d\n", wvalue);
+ break;
+ }
+ if (clear_bits) {
+ spin_lock_bh(&port->port_lock);
+ port->status &= ~clear_bits;
+ spin_unlock_bh(&port->port_lock);
+ }
+ oz_trace2(OZ_TRACE_HUB, "Port[%d] status = 0x%x\n", port_id,
+ ozhcd->ports[port_id-1].status);
+ return err;
+}
+/*------------------------------------------------------------------------------
+ * Context: process
+ */
+static int oz_get_port_status(struct usb_hcd *hcd, u16 windex, char *buf)
+{
+ struct oz_hcd *ozhcd;
+ u32 status = 0;
+ if ((windex < 1) || (windex > OZ_NB_PORTS))
+ return -EPIPE;
+ ozhcd = oz_hcd_private(hcd);
+ oz_trace2(OZ_TRACE_HUB, "GetPortStatus windex = %d\n", windex);
+ status = ozhcd->ports[windex-1].status;
+ put_unaligned(cpu_to_le32(status), (__le32 *)buf);
+ oz_trace2(OZ_TRACE_HUB, "Port[%d] status = %x\n", windex, status);
+ return 0;
+}
+/*------------------------------------------------------------------------------
+ * Context: process
+ */
+static int oz_hcd_hub_control(struct usb_hcd *hcd, u16 req_type, u16 wvalue,
+ u16 windex, char *buf, u16 wlength)
+{
+ int err = 0;
+ oz_trace2(OZ_TRACE_HUB, "oz_hcd_hub_control()\n");
+ switch (req_type) {
+ case ClearHubFeature:
+ oz_trace2(OZ_TRACE_HUB, "ClearHubFeature: %d\n", req_type);
+ break;
+ case ClearPortFeature:
+ err = oz_clear_port_feature(hcd, wvalue, windex);
+ break;
+ case GetHubDescriptor:
+ oz_get_hub_descriptor(hcd, (struct usb_hub_descriptor *)buf);
+ break;
+ case GetHubStatus:
+ oz_trace2(OZ_TRACE_HUB, "GetHubStatus: req_type = 0x%x\n",
+ req_type);
+ put_unaligned(__constant_cpu_to_le32(0), (__le32 *)buf);
+ break;
+ case GetPortStatus:
+ err = oz_get_port_status(hcd, windex, buf);
+ break;
+ case SetHubFeature:
+ oz_trace2(OZ_TRACE_HUB, "SetHubFeature: %d\n", req_type);
+ break;
+ case SetPortFeature:
+ err = oz_set_port_feature(hcd, wvalue, windex);
+ break;
+ default:
+ oz_trace2(OZ_TRACE_HUB, "Other: %d\n", req_type);
+ break;
+ }
+ return err;
+}
+/*------------------------------------------------------------------------------
+ * Context: process
+ */
+static int oz_hcd_bus_suspend(struct usb_hcd *hcd)
+{
+ struct oz_hcd *ozhcd;
+ oz_trace2(OZ_TRACE_HUB, "oz_hcd_hub_suspend()\n");
+ ozhcd = oz_hcd_private(hcd);
+ spin_lock_bh(&ozhcd->hcd_lock);
+ hcd->state = HC_STATE_SUSPENDED;
+ ozhcd->flags |= OZ_HDC_F_SUSPENDED;
+ spin_unlock_bh(&ozhcd->hcd_lock);
+ return 0;
+}
+/*------------------------------------------------------------------------------
+ * Context: process
+ */
+static int oz_hcd_bus_resume(struct usb_hcd *hcd)
+{
+ struct oz_hcd *ozhcd;
+ oz_trace2(OZ_TRACE_HUB, "oz_hcd_hub_resume()\n");
+ ozhcd = oz_hcd_private(hcd);
+ spin_lock_bh(&ozhcd->hcd_lock);
+ ozhcd->flags &= ~OZ_HDC_F_SUSPENDED;
+ hcd->state = HC_STATE_RUNNING;
+ spin_unlock_bh(&ozhcd->hcd_lock);
+ return 0;
+}
+/*------------------------------------------------------------------------------
+ */
+static void oz_plat_shutdown(struct platform_device *dev)
+{
+ oz_trace("oz_plat_shutdown()\n");
+}
+/*------------------------------------------------------------------------------
+ * Context: process
+ */
+static int oz_plat_probe(struct platform_device *dev)
+{
+ int i;
+ int err;
+ struct usb_hcd *hcd;
+ struct oz_hcd *ozhcd;
+ oz_trace("oz_plat_probe()\n");
+ hcd = usb_create_hcd(&g_oz_hc_drv, &dev->dev, dev_name(&dev->dev));
+ if (hcd == 0) {
+ oz_trace("Failed to created hcd object OK\n");
+ return -ENOMEM;
+ }
+ ozhcd = oz_hcd_private(hcd);
+ memset(ozhcd, 0, sizeof(*ozhcd));
+ INIT_LIST_HEAD(&ozhcd->urb_pending_list);
+ INIT_LIST_HEAD(&ozhcd->urb_cancel_list);
+ INIT_LIST_HEAD(&ozhcd->orphanage);
+ ozhcd->hcd = hcd;
+ ozhcd->conn_port = -1;
+ spin_lock_init(&ozhcd->hcd_lock);
+ for (i = 0; i < OZ_NB_PORTS; i++) {
+ struct oz_port *port = &ozhcd->ports[i];
+ port->ozhcd = ozhcd;
+ port->flags = 0;
+ port->status = 0;
+ port->bus_addr = 0xff;
+ spin_lock_init(&port->port_lock);
+ }
+ err = usb_add_hcd(hcd, 0, 0);
+ if (err) {
+ oz_trace("Failed to add hcd object OK\n");
+ usb_put_hcd(hcd);
+ return -1;
+ }
+ spin_lock_bh(&g_hcdlock);
+ g_ozhcd = ozhcd;
+ spin_unlock_bh(&g_hcdlock);
+ return 0;
+}
+/*------------------------------------------------------------------------------
+ * Context: unknown
+ */
+static int oz_plat_remove(struct platform_device *dev)
+{
+ struct usb_hcd *hcd = platform_get_drvdata(dev);
+ struct oz_hcd *ozhcd;
+ oz_trace("oz_plat_remove()\n");
+ if (hcd == 0)
+ return -1;
+ ozhcd = oz_hcd_private(hcd);
+ spin_lock_bh(&g_hcdlock);
+ if (ozhcd == g_ozhcd)
+ g_ozhcd = 0;
+ spin_unlock_bh(&g_hcdlock);
+ oz_trace("Clearing orphanage\n");
+ oz_hcd_clear_orphanage(ozhcd, -EPIPE);
+ oz_trace("Removing hcd\n");
+ usb_remove_hcd(hcd);
+ usb_put_hcd(hcd);
+ oz_empty_link_pool();
+ return 0;
+}
+/*------------------------------------------------------------------------------
+ * Context: unknown
+ */
+static int oz_plat_suspend(struct platform_device *dev, pm_message_t msg)
+{
+ oz_trace("oz_plat_suspend()\n");
+ return 0;
+}
+/*------------------------------------------------------------------------------
+ * Context: unknown
+ */
+static int oz_plat_resume(struct platform_device *dev)
+{
+ oz_trace("oz_plat_resume()\n");
+ return 0;
+}
+/*------------------------------------------------------------------------------
+ * Context: process
+ */
+int oz_hcd_init(void)
+{
+ int err;
+ if (usb_disabled())
+ return -ENODEV;
+ tasklet_init(&g_urb_process_tasklet, oz_urb_process_tasklet, 0);
+ tasklet_init(&g_urb_cancel_tasklet, oz_urb_cancel_tasklet, 0);
+ err = platform_driver_register(&g_oz_plat_drv);
+ oz_trace("platform_driver_register() returned %d\n", err);
+ if (err)
+ goto error;
+ g_plat_dev = platform_device_alloc(OZ_PLAT_DEV_NAME, -1);
+ if (g_plat_dev == 0) {
+ err = -ENOMEM;
+ goto error1;
+ }
+ oz_trace("platform_device_alloc() succeeded\n");
+ err = platform_device_add(g_plat_dev);
+ if (err)
+ goto error2;
+ oz_trace("platform_device_add() succeeded\n");
+ return 0;
+error2:
+ platform_device_put(g_plat_dev);
+error1:
+ platform_driver_unregister(&g_oz_plat_drv);
+error:
+ tasklet_disable(&g_urb_process_tasklet);
+ tasklet_disable(&g_urb_cancel_tasklet);
+ oz_trace("oz_hcd_init() failed %d\n", err);
+ return err;
+}
+/*------------------------------------------------------------------------------
+ * Context: process
+ */
+void oz_hcd_term(void)
+{
+ tasklet_disable(&g_urb_process_tasklet);
+ tasklet_disable(&g_urb_cancel_tasklet);
+ platform_device_unregister(g_plat_dev);
+ platform_driver_unregister(&g_oz_plat_drv);
+ oz_trace("Pending urbs:%d\n", atomic_read(&g_pending_urbs));
+}
diff --git a/drivers/staging/ozwpan/ozhcd.h b/drivers/staging/ozwpan/ozhcd.h
new file mode 100644
index 000000000000..9b30dfd09973
--- /dev/null
+++ b/drivers/staging/ozwpan/ozhcd.h
@@ -0,0 +1,15 @@
+/* -----------------------------------------------------------------------------
+ * Copyright (c) 2011 Ozmo Inc
+ * Released under the GNU General Public License Version 2 (GPLv2).
+ * ---------------------------------------------------------------------------*/
+#ifndef _OZHCD_H
+#define _OZHCD_H
+
+int oz_hcd_init(void);
+void oz_hcd_term(void);
+void *oz_hcd_pd_arrived(void *ctx);
+void oz_hcd_pd_departed(void *ctx);
+void oz_hcd_pd_reset(void *hpd, void *hport);
+
+#endif /* _OZHCD_H */
+
diff --git a/drivers/staging/ozwpan/ozmain.c b/drivers/staging/ozwpan/ozmain.c
new file mode 100644
index 000000000000..aaf2ccc0bcfb
--- /dev/null
+++ b/drivers/staging/ozwpan/ozmain.c
@@ -0,0 +1,58 @@
+/* -----------------------------------------------------------------------------
+ * Copyright (c) 2011 Ozmo Inc
+ * Released under the GNU General Public License Version 2 (GPLv2).
+ * -----------------------------------------------------------------------------
+ */
+#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/timer.h>
+#include <linux/sched.h>
+#include <linux/netdevice.h>
+#include <linux/errno.h>
+#include <linux/ieee80211.h>
+#include "ozconfig.h"
+#include "ozpd.h"
+#include "ozproto.h"
+#include "ozcdev.h"
+#include "oztrace.h"
+#include "ozevent.h"
+/*------------------------------------------------------------------------------
+ * The name of the 802.11 mac device. Empty string is the default value but a
+ * value can be supplied as a parameter to the module. An empty string means
+ * bind to nothing. '*' means bind to all netcards - this includes non-802.11
+ * netcards. Bindings can be added later using an IOCTL.
+ */
+char *g_net_dev = "";
+/*------------------------------------------------------------------------------
+ * Context: process
+ */
+static int __init ozwpan_init(void)
+{
+ oz_event_init();
+ oz_cdev_register();
+ oz_protocol_init(g_net_dev);
+ oz_app_enable(OZ_APPID_USB, 1);
+ oz_apps_init();
+ return 0;
+}
+/*------------------------------------------------------------------------------
+ * Context: process
+ */
+static void __exit ozwpan_exit(void)
+{
+ oz_protocol_term();
+ oz_apps_term();
+ oz_cdev_deregister();
+ oz_event_term();
+}
+/*------------------------------------------------------------------------------
+ */
+module_param(g_net_dev, charp, S_IRUGO);
+module_init(ozwpan_init);
+module_exit(ozwpan_exit);
+
+MODULE_AUTHOR("Chris Kelly");
+MODULE_DESCRIPTION("Ozmo Devices USB over WiFi hcd driver");
+MODULE_VERSION("1.0.8");
+MODULE_LICENSE("GPL");
+
diff --git a/drivers/staging/ozwpan/ozpd.c b/drivers/staging/ozwpan/ozpd.c
new file mode 100644
index 000000000000..2b45d3d1800c
--- /dev/null
+++ b/drivers/staging/ozwpan/ozpd.c
@@ -0,0 +1,832 @@
+/* -----------------------------------------------------------------------------
+ * Copyright (c) 2011 Ozmo Inc
+ * Released under the GNU General Public License Version 2 (GPLv2).
+ * -----------------------------------------------------------------------------
+ */
+#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/timer.h>
+#include <linux/sched.h>
+#include <linux/netdevice.h>
+#include <linux/errno.h>
+#include "ozconfig.h"
+#include "ozprotocol.h"
+#include "ozeltbuf.h"
+#include "ozpd.h"
+#include "ozproto.h"
+#include "oztrace.h"
+#include "ozevent.h"
+#include "ozcdev.h"
+#include "ozusbsvc.h"
+#include <asm/unaligned.h>
+#include <linux/uaccess.h>
+#include <net/psnap.h>
+/*------------------------------------------------------------------------------
+ */
+#define OZ_MAX_TX_POOL_SIZE 6
+/* Maximum number of uncompleted isoc frames that can be pending.
+ */
+#define OZ_MAX_SUBMITTED_ISOC 16
+/*------------------------------------------------------------------------------
+ */
+static struct oz_tx_frame *oz_tx_frame_alloc(struct oz_pd *pd);
+static void oz_tx_frame_free(struct oz_pd *pd, struct oz_tx_frame *f);
+static struct sk_buff *oz_build_frame(struct oz_pd *pd, struct oz_tx_frame *f);
+static int oz_send_isoc_frame(struct oz_pd *pd);
+static void oz_retire_frame(struct oz_pd *pd, struct oz_tx_frame *f);
+static void oz_isoc_stream_free(struct oz_isoc_stream *st);
+static int oz_send_next_queued_frame(struct oz_pd *pd, int *more_data);
+static void oz_isoc_destructor(struct sk_buff *skb);
+static int oz_def_app_init(void);
+static void oz_def_app_term(void);
+static int oz_def_app_start(struct oz_pd *pd, int resume);
+static void oz_def_app_stop(struct oz_pd *pd, int pause);
+static void oz_def_app_rx(struct oz_pd *pd, struct oz_elt *elt);
+/*------------------------------------------------------------------------------
+ * Counts the uncompleted isoc frames submitted to netcard.
+ */
+static atomic_t g_submitted_isoc = ATOMIC_INIT(0);
+/* Application handler functions.
+ */
+static struct oz_app_if g_app_if[OZ_APPID_MAX] = {
+ {oz_usb_init,
+ oz_usb_term,
+ oz_usb_start,
+ oz_usb_stop,
+ oz_usb_rx,
+ oz_usb_heartbeat,
+ oz_usb_farewell,
+ OZ_APPID_USB},
+
+ {oz_def_app_init,
+ oz_def_app_term,
+ oz_def_app_start,
+ oz_def_app_stop,
+ oz_def_app_rx,
+ 0,
+ 0,
+ OZ_APPID_UNUSED1},
+
+ {oz_def_app_init,
+ oz_def_app_term,
+ oz_def_app_start,
+ oz_def_app_stop,
+ oz_def_app_rx,
+ 0,
+ 0,
+ OZ_APPID_UNUSED2},
+
+ {oz_cdev_init,
+ oz_cdev_term,
+ oz_cdev_start,
+ oz_cdev_stop,
+ oz_cdev_rx,
+ 0,
+ 0,
+ OZ_APPID_SERIAL},
+};
+/*------------------------------------------------------------------------------
+ * Context: process
+ */
+static int oz_def_app_init(void)
+{
+ return 0;
+}
+/*------------------------------------------------------------------------------
+ * Context: process
+ */
+static void oz_def_app_term(void)
+{
+}
+/*------------------------------------------------------------------------------
+ * Context: softirq
+ */
+static int oz_def_app_start(struct oz_pd *pd, int resume)
+{
+ return 0;
+}
+/*------------------------------------------------------------------------------
+ * Context: softirq
+ */
+static void oz_def_app_stop(struct oz_pd *pd, int pause)
+{
+}
+/*------------------------------------------------------------------------------
+ * Context: softirq
+ */
+static void oz_def_app_rx(struct oz_pd *pd, struct oz_elt *elt)
+{
+}
+/*------------------------------------------------------------------------------
+ * Context: softirq or process
+ */
+void oz_pd_set_state(struct oz_pd *pd, unsigned state)
+{
+ pd->state = state;
+ oz_event_log(OZ_EVT_PD_STATE, 0, 0, 0, state);
+#ifdef WANT_TRACE
+ switch (state) {
+ case OZ_PD_S_IDLE:
+ oz_trace("PD State: OZ_PD_S_IDLE\n");
+ break;
+ case OZ_PD_S_CONNECTED:
+ oz_trace("PD State: OZ_PD_S_CONNECTED\n");
+ break;
+ case OZ_PD_S_STOPPED:
+ oz_trace("PD State: OZ_PD_S_STOPPED\n");
+ break;
+ case OZ_PD_S_SLEEP:
+ oz_trace("PD State: OZ_PD_S_SLEEP\n");
+ break;
+ }
+#endif /* WANT_TRACE */
+}
+/*------------------------------------------------------------------------------
+ * Context: softirq or process
+ */
+void oz_pd_get(struct oz_pd *pd)
+{
+ atomic_inc(&pd->ref_count);
+}
+/*------------------------------------------------------------------------------
+ * Context: softirq or process
+ */
+void oz_pd_put(struct oz_pd *pd)
+{
+ if (atomic_dec_and_test(&pd->ref_count))
+ oz_pd_destroy(pd);
+}
+/*------------------------------------------------------------------------------
+ * Context: softirq-serialized
+ */
+struct oz_pd *oz_pd_alloc(u8 *mac_addr)
+{
+ struct oz_pd *pd = kzalloc(sizeof(struct oz_pd), GFP_ATOMIC);
+ if (pd) {
+ int i;
+ atomic_set(&pd->ref_count, 2);
+ for (i = 0; i < OZ_APPID_MAX; i++)
+ spin_lock_init(&pd->app_lock[i]);
+ pd->last_rx_pkt_num = 0xffffffff;
+ oz_pd_set_state(pd, OZ_PD_S_IDLE);
+ pd->max_tx_size = OZ_MAX_TX_SIZE;
+ memcpy(pd->mac_addr, mac_addr, ETH_ALEN);
+ if (0 != oz_elt_buf_init(&pd->elt_buff)) {
+ kfree(pd);
+ pd = 0;
+ }
+ spin_lock_init(&pd->tx_frame_lock);
+ INIT_LIST_HEAD(&pd->tx_queue);
+ INIT_LIST_HEAD(&pd->farewell_list);
+ pd->last_sent_frame = &pd->tx_queue;
+ spin_lock_init(&pd->stream_lock);
+ INIT_LIST_HEAD(&pd->stream_list);
+ }
+ return pd;
+}
+/*------------------------------------------------------------------------------
+ * Context: softirq or process
+ */
+void oz_pd_destroy(struct oz_pd *pd)
+{
+ struct list_head *e;
+ struct oz_tx_frame *f;
+ struct oz_isoc_stream *st;
+ struct oz_farewell *fwell;
+ oz_trace("Destroying PD\n");
+ /* Delete any streams.
+ */
+ e = pd->stream_list.next;
+ while (e != &pd->stream_list) {
+ st = container_of(e, struct oz_isoc_stream, link);
+ e = e->next;
+ oz_isoc_stream_free(st);
+ }
+ /* Free any queued tx frames.
+ */
+ e = pd->tx_queue.next;
+ while (e != &pd->tx_queue) {
+ f = container_of(e, struct oz_tx_frame, link);
+ e = e->next;
+ oz_retire_frame(pd, f);
+ }
+ oz_elt_buf_term(&pd->elt_buff);
+ /* Free any farewells.
+ */
+ e = pd->farewell_list.next;
+ while (e != &pd->farewell_list) {
+ fwell = container_of(e, struct oz_farewell, link);
+ e = e->next;
+ kfree(fwell);
+ }
+ /* Deallocate all frames in tx pool.
+ */
+ while (pd->tx_pool) {
+ e = pd->tx_pool;
+ pd->tx_pool = e->next;
+ kfree(container_of(e, struct oz_tx_frame, link));
+ }
+ if (pd->net_dev)
+ dev_put(pd->net_dev);
+ kfree(pd);
+}
+/*------------------------------------------------------------------------------
+ * Context: softirq-serialized
+ */
+int oz_services_start(struct oz_pd *pd, u16 apps, int resume)
+{
+ struct oz_app_if *ai;
+ int rc = 0;
+ oz_trace("oz_services_start(0x%x) resume(%d)\n", apps, resume);
+ for (ai = g_app_if; ai < &g_app_if[OZ_APPID_MAX]; ai++) {
+ if (apps & (1<<ai->app_id)) {
+ if (ai->start(pd, resume)) {
+ rc = -1;
+ oz_trace("Unabled to start service %d\n",
+ ai->app_id);
+ break;
+ }
+ oz_polling_lock_bh();
+ pd->total_apps |= (1<<ai->app_id);
+ if (resume)
+ pd->paused_apps &= ~(1<<ai->app_id);
+ oz_polling_unlock_bh();
+ }
+ }
+ return rc;
+}
+/*------------------------------------------------------------------------------
+ * Context: softirq or process
+ */
+void oz_services_stop(struct oz_pd *pd, u16 apps, int pause)
+{
+ struct oz_app_if *ai;
+ oz_trace("oz_stop_services(0x%x) pause(%d)\n", apps, pause);
+ for (ai = g_app_if; ai < &g_app_if[OZ_APPID_MAX]; ai++) {
+ if (apps & (1<<ai->app_id)) {
+ oz_polling_lock_bh();
+ if (pause) {
+ pd->paused_apps |= (1<<ai->app_id);
+ } else {
+ pd->total_apps &= ~(1<<ai->app_id);
+ pd->paused_apps &= ~(1<<ai->app_id);
+ }
+ oz_polling_unlock_bh();
+ ai->stop(pd, pause);
+ }
+ }
+}
+/*------------------------------------------------------------------------------
+ * Context: softirq
+ */
+void oz_pd_heartbeat(struct oz_pd *pd, u16 apps)
+{
+ struct oz_app_if *ai;
+ int more = 0;
+ for (ai = g_app_if; ai < &g_app_if[OZ_APPID_MAX]; ai++) {
+ if (ai->heartbeat && (apps & (1<<ai->app_id))) {
+ if (ai->heartbeat(pd))
+ more = 1;
+ }
+ }
+ if (more)
+ oz_pd_request_heartbeat(pd);
+ if (pd->mode & OZ_F_ISOC_ANYTIME) {
+ int count = 8;
+ while (count-- && (oz_send_isoc_frame(pd) >= 0))
+ ;
+ }
+}
+/*------------------------------------------------------------------------------
+ * Context: softirq or process
+ */
+void oz_pd_stop(struct oz_pd *pd)
+{
+ u16 stop_apps = 0;
+ oz_trace("oz_pd_stop() State = 0x%x\n", pd->state);
+ oz_pd_indicate_farewells(pd);
+ oz_polling_lock_bh();
+ stop_apps = pd->total_apps;
+ pd->total_apps = 0;
+ pd->paused_apps = 0;
+ oz_polling_unlock_bh();
+ oz_services_stop(pd, stop_apps, 0);
+ oz_polling_lock_bh();
+ oz_pd_set_state(pd, OZ_PD_S_STOPPED);
+ /* Remove from PD list.*/
+ list_del(&pd->link);
+ oz_polling_unlock_bh();
+ oz_trace("pd ref count = %d\n", atomic_read(&pd->ref_count));
+ oz_timer_delete(pd, 0);
+ oz_pd_put(pd);
+}
+/*------------------------------------------------------------------------------
+ * Context: softirq
+ */
+int oz_pd_sleep(struct oz_pd *pd)
+{
+ int do_stop = 0;
+ u16 stop_apps = 0;
+ oz_polling_lock_bh();
+ if (pd->state & (OZ_PD_S_SLEEP | OZ_PD_S_STOPPED)) {
+ oz_polling_unlock_bh();
+ return 0;
+ }
+ if (pd->keep_alive_j && pd->session_id) {
+ oz_pd_set_state(pd, OZ_PD_S_SLEEP);
+ pd->pulse_time_j = jiffies + pd->keep_alive_j;
+ oz_trace("Sleep Now %lu until %lu\n",
+ jiffies, pd->pulse_time_j);
+ } else {
+ do_stop = 1;
+ }
+ stop_apps = pd->total_apps;
+ oz_polling_unlock_bh();
+ if (do_stop) {
+ oz_pd_stop(pd);
+ } else {
+ oz_services_stop(pd, stop_apps, 1);
+ oz_timer_add(pd, OZ_TIMER_STOP, jiffies + pd->keep_alive_j, 1);
+ }
+ return do_stop;
+}
+/*------------------------------------------------------------------------------
+ * Context: softirq
+ */
+static struct oz_tx_frame *oz_tx_frame_alloc(struct oz_pd *pd)
+{
+ struct oz_tx_frame *f = 0;
+ spin_lock_bh(&pd->tx_frame_lock);
+ if (pd->tx_pool) {
+ f = container_of(pd->tx_pool, struct oz_tx_frame, link);
+ pd->tx_pool = pd->tx_pool->next;
+ pd->tx_pool_count--;
+ }
+ spin_unlock_bh(&pd->tx_frame_lock);
+ if (f == 0)
+ f = kmalloc(sizeof(struct oz_tx_frame), GFP_ATOMIC);
+ if (f) {
+ f->total_size = sizeof(struct oz_hdr);
+ INIT_LIST_HEAD(&f->link);
+ INIT_LIST_HEAD(&f->elt_list);
+ }
+ return f;
+}
+/*------------------------------------------------------------------------------
+ * Context: softirq or process
+ */
+static void oz_tx_frame_free(struct oz_pd *pd, struct oz_tx_frame *f)
+{
+ spin_lock_bh(&pd->tx_frame_lock);
+ if (pd->tx_pool_count < OZ_MAX_TX_POOL_SIZE) {
+ f->link.next = pd->tx_pool;
+ pd->tx_pool = &f->link;
+ pd->tx_pool_count++;
+ f = 0;
+ } else {
+ kfree(f);
+ }
+ spin_unlock_bh(&pd->tx_frame_lock);
+ if (f)
+ kfree(f);
+}
+/*------------------------------------------------------------------------------
+ * Context: softirq
+ */
+int oz_prepare_frame(struct oz_pd *pd, int empty)
+{
+ struct oz_tx_frame *f;
+ if ((pd->mode & OZ_MODE_MASK) != OZ_MODE_TRIGGERED)
+ return -1;
+ if (pd->nb_queued_frames >= OZ_MAX_QUEUED_FRAMES)
+ return -1;
+ if (!empty && !oz_are_elts_available(&pd->elt_buff))
+ return -1;
+ f = oz_tx_frame_alloc(pd);
+ if (f == 0)
+ return -1;
+ f->hdr.control =
+ (OZ_PROTOCOL_VERSION<<OZ_VERSION_SHIFT) | OZ_F_ACK_REQUESTED;
+ ++pd->last_tx_pkt_num;
+ put_unaligned(cpu_to_le32(pd->last_tx_pkt_num), &f->hdr.pkt_num);
+ if (empty == 0) {
+ oz_select_elts_for_tx(&pd->elt_buff, 0, &f->total_size,
+ pd->max_tx_size, &f->elt_list);
+ }
+ spin_lock(&pd->tx_frame_lock);
+ list_add_tail(&f->link, &pd->tx_queue);
+ pd->nb_queued_frames++;
+ spin_unlock(&pd->tx_frame_lock);
+ return 0;
+}
+/*------------------------------------------------------------------------------
+ * Context: softirq-serialized
+ */
+static struct sk_buff *oz_build_frame(struct oz_pd *pd, struct oz_tx_frame *f)
+{
+ struct sk_buff *skb = 0;
+ struct net_device *dev = pd->net_dev;
+ struct oz_hdr *oz_hdr;
+ struct oz_elt *elt;
+ struct list_head *e;
+ /* Allocate skb with enough space for the lower layers as well
+ * as the space we need.
+ */
+ skb = alloc_skb(f->total_size + OZ_ALLOCATED_SPACE(dev), GFP_ATOMIC);
+ if (skb == 0)
+ return 0;
+ /* Reserve the head room for lower layers.
+ */
+ skb_reserve(skb, LL_RESERVED_SPACE(dev));
+ skb_reset_network_header(skb);
+ skb->dev = dev;
+ skb->protocol = htons(OZ_ETHERTYPE);
+ if (dev_hard_header(skb, dev, OZ_ETHERTYPE, pd->mac_addr,
+ dev->dev_addr, skb->len) < 0)
+ goto fail;
+ /* Push the tail to the end of the area we are going to copy to.
+ */
+ oz_hdr = (struct oz_hdr *)skb_put(skb, f->total_size);
+ f->hdr.last_pkt_num = pd->trigger_pkt_num & OZ_LAST_PN_MASK;
+ memcpy(oz_hdr, &f->hdr, sizeof(struct oz_hdr));
+ /* Copy the elements into the frame body.
+ */
+ elt = (struct oz_elt *)(oz_hdr+1);
+ for (e = f->elt_list.next; e != &f->elt_list; e = e->next) {
+ struct oz_elt_info *ei;
+ ei = container_of(e, struct oz_elt_info, link);
+ memcpy(elt, ei->data, ei->length);
+ elt = oz_next_elt(elt);
+ }
+ return skb;
+fail:
+ kfree_skb(skb);
+ return 0;
+}
+/*------------------------------------------------------------------------------
+ * Context: softirq or process
+ */
+static void oz_retire_frame(struct oz_pd *pd, struct oz_tx_frame *f)
+{
+ struct list_head *e;
+ struct oz_elt_info *ei;
+ e = f->elt_list.next;
+ while (e != &f->elt_list) {
+ ei = container_of(e, struct oz_elt_info, link);
+ e = e->next;
+ list_del_init(&ei->link);
+ if (ei->callback)
+ ei->callback(pd, ei->context);
+ spin_lock_bh(&pd->elt_buff.lock);
+ oz_elt_info_free(&pd->elt_buff, ei);
+ spin_unlock_bh(&pd->elt_buff.lock);
+ }
+ oz_tx_frame_free(pd, f);
+ if (pd->elt_buff.free_elts > pd->elt_buff.max_free_elts)
+ oz_trim_elt_pool(&pd->elt_buff);
+}
+/*------------------------------------------------------------------------------
+ * Context: softirq-serialized
+ */
+static int oz_send_next_queued_frame(struct oz_pd *pd, int *more_data)
+{
+ struct sk_buff *skb;
+ struct oz_tx_frame *f;
+ struct list_head *e;
+ *more_data = 0;
+ spin_lock(&pd->tx_frame_lock);
+ e = pd->last_sent_frame->next;
+ if (e == &pd->tx_queue) {
+ spin_unlock(&pd->tx_frame_lock);
+ return -1;
+ }
+ pd->last_sent_frame = e;
+ if (e->next != &pd->tx_queue)
+ *more_data = 1;
+ f = container_of(e, struct oz_tx_frame, link);
+ skb = oz_build_frame(pd, f);
+ spin_unlock(&pd->tx_frame_lock);
+ oz_trace2(OZ_TRACE_TX_FRAMES, "TX frame PN=0x%x\n", f->hdr.pkt_num);
+ if (skb) {
+ oz_event_log(OZ_EVT_TX_FRAME,
+ 0,
+ (((u16)f->hdr.control)<<8)|f->hdr.last_pkt_num,
+ 0, f->hdr.pkt_num);
+ if (dev_queue_xmit(skb) < 0)
+ return -1;
+ }
+ return 0;
+}
+/*------------------------------------------------------------------------------
+ * Context: softirq-serialized
+ */
+void oz_send_queued_frames(struct oz_pd *pd, int backlog)
+{
+ int more;
+ if (backlog < OZ_MAX_QUEUED_FRAMES) {
+ if (oz_send_next_queued_frame(pd, &more) >= 0) {
+ while (more && oz_send_next_queued_frame(pd, &more))
+ ;
+ } else {
+ if (((pd->mode & OZ_F_ISOC_ANYTIME) == 0)
+ || (pd->isoc_sent == 0)) {
+ if (oz_prepare_frame(pd, 1) >= 0)
+ oz_send_next_queued_frame(pd, &more);
+ }
+ }
+ } else {
+ oz_send_next_queued_frame(pd, &more);
+ }
+}
+/*------------------------------------------------------------------------------
+ * Context: softirq
+ */
+static int oz_send_isoc_frame(struct oz_pd *pd)
+{
+ struct sk_buff *skb = 0;
+ struct net_device *dev = pd->net_dev;
+ struct oz_hdr *oz_hdr;
+ struct oz_elt *elt;
+ struct list_head *e;
+ struct list_head list;
+ int total_size = sizeof(struct oz_hdr);
+ INIT_LIST_HEAD(&list);
+
+ oz_select_elts_for_tx(&pd->elt_buff, 1, &total_size,
+ pd->max_tx_size, &list);
+ if (list.next == &list)
+ return 0;
+ skb = alloc_skb(total_size + OZ_ALLOCATED_SPACE(dev), GFP_ATOMIC);
+ if (skb == 0) {
+ oz_trace("Cannot alloc skb\n");
+ oz_elt_info_free_chain(&pd->elt_buff, &list);
+ return -1;
+ }
+ skb_reserve(skb, LL_RESERVED_SPACE(dev));
+ skb_reset_network_header(skb);
+ skb->dev = dev;
+ skb->protocol = htons(OZ_ETHERTYPE);
+ if (dev_hard_header(skb, dev, OZ_ETHERTYPE, pd->mac_addr,
+ dev->dev_addr, skb->len) < 0) {
+ kfree_skb(skb);
+ return -1;
+ }
+ oz_hdr = (struct oz_hdr *)skb_put(skb, total_size);
+ oz_hdr->control = (OZ_PROTOCOL_VERSION<<OZ_VERSION_SHIFT) | OZ_F_ISOC;
+ oz_hdr->last_pkt_num = pd->trigger_pkt_num & OZ_LAST_PN_MASK;
+ elt = (struct oz_elt *)(oz_hdr+1);
+
+ for (e = list.next; e != &list; e = e->next) {
+ struct oz_elt_info *ei;
+ ei = container_of(e, struct oz_elt_info, link);
+ memcpy(elt, ei->data, ei->length);
+ elt = oz_next_elt(elt);
+ }
+ oz_event_log(OZ_EVT_TX_ISOC, 0, 0, 0, 0);
+ dev_queue_xmit(skb);
+ oz_elt_info_free_chain(&pd->elt_buff, &list);
+ return 0;
+}
+/*------------------------------------------------------------------------------
+ * Context: softirq-serialized
+ */
+void oz_retire_tx_frames(struct oz_pd *pd, u8 lpn)
+{
+ struct list_head *e;
+ struct oz_tx_frame *f;
+ struct list_head *first = 0;
+ struct list_head *last = 0;
+ u8 diff;
+ u32 pkt_num;
+
+ spin_lock(&pd->tx_frame_lock);
+ e = pd->tx_queue.next;
+ while (e != &pd->tx_queue) {
+ f = container_of(e, struct oz_tx_frame, link);
+ pkt_num = le32_to_cpu(get_unaligned(&f->hdr.pkt_num));
+ diff = (lpn - (pkt_num & OZ_LAST_PN_MASK)) & OZ_LAST_PN_MASK;
+ if (diff > OZ_LAST_PN_HALF_CYCLE)
+ break;
+ if (first == 0)
+ first = e;
+ last = e;
+ e = e->next;
+ pd->nb_queued_frames--;
+ }
+ if (first) {
+ last->next->prev = &pd->tx_queue;
+ pd->tx_queue.next = last->next;
+ last->next = 0;
+ }
+ pd->last_sent_frame = &pd->tx_queue;
+ spin_unlock(&pd->tx_frame_lock);
+ while (first) {
+ f = container_of(first, struct oz_tx_frame, link);
+ first = first->next;
+ oz_retire_frame(pd, f);
+ }
+}
+/*------------------------------------------------------------------------------
+ * Precondition: stream_lock must be held.
+ * Context: softirq
+ */
+static struct oz_isoc_stream *pd_stream_find(struct oz_pd *pd, u8 ep_num)
+{
+ struct list_head *e;
+ struct oz_isoc_stream *st;
+ list_for_each(e, &pd->stream_list) {
+ st = container_of(e, struct oz_isoc_stream, link);
+ if (st->ep_num == ep_num)
+ return st;
+ }
+ return 0;
+}
+/*------------------------------------------------------------------------------
+ * Context: softirq
+ */
+int oz_isoc_stream_create(struct oz_pd *pd, u8 ep_num)
+{
+ struct oz_isoc_stream *st =
+ kzalloc(sizeof(struct oz_isoc_stream), GFP_ATOMIC);
+ if (!st)
+ return -ENOMEM;
+ st->ep_num = ep_num;
+ spin_lock_bh(&pd->stream_lock);
+ if (!pd_stream_find(pd, ep_num)) {
+ list_add(&st->link, &pd->stream_list);
+ st = 0;
+ }
+ spin_unlock_bh(&pd->stream_lock);
+ if (st)
+ kfree(st);
+ return 0;
+}
+/*------------------------------------------------------------------------------
+ * Context: softirq or process
+ */
+static void oz_isoc_stream_free(struct oz_isoc_stream *st)
+{
+ if (st->skb)
+ kfree_skb(st->skb);
+ kfree(st);
+}
+/*------------------------------------------------------------------------------
+ * Context: softirq
+ */
+int oz_isoc_stream_delete(struct oz_pd *pd, u8 ep_num)
+{
+ struct oz_isoc_stream *st;
+ spin_lock_bh(&pd->stream_lock);
+ st = pd_stream_find(pd, ep_num);
+ if (st)
+ list_del(&st->link);
+ spin_unlock_bh(&pd->stream_lock);
+ if (st)
+ oz_isoc_stream_free(st);
+ return 0;
+}
+/*------------------------------------------------------------------------------
+ * Context: any
+ */
+static void oz_isoc_destructor(struct sk_buff *skb)
+{
+ atomic_dec(&g_submitted_isoc);
+ oz_event_log(OZ_EVT_TX_ISOC_DONE, atomic_read(&g_submitted_isoc),
+ 0, skb, 0);
+}
+/*------------------------------------------------------------------------------
+ * Context: softirq
+ */
+int oz_send_isoc_unit(struct oz_pd *pd, u8 ep_num, u8 *data, int len)
+{
+ struct net_device *dev = pd->net_dev;
+ struct oz_isoc_stream *st;
+ u8 nb_units = 0;
+ struct sk_buff *skb = 0;
+ struct oz_hdr *oz_hdr = 0;
+ int size = 0;
+ spin_lock_bh(&pd->stream_lock);
+ st = pd_stream_find(pd, ep_num);
+ if (st) {
+ skb = st->skb;
+ st->skb = 0;
+ nb_units = st->nb_units;
+ st->nb_units = 0;
+ oz_hdr = st->oz_hdr;
+ size = st->size;
+ }
+ spin_unlock_bh(&pd->stream_lock);
+ if (!st)
+ return 0;
+ if (!skb) {
+ /* Allocate enough space for max size frame. */
+ skb = alloc_skb(pd->max_tx_size + OZ_ALLOCATED_SPACE(dev),
+ GFP_ATOMIC);
+ if (skb == 0)
+ return 0;
+ /* Reserve the head room for lower layers. */
+ skb_reserve(skb, LL_RESERVED_SPACE(dev));
+ skb_reset_network_header(skb);
+ skb->dev = dev;
+ skb->protocol = htons(OZ_ETHERTYPE);
+ size = sizeof(struct oz_hdr) + sizeof(struct oz_isoc_large);
+ oz_hdr = (struct oz_hdr *)skb_put(skb, size);
+ }
+ memcpy(skb_put(skb, len), data, len);
+ size += len;
+ if (++nb_units < pd->ms_per_isoc) {
+ spin_lock_bh(&pd->stream_lock);
+ st->skb = skb;
+ st->nb_units = nb_units;
+ st->oz_hdr = oz_hdr;
+ st->size = size;
+ spin_unlock_bh(&pd->stream_lock);
+ } else {
+ struct oz_hdr oz;
+ struct oz_isoc_large iso;
+ spin_lock_bh(&pd->stream_lock);
+ iso.frame_number = st->frame_num;
+ st->frame_num += nb_units;
+ spin_unlock_bh(&pd->stream_lock);
+ oz.control =
+ (OZ_PROTOCOL_VERSION<<OZ_VERSION_SHIFT) | OZ_F_ISOC;
+ oz.last_pkt_num = pd->trigger_pkt_num & OZ_LAST_PN_MASK;
+ oz.pkt_num = 0;
+ iso.endpoint = ep_num;
+ iso.format = OZ_DATA_F_ISOC_LARGE;
+ iso.ms_data = nb_units;
+ memcpy(oz_hdr, &oz, sizeof(oz));
+ memcpy(oz_hdr+1, &iso, sizeof(iso));
+ if (dev_hard_header(skb, dev, OZ_ETHERTYPE, pd->mac_addr,
+ dev->dev_addr, skb->len) < 0) {
+ kfree_skb(skb);
+ return -1;
+ }
+ if (atomic_read(&g_submitted_isoc) < OZ_MAX_SUBMITTED_ISOC) {
+ skb->destructor = oz_isoc_destructor;
+ atomic_inc(&g_submitted_isoc);
+ oz_event_log(OZ_EVT_TX_ISOC, nb_units, iso.frame_number,
+ skb, atomic_read(&g_submitted_isoc));
+ if (dev_queue_xmit(skb) < 0)
+ return -1;
+ } else {
+ oz_event_log(OZ_EVT_TX_ISOC_DROP, 0, 0, 0, 0);
+ kfree_skb(skb);
+ }
+ }
+ return 0;
+}
+/*------------------------------------------------------------------------------
+ * Context: process
+ */
+void oz_apps_init(void)
+{
+ int i;
+ for (i = 0; i < OZ_APPID_MAX; i++)
+ if (g_app_if[i].init)
+ g_app_if[i].init();
+}
+/*------------------------------------------------------------------------------
+ * Context: process
+ */
+void oz_apps_term(void)
+{
+ int i;
+ /* Terminate all the apps. */
+ for (i = 0; i < OZ_APPID_MAX; i++)
+ if (g_app_if[i].term)
+ g_app_if[i].term();
+}
+/*------------------------------------------------------------------------------
+ * Context: softirq-serialized
+ */
+void oz_handle_app_elt(struct oz_pd *pd, u8 app_id, struct oz_elt *elt)
+{
+ struct oz_app_if *ai;
+ if (app_id == 0 || app_id > OZ_APPID_MAX)
+ return;
+ ai = &g_app_if[app_id-1];
+ ai->rx(pd, elt);
+}
+/*------------------------------------------------------------------------------
+ * Context: softirq or process
+ */
+void oz_pd_indicate_farewells(struct oz_pd *pd)
+{
+ struct oz_farewell *f;
+ struct oz_app_if *ai = &g_app_if[OZ_APPID_USB-1];
+ while (1) {
+ oz_polling_lock_bh();
+ if (list_empty(&pd->farewell_list)) {
+ oz_polling_unlock_bh();
+ break;
+ }
+ f = list_first_entry(&pd->farewell_list,
+ struct oz_farewell, link);
+ list_del(&f->link);
+ oz_polling_unlock_bh();
+ if (ai->farewell)
+ ai->farewell(pd, f->ep_num, f->report, f->len);
+ kfree(f);
+ }
+}
diff --git a/drivers/staging/ozwpan/ozpd.h b/drivers/staging/ozwpan/ozpd.h
new file mode 100644
index 000000000000..afc77f0260f0
--- /dev/null
+++ b/drivers/staging/ozwpan/ozpd.h
@@ -0,0 +1,121 @@
+/* -----------------------------------------------------------------------------
+ * Copyright (c) 2011 Ozmo Inc
+ * Released under the GNU General Public License Version 2 (GPLv2).
+ * -----------------------------------------------------------------------------
+ */
+#ifndef _OZPD_H_
+#define _OZPD_H_
+
+#include "ozeltbuf.h"
+
+/* PD state
+ */
+#define OZ_PD_S_IDLE 0x1
+#define OZ_PD_S_CONNECTED 0x2
+#define OZ_PD_S_SLEEP 0x4
+#define OZ_PD_S_STOPPED 0x8
+
+/* Timer event types.
+ */
+#define OZ_TIMER_TOUT 1
+#define OZ_TIMER_HEARTBEAT 2
+#define OZ_TIMER_STOP 3
+
+/* Data structure that hold information on a frame for transmisson. This is
+ * built when the frame is first transmitted and is used to rebuild the frame
+ * if a re-transmission is required.
+ */
+struct oz_tx_frame {
+ struct list_head link;
+ struct list_head elt_list;
+ struct oz_hdr hdr;
+ int total_size;
+};
+
+struct oz_isoc_stream {
+ struct list_head link;
+ u8 ep_num;
+ u8 frame_num;
+ u8 nb_units;
+ int size;
+ struct sk_buff *skb;
+ struct oz_hdr *oz_hdr;
+};
+
+struct oz_farewell {
+ struct list_head link;
+ u8 ep_num;
+ u8 index;
+ u8 report[1];
+ u8 len;
+};
+
+/* Data structure that holds information on a specific peripheral device (PD).
+ */
+struct oz_pd {
+ struct list_head link;
+ atomic_t ref_count;
+ u8 mac_addr[ETH_ALEN];
+ unsigned state;
+ unsigned state_flags;
+ unsigned send_flags;
+ u16 total_apps;
+ u16 paused_apps;
+ u8 session_id;
+ u8 param_rsp_status;
+ u8 pd_info;
+ u8 isoc_sent;
+ u32 last_rx_pkt_num;
+ u32 last_tx_pkt_num;
+ u32 trigger_pkt_num;
+ unsigned long pulse_time_j;
+ unsigned long timeout_time_j;
+ unsigned long pulse_period_j;
+ unsigned long presleep_j;
+ unsigned long keep_alive_j;
+ unsigned long last_rx_time_j;
+ struct oz_elt_buf elt_buff;
+ void *app_ctx[OZ_APPID_MAX];
+ spinlock_t app_lock[OZ_APPID_MAX];
+ int max_tx_size;
+ u8 heartbeat_requested;
+ u8 mode;
+ u8 ms_per_isoc;
+ unsigned max_stream_buffering;
+ int nb_queued_frames;
+ struct list_head *tx_pool;
+ int tx_pool_count;
+ spinlock_t tx_frame_lock;
+ struct list_head *last_sent_frame;
+ struct list_head tx_queue;
+ struct list_head farewell_list;
+ spinlock_t stream_lock;
+ struct list_head stream_list;
+ struct net_device *net_dev;
+};
+
+#define OZ_MAX_QUEUED_FRAMES 4
+
+struct oz_pd *oz_pd_alloc(u8 *mac_addr);
+void oz_pd_destroy(struct oz_pd *pd);
+void oz_pd_get(struct oz_pd *pd);
+void oz_pd_put(struct oz_pd *pd);
+void oz_pd_set_state(struct oz_pd *pd, unsigned state);
+void oz_pd_indicate_farewells(struct oz_pd *pd);
+int oz_pd_sleep(struct oz_pd *pd);
+void oz_pd_stop(struct oz_pd *pd);
+void oz_pd_heartbeat(struct oz_pd *pd, u16 apps);
+int oz_services_start(struct oz_pd *pd, u16 apps, int resume);
+void oz_services_stop(struct oz_pd *pd, u16 apps, int pause);
+int oz_prepare_frame(struct oz_pd *pd, int empty);
+void oz_send_queued_frames(struct oz_pd *pd, int backlog);
+void oz_retire_tx_frames(struct oz_pd *pd, u8 lpn);
+int oz_isoc_stream_create(struct oz_pd *pd, u8 ep_num);
+int oz_isoc_stream_delete(struct oz_pd *pd, u8 ep_num);
+int oz_send_isoc_unit(struct oz_pd *pd, u8 ep_num, u8 *data, int len);
+void oz_handle_app_elt(struct oz_pd *pd, u8 app_id, struct oz_elt *elt);
+void oz_apps_init(void);
+void oz_apps_term(void);
+
+#endif /* Sentry */
+
diff --git a/drivers/staging/ozwpan/ozproto.c b/drivers/staging/ozwpan/ozproto.c
new file mode 100644
index 000000000000..ad857eeabbb7
--- /dev/null
+++ b/drivers/staging/ozwpan/ozproto.c
@@ -0,0 +1,957 @@
+/* -----------------------------------------------------------------------------
+ * Copyright (c) 2011 Ozmo Inc
+ * Released under the GNU General Public License Version 2 (GPLv2).
+ * -----------------------------------------------------------------------------
+ */
+#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/timer.h>
+#include <linux/sched.h>
+#include <linux/netdevice.h>
+#include <linux/errno.h>
+#include <linux/ieee80211.h>
+#include "ozconfig.h"
+#include "ozprotocol.h"
+#include "ozeltbuf.h"
+#include "ozpd.h"
+#include "ozproto.h"
+#include "ozusbsvc.h"
+#include "oztrace.h"
+#include "ozappif.h"
+#include "ozevent.h"
+#include <asm/unaligned.h>
+#include <linux/uaccess.h>
+#include <net/psnap.h>
+/*------------------------------------------------------------------------------
+ */
+#define OZ_CF_CONN_SUCCESS 1
+#define OZ_CF_CONN_FAILURE 2
+
+#define OZ_DO_STOP 1
+#define OZ_DO_SLEEP 2
+
+/* States of the timer.
+ */
+#define OZ_TIMER_IDLE 0
+#define OZ_TIMER_SET 1
+#define OZ_TIMER_IN_HANDLER 2
+
+#define OZ_MAX_TIMER_POOL_SIZE 16
+
+/*------------------------------------------------------------------------------
+ */
+struct oz_binding {
+ struct packet_type ptype;
+ char name[OZ_MAX_BINDING_LEN];
+ struct oz_binding *next;
+};
+
+struct oz_timer {
+ struct list_head link;
+ struct oz_pd *pd;
+ unsigned long due_time;
+ int type;
+};
+/*------------------------------------------------------------------------------
+ * Static external variables.
+ */
+static DEFINE_SPINLOCK(g_polling_lock);
+static LIST_HEAD(g_pd_list);
+static struct oz_binding *g_binding ;
+static DEFINE_SPINLOCK(g_binding_lock);
+static struct sk_buff_head g_rx_queue;
+static u8 g_session_id;
+static u16 g_apps = 0x1;
+static int g_processing_rx;
+static struct timer_list g_timer;
+static struct oz_timer *g_cur_timer;
+static struct list_head *g_timer_pool;
+static int g_timer_pool_count;
+static int g_timer_state = OZ_TIMER_IDLE;
+static LIST_HEAD(g_timer_list);
+/*------------------------------------------------------------------------------
+ */
+static void oz_protocol_timer_start(void);
+/*------------------------------------------------------------------------------
+ * Context: softirq-serialized
+ */
+static u8 oz_get_new_session_id(u8 exclude)
+{
+ if (++g_session_id == 0)
+ g_session_id = 1;
+ if (g_session_id == exclude) {
+ if (++g_session_id == 0)
+ g_session_id = 1;
+ }
+ return g_session_id;
+}
+/*------------------------------------------------------------------------------
+ * Context: softirq-serialized
+ */
+static void oz_send_conn_rsp(struct oz_pd *pd, u8 status)
+{
+ struct sk_buff *skb;
+ struct net_device *dev = pd->net_dev;
+ struct oz_hdr *oz_hdr;
+ struct oz_elt *elt;
+ struct oz_elt_connect_rsp *body;
+ int sz = sizeof(struct oz_hdr) + sizeof(struct oz_elt) +
+ sizeof(struct oz_elt_connect_rsp);
+ skb = alloc_skb(sz + OZ_ALLOCATED_SPACE(dev), GFP_ATOMIC);
+ if (skb == 0)
+ return;
+ skb_reserve(skb, LL_RESERVED_SPACE(dev));
+ skb_reset_network_header(skb);
+ oz_hdr = (struct oz_hdr *)skb_put(skb, sz);
+ elt = (struct oz_elt *)(oz_hdr+1);
+ body = (struct oz_elt_connect_rsp *)(elt+1);
+ skb->dev = dev;
+ skb->protocol = htons(OZ_ETHERTYPE);
+ /* Fill in device header */
+ if (dev_hard_header(skb, dev, OZ_ETHERTYPE, pd->mac_addr,
+ dev->dev_addr, skb->len) < 0) {
+ kfree_skb(skb);
+ return;
+ }
+ oz_hdr->control = (OZ_PROTOCOL_VERSION<<OZ_VERSION_SHIFT);
+ oz_hdr->last_pkt_num = 0;
+ put_unaligned(0, &oz_hdr->pkt_num);
+ oz_event_log(OZ_EVT_CONNECT_RSP, 0, 0, 0, 0);
+ elt->type = OZ_ELT_CONNECT_RSP;
+ elt->length = sizeof(struct oz_elt_connect_rsp);
+ memset(body, 0, sizeof(struct oz_elt_connect_rsp));
+ body->status = status;
+ if (status == 0) {
+ body->mode = pd->mode;
+ body->session_id = pd->session_id;
+ put_unaligned(cpu_to_le16(pd->total_apps), &body->apps);
+ }
+ oz_trace("TX: OZ_ELT_CONNECT_RSP %d", status);
+ dev_queue_xmit(skb);
+ return;
+}
+/*------------------------------------------------------------------------------
+ * Context: softirq-serialized
+ */
+static void pd_set_keepalive(struct oz_pd *pd, u8 kalive)
+{
+ unsigned long keep_alive = kalive & OZ_KALIVE_VALUE_MASK;
+
+ switch (kalive & OZ_KALIVE_TYPE_MASK) {
+ case OZ_KALIVE_SPECIAL:
+ pd->keep_alive_j =
+ oz_ms_to_jiffies(keep_alive * 1000*60*60*24*20);
+ break;
+ case OZ_KALIVE_SECS:
+ pd->keep_alive_j = oz_ms_to_jiffies(keep_alive*1000);
+ break;
+ case OZ_KALIVE_MINS:
+ pd->keep_alive_j = oz_ms_to_jiffies(keep_alive*1000*60);
+ break;
+ case OZ_KALIVE_HOURS:
+ pd->keep_alive_j = oz_ms_to_jiffies(keep_alive*1000*60*60);
+ break;
+ default:
+ pd->keep_alive_j = 0;
+ }
+ oz_trace("Keepalive = %lu jiffies\n", pd->keep_alive_j);
+}
+/*------------------------------------------------------------------------------
+ * Context: softirq-serialized
+ */
+static void pd_set_presleep(struct oz_pd *pd, u8 presleep)
+{
+ if (presleep)
+ pd->presleep_j = oz_ms_to_jiffies(presleep*100);
+ else
+ pd->presleep_j = OZ_PRESLEEP_TOUT_J;
+ oz_trace("Presleep time = %lu jiffies\n", pd->presleep_j);
+}
+/*------------------------------------------------------------------------------
+ * Context: softirq-serialized
+ */
+static struct oz_pd *oz_connect_req(struct oz_pd *cur_pd, struct oz_elt *elt,
+ u8 *pd_addr, struct net_device *net_dev)
+{
+ struct oz_pd *pd;
+ struct oz_elt_connect_req *body =
+ (struct oz_elt_connect_req *)(elt+1);
+ u8 rsp_status = OZ_STATUS_SUCCESS;
+ u8 stop_needed = 0;
+ u16 new_apps = g_apps;
+ struct net_device *old_net_dev = 0;
+ struct oz_pd *free_pd = 0;
+ if (cur_pd) {
+ pd = cur_pd;
+ spin_lock_bh(&g_polling_lock);
+ } else {
+ struct oz_pd *pd2 = 0;
+ struct list_head *e;
+ pd = oz_pd_alloc(pd_addr);
+ if (pd == 0)
+ return 0;
+ pd->last_rx_time_j = jiffies;
+ spin_lock_bh(&g_polling_lock);
+ list_for_each(e, &g_pd_list) {
+ pd2 = container_of(e, struct oz_pd, link);
+ if (memcmp(pd2->mac_addr, pd_addr, ETH_ALEN) == 0) {
+ free_pd = pd;
+ pd = pd2;
+ break;
+ }
+ }
+ if (pd != pd2)
+ list_add_tail(&pd->link, &g_pd_list);
+ }
+ if (pd == 0) {
+ spin_unlock_bh(&g_polling_lock);
+ return 0;
+ }
+ if (pd->net_dev != net_dev) {
+ old_net_dev = pd->net_dev;
+ dev_hold(net_dev);
+ pd->net_dev = net_dev;
+ }
+ oz_trace("Host vendor: %d\n", body->host_vendor);
+ pd->max_tx_size = OZ_MAX_TX_SIZE;
+ pd->mode = body->mode;
+ pd->pd_info = body->pd_info;
+ if (pd->mode & OZ_F_ISOC_NO_ELTS) {
+ pd->mode |= OZ_F_ISOC_ANYTIME;
+ pd->ms_per_isoc = body->ms_per_isoc;
+ if (!pd->ms_per_isoc)
+ pd->ms_per_isoc = 4;
+ }
+ if (body->max_len_div16)
+ pd->max_tx_size = ((u16)body->max_len_div16)<<4;
+ oz_trace("Max frame:%u Ms per isoc:%u\n",
+ pd->max_tx_size, pd->ms_per_isoc);
+ pd->max_stream_buffering = 3*1024;
+ pd->timeout_time_j = jiffies + OZ_CONNECTION_TOUT_J;
+ pd->pulse_period_j = OZ_QUANTUM_J;
+ pd_set_presleep(pd, body->presleep);
+ pd_set_keepalive(pd, body->keep_alive);
+
+ new_apps &= le16_to_cpu(get_unaligned(&body->apps));
+ if ((new_apps & 0x1) && (body->session_id)) {
+ if (pd->session_id) {
+ if (pd->session_id != body->session_id) {
+ rsp_status = OZ_STATUS_SESSION_MISMATCH;
+ goto done;
+ }
+ } else {
+ new_apps &= ~0x1; /* Resume not permitted */
+ pd->session_id =
+ oz_get_new_session_id(body->session_id);
+ }
+ } else {
+ if (pd->session_id && !body->session_id) {
+ rsp_status = OZ_STATUS_SESSION_TEARDOWN;
+ stop_needed = 1;
+ } else {
+ new_apps &= ~0x1; /* Resume not permitted */
+ pd->session_id =
+ oz_get_new_session_id(body->session_id);
+ }
+ }
+done:
+ if (rsp_status == OZ_STATUS_SUCCESS) {
+ u16 start_apps = new_apps & ~pd->total_apps & ~0x1;
+ u16 stop_apps = pd->total_apps & ~new_apps & ~0x1;
+ u16 resume_apps = new_apps & pd->paused_apps & ~0x1;
+ spin_unlock_bh(&g_polling_lock);
+ oz_pd_set_state(pd, OZ_PD_S_CONNECTED);
+ oz_timer_delete(pd, OZ_TIMER_STOP);
+ oz_trace("new_apps=0x%x total_apps=0x%x paused_apps=0x%x\n",
+ new_apps, pd->total_apps, pd->paused_apps);
+ if (start_apps) {
+ if (oz_services_start(pd, start_apps, 0))
+ rsp_status = OZ_STATUS_TOO_MANY_PDS;
+ }
+ if (resume_apps)
+ if (oz_services_start(pd, resume_apps, 1))
+ rsp_status = OZ_STATUS_TOO_MANY_PDS;
+ if (stop_apps)
+ oz_services_stop(pd, stop_apps, 0);
+ oz_pd_request_heartbeat(pd);
+ } else {
+ spin_unlock_bh(&g_polling_lock);
+ }
+ oz_send_conn_rsp(pd, rsp_status);
+ if (rsp_status != OZ_STATUS_SUCCESS) {
+ if (stop_needed)
+ oz_pd_stop(pd);
+ oz_pd_put(pd);
+ pd = 0;
+ }
+ if (old_net_dev)
+ dev_put(old_net_dev);
+ if (free_pd)
+ oz_pd_destroy(free_pd);
+ return pd;
+}
+/*------------------------------------------------------------------------------
+ * Context: softirq-serialized
+ */
+static void oz_add_farewell(struct oz_pd *pd, u8 ep_num, u8 index,
+ u8 *report, u8 len)
+{
+ struct oz_farewell *f;
+ struct oz_farewell *f2;
+ int found = 0;
+ f = kmalloc(sizeof(struct oz_farewell) + len - 1, GFP_ATOMIC);
+ if (!f)
+ return;
+ f->ep_num = ep_num;
+ f->index = index;
+ memcpy(f->report, report, len);
+ oz_trace("RX: Adding farewell report\n");
+ spin_lock(&g_polling_lock);
+ list_for_each_entry(f2, &pd->farewell_list, link) {
+ if ((f2->ep_num == ep_num) && (f2->index == index)) {
+ found = 1;
+ list_del(&f2->link);
+ break;
+ }
+ }
+ list_add_tail(&f->link, &pd->farewell_list);
+ spin_unlock(&g_polling_lock);
+ if (found)
+ kfree(f2);
+}
+/*------------------------------------------------------------------------------
+ * Context: softirq-serialized
+ */
+static void oz_rx_frame(struct sk_buff *skb)
+{
+ u8 *mac_hdr;
+ u8 *src_addr;
+ struct oz_elt *elt;
+ int length;
+ struct oz_pd *pd = 0;
+ struct oz_hdr *oz_hdr = (struct oz_hdr *)skb_network_header(skb);
+ int dup = 0;
+ u32 pkt_num;
+
+ oz_event_log(OZ_EVT_RX_PROCESS, 0,
+ (((u16)oz_hdr->control)<<8)|oz_hdr->last_pkt_num,
+ 0, oz_hdr->pkt_num);
+ oz_trace2(OZ_TRACE_RX_FRAMES,
+ "RX frame PN=0x%x LPN=0x%x control=0x%x\n",
+ oz_hdr->pkt_num, oz_hdr->last_pkt_num, oz_hdr->control);
+ mac_hdr = skb_mac_header(skb);
+ src_addr = &mac_hdr[ETH_ALEN] ;
+ length = skb->len;
+
+ /* Check the version field */
+ if (oz_get_prot_ver(oz_hdr->control) != OZ_PROTOCOL_VERSION) {
+ oz_trace("Incorrect protocol version: %d\n",
+ oz_get_prot_ver(oz_hdr->control));
+ goto done;
+ }
+
+ pkt_num = le32_to_cpu(get_unaligned(&oz_hdr->pkt_num));
+
+ pd = oz_pd_find(src_addr);
+ if (pd) {
+ pd->last_rx_time_j = jiffies;
+ oz_timer_add(pd, OZ_TIMER_TOUT,
+ pd->last_rx_time_j + pd->presleep_j, 1);
+ if (pkt_num != pd->last_rx_pkt_num) {
+ pd->last_rx_pkt_num = pkt_num;
+ } else {
+ dup = 1;
+ oz_trace("Duplicate frame\n");
+ }
+ }
+
+ if (pd && !dup && ((pd->mode & OZ_MODE_MASK) == OZ_MODE_TRIGGERED)) {
+ pd->last_sent_frame = &pd->tx_queue;
+ if (oz_hdr->control & OZ_F_ACK) {
+ /* Retire completed frames */
+ oz_retire_tx_frames(pd, oz_hdr->last_pkt_num);
+ }
+ if ((oz_hdr->control & OZ_F_ACK_REQUESTED) &&
+ (pd->state == OZ_PD_S_CONNECTED)) {
+ int backlog = pd->nb_queued_frames;
+ pd->trigger_pkt_num = pkt_num;
+ /* Send queued frames */
+ while (oz_prepare_frame(pd, 0) >= 0)
+ ;
+ oz_send_queued_frames(pd, backlog);
+ }
+ }
+
+ length -= sizeof(struct oz_hdr);
+ elt = (struct oz_elt *)((u8 *)oz_hdr + sizeof(struct oz_hdr));
+
+ while (length >= sizeof(struct oz_elt)) {
+ length -= sizeof(struct oz_elt) + elt->length;
+ if (length < 0)
+ break;
+ switch (elt->type) {
+ case OZ_ELT_CONNECT_REQ:
+ oz_event_log(OZ_EVT_CONNECT_REQ, 0, 0, 0, 0);
+ oz_trace("RX: OZ_ELT_CONNECT_REQ\n");
+ pd = oz_connect_req(pd, elt, src_addr, skb->dev);
+ break;
+ case OZ_ELT_DISCONNECT:
+ oz_trace("RX: OZ_ELT_DISCONNECT\n");
+ if (pd)
+ oz_pd_sleep(pd);
+ break;
+ case OZ_ELT_UPDATE_PARAM_REQ: {
+ struct oz_elt_update_param *body =
+ (struct oz_elt_update_param *)(elt + 1);
+ oz_trace("RX: OZ_ELT_UPDATE_PARAM_REQ\n");
+ if (pd && (pd->state & OZ_PD_S_CONNECTED)) {
+ spin_lock(&g_polling_lock);
+ pd_set_keepalive(pd, body->keepalive);
+ pd_set_presleep(pd, body->presleep);
+ spin_unlock(&g_polling_lock);
+ }
+ }
+ break;
+ case OZ_ELT_FAREWELL_REQ: {
+ struct oz_elt_farewell *body =
+ (struct oz_elt_farewell *)(elt + 1);
+ oz_trace("RX: OZ_ELT_FAREWELL_REQ\n");
+ oz_add_farewell(pd, body->ep_num,
+ body->index, body->report,
+ elt->length + 1 - sizeof(*body));
+ }
+ break;
+ case OZ_ELT_APP_DATA:
+ if (pd && (pd->state & OZ_PD_S_CONNECTED)) {
+ struct oz_app_hdr *app_hdr =
+ (struct oz_app_hdr *)(elt+1);
+ if (dup)
+ break;
+ oz_handle_app_elt(pd, app_hdr->app_id, elt);
+ }
+ break;
+ default:
+ oz_trace("RX: Unknown elt %02x\n", elt->type);
+ }
+ elt = oz_next_elt(elt);
+ }
+done:
+ if (pd)
+ oz_pd_put(pd);
+ consume_skb(skb);
+}
+/*------------------------------------------------------------------------------
+ * Context: process
+ */
+void oz_protocol_term(void)
+{
+ struct list_head *chain = 0;
+ del_timer_sync(&g_timer);
+ /* Walk the list of bindings and remove each one.
+ */
+ spin_lock_bh(&g_binding_lock);
+ while (g_binding) {
+ struct oz_binding *b = g_binding;
+ g_binding = b->next;
+ spin_unlock_bh(&g_binding_lock);
+ dev_remove_pack(&b->ptype);
+ if (b->ptype.dev)
+ dev_put(b->ptype.dev);
+ kfree(b);
+ spin_lock_bh(&g_binding_lock);
+ }
+ spin_unlock_bh(&g_binding_lock);
+ /* Walk the list of PDs and stop each one. This causes the PD to be
+ * removed from the list so we can just pull each one from the head
+ * of the list.
+ */
+ spin_lock_bh(&g_polling_lock);
+ while (!list_empty(&g_pd_list)) {
+ struct oz_pd *pd =
+ list_first_entry(&g_pd_list, struct oz_pd, link);
+ oz_pd_get(pd);
+ spin_unlock_bh(&g_polling_lock);
+ oz_pd_stop(pd);
+ oz_pd_put(pd);
+ spin_lock_bh(&g_polling_lock);
+ }
+ chain = g_timer_pool;
+ g_timer_pool = 0;
+ spin_unlock_bh(&g_polling_lock);
+ while (chain) {
+ struct oz_timer *t = container_of(chain, struct oz_timer, link);
+ chain = chain->next;
+ kfree(t);
+ }
+ oz_trace("Protocol stopped\n");
+}
+/*------------------------------------------------------------------------------
+ * Context: softirq
+ */
+static void oz_pd_handle_timer(struct oz_pd *pd, int type)
+{
+ switch (type) {
+ case OZ_TIMER_TOUT:
+ oz_pd_sleep(pd);
+ break;
+ case OZ_TIMER_STOP:
+ oz_pd_stop(pd);
+ break;
+ case OZ_TIMER_HEARTBEAT: {
+ u16 apps = 0;
+ spin_lock_bh(&g_polling_lock);
+ pd->heartbeat_requested = 0;
+ if (pd->state & OZ_PD_S_CONNECTED)
+ apps = pd->total_apps;
+ spin_unlock_bh(&g_polling_lock);
+ if (apps)
+ oz_pd_heartbeat(pd, apps);
+ }
+ break;
+ }
+}
+/*------------------------------------------------------------------------------
+ * Context: softirq
+ */
+static void oz_protocol_timer(unsigned long arg)
+{
+ struct oz_timer *t;
+ struct oz_timer *t2;
+ struct oz_pd *pd;
+ spin_lock_bh(&g_polling_lock);
+ if (!g_cur_timer) {
+ /* This happens if we remove the current timer but can't stop
+ * the timer from firing. In this case just get out.
+ */
+ oz_event_log(OZ_EVT_TIMER, 0, 0, 0, 0);
+ spin_unlock_bh(&g_polling_lock);
+ return;
+ }
+ g_timer_state = OZ_TIMER_IN_HANDLER;
+ t = g_cur_timer;
+ g_cur_timer = 0;
+ list_del(&t->link);
+ spin_unlock_bh(&g_polling_lock);
+ do {
+ pd = t->pd;
+ oz_event_log(OZ_EVT_TIMER, 0, t->type, 0, 0);
+ oz_pd_handle_timer(pd, t->type);
+ spin_lock_bh(&g_polling_lock);
+ if (g_timer_pool_count < OZ_MAX_TIMER_POOL_SIZE) {
+ t->link.next = g_timer_pool;
+ g_timer_pool = &t->link;
+ g_timer_pool_count++;
+ t = 0;
+ }
+ if (!list_empty(&g_timer_list)) {
+ t2 = container_of(g_timer_list.next,
+ struct oz_timer, link);
+ if (time_before_eq(t2->due_time, jiffies))
+ list_del(&t2->link);
+ else
+ t2 = 0;
+ } else {
+ t2 = 0;
+ }
+ spin_unlock_bh(&g_polling_lock);
+ oz_pd_put(pd);
+ if (t)
+ kfree(t);
+ t = t2;
+ } while (t);
+ g_timer_state = OZ_TIMER_IDLE;
+ oz_protocol_timer_start();
+}
+/*------------------------------------------------------------------------------
+ * Context: softirq
+ */
+static void oz_protocol_timer_start(void)
+{
+ spin_lock_bh(&g_polling_lock);
+ if (!list_empty(&g_timer_list)) {
+ g_cur_timer =
+ container_of(g_timer_list.next, struct oz_timer, link);
+ if (g_timer_state == OZ_TIMER_SET) {
+ oz_event_log(OZ_EVT_TIMER_CTRL, 3,
+ (u16)g_cur_timer->type, 0,
+ (unsigned)g_cur_timer->due_time);
+ mod_timer(&g_timer, g_cur_timer->due_time);
+ } else {
+ oz_event_log(OZ_EVT_TIMER_CTRL, 4,
+ (u16)g_cur_timer->type, 0,
+ (unsigned)g_cur_timer->due_time);
+ g_timer.expires = g_cur_timer->due_time;
+ g_timer.function = oz_protocol_timer;
+ g_timer.data = 0;
+ add_timer(&g_timer);
+ }
+ g_timer_state = OZ_TIMER_SET;
+ } else {
+ oz_trace("No queued timers\n");
+ }
+ spin_unlock_bh(&g_polling_lock);
+}
+/*------------------------------------------------------------------------------
+ * Context: softirq or process
+ */
+void oz_timer_add(struct oz_pd *pd, int type, unsigned long due_time,
+ int remove)
+{
+ struct list_head *e;
+ struct oz_timer *t = 0;
+ int restart_needed = 0;
+ oz_event_log(OZ_EVT_TIMER_CTRL, 1, (u16)type, 0, (unsigned)due_time);
+ spin_lock(&g_polling_lock);
+ if (remove) {
+ list_for_each(e, &g_timer_list) {
+ t = container_of(e, struct oz_timer, link);
+ if ((t->pd == pd) && (t->type == type)) {
+ if (g_cur_timer == t) {
+ restart_needed = 1;
+ g_cur_timer = 0;
+ }
+ list_del(e);
+ break;
+ }
+ t = 0;
+ }
+ }
+ if (!t) {
+ if (g_timer_pool) {
+ t = container_of(g_timer_pool, struct oz_timer, link);
+ g_timer_pool = g_timer_pool->next;
+ g_timer_pool_count--;
+ } else {
+ t = kmalloc(sizeof(struct oz_timer), GFP_ATOMIC);
+ }
+ if (t) {
+ t->pd = pd;
+ t->type = type;
+ oz_pd_get(pd);
+ }
+ }
+ if (t) {
+ struct oz_timer *t2;
+ t->due_time = due_time;
+ list_for_each(e, &g_timer_list) {
+ t2 = container_of(e, struct oz_timer, link);
+ if (time_before(due_time, t2->due_time)) {
+ if (t2 == g_cur_timer) {
+ g_cur_timer = 0;
+ restart_needed = 1;
+ }
+ break;
+ }
+ }
+ list_add_tail(&t->link, e);
+ }
+ if (g_timer_state == OZ_TIMER_IDLE)
+ restart_needed = 1;
+ else if (g_timer_state == OZ_TIMER_IN_HANDLER)
+ restart_needed = 0;
+ spin_unlock(&g_polling_lock);
+ if (restart_needed)
+ oz_protocol_timer_start();
+}
+/*------------------------------------------------------------------------------
+ * Context: softirq or process
+ */
+void oz_timer_delete(struct oz_pd *pd, int type)
+{
+ struct list_head *chain = 0;
+ struct oz_timer *t;
+ struct oz_timer *n;
+ int restart_needed = 0;
+ int release = 0;
+ oz_event_log(OZ_EVT_TIMER_CTRL, 2, (u16)type, 0, 0);
+ spin_lock(&g_polling_lock);
+ list_for_each_entry_safe(t, n, &g_timer_list, link) {
+ if ((t->pd == pd) && ((type == 0) || (t->type == type))) {
+ if (g_cur_timer == t) {
+ restart_needed = 1;
+ g_cur_timer = 0;
+ del_timer(&g_timer);
+ }
+ list_del(&t->link);
+ release++;
+ if (g_timer_pool_count < OZ_MAX_TIMER_POOL_SIZE) {
+ t->link.next = g_timer_pool;
+ g_timer_pool = &t->link;
+ g_timer_pool_count++;
+ } else {
+ t->link.next = chain;
+ chain = &t->link;
+ }
+ if (type)
+ break;
+ }
+ }
+ if (g_timer_state == OZ_TIMER_IN_HANDLER)
+ restart_needed = 0;
+ else if (restart_needed)
+ g_timer_state = OZ_TIMER_IDLE;
+ spin_unlock(&g_polling_lock);
+ if (restart_needed)
+ oz_protocol_timer_start();
+ while (release--)
+ oz_pd_put(pd);
+ while (chain) {
+ t = container_of(chain, struct oz_timer, link);
+ chain = chain->next;
+ kfree(t);
+ }
+}
+/*------------------------------------------------------------------------------
+ * Context: softirq or process
+ */
+void oz_pd_request_heartbeat(struct oz_pd *pd)
+{
+ unsigned long now = jiffies;
+ unsigned long t;
+ spin_lock(&g_polling_lock);
+ if (pd->heartbeat_requested) {
+ spin_unlock(&g_polling_lock);
+ return;
+ }
+ if (pd->pulse_period_j)
+ t = ((now / pd->pulse_period_j) + 1) * pd->pulse_period_j;
+ else
+ t = now + 1;
+ pd->heartbeat_requested = 1;
+ spin_unlock(&g_polling_lock);
+ oz_timer_add(pd, OZ_TIMER_HEARTBEAT, t, 0);
+}
+/*------------------------------------------------------------------------------
+ * Context: softirq or process
+ */
+struct oz_pd *oz_pd_find(u8 *mac_addr)
+{
+ struct oz_pd *pd;
+ struct list_head *e;
+ spin_lock_bh(&g_polling_lock);
+ list_for_each(e, &g_pd_list) {
+ pd = container_of(e, struct oz_pd, link);
+ if (memcmp(pd->mac_addr, mac_addr, ETH_ALEN) == 0) {
+ atomic_inc(&pd->ref_count);
+ spin_unlock_bh(&g_polling_lock);
+ return pd;
+ }
+ }
+ spin_unlock_bh(&g_polling_lock);
+ return 0;
+}
+/*------------------------------------------------------------------------------
+ * Context: process
+ */
+void oz_app_enable(int app_id, int enable)
+{
+ if (app_id <= OZ_APPID_MAX) {
+ spin_lock_bh(&g_polling_lock);
+ if (enable)
+ g_apps |= (1<<app_id);
+ else
+ g_apps &= ~(1<<app_id);
+ spin_unlock_bh(&g_polling_lock);
+ }
+}
+/*------------------------------------------------------------------------------
+ * Context: softirq
+ */
+static int oz_pkt_recv(struct sk_buff *skb, struct net_device *dev,
+ struct packet_type *pt, struct net_device *orig_dev)
+{
+ oz_event_log(OZ_EVT_RX_FRAME, 0, 0, 0, 0);
+ skb = skb_share_check(skb, GFP_ATOMIC);
+ if (skb == 0)
+ return 0;
+ spin_lock_bh(&g_rx_queue.lock);
+ if (g_processing_rx) {
+ /* We already hold the lock so use __ variant.
+ */
+ __skb_queue_head(&g_rx_queue, skb);
+ spin_unlock_bh(&g_rx_queue.lock);
+ } else {
+ g_processing_rx = 1;
+ do {
+
+ spin_unlock_bh(&g_rx_queue.lock);
+ oz_rx_frame(skb);
+ spin_lock_bh(&g_rx_queue.lock);
+ if (skb_queue_empty(&g_rx_queue)) {
+ g_processing_rx = 0;
+ spin_unlock_bh(&g_rx_queue.lock);
+ break;
+ }
+ /* We already hold the lock so use __ variant.
+ */
+ skb = __skb_dequeue(&g_rx_queue);
+ } while (1);
+ }
+ return 0;
+}
+/*------------------------------------------------------------------------------
+ * Context: process
+ */
+void oz_binding_add(char *net_dev)
+{
+ struct oz_binding *binding;
+
+ binding = kmalloc(sizeof(struct oz_binding), GFP_ATOMIC);
+ if (binding) {
+ binding->ptype.type = __constant_htons(OZ_ETHERTYPE);
+ binding->ptype.func = oz_pkt_recv;
+ memcpy(binding->name, net_dev, OZ_MAX_BINDING_LEN);
+ if (net_dev && *net_dev) {
+ oz_trace("Adding binding: %s\n", net_dev);
+ binding->ptype.dev =
+ dev_get_by_name(&init_net, net_dev);
+ if (binding->ptype.dev == 0) {
+ oz_trace("Netdev %s not found\n", net_dev);
+ kfree(binding);
+ binding = 0;
+ }
+ } else {
+ oz_trace("Binding to all netcards\n");
+ binding->ptype.dev = 0;
+ }
+ if (binding) {
+ dev_add_pack(&binding->ptype);
+ spin_lock_bh(&g_binding_lock);
+ binding->next = g_binding;
+ g_binding = binding;
+ spin_unlock_bh(&g_binding_lock);
+ }
+ }
+}
+/*------------------------------------------------------------------------------
+ * Context: process
+ */
+static int compare_binding_name(char *s1, char *s2)
+{
+ int i;
+ for (i = 0; i < OZ_MAX_BINDING_LEN; i++) {
+ if (*s1 != *s2)
+ return 0;
+ if (!*s1++)
+ return 1;
+ s2++;
+ }
+ return 1;
+}
+/*------------------------------------------------------------------------------
+ * Context: process
+ */
+static void pd_stop_all_for_device(struct net_device *net_dev)
+{
+ struct list_head h;
+ struct oz_pd *pd;
+ struct oz_pd *n;
+ INIT_LIST_HEAD(&h);
+ spin_lock_bh(&g_polling_lock);
+ list_for_each_entry_safe(pd, n, &g_pd_list, link) {
+ if (pd->net_dev == net_dev) {
+ list_move(&pd->link, &h);
+ oz_pd_get(pd);
+ }
+ }
+ spin_unlock_bh(&g_polling_lock);
+ while (!list_empty(&h)) {
+ pd = list_first_entry(&h, struct oz_pd, link);
+ oz_pd_stop(pd);
+ oz_pd_put(pd);
+ }
+}
+/*------------------------------------------------------------------------------
+ * Context: process
+ */
+void oz_binding_remove(char *net_dev)
+{
+ struct oz_binding *binding = 0;
+ struct oz_binding **link;
+ oz_trace("Removing binding: %s\n", net_dev);
+ spin_lock_bh(&g_binding_lock);
+ binding = g_binding;
+ link = &g_binding;
+ while (binding) {
+ if (compare_binding_name(binding->name, net_dev)) {
+ oz_trace("Binding '%s' found\n", net_dev);
+ *link = binding->next;
+ break;
+ } else {
+ link = &binding;
+ binding = binding->next;
+ }
+ }
+ spin_unlock_bh(&g_binding_lock);
+ if (binding) {
+ dev_remove_pack(&binding->ptype);
+ if (binding->ptype.dev) {
+ dev_put(binding->ptype.dev);
+ pd_stop_all_for_device(binding->ptype.dev);
+ }
+ kfree(binding);
+ }
+}
+/*------------------------------------------------------------------------------
+ * Context: process
+ */
+static char *oz_get_next_device_name(char *s, char *dname, int max_size)
+{
+ while (*s == ',')
+ s++;
+ while (*s && (*s != ',') && max_size > 1) {
+ *dname++ = *s++;
+ max_size--;
+ }
+ *dname = 0;
+ return s;
+}
+/*------------------------------------------------------------------------------
+ * Context: process
+ */
+int oz_protocol_init(char *devs)
+{
+ skb_queue_head_init(&g_rx_queue);
+ if (devs && (devs[0] == '*')) {
+ oz_binding_add(0);
+ } else {
+ char d[32];
+ while (*devs) {
+ devs = oz_get_next_device_name(devs, d, sizeof(d));
+ if (d[0])
+ oz_binding_add(d);
+ }
+ }
+ init_timer(&g_timer);
+ return 0;
+}
+/*------------------------------------------------------------------------------
+ * Context: process
+ */
+int oz_get_pd_list(struct oz_mac_addr *addr, int max_count)
+{
+ struct oz_pd *pd;
+ struct list_head *e;
+ int count = 0;
+ spin_lock_bh(&g_polling_lock);
+ list_for_each(e, &g_pd_list) {
+ if (count >= max_count)
+ break;
+ pd = container_of(e, struct oz_pd, link);
+ memcpy(&addr[count++], pd->mac_addr, ETH_ALEN);
+ }
+ spin_unlock_bh(&g_polling_lock);
+ return count;
+}
+/*------------------------------------------------------------------------------
+*/
+void oz_polling_lock_bh(void)
+{
+ spin_lock_bh(&g_polling_lock);
+}
+/*------------------------------------------------------------------------------
+*/
+void oz_polling_unlock_bh(void)
+{
+ spin_unlock_bh(&g_polling_lock);
+}
diff --git a/drivers/staging/ozwpan/ozproto.h b/drivers/staging/ozwpan/ozproto.h
new file mode 100644
index 000000000000..89aea28bd8d5
--- /dev/null
+++ b/drivers/staging/ozwpan/ozproto.h
@@ -0,0 +1,69 @@
+/* -----------------------------------------------------------------------------
+ * Copyright (c) 2011 Ozmo Inc
+ * Released under the GNU General Public License Version 2 (GPLv2).
+ * -----------------------------------------------------------------------------
+ */
+#ifndef _OZPROTO_H
+#define _OZPROTO_H
+
+#include <asm/byteorder.h>
+#include "ozconfig.h"
+#include "ozappif.h"
+
+#define OZ_ALLOCATED_SPACE(__x) (LL_RESERVED_SPACE(__x)+(__x)->needed_tailroom)
+
+/* Converts millisecs to jiffies.
+ */
+#define oz_ms_to_jiffies(__x) (((__x)*1000)/HZ)
+
+/* Quantum milliseconds.
+ */
+#define OZ_QUANTUM_MS 8
+/* Quantum jiffies
+ */
+#define OZ_QUANTUM_J (oz_ms_to_jiffies(OZ_QUANTUM_MS))
+/* Default timeouts.
+ */
+#define OZ_CONNECTION_TOUT_J (2*HZ)
+#define OZ_PRESLEEP_TOUT_J (11*HZ)
+
+/* Maximun sizes of tx frames. */
+#define OZ_MAX_TX_SIZE 1514
+
+/* Application handler functions.
+ */
+typedef int (*oz_app_init_fn_t)(void);
+typedef void (*oz_app_term_fn_t)(void);
+typedef int (*oz_app_start_fn_t)(struct oz_pd *pd, int resume);
+typedef void (*oz_app_stop_fn_t)(struct oz_pd *pd, int pause);
+typedef void (*oz_app_rx_fn_t)(struct oz_pd *pd, struct oz_elt *elt);
+typedef int (*oz_app_hearbeat_fn_t)(struct oz_pd *pd);
+typedef void (*oz_app_farewell_fn_t)(struct oz_pd *pd, u8 ep_num,
+ u8 *data, u8 len);
+
+struct oz_app_if {
+ oz_app_init_fn_t init;
+ oz_app_term_fn_t term;
+ oz_app_start_fn_t start;
+ oz_app_stop_fn_t stop;
+ oz_app_rx_fn_t rx;
+ oz_app_hearbeat_fn_t heartbeat;
+ oz_app_farewell_fn_t farewell;
+ int app_id;
+};
+
+int oz_protocol_init(char *devs);
+void oz_protocol_term(void);
+int oz_get_pd_list(struct oz_mac_addr *addr, int max_count);
+void oz_app_enable(int app_id, int enable);
+struct oz_pd *oz_pd_find(u8 *mac_addr);
+void oz_binding_add(char *net_dev);
+void oz_binding_remove(char *net_dev);
+void oz_timer_add(struct oz_pd *pd, int type, unsigned long due_time,
+ int remove);
+void oz_timer_delete(struct oz_pd *pd, int type);
+void oz_pd_request_heartbeat(struct oz_pd *pd);
+void oz_polling_lock_bh(void);
+void oz_polling_unlock_bh(void);
+
+#endif /* _OZPROTO_H */
diff --git a/drivers/staging/ozwpan/ozprotocol.h b/drivers/staging/ozwpan/ozprotocol.h
new file mode 100644
index 000000000000..b3e7d77f3fff
--- /dev/null
+++ b/drivers/staging/ozwpan/ozprotocol.h
@@ -0,0 +1,372 @@
+/* -----------------------------------------------------------------------------
+ * Copyright (c) 2011 Ozmo Inc
+ * Released under the GNU General Public License Version 2 (GPLv2).
+ * -----------------------------------------------------------------------------
+ */
+#ifndef _OZPROTOCOL_H
+#define _OZPROTOCOL_H
+
+#define PACKED __packed
+
+#define OZ_ETHERTYPE 0x892e
+
+/* Status codes
+ */
+#define OZ_STATUS_SUCCESS 0
+#define OZ_STATUS_INVALID_PARAM 1
+#define OZ_STATUS_TOO_MANY_PDS 2
+#define OZ_STATUS_NOT_ALLOWED 4
+#define OZ_STATUS_SESSION_MISMATCH 5
+#define OZ_STATUS_SESSION_TEARDOWN 6
+
+/* This is the generic element header.
+ Every element starts with this.
+ */
+struct oz_elt {
+ u8 type;
+ u8 length;
+} PACKED;
+
+#define oz_next_elt(__elt) \
+ (struct oz_elt *)((u8 *)((__elt) + 1) + (__elt)->length)
+
+/* Protocol element IDs.
+ */
+#define OZ_ELT_CONNECT_REQ 0x06
+#define OZ_ELT_CONNECT_RSP 0x07
+#define OZ_ELT_DISCONNECT 0x08
+#define OZ_ELT_UPDATE_PARAM_REQ 0x11
+#define OZ_ELT_FAREWELL_REQ 0x12
+#define OZ_ELT_APP_DATA 0x31
+
+/* This is the Ozmo header which is the first Ozmo specific part
+ * of a frame and comes after the MAC header.
+ */
+struct oz_hdr {
+ u8 control;
+ u8 last_pkt_num;
+ u32 pkt_num;
+} PACKED;
+
+#define OZ_PROTOCOL_VERSION 0x1
+/* Bits in the control field. */
+#define OZ_VERSION_MASK 0xc
+#define OZ_VERSION_SHIFT 2
+#define OZ_F_ACK 0x10
+#define OZ_F_ISOC 0x20
+#define OZ_F_MORE_DATA 0x40
+#define OZ_F_ACK_REQUESTED 0x80
+
+#define oz_get_prot_ver(__x) (((__x) & OZ_VERSION_MASK) >> OZ_VERSION_SHIFT)
+
+/* Used to select the bits of packet number to put in the last_pkt_num.
+ */
+#define OZ_LAST_PN_MASK 0x00ff
+
+#define OZ_LAST_PN_HALF_CYCLE 127
+
+/* Connect request data structure.
+ */
+struct oz_elt_connect_req {
+ u8 mode;
+ u8 resv1[16];
+ u8 pd_info;
+ u8 session_id;
+ u8 presleep;
+ u8 resv2;
+ u8 host_vendor;
+ u8 keep_alive;
+ u16 apps;
+ u8 max_len_div16;
+ u8 ms_per_isoc;
+ u8 resv3[2];
+} PACKED;
+
+/* mode field bits.
+ */
+#define OZ_MODE_POLLED 0x0
+#define OZ_MODE_TRIGGERED 0x1
+#define OZ_MODE_MASK 0xf
+#define OZ_F_ISOC_NO_ELTS 0x40
+#define OZ_F_ISOC_ANYTIME 0x80
+
+/* Keep alive field.
+ */
+#define OZ_KALIVE_TYPE_MASK 0xc0
+#define OZ_KALIVE_VALUE_MASK 0x3f
+#define OZ_KALIVE_SPECIAL 0x00
+#define OZ_KALIVE_SECS 0x40
+#define OZ_KALIVE_MINS 0x80
+#define OZ_KALIVE_HOURS 0xc0
+
+/* Connect response data structure.
+ */
+struct oz_elt_connect_rsp {
+ u8 mode;
+ u8 status;
+ u8 resv1[3];
+ u8 session_id;
+ u16 apps;
+ u32 resv2;
+} PACKED;
+
+struct oz_elt_farewell {
+ u8 ep_num;
+ u8 index;
+ u8 report[1];
+} PACKED;
+
+struct oz_elt_update_param {
+ u8 resv1[16];
+ u8 presleep;
+ u8 resv2;
+ u8 host_vendor;
+ u8 keepalive;
+} PACKED;
+
+/* Header common to all application elements.
+ */
+struct oz_app_hdr {
+ u8 app_id;
+ u8 elt_seq_num;
+} PACKED;
+
+/* Values for app_id.
+ */
+#define OZ_APPID_USB 0x1
+#define OZ_APPID_UNUSED1 0x2
+#define OZ_APPID_UNUSED2 0x3
+#define OZ_APPID_SERIAL 0x4
+#define OZ_APPID_MAX OZ_APPID_SERIAL
+#define OZ_NB_APPS (OZ_APPID_MAX+1)
+
+/* USB header common to all elements for the USB application.
+ * This header extends the oz_app_hdr and comes directly after
+ * the element header in a USB application.
+ */
+struct oz_usb_hdr {
+ u8 app_id;
+ u8 elt_seq_num;
+ u8 type;
+} PACKED;
+
+
+
+/* USB requests element subtypes (type field of hs_usb_hdr).
+ */
+#define OZ_GET_DESC_REQ 1
+#define OZ_GET_DESC_RSP 2
+#define OZ_SET_CONFIG_REQ 3
+#define OZ_SET_CONFIG_RSP 4
+#define OZ_SET_INTERFACE_REQ 5
+#define OZ_SET_INTERFACE_RSP 6
+#define OZ_VENDOR_CLASS_REQ 7
+#define OZ_VENDOR_CLASS_RSP 8
+#define OZ_GET_STATUS_REQ 9
+#define OZ_GET_STATUS_RSP 10
+#define OZ_CLEAR_FEATURE_REQ 11
+#define OZ_CLEAR_FEATURE_RSP 12
+#define OZ_SET_FEATURE_REQ 13
+#define OZ_SET_FEATURE_RSP 14
+#define OZ_GET_CONFIGURATION_REQ 15
+#define OZ_GET_CONFIGURATION_RSP 16
+#define OZ_GET_INTERFACE_REQ 17
+#define OZ_GET_INTERFACE_RSP 18
+#define OZ_SYNCH_FRAME_REQ 19
+#define OZ_SYNCH_FRAME_RSP 20
+#define OZ_USB_ENDPOINT_DATA 23
+
+#define OZ_REQD_D2H 0x80
+
+struct oz_get_desc_req {
+ u8 app_id;
+ u8 elt_seq_num;
+ u8 type;
+ u8 req_id;
+ u16 offset;
+ u16 size;
+ u8 req_type;
+ u8 desc_type;
+ u16 w_index;
+ u8 index;
+} PACKED;
+
+/* Values for desc_type field.
+*/
+#define OZ_DESC_DEVICE 0x01
+#define OZ_DESC_CONFIG 0x02
+#define OZ_DESC_STRING 0x03
+
+/* Values for req_type field.
+ */
+#define OZ_RECP_MASK 0x1F
+#define OZ_RECP_DEVICE 0x00
+#define OZ_RECP_INTERFACE 0x01
+#define OZ_RECP_ENDPOINT 0x02
+
+#define OZ_REQT_MASK 0x60
+#define OZ_REQT_STD 0x00
+#define OZ_REQT_CLASS 0x20
+#define OZ_REQT_VENDOR 0x40
+
+struct oz_get_desc_rsp {
+ u8 app_id;
+ u8 elt_seq_num;
+ u8 type;
+ u8 req_id;
+ u16 offset;
+ u16 total_size;
+ u8 rcode;
+ u8 data[1];
+} PACKED;
+
+struct oz_feature_req {
+ u8 app_id;
+ u8 elt_seq_num;
+ u8 type;
+ u8 req_id;
+ u8 recipient;
+ u8 index;
+ u16 feature;
+} PACKED;
+
+struct oz_feature_rsp {
+ u8 app_id;
+ u8 elt_seq_num;
+ u8 type;
+ u8 req_id;
+ u8 rcode;
+} PACKED;
+
+struct oz_set_config_req {
+ u8 app_id;
+ u8 elt_seq_num;
+ u8 type;
+ u8 req_id;
+ u8 index;
+} PACKED;
+
+struct oz_set_config_rsp {
+ u8 app_id;
+ u8 elt_seq_num;
+ u8 type;
+ u8 req_id;
+ u8 rcode;
+} PACKED;
+
+struct oz_set_interface_req {
+ u8 app_id;
+ u8 elt_seq_num;
+ u8 type;
+ u8 req_id;
+ u8 index;
+ u8 alternative;
+} PACKED;
+
+struct oz_set_interface_rsp {
+ u8 app_id;
+ u8 elt_seq_num;
+ u8 type;
+ u8 req_id;
+ u8 rcode;
+} PACKED;
+
+struct oz_get_interface_req {
+ u8 app_id;
+ u8 elt_seq_num;
+ u8 type;
+ u8 req_id;
+ u8 index;
+} PACKED;
+
+struct oz_get_interface_rsp {
+ u8 app_id;
+ u8 elt_seq_num;
+ u8 type;
+ u8 req_id;
+ u8 rcode;
+ u8 alternative;
+} PACKED;
+
+struct oz_vendor_class_req {
+ u8 app_id;
+ u8 elt_seq_num;
+ u8 type;
+ u8 req_id;
+ u8 req_type;
+ u8 request;
+ u16 value;
+ u16 index;
+ u8 data[1];
+} PACKED;
+
+struct oz_vendor_class_rsp {
+ u8 app_id;
+ u8 elt_seq_num;
+ u8 type;
+ u8 req_id;
+ u8 rcode;
+ u8 data[1];
+} PACKED;
+
+struct oz_data {
+ u8 app_id;
+ u8 elt_seq_num;
+ u8 type;
+ u8 endpoint;
+ u8 format;
+} PACKED;
+
+struct oz_isoc_fixed {
+ u8 app_id;
+ u8 elt_seq_num;
+ u8 type;
+ u8 endpoint;
+ u8 format;
+ u8 unit_size;
+ u8 frame_number;
+ u8 data[1];
+} PACKED;
+
+struct oz_multiple_fixed {
+ u8 app_id;
+ u8 elt_seq_num;
+ u8 type;
+ u8 endpoint;
+ u8 format;
+ u8 unit_size;
+ u8 data[1];
+} PACKED;
+
+struct oz_fragmented {
+ u8 app_id;
+ u8 elt_seq_num;
+ u8 type;
+ u8 endpoint;
+ u8 format;
+ u16 total_size;
+ u16 offset;
+ u8 data[1];
+} PACKED;
+
+/* Note: the following does not get packaged in an element in the same way
+ * that other data formats are packaged. Instead the data is put in a frame
+ * directly after the oz_header and is the only permitted data in such a
+ * frame. The length of the data is directly determined from the frame size.
+ */
+struct oz_isoc_large {
+ u8 endpoint;
+ u8 format;
+ u8 ms_data;
+ u8 frame_number;
+} PACKED;
+
+#define OZ_DATA_F_TYPE_MASK 0xF
+#define OZ_DATA_F_MULTIPLE_FIXED 0x1
+#define OZ_DATA_F_MULTIPLE_VAR 0x2
+#define OZ_DATA_F_ISOC_FIXED 0x3
+#define OZ_DATA_F_ISOC_VAR 0x4
+#define OZ_DATA_F_FRAGMENTED 0x5
+#define OZ_DATA_F_ISOC_LARGE 0x7
+
+#endif /* _OZPROTOCOL_H */
diff --git a/drivers/staging/ozwpan/oztrace.c b/drivers/staging/ozwpan/oztrace.c
new file mode 100644
index 000000000000..353ead24fd7d
--- /dev/null
+++ b/drivers/staging/ozwpan/oztrace.c
@@ -0,0 +1,36 @@
+/* -----------------------------------------------------------------------------
+ * Copyright (c) 2011 Ozmo Inc
+ * Released under the GNU General Public License Version 2 (GPLv2).
+ * -----------------------------------------------------------------------------
+ */
+#include "ozconfig.h"
+#include "oztrace.h"
+
+#ifdef WANT_VERBOSE_TRACE
+unsigned long trace_flags =
+ 0
+#ifdef WANT_TRACE_STREAM
+ | OZ_TRACE_STREAM
+#endif /* WANT_TRACE_STREAM */
+#ifdef WANT_TRACE_URB
+ | OZ_TRACE_URB
+#endif /* WANT_TRACE_URB */
+
+#ifdef WANT_TRACE_CTRL_DETAIL
+ | OZ_TRACE_CTRL_DETAIL
+#endif /* WANT_TRACE_CTRL_DETAIL */
+
+#ifdef WANT_TRACE_HUB
+ | OZ_TRACE_HUB
+#endif /* WANT_TRACE_HUB */
+
+#ifdef WANT_TRACE_RX_FRAMES
+ | OZ_TRACE_RX_FRAMES
+#endif /* WANT_TRACE_RX_FRAMES */
+
+#ifdef WANT_TRACE_TX_FRAMES
+ | OZ_TRACE_TX_FRAMES
+#endif /* WANT_TRACE_TX_FRAMES */
+ ;
+#endif /* WANT_VERBOSE_TRACE */
+
diff --git a/drivers/staging/ozwpan/oztrace.h b/drivers/staging/ozwpan/oztrace.h
new file mode 100644
index 000000000000..8293b24c5a77
--- /dev/null
+++ b/drivers/staging/ozwpan/oztrace.h
@@ -0,0 +1,35 @@
+/* -----------------------------------------------------------------------------
+ * Copyright (c) 2011 Ozmo Inc
+ * Released under the GNU General Public License Version 2 (GPLv2).
+ * -----------------------------------------------------------------------------
+ */
+#ifndef _OZTRACE_H_
+#define _OZTRACE_H_
+#include "ozconfig.h"
+
+#define TRACE_PREFIX KERN_ALERT "OZWPAN: "
+
+#ifdef WANT_TRACE
+#define oz_trace(...) printk(TRACE_PREFIX __VA_ARGS__)
+#ifdef WANT_VERBOSE_TRACE
+extern unsigned long trace_flags;
+#define oz_trace2(_flag, ...) \
+ do { if (trace_flags & _flag) printk(TRACE_PREFIX __VA_ARGS__); \
+ } while (0)
+#else
+#define oz_trace2(...)
+#endif /* #ifdef WANT_VERBOSE_TRACE */
+#else
+#define oz_trace(...)
+#define oz_trace2(...)
+#endif /* #ifdef WANT_TRACE */
+
+#define OZ_TRACE_STREAM 0x1
+#define OZ_TRACE_URB 0x2
+#define OZ_TRACE_CTRL_DETAIL 0x4
+#define OZ_TRACE_HUB 0x8
+#define OZ_TRACE_RX_FRAMES 0x10
+#define OZ_TRACE_TX_FRAMES 0x20
+
+#endif /* Sentry */
+
diff --git a/drivers/staging/ozwpan/ozurbparanoia.c b/drivers/staging/ozwpan/ozurbparanoia.c
new file mode 100644
index 000000000000..55b9afbbe47b
--- /dev/null
+++ b/drivers/staging/ozwpan/ozurbparanoia.c
@@ -0,0 +1,53 @@
+/* -----------------------------------------------------------------------------
+ * Copyright (c) 2011 Ozmo Inc
+ * Released under the GNU General Public License Version 2 (GPLv2).
+ * -----------------------------------------------------------------------------
+ */
+#include <linux/usb.h>
+#include "ozconfig.h"
+#ifdef WANT_URB_PARANOIA
+#include "ozurbparanoia.h"
+#include "oztrace.h"
+/*-----------------------------------------------------------------------------
+ */
+#define OZ_MAX_URBS 1000
+struct urb *g_urb_memory[OZ_MAX_URBS];
+int g_nb_urbs;
+DEFINE_SPINLOCK(g_urb_mem_lock);
+/*-----------------------------------------------------------------------------
+ */
+void oz_remember_urb(struct urb *urb)
+{
+ unsigned long irq_state;
+ spin_lock_irqsave(&g_urb_mem_lock, irq_state);
+ if (g_nb_urbs < OZ_MAX_URBS) {
+ g_urb_memory[g_nb_urbs++] = urb;
+ oz_trace("%lu: urb up = %d %p\n", jiffies, g_nb_urbs, urb);
+ } else {
+ oz_trace("ERROR urb buffer full\n");
+ }
+ spin_unlock_irqrestore(&g_urb_mem_lock, irq_state);
+}
+/*------------------------------------------------------------------------------
+ */
+int oz_forget_urb(struct urb *urb)
+{
+ unsigned long irq_state;
+ int i;
+ int rc = -1;
+ spin_lock_irqsave(&g_urb_mem_lock, irq_state);
+ for (i = 0; i < g_nb_urbs; i++) {
+ if (g_urb_memory[i] == urb) {
+ rc = 0;
+ if (--g_nb_urbs > i)
+ memcpy(&g_urb_memory[i], &g_urb_memory[i+1],
+ (g_nb_urbs - i) * sizeof(struct urb *));
+ oz_trace("%lu: urb down = %d %p\n",
+ jiffies, g_nb_urbs, urb);
+ }
+ }
+ spin_unlock_irqrestore(&g_urb_mem_lock, irq_state);
+ return rc;
+}
+#endif /* #ifdef WANT_URB_PARANOIA */
+
diff --git a/drivers/staging/ozwpan/ozurbparanoia.h b/drivers/staging/ozwpan/ozurbparanoia.h
new file mode 100644
index 000000000000..00f5a3a81bc8
--- /dev/null
+++ b/drivers/staging/ozwpan/ozurbparanoia.h
@@ -0,0 +1,19 @@
+#ifndef _OZURBPARANOIA_H
+#define _OZURBPARANOIA_H
+/* -----------------------------------------------------------------------------
+ * Released under the GNU General Public License Version 2 (GPLv2).
+ * Copyright (c) 2011 Ozmo Inc
+ * -----------------------------------------------------------------------------
+ */
+
+#ifdef WANT_URB_PARANOIA
+void oz_remember_urb(struct urb *urb);
+int oz_forget_urb(struct urb *urb);
+#else
+#define oz_remember_urb(__x)
+#define oz_forget_urb(__x) 0
+#endif /* WANT_URB_PARANOIA */
+
+
+#endif /* _OZURBPARANOIA_H */
+
diff --git a/drivers/staging/ozwpan/ozusbif.h b/drivers/staging/ozwpan/ozusbif.h
new file mode 100644
index 000000000000..3acf5980d7cc
--- /dev/null
+++ b/drivers/staging/ozwpan/ozusbif.h
@@ -0,0 +1,43 @@
+/* -----------------------------------------------------------------------------
+ * Copyright (c) 2011 Ozmo Inc
+ * Released under the GNU General Public License Version 2 (GPLv2).
+ * -----------------------------------------------------------------------------
+ */
+#ifndef _OZUSBIF_H
+#define _OZUSBIF_H
+
+#include <linux/usb.h>
+
+/* Reference counting functions.
+ */
+void oz_usb_get(void *hpd);
+void oz_usb_put(void *hpd);
+
+/* Stream functions.
+ */
+int oz_usb_stream_create(void *hpd, u8 ep_num);
+int oz_usb_stream_delete(void *hpd, u8 ep_num);
+
+/* Request functions.
+ */
+int oz_usb_control_req(void *hpd, u8 req_id, struct usb_ctrlrequest *setup,
+ u8 *data, int data_len);
+int oz_usb_get_desc_req(void *hpd, u8 req_id, u8 req_type, u8 desc_type,
+ u8 index, u16 windex, int offset, int len);
+int oz_usb_send_isoc(void *hpd, u8 ep_num, struct urb *urb);
+void oz_usb_request_heartbeat(void *hpd);
+
+/* Confirmation functions.
+ */
+void oz_hcd_get_desc_cnf(void *hport, u8 req_id, int status,
+ u8 *desc, int length, int offset, int total_size);
+void oz_hcd_control_cnf(void *hport, u8 req_id, u8 rcode,
+ u8 *data, int data_len);
+
+/* Indication functions.
+ */
+void oz_hcd_data_ind(void *hport, u8 endpoint, u8 *data, int data_len);
+
+int oz_hcd_heartbeat(void *hport);
+
+#endif /* _OZUSBIF_H */
diff --git a/drivers/staging/ozwpan/ozusbsvc.c b/drivers/staging/ozwpan/ozusbsvc.c
new file mode 100644
index 000000000000..9e74f9602384
--- /dev/null
+++ b/drivers/staging/ozwpan/ozusbsvc.c
@@ -0,0 +1,245 @@
+/* -----------------------------------------------------------------------------
+ * Copyright (c) 2011 Ozmo Inc
+ * Released under the GNU General Public License Version 2 (GPLv2).
+ *
+ * This file provides protocol independent part of the implementation of the USB
+ * service for a PD.
+ * The implementation of this service is split into two parts the first of which
+ * is protocol independent and the second contains protocol specific details.
+ * This split is to allow alternative protocols to be defined.
+ * The implemenation of this service uses ozhcd.c to implement a USB HCD.
+ * -----------------------------------------------------------------------------
+ */
+#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/timer.h>
+#include <linux/sched.h>
+#include <linux/netdevice.h>
+#include <linux/errno.h>
+#include <linux/input.h>
+#include <asm/unaligned.h>
+#include "ozconfig.h"
+#include "ozprotocol.h"
+#include "ozeltbuf.h"
+#include "ozpd.h"
+#include "ozproto.h"
+#include "ozusbif.h"
+#include "ozhcd.h"
+#include "oztrace.h"
+#include "ozusbsvc.h"
+#include "ozevent.h"
+/*------------------------------------------------------------------------------
+ * This is called once when the driver is loaded to initialise the USB service.
+ * Context: process
+ */
+int oz_usb_init(void)
+{
+ oz_event_log(OZ_EVT_SERVICE, 1, OZ_APPID_USB, 0, 0);
+ return oz_hcd_init();
+}
+/*------------------------------------------------------------------------------
+ * This is called once when the driver is unloaded to terminate the USB service.
+ * Context: process
+ */
+void oz_usb_term(void)
+{
+ oz_event_log(OZ_EVT_SERVICE, 2, OZ_APPID_USB, 0, 0);
+ oz_hcd_term();
+}
+/*------------------------------------------------------------------------------
+ * This is called when the USB service is started or resumed for a PD.
+ * Context: softirq
+ */
+int oz_usb_start(struct oz_pd *pd, int resume)
+{
+ int rc = 0;
+ struct oz_usb_ctx *usb_ctx;
+ struct oz_usb_ctx *old_ctx = 0;
+ oz_event_log(OZ_EVT_SERVICE, 3, OZ_APPID_USB, 0, resume);
+ if (resume) {
+ oz_trace("USB service resumed.\n");
+ return 0;
+ }
+ oz_trace("USB service started.\n");
+ /* Create a USB context in case we need one. If we find the PD already
+ * has a USB context then we will destroy it.
+ */
+ usb_ctx = kzalloc(sizeof(struct oz_usb_ctx), GFP_ATOMIC);
+ if (usb_ctx == 0)
+ return -ENOMEM;
+ atomic_set(&usb_ctx->ref_count, 1);
+ usb_ctx->pd = pd;
+ usb_ctx->stopped = 0;
+ /* Install the USB context if the PD doesn't already have one.
+ * If it does already have one then destroy the one we have just
+ * created.
+ */
+ spin_lock_bh(&pd->app_lock[OZ_APPID_USB-1]);
+ old_ctx = pd->app_ctx[OZ_APPID_USB-1];
+ if (old_ctx == 0)
+ pd->app_ctx[OZ_APPID_USB-1] = usb_ctx;
+ oz_usb_get(pd->app_ctx[OZ_APPID_USB-1]);
+ spin_unlock_bh(&pd->app_lock[OZ_APPID_USB-1]);
+ if (old_ctx) {
+ oz_trace("Already have USB context.\n");
+ kfree(usb_ctx);
+ usb_ctx = old_ctx;
+ } else if (usb_ctx) {
+ /* Take a reference to the PD. This will be released when
+ * the USB context is destroyed.
+ */
+ oz_pd_get(pd);
+ }
+ /* If we already had a USB context and had obtained a port from
+ * the USB HCD then just reset the port. If we didn't have a port
+ * then report the arrival to the USB HCD so we get one.
+ */
+ if (usb_ctx->hport) {
+ oz_hcd_pd_reset(usb_ctx, usb_ctx->hport);
+ } else {
+ usb_ctx->hport = oz_hcd_pd_arrived(usb_ctx);
+ if (usb_ctx->hport == 0) {
+ oz_trace("USB hub returned null port.\n");
+ spin_lock_bh(&pd->app_lock[OZ_APPID_USB-1]);
+ pd->app_ctx[OZ_APPID_USB-1] = 0;
+ spin_unlock_bh(&pd->app_lock[OZ_APPID_USB-1]);
+ oz_usb_put(usb_ctx);
+ rc = -1;
+ }
+ }
+ oz_usb_put(usb_ctx);
+ return rc;
+}
+/*------------------------------------------------------------------------------
+ * This is called when the USB service is stopped or paused for a PD.
+ * Context: softirq or process
+ */
+void oz_usb_stop(struct oz_pd *pd, int pause)
+{
+ struct oz_usb_ctx *usb_ctx;
+ oz_event_log(OZ_EVT_SERVICE, 4, OZ_APPID_USB, 0, pause);
+ if (pause) {
+ oz_trace("USB service paused.\n");
+ return;
+ }
+ spin_lock_bh(&pd->app_lock[OZ_APPID_USB-1]);
+ usb_ctx = (struct oz_usb_ctx *)pd->app_ctx[OZ_APPID_USB-1];
+ pd->app_ctx[OZ_APPID_USB-1] = 0;
+ spin_unlock_bh(&pd->app_lock[OZ_APPID_USB-1]);
+ if (usb_ctx) {
+ unsigned long tout = jiffies + HZ;
+ oz_trace("USB service stopping...\n");
+ usb_ctx->stopped = 1;
+ /* At this point the reference count on the usb context should
+ * be 2 - one from when we created it and one from the hcd
+ * which claims a reference. Since stopped = 1 no one else
+ * should get in but someone may already be in. So wait
+ * until they leave but timeout after 1 second.
+ */
+ while ((atomic_read(&usb_ctx->ref_count) > 2) &&
+ time_before(jiffies, tout))
+ ;
+ oz_trace("USB service stopped.\n");
+ oz_hcd_pd_departed(usb_ctx->hport);
+ /* Release the reference taken in oz_usb_start.
+ */
+ oz_usb_put(usb_ctx);
+ }
+}
+/*------------------------------------------------------------------------------
+ * This increments the reference count of the context area for a specific PD.
+ * This ensures this context area does not disappear while still in use.
+ * Context: softirq
+ */
+void oz_usb_get(void *hpd)
+{
+ struct oz_usb_ctx *usb_ctx = (struct oz_usb_ctx *)hpd;
+ atomic_inc(&usb_ctx->ref_count);
+}
+/*------------------------------------------------------------------------------
+ * This decrements the reference count of the context area for a specific PD
+ * and destroys the context area if the reference count becomes zero.
+ * Context: softirq or process
+ */
+void oz_usb_put(void *hpd)
+{
+ struct oz_usb_ctx *usb_ctx = (struct oz_usb_ctx *)hpd;
+ if (atomic_dec_and_test(&usb_ctx->ref_count)) {
+ oz_trace("Dealloc USB context.\n");
+ oz_pd_put(usb_ctx->pd);
+ kfree(usb_ctx);
+ }
+}
+/*------------------------------------------------------------------------------
+ * Context: softirq
+ */
+int oz_usb_heartbeat(struct oz_pd *pd)
+{
+ struct oz_usb_ctx *usb_ctx;
+ int rc = 0;
+ spin_lock_bh(&pd->app_lock[OZ_APPID_USB-1]);
+ usb_ctx = (struct oz_usb_ctx *)pd->app_ctx[OZ_APPID_USB-1];
+ if (usb_ctx)
+ oz_usb_get(usb_ctx);
+ spin_unlock_bh(&pd->app_lock[OZ_APPID_USB-1]);
+ if (usb_ctx == 0)
+ return rc;
+ if (usb_ctx->stopped)
+ goto done;
+ if (usb_ctx->hport)
+ if (oz_hcd_heartbeat(usb_ctx->hport))
+ rc = 1;
+done:
+ oz_usb_put(usb_ctx);
+ return rc;
+}
+/*------------------------------------------------------------------------------
+ * Context: softirq
+ */
+int oz_usb_stream_create(void *hpd, u8 ep_num)
+{
+ struct oz_usb_ctx *usb_ctx = (struct oz_usb_ctx *)hpd;
+ struct oz_pd *pd = usb_ctx->pd;
+ oz_trace("oz_usb_stream_create(0x%x)\n", ep_num);
+ if (pd->mode & OZ_F_ISOC_NO_ELTS) {
+ oz_isoc_stream_create(pd, ep_num);
+ } else {
+ oz_pd_get(pd);
+ if (oz_elt_stream_create(&pd->elt_buff, ep_num,
+ 4*pd->max_tx_size)) {
+ oz_pd_put(pd);
+ return -1;
+ }
+ }
+ return 0;
+}
+/*------------------------------------------------------------------------------
+ * Context: softirq
+ */
+int oz_usb_stream_delete(void *hpd, u8 ep_num)
+{
+ struct oz_usb_ctx *usb_ctx = (struct oz_usb_ctx *)hpd;
+ if (usb_ctx) {
+ struct oz_pd *pd = usb_ctx->pd;
+ if (pd) {
+ oz_trace("oz_usb_stream_delete(0x%x)\n", ep_num);
+ if (pd->mode & OZ_F_ISOC_NO_ELTS) {
+ oz_isoc_stream_delete(pd, ep_num);
+ } else {
+ if (oz_elt_stream_delete(&pd->elt_buff, ep_num))
+ return -1;
+ oz_pd_put(pd);
+ }
+ }
+ }
+ return 0;
+}
+/*------------------------------------------------------------------------------
+ * Context: softirq or process
+ */
+void oz_usb_request_heartbeat(void *hpd)
+{
+ struct oz_usb_ctx *usb_ctx = (struct oz_usb_ctx *)hpd;
+ if (usb_ctx && usb_ctx->pd)
+ oz_pd_request_heartbeat(usb_ctx->pd);
+}
diff --git a/drivers/staging/ozwpan/ozusbsvc.h b/drivers/staging/ozwpan/ozusbsvc.h
new file mode 100644
index 000000000000..58e05a59be31
--- /dev/null
+++ b/drivers/staging/ozwpan/ozusbsvc.h
@@ -0,0 +1,32 @@
+/* -----------------------------------------------------------------------------
+ * Copyright (c) 2011 Ozmo Inc
+ * Released under the GNU General Public License Version 2 (GPLv2).
+ * -----------------------------------------------------------------------------
+ */
+#ifndef _OZUSBSVC_H
+#define _OZUSBSVC_H
+
+/*------------------------------------------------------------------------------
+ * Per PD context info stored in application context area of PD.
+ * This object is reference counted to ensure it doesn't disappear while
+ * still in use.
+ */
+struct oz_usb_ctx {
+ atomic_t ref_count;
+ u8 tx_seq_num;
+ u8 rx_seq_num;
+ struct oz_pd *pd;
+ void *hport;
+ int stopped;
+};
+
+int oz_usb_init(void);
+void oz_usb_term(void);
+int oz_usb_start(struct oz_pd *pd, int resume);
+void oz_usb_stop(struct oz_pd *pd, int pause);
+void oz_usb_rx(struct oz_pd *pd, struct oz_elt *elt);
+int oz_usb_heartbeat(struct oz_pd *pd);
+void oz_usb_farewell(struct oz_pd *pd, u8 ep_num, u8 *data, u8 len);
+
+#endif /* _OZUSBSVC_H */
+
diff --git a/drivers/staging/ozwpan/ozusbsvc1.c b/drivers/staging/ozwpan/ozusbsvc1.c
new file mode 100644
index 000000000000..66bd576bb5e9
--- /dev/null
+++ b/drivers/staging/ozwpan/ozusbsvc1.c
@@ -0,0 +1,437 @@
+/* -----------------------------------------------------------------------------
+ * Copyright (c) 2011 Ozmo Inc
+ * Released under the GNU General Public License Version 2 (GPLv2).
+ *
+ * This file implements the protocol specific parts of the USB service for a PD.
+ * -----------------------------------------------------------------------------
+ */
+#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/timer.h>
+#include <linux/sched.h>
+#include <linux/netdevice.h>
+#include <linux/errno.h>
+#include <linux/input.h>
+#include <asm/unaligned.h>
+#include "ozconfig.h"
+#include "ozprotocol.h"
+#include "ozeltbuf.h"
+#include "ozpd.h"
+#include "ozproto.h"
+#include "ozusbif.h"
+#include "ozhcd.h"
+#include "oztrace.h"
+#include "ozusbsvc.h"
+#include "ozevent.h"
+/*------------------------------------------------------------------------------
+ */
+#define MAX_ISOC_FIXED_DATA (253-sizeof(struct oz_isoc_fixed))
+/*------------------------------------------------------------------------------
+ * Context: softirq
+ */
+static int oz_usb_submit_elt(struct oz_elt_buf *eb, struct oz_elt_info *ei,
+ struct oz_usb_ctx *usb_ctx, u8 strid, u8 isoc)
+{
+ int ret;
+ struct oz_elt *elt = (struct oz_elt *)ei->data;
+ struct oz_app_hdr *app_hdr = (struct oz_app_hdr *)(elt+1);
+ elt->type = OZ_ELT_APP_DATA;
+ ei->app_id = OZ_APPID_USB;
+ ei->length = elt->length + sizeof(struct oz_elt);
+ app_hdr->app_id = OZ_APPID_USB;
+ spin_lock_bh(&eb->lock);
+ if (isoc == 0) {
+ app_hdr->elt_seq_num = usb_ctx->tx_seq_num++;
+ if (usb_ctx->tx_seq_num == 0)
+ usb_ctx->tx_seq_num = 1;
+ }
+ ret = oz_queue_elt_info(eb, isoc, strid, ei);
+ if (ret)
+ oz_elt_info_free(eb, ei);
+ spin_unlock_bh(&eb->lock);
+ return ret;
+}
+/*------------------------------------------------------------------------------
+ * Context: softirq
+ */
+int oz_usb_get_desc_req(void *hpd, u8 req_id, u8 req_type, u8 desc_type,
+ u8 index, u16 windex, int offset, int len)
+{
+ struct oz_usb_ctx *usb_ctx = (struct oz_usb_ctx *)hpd;
+ struct oz_pd *pd = usb_ctx->pd;
+ struct oz_elt *elt;
+ struct oz_get_desc_req *body;
+ struct oz_elt_buf *eb = &pd->elt_buff;
+ struct oz_elt_info *ei = oz_elt_info_alloc(&pd->elt_buff);
+ oz_trace(" req_type = 0x%x\n", req_type);
+ oz_trace(" desc_type = 0x%x\n", desc_type);
+ oz_trace(" index = 0x%x\n", index);
+ oz_trace(" windex = 0x%x\n", windex);
+ oz_trace(" offset = 0x%x\n", offset);
+ oz_trace(" len = 0x%x\n", len);
+ if (len > 200)
+ len = 200;
+ if (ei == 0)
+ return -1;
+ elt = (struct oz_elt *)ei->data;
+ elt->length = sizeof(struct oz_get_desc_req);
+ body = (struct oz_get_desc_req *)(elt+1);
+ body->type = OZ_GET_DESC_REQ;
+ body->req_id = req_id;
+ put_unaligned(cpu_to_le16(offset), &body->offset);
+ put_unaligned(cpu_to_le16(len), &body->size);
+ body->req_type = req_type;
+ body->desc_type = desc_type;
+ body->w_index = windex;
+ body->index = index;
+ return oz_usb_submit_elt(eb, ei, usb_ctx, 0, 0);
+}
+/*------------------------------------------------------------------------------
+ * Context: tasklet
+ */
+static int oz_usb_set_config_req(void *hpd, u8 req_id, u8 index)
+{
+ struct oz_usb_ctx *usb_ctx = (struct oz_usb_ctx *)hpd;
+ struct oz_pd *pd = usb_ctx->pd;
+ struct oz_elt *elt;
+ struct oz_elt_buf *eb = &pd->elt_buff;
+ struct oz_elt_info *ei = oz_elt_info_alloc(&pd->elt_buff);
+ struct oz_set_config_req *body;
+ if (ei == 0)
+ return -1;
+ elt = (struct oz_elt *)ei->data;
+ elt->length = sizeof(struct oz_set_config_req);
+ body = (struct oz_set_config_req *)(elt+1);
+ body->type = OZ_SET_CONFIG_REQ;
+ body->req_id = req_id;
+ body->index = index;
+ return oz_usb_submit_elt(eb, ei, usb_ctx, 0, 0);
+}
+/*------------------------------------------------------------------------------
+ * Context: tasklet
+ */
+static int oz_usb_set_interface_req(void *hpd, u8 req_id, u8 index, u8 alt)
+{
+ struct oz_usb_ctx *usb_ctx = (struct oz_usb_ctx *)hpd;
+ struct oz_pd *pd = usb_ctx->pd;
+ struct oz_elt *elt;
+ struct oz_elt_buf *eb = &pd->elt_buff;
+ struct oz_elt_info *ei = oz_elt_info_alloc(&pd->elt_buff);
+ struct oz_set_interface_req *body;
+ if (ei == 0)
+ return -1;
+ elt = (struct oz_elt *)ei->data;
+ elt->length = sizeof(struct oz_set_interface_req);
+ body = (struct oz_set_interface_req *)(elt+1);
+ body->type = OZ_SET_INTERFACE_REQ;
+ body->req_id = req_id;
+ body->index = index;
+ body->alternative = alt;
+ return oz_usb_submit_elt(eb, ei, usb_ctx, 0, 0);
+}
+/*------------------------------------------------------------------------------
+ * Context: tasklet
+ */
+static int oz_usb_set_clear_feature_req(void *hpd, u8 req_id, u8 type,
+ u8 recipient, u8 index, __le16 feature)
+{
+ struct oz_usb_ctx *usb_ctx = (struct oz_usb_ctx *)hpd;
+ struct oz_pd *pd = usb_ctx->pd;
+ struct oz_elt *elt;
+ struct oz_elt_buf *eb = &pd->elt_buff;
+ struct oz_elt_info *ei = oz_elt_info_alloc(&pd->elt_buff);
+ struct oz_feature_req *body;
+ if (ei == 0)
+ return -1;
+ elt = (struct oz_elt *)ei->data;
+ elt->length = sizeof(struct oz_feature_req);
+ body = (struct oz_feature_req *)(elt+1);
+ body->type = type;
+ body->req_id = req_id;
+ body->recipient = recipient;
+ body->index = index;
+ put_unaligned(feature, &body->feature);
+ return oz_usb_submit_elt(eb, ei, usb_ctx, 0, 0);
+}
+/*------------------------------------------------------------------------------
+ * Context: tasklet
+ */
+static int oz_usb_vendor_class_req(void *hpd, u8 req_id, u8 req_type,
+ u8 request, __le16 value, __le16 index, u8 *data, int data_len)
+{
+ struct oz_usb_ctx *usb_ctx = (struct oz_usb_ctx *)hpd;
+ struct oz_pd *pd = usb_ctx->pd;
+ struct oz_elt *elt;
+ struct oz_elt_buf *eb = &pd->elt_buff;
+ struct oz_elt_info *ei = oz_elt_info_alloc(&pd->elt_buff);
+ struct oz_vendor_class_req *body;
+ if (ei == 0)
+ return -1;
+ elt = (struct oz_elt *)ei->data;
+ elt->length = sizeof(struct oz_vendor_class_req) - 1 + data_len;
+ body = (struct oz_vendor_class_req *)(elt+1);
+ body->type = OZ_VENDOR_CLASS_REQ;
+ body->req_id = req_id;
+ body->req_type = req_type;
+ body->request = request;
+ put_unaligned(value, &body->value);
+ put_unaligned(index, &body->index);
+ if (data_len)
+ memcpy(body->data, data, data_len);
+ return oz_usb_submit_elt(eb, ei, usb_ctx, 0, 0);
+}
+/*------------------------------------------------------------------------------
+ * Context: tasklet
+ */
+int oz_usb_control_req(void *hpd, u8 req_id, struct usb_ctrlrequest *setup,
+ u8 *data, int data_len)
+{
+ unsigned wvalue = le16_to_cpu(setup->wValue);
+ unsigned windex = le16_to_cpu(setup->wIndex);
+ unsigned wlength = le16_to_cpu(setup->wLength);
+ int rc = 0;
+ oz_event_log(OZ_EVT_CTRL_REQ, setup->bRequest, req_id,
+ (void *)(((unsigned long)(setup->wValue))<<16 |
+ ((unsigned long)setup->wIndex)),
+ setup->bRequestType);
+ if ((setup->bRequestType & USB_TYPE_MASK) == USB_TYPE_STANDARD) {
+ switch (setup->bRequest) {
+ case USB_REQ_GET_DESCRIPTOR:
+ rc = oz_usb_get_desc_req(hpd, req_id,
+ setup->bRequestType, (u8)(wvalue>>8),
+ (u8)wvalue, setup->wIndex, 0, wlength);
+ break;
+ case USB_REQ_SET_CONFIGURATION:
+ rc = oz_usb_set_config_req(hpd, req_id, (u8)wvalue);
+ break;
+ case USB_REQ_SET_INTERFACE: {
+ u8 if_num = (u8)windex;
+ u8 alt = (u8)wvalue;
+ rc = oz_usb_set_interface_req(hpd, req_id,
+ if_num, alt);
+ }
+ break;
+ case USB_REQ_SET_FEATURE:
+ rc = oz_usb_set_clear_feature_req(hpd, req_id,
+ OZ_SET_FEATURE_REQ,
+ setup->bRequestType & 0xf, (u8)windex,
+ setup->wValue);
+ break;
+ case USB_REQ_CLEAR_FEATURE:
+ rc = oz_usb_set_clear_feature_req(hpd, req_id,
+ OZ_CLEAR_FEATURE_REQ,
+ setup->bRequestType & 0xf,
+ (u8)windex, setup->wValue);
+ break;
+ }
+ } else {
+ rc = oz_usb_vendor_class_req(hpd, req_id, setup->bRequestType,
+ setup->bRequest, setup->wValue, setup->wIndex,
+ data, data_len);
+ }
+ return rc;
+}
+/*------------------------------------------------------------------------------
+ * Context: softirq
+ */
+int oz_usb_send_isoc(void *hpd, u8 ep_num, struct urb *urb)
+{
+ struct oz_usb_ctx *usb_ctx = (struct oz_usb_ctx *)hpd;
+ struct oz_pd *pd = usb_ctx->pd;
+ struct oz_elt_buf *eb;
+ int i;
+ int hdr_size;
+ u8 *data;
+ struct usb_iso_packet_descriptor *desc;
+
+ if (pd->mode & OZ_F_ISOC_NO_ELTS) {
+ for (i = 0; i < urb->number_of_packets; i++) {
+ u8 *data;
+ desc = &urb->iso_frame_desc[i];
+ data = ((u8 *)urb->transfer_buffer)+desc->offset;
+ oz_send_isoc_unit(pd, ep_num, data, desc->length);
+ }
+ return 0;
+ }
+
+ hdr_size = sizeof(struct oz_isoc_fixed) - 1;
+ eb = &pd->elt_buff;
+ i = 0;
+ while (i < urb->number_of_packets) {
+ struct oz_elt_info *ei = oz_elt_info_alloc(eb);
+ struct oz_elt *elt;
+ struct oz_isoc_fixed *body;
+ int unit_count;
+ int unit_size;
+ int rem;
+ if (ei == 0)
+ return -1;
+ rem = MAX_ISOC_FIXED_DATA;
+ elt = (struct oz_elt *)ei->data;
+ body = (struct oz_isoc_fixed *)(elt + 1);
+ body->type = OZ_USB_ENDPOINT_DATA;
+ body->endpoint = ep_num;
+ body->format = OZ_DATA_F_ISOC_FIXED;
+ unit_size = urb->iso_frame_desc[i].length;
+ body->unit_size = (u8)unit_size;
+ data = ((u8 *)(elt+1)) + hdr_size;
+ unit_count = 0;
+ while (i < urb->number_of_packets) {
+ desc = &urb->iso_frame_desc[i];
+ if ((unit_size == desc->length) &&
+ (desc->length <= rem)) {
+ memcpy(data, ((u8 *)urb->transfer_buffer) +
+ desc->offset, unit_size);
+ data += unit_size;
+ rem -= unit_size;
+ unit_count++;
+ desc->status = 0;
+ desc->actual_length = desc->length;
+ i++;
+ } else {
+ break;
+ }
+ }
+ elt->length = hdr_size + MAX_ISOC_FIXED_DATA - rem;
+ /* Store the number of units in body->frame_number for the
+ * moment. This field will be correctly determined before
+ * the element is sent. */
+ body->frame_number = (u8)unit_count;
+ oz_usb_submit_elt(eb, ei, usb_ctx, ep_num,
+ pd->mode & OZ_F_ISOC_ANYTIME);
+ }
+ return 0;
+}
+/*------------------------------------------------------------------------------
+ * Context: softirq-serialized
+ */
+void oz_usb_handle_ep_data(struct oz_usb_ctx *usb_ctx,
+ struct oz_usb_hdr *usb_hdr, int len)
+{
+ struct oz_data *data_hdr = (struct oz_data *)usb_hdr;
+ switch (data_hdr->format) {
+ case OZ_DATA_F_MULTIPLE_FIXED: {
+ struct oz_multiple_fixed *body =
+ (struct oz_multiple_fixed *)data_hdr;
+ u8 *data = body->data;
+ int n = (len - sizeof(struct oz_multiple_fixed)+1)
+ / body->unit_size;
+ while (n--) {
+ oz_hcd_data_ind(usb_ctx->hport, body->endpoint,
+ data, body->unit_size);
+ data += body->unit_size;
+ }
+ }
+ break;
+ case OZ_DATA_F_ISOC_FIXED: {
+ struct oz_isoc_fixed *body =
+ (struct oz_isoc_fixed *)data_hdr;
+ int data_len = len-sizeof(struct oz_isoc_fixed)+1;
+ int unit_size = body->unit_size;
+ u8 *data = body->data;
+ int count;
+ int i;
+ if (!unit_size)
+ break;
+ count = data_len/unit_size;
+ for (i = 0; i < count; i++) {
+ oz_hcd_data_ind(usb_ctx->hport,
+ body->endpoint, data, unit_size);
+ data += unit_size;
+ }
+ }
+ break;
+ }
+
+}
+/*------------------------------------------------------------------------------
+ * This is called when the PD has received a USB element. The type of element
+ * is determined and is then passed to an appropriate handler function.
+ * Context: softirq-serialized
+ */
+void oz_usb_rx(struct oz_pd *pd, struct oz_elt *elt)
+{
+ struct oz_usb_hdr *usb_hdr = (struct oz_usb_hdr *)(elt + 1);
+ struct oz_usb_ctx *usb_ctx;
+
+ spin_lock_bh(&pd->app_lock[OZ_APPID_USB-1]);
+ usb_ctx = (struct oz_usb_ctx *)pd->app_ctx[OZ_APPID_USB-1];
+ if (usb_ctx)
+ oz_usb_get(usb_ctx);
+ spin_unlock_bh(&pd->app_lock[OZ_APPID_USB-1]);
+ if (usb_ctx == 0)
+ return; /* Context has gone so nothing to do. */
+ if (usb_ctx->stopped)
+ goto done;
+ /* If sequence number is non-zero then check it is not a duplicate.
+ * Zero sequence numbers are always accepted.
+ */
+ if (usb_hdr->elt_seq_num != 0) {
+ if (((usb_ctx->rx_seq_num - usb_hdr->elt_seq_num) & 0x80) == 0)
+ /* Reject duplicate element. */
+ goto done;
+ }
+ usb_ctx->rx_seq_num = usb_hdr->elt_seq_num;
+ switch (usb_hdr->type) {
+ case OZ_GET_DESC_RSP: {
+ struct oz_get_desc_rsp *body =
+ (struct oz_get_desc_rsp *)usb_hdr;
+ int data_len = elt->length -
+ sizeof(struct oz_get_desc_rsp) + 1;
+ u16 offs = le16_to_cpu(get_unaligned(&body->offset));
+ u16 total_size =
+ le16_to_cpu(get_unaligned(&body->total_size));
+ oz_trace("USB_REQ_GET_DESCRIPTOR - cnf\n");
+ oz_hcd_get_desc_cnf(usb_ctx->hport, body->req_id,
+ body->rcode, body->data,
+ data_len, offs, total_size);
+ }
+ break;
+ case OZ_SET_CONFIG_RSP: {
+ struct oz_set_config_rsp *body =
+ (struct oz_set_config_rsp *)usb_hdr;
+ oz_hcd_control_cnf(usb_ctx->hport, body->req_id,
+ body->rcode, 0, 0);
+ }
+ break;
+ case OZ_SET_INTERFACE_RSP: {
+ struct oz_set_interface_rsp *body =
+ (struct oz_set_interface_rsp *)usb_hdr;
+ oz_hcd_control_cnf(usb_ctx->hport,
+ body->req_id, body->rcode, 0, 0);
+ }
+ break;
+ case OZ_VENDOR_CLASS_RSP: {
+ struct oz_vendor_class_rsp *body =
+ (struct oz_vendor_class_rsp *)usb_hdr;
+ oz_hcd_control_cnf(usb_ctx->hport, body->req_id,
+ body->rcode, body->data, elt->length-
+ sizeof(struct oz_vendor_class_rsp)+1);
+ }
+ break;
+ case OZ_USB_ENDPOINT_DATA:
+ oz_usb_handle_ep_data(usb_ctx, usb_hdr, elt->length);
+ break;
+ }
+done:
+ oz_usb_put(usb_ctx);
+}
+/*------------------------------------------------------------------------------
+ * Context: softirq, process
+ */
+void oz_usb_farewell(struct oz_pd *pd, u8 ep_num, u8 *data, u8 len)
+{
+ struct oz_usb_ctx *usb_ctx;
+ spin_lock_bh(&pd->app_lock[OZ_APPID_USB-1]);
+ usb_ctx = (struct oz_usb_ctx *)pd->app_ctx[OZ_APPID_USB-1];
+ if (usb_ctx)
+ oz_usb_get(usb_ctx);
+ spin_unlock_bh(&pd->app_lock[OZ_APPID_USB-1]);
+ if (usb_ctx == 0)
+ return; /* Context has gone so nothing to do. */
+ if (!usb_ctx->stopped) {
+ oz_trace("Farewell indicated ep = 0x%x\n", ep_num);
+ oz_hcd_data_ind(usb_ctx->hport, ep_num, data, len);
+ }
+ oz_usb_put(usb_ctx);
+}