/* * Copyright (C) 2009 Frank Morgner * * This file is part of ccid. * * ccid 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 3 of the License, or (at your option) any later * version. * * ccid 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 * ccid. If not, see . */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include //#include // #include #ifdef AIO /* this aio code works with libaio-0.3.106 */ #include #endif #include "usbstring.h" #include "ccid.h" #include "util.h" #define DRIVER_VENDOR_NUM 0x0D46 /* KOBIL Systems */ #define DRIVER_PRODUCT_NUM 0x3010 /* KOBIL Class 3 Reader */ static int vendorid = DRIVER_VENDOR_NUM; static int productid = DRIVER_PRODUCT_NUM; static int verbose = 0; static int debug = 0; static int dohid = 0; static int doint = 0; static int doinfo = 0; static int usb_reader_num = -1; static const char *cdriver = NULL; #define OPT_HELP 'h' #define OPT_INTERRUPT 'n' #define OPT_READER 'r' #define OPT_SERIAL 's' #define OPT_PRODUCT 'p' #define OPT_VENDOR 'e' #define OPT_VERBOSE 'v' #define OPT_INFO 'o' #define OPT_CARD 'c' static const struct option options[] = { /*{ "hid", no_argument, &dohid, 1 },*/ { "help", no_argument, NULL, OPT_HELP }, { "reader", required_argument, NULL, OPT_READER }, { "card-driver", required_argument, NULL, OPT_CARD }, { "serial", required_argument, NULL, OPT_SERIAL }, { "product", required_argument, NULL, OPT_PRODUCT }, { "vendor", required_argument, NULL, OPT_VENDOR }, { "interrupt", no_argument, &doint, OPT_INTERRUPT }, { "verbose", no_argument, NULL, OPT_VERBOSE }, { "info", no_argument, NULL, OPT_INFO }, { NULL, 0, NULL, 0 } }; static const char *option_help[] = { /*"Emulate HID device",*/ "Print help and exit", "Number of reader to use (default: auto-detect)", "Which card driver to use (default: auto-detect)", "USB serial number (default: random)", "USB product ID (default: 0x3010)", "USB vendor ID (default: 0x0D46)", "Add interrupt pipe for CCID", "Use (several times) to be more verbose", "Print version, available readers and drivers.", }; /* NOTE: these IDs don't imply endpoint numbering; host side drivers * should use endpoint descriptors, or perhaps bcdDevice, to configure * such things. Other product IDs could have different policies. */ /*-------------------------------------------------------------------------*/ /* these descriptors are modified based on what controller we find */ extern struct ccid_class_descriptor ccid_desc; #define STRINGID_MFGR 1 #define STRINGID_PRODUCT 2 #define STRINGID_SERIAL 3 #define STRINGID_CONFIG 4 #define STRINGID_INTERFACE 5 #define STRINGID_HID_INTERFACE 6 static struct usb_device_descriptor device_desc = { .bLength = sizeof device_desc, //.bcdUSB = __constant_cpu_to_le16 (0x0110), .bDescriptorType = USB_DT_DEVICE, .bcdUSB = __constant_cpu_to_le16 (0x0200), .bDeviceClass = USB_CLASS_VENDOR_SPEC, .bDeviceSubClass = 0, .bDeviceProtocol = 0, // .bMaxPacketSize0 ... set by gadgetfs .idVendor = __constant_cpu_to_le16 (DRIVER_VENDOR_NUM), .idProduct = __constant_cpu_to_le16 (DRIVER_PRODUCT_NUM), .iManufacturer = STRINGID_MFGR, .iProduct = STRINGID_PRODUCT, .iSerialNumber = STRINGID_SERIAL, .bNumConfigurations = 1, }; #define MAX_USB_POWER 1 #define CONFIG_VALUE 3 static struct usb_config_descriptor config = { .bLength = sizeof config, .bDescriptorType = USB_DT_CONFIG, /* must compute wTotalLength ... */ .bNumInterfaces = 1, .bConfigurationValue = CONFIG_VALUE, .iConfiguration = STRINGID_CONFIG, .bmAttributes = USB_CONFIG_ATT_ONE | USB_CONFIG_ATT_SELFPOWER, .bMaxPower = (MAX_USB_POWER + 1) / 2, }; static struct usb_interface_descriptor source_sink_intf = { .bLength = sizeof source_sink_intf, .bDescriptorType = USB_DT_INTERFACE, .bInterfaceClass = USB_CLASS_CSCID, .iInterface = STRINGID_INTERFACE, }; static struct hid_class_descriptor hid_desc = { .bLength = sizeof hid_desc, .bDescriptorType = 0x21, .bcdHID = __constant_cpu_to_le16(0x101), .bCountryCode = 0, .bNumDescriptors = 0, }; /* Full speed configurations are used for full-speed only devices as * well as dual-speed ones (the only kind with high speed support). */ static struct usb_endpoint_descriptor fs_source_desc = { .bLength = USB_DT_ENDPOINT_SIZE, .bDescriptorType = USB_DT_ENDPOINT, .bmAttributes = USB_ENDPOINT_XFER_BULK, /* NOTE some controllers may need FS bulk max packet size * to be smaller. it would be a chip-specific option. */ .wMaxPacketSize = __constant_cpu_to_le16 (64), }; static struct usb_endpoint_descriptor fs_sink_desc = { .bLength = USB_DT_ENDPOINT_SIZE, .bDescriptorType = USB_DT_ENDPOINT, .bmAttributes = USB_ENDPOINT_XFER_BULK, .wMaxPacketSize = __constant_cpu_to_le16 (64), }; /* some devices can handle other result packet sizes */ /*#define STATUS_MAXPACKET 16*/ /*#define LOG2_STATUS_POLL_MSEC 5*/ #define STATUS_MAXPACKET 8 #define LOG2_STATUS_POLL_MSEC 3 static struct usb_endpoint_descriptor fs_status_desc = { .bLength = USB_DT_ENDPOINT_SIZE, .bDescriptorType = USB_DT_ENDPOINT, .bmAttributes = USB_ENDPOINT_XFER_INT, .wMaxPacketSize = __constant_cpu_to_le16 (STATUS_MAXPACKET), .bInterval = (1 << LOG2_STATUS_POLL_MSEC), }; static struct usb_endpoint_descriptor fs_hid_status_desc = { .bLength = USB_DT_ENDPOINT_SIZE, .bDescriptorType = USB_DT_ENDPOINT, .bmAttributes = USB_ENDPOINT_XFER_INT, .wMaxPacketSize = __constant_cpu_to_le16 (STATUS_MAXPACKET), .bInterval = (1 << LOG2_STATUS_POLL_MSEC), }; static const struct usb_endpoint_descriptor *fs_eps [3] = { &fs_source_desc, &fs_sink_desc, &fs_status_desc, }; /* High speed configurations are used only in addition to a full-speed * ones ... since all high speed devices support full speed configs. * Of course, not all hardware supports high speed configurations. */ static struct usb_endpoint_descriptor hs_source_desc = { .bLength = USB_DT_ENDPOINT_SIZE, .bDescriptorType = USB_DT_ENDPOINT, .bmAttributes = USB_ENDPOINT_XFER_BULK, .wMaxPacketSize = __constant_cpu_to_le16 (512), }; static struct usb_endpoint_descriptor hs_sink_desc = { .bLength = USB_DT_ENDPOINT_SIZE, .bDescriptorType = USB_DT_ENDPOINT, .bmAttributes = USB_ENDPOINT_XFER_BULK, .wMaxPacketSize = __constant_cpu_to_le16 (512), .bInterval = 1, }; static struct usb_endpoint_descriptor hs_status_desc = { .bLength = USB_DT_ENDPOINT_SIZE, .bDescriptorType = USB_DT_ENDPOINT, .bmAttributes = USB_ENDPOINT_XFER_INT, .wMaxPacketSize = __constant_cpu_to_le16 (STATUS_MAXPACKET), .bInterval = LOG2_STATUS_POLL_MSEC + 3, }; static struct usb_endpoint_descriptor hs_hid_status_desc = { .bLength = USB_DT_ENDPOINT_SIZE, .bDescriptorType = USB_DT_ENDPOINT, .bmAttributes = USB_ENDPOINT_XFER_INT, .wMaxPacketSize = __constant_cpu_to_le16 (STATUS_MAXPACKET), .bInterval = LOG2_STATUS_POLL_MSEC + 3, }; static const struct usb_endpoint_descriptor *hs_eps [] = { &hs_source_desc, &hs_sink_desc, &hs_status_desc, }; /*-------------------------------------------------------------------------*/ /* 56 is the maximum for the KOBIL Class 3 Reader */ static char serial [57]; static struct usb_string stringtab [] = { { STRINGID_MFGR, "morgner@informatik.hu-berlin.de", }, { STRINGID_PRODUCT, "CCID to PCSC Gadget", }, { STRINGID_SERIAL, serial, }, { STRINGID_CONFIG, "The Configuration", }, { STRINGID_INTERFACE, "CCID to PCSC", }, { STRINGID_HID_INTERFACE, "Human Device Interface Gadget", }, }; static struct usb_gadget_strings strings = { .language = 0x0409, /* "en-us" */ .strings = stringtab, }; /*-------------------------------------------------------------------------*/ /* kernel drivers could autoconfigure like this too ... if * they were willing to waste the relevant code/data space. */ static int HIGHSPEED; static char *DEVNAME; static char *EP_IN_NAME, *EP_OUT_NAME, *EP_STATUS_NAME, *EP_HID_STATUS_NAME; /* gadgetfs currently has no chunking (or O_DIRECT/zerocopy) support * to turn big requests into lots of smaller ones; so this is "small". */ #define USB_BUFSIZE (7 * 1024) static enum usb_device_speed current_speed; static inline int min(unsigned a, unsigned b) { return (a < b) ? a : b; } static int autoconfig () { struct stat statb; /* NetChip 2280 PCI device or dummy_hcd, high/full speed */ if (stat (DEVNAME = "net2280", &statb) == 0 || stat (DEVNAME = "dummy_udc", &statb) == 0) { HIGHSPEED = 1; device_desc.bcdDevice = __constant_cpu_to_le16 (0x0100), fs_source_desc.bEndpointAddress = hs_source_desc.bEndpointAddress = USB_DIR_IN | 7; EP_IN_NAME = "ep-a"; fs_sink_desc.bEndpointAddress = hs_sink_desc.bEndpointAddress = USB_DIR_OUT | 3; EP_OUT_NAME = "ep-b"; source_sink_intf.bNumEndpoints = 3; fs_status_desc.bEndpointAddress = hs_status_desc.bEndpointAddress = USB_DIR_IN | 11; EP_STATUS_NAME = "ep-f"; if (dohid) { // TODO EP_HID_STATUS_NAME? EP_HID_STATUS_NAME = "ep-e"; fs_hid_status_desc.bEndpointAddress = hs_hid_status_desc.bEndpointAddress = USB_DIR_IN | 10; } /* Intel PXA 2xx processor, full speed only */ } else if (stat (DEVNAME = "pxa2xx_udc", &statb) == 0) { HIGHSPEED = 0; device_desc.bcdDevice = __constant_cpu_to_le16 (0x0101), fs_source_desc.bEndpointAddress = USB_DIR_IN | 6; EP_IN_NAME = "ep6in-bulk"; fs_sink_desc.bEndpointAddress = USB_DIR_OUT | 7; EP_OUT_NAME = "ep7out-bulk"; /* using bulk for this since the pxa interrupt endpoints * always use the no-toggle scheme (discouraged). */ source_sink_intf.bNumEndpoints = 3; fs_status_desc.bEndpointAddress = USB_DIR_IN | 11; EP_STATUS_NAME = "ep11in-bulk"; if (dohid) { EP_HID_STATUS_NAME = "ep12in-bulk"; fs_hid_status_desc.bEndpointAddress = USB_DIR_IN | 12; } #if 0 /* AMD au1x00 processor, full speed only */ } else if (stat (DEVNAME = "au1x00_udc", &statb) == 0) { HIGHSPEED = 0; device_desc.bcdDevice = __constant_cpu_to_le16 (0x0102), fs_source_desc.bEndpointAddress = USB_DIR_IN | 2; EP_IN_NAME = "ep2in"; fs_sink_desc.bEndpointAddress = USB_DIR_OUT | 4; EP_OUT_NAME = "ep4out"; source_sink_intf.bNumEndpoints = 3; fs_status_desc.bEndpointAddress = USB_DIR_IN | 3; EP_STATUS_NAME = "ep3in"; /* Intel SA-1100 processor, full speed only */ } else if (stat (DEVNAME = "sa1100", &statb) == 0) { HIGHSPEED = 0; device_desc.bcdDevice = __constant_cpu_to_le16 (0x0103), fs_source_desc.bEndpointAddress = USB_DIR_IN | 2; EP_IN_NAME = "ep2in-bulk"; fs_sink_desc.bEndpointAddress = USB_DIR_OUT | 1; EP_OUT_NAME = "ep1out-bulk"; source_sink_intf.bNumEndpoints = 2; ccid_desc.dwFeatures &= ~0x100000; // USB Wake up signaling not supported EP_STATUS_NAME = 0; #endif /* Toshiba TC86c001 PCI device, full speed only */ } else if (stat (DEVNAME = "goku_udc", &statb) == 0) { HIGHSPEED = 0; device_desc.bcdDevice = __constant_cpu_to_le16 (0x0104), fs_source_desc.bEndpointAddress = USB_DIR_IN | 2; EP_IN_NAME = "ep2-bulk"; fs_sink_desc.bEndpointAddress = USB_DIR_OUT | 1; EP_OUT_NAME = "ep1-bulk"; if (dohid) { source_sink_intf.bNumEndpoints = 2; // USB Wake up signaling not supported ccid_desc.dwFeatures &= ~__constant_cpu_to_le32(0x100000); EP_HID_STATUS_NAME = "ep3-bulk"; fs_hid_status_desc.bEndpointAddress = USB_DIR_IN | 3; } else { source_sink_intf.bNumEndpoints = 3; EP_STATUS_NAME = "ep3-bulk"; fs_status_desc.bEndpointAddress = USB_DIR_IN | 3; } /* Samsung S3C24xx series, full speed only */ } else if (stat (DEVNAME = "s3c2410_udc", &statb) == 0) { HIGHSPEED = 0; device_desc.bcdDevice = __constant_cpu_to_le16 (0x0110), fs_source_desc.bEndpointAddress = USB_DIR_IN | 2; EP_IN_NAME = "ep2-bulk"; fs_sink_desc.bEndpointAddress = USB_DIR_OUT | 1; EP_OUT_NAME = "ep1-bulk"; source_sink_intf.bNumEndpoints = 3; fs_status_desc.bEndpointAddress = USB_DIR_IN | 3; EP_STATUS_NAME = "ep3-bulk"; if (dohid) { EP_HID_STATUS_NAME = "ep4-bulk"; fs_hid_status_desc.bEndpointAddress = USB_DIR_IN | 4; } /* Renesas SH77xx processors, full speed only */ } else if (stat (DEVNAME = "sh_udc", &statb) == 0) { HIGHSPEED = 0; device_desc.bcdDevice = __constant_cpu_to_le16 (0x0105), fs_source_desc.bEndpointAddress = USB_DIR_IN | 2; EP_IN_NAME = "ep2in-bulk"; fs_sink_desc.bEndpointAddress = USB_DIR_OUT | 1; EP_OUT_NAME = "ep1out-bulk"; source_sink_intf.bNumEndpoints = 3; fs_status_desc.bEndpointAddress = USB_DIR_IN | 3; EP_STATUS_NAME = "ep3in-bulk"; if (dohid) { EP_HID_STATUS_NAME = "ep4in-bulk"; fs_hid_status_desc.bEndpointAddress = USB_DIR_IN | 4; } /* OMAP 1610 and newer devices, full speed only, fifo mode 0 or 3 */ } else if (stat (DEVNAME = "omap_udc", &statb) == 0) { HIGHSPEED = 0; device_desc.bcdDevice = __constant_cpu_to_le16 (0x0106), fs_source_desc.bEndpointAddress = USB_DIR_IN | 1; EP_IN_NAME = "ep1in-bulk"; fs_sink_desc.bEndpointAddress = USB_DIR_OUT | 2; EP_OUT_NAME = "ep2out-bulk"; source_sink_intf.bNumEndpoints = 3; fs_status_desc.bEndpointAddress = USB_DIR_IN | 3; EP_STATUS_NAME = "ep3in-int"; if (dohid) { EP_HID_STATUS_NAME = "ep4in-bulk"; fs_hid_status_desc.bEndpointAddress = USB_DIR_IN | 4; } /* Something based on Mentor USB Highspeed Dual-Role Controller */ } else if (stat (DEVNAME = "musb_hdrc", &statb) == 0) { HIGHSPEED = 1; device_desc.bcdDevice = __constant_cpu_to_le16 (0x0107), fs_source_desc.bEndpointAddress = hs_source_desc.bEndpointAddress = USB_DIR_IN | 1; EP_IN_NAME = "ep1in"; fs_sink_desc.bEndpointAddress = hs_sink_desc.bEndpointAddress = USB_DIR_OUT | 1; EP_OUT_NAME = "ep1out"; source_sink_intf.bNumEndpoints = 3; fs_status_desc.bEndpointAddress = hs_status_desc.bEndpointAddress = USB_DIR_IN | 3; EP_STATUS_NAME = "ep3"; /* Atmel AT91 processors, full speed only */ } else if (stat (DEVNAME = "at91_udc", &statb) == 0) { HIGHSPEED = 0; device_desc.bcdDevice = __constant_cpu_to_le16 (0x0106), fs_source_desc.bEndpointAddress = USB_DIR_IN | 1; EP_IN_NAME = "ep1"; fs_sink_desc.bEndpointAddress = USB_DIR_OUT | 2; EP_OUT_NAME = "ep2"; source_sink_intf.bNumEndpoints = 3; fs_status_desc.bEndpointAddress = USB_DIR_IN | 3; EP_STATUS_NAME = "ep3-int"; if (dohid) { EP_HID_STATUS_NAME = "ep4"; fs_hid_status_desc.bEndpointAddress = USB_DIR_IN | 4; } /* Sharp LH740x processors, full speed only */ } else if (stat (DEVNAME = "lh740x_udc", &statb) == 0) { HIGHSPEED = 0; device_desc.bcdDevice = __constant_cpu_to_le16 (0x0106), fs_source_desc.bEndpointAddress = USB_DIR_IN | 1; EP_IN_NAME = "ep1in-bulk"; fs_sink_desc.bEndpointAddress = USB_DIR_OUT | 2; EP_OUT_NAME = "ep2out-bulk"; if (dohid) { source_sink_intf.bNumEndpoints = 2; // USB Wake up signaling not supported ccid_desc.dwFeatures &= ~__constant_cpu_to_le32(0x100000); fs_hid_status_desc.bEndpointAddress = USB_DIR_IN | 3; EP_HID_STATUS_NAME = "ep3in-int"; } else { source_sink_intf.bNumEndpoints = 3; fs_status_desc.bEndpointAddress = USB_DIR_IN | 3; EP_STATUS_NAME = "ep3in-int"; } /* Atmel AT32AP700x processors, high/full speed */ } else if (stat (DEVNAME = "atmel_usba_udc", &statb) == 0) { HIGHSPEED = 1; device_desc.bcdDevice = __constant_cpu_to_le16 (0x0108); fs_source_desc.bEndpointAddress = hs_source_desc.bEndpointAddress = USB_DIR_IN | 1; EP_IN_NAME = "ep1in-bulk"; fs_sink_desc.bEndpointAddress = hs_sink_desc.bEndpointAddress = USB_DIR_OUT | 2; EP_OUT_NAME = "ep2out-bulk"; if (dohid) { source_sink_intf.bNumEndpoints = 2; // USB Wake up signaling not supported ccid_desc.dwFeatures &= ~__constant_cpu_to_le32(0x100000); fs_hid_status_desc.bEndpointAddress = hs_hid_status_desc.bEndpointAddress = USB_DIR_IN | 3; EP_HID_STATUS_NAME = "ep3in-int"; } else { source_sink_intf.bNumEndpoints = 3; fs_status_desc.bEndpointAddress = hs_status_desc.bEndpointAddress = USB_DIR_IN | 3; EP_STATUS_NAME = "ep3in-int"; } } else { DEVNAME = 0; return -ENODEV; } if (!doint) { source_sink_intf.bNumEndpoints = 2; // USB Wake up signaling not supported ccid_desc.dwFeatures &= ~__constant_cpu_to_le32(0x100000); } return 0; } #ifdef AIO static int iso; static int interval; static unsigned iosize; static unsigned bufsize = USB_BUFSIZE; /* This is almost the only place where usb needs to know whether we're * driving an isochronous stream or a bulk one. */ static int iso_autoconfig () { struct stat statb; /* ISO endpoints "must not be part of a default interface setting". * Never do it like this in "real" code! This uses the default * setting (alt 0) because it's the only one pxa supports. * * This code doesn't adjust the sample rate based on feedback. */ device_desc.idProduct = __constant_cpu_to_le16(productid); /* NetChip 2280 PCI device or dummy_hcd, high/full speed */ if (stat (DEVNAME = "net2280", &statb) == 0 || stat (DEVNAME = "dummy_udc", &statb) == 0) { unsigned bInterval, wMaxPacketSize; HIGHSPEED = 1; device_desc.bcdDevice = __constant_cpu_to_le16 (0x0100); /* this code won't use two or four uframe periods */ if (bufsize > 1024) { interval = 0; bInterval = 1; /* "modprobe net2280 fifo_mode=1" may be needed */ if (bufsize > (2 * 1024)) { wMaxPacketSize = min ((bufsize + 2)/3, 1024); bufsize = min (3 * wMaxPacketSize, bufsize); wMaxPacketSize |= 2 << 11; } else { wMaxPacketSize = min ((bufsize + 1)/2, 1024); wMaxPacketSize |= 1 << 11; } } else { bInterval = interval + 4; wMaxPacketSize = bufsize; } fs_source_desc.bEndpointAddress = hs_source_desc.bEndpointAddress = USB_DIR_IN | 7; fs_source_desc.bmAttributes = hs_source_desc.bmAttributes = USB_ENDPOINT_XFER_ISOC; fs_source_desc.wMaxPacketSize = min (bufsize, 1023); hs_source_desc.wMaxPacketSize = wMaxPacketSize; fs_source_desc.bInterval = interval + 1; hs_source_desc.bInterval = bInterval; EP_IN_NAME = "ep-a"; fs_sink_desc.bEndpointAddress = hs_sink_desc.bEndpointAddress = USB_DIR_OUT | 3; fs_sink_desc.bmAttributes = hs_sink_desc.bmAttributes = USB_ENDPOINT_XFER_ISOC; fs_sink_desc.wMaxPacketSize = min (bufsize, 1023); hs_sink_desc.wMaxPacketSize = wMaxPacketSize; fs_sink_desc.bInterval = interval + 1; hs_sink_desc.bInterval = bInterval; EP_OUT_NAME = "ep-b"; source_sink_intf.bNumEndpoints = 3; fs_status_desc.bEndpointAddress = hs_status_desc.bEndpointAddress = USB_DIR_IN | 11; EP_STATUS_NAME = "ep-f"; /* Intel PXA 2xx processor, full speed only */ } else if (stat (DEVNAME = "pxa2xx_udc", &statb) == 0) { HIGHSPEED = 0; device_desc.bcdDevice = __constant_cpu_to_le16 (0x0101), bufsize = min (bufsize, 256); fs_source_desc.bEndpointAddress = USB_DIR_IN | 3; fs_source_desc.bmAttributes = USB_ENDPOINT_XFER_ISOC; fs_source_desc.wMaxPacketSize = bufsize; fs_source_desc.bInterval = interval; EP_IN_NAME = "ep3in-iso"; fs_sink_desc.bEndpointAddress = USB_DIR_OUT | 4; fs_sink_desc.bmAttributes = USB_ENDPOINT_XFER_ISOC; fs_sink_desc.wMaxPacketSize = bufsize; fs_sink_desc.bInterval = interval; EP_OUT_NAME = "ep4out-iso"; /* using bulk for this since the pxa interrupt endpoints * always use the no-toggle scheme (discouraged). */ source_sink_intf.bNumEndpoints = 3; fs_status_desc.bEndpointAddress = USB_DIR_IN | 11; EP_STATUS_NAME = "ep11in-bulk"; /* OMAP 1610 and newer devices, full speed only, fifo mode 3 */ } else if (stat (DEVNAME = "omap_udc", &statb) == 0) { HIGHSPEED = 0; device_desc.bcdDevice = __constant_cpu_to_le16 (0x0102), fs_source_desc.bEndpointAddress = USB_DIR_IN | 7; fs_source_desc.bmAttributes = USB_ENDPOINT_XFER_ISOC; fs_source_desc.wMaxPacketSize = min (bufsize, 256); fs_source_desc.bInterval = interval; EP_IN_NAME = "ep7in-iso"; fs_sink_desc.bEndpointAddress = USB_DIR_OUT | 8; fs_sink_desc.bmAttributes = USB_ENDPOINT_XFER_ISOC; fs_sink_desc.wMaxPacketSize = min (bufsize, 256); fs_sink_desc.bInterval = interval; EP_OUT_NAME = "ep8out-iso"; source_sink_intf.bNumEndpoints = 3; fs_status_desc.bEndpointAddress = USB_DIR_IN | 9; EP_STATUS_NAME = "ep9in-int"; /* Something based on Mentor USB Highspeed Dual-Role Controller; * assumes a core that doesn't include high bandwidth support. */ } else if (stat (DEVNAME = "musb_hdrc", &statb) == 0) { unsigned bInterval, wMaxPacketSize; HIGHSPEED = 1; device_desc.bcdDevice = __constant_cpu_to_le16 (0x0103); bInterval = interval + 4; wMaxPacketSize = bufsize; fs_source_desc.bEndpointAddress = hs_source_desc.bEndpointAddress = USB_DIR_IN | 1; fs_source_desc.bmAttributes = hs_source_desc.bmAttributes = USB_ENDPOINT_XFER_ISOC; fs_source_desc.wMaxPacketSize = min (bufsize, 1023); hs_source_desc.wMaxPacketSize = wMaxPacketSize; fs_source_desc.bInterval = interval + 1; hs_source_desc.bInterval = bInterval; EP_IN_NAME = "ep1in"; fs_sink_desc.bEndpointAddress = hs_sink_desc.bEndpointAddress = USB_DIR_OUT | 1; fs_sink_desc.bmAttributes = hs_sink_desc.bmAttributes = USB_ENDPOINT_XFER_ISOC; fs_sink_desc.wMaxPacketSize = min (bufsize, 1023); hs_sink_desc.wMaxPacketSize = wMaxPacketSize; fs_sink_desc.bInterval = interval + 1; hs_sink_desc.bInterval = bInterval; EP_OUT_NAME = "ep1out"; source_sink_intf.bNumEndpoints = 3; fs_status_desc.bEndpointAddress = hs_status_desc.bEndpointAddress = USB_DIR_IN | 11; EP_STATUS_NAME = "ep3"; /* Atmel AT91 processors, full speed only */ } else if (stat (DEVNAME = "at91_udc", &statb) == 0) { HIGHSPEED = 0; device_desc.bcdDevice = __constant_cpu_to_le16 (0x0104), fs_source_desc.bEndpointAddress = USB_DIR_IN | 4; fs_source_desc.bmAttributes = USB_ENDPOINT_XFER_ISOC; fs_source_desc.wMaxPacketSize = min (bufsize, 256); fs_source_desc.bInterval = interval; EP_IN_NAME = "ep4"; fs_sink_desc.bEndpointAddress = USB_DIR_OUT | 2; fs_sink_desc.bmAttributes = USB_ENDPOINT_XFER_ISOC; fs_sink_desc.wMaxPacketSize = min (bufsize, 256); fs_sink_desc.bInterval = interval; EP_OUT_NAME = "ep5"; source_sink_intf.bNumEndpoints = 3; fs_status_desc.bEndpointAddress = USB_DIR_IN | 3; EP_STATUS_NAME = "ep3-int"; /* Atmel AT32AP700x processors, high/full speed */ } else if (stat (DEVNAME = "atmel_usba_udc", &statb) == 0){ HIGHSPEED = 1; device_desc.bcdDevice = __constant_cpu_to_le16 (0x0105); fs_source_desc.bEndpointAddress = hs_source_desc.bEndpointAddress = USB_DIR_IN | 5; fs_source_desc.bmAttributes = hs_source_desc.bmAttributes = USB_ENDPOINT_XFER_ISOC; fs_source_desc.wMaxPacketSize = hs_source_desc.wMaxPacketSize = __cpu_to_le16(min (bufsize, 1024)); fs_source_desc.bInterval = hs_source_desc.bInterval = interval; EP_IN_NAME = "ep5in-iso"; fs_sink_desc.bEndpointAddress = hs_sink_desc.bEndpointAddress = USB_DIR_OUT | 6; fs_sink_desc.bmAttributes = hs_sink_desc.bmAttributes = USB_ENDPOINT_XFER_ISOC; fs_sink_desc.wMaxPacketSize = hs_sink_desc.wMaxPacketSize = __cpu_to_le16(min (bufsize, 1024)); fs_sink_desc.bInterval = hs_sink_desc.bInterval = interval; EP_OUT_NAME = "ep6out-iso"; source_sink_intf.bNumEndpoints = 3; fs_status_desc.bEndpointAddress = hs_status_desc.bEndpointAddress = USB_DIR_IN | 3; EP_STATUS_NAME = "ep3in-int"; } else { DEVNAME = 0; return -ENODEV; } if (verbose) { fprintf (stderr, "iso fs wMaxPacket %04x bInterval %02x\n", __le16_to_cpu(fs_sink_desc.wMaxPacketSize), fs_sink_desc.bInterval); if (HIGHSPEED) fprintf (stderr, "iso hs wMaxPacket %04x bInterval %02x\n", __le16_to_cpu(hs_sink_desc.wMaxPacketSize), hs_sink_desc.bInterval); } return 0; } #else #define iso 0 #endif /* AIO */ /*-------------------------------------------------------------------------*/ /* full duplex data, with at least three threads: ep0, sink, and source */ static pthread_t ep0; static pthread_t ccid_thread; static pthread_t hidthread; static int source_fd = -1; static int sink_fd = -1; static int status_fd = -1; static int hidstatus_fd = -1; // FIXME no result i/o yet static void close_fd (void *fd_ptr) { int result, fd; fd = *(int *)fd_ptr; *(int *)fd_ptr = -1; /* test the FIFO ioctls (non-ep0 code paths) */ if (pthread_self () != ep0) { result = ioctl (fd, GADGETFS_FIFO_STATUS); if (result < 0) { /* ENODEV reported after disconnect */ if (errno != ENODEV && errno != EOPNOTSUPP) perror ("get fifo result"); } else { fprintf (stderr, "fd %d, unclaimed = %d\n", fd, result); if (result) { result = ioctl (fd, GADGETFS_FIFO_FLUSH); if (result < 0) perror ("fifo flush"); } } } if (close (fd) < 0) perror ("close fd"); else fprintf (stderr, "closed fd\n"); } /* you should be able to open and configure endpoints * whether or not the host is connected */ static int ep_config (char *name, const char *label, struct usb_endpoint_descriptor *fs, struct usb_endpoint_descriptor *hs ) { int fd, result; char buf [USB_BUFSIZE]; /* open and initialize with endpoint descriptor(s) */ fd = open (name, O_RDWR); if (fd < 0) { result = -errno; fprintf (stderr, "%s open %s error %d (%s)\n", label, name, errno, strerror (errno)); return result; } /* one (fs or ls) or two (fs + hs) sets of config descriptors */ *(__u32 *)buf = 1; /* tag for this format */ memcpy (buf + 4, fs, USB_DT_ENDPOINT_SIZE); if (HIGHSPEED) memcpy (buf + 4 + USB_DT_ENDPOINT_SIZE, hs, USB_DT_ENDPOINT_SIZE); result = write (fd, buf, 4 + USB_DT_ENDPOINT_SIZE + (HIGHSPEED ? USB_DT_ENDPOINT_SIZE : 0)); if (result < 0) { result = -errno; fprintf (stderr, "%s config %s error %d (%s)\n", label, name, errno, strerror (errno)); close (fd); return result; } else if (verbose > 1) { unsigned long id; id = pthread_self (); fprintf (stderr, "%s (%ld): fd %d\n", label, id, fd); } return fd; } #define source_open(name) \ ep_config(name,__FUNCTION__, &fs_source_desc, &hs_source_desc) #define sink_open(name) \ ep_config(name,__FUNCTION__, &fs_sink_desc, &hs_sink_desc) #define status_open(name) \ ep_config(name,__FUNCTION__, &fs_status_desc, &hs_status_desc) #define hidstatus_open(name) \ ep_config(name,__FUNCTION__, &fs_hid_status_desc, &hs_hid_status_desc) /* ccid thread, forwards ccid requests to pcsc and returns results */ static void *ccid (void *param) { pthread_t interrupt_thread; void *interrupt (void *param) { status_fd = status_open (((char **) param)[0]); if (source_fd < 0) { /* an error concerning status endpoint is not fatal for in/out bulk*/ /* transfer */ if (verbose > 1) perror("status_fd"); pthread_exit(0); } pthread_cleanup_push (close_fd, &status_fd); int result; RDR_to_PC_NotifySlotChange_t *slotchange; do { /* original LinuxThreads cancelation didn't work right */ /* so test for it explicitly. */ pthread_testcancel (); if (ccid_state_changed(&slotchange, -1)) { /* don't bother host, when nothing changed */ if (verbose > 1) fprintf(stderr, "interrupt loop: writing RDR_to_PC_NotifySlotChange... "); result = write(status_fd, slotchange, sizeof *slotchange); if (verbose > 1) fprintf(stderr, "done.\n"); } } while (result >= 0); if (errno != ESHUTDOWN || result < 0) { perror ("interrupt loop aborted"); pthread_cancel(ccid_thread); pthread_cancel(ep0); } pthread_cleanup_pop (1); return 0; } void close_interrupt () { if (doint) { pthread_cancel(interrupt_thread); if (pthread_join (interrupt_thread, 0) != 0) perror ("can't join interrupt thread"); fprintf (stderr, "cancled interrupt loop\n"); } } char **names = (char **) param; char *source_name = names[0]; char *sink_name = names[1]; char *status_name = names[2]; int result; source_fd = source_open (source_name); if (source_fd < 0) { if (verbose > 1) perror("source_fd"); goto error; } pthread_cleanup_push (close_fd, &source_fd); sink_fd = sink_open (sink_name); if (sink_fd < 0) { if (verbose > 1) perror("sink_fd"); goto error; } pthread_cleanup_push (close_fd, &sink_fd); pthread_cleanup_push (ccid_shutdown, NULL); if (doint) { static char * interruptnames[1]; interruptnames[0] = status_name; if (pthread_create (&interrupt_thread, NULL, interrupt, (void *) interruptnames) != 0) { perror ("can't create interrupt thread"); goto error; } } else if (verbose) { fprintf (stderr, "interrupt pipe disabled\n"); } pthread_cleanup_push (close_interrupt, NULL); __u8 *outbuf = NULL; pthread_cleanup_push (free, outbuf); size_t bufsize = 512; __u8 *inbuf = malloc(bufsize); pthread_cleanup_push (free, inbuf); if (inbuf == NULL) { if (verbose > 1) perror("malloc"); goto error; } do { /* original LinuxThreads cancelation didn't work right * so test for it explicitly. */ pthread_testcancel (); if (verbose > 1) fprintf(stderr, "reading %lu bytes... ", (long unsigned) bufsize); result = read(sink_fd, inbuf, bufsize); if (result < 0) break; if (verbose > 1) fprintf(stderr, "got %d, done.\n", result); if (!result) break; result = ccid_parse_bulkin(inbuf, &outbuf); if (result < 0) break; if (verbose > 1) fprintf(stderr, "writing %d bytes... ", result); result = write(source_fd, outbuf, result); if (verbose > 1) fprintf(stderr, "done.\n"); } while (result >= 0); if (errno != ESHUTDOWN || result < 0) { perror ("ccid loop aborted"); pthread_cancel(ep0); } pthread_cleanup_pop (1); pthread_cleanup_pop (1); pthread_cleanup_pop (1); pthread_cleanup_pop (1); pthread_cleanup_pop (1); pthread_cleanup_pop (1); fflush (stdout); fflush (stderr); return 0; error: pthread_cancel(ep0); pthread_exit(0); } static void *hid (void *param) { char **names = (char **) param; char *status_name = names[0]; int result = 0; hidstatus_fd = hidstatus_open (status_name); if (hidstatus_fd < 0) { if (verbose > 1) perror("hidstatus_fd"); goto error; } pthread_cleanup_push (close_fd, &hidstatus_fd); __u8 *outbuf = NULL; pthread_cleanup_push (free, outbuf); size_t bufsize = 512; __u8 *inbuf = malloc(bufsize); pthread_cleanup_push (free, inbuf); if (inbuf == NULL) { if (verbose > 1) perror("malloc"); goto error; } do { /* original LinuxThreads cancelation didn't work right * so test for it explicitly. */ pthread_testcancel (); } while (result >= 0); if (errno != ESHUTDOWN || result < 0) { perror ("hid loop aborted"); pthread_cancel(ep0); } pthread_cleanup_pop (1); pthread_cleanup_pop (1); pthread_cleanup_pop (1); fflush (stdout); fflush (stderr); return 0; error: pthread_cancel(ep0); pthread_exit(0); } static void start_io () { //int tmp; sigset_t allsig, oldsig; #ifdef AIO /* iso uses the same API as bulk/interrupt. we queue one * (u)frame's worth of data per i/o request, and the host * polls that queue once per interval. */ switch (current_speed) { case USB_SPEED_FULL: if (iso) iosize = __le16_to_cpup (&fs_source_desc .wMaxPacketSize); else iosize = bufsize; break; case USB_SPEED_HIGH: /* for iso, we updated bufsize earlier */ iosize = bufsize; break; default: fprintf (stderr, "bogus link speed %d\n", current_speed); return; } #endif /* AIO */ sigfillset (&allsig); errno = pthread_sigmask (SIG_SETMASK, &allsig, &oldsig); if (errno < 0) { perror ("set thread signal mask"); return; } /* is it true that the LSB requires programs to disconnect * from their controlling tty before pthread_create()? * why? this clearly doesn't ... */ static char *names[2]; names[0] = EP_IN_NAME; names[1] = EP_OUT_NAME; names[2] = EP_STATUS_NAME; if (pthread_create (&ccid_thread, NULL, ccid, (void *) names) != 0) { perror ("can't create ccid thread"); goto cleanup; } static char * hidnames[1]; hidnames[0] = EP_HID_STATUS_NAME; if (dohid) { config.bNumInterfaces = 2; if (pthread_create (&hidthread, NULL, hid, (void *) hidnames) != 0) { perror ("can't create hid thread"); goto cleanup; } } else { if (verbose) fprintf (stderr, "HID disabled.\n"); } /* give the other threads a chance to run before we report * success to the host. * FIXME better yet, use pthread_cond_timedwait() and * synchronize on ep config success. */ sched_yield (); cleanup: errno = pthread_sigmask (SIG_SETMASK, &oldsig, 0); if (errno != 0) { perror ("restore sigmask"); exit (-1); } } static void stop_io () { if (!pthread_equal (ccid_thread, ep0)) { pthread_cancel (ccid_thread); if (pthread_join (ccid_thread, 0) != 0) perror ("can't join ccid thread"); ccid_thread = ep0; fprintf (stderr, "cancled ccid\n"); } if (!pthread_equal (ccid_thread, ep0)) { pthread_cancel (hidthread); if (pthread_join (hidthread, 0) != 0) perror ("can't join hid thread"); hidthread = ep0; fprintf (stderr, "cancled hid\n"); } } /*-------------------------------------------------------------------------*/ static char * build_config (char *cp, const struct usb_endpoint_descriptor **ep, const struct usb_endpoint_descriptor *hid_ep) { struct usb_config_descriptor *c; int i; c = (struct usb_config_descriptor *) cp; memcpy (cp, &config, config.bLength); cp += config.bLength; memcpy (cp, &source_sink_intf, sizeof source_sink_intf); cp += sizeof source_sink_intf; // Append vendor class specification memcpy (cp, &ccid_desc, sizeof ccid_desc); cp += sizeof ccid_desc; for (i = 0; i < source_sink_intf.bNumEndpoints; i++) { memcpy (cp, ep [i], USB_DT_ENDPOINT_SIZE); cp += USB_DT_ENDPOINT_SIZE; } if (dohid) { struct usb_interface_descriptor hid_intf = { .bLength = sizeof hid_intf, .bDescriptorType = USB_DT_INTERFACE, .bInterfaceNumber = 1, .bInterfaceClass = USB_CLASS_HID, .iInterface = STRINGID_HID_INTERFACE, .bNumEndpoints = 1, }; memcpy (cp, &hid_intf, sizeof hid_intf); cp += sizeof hid_intf; // Append vendor class specification memcpy (cp, &hid_desc, sizeof hid_desc); cp += sizeof hid_desc; for (i = 0; i < hid_intf.bNumEndpoints; i++) { memcpy (cp, &(hid_ep [i]), USB_DT_ENDPOINT_SIZE); cp += USB_DT_ENDPOINT_SIZE; } } c->wTotalLength = __cpu_to_le16 (cp - (char *) c); return cp; } static int init_device (void) { char buf [4096], *cp = &buf [0]; int fd; int result; #ifdef AIO if (iso) result = iso_autoconfig (); else #endif result = autoconfig (); if (result < 0) { fprintf (stderr, "?? don't recognize /dev/gadget %s device\n", iso ? "iso" : "bulk"); return result; } fd = open (DEVNAME, O_RDWR); if (fd < 0) { perror (DEVNAME); return -errno; } *(__u32 *)cp = 0; /* tag for this format */ cp += 4; /* write full then high speed configs */ cp = build_config (cp, fs_eps, &fs_hid_status_desc); if (HIGHSPEED) cp = build_config (cp, hs_eps, &hs_hid_status_desc); device_desc.idVendor = __cpu_to_le16 (vendorid); device_desc.idProduct = __cpu_to_le16 (productid); if (verbose) { fprintf(stderr, "idVendor=%04X idProduct=%04X\n", vendorid, productid); } /* and device descriptor at the end */ memcpy (cp, &device_desc, sizeof device_desc); cp += sizeof device_desc; result = write (fd, &buf [0], cp - &buf [0]); if (result < 0) { perror ("write dev descriptors"); close (fd); return result; } else if (result != (cp - buf)) { fprintf (stderr, "dev init, wrote %d expected %ld\n", result, (long int) (cp - buf)); close (fd); return -EIO; } return fd; } static void handle_control (int fd, struct usb_ctrlrequest *setup) { int result, tmp; __u8 buf [256]; __u16 value, index, length; value = __le16_to_cpu(setup->wValue); index = __le16_to_cpu(setup->wIndex); length = __le16_to_cpu(setup->wLength); if ((setup->bRequestType & USB_TYPE_MASK) != USB_TYPE_STANDARD) goto special; switch (setup->bRequest) { /* usb 2.0 spec ch9 requests */ case USB_REQ_GET_DESCRIPTOR: if (verbose > 1) fprintf(stderr, "USB_REQ_GET_DESCRIPTOR\n"); if (setup->bRequestType != USB_DIR_IN) goto stall; switch (value >> 8) { case USB_DT_STRING: tmp = value & 0x0ff; if (verbose > 1) fprintf (stderr, "... get string %d lang %04x\n", tmp, index); if (tmp != 0 && index != strings.language) { fprintf (stderr, "wrong language\n"); goto stall; } memset (buf, 0, 256); /* zero all the bytes */ result = usb_gadget_get_string (&strings, tmp, buf); if (result < 0) { perror("usb_gadget_get_string"); goto stall; } tmp = result; if (length < tmp) tmp = length; result = write (fd, buf, tmp); if (result < 0) { if (errno == EIDRM) fprintf (stderr, "string timeout\n"); else perror ("write string data"); } else if (result != tmp) { fprintf (stderr, "short string write, %d\n", result); } break; default: goto stall; } return; case USB_REQ_SET_CONFIGURATION: if (verbose > 1) fprintf (stderr, "USB_REQ_SET_CONFIGURATION #%d\n", value); if (setup->bRequestType != USB_DIR_OUT) goto stall; /* Kernel is normally waiting for us to finish reconfiguring * the device. * * Some hardware can't, notably older PXA2xx hardware. (With * racey and restrictive config change automagic. PXA 255 is * OK, most PXA 250s aren't. If it has a UDC CFR register, * it can handle deferred response for SET_CONFIG.) To handle * such hardware, don't write code this way ... instead, keep * the endpoints always active and don't rely on seeing any * config change events, either this or SET_INTERFACE. */ switch (value) { case CONFIG_VALUE: if ( source_fd >= 0 || sink_fd >= 0 || status_fd >= 0 || hidstatus_fd >= 0 ) stop_io (); start_io (); break; case 0: stop_io (); break; default: /* kernel bug -- "can't happen" */ fprintf (stderr, "? illegal config\n"); goto stall; } /* ... ack (a write would stall) */ result = read (fd, &result, 0); if (result) perror ("ack SET_CONFIGURATION"); return; case USB_REQ_GET_INTERFACE: if (verbose > 1) fprintf (stderr, "USB_REQ_GET_INTERFACE\n"); if (setup->bRequestType != (USB_DIR_IN|USB_RECIP_INTERFACE) || index != 0 || length > 1) goto stall; /* only one altsetting in this driver */ buf [0] = 0; result = write (fd, buf, length); if (result < 0) { if (errno == EIDRM) fprintf (stderr, "GET_INTERFACE timeout\n"); else perror ("write GET_INTERFACE data"); } else if (result != length) { fprintf (stderr, "short GET_INTERFACE write, %d\n", result); } return; case USB_REQ_SET_INTERFACE: if (verbose > 1) fprintf (stderr, "USB_REQ_SET_INTERFACE\n"); if (setup->bRequestType != USB_RECIP_INTERFACE || index != 0 || value != 0) goto stall; /* just reset toggle/halt for the interface's endpoints */ result = 0; if (ioctl (source_fd, GADGETFS_CLEAR_HALT) < 0) { result = errno; perror ("reset source fd"); } if (ioctl (sink_fd, GADGETFS_CLEAR_HALT) < 0) { result = errno; perror ("reset sink fd"); } if (status_fd > 0) { if (ioctl (status_fd, GADGETFS_CLEAR_HALT) < 0) { result = errno; perror ("reset status fd"); } } if (hidstatus_fd > 0) { if (ioctl (hidstatus_fd, GADGETFS_CLEAR_HALT) < 0) { result = errno; perror ("reset hidstatus fd"); } } /* FIXME eventually reset the result endpoint too */ if (result) goto stall; /* ... and ack (a write would stall) */ result = read (fd, &result, 0); if (result) perror ("ack SET_INTERFACE"); return; default: goto stall; } special: switch (setup->bRequestType) { case USB_REQ_CCID: { __u8 *outbuf = NULL; result = ccid_parse_control(setup, &outbuf); if (result < 0 || result > 256) goto stall; if (verbose > 1) fprintf(stderr, "writing %d bytes... ", result); result = write (fd, outbuf, result); if (verbose > 1) fprintf(stderr, "done.\n"); if (result < 0) goto stall; } return; default: goto stall; } stall: if (verbose) { fprintf (stderr, "... protocol stall %02x.%02x\n", setup->bRequestType, setup->bRequest); for (tmp = 0; tmp<5; tmp++) { printf("%d: %s\n", stringtab[tmp].id, stringtab[tmp].s); } } /* non-iso endpoints are stalled by issuing an i/o request * in the "wrong" direction. ep0 is special only because * the direction isn't fixed. */ if (setup->bRequestType & USB_DIR_IN) result = read (fd, &result, 0); else result = write (fd, &result, 0); if (result != -1) fprintf (stderr, "can't stall ep0 for %02x.%02x\n", setup->bRequestType, setup->bRequest); else if (errno != EL2HLT) perror ("ep0 stall"); } static void signothing (int sig, siginfo_t *info, void *ptr) { /* NOP */ if (verbose > 1) fprintf (stderr, "%s %d\n", __FUNCTION__, sig); } static const char *speed (enum usb_device_speed s) { switch (s) { case USB_SPEED_LOW: return "low speed"; case USB_SPEED_FULL: return "full speed"; case USB_SPEED_HIGH: return "high speed"; default: return "UNKNOWN speed"; } } /*-------------------------------------------------------------------------*/ /* control thread, handles main event loop */ #define NEVENT 5 #define LOGDELAY (15 * 60) /* seconds before stdout timestamp */ static void *ep0_thread (void *param) { int fd = *(int*) param; struct sigaction action; time_t now, last; struct pollfd ep0_poll; ccid_thread = ep0 = pthread_self (); pthread_cleanup_push (close_fd, param); pthread_cleanup_push (stop_io, NULL); /* REVISIT signal handling ... normally one pthread should * be doing sigwait() to handle all async signals. */ action.sa_sigaction = signothing; sigfillset (&action.sa_mask); action.sa_flags = SA_SIGINFO; if (sigaction (SIGINT, &action, NULL) < 0) { perror ("SIGINT"); return 0; } if (sigaction (SIGQUIT, &action, NULL) < 0) { perror ("SIGQUIT"); return 0; } ep0_poll.fd = fd; ep0_poll.events = POLLIN | POLLOUT | POLLHUP; /* event loop */ last = 0; for (;;) { int tmp; struct usb_gadgetfs_event event [NEVENT]; int connected = 0; int i, nevent; /* Use poll() to test that mechanism, to generate * activity timestamps, and to make it easier to * tweak this code to work without pthreads. When * AIO is needed without pthreads, ep0 can be driven * instead using SIGIO. */ tmp = poll(&ep0_poll, 1, -1); if (verbose) { time (&now); if ((now - last) > LOGDELAY) { char timebuf[26]; last = now; ctime_r (&now, timebuf); printf ("\n** %s", timebuf); } } if (tmp < 0) { /* exit path includes EINTR exits */ perror("poll"); break; } tmp = read (fd, &event, sizeof event); if (tmp < 0) { if (errno == EAGAIN) { sleep (1); continue; } perror ("ep0 read after poll"); goto done; } nevent = tmp / sizeof event [0]; if (nevent != 1 && verbose > 1) fprintf (stderr, "read %d ep0 events\n", nevent); for (i = 0; i < nevent; i++) { switch (event [i].type) { case GADGETFS_NOP: if (verbose) fprintf (stderr, "NOP\n"); break; case GADGETFS_CONNECT: connected = 1; current_speed = event [i].u.speed; if (verbose) fprintf (stderr, "CONNECT %s\n", speed (event [i].u.speed)); break; case GADGETFS_SETUP: connected = 1; handle_control (fd, &event [i].u.setup); break; case GADGETFS_DISCONNECT: connected = 0; current_speed = USB_SPEED_UNKNOWN; if (verbose) fprintf(stderr, "DISCONNECT\n"); stop_io (); break; case GADGETFS_SUSPEND: // connected = 1; if (verbose) fprintf (stderr, "SUSPEND\n"); break; default: fprintf (stderr, "* unhandled event %d\n", event [i].type); } } continue; done: fflush (stdout); if (connected) stop_io (); break; } if (verbose) fprintf (stderr, "ep0 done.\n"); pthread_cleanup_pop (1); pthread_cleanup_pop (1); fflush (stdout); fflush (stderr); return 0; } int main (int argc, char **argv) { int fd, c, i; int oindex = 0; /* random initial serial number */ srand ((int) time (0)); for (i = 0; i < sizeof serial - 1; ) { c = rand () % 127; if ((('a' <= c && c <= 'z') || ('0' <= c && c <= '9'))) serial [i++] = c; } while (1) { c = getopt_long(argc, argv, "hnr:s:i::u::a::z::I::A::p:e:voc:", options, &oindex); if (c == -1) break; switch (c) { case OPT_HELP: print_usage(argv[0] , options, option_help); exit(0); break; case OPT_READER: if (sscanf(optarg, "%d", &usb_reader_num) != 1) { parse_error(argv[0], options, option_help, optarg, oindex); exit(2); } break; case OPT_SERIAL: if (sscanf(optarg, "%56s", serial) != 1) { parse_error(argv[0], options, option_help, optarg, oindex); exit(2); } break; case OPT_PRODUCT: if (sscanf(optarg, "%x", &productid) != 1) { parse_error(argv[0], options, option_help, optarg, oindex); exit(2); } break; case OPT_VENDOR: if (sscanf(optarg, "%x", &vendorid) != 1) { parse_error(argv[0], options, option_help, optarg, oindex); exit(2); } break; case OPT_CARD: cdriver = optarg; break; case OPT_VERBOSE: verbose++; break; case OPT_INFO: doinfo++; break; case '?': /* fall through */ default: exit(1); break; } } if (optind < argc) { fprintf (stderr, "Unknown argument%s:", optind+1 == argc ? "" : "s"); while (optind < argc) { fprintf(stderr, " \"%s\"", argv[optind++]); fprintf(stderr, "%c", optind == argc ? '\n' : ','); } exit(1); } if (doinfo) { fprintf(stderr, "%s 0.9 written by Frank Morgner.\n\n" , argv[0]); return print_avail(verbose); } if (ccid_initialize(usb_reader_num, cdriver, verbose) < 0) { perror("Can't initialize ccid"); return 1; } if (verbose) fprintf (stderr, "serial=\"%s\"\n", serial); if (chdir ("/dev/gadget") < 0) { perror ("can't chdir /dev/gadget"); return 1; } fd = init_device (); if (fd < 0) return 1; if (debug) fprintf (stderr, "/dev/gadget/%s ep0 configured\n", DEVNAME); fflush (stdout); fflush (stderr); (void) ep0_thread (&fd); return 0; }