diff options
| author | David Brownell <dbrownell@users.sourceforge.net> | 2008-06-19 21:18:50 -0400 |
|---|---|---|
| committer | Greg Kroah-Hartman <gregkh@suse.de> | 2008-07-21 18:16:07 -0400 |
| commit | 61d8baea5d02f0f00fb789ce5551cbd8f8b77087 (patch) | |
| tree | 90e52cbef05d698cb6b5a361930f19ea9b6fba17 | |
| parent | 4d5a73dc39c1e1d8ba5feec5c6234ae920c59161 (diff) | |
usb gadget serial: split out generic serial function
Split out the generic serial support into a "function driver". This
closely mimics the ACM support, but with a MUCH simpler control model.
Signed-off-by: David Brownell <dbrownell@users.sourceforge.net>
Signed-off-by: Greg Kroah-Hartman <gregkh@suse.de>
| -rw-r--r-- | Documentation/DocBook/gadget.tmpl | 1 | ||||
| -rw-r--r-- | drivers/usb/gadget/Makefile | 2 | ||||
| -rw-r--r-- | drivers/usb/gadget/f_serial.c | 296 | ||||
| -rw-r--r-- | drivers/usb/gadget/u_serial.h | 1 |
4 files changed, 299 insertions, 1 deletions
diff --git a/Documentation/DocBook/gadget.tmpl b/Documentation/DocBook/gadget.tmpl index b7e266c48ac8..ea3bc9565e6a 100644 --- a/Documentation/DocBook/gadget.tmpl +++ b/Documentation/DocBook/gadget.tmpl | |||
| @@ -557,6 +557,7 @@ Near-term plans include converting all of them, except for "gadgetfs". | |||
| 557 | </para> | 557 | </para> |
| 558 | 558 | ||
| 559 | !Edrivers/usb/gadget/f_acm.c | 559 | !Edrivers/usb/gadget/f_acm.c |
| 560 | !Edrivers/usb/gadget/f_serial.c | ||
| 560 | 561 | ||
| 561 | </sect1> | 562 | </sect1> |
| 562 | 563 | ||
diff --git a/drivers/usb/gadget/Makefile b/drivers/usb/gadget/Makefile index e438a018ad67..47ecb3a9b3a6 100644 --- a/drivers/usb/gadget/Makefile +++ b/drivers/usb/gadget/Makefile | |||
| @@ -26,7 +26,7 @@ C_UTILS = composite.o usbstring.o config.o epautoconf.o | |||
| 26 | 26 | ||
| 27 | g_zero-objs := zero.o f_sourcesink.o f_loopback.o $(C_UTILS) | 27 | g_zero-objs := zero.o f_sourcesink.o f_loopback.o $(C_UTILS) |
| 28 | g_ether-objs := ether.o usbstring.o config.o epautoconf.o | 28 | g_ether-objs := ether.o usbstring.o config.o epautoconf.o |
| 29 | g_serial-objs := serial.o u_serial.o f_acm.o $(C_UTILS) | 29 | g_serial-objs := serial.o u_serial.o f_acm.o f_serial.o $(C_UTILS) |
| 30 | g_midi-objs := gmidi.o usbstring.o config.o epautoconf.o | 30 | g_midi-objs := gmidi.o usbstring.o config.o epautoconf.o |
| 31 | gadgetfs-objs := inode.o | 31 | gadgetfs-objs := inode.o |
| 32 | g_file_storage-objs := file_storage.o usbstring.o config.o \ | 32 | g_file_storage-objs := file_storage.o usbstring.o config.o \ |
diff --git a/drivers/usb/gadget/f_serial.c b/drivers/usb/gadget/f_serial.c new file mode 100644 index 000000000000..1b6bde9aaed5 --- /dev/null +++ b/drivers/usb/gadget/f_serial.c | |||
| @@ -0,0 +1,296 @@ | |||
| 1 | /* | ||
| 2 | * f_serial.c - generic USB serial function driver | ||
| 3 | * | ||
| 4 | * Copyright (C) 2003 Al Borchers (alborchers@steinerpoint.com) | ||
| 5 | * Copyright (C) 2008 by David Brownell | ||
| 6 | * Copyright (C) 2008 by Nokia Corporation | ||
| 7 | * | ||
| 8 | * This software is distributed under the terms of the GNU General | ||
| 9 | * Public License ("GPL") as published by the Free Software Foundation, | ||
| 10 | * either version 2 of that License or (at your option) any later version. | ||
| 11 | */ | ||
| 12 | |||
| 13 | #include <linux/kernel.h> | ||
| 14 | #include <linux/device.h> | ||
| 15 | |||
| 16 | #include "u_serial.h" | ||
| 17 | #include "gadget_chips.h" | ||
| 18 | |||
| 19 | |||
| 20 | /* | ||
| 21 | * This function packages a simple "generic serial" port with no real | ||
| 22 | * control mechanisms, just raw data transfer over two bulk endpoints. | ||
| 23 | * | ||
| 24 | * Because it's not standardized, this isn't as interoperable as the | ||
| 25 | * CDC ACM driver. However, for many purposes it's just as functional | ||
| 26 | * if you can arrange appropriate host side drivers. | ||
| 27 | */ | ||
| 28 | |||
| 29 | struct gser_descs { | ||
| 30 | struct usb_endpoint_descriptor *in; | ||
| 31 | struct usb_endpoint_descriptor *out; | ||
| 32 | }; | ||
| 33 | |||
| 34 | struct f_gser { | ||
| 35 | struct gserial port; | ||
| 36 | u8 data_id; | ||
| 37 | u8 port_num; | ||
| 38 | |||
| 39 | struct usb_descriptor_header **fs_function; | ||
| 40 | struct gser_descs fs; | ||
| 41 | struct usb_descriptor_header **hs_function; | ||
| 42 | struct gser_descs hs; | ||
| 43 | }; | ||
| 44 | |||
| 45 | static inline struct f_gser *func_to_gser(struct usb_function *f) | ||
| 46 | { | ||
| 47 | return container_of(f, struct f_gser, port.func); | ||
| 48 | } | ||
| 49 | |||
| 50 | /*-------------------------------------------------------------------------*/ | ||
| 51 | |||
| 52 | /* interface descriptor: */ | ||
| 53 | |||
| 54 | static struct usb_interface_descriptor gser_interface_desc __initdata = { | ||
| 55 | .bLength = USB_DT_INTERFACE_SIZE, | ||
| 56 | .bDescriptorType = USB_DT_INTERFACE, | ||
| 57 | /* .bInterfaceNumber = DYNAMIC */ | ||
| 58 | .bNumEndpoints = 2, | ||
| 59 | .bInterfaceClass = USB_CLASS_VENDOR_SPEC, | ||
| 60 | .bInterfaceSubClass = 0, | ||
| 61 | .bInterfaceProtocol = 0, | ||
| 62 | /* .iInterface = DYNAMIC */ | ||
| 63 | }; | ||
| 64 | |||
| 65 | /* full speed support: */ | ||
| 66 | |||
| 67 | static struct usb_endpoint_descriptor gser_fs_in_desc __initdata = { | ||
| 68 | .bLength = USB_DT_ENDPOINT_SIZE, | ||
| 69 | .bDescriptorType = USB_DT_ENDPOINT, | ||
| 70 | .bEndpointAddress = USB_DIR_IN, | ||
| 71 | .bmAttributes = USB_ENDPOINT_XFER_BULK, | ||
| 72 | }; | ||
| 73 | |||
| 74 | static struct usb_endpoint_descriptor gser_fs_out_desc __initdata = { | ||
| 75 | .bLength = USB_DT_ENDPOINT_SIZE, | ||
| 76 | .bDescriptorType = USB_DT_ENDPOINT, | ||
| 77 | .bEndpointAddress = USB_DIR_OUT, | ||
| 78 | .bmAttributes = USB_ENDPOINT_XFER_BULK, | ||
| 79 | }; | ||
| 80 | |||
| 81 | static struct usb_descriptor_header *gser_fs_function[] __initdata = { | ||
| 82 | (struct usb_descriptor_header *) &gser_interface_desc, | ||
| 83 | (struct usb_descriptor_header *) &gser_fs_in_desc, | ||
| 84 | (struct usb_descriptor_header *) &gser_fs_out_desc, | ||
| 85 | NULL, | ||
| 86 | }; | ||
| 87 | |||
| 88 | /* high speed support: */ | ||
| 89 | |||
| 90 | static struct usb_endpoint_descriptor gser_hs_in_desc __initdata = { | ||
| 91 | .bLength = USB_DT_ENDPOINT_SIZE, | ||
| 92 | .bDescriptorType = USB_DT_ENDPOINT, | ||
| 93 | .bmAttributes = USB_ENDPOINT_XFER_BULK, | ||
| 94 | .wMaxPacketSize = __constant_cpu_to_le16(512), | ||
| 95 | }; | ||
| 96 | |||
| 97 | static struct usb_endpoint_descriptor gser_hs_out_desc __initdata = { | ||
| 98 | .bLength = USB_DT_ENDPOINT_SIZE, | ||
| 99 | .bDescriptorType = USB_DT_ENDPOINT, | ||
| 100 | .bmAttributes = USB_ENDPOINT_XFER_BULK, | ||
| 101 | .wMaxPacketSize = __constant_cpu_to_le16(512), | ||
| 102 | }; | ||
| 103 | |||
| 104 | static struct usb_descriptor_header *gser_hs_function[] __initdata = { | ||
| 105 | (struct usb_descriptor_header *) &gser_interface_desc, | ||
| 106 | (struct usb_descriptor_header *) &gser_hs_in_desc, | ||
| 107 | (struct usb_descriptor_header *) &gser_hs_out_desc, | ||
| 108 | NULL, | ||
| 109 | }; | ||
| 110 | |||
| 111 | /* string descriptors: */ | ||
| 112 | |||
| 113 | static struct usb_string gser_string_defs[] = { | ||
| 114 | [0].s = "Generic Serial", | ||
| 115 | { } /* end of list */ | ||
| 116 | }; | ||
| 117 | |||
| 118 | static struct usb_gadget_strings gser_string_table = { | ||
| 119 | .language = 0x0409, /* en-us */ | ||
| 120 | .strings = gser_string_defs, | ||
| 121 | }; | ||
| 122 | |||
| 123 | static struct usb_gadget_strings *gser_strings[] = { | ||
| 124 | &gser_string_table, | ||
| 125 | NULL, | ||
| 126 | }; | ||
| 127 | |||
| 128 | /*-------------------------------------------------------------------------*/ | ||
| 129 | |||
| 130 | static int gser_set_alt(struct usb_function *f, unsigned intf, unsigned alt) | ||
| 131 | { | ||
| 132 | struct f_gser *gser = func_to_gser(f); | ||
| 133 | struct usb_composite_dev *cdev = f->config->cdev; | ||
| 134 | |||
| 135 | /* we know alt == 0, so this is an activation or a reset */ | ||
| 136 | |||
| 137 | if (gser->port.in->driver_data) { | ||
| 138 | DBG(cdev, "reset generic ttyGS%d\n", gser->port_num); | ||
| 139 | gserial_disconnect(&gser->port); | ||
| 140 | } else { | ||
| 141 | DBG(cdev, "activate generic ttyGS%d\n", gser->port_num); | ||
| 142 | gser->port.in_desc = ep_choose(cdev->gadget, | ||
| 143 | gser->hs.in, gser->fs.in); | ||
| 144 | gser->port.out_desc = ep_choose(cdev->gadget, | ||
| 145 | gser->hs.out, gser->fs.out); | ||
| 146 | } | ||
| 147 | gserial_connect(&gser->port, gser->port_num); | ||
| 148 | return 0; | ||
| 149 | } | ||
| 150 | |||
| 151 | static void gser_disable(struct usb_function *f) | ||
| 152 | { | ||
| 153 | struct f_gser *gser = func_to_gser(f); | ||
| 154 | struct usb_composite_dev *cdev = f->config->cdev; | ||
| 155 | |||
| 156 | DBG(cdev, "generic ttyGS%d deactivated\n", gser->port_num); | ||
| 157 | gserial_disconnect(&gser->port); | ||
| 158 | } | ||
| 159 | |||
| 160 | /*-------------------------------------------------------------------------*/ | ||
| 161 | |||
| 162 | /* serial function driver setup/binding */ | ||
| 163 | |||
| 164 | static int __init | ||
| 165 | gser_bind(struct usb_configuration *c, struct usb_function *f) | ||
| 166 | { | ||
| 167 | struct usb_composite_dev *cdev = c->cdev; | ||
| 168 | struct f_gser *gser = func_to_gser(f); | ||
| 169 | int status; | ||
| 170 | struct usb_ep *ep; | ||
| 171 | |||
| 172 | /* allocate instance-specific interface IDs */ | ||
| 173 | status = usb_interface_id(c, f); | ||
| 174 | if (status < 0) | ||
| 175 | goto fail; | ||
| 176 | gser->data_id = status; | ||
| 177 | gser_interface_desc.bInterfaceNumber = status; | ||
| 178 | |||
| 179 | status = -ENODEV; | ||
| 180 | |||
| 181 | /* allocate instance-specific endpoints */ | ||
| 182 | ep = usb_ep_autoconfig(cdev->gadget, &gser_fs_in_desc); | ||
| 183 | if (!ep) | ||
| 184 | goto fail; | ||
| 185 | gser->port.in = ep; | ||
| 186 | ep->driver_data = cdev; /* claim */ | ||
| 187 | |||
| 188 | ep = usb_ep_autoconfig(cdev->gadget, &gser_fs_out_desc); | ||
| 189 | if (!ep) | ||
| 190 | goto fail; | ||
| 191 | gser->port.out = ep; | ||
| 192 | ep->driver_data = cdev; /* claim */ | ||
| 193 | |||
| 194 | /* copy descriptors, and track endpoint copies */ | ||
| 195 | f->descriptors = usb_copy_descriptors(gser_fs_function); | ||
| 196 | |||
| 197 | gser->fs.in = usb_find_endpoint(gser_fs_function, | ||
| 198 | f->descriptors, &gser_fs_in_desc); | ||
| 199 | gser->fs.out = usb_find_endpoint(gser_fs_function, | ||
| 200 | f->descriptors, &gser_fs_out_desc); | ||
| 201 | |||
| 202 | |||
| 203 | /* support all relevant hardware speeds... we expect that when | ||
| 204 | * hardware is dual speed, all bulk-capable endpoints work at | ||
| 205 | * both speeds | ||
| 206 | */ | ||
| 207 | if (gadget_is_dualspeed(c->cdev->gadget)) { | ||
| 208 | gser_hs_in_desc.bEndpointAddress = | ||
| 209 | gser_fs_in_desc.bEndpointAddress; | ||
| 210 | gser_hs_out_desc.bEndpointAddress = | ||
| 211 | gser_fs_out_desc.bEndpointAddress; | ||
| 212 | |||
| 213 | /* copy descriptors, and track endpoint copies */ | ||
| 214 | f->hs_descriptors = usb_copy_descriptors(gser_hs_function); | ||
| 215 | |||
| 216 | gser->hs.in = usb_find_endpoint(gser_hs_function, | ||
| 217 | f->hs_descriptors, &gser_hs_in_desc); | ||
| 218 | gser->hs.out = usb_find_endpoint(gser_hs_function, | ||
| 219 | f->hs_descriptors, &gser_hs_out_desc); | ||
| 220 | } | ||
| 221 | |||
| 222 | DBG(cdev, "generic ttyGS%d: %s speed IN/%s OUT/%s\n", | ||
| 223 | gser->port_num, | ||
| 224 | gadget_is_dualspeed(c->cdev->gadget) ? "dual" : "full", | ||
| 225 | gser->port.in->name, gser->port.out->name); | ||
| 226 | return 0; | ||
| 227 | |||
| 228 | fail: | ||
| 229 | /* we might as well release our claims on endpoints */ | ||
| 230 | if (gser->port.out) | ||
| 231 | gser->port.out->driver_data = NULL; | ||
| 232 | if (gser->port.in) | ||
| 233 | gser->port.in->driver_data = NULL; | ||
| 234 | |||
| 235 | ERROR(cdev, "%s: can't bind, err %d\n", f->name, status); | ||
| 236 | |||
| 237 | return status; | ||
| 238 | } | ||
| 239 | |||
| 240 | static void | ||
| 241 | gser_unbind(struct usb_configuration *c, struct usb_function *f) | ||
| 242 | { | ||
| 243 | if (gadget_is_dualspeed(c->cdev->gadget)) | ||
| 244 | usb_free_descriptors(f->hs_descriptors); | ||
| 245 | usb_free_descriptors(f->descriptors); | ||
| 246 | kfree(func_to_gser(f)); | ||
| 247 | } | ||
| 248 | |||
| 249 | /** | ||
| 250 | * gser_bind_config - add a generic serial function to a configuration | ||
| 251 | * @c: the configuration to support the serial instance | ||
| 252 | * @port_num: /dev/ttyGS* port this interface will use | ||
| 253 | * Context: single threaded during gadget setup | ||
| 254 | * | ||
| 255 | * Returns zero on success, else negative errno. | ||
| 256 | * | ||
| 257 | * Caller must have called @gserial_setup() with enough ports to | ||
| 258 | * handle all the ones it binds. Caller is also responsible | ||
| 259 | * for calling @gserial_cleanup() before module unload. | ||
| 260 | */ | ||
| 261 | int __init gser_bind_config(struct usb_configuration *c, u8 port_num) | ||
| 262 | { | ||
| 263 | struct f_gser *gser; | ||
| 264 | int status; | ||
| 265 | |||
| 266 | /* REVISIT might want instance-specific strings to help | ||
| 267 | * distinguish instances ... | ||
| 268 | */ | ||
| 269 | |||
| 270 | /* maybe allocate device-global string ID */ | ||
| 271 | if (gser_string_defs[0].id == 0) { | ||
| 272 | status = usb_string_id(c->cdev); | ||
| 273 | if (status < 0) | ||
| 274 | return status; | ||
| 275 | gser_string_defs[0].id = status; | ||
| 276 | } | ||
| 277 | |||
| 278 | /* allocate and initialize one new instance */ | ||
| 279 | gser = kzalloc(sizeof *gser, GFP_KERNEL); | ||
| 280 | if (!gser) | ||
| 281 | return -ENOMEM; | ||
| 282 | |||
| 283 | gser->port_num = port_num; | ||
| 284 | |||
| 285 | gser->port.func.name = "gser"; | ||
| 286 | gser->port.func.strings = gser_strings; | ||
| 287 | gser->port.func.bind = gser_bind; | ||
| 288 | gser->port.func.unbind = gser_unbind; | ||
| 289 | gser->port.func.set_alt = gser_set_alt; | ||
| 290 | gser->port.func.disable = gser_disable; | ||
| 291 | |||
| 292 | status = usb_add_function(c, &gser->port.func); | ||
| 293 | if (status) | ||
| 294 | kfree(gser); | ||
| 295 | return status; | ||
| 296 | } | ||
diff --git a/drivers/usb/gadget/u_serial.h b/drivers/usb/gadget/u_serial.h index 83b45a3801ff..f9ce3005d751 100644 --- a/drivers/usb/gadget/u_serial.h +++ b/drivers/usb/gadget/u_serial.h | |||
| @@ -58,5 +58,6 @@ void gserial_disconnect(struct gserial *); | |||
| 58 | 58 | ||
| 59 | /* functions are bound to configurations by a config or gadget driver */ | 59 | /* functions are bound to configurations by a config or gadget driver */ |
| 60 | int acm_bind_config(struct usb_configuration *c, u8 port_num); | 60 | int acm_bind_config(struct usb_configuration *c, u8 port_num); |
| 61 | int gser_bind_config(struct usb_configuration *c, u8 port_num); | ||
| 61 | 62 | ||
| 62 | #endif /* __U_SERIAL_H */ | 63 | #endif /* __U_SERIAL_H */ |
