usb gadget: cdc obex glue
authorFelipe Balbi <felipe.balbi@nokia.com>
Tue, 19 Aug 2008 00:39:30 +0000 (17:39 -0700)
committerGreg Kroah-Hartman <gregkh@suse.de>
Fri, 17 Oct 2008 21:40:53 +0000 (14:40 -0700)
The following patch introduces a new f_obex.c function driver.
It allows userspace obex servers to use usb as transport layer
for their messages.

[ dbrownell@users.sourceforge.net: various fixes and cleanups ]

Signed-off-by: Felipe Balbi <felipe.balbi@nokia.com>
Signed-off-by: David Brownell <dbrownell@users.sourceforge.net>
Signed-off-by: Greg Kroah-Hartman <gregkh@suse.de>
Documentation/DocBook/gadget.tmpl
drivers/usb/gadget/Kconfig
drivers/usb/gadget/f_obex.c [new file with mode: 0644]
drivers/usb/gadget/serial.c
drivers/usb/gadget/u_serial.h
include/linux/usb/cdc.h

index ea3bc9565e6a7e7ae48a168841c222e07bfd0154..6ef2f0073e5aa45b72b33ffc9a52d1c650b8a3fb 100644 (file)
@@ -557,6 +557,9 @@ Near-term plans include converting all of them, except for "gadgetfs".
 </para>
 
 !Edrivers/usb/gadget/f_acm.c
+!Edrivers/usb/gadget/f_ecm.c
+!Edrivers/usb/gadget/f_subset.c
+!Edrivers/usb/gadget/f_obex.c
 !Edrivers/usb/gadget/f_serial.c
 
 </sect1>
index 5dda2dc708dbfd9c0d50cc0cd6c9229badb7366d..80a7c02dc951ab8b78de7539b4bc5c07427fdce2 100644 (file)
@@ -576,19 +576,23 @@ config USB_FILE_STORAGE_TEST
          normal operation.
 
 config USB_G_SERIAL
-       tristate "Serial Gadget (with CDC ACM support)"
+       tristate "Serial Gadget (with CDC ACM and CDC OBEX support)"
        help
          The Serial Gadget talks to the Linux-USB generic serial driver.
          This driver supports a CDC-ACM module option, which can be used
          to interoperate with MS-Windows hosts or with the Linux-USB
          "cdc-acm" driver.
 
+         This driver also supports a CDC-OBEX option.  You will need a
+         user space OBEX server talking to /dev/ttyGS*, since the kernel
+         itself doesn't implement the OBEX protocol.
+
          Say "y" to link the driver statically, or "m" to build a
          dynamically linked module called "g_serial".
 
          For more information, see Documentation/usb/gadget_serial.txt
          which includes instructions and a "driver info file" needed to
-         make MS-Windows work with this driver.
+         make MS-Windows work with CDC ACM.
 
 config USB_MIDI_GADGET
        tristate "MIDI Gadget (EXPERIMENTAL)"
diff --git a/drivers/usb/gadget/f_obex.c b/drivers/usb/gadget/f_obex.c
new file mode 100644 (file)
index 0000000..86241b2
--- /dev/null
@@ -0,0 +1,446 @@
+/*
+ * f_obex.c -- USB CDC OBEX function driver
+ *
+ * Copyright (C) 2008 Nokia Corporation
+ * Contact: Felipe Balbi <felipe.balbi@nokia.com>
+ *
+ * Based on f_acm.c by Al Borchers and David Brownell.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+/* #define VERBOSE_DEBUG */
+
+#include <linux/kernel.h>
+#include <linux/utsname.h>
+#include <linux/device.h>
+
+#include "u_serial.h"
+#include "gadget_chips.h"
+
+
+/*
+ * This CDC OBEX function support just packages a TTY-ish byte stream.
+ * A user mode server will put it into "raw" mode and handle all the
+ * relevant protocol details ... this is just a kernel passthrough.
+ *
+ * REVISIT this driver shouldn't actually activate before that user mode
+ * server is ready to respond!  When the "serial gadget" utility code
+ * adds open/close notifications, this driver should use them with new
+ * (TBS) composite gadget hooks that wrap usb_gadget_disconnect() and
+ * usb_gadget_connect() calls with refcounts ... disconnect() when we
+ * bind, then connect() when the user server code is ready to respond.
+ */
+
+struct obex_ep_descs {
+       struct usb_endpoint_descriptor  *obex_in;
+       struct usb_endpoint_descriptor  *obex_out;
+};
+
+struct f_obex {
+       struct gserial                  port;
+       u8                              ctrl_id;
+       u8                              data_id;
+       u8                              port_num;
+
+       struct obex_ep_descs            fs;
+       struct obex_ep_descs            hs;
+};
+
+static inline struct f_obex *func_to_obex(struct usb_function *f)
+{
+       return container_of(f, struct f_obex, port.func);
+}
+
+/*-------------------------------------------------------------------------*/
+
+#define OBEX_CTRL_IDX  0
+#define OBEX_DATA_IDX  1
+
+static struct usb_string obex_string_defs[] = {
+       [OBEX_CTRL_IDX].s       = "CDC Object Exchange (OBEX)",
+       [OBEX_DATA_IDX].s       = "CDC OBEX Data",
+       {  },   /* end of list */
+};
+
+static struct usb_gadget_strings obex_string_table = {
+       .language               = 0x0409,       /* en-US */
+       .strings                = obex_string_defs,
+};
+
+static struct usb_gadget_strings *obex_strings[] = {
+       &obex_string_table,
+       NULL,
+};
+
+/*-------------------------------------------------------------------------*/
+
+static struct usb_interface_descriptor obex_control_intf __initdata = {
+       .bLength                = sizeof(obex_control_intf),
+       .bDescriptorType        = USB_DT_INTERFACE,
+       .bInterfaceNumber       = 0,
+
+       .bAlternateSetting      = 0,
+       .bNumEndpoints          = 0,
+       .bInterfaceClass        = USB_CLASS_COMM,
+       .bInterfaceSubClass     = USB_CDC_SUBCLASS_OBEX,
+};
+
+static struct usb_interface_descriptor obex_data_nop_intf __initdata = {
+       .bLength                = sizeof(obex_data_nop_intf),
+       .bDescriptorType        = USB_DT_INTERFACE,
+       .bInterfaceNumber       = 1,
+
+       .bAlternateSetting      = 0,
+       .bNumEndpoints          = 0,
+       .bInterfaceClass        = USB_CLASS_CDC_DATA,
+};
+
+static struct usb_interface_descriptor obex_data_intf __initdata = {
+       .bLength                = sizeof(obex_data_intf),
+       .bDescriptorType        = USB_DT_INTERFACE,
+       .bInterfaceNumber       = 2,
+
+       .bAlternateSetting      = 1,
+       .bNumEndpoints          = 2,
+       .bInterfaceClass        = USB_CLASS_CDC_DATA,
+};
+
+static struct usb_cdc_header_desc obex_cdc_header_desc __initdata = {
+       .bLength                = sizeof(obex_cdc_header_desc),
+       .bDescriptorType        = USB_DT_CS_INTERFACE,
+       .bDescriptorSubType     = USB_CDC_HEADER_TYPE,
+       .bcdCDC                 = __constant_cpu_to_le16(0x0120),
+};
+
+static struct usb_cdc_union_desc obex_cdc_union_desc __initdata = {
+       .bLength                = sizeof(obex_cdc_union_desc),
+       .bDescriptorType        = USB_DT_CS_INTERFACE,
+       .bDescriptorSubType     = USB_CDC_UNION_TYPE,
+       .bMasterInterface0      = 1,
+       .bSlaveInterface0       = 2,
+};
+
+static struct usb_cdc_obex_desc obex_desc __initdata = {
+       .bLength                = sizeof(obex_desc),
+       .bDescriptorType        = USB_DT_CS_INTERFACE,
+       .bDescriptorSubType     = USB_CDC_OBEX_TYPE,
+       .bcdVersion             = __constant_cpu_to_le16(0x0100),
+};
+
+/* High-Speed Support */
+
+static struct usb_endpoint_descriptor obex_hs_ep_out_desc __initdata = {
+       .bLength                = USB_DT_ENDPOINT_SIZE,
+       .bDescriptorType        = USB_DT_ENDPOINT,
+
+       .bEndpointAddress       = USB_DIR_OUT,
+       .bmAttributes           = USB_ENDPOINT_XFER_BULK,
+       .wMaxPacketSize         = __constant_cpu_to_le16(512),
+};
+
+static struct usb_endpoint_descriptor obex_hs_ep_in_desc __initdata = {
+       .bLength                = USB_DT_ENDPOINT_SIZE,
+       .bDescriptorType        = USB_DT_ENDPOINT,
+
+       .bEndpointAddress       = USB_DIR_IN,
+       .bmAttributes           = USB_ENDPOINT_XFER_BULK,
+       .wMaxPacketSize         = __constant_cpu_to_le16(512),
+};
+
+static struct usb_descriptor_header *hs_function[] __initdata = {
+       (struct usb_descriptor_header *) &obex_control_intf,
+       (struct usb_descriptor_header *) &obex_cdc_header_desc,
+       (struct usb_descriptor_header *) &obex_desc,
+       (struct usb_descriptor_header *) &obex_cdc_union_desc,
+
+       (struct usb_descriptor_header *) &obex_data_nop_intf,
+       (struct usb_descriptor_header *) &obex_data_intf,
+       (struct usb_descriptor_header *) &obex_hs_ep_in_desc,
+       (struct usb_descriptor_header *) &obex_hs_ep_out_desc,
+       NULL,
+};
+
+/* Full-Speed Support */
+
+static struct usb_endpoint_descriptor obex_fs_ep_in_desc __initdata = {
+       .bLength                = USB_DT_ENDPOINT_SIZE,
+       .bDescriptorType        = USB_DT_ENDPOINT,
+
+       .bEndpointAddress       = USB_DIR_IN,
+       .bmAttributes           = USB_ENDPOINT_XFER_BULK,
+};
+
+static struct usb_endpoint_descriptor obex_fs_ep_out_desc __initdata = {
+       .bLength                = USB_DT_ENDPOINT_SIZE,
+       .bDescriptorType        = USB_DT_ENDPOINT,
+
+       .bEndpointAddress       = USB_DIR_OUT,
+       .bmAttributes           = USB_ENDPOINT_XFER_BULK,
+};
+
+static struct usb_descriptor_header *fs_function[] __initdata = {
+       (struct usb_descriptor_header *) &obex_control_intf,
+       (struct usb_descriptor_header *) &obex_cdc_header_desc,
+       (struct usb_descriptor_header *) &obex_desc,
+       (struct usb_descriptor_header *) &obex_cdc_union_desc,
+
+       (struct usb_descriptor_header *) &obex_data_nop_intf,
+       (struct usb_descriptor_header *) &obex_data_intf,
+       (struct usb_descriptor_header *) &obex_fs_ep_in_desc,
+       (struct usb_descriptor_header *) &obex_fs_ep_out_desc,
+       NULL,
+};
+
+/*-------------------------------------------------------------------------*/
+
+static int obex_set_alt(struct usb_function *f, unsigned intf, unsigned alt)
+{
+       struct f_obex           *obex = func_to_obex(f);
+       struct usb_composite_dev *cdev = f->config->cdev;
+
+       if (intf == obex->ctrl_id) {
+               if (alt != 0)
+                       goto fail;
+               /* NOP */
+               DBG(cdev, "reset obex ttyGS%d control\n", obex->port_num);
+
+       } else if (intf == obex->data_id) {
+               if (alt > 1)
+                       goto fail;
+
+               if (obex->port.in->driver_data) {
+                       DBG(cdev, "reset obex ttyGS%d\n", obex->port_num);
+                       gserial_disconnect(&obex->port);
+               }
+
+               if (!obex->port.in_desc) {
+                       DBG(cdev, "init obex ttyGS%d\n", obex->port_num);
+                       obex->port.in_desc = ep_choose(cdev->gadget,
+                                       obex->hs.obex_in, obex->fs.obex_in);
+                       obex->port.out_desc = ep_choose(cdev->gadget,
+                                       obex->hs.obex_out, obex->fs.obex_out);
+               }
+
+               if (alt == 1) {
+                       DBG(cdev, "activate obex ttyGS%d\n", obex->port_num);
+                       gserial_connect(&obex->port, obex->port_num);
+               }
+
+       } else
+               goto fail;
+
+       return 0;
+
+fail:
+       return -EINVAL;
+}
+
+static int obex_get_alt(struct usb_function *f, unsigned intf)
+{
+       struct f_obex           *obex = func_to_obex(f);
+
+       if (intf == obex->ctrl_id)
+               return 0;
+
+       return obex->port.in->driver_data ? 1 : 0;
+}
+
+static void obex_disable(struct usb_function *f)
+{
+       struct f_obex   *obex = func_to_obex(f);
+       struct usb_composite_dev *cdev = f->config->cdev;
+
+       DBG(cdev, "obex ttyGS%d disable\n", obex->port_num);
+       gserial_disconnect(&obex->port);
+}
+
+/*-------------------------------------------------------------------------*/
+
+static int __init
+obex_bind(struct usb_configuration *c, struct usb_function *f)
+{
+       struct usb_composite_dev *cdev = c->cdev;
+       struct f_obex           *obex = func_to_obex(f);
+       int                     status;
+       struct usb_ep           *ep;
+
+       /* allocate instance-specific interface IDs, and patch descriptors */
+
+       status = usb_interface_id(c, f);
+       if (status < 0)
+               goto fail;
+       obex->ctrl_id = status;
+
+       obex_control_intf.bInterfaceNumber = status;
+       obex_cdc_union_desc.bMasterInterface0 = status;
+
+       status = usb_interface_id(c, f);
+       if (status < 0)
+               goto fail;
+       obex->data_id = status;
+
+       obex_data_nop_intf.bInterfaceNumber = status;
+       obex_data_intf.bInterfaceNumber = status;
+       obex_cdc_union_desc.bSlaveInterface0 = status;
+
+       /* allocate instance-specific endpoints */
+
+       ep = usb_ep_autoconfig(cdev->gadget, &obex_fs_ep_in_desc);
+       if (!ep)
+               goto fail;
+       obex->port.in = ep;
+       ep->driver_data = cdev; /* claim */
+
+       ep = usb_ep_autoconfig(cdev->gadget, &obex_fs_ep_out_desc);
+       if (!ep)
+               goto fail;
+       obex->port.out = ep;
+       ep->driver_data = cdev; /* claim */
+
+       /* copy descriptors, and track endpoint copies */
+       f->descriptors = usb_copy_descriptors(fs_function);
+
+       obex->fs.obex_in = usb_find_endpoint(fs_function,
+                       f->descriptors, &obex_fs_ep_in_desc);
+       obex->fs.obex_out = usb_find_endpoint(fs_function,
+                       f->descriptors, &obex_fs_ep_out_desc);
+
+       /* support all relevant hardware speeds... we expect that when
+        * hardware is dual speed, all bulk-capable endpoints work at
+        * both speeds
+        */
+       if (gadget_is_dualspeed(c->cdev->gadget)) {
+
+               obex_hs_ep_in_desc.bEndpointAddress =
+                               obex_fs_ep_in_desc.bEndpointAddress;
+               obex_hs_ep_out_desc.bEndpointAddress =
+                               obex_fs_ep_out_desc.bEndpointAddress;
+
+               /* copy descriptors, and track endpoint copies */
+               f->hs_descriptors = usb_copy_descriptors(hs_function);
+
+               obex->hs.obex_in = usb_find_endpoint(hs_function,
+                               f->descriptors, &obex_hs_ep_in_desc);
+               obex->hs.obex_out = usb_find_endpoint(hs_function,
+                               f->descriptors, &obex_hs_ep_out_desc);
+       }
+
+       DBG(cdev, "obex ttyGS%d: %s speed IN/%s OUT/%s\n",
+                       obex->port_num,
+                       gadget_is_dualspeed(c->cdev->gadget) ? "dual" : "full",
+                       obex->port.in->name, obex->port.out->name);
+
+       return 0;
+
+fail:
+       /* we might as well release our claims on endpoints */
+       if (obex->port.out)
+               obex->port.out->driver_data = NULL;
+       if (obex->port.in)
+               obex->port.in->driver_data = NULL;
+
+       ERROR(cdev, "%s/%p: can't bind, err %d\n", f->name, f, status);
+
+       return status;
+}
+
+static void
+obex_unbind(struct usb_configuration *c, struct usb_function *f)
+{
+       if (gadget_is_dualspeed(c->cdev->gadget))
+               usb_free_descriptors(f->hs_descriptors);
+       usb_free_descriptors(f->descriptors);
+       kfree(func_to_obex(f));
+}
+
+/* Some controllers can't support CDC OBEX ... */
+static inline bool can_support_obex(struct usb_configuration *c)
+{
+       /* Since the first interface is a NOP, we can ignore the
+        * issue of multi-interface support on most controllers.
+        *
+        * Altsettings are mandatory, however...
+        */
+       if (!gadget_supports_altsettings(c->cdev->gadget))
+               return false;
+
+       /* everything else is *probably* fine ... */
+       return true;
+}
+
+/**
+ * obex_bind_config - add a CDC OBEX function to a configuration
+ * @c: the configuration to support the CDC OBEX instance
+ * @port_num: /dev/ttyGS* port this interface will use
+ * Context: single threaded during gadget setup
+ *
+ * Returns zero on success, else negative errno.
+ *
+ * Caller must have called @gserial_setup() with enough ports to
+ * handle all the ones it binds.  Caller is also responsible
+ * for calling @gserial_cleanup() before module unload.
+ */
+int __init obex_bind_config(struct usb_configuration *c, u8 port_num)
+{
+       struct f_obex   *obex;
+       int             status;
+
+       if (!can_support_obex(c))
+               return -EINVAL;
+
+       /* maybe allocate device-global string IDs, and patch descriptors */
+       if (obex_string_defs[OBEX_CTRL_IDX].id == 0) {
+               status = usb_string_id(c->cdev);
+               if (status < 0)
+                       return status;
+               obex_string_defs[OBEX_CTRL_IDX].id = status;
+
+               obex_control_intf.iInterface = status;
+
+               status = usb_string_id(c->cdev);
+               if (status < 0)
+                       return status;
+               obex_string_defs[OBEX_DATA_IDX].id = status;
+
+               obex_data_nop_intf.iInterface =
+                       obex_data_intf.iInterface = status;
+       }
+
+       /* allocate and initialize one new instance */
+       obex = kzalloc(sizeof *obex, GFP_KERNEL);
+       if (!obex)
+               return -ENOMEM;
+
+       obex->port_num = port_num;
+
+       obex->port.func.name = "obex";
+       obex->port.func.strings = obex_strings;
+       /* descriptors are per-instance copies */
+       obex->port.func.bind = obex_bind;
+       obex->port.func.unbind = obex_unbind;
+       obex->port.func.set_alt = obex_set_alt;
+       obex->port.func.get_alt = obex_get_alt;
+       obex->port.func.disable = obex_disable;
+
+       status = usb_add_function(c, &obex->port.func);
+       if (status)
+               kfree(obex);
+
+       return status;
+}
+
+MODULE_AUTHOR("Felipe Balbi");
+MODULE_LICENSE("GPL");
index 3faa7a7022df24926270d27399cfa92c69062b0e..2dee848b2f59637dbc2cfebacfca1085eb4ff208 100644 (file)
@@ -43,6 +43,7 @@
 #include "epautoconf.c"
 
 #include "f_acm.c"
+#include "f_obex.c"
 #include "f_serial.c"
 #include "u_serial.c"
 
@@ -56,6 +57,7 @@
 #define GS_VENDOR_ID                   0x0525  /* NetChip */
 #define GS_PRODUCT_ID                  0xa4a6  /* Linux-USB Serial Gadget */
 #define GS_CDC_PRODUCT_ID              0xa4a7  /* ... as CDC-ACM */
+#define GS_CDC_OBEX_PRODUCT_ID         0xa4a9  /* ... as CDC-OBEX */
 
 /* string IDs are assigned dynamically */
 
@@ -125,6 +127,10 @@ static int use_acm = true;
 module_param(use_acm, bool, 0);
 MODULE_PARM_DESC(use_acm, "Use CDC ACM, default=yes");
 
+static int use_obex = false;
+module_param(use_obex, bool, 0);
+MODULE_PARM_DESC(use_obex, "Use CDC OBEX, default=no");
+
 static unsigned n_ports = 1;
 module_param(n_ports, uint, 0);
 MODULE_PARM_DESC(n_ports, "number of ports to create, default=1");
@@ -139,6 +145,8 @@ static int __init serial_bind_config(struct usb_configuration *c)
        for (i = 0; i < n_ports && status == 0; i++) {
                if (use_acm)
                        status = acm_bind_config(c, i);
+               else if (use_obex)
+                       status = obex_bind_config(c, i);
                else
                        status = gser_bind_config(c, i);
        }
@@ -249,6 +257,12 @@ static int __init init(void)
                device_desc.bDeviceClass = USB_CLASS_COMM;
                device_desc.idProduct =
                                __constant_cpu_to_le16(GS_CDC_PRODUCT_ID);
+       } else if (use_obex) {
+               serial_config_driver.label = "CDC OBEX config";
+               serial_config_driver.bConfigurationValue = 3;
+               device_desc.bDeviceClass = USB_CLASS_COMM;
+               device_desc.idProduct =
+                       __constant_cpu_to_le16(GS_CDC_OBEX_PRODUCT_ID);
        } else {
                serial_config_driver.label = "Generic Serial config";
                serial_config_driver.bConfigurationValue = 1;
index af3910d01aea65f7b92e16e7e10770b0ccd74828..300f0ed9475d9799f2f5a5dcc51686ab76211452 100644 (file)
@@ -62,5 +62,6 @@ void gserial_disconnect(struct gserial *);
 /* functions are bound to configurations by a config or gadget driver */
 int acm_bind_config(struct usb_configuration *c, u8 port_num);
 int gser_bind_config(struct usb_configuration *c, u8 port_num);
+int obex_bind_config(struct usb_configuration *c, u8 port_num);
 
 #endif /* __U_SERIAL_H */
index ca228bb942186e5ab984baec8d5326cd56d4f0d6..18a729343ffae3edd927fca1d4855b0f2e1bc0b2 100644 (file)
@@ -160,6 +160,15 @@ struct usb_cdc_mdlm_detail_desc {
        __u8    bDetailData[0];
 } __attribute__ ((packed));
 
+/* "OBEX Control Model Functional Descriptor" */
+struct usb_cdc_obex_desc {
+       __u8    bLength;
+       __u8    bDescriptorType;
+       __u8    bDescriptorSubType;
+
+       __le16  bcdVersion;
+} __attribute__ ((packed));
+
 /*-------------------------------------------------------------------------*/
 
 /*