/*
* 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 "config.h"
#include "usbstring.h"
#include "ccid.h"
#include "binutil.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 const char *doserial = NULL;
static const char *doiintf = NULL;
static int usb_reader_num = -1;
static const char *cdriver = NULL;
static const char *gadgetfs = "/dev/gadget";
#define OPT_HELP 'h'
#define OPT_INTERRUPT 'n'
#define OPT_READER 'r'
#define OPT_SERIAL 's'
#define OPT_IINTERFACE 'i'
#define OPT_PRODUCT 'p'
#define OPT_VENDOR 'e'
#define OPT_VERBOSE 'v'
#define OPT_INFO 'o'
#define OPT_CARD 'c'
#define OPT_GADGETFS 'g'
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 },
{ "interface", required_argument, NULL, OPT_IINTERFACE },
{ "product", required_argument, NULL, OPT_PRODUCT },
{ "vendor", required_argument, NULL, OPT_VENDOR },
{ "interrupt", no_argument, NULL, OPT_INTERRUPT },
{ "verbose", no_argument, NULL, OPT_VERBOSE },
{ "info", no_argument, NULL, OPT_INFO },
{ "gadgetfs", required_argument, NULL, OPT_GADGETFS },
{ NULL, 0, NULL, 0 }
};
static const char *option_help[] = {
/*"Emulate HID device",*/
"Print help and exit",
"Number of reader (default: auto-detect)",
"Card driver to use (default: auto-detect)",
"USB serial number (default: random)",
"USB iInterface (default: notification status)",
"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",
"Directory where GadgetFS is mounted",
};
/* 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,
.bInterfaceSubClass = 0,
.bInterfaceProtocol = 0,
.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 */
/* Flawfinder: ignore */
static char serial [57];
static const char interrupt_on_string[] = "Insertion and removal events enabled";
static const char interrupt_off_string[] = "Insertion and removal events disabled";
static struct usb_string stringtab [] = {
{ STRINGID_MFGR, "Virtual Smart Card Architecture", },
{ STRINGID_PRODUCT, "CCID Emulator", },
{ STRINGID_SERIAL, serial, },
#ifdef WITH_PACE
{ STRINGID_CONFIG, "PACE support enabled", },
#else
{ STRINGID_CONFIG, "PACE support disabled", },
#endif
{ STRINGID_INTERFACE, interrupt_on_string, },
{ 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;
int i;
if (!doiintf) {
for (i=0; i 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 pthread_t interrupt_thread;
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
if (debug)
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)
static 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 (%d written).\n", result);
}
} while (result >= 0);
if (errno != ESHUTDOWN || result < 0) {
perror ("interrupt loop aborted");
pthread_cancel(ep0);
}
pthread_cleanup_pop (1);
fflush (stdout);
fflush (stderr);
return 0;
}
/* ccid thread, forwards ccid requests to pcsc and returns results */
static void *ccid (void *param)
{
char **names = (char **) param;
char *source_name = names[0];
char *sink_name = names[1];
int result;
size_t bufsize = sizeof(PC_to_RDR_XfrBlock_t) + CCID_EXT_APDU_MAX;
__u8 inbuf[bufsize];
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);
__u8 *outbuf = NULL;
pthread_cleanup_push (free, outbuf);
do {
/* original LinuxThreads cancelation didn't work right
* so test for it explicitly.
*/
pthread_testcancel ();
if (verbose > 1)
fprintf(stderr, "bulk loop: reading %lu bytes... ", (long unsigned) bufsize);
result = read(sink_fd, inbuf, bufsize);
if (result < 0) break;
if (verbose > 1)
fprintf(stderr, "bulk loop: got %d, done.\n", result);
if (!result) break;
result = ccid_parse_bulkout(inbuf, result, &outbuf);
if (result < 0) break;
if (verbose > 1)
fprintf(stderr, "bulk loop: writing %d bytes... ", result);
result = write(source_fd, outbuf, result);
if (verbose > 1)
fprintf(stderr, "done (%d written).\n", result);
} 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);
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 (void)
{
//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;
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");
}
if (doint) {
static char * interruptnames[1];
interruptnames[0] = EP_STATUS_NAME;
if (pthread_create (&interrupt_thread, NULL, interrupt, (void *)
interruptnames) != 0) {
perror ("can't create interrupt thread");
goto cleanup;
}
} else if (verbose) {
fprintf (stderr, "interrupt pipe 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 (hidthread, ep0)) {
pthread_cancel (hidthread);
if (pthread_join (hidthread, 0) != 0)
perror ("can't join hid thread");
hidthread = ep0;
fprintf (stderr, "cancled hid\n");
}
if (!pthread_equal (interrupt_thread, ep0)) {
pthread_cancel (interrupt_thread);
if (pthread_join (interrupt_thread, 0) != 0)
perror ("can't join interrupt thread");
interrupt_thread = ep0;
fprintf (stderr, "cancled interrupt\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 %s %s device\n",
gadgetfs, 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, returning defaults\n");
tmp = 0;
}
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, "control loop: writing %d bytes... ", result);
result = write (fd, outbuf, result);
/* TODO outbuf should also be freed on error */
free(outbuf);
if (result < 0)
goto stall;
if (verbose > 1)
fprintf(stderr, "done (%d written).\n", result);
} 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)
{
unsigned int seconds = 4;
switch (sig) {
case SIGQUIT:
seconds /= 2;
case SIGINT:
if (verbose > 1)
fprintf (stderr, "\nInitializing shutdown, "
"waiting %d seconds for threads to terminate\n",
seconds);
pthread_cancel(ep0);
sleep(seconds);
fprintf (stderr, "Doing immediate shutdown.\n");
exit(1);
break;
default:
if (verbose > 1)
fprintf (stderr, "Received unhandled signal %u\n",
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;
interrupt_thread = ccid_thread = hidthread = ep0 = pthread_self ();
pthread_cleanup_push (stop_io, NULL);
pthread_cleanup_push (close_fd, param);
/* 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)
{
/*printf("%s:%d\n", __FILE__, __LINE__);*/
int fd, c, i;
int oindex = 0;
while (1) {
c = getopt_long(argc, argv, "hnr:s:i:p:e:voc:g:", options, &oindex);
if (c == -1)
break;
switch (c) {
case OPT_HELP:
print_usage(argv[0] , options, option_help);
exit(0);
break;
case OPT_READER:
errno = 0;
usb_reader_num = strtol(optarg, NULL, 10);
if (errno) {
parse_error(argv[0], options, option_help, optarg, oindex);
exit(2);
}
break;
case OPT_SERIAL:
doserial = optarg;
break;
case OPT_IINTERFACE:
doiintf = optarg;
break;
case OPT_PRODUCT:
errno = 0;
productid = strtol(optarg, NULL, 16);
if (errno) {
parse_error(argv[0], options, option_help, optarg, oindex);
exit(2);
}
break;
case OPT_VENDOR:
errno = 0;
vendorid = strtol(optarg, NULL, 16);
if (errno) {
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 OPT_INTERRUPT:
doint++;
break;
case OPT_GADGETFS:
gadgetfs = optarg;
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 %s written by Frank Morgner.\n\n" ,
argv[0], VERSION);
return print_avail(verbose);
}
if (ccid_initialize(usb_reader_num, cdriver, verbose) < 0) {
fprintf (stderr, "Can't initialize ccid\n");
return 1;
}
if (chdir (gadgetfs) < 0) {
fprintf (stderr, "Error changing directory to %s\n", gadgetfs);
return 1;
}
if (doserial) {
for (i=0; i