* [Qemu-devel] [PATCH 0/7] usb-ccid (v15) @ 2011-01-11 8:42 Alon Levy 2011-01-11 8:42 ` [Qemu-devel] [PATCH 1/7] usb-ccid: add CCID bus Alon Levy ` (8 more replies) 0 siblings, 9 replies; 35+ messages in thread From: Alon Levy @ 2011-01-11 8:42 UTC (permalink / raw) To: qemu-devel This patchset adds three new devices, usb-ccid, ccid-card-passthru and ccid-card-emulated, providing a CCID bus, a simple passthru protocol implementing card requiring a client, and a standalone emulated card. It also introduces a new directory libcaccard with CAC card emulation, CAC is a type of ISO 7816 smart card. Tree for pull: git://anongit.freedesktop.org/~alon/qemu usb_ccid.v15 v14-v15 changes: * add patch with --enable-smartcard and --disable-smartcard and only disable ccid-card-emulated if nss not found. * add patch with description strings * s/libcaccard/libcacard/ in docs/ccid.txt v13-v14 changes: - support device_del/device_add on ccid-card-* and usb-ccid * usb-ccid: * lose card reference when card device deleted * check slot number and deny adding a slot if one is already added. * ccid-card-*: use qdev_simple_unplug_cb in both emulated and passthru ccid cards, the exitfn already takes care of triggering card removal in the usb dev. * libcacard: * remove double include of config-host.mak * add replay of card events to libcacard to support second and more emulation * don't initialize more then once (doesn't support it right now, so one thread, NSS thread, is left when device_del is done) * add VCARD_EMUL_INIT_ALREADY_INITED * ccid-card-emulated: * take correct mutexes on signaling to fix deadlocks on device_del * allow card insertion/removal event without proper reader insertion event v12-v13 changes: * libcacard: * fix Makefile clean to remove vscclient * fix double include of config-host in Makefile * usb-ccid: remove attach/detach logic, usb is always attached. Guest doesn't care if there is a reader attached with no card anyway. * ccid-card-passthru: don't close chr_dev on removal, makes it possible to use device_del/device_add to create remove/insertion for debugging. v11-v12 changes: * fix out of tree build v10-v11 changes: * fix last patch that removed one of the doc files. * updated flow table in docs/ccid.txt v8-v10 changes: * usb-ccid: * add slot for future use (Gerd) * ifdef ENABLE_MIGRATION for migration support on account of usb migration not being ready in general. (Gerd) * verbosified commit messages. (Gerd) * put libcacard docs in libcacard commit. (Gerd) v8-v9 changes: * Blue Swirl comments: * white space fixes * enabled by default, disabled only if missing nss * forgotten fix from v8 (don't build libcacard.so) * added a note about device being little endian * library renamed from libcaccard to libcacard * squashed both of libcacard patches, they touched different files anyway. v7-v8 changes: * Blue Swirl comments: * usb-ccid: deannonymize some structs * usb-ccid: coding style change - answer_t and bulk_in_t fixed * usb-ccid: handle endianess conversion between guest and host * usb-ccid: s/ccid_bulk_in_copy_out/ccid_bulk_in_copy_to_guest/ * ccid-card-emulated: fix segfault if backend not specified * ccid-card-emulated: let last reader inserted win * libcaccard: remove double vscard_common.h v6->v7 changes: * external libcaccard became internal directory libcaccard * statically link object files into qemu * produce libcaccard.so for usage by external projects * applied coding style to new code (please check me) - did not use the qemu options parsing for libcaccard, since it seems to draw large amounts of qemu code (monitor for instance). v5->v6 changes: * really remove static debug (I apologize for claiming to have done so before) v4->v5 changes: * rebased to latest * remove static debug in card devices * fix --enable-smartcard to link * stall instead of assert when exceeding BULK_OUT_DATA_SIZE * make ccid_reserve_recv_buf for too large len discard message, not exit * make ccid_reserve_recv_buf return void* * fix typo * remove commented code in VMState v3->v4: * remove ccid field in CCIDBus * remove static debug in bus * add back docs v2->v3: * split into bus (usb-ccid.c, uses ccid.h) and card (ccid-card-passthru.c). * removed documentation (being revised). v1->v2: * all QSIMPLEQ turned into fixed sized rings * all allocated buffers turned into fixed size buffers * added migration support * added a message to tell client qemu has migrated to ip:port * for lack of monitor commands ip:port are 0:0, which causes the updated vscclient to connect to one port higher on the same host. will add monitor commands in a separate patch. tested with current setup. Alon Levy (6): usb-ccid: add CCID bus ccid: add passthru card device ccid: add ccid-card-emulated device (v2) ccid: add docs ccid: configure: add --enable/disable and nss only disable ccid: add qdev description strings Robert Relyea (1): libcacard: initial commit after coding style fixes Makefile | 6 +- Makefile.objs | 7 + Makefile.target | 2 + configure | 48 ++ docs/ccid.txt | 135 +++++ docs/libcacard.txt | 483 +++++++++++++++ hw/ccid-card-emulated.c | 535 +++++++++++++++++ hw/ccid-card-passthru.c | 273 +++++++++ hw/ccid.h | 35 ++ hw/usb-ccid.c | 1356 +++++++++++++++++++++++++++++++++++++++++++ libcacard/Makefile | 14 + libcacard/cac.c | 411 +++++++++++++ libcacard/cac.h | 20 + libcacard/card_7816.c | 780 +++++++++++++++++++++++++ libcacard/card_7816.h | 60 ++ libcacard/card_7816t.h | 163 ++++++ libcacard/config.h | 81 +++ libcacard/event.c | 112 ++++ libcacard/eventt.h | 28 + libcacard/link_test.c | 20 + libcacard/mutex.h | 59 ++ libcacard/passthru.c | 612 +++++++++++++++++++ libcacard/passthru.h | 50 ++ libcacard/vcard.c | 350 +++++++++++ libcacard/vcard.h | 85 +++ libcacard/vcard_emul.h | 62 ++ libcacard/vcard_emul_nss.c | 1171 +++++++++++++++++++++++++++++++++++++ libcacard/vcard_emul_type.c | 60 ++ libcacard/vcard_emul_type.h | 29 + libcacard/vcardt.h | 66 +++ libcacard/vevent.h | 26 + libcacard/vreader.c | 526 +++++++++++++++++ libcacard/vreader.h | 54 ++ libcacard/vreadert.h | 23 + libcacard/vscard_common.h | 130 ++++ libcacard/vscclient.c | 710 ++++++++++++++++++++++ 36 files changed, 8580 insertions(+), 2 deletions(-) create mode 100644 docs/ccid.txt create mode 100644 docs/libcacard.txt create mode 100644 hw/ccid-card-emulated.c create mode 100644 hw/ccid-card-passthru.c create mode 100644 hw/ccid.h create mode 100644 hw/usb-ccid.c create mode 100644 libcacard/Makefile create mode 100644 libcacard/cac.c create mode 100644 libcacard/cac.h create mode 100644 libcacard/card_7816.c create mode 100644 libcacard/card_7816.h create mode 100644 libcacard/card_7816t.h create mode 100644 libcacard/config.h create mode 100644 libcacard/event.c create mode 100644 libcacard/eventt.h create mode 100644 libcacard/link_test.c create mode 100644 libcacard/mutex.h create mode 100644 libcacard/passthru.c create mode 100644 libcacard/passthru.h create mode 100644 libcacard/vcard.c create mode 100644 libcacard/vcard.h create mode 100644 libcacard/vcard_emul.h create mode 100644 libcacard/vcard_emul_nss.c create mode 100644 libcacard/vcard_emul_type.c create mode 100644 libcacard/vcard_emul_type.h create mode 100644 libcacard/vcardt.h create mode 100644 libcacard/vevent.h create mode 100644 libcacard/vreader.c create mode 100644 libcacard/vreader.h create mode 100644 libcacard/vreadert.h create mode 100644 libcacard/vscard_common.h create mode 100644 libcacard/vscclient.c -- 1.7.3.4 ^ permalink raw reply [flat|nested] 35+ messages in thread
* [Qemu-devel] [PATCH 1/7] usb-ccid: add CCID bus 2011-01-11 8:42 [Qemu-devel] [PATCH 0/7] usb-ccid (v15) Alon Levy @ 2011-01-11 8:42 ` Alon Levy 2011-01-11 8:42 ` [Qemu-devel] [PATCH 2/7] ccid: add passthru card device Alon Levy ` (7 subsequent siblings) 8 siblings, 0 replies; 35+ messages in thread From: Alon Levy @ 2011-01-11 8:42 UTC (permalink / raw) To: qemu-devel A CCID device is a smart card reader. It is a USB device, defined at [1]. This patch introduces the usb-ccid device that is a ccid bus. Next patches will introduce two card types to use it, a passthru card and an emulated card. [1] http://www.usb.org/developers/devclass_docs/DWG_Smart-Card_CCID_Rev110. Signed-off-by: Alon Levy <alevy@redhat.com> --- Makefile.objs | 1 + configure | 6 + hw/ccid.h | 35 ++ hw/usb-ccid.c | 1355 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 1397 insertions(+), 0 deletions(-) create mode 100644 hw/ccid.h create mode 100644 hw/usb-ccid.c diff --git a/Makefile.objs b/Makefile.objs index d6b3d60..7da4771 100644 --- a/Makefile.objs +++ b/Makefile.objs @@ -197,6 +197,7 @@ hw-obj-$(CONFIG_FDC) += fdc.o hw-obj-$(CONFIG_ACPI) += acpi.o acpi_piix4.o hw-obj-$(CONFIG_APM) += pm_smbus.o apm.o hw-obj-$(CONFIG_DMA) += dma.o +hw-obj-$(CONFIG_SMARTCARD) += usb-ccid.o # PPC devices hw-obj-$(CONFIG_OPENPIC) += openpic.o diff --git a/configure b/configure index 831a741..839980c 100755 --- a/configure +++ b/configure @@ -334,6 +334,7 @@ trace_backend="nop" trace_file="trace" spice="" rbd="" +smartcard="yes" # OS specific if check_define __linux__ ; then @@ -2441,6 +2442,7 @@ echo "Trace output file $trace_file-<pid>" echo "spice support $spice" echo "rbd support $rbd" echo "xfsctl support $xfs" +echo "smartcard support $smartcard" if test $sdl_too_old = "yes"; then echo "-> Your SDL version is too old - please upgrade to have SDL support" @@ -2710,6 +2712,10 @@ if test "$spice" = "yes" ; then echo "CONFIG_SPICE=y" >> $config_host_mak fi +if test "$smartcard" = "yes" ; then + echo "CONFIG_SMARTCARD=y" >> $config_host_mak +fi + # XXX: suppress that if [ "$bsd" = "yes" ] ; then echo "CONFIG_BSD=y" >> $config_host_mak diff --git a/hw/ccid.h b/hw/ccid.h new file mode 100644 index 0000000..af59070 --- /dev/null +++ b/hw/ccid.h @@ -0,0 +1,35 @@ +#ifndef __CCID_H__ +#define __CCID_H__ + +#include "qdev.h" + +typedef struct CCIDCardState CCIDCardState; +typedef struct CCIDCardInfo CCIDCardInfo; + +struct CCIDCardState { + DeviceState qdev; + uint32_t slot; // For future use with multiple slot reader. +}; + +struct CCIDCardInfo { + DeviceInfo qdev; + void (*print)(Monitor *mon, CCIDCardState *card, int indent); + const uint8_t *(*get_atr)(CCIDCardState *card, uint32_t *len); + void (*apdu_from_guest)(CCIDCardState *card, const uint8_t *apdu, uint32_t len); + int (*exitfn)(CCIDCardState *card); + int (*initfn)(CCIDCardState *card); +}; + +void ccid_card_send_apdu_to_guest(CCIDCardState *card, uint8_t* apdu, uint32_t len); +void ccid_card_card_removed(CCIDCardState *card); +void ccid_card_card_inserted(CCIDCardState *card); +void ccid_card_card_error(CCIDCardState *card, uint64_t error); +void ccid_card_qdev_register(CCIDCardInfo *card); + +/* support guest visible insertion/removal of ccid devices based on actual + * devices connected/removed. Called by card implementation (passthru, local) */ +int ccid_card_ccid_attach(CCIDCardState *card); +void ccid_card_ccid_detach(CCIDCardState *card); + +#endif // __CCID_H__ + diff --git a/hw/usb-ccid.c b/hw/usb-ccid.c new file mode 100644 index 0000000..58f69a6 --- /dev/null +++ b/hw/usb-ccid.c @@ -0,0 +1,1355 @@ +/* + * CCID Device emulation + * + * Based on usb-serial.c: + * Copyright (c) 2006 CodeSourcery. + * Copyright (c) 2008 Samuel Thibault <samuel.thibault@ens-lyon.org> + * Written by Paul Brook, reused for FTDI by Samuel Thibault, + * Reused for CCID by Alon Levy. + * Contributed to by Robert Relyea + * Copyright (c) 2010 Red Hat. + * + * This code is licenced under the LGPL. + */ + +/* References: + * + * CCID Specification Revision 1.1 April 22nd 2005 + * "Universal Serial Bus, Device Class: Smart Card" + * Specification for Integrated Circuit(s) Cards Interface Devices + * + * Endianess note: from the spec (1.3) + * "Fields that are larger than a byte are stored in little endian + * + * KNOWN BUGS + * 1. remove/insert can sometimes result in removed state instead of inserted. + * This is a result of the following: + * symptom: dmesg shows ERMOTEIO (-121), pcscd shows -99. This happens + * when we send a too short packet, seen in uhci-usb.c, resulting from + * a urb requesting SPD and us returning a smaller packet. + * Not sure which messages trigger this. + * + * Migration note: + * + * All the VMStateDescription's are left here for future use, but + * not enabled right now since there is no support for USB migration. + * + * To enable define ENABLE_MIGRATION + */ + +#include "qemu-common.h" +#include "qemu-error.h" +#include "usb.h" +#include "monitor.h" + +#include "hw/ccid.h" + +//#define DEBUG_CCID + +#define DPRINTF(s, lvl, fmt, ...) \ +do { if (lvl <= s->debug) { printf("usb-ccid: " fmt , ## __VA_ARGS__); } } while (0) + +#define CCID_DEV_NAME "usb-ccid" + +/* The two options for variable sized buffers: + * make them constant size, for large enough constant, + * or handle the migration complexity - VMState doesn't handle this case. + * sizes are expected never to be exceeded, unless guest misbehaves. */ +#define BULK_OUT_DATA_SIZE 65536 +#define PENDING_ANSWERS_NUM 128 + +#define BULK_IN_BUF_SIZE 384 +#define BULK_IN_PENDING_NUM 8 + +#define InterfaceOutClass ((USB_DIR_OUT|USB_TYPE_CLASS|USB_RECIP_INTERFACE)<<8) +#define InterfaceInClass ((USB_DIR_IN |USB_TYPE_CLASS|USB_RECIP_INTERFACE)<<8) + +#define CCID_CONTROL_ABORT 0x1 +#define CCID_CONTROL_GET_CLOCK_FREQUENCIES 0x2 +#define CCID_CONTROL_GET_DATA_RATES 0x3 + +#define CCID_PRODUCT_DESCRIPTION "QEMU USB CCID" +#define CCID_VENDOR_DESCRIPTION "QEMU " QEMU_VERSION +#define CCID_INTERFACE_NAME "CCID Interface" +#define CCID_SERIAL_NUMBER_STRING "1" +/* Using Gemplus Vendor and Product id + Effect on various drivers: + * usbccid.sys (winxp, others untested) is a class driver so it doesn't care. + * linux has a number of class drivers, but openct filters based on + vendor/product (/etc/openct.conf under fedora), hence Gemplus. + */ +#define CCID_VENDOR_ID 0x08e6 +#define CCID_PRODUCT_ID 0x4433 +#define CCID_DEVICE_VERSION 0x0000 + +/* BULK_OUT messages from PC to Reader + Defined in CCID Rev 1.1 6.1 (page 26) + */ +#define CCID_MESSAGE_TYPE_PC_to_RDR_IccPowerOn 0x62 +#define CCID_MESSAGE_TYPE_PC_to_RDR_IccPowerOff 0x63 +#define CCID_MESSAGE_TYPE_PC_to_RDR_GetSlotStatus 0x65 +#define CCID_MESSAGE_TYPE_PC_to_RDR_XfrBlock 0x6f +#define CCID_MESSAGE_TYPE_PC_to_RDR_GetParameters 0x6c +#define CCID_MESSAGE_TYPE_PC_to_RDR_ResetParameters 0x6d +#define CCID_MESSAGE_TYPE_PC_to_RDR_SetParameters 0x61 +#define CCID_MESSAGE_TYPE_PC_to_RDR_Escape 0x6b +#define CCID_MESSAGE_TYPE_PC_to_RDR_IccClock 0x6e +#define CCID_MESSAGE_TYPE_PC_to_RDR_T0APDU 0x6a +#define CCID_MESSAGE_TYPE_PC_to_RDR_Secure 0x69 +#define CCID_MESSAGE_TYPE_PC_to_RDR_Mechanical 0x71 +#define CCID_MESSAGE_TYPE_PC_to_RDR_Abort 0x72 +#define CCID_MESSAGE_TYPE_PC_to_RDR_SetDataRateAndClockFrequency 0x73 + +/* BULK_IN messages from Reader to PC + Defined in CCID Rev 1.1 6.2 (page 48) + */ +#define CCID_MESSAGE_TYPE_RDR_to_PC_DataBlock 0x80 +#define CCID_MESSAGE_TYPE_RDR_to_PC_SlotStatus 0x81 +#define CCID_MESSAGE_TYPE_RDR_to_PC_Parameters 0x82 +#define CCID_MESSAGE_TYPE_RDR_to_PC_Escape 0x83 +#define CCID_MESSAGE_TYPE_RDR_to_PC_DataRateAndClockFrequency 0x84 + +/* INTERRUPT_IN messages from Reader to PC + Defined in CCID Rev 1.1 6.3 (page 56) + */ +#define CCID_MESSAGE_TYPE_RDR_to_PC_NotifySlotChange 0x50 +#define CCID_MESSAGE_TYPE_RDR_to_PC_HardwareError 0x51 + +/* Endpoints for CCID - addresses are up to us to decide. + To support slot insertion and removal we must have an interrupt in ep + in addition we need a bulk in and bulk out ep + 5.2, page 20 + */ +#define CCID_INT_IN_EP 1 +#define CCID_BULK_IN_EP 2 +#define CCID_BULK_OUT_EP 3 + +/* bmSlotICCState masks */ +#define SLOT_0_STATE_MASK 1 +#define SLOT_0_CHANGED_MASK 2 + +/* Status codes that go in bStatus (see 6.2.6) */ +enum { + ICC_STATUS_PRESENT_ACTIVE = 0, + ICC_STATUS_PRESENT_INACTIVE, + ICC_STATUS_NOT_PRESENT +}; + +enum { + COMMAND_STATUS_NO_ERROR = 0, + COMMAND_STATUS_FAILED, + COMMAND_STATUS_TIME_EXTENSION_REQUIRED +}; + +/* Error codes that go in bError (see 6.2.6) + */ +enum { + ERROR_CMD_NOT_SUPPORTED = 0, + ERROR_CMD_ABORTED = -1, + ERROR_ICC_MUTE = -2, + ERROR_XFR_PARITY_ERROR = -3, + ERROR_XFR_OVERRUN = -4, + ERROR_HW_ERROR = -5, +}; + +/* 6.2.6 RDR_to_PC_SlotStatus definitions */ +enum { + CLOCK_STATUS_RUNNING = 0, + /* 0 - Clock Running, 1 - Clock stopped in State L, 2 - H, + 3 - unkonwn state. rest are RFU + */ +}; + +typedef struct __attribute__ ((__packed__)) CCID_Header { + uint8_t bMessageType; + uint32_t dwLength; + uint8_t bSlot; + uint8_t bSeq; +} CCID_Header; + +typedef struct __attribute__ ((__packed__)) CCID_BULK_IN { + CCID_Header hdr; + uint8_t bStatus; /* Only used in BULK_IN */ + uint8_t bError; /* Only used in BULK_IN */ +} CCID_BULK_IN; + +typedef struct __attribute__ ((__packed__)) CCID_SlotStatus { + CCID_BULK_IN b; + uint8_t bClockStatus; +} CCID_SlotStatus; + +typedef struct __attribute__ ((__packed__)) CCID_Parameter { + CCID_BULK_IN b; + uint8_t bProtocolNum; + uint8_t abProtocolDataStructure[0]; +} CCID_Parameter; + +typedef struct __attribute__ ((__packed__)) CCID_DataBlock { + CCID_BULK_IN b; + uint8_t bChainParameter; + uint8_t abData[0]; +} CCID_DataBlock; + +/* 6.1.4 PC_to_RDR_XfrBlock */ +typedef struct __attribute__ ((__packed__)) CCID_XferBlock { + CCID_Header hdr; + uint8_t bBWI; /* Block Waiting Timeout */ + uint16_t wLevelParameter; /* XXX currently unused */ + uint8_t abData[0]; +} CCID_XferBlock; + +typedef struct __attribute__ ((__packed__)) CCID_IccPowerOn { + CCID_Header hdr; + uint8_t bPowerSelect; + uint16_t abRFU; +} CCID_IccPowerOn; + +typedef struct __attribute__ ((__packed__)) CCID_IccPowerOff { + CCID_Header hdr; + uint16_t abRFU; +} CCID_IccPowerOff; + +typedef struct __attribute__ ((__packed__)) CCID_SetParameter { + CCID_Header hdr; + uint8_t bProtocolNum; + uint8_t abProtocolDataStructure[0]; +} CCID_SetParameter; + +typedef struct CCID_Notify_Slot_Change { + uint8_t bMessageType; /* CCID_MESSAGE_TYPE_RDR_to_PC_NotifySlotChange */ + uint8_t bmSlotICCState; +} CCID_Notify_Slot_Change; + +/* used for DataBlock response to XferBlock */ +typedef struct Answer { + uint8_t slot; + uint8_t seq; +} Answer; + +/* pending BULK_IN messages */ +typedef struct BulkIn { + uint8_t data[BULK_IN_BUF_SIZE]; + uint32_t len; + uint32_t pos; +} BulkIn; + +enum { + MIGRATION_NONE, + MIGRATION_MIGRATED, +}; + +typedef struct CCIDBus CCIDBus; +typedef struct USBCCIDState USBCCIDState; + +#define MAX_PROTOCOL_SIZE 7 + +/** + * powered - defaults to true, changed by PowerOn/PowerOff messages + */ +struct USBCCIDState { + USBDevice dev; + CCIDBus *bus; + CCIDCardState *card; + CCIDCardInfo *cardinfo; /* caching the info pointer */ + uint8_t debug; + BulkIn bulk_in_pending[BULK_IN_PENDING_NUM]; /* circular */ + uint32_t bulk_in_pending_start; + uint32_t bulk_in_pending_end; /* first free */ + uint32_t bulk_in_pending_num; + BulkIn *current_bulk_in; + uint8_t bulk_out_data[BULK_OUT_DATA_SIZE]; + uint32_t bulk_out_pos; + uint8_t bmSlotICCState; + uint8_t powered; + uint8_t notify_slot_change; + uint64_t last_answer_error; + Answer pending_answers[PENDING_ANSWERS_NUM]; + uint32_t pending_answers_start; + uint32_t pending_answers_end; + uint32_t pending_answers_num; + uint8_t bError; + uint8_t bmCommandStatus; + uint8_t bProtocolNum; + uint8_t abProtocolDataStructure[MAX_PROTOCOL_SIZE]; + uint32_t ulProtocolDataStructureSize; + uint32_t state_vmstate; + uint8_t migration_state; + uint32_t migration_target_ip; + uint16_t migration_target_port; +}; + +/* Slot specific variables. We emulate a single slot card reader. + */ + + +/* CCID Spec chapter 4: CCID uses a standard device descriptor per Chapter 9, + * "USB Device Framework", section 9.6.1, in the Universal Serial Bus + * Specification. + * + * This device implemented based on the spec and with an Athena Smart Card + * Reader as reference: + * 0dc3:1004 Athena Smartcard Solutions, Inc. + */ + +static const uint8_t qemu_ccid_dev_descriptor[] = { + 0x12, /* u8 bLength; */ + USB_DT_DEVICE, /* u8 bDescriptorType; Device */ + 0x10, 0x01, /* u16 bcdUSB; v1.1 */ + + 0x00, /* u8 bDeviceClass; */ + 0x00, /* u8 bDeviceSubClass; */ + 0x00, /* u8 bDeviceProtocol; [ low/full speeds only ] */ + 0x40, /* u8 bMaxPacketSize0; 8 Bytes (valid: 8,16,32,64) */ + + /* Vendor and product id are arbitrary. */ + /* u16 idVendor */ + CCID_VENDOR_ID & 0xff, CCID_VENDOR_ID >> 8, + /* u16 idProduct */ + CCID_PRODUCT_ID & 0xff, CCID_PRODUCT_ID >> 8, + /* u16 bcdDevice */ + CCID_DEVICE_VERSION & 0xff, CCID_DEVICE_VERSION >> 8, + 0x01, /* u8 iManufacturer; */ + 0x02, /* u8 iProduct; */ + 0x03, /* u8 iSerialNumber; */ + 0x01, /* u8 bNumConfigurations; */ +}; + +static const uint8_t qemu_ccid_config_descriptor[] = { + + /* one configuration */ + 0x09, /* u8 bLength; */ + USB_DT_CONFIG, /* u8 bDescriptorType; Configuration */ + 0x5d, 0x00, /* u16 wTotalLength; 9+9+54+7+7+7 */ + 0x01, /* u8 bNumInterfaces; (1) */ + 0x01, /* u8 bConfigurationValue; */ + 0x00, /* u8 iConfiguration; */ + 0xe0, /* u8 bmAttributes; + Bit 7: must be set, + 6: Self-powered, + 5: Remote wakeup, + 4..0: resvd */ + 100/2, /* u8 MaxPower; 50 == 100mA */ + + /* one interface */ + 0x09, /* u8 if_bLength; */ + USB_DT_INTERFACE, /* u8 if_bDescriptorType; Interface */ + 0x00, /* u8 if_bInterfaceNumber; */ + 0x00, /* u8 if_bAlternateSetting; */ + 0x03, /* u8 if_bNumEndpoints; */ + 0x0b, /* u8 if_bInterfaceClass; Smart Card Device Class */ + 0x00, /* u8 if_bInterfaceSubClass; Subclass code */ + 0x00, /* u8 if_bInterfaceProtocol; Protocol code */ + 0x04, /* u8 if_iInterface; Index of string descriptor */ + + /* Smart Card Device Class Descriptor */ + 0x36, /* u8 bLength; */ + 0x21, /* u8 bDescriptorType; Functional */ + 0x10, 0x01, /* u16 bcdCCID; CCID Specification Release Number. */ + 0x00, /* u8 bMaxSlotIndex; The index of the highest available + slot on this device. All slots are consecutive starting + at 00h. */ + 0x07, /* u8 bVoltageSupport; 01h - 5.0v, 02h - 3.0, 03 - 1.8 */ + + 0x03, 0x00, /* u32 dwProtocols; RRRR PPPP. RRRR = 0000h.*/ + 0x00, 0x00, /* PPPP: 0001h = Protocol T=0, 0002h = Protocol T=1 */ + /* u32 dwDefaultClock; in kHZ (0x0fa0 is 4 MHz) */ + 0xa0, 0x0f, 0x00, 0x00, + /* u32 dwMaximumClock; */ + 0x00, 0x00, 0x01, 0x00, + 0x00, /* u8 bNumClockSupported; 0 means just the default and max. */ + /* u32 dwDataRate ;bps. 9600 == 00002580h */ + 0x80, 0x25, 0x00, 0x00, + /* u32 dwMaxDataRate ; 11520 bps == 0001C200h */ + 0x00, 0xC2, 0x01, 0x00, + 0x00, /* u8 bNumDataRatesSupported; 00 means all rates between + * default and max */ + /* u32 dwMaxIFSD; maximum IFSD supported by CCID for protocol + * T=1 (Maximum seen from various cards) */ + 0xfe, 0x00, 0x00, 0x00, + /* u32 dwSyncProtocols; 1 - 2-wire, 2 - 3-wire, 4 - I2C */ + 0x00, 0x00, 0x00, 0x00, + /* u32 dwMechanical; 0 - no special characteristics. */ + 0x00, 0x00, 0x00, 0x00, + /* u32 dwFeatures; + * 0 - No special characteristics + * + 2 Automatic parameter configuration based on ATR data + * + 4 Automatic activation of ICC on inserting + * + 8 Automatic ICC voltage selection + * + 10 Automatic ICC clock frequency change + * + 20 Automatic baud rate change + * + 40 Automatic parameters negotiation made by the CCID + * + 80 automatic PPS made by the CCID + * 100 CCID can set ICC in clock stop mode + * 200 NAD value other then 00 accepted (T=1 protocol) + * + 400 Automatic IFSD exchange as first exchange (T=1) + * One of the following only: + * + 10000 TPDU level exchanges with CCID + * 20000 Short APDU level exchange with CCID + * 40000 Short and Extended APDU level exchange with CCID + * + * + 100000 USB Wake up signaling supported on card insertion + * and removal. Must set bit 5 in bmAttributes in Configuration + * descriptor if 100000 is set.*/ + 0xfe, 0x04, 0x11, 0x00, + /* u32 dwMaxCCIDMessageLength; For extended APDU in [261 + 10 + * , 65544 + 10]. Otherwise the minimum is wMaxPacketSize of + * the Bulk-OUT endpoint */ + 0x12, 0x00, 0x01, 0x00, + 0xFF, /* u8 bClassGetResponse; Significant only for CCID that + * offers an APDU level for exchanges. Indicates the default + * class value used by the CCID when it sends a Get Response + * command to perform the transportation of an APDU by T=0 + * protocol + * FFh indicates that the CCID echos the class of the APDU. + */ + 0xFF, /* u8 bClassEnvelope; EAPDU only. Envelope command for T=0 */ + 0x00, 0x00, /* u16 wLcdLayout; XXYY Number of lines (XX) and chars per + * line for LCD display used for PIN entry. 0000 - no LCD */ + 0x01, /* u8 bPINSupport; 01h PIN Verification, + * 02h PIN Modification */ + 0x01, /* u8 bMaxCCIDBusySlots; */ + + /* Interrupt-IN endpoint */ + 0x07, /* u8 ep_bLength; */ + /* u8 ep_bDescriptorType; Endpoint */ + USB_DT_ENDPOINT, + /* u8 ep_bEndpointAddress; IN Endpoint 1 */ + 0x80 | CCID_INT_IN_EP, + 0x03, /* u8 ep_bmAttributes; Interrupt */ + 0x40, 0x00, /* u16 ep_wMaxPacketSize; */ + 0xff, /* u8 ep_bInterval; */ + + /* Bulk-In endpoint */ + 0x07, /* u8 ep_bLength; */ + /* u8 ep_bDescriptorType; Endpoint */ + USB_DT_ENDPOINT, + /* u8 ep_bEndpointAddress; IN Endpoint 2 */ + 0x80 | CCID_BULK_IN_EP, + 0x02, /* u8 ep_bmAttributes; Bulk */ + 0x40, 0x00, /* u16 ep_wMaxPacketSize; */ + 0x00, /* u8 ep_bInterval; */ + + /* Bulk-Out endpoint */ + 0x07, /* u8 ep_bLength; */ + /* u8 ep_bDescriptorType; Endpoint */ + USB_DT_ENDPOINT, + /* u8 ep_bEndpointAddress; OUT Endpoint 3 */ + CCID_BULK_OUT_EP, + 0x02, /* u8 ep_bmAttributes; Bulk */ + 0x40, 0x00, /* u16 ep_wMaxPacketSize; */ + 0x00, /* u8 ep_bInterval; */ + +}; + +static bool ccid_has_pending_answers(USBCCIDState *s) +{ + return s->pending_answers_num > 0; +} + +static void ccid_clear_pending_answers(USBCCIDState *s) +{ + s->pending_answers_num = 0; + s->pending_answers_start = 0; + s->pending_answers_end = 0; +} + +static void ccid_print_pending_answers(USBCCIDState *s) +{ +#ifdef DEBUG_CCID + Answer *answer; + int i, count; + + printf("usb-ccid: pending answers:"); + if (!ccid_has_pending_answers(s)) { + printf(" empty\n"); + return; + } + for (i = s->pending_answers_start, count=s->pending_answers_num ; + count > 0; count--, i++) { + answer = &s->pending_answers[i % PENDING_ANSWERS_NUM]; + if (count == 1) { + printf("%d:%d\n", answer->slot, answer->seq); + } else { + printf("%d:%d,", answer->slot, answer->seq); + } + } +#endif +} + +static void ccid_add_pending_answer(USBCCIDState *s, CCID_Header *hdr) +{ + Answer* answer; + + assert(s->pending_answers_num++ < PENDING_ANSWERS_NUM); + answer = &s->pending_answers[(s->pending_answers_end++) % PENDING_ANSWERS_NUM]; + answer->slot = hdr->bSlot; + answer->seq = hdr->bSeq; + ccid_print_pending_answers(s); +} + +static void ccid_remove_pending_answer(USBCCIDState *s, + uint8_t *slot, uint8_t *seq) +{ + Answer *answer; + + assert(s->pending_answers_num-- > 0); + answer = &s->pending_answers[(s->pending_answers_start++) % PENDING_ANSWERS_NUM]; + *slot = answer->slot; + *seq = answer->seq; + ccid_print_pending_answers(s); +} + +static void ccid_bulk_in_clear(USBCCIDState *s) +{ + s->bulk_in_pending_start = 0; + s->bulk_in_pending_end = 0; + s->bulk_in_pending_num = 0; +} + +static void ccid_bulk_in_release(USBCCIDState *s) +{ + assert(s->current_bulk_in != NULL); + s->current_bulk_in->pos = 0; + s->current_bulk_in = NULL; +} + +static void ccid_bulk_in_get(USBCCIDState *s) +{ + if (s->current_bulk_in != NULL || s->bulk_in_pending_num == 0) { + return; + } + assert(s->bulk_in_pending_num > 0); + s->bulk_in_pending_num--; + s->current_bulk_in = &s->bulk_in_pending[ + (s->bulk_in_pending_start++) % BULK_IN_PENDING_NUM]; +} + +static void* ccid_reserve_recv_buf(USBCCIDState* s, uint16_t len) +{ + BulkIn* bulk_in; + + DPRINTF(s, 4, "%s: QUEUE: reserve %d bytes\n", __func__, len); + + /* look for an existing element */ + if (len > BULK_IN_BUF_SIZE) { + printf("usb-ccid.c: %s: len larger then max (%d>%d). discarding message.\n", + __func__, len, BULK_IN_BUF_SIZE); + return NULL; + } + if (s->bulk_in_pending_num >= BULK_IN_PENDING_NUM) { + printf("usb-ccid.c: %s: No free bulk_in buffers. discarding message.\n", + __func__); + return NULL; + } + bulk_in = &s->bulk_in_pending[(s->bulk_in_pending_end++) % BULK_IN_PENDING_NUM]; + s->bulk_in_pending_num++; + bulk_in->len = len; + return bulk_in->data; +} + +static void ccid_reset(USBCCIDState *s) +{ + ccid_bulk_in_clear(s); + ccid_clear_pending_answers(s); +} + +static void ccid_detach(USBCCIDState *s) +{ + ccid_reset(s); +} + +static void ccid_handle_reset(USBDevice *dev) +{ + USBCCIDState *s = DO_UPCAST(USBCCIDState, dev, dev); + + DPRINTF(s, 1, "Reset\n"); + + ccid_reset(s); +} + +static int ccid_handle_control(USBDevice *dev, int request, int value, + int index, int length, uint8_t *data) +{ + USBCCIDState *s = DO_UPCAST(USBCCIDState, dev, dev); + int ret = 0; + + DPRINTF(s, 1, "got control %x, value %x\n",request, value); + switch (request) { + case DeviceRequest | USB_REQ_GET_STATUS: + data[0] = (0 << USB_DEVICE_SELF_POWERED) | + (dev->remote_wakeup << USB_DEVICE_REMOTE_WAKEUP); + data[1] = 0x00; + ret = 2; + break; + case DeviceOutRequest | USB_REQ_CLEAR_FEATURE: + if (value == USB_DEVICE_REMOTE_WAKEUP) { + dev->remote_wakeup = 0; + } else { + goto fail; + } + ret = 0; + break; + case DeviceOutRequest | USB_REQ_SET_FEATURE: + if (value == USB_DEVICE_REMOTE_WAKEUP) { + dev->remote_wakeup = 1; + } else { + goto fail; + } + ret = 0; + break; + case DeviceOutRequest | USB_REQ_SET_ADDRESS: + dev->addr = value; + ret = 0; + break; + case DeviceRequest | USB_REQ_GET_DESCRIPTOR: + switch(value >> 8) { + case USB_DT_DEVICE: + memcpy(data, qemu_ccid_dev_descriptor, + sizeof(qemu_ccid_dev_descriptor)); + ret = sizeof(qemu_ccid_dev_descriptor); + break; + case USB_DT_CONFIG: + memcpy(data, qemu_ccid_config_descriptor, + sizeof(qemu_ccid_config_descriptor)); + ret = sizeof(qemu_ccid_config_descriptor); + break; + case USB_DT_STRING: + switch(value & 0xff) { + case 0: + /* language ids */ + data[0] = 4; + data[1] = 3; + data[2] = 0x09; + data[3] = 0x04; + ret = 4; + break; + case 1: + /* vendor description */ + ret = set_usb_string(data, CCID_VENDOR_DESCRIPTION); + break; + case 2: + /* product description */ + ret = set_usb_string(data, CCID_PRODUCT_DESCRIPTION); + break; + case 3: + /* serial number */ + ret = set_usb_string(data, CCID_SERIAL_NUMBER_STRING); + break; + case 4: + /* interface name */ + ret = set_usb_string(data, CCID_INTERFACE_NAME); + break; + default: + goto fail; + } + break; + default: + goto fail; + } + break; + case DeviceRequest | USB_REQ_GET_CONFIGURATION: + data[0] = 1; + ret = 1; + break; + case DeviceOutRequest | USB_REQ_SET_CONFIGURATION: + /* Only one configuration - we just ignore the request */ + ret = 0; + break; + case DeviceRequest | USB_REQ_GET_INTERFACE: + data[0] = 0; + ret = 1; + break; + case InterfaceOutRequest | USB_REQ_SET_INTERFACE: + ret = 0; + break; + case EndpointOutRequest | USB_REQ_CLEAR_FEATURE: + ret = 0; + break; + + /* Class specific requests. */ + case InterfaceOutClass | CCID_CONTROL_ABORT: + DPRINTF(s, 1, "ccid_control abort UNIMPLEMENTED\n"); + ret = USB_RET_STALL; + break; + case InterfaceInClass | CCID_CONTROL_GET_CLOCK_FREQUENCIES: + DPRINTF(s, 1, "ccid_control get clock frequencies UNIMPLEMENTED\n"); + ret = USB_RET_STALL; + break; + case InterfaceInClass | CCID_CONTROL_GET_DATA_RATES: + DPRINTF(s, 1, "ccid_control get data rates UNIMPLEMENTED\n"); + ret = USB_RET_STALL; + break; + default: + fail: + DPRINTF(s, 1, "got unsupported/bogus control %x, value %x\n", request, value); + ret = USB_RET_STALL; + break; + } + return ret; +} + +static bool ccid_card_inserted(USBCCIDState *s) +{ + return s->bmSlotICCState & SLOT_0_STATE_MASK; +} + +static uint8_t ccid_card_status(USBCCIDState *s) +{ + return ccid_card_inserted(s) + ? (s->powered ? + ICC_STATUS_PRESENT_ACTIVE + : ICC_STATUS_PRESENT_INACTIVE + ) + : ICC_STATUS_NOT_PRESENT; +} + +static uint8_t ccid_calc_status(USBCCIDState *s) +{ + /* page 55, 6.2.6, calculation of bStatus from bmICCStatus and + bmCommandStatus + */ + uint8_t ret = ccid_card_status(s) | (s->bmCommandStatus << 6); + DPRINTF(s, 4, "status = %d\n", ret); + return ret; +} + +static void ccid_reset_error_status(USBCCIDState* s) +{ + s->bError = ERROR_CMD_NOT_SUPPORTED; + s->bmCommandStatus = COMMAND_STATUS_NO_ERROR; +} + +static void ccid_write_slot_status(USBCCIDState* s, CCID_Header* recv) +{ + CCID_SlotStatus *h = ccid_reserve_recv_buf(s, sizeof(CCID_SlotStatus)); + if (h == NULL) { + return; + } + h->b.hdr.bMessageType = CCID_MESSAGE_TYPE_RDR_to_PC_SlotStatus; + h->b.hdr.dwLength = 0; + h->b.hdr.bSlot = recv->bSlot; + h->b.hdr.bSeq = recv->bSeq; + h->b.bStatus = ccid_calc_status(s); + h->b.bError = s->bError; + h->bClockStatus = CLOCK_STATUS_RUNNING; + ccid_reset_error_status(s); +} + +static void ccid_write_parameters(USBCCIDState* s, CCID_Header* recv) +{ + CCID_Parameter *h; + uint32_t len = s->ulProtocolDataStructureSize; + + h = ccid_reserve_recv_buf(s, sizeof(CCID_Parameter) + len); + if (h == NULL) { + return; + } + h->b.hdr.bMessageType = CCID_MESSAGE_TYPE_RDR_to_PC_Parameters; + h->b.hdr.dwLength = 0; + h->b.hdr.bSlot = recv->bSlot; + h->b.hdr.bSeq = recv->bSeq; + h->b.bStatus = ccid_calc_status(s); + h->b.bError = s->bError; + h->bProtocolNum = s->bProtocolNum; + memcpy(h->abProtocolDataStructure, s->abProtocolDataStructure, len); + ccid_reset_error_status(s); +} + +static void ccid_write_data_block( + USBCCIDState* s, uint8_t slot, uint8_t seq, + const uint8_t* data, uint32_t len) +{ + CCID_DataBlock *p = ccid_reserve_recv_buf(s, sizeof(*p) + len); + + if (p == NULL) { + return; + } + p->b.hdr.bMessageType = CCID_MESSAGE_TYPE_RDR_to_PC_DataBlock; + p->b.hdr.dwLength = cpu_to_le32(len); + p->b.hdr.bSlot = slot; + p->b.hdr.bSeq = seq; + p->b.bStatus = ccid_calc_status(s); + p->b.bError = s->bError; +#ifdef DEBUG_CCID + if (p->b.bError) { + DPRINTF(s, 4, "error %d", p->b.bError); + } +#endif + memcpy(p->abData, data, len); + ccid_reset_error_status(s); +} + +static void ccid_write_data_block_answer(USBCCIDState* s, + const uint8_t* data, uint32_t len) +{ + uint8_t seq; + uint8_t slot; + + if (!ccid_has_pending_answers(s)) { + abort(); + } + ccid_remove_pending_answer(s, &slot, &seq); + ccid_write_data_block(s, slot, seq, data, len); +} + +static void ccid_write_data_block_atr(USBCCIDState* s, CCID_Header* recv) +{ + const uint8_t *atr = NULL; + uint32_t len = 0; + + if (s->card) { + atr = s->cardinfo->get_atr(s->card, &len); + } + ccid_write_data_block(s, recv->bSlot, recv->bSeq, atr, len); +} + +static void ccid_set_parameters(USBCCIDState *s, CCID_Header *recv) +{ + CCID_SetParameter *ph = (CCID_SetParameter *) recv; + uint32_t len = 0; + if (ph->bProtocolNum == 0) { + len = 5; + } + if (ph->bProtocolNum == 1) { + len = 7; + } + if (len == 0) { + s->bmCommandStatus = COMMAND_STATUS_FAILED; + s->bError = 7; /* Protocol invalid or not supported */ + return; + } + s->bProtocolNum = ph->bProtocolNum; + memcpy(s->abProtocolDataStructure, ph->abProtocolDataStructure, len); + s->ulProtocolDataStructureSize = len; + DPRINTF(s, 1, "%s: using len %d\n", __func__, len); +} + +/* must be 5 bytes for T=0, 7 bytes for T=1 + * See page 52 */ +static const uint8_t abDefaultProtocolDataStructure[7] = + { 0x77, 0x00, 0x00, 0x00, 0x00, 0xfe /*IFSC*/, 0x00 /*NAD*/ }; + +static void ccid_reset_parameters(USBCCIDState *s) +{ + uint32_t len = sizeof(abDefaultProtocolDataStructure); + + s->bProtocolNum = 1; /* T=1 */ + s->ulProtocolDataStructureSize = len; + memcpy(s->abProtocolDataStructure, abDefaultProtocolDataStructure, len); +} + +static void ccid_report_error_failed(USBCCIDState *s, uint8_t error) +{ + s->bmCommandStatus = COMMAND_STATUS_FAILED; + s->bError = error; +} + +/* NOTE: only a single slot is supported (SLOT_0) + */ +static void ccid_on_slot_change(USBCCIDState* s, bool full) +{ + /* RDR_to_PC_NotifySlotChange, 6.3.1 page 56 + */ + uint8_t current = s->bmSlotICCState; + if (full) { + s->bmSlotICCState |= SLOT_0_STATE_MASK; + } else { + s->bmSlotICCState &= ~SLOT_0_STATE_MASK; + } + if (current != s->bmSlotICCState) { + s->bmSlotICCState |= SLOT_0_CHANGED_MASK; + } + s->notify_slot_change = true; +} + +static void ccid_write_data_block_error( + USBCCIDState *s, uint8_t slot, uint8_t seq) +{ + ccid_write_data_block(s, slot, seq, NULL, 0); +} + +static void ccid_on_apdu_from_guest(USBCCIDState *s, CCID_XferBlock *recv) +{ + uint32_t len; + + if (ccid_card_status(s) != ICC_STATUS_PRESENT_ACTIVE) { + DPRINTF(s, 1, "usb-ccid: not sending apdu to client, no card connected\n"); + ccid_write_data_block_error(s, recv->hdr.bSlot, recv->hdr.bSeq); + return; + } + len = le32_to_cpu(recv->hdr.dwLength); + DPRINTF(s, 1, "%s: seq %d, len %d\n", __FUNCTION__, + recv->hdr.bSeq, len); + ccid_add_pending_answer(s, (CCID_Header*)recv); + if (s->card) { + s->cardinfo->apdu_from_guest(s->card, recv->abData, len); + } else { + printf("warning: discarded apdu\n"); + } +} + +/* handle a single USB_TOKEN_OUT, return value returned to guest. + * 0 - all ok + * USB_RET_STALL - failed to handle packet */ +static int ccid_handle_bulk_out(USBCCIDState *s, USBPacket *p) +{ + CCID_Header* ccid_header; + + if (p->len + s->bulk_out_pos > BULK_OUT_DATA_SIZE) { + return USB_RET_STALL; + } + ccid_header = (CCID_Header*)s->bulk_out_data; + memcpy(s->bulk_out_data + s->bulk_out_pos, p->data, p->len); + s->bulk_out_pos += p->len; + if (p->len == 64) { + DPRINTF(s, 4, "usb-ccid: bulk_in: expecting more packets (%d/%d)\n", + p->len, ccid_header->dwLength); + return 0; + } + if (s->bulk_out_pos < 10) { + DPRINTF(s, 1, "%s: bad USB_TOKEN_OUT length, should be at least 10 bytes\n", __func__); + } else { + DPRINTF(s, 3, "%s %x\n", __func__, ccid_header->bMessageType); + switch (ccid_header->bMessageType) { + case CCID_MESSAGE_TYPE_PC_to_RDR_GetSlotStatus: + ccid_write_slot_status(s, ccid_header); + break; + case CCID_MESSAGE_TYPE_PC_to_RDR_IccPowerOn: + DPRINTF(s, 1, "PowerOn: %d\n", + ((CCID_IccPowerOn*)(ccid_header))->bPowerSelect); + s->powered = true; + if (!ccid_card_inserted(s)) { + ccid_report_error_failed(s, ERROR_ICC_MUTE); + } + /* atr is written regardless of error. */ + ccid_write_data_block_atr(s, ccid_header); + break; + case CCID_MESSAGE_TYPE_PC_to_RDR_IccPowerOff: + DPRINTF(s, 1, "PowerOff\n"); + ccid_reset_error_status(s); + s->powered = false; + ccid_write_slot_status(s, ccid_header); + break; + case CCID_MESSAGE_TYPE_PC_to_RDR_XfrBlock: + ccid_on_apdu_from_guest(s, (CCID_XferBlock*)s->bulk_out_data); + break; + case CCID_MESSAGE_TYPE_PC_to_RDR_SetParameters: + ccid_reset_error_status(s); + ccid_set_parameters(s, ccid_header); + ccid_write_parameters(s, ccid_header); + break; + case CCID_MESSAGE_TYPE_PC_to_RDR_ResetParameters: + ccid_reset_error_status(s); + ccid_reset_parameters(s); + ccid_write_parameters(s, ccid_header); + break; + case CCID_MESSAGE_TYPE_PC_to_RDR_GetParameters: + ccid_reset_error_status(s); + ccid_write_parameters(s, ccid_header); + break; + default: + DPRINTF(s, 1, "handle_data: ERROR: unhandled message type %Xh\n", + ccid_header->bMessageType); + /* the caller is expecting the device to respond, tell it we + * do't support the operation */ + ccid_report_error_failed(s, ERROR_CMD_NOT_SUPPORTED); + ccid_write_slot_status(s, ccid_header); + break; + } + } + s->bulk_out_pos = 0; + return 0; +} + +static int ccid_bulk_in_copy_to_guest(USBCCIDState *s, uint8_t *data, int len) +{ + int ret = 0; + + assert(len>0); + ccid_bulk_in_get(s); + if (s->current_bulk_in != NULL) { + ret = MIN(s->current_bulk_in->len - s->current_bulk_in->pos, len); + memcpy(data, s->current_bulk_in->data + s->current_bulk_in->pos, ret); + s->current_bulk_in->pos += ret; + if (s->current_bulk_in->pos == s->current_bulk_in->len) { + ccid_bulk_in_release(s); + } + } else { + ret = USB_RET_NAK; /* return when device has no data - usb 2.0 spec Table 8-4 */ + } + if (ret > 0) { + DPRINTF(s, 3, "%s: %d/%d req/act to guest (BULK_IN)\n", __func__, len, ret); + } + if (ret != USB_RET_NAK && ret < len) { + DPRINTF(s, 1, "%s: returning short (EREMOTEIO) %d < %d\n", __func__, ret, len); + } + return ret; +} + +static int ccid_handle_data(USBDevice *dev, USBPacket *p) +{ + USBCCIDState *s = DO_UPCAST(USBCCIDState, dev, dev); + int ret = 0; + uint8_t *data = p->data; + int len = p->len; + + switch (p->pid) { + case USB_TOKEN_OUT: + ret = ccid_handle_bulk_out(s, p); + break; + + case USB_TOKEN_IN: + switch (p->devep & 0xf) { + case CCID_BULK_IN_EP: + if (!len) { + ret = USB_RET_NAK; + } else { + ret = ccid_bulk_in_copy_to_guest(s, data, len); + } + break; + case CCID_INT_IN_EP: + if (s->notify_slot_change) { + /* page 56, RDR_to_PC_NotifySlotChange */ + data[0] = CCID_MESSAGE_TYPE_RDR_to_PC_NotifySlotChange; + data[1] = s->bmSlotICCState; + ret = 2; + s->notify_slot_change = false; + s->bmSlotICCState &= ~SLOT_0_CHANGED_MASK; + DPRINTF(s, 2, "handle_data: int_in: notify_slot_change %X, requested len %d\n", + s->bmSlotICCState, len); + } + break; + default: + DPRINTF(s, 1, "Bad endpoint\n"); + break; + } + break; + default: + DPRINTF(s, 1, "Bad token\n"); + ret = USB_RET_STALL; + break; + } + + return ret; +} + +static void ccid_handle_destroy(USBDevice *dev) +{ + USBCCIDState *s = DO_UPCAST(USBCCIDState, dev, dev); + + ccid_bulk_in_clear(s); +} + +static void ccid_flush_pending_answers(USBCCIDState *s) { + while (ccid_has_pending_answers(s)) { + ccid_write_data_block_answer(s, NULL, 0); + } +} + +static Answer *ccid_peek_next_answer(USBCCIDState *s) +{ + return s->pending_answers_num == 0 + ? NULL + : &s->pending_answers[s->pending_answers_start % PENDING_ANSWERS_NUM]; +} + +static void ccid_bus_dev_print(Monitor *mon, DeviceState *qdev, int indent) +{ + CCIDCardState *card = DO_UPCAST(CCIDCardState, qdev, qdev); + CCIDCardInfo *info = DO_UPCAST(CCIDCardInfo, qdev, qdev->info); + + if (info->print) { + info->print(mon, card, indent); + } +} + +struct CCIDBus { + BusState qbus; +}; + +static struct BusInfo ccid_bus_info = { + .name = "ccid-bus", + .size = sizeof(CCIDBus), + .print_dev = ccid_bus_dev_print, + .props = (Property[]) { + DEFINE_PROP_UINT32("slot", struct CCIDCardState, slot, 0), + DEFINE_PROP_END_OF_LIST(), + } +}; + +static CCIDBus *ccid_bus_new(DeviceState *dev) +{ + CCIDBus *bus; + + bus = FROM_QBUS(CCIDBus, qbus_create(&ccid_bus_info, dev, NULL)); + bus->qbus.allow_hotplug = 1; + + return bus; +} + +void ccid_card_send_apdu_to_guest(CCIDCardState *card, uint8_t* apdu, uint32_t len) +{ + USBCCIDState *s = DO_UPCAST(USBCCIDState, dev.qdev, card->qdev.parent_bus->parent); + Answer *answer; + + if (!ccid_has_pending_answers(s)) { + DPRINTF(s, 1, "CCID ERROR: got an APDU without pending answers\n"); + return; + } + s->bmCommandStatus = COMMAND_STATUS_NO_ERROR; + answer = ccid_peek_next_answer(s); + if (answer == NULL) { + abort(); + } + DPRINTF(s, 1, "APDU returned to guest %d (answer seq %d, slot %d)\n", + len, answer->seq, answer->slot); + ccid_write_data_block_answer(s, apdu, len); +} + +void ccid_card_card_removed(CCIDCardState *card) +{ + USBCCIDState *s = DO_UPCAST(USBCCIDState, dev.qdev, card->qdev.parent_bus->parent); + + ccid_on_slot_change(s, false); + ccid_flush_pending_answers(s); + ccid_reset(s); +} + +int ccid_card_ccid_attach(CCIDCardState *card) +{ + USBCCIDState *s = DO_UPCAST(USBCCIDState, dev.qdev, card->qdev.parent_bus->parent); + + DPRINTF(s, 1, "CCID Attach\n"); + if (s->migration_state == MIGRATION_MIGRATED) { + s->migration_state = MIGRATION_NONE; + } + return 0; +} + +void ccid_card_ccid_detach(CCIDCardState *card) +{ + USBCCIDState *s = DO_UPCAST(USBCCIDState, dev.qdev, card->qdev.parent_bus->parent); + + DPRINTF(s, 1, "CCID Detach\n"); + if (ccid_card_inserted(s)) { + ccid_on_slot_change(s, false); + } + ccid_detach(s); +} + +void ccid_card_card_error(CCIDCardState *card, uint64_t error) +{ + USBCCIDState *s = DO_UPCAST(USBCCIDState, dev.qdev, card->qdev.parent_bus->parent); + + s->bmCommandStatus = COMMAND_STATUS_FAILED; + s->last_answer_error = error; + DPRINTF(s, 1, "VSC_Error: %lX\n", s->last_answer_error); + /* TODO: these error's should be more verbose and propogated to the guest. + * */ + ccid_write_data_block_answer(s, NULL, 0); +} + +void ccid_card_card_inserted(CCIDCardState *card) +{ + USBCCIDState *s = DO_UPCAST(USBCCIDState, dev.qdev, card->qdev.parent_bus->parent); + + s->bmCommandStatus = COMMAND_STATUS_NO_ERROR; + ccid_flush_pending_answers(s); + ccid_on_slot_change(s, true); +} + +static int ccid_card_exit(DeviceState *qdev) +{ + int ret = 0; + CCIDCardState *card = DO_UPCAST(CCIDCardState, qdev, qdev); + CCIDCardInfo *info = DO_UPCAST(CCIDCardInfo, qdev, qdev->info); + USBCCIDState *s = DO_UPCAST(USBCCIDState, dev.qdev, card->qdev.parent_bus->parent); + + if (ccid_card_inserted(s)) { + ccid_card_card_removed(card); + } + if (info->exitfn) { + ret = info->exitfn(card); + } + s->card = NULL; + s->cardinfo = NULL; + return ret; +} + +static int ccid_card_init(DeviceState *qdev, DeviceInfo *base) +{ + CCIDCardState *card = DO_UPCAST(CCIDCardState, qdev, qdev); + CCIDCardInfo *info = DO_UPCAST(CCIDCardInfo, qdev, base); + USBCCIDState *s = DO_UPCAST(USBCCIDState, dev.qdev, card->qdev.parent_bus->parent); + int ret = 0; + + if (card->slot != 0) { + fprintf(stderr, "Warning: usb-ccid supports one slot, can't add %d", + card->slot); + return -1; + } + if (s->card != NULL) { + fprintf(stderr, "Warning: usb-ccid card already full, not adding\n"); + return -1; + } + ret = info->initfn ? info->initfn(card) : ret; + if (ret == 0) { + s->card = card; + s->cardinfo = info; + } + return ret; +} + +void ccid_card_qdev_register(CCIDCardInfo *card) +{ + card->qdev.bus_info = &ccid_bus_info; + card->qdev.init = ccid_card_init; + card->qdev.exit = ccid_card_exit; + qdev_register(&card->qdev); +} + +static int ccid_initfn(USBDevice *dev) +{ + USBCCIDState *s = DO_UPCAST(USBCCIDState, dev, dev); + + s->bus = ccid_bus_new(&dev->qdev); + s->card = NULL; + s->cardinfo = NULL; + s->migration_state = MIGRATION_NONE; + s->migration_target_ip = 0; + s->migration_target_port = 0; + s->dev.speed = USB_SPEED_FULL; + s->notify_slot_change = false; + s->powered = true; + s->pending_answers_num = 0; + s->last_answer_error = 0; + s->bulk_in_pending_start = 0; + s->bulk_in_pending_end = 0; + s->current_bulk_in = NULL; + ccid_reset_error_status(s); + s->bulk_out_pos = 0; + ccid_reset_parameters(s); + ccid_reset(s); + return 0; +} + +#ifdef ENABLE_MIGRATION +static int ccid_post_load(void *opaque, int version_id) +{ + USBCCIDState *s = opaque; + + // This must be done after usb_device_attach, which sets state to ATTACHED, + // while it must be DEFAULT in order to accept packets (like it is after + // reset, but reset will reset our addr and call our reset handler which + // may change state, and we don't want to do that when migrating). + s->dev.state = s->state_vmstate; + return 0; +} + +static void ccid_pre_save(void *opaque) +{ + USBCCIDState *s = opaque; + + s->state_vmstate = s->dev.state; + if (s->dev.attached) { + // migrating an open device, ignore reconnection CHR_EVENT to avoid an + // erronous detach. + s->migration_state = MIGRATION_MIGRATED; + } +} + +static VMStateDescription bulk_in_vmstate = { + .name = "CCID BulkIn state", + .version_id = 1, + .minimum_version_id = 1, + .fields = (VMStateField []) { + VMSTATE_BUFFER(data, BulkIn), + VMSTATE_UINT32(len, BulkIn), + VMSTATE_UINT32(pos, BulkIn), + VMSTATE_END_OF_LIST() + } +}; + +static VMStateDescription answer_vmstate = { + .name = "CCID Answer state", + .version_id = 1, + .minimum_version_id = 1, + .fields = (VMStateField []) { + VMSTATE_UINT8(slot, Answer), + VMSTATE_UINT8(seq, Answer), + VMSTATE_END_OF_LIST() + } +}; + +static VMStateDescription usb_device_vmstate = { + .name = "usb_device", + .version_id = 1, + .minimum_version_id = 1, + .fields = (VMStateField []) { + VMSTATE_UINT8(addr, USBDevice), + VMSTATE_BUFFER(setup_buf, USBDevice), + VMSTATE_BUFFER(data_buf, USBDevice), + VMSTATE_END_OF_LIST() + } +}; + +static VMStateDescription ccid_vmstate = { + .name = CCID_DEV_NAME, + .version_id = 1, + .minimum_version_id = 1, + .post_load = ccid_post_load, + .pre_save = ccid_pre_save, + .fields = (VMStateField []) { + VMSTATE_STRUCT(dev, USBCCIDState, 1, usb_device_vmstate, USBDevice), + VMSTATE_UINT8(debug, USBCCIDState), + VMSTATE_BUFFER(bulk_out_data, USBCCIDState), + VMSTATE_UINT32(bulk_out_pos, USBCCIDState), + VMSTATE_UINT8(bmSlotICCState, USBCCIDState), + VMSTATE_UINT8(powered, USBCCIDState), + VMSTATE_UINT8(notify_slot_change, USBCCIDState), + VMSTATE_UINT64(last_answer_error, USBCCIDState), + VMSTATE_UINT8(bError, USBCCIDState), + VMSTATE_UINT8(bmCommandStatus, USBCCIDState), + VMSTATE_UINT8(bProtocolNum, USBCCIDState), + VMSTATE_BUFFER(abProtocolDataStructure, USBCCIDState), + VMSTATE_UINT32(ulProtocolDataStructureSize, USBCCIDState), + VMSTATE_STRUCT_ARRAY(bulk_in_pending, USBCCIDState, + BULK_IN_PENDING_NUM, 1, bulk_in_vmstate, BulkIn), + VMSTATE_UINT32(bulk_in_pending_start, USBCCIDState), + VMSTATE_UINT32(bulk_in_pending_end, USBCCIDState), + VMSTATE_STRUCT_ARRAY(pending_answers, USBCCIDState, + PENDING_ANSWERS_NUM, 1, answer_vmstate, Answer), + VMSTATE_UINT32(pending_answers_num, USBCCIDState), + VMSTATE_UINT8(migration_state, USBCCIDState), + VMSTATE_UINT32(state_vmstate, USBCCIDState), + VMSTATE_END_OF_LIST() + } +}; +#endif // ENABLE_MIGRATION + +static struct USBDeviceInfo ccid_info = { + .product_desc = "QEMU USB CCID", + .qdev.name = CCID_DEV_NAME, + .qdev.size = sizeof(USBCCIDState), + .init = ccid_initfn, + .handle_packet = usb_generic_handle_packet, + .handle_reset = ccid_handle_reset, + .handle_control = ccid_handle_control, + .handle_data = ccid_handle_data, + .handle_destroy = ccid_handle_destroy, + .usbdevice_name = "ccid", + .qdev.props = (Property[]) { + DEFINE_PROP_UINT8("debug", USBCCIDState, debug, 0), + DEFINE_PROP_END_OF_LIST(), + }, +#ifdef ENABLE_MIGRATION + .qdev.vmsd = &ccid_vmstate, +#endif +}; + + +static void ccid_register_devices(void) +{ + usb_qdev_register(&ccid_info); +} +device_init(ccid_register_devices) -- 1.7.3.4 ^ permalink raw reply related [flat|nested] 35+ messages in thread
* [Qemu-devel] [PATCH 2/7] ccid: add passthru card device 2011-01-11 8:42 [Qemu-devel] [PATCH 0/7] usb-ccid (v15) Alon Levy 2011-01-11 8:42 ` [Qemu-devel] [PATCH 1/7] usb-ccid: add CCID bus Alon Levy @ 2011-01-11 8:42 ` Alon Levy 2011-01-25 14:17 ` Anthony Liguori 2011-01-11 8:42 ` [Qemu-devel] [PATCH 3/7] libcacard: initial commit after coding style fixes Alon Levy ` (6 subsequent siblings) 8 siblings, 1 reply; 35+ messages in thread From: Alon Levy @ 2011-01-11 8:42 UTC (permalink / raw) To: qemu-devel The passthru ccid card is a device sitting on the usb-ccid bus and using a chardevice to communicate with a remote device using the VSCard protocol defined in libcacard/vscard_common.h Usage docs available in following patch in docs/ccid.txt Signed-off-by: Alon Levy <alevy@redhat.com> --- Makefile.objs | 2 +- hw/ccid-card-passthru.c | 273 +++++++++++++++++++++++++++++++++++++++++++++ libcacard/vscard_common.h | 130 +++++++++++++++++++++ 3 files changed, 404 insertions(+), 1 deletions(-) create mode 100644 hw/ccid-card-passthru.c create mode 100644 libcacard/vscard_common.h diff --git a/Makefile.objs b/Makefile.objs index 7da4771..274db5e 100644 --- a/Makefile.objs +++ b/Makefile.objs @@ -197,7 +197,7 @@ hw-obj-$(CONFIG_FDC) += fdc.o hw-obj-$(CONFIG_ACPI) += acpi.o acpi_piix4.o hw-obj-$(CONFIG_APM) += pm_smbus.o apm.o hw-obj-$(CONFIG_DMA) += dma.o -hw-obj-$(CONFIG_SMARTCARD) += usb-ccid.o +hw-obj-$(CONFIG_SMARTCARD) += usb-ccid.o ccid-card-passthru.o # PPC devices hw-obj-$(CONFIG_OPENPIC) += openpic.o diff --git a/hw/ccid-card-passthru.c b/hw/ccid-card-passthru.c new file mode 100644 index 0000000..6ec4f21 --- /dev/null +++ b/hw/ccid-card-passthru.c @@ -0,0 +1,273 @@ +/* + * CCID Card Device emulation + * + * Copyright (c) 2010 Red Hat. + * Written by Alon Levy. + * + * This code is licenced under the LGPL. + */ + +#include "qemu-char.h" +#include "monitor.h" +#include "hw/ccid.h" +#include "libcacard/vscard_common.h" + +#define DPRINTF(card, lvl, fmt, ...) \ +do { if (lvl <= card->debug) { printf("ccid-card: " fmt , ## __VA_ARGS__); } } while (0) + +/* Passthru card */ + + +// TODO: do we still need this? +uint8_t DEFAULT_ATR[] = { +/* From some example somewhere + 0x3B, 0xB0, 0x18, 0x00, 0xD1, 0x81, 0x05, 0xB1, 0x40, 0x38, 0x1F, 0x03, 0x28 + */ + +/* From an Athena smart card */ + 0x3B, 0xD5, 0x18, 0xFF, 0x80, 0x91, 0xFE, 0x1F, 0xC3, 0x80, 0x73, 0xC8, 0x21, 0x13, 0x08 + +}; /* maximum size of ATR - from 7816-3 */ + + +#define PASSTHRU_DEV_NAME "ccid-card-passthru" +#define VSCARD_IN_SIZE 65536 +#define MAX_ATR_SIZE 40 + +typedef struct PassthruState PassthruState; + +struct PassthruState { + CCIDCardState base; + CharDriverState *cs; + uint8_t vscard_in_data[VSCARD_IN_SIZE]; + uint32_t vscard_in_pos; + uint32_t vscard_in_hdr; + uint8_t atr[MAX_ATR_SIZE]; + uint8_t atr_length; + uint8_t debug; +}; + +/* VSCard protocol over chardev + * This code should not depend on the card type. + * */ + +static void ccid_card_vscard_send_msg( + PassthruState *s, VSCMsgType type, reader_id_t reader_id, + const uint8_t* payload, uint32_t length) +{ + VSCMsgHeader scr_msg_header; + + scr_msg_header.type = type; + scr_msg_header.reader_id = reader_id; + scr_msg_header.length = length; + qemu_chr_write(s->cs, (uint8_t*)&scr_msg_header, sizeof(VSCMsgHeader)); + qemu_chr_write(s->cs, payload, length); +} + +static void ccid_card_vscard_send_apdu( + PassthruState *s, const uint8_t* apdu, uint32_t length) +{ + ccid_card_vscard_send_msg(s, VSC_APDU, VSCARD_MINIMAL_READER_ID, apdu, length); +} + +static void ccid_card_vscard_send_error( + PassthruState *s, reader_id_t reader_id, VSCErrorCode code) +{ + VSCMsgError msg = {.code=code}; + + ccid_card_vscard_send_msg(s, VSC_Error, reader_id, (uint8_t*)&msg, sizeof(msg)); +} + +static void ccid_card_vscard_send_init(PassthruState *s) +{ + VSCMsgInit msg = {.version=VSCARD_VERSION}; + + ccid_card_vscard_send_msg(s, VSC_Init, VSCARD_UNDEFINED_READER_ID, + (uint8_t*)&msg, sizeof(msg)); +} + +static int ccid_card_vscard_can_read(void *opaque) +{ + return 65535; +} + +static void ccid_card_vscard_handle_message(PassthruState *card, + VSCMsgHeader* scr_msg_header) +{ + uint8_t *data = (uint8_t*)&scr_msg_header[1]; + + switch (scr_msg_header->type) { + case VSC_ATR: + DPRINTF(card, 1, "VSC_ATR %d\n", scr_msg_header->length); + assert(scr_msg_header->length <= MAX_ATR_SIZE); + memcpy(card->atr, data, scr_msg_header->length); + card->atr_length = scr_msg_header->length; + ccid_card_card_inserted(&card->base); + break; + case VSC_APDU: + ccid_card_send_apdu_to_guest(&card->base, data, scr_msg_header->length); + break; + case VSC_CardRemove: + DPRINTF(card, 1, "VSC_CardRemove\n"); + ccid_card_card_removed(&card->base); + break; + case VSC_Init: + break; + case VSC_Error: + ccid_card_card_error(&card->base, *(uint64_t*)data); + break; + case VSC_ReaderAdd: + if (ccid_card_ccid_attach(&card->base) < 0) { + ccid_card_vscard_send_error(card, VSCARD_UNDEFINED_READER_ID, + VSC_CANNOT_ADD_MORE_READERS); + } else { + ccid_card_vscard_send_msg(card, VSC_ReaderAddResponse, + VSCARD_MINIMAL_READER_ID, NULL, 0); + } + break; + case VSC_ReaderRemove: + ccid_card_ccid_detach(&card->base); + break; + default: + printf("usb-ccid: chardev: unexpected message of type %X\n", + scr_msg_header->type); + ccid_card_vscard_send_error(card, scr_msg_header->reader_id, + VSC_GENERAL_ERROR); + } +} + +static void ccid_card_vscard_read(void *opaque, const uint8_t *buf, int size) +{ + PassthruState *card = opaque; + VSCMsgHeader *hdr; + + assert(card->vscard_in_pos + size <= VSCARD_IN_SIZE); + memcpy(card->vscard_in_data + card->vscard_in_pos, buf, size); + card->vscard_in_pos += size; + hdr = (VSCMsgHeader*)(card->vscard_in_data + card->vscard_in_hdr); + + while ((card->vscard_in_pos - card->vscard_in_hdr >= sizeof(VSCMsgHeader)) && + (card->vscard_in_pos - card->vscard_in_hdr - sizeof(VSCMsgHeader) >= + hdr->length)) { + ccid_card_vscard_handle_message(card, hdr); + card->vscard_in_hdr += hdr->length + sizeof(VSCMsgHeader); + hdr = (VSCMsgHeader*)(card->vscard_in_data + card->vscard_in_hdr); + } + if (card->vscard_in_hdr == card->vscard_in_pos) { + card->vscard_in_pos = card->vscard_in_hdr = 0; + } +} + +static void ccid_card_vscard_event(void *opaque, int event) +{ + PassthruState *card = opaque; + + switch (event) { + case CHR_EVENT_BREAK: + break; + case CHR_EVENT_FOCUS: + break; + case CHR_EVENT_OPENED: + DPRINTF(card, 1, "%s: CHR_EVENT_OPENED\n", __func__); + break; + } +} + +/* End VSCard handling */ + +static void passthru_apdu_from_guest(CCIDCardState *base, const uint8_t *apdu, uint32_t len) +{ + PassthruState *card = DO_UPCAST(PassthruState, base, base); + + if (!card->cs) { + printf("ccid-passthru: no chardev, discarding apdu length %d\n", len); + return; + } + ccid_card_vscard_send_apdu(card, apdu, len); +} + +static const uint8_t* passthru_get_atr(CCIDCardState *base, uint32_t *len) +{ + PassthruState *card = DO_UPCAST(PassthruState, base, base); + + *len = card->atr_length; + return card->atr; +} + +static int passthru_initfn(CCIDCardState *base) +{ + PassthruState *card = DO_UPCAST(PassthruState, base, base); + + card->vscard_in_pos = 0; + card->vscard_in_hdr = 0; + if (card->cs) { + DPRINTF(card, 1, "initing chardev\n"); + qemu_chr_add_handlers(card->cs, + ccid_card_vscard_can_read, + ccid_card_vscard_read, + ccid_card_vscard_event, card); + ccid_card_vscard_send_init(card); + } + assert(sizeof(DEFAULT_ATR) <= MAX_ATR_SIZE); + memcpy(card->atr, DEFAULT_ATR, sizeof(DEFAULT_ATR)); + card->atr_length = sizeof(DEFAULT_ATR); + return 0; +} + +static int passthru_exitfn(CCIDCardState *base) +{ + return 0; +} + +static void passthru_pre_save(void *opaque) +{ + PassthruState *card = opaque; + VSCMsgReconnect reconnect; + + reconnect.ip = 0; // TODO - does the bus keep the target ip? s->migration_target_ip; + reconnect.port = 0; // TODO - does the bus keep the target ip? s->migration_target_port; + ccid_card_vscard_send_msg(card, VSC_Reconnect, VSCARD_UNDEFINED_READER_ID, + (uint8_t*)&reconnect, sizeof(reconnect)); +} + +static VMStateDescription passthru_vmstate = { + .name = PASSTHRU_DEV_NAME, + .version_id = 1, + .minimum_version_id = 1, + .pre_save = passthru_pre_save, + .fields = (VMStateField []) { + VMSTATE_BUFFER(vscard_in_data, PassthruState), + VMSTATE_UINT32(vscard_in_pos, PassthruState), + VMSTATE_UINT32(vscard_in_hdr, PassthruState), + VMSTATE_BUFFER(atr, PassthruState), + VMSTATE_UINT8(atr_length, PassthruState), + VMSTATE_END_OF_LIST() + } +}; + +static CCIDCardInfo passthru_card_info = { + .qdev.name = PASSTHRU_DEV_NAME, + .qdev.size = sizeof(PassthruState), + .qdev.vmsd = &passthru_vmstate, + .initfn = passthru_initfn, + .exitfn = passthru_exitfn, + .get_atr = passthru_get_atr, + .apdu_from_guest = passthru_apdu_from_guest, + .qdev.unplug = qdev_simple_unplug_cb, + .qdev.props = (Property[]) { + DEFINE_PROP_CHR("chardev", PassthruState, cs), + DEFINE_PROP_UINT8("debug", PassthruState, debug, 0), + DEFINE_PROP_END_OF_LIST(), + }, +}; + +static void ccid_card_passthru_register_devices(void) +{ + ccid_card_qdev_register(&passthru_card_info); + // TODO: passthru local card (or: just a case of passthru with no chardev + // given and instead some other arguments that would be required for local + // card anyway and can be shared with the emulated local card) + // TODO: emulated local card +} + +device_init(ccid_card_passthru_register_devices) diff --git a/libcacard/vscard_common.h b/libcacard/vscard_common.h new file mode 100644 index 0000000..9ff1295 --- /dev/null +++ b/libcacard/vscard_common.h @@ -0,0 +1,130 @@ +/* Virtual Smart Card protocol definition + * + * This protocol is between a host implementing a group of virtual smart card + * reader, and a client implementing a virtual smart card, or passthrough to + * a real card. + * + * The current implementation passes the raw APDU's from 7816 and additionally + * contains messages to setup and teardown readers, handle insertion and + * removal of cards, negotiate the protocol and provide for error responses. + * + * Copyright (c) 2010 Red Hat. + * + * This code is licensed under the LGPL. + */ + +#ifndef _VSCARD_COMMON_H +#define _VSCARD_COMMON_H + +#include <stdint.h> + +#define VERSION_MAJOR_BITS 11 +#define VERSION_MIDDLE_BITS 11 +#define VERSION_MINOR_BITS 10 + +#define MAKE_VERSION(major, middle, minor) \ + ( (major << (VERSION_MINOR_BITS + VERSION_MIDDLE_BITS)) \ + | (middle << VERSION_MINOR_BITS) \ + | (minor) ) + +/** IMPORTANT NOTE on VERSION + * + * The version below MUST be changed whenever a change in this file is made. + * + * The last digit, the minor, is for bug fix changes only. + * + * The middle digit is for backward / forward compatible changes, updates + * to the existing messages, addition of fields. + * + * The major digit is for a breaking change of protocol, presumably + * something that cannot be accomodated with the existing protocol. + */ + +#define VSCARD_VERSION MAKE_VERSION(0,0,1) + +typedef enum { + VSC_Init, + VSC_Error, + VSC_ReaderAdd, + VSC_ReaderAddResponse, + VSC_ReaderRemove, + VSC_ATR, + VSC_CardRemove, + VSC_APDU, + VSC_Reconnect +} VSCMsgType; + +typedef enum { + VSC_GENERAL_ERROR=1, + VSC_CANNOT_ADD_MORE_READERS, +} VSCErrorCode; + +typedef uint32_t reader_id_t; +#define VSCARD_UNDEFINED_READER_ID 0xffffffff +#define VSCARD_MINIMAL_READER_ID 0 + +typedef struct VSCMsgHeader { + VSCMsgType type; + reader_id_t reader_id; + uint32_t length; + uint8_t data[0]; +} VSCMsgHeader; + +/* VSCMsgInit Client <-> Host + * Host replies with allocated reader id in ReaderAddResponse + * */ +typedef struct VSCMsgInit { + uint32_t version; +} VSCMsgInit; + +/* VSCMsgError Client <-> Host + * */ +typedef struct VSCMsgError { + uint32_t code; +} VSCMsgError; + +/* VSCMsgReaderAdd Client -> Host + * Host replies with allocated reader id in ReaderAddResponse + * name - name of the reader on client side. + * */ +typedef struct VSCMsgReaderAdd { + uint8_t name[0]; +} VSCMsgReaderAdd; + +/* VSCMsgReaderAddResponse Host -> Client + * Reply to ReaderAdd + * */ +typedef struct VSCMsgReaderAddResponse { +} VSCMsgReaderAddResponse; + +/* VSCMsgReaderRemove Client -> Host + * */ +typedef struct VSCMsgReaderRemove { +} VSCMsgReaderRemove; + +/* VSCMsgATR Client -> Host + * Answer to reset. Sent for card insertion or card reset. + * */ +typedef struct VSCMsgATR { + uint8_t atr[0]; +} VSCMsgATR; + +/* VSCMsgCardRemove Client -> Host + * */ +typedef struct VSCMsgCardRemove { +} VSCMsgCardRemove; + +/* VSCMsgAPDU Client <-> Host + * */ +typedef struct VSCMsgAPDU { + uint8_t data[0]; +} VSCMsgAPDU; + +/* VSCMsgReconnect Host -> Client + * */ +typedef struct VSCMsgReconnect { + uint32_t ip; + uint16_t port; +} VSCMsgReconnect; + +#endif -- 1.7.3.4 ^ permalink raw reply related [flat|nested] 35+ messages in thread
* Re: [Qemu-devel] [PATCH 2/7] ccid: add passthru card device 2011-01-11 8:42 ` [Qemu-devel] [PATCH 2/7] ccid: add passthru card device Alon Levy @ 2011-01-25 14:17 ` Anthony Liguori 2011-01-25 16:21 ` Alon Levy ` (2 more replies) 0 siblings, 3 replies; 35+ messages in thread From: Anthony Liguori @ 2011-01-25 14:17 UTC (permalink / raw) To: Alon Levy; +Cc: qemu-devel On 01/11/2011 02:42 AM, Alon Levy wrote: > diff --git a/libcacard/vscard_common.h b/libcacard/vscard_common.h > new file mode 100644 > index 0000000..9ff1295 > --- /dev/null > +++ b/libcacard/vscard_common.h > This file (and the .c file) need a coding style pass to fixup comments and the use of _ as a prefix but I want to focus on the protocol itself. First, let's get a written spec into the wiki. I think it's important that all of our compatibility protocols are documented in a more formal way such that can be reviewed by a wider audience. > @@ -0,0 +1,130 @@ > +/* Virtual Smart Card protocol definition > + * > + * This protocol is between a host implementing a group of virtual smart card > + * reader, and a client implementing a virtual smart card, or passthrough to > + * a real card. > + * > + * The current implementation passes the raw APDU's from 7816 and additionally > + * contains messages to setup and teardown readers, handle insertion and > + * removal of cards, negotiate the protocol and provide for error responses. > + * > + * Copyright (c) 2010 Red Hat. > + * > + * This code is licensed under the LGPL. > + */ > + > +#ifndef _VSCARD_COMMON_H > +#define _VSCARD_COMMON_H > + > +#include<stdint.h> > + > +#define VERSION_MAJOR_BITS 11 > +#define VERSION_MIDDLE_BITS 11 > +#define VERSION_MINOR_BITS 10 > Distros make versioning not enough. Inevitably, someone wants to back port a bug fix or a feature for some RHEL7.2 release or something like that. Feature negotiation has worked pretty well for us and I'd suggest using it within the protocol. > +#define MAKE_VERSION(major, middle, minor) \ > + ( (major<< (VERSION_MINOR_BITS + VERSION_MIDDLE_BITS)) \ > + | (middle<< VERSION_MINOR_BITS) \ > + | (minor) ) > + > +/** IMPORTANT NOTE on VERSION > + * > + * The version below MUST be changed whenever a change in this file is made. > + * > + * The last digit, the minor, is for bug fix changes only. > + * > + * The middle digit is for backward / forward compatible changes, updates > + * to the existing messages, addition of fields. > + * > + * The major digit is for a breaking change of protocol, presumably > + * something that cannot be accomodated with the existing protocol. > + */ > + > +#define VSCARD_VERSION MAKE_VERSION(0,0,1) > + > +typedef enum { > + VSC_Init, > + VSC_Error, > + VSC_ReaderAdd, > + VSC_ReaderAddResponse, > + VSC_ReaderRemove, > + VSC_ATR, > + VSC_CardRemove, > + VSC_APDU, > + VSC_Reconnect > +} VSCMsgType; > Should number the enum to be specific at least. > + > +typedef enum { > + VSC_GENERAL_ERROR=1, > + VSC_CANNOT_ADD_MORE_READERS, > +} VSCErrorCode; > + > +typedef uint32_t reader_id_t; > This namespace is reserved by C. > +#define VSCARD_UNDEFINED_READER_ID 0xffffffff > +#define VSCARD_MINIMAL_READER_ID 0 > + > +typedef struct VSCMsgHeader { > + VSCMsgType type; > + reader_id_t reader_id; > + uint32_t length; > Is length just the data length or the whole message length? > + uint8_t data[0]; > +} VSCMsgHeader; > + > +/* VSCMsgInit Client<-> Host > + * Host replies with allocated reader id in ReaderAddResponse > + * */ > +typedef struct VSCMsgInit { > + uint32_t version; > +} VSCMsgInit; > + > +/* VSCMsgError Client<-> Host > + * */ > +typedef struct VSCMsgError { > + uint32_t code; > +} VSCMsgError; > + > +/* VSCMsgReaderAdd Client -> Host > + * Host replies with allocated reader id in ReaderAddResponse > + * name - name of the reader on client side. > + * */ > +typedef struct VSCMsgReaderAdd { > + uint8_t name[0]; > Is this a string? > +} VSCMsgReaderAdd; > + > +/* VSCMsgReaderAddResponse Host -> Client > + * Reply to ReaderAdd > + * */ > +typedef struct VSCMsgReaderAddResponse { > +} VSCMsgReaderAddResponse; > + > +/* VSCMsgReaderRemove Client -> Host > + * */ > +typedef struct VSCMsgReaderRemove { > +} VSCMsgReaderRemove; > + > +/* VSCMsgATR Client -> Host > + * Answer to reset. Sent for card insertion or card reset. > + * */ > +typedef struct VSCMsgATR { > + uint8_t atr[0]; > +} VSCMsgATR; > + > +/* VSCMsgCardRemove Client -> Host > + * */ > +typedef struct VSCMsgCardRemove { > +} VSCMsgCardRemove; > + > +/* VSCMsgAPDU Client<-> Host > + * */ > +typedef struct VSCMsgAPDU { > + uint8_t data[0]; > +} VSCMsgAPDU; > + > +/* VSCMsgReconnect Host -> Client > + * */ > +typedef struct VSCMsgReconnect { > + uint32_t ip; > This is not ipv6 friendly. Two strings would be a better choice. Regards, Anthony Liguori > + uint16_t port; > +} VSCMsgReconnect; > + > +#endif > ^ permalink raw reply [flat|nested] 35+ messages in thread
* Re: [Qemu-devel] [PATCH 2/7] ccid: add passthru card device 2011-01-25 14:17 ` Anthony Liguori @ 2011-01-25 16:21 ` Alon Levy 2011-01-25 16:24 ` Anthony Liguori 2011-01-27 21:13 ` Alon Levy 2011-01-30 17:35 ` Alon Levy 2 siblings, 1 reply; 35+ messages in thread From: Alon Levy @ 2011-01-25 16:21 UTC (permalink / raw) To: Anthony Liguori; +Cc: qemu-devel On Tue, Jan 25, 2011 at 08:17:32AM -0600, Anthony Liguori wrote: > On 01/11/2011 02:42 AM, Alon Levy wrote: > >diff --git a/libcacard/vscard_common.h b/libcacard/vscard_common.h > >new file mode 100644 > >index 0000000..9ff1295 > >--- /dev/null > >+++ b/libcacard/vscard_common.h > > This file (and the .c file) need a coding style pass to fixup > comments and the use of _ as a prefix but I want to focus on the > protocol itself. > > First, let's get a written spec into the wiki. I think it's > important that all of our compatibility protocols are documented in > a more formal way such that can be reviewed by a wider audience. ok, I'll create Features/Smartcard/Protocol > > >@@ -0,0 +1,130 @@ > >+/* Virtual Smart Card protocol definition > >+ * > >+ * This protocol is between a host implementing a group of virtual smart card > >+ * reader, and a client implementing a virtual smart card, or passthrough to > >+ * a real card. > >+ * > >+ * The current implementation passes the raw APDU's from 7816 and additionally > >+ * contains messages to setup and teardown readers, handle insertion and > >+ * removal of cards, negotiate the protocol and provide for error responses. > >+ * > >+ * Copyright (c) 2010 Red Hat. > >+ * > >+ * This code is licensed under the LGPL. > >+ */ > >+ > >+#ifndef _VSCARD_COMMON_H > >+#define _VSCARD_COMMON_H > >+ > >+#include<stdint.h> > >+ > >+#define VERSION_MAJOR_BITS 11 > >+#define VERSION_MIDDLE_BITS 11 > >+#define VERSION_MINOR_BITS 10 > > Distros make versioning not enough. Inevitably, someone wants to > back port a bug fix or a feature for some RHEL7.2 release or > something like that. > > Feature negotiation has worked pretty well for us and I'd suggest > using it within the protocol. > Suggestion accepted. > >+#define MAKE_VERSION(major, middle, minor) \ > >+ ( (major<< (VERSION_MINOR_BITS + VERSION_MIDDLE_BITS)) \ > >+ | (middle<< VERSION_MINOR_BITS) \ > >+ | (minor) ) > >+ > >+/** IMPORTANT NOTE on VERSION > >+ * > >+ * The version below MUST be changed whenever a change in this file is made. > >+ * > >+ * The last digit, the minor, is for bug fix changes only. > >+ * > >+ * The middle digit is for backward / forward compatible changes, updates > >+ * to the existing messages, addition of fields. > >+ * > >+ * The major digit is for a breaking change of protocol, presumably > >+ * something that cannot be accomodated with the existing protocol. > >+ */ > >+ > >+#define VSCARD_VERSION MAKE_VERSION(0,0,1) > >+ > >+typedef enum { > >+ VSC_Init, > >+ VSC_Error, > >+ VSC_ReaderAdd, > >+ VSC_ReaderAddResponse, > >+ VSC_ReaderRemove, > >+ VSC_ATR, > >+ VSC_CardRemove, > >+ VSC_APDU, > >+ VSC_Reconnect > >+} VSCMsgType; > > Should number the enum to be specific at least. will fix. > > >+ > >+typedef enum { > >+ VSC_GENERAL_ERROR=1, > >+ VSC_CANNOT_ADD_MORE_READERS, > >+} VSCErrorCode; > >+ > >+typedef uint32_t reader_id_t; > > This namespace is reserved by C. reader_id_t is reserved? > > >+#define VSCARD_UNDEFINED_READER_ID 0xffffffff > >+#define VSCARD_MINIMAL_READER_ID 0 > >+ > >+typedef struct VSCMsgHeader { > >+ VSCMsgType type; > >+ reader_id_t reader_id; > >+ uint32_t length; > > Is length just the data length or the whole message length? > data length, I'll add a comment. > >+ uint8_t data[0]; > >+} VSCMsgHeader; > >+ > >+/* VSCMsgInit Client<-> Host > >+ * Host replies with allocated reader id in ReaderAddResponse > >+ * */ > >+typedef struct VSCMsgInit { > >+ uint32_t version; > >+} VSCMsgInit; > >+ > >+/* VSCMsgError Client<-> Host > >+ * */ > >+typedef struct VSCMsgError { > >+ uint32_t code; > >+} VSCMsgError; > >+ > >+/* VSCMsgReaderAdd Client -> Host > >+ * Host replies with allocated reader id in ReaderAddResponse > >+ * name - name of the reader on client side. > >+ * */ > >+typedef struct VSCMsgReaderAdd { > >+ uint8_t name[0]; > > Is this a string? > Yes. You expect char? > >+} VSCMsgReaderAdd; > >+ > >+/* VSCMsgReaderAddResponse Host -> Client > >+ * Reply to ReaderAdd > >+ * */ > >+typedef struct VSCMsgReaderAddResponse { > >+} VSCMsgReaderAddResponse; > >+ > >+/* VSCMsgReaderRemove Client -> Host > >+ * */ > >+typedef struct VSCMsgReaderRemove { > >+} VSCMsgReaderRemove; > >+ > >+/* VSCMsgATR Client -> Host > >+ * Answer to reset. Sent for card insertion or card reset. > >+ * */ > >+typedef struct VSCMsgATR { > >+ uint8_t atr[0]; > >+} VSCMsgATR; > >+ > >+/* VSCMsgCardRemove Client -> Host > >+ * */ > >+typedef struct VSCMsgCardRemove { > >+} VSCMsgCardRemove; > >+ > >+/* VSCMsgAPDU Client<-> Host > >+ * */ > >+typedef struct VSCMsgAPDU { > >+ uint8_t data[0]; > >+} VSCMsgAPDU; > >+ > >+/* VSCMsgReconnect Host -> Client > >+ * */ > >+typedef struct VSCMsgReconnect { > >+ uint32_t ip; > > This is not ipv6 friendly. Two strings would be a better choice. > Will fix. > Regards, > > Anthony Liguori > > >+ uint16_t port; > >+} VSCMsgReconnect; > >+ > >+#endif > ^ permalink raw reply [flat|nested] 35+ messages in thread
* Re: [Qemu-devel] [PATCH 2/7] ccid: add passthru card device 2011-01-25 16:21 ` Alon Levy @ 2011-01-25 16:24 ` Anthony Liguori 2011-01-25 16:50 ` Alon Levy 0 siblings, 1 reply; 35+ messages in thread From: Anthony Liguori @ 2011-01-25 16:24 UTC (permalink / raw) To: qemu-devel On 01/25/2011 10:21 AM, Alon Levy wrote: > On Tue, Jan 25, 2011 at 08:17:32AM -0600, Anthony Liguori wrote: > >> On 01/11/2011 02:42 AM, Alon Levy wrote: >> >>> diff --git a/libcacard/vscard_common.h b/libcacard/vscard_common.h >>> new file mode 100644 >>> index 0000000..9ff1295 >>> --- /dev/null >>> +++ b/libcacard/vscard_common.h >>> >> This file (and the .c file) need a coding style pass to fixup >> comments and the use of _ as a prefix but I want to focus on the >> protocol itself. >> >> First, let's get a written spec into the wiki. I think it's >> important that all of our compatibility protocols are documented in >> a more formal way such that can be reviewed by a wider audience. >> > ok, I'll create Features/Smartcard/Protocol > > >> >>> @@ -0,0 +1,130 @@ >>> +/* Virtual Smart Card protocol definition >>> + * >>> + * This protocol is between a host implementing a group of virtual smart card >>> + * reader, and a client implementing a virtual smart card, or passthrough to >>> + * a real card. >>> + * >>> + * The current implementation passes the raw APDU's from 7816 and additionally >>> + * contains messages to setup and teardown readers, handle insertion and >>> + * removal of cards, negotiate the protocol and provide for error responses. >>> + * >>> + * Copyright (c) 2010 Red Hat. >>> + * >>> + * This code is licensed under the LGPL. >>> + */ >>> + >>> +#ifndef _VSCARD_COMMON_H >>> +#define _VSCARD_COMMON_H >>> + >>> +#include<stdint.h> >>> + >>> +#define VERSION_MAJOR_BITS 11 >>> +#define VERSION_MIDDLE_BITS 11 >>> +#define VERSION_MINOR_BITS 10 >>> >> Distros make versioning not enough. Inevitably, someone wants to >> back port a bug fix or a feature for some RHEL7.2 release or >> something like that. >> >> Feature negotiation has worked pretty well for us and I'd suggest >> using it within the protocol. >> >> > Suggestion accepted. > > >>> +#define MAKE_VERSION(major, middle, minor) \ >>> + ( (major<< (VERSION_MINOR_BITS + VERSION_MIDDLE_BITS)) \ >>> + | (middle<< VERSION_MINOR_BITS) \ >>> + | (minor) ) >>> + >>> +/** IMPORTANT NOTE on VERSION >>> + * >>> + * The version below MUST be changed whenever a change in this file is made. >>> + * >>> + * The last digit, the minor, is for bug fix changes only. >>> + * >>> + * The middle digit is for backward / forward compatible changes, updates >>> + * to the existing messages, addition of fields. >>> + * >>> + * The major digit is for a breaking change of protocol, presumably >>> + * something that cannot be accomodated with the existing protocol. >>> + */ >>> + >>> +#define VSCARD_VERSION MAKE_VERSION(0,0,1) >>> + >>> +typedef enum { >>> + VSC_Init, >>> + VSC_Error, >>> + VSC_ReaderAdd, >>> + VSC_ReaderAddResponse, >>> + VSC_ReaderRemove, >>> + VSC_ATR, >>> + VSC_CardRemove, >>> + VSC_APDU, >>> + VSC_Reconnect >>> +} VSCMsgType; >>> >> Should number the enum to be specific at least. >> > will fix. > > >> >>> + >>> +typedef enum { >>> + VSC_GENERAL_ERROR=1, >>> + VSC_CANNOT_ADD_MORE_READERS, >>> +} VSCErrorCode; >>> + >>> +typedef uint32_t reader_id_t; >>> >> This namespace is reserved by C. >> > reader_id_t is reserved? > Anything with the suffix '_t' is reserved by the standard library. It's a widely violated rule, but we have run into problems from not obeying it. >>> +/* VSCMsgReaderAdd Client -> Host >>> + * Host replies with allocated reader id in ReaderAddResponse >>> + * name - name of the reader on client side. >>> + * */ >>> +typedef struct VSCMsgReaderAdd { >>> + uint8_t name[0]; >>> >> Is this a string? >> >> > Yes. You expect char? > Yes, also, what's the encoding (UTF-8)? Regards, Anthony Liguori ^ permalink raw reply [flat|nested] 35+ messages in thread
* Re: [Qemu-devel] [PATCH 2/7] ccid: add passthru card device 2011-01-25 16:24 ` Anthony Liguori @ 2011-01-25 16:50 ` Alon Levy 0 siblings, 0 replies; 35+ messages in thread From: Alon Levy @ 2011-01-25 16:50 UTC (permalink / raw) To: Anthony Liguori; +Cc: qemu-devel On Tue, Jan 25, 2011 at 10:24:53AM -0600, Anthony Liguori wrote: > On 01/25/2011 10:21 AM, Alon Levy wrote: > >On Tue, Jan 25, 2011 at 08:17:32AM -0600, Anthony Liguori wrote: > >>On 01/11/2011 02:42 AM, Alon Levy wrote: > >>>diff --git a/libcacard/vscard_common.h b/libcacard/vscard_common.h > >>>new file mode 100644 > >>>index 0000000..9ff1295 > >>>--- /dev/null > >>>+++ b/libcacard/vscard_common.h > >>This file (and the .c file) need a coding style pass to fixup > >>comments and the use of _ as a prefix but I want to focus on the > >>protocol itself. > >> > >>First, let's get a written spec into the wiki. I think it's > >>important that all of our compatibility protocols are documented in > >>a more formal way such that can be reviewed by a wider audience. > >ok, I'll create Features/Smartcard/Protocol > > > >>>@@ -0,0 +1,130 @@ > >>>+/* Virtual Smart Card protocol definition > >>>+ * > >>>+ * This protocol is between a host implementing a group of virtual smart card > >>>+ * reader, and a client implementing a virtual smart card, or passthrough to > >>>+ * a real card. > >>>+ * > >>>+ * The current implementation passes the raw APDU's from 7816 and additionally > >>>+ * contains messages to setup and teardown readers, handle insertion and > >>>+ * removal of cards, negotiate the protocol and provide for error responses. > >>>+ * > >>>+ * Copyright (c) 2010 Red Hat. > >>>+ * > >>>+ * This code is licensed under the LGPL. > >>>+ */ > >>>+ > >>>+#ifndef _VSCARD_COMMON_H > >>>+#define _VSCARD_COMMON_H > >>>+ > >>>+#include<stdint.h> > >>>+ > >>>+#define VERSION_MAJOR_BITS 11 > >>>+#define VERSION_MIDDLE_BITS 11 > >>>+#define VERSION_MINOR_BITS 10 > >>Distros make versioning not enough. Inevitably, someone wants to > >>back port a bug fix or a feature for some RHEL7.2 release or > >>something like that. > >> > >>Feature negotiation has worked pretty well for us and I'd suggest > >>using it within the protocol. > >> > >Suggestion accepted. > > > >>>+#define MAKE_VERSION(major, middle, minor) \ > >>>+ ( (major<< (VERSION_MINOR_BITS + VERSION_MIDDLE_BITS)) \ > >>>+ | (middle<< VERSION_MINOR_BITS) \ > >>>+ | (minor) ) > >>>+ > >>>+/** IMPORTANT NOTE on VERSION > >>>+ * > >>>+ * The version below MUST be changed whenever a change in this file is made. > >>>+ * > >>>+ * The last digit, the minor, is for bug fix changes only. > >>>+ * > >>>+ * The middle digit is for backward / forward compatible changes, updates > >>>+ * to the existing messages, addition of fields. > >>>+ * > >>>+ * The major digit is for a breaking change of protocol, presumably > >>>+ * something that cannot be accomodated with the existing protocol. > >>>+ */ > >>>+ > >>>+#define VSCARD_VERSION MAKE_VERSION(0,0,1) > >>>+ > >>>+typedef enum { > >>>+ VSC_Init, > >>>+ VSC_Error, > >>>+ VSC_ReaderAdd, > >>>+ VSC_ReaderAddResponse, > >>>+ VSC_ReaderRemove, > >>>+ VSC_ATR, > >>>+ VSC_CardRemove, > >>>+ VSC_APDU, > >>>+ VSC_Reconnect > >>>+} VSCMsgType; > >>Should number the enum to be specific at least. > >will fix. > > > >>>+ > >>>+typedef enum { > >>>+ VSC_GENERAL_ERROR=1, > >>>+ VSC_CANNOT_ADD_MORE_READERS, > >>>+} VSCErrorCode; > >>>+ > >>>+typedef uint32_t reader_id_t; > >>This namespace is reserved by C. > >reader_id_t is reserved? > > Anything with the suffix '_t' is reserved by the standard library. > > It's a widely violated rule, but we have run into problems from not > obeying it. I thought qemu coding style said something explicitly about using _t for types that alias basic types - this is actually a change I did to comply.. > > >>>+/* VSCMsgReaderAdd Client -> Host > >>>+ * Host replies with allocated reader id in ReaderAddResponse > >>>+ * name - name of the reader on client side. > >>>+ * */ > >>>+typedef struct VSCMsgReaderAdd { > >>>+ uint8_t name[0]; > >>Is this a string? > >> > >Yes. You expect char? > > Yes, also, what's the encoding (UTF-8)? It's not actually printed anywhere right now, so I'd say unspecififed. I'll document UTF-8. > > Regards, > > Anthony Liguori > ^ permalink raw reply [flat|nested] 35+ messages in thread
* Re: [Qemu-devel] [PATCH 2/7] ccid: add passthru card device 2011-01-25 14:17 ` Anthony Liguori 2011-01-25 16:21 ` Alon Levy @ 2011-01-27 21:13 ` Alon Levy 2011-01-27 21:42 ` Anthony Liguori 2011-01-30 17:35 ` Alon Levy 2 siblings, 1 reply; 35+ messages in thread From: Alon Levy @ 2011-01-27 21:13 UTC (permalink / raw) To: Anthony Liguori; +Cc: qemu-devel On Tue, Jan 25, 2011 at 08:17:32AM -0600, Anthony Liguori wrote: > On 01/11/2011 02:42 AM, Alon Levy wrote: > >diff --git a/libcacard/vscard_common.h b/libcacard/vscard_common.h > >new file mode 100644 > >index 0000000..9ff1295 > >--- /dev/null > >+++ b/libcacard/vscard_common.h > > This file (and the .c file) need a coding style pass to fixup > comments and the use of _ as a prefix but I want to focus on the > protocol itself. > > First, let's get a written spec into the wiki. I think it's > important that all of our compatibility protocols are documented in > a more formal way such that can be reviewed by a wider audience. > > >@@ -0,0 +1,130 @@ > >+/* Virtual Smart Card protocol definition > >+ * > >+ * This protocol is between a host implementing a group of virtual smart card > >+ * reader, and a client implementing a virtual smart card, or passthrough to > >+ * a real card. > >+ * > >+ * The current implementation passes the raw APDU's from 7816 and additionally > >+ * contains messages to setup and teardown readers, handle insertion and > >+ * removal of cards, negotiate the protocol and provide for error responses. > >+ * > >+ * Copyright (c) 2010 Red Hat. > >+ * > >+ * This code is licensed under the LGPL. > >+ */ > >+ > >+#ifndef _VSCARD_COMMON_H > >+#define _VSCARD_COMMON_H > >+ > >+#include<stdint.h> > >+ > >+#define VERSION_MAJOR_BITS 11 > >+#define VERSION_MIDDLE_BITS 11 > >+#define VERSION_MINOR_BITS 10 > > Distros make versioning not enough. Inevitably, someone wants to > back port a bug fix or a feature for some RHEL7.2 release or > something like that. > > Feature negotiation has worked pretty well for us and I'd suggest > using it within the protocol. > > >+#define MAKE_VERSION(major, middle, minor) \ > >+ ( (major<< (VERSION_MINOR_BITS + VERSION_MIDDLE_BITS)) \ > >+ | (middle<< VERSION_MINOR_BITS) \ > >+ | (minor) ) > >+ > >+/** IMPORTANT NOTE on VERSION > >+ * > >+ * The version below MUST be changed whenever a change in this file is made. > >+ * > >+ * The last digit, the minor, is for bug fix changes only. > >+ * > >+ * The middle digit is for backward / forward compatible changes, updates > >+ * to the existing messages, addition of fields. > >+ * > >+ * The major digit is for a breaking change of protocol, presumably > >+ * something that cannot be accomodated with the existing protocol. > >+ */ > >+ > >+#define VSCARD_VERSION MAKE_VERSION(0,0,1) > >+ > >+typedef enum { > >+ VSC_Init, > >+ VSC_Error, > >+ VSC_ReaderAdd, > >+ VSC_ReaderAddResponse, > >+ VSC_ReaderRemove, > >+ VSC_ATR, > >+ VSC_CardRemove, > >+ VSC_APDU, > >+ VSC_Reconnect > >+} VSCMsgType; > > Should number the enum to be specific at least. > > >+ > >+typedef enum { > >+ VSC_GENERAL_ERROR=1, > >+ VSC_CANNOT_ADD_MORE_READERS, > >+} VSCErrorCode; > >+ > >+typedef uint32_t reader_id_t; > > This namespace is reserved by C. > > >+#define VSCARD_UNDEFINED_READER_ID 0xffffffff > >+#define VSCARD_MINIMAL_READER_ID 0 > >+ > >+typedef struct VSCMsgHeader { > >+ VSCMsgType type; > >+ reader_id_t reader_id; > >+ uint32_t length; > > Is length just the data length or the whole message length? > The data length. Is this enough to document? > >+ uint8_t data[0]; > >+} VSCMsgHeader; > >+ > >+/* VSCMsgInit Client<-> Host > >+ * Host replies with allocated reader id in ReaderAddResponse > >+ * */ > >+typedef struct VSCMsgInit { > >+ uint32_t version; > >+} VSCMsgInit; > >+ > >+/* VSCMsgError Client<-> Host > >+ * */ > >+typedef struct VSCMsgError { > >+ uint32_t code; > >+} VSCMsgError; > >+ > >+/* VSCMsgReaderAdd Client -> Host > >+ * Host replies with allocated reader id in ReaderAddResponse > >+ * name - name of the reader on client side. > >+ * */ > >+typedef struct VSCMsgReaderAdd { > >+ uint8_t name[0]; > > Is this a string? > > >+} VSCMsgReaderAdd; > >+ > >+/* VSCMsgReaderAddResponse Host -> Client > >+ * Reply to ReaderAdd > >+ * */ > >+typedef struct VSCMsgReaderAddResponse { > >+} VSCMsgReaderAddResponse; > >+ > >+/* VSCMsgReaderRemove Client -> Host > >+ * */ > >+typedef struct VSCMsgReaderRemove { > >+} VSCMsgReaderRemove; > >+ > >+/* VSCMsgATR Client -> Host > >+ * Answer to reset. Sent for card insertion or card reset. > >+ * */ > >+typedef struct VSCMsgATR { > >+ uint8_t atr[0]; > >+} VSCMsgATR; > >+ > >+/* VSCMsgCardRemove Client -> Host > >+ * */ > >+typedef struct VSCMsgCardRemove { > >+} VSCMsgCardRemove; > >+ > >+/* VSCMsgAPDU Client<-> Host > >+ * */ > >+typedef struct VSCMsgAPDU { > >+ uint8_t data[0]; > >+} VSCMsgAPDU; > >+ > >+/* VSCMsgReconnect Host -> Client > >+ * */ > >+typedef struct VSCMsgReconnect { > >+ uint32_t ip; > > This is not ipv6 friendly. Two strings would be a better choice. A string for host makes sense, why for port? isn't a 32 bit port enough? > > Regards, > > Anthony Liguori > > >+ uint16_t port; > >+} VSCMsgReconnect; > >+ > >+#endif > ^ permalink raw reply [flat|nested] 35+ messages in thread
* Re: [Qemu-devel] [PATCH 2/7] ccid: add passthru card device 2011-01-27 21:13 ` Alon Levy @ 2011-01-27 21:42 ` Anthony Liguori 0 siblings, 0 replies; 35+ messages in thread From: Anthony Liguori @ 2011-01-27 21:42 UTC (permalink / raw) To: qemu-devel; +Cc: Alon Levy On 01/27/2011 03:13 PM, Alon Levy wrote: >> This is not ipv6 friendly. Two strings would be a better choice. >> > A string for host makes sense, why for port? isn't a 32 bit port enough? > For an protocol, an integer is probably fine. For an API, a string is nice to allow service names too. Regards, Anthony Liguori >> Regards, >> >> Anthony Liguori >> >> >>> + uint16_t port; >>> +} VSCMsgReconnect; >>> + >>> +#endif >>> >> ^ permalink raw reply [flat|nested] 35+ messages in thread
* Re: [Qemu-devel] [PATCH 2/7] ccid: add passthru card device 2011-01-25 14:17 ` Anthony Liguori 2011-01-25 16:21 ` Alon Levy 2011-01-27 21:13 ` Alon Levy @ 2011-01-30 17:35 ` Alon Levy 2 siblings, 0 replies; 35+ messages in thread From: Alon Levy @ 2011-01-30 17:35 UTC (permalink / raw) To: Anthony Liguori; +Cc: qemu-devel On Tue, Jan 25, 2011 at 08:17:32AM -0600, Anthony Liguori wrote: > On 01/11/2011 02:42 AM, Alon Levy wrote: > >diff --git a/libcacard/vscard_common.h b/libcacard/vscard_common.h > >new file mode 100644 > >index 0000000..9ff1295 > >--- /dev/null > >+++ b/libcacard/vscard_common.h > > This file (and the .c file) need a coding style pass to fixup > comments and the use of _ as a prefix but I want to focus on the > protocol itself. > > First, let's get a written spec into the wiki. I think it's > important that all of our compatibility protocols are documented in > a more formal way such that can be reviewed by a wider audience. http://wiki.qeum.org/Features/Smartcard I'm still working on the rest, but you can review and comment on it. I've done a number of changes from the submitted here. I guess the idea is that iterations on the wiki can be faster? The changes done to the protocol: Removed Reconnect - doesn't scale easily, the same work should be done by whomever is initiating the migration, or via other mechanisms (i.e. spice) Added Flush/FlushComplete - still need to be able to tell client to wrap up the outstanding operations in any way. I'm planning on implementing this using register_savevm_live. Fixes suggested by you - set the enum, removed _ from surrounding #ifdef (btw - why does no one use #pragma once? IIUC it's supported by gcc?) The major issue I haven't tackled yet is the thread removal in ccid-card-emulated.c is that a blocker for integration? Can it be tackled later? Alon [snip] ^ permalink raw reply [flat|nested] 35+ messages in thread
* [Qemu-devel] [PATCH 3/7] libcacard: initial commit after coding style fixes 2011-01-11 8:42 [Qemu-devel] [PATCH 0/7] usb-ccid (v15) Alon Levy 2011-01-11 8:42 ` [Qemu-devel] [PATCH 1/7] usb-ccid: add CCID bus Alon Levy 2011-01-11 8:42 ` [Qemu-devel] [PATCH 2/7] ccid: add passthru card device Alon Levy @ 2011-01-11 8:42 ` Alon Levy 2011-01-25 14:19 ` Anthony Liguori 2011-01-11 8:42 ` [Qemu-devel] [PATCH 4/7] ccid: add ccid-card-emulated device (v2) Alon Levy ` (5 subsequent siblings) 8 siblings, 1 reply; 35+ messages in thread From: Alon Levy @ 2011-01-11 8:42 UTC (permalink / raw) To: qemu-devel From: Robert Relyea <rrelyea@redhat.com> libcacard emulates a Common Access Card (CAC) which is a standard for smartcards. It is used by the emulated ccid card introduced in a following patch. Docs are available in docs/libcacard.txt Signed-off-by: Alon Levy <alevy@redhat.com> --- Makefile | 6 +- Makefile.objs | 5 + Makefile.target | 2 + configure | 25 + docs/libcacard.txt | 483 ++++++++++++++++++ libcacard/Makefile | 14 + libcacard/cac.c | 411 +++++++++++++++ libcacard/cac.h | 20 + libcacard/card_7816.c | 780 ++++++++++++++++++++++++++++ libcacard/card_7816.h | 60 +++ libcacard/card_7816t.h | 163 ++++++ libcacard/config.h | 81 +++ libcacard/event.c | 112 ++++ libcacard/eventt.h | 28 + libcacard/link_test.c | 20 + libcacard/mutex.h | 59 +++ libcacard/passthru.c | 612 ++++++++++++++++++++++ libcacard/passthru.h | 50 ++ libcacard/vcard.c | 350 +++++++++++++ libcacard/vcard.h | 85 ++++ libcacard/vcard_emul.h | 62 +++ libcacard/vcard_emul_nss.c | 1171 +++++++++++++++++++++++++++++++++++++++++++ libcacard/vcard_emul_type.c | 60 +++ libcacard/vcard_emul_type.h | 29 ++ libcacard/vcardt.h | 66 +++ libcacard/vevent.h | 26 + libcacard/vreader.c | 526 +++++++++++++++++++ libcacard/vreader.h | 54 ++ libcacard/vreadert.h | 23 + libcacard/vscclient.c | 710 ++++++++++++++++++++++++++ 30 files changed, 6091 insertions(+), 2 deletions(-) create mode 100644 docs/libcacard.txt create mode 100644 libcacard/Makefile create mode 100644 libcacard/cac.c create mode 100644 libcacard/cac.h create mode 100644 libcacard/card_7816.c create mode 100644 libcacard/card_7816.h create mode 100644 libcacard/card_7816t.h create mode 100644 libcacard/config.h create mode 100644 libcacard/event.c create mode 100644 libcacard/eventt.h create mode 100644 libcacard/link_test.c create mode 100644 libcacard/mutex.h create mode 100644 libcacard/passthru.c create mode 100644 libcacard/passthru.h create mode 100644 libcacard/vcard.c create mode 100644 libcacard/vcard.h create mode 100644 libcacard/vcard_emul.h create mode 100644 libcacard/vcard_emul_nss.c create mode 100644 libcacard/vcard_emul_type.c create mode 100644 libcacard/vcard_emul_type.h create mode 100644 libcacard/vcardt.h create mode 100644 libcacard/vevent.h create mode 100644 libcacard/vreader.c create mode 100644 libcacard/vreader.h create mode 100644 libcacard/vreadert.h create mode 100644 libcacard/vscclient.c diff --git a/Makefile b/Makefile index 6d601ee..b522d0a 100644 --- a/Makefile +++ b/Makefile @@ -173,6 +173,8 @@ check-qlist: check-qlist.o qlist.o qint.o $(CHECK_PROG_DEPS) check-qfloat: check-qfloat.o qfloat.o $(CHECK_PROG_DEPS) check-qjson: check-qjson.o qfloat.o qint.o qdict.o qstring.o qlist.o qbool.o qjson.o json-streamer.o json-lexer.o json-parser.o $(CHECK_PROG_DEPS) +QEMULIBS=libhw32 libhw64 libuser libdis libdis-user libcacard + clean: # avoid old build problems by removing potentially incorrect old files rm -f config.mak op-i386.h opc-i386.h gen-op-i386.h op-arm.h opc-arm.h gen-op-arm.h @@ -184,7 +186,7 @@ clean: rm -f trace-dtrace.dtrace trace-dtrace.dtrace-timestamp rm -f trace-dtrace.h trace-dtrace.h-timestamp $(MAKE) -C tests clean - for d in $(ALL_SUBDIRS) libhw32 libhw64 libuser libdis libdis-user; do \ + for d in $(ALL_SUBDIRS) $(QEMULIBS); do \ if test -d $$d; then $(MAKE) -C $$d $@ || exit 1; fi; \ rm -f $$d/qemu-options.def; \ done @@ -195,7 +197,7 @@ distclean: clean rm -f roms/seabios/config.mak roms/vgabios/config.mak rm -f qemu-doc.info qemu-doc.aux qemu-doc.cp qemu-doc.dvi qemu-doc.fn qemu-doc.info qemu-doc.ky qemu-doc.log qemu-doc.pdf qemu-doc.pg qemu-doc.toc qemu-doc.tp qemu-doc.vr rm -f qemu-tech.info qemu-tech.aux qemu-tech.cp qemu-tech.dvi qemu-tech.fn qemu-tech.info qemu-tech.ky qemu-tech.log qemu-tech.pdf qemu-tech.pg qemu-tech.toc qemu-tech.tp qemu-tech.vr - for d in $(TARGET_DIRS) libhw32 libhw64 libuser libdis libdis-user; do \ + for d in $(TARGET_DIRS) $(QEMULIBS); do \ rm -rf $$d || exit 1 ; \ done diff --git a/Makefile.objs b/Makefile.objs index 274db5e..6a0030b 100644 --- a/Makefile.objs +++ b/Makefile.objs @@ -315,6 +315,11 @@ user-obj-y += qemu-timer-common.o endif endif +###################################################################### +# smartcard + +libcacard-y = cac.o event.o passthru.o vcard.o vreader.o vcard_emul_nss.o vcard_emul_type.o card_7816.o + vl.o: QEMU_CFLAGS+=$(GPROF_CFLAGS) vl.o: QEMU_CFLAGS+=$(SDL_CFLAGS) diff --git a/Makefile.target b/Makefile.target index a5e217e..419a530 100644 --- a/Makefile.target +++ b/Makefile.target @@ -335,6 +335,8 @@ obj-y += $(addprefix $(HWDIR)/, $(hw-obj-y)) endif # CONFIG_SOFTMMU +obj-y += $(addprefix ../libcacard/, $(libcacard-$(CONFIG_SMARTCARD))) + obj-y += $(addprefix ../, $(trace-obj-y)) obj-$(CONFIG_GDBSTUB_XML) += gdbstub-xml.o diff --git a/configure b/configure index 839980c..4567057 100755 --- a/configure +++ b/configure @@ -2209,6 +2209,19 @@ EOF fi fi +# check for libcacard for smartcard support +smartcard_cflags="-I\$(SRC_PATH)/libcacard" +libcacard_libs=$($pkgconfig --libs nss 2>/dev/null) +libcacard_cflags=$($pkgconfig --cflags nss) +# TODO - what's the minimal nss version we support? +if $pkgconfig --atleast-version=3.12.8 nss; then + smartcard="yes" + QEMU_CFLAGS="$QEMU_CFLAGS $smartcard_cflags $libcacard_cflags" + LIBS="$libcacard_libs $LIBS" +else + smartcard="no" +fi + ########################################## ########################################## @@ -3046,6 +3059,11 @@ fi if test "$target_darwin_user" = "yes" ; then echo "CONFIG_DARWIN_USER=y" >> $config_target_mak fi +if test "$smartcard" = "yes" ; then + echo "subdir-$target: subdir-libcacard" >> $config_host_mak + echo "libcacard_libs=$libcacard_libs" >> $config_host_mak + echo "libcacard_cflags=$libcacard_cflags" >> $config_host_mak +fi list="" if test ! -z "$gdb_xml_files" ; then for x in $gdb_xml_files; do @@ -3266,6 +3284,13 @@ for hwlib in 32 64; do echo "QEMU_CFLAGS+=-DTARGET_PHYS_ADDR_BITS=$hwlib" > $d/config.mak done +if [ $source_path != $workdir ]; then + # out of tree build + mkdir -p libcacard + rm -f libcacard/Makefile + ln -s $source_path/libcacard/Makefile libcacard/Makefile +fi + d=libuser mkdir -p $d rm -f $d/Makefile diff --git a/docs/libcacard.txt b/docs/libcacard.txt new file mode 100644 index 0000000..5dee6fa --- /dev/null +++ b/docs/libcacard.txt @@ -0,0 +1,483 @@ +This file documents the CAC (Common Access Card) library in the libcacard +subdirectory. + +Virtual Smart Card Emulator + +This emulator is designed to provide emulation of actual smart cards to a +virtual card reader running in a guest virtual machine. The emulated smart +cards can be representations of real smart cards, where the necessary functions +such as signing, card removal/insertion, etc. are mapped to real, physical +cards which are shared with the client machine the emulator is running on, or +the cards could be pure software constructs. + +The emulator is structured to allow multiple replacable or additional pieces, +so it can be easily modified for future requirements. The primary envisioned +modifications are: + +1) The socket connection to the virtual card reader (presumably a CCID reader, +but other ISO-7816 compatible readers could be used). The code that handles +this is in vscclient.c. + +2) The virtual card low level emulation. This is currently supplied by using +NSS. This emulation could be replaced by implementations based on other +security libraries, including but not limitted to openssl+pkcs#11 library, +raw pkcs#11, Microsoft CAPI, direct opensc calls, etc. The code that handles +this is in vcard_emul_nss.c. + +3) Emulation for new types of cards. The current implementation emulates the +original DoD CAC standard with separate pki containers. This emulator lives in +cac.c. More than one card type emulator could be included. Other cards could +be emulated as well, including PIV, newer versions of CAC, PKCS #15, etc. + +-------------------- +Replacing the Socket Based Virtual Reader Interface. + +The current implementation contains a replacable module vscclient.c. The +current vscclient.c implements a sockets interface to the virtual ccid reader +on the guest. CCID commands that are pertinent to emulation are passed +across the socket, and their responses are passed back along that same socket. +The protocol that vscclient uses is defined in vscard_common.h and connects +to a qemu ccid usb device. Since this socket runs as a client, vscclient.c +implements a program with a main entry. It also handles argument parsing for +the emulator. + +An application that wants to use the virtual reader can replace vscclient.c +with it's own implementation that connects to it's own CCID reader. The calls +that the CCID reader can call are: + + VReaderList * vreader_get_reader_list(); + + This function returns a list of virtual readers. These readers may map to + physical devices, or simulated devices depending on vcard the back end. Each + reader in the list should represent a reader to the virtual machine. Virtual + USB address mapping is left to the CCID reader front end. This call can be + made any time to get an updated list. The returned list is a copy of the + internal list that can be referenced by the caller without locking. This copy + must be freed by the caller with vreader_list_delete when it is no longer + needed. + + VReaderListEntry *vreader_list_get_first(VReaderList *); + + This function gets the first entry on the reader list. Along with + vreader_list_get_next(), vreader_list_get_first() can be used to walk the + reader list returned from vreader_get_reader_list(). VReaderListEntries are + part of the list themselves and do not need to be freed separately from the + list. If there are no entries on the list, it will return NULL. + + VReaderListEntry *vreader_list_get_next(VReaderListEntry *); + + This function gets the next entry in the list. If there are no more entries + it will return NULL. + + VReader * vreader_list_get_reader(VReaderListEntry *) + + This function returns the reader stored in the reader List entry. Caller gets + a new reference to a reader. The caller must free it's reference when it is + finished with vreader_free(). + + void vreader_free(VReader *reader); + + This function frees a reference to a reader. Reader's are reference counted + and are automatically deleted when the last reference is freed. + + void vreader_list_delete(VReaderList *list); + + This function frees the list, all the elements on the list, and all the + reader references held by the list. + + VReaderStatus vreader_power_on(VReader *reader, char *atr, int *len); + + This functions simulates a card power on. Virtual cards do not care about + the actual voltage and other physical parameters, but it does care that the + card is actually on or off. Cycling the card causes the card to reset. If + the caller provides enough space, vreader_power_on will return the ATR of + the virtual card. The amount of space provided in atr should be indicated + in *len. The function modifies *len to be the actual length of of the + returned ATR. + + VReaderStatus vreader_power_off(VReader *reader); + + This function simulates a power off of a virtual card. + + VReaderStatus vreader_xfer_bytes(VReader *reader, unsigne char *send_buf, + int send_buf_len, + unsigned char *receive_buf, + int receive_buf_len); + + This functions send a raw apdu to a card and returns the card's response. + The CCID front end should return the response back. Most of the emulation + is driven from these APDUs. + + VReaderStatus vreader_card_is_present(VReader *reader); + + This function returns whether or not the reader has a card inserted. The + vreader_power_on, vreader_power_off, and vreader_xfer_bytes will return + VREADER_NO_CARD. + + const char *vreader_get_name(VReader *reader); + + This function returns the name of the reader. The name comes from the card + emulator level and is usually related to the name of the physical reader. + + VReaderID vreader_get_id(VReader *reader); + + This function returns the id of a reader. All readers start out with an id + of -1. The application can set the id with vreader_set_id. + + VReaderStatus vreader_get_id(VReader *reader, VReaderID id); + + This function sets the reader id. The application is responsible for making + sure that the id is unique for all readers it is actively using. + + VReader *vreader_find_reader_by_id(VReaderID id); + + This function returns the reader which matches the id. If two readers match, + only one is returned. The function returns NULL if the id is -1. + + Event *vevent_wait_next_vevent(); + + This function blocks waiting for reader and card insertion events. There + will be one event for each card insertion, each card removal, each reader + insertion and each reader removal. At start up, events are created for all + the initial readers found, as well as all the cards that are inserted. + + Event *vevent_get_next_vevent(); + + This function returns a pending event if it exists, otherwise it returns + NULL. It does not block. + +---------------- +Card Type Emulator: Adding a New Virtual Card Type + +The ISO 7816 card spec describes 2 types of cards: + 1) File system cards, where the smartcard is managed by reading and writing +data to files in a file system. There is currently only boiler plate +implemented for file system cards. + 2) VM cards, where the card has loadable applets which perform the card +functions. The current implementation supports VM cards. + +In the case of VM cards, the difference between various types of cards is +really what applets have been installed in that card. This structure is +mirrored in card type emulators. The 7816 emulator already handles the basic +ISO 7186 commands. Card type emulators simply need to add the virtual applets +which emulate the real card applets. Card type emulators have exactly one +public entry point: + + VCARDStatus xxx_card_init(VCard *card, const char *flags, + const unsigned char *cert[], + int cert_len[], + VCardKey *key[], + int cert_count); + + The parameters for this are: + card - the virtual card structure which will prepresent this card. + flags - option flags that may be specific to this card type. + cert - array of binary certificates. + cert_len - array of lengths of each of the certificates specified in cert. + key - array of opaque key structures representing the private keys on + the card. + cert_count - number of entries in cert, cert_len, and key arrays. + + Any cert, cert_len, or key with the same index are matching sets. That is + cert[0] is cert_len[0] long and has the corresponsing private key of key[0]. + +The card type emulator is expected to own the VCardKeys, but it should copy +any raw cert data it wants to save. It can create new applets and add them to +the card using the following functions: + + VCardApplet *vcard_new_applet(VCardProcessAPDU apdu_func, + VCardResetApplet reset_func, + const unsigned char *aid, + int aid_len); + + This function creates a new applet. Applet structures store the following + information: + 1) the AID of the applet (set by aid and aid_len). + 2) a function to handle APDUs for this applet. (set by apdu_func, more on + this below). + 3) a function to reset the applet state when the applet is selected. + (set by reset_func, more on this below). + 3) applet private data, a data pointer used by the card type emulator to + store any data or state it needs to complete requests. (set by a + separate call). + 4) applet private data free, a function used to free the applet private + data when the applet itself is destroyed. + The created applet can be added to the card with vcard_add_applet below. + + void vcard_set_applet_private(VCardApplet *applet, + VCardAppletPrivate *private, + VCardAppletPrivateFree private_free); + This function sets the private data and the corresponding free function. + VCardAppletPrivate is an opaque data structure to the rest of the emulator. + The card type emulator can define it any way it wants by defining + struct VCardAppletPrivateStruct {};. If there is already a private data + structure on the applet, the old one is freed before the new one is set up. + passing two NULL clear any existing private data. + + VCardStatus vcard_add_applet(VCard *card, VCardApplet *applet); + + Add an applet onto the list of applets attached to the card. Once an applet + has been added, it can be selected by it's aid, and then commands will be + routed to it VCardProcessAPDU function. This function adopts the applet the + passed int applet. Note: 2 applets with the same AID should not be added to + the same card. It's permissible to add more than one applet. Multiple applets + may have the same VCardPRocessAPDU entry point. + +The certs and keys should be attached to private data associated with one or +more appropriate applets for that card. Control will come to the card type +emulators once one of its applets are selected through the VCardProcessAPDU +function it specified when it created the applet. + +The signature of VCardResetApplet is: + VCardStatus (*VCardResetApplet) (VCard *card, int channel); + This function will reset the any internal applet state that needs to be + cleared after a select applet call. It should return VCARD_DONE; + +The signature of VCardProcessAPDU is: + VCardStatus (*VCardProcessAPDU)(VCard *card, VCardAPDU *apdu, + VCardResponse **response); + This function examines the APDU and determines whether it should process + the apdu directly, reject the apdu as invalid, or pass the apdu on to + the basic 7816 emulator for processing. + If the 7816 emulator should process the apdu, then the VCardProcessAPDU + should return VCARD_NEXT. + If there is an error, then VCardProcessAPDU should return an error + response using vcard_make_response and the appropriate 7816 error code + (see card_7816t.h) or vcard_make_response with a card type specific error + code. It should then return VCARD_DONE. + If the apdu can be processed correctly, VCardProcessAPDU should do so, + set the response value appropriately for that APDU, and return VCARD_DONE. + VCardProcessAPDU should always set the response if it returns VCARD_DONE. + It should always either return VCARD_DONE or VCARD_NEXT. + +Parsing the APDU -- + +Prior to processing calling the card type emulator's VCardProcessAPDU function, the emulator has already decoded the APDU header and set several fields: + + apdu->a_data - The raw apdu data bytes. + apdu->a_len - The len of the raw apdu data. + apdu->a_body - The start of any post header parameter data. + apdu->a_Lc - The parameter length value. + apdu->a_Le - The expected length of any returned data. + apdu->a_cla - The raw apdu class. + apdu->a_channel - The channel (decoded from the class). + apdu->a_secure_messaging_type - The decoded secure messagin type + (from class). + apdu->a_type - The decode class type. + apdu->a_gen_type - the generic class type (7816, PROPRIETARY, RFU, PTS). + apdu->a_ins - The instruction byte. + apdu->a_p1 - Parameter 1. + apdu->a_p2 - Parameter 2. + +Creating a Response -- + +The expected result of any APDU call is a response. The card type emulator must +set *response with an appropriate VCardResponse value if it returns VCARD_DONE. +Reponses could be as simple as returning a 2 byte status word response, to as +complex as returning a block of data along with a 2 byte response. Which is +returned will depend on the semantics of the APDU. The following functions will +create card responses. + + VCardResponse *vcard_make_response(VCard7816Status status); + + This is the most basic function to get a response. This function will + return a response the consists soley one 2 byte status code. If that status + code is defined in card_7816t.h, then this function is guarrenteed to + return a response with that status. If a cart type specific status code + is passed and vcard_make_response fails to allocate the appropriate memory + for that response, then vcard_make_response will return a VCardResponse + of VCARD7816_STATUS_EXC_ERROR_MEMORY. In any case, this function is + guarrenteed to return a valid VCardResponse. + + VCardResponse *vcard_response_new(unsigned char *buf, int len, + VCard7816Status status); + + This function is similar to vcard_make_response except it includes some + returned data with the response. It could also fail to allocate enough + memory, in which case it will return NULL. + + VCardResponse *vcard_response_new_status_bytes(unsigned char sw1, + unsigned char sw2); + + Sometimes in 7816 the response bytes are treated as two separate bytes with + split meanings. This function allows you to create a response based on + two separate bytes. This function could fail, in which case it will return + NULL. + + VCardResponse *vcard_response_new_bytes(unsigned char *buf, int len, + unsigned char sw1, + unsigned char sw2); + + This function is the same as vcard_response_new except you may specify + the status as two separate bytes like vcard_response_new_status_bytes. + + +Implementing functionality --- + +The following helper functions access information about the current card +and applet. + + VCARDAppletPrivate *vcard_get_current_applet_private(VCard *card, + int channel); + + This function returns any private data set by the card type emulator on + the currently selected applet. The card type emulator keeps track of the + current applet state in this data structure. Any certs and keys associated + with a particular applet is also stored here. + + int vcard_emul_get_login_count(VCard *card); + + This function returns the the number of remaing login attempts for this + card. If the card emulator does not know, or the card does not have a + way of giving this information, this function returns -1. + + + VCard7816Status vcard_emul_login(VCard *card, unsigned char *pin, + int pin_len); + + This function logins into the card and return the standard 7816 status + word depending on the success or failure of the call. + + void vcard_emul_delete_key(VCardKey *key); + + This function frees the VCardKey passed in to xxxx_card_init. The card + type emulator is responsible for freeing this key when it no longer needs + it. + + VCard7816Status vcard_emul_rsa_op(VCard *card, VCardKey *key, + unsigned char *buffer, + int buffer_size); + + This function does a raw rsa op on the buffer with the given key. + +The sample card type emulator is found in cac.c. It implements the cac specific +applets. Only those applets needed by the coolkey pkcs#11 driver on the guest +have been implemented. To support the full range CAC middleware, a complete CAC +card according to the CAC specs should be implemented here. + +------------------------------ +Virtual Card Emulator + +This code accesses both real smart cards and simulated smart cards through +services provided on the client. The current implementation uses NSS, which +already knows how to talk to various PKCS #11 modules on the client, and is +portable to most operating systems. A particular emulator can have only one +virtual card implementation at a time. + +The virtual card emulator consists of a series of virtual card services. In +addition to the services describe above (services starting with +vcard_emul_xxxx), the virtual card emulator also provides the following +functions: + + VCardEmulError vcard_emul_init(cont VCardEmulOptions *options); + + The options structure is built by another function in the virtual card + interface where a string of virtual card emulator specific strings are + mapped to the options. The actual structure is defined by the virutal card + emulator and is used to determine the configuration of soft cards, or to + determine which physical cards to present to the guest. + + The vcard_emul_init function will build up sets of readers, create any + threads that are needed to watch for changes in the reader state. If readers + have cards present in them, they are also initialized. + + Readers are created with the function. + + VReader *vreader_new(VReaderEmul *reader_emul, + VReaderEmulFree reader_emul_free); + + The freeFunc is used to free the VReaderEmul * when the reader is + destroyed. The VReaderEmul structure is an opaque structure to the + rest of the code, but defined by the virtual card emulator, which can + use it to store any reader specific state. + + Once the reader has been created, it can be added to the front end with the + call: + + VReaderStatus vreader_add_reader(VReader *reader); + + This function will automatically generate the appropriate new reader + events and add the reader to the list. + + To create a new card, the virtual card emulator will call a similiar + function. + + VCard *vcard_new(VCardEmul *card_emul, + VCardEmulFree card_emul_free); + + Like vreader_new, this function takes a virtual card emulator specific + structure which it uses to keep track of the card state. + + Once the card is created, it is attached to a card type emulator with the + following function: + + VCardStatus vcard_init(VCard *vcard, VCardEmulType type, + const char *flags, + unsigned char *const *certs, + int *cert_len, + VCardKey *key[], + int cert_count); + + The vcard is the value returned from vcard_new. The type is the + card type emulator that this card should presented to the guest as. + The flags are card type emulator specific options. The certs, + cert_len, and keys are all arrays of length cert_count. These are the + the same of the parameters xxxx_card_init() accepts. + + Finally the card is associated with it's reader by the call: + + VReaderStatus vreader_insert_card(VReader *vreader, VCard *vcard); + + This function, like vreader_add_reader, will take care of any event + notification for the card insert. + + + VCardEmulError vcard_emul_force_card_remove(VReader *vreader); + + Force a card that is present to appear to be removed to the guest, even if + that card is a physical card and is present. + + + VCardEmulError vcard_emul_force_card_insert(VReader *reader); + + Force a card that has been removed by vcard_emul_force_card_remove to be + reinserted from the point of view of the guest. This will only work if the + card is physically present (which is always true fro a soft card). + + void vcard_emul_get_atr(Vcard *card, unsigned char *atr, int *atr_len); + + Return the virtual ATR for the card. By convention this should be the value + VCARD_ATR_PREFIX(size) followed by several ascii bytes related to this + particular emulator. For instance the NSS emulator returns + {VCARD_ATR_PREFIX(3), 'N', 'S', 'S' }. Do ot return more data then *atr_len; + + void vcard_emul_reset(VCard *card, VCardPower power) + + Set the state of 'card' to the current power level and reset its internal + state (logout, etc). + +------------------------------------------------------- +List of files and their function: +README - This file +card_7816.c - emulate basic 7816 functionality. Parse APDUs. +card_7816.h - apdu and response services definitions. +card_7816t.h - 7816 specific structures, types and definitions. +event.c - event handling code. +event.h - event handling services definitions. +eventt.h - event handling structures and types +vcard.c - handle common virtual card services like creation, destruction, and + applet management. +vcard.h - common virtual card services function definitions. +vcardt.h - comon virtual card types +vreader.c - common virtual reader services. +vreader.h - common virtual reader services definitions. +vreadert.h - comon virtual reader types. +vcard_emul_type.c - manage the card type emulators. +vcard_emul_type.h - definitions for card type emulators. +cac.c - card type emulator for CAC cards +vcard_emul.h - virtual card emulator service definitions. +vcard_emul_nss.c - virtual card emulator implementation for nss. +vscclient.c - socket connection to guest qemu usb driver. +vscard_common.h - common header with the guest qemu usb driver. +mutex.h - header file for machine independent mutexes. +link_test.c - static test to make sure all the symbols are properly defined. diff --git a/libcacard/Makefile b/libcacard/Makefile new file mode 100644 index 0000000..b146779 --- /dev/null +++ b/libcacard/Makefile @@ -0,0 +1,14 @@ +include ../config-host.mak +include $(SRC_PATH)/Makefile.objs +include $(SRC_PATH)/rules.mak + +$(call set-vpath, $(SRC_PATH):$(SRC_PATH)/libcacard) + +vscclient: $(libcacard-y) vscclient.o + gcc $(libcacard_libs) -o $@ $^ + +all: vscclient + +clean: + rm -f *.o */*.o *.d */*.d *.a */*.a *~ */*~ vscclient + diff --git a/libcacard/cac.c b/libcacard/cac.c new file mode 100644 index 0000000..e51caec --- /dev/null +++ b/libcacard/cac.c @@ -0,0 +1,411 @@ +/* + * implement the applets for the CAC card. + */ +#include "cac.h" +#include "vcard.h" +#include "vcard_emul.h" +#include "card_7816.h" +#include <stdlib.h> +#include <string.h> + +#define CAC_GET_PROPERTIES 0x56 +#define CAC_GET_ACR 0x4c +#define CAC_READ_BUFFER 0x52 +#define CAC_UPDATE_BUFFER 0x58 +#define CAC_SIGN_DECRYPT 0x42 +#define CAC_GET_CERTIFICATE 0x36 + +/* private data for PKI applets */ +typedef struct CACPKIAppletDataStruct { + unsigned char *cert; + int cert_len; + unsigned char *cert_buffer; + int cert_buffer_len; + unsigned char *sign_buffer; + int sign_buffer_len; + VCardKey *key; +} CACPKIAppletData; + +/* + * CAC applet private data + */ +struct VCardAppletPrivateStruct { + union { + CACPKIAppletData pki_data; + void *reserved; + } u; +}; + +/* + * handle all the APDU's that are common to all CAC applets + */ +static VCardStatus +cac_common_process_apdu(VCard *card, VCardAPDU *apdu, VCardResponse **response) +{ + int ef; + + switch (apdu->a_ins) { + case VCARD7816_INS_SELECT_FILE: + if (apdu->a_p1 != 0x02) { + /* let the 7816 code handle applet switches */ + return VCARD_NEXT; + } + /* handle file id setting */ + if (apdu->a_Lc != 2) { + *response = vcard_make_response( + VCARD7816_STATUS_ERROR_DATA_INVALID); + return VCARD_DONE; + } + /* CAC 1.0 only supports ef = 0 */ + ef = apdu->a_body[0] | (apdu->a_body[1] << 8); + if (ef != 0 ) { + *response = vcard_make_response( + VCARD7816_STATUS_ERROR_FILE_NOT_FOUND); + return VCARD_DONE; + } + *response = vcard_make_response(VCARD7816_STATUS_SUCCESS); + return VCARD_DONE; + case VCARD7816_INS_GET_RESPONSE: + case VCARD7816_INS_VERIFY: + /* let the 7816 code handle these */ + return VCARD_NEXT; + case CAC_GET_PROPERTIES: + case CAC_GET_ACR: + /* skip these for now, this will probably be needed */ + *response = vcard_make_response(VCARD7816_STATUS_ERROR_P1_P2_INCORRECT); + return VCARD_DONE; + } + *response = vcard_make_response( + VCARD7816_STATUS_ERROR_COMMAND_NOT_SUPPORTED); + return VCARD_DONE; +} + +/* + * resest the inter call state between applet selects + */ +static VCardStatus +cac_applet_pki_reset(VCard *card, int channel) +{ + VCardAppletPrivate *applet_private = NULL; + CACPKIAppletData *pki_applet = NULL; + applet_private = vcard_get_current_applet_private(card, channel); + ASSERT(applet_private); + pki_applet = &(applet_private->u.pki_data); + + pki_applet->cert_buffer = NULL; + if (pki_applet->sign_buffer) { + free(pki_applet->sign_buffer); + pki_applet->sign_buffer = NULL; + } + pki_applet->cert_buffer_len = 0; + pki_applet->sign_buffer_len = 0; + return VCARD_DONE; +} + +static VCardStatus +cac_applet_pki_process_apdu(VCard *card, VCardAPDU *apdu, + VCardResponse **response) +{ + CACPKIAppletData *pki_applet = NULL; + VCardAppletPrivate *applet_private = NULL; + int size, next; + unsigned char *sign_buffer; + vcard_7816_status_t status; + + applet_private = vcard_get_current_applet_private(card, apdu->a_channel); + ASSERT(applet_private); + pki_applet = &(applet_private->u.pki_data); + + switch (apdu->a_ins) { + case CAC_UPDATE_BUFFER: + *response = vcard_make_response( + VCARD7816_STATUS_ERROR_CONDITION_NOT_SATISFIED); + return VCARD_DONE; + case CAC_GET_CERTIFICATE: + if ((apdu->a_p2 != 0) || (apdu->a_p1 != 0)) { + *response = vcard_make_response( + VCARD7816_STATUS_ERROR_P1_P2_INCORRECT); + break; + } + ASSERT(pki_applet->cert != NULL); + size = apdu->a_Le; + if (pki_applet->cert_buffer == NULL) { + pki_applet->cert_buffer=pki_applet->cert; + pki_applet->cert_buffer_len=pki_applet->cert_len; + } + size = MIN(size, pki_applet->cert_buffer_len); + next = MIN(255, pki_applet->cert_buffer_len - size); + *response = vcard_response_new_bytes( + card, pki_applet->cert_buffer, size, + apdu->a_Le, next ? + VCARD7816_SW1_WARNING_CHANGE : + VCARD7816_SW1_SUCCESS, + next); + pki_applet->cert_buffer += size; + pki_applet->cert_buffer_len -= size; + if ((*response == NULL) || (next == 0)) { + pki_applet->cert_buffer=NULL; + } + if (*response == NULL) { + *response = vcard_make_response( + VCARD7816_STATUS_EXC_ERROR_MEMORY_FAILURE); + } + return VCARD_DONE; + case CAC_SIGN_DECRYPT: + if (apdu->a_p2 != 0) { + *response = vcard_make_response( + VCARD7816_STATUS_ERROR_P1_P2_INCORRECT); + break; + } + size = apdu->a_Lc; + + sign_buffer = realloc(pki_applet->sign_buffer, + pki_applet->sign_buffer_len+size); + if (sign_buffer == NULL) { + free(pki_applet->sign_buffer); + pki_applet->sign_buffer = NULL; + pki_applet->sign_buffer_len = 0; + *response = vcard_make_response( + VCARD7816_STATUS_EXC_ERROR_MEMORY_FAILURE); + return VCARD_DONE; + } + memcpy(sign_buffer+pki_applet->sign_buffer_len, apdu->a_body, size); + size += pki_applet->sign_buffer_len; + switch (apdu->a_p1) { + case 0x80: + /* p1 == 0x80 means we haven't yet sent the whole buffer, wait for + * the rest */ + pki_applet->sign_buffer = sign_buffer; + pki_applet->sign_buffer_len = size; + *response = vcard_make_response(VCARD7816_STATUS_SUCCESS); + return VCARD_DONE; + case 0x00: + /* we now have the whole buffer, do the operation, result will be + * in the sign_buffer */ + status = vcard_emul_rsa_op(card, pki_applet->key, + sign_buffer, size); + if (status != VCARD7816_STATUS_SUCCESS) { + *response = vcard_make_response(status); + break; + } + *response = vcard_response_new(card, sign_buffer, size, apdu->a_Le, + VCARD7816_STATUS_SUCCESS); + if (*response == NULL) { + *response = vcard_make_response( + VCARD7816_STATUS_EXC_ERROR_MEMORY_FAILURE); + } + break; + default: + *response = vcard_make_response( + VCARD7816_STATUS_ERROR_P1_P2_INCORRECT); + break; + } + free(sign_buffer); + pki_applet->sign_buffer = NULL; + pki_applet->sign_buffer_len = 0; + return VCARD_DONE; + case CAC_READ_BUFFER: + /* new CAC call, go ahead and use the old version for now */ + /* TODO: implement */ + *response = vcard_make_response( + VCARD7816_STATUS_ERROR_COMMAND_NOT_SUPPORTED); + return VCARD_DONE; + } + return cac_common_process_apdu(card, apdu, response); +} + + +static VCardStatus +cac_applet_id_process_apdu(VCard *card, VCardAPDU *apdu, + VCardResponse **response) +{ + switch (apdu->a_ins) { + case CAC_UPDATE_BUFFER: + *response = vcard_make_response( + VCARD7816_STATUS_ERROR_CONDITION_NOT_SATISFIED); + return VCARD_DONE; + case CAC_READ_BUFFER: + /* new CAC call, go ahead and use the old version for now */ + /* TODO: implement */ + *response = vcard_make_response( + VCARD7816_STATUS_ERROR_COMMAND_NOT_SUPPORTED); + return VCARD_DONE; + } + return cac_common_process_apdu(card, apdu, response); +} + + +/* + * TODO: if we ever want to support general CAC middleware, we will need to + * implement the various containers. + */ +static VCardStatus +cac_applet_container_process_apdu(VCard *card, VCardAPDU *apdu, + VCardResponse **response) +{ + switch (apdu->a_ins) { + case CAC_READ_BUFFER: + case CAC_UPDATE_BUFFER: + *response = vcard_make_response( + VCARD7816_STATUS_ERROR_COMMAND_NOT_SUPPORTED); + return VCARD_DONE; + default: + break; + } + return cac_common_process_apdu(card, apdu, response); +} + +/* + * utilities for creating and destroying the private applet data + */ +static void +cac_delete_pki_applet_private(VCardAppletPrivate *applet_private) +{ + CACPKIAppletData *pki_applet_data = NULL; + if (pki_applet_data == NULL) { + return; + } + pki_applet_data = &(applet_private->u.pki_data); + if (pki_applet_data->cert != NULL) { + free(pki_applet_data->cert); + } + if (pki_applet_data->sign_buffer != NULL) { + free(pki_applet_data->sign_buffer); + } + if (pki_applet_data->key != NULL) { + vcard_emul_delete_key(pki_applet_data->key); + } + free(applet_private); +} + +static VCardAppletPrivate * +cac_new_pki_applet_private(const unsigned char *cert, + int cert_len, VCardKey *key) +{ + CACPKIAppletData *pki_applet_data = NULL; + VCardAppletPrivate *applet_private = NULL; + applet_private = (VCardAppletPrivate *)malloc(sizeof(VCardAppletPrivate)); + + if (applet_private == NULL) { + goto fail; + } + pki_applet_data= &(applet_private->u.pki_data); + pki_applet_data->cert_buffer = NULL; + pki_applet_data->cert_buffer_len = 0; + pki_applet_data->sign_buffer = NULL; + pki_applet_data->sign_buffer_len = 0; + pki_applet_data->key = NULL; + pki_applet_data->cert = (unsigned char *)malloc(cert_len+1); + if (pki_applet_data->cert == NULL) { + goto fail; + } + /* + * if we want to support compression, then we simply change the 0 to a 1 + * and compress the cert data with libz + */ + pki_applet_data->cert[0] = 0; /* not compressed */ + memcpy(&pki_applet_data->cert[1], cert, cert_len); + pki_applet_data->cert_len = cert_len+1; + + pki_applet_data->key = key; + return applet_private; + +fail: + if (applet_private) { + cac_delete_pki_applet_private(applet_private); + } + return NULL; +} + + +/* + * create a new cac applet which links to a given cert + */ +static VCardApplet * +cac_new_pki_applet(int i, const unsigned char *cert, + int cert_len, VCardKey *key) +{ + VCardAppletPrivate *applet_private = NULL; + VCardApplet *applet = NULL; + unsigned char pki_aid[] = { 0xa0, 0x00, 0x00, 0x00, 0x79, 0x01, 0x00 }; + int pki_aid_len = sizeof (pki_aid); + + pki_aid[pki_aid_len-1] = i; + + applet_private = cac_new_pki_applet_private(cert, cert_len, key); + if (applet_private == NULL) { + goto failure; + } + applet = vcard_new_applet(cac_applet_pki_process_apdu, cac_applet_pki_reset, + pki_aid, pki_aid_len); + if (applet == NULL) { + goto failure; + } + vcard_set_applet_private(applet, applet_private, + cac_delete_pki_applet_private); + applet_private = NULL; + + return applet; + +failure: + if (applet_private != NULL) { + cac_delete_pki_applet_private(applet_private); + } + return NULL; +} + + +static unsigned char cac_default_container_aid[] = + { 0xa0, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00 }; +static unsigned char cac_id_aid[] = + { 0xa0, 0x00, 0x00, 0x00, 0x79, 0x03, 0x00 }; +/* + * Initialize the cac card. This is the only public function in this file. All + * the rest are connected through function pointers. + */ +VCardStatus +cac_card_init(VReader *reader, VCard *card, + const char *params, + unsigned char * const *cert, + int cert_len[], + VCardKey *key[] /* adopt the keys*/, + int cert_count) +{ + int i; + VCardApplet *applet; + + /* CAC Cards are VM Cards */ + vcard_set_type(card,VCARD_VM); + + /* create one PKI applet for each cert */ + for (i=0; i < cert_count; i++) { + applet = cac_new_pki_applet(i, cert[i], cert_len[i], key[i]); + if (applet == NULL) { + goto failure; + } + vcard_add_applet(card, applet); + } + + /* create a default blank container applet */ + applet = vcard_new_applet(cac_applet_container_process_apdu, + NULL, cac_default_container_aid, + sizeof(cac_default_container_aid)); + if (applet == NULL) { + goto failure; + } + vcard_add_applet(card, applet); + + /* create a default blank container applet */ + applet = vcard_new_applet(cac_applet_id_process_apdu, + NULL, cac_id_aid, + sizeof(cac_id_aid)); + if (applet == NULL) { + goto failure; + } + vcard_add_applet(card, applet); + return VCARD_DONE; + +failure: + return VCARD_FAIL; +} + diff --git a/libcacard/cac.h b/libcacard/cac.h new file mode 100644 index 0000000..bb2a9f0 --- /dev/null +++ b/libcacard/cac.h @@ -0,0 +1,20 @@ +/* + * defines the entry point for the cac card. Only used by cac.c anc + * vcard_emul_type.c + */ +#ifndef CAC_H +#define CAC_H 1 +#include "vcard.h" +#include "vreader.h" +/* + * Initialize the cac card. This is the only public function in this file. All + * the rest are connected through function pointers. + */ +VCardStatus cac_card_init(VReader *reader, VCard *card, const char *params, + unsigned char * const *cert, int cert_len[], + VCardKey *key[] /* adopt the keys*/, + int cert_count); + +/* not yet implemented */ +VCardStatus cac_is_cac_card(VReader *reader); +#endif diff --git a/libcacard/card_7816.c b/libcacard/card_7816.c new file mode 100644 index 0000000..e2d8466 --- /dev/null +++ b/libcacard/card_7816.c @@ -0,0 +1,780 @@ +/* + * Implement the 7816 portion of the card spec + * + */ + +#include "vcard.h" +#include "vcard_emul.h" +#include "card_7816.h" +#include <stdlib.h> +#include <string.h> + +/* + * set the status bytes based on the status word + */ +static void +vcard_response_set_status(VCardResponse *response, vcard_7816_status_t status) +{ + unsigned char sw1, sw2; + response->b_status = status; /* make sure the status and swX representations + * are consistent */ + sw1 = (status >> 8) & 0xff; + sw2 = status & 0xff; + response->b_sw1 = sw1; + response->b_sw2 = sw2; + response->b_data[response->b_len] = sw1; + response->b_data[response->b_len+1] = sw2; +} + +/* + * set the status bytes in a response buffer + */ +static void +vcard_response_set_status_bytes(VCardResponse *response, + unsigned char sw1, unsigned char sw2) +{ + response->b_status = sw1 << 8 | sw2; + response->b_sw1 = sw1; + response->b_sw2 = sw2; + response->b_data[response->b_len] = sw1; + response->b_data[response->b_len+1] = sw2; +} + +/* + * allocate a VCardResponse structure, plus space for the data buffer, and + * set up everything but the resonse bytes. + */ +VCardResponse * +vcard_response_new_data(unsigned char *buf, int len) +{ + VCardResponse *new_response; + + new_response = (VCardResponse *)malloc(sizeof(VCardResponse)); + if (!new_response) { + return NULL; + } + new_response->b_data = malloc(len+2); + if (!new_response->b_data) { + free(new_response); + return NULL; + } + memcpy(new_response->b_data, buf, len); + new_response->b_total_len = len+2; + new_response->b_len = len; + new_response->b_type = VCARD_MALLOC; + return new_response; +} + +static VCardResponse * +vcard_init_buffer_response(VCard *card, unsigned char *buf, int len) +{ + VCardResponse *response; + VCardBufferResponse *buffer_response; + + buffer_response =vcard_get_buffer_response(card); + if (buffer_response) { + vcard_set_buffer_response(card, NULL); + vcard_buffer_response_delete(buffer_response); + } + buffer_response = vcard_buffer_response_new(buf, len); + if (buffer_response == NULL) { + return NULL; + } + response = vcard_response_new_status_bytes(VCARD7816_SW1_RESPONSE_BYTES, + len > 255 ? 0 : len); + if (response == NULL) { + return NULL; + } + vcard_set_buffer_response(card,buffer_response); + return response; +} + +/* + * general buffer to hold results from APDU calls + */ +VCardResponse * +vcard_response_new(VCard *card, unsigned char *buf, + int len, int Le, vcard_7816_status_t status) +{ + VCardResponse *new_response; + + if (len > Le) { + return vcard_init_buffer_response(card, buf, len); + } + new_response = vcard_response_new_data(buf,len); + if (new_response == NULL) { + return NULL; + } + vcard_response_set_status(new_response,status); + return new_response; +} + +/* + * general buffer to hold results from APDU calls + */ +VCardResponse * +vcard_response_new_bytes(VCard *card, unsigned char *buf, int len, int Le, + unsigned char sw1, unsigned char sw2) +{ + VCardResponse *new_response; + + if (len > Le) { + return vcard_init_buffer_response(card, buf, len); + } + new_response = vcard_response_new_data(buf,len); + if (new_response == NULL) { + return NULL; + } + vcard_response_set_status_bytes(new_response,sw1,sw2); + return new_response; +} + +/* + * get a new Reponse buffer that only has a status. + */ +static VCardResponse * +vcard_response_new_status(vcard_7816_status_t status) +{ + VCardResponse *new_response; + + new_response = (VCardResponse *)malloc(sizeof(VCardResponse)); + if (!new_response) { + return NULL; + } + new_response->b_data = &new_response->b_sw1; + new_response->b_len = 0; + new_response->b_total_len = 2; + new_response->b_type = VCARD_MALLOC_STRUCT; + vcard_response_set_status(new_response,status); + return new_response; +} + +/* + * same as above, but specify the status as separate bytes + */ +VCardResponse * +vcard_response_new_status_bytes(unsigned char sw1, unsigned char sw2) +{ + VCardResponse *new_response; + + new_response = (VCardResponse *)malloc(sizeof(VCardResponse)); + if (!new_response) { + return NULL; + } + new_response->b_data = &new_response->b_sw1; + new_response->b_len = 0; + new_response->b_total_len = 2; + new_response->b_type = VCARD_MALLOC_STRUCT; + vcard_response_set_status_bytes(new_response, sw1, sw2); + return new_response; +} + + +/* + * free the response buffer. The Buffer has a type to handle the buffer + * allocated in other ways than through malloc. + */ +void +vcard_response_delete(VCardResponse *response) +{ + if (response == NULL) { + return; + } + switch (response->b_type) { + case VCARD_MALLOC: + /* everything was malloc'ed */ + if (response->b_data) { + free(response->b_data); + } + free(response); + break; + case VCARD_MALLOC_DATA: + /* only the data buffer was malloc'ed */ + if (response->b_data) { + free(response->b_data); + } + break; + case VCARD_MALLOC_STRUCT: + /* only the structure was malloc'ed */ + free(response); + break; + case VCARD_STATIC: + break; + } +} + +/* + * decode the class bit and set our generic type field, channel, and + * secure messaging values. + */ +static vcard_7816_status_t +vcard_apdu_set_class(VCardAPDU *apdu) { + apdu->a_channel = 0; + apdu->a_secure_messaging = 0; + apdu->a_type = apdu->a_cla & 0xf0; + apdu->a_gen_type = VCARD_7816_ISO; + + /* parse the class tables 8 & 9 of the 7816-4 Part 4 spec */ + switch (apdu->a_type) { + /* we only support the basic types */ + case 0x00: + case 0x80: + case 0x90: + case 0xa0: + apdu->a_channel = apdu->a_cla & 3; + apdu->a_secure_messaging = apdu->a_cla & 0xe; + break; + case 0xb0: + case 0xc0: + break; + + case 0x10: + case 0x20: + case 0x30: + case 0x40: + case 0x50: + case 0x60: + case 0x70: + /* Reserved for future use */ + apdu->a_gen_type = VCARD_7816_RFU; + break; + case 0xd0: + case 0xe0: + case 0xf0: + default: + apdu->a_gen_type = + (apdu->a_cla == 0xff)? VCARD_7816_PTS : VCARD_7816_PROPIETARY; + break; + } + return VCARD7816_STATUS_SUCCESS; +} + +/* + * set the Le and Lc fiels according to table 5 of the + * 7816-4 part 4 spec + */ +static vcard_7816_status_t +vcard_apdu_set_length(VCardAPDU *apdu) +{ + int L, Le; + + /* process according to table 5 of the 7816-4 Part 4 spec. + * variable names match the variables in the spec */ + L = apdu->a_len-4; /* fixed APDU header */ + apdu->a_Lc = 0; + apdu->a_Le = 0; + apdu->a_body = NULL; + switch (L) { + case 0: + /* 1 minimal apdu */ + return VCARD7816_STATUS_SUCCESS; + case 1: + /* 2S only return values apdu */ + /* zero maps to 256 here */ + apdu->a_Le = apdu->a_header->ah_Le ? + apdu->a_header->ah_Le : 256; + return VCARD7816_STATUS_SUCCESS; + default: + /* if the ah_Le byte is zero and we have more than + * 1 byte in the header, then we must be using extended Le and Lc. + * process the extended now. */ + if (apdu->a_header->ah_Le == 0) { + if (L < 3) { + /* coding error, need at least 3 bytes */ + return VCARD7816_STATUS_ERROR_WRONG_LENGTH; + } + /* calculate the first extended value. Could be either Le or Lc */ + Le = (apdu->a_header->ah_body[0] << 8) + || apdu->a_header->ah_body[1]; + if (L == 3) { + /* 2E extended, return data only */ + /* zero maps to 65536 */ + apdu->a_Le = Le ? Le : 65536; + return VCARD7816_STATUS_SUCCESS; + } + if (Le == 0) { + /* reserved for future use, probably for next time we need + * to extend the lengths */ + return VCARD7816_STATUS_ERROR_WRONG_LENGTH; + } + /* we know that the first extended value is Lc now */ + apdu->a_Lc = Le; + apdu->a_body = &apdu->a_header->ah_body[2]; + if (L == Le+3) { + /* 3E extended, only body parameters */ + return VCARD7816_STATUS_SUCCESS; + } + if (L == Le+5) { + /* 4E extended, parameters and return data */ + Le = (apdu->a_data[apdu->a_len-2] << 8) + || apdu->a_data[apdu->a_len-1]; + apdu->a_Le = Le ? Le : 65536; + return VCARD7816_STATUS_SUCCESS; + } + return VCARD7816_STATUS_ERROR_WRONG_LENGTH; + } + /* not extended */ + apdu->a_Lc= apdu->a_header->ah_Le; + apdu->a_body = &apdu->a_header->ah_body[0]; + if (L == apdu->a_Lc + 1) { + /* 3S only body parameters */ + return VCARD7816_STATUS_SUCCESS; + } + if (L == apdu->a_Lc + 2) { + /* 4S parameters and return data */ + Le = apdu->a_data[apdu->a_len-1]; + apdu->a_Le = Le ? Le : 256; + return VCARD7816_STATUS_SUCCESS; + } + break; + } + return VCARD7816_STATUS_ERROR_WRONG_LENGTH; +} + +/* + * create a new APDU from a raw set of bytes. This will decode all the + * above fields. users of VCARDAPDU's can then depend on the already decoded + * values. + */ +VCardAPDU * +vcard_apdu_new(unsigned char *raw_apdu, int len, vcard_7816_status_t *status) +{ + VCardAPDU *new_apdu; + + *status = VCARD7816_STATUS_EXC_ERROR_MEMORY_FAILURE; + if (len < 4) { + *status = VCARD7816_STATUS_ERROR_WRONG_LENGTH; + return NULL; + } + + new_apdu = (VCardAPDU *)malloc(sizeof(VCardAPDU)); + if (!new_apdu) { + return NULL; + } + new_apdu->a_data = malloc(len); + if (!new_apdu->a_data) { + free(new_apdu); + return NULL; + } + memcpy(new_apdu->a_data, raw_apdu, len); + new_apdu->a_len = len; + *status = vcard_apdu_set_class(new_apdu); + if (*status != VCARD7816_STATUS_SUCCESS) { + free(new_apdu); + return NULL; + } + *status = vcard_apdu_set_length(new_apdu); + if (*status != VCARD7816_STATUS_SUCCESS) { + free(new_apdu); + new_apdu = NULL; + } + return new_apdu; +} + +void +vcard_apdu_delete(VCardAPDU *apdu) +{ + if (apdu == NULL) { + return; + } + if (apdu->a_data) { + free(apdu->a_data); + } + free(apdu); +} + + +/* + * declare response buffers for all the 7816 defined error codes + */ +VCARD_RESPONSE_NEW_STATIC_STATUS(VCARD7816_STATUS_SUCCESS) +VCARD_RESPONSE_NEW_STATIC_STATUS(VCARD7816_STATUS_WARNING) +VCARD_RESPONSE_NEW_STATIC_STATUS(VCARD7816_STATUS_WARNING_RET_CORUPT) +VCARD_RESPONSE_NEW_STATIC_STATUS(VCARD7816_STATUS_WARNING_BUF_END_BEFORE_LE) +VCARD_RESPONSE_NEW_STATIC_STATUS(VCARD7816_STATUS_WARNING_INVALID_FILE_SELECTED) +VCARD_RESPONSE_NEW_STATIC_STATUS(VCARD7816_STATUS_WARNING_FCI_FORMAT_INVALID) +VCARD_RESPONSE_NEW_STATIC_STATUS(VCARD7816_STATUS_WARNING_CHANGE) +VCARD_RESPONSE_NEW_STATIC_STATUS(VCARD7816_STATUS_WARNING_FILE_FILLED) +VCARD_RESPONSE_NEW_STATIC_STATUS(VCARD7816_STATUS_EXC_ERROR) +VCARD_RESPONSE_NEW_STATIC_STATUS(VCARD7816_STATUS_EXC_ERROR_CHANGE) +VCARD_RESPONSE_NEW_STATIC_STATUS(VCARD7816_STATUS_EXC_ERROR_MEMORY_FAILURE) +VCARD_RESPONSE_NEW_STATIC_STATUS(VCARD7816_STATUS_ERROR_WRONG_LENGTH) +VCARD_RESPONSE_NEW_STATIC_STATUS(VCARD7816_STATUS_ERROR_CLA_NOT_SUPPORTED) +VCARD_RESPONSE_NEW_STATIC_STATUS(VCARD7816_STATUS_ERROR_CHANNEL_NOT_SUPPORTED) +VCARD_RESPONSE_NEW_STATIC_STATUS(VCARD7816_STATUS_ERROR_SECURE_NOT_SUPPORTED) +VCARD_RESPONSE_NEW_STATIC_STATUS(VCARD7816_STATUS_ERROR_COMMAND_NOT_SUPPORTED) +VCARD_RESPONSE_NEW_STATIC_STATUS( + VCARD7816_STATUS_ERROR_COMMAND_INCOMPATIBLE_WITH_FILE) +VCARD_RESPONSE_NEW_STATIC_STATUS(VCARD7816_STATUS_ERROR_SECURITY_NOT_SATISFIED) +VCARD_RESPONSE_NEW_STATIC_STATUS(VCARD7816_STATUS_ERROR_AUTHENTICATION_BLOCKED) +VCARD_RESPONSE_NEW_STATIC_STATUS(VCARD7816_STATUS_ERROR_DATA_INVALID) +VCARD_RESPONSE_NEW_STATIC_STATUS(VCARD7816_STATUS_ERROR_CONDITION_NOT_SATISFIED) +VCARD_RESPONSE_NEW_STATIC_STATUS(VCARD7816_STATUS_ERROR_DATA_NO_EF) +VCARD_RESPONSE_NEW_STATIC_STATUS(VCARD7816_STATUS_ERROR_SM_OBJECT_MISSING) +VCARD_RESPONSE_NEW_STATIC_STATUS(VCARD7816_STATUS_ERROR_SM_OBJECT_INCORRECT) +VCARD_RESPONSE_NEW_STATIC_STATUS(VCARD7816_STATUS_ERROR_WRONG_PARAMETERS) +VCARD_RESPONSE_NEW_STATIC_STATUS( + VCARD7816_STATUS_ERROR_WRONG_PARAMETERS_IN_DATA) +VCARD_RESPONSE_NEW_STATIC_STATUS(VCARD7816_STATUS_ERROR_FUNCTION_NOT_SUPPORTED) +VCARD_RESPONSE_NEW_STATIC_STATUS(VCARD7816_STATUS_ERROR_FILE_NOT_FOUND) +VCARD_RESPONSE_NEW_STATIC_STATUS(VCARD7816_STATUS_ERROR_RECORD_NOT_FOUND) +VCARD_RESPONSE_NEW_STATIC_STATUS(VCARD7816_STATUS_ERROR_NO_SPACE_FOR_FILE) +VCARD_RESPONSE_NEW_STATIC_STATUS(VCARD7816_STATUS_ERROR_LC_TLV_INCONSISTENT) +VCARD_RESPONSE_NEW_STATIC_STATUS(VCARD7816_STATUS_ERROR_P1_P2_INCORRECT) +VCARD_RESPONSE_NEW_STATIC_STATUS(VCARD7816_STATUS_ERROR_LC_P1_P2_INCONSISTENT) +VCARD_RESPONSE_NEW_STATIC_STATUS(VCARD7816_STATUS_ERROR_DATA_NOT_FOUND) +VCARD_RESPONSE_NEW_STATIC_STATUS(VCARD7816_STATUS_ERROR_WRONG_PARAMETERS_2) +VCARD_RESPONSE_NEW_STATIC_STATUS(VCARD7816_STATUS_ERROR_INS_CODE_INVALID) +VCARD_RESPONSE_NEW_STATIC_STATUS(VCARD7816_STATUS_ERROR_CLA_INVALID) +VCARD_RESPONSE_NEW_STATIC_STATUS(VCARD7816_STATUS_ERROR_GENERAL) + +/* + * return a single response code. This function cannot fail. It will always + * return a response. + */ +VCardResponse * +vcard_make_response(vcard_7816_status_t status) +{ + VCardResponse *response = NULL; + + switch (status) { + /* known 7816 response codes */ + case VCARD7816_STATUS_SUCCESS: + return VCARD_RESPONSE_GET_STATIC( + VCARD7816_STATUS_SUCCESS); + case VCARD7816_STATUS_WARNING: + return VCARD_RESPONSE_GET_STATIC( + VCARD7816_STATUS_WARNING); + case VCARD7816_STATUS_WARNING_RET_CORUPT: + return VCARD_RESPONSE_GET_STATIC( + VCARD7816_STATUS_WARNING_RET_CORUPT); + case VCARD7816_STATUS_WARNING_BUF_END_BEFORE_LE: + return VCARD_RESPONSE_GET_STATIC( + VCARD7816_STATUS_WARNING_BUF_END_BEFORE_LE); + case VCARD7816_STATUS_WARNING_INVALID_FILE_SELECTED: + return VCARD_RESPONSE_GET_STATIC( + VCARD7816_STATUS_WARNING_INVALID_FILE_SELECTED); + case VCARD7816_STATUS_WARNING_FCI_FORMAT_INVALID: + return VCARD_RESPONSE_GET_STATIC( + VCARD7816_STATUS_WARNING_FCI_FORMAT_INVALID); + case VCARD7816_STATUS_WARNING_CHANGE: + return VCARD_RESPONSE_GET_STATIC( + VCARD7816_STATUS_WARNING_CHANGE); + case VCARD7816_STATUS_WARNING_FILE_FILLED: + return VCARD_RESPONSE_GET_STATIC( + VCARD7816_STATUS_WARNING_FILE_FILLED); + case VCARD7816_STATUS_EXC_ERROR: + return VCARD_RESPONSE_GET_STATIC( + VCARD7816_STATUS_EXC_ERROR); + case VCARD7816_STATUS_EXC_ERROR_CHANGE: + return VCARD_RESPONSE_GET_STATIC( + VCARD7816_STATUS_EXC_ERROR_CHANGE); + case VCARD7816_STATUS_EXC_ERROR_MEMORY_FAILURE: + return VCARD_RESPONSE_GET_STATIC( + VCARD7816_STATUS_EXC_ERROR_MEMORY_FAILURE); + case VCARD7816_STATUS_ERROR_WRONG_LENGTH: + return VCARD_RESPONSE_GET_STATIC( + VCARD7816_STATUS_ERROR_WRONG_LENGTH); + case VCARD7816_STATUS_ERROR_CLA_NOT_SUPPORTED: + return VCARD_RESPONSE_GET_STATIC( + VCARD7816_STATUS_ERROR_CLA_NOT_SUPPORTED); + case VCARD7816_STATUS_ERROR_CHANNEL_NOT_SUPPORTED: + return VCARD_RESPONSE_GET_STATIC( + VCARD7816_STATUS_ERROR_CHANNEL_NOT_SUPPORTED); + case VCARD7816_STATUS_ERROR_SECURE_NOT_SUPPORTED: + return VCARD_RESPONSE_GET_STATIC( + VCARD7816_STATUS_ERROR_SECURE_NOT_SUPPORTED); + case VCARD7816_STATUS_ERROR_COMMAND_NOT_SUPPORTED: + return VCARD_RESPONSE_GET_STATIC( + VCARD7816_STATUS_ERROR_COMMAND_NOT_SUPPORTED); + case VCARD7816_STATUS_ERROR_COMMAND_INCOMPATIBLE_WITH_FILE: + return VCARD_RESPONSE_GET_STATIC( + VCARD7816_STATUS_ERROR_COMMAND_INCOMPATIBLE_WITH_FILE); + case VCARD7816_STATUS_ERROR_SECURITY_NOT_SATISFIED: + return VCARD_RESPONSE_GET_STATIC( + VCARD7816_STATUS_ERROR_SECURITY_NOT_SATISFIED); + case VCARD7816_STATUS_ERROR_AUTHENTICATION_BLOCKED: + return VCARD_RESPONSE_GET_STATIC( + VCARD7816_STATUS_ERROR_AUTHENTICATION_BLOCKED); + case VCARD7816_STATUS_ERROR_DATA_INVALID: + return VCARD_RESPONSE_GET_STATIC( + VCARD7816_STATUS_ERROR_DATA_INVALID); + case VCARD7816_STATUS_ERROR_CONDITION_NOT_SATISFIED: + return VCARD_RESPONSE_GET_STATIC( + VCARD7816_STATUS_ERROR_CONDITION_NOT_SATISFIED); + case VCARD7816_STATUS_ERROR_DATA_NO_EF: + return VCARD_RESPONSE_GET_STATIC( + VCARD7816_STATUS_ERROR_DATA_NO_EF); + case VCARD7816_STATUS_ERROR_SM_OBJECT_MISSING: + return VCARD_RESPONSE_GET_STATIC( + VCARD7816_STATUS_ERROR_SM_OBJECT_MISSING); + case VCARD7816_STATUS_ERROR_SM_OBJECT_INCORRECT: + return VCARD_RESPONSE_GET_STATIC( + VCARD7816_STATUS_ERROR_SM_OBJECT_INCORRECT); + case VCARD7816_STATUS_ERROR_WRONG_PARAMETERS: + return VCARD_RESPONSE_GET_STATIC( + VCARD7816_STATUS_ERROR_WRONG_PARAMETERS); + case VCARD7816_STATUS_ERROR_WRONG_PARAMETERS_IN_DATA: + return VCARD_RESPONSE_GET_STATIC( + VCARD7816_STATUS_ERROR_WRONG_PARAMETERS_IN_DATA); + case VCARD7816_STATUS_ERROR_FUNCTION_NOT_SUPPORTED: + return VCARD_RESPONSE_GET_STATIC( + VCARD7816_STATUS_ERROR_FUNCTION_NOT_SUPPORTED); + case VCARD7816_STATUS_ERROR_FILE_NOT_FOUND: + return VCARD_RESPONSE_GET_STATIC( + VCARD7816_STATUS_ERROR_FILE_NOT_FOUND); + case VCARD7816_STATUS_ERROR_RECORD_NOT_FOUND: + return VCARD_RESPONSE_GET_STATIC( + VCARD7816_STATUS_ERROR_RECORD_NOT_FOUND); + case VCARD7816_STATUS_ERROR_NO_SPACE_FOR_FILE: + return VCARD_RESPONSE_GET_STATIC( + VCARD7816_STATUS_ERROR_NO_SPACE_FOR_FILE); + case VCARD7816_STATUS_ERROR_LC_TLV_INCONSISTENT: + return VCARD_RESPONSE_GET_STATIC( + VCARD7816_STATUS_ERROR_LC_TLV_INCONSISTENT); + case VCARD7816_STATUS_ERROR_P1_P2_INCORRECT: + return VCARD_RESPONSE_GET_STATIC( + VCARD7816_STATUS_ERROR_P1_P2_INCORRECT); + case VCARD7816_STATUS_ERROR_LC_P1_P2_INCONSISTENT: + return VCARD_RESPONSE_GET_STATIC( + VCARD7816_STATUS_ERROR_LC_P1_P2_INCONSISTENT); + case VCARD7816_STATUS_ERROR_DATA_NOT_FOUND: + return VCARD_RESPONSE_GET_STATIC( + VCARD7816_STATUS_ERROR_DATA_NOT_FOUND); + case VCARD7816_STATUS_ERROR_WRONG_PARAMETERS_2: + return VCARD_RESPONSE_GET_STATIC( + VCARD7816_STATUS_ERROR_WRONG_PARAMETERS_2); + case VCARD7816_STATUS_ERROR_INS_CODE_INVALID: + return VCARD_RESPONSE_GET_STATIC( + VCARD7816_STATUS_ERROR_INS_CODE_INVALID); + case VCARD7816_STATUS_ERROR_CLA_INVALID: + return VCARD_RESPONSE_GET_STATIC( + VCARD7816_STATUS_ERROR_CLA_INVALID); + case VCARD7816_STATUS_ERROR_GENERAL: + return VCARD_RESPONSE_GET_STATIC( + VCARD7816_STATUS_ERROR_GENERAL); + default: + /* we don't know this status code, create a response buffer to + * hold it */ + response = vcard_response_new_status(status); + if (response == NULL) { + /* couldn't allocate the buffer, return memmory error */ + return VCARD_RESPONSE_GET_STATIC( + VCARD7816_STATUS_EXC_ERROR_MEMORY_FAILURE); + } + } + ASSERT(response); + return response; +} + +/* + * Add File card support here if you need it. + */ +static VCardStatus +vcard7816_file_system_process_apdu(VCard *card, VCardAPDU *apdu, + VCardResponse **response) +{ + /* TODO: if we want to support a virtual file system card, we do it here. + * It would probably be a pkcs #15 card type */ + *response = vcard_make_response( + VCARD7816_STATUS_ERROR_COMMAND_NOT_SUPPORTED); + return VCARD_DONE; +} + +/* + * VM card (including java cards) + */ +static VCardStatus +vcard7816_vm_process_apdu(VCard *card, VCardAPDU *apdu, + VCardResponse **response) +{ + int bytes_to_copy, next_byte_count, count; + VCardApplet *current_applet; + VCardBufferResponse *buffer_response; + vcard_7816_status_t status; + + /* parse the class first */ + if (apdu->a_gen_type != VCARD_7816_ISO) { + *response = vcard_make_response( + VCARD7816_STATUS_ERROR_COMMAND_NOT_SUPPORTED); + return VCARD_DONE; + } + + /* use a switch so that if we need to support secure channel stuff later, + * we know where to put it */ + switch (apdu->a_secure_messaging) { + case 0x0: /* no SM */ + break; + case 0x4: /* proprietary SM */ + case 0x8: /* header not authenticated */ + case 0xc: /* header authenticated */ + default: + /* for now, don't try to support secure channel stuff in the + * virtual card. */ + *response = vcard_make_response( + VCARD7816_STATUS_ERROR_SECURE_NOT_SUPPORTED); + return VCARD_DONE; + } + + /* now parse the instruction */ + switch (apdu->a_ins) { + case VCARD7816_INS_MANAGE_CHANNEL: /* secure channel op */ + case VCARD7816_INS_EXTERNAL_AUTHENTICATE: /* secure channel op */ + case VCARD7816_INS_GET_CHALLENGE: /* secure channel op */ + case VCARD7816_INS_INTERNAL_AUTHENTICATE: /* secure channel op */ + case VCARD7816_INS_ERASE_BINARY: /* applet control op */ + case VCARD7816_INS_READ_BINARY: /* applet control op */ + case VCARD7816_INS_WRITE_BINARY: /* applet control op */ + case VCARD7816_INS_UPDATE_BINARY: /* applet control op */ + case VCARD7816_INS_READ_RECORD: /* file op */ + case VCARD7816_INS_WRITE_RECORD: /* file op */ + case VCARD7816_INS_UPDATE_RECORD: /* file op */ + case VCARD7816_INS_APPEND_RECORD: /* file op */ + case VCARD7816_INS_ENVELOPE: + case VCARD7816_INS_PUT_DATA: + *response = vcard_make_response( + VCARD7816_STATUS_ERROR_COMMAND_NOT_SUPPORTED); + break; + + case VCARD7816_INS_SELECT_FILE: + if (apdu->a_p1 != 0x04) { + *response = vcard_make_response( + VCARD7816_STATUS_ERROR_FUNCTION_NOT_SUPPORTED); + break; + } + + /* side effect, deselect the current applet if no applet has been found + * */ + current_applet = vcard_find_applet(card, apdu->a_body, apdu->a_Lc); + vcard_select_applet(card, apdu->a_channel, current_applet); + if (current_applet) { + unsigned char *aid; + int aid_len; + aid = vcard_applet_get_aid(current_applet, &aid_len); + *response = vcard_response_new(card, aid, aid_len, apdu->a_Le, + VCARD7816_STATUS_SUCCESS); + } else { + *response = vcard_make_response( + VCARD7816_STATUS_ERROR_FILE_NOT_FOUND); + } + break; + + case VCARD7816_INS_VERIFY: + if ((apdu->a_p1 != 0x00) || (apdu->a_p2 != 0x00)) { + *response = vcard_make_response( + VCARD7816_STATUS_ERROR_WRONG_PARAMETERS); + } else { + if (apdu->a_Lc == 0) { + /* handle pin count if possible */ + count = vcard_emul_get_login_count(card); + if (count < 0) { + *response = vcard_make_response( + VCARD7816_STATUS_ERROR_DATA_NOT_FOUND); + } else { + if (count > 0xf) { + count = 0xf; + } + *response = vcard_response_new_status_bytes( + VCARD7816_SW1_WARNING_CHANGE, + 0xc0 | count); + if (*response == NULL) { + *response = vcard_make_response( + VCARD7816_STATUS_EXC_ERROR_MEMORY_FAILURE); + } + } + } else { + status = vcard_emul_login(card, apdu->a_body, apdu->a_Lc); + *response = vcard_make_response(status); + } + } + break; + + case VCARD7816_INS_GET_RESPONSE: + buffer_response = vcard_get_buffer_response(card); + if (!buffer_response) { + *response = vcard_make_response( + VCARD7816_STATUS_ERROR_DATA_NOT_FOUND); + /* handle error */ + } + bytes_to_copy = MIN(buffer_response->len, apdu->a_Le); + next_byte_count = MIN(256, buffer_response->len - bytes_to_copy); + *response = vcard_response_new_bytes( + card, buffer_response->current, bytes_to_copy, + apdu->a_Le, + next_byte_count ? + VCARD7816_SW1_RESPONSE_BYTES: VCARD7816_SW1_SUCCESS, + next_byte_count); + buffer_response->current += bytes_to_copy; + buffer_response->len -= bytes_to_copy; + if (*response == NULL || (next_byte_count == 0)) { + vcard_set_buffer_response(card,NULL); + vcard_buffer_response_delete(buffer_response); + } + if (*response == NULL) { + *response = + vcard_make_response(VCARD7816_STATUS_EXC_ERROR_MEMORY_FAILURE); + } + break; + + case VCARD7816_INS_GET_DATA: + *response = + vcard_make_response(VCARD7816_STATUS_ERROR_COMMAND_NOT_SUPPORTED); + break; + + default: + *response = + vcard_make_response(VCARD7816_STATUS_ERROR_COMMAND_NOT_SUPPORTED); + break; + } + + /* response should have been set somewhere */ + ASSERT(*response != NULL); + return VCARD_DONE; +} + + +/* + * APDU processing starts here. This routes the card processing stuff to the + * right location. + */ +VCardStatus +vcard_process_apdu(VCard *card, VCardAPDU *apdu, VCardResponse **response) +{ + VCardStatus status; + VCardBufferResponse *buffer_response; + + /* first handle any PTS commands, which aren't really APDU's */ + if (apdu->a_type == VCARD_7816_PTS) { + /* the PTS responses aren't really responses either */ + *response = vcard_response_new_data(apdu->a_data, apdu->a_len); + /* PTS responses have no status bytes */ + (*response)->b_total_len = (*response)->b_len; + return VCARD_DONE; + } + buffer_response = vcard_get_buffer_response(card); + if (buffer_response && apdu->a_ins != VCARD7816_INS_GET_RESPONSE) { + /* clear out buffer_response, return an error */ + vcard_set_buffer_response(card,NULL); + vcard_buffer_response_delete(buffer_response); + *response = vcard_make_response(VCARD7816_STATUS_EXC_ERROR); + return VCARD_DONE; + } + + status = vcard_process_applet_apdu(card, apdu, response); + if (status != VCARD_NEXT) { + return status; + } + switch (vcard_get_type(card)) { + case VCARD_FILE_SYSTEM: + return vcard7816_file_system_process_apdu(card,apdu,response); + case VCARD_VM: + return vcard7816_vm_process_apdu(card,apdu,response); + case VCARD_DIRECT: + /* if we are type direct, then the applet should handle everything */ + assert("VCARD_DIRECT: applet failure"); + break; + } + *response = + vcard_make_response(VCARD7816_STATUS_ERROR_COMMAND_NOT_SUPPORTED); + return VCARD_DONE; +} diff --git a/libcacard/card_7816.h b/libcacard/card_7816.h new file mode 100644 index 0000000..4351b1c --- /dev/null +++ b/libcacard/card_7816.h @@ -0,0 +1,60 @@ +/* + * Implement the 7816 portion of the card spec + * + */ +#ifndef CARD_7816_H +#define CARD_7816_H 1 + +#include "card_7816t.h" +#include "vcardt.h" + +/* + * constructors for VCardResponse's + */ +/* response from a return buffer and a status */ +VCardResponse *vcard_response_new(VCard *card, unsigned char *buf, int len, + int Le, vcard_7816_status_t status); +/* response from a return buffer and status bytes */ +VCardResponse *vcard_response_new_bytes(VCard *card, unsigned char *buf, + int len, int Le, + unsigned char sw1, unsigned char sw2); +/* response from just status bytes */ +VCardResponse *vcard_response_new_status_bytes(unsigned char sw1, + unsigned char sw2); +/* response from just status: NOTE this cannot fail, it will alwyas return a + * valid response, if it can't allocate memory, the response will be + * VCARD7816_STATUS_EXC_ERROR_MEMORY_FAILURE */ +VCardResponse *vcard_make_response(vcard_7816_status_t status); + +/* create a raw response (status has already been encoded */ +VCardResponse *vcard_response_new_data(unsigned char *buf, int len); + + + + +/* + * destructor for VCardResponse. + * Can be called with a NULL response + */ +void vcard_response_delete(VCardResponse *response); + +/* + * constructor for VCardAPDU + */ +VCardAPDU *vcard_apdu_new(unsigned char *raw_apdu, int len, + unsigned short *status); + +/* + * destructor for VCardAPDU + * Can be called with a NULL apdu + */ +void vcard_apdu_delete(VCardAPDU *apdu); + +/* + * APDU processing starts here. This routes the card processing stuff to the + * right location. Always returns a valid response. + */ +VCardStatus vcard_process_apdu(VCard *card, VCardAPDU *apdu, + VCardResponse **response); + +#endif diff --git a/libcacard/card_7816t.h b/libcacard/card_7816t.h new file mode 100644 index 0000000..531455c --- /dev/null +++ b/libcacard/card_7816t.h @@ -0,0 +1,163 @@ +/* + * Implement the 7816 portion of the card spec + * + */ +#ifndef CARD_7816T_H +#define CARD_7816T_H 1 + +typedef unsigned short vcard_7816_status_t; + +struct VCardResponseStruct { + unsigned char *b_data; + vcard_7816_status_t b_status; + unsigned char b_sw1; + unsigned char b_sw2; + int b_len; + int b_total_len; + enum VCardResponseBufferType { + VCARD_MALLOC, + VCARD_MALLOC_DATA, + VCARD_MALLOC_STRUCT, + VCARD_STATIC + } b_type; +}; + +#define VCARD_RESPONSE_NEW_STATIC_STATUS(stat) \ +static const VCardResponse VCardResponse##stat = \ + {(unsigned char *)&VCardResponse##stat.b_sw1, (stat), ((stat) >> 8), \ + ((stat) & 0xff), 0, 2, VCARD_STATIC}; + +#define VCARD_RESPONSE_NEW_STATIC_STATUS_BYTES(sw1, sw2) \ +static const VCardResponse VCARDResponse##sw1 = \ + {(unsigned char *)&VCardResponse##name.b_sw1, ((sw1) << 8 | (sw2)), \ + (sw1), (sw2), 0, 2, VCARD_STATIC}; + +/* cast away the const, callers need may need to 'free' the + * result, and const implies that they don't */ +#define VCARD_RESPONSE_GET_STATIC(name) \ + ((VCardResponse *)(&VCardResponse##name)) + +typedef enum { + VCARD_7816_ISO, + VCARD_7816_RFU, + VCARD_7816_PTS, + VCARD_7816_PROPIETARY +} VCardAPDUType; + + +/* + * 7816 header. All APDU's have this header. + * They must be laid out in this order. + */ +struct VCardAPDUHeader { + unsigned char ah_cla; + unsigned char ah_ins; + unsigned char ah_p1; + unsigned char ah_p2; + unsigned char ah_Le; + unsigned char ah_body[1]; /* indefinate length */ +}; + +/* + * 7816 APDU structure. The raw bytes are stored in the union and can be + * accessed directly through u.data (which is aliased as a_data). + * + * Names of the fields match the 7816 documentation. + */ +struct VCardAPDUStruct { + int a_len; /* length of the whole buffer, including header */ + int a_Lc; /* 7816 Lc (parameter length) value */ + int a_Le; /* 7816 Le (expected result length) value */ + unsigned char *a_body; /* pointer to the parameter */ + int a_channel; /* decoded channel */ + int a_secure_messaging; /* decoded secure messaging type */ + int a_type; /* decoded type from cla (top nibble of class) */ + VCardAPDUType a_gen_type; /* generic type (7816, PROPRIETARY, RFU, etc) */ + union { + struct VCardAPDUHeader *header; + unsigned char *data; + } u; +/* give the subfields a unified look */ +#define a_header u.header +#define a_data u.data +#define a_cla a_header->ah_cla /* class */ +#define a_ins a_header->ah_ins /* instruction */ +#define a_p1 a_header->ah_p1 /* parameter 1 */ +#define a_p2 a_header->ah_p2 /* parameter 2 */ +}; + +/* 7816 status codes */ +#define VCARD7816_STATUS_SUCCESS 0x9000 +#define VCARD7816_STATUS_WARNING 0x6200 +#define VCARD7816_STATUS_WARNING_RET_CORUPT 0x6281 +#define VCARD7816_STATUS_WARNING_BUF_END_BEFORE_LE 0x6282 +#define VCARD7816_STATUS_WARNING_INVALID_FILE_SELECTED 0x6283 +#define VCARD7816_STATUS_WARNING_FCI_FORMAT_INVALID 0x6284 +#define VCARD7816_STATUS_WARNING_CHANGE 0x6300 +#define VCARD7816_STATUS_WARNING_FILE_FILLED 0x6381 +#define VCARD7816_STATUS_EXC_ERROR 0x6400 +#define VCARD7816_STATUS_EXC_ERROR_CHANGE 0x6500 +#define VCARD7816_STATUS_EXC_ERROR_MEMORY_FAILURE 0x6581 +#define VCARD7816_STATUS_ERROR_WRONG_LENGTH 0x6700 +#define VCARD7816_STATUS_ERROR_CLA_NOT_SUPPORTED 0x6800 +#define VCARD7816_STATUS_ERROR_CHANNEL_NOT_SUPPORTED 0x6881 +#define VCARD7816_STATUS_ERROR_SECURE_NOT_SUPPORTED 0x6882 +#define VCARD7816_STATUS_ERROR_COMMAND_NOT_SUPPORTED 0x6900 +#define VCARD7816_STATUS_ERROR_COMMAND_INCOMPATIBLE_WITH_FILE 0x6981 +#define VCARD7816_STATUS_ERROR_SECURITY_NOT_SATISFIED 0x6982 +#define VCARD7816_STATUS_ERROR_AUTHENTICATION_BLOCKED 0x6983 +#define VCARD7816_STATUS_ERROR_DATA_INVALID 0x6984 +#define VCARD7816_STATUS_ERROR_CONDITION_NOT_SATISFIED 0x6985 +#define VCARD7816_STATUS_ERROR_DATA_NO_EF 0x6986 +#define VCARD7816_STATUS_ERROR_SM_OBJECT_MISSING 0x6987 +#define VCARD7816_STATUS_ERROR_SM_OBJECT_INCORRECT 0x6988 +#define VCARD7816_STATUS_ERROR_WRONG_PARAMETERS 0x6a00 +#define VCARD7816_STATUS_ERROR_WRONG_PARAMETERS_IN_DATA 0x6a80 +#define VCARD7816_STATUS_ERROR_FUNCTION_NOT_SUPPORTED 0x6a81 +#define VCARD7816_STATUS_ERROR_FILE_NOT_FOUND 0x6a82 +#define VCARD7816_STATUS_ERROR_RECORD_NOT_FOUND 0x6a83 +#define VCARD7816_STATUS_ERROR_NO_SPACE_FOR_FILE 0x6a84 +#define VCARD7816_STATUS_ERROR_LC_TLV_INCONSISTENT 0x6a85 +#define VCARD7816_STATUS_ERROR_P1_P2_INCORRECT 0x6a86 +#define VCARD7816_STATUS_ERROR_LC_P1_P2_INCONSISTENT 0x6a87 +#define VCARD7816_STATUS_ERROR_DATA_NOT_FOUND 0x6a88 +#define VCARD7816_STATUS_ERROR_WRONG_PARAMETERS_2 0x6b00 +#define VCARD7816_STATUS_ERROR_INS_CODE_INVALID 0x6d00 +#define VCARD7816_STATUS_ERROR_CLA_INVALID 0x6e00 +#define VCARD7816_STATUS_ERROR_GENERAL 0x6f00 +/* 7816 sw1 codes */ +#define VCARD7816_SW1_SUCCESS 0x90 +#define VCARD7816_SW1_RESPONSE_BYTES 0x61 +#define VCARD7816_SW1_WARNING 0x62 +#define VCARD7816_SW1_WARNING_CHANGE 0x63 +#define VCARD7816_SW1_EXC_ERROR 0x64 +#define VCARD7816_SW1_EXC_ERROR_CHANGE 0x65 +#define VCARD7816_SW1_ERROR_WRONG_LENGTH 0x67 +#define VCARD7816_SW1_CLA_ERROR 0x68 +#define VCARD7816_SW1_COMMAND_ERROR 0x69 +#define VCARD7816_SW1_P1_P2_ERROR 0x6a +#define VCARD7816_SW1_LE_ERROR 0x6c +#define VCARD7816_SW1_INS_ERROR 0x6d +#define VCARD7816_SW1_CLA_NOT_SUPPORTED 0x6e + +/* 7816 Instructions */ +#define VCARD7816_INS_MANAGE_CHANNEL 0x70 +#define VCARD7816_INS_EXTERNAL_AUTHENTICATE 0x82 +#define VCARD7816_INS_GET_CHALLENGE 0x84 +#define VCARD7816_INS_INTERNAL_AUTHENTICATE 0x88 +#define VCARD7816_INS_ERASE_BINARY 0x0e +#define VCARD7816_INS_READ_BINARY 0xb0 +#define VCARD7816_INS_WRITE_BINARY 0xd0 +#define VCARD7816_INS_UPDATE_BINARY 0xd6 +#define VCARD7816_INS_READ_RECORD 0xb2 +#define VCARD7816_INS_WRITE_RECORD 0xd2 +#define VCARD7816_INS_UPDATE_RECORD 0xdc +#define VCARD7816_INS_APPEND_RECORD 0xe2 +#define VCARD7816_INS_ENVELOPE 0xc2 +#define VCARD7816_INS_PUT_DATA 0xda +#define VCARD7816_INS_GET_DATA 0xca +#define VCARD7816_INS_SELECT_FILE 0xa4 +#define VCARD7816_INS_VERIFY 0x20 +#define VCARD7816_INS_GET_RESPONSE 0xc0 + +#endif diff --git a/libcacard/config.h b/libcacard/config.h new file mode 100644 index 0000000..19775eb --- /dev/null +++ b/libcacard/config.h @@ -0,0 +1,81 @@ +/* config.h. Generated from config.h.in by configure. */ +/* config.h.in. Generated from configure.ac by autoheader. */ + +/* Define if building universal (internal helper macro) */ +/* #undef AC_APPLE_UNIVERSAL_BUILD */ + +/* Define to 1 if you have the <dlfcn.h> header file. */ +#define HAVE_DLFCN_H 1 + +/* Define to 1 if you have the <inttypes.h> header file. */ +#define HAVE_INTTYPES_H 1 + +/* Define to 1 if you have the <memory.h> header file. */ +#define HAVE_MEMORY_H 1 + +/* Define to 1 if you have the <stdint.h> header file. */ +#define HAVE_STDINT_H 1 + +/* Define to 1 if you have the <stdlib.h> header file. */ +#define HAVE_STDLIB_H 1 + +/* Define to 1 if you have the <strings.h> header file. */ +#define HAVE_STRINGS_H 1 + +/* Define to 1 if you have the <string.h> header file. */ +#define HAVE_STRING_H 1 + +/* Define to 1 if you have the <sys/stat.h> header file. */ +#define HAVE_SYS_STAT_H 1 + +/* Define to 1 if you have the <sys/types.h> header file. */ +#define HAVE_SYS_TYPES_H 1 + +/* Define to 1 if you have the <unistd.h> header file. */ +#define HAVE_UNISTD_H 1 + +/* Define to the sub-directory in which libtool stores uninstalled libraries. + */ +#define LT_OBJDIR ".libs/" + +/* Define to 1 if your C compiler doesn't accept -c and -o together. */ +/* #undef NO_MINUS_C_MINUS_O */ + +/* Name of package */ +#define PACKAGE "cac_card" + +/* Define to the address where bug reports for this package should be sent. */ +#define PACKAGE_BUGREPORT "" + +/* Define to the full name of this package. */ +#define PACKAGE_NAME "cac_card" + +/* Define to the full name and version of this package. */ +#define PACKAGE_STRING "cac_card 0.0.1" + +/* Define to the one symbol short name of this package. */ +#define PACKAGE_TARNAME "cac_card" + +/* Define to the home page for this package. */ +#define PACKAGE_URL "" + +/* Define to the version of this package. */ +#define PACKAGE_VERSION "0.0.1" + +/* Define to 1 if you have the ANSI C header files. */ +#define STDC_HEADERS 1 + +/* Version number of package */ +#define VERSION "0.0.1" + +/* Define WORDS_BIGENDIAN to 1 if your processor stores words with the most + significant byte first (like Motorola and SPARC, unlike Intel). */ +#if defined AC_APPLE_UNIVERSAL_BUILD +# if defined __BIG_ENDIAN__ +# define WORDS_BIGENDIAN 1 +# endif +#else +# ifndef WORDS_BIGENDIAN +/* # undef WORDS_BIGENDIAN */ +# endif +#endif diff --git a/libcacard/event.c b/libcacard/event.c new file mode 100644 index 0000000..25d99ae --- /dev/null +++ b/libcacard/event.c @@ -0,0 +1,112 @@ +/* + * + */ +#include "vcard.h" +#include "vreader.h" +#include "vevent.h" + +/* + * OS includes + */ +#include <stdlib.h> + +/* + * from spice + */ +#include "mutex.h" + +VEvent * +vevent_new(VEventType type, VReader *reader, VCard *card) +{ + VEvent *new_vevent; + + new_vevent = (VEvent *)malloc(sizeof(VEvent)); + if (new_vevent == NULL) { + return NULL; + } + new_vevent->next = NULL; + new_vevent->type = type; + new_vevent->reader = vreader_reference(reader); + new_vevent->card = vcard_reference(card); + + return new_vevent; +} + +void +vevent_delete(VEvent *vevent) +{ + if (vevent == NULL) { + return; + } + vreader_free(vevent->reader); + vcard_free(vevent->card); + free(vevent); +} + +/* + * VEvent queue management + */ + +static VEvent *vevent_queue_head = NULL; +static VEvent *vevent_queue_tail = NULL; +static mutex_t vevent_queue_lock; +static condition_t vevent_queue_condition; + +void vevent_queue_init(void) +{ + MUTEX_INIT(vevent_queue_lock); + CONDITION_INIT(vevent_queue_condition); + vevent_queue_head = vevent_queue_tail = NULL; +} + +void +vevent_queue_vevent(VEvent *vevent) +{ + vevent->next = NULL; + MUTEX_LOCK(vevent_queue_lock); + if (vevent_queue_head) { + assert(vevent_queue_tail); + vevent_queue_tail->next = vevent; + } else { + vevent_queue_head = vevent; + } + vevent_queue_tail = vevent; + CONDITION_NOTIFY(vevent_queue_condition); + MUTEX_UNLOCK(vevent_queue_lock); +} + +/* must have lock */ +static VEvent * +vevent_dequeue_vevent(void) +{ + VEvent *vevent = NULL; + if (vevent_queue_head) { + vevent = vevent_queue_head; + vevent_queue_head = vevent->next; + vevent->next = NULL; + } + return vevent; +} + +VEvent * vevent_wait_next_vevent(void) +{ + VEvent *vevent; + + MUTEX_LOCK(vevent_queue_lock); + while ((vevent = vevent_dequeue_vevent()) == NULL) { + CONDITION_WAIT(vevent_queue_condition, vevent_queue_lock); + } + MUTEX_UNLOCK(vevent_queue_lock); + return vevent; +} + +VEvent * vevent_get_next_vevent(void) +{ + VEvent *vevent; + + MUTEX_LOCK(vevent_queue_lock); + vevent = vevent_dequeue_vevent(); + MUTEX_UNLOCK(vevent_queue_lock); + return vevent; +} + diff --git a/libcacard/eventt.h b/libcacard/eventt.h new file mode 100644 index 0000000..4c3df4f --- /dev/null +++ b/libcacard/eventt.h @@ -0,0 +1,28 @@ +/* + * + */ + +#ifndef EVENTT_H +#define EVENTT_H 1 +#include "vreadert.h" +#include "vcardt.h" + +typedef struct VEventStruct VEvent; + +typedef enum { + VEVENT_READER_INSERT, + VEVENT_READER_REMOVE, + VEVENT_CARD_INSERT, + VEVENT_CARD_REMOVE, + VEVENT_LAST, +} VEventType; + +struct VEventStruct { + VEvent *next; + VEventType type; + VReader *reader; + VCard *card; +}; +#endif + + diff --git a/libcacard/link_test.c b/libcacard/link_test.c new file mode 100644 index 0000000..40d2c6d --- /dev/null +++ b/libcacard/link_test.c @@ -0,0 +1,20 @@ +/* + * + */ +#include <stdio.h> +#include "vcard.h" + +VCardStatus cac_card_init(const char *flags, VCard *card, + const unsigned char *cert[], + int cert_len[], VCardKey *key[] /* adopt the keys*/, + int cert_count); +/* + * this will crash... just test the linkage right now + */ + +main(int argc, char **argv) +{ + VCard *card; /* no constructor yet */ + cac_card_init("", card, NULL, 0, NULL, 0); +} + diff --git a/libcacard/mutex.h b/libcacard/mutex.h new file mode 100644 index 0000000..db44814 --- /dev/null +++ b/libcacard/mutex.h @@ -0,0 +1,59 @@ +/* -*- Mode: C; c-basic-offset: 4; indent-tabs-mode: nil -*- */ +/* + Copyright (C) 2009 Red Hat, Inc. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library 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 + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, see <http://www.gnu.org/licenses/>. +*/ + +/* + * This header file provides a way of mapping windows and linux thread calls + * to a set of macros. Ideally this would be shared by whatever subsystem we + * link with. + */ + +#ifndef _H_MUTEX +#define _H_MUTEX +#ifdef _WIN32 +#include <windows.h> +typedef CRITICAL_SECTION mutex_t; +#define MUTEX_INIT(mutex) InitializeCriticalSection(&mutex) +#define MUTEX_LOCK(mutex) EnterCriticalSection(&mutex) +#define MUTEX_UNLOCK(mutex) LeaveCriticalSection(&mutex) +typedef CONDITION_VARIABLE condition_t; +#define CONDITION_INIT(cond) InitializeConditionVariable(&cond) +#define CONDITION_WAIT(cond,mutex) \ + SleepConditionVariableCS(&cond,&mutex,INFINTE) +#define CONDITION_NOTIFY(cond) WakeConditionVariable(&cond) +typedef uint32_t thread_t; +typedef HANDLE thread_status_t; +#define THREAD_CREATE(tid, func, arg) \ + CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)func, arg, 0, &tid) +#define THREAD_SUCCESS(status) ((status) != NULL) +#else +#include <pthread.h> +typedef pthread_mutex_t mutex_t; +#define MUTEX_INIT(mutex) pthread_mutex_init(&mutex, NULL) +#define MUTEX_LOCK(mutex) pthread_mutex_lock(&mutex) +#define MUTEX_UNLOCK(mutex) pthread_mutex_unlock(&mutex) +typedef pthread_cond_t condition_t; +#define CONDITION_INIT(cond) pthread_cond_init(&cond, NULL) +#define CONDITION_WAIT(cond,mutex) pthread_cond_wait(&cond,&mutex) +#define CONDITION_NOTIFY(cond) pthread_cond_signal(&cond) +typedef pthread_t thread_t; +typedef int thread_status_t; +#define THREAD_CREATE(tid, func, arg) pthread_create(&tid, NULL, func, arg) +#define THREAD_SUCCESS(status) ((status) == 0) +#endif + +#endif // _H_MUTEX diff --git a/libcacard/passthru.c b/libcacard/passthru.c new file mode 100644 index 0000000..09471c7 --- /dev/null +++ b/libcacard/passthru.c @@ -0,0 +1,612 @@ +/* + * implement the applets for the CAC card. + */ +#ifdef USE_PASSTHRU +#include "vcard.h" +#include "vcard_emul.h" +#include "card_7816.h" +#include "vreader.h" +#include "mutex.h" +#include "vcard_emul.h" +#include "passthru.h" +#include <stdlib.h> +#include <string.h> +#include <pcsclite.h> + +/* + * Passthru applet private data + */ +struct VCardAppletPrivateStruct { + char *reader_name; + /* pcsc-lite parameters */ + SCARDHANDLE hCard; + uint32_t hProtocol; + SCARD_IO_REQUEST *send_io; + unsigned char atr[MAX_ATR_SIZE]; + int atr_len; +}; + +static SCARDCONTEXT global_context = 0; + +#define MAX_RESPONSE_LENGTH 261 /*65537 */ +/* + * handle all the APDU's that are common to all CAC applets + */ +static VCardStatus +passthru_process_apdu(VCard *card, VCardAPDU *apdu, VCardResponse **response) +{ + LONG rv; + unsigned char buf[MAX_RESPONSE_LENGTH]; + uint32_t len = MAX_RESPONSE_LENGTH; + VCardAppletPrivate *applet_private = NULL; + SCARD_IO_REQUEST receive_io; + + applet_private = vcard_get_current_applet_private(card, 0); + if (applet_private == NULL) { + *response = vcard_make_response(VCARD7816_STATUS_EXC_ERROR); + return VCARD_DONE; + } + + rv = SCardTransmit(applet_private->hCard, applet_private->send_io, + apdu->a_data, apdu->a_len, &receive_io, buf, &len); + if (rv != SCARD_S_SUCCESS) { + *response = vcard_make_response(VCARD7816_STATUS_EXC_ERROR); + return VCARD_DONE; + } + + *response = vcard_response_new_data(buf,len); + if (*response == NULL) { + *response = + vcard_make_response(VCARD7816_STATUS_EXC_ERROR_MEMORY_FAILURE); + } else { + (*response)->b_total_len = (*response)->b_len; + } + return VCARD_DONE; +} + +static void +passthru_card_set_atr(VCard *card, unsigned char *atr, int atr_len) +{ + VCardAppletPrivate *applet_private = NULL; + applet_private = vcard_get_current_applet_private(card, 0); + if (applet_private == NULL) { + return; + } + applet_private->atr_len = MIN(atr_len, sizeof(applet_private->atr)); + memcpy(applet_private->atr, atr, applet_private->atr_len); +} + +static void passthru_card_get_atr(VCard *card, unsigned char *atr, int *atr_len) +{ + VCardAppletPrivate *applet_private = NULL; + SCARD_READERSTATE *state; + + applet_private = vcard_get_current_applet_private(card, 0); + if ((applet_private == NULL) || (applet_private->atr_len == 0)) { + vcard_emul_get_atr(card, atr, atr_len); + return; + } + *atr_len = MIN(applet_private->atr_len, *atr_len); + memcpy(atr,applet_private->atr,*atr_len); + return; +} + +/* + * resest the inter call state between applet selects + */ +static VCardStatus +passthru_reset(VCard *card, int channel) +{ + return VCARD_DONE; +} + +static VCardStatus +passthru_pcsc_lite_init() +{ + LONG rv; + if (global_context != 0) { + return VCARD_DONE; + } + rv = SCardEstablishContext(SCARD_SCOPE_SYSTEM, NULL, NULL, &global_context); + if (rv != SCARD_S_SUCCESS) { + return VCARD_FAIL; + } + return VCARD_DONE; +} + +/* + * match if s1 is completely contained in s2 + */ +static int +string_match(const char *s1, const char *s2) +{ + int len = strlen(s1); + const char *start; + + for (start = strchr(s2, *s1); start; start = strchr(start+1, *s1)) { + if (strncmp(start, s1, len) == 0) { + return 1; + } + } + return 0; +} + + +/* + * Look for the reader that best matches the name for VReader + */ +static char * +passthru_get_reader_name(VReader *reader) +{ + const char *reader_name = vreader_get_name(reader); + char *reader_list = NULL; + char *reader_entry = NULL; + char *reader_match = NULL; + uint32_t reader_string_length; + VCardStatus status; + LONG rv; + + if (reader_name == NULL) { + return NULL; + } + + status = passthru_pcsc_lite_init(); + if (status != VCARD_DONE) { + return NULL; + } + + + /* find the existing reader names */ + rv = SCardListReaders(global_context, NULL, NULL, &reader_string_length); + if (rv != SCARD_S_SUCCESS) { + return NULL; + } + reader_list = (char *)malloc(reader_string_length); + rv = SCardListReaders(global_context, NULL, reader_list, + &reader_string_length); + if (rv != SCARD_S_SUCCESS) { + goto cleanup; + } + + /* match that name */ + for (reader_entry= reader_list;*reader_entry; + reader_entry += strlen(reader_entry)+1) { + if (string_match(reader_entry, reader_name)) { + reader_match = strdup(reader_entry); + break; + } + } +cleanup: + if (reader_list) { + free(reader_list); + } + return reader_match; +} + + +/* + * utilities for creating and destroying the private applet data + */ +static void +passthru_delete_applet_private(VCardAppletPrivate *applet_private) +{ + if (applet_private == NULL) { + return; + } + if (applet_private->hCard) { + SCardDisconnect(applet_private->hCard,SCARD_LEAVE_CARD); + } + if (applet_private->reader_name != NULL) { + free(applet_private->reader_name); + } + free(applet_private); +} + +static VCardAppletPrivate * +passthru_new_applet_private(VReader *reader) +{ + VCardAppletPrivate *applet_private = NULL; + LONG rv; + + applet_private = (VCardAppletPrivate *)malloc(sizeof(VCardAppletPrivate)); + + if (applet_private == NULL) { + goto fail; + } + applet_private->hCard = 0; + applet_private->reader_name = NULL; + + applet_private->reader_name = passthru_get_reader_name(reader); + if (applet_private->reader_name == NULL) { + goto fail; + } + + rv = SCardConnect( global_context, applet_private->reader_name, + SCARD_SHARE_DIRECT, SCARD_PROTOCOL_T0|SCARD_PROTOCOL_T1, + &applet_private->hCard, + &applet_private->hProtocol); + if (rv != SCARD_S_SUCCESS) { + goto fail; + } + + if (applet_private->hProtocol == SCARD_PROTOCOL_T0) { + applet_private->send_io = SCARD_PCI_T0; + } else { + applet_private->send_io = SCARD_PCI_T1; + } + applet_private->atr_len = 0; + return applet_private; + +fail: + if (applet_private) { + passthru_delete_applet_private(applet_private); + } + return NULL; +} + + +/* + * create a new applet which links to our override function. + */ +static VCardApplet * +passthru_new_applet(VReader *reader) +{ + VCardAppletPrivate *applet_private = NULL; + VCardApplet *applet = NULL; + unsigned char passthru_aid[] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; + int passthru_aid_len = sizeof (passthru_aid); + + applet_private = passthru_new_applet_private(reader); + if (applet_private == NULL) { + goto failure; + } + applet = vcard_new_applet(passthru_process_apdu, passthru_reset, + passthru_aid, passthru_aid_len); + if (applet == NULL) { + goto failure; + } + vcard_set_applet_private(applet, applet_private, + passthru_delete_applet_private); + applet_private = NULL; + + return applet; + +failure: + if (applet_private != NULL) { + passthru_delete_applet_private(applet_private); + } + return NULL; +} + + + +/* + * Initialize the card. This is the only 'card type emulator' portion of this + * the rest are connected through function pointers. + */ +VCardStatus +passthru_card_init(VReader *vreader, VCard *card, + const char *flags, + unsigned char * const *cert, + int cert_len[], + VCardKey *key[] /* adopt the keys*/, + int cert_count) +{ + int i; + VCardApplet *applet; + + /* Don't do soft emulation of the 7816, pass everything to the card */ + vcard_set_type(card,VCARD_DIRECT); + + applet = passthru_new_applet(vreader); + if (applet == NULL) { + goto failure; + } + + vcard_add_applet(card, applet); + + /* we are adopting the keys, so free them now (since we don't use them) */ + for (i=0; i < cert_count; i++) { + vcard_emul_delete_key(key[i]); + } + + return VCARD_DONE; + +failure: + return VCARD_FAIL; +} + +/* + * Begin passthru_emul code. This emulator only works with the passthru card + * type. + * + */ + +/* + * Get the state entry that matches this reader. If none found, return NULL + */ +static SCARD_READERSTATE_A * +passthru_get_reader_state(SCARD_READERSTATE_A *reader_states, + int reader_count, char *name) +{ + int i; + + for (i=0; i < reader_count; i++) { + if (name == NULL && reader_states[i].szReader == NULL) { + // looking for a blank slot to return + return &reader_states[i]; + } + if (name == NULL || reader_states[i].szReader == NULL) { + continue; + } + if (strcmp(name, reader_states[i].szReader) == 0) { + return &reader_states[i]; + } + } + return NULL; +} + +/* + * find a card slot that has been cleared out + */ +static SCARD_READERSTATE_A * +passthru_get_blank_reader(SCARD_READERSTATE_A *reader_states, int reader_count) +{ + return passthru_get_reader_state(reader_states, reader_count, NULL); +} + + +/* + * This is the main work of the emulator, handling the thread that looks for + * changes in the readers and the cards. + */ +static void * +passthru_emul_event_thread(void *args) +{ + char *reader_list = NULL; + int reader_list_len = 0; + SCARD_READERSTATE_A *reader_states = NULL; + int reader_count = 0; /* number of active readers */ + int max_reader_count = 0; /* size of the reader_state array (including + inactive readers) */ + LONG rv; + int timeout=1000; + int i; + + do { + /* variables to hold on to our new values until we are ready to replace + * our old values */ + char *new_reader_list = NULL; + int new_reader_list_len = 0; + int new_reader_count = 0; + + /* other temps */ + char * reader_entry; + VReader *reader; + + /* + * First check to see if the reader list has changed + */ + rv = SCardListReaders(global_context, NULL, NULL, &new_reader_list_len); + if (rv != SCARD_S_SUCCESS) { + goto next; + } + /* + * If the names have changed, we need to update our list and states. + * This is where we detect reader insertions and removals. + */ + if (new_reader_list_len != reader_list_len) { + /* update the list */ + new_reader_list = (char *)malloc(new_reader_list_len); + if (new_reader_list == NULL) { + goto next; + } + rv = SCardListReaders(global_context, NULL, new_reader_list, + &new_reader_list_len); + if (rv != SCARD_S_SUCCESS) { + free(new_reader_list); + goto next; + } + /* clear out our event state */ + for (i=0; i < reader_count; i++) { + reader_states[i].dwEventState = 0; + } + /* count the readers and mark the ones that are still with us */ + for (reader_entry = new_reader_list; *reader_entry; + reader_entry += strlen(reader_entry)+1) { + SCARD_READERSTATE_A *this_state; + new_reader_count++; + /* if the reader is still on the list, mark it present */ + this_state = passthru_get_reader_state(reader_states, + reader_count, + reader_entry); + if (this_state) { + this_state->dwEventState = SCARD_STATE_PRESENT; + } + } + /* eject any removed readers */ + for (i=0; i < reader_count; i++) { + if (reader_states[i].dwEventState == SCARD_STATE_PRESENT) { + reader_states[i].dwEventState = 0; + continue; + } + reader = vreader_get_reader_by_name(reader_states[i].szReader); + vreader_remove_reader(reader); + vreader_free(reader); + reader_states[i].szReader = NULL; + } + /* handle the shrinking list */ + if (new_reader_count < reader_count) { + /* fold all the valid entries at the end of our reader_states + * array up into those locations vacated by ejected readers. */ + for (i=reader_count-1; i < (new_reader_count -1); i--) { + if (reader_states[i].szReader) { + SCARD_READERSTATE_A *blank_reader; + blank_reader = + passthru_get_blank_reader(reader_states, + new_reader_count); + assert(blank_reader); + *blank_reader = reader_states[i]; + reader_states[i].szReader = NULL; + } + } + } + /* handle the growing list */ + if (new_reader_count > max_reader_count) { + SCARD_READERSTATE_A *new_reader_states; + + /* grow the list */ + new_reader_states = + (SCARD_READERSTATE_A *)realloc(reader_states, + sizeof(SCARD_READERSTATE_A)*new_reader_count); + if (new_reader_states) { + /* successful, update our current state */ + reader_states = new_reader_states; + max_reader_count = new_reader_count; + } else { + new_reader_count = max_reader_count; /* couldn't get enough + * space to handle + * all the new readers + * */ + } + /* mark our new entries as empty */ + for (i=reader_count; i > new_reader_count; i++) { + reader_states[i].szReader = NULL; + } + } + /* now walk the reader list, updating the state */ + for (reader_entry = new_reader_list; *reader_entry; + reader_entry += strlen(reader_entry)+1) { + SCARD_READERSTATE_A *this_state; + this_state = passthru_get_reader_state(reader_states, + new_reader_count, + reader_entry); + if (this_state) { + /* replace the old copy of the string with the new copy. + * This will allow us to free reader_list at the end */ + reader_states->szReader = reader_entry; + continue; + } + /* this is a new reader, add it to the list */ + this_state = + passthru_get_blank_reader(reader_states, new_reader_count); + if (!this_state) { + continue; /* this can happen of we couldn't get enough + slots in the grow list */ + } + this_state->szReader = reader_entry; + this_state->dwCurrentState = SCARD_STATE_UNAWARE; + reader = vreader_new(reader_entry, NULL, NULL); + if (reader) { + vreader_add_reader(reader); + } + vreader_free(reader); + } + /* finally update our current variables */ + free(reader_list); + reader_list = new_reader_list; + reader_list_len = new_reader_list_len; + reader_count = new_reader_count; + } +next: + rv = SCardGetStatusChange(global_context, timeout, + reader_states, reader_count); + if (rv == SCARD_E_TIMEOUT) { + continue; /* check for new readers */ + } + if (rv != SCARD_S_SUCCESS) { + static int restarts = 0; + VCardStatus status; + + /* try resetting the pcsc_lite subsystem */ + SCardReleaseContext(global_context); + global_context = 0; /* should close it */ + printf("***** SCard failure %x\n", rv); + restarts++; + if (restarts >= 3) { + printf("***** SCard failed %d times\n", restarts); + return; /* exit thread */ + } + status = passthru_pcsc_lite_init(); + assert(status == CARD_DONE); + sleep(1); + continue; + } + /* deal with card insertion/removal */ + for (i=0; i < reader_count ; i++) { + if ((reader_states[i].dwEventState & SCARD_STATE_CHANGED) == 0) { + continue; + } + reader_states[i].dwCurrentState = reader_states[i].dwEventState; + reader = vreader_get_reader_by_name(reader_states[i].szReader); + if (reader == NULL) { + continue; + } + if (reader_states[i].dwEventState & SCARD_STATE_EMPTY) { + if (vreader_card_is_present(reader) == VREADER_OK) { + vreader_insert_card(reader, NULL); + } + } + if (reader_states[i].dwEventState & SCARD_STATE_PRESENT) { + VCard *card; + VCardStatus status = VCARD_FAIL; + /* if there already was a card present, eject it before we + * insert the new one */ + if (vreader_card_is_present(reader) == VREADER_OK) { + vreader_insert_card(reader, NULL); + } + + card = vcard_new(NULL, NULL); + if (card != NULL) { + status = passthru_card_init(reader, card, "", + NULL, NULL, NULL, 0); + passthru_card_set_atr(card, reader_states[i].rgbAtr, + reader_states[i].cbAtr); + vcard_set_atr_func(card, passthru_card_get_atr); + } + if (status == VCARD_DONE) { + vreader_insert_card(reader, card); + } + vcard_free(card); + } + vreader_free(reader); + } + + } while (1); + return NULL; +} + +/* + * Initializing the passthru emul is simply initializing pcsc-lite and + * launching the event thread. + */ +VCardStatus +passthru_emul_init(VCardEmulOptions *options) +{ + thread_t tid; + thread_status_t tstatus; + VCardStatus status; + + vreader_init(); + vevent_queue_init(); + + status = passthru_pcsc_lite_init(); + if (status != VCARD_DONE) { + return status; + } + + /* launch reader thread */ + tstatus = THREAD_CREATE(tid, passthru_emul_event_thread, NULL); + if (!THREAD_SUCCESS(tstatus)) { + return VCARD_FAIL; + } + return VCARD_DONE; +} + + +VCardEmulOptions * +passthru_emul_options(const char *args) +{ + return NULL; +} +#endif diff --git a/libcacard/passthru.h b/libcacard/passthru.h new file mode 100644 index 0000000..3589d12 --- /dev/null +++ b/libcacard/passthru.h @@ -0,0 +1,50 @@ +/* + * passthru card type emulator and passhtru emulator. + * + * passhtru card type emulator can be used with other low level card emulators, + * as long as they can recognize card insertion and removals. + * + * the passthru vcard_emulator, can only use passthru card types. + * + * Be careful using passthru. 1) passthru does not know the locking state of + * the card from the guest side, and thus does not try to get locks. This means + * client access can interfere with the guest use of the card. 2) passthru does + * not provide the guest and client unique login states for the card. That + * means that it is possible for the guest to access private data on the + * card without authenticating. You have been warned. + * + * Passthru is most useful in the following cases: 1) provisioning. Card type + * emulators cannot emulate the open platform secure connections because the + * client software does not have access to the global platform keys on the + * card. Passthru drives these apdu's directly to the card. 2) odd cards. If + * you have guest software the knows how to access the card, but no client + * side PKCS #11 module, then passthru can provide access to those cards. + */ + +#ifndef PASSTHRU_H +#define PASSTHRU_H 1 + +#include "vcard.h" +#include "vcard_emul.h" +#include "vreader.h" + +/* + * Initialize the card. This is the only 'card type emulator' portion of this + * the rest are connected through function pointers. NOTE: certs are ignored, + * keys are freed. + */ +VCardStatus passthru_card_init(VReader *vreader, VCard *card, + const char *flags, unsigned char * const *cert, int cert_len[], + VCardKey *key[], int cert_count); + +/* + * Use this instead of vcard_emul_init to initialize passthru. + * passthru is the exception to the rule that only one emul can be compiled + * at once. NOTE: you can still have only one emul active at once. The + * passhtru card type emul, however can be used with other emuls. + * + * passthru does not support other card type emuls. + */ +VCardStatus passthru_emul_init(VCardEmulOptions *options); +VCardEmulOptions *passthru_emul_options(const char *args); +#endif diff --git a/libcacard/vcard.c b/libcacard/vcard.c new file mode 100644 index 0000000..9672001 --- /dev/null +++ b/libcacard/vcard.c @@ -0,0 +1,350 @@ +/* + * implement the Java card standard. + * + */ +#include <stdlib.h> +#include <string.h> +#include "vcard.h" +#include "vcard_emul.h" +#include "card_7816t.h" + +struct VCardAppletStruct { + VCardApplet *next; + VCardProcessAPDU process_apdu; + VCardResetApplet reset_applet; + unsigned char *aid; + int aid_len; + void *applet_private; + VCardAppletPrivateFree applet_private_free; +}; + +struct VCardStruct { + int reference_count; + VCardApplet *applet_list; + VCardApplet *current_applet[MAX_CHANNEL]; + VCardBufferResponse *vcard_buffer_response; + VCardType type; + VCardEmul *vcard_private; + VCardEmulFree vcard_private_free; + VCardGetAtr vcard_get_atr; +}; + +VCardBufferResponse * +vcard_buffer_response_new(unsigned char *buffer, int size) +{ + VCardBufferResponse *new_buffer; + + new_buffer = (VCardBufferResponse *)malloc(sizeof(VCardBufferResponse)); + if (new_buffer == NULL) { + return NULL; + } + new_buffer->buffer = (unsigned char *)malloc(size); + if (new_buffer->buffer == NULL) { + free(new_buffer); + return NULL; + } + memcpy(new_buffer->buffer, buffer, size); + new_buffer->buffer_len = size; + new_buffer->current = new_buffer->buffer; + new_buffer->len = size; + return new_buffer; +} + +void +vcard_buffer_response_delete(VCardBufferResponse *buffer_response) +{ + if (buffer_response == NULL) { + return; + } + if (buffer_response->buffer) { + free(buffer_response->buffer); + } + free(buffer_response); +} + + +/* + * clean up state after a reset + */ +void +vcard_reset(VCard *card, VCardPower power) +{ + int i; + VCardApplet *applet = NULL; + + if (card->type == VCARD_DIRECT) { + /* select the last applet */ + VCardApplet *current_applet = NULL; + for (current_applet = card->applet_list; current_applet; + current_applet = current_applet->next) { + applet = current_applet; + } + } + for (i=0; i < MAX_CHANNEL; i++) { + card->current_applet[i] = applet; + } + if (card->vcard_buffer_response) { + vcard_buffer_response_delete(card->vcard_buffer_response); + card->vcard_buffer_response = NULL; + } + vcard_emul_reset(card, power); + if (applet) { + applet->reset_applet(card, 0); + } +} + +/* applet utilities */ + +/* + * applet utilities + */ +/* constructor */ +VCardApplet * +vcard_new_applet(VCardProcessAPDU applet_process_function, + VCardResetApplet applet_reset_function, + unsigned char *aid, int aid_len) +{ + VCardApplet *applet; + + applet = (VCardApplet *)malloc(sizeof(VCardApplet)); + if (applet == NULL) { + return NULL; + } + applet->next = NULL; + applet->applet_private = NULL; + applet->applet_private_free = NULL; + applet->process_apdu = applet_process_function; + applet->reset_applet = applet_reset_function; + + applet->aid = malloc(aid_len); + if (applet->aid == NULL) { + free(applet); + return NULL; + } + memcpy(applet->aid, aid, aid_len); + applet->aid_len = aid_len; + return applet; +} + +/* destructor */ +void +vcard_delete_applet(VCardApplet *applet) +{ + if (applet == NULL) { + return; + } + if (applet->applet_private_free) { + applet->applet_private_free(applet->applet_private); + applet->applet_private = NULL; + } + if (applet->aid) { + free(applet->aid); + applet->aid = NULL; + } + free(applet); +} + +/* accessor */ +void +vcard_set_applet_private(VCardApplet *applet, VCardAppletPrivate *private, + VCardAppletPrivateFree private_free) +{ + if (applet->applet_private_free) { + applet->applet_private_free(applet->applet_private); + } + applet->applet_private = private; + applet->applet_private_free = private_free; +} + +VCard * +vcard_new(VCardEmul *private, VCardEmulFree private_free) +{ + VCard *new_card; + int i; + + new_card = (VCard *)malloc(sizeof(VCard)); + new_card->applet_list = NULL; + for (i=0; i < MAX_CHANNEL; i++) { + new_card->current_applet[i] = NULL; + } + new_card->vcard_buffer_response = NULL; + new_card->type = VCARD_VM; + new_card->vcard_private = private; + new_card->vcard_private_free = private_free; + new_card->vcard_get_atr = NULL; + new_card->reference_count = 1; + return new_card; +} + +VCard * +vcard_reference(VCard *vcard) +{ + if (vcard == NULL) { + return NULL; + } + vcard->reference_count++; + return vcard; +} + +void +vcard_free(VCard *vcard) +{ + VCardApplet *current_applet = NULL; + VCardApplet *next_applet = NULL; + + if (vcard == NULL) { + return; + } + vcard->reference_count--; + if (vcard->reference_count != 0) { + return; + } + if (vcard->vcard_private_free) { + (*vcard->vcard_private_free)(vcard->vcard_private); + vcard->vcard_private_free = 0; + vcard->vcard_private = 0; + } + for (current_applet = vcard->applet_list; current_applet; + current_applet = next_applet) { + next_applet = current_applet->next; + vcard_delete_applet(current_applet); + } + vcard_buffer_response_delete(vcard->vcard_buffer_response); + free(vcard); + return; +} + +void +vcard_get_atr(VCard *vcard, unsigned char *atr, int *atr_len) +{ + if (vcard->vcard_get_atr) { + (*vcard->vcard_get_atr)(vcard, atr, atr_len); + return; + } + vcard_emul_get_atr(vcard, atr, atr_len); +} + +void +vcard_set_atr_func(VCard *card, VCardGetAtr vcard_get_atr) +{ + card-> vcard_get_atr = vcard_get_atr; +} + + +VCardStatus +vcard_add_applet(VCard *card, VCardApplet *applet) +{ + applet->next = card->applet_list; + card->applet_list = applet; + /* if our card-type is direct, always call the applet */ + if (card->type == VCARD_DIRECT) { + int i; + + for (i=0; i < MAX_CHANNEL; i++) { + card->current_applet[i] = applet; + } + } + return VCARD_DONE; +} + +/* + * manage applets + */ +VCardApplet * +vcard_find_applet(VCard *card, unsigned char *aid, int aid_len) +{ + VCardApplet *current_applet; + + for (current_applet = card->applet_list; current_applet; + current_applet = current_applet->next) { + if (current_applet->aid_len != aid_len) { + continue; + } + if (memcmp(current_applet->aid, aid, aid_len) == 0) { + break; + } + } + return current_applet; +} + +unsigned char * +vcard_applet_get_aid(VCardApplet *applet, int *aid_len) +{ + if (applet == NULL) { + return NULL; + } + *aid_len = applet->aid_len; + return applet->aid; +} + + +void +vcard_select_applet(VCard *card, int channel, VCardApplet *applet) +{ + ASSERT(channel < MAX_CHANNEL); + card->current_applet[channel] = applet; + /* reset the applet */ + if (applet && applet->reset_applet) { + applet->reset_applet(card, channel); + } +} + +VCardAppletPrivate * +vcard_get_current_applet_private(VCard *card, int channel) +{ + VCardApplet *applet = card->current_applet[channel]; + + if (applet == NULL) { + return NULL; + } + return applet->applet_private; +} + +VCardStatus +vcard_process_applet_apdu(VCard *card, VCardAPDU *apdu, + VCardResponse **response) +{ + if (card->current_applet[apdu->a_channel]) { + return card->current_applet[apdu->a_channel]->process_apdu( + card, apdu, response); + } + return VCARD_NEXT; +} + +/* + * Accessor functions + */ +/* accessor functions for the response buffer */ +VCardBufferResponse * +vcard_get_buffer_response(VCard *card) +{ + return card->vcard_buffer_response; +} + +void +vcard_set_buffer_response(VCard *card, VCardBufferResponse *buffer) +{ + card->vcard_buffer_response = buffer; +} + + +/* accessor functions for the type */ +VCardType +vcard_get_type(VCard *card) +{ + return card->type; +} + +void +vcard_set_type(VCard *card, VCardType type) +{ + card->type = type; +} + +/* accessor for private data */ +VCardEmul * +vcard_get_private(VCard *vcard) +{ + return vcard->vcard_private; +} + diff --git a/libcacard/vcard.h b/libcacard/vcard.h new file mode 100644 index 0000000..8dbd761 --- /dev/null +++ b/libcacard/vcard.h @@ -0,0 +1,85 @@ +/* + * + */ +#ifndef VCARD_H +#define VCARD_H 1 + +#include "vcardt.h" + +/* + * response buffer constructors and destructors. + * + * response buffers are used when we need to return more data than will fit in + * a normal APDU response (nominally 254 bytes). + */ +VCardBufferResponse *vcard_buffer_response_new(unsigned char *buffer, int size); +void vcard_buffer_response_delete(VCardBufferResponse *buffer_response); + + +/* + * clean up state on reset + */ +void vcard_reset(VCard *card, VCardPower power); + +/* + * applet utilities + */ +/* + * Constructor for a VCardApplet + */ +VCardApplet *vcard_new_applet(VCardProcessAPDU applet_process_function, + VCardResetApplet applet_reset_function, + unsigned char *aid, int aid_len); + +/* + * destructor for a VCardApplet + * Can be called with a NULL applet + */ +void vcard_delete_applet(VCardApplet *applet); + +/* accessor - set the card type specific private data */ +void vcard_set_applet_private(VCardApplet *applet, VCardAppletPrivate *_private, + VCardAppletPrivateFree private_free); + +/* set type of vcard */ +void vcard_set_type(VCard *card, VCardType type); + +/* + * utilities interacting with the current applet + */ +/* add a new applet to a card */ +VCardStatus vcard_add_applet(VCard *card, VCardApplet *applet); +/* find the applet on the card with the given aid */ +VCardApplet *vcard_find_applet(VCard *card, unsigned char *aid, int aid_len); +/* set the following applet to be current on the given channel */ +void vcard_select_applet(VCard *card, int channel, VCardApplet *applet); +/* get the card type specific private data on the given channel */ +VCardAppletPrivate * vcard_get_current_applet_private(VCard *card, int channel); +/* fetch the applet's id */ +unsigned char *vcard_applet_get_aid(VCardApplet *applet, int *aid_len); + +/* process the apdu for the current selected applet/file */ +VCardStatus vcard_process_applet_apdu(VCard *card, VCardAPDU *apdu, + VCardResponse **response); +/* + * VCard utilities + */ +/* constructor */ +VCard * vcard_new(VCardEmul *_private, VCardEmulFree private_free); +/* get a reference */ +VCard * vcard_reference(VCard *); +/* destructor (reference counted) */ +void vcard_free(VCard *); +/* get the atr from the card */ +void vcard_get_atr(VCard *card, unsigned char *atr, int *atr_len); +void vcard_set_atr_func(VCard *card, VCardGetAtr vcard_get_atr); + +/* accessor functions for the response buffer */ +VCardBufferResponse *vcard_get_buffer_response(VCard *card); +void vcard_set_buffer_response(VCard *card, VCardBufferResponse *buffer); +/* accessor functions for the type */ +VCardType vcard_get_type(VCard *card); +/* get the private data */ +VCardEmul *vcard_get_private(VCard *card); + +#endif diff --git a/libcacard/vcard_emul.h b/libcacard/vcard_emul.h new file mode 100644 index 0000000..5df01a8 --- /dev/null +++ b/libcacard/vcard_emul.h @@ -0,0 +1,62 @@ +/* + * This is the actual card emulator. + * + * These functions can be implemented in different ways on different platforms + * using the underlying system primitives. For Linux it uses NSS, though direct + * to PKCS #11, openssl+pkcs11, or even gnu crypto libraries+pkcs #11 could be + * used. On Windows CAPI could be used. + */ + +#ifndef VCARD_EMUL_H +#define VCARD_EMUL_H 1 + +#include "card_7816t.h" +#include "vcard.h" +#include "vcard_emul_type.h" + +/* + * types + */ +typedef enum { + VCARD_EMUL_OK =0, + VCARD_EMUL_FAIL, + // return values by vcard_emul_init + VCARD_EMUL_INIT_ALREADY_INITED, +} VCardEmulError; + +/* options are emul specific. call card_emul_parse_args to change a string + * To an options struct */ +typedef struct VCardEmulOptionsStruct VCardEmulOptions; + +/* + * Login functions + */ +/* return the number of login attempts still possible on the card. if unknown, + * return -1 */ +int vcard_emul_get_login_count(VCard *card); +/* login into the card, return the 7816 status word (sw2 || sw1) */ +vcard_7816_status_t vcard_emul_login(VCard *card, unsigned char *pin, + int pin_len); + +/* + * key functions + */ +/* delete a key */ +void vcard_emul_delete_key(VCardKey *key); +/* RSA sign/decrypt with the key, signature happens 'in place' */ +vcard_7816_status_t vcard_emul_rsa_op(VCard *card, VCardKey *key, + unsigned char *buffer, int buffer_size); + +void vcard_emul_reset(VCard *card, VCardPower power); +void vcard_emul_get_atr(VCard *card, unsigned char *atr, int *atr_len); + +/* Re-insert of a card that has been removed by force removal */ +VCardEmulError vcard_emul_force_card_insert(VReader *vreader); +/* Force a card removal even if the card is not physically removed */ +VCardEmulError vcard_emul_force_card_remove(VReader *vreader); + +VCardEmulOptions *vcard_emul_options(const char *args); +VCardEmulError vcard_emul_init(const VCardEmulOptions *options); +void vcard_emul_replay_insertion_events(void); +void vcard_emul_usage(void); +#endif diff --git a/libcacard/vcard_emul_nss.c b/libcacard/vcard_emul_nss.c new file mode 100644 index 0000000..33404c2 --- /dev/null +++ b/libcacard/vcard_emul_nss.c @@ -0,0 +1,1171 @@ +/* + * This is the actual card emulator. + * + * These functions can be implemented in different ways on different platforms + * using the underlying system primitives. For Linux it uses NSS, though direct + * to PKCS #11, openssl+pkcs11, or even gnu crypto libraries+pkcs #11 could be + * used. On Windows CAPI could be used. + */ +#include "vcard.h" +#include "card_7816t.h" +#include "vcard_emul.h" +#include "vreader.h" +#include "vevent.h" + +/* + * NSS headers + */ +#include <nss.h> +#include <pk11pub.h> +#include <cert.h> +#include <key.h> +#include <secmod.h> +#include <prthread.h> +#include <secerr.h> + +/* + * system headers + */ +#include <stdlib.h> +#include <string.h> + + +struct VCardKeyStruct { + CERTCertificate *cert; + PK11SlotInfo *slot; + SECKEYPrivateKey *key; +}; + + +typedef struct VirtualReaderOptionsStruct VirtualReaderOptions; + +struct VReaderEmulStruct { + PK11SlotInfo *slot; + VCardEmulType default_type; + char *type_params; + PRBool present; + int series; + VCard *saved_vcard; +}; + +/* + * NSS Specific options + */ +struct VirtualReaderOptionsStruct { + char *name; + char *vname; + VCardEmulType card_type; + char *type_params; + char **cert_name; + int cert_count; +}; + +struct VCardEmulOptionsStruct { + void *nss_db; + VirtualReaderOptions *vreader; + int vreader_count; + VCardEmulType hw_card_type; + const char *hw_type_params; + PRBool use_hw; +}; + +static int nss_emul_init = 0; + +/* if we have more that just the slot, define + * VCardEmulStruct here */ + +/* + * allocate the set of arrays for certs, cert_len, key + */ +static PRBool +vcard_emul_alloc_arrays(unsigned char ***certsp, int **cert_lenp, + VCardKey ***keysp, int cert_count) +{ + *certsp = NULL; + *cert_lenp = NULL; + *keysp = NULL; + *certsp = (unsigned char **)malloc(sizeof(unsigned char *)*cert_count); + if (*certsp == NULL) { + return PR_FALSE; + } + *cert_lenp = (int *)malloc(sizeof(int)*cert_count); + if (*cert_lenp == NULL) { + free(*certsp); + *certsp = NULL; + return PR_FALSE; + } + *keysp = (VCardKey **)malloc(sizeof(VCardKey *)*cert_count); + if (*keysp != NULL) { + return PR_TRUE; + } + free(*cert_lenp); + free(*certsp); + *cert_lenp = NULL; + *certsp = NULL; + return PR_FALSE; +} + +/* + * Emulator specific card information + */ +typedef struct CardEmulCardStruct CardEmulPrivate; + +static VCardEmul * +vcard_emul_new_card(PK11SlotInfo *slot) +{ + PK11_ReferenceSlot(slot); + /* currently we don't need anything other than the slot */ + return (VCardEmul *)slot; +} + +static void +vcard_emul_delete_card(VCardEmul *vcard_emul) +{ + PK11SlotInfo *slot = (PK11SlotInfo *)vcard_emul; + if (slot == NULL) { + return; + } + PK11_FreeSlot(slot); +} + +static PK11SlotInfo * +vcard_emul_card_get_slot(VCard *card) +{ + /* note, the card is holding the reference, no need to get another one */ + return (PK11SlotInfo *)vcard_get_private(card); +} + + +/* + * key functions + */ +/* private constructure */ +static VCardKey * +vcard_emul_make_key(PK11SlotInfo *slot, CERTCertificate *cert) +{ + VCardKey * key; + + key = (VCardKey *)malloc(sizeof(VCardKey)); + if (key == NULL) { + return NULL; + } + key->slot = PK11_ReferenceSlot(slot); + key->cert = CERT_DupCertificate(cert); + /* NOTE: if we aren't logged into the token, this could return NULL */ + /* NOTE: the cert is a temp cert, not necessarily the cert in the token, + * use the DER version of this function */ + key->key = PK11_FindKeyByDERCert(slot,cert, NULL); + return key; +} + +/* destructor */ +void +vcard_emul_delete_key(VCardKey *key) +{ + if (!nss_emul_init || (key == NULL)) { + return; + } + if (key->key) { + SECKEY_DestroyPrivateKey(key->key); + key->key = NULL; + } + if (key->cert) { + CERT_DestroyCertificate(key->cert); + } + if (key->slot) { + PK11_FreeSlot(key->slot); + } + return; +} + +/* + * grab the nss key from a VCardKey. If it doesn't exist, try to look it up + */ +static SECKEYPrivateKey * +vcard_emul_get_nss_key(VCardKey *key) +{ + if (key->key) { + return key->key; + } + /* NOTE: if we aren't logged into the token, this could return NULL */ + key->key = PK11_FindPrivateKeyFromCert(key->slot,key->cert, NULL); + return key->key; +} + +/* + * Map NSS errors to 7816 errors + */ +static vcard_7816_status_t +vcard_emul_map_error(int error) +{ + switch (error) { + case SEC_ERROR_TOKEN_NOT_LOGGED_IN: + return VCARD7816_STATUS_ERROR_CONDITION_NOT_SATISFIED; + case SEC_ERROR_BAD_DATA: + case SEC_ERROR_OUTPUT_LEN: + case SEC_ERROR_INPUT_LEN: + case SEC_ERROR_INVALID_ARGS: + case SEC_ERROR_INVALID_ALGORITHM: + case SEC_ERROR_NO_KEY: + case SEC_ERROR_INVALID_KEY: + case SEC_ERROR_DECRYPTION_DISALLOWED: + return VCARD7816_STATUS_ERROR_DATA_INVALID; + case SEC_ERROR_NO_MEMORY: + return VCARD7816_STATUS_EXC_ERROR_MEMORY_FAILURE; + } + return VCARD7816_STATUS_EXC_ERROR_CHANGE; +} + +/* RSA sign/decrypt with the key, signature happens 'in place' */ +vcard_7816_status_t +vcard_emul_rsa_op(VCard *card, VCardKey *key, + unsigned char *buffer, int buffer_size) +{ + SECKEYPrivateKey *priv_key; + unsigned signature_len; + SECStatus rv; + + if ((!nss_emul_init) || (key == NULL)) { + /* couldn't get the key, indicate that we aren't logged in */ + return VCARD7816_STATUS_ERROR_CONDITION_NOT_SATISFIED; + } + priv_key = vcard_emul_get_nss_key(key); + + /* + * this is only true of the rsa signature + */ + signature_len = PK11_SignatureLen(priv_key); + if (buffer_size != signature_len) { + return VCARD7816_STATUS_ERROR_DATA_INVALID; + } + rv = PK11_PrivDecryptRaw(priv_key, buffer, &signature_len, signature_len, + buffer, buffer_size); + if (rv != SECSuccess) { + return vcard_emul_map_error(PORT_GetError()); + } + ASSERT(buffer_size == signature_len); + return VCARD7816_STATUS_SUCCESS; +} + +/* + * Login functions + */ +/* return the number of login attempts still possible on the card. if unknown, + * return -1 */ +int +vcard_emul_get_login_count(VCard *card) +{ + return -1; +} + +/* login into the card, return the 7816 status word (sw2 || sw1) */ +vcard_7816_status_t +vcard_emul_login(VCard *card, unsigned char *pin, int pin_len) +{ + PK11SlotInfo *slot; + unsigned char *pin_string = NULL; + int i; + SECStatus rv; + + if (!nss_emul_init) { + return VCARD7816_STATUS_ERROR_CONDITION_NOT_SATISFIED; + } + slot = vcard_emul_card_get_slot(card); + /* We depend on the PKCS #11 module internal login state here because we + * create a separate process to handle each guest instance. If we needed + * to handle multiple guests from one process, then we would need to keep + * a lot of extra state in our card structure + * */ + pin_string = malloc(pin_len+1); + if (pin_string == NULL) { + return VCARD7816_STATUS_EXC_ERROR_MEMORY_FAILURE; + } + memcpy(pin_string,pin,pin_len); + pin_string[pin_len] = 0; + + /* handle CAC expanded pins correctly */ + for (i = pin_len-1; i >= 0 && (pin_string[i] == 0xff); i--) { + pin_string[i] = 0; + } + + rv = PK11_Authenticate(slot, PR_FALSE, pin_string); + memset(pin_string, 0, pin_len); /* don't let the pin hang around in memory + to be snooped */ + free(pin_string); + if (rv == SECSuccess) { + return VCARD7816_STATUS_SUCCESS; + } + /* map the error from port get error */ + return VCARD7816_STATUS_ERROR_CONDITION_NOT_SATISFIED; +} + +void +vcard_emul_reset(VCard *card, VCardPower power) +{ + PK11SlotInfo *slot; + + if (!nss_emul_init) { + return; + } + + /* if we reset the card (either power on or power off), we loose our login + * state */ + /* TODO: we may also need to send insertion/removal events? */ + slot = vcard_emul_card_get_slot(card); + (void)PK11_Logout(slot); + return; +} + + +static VReader * +vcard_emul_find_vreader_from_slot(PK11SlotInfo *slot) +{ + VReaderList *reader_list = vreader_get_reader_list(); + VReaderListEntry *current_entry = NULL; + + if (reader_list == NULL) { + return NULL; + } + for (current_entry= vreader_list_get_first(reader_list); current_entry; + current_entry=vreader_list_get_next(current_entry)) { + VReader *reader = vreader_list_get_reader(current_entry); + VReaderEmul *reader_emul = vreader_get_private(reader); + if (reader_emul->slot == slot) { + return reader; + } + vreader_free(reader); + } + + return NULL; +} + +/* + * create a new reader emul + */ +static VReaderEmul * +vreader_emul_new(PK11SlotInfo *slot, VCardEmulType type, const char *params) +{ + VReaderEmul *new_reader_emul; + + new_reader_emul = (VReaderEmul *)malloc(sizeof(VReaderEmul)); + if (new_reader_emul == NULL) { + return NULL; + } + + new_reader_emul->slot = PK11_ReferenceSlot(slot); + new_reader_emul->default_type = type; + new_reader_emul->type_params = strdup(params); + new_reader_emul->present = PR_FALSE; + new_reader_emul->series = 0; + new_reader_emul->saved_vcard = NULL; + return new_reader_emul; +} + +static void +vreader_emul_delete(VReaderEmul *vreader_emul) +{ + if (vreader_emul == NULL) { + return; + } + if (vreader_emul->slot) { + PK11_FreeSlot(vreader_emul->slot); + } + if (vreader_emul->type_params) { + free(vreader_emul->type_params); + } + free(vreader_emul); +} + +/* + * TODO: move this to emulater non-specific file + */ +static VCardEmulType +vcard_emul_get_type(VReader *vreader) +{ + VReaderEmul *vreader_emul; + + vreader_emul = vreader_get_private(vreader); + if (vreader_emul && vreader_emul->default_type != VCARD_EMUL_NONE) { + return vreader_emul->default_type; + } + + return vcard_emul_type_select(vreader); +} +/* + * TODO: move this to emulater non-specific file + */ +static const char * +vcard_emul_get_type_params(VReader *vreader) +{ + VReaderEmul *vreader_emul; + + vreader_emul = vreader_get_private(vreader); + if (vreader_emul && vreader_emul->type_params) { + return vreader_emul->type_params; + } + + return ""; +} + +/* pull the slot out of the reader private data */ +static PK11SlotInfo * +vcard_emul_reader_get_slot(VReader *vreader) +{ + VReaderEmul *vreader_emul = vreader_get_private(vreader); + if (vreader_emul == NULL) { + return NULL; + } + return vreader_emul->slot; +} + +/* + * Card ATR's map to physical cards. VCARD_ATR_PREFIX will set appropriate + * historical bytes for any software emulated card. The remaining bytes can be + * used to indicate the actual emulator + */ +static const unsigned char nss_atr[] = { VCARD_ATR_PREFIX(3), 'N', 'S', 'S' }; + +void +vcard_emul_get_atr(VCard *card, unsigned char *atr, int *atr_len) +{ + int len = MIN(sizeof(nss_atr), *atr_len); + ASSERT(atr != NULL); + + memcpy(atr, nss_atr, len); + *atr_len = len; + return; +} + +/* + * create a new card from certs and keys + */ +static VCard * +vcard_emul_make_card(VReader *reader, + unsigned char * const *certs, int *cert_len, + VCardKey *keys[], int cert_count) +{ + VCardEmul *vcard_emul; + VCard *vcard; + PK11SlotInfo *slot; + VCardEmulType type; + const char *params; + + type = vcard_emul_get_type(reader); + + /* ignore the inserted card */ + if (type == VCARD_EMUL_NONE) { + return NULL; + } + slot = vcard_emul_reader_get_slot(reader); + if (slot == NULL) { + return NULL; + } + + params = vcard_emul_get_type_params(reader); + /* params these can be NULL */ + + vcard_emul = vcard_emul_new_card(slot); + if (vcard_emul == NULL) { + return NULL; + } + vcard = vcard_new(vcard_emul, vcard_emul_delete_card); + if (vcard == NULL) { + vcard_emul_delete_card(vcard_emul); + return NULL; + } + vcard_init(reader, vcard, type, params, certs, cert_len, keys, cert_count); + return vcard; +} + + +/* + * 'clone' a physical card as a virtual card + */ +static VCard * +vcard_emul_mirror_card(VReader *vreader) +{ + /* + * lookup certs using the C_FindObjects. The Stan Cert handle won't give + * us the real certs until we log in. + */ + PK11GenericObject *firstObj, *thisObj; + int cert_count; + unsigned char **certs; + int *cert_len; + VCardKey **keys; + PK11SlotInfo *slot; + PRBool ret; + + slot = vcard_emul_reader_get_slot(vreader); + if (slot == NULL) { + return NULL; + } + + firstObj = PK11_FindGenericObjects(slot, CKO_CERTIFICATE); + if (firstObj == NULL) { + return NULL; + } + + /* count the certs */ + cert_count=0; + for (thisObj = firstObj; thisObj; + thisObj = PK11_GetNextGenericObject(thisObj)) { + cert_count++; + } + + if (cert_count == 0) { + PK11_DestroyGenericObjects(firstObj); + return NULL; + } + + /* allocate the arrays */ + ret = vcard_emul_alloc_arrays(&certs,&cert_len, &keys, cert_count); + if (ret == PR_FALSE) { + return NULL; + } + + /* fill in the arrays */ + cert_count = 0; + for (thisObj = firstObj; thisObj; + thisObj = PK11_GetNextGenericObject(thisObj)) { + SECItem derCert; + CERTCertificate *cert; + SECStatus rv; + + rv = PK11_ReadRawAttribute(PK11_TypeGeneric, thisObj, + CKA_VALUE, &derCert); + if (rv != SECSuccess) { + continue; + } + /* create floating temp cert. This gives us a cert structure even if + * the token isn't logged in */ + cert = CERT_NewTempCertificate(CERT_GetDefaultCertDB(), &derCert, + NULL, PR_FALSE, PR_TRUE); + SECITEM_FreeItem(&derCert, PR_FALSE); + if (cert == NULL) { + continue; + } + + certs[cert_count] = cert->derCert.data; + cert_len[cert_count] = cert->derCert.len; + keys[cert_count] = vcard_emul_make_key(slot, cert); + cert_count++; + CERT_DestroyCertificate(cert); /* key obj still has a reference */ + } + + /* now create the card */ + return vcard_emul_make_card(vreader, certs, cert_len, keys, cert_count); +} + +static VCardEmulType default_card_type = VCARD_EMUL_NONE; +static const char *default_type_params = ""; + +/* + * This thread looks for card and reader insertions and puts events on the + * event queue + */ +static void +vcard_emul_event_thread(void *arg) +{ + PK11SlotInfo *slot; + VReader *vreader; + VReaderEmul *vreader_emul; + VCard *vcard; + SECMODModule *module = (SECMODModule *)arg; + + do { + slot = SECMOD_WaitForAnyTokenEvent(module, 0, 500); + if (slot == NULL) { + break; + } + vreader = vcard_emul_find_vreader_from_slot(slot); + if (vreader == NULL) { + /* new vreader */ + vreader_emul = vreader_emul_new(slot, default_card_type, + default_type_params); + vreader = vreader_new(PK11_GetSlotName(slot), vreader_emul, + vreader_emul_delete); + PK11_FreeSlot(slot); + slot = NULL; + vreader_add_reader(vreader); + vreader_free(vreader); + continue; + } + /* card remove/insert */ + vreader_emul = vreader_get_private(vreader); + if (PK11_IsPresent(slot)) { + int series = PK11_GetSlotSeries(slot); + if (series != vreader_emul->series) { + if (vreader_emul->present) { + vreader_insert_card(vreader, NULL); + } + vcard = vcard_emul_mirror_card(vreader); + vreader_insert_card(vreader, vcard); + vcard_free(vcard); + } + vreader_emul->series = series; + vreader_emul->present = 1; + vreader_free(vreader); + PK11_FreeSlot(slot); + continue; + } + if (vreader_emul->present) { + vreader_insert_card(vreader, NULL); + } + vreader_emul->series = 0; + vreader_emul->present = 0; + PK11_FreeSlot(slot); + vreader_free(vreader); + } while(1); +} + +/* if the card is inserted when we start up, make sure our state is correct */ +static void +vcard_emul_init_series(VReader *vreader, VCard *vcard) +{ + VReaderEmul *vreader_emul = vreader_get_private(vreader); + PK11SlotInfo *slot = vreader_emul->slot; + + vreader_emul->present = PK11_IsPresent(slot); + vreader_emul->series = PK11_GetSlotSeries(slot); + if (vreader_emul->present == 0) { + vreader_insert_card(vreader, NULL); + } +} + +/* + * each module has a separate wait call, create a thread for each module that + * we are using. + */ +static void +vcard_emul_new_event_thread(SECMODModule *module) +{ + PR_CreateThread(PR_SYSTEM_THREAD, vcard_emul_event_thread, + module, PR_PRIORITY_HIGH, PR_GLOBAL_THREAD, + PR_UNJOINABLE_THREAD, 0); +} + +static const VCardEmulOptions default_options = { + .nss_db = NULL, + .vreader = NULL, + .vreader_count = 0, + .hw_card_type = VCARD_EMUL_CAC, + .hw_type_params = "", + .use_hw = PR_TRUE +}; + + +/* + * NSS needs the app to supply a password prompt. In our case the only time + * the password is supplied is as part of the Login APDU. The actual password + * is passed in the pw_arg in that case. In all other cases pw_arg should be + * NULL. + */ +static char * +vcard_emul_get_password(PK11SlotInfo *slot, PRBool retries, void *pw_arg) +{ + /* if it didn't work the first time, don't keep trying */ + if (retries) { + return NULL; + } + /* we are looking up a password when we don't have one in hand */ + if (pw_arg == NULL) { + return NULL; + } + /* TODO: we really should verify that were are using the right slot */ + return PORT_Strdup(pw_arg); +} + +/* Force a card removal even if the card is not physically removed */ +VCardEmulError +vcard_emul_force_card_remove(VReader *vreader) +{ + if (!nss_emul_init || (vreader_card_is_present(vreader) != VREADER_OK)) { + return VCARD_EMUL_FAIL; /* card is already removed */ + } + + /* OK, remove it */ + vreader_insert_card(vreader, NULL); + return VCARD_EMUL_OK; +} + +/* Re-insert of a card that has been removed by force removal */ +VCardEmulError +vcard_emul_force_card_insert(VReader *vreader) +{ + VReaderEmul *vreader_emul; + VCard *vcard; + + if (!nss_emul_init || (vreader_card_is_present(vreader) == VREADER_OK)) { + return VCARD_EMUL_FAIL; /* card is already removed */ + } + vreader_emul = vreader_get_private(vreader); + + /* if it's a softcard, get the saved vcard from the reader emul structure */ + if (vreader_emul->saved_vcard) { + vcard = vcard_reference(vreader_emul->saved_vcard); + } else { + /* it must be a physical card, rebuild it */ + if (!PK11_IsPresent(vreader_emul->slot)) { + /* physical card has been removed, not way to reinsert it */ + return VCARD_EMUL_FAIL; + } + vcard = vcard_emul_mirror_card(vreader); + } + vreader_insert_card(vreader, vcard); + vcard_free(vcard); + + return VCARD_EMUL_OK; +} + +/* Previously we returned FAIL if no readers found. This makes + * no sense when using hardware, since there may be no readers connected + * at the time vcard_emul_init is called, but they will be properly + * recognized later. So Instead return FAIL only if no_hw==1 and no + * vcards can be created (indicates error with certificates provided + * or db), or if any other higher level error (NSS error, missing coolkey). */ +static int vcard_emul_init_called = 0; + +VCardEmulError +vcard_emul_init(const VCardEmulOptions *options) +{ + SECStatus rv; + PRBool ret, has_readers=PR_FALSE, need_module; + VReader *vreader; + VReaderEmul *vreader_emul; + SECMODListLock *module_lock; + SECMODModuleList *module_list; + SECMODModuleList *mlp; + int i; + + if (vcard_emul_init_called) { + return VCARD_EMUL_INIT_ALREADY_INITED; + } + vcard_emul_init_called = 1; + vreader_init(); + vevent_queue_init(); + + if (options == NULL) { + options = &default_options; + } + + /* first initialize NSS */ + if (options->nss_db) { + rv = NSS_Init(options->nss_db); + } else { + rv = NSS_Init("sql:/etc/pki/nssdb"); + } + if (rv != SECSuccess) { + return VCARD_EMUL_FAIL; + } + /* Set password callback function */ + PK11_SetPasswordFunc(vcard_emul_get_password); + + /* set up soft cards emulated by software certs rather than physical cards + * */ + for (i = 0; i < options->vreader_count; i++) { + int j; + int cert_count; + unsigned char **certs; + int *cert_len; + VCardKey **keys; + PK11SlotInfo *slot; + + slot = PK11_FindSlotByName(options->vreader[i].name); + if (slot == NULL) { + continue; + } + vreader_emul = vreader_emul_new(slot, options->vreader[i].card_type, + options->vreader[i].type_params); + vreader = vreader_new(options->vreader[i].vname, vreader_emul, + vreader_emul_delete); + vreader_add_reader(vreader); + cert_count = options->vreader[i].cert_count; + + ret = vcard_emul_alloc_arrays(&certs, &cert_len, &keys, + options->vreader[i].cert_count); + if (ret == PR_FALSE) { + continue; + } + cert_count = 0; + for (j=0; j < options->vreader[i].cert_count; j++) { + /* we should have a better way of identifying certs than by + * nickname here */ + CERTCertificate *cert = PK11_FindCertFromNickname( + options->vreader[i].cert_name[j], + NULL); + if (cert == NULL) { + continue; + } + certs[cert_count] = cert->derCert.data; + cert_len[cert_count] = cert->derCert.len; + keys[cert_count] = vcard_emul_make_key(slot, cert); + /* this is safe because the key is still holding a cert reference */ + CERT_DestroyCertificate(cert); + cert_count++; + } + if (cert_count) { + VCard *vcard = vcard_emul_make_card(vreader, certs, cert_len, + keys, cert_count); + vreader_insert_card(vreader, vcard); + vcard_emul_init_series(vreader, vcard); + /* allow insertion and removal of soft cards */ + vreader_emul->saved_vcard = vcard_reference(vcard); + vcard_free(vcard); + vreader_free(vreader); + has_readers = PR_TRUE; + } + } + + /* if we aren't suppose to use hw, skip looking up hardware tokens */ + if (!options->use_hw) { + nss_emul_init = has_readers; + return has_readers ? VCARD_EMUL_OK : VCARD_EMUL_FAIL; + } + + /* make sure we have some PKCS #11 module loaded */ + module_lock = SECMOD_GetDefaultModuleListLock(); + module_list = SECMOD_GetDefaultModuleList(); + need_module = !has_readers; + SECMOD_GetReadLock(module_lock); + for (mlp = module_list; mlp; mlp = mlp->next) { + SECMODModule * module = mlp->module; + if (SECMOD_HasRemovableSlots(module)) { + need_module = PR_FALSE; + break; + } + } + SECMOD_ReleaseReadLock(module_lock); + + if (need_module) { + SECMODModule *module; + module = SECMOD_LoadUserModule( + (char*)"library=libcoolkeypk11.so name=Coolkey", + NULL, PR_FALSE); + if (module == NULL) { + return VCARD_EMUL_FAIL; + } + SECMOD_DestroyModule(module); /* free our reference, Module will still + * be on the list. + * until we destroy it */ + } + + /* now examine all the slots, finding which should be readers */ + /* We should control this with options. For now we mirror out any + * removable hardware slot */ + default_card_type = options->hw_card_type; + default_type_params = strdup(options->hw_type_params); + + SECMOD_GetReadLock(module_lock); + for (mlp = module_list; mlp; mlp = mlp->next) { + SECMODModule * module = mlp->module; + PRBool has_emul_slots = PR_FALSE; + + if (module == NULL) { + continue; + } + + for (i=0; i < module->slotCount; i++) { + PK11SlotInfo *slot = module->slots[i]; + + /* only map removable HW slots */ + if (slot == NULL || !PK11_IsRemovable(slot) || !PK11_IsHW(slot)) { + continue; + } + vreader_emul = vreader_emul_new(slot, options->hw_card_type, + options->hw_type_params); + vreader = vreader_new(PK11_GetSlotName(slot), vreader_emul, + vreader_emul_delete); + vreader_add_reader(vreader); + + has_readers = PR_TRUE; + has_emul_slots = PR_TRUE; + + if (PK11_IsPresent(slot)) { + VCardEmulType type; + VCard *vcard; + type = vcard_emul_get_type(vreader); + vcard = vcard_emul_mirror_card(vreader); + vreader_insert_card(vreader, vcard); + vcard_emul_init_series(vreader, vcard); + vcard_free(vcard); + } + } + if (has_emul_slots) { + vcard_emul_new_event_thread(module); + } + } + SECMOD_ReleaseReadLock(module_lock); + nss_emul_init = has_readers; + + return VCARD_EMUL_OK; +} + +/* Recreate card insert events for all readers (user should + * deduce implied reader insert. perhaps do a reader insert as well?) + */ +void +vcard_emul_replay_insertion_events(void) +{ + VReaderListEntry *current_entry; + VReaderListEntry *next_entry = NULL; + VReaderList *list = vreader_get_reader_list(); + + for (current_entry= vreader_list_get_first(list); current_entry; + current_entry=next_entry) { + VReader *vreader = vreader_list_get_reader(current_entry); + next_entry = vreader_list_get_next(current_entry); + vreader_queue_card_event(vreader); + } +} + +/* + * Silly little functions to help parsing our argument string + */ +static char * +copy_string(const char *str, int str_len) +{ + char *new_str; + + new_str = malloc(str_len+1); + memcpy(new_str, str, str_len); + new_str[str_len] = 0; + return new_str; +} + +static int +count_tokens(const char *str, char token, char token_end) +{ + int count = 0; + + for (;*str;str++) { + if (*str == token) { + count++; + } + if (*str == token_end) { + break; + } + } + return count; +} + +static const char * +find_token(const char *str, char token, char token_end) +{ + /* just do the blind simple thing */ + for (;*str;str++) { + if ((*str == token) || (*str == token_end)) { + break; + } + } + return str; +} + +static const char * +strip(const char *str) +{ + for(;*str; str++) { + if ((*str != ' ') && (*str != '\n') && + (*str != '\t') && (*str != '\r')) { + break; + } + } + return str; +} + +static const char * +find_blank(const char *str) +{ + for(;*str; str++) { + if ((*str == ' ') || (*str == '\n') || + (*str == '\t') || (*str == '\r')) { + break; + } + } + return str; +} + + +/* + * We really want to use some existing argument parsing library here. That + * would give us a consistant look */ +static VCardEmulOptions options; +#define READER_STEP 4 + +VCardEmulOptions * +vcard_emul_options(const char *args) +{ + int reader_count = 0; + VCardEmulOptions *opts; + char type_str[100]; + int type_len; + + /* Allow the future use of allocating the options structure on the fly */ + memcpy(&options, &default_options, sizeof(options)); + opts = &options; + + do { + args = strip(args); /* strip off the leading spaces */ + if (*args == ',') { + continue; + } + /* soft=(slot_name,virt_name,emul_type,emul_flags,cert_1, (no eol) + * cert_2,cert_3...) */ + if (strncmp(args,"soft=",5) == 0) { + const char *name; + const char *vname; + const char *type_params; + VCardEmulType type; + int name_length, vname_length, type_params_length, count, i; + VirtualReaderOptions *vreaderOpt = NULL; + + args = strip(args+5); + if (*args != '(') { + continue; + } + name = args; + args = find_token(args+1,',',')'); + if (*args == 0) { + break; + } + if (*args == ')') { + args++; + continue; + } + args = strip(args+1); + name_length = args - name - 2; + vname = args; + args = find_token(args+1,',',')'); + if (*args == 0) { + break; + } + if (*args == ')') { + args++; + continue; + } + vname_length = args - name - 2; + args = strip(args+1); + type_len = find_token(args,',',')') - args; + assert(sizeof(type_str) > type_len); + strncpy(type_str, args, type_len); + type_str[type_len] = 0; + type = vcard_emul_type_from_string(type_str); + args = find_token(args,',',')'); + if (*args == 0) { + break; + } + if (*args == ')') { + args++; + continue; + } + args = strip(args++); + type_params=args; + args = find_token(args+1,',',')'); + if (*args == 0) { + break; + } + if (*args == ')') { + args++; + continue; + } + type_params_length = args - name; + args = strip(args++); + if (*args == 0) { + break; + } + + if (opts->vreader_count >= reader_count) { + reader_count += READER_STEP; + vreaderOpt = realloc(opts->vreader, + reader_count*sizeof(*vreaderOpt)); + if (vreaderOpt == NULL) { + return opts; /* we're done */ + } + } + opts->vreader = vreaderOpt; + vreaderOpt = &vreaderOpt[opts->vreader_count]; + vreaderOpt->name = copy_string(name, name_length); + vreaderOpt->vname = copy_string(vname, vname_length); + vreaderOpt->card_type = type; + vreaderOpt->type_params = copy_string(name, name_length); + count = count_tokens(args,',',')'); + vreaderOpt->cert_count = count; + vreaderOpt->cert_name = (char **)malloc(count*sizeof(char *)); + for (i=0; i < count; i++) { + const char *cert = args + 1; + args = find_token(args + 1, ',', ')'); + vreaderOpt->cert_name[i] = copy_string(cert, args - cert); + } + if (*args == ')') { + args++; + } + opts->vreader_count++; + /* use_hw= */ + } else if (strncmp(args,"use_hw=",7) == 0) { + args = strip(args+7); + if (*args == '0' || *args == 'N' || *args == 'n' || *args == 'F') { + opts->use_hw = PR_FALSE; + } else { + opts->use_hw = PR_TRUE; + } + args = find_blank(args); + /* hw_type= */ + } else if (strncmp(args,"hw_type=",8) == 0) { + args = strip(args+8); + opts->hw_card_type = vcard_emul_type_from_string(args); + args = find_blank(args); + /* hw_params= */ + } else if (strncmp(args,"hw_params=",10) == 0) { + const char *params; + args = strip(args+10); + params= args; + args = find_blank(args); + opts->hw_type_params = copy_string(params, args-params); + /* db="/data/base/path" */ + } else if (strncmp(args,"db=",3) == 0) { + const char *db; + args = strip(args+3); + if (*args != '"') { + continue; + } + args++; + db = args; + args = find_token(args, '"', '\n'); + opts->nss_db = copy_string(db,args-db); + if (*args != 0) { + args++; + } + } else args = find_blank(args); + } while (*args != 0); + + return opts; +} + +void +vcard_emul_usage(void) +{ + fprintf(stderr, +"emul args: comma separated list of the following arguments\n" +" db={nss_database} (default sql:/etc/pki/nssdb)\n" +" use_hw=[yes|no] (default yes)\n" +" hw_type={card_type_to_emulate} (default CAC)\n" +" hw_param={param_for_card} (default \"\")\n" +" soft=({slot_name},{vreader_name},{card_type_to_emulate},{params_for_card},\n" +" {cert1},{cert2},{cert3} (default none)\n" +"\n" +" {nss_database} The location of the NSS cert & key database\n" +" {card_type_to_emulate} What card interface to present to the guest\n" +" {param_for_card} Card interface specific parameters\n" +" {slot_name} NSS slot that contains the certs\n" +" {vreader_name} Virutal reader name to present to the guest\n" +" {certN} Nickname of the certificate n on the virtual card\n" +"\n" +"These parameters come as a single string separated by blanks or newlines." +"\n" +"Unless use_hw is set to no, all tokens that look like removable hardware\n" +"tokens will be presented to the guest using the emulator specified by \n" +"hw_type, and parameters of hw_param.\n" +"\n" +"If more one or more soft= parameters are specified, these readers will be\n" +"presented to the guest\n"); +} diff --git a/libcacard/vcard_emul_type.c b/libcacard/vcard_emul_type.c new file mode 100644 index 0000000..adbc54b --- /dev/null +++ b/libcacard/vcard_emul_type.c @@ -0,0 +1,60 @@ +/* + * This file contains utility functions which abstract the different card + * types. The goal is that new card types can easily be added by simply + * changing this file and vcard_emul_type.h. It is currently not a requirement + * to dynamically add new card types. + */ + +#include <strings.h> +#include "vcardt.h" +#include "vcard_emul_type.h" +#include "cac.h" +#include "passthru.h" + +VCardStatus vcard_init(VReader *vreader, VCard *vcard, + VCardEmulType type, const char * params, + unsigned char *const *cert, int cert_len[], + VCardKey *key[], int cert_count) +{ + switch (type) { + case VCARD_EMUL_NONE: + break; + case VCARD_EMUL_CAC: + return cac_card_init(vreader, vcard, params, + cert, cert_len, key, cert_count); +#ifdef USE_PASSTHRU + case VCARD_EMUL_PASSTHRU: + return passthru_card_init(vreader, vcard, params, + cert, cert_len, key, cert_count); +#endif + /* add new ones here */ + default: + break; + } + return VCARD_FAIL; +} + +VCardEmulType vcard_emul_type_select(VReader *vreader) +{ +#ifdef notdef + /* since there is only one emulator no need to call this function */ + if (cac_is_cac_card(vreader) == VCARD_DONE) { + return VCARD_EMUL_CAC; + } +#endif + /* return the default */ + return VCARD_EMUL_CAC; +} + +VCardEmulType vcard_emul_type_from_string(const char *type_string) +{ + if (strcasecmp(type_string,"CAC") == 0) { + return VCARD_EMUL_CAC; + } +#ifdef USE_PASSTHRU + if (strcasecmp(type_string,"PASSTHRU") == 0) { + return VCARD_EMUL_PASSTHRU; + } +#endif + return VCARD_EMUL_NONE; +} diff --git a/libcacard/vcard_emul_type.h b/libcacard/vcard_emul_type.h new file mode 100644 index 0000000..da15528 --- /dev/null +++ b/libcacard/vcard_emul_type.h @@ -0,0 +1,29 @@ +/* + * This header file abstracts the different card types. The goal is new card + * types can easily be added by simply changing this file and + * vcard_emul_type.c. It is currently not a requirement to dynamically add new + * card types. + */ + +#ifndef VCARD_EMUL_TYPE_H +#define VCARD_EMUL_TYPE_H 1 +#include "vcardt.h" +#include "vreadert.h" + +/* + * types + */ +typedef enum { + VCARD_EMUL_NONE =0, + VCARD_EMUL_CAC, + VCARD_EMUL_PASSTHRU +} VCardEmulType; + +/* functions used by the rest of the emulator */ +VCardStatus vcard_init(VReader *vreader, VCard *vcard, VCardEmulType type, + const char *params, unsigned char * const *cert, + int cert_len[], VCardKey *key[], int cert_count); +VCardEmulType vcard_emul_type_select(VReader *vreader); +VCardEmulType vcard_emul_type_from_string(const char *type_string); + +#endif diff --git a/libcacard/vcardt.h b/libcacard/vcardt.h new file mode 100644 index 0000000..e371451 --- /dev/null +++ b/libcacard/vcardt.h @@ -0,0 +1,66 @@ +/* + * + */ +#ifndef VCARDT_H +#define VCARDT_H 1 + +/* + * these should come from some common spice header file + */ +#include <assert.h> +#ifndef ASSERT +#define ASSERT assert +#endif +#ifndef MIN +#define MIN(x,y) ((x)>(y)?(y):(x)) +#define MAX(x,y) ((x)>(y)?(x):(y)) +#endif + +typedef struct VCardStruct VCard; +typedef struct VCardAPDUStruct VCardAPDU; +typedef struct VCardResponseStruct VCardResponse; +typedef struct VCardBufferResponseStruct VCardBufferResponse; +typedef struct VCardAppletStruct VCardApplet; +typedef struct VCardAppletPrivateStruct VCardAppletPrivate; +typedef struct VCardKeyStruct VCardKey; /* opaque */ +typedef struct VCardEmulStruct VCardEmul; + +#define MAX_CHANNEL 4 + +/* create an ATR with appropriate historical bytes */ +#define VCARD_ATR_PREFIX(size) 0x3b, 0x66+(size), 0x00, 0xff, \ + 'V', 'C', 'A', 'R', 'D', '_' + + +typedef enum { + VCARD_DONE, + VCARD_NEXT, + VCARD_FAIL +} VCardStatus; + +typedef enum { + VCARD_FILE_SYSTEM, + VCARD_VM, + VCARD_DIRECT +} VCardType; + +typedef enum { + VCARD_POWER_ON, + VCARD_POWER_OFF +} VCardPower; + +typedef VCardStatus (*VCardProcessAPDU)(VCard *card, VCardAPDU *apdu, + VCardResponse **response); +typedef VCardStatus (*VCardResetApplet)(VCard *card, int channel); +typedef void (*VCardAppletPrivateFree) (VCardAppletPrivate *); +typedef void (*VCardEmulFree) (VCardEmul *); +typedef void (*VCardGetAtr) (VCard *, unsigned char *atr, int *atr_len); + +struct VCardBufferResponseStruct { + unsigned char *buffer; + int buffer_len; + unsigned char *current; + int len; +}; + +#endif diff --git a/libcacard/vevent.h b/libcacard/vevent.h new file mode 100644 index 0000000..f202ea8 --- /dev/null +++ b/libcacard/vevent.h @@ -0,0 +1,26 @@ +/* + * + */ +#ifndef EVENT_H +#define EVENT_H 1 +#include "eventt.h" +#include "vreadert.h" +#include "vcardt.h" + +VEvent *vevent_new(VEventType type, VReader *reader, VCard *card); +void vevent_delete(VEvent *); + +/* + * VEvent queueing services + */ +void vevent_queue_vevent(VEvent *); +void vevent_queue_init(void); + +/* + * VEvent dequeing services + */ +VEvent *vevent_wait_next_vevent(void); +VEvent *vevent_get_next_vevent(void); + + +#endif diff --git a/libcacard/vreader.c b/libcacard/vreader.c new file mode 100644 index 0000000..5e46019 --- /dev/null +++ b/libcacard/vreader.c @@ -0,0 +1,526 @@ +/* + * emulate the reader + */ +#include "vcard.h" +#include "vcard_emul.h" +#include "card_7816.h" +#include "vreader.h" +#include "vevent.h" + +/* + * System includes + */ +#include <stdlib.h> +#include <string.h> + +/* + * spice includes + */ +#include "mutex.h" + +struct VReaderStruct { + int reference_count; + VCard *card; + char *name; + vreader_id_t id; + mutex_t lock; + VReaderEmul *reader_private; + VReaderEmulFree reader_private_free; +}; + +/* manage locking */ +static inline void +vreader_lock(VReader *reader) +{ + MUTEX_LOCK(reader->lock); +} + +static inline void +vreader_unlock(VReader *reader) +{ + MUTEX_UNLOCK(reader->lock); +} + +/* + * vreader constructor + */ +VReader * +vreader_new(const char *name, VReaderEmul *private, + VReaderEmulFree private_free) +{ + VReader *reader; + + reader = (VReader *)malloc(sizeof(VReader)); + if (reader == NULL) { + return NULL; + } + MUTEX_INIT(reader->lock); + reader->reference_count = 1; + reader->name = name ? strdup(name) : NULL; + reader->card = NULL; + reader->id = (vreader_id_t)-1; + reader->reader_private = private; + reader->reader_private_free = private_free; + return reader; +} + +/* get a reference */ +VReader* +vreader_reference(VReader *reader) +{ + if (reader == NULL) { + return NULL; + } + vreader_lock(reader); + reader->reference_count++; + vreader_unlock(reader); + return reader; +} + +/* free a reference */ +void +vreader_free(VReader *reader) +{ + if (reader == NULL) { + return; + } + vreader_lock(reader); + if (reader->reference_count-- > 1) { + vreader_unlock(reader); + return; + } + vreader_unlock(reader); + if (reader->card) { + vcard_free(reader->card); + } + if (reader->name) { + free(reader->name); + } + if (reader->reader_private_free) { + reader->reader_private_free(reader->reader_private); + } + free(reader); + return; +} + +static VCard * +vreader_get_card(VReader *reader) +{ + VCard *card; + + vreader_lock(reader); + card = vcard_reference(reader->card); + vreader_unlock(reader); + return card; +} + +VReaderStatus +vreader_card_is_present(VReader *reader) +{ + VCard *card = vreader_get_card(reader); + + if (card == NULL) { + return VREADER_NO_CARD; + } + vcard_free(card); + return VREADER_OK; +} + +vreader_id_t +vreader_get_id(VReader *reader) +{ + if (reader == NULL) { + return (vreader_id_t)-1; + } + return reader->id; +} + +VReaderStatus +vreader_set_id(VReader *reader, vreader_id_t id) +{ + if (reader == NULL) { + return VREADER_NO_CARD; + } + reader->id = id; + return VREADER_OK; +} + +const char * +vreader_get_name(VReader *reader) +{ + if (reader == NULL) { + return NULL; + } + return reader->name; +} + +VReaderEmul * +vreader_get_private(VReader *reader) +{ + return reader->reader_private; +} + +static VReaderStatus +vreader_reset(VReader *reader, VCardPower power, unsigned char *atr, int *len) +{ + VCard *card = vreader_get_card(reader); + + if (card == NULL) { + return VREADER_NO_CARD; + } + /* + * clean up our state + */ + vcard_reset(card, power); + if (atr) { + vcard_get_atr(card, atr, len); + } + vcard_free(card); /* free our reference */ + return VREADER_OK; +} + +VReaderStatus +vreader_power_on(VReader *reader, unsigned char *atr, int *len) +{ + return vreader_reset(reader, VCARD_POWER_ON, atr, len ); +} + +VReaderStatus +vreader_power_off(VReader *reader) +{ + return vreader_reset(reader, VCARD_POWER_OFF, NULL, 0); +} + + +VReaderStatus +vreader_xfr_bytes(VReader *reader, + unsigned char *send_buf, int send_buf_len, + unsigned char *receive_buf, int *receive_buf_len) +{ + VCardAPDU *apdu; + VCardResponse *response = NULL; + VCardStatus card_status; + unsigned short status; + VCard *card = vreader_get_card(reader); + + if (card == NULL) { + return VREADER_NO_CARD; + } + + apdu = vcard_apdu_new(send_buf, send_buf_len, &status); + if (apdu == NULL) { + response = vcard_make_response(status); + card_status = VCARD_DONE; + } else { + card_status = vcard_process_apdu(card, apdu, &response); + } + ASSERT(card_status == VCARD_DONE); + if (card_status == VCARD_DONE) { + int size = MIN(*receive_buf_len, response->b_total_len); + memcpy(receive_buf, response->b_data, size); + *receive_buf_len = size; + } + vcard_response_delete(response); + vcard_apdu_delete(apdu); + vcard_free(card); /* free our reference */ + return VREADER_OK; +} + +struct VReaderListStruct { + VReaderListEntry *head; + VReaderListEntry *tail; +}; + +struct VReaderListEntryStruct { + VReaderListEntry *next; + VReaderListEntry *prev; + VReader *reader; +}; + + +static VReaderListEntry * +vreader_list_entry_new(VReader *reader) +{ + VReaderListEntry *new_reader_list_entry; + + new_reader_list_entry = (VReaderListEntry *) + malloc(sizeof(VReaderListEntry)); + if (new_reader_list_entry == NULL) { + return NULL; + } + new_reader_list_entry->next = NULL; + new_reader_list_entry->prev = NULL; + new_reader_list_entry->reader = vreader_reference(reader); + return new_reader_list_entry; +} + +static void +vreader_list_entry_delete(VReaderListEntry *entry) +{ + if (entry == NULL) { + return; + } + vreader_free(entry->reader); + free(entry); +} + + +static VReaderList * +vreader_list_new(void) +{ + VReaderList *new_reader_list; + + new_reader_list = (VReaderList *)malloc(sizeof(VReaderList)); + if (new_reader_list == NULL) { + return NULL; + } + new_reader_list->head = NULL; + new_reader_list->tail = NULL; + return new_reader_list; +} + +void +vreader_list_delete(VReaderList *list) +{ + VReaderListEntry *current_entry; + VReaderListEntry *next_entry = NULL; + for (current_entry= vreader_list_get_first(list); current_entry; + current_entry=next_entry) { + next_entry = vreader_list_get_next(current_entry); + vreader_list_entry_delete(current_entry); + } + list->head = NULL; + list->tail = NULL; + free(list); +} + + +VReaderListEntry * +vreader_list_get_first(VReaderList *list) +{ + return list ? list->head : NULL; +} + +VReaderListEntry * +vreader_list_get_next(VReaderListEntry *current) +{ + return current ? current->next : NULL; +} + +VReader * +vreader_list_get_reader(VReaderListEntry *entry) +{ + return entry ? vreader_reference(entry->reader) : NULL; +} + +static void +vreader_queue(VReaderList *list, VReaderListEntry *entry) +{ + if (entry == NULL) { + return; + } + entry->next = NULL; + entry->prev = list->tail; + if (list->head) { + list->tail->next = entry; + } else { + list->head = entry; + } + list->tail = entry; +} + +static void +vreader_dequeue(VReaderList *list, VReaderListEntry *entry) +{ + if (entry == NULL) { + return; + } + if (entry->next == NULL) { + list->tail = entry->prev; + } else if (entry->prev == NULL) { + list->head = entry->next; + } else { + entry->prev->next = entry->next; + entry->next->prev = entry->prev; + } + if ((list->tail == NULL) || (list->head == NULL)) { + list->head = list->tail = NULL; + } + entry->next = entry->prev = NULL; +} + +static VReaderList *vreader_list = NULL; +static mutex_t vreader_list_mutex; + +static void +vreader_list_init(void) +{ + vreader_list = vreader_list_new(); + MUTEX_INIT(vreader_list_mutex); +} + +static void +vreader_list_lock(void) +{ + MUTEX_LOCK(vreader_list_mutex); +} + +static void +vreader_list_unlock(void) +{ + MUTEX_UNLOCK(vreader_list_mutex); +} + +static VReaderList * +vreader_copy_list(VReaderList *list) +{ + VReaderList *new_list = NULL; + VReaderListEntry *current_entry = NULL; + + new_list = vreader_list_new(); + if (new_list == NULL) { + return NULL; + } + for (current_entry= vreader_list_get_first(list); current_entry; + current_entry=vreader_list_get_next(current_entry)) { + VReader *reader = vreader_list_get_reader(current_entry); + VReaderListEntry *new_entry = vreader_list_entry_new(reader); + + vreader_free(reader); + vreader_queue(new_list, new_entry); + } + return new_list; +} + +VReaderList * +vreader_get_reader_list(void) +{ + VReaderList *new_reader_list; + + vreader_list_lock(); + new_reader_list = vreader_copy_list(vreader_list); + vreader_list_unlock(); + return new_reader_list; +} + +VReader * +vreader_get_reader_by_id(vreader_id_t id) +{ + VReader *reader = NULL; + VReaderListEntry *current_entry = NULL; + + if (id == (vreader_id_t) -1) { + return NULL; + } + + vreader_list_lock(); + for (current_entry = vreader_list_get_first(vreader_list); current_entry; + current_entry = vreader_list_get_next(current_entry)) { + VReader *creader = vreader_list_get_reader(current_entry); + if (creader->id == id) { + reader = creader; + break; + } + vreader_free(creader); + } + vreader_list_unlock(); + return reader; +} + +VReader * +vreader_get_reader_by_name(const char *name) +{ + VReader *reader = NULL; + VReaderListEntry *current_entry = NULL; + + vreader_list_lock(); + for (current_entry = vreader_list_get_first(vreader_list); current_entry; + current_entry = vreader_list_get_next(current_entry)) { + VReader *creader = vreader_list_get_reader(current_entry); + if (strcmp(creader->name, name) == 0) { + reader = creader; + break; + } + vreader_free(creader); + } + vreader_list_unlock(); + return reader; +} + +/* called from card_emul to initialize the readers */ +VReaderStatus +vreader_add_reader(VReader *reader) +{ + VReaderListEntry *reader_entry; + + reader_entry = vreader_list_entry_new(reader); + if (reader_entry == NULL) { + return VREADER_OUT_OF_MEMORY; + } + vreader_list_lock(); + vreader_queue(vreader_list, reader_entry); + vreader_list_unlock(); + vevent_queue_vevent(vevent_new(VEVENT_READER_INSERT, reader, NULL)); + return VREADER_OK; +} + + +VReaderStatus +vreader_remove_reader(VReader *reader) +{ + VReaderListEntry *current_entry; + + vreader_list_lock(); + for (current_entry= vreader_list_get_first(vreader_list); current_entry; + current_entry=vreader_list_get_next(current_entry)) { + if (current_entry->reader == reader) { + break; + } + } + vreader_dequeue(vreader_list, current_entry); + vreader_list_unlock(); + vreader_list_entry_delete(current_entry); + vevent_queue_vevent(vevent_new(VEVENT_READER_REMOVE, reader, NULL)); + return VREADER_OK; +} + +/* + * Generate VEVENT_CARD_INSERT or VEVENT_CARD_REMOVE based on vreader + * state. Separated from vreader_insert_card to allow replaying events + * for a given state. + */ +void +vreader_queue_card_event(VReader *reader) +{ + vevent_queue_vevent(vevent_new( + reader->card ? VEVENT_CARD_INSERT : VEVENT_CARD_REMOVE, reader, + reader->card)); +} + +/* + * insert/remove a new card. for removal, card == NULL + */ +VReaderStatus +vreader_insert_card(VReader *reader, VCard *card) +{ + vreader_lock(reader); + if (reader->card) { + /* decrement reference count */ + vcard_free(reader->card); + reader->card = NULL; + } + reader->card = vcard_reference(card); + vreader_unlock(reader); + vreader_queue_card_event(reader); + return VREADER_OK; +} + +/* + * initialize all the static reader structures + */ +void +vreader_init(void) +{ + vreader_list_init(); +} + diff --git a/libcacard/vreader.h b/libcacard/vreader.h new file mode 100644 index 0000000..c7054da --- /dev/null +++ b/libcacard/vreader.h @@ -0,0 +1,54 @@ +/* + * + */ + +#ifndef VREADER_H +#define VREADER_H 1 + +#include "eventt.h" +#include "vreadert.h" +#include "vcardt.h" + +/* + * calls for reader front end + */ +VReaderStatus vreader_power_on(VReader *reader, unsigned char *atr, int *len); +VReaderStatus vreader_power_off(VReader *reader); +VReaderStatus vreader_xfr_bytes(VReader *reader, unsigned char *send_buf, + int send_buf_len, unsigned char *receive_buf, + int *receive_buf_len); + +/* constructor */ +VReader *vreader_new(const char *readerName, VReaderEmul *emul_private, + VReaderEmulFree private_free); +/* get a new reference to a reader */ +VReader *vreader_reference(VReader *reader); +/* "destructor" (readers are reference counted) */ +void vreader_free(VReader *reader); + +/* accessors */ +VReaderEmul *vreader_get_private(VReader *); +VReaderStatus vreader_card_is_present(VReader *reader); +void vreader_queue_card_event(VReader *reader); +const char *vreader_get_name(VReader *reader); +vreader_id_t vreader_get_id(VReader *reader); +VReaderStatus vreader_set_id(VReader *reader, vreader_id_t id); + +/* list operations */ +VReaderList *vreader_get_reader_list(void); +void vreader_list_delete(VReaderList *list); +VReader *vreader_list_get_reader(VReaderListEntry *entry); +VReaderListEntry *vreader_list_get_first(VReaderList *list); +VReaderListEntry *vreader_list_get_next(VReaderListEntry *list); +VReader *vreader_get_reader_by_id(vreader_id_t id); +VReader *vreader_get_reader_by_name(const char *name); + +/* + * list tools for vcard_emul + */ +void vreader_init(void); +VReaderStatus vreader_add_reader(VReader *reader); +VReaderStatus vreader_remove_reader(VReader *reader); +VReaderStatus vreader_insert_card(VReader *reader, VCard *card); + +#endif diff --git a/libcacard/vreadert.h b/libcacard/vreadert.h new file mode 100644 index 0000000..4c01259 --- /dev/null +++ b/libcacard/vreadert.h @@ -0,0 +1,23 @@ +/* + * + */ + +#ifndef VREADERT_H +#define VREADERT_H 1 + +typedef enum { + VREADER_OK=0, + VREADER_NO_CARD, + VREADER_OUT_OF_MEMORY +} VReaderStatus; + +typedef unsigned int vreader_id_t; +typedef struct VReaderStruct VReader; +typedef struct VReaderListStruct VReaderList; +typedef struct VReaderListEntryStruct VReaderListEntry; + +typedef struct VReaderEmulStruct VReaderEmul; +typedef void (*VReaderEmulFree)(VReaderEmul *); + +#endif + diff --git a/libcacard/vscclient.c b/libcacard/vscclient.c new file mode 100644 index 0000000..4dd8a35 --- /dev/null +++ b/libcacard/vscclient.c @@ -0,0 +1,710 @@ +#include <sys/types.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +#include <sys/types.h> +#include <sys/socket.h> +#include <netdb.h> +#include <netinet/in.h> + +#include "vscard_common.h" + +#include "vreader.h" +#include "vcard_emul.h" +#include "vevent.h" +#include "passthru.h" + +#include "mutex.h" + +int verbose = 0; + +int sock; + +static void +print_byte_array ( + uint8_t *arrBytes, + unsigned int nSize +) { + int i; + for (i=0; i < nSize; i++) { + printf ("%02X ", arrBytes[i]); + } + printf ("\n"); +} + +static void +print_usage (void) { + printf ("vscclient [-c <certname> .. -e <emul_args> -d <level>%s] " + "<host> <port> \n", +#ifdef USE_PASSTHRU + " -p"); + printf (" -p use passthrough mode\n"); +#else + ""); +#endif + vcard_emul_usage(); +} + +static char* +ip_numeric_to_char( + uint32_t ip +) { + char buf[4*4]; + + sprintf(buf, "%d.%d.%d.%d", (ip & 0xff000000) >> 24, (ip & 0xff0000) >> 16, + (ip & 0xff00) >> 8, ip & 0xff); + return strdup(buf); +} + +static mutex_t write_lock; + +static int +send_msg ( + VSCMsgType type, + uint32_t reader_id, + const void *msg, + unsigned int length +) { + int rv; + VSCMsgHeader mhHeader; + + MUTEX_LOCK(write_lock); + + if (verbose > 10) { + printf("sending type=%d id=%d, len =%d (0x%x)\n", + type, reader_id, length, length); + } + + mhHeader.type = type; + mhHeader.reader_id = 0; + mhHeader.length = length; + rv = write ( + sock, + &mhHeader, + sizeof (mhHeader) + ); + if (rv < 0) { + /* Error */ + printf ("write header error\n"); + close (sock); + MUTEX_UNLOCK(write_lock); + return (16); + } + rv = write ( + sock, + msg, + length + ); + if (rv < 0) { + /* Error */ + printf ("write error\n"); + close (sock); + MUTEX_UNLOCK(write_lock); + return (16); + } + MUTEX_UNLOCK(write_lock); + + return (0); +} + +static VReader *pending_reader = NULL; +static mutex_t pending_reader_lock; +static condition_t pending_reader_condition; + +#define MAX_ATR_LEN 40 +static void * +event_thread(void *arg) +{ + unsigned char atr[ MAX_ATR_LEN]; + int atr_len = MAX_ATR_LEN; + VEvent *event = NULL; + unsigned int reader_id; + + + while (1) { + const char *reader_name; + + event = vevent_wait_next_vevent(); + if (event == NULL) { + break; + } + reader_id = vreader_get_id(event->reader); + if (reader_id == VSCARD_UNDEFINED_READER_ID && + event->type != VEVENT_READER_INSERT) { + /* ignore events from readers qemu has rejected */ + /* if qemu is still deciding on this reader, wait to see if need to + * forward this event */ + MUTEX_LOCK(pending_reader_lock); + if (!pending_reader || (pending_reader != event->reader)) { + /* wasn't for a pending reader, this reader has already been + * rejected by qemu */ + MUTEX_UNLOCK(pending_reader_lock); + vevent_delete(event); + continue; + } + /* this reader hasn't been told it's status from qemu yet, wait for + * that status */ + while (pending_reader != NULL) { + CONDITION_WAIT(pending_reader_condition,pending_reader_lock); + } + MUTEX_UNLOCK(pending_reader_lock); + /* now recheck the id */ + reader_id = vreader_get_id(event->reader); + if (reader_id == VSCARD_UNDEFINED_READER_ID) { + /* this reader was rejected */ + vevent_delete(event); + continue; + } + /* reader was accepted, now forward the event */ + } + switch (event->type) { + case VEVENT_READER_INSERT: + /* tell qemu to insert a new CCID reader */ + /* wait until qemu has responded to our first reader insert + * before we send a second. That way we won't confuse the responses + * */ + MUTEX_LOCK(pending_reader_lock); + while (pending_reader != NULL) { + CONDITION_WAIT(pending_reader_condition,pending_reader_lock); + } + pending_reader = vreader_reference(event->reader); + MUTEX_UNLOCK(pending_reader_lock); + reader_name = vreader_get_name(event->reader); + if (verbose > 10) { + printf (" READER INSERT: %s\n", reader_name); + } + send_msg ( + VSC_ReaderAdd, + reader_id, /* currerntly VSCARD_UNDEFINED_READER_ID */ + NULL, 0 + /*reader_name, + strlen(reader_name) */ + ); + + break; + case VEVENT_READER_REMOVE: + /* future, tell qemu that an old CCID reader has been removed */ + if (verbose > 10) { + printf (" READER REMOVE: %d \n", reader_id); + } + send_msg( + VSC_ReaderRemove, + reader_id, + NULL, + 0 + ); + break; + case VEVENT_CARD_INSERT: + /* get the ATR (intended as a response to a power on from the + * reader */ + atr_len = MAX_ATR_LEN; + vreader_power_on(event->reader, atr, &atr_len); + /* ATR call functions as a Card Insert event */ + if (verbose > 10) { + printf (" CARD INSERT %d: ", reader_id); + print_byte_array (atr, atr_len); + } + send_msg ( + VSC_ATR, + reader_id, + atr, + atr_len + ); + break; + case VEVENT_CARD_REMOVE: + // Card removed + if (verbose > 10) { + printf (" CARD REMOVE %d: \n", reader_id); + } + send_msg ( + VSC_CardRemove, + reader_id, + NULL, + 0 + ); + break; + default: + break; + } + vevent_delete(event); + } + return NULL; +} + + +static unsigned int +get_id_from_string(char *string, unsigned int default_id) +{ + unsigned int id = atoi(string); + + /* don't accidentally swith to zero because no numbers have been supplied */ + if ((id == 0) && *string != '0') { + return default_id; + } + return id; +} + +static void +do_command(void) +{ + char inbuf[255]; + char *string; + VCardEmulError error; + static unsigned int default_reader_id = 0; + unsigned int reader_id; + VReader *reader = NULL; + + reader_id = default_reader_id; + string = fgets(inbuf, sizeof(inbuf), stdin); + if (string != NULL) { + if (strncmp(string,"exit",4) == 0) { + /* remove all the readers */ + VReaderList *list = vreader_get_reader_list(); + VReaderListEntry *reader_entry; + printf("Active Readers:\n"); + for (reader_entry = vreader_list_get_first(list); reader_entry; + reader_entry = vreader_list_get_next(reader_entry)) { + VReader *reader = vreader_list_get_reader(reader_entry); + vreader_id_t reader_id; + reader_id=vreader_get_id(reader); + if (reader_id == -1) { + continue; + } + /* be nice and signal card removal first (qemu probably should + * do this itself) */ + if (vreader_card_is_present(reader) == VREADER_OK) { + send_msg ( + VSC_CardRemove, + reader_id, + NULL, + 0 + ); + } + send_msg ( + VSC_ReaderRemove, + reader_id, + NULL, + 0 + ); + } + exit(0); + } else if (strncmp(string,"insert",6) == 0) { + if (string[6] == ' ') { + reader_id = get_id_from_string(&string[7], reader_id); + } + reader = vreader_get_reader_by_id(reader_id); + error = vcard_emul_force_card_insert(reader); + printf("insert %s, returned %d\n", reader ? vreader_get_name(reader) + : "invalid reader", error); + } else if (strncmp(string,"remove",6) == 0) { + if (string[6] == ' ') { + reader_id = get_id_from_string(&string[7], reader_id); + } + reader = vreader_get_reader_by_id(reader_id); + error = vcard_emul_force_card_remove(reader); + printf("remove %s, returned %d\n", reader ? vreader_get_name(reader) + : "invalid reader", error); + } else if (strncmp(string,"select",6) == 0) { + if (string[6] == ' ') { + reader_id = get_id_from_string(&string[7], + VSCARD_UNDEFINED_READER_ID); + } + if (reader_id != VSCARD_UNDEFINED_READER_ID) { + reader = vreader_get_reader_by_id(reader_id); + } + if (reader) { + printf("Selecting reader %d, %s\n", reader_id, + vreader_get_name(reader)); + default_reader_id = reader_id; + } else { + printf("Reader with id %d not found\n", reader_id); + } + } else if (strncmp(string,"debug",5) == 0) { + if (string[5] == ' ') { + verbose = get_id_from_string(&string[6],0); + } + printf ("debug level = %d\n", verbose); + } else if (strncmp(string,"list",4) == 0) { + VReaderList *list = vreader_get_reader_list(); + VReaderListEntry *reader_entry; + printf("Active Readers:\n"); + for (reader_entry = vreader_list_get_first(list); reader_entry; + reader_entry = vreader_list_get_next(reader_entry)) { + VReader *reader = vreader_list_get_reader(reader_entry); + vreader_id_t reader_id; + reader_id=vreader_get_id(reader); + if (reader_id == -1) { + continue; + } + printf("%3d %s %s\n",reader_id, + vreader_card_is_present(reader) == VREADER_OK ? + "CARD_PRESENT": " ", + vreader_get_name(reader)); + } + printf("Inactive Readers:\n"); + for (reader_entry = vreader_list_get_first(list); reader_entry; + reader_entry = vreader_list_get_next(reader_entry)) { + VReader *reader = vreader_list_get_reader(reader_entry); + vreader_id_t reader_id; + reader_id=vreader_get_id(reader); + if (reader_id != -1) { + continue; + } + + printf("INA %s %s\n", + vreader_card_is_present(reader) == VREADER_OK ? + "CARD_PRESENT": " ", + vreader_get_name(reader)); + } + } else if (*string != 0) { + printf("valid commands: \n"); + printf("insert [reader_id]\n"); + printf("remove [reader_id]\n"); + printf("select reader_id\n"); + printf("list\n"); + printf("debug [level]\n"); + printf("exit\n"); + } + } + vreader_free(reader); + printf("> "); + fflush(stdout); +} + + +#define APDUBufSize 270 + +// just for ease of parsing command line arguments. +#define MAX_CERTS 100 + +static int +connect_to_qemu ( + const char *ip, + uint32_t port +) { + struct addrinfo hints; + struct addrinfo* server; + int ret; + char port_str[10]; + + sock = socket ( + AF_INET, + SOCK_STREAM, + 0 + ); + if (sock < 0) { + // Error + printf ("Error opening socket!\n"); + } + + memset(&hints, 0, sizeof(struct addrinfo)); + hints.ai_family = AF_INET; + hints.ai_socktype = SOCK_STREAM; + hints.ai_flags = 0; + hints.ai_protocol = 0; /* Any protocol */ + snprintf(port_str, sizeof(port_str) - 1, "%d", port); + + ret = getaddrinfo(ip, port_str, &hints, &server); + + if (ret != 0) { + printf ("getaddrinfo failed\n"); + return (5); + } + + if (connect ( + sock, + server->ai_addr, + server->ai_addrlen + ) < 0 + ) { + // Error + printf ("Could not connect\n"); + return (5); + } + if (verbose) { + printf ("Connected (sizeof Header=%zd)!\n", sizeof (VSCMsgHeader)); + } + return sock; +} + +int +main ( + int argc, + char *argv[] +) { + char* qemu_ip; + uint16_t qemu_port; + VSCMsgHeader mhHeader; + VSCMsgError *error_msg; + + int rv; + int dwSendLength; + int dwRecvLength; + uint8_t pbRecvBuffer[APDUBufSize]; + uint8_t pbSendBuffer[APDUBufSize]; + VReaderStatus reader_status; + VReader *reader = NULL; + VCardEmulOptions *command_line_options = NULL; + pthread_t thread_id; + int passthru = 0; + + char* cert_names[MAX_CERTS]; + char* emul_args = NULL; + int cert_count = 0; + int c; + + while ((c = getopt(argc, argv, "c:e:pd:")) != -1) { + switch (c) { + case 'c': + if (cert_count >= MAX_CERTS) { + printf("too many certificates (max = %d)\n", MAX_CERTS); + exit (5); + } + cert_names[cert_count++] = optarg; + break; + case 'e': + emul_args = optarg; + break; + case 'p': +#ifdef USE_PASSTHRU + passthru = 1; +#else + print_usage(); + exit(4); +#endif + break; + case 'd': + verbose = get_id_from_string(optarg,1); + break; + } + } + + if (argc - optind != 2) { + print_usage(); + exit (4); + } + + if (!passthru && cert_count > 0) { + char *new_args; + int len, i; + /* if we've given some -c options, we clearly we want do so some + * software emulation. add that emulation now. this is NSS Emulator + * specific */ + if (emul_args == NULL) { + emul_args = (char*)"db=\"/etc/pki/nssdb\""; + } +#define SOFT_STRING ",soft=(,Virtual Reader,CAC,," + /* 2 == close paren & null */ + len = strlen(emul_args) + strlen(SOFT_STRING) + 2; + for (i=0; i < cert_count; i++) { + len +=strlen(cert_names[i])+1; /* 1 == comma */ + } + new_args = malloc(len); + strcpy(new_args,emul_args); + strcat(new_args,SOFT_STRING); + for (i=0; i < cert_count; i++) { + strcat(new_args,cert_names[i]); + strcat(new_args,","); + } + strcat(new_args,")"); + emul_args = new_args; + } + if (emul_args) { +#ifdef USE_PASSTHRU + command_line_options = passthru ? passthru_emul_options(emul_args) : +#else + command_line_options = +#endif + vcard_emul_options(emul_args); + } + + qemu_ip = strdup(argv[argc - 2]); + qemu_port = (uint16_t)atoi(argv[argc -1]); + sock = connect_to_qemu(qemu_ip, qemu_port); + + /* remove whatever reader might be left in qemu, + * in case of a unclean previous exit. */ + send_msg( + VSC_ReaderRemove, + VSCARD_MINIMAL_READER_ID, + NULL, + 0 + ); + + MUTEX_INIT(write_lock); + MUTEX_INIT(pending_reader_lock); + CONDITION_INIT(pending_reader_condition); + +#ifdef USE_PASSTHRU + if (passthru) { + passthru_emul_init(command_line_options); + } else +#endif + vcard_emul_init(command_line_options); + + /* launch the event_thread. This will trigger reader adds for all the + * existing readers */ + rv = pthread_create(&thread_id, NULL, event_thread, reader); + if (rv < 0) { + perror("pthread_create"); + exit (1); + } + + printf("> "); + fflush(stdout); + + do { + fd_set fds; + + FD_ZERO(&fds); + FD_SET(1,&fds); + FD_SET(sock,&fds); + + /* waiting on input from the socket */ + rv = select(sock+1, &fds, NULL, NULL, NULL); + if (rv < 0) { + /* handle error */ + perror("select"); + return (7); + } + if (FD_ISSET(1,&fds)) { + do_command(); + } + if (!FD_ISSET(sock,&fds)) { + continue; + } + + rv = read ( + sock, + &mhHeader, + sizeof (mhHeader) + ); + if (rv < sizeof(mhHeader)) { + /* Error */ + if (rv < 0) { + perror("header read error\n"); + } else { + printf ("header short read %d\n", rv); + } + return (8); + } + if (verbose) { + printf ("Header: type=%d, reader_id=%d length=%d (0x%x)\n", + mhHeader.type, mhHeader.reader_id, mhHeader.length, + mhHeader.length); + } + switch (mhHeader.type) { + case VSC_APDU: + rv = read ( + sock, + pbSendBuffer, + mhHeader.length + ); + if (rv < 0) { + /* Error */ + printf ("read error\n"); + close (sock); + return (8); + } + if (verbose) { + printf (" recv APDU: "); + print_byte_array (pbSendBuffer, mhHeader.length); + } + /* Transmit recieved APDU */ + dwSendLength = mhHeader.length; + dwRecvLength = sizeof(pbRecvBuffer); + reader = vreader_get_reader_by_id(mhHeader.reader_id); + reader_status = vreader_xfr_bytes(reader, + pbSendBuffer, dwSendLength, + pbRecvBuffer, &dwRecvLength); + if (reader_status == VREADER_OK) { + mhHeader.length = dwRecvLength; + if (verbose) { + printf (" send response: "); + print_byte_array (pbRecvBuffer, mhHeader.length); + } + send_msg ( + VSC_APDU, + mhHeader.reader_id, + pbRecvBuffer, + dwRecvLength + ); + } else { + rv = reader_status; /* warning: not meaningful */ + send_msg ( + VSC_Error, + mhHeader.reader_id, + &rv, + sizeof (uint32_t) + ); + } + vreader_free(reader); + reader = NULL; /* we've freed it, don't use it by accident + again */ + break; + case VSC_Reconnect: + { + VSCMsgReconnect reconnect; + + if (read(sock, (char*)&reconnect, mhHeader.length) < 0) { + printf ("read error\n"); + close (sock); + return (8); + } + if (reconnect.ip != 0) { + reconnect.ip = ntohl(reconnect.ip); + free(qemu_ip); + qemu_ip = ip_numeric_to_char(reconnect.ip); + qemu_port = reconnect.port; + } else { + printf("info: reconnect with no target ip:port: " + "bumping port by one and reconnecting\n"); + qemu_port = qemu_port + 1; + } + /* sent when qemu is migrating, we need to close the socket + * and reconnect. */ + close(sock); + printf("reconnecting to %s:%d\n", qemu_ip, qemu_port); + sock = connect_to_qemu(qemu_ip, qemu_port); + } + break; + case VSC_ReaderAddResponse: + MUTEX_LOCK(pending_reader_lock); + if (pending_reader) { + vreader_set_id(pending_reader, mhHeader.reader_id); + vreader_free(pending_reader); + pending_reader = NULL; + CONDITION_NOTIFY(pending_reader_condition); + } + MUTEX_UNLOCK(pending_reader_lock); + break; + case VSC_Error: + rv = read ( + sock, + pbSendBuffer, + mhHeader.length + ); + error_msg = (VSCMsgError *) pbSendBuffer; + printf("error: qemu refused to add reader\n"); + if (error_msg->code == VSC_CANNOT_ADD_MORE_READERS) { + /* clear pending reader, qemu can't handle any more */ + MUTEX_LOCK(pending_reader_lock); + if (pending_reader) { + pending_reader = NULL; + /* make sure the event loop doesn't hang */ + CONDITION_NOTIFY(pending_reader_condition); + } + MUTEX_UNLOCK(pending_reader_lock); + } + break; + default: + printf ("Default\n"); + return 0; + } + } while (rv >= 0); + + + return (0); +} -- 1.7.3.4 ^ permalink raw reply related [flat|nested] 35+ messages in thread
* Re: [Qemu-devel] [PATCH 3/7] libcacard: initial commit after coding style fixes 2011-01-11 8:42 ` [Qemu-devel] [PATCH 3/7] libcacard: initial commit after coding style fixes Alon Levy @ 2011-01-25 14:19 ` Anthony Liguori 0 siblings, 0 replies; 35+ messages in thread From: Anthony Liguori @ 2011-01-25 14:19 UTC (permalink / raw) To: Alon Levy; +Cc: qemu-devel On 01/11/2011 02:42 AM, Alon Levy wrote: > From: Robert Relyea<rrelyea@redhat.com> > > libcacard emulates a Common Access Card (CAC) which is a standard > for smartcards. It is used by the emulated ccid card introduced in > a following patch. Docs are available in docs/libcacard.txt > > Signed-off-by: Alon Levy<alevy@redhat.com> > I think importing the code like this is a reasonable place to start. Regards, Anthony Liguori ^ permalink raw reply [flat|nested] 35+ messages in thread
* [Qemu-devel] [PATCH 4/7] ccid: add ccid-card-emulated device (v2) 2011-01-11 8:42 [Qemu-devel] [PATCH 0/7] usb-ccid (v15) Alon Levy ` (2 preceding siblings ...) 2011-01-11 8:42 ` [Qemu-devel] [PATCH 3/7] libcacard: initial commit after coding style fixes Alon Levy @ 2011-01-11 8:42 ` Alon Levy 2011-01-25 14:21 ` Anthony Liguori 2011-01-11 8:42 ` [Qemu-devel] [PATCH 5/7] ccid: add docs Alon Levy ` (4 subsequent siblings) 8 siblings, 1 reply; 35+ messages in thread From: Alon Levy @ 2011-01-11 8:42 UTC (permalink / raw) To: qemu-devel This devices uses libcacard (internal) to emulate a smartcard conforming to the CAC standard. It attaches to the usb-ccid bus. Usage instructions (example command lines) are in the following patch in docs/ccid.txt. It uses libcacard which uses nss, so it can work with both hw cards and certificates (files). changes from v1: remove stale comments, use only c-style comments bugfix, forgot to set recv_len change reader name to 'Virtual Reader' Signed-off-by: Alon Levy <alevy@redhat.com> --- Makefile.objs | 2 +- hw/ccid-card-emulated.c | 534 +++++++++++++++++++++++++++++++++++++++++++++++ hw/ccid-card-passthru.c | 1 - 3 files changed, 535 insertions(+), 2 deletions(-) create mode 100644 hw/ccid-card-emulated.c diff --git a/Makefile.objs b/Makefile.objs index 6a0030b..303b642 100644 --- a/Makefile.objs +++ b/Makefile.objs @@ -197,7 +197,7 @@ hw-obj-$(CONFIG_FDC) += fdc.o hw-obj-$(CONFIG_ACPI) += acpi.o acpi_piix4.o hw-obj-$(CONFIG_APM) += pm_smbus.o apm.o hw-obj-$(CONFIG_DMA) += dma.o -hw-obj-$(CONFIG_SMARTCARD) += usb-ccid.o ccid-card-passthru.o +hw-obj-$(CONFIG_SMARTCARD) += usb-ccid.o ccid-card-passthru.o ccid-card-emulated.o # PPC devices hw-obj-$(CONFIG_OPENPIC) += openpic.o diff --git a/hw/ccid-card-emulated.c b/hw/ccid-card-emulated.c new file mode 100644 index 0000000..5531ce1 --- /dev/null +++ b/hw/ccid-card-emulated.c @@ -0,0 +1,534 @@ +/* + * CCID Card Device. Emulated card. + * + * It can be used to provide access to the local hardware in a non exclusive + * way, or it can use certificates. It requires the usb-ccid bus. + * + * Usage 1: standard, mirror hardware reader+card: + * qemu .. -usb -device usb-ccid -device ccid-card-emulated + * + * Usage 2: use certificates, no hardware required + * one time: create the certificates: + * for i in 1 2 3; do certutil -d /etc/pki/nssdb -x -t "CT,CT,CT" -S -s "CN=user$i" -n user$i; done + * qemu .. -usb -device usb-ccid -device ccid-card-emulated,cert1=user1,cert2=user2,cert3=user3 + * + * If you use a non default db for the certificates you can specify it using the db parameter. + * + * + * Copyright (c) 2010 Red Hat. + * Written by Alon Levy. + * + * This code is licenced under the LGPL. + */ + +#include <pthread.h> +#include <eventt.h> +#include <vevent.h> +#include <vreader.h> +#include <vcard_emul.h> +#include "qemu-char.h" +#include "monitor.h" +#include "hw/ccid.h" + +#define DPRINTF(card, lvl, fmt, ...) \ +do { if (lvl <= card->debug) { printf("ccid-card-emul: %s: " fmt , __func__, ## __VA_ARGS__); } } while (0) + +#define EMULATED_DEV_NAME "ccid-card-emulated" + +#define BACKEND_NSS_EMULATED "nss-emulated" /* the default */ +#define BACKEND_CERTIFICATES "certificates" + +typedef struct EmulatedState EmulatedState; + +enum { + EMUL_READER_INSERT = 0, + EMUL_READER_REMOVE, + EMUL_CARD_INSERT, + EMUL_CARD_REMOVE, + EMUL_GUEST_APDU, + EMUL_RESPONSE_APDU, + EMUL_ERROR, +}; + +static const char* emul_event_to_string(uint32_t emul_event) +{ + switch (emul_event) { + case EMUL_READER_INSERT: return "EMUL_READER_INSERT"; + case EMUL_READER_REMOVE: return "EMUL_READER_REMOVE"; + case EMUL_CARD_INSERT: return "EMUL_CARD_INSERT"; + case EMUL_CARD_REMOVE: return "EMUL_CARD_REMOVE"; + case EMUL_GUEST_APDU: return "EMUL_GUEST_APDU"; + case EMUL_RESPONSE_APDU: return "EMUL_RESPONSE_APDU"; + case EMUL_ERROR: return "EMUL_ERROR"; + default: + break; + } + return "UNKNOWN"; +} + +typedef struct EmulEvent { + QSIMPLEQ_ENTRY(EmulEvent) entry; + union { + struct { + uint32_t type; + } gen; + struct { + uint32_t type; + uint64_t code; + } error; + struct { + uint32_t type; + uint32_t len; + uint8_t data[]; + } data; + } p; +} EmulEvent; + +#define MAX_ATR_SIZE 40 +struct EmulatedState { + CCIDCardState base; + uint8_t debug; + char* backend; + char* cert1; + char* cert2; + char* cert3; + char* db; + uint8_t atr[MAX_ATR_SIZE]; + uint8_t atr_length; + QSIMPLEQ_HEAD(event_list, EmulEvent) event_list; + pthread_mutex_t event_list_mutex; + VReader *reader; + QSIMPLEQ_HEAD(guest_apdu_list, EmulEvent) guest_apdu_list; + pthread_mutex_t vreader_mutex; /* and guest_apdu_list mutex */ + pthread_mutex_t handle_apdu_mutex; + pthread_cond_t handle_apdu_cond; + int pipe[2]; + int quit_apdu_thread; + pthread_mutex_t apdu_thread_quit_mutex; + pthread_cond_t apdu_thread_quit_cond; +}; + +static void emulated_apdu_from_guest(CCIDCardState *base, const uint8_t *apdu, uint32_t len) +{ + EmulatedState *card = DO_UPCAST(EmulatedState, base, base); + EmulEvent *event = (EmulEvent*)malloc(sizeof(EmulEvent) + len); + + assert(event); + event->p.data.type = EMUL_GUEST_APDU; + event->p.data.len = len; + memcpy(event->p.data.data, apdu, len); + pthread_mutex_lock(&card->vreader_mutex); + QSIMPLEQ_INSERT_TAIL(&card->guest_apdu_list, event, entry); + pthread_mutex_unlock(&card->vreader_mutex); + pthread_mutex_lock(&card->handle_apdu_mutex); + pthread_cond_signal(&card->handle_apdu_cond); + pthread_mutex_unlock(&card->handle_apdu_mutex); +} + +static const uint8_t* emulated_get_atr(CCIDCardState *base, uint32_t *len) +{ + EmulatedState *card = DO_UPCAST(EmulatedState, base, base); + + *len = card->atr_length; + return card->atr; +} + +static void emulated_push_event(EmulatedState *card, EmulEvent *event) +{ + pthread_mutex_lock(&card->event_list_mutex); + QSIMPLEQ_INSERT_TAIL(&(card->event_list), event, entry); + pthread_mutex_unlock(&card->event_list_mutex); + if (write(card->pipe[1], card, 1) != 1) { + DPRINTF(card, 1, "write to pipe failed\n"); + } +} + +static void emulated_push_type(EmulatedState *card, uint32_t type) +{ + EmulEvent *event = (EmulEvent*)malloc(sizeof(EmulEvent)); + + assert(event); + event->p.gen.type = type; + emulated_push_event(card, event); +} + +static void emulated_push_error(EmulatedState *card, uint64_t code) +{ + EmulEvent *event = (EmulEvent*)malloc(sizeof(EmulEvent)); + + assert(event); + event->p.error.type = EMUL_ERROR; + event->p.error.code = code; + emulated_push_event(card, event); +} + +static void emulated_push_data_type(EmulatedState *card, uint32_t type, + const uint8_t *data, uint32_t len) +{ + EmulEvent *event = (EmulEvent*)malloc(sizeof(EmulEvent) + len); + + assert(event); + event->p.data.type = type; + event->p.data.len = len; + memcpy(event->p.data.data, data, len); + emulated_push_event(card, event); +} + +static void emulated_push_reader_insert(EmulatedState *card) +{ + emulated_push_type(card, EMUL_READER_INSERT); +} + +static void emulated_push_reader_remove(EmulatedState *card) +{ + emulated_push_type(card, EMUL_READER_REMOVE); +} + +static void emulated_push_card_insert(EmulatedState *card, + const uint8_t *atr, uint32_t len) +{ + emulated_push_data_type(card, EMUL_CARD_INSERT, atr, len); +} + +static void emulated_push_card_remove(EmulatedState *card) +{ + emulated_push_type(card, EMUL_CARD_REMOVE); +} + +static void emulated_push_response_apdu(EmulatedState *card, + const uint8_t* apdu, uint32_t len) +{ + emulated_push_data_type(card, EMUL_RESPONSE_APDU, apdu, len); +} + +#define APDU_BUF_SIZE 270 +static void *handle_apdu_thread(void* arg) +{ + EmulatedState *card = arg; + uint8_t recv_data[APDU_BUF_SIZE]; + int recv_len; + VReaderStatus reader_status; + EmulEvent *event; + + while (1) { + pthread_mutex_lock(&card->handle_apdu_mutex); + pthread_cond_wait(&card->handle_apdu_cond, &card->handle_apdu_mutex); + pthread_mutex_unlock(&card->handle_apdu_mutex); + if (card->quit_apdu_thread) { + card->quit_apdu_thread = 0; // debugging + break; + } + pthread_mutex_lock(&card->vreader_mutex); + while (!QSIMPLEQ_EMPTY(&card->guest_apdu_list)) { + event = QSIMPLEQ_FIRST(&card->guest_apdu_list); + assert((unsigned long)event > 1000); + QSIMPLEQ_REMOVE_HEAD(&card->guest_apdu_list, entry); + if (event->p.data.type != EMUL_GUEST_APDU) { + DPRINTF(card, 1, "unexpected message in handle_apdu_thread\n"); + free(event); + continue; + } + if (card->reader == NULL) { + DPRINTF(card, 1, "reader is NULL\n"); + free(event); + continue; + } + recv_len = sizeof(recv_data); + reader_status = vreader_xfr_bytes(card->reader, + event->p.data.data, event->p.data.len, + recv_data, &recv_len); + DPRINTF(card, 2, "got back apdu of length %d\n", recv_len); + if (reader_status == VREADER_OK) { + emulated_push_response_apdu(card, recv_data, recv_len); + } else { + emulated_push_error(card, reader_status); + } + free(event); + } + pthread_mutex_unlock(&card->vreader_mutex); + } + pthread_mutex_lock(&card->apdu_thread_quit_mutex); + pthread_cond_signal(&card->apdu_thread_quit_cond); + pthread_mutex_unlock(&card->apdu_thread_quit_mutex); + return NULL; +} + +static void *event_thread(void *arg) +{ + int atr_len = MAX_ATR_SIZE; + uint8_t atr[MAX_ATR_SIZE]; + VEvent *event = NULL; + EmulatedState *card = arg; + + while (1) { + const char *reader_name; + + event = vevent_wait_next_vevent(); + if (event == NULL || event->type == VEVENT_LAST) { + break; + } + if (event->type != VEVENT_READER_INSERT) { + if (card->reader == NULL && event->reader != NULL) { + // Happens after device_add followed by card remove or insert. + // XXX: create synthetic add_reader events if vcard_emul_init + // already called, which happens if device_del and device_add are + // called + card->reader = vreader_reference(event->reader); + } else { + if (event->reader != card->reader) { + fprintf(stderr, "ERROR: wrong reader: quiting event_thread\n"); + break; + } + } + } + switch (event->type) { + case VEVENT_READER_INSERT: + /* TODO: take a specific reader. i.e. track which reader + * we are seeing here, check it is the one we want (the first, + * or by a particular name), and ignore if we don't want it. + */ + reader_name = vreader_get_name(event->reader); + if (card->reader != NULL) { + DPRINTF(card, 2, "READER INSERT - replacing %s with %s\n", + vreader_get_name(card->reader), reader_name); + pthread_mutex_lock(&card->vreader_mutex); + vreader_free(card->reader); + pthread_mutex_unlock(&card->vreader_mutex); + emulated_push_reader_remove(card); + } + pthread_mutex_lock(&card->vreader_mutex); + DPRINTF(card, 2, "READER INSERT %s\n", reader_name); + card->reader = vreader_reference(event->reader); + pthread_mutex_unlock(&card->vreader_mutex); + emulated_push_reader_insert(card); + break; + case VEVENT_READER_REMOVE: + DPRINTF(card, 2, " READER REMOVE: %s \n", + vreader_get_name(event->reader)); + pthread_mutex_lock(&card->vreader_mutex); + vreader_free(card->reader); + card->reader = NULL; + pthread_mutex_unlock(&card->vreader_mutex); + emulated_push_reader_remove(card); + break; + case VEVENT_CARD_INSERT: + /* get the ATR (intended as a response to a power on from the + * reader */ + atr_len = MAX_ATR_SIZE; + vreader_power_on(event->reader, atr, &atr_len); + card->atr_length = (uint8_t)atr_len; + DPRINTF(card, 2, " CARD INSERT\n"); + emulated_push_card_insert(card, atr, atr_len); + break; + case VEVENT_CARD_REMOVE: + DPRINTF(card, 2, " CARD REMOVE\n"); + emulated_push_card_remove(card); + break; + case VEVENT_LAST: /* quit */ + vevent_delete(event); + return NULL; + break; + default: + break; + } + vevent_delete(event); + } + return NULL; +} + +static void pipe_read(void *opaque) +{ + EmulatedState *card = opaque; + EmulEvent *event, *next; + char dummy; + int len; + + do { + len = read(card->pipe[0], &dummy, sizeof(dummy)); + } while (len == sizeof(dummy)); + pthread_mutex_lock(&card->event_list_mutex); + QSIMPLEQ_FOREACH_SAFE(event, &card->event_list, entry, next) { + DPRINTF(card, 2, "event %s\n", emul_event_to_string(event->p.gen.type)); + switch (event->p.gen.type) { + case EMUL_RESPONSE_APDU: + ccid_card_send_apdu_to_guest(&card->base, event->p.data.data, + event->p.data.len); + break; + case EMUL_READER_INSERT: + ccid_card_ccid_attach(&card->base); + break; + case EMUL_READER_REMOVE: + ccid_card_ccid_detach(&card->base); + break; + case EMUL_CARD_INSERT: + assert(event->p.data.len <= MAX_ATR_SIZE); + card->atr_length = event->p.data.len; + memcpy(card->atr, event->p.data.data, card->atr_length); + ccid_card_card_inserted(&card->base); + break; + case EMUL_CARD_REMOVE: + ccid_card_card_removed(&card->base); + break; + case EMUL_ERROR: + ccid_card_card_error(&card->base, event->p.error.code); + break; + default: + DPRINTF(card, 2, "unexpected event\n"); + break; + } + free(event); + } + QSIMPLEQ_INIT(&card->event_list); + pthread_mutex_unlock(&card->event_list_mutex); +} + +static int init_pipe_signaling(EmulatedState *card) +{ + if (pipe(card->pipe) < 0) { + DPRINTF(card, 2, "pipe creation failed\n"); + return -1; + } + fcntl(card->pipe[0], F_SETFL, O_NONBLOCK); + fcntl(card->pipe[1], F_SETFL, O_NONBLOCK); + fcntl(card->pipe[0], F_SETOWN, getpid()); + qemu_set_fd_handler(card->pipe[0], pipe_read, NULL, card); + return 0; +} + +#define CERTIFICATES_DEFAULT_DB "/etc/pki/nssdb" +#define CERTIFICATES_ARGS_TEMPLATE "db=\"%s\" use_hw=no soft=(,Virtual Reader,CAC,,%s,%s,%s)" + +static int wrap_vcard_emul_init(VCardEmulOptions *options) +{ + static int called = 0; + static int options_was_null = 0; + + if (called) { + if ((options == NULL) != options_was_null) { + printf("%s: warning: running emulated with certificates and emulated side by side is not supported\n", __FUNCTION__); + return VCARD_EMUL_FAIL; + } + vcard_emul_replay_insertion_events(); + return VCARD_EMUL_OK; + } + options_was_null = (options == NULL); + called = 1; + return vcard_emul_init(options); +} + +static int emulated_initialize_vcard_from_certificates(EmulatedState *card) +{ + char emul_args[200]; + VCardEmulOptions *options = NULL; + + snprintf(emul_args, sizeof(emul_args) - 1, CERTIFICATES_ARGS_TEMPLATE, + card->db ? card->db : CERTIFICATES_DEFAULT_DB, + card->cert1, card->cert2, card->cert3); + options = vcard_emul_options(emul_args); + if (options == NULL) { + printf("%s: warning: not using certificates due to initialization error\n", __func__); + } + return wrap_vcard_emul_init(options); +} + +static int emulated_initfn(CCIDCardState *base) +{ + EmulatedState *card = DO_UPCAST(EmulatedState, base, base); + int rv; + pthread_t thread_id; + VCardEmulError ret; + + QSIMPLEQ_INIT(&card->event_list); + QSIMPLEQ_INIT(&card->guest_apdu_list); + pthread_mutex_init(&card->event_list_mutex, NULL); + pthread_mutex_init(&card->vreader_mutex, NULL); + pthread_mutex_init(&card->handle_apdu_mutex, NULL); + pthread_cond_init(&card->handle_apdu_cond, NULL); + card->reader = NULL; + card->quit_apdu_thread = 0; + if (init_pipe_signaling(card) < 0) { + return -1; + } + if (!card->backend) { + card->backend = strdup((char*)BACKEND_NSS_EMULATED); + } + /* TODO: a passthru backened that works on local machine. third card type? */ + if (strcmp(BACKEND_CERTIFICATES, card->backend) == 0 + && card->cert1 != NULL && card->cert2 != NULL && card->cert3 != NULL) { + ret = emulated_initialize_vcard_from_certificates(card); + } else { + if (strcmp(BACKEND_CERTIFICATES, card->backend) == 0) { + printf("%s: you must provide all three certs for certificates backend\n", + EMULATED_DEV_NAME); + return -1; + } + if (card->backend && strcmp(BACKEND_NSS_EMULATED, card->backend) != 0) { + printf("%s: bad backend specified. The options are:\n%s (default), %s.\n", + EMULATED_DEV_NAME, BACKEND_NSS_EMULATED, BACKEND_CERTIFICATES); + return -1; + } + /* default to mirroring the local hardware readers */ + ret = wrap_vcard_emul_init(NULL); + } + if (ret != VCARD_EMUL_OK) { + printf("%s: failed to initialize vcard\n", EMULATED_DEV_NAME); + return -1; + } + rv = pthread_create(&thread_id, NULL, event_thread, card); + if (rv < 0) { + printf("%s: error creating event thread\n", EMULATED_DEV_NAME); + return -1; + } + rv = pthread_create(&thread_id, NULL, handle_apdu_thread, card); + if (rv < 0) { + printf("%s: error creating handle_apdu thread\n", EMULATED_DEV_NAME); + return -1; + } + return 0; +} + +static int emulated_exitfn(CCIDCardState *base) +{ + EmulatedState *card = DO_UPCAST(EmulatedState, base, base); + VEvent *vevent = vevent_new(VEVENT_LAST, NULL, NULL); + + vevent_queue_vevent(vevent); /* stop vevent thread */ + pthread_mutex_lock(&card->apdu_thread_quit_mutex); + card->quit_apdu_thread = 1; /* stop handle_apdu thread */ + pthread_cond_signal(&card->handle_apdu_cond); + pthread_cond_wait(&card->apdu_thread_quit_cond, &card->apdu_thread_quit_mutex); + /* handle_apdu thread stopped, can destroy all of it's mutexes */ + pthread_cond_destroy(&card->handle_apdu_cond); + pthread_cond_destroy(&card->apdu_thread_quit_cond); + pthread_mutex_destroy(&card->apdu_thread_quit_mutex); + pthread_mutex_destroy(&card->handle_apdu_mutex); + pthread_mutex_destroy(&card->vreader_mutex); + pthread_mutex_destroy(&card->event_list_mutex); + return 0; +} + +static CCIDCardInfo emulated_card_info = { + .qdev.name = EMULATED_DEV_NAME, + .qdev.size = sizeof(EmulatedState), + .initfn = emulated_initfn, + .exitfn = emulated_exitfn, + .get_atr = emulated_get_atr, + .apdu_from_guest = emulated_apdu_from_guest, + .qdev.unplug = qdev_simple_unplug_cb, + .qdev.props = (Property[]) { + DEFINE_PROP_STRING("backend", EmulatedState, backend), + DEFINE_PROP_STRING("cert1", EmulatedState, cert1), + DEFINE_PROP_STRING("cert2", EmulatedState, cert2), + DEFINE_PROP_STRING("cert3", EmulatedState, cert3), + DEFINE_PROP_STRING("db", EmulatedState, db), + DEFINE_PROP_UINT8("debug", EmulatedState, debug, 0), + DEFINE_PROP_END_OF_LIST(), + }, +}; + +static void ccid_card_emulated_register_devices(void) +{ + ccid_card_qdev_register(&emulated_card_info); +} + +device_init(ccid_card_emulated_register_devices) diff --git a/hw/ccid-card-passthru.c b/hw/ccid-card-passthru.c index 6ec4f21..f9fb82c 100644 --- a/hw/ccid-card-passthru.c +++ b/hw/ccid-card-passthru.c @@ -253,7 +253,6 @@ static CCIDCardInfo passthru_card_info = { .exitfn = passthru_exitfn, .get_atr = passthru_get_atr, .apdu_from_guest = passthru_apdu_from_guest, - .qdev.unplug = qdev_simple_unplug_cb, .qdev.props = (Property[]) { DEFINE_PROP_CHR("chardev", PassthruState, cs), DEFINE_PROP_UINT8("debug", PassthruState, debug, 0), -- 1.7.3.4 ^ permalink raw reply related [flat|nested] 35+ messages in thread
* Re: [Qemu-devel] [PATCH 4/7] ccid: add ccid-card-emulated device (v2) 2011-01-11 8:42 ` [Qemu-devel] [PATCH 4/7] ccid: add ccid-card-emulated device (v2) Alon Levy @ 2011-01-25 14:21 ` Anthony Liguori 2011-01-25 16:24 ` Alon Levy 2011-01-31 19:28 ` Alon Levy 0 siblings, 2 replies; 35+ messages in thread From: Anthony Liguori @ 2011-01-25 14:21 UTC (permalink / raw) To: Alon Levy; +Cc: qemu-devel On 01/11/2011 02:42 AM, Alon Levy wrote: > This devices uses libcacard (internal) to emulate a smartcard conforming > to the CAC standard. It attaches to the usb-ccid bus. Usage instructions > (example command lines) are in the following patch in docs/ccid.txt. It > uses libcacard which uses nss, so it can work with both hw cards and > certificates (files). > > changes from v1: > remove stale comments, use only c-style comments > bugfix, forgot to set recv_len > change reader name to 'Virtual Reader' > > Signed-off-by: Alon Levy<alevy@redhat.com> > --- > Makefile.objs | 2 +- > hw/ccid-card-emulated.c | 534 +++++++++++++++++++++++++++++++++++++++++++++++ > hw/ccid-card-passthru.c | 1 - > 3 files changed, 535 insertions(+), 2 deletions(-) > create mode 100644 hw/ccid-card-emulated.c > > diff --git a/Makefile.objs b/Makefile.objs > index 6a0030b..303b642 100644 > --- a/Makefile.objs > +++ b/Makefile.objs > @@ -197,7 +197,7 @@ hw-obj-$(CONFIG_FDC) += fdc.o > hw-obj-$(CONFIG_ACPI) += acpi.o acpi_piix4.o > hw-obj-$(CONFIG_APM) += pm_smbus.o apm.o > hw-obj-$(CONFIG_DMA) += dma.o > -hw-obj-$(CONFIG_SMARTCARD) += usb-ccid.o ccid-card-passthru.o > +hw-obj-$(CONFIG_SMARTCARD) += usb-ccid.o ccid-card-passthru.o ccid-card-emulated.o > > # PPC devices > hw-obj-$(CONFIG_OPENPIC) += openpic.o > diff --git a/hw/ccid-card-emulated.c b/hw/ccid-card-emulated.c > new file mode 100644 > index 0000000..5531ce1 > --- /dev/null > +++ b/hw/ccid-card-emulated.c > @@ -0,0 +1,534 @@ > +/* > + * CCID Card Device. Emulated card. > + * > + * It can be used to provide access to the local hardware in a non exclusive > + * way, or it can use certificates. It requires the usb-ccid bus. > + * > + * Usage 1: standard, mirror hardware reader+card: > + * qemu .. -usb -device usb-ccid -device ccid-card-emulated > + * > + * Usage 2: use certificates, no hardware required > + * one time: create the certificates: > + * for i in 1 2 3; do certutil -d /etc/pki/nssdb -x -t "CT,CT,CT" -S -s "CN=user$i" -n user$i; done > + * qemu .. -usb -device usb-ccid -device ccid-card-emulated,cert1=user1,cert2=user2,cert3=user3 > + * > + * If you use a non default db for the certificates you can specify it using the db parameter. > + * > + * > + * Copyright (c) 2010 Red Hat. > + * Written by Alon Levy. > + * > + * This code is licenced under the LGPL. > + */ > + > +#include<pthread.h> > +#include<eventt.h> > +#include<vevent.h> > +#include<vreader.h> > +#include<vcard_emul.h> > +#include "qemu-char.h" > +#include "monitor.h" > +#include "hw/ccid.h" > + > +#define DPRINTF(card, lvl, fmt, ...) \ > +do { if (lvl<= card->debug) { printf("ccid-card-emul: %s: " fmt , __func__, ## __VA_ARGS__); } } while (0) > + > +#define EMULATED_DEV_NAME "ccid-card-emulated" > + > +#define BACKEND_NSS_EMULATED "nss-emulated" /* the default */ > +#define BACKEND_CERTIFICATES "certificates" > + > +typedef struct EmulatedState EmulatedState; > + > +enum { > + EMUL_READER_INSERT = 0, > + EMUL_READER_REMOVE, > + EMUL_CARD_INSERT, > + EMUL_CARD_REMOVE, > + EMUL_GUEST_APDU, > + EMUL_RESPONSE_APDU, > + EMUL_ERROR, > +}; > + > +static const char* emul_event_to_string(uint32_t emul_event) > +{ > + switch (emul_event) { > + case EMUL_READER_INSERT: return "EMUL_READER_INSERT"; > + case EMUL_READER_REMOVE: return "EMUL_READER_REMOVE"; > + case EMUL_CARD_INSERT: return "EMUL_CARD_INSERT"; > + case EMUL_CARD_REMOVE: return "EMUL_CARD_REMOVE"; > + case EMUL_GUEST_APDU: return "EMUL_GUEST_APDU"; > + case EMUL_RESPONSE_APDU: return "EMUL_RESPONSE_APDU"; > + case EMUL_ERROR: return "EMUL_ERROR"; > + default: > + break; > + } > + return "UNKNOWN"; > +} > + > +typedef struct EmulEvent { > + QSIMPLEQ_ENTRY(EmulEvent) entry; > + union { > + struct { > + uint32_t type; > + } gen; > + struct { > + uint32_t type; > + uint64_t code; > + } error; > + struct { > + uint32_t type; > + uint32_t len; > + uint8_t data[]; > + } data; > + } p; > +} EmulEvent; > + > +#define MAX_ATR_SIZE 40 > +struct EmulatedState { > + CCIDCardState base; > + uint8_t debug; > + char* backend; > + char* cert1; > + char* cert2; > + char* cert3; > + char* db; > + uint8_t atr[MAX_ATR_SIZE]; > + uint8_t atr_length; > + QSIMPLEQ_HEAD(event_list, EmulEvent) event_list; > + pthread_mutex_t event_list_mutex; > + VReader *reader; > + QSIMPLEQ_HEAD(guest_apdu_list, EmulEvent) guest_apdu_list; > + pthread_mutex_t vreader_mutex; /* and guest_apdu_list mutex */ > + pthread_mutex_t handle_apdu_mutex; > + pthread_cond_t handle_apdu_cond; > + int pipe[2]; > + int quit_apdu_thread; > + pthread_mutex_t apdu_thread_quit_mutex; > + pthread_cond_t apdu_thread_quit_cond; > +}; > + > +static void emulated_apdu_from_guest(CCIDCardState *base, const uint8_t *apdu, uint32_t len) > +{ > + EmulatedState *card = DO_UPCAST(EmulatedState, base, base); > + EmulEvent *event = (EmulEvent*)malloc(sizeof(EmulEvent) + len); > + > + assert(event); > + event->p.data.type = EMUL_GUEST_APDU; > + event->p.data.len = len; > + memcpy(event->p.data.data, apdu, len); > + pthread_mutex_lock(&card->vreader_mutex); > + QSIMPLEQ_INSERT_TAIL(&card->guest_apdu_list, event, entry); > + pthread_mutex_unlock(&card->vreader_mutex); > + pthread_mutex_lock(&card->handle_apdu_mutex); > + pthread_cond_signal(&card->handle_apdu_cond); > + pthread_mutex_unlock(&card->handle_apdu_mutex); > +} > + > +static const uint8_t* emulated_get_atr(CCIDCardState *base, uint32_t *len) > +{ > + EmulatedState *card = DO_UPCAST(EmulatedState, base, base); > + > + *len = card->atr_length; > + return card->atr; > +} > + > +static void emulated_push_event(EmulatedState *card, EmulEvent *event) > +{ > + pthread_mutex_lock(&card->event_list_mutex); > + QSIMPLEQ_INSERT_TAIL(&(card->event_list), event, entry); > + pthread_mutex_unlock(&card->event_list_mutex); > + if (write(card->pipe[1], card, 1) != 1) { > + DPRINTF(card, 1, "write to pipe failed\n"); > + } > +} > + > +static void emulated_push_type(EmulatedState *card, uint32_t type) > +{ > + EmulEvent *event = (EmulEvent*)malloc(sizeof(EmulEvent)); > + > + assert(event); > + event->p.gen.type = type; > + emulated_push_event(card, event); > +} > + > +static void emulated_push_error(EmulatedState *card, uint64_t code) > +{ > + EmulEvent *event = (EmulEvent*)malloc(sizeof(EmulEvent)); > + > + assert(event); > + event->p.error.type = EMUL_ERROR; > + event->p.error.code = code; > + emulated_push_event(card, event); > +} > + > +static void emulated_push_data_type(EmulatedState *card, uint32_t type, > + const uint8_t *data, uint32_t len) > +{ > + EmulEvent *event = (EmulEvent*)malloc(sizeof(EmulEvent) + len); > + > + assert(event); > + event->p.data.type = type; > + event->p.data.len = len; > + memcpy(event->p.data.data, data, len); > + emulated_push_event(card, event); > +} > + > +static void emulated_push_reader_insert(EmulatedState *card) > +{ > + emulated_push_type(card, EMUL_READER_INSERT); > +} > + > +static void emulated_push_reader_remove(EmulatedState *card) > +{ > + emulated_push_type(card, EMUL_READER_REMOVE); > +} > + > +static void emulated_push_card_insert(EmulatedState *card, > + const uint8_t *atr, uint32_t len) > +{ > + emulated_push_data_type(card, EMUL_CARD_INSERT, atr, len); > +} > + > +static void emulated_push_card_remove(EmulatedState *card) > +{ > + emulated_push_type(card, EMUL_CARD_REMOVE); > +} > + > +static void emulated_push_response_apdu(EmulatedState *card, > + const uint8_t* apdu, uint32_t len) > +{ > + emulated_push_data_type(card, EMUL_RESPONSE_APDU, apdu, len); > +} > + > +#define APDU_BUF_SIZE 270 > +static void *handle_apdu_thread(void* arg) > +{ > + EmulatedState *card = arg; > + uint8_t recv_data[APDU_BUF_SIZE]; > + int recv_len; > + VReaderStatus reader_status; > + EmulEvent *event; > + > + while (1) { > + pthread_mutex_lock(&card->handle_apdu_mutex); > + pthread_cond_wait(&card->handle_apdu_cond,&card->handle_apdu_mutex); > + pthread_mutex_unlock(&card->handle_apdu_mutex); > + if (card->quit_apdu_thread) { > + card->quit_apdu_thread = 0; // debugging > + break; > + } > + pthread_mutex_lock(&card->vreader_mutex); > + while (!QSIMPLEQ_EMPTY(&card->guest_apdu_list)) { > + event = QSIMPLEQ_FIRST(&card->guest_apdu_list); > + assert((unsigned long)event> 1000); > + QSIMPLEQ_REMOVE_HEAD(&card->guest_apdu_list, entry); > + if (event->p.data.type != EMUL_GUEST_APDU) { > + DPRINTF(card, 1, "unexpected message in handle_apdu_thread\n"); > + free(event); > + continue; > + } > + if (card->reader == NULL) { > + DPRINTF(card, 1, "reader is NULL\n"); > + free(event); > + continue; > + } > + recv_len = sizeof(recv_data); > + reader_status = vreader_xfr_bytes(card->reader, > + event->p.data.data, event->p.data.len, > + recv_data,&recv_len); > + DPRINTF(card, 2, "got back apdu of length %d\n", recv_len); > + if (reader_status == VREADER_OK) { > + emulated_push_response_apdu(card, recv_data, recv_len); > + } else { > + emulated_push_error(card, reader_status); > + } > + free(event); > + } > + pthread_mutex_unlock(&card->vreader_mutex); > + } > + pthread_mutex_lock(&card->apdu_thread_quit_mutex); > + pthread_cond_signal(&card->apdu_thread_quit_cond); > + pthread_mutex_unlock(&card->apdu_thread_quit_mutex); > + return NULL; > +} > + > +static void *event_thread(void *arg) > +{ > + int atr_len = MAX_ATR_SIZE; > + uint8_t atr[MAX_ATR_SIZE]; > + VEvent *event = NULL; > + EmulatedState *card = arg; > + > + while (1) { > + const char *reader_name; > + > + event = vevent_wait_next_vevent(); > + if (event == NULL || event->type == VEVENT_LAST) { > + break; > + } > + if (event->type != VEVENT_READER_INSERT) { > + if (card->reader == NULL&& event->reader != NULL) { > + // Happens after device_add followed by card remove or insert. > + // XXX: create synthetic add_reader events if vcard_emul_init > + // already called, which happens if device_del and device_add are > + // called > + card->reader = vreader_reference(event->reader); > + } else { > + if (event->reader != card->reader) { > + fprintf(stderr, "ERROR: wrong reader: quiting event_thread\n"); > + break; > + } > + } > + } > + switch (event->type) { > + case VEVENT_READER_INSERT: > + /* TODO: take a specific reader. i.e. track which reader > + * we are seeing here, check it is the one we want (the first, > + * or by a particular name), and ignore if we don't want it. > + */ > + reader_name = vreader_get_name(event->reader); > + if (card->reader != NULL) { > + DPRINTF(card, 2, "READER INSERT - replacing %s with %s\n", > + vreader_get_name(card->reader), reader_name); > + pthread_mutex_lock(&card->vreader_mutex); > + vreader_free(card->reader); > + pthread_mutex_unlock(&card->vreader_mutex); > + emulated_push_reader_remove(card); > + } > + pthread_mutex_lock(&card->vreader_mutex); > + DPRINTF(card, 2, "READER INSERT %s\n", reader_name); > + card->reader = vreader_reference(event->reader); > + pthread_mutex_unlock(&card->vreader_mutex); > + emulated_push_reader_insert(card); > + break; > + case VEVENT_READER_REMOVE: > + DPRINTF(card, 2, " READER REMOVE: %s \n", > + vreader_get_name(event->reader)); > + pthread_mutex_lock(&card->vreader_mutex); > + vreader_free(card->reader); > + card->reader = NULL; > + pthread_mutex_unlock(&card->vreader_mutex); > + emulated_push_reader_remove(card); > + break; > + case VEVENT_CARD_INSERT: > + /* get the ATR (intended as a response to a power on from the > + * reader */ > + atr_len = MAX_ATR_SIZE; > + vreader_power_on(event->reader, atr,&atr_len); > + card->atr_length = (uint8_t)atr_len; > + DPRINTF(card, 2, " CARD INSERT\n"); > + emulated_push_card_insert(card, atr, atr_len); > + break; > + case VEVENT_CARD_REMOVE: > + DPRINTF(card, 2, " CARD REMOVE\n"); > + emulated_push_card_remove(card); > + break; > + case VEVENT_LAST: /* quit */ > + vevent_delete(event); > + return NULL; > + break; > + default: > + break; > + } > + vevent_delete(event); > + } > + return NULL; > +} > + > +static void pipe_read(void *opaque) > +{ > + EmulatedState *card = opaque; > + EmulEvent *event, *next; > + char dummy; > + int len; > + > + do { > + len = read(card->pipe[0],&dummy, sizeof(dummy)); > + } while (len == sizeof(dummy)); > + pthread_mutex_lock(&card->event_list_mutex); > + QSIMPLEQ_FOREACH_SAFE(event,&card->event_list, entry, next) { > + DPRINTF(card, 2, "event %s\n", emul_event_to_string(event->p.gen.type)); > + switch (event->p.gen.type) { > + case EMUL_RESPONSE_APDU: > + ccid_card_send_apdu_to_guest(&card->base, event->p.data.data, > + event->p.data.len); > + break; > + case EMUL_READER_INSERT: > + ccid_card_ccid_attach(&card->base); > + break; > + case EMUL_READER_REMOVE: > + ccid_card_ccid_detach(&card->base); > + break; > + case EMUL_CARD_INSERT: > + assert(event->p.data.len<= MAX_ATR_SIZE); > + card->atr_length = event->p.data.len; > + memcpy(card->atr, event->p.data.data, card->atr_length); > + ccid_card_card_inserted(&card->base); > + break; > + case EMUL_CARD_REMOVE: > + ccid_card_card_removed(&card->base); > + break; > + case EMUL_ERROR: > + ccid_card_card_error(&card->base, event->p.error.code); > + break; > + default: > + DPRINTF(card, 2, "unexpected event\n"); > + break; > + } > + free(event); > + } > + QSIMPLEQ_INIT(&card->event_list); > + pthread_mutex_unlock(&card->event_list_mutex); > +} > + > +static int init_pipe_signaling(EmulatedState *card) > +{ > + if (pipe(card->pipe)< 0) { > + DPRINTF(card, 2, "pipe creation failed\n"); > + return -1; > + } > + fcntl(card->pipe[0], F_SETFL, O_NONBLOCK); > + fcntl(card->pipe[1], F_SETFL, O_NONBLOCK); > + fcntl(card->pipe[0], F_SETOWN, getpid()); > + qemu_set_fd_handler(card->pipe[0], pipe_read, NULL, card); > + return 0; > +} > + > +#define CERTIFICATES_DEFAULT_DB "/etc/pki/nssdb" > +#define CERTIFICATES_ARGS_TEMPLATE "db=\"%s\" use_hw=no soft=(,Virtual Reader,CAC,,%s,%s,%s)" > + > +static int wrap_vcard_emul_init(VCardEmulOptions *options) > +{ > + static int called = 0; > + static int options_was_null = 0; > + > + if (called) { > + if ((options == NULL) != options_was_null) { > + printf("%s: warning: running emulated with certificates and emulated side by side is not supported\n", __FUNCTION__); > + return VCARD_EMUL_FAIL; > + } > + vcard_emul_replay_insertion_events(); > + return VCARD_EMUL_OK; > + } > + options_was_null = (options == NULL); > + called = 1; > + return vcard_emul_init(options); > +} > + > +static int emulated_initialize_vcard_from_certificates(EmulatedState *card) > +{ > + char emul_args[200]; > + VCardEmulOptions *options = NULL; > + > + snprintf(emul_args, sizeof(emul_args) - 1, CERTIFICATES_ARGS_TEMPLATE, > + card->db ? card->db : CERTIFICATES_DEFAULT_DB, > + card->cert1, card->cert2, card->cert3); > + options = vcard_emul_options(emul_args); > + if (options == NULL) { > + printf("%s: warning: not using certificates due to initialization error\n", __func__); > + } > + return wrap_vcard_emul_init(options); > +} > + > +static int emulated_initfn(CCIDCardState *base) > +{ > + EmulatedState *card = DO_UPCAST(EmulatedState, base, base); > + int rv; > + pthread_t thread_id; > + VCardEmulError ret; > + > + QSIMPLEQ_INIT(&card->event_list); > + QSIMPLEQ_INIT(&card->guest_apdu_list); > + pthread_mutex_init(&card->event_list_mutex, NULL); > + pthread_mutex_init(&card->vreader_mutex, NULL); > + pthread_mutex_init(&card->handle_apdu_mutex, NULL); > + pthread_cond_init(&card->handle_apdu_cond, NULL); > + card->reader = NULL; > + card->quit_apdu_thread = 0; > + if (init_pipe_signaling(card)< 0) { > + return -1; > + } > + if (!card->backend) { > + card->backend = strdup((char*)BACKEND_NSS_EMULATED); > + } > + /* TODO: a passthru backened that works on local machine. third card type? */ > + if (strcmp(BACKEND_CERTIFICATES, card->backend) == 0 > +&& card->cert1 != NULL&& card->cert2 != NULL&& card->cert3 != NULL) { > + ret = emulated_initialize_vcard_from_certificates(card); > + } else { > + if (strcmp(BACKEND_CERTIFICATES, card->backend) == 0) { > + printf("%s: you must provide all three certs for certificates backend\n", > + EMULATED_DEV_NAME); > + return -1; > + } > + if (card->backend&& strcmp(BACKEND_NSS_EMULATED, card->backend) != 0) { > + printf("%s: bad backend specified. The options are:\n%s (default), %s.\n", > + EMULATED_DEV_NAME, BACKEND_NSS_EMULATED, BACKEND_CERTIFICATES); > + return -1; > + } > + /* default to mirroring the local hardware readers */ > + ret = wrap_vcard_emul_init(NULL); > + } > + if (ret != VCARD_EMUL_OK) { > + printf("%s: failed to initialize vcard\n", EMULATED_DEV_NAME); > + return -1; > + } > + rv = pthread_create(&thread_id, NULL, event_thread, card); > + if (rv< 0) { > + printf("%s: error creating event thread\n", EMULATED_DEV_NAME); > + return -1; > + } > Why can't this be implemented with qemu_set_fd_handler? The event thread si just spinning on read. Regards, Anthony Liguori > + rv = pthread_create(&thread_id, NULL, handle_apdu_thread, card); > + if (rv< 0) { > + printf("%s: error creating handle_apdu thread\n", EMULATED_DEV_NAME); > + return -1; > + } > + return 0; > +} > + > +static int emulated_exitfn(CCIDCardState *base) > +{ > + EmulatedState *card = DO_UPCAST(EmulatedState, base, base); > + VEvent *vevent = vevent_new(VEVENT_LAST, NULL, NULL); > + > + vevent_queue_vevent(vevent); /* stop vevent thread */ > + pthread_mutex_lock(&card->apdu_thread_quit_mutex); > + card->quit_apdu_thread = 1; /* stop handle_apdu thread */ > + pthread_cond_signal(&card->handle_apdu_cond); > + pthread_cond_wait(&card->apdu_thread_quit_cond,&card->apdu_thread_quit_mutex); > + /* handle_apdu thread stopped, can destroy all of it's mutexes */ > + pthread_cond_destroy(&card->handle_apdu_cond); > + pthread_cond_destroy(&card->apdu_thread_quit_cond); > + pthread_mutex_destroy(&card->apdu_thread_quit_mutex); > + pthread_mutex_destroy(&card->handle_apdu_mutex); > + pthread_mutex_destroy(&card->vreader_mutex); > + pthread_mutex_destroy(&card->event_list_mutex); > + return 0; > +} > + > +static CCIDCardInfo emulated_card_info = { > + .qdev.name = EMULATED_DEV_NAME, > + .qdev.size = sizeof(EmulatedState), > + .initfn = emulated_initfn, > + .exitfn = emulated_exitfn, > + .get_atr = emulated_get_atr, > + .apdu_from_guest = emulated_apdu_from_guest, > + .qdev.unplug = qdev_simple_unplug_cb, > + .qdev.props = (Property[]) { > + DEFINE_PROP_STRING("backend", EmulatedState, backend), > + DEFINE_PROP_STRING("cert1", EmulatedState, cert1), > + DEFINE_PROP_STRING("cert2", EmulatedState, cert2), > + DEFINE_PROP_STRING("cert3", EmulatedState, cert3), > + DEFINE_PROP_STRING("db", EmulatedState, db), > + DEFINE_PROP_UINT8("debug", EmulatedState, debug, 0), > + DEFINE_PROP_END_OF_LIST(), > + }, > +}; > + > +static void ccid_card_emulated_register_devices(void) > +{ > + ccid_card_qdev_register(&emulated_card_info); > +} > + > +device_init(ccid_card_emulated_register_devices) > diff --git a/hw/ccid-card-passthru.c b/hw/ccid-card-passthru.c > index 6ec4f21..f9fb82c 100644 > --- a/hw/ccid-card-passthru.c > +++ b/hw/ccid-card-passthru.c > @@ -253,7 +253,6 @@ static CCIDCardInfo passthru_card_info = { > .exitfn = passthru_exitfn, > .get_atr = passthru_get_atr, > .apdu_from_guest = passthru_apdu_from_guest, > - .qdev.unplug = qdev_simple_unplug_cb, > .qdev.props = (Property[]) { > DEFINE_PROP_CHR("chardev", PassthruState, cs), > DEFINE_PROP_UINT8("debug", PassthruState, debug, 0), > ^ permalink raw reply [flat|nested] 35+ messages in thread
* Re: [Qemu-devel] [PATCH 4/7] ccid: add ccid-card-emulated device (v2) 2011-01-25 14:21 ` Anthony Liguori @ 2011-01-25 16:24 ` Alon Levy 2011-01-25 16:27 ` Anthony Liguori 2011-01-31 19:28 ` Alon Levy 1 sibling, 1 reply; 35+ messages in thread From: Alon Levy @ 2011-01-25 16:24 UTC (permalink / raw) To: Anthony Liguori; +Cc: qemu-devel On Tue, Jan 25, 2011 at 08:21:13AM -0600, Anthony Liguori wrote: > On 01/11/2011 02:42 AM, Alon Levy wrote: > >This devices uses libcacard (internal) to emulate a smartcard conforming > >to the CAC standard. It attaches to the usb-ccid bus. Usage instructions > >(example command lines) are in the following patch in docs/ccid.txt. It > >uses libcacard which uses nss, so it can work with both hw cards and > >certificates (files). > > > >changes from v1: > > remove stale comments, use only c-style comments > > bugfix, forgot to set recv_len > > change reader name to 'Virtual Reader' > > > >Signed-off-by: Alon Levy<alevy@redhat.com> > >--- > > Makefile.objs | 2 +- > > hw/ccid-card-emulated.c | 534 +++++++++++++++++++++++++++++++++++++++++++++++ > > hw/ccid-card-passthru.c | 1 - > > 3 files changed, 535 insertions(+), 2 deletions(-) > > create mode 100644 hw/ccid-card-emulated.c > > > >diff --git a/Makefile.objs b/Makefile.objs > >index 6a0030b..303b642 100644 > >--- a/Makefile.objs > >+++ b/Makefile.objs > >@@ -197,7 +197,7 @@ hw-obj-$(CONFIG_FDC) += fdc.o > > hw-obj-$(CONFIG_ACPI) += acpi.o acpi_piix4.o > > hw-obj-$(CONFIG_APM) += pm_smbus.o apm.o > > hw-obj-$(CONFIG_DMA) += dma.o > >-hw-obj-$(CONFIG_SMARTCARD) += usb-ccid.o ccid-card-passthru.o > >+hw-obj-$(CONFIG_SMARTCARD) += usb-ccid.o ccid-card-passthru.o ccid-card-emulated.o > > > > # PPC devices > > hw-obj-$(CONFIG_OPENPIC) += openpic.o > >diff --git a/hw/ccid-card-emulated.c b/hw/ccid-card-emulated.c > >new file mode 100644 > >index 0000000..5531ce1 > >--- /dev/null > >+++ b/hw/ccid-card-emulated.c > >@@ -0,0 +1,534 @@ > >+/* > >+ * CCID Card Device. Emulated card. > >+ * > >+ * It can be used to provide access to the local hardware in a non exclusive > >+ * way, or it can use certificates. It requires the usb-ccid bus. > >+ * > >+ * Usage 1: standard, mirror hardware reader+card: > >+ * qemu .. -usb -device usb-ccid -device ccid-card-emulated > >+ * > >+ * Usage 2: use certificates, no hardware required > >+ * one time: create the certificates: > >+ * for i in 1 2 3; do certutil -d /etc/pki/nssdb -x -t "CT,CT,CT" -S -s "CN=user$i" -n user$i; done > >+ * qemu .. -usb -device usb-ccid -device ccid-card-emulated,cert1=user1,cert2=user2,cert3=user3 > >+ * > >+ * If you use a non default db for the certificates you can specify it using the db parameter. > >+ * > >+ * > >+ * Copyright (c) 2010 Red Hat. > >+ * Written by Alon Levy. > >+ * > >+ * This code is licenced under the LGPL. > >+ */ > >+ > >+#include<pthread.h> > >+#include<eventt.h> > >+#include<vevent.h> > >+#include<vreader.h> > >+#include<vcard_emul.h> > >+#include "qemu-char.h" > >+#include "monitor.h" > >+#include "hw/ccid.h" > >+ > >+#define DPRINTF(card, lvl, fmt, ...) \ > >+do { if (lvl<= card->debug) { printf("ccid-card-emul: %s: " fmt , __func__, ## __VA_ARGS__); } } while (0) > >+ > >+#define EMULATED_DEV_NAME "ccid-card-emulated" > >+ > >+#define BACKEND_NSS_EMULATED "nss-emulated" /* the default */ > >+#define BACKEND_CERTIFICATES "certificates" > >+ > >+typedef struct EmulatedState EmulatedState; > >+ > >+enum { > >+ EMUL_READER_INSERT = 0, > >+ EMUL_READER_REMOVE, > >+ EMUL_CARD_INSERT, > >+ EMUL_CARD_REMOVE, > >+ EMUL_GUEST_APDU, > >+ EMUL_RESPONSE_APDU, > >+ EMUL_ERROR, > >+}; > >+ > >+static const char* emul_event_to_string(uint32_t emul_event) > >+{ > >+ switch (emul_event) { > >+ case EMUL_READER_INSERT: return "EMUL_READER_INSERT"; > >+ case EMUL_READER_REMOVE: return "EMUL_READER_REMOVE"; > >+ case EMUL_CARD_INSERT: return "EMUL_CARD_INSERT"; > >+ case EMUL_CARD_REMOVE: return "EMUL_CARD_REMOVE"; > >+ case EMUL_GUEST_APDU: return "EMUL_GUEST_APDU"; > >+ case EMUL_RESPONSE_APDU: return "EMUL_RESPONSE_APDU"; > >+ case EMUL_ERROR: return "EMUL_ERROR"; > >+ default: > >+ break; > >+ } > >+ return "UNKNOWN"; > >+} > >+ > >+typedef struct EmulEvent { > >+ QSIMPLEQ_ENTRY(EmulEvent) entry; > >+ union { > >+ struct { > >+ uint32_t type; > >+ } gen; > >+ struct { > >+ uint32_t type; > >+ uint64_t code; > >+ } error; > >+ struct { > >+ uint32_t type; > >+ uint32_t len; > >+ uint8_t data[]; > >+ } data; > >+ } p; > >+} EmulEvent; > >+ > >+#define MAX_ATR_SIZE 40 > >+struct EmulatedState { > >+ CCIDCardState base; > >+ uint8_t debug; > >+ char* backend; > >+ char* cert1; > >+ char* cert2; > >+ char* cert3; > >+ char* db; > >+ uint8_t atr[MAX_ATR_SIZE]; > >+ uint8_t atr_length; > >+ QSIMPLEQ_HEAD(event_list, EmulEvent) event_list; > >+ pthread_mutex_t event_list_mutex; > >+ VReader *reader; > >+ QSIMPLEQ_HEAD(guest_apdu_list, EmulEvent) guest_apdu_list; > >+ pthread_mutex_t vreader_mutex; /* and guest_apdu_list mutex */ > >+ pthread_mutex_t handle_apdu_mutex; > >+ pthread_cond_t handle_apdu_cond; > >+ int pipe[2]; > >+ int quit_apdu_thread; > >+ pthread_mutex_t apdu_thread_quit_mutex; > >+ pthread_cond_t apdu_thread_quit_cond; > >+}; > >+ > >+static void emulated_apdu_from_guest(CCIDCardState *base, const uint8_t *apdu, uint32_t len) > >+{ > >+ EmulatedState *card = DO_UPCAST(EmulatedState, base, base); > >+ EmulEvent *event = (EmulEvent*)malloc(sizeof(EmulEvent) + len); > >+ > >+ assert(event); > >+ event->p.data.type = EMUL_GUEST_APDU; > >+ event->p.data.len = len; > >+ memcpy(event->p.data.data, apdu, len); > >+ pthread_mutex_lock(&card->vreader_mutex); > >+ QSIMPLEQ_INSERT_TAIL(&card->guest_apdu_list, event, entry); > >+ pthread_mutex_unlock(&card->vreader_mutex); > >+ pthread_mutex_lock(&card->handle_apdu_mutex); > >+ pthread_cond_signal(&card->handle_apdu_cond); > >+ pthread_mutex_unlock(&card->handle_apdu_mutex); > >+} > >+ > >+static const uint8_t* emulated_get_atr(CCIDCardState *base, uint32_t *len) > >+{ > >+ EmulatedState *card = DO_UPCAST(EmulatedState, base, base); > >+ > >+ *len = card->atr_length; > >+ return card->atr; > >+} > >+ > >+static void emulated_push_event(EmulatedState *card, EmulEvent *event) > >+{ > >+ pthread_mutex_lock(&card->event_list_mutex); > >+ QSIMPLEQ_INSERT_TAIL(&(card->event_list), event, entry); > >+ pthread_mutex_unlock(&card->event_list_mutex); > >+ if (write(card->pipe[1], card, 1) != 1) { > >+ DPRINTF(card, 1, "write to pipe failed\n"); > >+ } > >+} > >+ > >+static void emulated_push_type(EmulatedState *card, uint32_t type) > >+{ > >+ EmulEvent *event = (EmulEvent*)malloc(sizeof(EmulEvent)); > >+ > >+ assert(event); > >+ event->p.gen.type = type; > >+ emulated_push_event(card, event); > >+} > >+ > >+static void emulated_push_error(EmulatedState *card, uint64_t code) > >+{ > >+ EmulEvent *event = (EmulEvent*)malloc(sizeof(EmulEvent)); > >+ > >+ assert(event); > >+ event->p.error.type = EMUL_ERROR; > >+ event->p.error.code = code; > >+ emulated_push_event(card, event); > >+} > >+ > >+static void emulated_push_data_type(EmulatedState *card, uint32_t type, > >+ const uint8_t *data, uint32_t len) > >+{ > >+ EmulEvent *event = (EmulEvent*)malloc(sizeof(EmulEvent) + len); > >+ > >+ assert(event); > >+ event->p.data.type = type; > >+ event->p.data.len = len; > >+ memcpy(event->p.data.data, data, len); > >+ emulated_push_event(card, event); > >+} > >+ > >+static void emulated_push_reader_insert(EmulatedState *card) > >+{ > >+ emulated_push_type(card, EMUL_READER_INSERT); > >+} > >+ > >+static void emulated_push_reader_remove(EmulatedState *card) > >+{ > >+ emulated_push_type(card, EMUL_READER_REMOVE); > >+} > >+ > >+static void emulated_push_card_insert(EmulatedState *card, > >+ const uint8_t *atr, uint32_t len) > >+{ > >+ emulated_push_data_type(card, EMUL_CARD_INSERT, atr, len); > >+} > >+ > >+static void emulated_push_card_remove(EmulatedState *card) > >+{ > >+ emulated_push_type(card, EMUL_CARD_REMOVE); > >+} > >+ > >+static void emulated_push_response_apdu(EmulatedState *card, > >+ const uint8_t* apdu, uint32_t len) > >+{ > >+ emulated_push_data_type(card, EMUL_RESPONSE_APDU, apdu, len); > >+} > >+ > >+#define APDU_BUF_SIZE 270 > >+static void *handle_apdu_thread(void* arg) > >+{ > >+ EmulatedState *card = arg; > >+ uint8_t recv_data[APDU_BUF_SIZE]; > >+ int recv_len; > >+ VReaderStatus reader_status; > >+ EmulEvent *event; > >+ > >+ while (1) { > >+ pthread_mutex_lock(&card->handle_apdu_mutex); > >+ pthread_cond_wait(&card->handle_apdu_cond,&card->handle_apdu_mutex); > >+ pthread_mutex_unlock(&card->handle_apdu_mutex); > >+ if (card->quit_apdu_thread) { > >+ card->quit_apdu_thread = 0; // debugging > >+ break; > >+ } > >+ pthread_mutex_lock(&card->vreader_mutex); > >+ while (!QSIMPLEQ_EMPTY(&card->guest_apdu_list)) { > >+ event = QSIMPLEQ_FIRST(&card->guest_apdu_list); > >+ assert((unsigned long)event> 1000); > >+ QSIMPLEQ_REMOVE_HEAD(&card->guest_apdu_list, entry); > >+ if (event->p.data.type != EMUL_GUEST_APDU) { > >+ DPRINTF(card, 1, "unexpected message in handle_apdu_thread\n"); > >+ free(event); > >+ continue; > >+ } > >+ if (card->reader == NULL) { > >+ DPRINTF(card, 1, "reader is NULL\n"); > >+ free(event); > >+ continue; > >+ } > >+ recv_len = sizeof(recv_data); > >+ reader_status = vreader_xfr_bytes(card->reader, > >+ event->p.data.data, event->p.data.len, > >+ recv_data,&recv_len); > >+ DPRINTF(card, 2, "got back apdu of length %d\n", recv_len); > >+ if (reader_status == VREADER_OK) { > >+ emulated_push_response_apdu(card, recv_data, recv_len); > >+ } else { > >+ emulated_push_error(card, reader_status); > >+ } > >+ free(event); > >+ } > >+ pthread_mutex_unlock(&card->vreader_mutex); > >+ } > >+ pthread_mutex_lock(&card->apdu_thread_quit_mutex); > >+ pthread_cond_signal(&card->apdu_thread_quit_cond); > >+ pthread_mutex_unlock(&card->apdu_thread_quit_mutex); > >+ return NULL; > >+} > >+ > >+static void *event_thread(void *arg) > >+{ > >+ int atr_len = MAX_ATR_SIZE; > >+ uint8_t atr[MAX_ATR_SIZE]; > >+ VEvent *event = NULL; > >+ EmulatedState *card = arg; > >+ > >+ while (1) { > >+ const char *reader_name; > >+ > >+ event = vevent_wait_next_vevent(); > >+ if (event == NULL || event->type == VEVENT_LAST) { > >+ break; > >+ } > >+ if (event->type != VEVENT_READER_INSERT) { > >+ if (card->reader == NULL&& event->reader != NULL) { > >+ // Happens after device_add followed by card remove or insert. > >+ // XXX: create synthetic add_reader events if vcard_emul_init > >+ // already called, which happens if device_del and device_add are > >+ // called > >+ card->reader = vreader_reference(event->reader); > >+ } else { > >+ if (event->reader != card->reader) { > >+ fprintf(stderr, "ERROR: wrong reader: quiting event_thread\n"); > >+ break; > >+ } > >+ } > >+ } > >+ switch (event->type) { > >+ case VEVENT_READER_INSERT: > >+ /* TODO: take a specific reader. i.e. track which reader > >+ * we are seeing here, check it is the one we want (the first, > >+ * or by a particular name), and ignore if we don't want it. > >+ */ > >+ reader_name = vreader_get_name(event->reader); > >+ if (card->reader != NULL) { > >+ DPRINTF(card, 2, "READER INSERT - replacing %s with %s\n", > >+ vreader_get_name(card->reader), reader_name); > >+ pthread_mutex_lock(&card->vreader_mutex); > >+ vreader_free(card->reader); > >+ pthread_mutex_unlock(&card->vreader_mutex); > >+ emulated_push_reader_remove(card); > >+ } > >+ pthread_mutex_lock(&card->vreader_mutex); > >+ DPRINTF(card, 2, "READER INSERT %s\n", reader_name); > >+ card->reader = vreader_reference(event->reader); > >+ pthread_mutex_unlock(&card->vreader_mutex); > >+ emulated_push_reader_insert(card); > >+ break; > >+ case VEVENT_READER_REMOVE: > >+ DPRINTF(card, 2, " READER REMOVE: %s \n", > >+ vreader_get_name(event->reader)); > >+ pthread_mutex_lock(&card->vreader_mutex); > >+ vreader_free(card->reader); > >+ card->reader = NULL; > >+ pthread_mutex_unlock(&card->vreader_mutex); > >+ emulated_push_reader_remove(card); > >+ break; > >+ case VEVENT_CARD_INSERT: > >+ /* get the ATR (intended as a response to a power on from the > >+ * reader */ > >+ atr_len = MAX_ATR_SIZE; > >+ vreader_power_on(event->reader, atr,&atr_len); > >+ card->atr_length = (uint8_t)atr_len; > >+ DPRINTF(card, 2, " CARD INSERT\n"); > >+ emulated_push_card_insert(card, atr, atr_len); > >+ break; > >+ case VEVENT_CARD_REMOVE: > >+ DPRINTF(card, 2, " CARD REMOVE\n"); > >+ emulated_push_card_remove(card); > >+ break; > >+ case VEVENT_LAST: /* quit */ > >+ vevent_delete(event); > >+ return NULL; > >+ break; > >+ default: > >+ break; > >+ } > >+ vevent_delete(event); > >+ } > >+ return NULL; > >+} > >+ > >+static void pipe_read(void *opaque) > >+{ > >+ EmulatedState *card = opaque; > >+ EmulEvent *event, *next; > >+ char dummy; > >+ int len; > >+ > >+ do { > >+ len = read(card->pipe[0],&dummy, sizeof(dummy)); > >+ } while (len == sizeof(dummy)); > >+ pthread_mutex_lock(&card->event_list_mutex); > >+ QSIMPLEQ_FOREACH_SAFE(event,&card->event_list, entry, next) { > >+ DPRINTF(card, 2, "event %s\n", emul_event_to_string(event->p.gen.type)); > >+ switch (event->p.gen.type) { > >+ case EMUL_RESPONSE_APDU: > >+ ccid_card_send_apdu_to_guest(&card->base, event->p.data.data, > >+ event->p.data.len); > >+ break; > >+ case EMUL_READER_INSERT: > >+ ccid_card_ccid_attach(&card->base); > >+ break; > >+ case EMUL_READER_REMOVE: > >+ ccid_card_ccid_detach(&card->base); > >+ break; > >+ case EMUL_CARD_INSERT: > >+ assert(event->p.data.len<= MAX_ATR_SIZE); > >+ card->atr_length = event->p.data.len; > >+ memcpy(card->atr, event->p.data.data, card->atr_length); > >+ ccid_card_card_inserted(&card->base); > >+ break; > >+ case EMUL_CARD_REMOVE: > >+ ccid_card_card_removed(&card->base); > >+ break; > >+ case EMUL_ERROR: > >+ ccid_card_card_error(&card->base, event->p.error.code); > >+ break; > >+ default: > >+ DPRINTF(card, 2, "unexpected event\n"); > >+ break; > >+ } > >+ free(event); > >+ } > >+ QSIMPLEQ_INIT(&card->event_list); > >+ pthread_mutex_unlock(&card->event_list_mutex); > >+} > >+ > >+static int init_pipe_signaling(EmulatedState *card) > >+{ > >+ if (pipe(card->pipe)< 0) { > >+ DPRINTF(card, 2, "pipe creation failed\n"); > >+ return -1; > >+ } > >+ fcntl(card->pipe[0], F_SETFL, O_NONBLOCK); > >+ fcntl(card->pipe[1], F_SETFL, O_NONBLOCK); > >+ fcntl(card->pipe[0], F_SETOWN, getpid()); > >+ qemu_set_fd_handler(card->pipe[0], pipe_read, NULL, card); > >+ return 0; > >+} > >+ > >+#define CERTIFICATES_DEFAULT_DB "/etc/pki/nssdb" > >+#define CERTIFICATES_ARGS_TEMPLATE "db=\"%s\" use_hw=no soft=(,Virtual Reader,CAC,,%s,%s,%s)" > >+ > >+static int wrap_vcard_emul_init(VCardEmulOptions *options) > >+{ > >+ static int called = 0; > >+ static int options_was_null = 0; > >+ > >+ if (called) { > >+ if ((options == NULL) != options_was_null) { > >+ printf("%s: warning: running emulated with certificates and emulated side by side is not supported\n", __FUNCTION__); > >+ return VCARD_EMUL_FAIL; > >+ } > >+ vcard_emul_replay_insertion_events(); > >+ return VCARD_EMUL_OK; > >+ } > >+ options_was_null = (options == NULL); > >+ called = 1; > >+ return vcard_emul_init(options); > >+} > >+ > >+static int emulated_initialize_vcard_from_certificates(EmulatedState *card) > >+{ > >+ char emul_args[200]; > >+ VCardEmulOptions *options = NULL; > >+ > >+ snprintf(emul_args, sizeof(emul_args) - 1, CERTIFICATES_ARGS_TEMPLATE, > >+ card->db ? card->db : CERTIFICATES_DEFAULT_DB, > >+ card->cert1, card->cert2, card->cert3); > >+ options = vcard_emul_options(emul_args); > >+ if (options == NULL) { > >+ printf("%s: warning: not using certificates due to initialization error\n", __func__); > >+ } > >+ return wrap_vcard_emul_init(options); > >+} > >+ > >+static int emulated_initfn(CCIDCardState *base) > >+{ > >+ EmulatedState *card = DO_UPCAST(EmulatedState, base, base); > >+ int rv; > >+ pthread_t thread_id; > >+ VCardEmulError ret; > >+ > >+ QSIMPLEQ_INIT(&card->event_list); > >+ QSIMPLEQ_INIT(&card->guest_apdu_list); > >+ pthread_mutex_init(&card->event_list_mutex, NULL); > >+ pthread_mutex_init(&card->vreader_mutex, NULL); > >+ pthread_mutex_init(&card->handle_apdu_mutex, NULL); > >+ pthread_cond_init(&card->handle_apdu_cond, NULL); > >+ card->reader = NULL; > >+ card->quit_apdu_thread = 0; > >+ if (init_pipe_signaling(card)< 0) { > >+ return -1; > >+ } > >+ if (!card->backend) { > >+ card->backend = strdup((char*)BACKEND_NSS_EMULATED); > >+ } > >+ /* TODO: a passthru backened that works on local machine. third card type? */ > >+ if (strcmp(BACKEND_CERTIFICATES, card->backend) == 0 > >+&& card->cert1 != NULL&& card->cert2 != NULL&& card->cert3 != NULL) { > >+ ret = emulated_initialize_vcard_from_certificates(card); > >+ } else { > >+ if (strcmp(BACKEND_CERTIFICATES, card->backend) == 0) { > >+ printf("%s: you must provide all three certs for certificates backend\n", > >+ EMULATED_DEV_NAME); > >+ return -1; > >+ } > >+ if (card->backend&& strcmp(BACKEND_NSS_EMULATED, card->backend) != 0) { > >+ printf("%s: bad backend specified. The options are:\n%s (default), %s.\n", > >+ EMULATED_DEV_NAME, BACKEND_NSS_EMULATED, BACKEND_CERTIFICATES); > >+ return -1; > >+ } > >+ /* default to mirroring the local hardware readers */ > >+ ret = wrap_vcard_emul_init(NULL); > >+ } > >+ if (ret != VCARD_EMUL_OK) { > >+ printf("%s: failed to initialize vcard\n", EMULATED_DEV_NAME); > >+ return -1; > >+ } > >+ rv = pthread_create(&thread_id, NULL, event_thread, card); > >+ if (rv< 0) { > >+ printf("%s: error creating event thread\n", EMULATED_DEV_NAME); > >+ return -1; > >+ } > > Why can't this be implemented with qemu_set_fd_handler? The event > thread si just spinning on read. > The issue is not letting NSS block any of qemu's threads. > Regards, > > Anthony Liguori > > >+ rv = pthread_create(&thread_id, NULL, handle_apdu_thread, card); > >+ if (rv< 0) { > >+ printf("%s: error creating handle_apdu thread\n", EMULATED_DEV_NAME); > >+ return -1; > >+ } > >+ return 0; > >+} > >+ > >+static int emulated_exitfn(CCIDCardState *base) > >+{ > >+ EmulatedState *card = DO_UPCAST(EmulatedState, base, base); > >+ VEvent *vevent = vevent_new(VEVENT_LAST, NULL, NULL); > >+ > >+ vevent_queue_vevent(vevent); /* stop vevent thread */ > >+ pthread_mutex_lock(&card->apdu_thread_quit_mutex); > >+ card->quit_apdu_thread = 1; /* stop handle_apdu thread */ > >+ pthread_cond_signal(&card->handle_apdu_cond); > >+ pthread_cond_wait(&card->apdu_thread_quit_cond,&card->apdu_thread_quit_mutex); > >+ /* handle_apdu thread stopped, can destroy all of it's mutexes */ > >+ pthread_cond_destroy(&card->handle_apdu_cond); > >+ pthread_cond_destroy(&card->apdu_thread_quit_cond); > >+ pthread_mutex_destroy(&card->apdu_thread_quit_mutex); > >+ pthread_mutex_destroy(&card->handle_apdu_mutex); > >+ pthread_mutex_destroy(&card->vreader_mutex); > >+ pthread_mutex_destroy(&card->event_list_mutex); > >+ return 0; > >+} > >+ > >+static CCIDCardInfo emulated_card_info = { > >+ .qdev.name = EMULATED_DEV_NAME, > >+ .qdev.size = sizeof(EmulatedState), > >+ .initfn = emulated_initfn, > >+ .exitfn = emulated_exitfn, > >+ .get_atr = emulated_get_atr, > >+ .apdu_from_guest = emulated_apdu_from_guest, > >+ .qdev.unplug = qdev_simple_unplug_cb, > >+ .qdev.props = (Property[]) { > >+ DEFINE_PROP_STRING("backend", EmulatedState, backend), > >+ DEFINE_PROP_STRING("cert1", EmulatedState, cert1), > >+ DEFINE_PROP_STRING("cert2", EmulatedState, cert2), > >+ DEFINE_PROP_STRING("cert3", EmulatedState, cert3), > >+ DEFINE_PROP_STRING("db", EmulatedState, db), > >+ DEFINE_PROP_UINT8("debug", EmulatedState, debug, 0), > >+ DEFINE_PROP_END_OF_LIST(), > >+ }, > >+}; > >+ > >+static void ccid_card_emulated_register_devices(void) > >+{ > >+ ccid_card_qdev_register(&emulated_card_info); > >+} > >+ > >+device_init(ccid_card_emulated_register_devices) > >diff --git a/hw/ccid-card-passthru.c b/hw/ccid-card-passthru.c > >index 6ec4f21..f9fb82c 100644 > >--- a/hw/ccid-card-passthru.c > >+++ b/hw/ccid-card-passthru.c > >@@ -253,7 +253,6 @@ static CCIDCardInfo passthru_card_info = { > > .exitfn = passthru_exitfn, > > .get_atr = passthru_get_atr, > > .apdu_from_guest = passthru_apdu_from_guest, > >- .qdev.unplug = qdev_simple_unplug_cb, > > .qdev.props = (Property[]) { > > DEFINE_PROP_CHR("chardev", PassthruState, cs), > > DEFINE_PROP_UINT8("debug", PassthruState, debug, 0), > ^ permalink raw reply [flat|nested] 35+ messages in thread
* Re: [Qemu-devel] [PATCH 4/7] ccid: add ccid-card-emulated device (v2) 2011-01-25 16:24 ` Alon Levy @ 2011-01-25 16:27 ` Anthony Liguori 0 siblings, 0 replies; 35+ messages in thread From: Anthony Liguori @ 2011-01-25 16:27 UTC (permalink / raw) To: qemu-devel On 01/25/2011 10:24 AM, Alon Levy wrote: > On Tue, Jan 25, 2011 at 08:21:13AM -0600, Anthony Liguori wrote: > >> On 01/11/2011 02:42 AM, Alon Levy wrote: >> >>> This devices uses libcacard (internal) to emulate a smartcard conforming >>> to the CAC standard. It attaches to the usb-ccid bus. Usage instructions >>> (example command lines) are in the following patch in docs/ccid.txt. It >>> uses libcacard which uses nss, so it can work with both hw cards and >>> certificates (files). >>> >>> changes from v1: >>> remove stale comments, use only c-style comments >>> bugfix, forgot to set recv_len >>> change reader name to 'Virtual Reader' >>> >>> Signed-off-by: Alon Levy<alevy@redhat.com> >>> --- >>> Makefile.objs | 2 +- >>> hw/ccid-card-emulated.c | 534 +++++++++++++++++++++++++++++++++++++++++++++++ >>> hw/ccid-card-passthru.c | 1 - >>> 3 files changed, 535 insertions(+), 2 deletions(-) >>> create mode 100644 hw/ccid-card-emulated.c >>> >>> diff --git a/Makefile.objs b/Makefile.objs >>> index 6a0030b..303b642 100644 >>> --- a/Makefile.objs >>> +++ b/Makefile.objs >>> @@ -197,7 +197,7 @@ hw-obj-$(CONFIG_FDC) += fdc.o >>> hw-obj-$(CONFIG_ACPI) += acpi.o acpi_piix4.o >>> hw-obj-$(CONFIG_APM) += pm_smbus.o apm.o >>> hw-obj-$(CONFIG_DMA) += dma.o >>> -hw-obj-$(CONFIG_SMARTCARD) += usb-ccid.o ccid-card-passthru.o >>> +hw-obj-$(CONFIG_SMARTCARD) += usb-ccid.o ccid-card-passthru.o ccid-card-emulated.o >>> >>> # PPC devices >>> hw-obj-$(CONFIG_OPENPIC) += openpic.o >>> diff --git a/hw/ccid-card-emulated.c b/hw/ccid-card-emulated.c >>> new file mode 100644 >>> index 0000000..5531ce1 >>> --- /dev/null >>> +++ b/hw/ccid-card-emulated.c >>> @@ -0,0 +1,534 @@ >>> +/* >>> + * CCID Card Device. Emulated card. >>> + * >>> + * It can be used to provide access to the local hardware in a non exclusive >>> + * way, or it can use certificates. It requires the usb-ccid bus. >>> + * >>> + * Usage 1: standard, mirror hardware reader+card: >>> + * qemu .. -usb -device usb-ccid -device ccid-card-emulated >>> + * >>> + * Usage 2: use certificates, no hardware required >>> + * one time: create the certificates: >>> + * for i in 1 2 3; do certutil -d /etc/pki/nssdb -x -t "CT,CT,CT" -S -s "CN=user$i" -n user$i; done >>> + * qemu .. -usb -device usb-ccid -device ccid-card-emulated,cert1=user1,cert2=user2,cert3=user3 >>> + * >>> + * If you use a non default db for the certificates you can specify it using the db parameter. >>> + * >>> + * >>> + * Copyright (c) 2010 Red Hat. >>> + * Written by Alon Levy. >>> + * >>> + * This code is licenced under the LGPL. >>> + */ >>> + >>> +#include<pthread.h> >>> +#include<eventt.h> >>> +#include<vevent.h> >>> +#include<vreader.h> >>> +#include<vcard_emul.h> >>> +#include "qemu-char.h" >>> +#include "monitor.h" >>> +#include "hw/ccid.h" >>> + >>> +#define DPRINTF(card, lvl, fmt, ...) \ >>> +do { if (lvl<= card->debug) { printf("ccid-card-emul: %s: " fmt , __func__, ## __VA_ARGS__); } } while (0) >>> + >>> +#define EMULATED_DEV_NAME "ccid-card-emulated" >>> + >>> +#define BACKEND_NSS_EMULATED "nss-emulated" /* the default */ >>> +#define BACKEND_CERTIFICATES "certificates" >>> + >>> +typedef struct EmulatedState EmulatedState; >>> + >>> +enum { >>> + EMUL_READER_INSERT = 0, >>> + EMUL_READER_REMOVE, >>> + EMUL_CARD_INSERT, >>> + EMUL_CARD_REMOVE, >>> + EMUL_GUEST_APDU, >>> + EMUL_RESPONSE_APDU, >>> + EMUL_ERROR, >>> +}; >>> + >>> +static const char* emul_event_to_string(uint32_t emul_event) >>> +{ >>> + switch (emul_event) { >>> + case EMUL_READER_INSERT: return "EMUL_READER_INSERT"; >>> + case EMUL_READER_REMOVE: return "EMUL_READER_REMOVE"; >>> + case EMUL_CARD_INSERT: return "EMUL_CARD_INSERT"; >>> + case EMUL_CARD_REMOVE: return "EMUL_CARD_REMOVE"; >>> + case EMUL_GUEST_APDU: return "EMUL_GUEST_APDU"; >>> + case EMUL_RESPONSE_APDU: return "EMUL_RESPONSE_APDU"; >>> + case EMUL_ERROR: return "EMUL_ERROR"; >>> + default: >>> + break; >>> + } >>> + return "UNKNOWN"; >>> +} >>> + >>> +typedef struct EmulEvent { >>> + QSIMPLEQ_ENTRY(EmulEvent) entry; >>> + union { >>> + struct { >>> + uint32_t type; >>> + } gen; >>> + struct { >>> + uint32_t type; >>> + uint64_t code; >>> + } error; >>> + struct { >>> + uint32_t type; >>> + uint32_t len; >>> + uint8_t data[]; >>> + } data; >>> + } p; >>> +} EmulEvent; >>> + >>> +#define MAX_ATR_SIZE 40 >>> +struct EmulatedState { >>> + CCIDCardState base; >>> + uint8_t debug; >>> + char* backend; >>> + char* cert1; >>> + char* cert2; >>> + char* cert3; >>> + char* db; >>> + uint8_t atr[MAX_ATR_SIZE]; >>> + uint8_t atr_length; >>> + QSIMPLEQ_HEAD(event_list, EmulEvent) event_list; >>> + pthread_mutex_t event_list_mutex; >>> + VReader *reader; >>> + QSIMPLEQ_HEAD(guest_apdu_list, EmulEvent) guest_apdu_list; >>> + pthread_mutex_t vreader_mutex; /* and guest_apdu_list mutex */ >>> + pthread_mutex_t handle_apdu_mutex; >>> + pthread_cond_t handle_apdu_cond; >>> + int pipe[2]; >>> + int quit_apdu_thread; >>> + pthread_mutex_t apdu_thread_quit_mutex; >>> + pthread_cond_t apdu_thread_quit_cond; >>> +}; >>> + >>> +static void emulated_apdu_from_guest(CCIDCardState *base, const uint8_t *apdu, uint32_t len) >>> +{ >>> + EmulatedState *card = DO_UPCAST(EmulatedState, base, base); >>> + EmulEvent *event = (EmulEvent*)malloc(sizeof(EmulEvent) + len); >>> + >>> + assert(event); >>> + event->p.data.type = EMUL_GUEST_APDU; >>> + event->p.data.len = len; >>> + memcpy(event->p.data.data, apdu, len); >>> + pthread_mutex_lock(&card->vreader_mutex); >>> + QSIMPLEQ_INSERT_TAIL(&card->guest_apdu_list, event, entry); >>> + pthread_mutex_unlock(&card->vreader_mutex); >>> + pthread_mutex_lock(&card->handle_apdu_mutex); >>> + pthread_cond_signal(&card->handle_apdu_cond); >>> + pthread_mutex_unlock(&card->handle_apdu_mutex); >>> +} >>> + >>> +static const uint8_t* emulated_get_atr(CCIDCardState *base, uint32_t *len) >>> +{ >>> + EmulatedState *card = DO_UPCAST(EmulatedState, base, base); >>> + >>> + *len = card->atr_length; >>> + return card->atr; >>> +} >>> + >>> +static void emulated_push_event(EmulatedState *card, EmulEvent *event) >>> +{ >>> + pthread_mutex_lock(&card->event_list_mutex); >>> + QSIMPLEQ_INSERT_TAIL(&(card->event_list), event, entry); >>> + pthread_mutex_unlock(&card->event_list_mutex); >>> + if (write(card->pipe[1], card, 1) != 1) { >>> + DPRINTF(card, 1, "write to pipe failed\n"); >>> + } >>> +} >>> + >>> +static void emulated_push_type(EmulatedState *card, uint32_t type) >>> +{ >>> + EmulEvent *event = (EmulEvent*)malloc(sizeof(EmulEvent)); >>> + >>> + assert(event); >>> + event->p.gen.type = type; >>> + emulated_push_event(card, event); >>> +} >>> + >>> +static void emulated_push_error(EmulatedState *card, uint64_t code) >>> +{ >>> + EmulEvent *event = (EmulEvent*)malloc(sizeof(EmulEvent)); >>> + >>> + assert(event); >>> + event->p.error.type = EMUL_ERROR; >>> + event->p.error.code = code; >>> + emulated_push_event(card, event); >>> +} >>> + >>> +static void emulated_push_data_type(EmulatedState *card, uint32_t type, >>> + const uint8_t *data, uint32_t len) >>> +{ >>> + EmulEvent *event = (EmulEvent*)malloc(sizeof(EmulEvent) + len); >>> + >>> + assert(event); >>> + event->p.data.type = type; >>> + event->p.data.len = len; >>> + memcpy(event->p.data.data, data, len); >>> + emulated_push_event(card, event); >>> +} >>> + >>> +static void emulated_push_reader_insert(EmulatedState *card) >>> +{ >>> + emulated_push_type(card, EMUL_READER_INSERT); >>> +} >>> + >>> +static void emulated_push_reader_remove(EmulatedState *card) >>> +{ >>> + emulated_push_type(card, EMUL_READER_REMOVE); >>> +} >>> + >>> +static void emulated_push_card_insert(EmulatedState *card, >>> + const uint8_t *atr, uint32_t len) >>> +{ >>> + emulated_push_data_type(card, EMUL_CARD_INSERT, atr, len); >>> +} >>> + >>> +static void emulated_push_card_remove(EmulatedState *card) >>> +{ >>> + emulated_push_type(card, EMUL_CARD_REMOVE); >>> +} >>> + >>> +static void emulated_push_response_apdu(EmulatedState *card, >>> + const uint8_t* apdu, uint32_t len) >>> +{ >>> + emulated_push_data_type(card, EMUL_RESPONSE_APDU, apdu, len); >>> +} >>> + >>> +#define APDU_BUF_SIZE 270 >>> +static void *handle_apdu_thread(void* arg) >>> +{ >>> + EmulatedState *card = arg; >>> + uint8_t recv_data[APDU_BUF_SIZE]; >>> + int recv_len; >>> + VReaderStatus reader_status; >>> + EmulEvent *event; >>> + >>> + while (1) { >>> + pthread_mutex_lock(&card->handle_apdu_mutex); >>> + pthread_cond_wait(&card->handle_apdu_cond,&card->handle_apdu_mutex); >>> + pthread_mutex_unlock(&card->handle_apdu_mutex); >>> + if (card->quit_apdu_thread) { >>> + card->quit_apdu_thread = 0; // debugging >>> + break; >>> + } >>> + pthread_mutex_lock(&card->vreader_mutex); >>> + while (!QSIMPLEQ_EMPTY(&card->guest_apdu_list)) { >>> + event = QSIMPLEQ_FIRST(&card->guest_apdu_list); >>> + assert((unsigned long)event> 1000); >>> + QSIMPLEQ_REMOVE_HEAD(&card->guest_apdu_list, entry); >>> + if (event->p.data.type != EMUL_GUEST_APDU) { >>> + DPRINTF(card, 1, "unexpected message in handle_apdu_thread\n"); >>> + free(event); >>> + continue; >>> + } >>> + if (card->reader == NULL) { >>> + DPRINTF(card, 1, "reader is NULL\n"); >>> + free(event); >>> + continue; >>> + } >>> + recv_len = sizeof(recv_data); >>> + reader_status = vreader_xfr_bytes(card->reader, >>> + event->p.data.data, event->p.data.len, >>> + recv_data,&recv_len); >>> + DPRINTF(card, 2, "got back apdu of length %d\n", recv_len); >>> + if (reader_status == VREADER_OK) { >>> + emulated_push_response_apdu(card, recv_data, recv_len); >>> + } else { >>> + emulated_push_error(card, reader_status); >>> + } >>> + free(event); >>> + } >>> + pthread_mutex_unlock(&card->vreader_mutex); >>> + } >>> + pthread_mutex_lock(&card->apdu_thread_quit_mutex); >>> + pthread_cond_signal(&card->apdu_thread_quit_cond); >>> + pthread_mutex_unlock(&card->apdu_thread_quit_mutex); >>> + return NULL; >>> +} >>> + >>> +static void *event_thread(void *arg) >>> +{ >>> + int atr_len = MAX_ATR_SIZE; >>> + uint8_t atr[MAX_ATR_SIZE]; >>> + VEvent *event = NULL; >>> + EmulatedState *card = arg; >>> + >>> + while (1) { >>> + const char *reader_name; >>> + >>> + event = vevent_wait_next_vevent(); >>> + if (event == NULL || event->type == VEVENT_LAST) { >>> + break; >>> + } >>> + if (event->type != VEVENT_READER_INSERT) { >>> + if (card->reader == NULL&& event->reader != NULL) { >>> + // Happens after device_add followed by card remove or insert. >>> + // XXX: create synthetic add_reader events if vcard_emul_init >>> + // already called, which happens if device_del and device_add are >>> + // called >>> + card->reader = vreader_reference(event->reader); >>> + } else { >>> + if (event->reader != card->reader) { >>> + fprintf(stderr, "ERROR: wrong reader: quiting event_thread\n"); >>> + break; >>> + } >>> + } >>> + } >>> + switch (event->type) { >>> + case VEVENT_READER_INSERT: >>> + /* TODO: take a specific reader. i.e. track which reader >>> + * we are seeing here, check it is the one we want (the first, >>> + * or by a particular name), and ignore if we don't want it. >>> + */ >>> + reader_name = vreader_get_name(event->reader); >>> + if (card->reader != NULL) { >>> + DPRINTF(card, 2, "READER INSERT - replacing %s with %s\n", >>> + vreader_get_name(card->reader), reader_name); >>> + pthread_mutex_lock(&card->vreader_mutex); >>> + vreader_free(card->reader); >>> + pthread_mutex_unlock(&card->vreader_mutex); >>> + emulated_push_reader_remove(card); >>> + } >>> + pthread_mutex_lock(&card->vreader_mutex); >>> + DPRINTF(card, 2, "READER INSERT %s\n", reader_name); >>> + card->reader = vreader_reference(event->reader); >>> + pthread_mutex_unlock(&card->vreader_mutex); >>> + emulated_push_reader_insert(card); >>> + break; >>> + case VEVENT_READER_REMOVE: >>> + DPRINTF(card, 2, " READER REMOVE: %s \n", >>> + vreader_get_name(event->reader)); >>> + pthread_mutex_lock(&card->vreader_mutex); >>> + vreader_free(card->reader); >>> + card->reader = NULL; >>> + pthread_mutex_unlock(&card->vreader_mutex); >>> + emulated_push_reader_remove(card); >>> + break; >>> + case VEVENT_CARD_INSERT: >>> + /* get the ATR (intended as a response to a power on from the >>> + * reader */ >>> + atr_len = MAX_ATR_SIZE; >>> + vreader_power_on(event->reader, atr,&atr_len); >>> + card->atr_length = (uint8_t)atr_len; >>> + DPRINTF(card, 2, " CARD INSERT\n"); >>> + emulated_push_card_insert(card, atr, atr_len); >>> + break; >>> + case VEVENT_CARD_REMOVE: >>> + DPRINTF(card, 2, " CARD REMOVE\n"); >>> + emulated_push_card_remove(card); >>> + break; >>> + case VEVENT_LAST: /* quit */ >>> + vevent_delete(event); >>> + return NULL; >>> + break; >>> + default: >>> + break; >>> + } >>> + vevent_delete(event); >>> + } >>> + return NULL; >>> +} >>> + >>> +static void pipe_read(void *opaque) >>> +{ >>> + EmulatedState *card = opaque; >>> + EmulEvent *event, *next; >>> + char dummy; >>> + int len; >>> + >>> + do { >>> + len = read(card->pipe[0],&dummy, sizeof(dummy)); >>> + } while (len == sizeof(dummy)); >>> + pthread_mutex_lock(&card->event_list_mutex); >>> + QSIMPLEQ_FOREACH_SAFE(event,&card->event_list, entry, next) { >>> + DPRINTF(card, 2, "event %s\n", emul_event_to_string(event->p.gen.type)); >>> + switch (event->p.gen.type) { >>> + case EMUL_RESPONSE_APDU: >>> + ccid_card_send_apdu_to_guest(&card->base, event->p.data.data, >>> + event->p.data.len); >>> + break; >>> + case EMUL_READER_INSERT: >>> + ccid_card_ccid_attach(&card->base); >>> + break; >>> + case EMUL_READER_REMOVE: >>> + ccid_card_ccid_detach(&card->base); >>> + break; >>> + case EMUL_CARD_INSERT: >>> + assert(event->p.data.len<= MAX_ATR_SIZE); >>> + card->atr_length = event->p.data.len; >>> + memcpy(card->atr, event->p.data.data, card->atr_length); >>> + ccid_card_card_inserted(&card->base); >>> + break; >>> + case EMUL_CARD_REMOVE: >>> + ccid_card_card_removed(&card->base); >>> + break; >>> + case EMUL_ERROR: >>> + ccid_card_card_error(&card->base, event->p.error.code); >>> + break; >>> + default: >>> + DPRINTF(card, 2, "unexpected event\n"); >>> + break; >>> + } >>> + free(event); >>> + } >>> + QSIMPLEQ_INIT(&card->event_list); >>> + pthread_mutex_unlock(&card->event_list_mutex); >>> +} >>> + >>> +static int init_pipe_signaling(EmulatedState *card) >>> +{ >>> + if (pipe(card->pipe)< 0) { >>> + DPRINTF(card, 2, "pipe creation failed\n"); >>> + return -1; >>> + } >>> + fcntl(card->pipe[0], F_SETFL, O_NONBLOCK); >>> + fcntl(card->pipe[1], F_SETFL, O_NONBLOCK); >>> + fcntl(card->pipe[0], F_SETOWN, getpid()); >>> + qemu_set_fd_handler(card->pipe[0], pipe_read, NULL, card); >>> + return 0; >>> +} >>> + >>> +#define CERTIFICATES_DEFAULT_DB "/etc/pki/nssdb" >>> +#define CERTIFICATES_ARGS_TEMPLATE "db=\"%s\" use_hw=no soft=(,Virtual Reader,CAC,,%s,%s,%s)" >>> + >>> +static int wrap_vcard_emul_init(VCardEmulOptions *options) >>> +{ >>> + static int called = 0; >>> + static int options_was_null = 0; >>> + >>> + if (called) { >>> + if ((options == NULL) != options_was_null) { >>> + printf("%s: warning: running emulated with certificates and emulated side by side is not supported\n", __FUNCTION__); >>> + return VCARD_EMUL_FAIL; >>> + } >>> + vcard_emul_replay_insertion_events(); >>> + return VCARD_EMUL_OK; >>> + } >>> + options_was_null = (options == NULL); >>> + called = 1; >>> + return vcard_emul_init(options); >>> +} >>> + >>> +static int emulated_initialize_vcard_from_certificates(EmulatedState *card) >>> +{ >>> + char emul_args[200]; >>> + VCardEmulOptions *options = NULL; >>> + >>> + snprintf(emul_args, sizeof(emul_args) - 1, CERTIFICATES_ARGS_TEMPLATE, >>> + card->db ? card->db : CERTIFICATES_DEFAULT_DB, >>> + card->cert1, card->cert2, card->cert3); >>> + options = vcard_emul_options(emul_args); >>> + if (options == NULL) { >>> + printf("%s: warning: not using certificates due to initialization error\n", __func__); >>> + } >>> + return wrap_vcard_emul_init(options); >>> +} >>> + >>> +static int emulated_initfn(CCIDCardState *base) >>> +{ >>> + EmulatedState *card = DO_UPCAST(EmulatedState, base, base); >>> + int rv; >>> + pthread_t thread_id; >>> + VCardEmulError ret; >>> + >>> + QSIMPLEQ_INIT(&card->event_list); >>> + QSIMPLEQ_INIT(&card->guest_apdu_list); >>> + pthread_mutex_init(&card->event_list_mutex, NULL); >>> + pthread_mutex_init(&card->vreader_mutex, NULL); >>> + pthread_mutex_init(&card->handle_apdu_mutex, NULL); >>> + pthread_cond_init(&card->handle_apdu_cond, NULL); >>> + card->reader = NULL; >>> + card->quit_apdu_thread = 0; >>> + if (init_pipe_signaling(card)< 0) { >>> + return -1; >>> + } >>> + if (!card->backend) { >>> + card->backend = strdup((char*)BACKEND_NSS_EMULATED); >>> + } >>> + /* TODO: a passthru backened that works on local machine. third card type? */ >>> + if (strcmp(BACKEND_CERTIFICATES, card->backend) == 0 >>> +&& card->cert1 != NULL&& card->cert2 != NULL&& card->cert3 != NULL) { >>> + ret = emulated_initialize_vcard_from_certificates(card); >>> + } else { >>> + if (strcmp(BACKEND_CERTIFICATES, card->backend) == 0) { >>> + printf("%s: you must provide all three certs for certificates backend\n", >>> + EMULATED_DEV_NAME); >>> + return -1; >>> + } >>> + if (card->backend&& strcmp(BACKEND_NSS_EMULATED, card->backend) != 0) { >>> + printf("%s: bad backend specified. The options are:\n%s (default), %s.\n", >>> + EMULATED_DEV_NAME, BACKEND_NSS_EMULATED, BACKEND_CERTIFICATES); >>> + return -1; >>> + } >>> + /* default to mirroring the local hardware readers */ >>> + ret = wrap_vcard_emul_init(NULL); >>> + } >>> + if (ret != VCARD_EMUL_OK) { >>> + printf("%s: failed to initialize vcard\n", EMULATED_DEV_NAME); >>> + return -1; >>> + } >>> + rv = pthread_create(&thread_id, NULL, event_thread, card); >>> + if (rv< 0) { >>> + printf("%s: error creating event thread\n", EMULATED_DEV_NAME); >>> + return -1; >>> + } >>> >> Why can't this be implemented with qemu_set_fd_handler? The event >> thread si just spinning on read. >> >> > The issue is not letting NSS block any of qemu's threads. > I'd rather use a thread pool to execute NSS calls asynchronously. None of QEMU is really thread safe which means you'll have to be really careful about what goes into these threads. I think this leads to code that is very subtle and easy to break. Regards, Anthony Liguori > >> Regards, >> >> Anthony Liguori >> >> >>> + rv = pthread_create(&thread_id, NULL, handle_apdu_thread, card); >>> + if (rv< 0) { >>> + printf("%s: error creating handle_apdu thread\n", EMULATED_DEV_NAME); >>> + return -1; >>> + } >>> + return 0; >>> +} >>> + >>> +static int emulated_exitfn(CCIDCardState *base) >>> +{ >>> + EmulatedState *card = DO_UPCAST(EmulatedState, base, base); >>> + VEvent *vevent = vevent_new(VEVENT_LAST, NULL, NULL); >>> + >>> + vevent_queue_vevent(vevent); /* stop vevent thread */ >>> + pthread_mutex_lock(&card->apdu_thread_quit_mutex); >>> + card->quit_apdu_thread = 1; /* stop handle_apdu thread */ >>> + pthread_cond_signal(&card->handle_apdu_cond); >>> + pthread_cond_wait(&card->apdu_thread_quit_cond,&card->apdu_thread_quit_mutex); >>> + /* handle_apdu thread stopped, can destroy all of it's mutexes */ >>> + pthread_cond_destroy(&card->handle_apdu_cond); >>> + pthread_cond_destroy(&card->apdu_thread_quit_cond); >>> + pthread_mutex_destroy(&card->apdu_thread_quit_mutex); >>> + pthread_mutex_destroy(&card->handle_apdu_mutex); >>> + pthread_mutex_destroy(&card->vreader_mutex); >>> + pthread_mutex_destroy(&card->event_list_mutex); >>> + return 0; >>> +} >>> + >>> +static CCIDCardInfo emulated_card_info = { >>> + .qdev.name = EMULATED_DEV_NAME, >>> + .qdev.size = sizeof(EmulatedState), >>> + .initfn = emulated_initfn, >>> + .exitfn = emulated_exitfn, >>> + .get_atr = emulated_get_atr, >>> + .apdu_from_guest = emulated_apdu_from_guest, >>> + .qdev.unplug = qdev_simple_unplug_cb, >>> + .qdev.props = (Property[]) { >>> + DEFINE_PROP_STRING("backend", EmulatedState, backend), >>> + DEFINE_PROP_STRING("cert1", EmulatedState, cert1), >>> + DEFINE_PROP_STRING("cert2", EmulatedState, cert2), >>> + DEFINE_PROP_STRING("cert3", EmulatedState, cert3), >>> + DEFINE_PROP_STRING("db", EmulatedState, db), >>> + DEFINE_PROP_UINT8("debug", EmulatedState, debug, 0), >>> + DEFINE_PROP_END_OF_LIST(), >>> + }, >>> +}; >>> + >>> +static void ccid_card_emulated_register_devices(void) >>> +{ >>> + ccid_card_qdev_register(&emulated_card_info); >>> +} >>> + >>> +device_init(ccid_card_emulated_register_devices) >>> diff --git a/hw/ccid-card-passthru.c b/hw/ccid-card-passthru.c >>> index 6ec4f21..f9fb82c 100644 >>> --- a/hw/ccid-card-passthru.c >>> +++ b/hw/ccid-card-passthru.c >>> @@ -253,7 +253,6 @@ static CCIDCardInfo passthru_card_info = { >>> .exitfn = passthru_exitfn, >>> .get_atr = passthru_get_atr, >>> .apdu_from_guest = passthru_apdu_from_guest, >>> - .qdev.unplug = qdev_simple_unplug_cb, >>> .qdev.props = (Property[]) { >>> DEFINE_PROP_CHR("chardev", PassthruState, cs), >>> DEFINE_PROP_UINT8("debug", PassthruState, debug, 0), >>> >> ^ permalink raw reply [flat|nested] 35+ messages in thread
* Re: [Qemu-devel] [PATCH 4/7] ccid: add ccid-card-emulated device (v2) 2011-01-25 14:21 ` Anthony Liguori 2011-01-25 16:24 ` Alon Levy @ 2011-01-31 19:28 ` Alon Levy 1 sibling, 0 replies; 35+ messages in thread From: Alon Levy @ 2011-01-31 19:28 UTC (permalink / raw) To: Anthony Liguori; +Cc: qemu-devel On Tue, Jan 25, 2011 at 08:21:13AM -0600, Anthony Liguori wrote: > On 01/11/2011 02:42 AM, Alon Levy wrote: > >This devices uses libcacard (internal) to emulate a smartcard conforming > >to the CAC standard. It attaches to the usb-ccid bus. Usage instructions > >(example command lines) are in the following patch in docs/ccid.txt. It > >uses libcacard which uses nss, so it can work with both hw cards and > >certificates (files). > > > >changes from v1: > > remove stale comments, use only c-style comments > > bugfix, forgot to set recv_len > > change reader name to 'Virtual Reader' > > > >Signed-off-by: Alon Levy<alevy@redhat.com> > >--- > > Makefile.objs | 2 +- > > hw/ccid-card-emulated.c | 534 +++++++++++++++++++++++++++++++++++++++++++++++ > > hw/ccid-card-passthru.c | 1 - > > 3 files changed, 535 insertions(+), 2 deletions(-) > > create mode 100644 hw/ccid-card-emulated.c > > > >diff --git a/Makefile.objs b/Makefile.objs > >index 6a0030b..303b642 100644 > >--- a/Makefile.objs > >+++ b/Makefile.objs > >@@ -197,7 +197,7 @@ hw-obj-$(CONFIG_FDC) += fdc.o > > hw-obj-$(CONFIG_ACPI) += acpi.o acpi_piix4.o > > hw-obj-$(CONFIG_APM) += pm_smbus.o apm.o > > hw-obj-$(CONFIG_DMA) += dma.o > >-hw-obj-$(CONFIG_SMARTCARD) += usb-ccid.o ccid-card-passthru.o > >+hw-obj-$(CONFIG_SMARTCARD) += usb-ccid.o ccid-card-passthru.o ccid-card-emulated.o > > > > # PPC devices > > hw-obj-$(CONFIG_OPENPIC) += openpic.o > >diff --git a/hw/ccid-card-emulated.c b/hw/ccid-card-emulated.c > >new file mode 100644 > >index 0000000..5531ce1 > >--- /dev/null > >+++ b/hw/ccid-card-emulated.c > >@@ -0,0 +1,534 @@ > >+/* > >+ * CCID Card Device. Emulated card. > >+ * > >+ * It can be used to provide access to the local hardware in a non exclusive > >+ * way, or it can use certificates. It requires the usb-ccid bus. > >+ * > >+ * Usage 1: standard, mirror hardware reader+card: > >+ * qemu .. -usb -device usb-ccid -device ccid-card-emulated > >+ * > >+ * Usage 2: use certificates, no hardware required > >+ * one time: create the certificates: > >+ * for i in 1 2 3; do certutil -d /etc/pki/nssdb -x -t "CT,CT,CT" -S -s "CN=user$i" -n user$i; done > >+ * qemu .. -usb -device usb-ccid -device ccid-card-emulated,cert1=user1,cert2=user2,cert3=user3 > >+ * > >+ * If you use a non default db for the certificates you can specify it using the db parameter. > >+ * > >+ * > >+ * Copyright (c) 2010 Red Hat. > >+ * Written by Alon Levy. > >+ * > >+ * This code is licenced under the LGPL. > >+ */ > >+ > >+#include<pthread.h> > >+#include<eventt.h> > >+#include<vevent.h> > >+#include<vreader.h> > >+#include<vcard_emul.h> > >+#include "qemu-char.h" > >+#include "monitor.h" > >+#include "hw/ccid.h" > >+ > >+#define DPRINTF(card, lvl, fmt, ...) \ > >+do { if (lvl<= card->debug) { printf("ccid-card-emul: %s: " fmt , __func__, ## __VA_ARGS__); } } while (0) > >+ > >+#define EMULATED_DEV_NAME "ccid-card-emulated" > >+ > >+#define BACKEND_NSS_EMULATED "nss-emulated" /* the default */ > >+#define BACKEND_CERTIFICATES "certificates" > >+ > >+typedef struct EmulatedState EmulatedState; > >+ > >+enum { > >+ EMUL_READER_INSERT = 0, > >+ EMUL_READER_REMOVE, > >+ EMUL_CARD_INSERT, > >+ EMUL_CARD_REMOVE, > >+ EMUL_GUEST_APDU, > >+ EMUL_RESPONSE_APDU, > >+ EMUL_ERROR, > >+}; > >+ > >+static const char* emul_event_to_string(uint32_t emul_event) > >+{ > >+ switch (emul_event) { > >+ case EMUL_READER_INSERT: return "EMUL_READER_INSERT"; > >+ case EMUL_READER_REMOVE: return "EMUL_READER_REMOVE"; > >+ case EMUL_CARD_INSERT: return "EMUL_CARD_INSERT"; > >+ case EMUL_CARD_REMOVE: return "EMUL_CARD_REMOVE"; > >+ case EMUL_GUEST_APDU: return "EMUL_GUEST_APDU"; > >+ case EMUL_RESPONSE_APDU: return "EMUL_RESPONSE_APDU"; > >+ case EMUL_ERROR: return "EMUL_ERROR"; > >+ default: > >+ break; > >+ } > >+ return "UNKNOWN"; > >+} > >+ > >+typedef struct EmulEvent { > >+ QSIMPLEQ_ENTRY(EmulEvent) entry; > >+ union { > >+ struct { > >+ uint32_t type; > >+ } gen; > >+ struct { > >+ uint32_t type; > >+ uint64_t code; > >+ } error; > >+ struct { > >+ uint32_t type; > >+ uint32_t len; > >+ uint8_t data[]; > >+ } data; > >+ } p; > >+} EmulEvent; > >+ > >+#define MAX_ATR_SIZE 40 > >+struct EmulatedState { > >+ CCIDCardState base; > >+ uint8_t debug; > >+ char* backend; > >+ char* cert1; > >+ char* cert2; > >+ char* cert3; > >+ char* db; > >+ uint8_t atr[MAX_ATR_SIZE]; > >+ uint8_t atr_length; > >+ QSIMPLEQ_HEAD(event_list, EmulEvent) event_list; > >+ pthread_mutex_t event_list_mutex; > >+ VReader *reader; > >+ QSIMPLEQ_HEAD(guest_apdu_list, EmulEvent) guest_apdu_list; > >+ pthread_mutex_t vreader_mutex; /* and guest_apdu_list mutex */ > >+ pthread_mutex_t handle_apdu_mutex; > >+ pthread_cond_t handle_apdu_cond; > >+ int pipe[2]; > >+ int quit_apdu_thread; > >+ pthread_mutex_t apdu_thread_quit_mutex; > >+ pthread_cond_t apdu_thread_quit_cond; > >+}; > >+ > >+static void emulated_apdu_from_guest(CCIDCardState *base, const uint8_t *apdu, uint32_t len) > >+{ > >+ EmulatedState *card = DO_UPCAST(EmulatedState, base, base); > >+ EmulEvent *event = (EmulEvent*)malloc(sizeof(EmulEvent) + len); > >+ > >+ assert(event); > >+ event->p.data.type = EMUL_GUEST_APDU; > >+ event->p.data.len = len; > >+ memcpy(event->p.data.data, apdu, len); > >+ pthread_mutex_lock(&card->vreader_mutex); > >+ QSIMPLEQ_INSERT_TAIL(&card->guest_apdu_list, event, entry); > >+ pthread_mutex_unlock(&card->vreader_mutex); > >+ pthread_mutex_lock(&card->handle_apdu_mutex); > >+ pthread_cond_signal(&card->handle_apdu_cond); > >+ pthread_mutex_unlock(&card->handle_apdu_mutex); > >+} > >+ > >+static const uint8_t* emulated_get_atr(CCIDCardState *base, uint32_t *len) > >+{ > >+ EmulatedState *card = DO_UPCAST(EmulatedState, base, base); > >+ > >+ *len = card->atr_length; > >+ return card->atr; > >+} > >+ > >+static void emulated_push_event(EmulatedState *card, EmulEvent *event) > >+{ > >+ pthread_mutex_lock(&card->event_list_mutex); > >+ QSIMPLEQ_INSERT_TAIL(&(card->event_list), event, entry); > >+ pthread_mutex_unlock(&card->event_list_mutex); > >+ if (write(card->pipe[1], card, 1) != 1) { > >+ DPRINTF(card, 1, "write to pipe failed\n"); > >+ } > >+} > >+ > >+static void emulated_push_type(EmulatedState *card, uint32_t type) > >+{ > >+ EmulEvent *event = (EmulEvent*)malloc(sizeof(EmulEvent)); > >+ > >+ assert(event); > >+ event->p.gen.type = type; > >+ emulated_push_event(card, event); > >+} > >+ > >+static void emulated_push_error(EmulatedState *card, uint64_t code) > >+{ > >+ EmulEvent *event = (EmulEvent*)malloc(sizeof(EmulEvent)); > >+ > >+ assert(event); > >+ event->p.error.type = EMUL_ERROR; > >+ event->p.error.code = code; > >+ emulated_push_event(card, event); > >+} > >+ > >+static void emulated_push_data_type(EmulatedState *card, uint32_t type, > >+ const uint8_t *data, uint32_t len) > >+{ > >+ EmulEvent *event = (EmulEvent*)malloc(sizeof(EmulEvent) + len); > >+ > >+ assert(event); > >+ event->p.data.type = type; > >+ event->p.data.len = len; > >+ memcpy(event->p.data.data, data, len); > >+ emulated_push_event(card, event); > >+} > >+ > >+static void emulated_push_reader_insert(EmulatedState *card) > >+{ > >+ emulated_push_type(card, EMUL_READER_INSERT); > >+} > >+ > >+static void emulated_push_reader_remove(EmulatedState *card) > >+{ > >+ emulated_push_type(card, EMUL_READER_REMOVE); > >+} > >+ > >+static void emulated_push_card_insert(EmulatedState *card, > >+ const uint8_t *atr, uint32_t len) > >+{ > >+ emulated_push_data_type(card, EMUL_CARD_INSERT, atr, len); > >+} > >+ > >+static void emulated_push_card_remove(EmulatedState *card) > >+{ > >+ emulated_push_type(card, EMUL_CARD_REMOVE); > >+} > >+ > >+static void emulated_push_response_apdu(EmulatedState *card, > >+ const uint8_t* apdu, uint32_t len) > >+{ > >+ emulated_push_data_type(card, EMUL_RESPONSE_APDU, apdu, len); > >+} > >+ > >+#define APDU_BUF_SIZE 270 > >+static void *handle_apdu_thread(void* arg) > >+{ > >+ EmulatedState *card = arg; > >+ uint8_t recv_data[APDU_BUF_SIZE]; > >+ int recv_len; > >+ VReaderStatus reader_status; > >+ EmulEvent *event; > >+ > >+ while (1) { > >+ pthread_mutex_lock(&card->handle_apdu_mutex); > >+ pthread_cond_wait(&card->handle_apdu_cond,&card->handle_apdu_mutex); > >+ pthread_mutex_unlock(&card->handle_apdu_mutex); > >+ if (card->quit_apdu_thread) { > >+ card->quit_apdu_thread = 0; // debugging > >+ break; > >+ } > >+ pthread_mutex_lock(&card->vreader_mutex); > >+ while (!QSIMPLEQ_EMPTY(&card->guest_apdu_list)) { > >+ event = QSIMPLEQ_FIRST(&card->guest_apdu_list); > >+ assert((unsigned long)event> 1000); > >+ QSIMPLEQ_REMOVE_HEAD(&card->guest_apdu_list, entry); > >+ if (event->p.data.type != EMUL_GUEST_APDU) { > >+ DPRINTF(card, 1, "unexpected message in handle_apdu_thread\n"); > >+ free(event); > >+ continue; > >+ } > >+ if (card->reader == NULL) { > >+ DPRINTF(card, 1, "reader is NULL\n"); > >+ free(event); > >+ continue; > >+ } > >+ recv_len = sizeof(recv_data); > >+ reader_status = vreader_xfr_bytes(card->reader, > >+ event->p.data.data, event->p.data.len, > >+ recv_data,&recv_len); > >+ DPRINTF(card, 2, "got back apdu of length %d\n", recv_len); > >+ if (reader_status == VREADER_OK) { > >+ emulated_push_response_apdu(card, recv_data, recv_len); > >+ } else { > >+ emulated_push_error(card, reader_status); > >+ } > >+ free(event); > >+ } > >+ pthread_mutex_unlock(&card->vreader_mutex); > >+ } > >+ pthread_mutex_lock(&card->apdu_thread_quit_mutex); > >+ pthread_cond_signal(&card->apdu_thread_quit_cond); > >+ pthread_mutex_unlock(&card->apdu_thread_quit_mutex); > >+ return NULL; > >+} > >+ > >+static void *event_thread(void *arg) > >+{ > >+ int atr_len = MAX_ATR_SIZE; > >+ uint8_t atr[MAX_ATR_SIZE]; > >+ VEvent *event = NULL; > >+ EmulatedState *card = arg; > >+ > >+ while (1) { > >+ const char *reader_name; > >+ > >+ event = vevent_wait_next_vevent(); > >+ if (event == NULL || event->type == VEVENT_LAST) { > >+ break; > >+ } > >+ if (event->type != VEVENT_READER_INSERT) { > >+ if (card->reader == NULL&& event->reader != NULL) { > >+ // Happens after device_add followed by card remove or insert. > >+ // XXX: create synthetic add_reader events if vcard_emul_init > >+ // already called, which happens if device_del and device_add are > >+ // called > >+ card->reader = vreader_reference(event->reader); > >+ } else { > >+ if (event->reader != card->reader) { > >+ fprintf(stderr, "ERROR: wrong reader: quiting event_thread\n"); > >+ break; > >+ } > >+ } > >+ } > >+ switch (event->type) { > >+ case VEVENT_READER_INSERT: > >+ /* TODO: take a specific reader. i.e. track which reader > >+ * we are seeing here, check it is the one we want (the first, > >+ * or by a particular name), and ignore if we don't want it. > >+ */ > >+ reader_name = vreader_get_name(event->reader); > >+ if (card->reader != NULL) { > >+ DPRINTF(card, 2, "READER INSERT - replacing %s with %s\n", > >+ vreader_get_name(card->reader), reader_name); > >+ pthread_mutex_lock(&card->vreader_mutex); > >+ vreader_free(card->reader); > >+ pthread_mutex_unlock(&card->vreader_mutex); > >+ emulated_push_reader_remove(card); > >+ } > >+ pthread_mutex_lock(&card->vreader_mutex); > >+ DPRINTF(card, 2, "READER INSERT %s\n", reader_name); > >+ card->reader = vreader_reference(event->reader); > >+ pthread_mutex_unlock(&card->vreader_mutex); > >+ emulated_push_reader_insert(card); > >+ break; > >+ case VEVENT_READER_REMOVE: > >+ DPRINTF(card, 2, " READER REMOVE: %s \n", > >+ vreader_get_name(event->reader)); > >+ pthread_mutex_lock(&card->vreader_mutex); > >+ vreader_free(card->reader); > >+ card->reader = NULL; > >+ pthread_mutex_unlock(&card->vreader_mutex); > >+ emulated_push_reader_remove(card); > >+ break; > >+ case VEVENT_CARD_INSERT: > >+ /* get the ATR (intended as a response to a power on from the > >+ * reader */ > >+ atr_len = MAX_ATR_SIZE; > >+ vreader_power_on(event->reader, atr,&atr_len); > >+ card->atr_length = (uint8_t)atr_len; > >+ DPRINTF(card, 2, " CARD INSERT\n"); > >+ emulated_push_card_insert(card, atr, atr_len); > >+ break; > >+ case VEVENT_CARD_REMOVE: > >+ DPRINTF(card, 2, " CARD REMOVE\n"); > >+ emulated_push_card_remove(card); > >+ break; > >+ case VEVENT_LAST: /* quit */ > >+ vevent_delete(event); > >+ return NULL; > >+ break; > >+ default: > >+ break; > >+ } > >+ vevent_delete(event); > >+ } > >+ return NULL; > >+} > >+ > >+static void pipe_read(void *opaque) > >+{ > >+ EmulatedState *card = opaque; > >+ EmulEvent *event, *next; > >+ char dummy; > >+ int len; > >+ > >+ do { > >+ len = read(card->pipe[0],&dummy, sizeof(dummy)); > >+ } while (len == sizeof(dummy)); > >+ pthread_mutex_lock(&card->event_list_mutex); > >+ QSIMPLEQ_FOREACH_SAFE(event,&card->event_list, entry, next) { > >+ DPRINTF(card, 2, "event %s\n", emul_event_to_string(event->p.gen.type)); > >+ switch (event->p.gen.type) { > >+ case EMUL_RESPONSE_APDU: > >+ ccid_card_send_apdu_to_guest(&card->base, event->p.data.data, > >+ event->p.data.len); > >+ break; > >+ case EMUL_READER_INSERT: > >+ ccid_card_ccid_attach(&card->base); > >+ break; > >+ case EMUL_READER_REMOVE: > >+ ccid_card_ccid_detach(&card->base); > >+ break; > >+ case EMUL_CARD_INSERT: > >+ assert(event->p.data.len<= MAX_ATR_SIZE); > >+ card->atr_length = event->p.data.len; > >+ memcpy(card->atr, event->p.data.data, card->atr_length); > >+ ccid_card_card_inserted(&card->base); > >+ break; > >+ case EMUL_CARD_REMOVE: > >+ ccid_card_card_removed(&card->base); > >+ break; > >+ case EMUL_ERROR: > >+ ccid_card_card_error(&card->base, event->p.error.code); > >+ break; > >+ default: > >+ DPRINTF(card, 2, "unexpected event\n"); > >+ break; > >+ } > >+ free(event); > >+ } > >+ QSIMPLEQ_INIT(&card->event_list); > >+ pthread_mutex_unlock(&card->event_list_mutex); > >+} > >+ > >+static int init_pipe_signaling(EmulatedState *card) > >+{ > >+ if (pipe(card->pipe)< 0) { > >+ DPRINTF(card, 2, "pipe creation failed\n"); > >+ return -1; > >+ } > >+ fcntl(card->pipe[0], F_SETFL, O_NONBLOCK); > >+ fcntl(card->pipe[1], F_SETFL, O_NONBLOCK); > >+ fcntl(card->pipe[0], F_SETOWN, getpid()); > >+ qemu_set_fd_handler(card->pipe[0], pipe_read, NULL, card); > >+ return 0; > >+} > >+ > >+#define CERTIFICATES_DEFAULT_DB "/etc/pki/nssdb" > >+#define CERTIFICATES_ARGS_TEMPLATE "db=\"%s\" use_hw=no soft=(,Virtual Reader,CAC,,%s,%s,%s)" > >+ > >+static int wrap_vcard_emul_init(VCardEmulOptions *options) > >+{ > >+ static int called = 0; > >+ static int options_was_null = 0; > >+ > >+ if (called) { > >+ if ((options == NULL) != options_was_null) { > >+ printf("%s: warning: running emulated with certificates and emulated side by side is not supported\n", __FUNCTION__); > >+ return VCARD_EMUL_FAIL; > >+ } > >+ vcard_emul_replay_insertion_events(); > >+ return VCARD_EMUL_OK; > >+ } > >+ options_was_null = (options == NULL); > >+ called = 1; > >+ return vcard_emul_init(options); > >+} > >+ > >+static int emulated_initialize_vcard_from_certificates(EmulatedState *card) > >+{ > >+ char emul_args[200]; > >+ VCardEmulOptions *options = NULL; > >+ > >+ snprintf(emul_args, sizeof(emul_args) - 1, CERTIFICATES_ARGS_TEMPLATE, > >+ card->db ? card->db : CERTIFICATES_DEFAULT_DB, > >+ card->cert1, card->cert2, card->cert3); > >+ options = vcard_emul_options(emul_args); > >+ if (options == NULL) { > >+ printf("%s: warning: not using certificates due to initialization error\n", __func__); > >+ } > >+ return wrap_vcard_emul_init(options); > >+} > >+ > >+static int emulated_initfn(CCIDCardState *base) > >+{ > >+ EmulatedState *card = DO_UPCAST(EmulatedState, base, base); > >+ int rv; > >+ pthread_t thread_id; > >+ VCardEmulError ret; > >+ > >+ QSIMPLEQ_INIT(&card->event_list); > >+ QSIMPLEQ_INIT(&card->guest_apdu_list); > >+ pthread_mutex_init(&card->event_list_mutex, NULL); > >+ pthread_mutex_init(&card->vreader_mutex, NULL); > >+ pthread_mutex_init(&card->handle_apdu_mutex, NULL); > >+ pthread_cond_init(&card->handle_apdu_cond, NULL); > >+ card->reader = NULL; > >+ card->quit_apdu_thread = 0; > >+ if (init_pipe_signaling(card)< 0) { > >+ return -1; > >+ } > >+ if (!card->backend) { > >+ card->backend = strdup((char*)BACKEND_NSS_EMULATED); > >+ } > >+ /* TODO: a passthru backened that works on local machine. third card type? */ > >+ if (strcmp(BACKEND_CERTIFICATES, card->backend) == 0 > >+&& card->cert1 != NULL&& card->cert2 != NULL&& card->cert3 != NULL) { > >+ ret = emulated_initialize_vcard_from_certificates(card); > >+ } else { > >+ if (strcmp(BACKEND_CERTIFICATES, card->backend) == 0) { > >+ printf("%s: you must provide all three certs for certificates backend\n", > >+ EMULATED_DEV_NAME); > >+ return -1; > >+ } > >+ if (card->backend&& strcmp(BACKEND_NSS_EMULATED, card->backend) != 0) { > >+ printf("%s: bad backend specified. The options are:\n%s (default), %s.\n", > >+ EMULATED_DEV_NAME, BACKEND_NSS_EMULATED, BACKEND_CERTIFICATES); > >+ return -1; > >+ } > >+ /* default to mirroring the local hardware readers */ > >+ ret = wrap_vcard_emul_init(NULL); > >+ } > >+ if (ret != VCARD_EMUL_OK) { > >+ printf("%s: failed to initialize vcard\n", EMULATED_DEV_NAME); > >+ return -1; > >+ } > >+ rv = pthread_create(&thread_id, NULL, event_thread, card); > >+ if (rv< 0) { > >+ printf("%s: error creating event thread\n", EMULATED_DEV_NAME); > >+ return -1; > >+ } > > Why can't this be implemented with qemu_set_fd_handler? The event > thread si just spinning on read. > I've looked a little more closely at why I did it exactly this way. The short answer is that both threads are not spinning, they are waiting on conditions, the event_thread on the libcacard vevent_queue condition, and the apdu_thread on the ccid-card-emulated internal guest_apdu queue condition, triggered by the main thread. This is the ccid-card-emulated threads and flow (hope it's understandable): libcacard details: vcard_emul_event_thread for each reader blocking on SECMOD_WaitForAnyTokenEvent pushes events to vevent queue ccid-card-emulated details: event_thread read vevent queue -> {reader,card}x{insert,remove} -> write to apdu_pipe (writes to apdu_pipe are mutex protected) handle_apdu_thread pop guest_apdu_list -> vreader_xfr_bytes -> write result to apdu_pipe main_thread usb event apdu from guest -> push to guest_apdu_list apdu_pipe read apdu to guest ccid attach ccid detach ccid card removed ccid card inserted If I had a mechanism to let the qemu main loop wait on a mutex or condition variable I could remove both event_thread and handle_apdu_thread. (reading a little more about this let me to believe the solution I came up with, i.e. a queue and a condition variable, is commonly used for this in POSIX). But right now: I need a separate thread to block on the vevent queue condition (pthread_cond_t/CONDITION_VARIABLE in linux/windows). I need a separate thread to call vreader_xfr_bytes (it may block on the NSS thread). Alternatives: launch a thread for each apdu from guest: since the apdu's are linear any way (i.e. each one would wait for the next) this seems wasteful. Since we need to wait on a condition signaled by NSS, there is no alternative then having a separate thread for that. Alon > Regards, > > Anthony Liguori > > >+ rv = pthread_create(&thread_id, NULL, handle_apdu_thread, card); > >+ if (rv< 0) { > >+ printf("%s: error creating handle_apdu thread\n", EMULATED_DEV_NAME); > >+ return -1; > >+ } > >+ return 0; > >+} > >+ > >+static int emulated_exitfn(CCIDCardState *base) > >+{ > >+ EmulatedState *card = DO_UPCAST(EmulatedState, base, base); > >+ VEvent *vevent = vevent_new(VEVENT_LAST, NULL, NULL); > >+ > >+ vevent_queue_vevent(vevent); /* stop vevent thread */ > >+ pthread_mutex_lock(&card->apdu_thread_quit_mutex); > >+ card->quit_apdu_thread = 1; /* stop handle_apdu thread */ > >+ pthread_cond_signal(&card->handle_apdu_cond); > >+ pthread_cond_wait(&card->apdu_thread_quit_cond,&card->apdu_thread_quit_mutex); > >+ /* handle_apdu thread stopped, can destroy all of it's mutexes */ > >+ pthread_cond_destroy(&card->handle_apdu_cond); > >+ pthread_cond_destroy(&card->apdu_thread_quit_cond); > >+ pthread_mutex_destroy(&card->apdu_thread_quit_mutex); > >+ pthread_mutex_destroy(&card->handle_apdu_mutex); > >+ pthread_mutex_destroy(&card->vreader_mutex); > >+ pthread_mutex_destroy(&card->event_list_mutex); > >+ return 0; > >+} > >+ > >+static CCIDCardInfo emulated_card_info = { > >+ .qdev.name = EMULATED_DEV_NAME, > >+ .qdev.size = sizeof(EmulatedState), > >+ .initfn = emulated_initfn, > >+ .exitfn = emulated_exitfn, > >+ .get_atr = emulated_get_atr, > >+ .apdu_from_guest = emulated_apdu_from_guest, > >+ .qdev.unplug = qdev_simple_unplug_cb, > >+ .qdev.props = (Property[]) { > >+ DEFINE_PROP_STRING("backend", EmulatedState, backend), > >+ DEFINE_PROP_STRING("cert1", EmulatedState, cert1), > >+ DEFINE_PROP_STRING("cert2", EmulatedState, cert2), > >+ DEFINE_PROP_STRING("cert3", EmulatedState, cert3), > >+ DEFINE_PROP_STRING("db", EmulatedState, db), > >+ DEFINE_PROP_UINT8("debug", EmulatedState, debug, 0), > >+ DEFINE_PROP_END_OF_LIST(), > >+ }, > >+}; > >+ > >+static void ccid_card_emulated_register_devices(void) > >+{ > >+ ccid_card_qdev_register(&emulated_card_info); > >+} > >+ > >+device_init(ccid_card_emulated_register_devices) > >diff --git a/hw/ccid-card-passthru.c b/hw/ccid-card-passthru.c > >index 6ec4f21..f9fb82c 100644 > >--- a/hw/ccid-card-passthru.c > >+++ b/hw/ccid-card-passthru.c > >@@ -253,7 +253,6 @@ static CCIDCardInfo passthru_card_info = { > > .exitfn = passthru_exitfn, > > .get_atr = passthru_get_atr, > > .apdu_from_guest = passthru_apdu_from_guest, > >- .qdev.unplug = qdev_simple_unplug_cb, > > .qdev.props = (Property[]) { > > DEFINE_PROP_CHR("chardev", PassthruState, cs), > > DEFINE_PROP_UINT8("debug", PassthruState, debug, 0), > ^ permalink raw reply [flat|nested] 35+ messages in thread
* [Qemu-devel] [PATCH 5/7] ccid: add docs 2011-01-11 8:42 [Qemu-devel] [PATCH 0/7] usb-ccid (v15) Alon Levy ` (3 preceding siblings ...) 2011-01-11 8:42 ` [Qemu-devel] [PATCH 4/7] ccid: add ccid-card-emulated device (v2) Alon Levy @ 2011-01-11 8:42 ` Alon Levy 2011-01-11 8:42 ` [Qemu-devel] [PATCH 6/7] ccid: configure: add --enable/disable and nss only disable Alon Levy ` (3 subsequent siblings) 8 siblings, 0 replies; 35+ messages in thread From: Alon Levy @ 2011-01-11 8:42 UTC (permalink / raw) To: qemu-devel Add documentation for the usb-ccid device and accompanying two card devices, ccid-card-emulated and ccid-card-passthru. --- docs/ccid.txt | 135 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 files changed, 135 insertions(+), 0 deletions(-) create mode 100644 docs/ccid.txt diff --git a/docs/ccid.txt b/docs/ccid.txt new file mode 100644 index 0000000..b8e504a --- /dev/null +++ b/docs/ccid.txt @@ -0,0 +1,135 @@ +Qemu CCID Device Documentation. + +Contents +1. USB CCID device +2. Building +3. Using ccid-card-emulated with hardware +4. Using ccid-card-emulated with certificates +5. Using ccid-card-passthru with client side hardware +6. Using ccid-card-passthru with client side certificates +7. Passthrough protocol scenario +8. libcacard + +1. USB CCID device + +The USB CCID device is a USB device implementing the CCID specification, which +lets one connect smart card readers that implement the same spec. For more +information see the specification: + + Universal Serial Bus + Device Class: Smart Card + CCID + Specification for + Integrated Circuit(s) Cards Interface Devices + Revision 1.1 + April 22rd, 2005 + +Smartcard are used for authentication, single sign on, decryption in +public/private schemes and digital signatures. A smartcard reader on the client +cannot be used on a guest with simple usb passthrough since it will then not be +available on the client, possibly locking the computer when it is "removed". On +the other hand this device can let you use the smartcard on both the client and +the guest machine. It is also possible to have a completely virtual smart card +reader and smart card (i.e. not backed by a physical device) using this device. + +2. Building + +The cryptographic functions and access to the physical card is done via NSS. + +Installing NSS: + +In redhat/fedora: + yum install nss-devel +In ubuntu/debian: + apt-get install libnss3-dev + (not tested on ubuntu) + +Configuring and building: + ./configure --enable-smartcard && make + +3. Using ccid-card-emulated with hardware + +Assuming you have a working smartcard on the host with the current +user, using NSS, qemu acts as another NSS client using ccid-card-emulated: + + qemu -usb -device usb-ccid -device ccid-card-emualated + +4. Using ccid-card-emulated with certificates + +You must create the certificates. This is a one time process. We use NSS +certificates: + + certutil -d /etc/pki/nssdb -x -t "CT,CT,CT" -S -s "CN=cert1" -n cert1 + +Note: you must have exactly three certificates. + +Assuming the current user can access the certificates (use certutil -L to +verify), you can use the emulated card type with the certificates backend: + + qemu -usb -device usb-ccid -device ccid-card-emulated,backend=certificates,cert1=cert1,cert2=cert2,cert3=cert3 + +5. Using ccid-card-passthru with client side hardware + +on the host specify the ccid-card-passthru device with a suitable chardev: + + qemu -chardev socket,server,host=0.0.0.0,port=2001,id=ccid,nowait -usb -device usb-ccid -device ccid-card-passthru,chardev=ccid + +on the client run vscclient, built when you built the libcacard library: + libcacard/vscclient <qemu-host> 2001 + +6. Using ccid-card-passthru with client side certificates + +Run qemu as per #5, and run vscclient as follows: +(Note: vscclient command line interface is in a state of change) + + libcacard/vscclient -e "db=\"/etc/pki/nssdb\" use_hw=no soft=(,Test,CAC,,cert1,cert2,cert3)" <qemu-host> 2001 + +7. Passthrough protocol scenario + +This is a typical interchange of messages when using the passthru card device. +usb-ccid is a usb device. It defaults to an unattached usb device on startup. +usb-ccid expects a chardev and expects the protocol defined in +cac_card/vscard_common.h to be passed over that. +The usb-ccid device can be in one of three modes: + * detached + * attached with no card + * attached with card + +A typical interchange is: (the arrow shows who started each exchange, it can be client +originated or guest originated) + +client event | vscclient | passthru | usb-ccid | guest event +---------------------------------------------------------------------------------------------- + | VSC_Init | | | + | VSC_ReaderAdd | | attach | + | | | | sees new usb device. +card inserted -> | | | | + | VSC_ATR | insert | insert | see new card + | | | | + | VSC_APDU | VSC_APDU | | <- guest sends APDU +client<->physical | | | | +card APDU exchange| | | | +client response ->| VSC_APDU | VSC_APDU | | receive APDU response + ... + [APDU<->APDU repeats several times] + ... +card removed -> | | | | + | VSC_CardRemove | remove | remove | card removed + ... + [(card insert, apdu's, card remove) repeat] + ... +kill/quit | | | | + vscclient | | | | + | VSC_ReaderRemove | | detach | + | | | | usb device removed. + + +8. libcacard + +ccid-card-passthru and vscclient use libcacard as the card emulator. +libcacard implements a completely virtual CAC (DoD standard for smart cards) +compliant card and uses NSS to actually retrive certificates and do any +encryption using the backend (real reader + card or file backed certificates). + +For documentation of cac_card see README in libcacard subdirectory. + -- 1.7.3.4 ^ permalink raw reply related [flat|nested] 35+ messages in thread
* [Qemu-devel] [PATCH 6/7] ccid: configure: add --enable/disable and nss only disable 2011-01-11 8:42 [Qemu-devel] [PATCH 0/7] usb-ccid (v15) Alon Levy ` (4 preceding siblings ...) 2011-01-11 8:42 ` [Qemu-devel] [PATCH 5/7] ccid: add docs Alon Levy @ 2011-01-11 8:42 ` Alon Levy 2011-01-11 8:42 ` [Qemu-devel] [PATCH 6/6] ccid: configure: add --enable-smartcard and --disable-smartcard Alon Levy ` (2 subsequent siblings) 8 siblings, 0 replies; 35+ messages in thread From: Alon Levy @ 2011-01-11 8:42 UTC (permalink / raw) To: qemu-devel add --enable-smartcard and --disable-smartcard flags, and let the nss check only disable building the ccid-card-emulated device, since both the usb-ccid and ccid-card-passthru don't depend on it. --- Makefile.objs | 3 ++- Makefile.target | 2 +- configure | 39 ++++++++++++++++++++++++++++----------- 3 files changed, 31 insertions(+), 13 deletions(-) diff --git a/Makefile.objs b/Makefile.objs index 303b642..03cc91e 100644 --- a/Makefile.objs +++ b/Makefile.objs @@ -197,7 +197,8 @@ hw-obj-$(CONFIG_FDC) += fdc.o hw-obj-$(CONFIG_ACPI) += acpi.o acpi_piix4.o hw-obj-$(CONFIG_APM) += pm_smbus.o apm.o hw-obj-$(CONFIG_DMA) += dma.o -hw-obj-$(CONFIG_SMARTCARD) += usb-ccid.o ccid-card-passthru.o ccid-card-emulated.o +hw-obj-$(CONFIG_SMARTCARD) += usb-ccid.o ccid-card-passthru.o +hw-obj-$(CONFIG_SMARTCARD_NSS) += ccid-card-emulated.o # PPC devices hw-obj-$(CONFIG_OPENPIC) += openpic.o diff --git a/Makefile.target b/Makefile.target index 419a530..0222c6b 100644 --- a/Makefile.target +++ b/Makefile.target @@ -335,7 +335,7 @@ obj-y += $(addprefix $(HWDIR)/, $(hw-obj-y)) endif # CONFIG_SOFTMMU -obj-y += $(addprefix ../libcacard/, $(libcacard-$(CONFIG_SMARTCARD))) +obj-y += $(addprefix ../libcacard/, $(libcacard-$(CONFIG_SMARTCARD_NSS))) obj-y += $(addprefix ../, $(trace-obj-y)) obj-$(CONFIG_GDBSTUB_XML) += gdbstub-xml.o diff --git a/configure b/configure index 4567057..de2e498 100755 --- a/configure +++ b/configure @@ -335,6 +335,7 @@ trace_file="trace" spice="" rbd="" smartcard="yes" +smartcard_nss="yes" # OS specific if check_define __linux__ ; then @@ -748,6 +749,10 @@ for opt do ;; --enable-rbd) rbd="yes" ;; + --disable-smartcard) smartcard="no" + ;; + --enable-smartcard) smartcard="yes" + ;; *) echo "ERROR: unknown option $opt"; show_help="yes" ;; esac @@ -942,6 +947,8 @@ echo " Default:trace-<pid>" echo " --disable-spice disable spice" echo " --enable-spice enable spice" echo " --enable-rbd enable building the rados block device (rbd)" +echo " --disable-smartcard disable smartcard support" +echo " --enable-smartcard enable smartcard support" echo "" echo "NOTE: The object files are built at the place where configure is launched" exit 1 @@ -2210,16 +2217,21 @@ EOF fi # check for libcacard for smartcard support -smartcard_cflags="-I\$(SRC_PATH)/libcacard" -libcacard_libs=$($pkgconfig --libs nss 2>/dev/null) -libcacard_cflags=$($pkgconfig --cflags nss) -# TODO - what's the minimal nss version we support? -if $pkgconfig --atleast-version=3.12.8 nss; then - smartcard="yes" - QEMU_CFLAGS="$QEMU_CFLAGS $smartcard_cflags $libcacard_cflags" - LIBS="$libcacard_libs $LIBS" -else - smartcard="no" +if test "$smartcard" != "no" ; then + smartcard_cflags="-I\$(SRC_PATH)/libcacard" + libcacard_libs=$($pkgconfig --libs nss 2>/dev/null) + libcacard_cflags=$($pkgconfig --cflags nss 2>/dev/null) + # TODO - what's the minimal nss version we support? + if $pkgconfig --atleast-version=3.12.8 nss >/dev/null 2>&1 ; then + smartcard_nss="yes" + QEMU_CFLAGS="$QEMU_CFLAGS $smartcard_cflags $libcacard_cflags" + LIBS="$libcacard_libs $LIBS" + else + smartcard_nss="no" + fi +fi +if test "$smartcard" == "no" ; then + smartcard_nss="no" fi ########################################## @@ -2456,6 +2468,7 @@ echo "spice support $spice" echo "rbd support $rbd" echo "xfsctl support $xfs" echo "smartcard support $smartcard" +echo " \_ nss support $smartcard_nss" if test $sdl_too_old = "yes"; then echo "-> Your SDL version is too old - please upgrade to have SDL support" @@ -2729,6 +2742,10 @@ if test "$smartcard" = "yes" ; then echo "CONFIG_SMARTCARD=y" >> $config_host_mak fi +if test "$smartcard_nss" = "yes" ; then + echo "CONFIG_SMARTCARD_NSS=y" >> $config_host_mak +fi + # XXX: suppress that if [ "$bsd" = "yes" ] ; then echo "CONFIG_BSD=y" >> $config_host_mak @@ -3059,7 +3076,7 @@ fi if test "$target_darwin_user" = "yes" ; then echo "CONFIG_DARWIN_USER=y" >> $config_target_mak fi -if test "$smartcard" = "yes" ; then +if test "$smartcard_nss" = "yes" ; then echo "subdir-$target: subdir-libcacard" >> $config_host_mak echo "libcacard_libs=$libcacard_libs" >> $config_host_mak echo "libcacard_cflags=$libcacard_cflags" >> $config_host_mak -- 1.7.3.4 ^ permalink raw reply related [flat|nested] 35+ messages in thread
* [Qemu-devel] [PATCH 6/6] ccid: configure: add --enable-smartcard and --disable-smartcard 2011-01-11 8:42 [Qemu-devel] [PATCH 0/7] usb-ccid (v15) Alon Levy ` (5 preceding siblings ...) 2011-01-11 8:42 ` [Qemu-devel] [PATCH 6/7] ccid: configure: add --enable/disable and nss only disable Alon Levy @ 2011-01-11 8:42 ` Alon Levy 2011-01-11 9:03 ` Alon Levy 2011-01-11 8:42 ` [Qemu-devel] [PATCH 7/7] ccid: add qdev description strings Alon Levy 2011-01-17 15:56 ` [Qemu-devel] [PATCH 0/7] usb-ccid (v15) Alon Levy 8 siblings, 1 reply; 35+ messages in thread From: Alon Levy @ 2011-01-11 8:42 UTC (permalink / raw) To: qemu-devel --- configure | 28 ++++++++++++++++++---------- 1 files changed, 18 insertions(+), 10 deletions(-) diff --git a/configure b/configure index 4567057..ebc8250 100755 --- a/configure +++ b/configure @@ -748,6 +748,10 @@ for opt do ;; --enable-rbd) rbd="yes" ;; + --disable-smartcard) smartcard="no" + ;; + --enable-smartcard) smartcard="yes" + ;; *) echo "ERROR: unknown option $opt"; show_help="yes" ;; esac @@ -942,6 +946,8 @@ echo " Default:trace-<pid>" echo " --disable-spice disable spice" echo " --enable-spice enable spice" echo " --enable-rbd enable building the rados block device (rbd)" +echo " --disable-smartcard disable smartcard support" +echo " --enable-smartcard enable smartcard support" echo "" echo "NOTE: The object files are built at the place where configure is launched" exit 1 @@ -2210,16 +2216,18 @@ EOF fi # check for libcacard for smartcard support -smartcard_cflags="-I\$(SRC_PATH)/libcacard" -libcacard_libs=$($pkgconfig --libs nss 2>/dev/null) -libcacard_cflags=$($pkgconfig --cflags nss) -# TODO - what's the minimal nss version we support? -if $pkgconfig --atleast-version=3.12.8 nss; then - smartcard="yes" - QEMU_CFLAGS="$QEMU_CFLAGS $smartcard_cflags $libcacard_cflags" - LIBS="$libcacard_libs $LIBS" -else - smartcard="no" +if test "$smartcard" != "no" ; then + smartcard_cflags="-I\$(SRC_PATH)/libcacard" + libcacard_libs=$($pkgconfig --libs nss 2>/dev/null) + libcacard_cflags=$($pkgconfig --cflags nss) + # TODO - what's the minimal nss version we support? + if $pkgconfig --atleast-version=3.12.8 nss; then + smartcard="yes" + QEMU_CFLAGS="$QEMU_CFLAGS $smartcard_cflags $libcacard_cflags" + LIBS="$libcacard_libs $LIBS" + else + smartcard="no" + fi fi ########################################## -- 1.7.3.4 ^ permalink raw reply related [flat|nested] 35+ messages in thread
* Re: [Qemu-devel] [PATCH 6/6] ccid: configure: add --enable-smartcard and --disable-smartcard 2011-01-11 8:42 ` [Qemu-devel] [PATCH 6/6] ccid: configure: add --enable-smartcard and --disable-smartcard Alon Levy @ 2011-01-11 9:03 ` Alon Levy 0 siblings, 0 replies; 35+ messages in thread From: Alon Levy @ 2011-01-11 9:03 UTC (permalink / raw) To: qemu-devel On Tue, Jan 11, 2011 at 10:42:38AM +0200, Alon Levy wrote: > --- > configure | 28 ++++++++++++++++++---------- > 1 files changed, 18 insertions(+), 10 deletions(-) > [snip] Another oops, not a good morning. Ignore this patch, the real 6/7 will be arriving momentarily. (forgot to clear the patches.usb_ccid.v15 directory). Alon ^ permalink raw reply [flat|nested] 35+ messages in thread
* [Qemu-devel] [PATCH 7/7] ccid: add qdev description strings 2011-01-11 8:42 [Qemu-devel] [PATCH 0/7] usb-ccid (v15) Alon Levy ` (6 preceding siblings ...) 2011-01-11 8:42 ` [Qemu-devel] [PATCH 6/6] ccid: configure: add --enable-smartcard and --disable-smartcard Alon Levy @ 2011-01-11 8:42 ` Alon Levy 2011-01-17 15:56 ` [Qemu-devel] [PATCH 0/7] usb-ccid (v15) Alon Levy 8 siblings, 0 replies; 35+ messages in thread From: Alon Levy @ 2011-01-11 8:42 UTC (permalink / raw) To: qemu-devel --- hw/ccid-card-emulated.c | 1 + hw/ccid-card-passthru.c | 1 + hw/usb-ccid.c | 1 + 3 files changed, 3 insertions(+), 0 deletions(-) diff --git a/hw/ccid-card-emulated.c b/hw/ccid-card-emulated.c index 5531ce1..5e85e8e 100644 --- a/hw/ccid-card-emulated.c +++ b/hw/ccid-card-emulated.c @@ -509,6 +509,7 @@ static int emulated_exitfn(CCIDCardState *base) static CCIDCardInfo emulated_card_info = { .qdev.name = EMULATED_DEV_NAME, + .qdev.desc = "emulated smartcard", .qdev.size = sizeof(EmulatedState), .initfn = emulated_initfn, .exitfn = emulated_exitfn, diff --git a/hw/ccid-card-passthru.c b/hw/ccid-card-passthru.c index f9fb82c..351651c 100644 --- a/hw/ccid-card-passthru.c +++ b/hw/ccid-card-passthru.c @@ -247,6 +247,7 @@ static VMStateDescription passthru_vmstate = { static CCIDCardInfo passthru_card_info = { .qdev.name = PASSTHRU_DEV_NAME, + .qdev.desc = "passthrough smartcard", .qdev.size = sizeof(PassthruState), .qdev.vmsd = &passthru_vmstate, .initfn = passthru_initfn, diff --git a/hw/usb-ccid.c b/hw/usb-ccid.c index 58f69a6..7b2ea2a 100644 --- a/hw/usb-ccid.c +++ b/hw/usb-ccid.c @@ -1330,6 +1330,7 @@ static VMStateDescription ccid_vmstate = { static struct USBDeviceInfo ccid_info = { .product_desc = "QEMU USB CCID", .qdev.name = CCID_DEV_NAME, + .qdev.desc = "CCID Rev 1.1 smartcard reader", .qdev.size = sizeof(USBCCIDState), .init = ccid_initfn, .handle_packet = usb_generic_handle_packet, -- 1.7.3.4 ^ permalink raw reply related [flat|nested] 35+ messages in thread
* Re: [Qemu-devel] [PATCH 0/7] usb-ccid (v15) 2011-01-11 8:42 [Qemu-devel] [PATCH 0/7] usb-ccid (v15) Alon Levy ` (7 preceding siblings ...) 2011-01-11 8:42 ` [Qemu-devel] [PATCH 7/7] ccid: add qdev description strings Alon Levy @ 2011-01-17 15:56 ` Alon Levy 8 siblings, 0 replies; 35+ messages in thread From: Alon Levy @ 2011-01-17 15:56 UTC (permalink / raw) To: qemu-devel Ping On Tue, Jan 11, 2011 at 10:42:31AM +0200, Alon Levy wrote: > This patchset adds three new devices, usb-ccid, ccid-card-passthru and > ccid-card-emulated, providing a CCID bus, a simple passthru protocol > implementing card requiring a client, and a standalone emulated card. > > It also introduces a new directory libcaccard with CAC card emulation, > CAC is a type of ISO 7816 smart card. > > Tree for pull: git://anongit.freedesktop.org/~alon/qemu usb_ccid.v15 > > v14-v15 changes: > * add patch with --enable-smartcard and --disable-smartcard and only > disable ccid-card-emulated if nss not found. > * add patch with description strings > * s/libcaccard/libcacard/ in docs/ccid.txt > > v13-v14 changes: > - support device_del/device_add on ccid-card-* and usb-ccid > * usb-ccid: > * lose card reference when card device deleted > * check slot number and deny adding a slot if one is already added. > * ccid-card-*: use qdev_simple_unplug_cb in both emulated and passthru ccid cards, > the exitfn already takes care of triggering card removal in the usb dev. > * libcacard: > * remove double include of config-host.mak > * add replay of card events to libcacard to support second and more emulation > * don't initialize more then once (doesn't support it right now, so one > thread, NSS thread, is left when device_del is done) > * add VCARD_EMUL_INIT_ALREADY_INITED > * ccid-card-emulated: > * take correct mutexes on signaling to fix deadlocks on device_del > * allow card insertion/removal event without proper reader insertion event > > v12-v13 changes: > * libcacard: > * fix Makefile clean to remove vscclient > * fix double include of config-host in Makefile > * usb-ccid: remove attach/detach logic, usb is always attached. Guest > doesn't care if there is a reader attached with no card anyway. > * ccid-card-passthru: don't close chr_dev on removal, makes it possible > to use device_del/device_add to create remove/insertion for debugging. > > v11-v12 changes: > * fix out of tree build > > v10-v11 changes: > * fix last patch that removed one of the doc files. > * updated flow table in docs/ccid.txt > > v8-v10 changes: > * usb-ccid: > * add slot for future use (Gerd) > * ifdef ENABLE_MIGRATION for migration support on account of usb > migration not being ready in general. (Gerd) > * verbosified commit messages. (Gerd) > * put libcacard docs in libcacard commit. (Gerd) > > v8-v9 changes: > * Blue Swirl comments: > * white space fixes > * enabled by default, disabled only if missing nss > * forgotten fix from v8 (don't build libcacard.so) > * added a note about device being little endian > * library renamed from libcaccard to libcacard > * squashed both of libcacard patches, they touched different files anyway. > > v7-v8 changes: > * Blue Swirl comments: > * usb-ccid: deannonymize some structs > * usb-ccid: coding style change - answer_t and bulk_in_t fixed > * usb-ccid: handle endianess conversion between guest and host > * usb-ccid: s/ccid_bulk_in_copy_out/ccid_bulk_in_copy_to_guest/ > * ccid-card-emulated: fix segfault if backend not specified > * ccid-card-emulated: let last reader inserted win > * libcaccard: remove double vscard_common.h > > v6->v7 changes: > * external libcaccard became internal directory libcaccard > * statically link object files into qemu > * produce libcaccard.so for usage by external projects > * applied coding style to new code (please check me) > - did not use the qemu options parsing for libcaccard, since > it seems to draw large amounts of qemu code (monitor for instance). > > v5->v6 changes: > * really remove static debug (I apologize for claiming to have done so before) > > v4->v5 changes: > * rebased to latest > * remove static debug in card devices > * fix --enable-smartcard to link > * stall instead of assert when exceeding BULK_OUT_DATA_SIZE > * make ccid_reserve_recv_buf for too large len discard message, not exit > * make ccid_reserve_recv_buf return void* > * fix typo > * remove commented code in VMState > > v3->v4: > * remove ccid field in CCIDBus > * remove static debug in bus > * add back docs > > v2->v3: > * split into bus (usb-ccid.c, uses ccid.h) and card (ccid-card-passthru.c). > * removed documentation (being revised). > > v1->v2: > * all QSIMPLEQ turned into fixed sized rings > * all allocated buffers turned into fixed size buffers > * added migration support > * added a message to tell client qemu has migrated to ip:port > * for lack of monitor commands ip:port are 0:0, which causes the updated > vscclient to connect to one port higher on the same host. will add monitor > commands in a separate patch. tested with current setup. > > Alon Levy (6): > usb-ccid: add CCID bus > ccid: add passthru card device > ccid: add ccid-card-emulated device (v2) > ccid: add docs > ccid: configure: add --enable/disable and nss only disable > ccid: add qdev description strings > > Robert Relyea (1): > libcacard: initial commit after coding style fixes > > Makefile | 6 +- > Makefile.objs | 7 + > Makefile.target | 2 + > configure | 48 ++ > docs/ccid.txt | 135 +++++ > docs/libcacard.txt | 483 +++++++++++++++ > hw/ccid-card-emulated.c | 535 +++++++++++++++++ > hw/ccid-card-passthru.c | 273 +++++++++ > hw/ccid.h | 35 ++ > hw/usb-ccid.c | 1356 +++++++++++++++++++++++++++++++++++++++++++ > libcacard/Makefile | 14 + > libcacard/cac.c | 411 +++++++++++++ > libcacard/cac.h | 20 + > libcacard/card_7816.c | 780 +++++++++++++++++++++++++ > libcacard/card_7816.h | 60 ++ > libcacard/card_7816t.h | 163 ++++++ > libcacard/config.h | 81 +++ > libcacard/event.c | 112 ++++ > libcacard/eventt.h | 28 + > libcacard/link_test.c | 20 + > libcacard/mutex.h | 59 ++ > libcacard/passthru.c | 612 +++++++++++++++++++ > libcacard/passthru.h | 50 ++ > libcacard/vcard.c | 350 +++++++++++ > libcacard/vcard.h | 85 +++ > libcacard/vcard_emul.h | 62 ++ > libcacard/vcard_emul_nss.c | 1171 +++++++++++++++++++++++++++++++++++++ > libcacard/vcard_emul_type.c | 60 ++ > libcacard/vcard_emul_type.h | 29 + > libcacard/vcardt.h | 66 +++ > libcacard/vevent.h | 26 + > libcacard/vreader.c | 526 +++++++++++++++++ > libcacard/vreader.h | 54 ++ > libcacard/vreadert.h | 23 + > libcacard/vscard_common.h | 130 ++++ > libcacard/vscclient.c | 710 ++++++++++++++++++++++ > 36 files changed, 8580 insertions(+), 2 deletions(-) > create mode 100644 docs/ccid.txt > create mode 100644 docs/libcacard.txt > create mode 100644 hw/ccid-card-emulated.c > create mode 100644 hw/ccid-card-passthru.c > create mode 100644 hw/ccid.h > create mode 100644 hw/usb-ccid.c > create mode 100644 libcacard/Makefile > create mode 100644 libcacard/cac.c > create mode 100644 libcacard/cac.h > create mode 100644 libcacard/card_7816.c > create mode 100644 libcacard/card_7816.h > create mode 100644 libcacard/card_7816t.h > create mode 100644 libcacard/config.h > create mode 100644 libcacard/event.c > create mode 100644 libcacard/eventt.h > create mode 100644 libcacard/link_test.c > create mode 100644 libcacard/mutex.h > create mode 100644 libcacard/passthru.c > create mode 100644 libcacard/passthru.h > create mode 100644 libcacard/vcard.c > create mode 100644 libcacard/vcard.h > create mode 100644 libcacard/vcard_emul.h > create mode 100644 libcacard/vcard_emul_nss.c > create mode 100644 libcacard/vcard_emul_type.c > create mode 100644 libcacard/vcard_emul_type.h > create mode 100644 libcacard/vcardt.h > create mode 100644 libcacard/vevent.h > create mode 100644 libcacard/vreader.c > create mode 100644 libcacard/vreader.h > create mode 100644 libcacard/vreadert.h > create mode 100644 libcacard/vscard_common.h > create mode 100644 libcacard/vscclient.c > > -- > 1.7.3.4 > > ^ permalink raw reply [flat|nested] 35+ messages in thread
* [Qemu-devel] [PATCH v20 0/7] usb-ccid @ 2011-02-23 11:20 Alon Levy 2011-02-23 11:20 ` [Qemu-devel] [PATCH 1/7] usb-ccid: add CCID bus Alon Levy 0 siblings, 1 reply; 35+ messages in thread From: Alon Levy @ 2011-02-23 11:20 UTC (permalink / raw) To: qemu-devel This patchset adds three new devices, usb-ccid, ccid-card-passthru and ccid-card-emulated, providing a CCID bus, a simple passthru protocol implementing card requiring a client, and a standalone emulated card. It also introduces a new directory libcaccard with CAC card emulation, CAC is a type of ISO 7816 smart card. Tree for pull: git://anongit.freedesktop.org/~alon/qemu usb_ccid.v20 v19->v20 changes: * checkpatch.pl. Here are the remaining errors with explanation: * ignored 5 macro errors of the type "ERROR: Macros with complex values should be enclosed in parenthesis" because fixing them breaks current code, if it really bothers someone I can fix it. * four of them are in libcacard/card_7816t.h: /* give the subfields a unified look */ .. #define a_cla a_header->ah_cla /* class */ #define a_ins a_header->ah_ins /* instruction */ #define a_p1 a_header->ah_p1 /* parameter 1 */ #define a_p2 a_header->ah_p2 /* parameter 2 */ * and the fifth: #4946: FILE: libcacard/vcardt.h:31: +#define VCARD_ATR_PREFIX(size) 0x3b, 0x66+(size), 0x00, 0xff, \ + 'V', 'C', 'A', 'R', 'D', '_' * Ignored this warning since I couldn't figure it out, and it's a test file: WARNING: externs should be avoided in .c files #2343: FILE: libcacard/link_test.c:7: +VCardStatus cac_card_init(const char *flags, VCard *card, v18-v19 changes: * more merges, down to a single digit number of patches. * drop enumeration property, use string. * rebased (trivial) v17-v18 changes: * merge vscard_common.h patches. * actually provide a tree to pull. v16-v17 changes: * merged all the "v15->v16" patches * merged some more wherever it was easy (all same file commits). * added signed off by to first four patches * ccid.h: added copyright, removed underscore in defines, and replaced non C89 comments v15-v16 changes: * split vscard_common introducing patch for ease of review * sum of commit logs for the v15-v16 commits: (whitespace fixes removed for space, see original commit messages in later patches) * usb-ccid: * fix abort on client answer after card remove * enable migration * remove side affect code from asserts * return consistent self-powered state * mask out reserved bits in ccid_set_parameters * add missing abRFU in SetParameters (no affect on linux guest) * vscard_common.h protocol change: * VSCMsgInit capabilities and magic * removed ReaderResponse, will use Error instead with code==VSC_SUCCESS. * added Flush and FlushComplete, remove Reconnect. * define VSCARD_MAGIC * added error code VSC_SUCCESS. * ccid-card-passthru * return correct size * return error instead of assert if client sent too large ATR * don't assert if client sent too large a size, but add asserts for indices to buffer * reset vscard_in indices on chardev disconnect * handle init from client * error if no chardev supplied * use ntoh, hton * eradicate reader_id_t * remove Reconnect usage (removed from VSCARD protocol) * send VSC_SUCCESS on card insert/remove and reader add/remove * ccid-card-emulated * fix error reporting in initfn v14-v15 changes: * add patch with --enable-smartcard and --disable-smartcard and only disable ccid-card-emulated if nss not found. * add patch with description strings * s/libcaccard/libcacard/ in docs/ccid.txt v13-v14 changes: - support device_del/device_add on ccid-card-* and usb-ccid * usb-ccid: * lose card reference when card device deleted * check slot number and deny adding a slot if one is already added. * ccid-card-*: use qdev_simple_unplug_cb in both emulated and passthru ccid cards, the exitfn already takes care of triggering card removal in the usb dev. * libcacard: * remove double include of config-host.mak * add replay of card events to libcacard to support second and more emulation * don't initialize more then once (doesn't support it right now, so one thread, NSS thread, is left when device_del is done) * add VCARD_EMUL_INIT_ALREADY_INITED * ccid-card-emulated: * take correct mutexes on signaling to fix deadlocks on device_del * allow card insertion/removal event without proper reader insertion event v12-v13 changes: * libcacard: * fix Makefile clean to remove vscclient * fix double include of config-host in Makefile * usb-ccid: remove attach/detach logic, usb is always attached. Guest doesn't care if there is a reader attached with no card anyway. * ccid-card-passthru: don't close chr_dev on removal, makes it possible to use device_del/device_add to create remove/insertion for debugging. v11-v12 changes: * fix out of tree build v10-v11 changes: * fix last patch that removed one of the doc files. * updated flow table in docs/ccid.txt v8-v10 changes: * usb-ccid: * add slot for future use (Gerd) * ifdef ENABLE_MIGRATION for migration support on account of usb migration not being ready in general. (Gerd) * verbosified commit messages. (Gerd) * put libcacard docs in libcacard commit. (Gerd) v8-v9 changes: * Blue Swirl comments: * white space fixes * enabled by default, disabled only if missing nss * forgotten fix from v8 (don't build libcacard.so) * added a note about device being little endian * library renamed from libcaccard to libcacard * squashed both of libcacard patches, they touched different files anyway. v7-v8 changes: * Blue Swirl comments: * usb-ccid: deannonymize some structs * usb-ccid: coding style change - answer_t and bulk_in_t fixed * usb-ccid: handle endianess conversion between guest and host * usb-ccid: s/ccid_bulk_in_copy_out/ccid_bulk_in_copy_to_guest/ * ccid-card-emulated: fix segfault if backend not specified * ccid-card-emulated: let last reader inserted win * libcaccard: remove double vscard_common.h v6->v7 changes: * external libcaccard became internal directory libcaccard * statically link object files into qemu * produce libcaccard.so for usage by external projects * applied coding style to new code (please check me) - did not use the qemu options parsing for libcaccard, since it seems to draw large amounts of qemu code (monitor for instance). v5->v6 changes: * really remove static debug (I apologize for claiming to have done so before) v4->v5 changes: * rebased to latest * remove static debug in card devices * fix --enable-smartcard to link * stall instead of assert when exceeding BULK_OUT_DATA_SIZE * make ccid_reserve_recv_buf for too large len discard message, not exit * make ccid_reserve_recv_buf return void* * fix typo * remove commented code in VMState v3->v4: * remove ccid field in CCIDBus * remove static debug in bus * add back docs v2->v3: * split into bus (usb-ccid.c, uses ccid.h) and card (ccid-card-passthru.c). * removed documentation (being revised). v1->v2: * all QSIMPLEQ turned into fixed sized rings * all allocated buffers turned into fixed size buffers * added migration support * added a message to tell client qemu has migrated to ip:port * for lack of monitor commands ip:port are 0:0, which causes the updated vscclient to connect to one port higher on the same host. will add monitor commands in a separate patch. tested with current setup. Alon Levy (6): usb-ccid: add CCID bus introduce libcacard/vscard_common.h ccid: add passthru card device ccid: add ccid-card-emulated device ccid: add docs ccid: configure: improve --enable-smartcard flags Robert Relyea (1): libcacard: initial commit Makefile | 6 +- Makefile.objs | 7 + Makefile.target | 2 + configure | 60 ++ docs/ccid.txt | 135 +++++ docs/libcacard.txt | 483 +++++++++++++++ hw/ccid-card-emulated.c | 599 +++++++++++++++++++ hw/ccid-card-passthru.c | 337 +++++++++++ hw/ccid.h | 54 ++ hw/usb-ccid.c | 1391 +++++++++++++++++++++++++++++++++++++++++++ libcacard/Makefile | 14 + libcacard/cac.c | 411 +++++++++++++ libcacard/cac.h | 20 + libcacard/card_7816.c | 780 ++++++++++++++++++++++++ libcacard/card_7816.h | 60 ++ libcacard/card_7816t.h | 163 +++++ libcacard/event.c | 112 ++++ libcacard/eventt.h | 28 + libcacard/link_test.c | 20 + libcacard/mutex.h | 59 ++ libcacard/passthru.c | 612 +++++++++++++++++++ libcacard/passthru.h | 50 ++ libcacard/vcard.c | 350 +++++++++++ libcacard/vcard.h | 85 +++ libcacard/vcard_emul.h | 62 ++ libcacard/vcard_emul_nss.c | 1195 +++++++++++++++++++++++++++++++++++++ libcacard/vcard_emul_type.c | 60 ++ libcacard/vcard_emul_type.h | 29 + libcacard/vcardt.h | 66 ++ libcacard/vevent.h | 26 + libcacard/vreader.c | 526 ++++++++++++++++ libcacard/vreader.h | 54 ++ libcacard/vreadert.h | 23 + libcacard/vscard_common.h | 167 ++++++ libcacard/vscclient.c | 744 +++++++++++++++++++++++ 35 files changed, 8788 insertions(+), 2 deletions(-) create mode 100644 docs/ccid.txt create mode 100644 docs/libcacard.txt create mode 100644 hw/ccid-card-emulated.c create mode 100644 hw/ccid-card-passthru.c create mode 100644 hw/ccid.h create mode 100644 hw/usb-ccid.c create mode 100644 libcacard/Makefile create mode 100644 libcacard/cac.c create mode 100644 libcacard/cac.h create mode 100644 libcacard/card_7816.c create mode 100644 libcacard/card_7816.h create mode 100644 libcacard/card_7816t.h create mode 100644 libcacard/event.c create mode 100644 libcacard/eventt.h create mode 100644 libcacard/link_test.c create mode 100644 libcacard/mutex.h create mode 100644 libcacard/passthru.c create mode 100644 libcacard/passthru.h create mode 100644 libcacard/vcard.c create mode 100644 libcacard/vcard.h create mode 100644 libcacard/vcard_emul.h create mode 100644 libcacard/vcard_emul_nss.c create mode 100644 libcacard/vcard_emul_type.c create mode 100644 libcacard/vcard_emul_type.h create mode 100644 libcacard/vcardt.h create mode 100644 libcacard/vevent.h create mode 100644 libcacard/vreader.c create mode 100644 libcacard/vreader.h create mode 100644 libcacard/vreadert.h create mode 100644 libcacard/vscard_common.h create mode 100644 libcacard/vscclient.c -- 1.7.4.1 ^ permalink raw reply [flat|nested] 35+ messages in thread
* [Qemu-devel] [PATCH 1/7] usb-ccid: add CCID bus 2011-02-23 11:20 [Qemu-devel] [PATCH v20 0/7] usb-ccid Alon Levy @ 2011-02-23 11:20 ` Alon Levy 2011-03-14 13:54 ` Jes Sorensen 0 siblings, 1 reply; 35+ messages in thread From: Alon Levy @ 2011-02-23 11:20 UTC (permalink / raw) To: qemu-devel A CCID device is a smart card reader. It is a USB device, defined at [1]. This patch introduces the usb-ccid device that is a ccid bus. Next patches will introduce two card types to use it, a passthru card and an emulated card. [1] http://www.usb.org/developers/devclass_docs/DWG_Smart-Card_CCID_Rev110. Signed-off-by: Alon Levy <alevy@redhat.com> --- changes from v19->v20: * checkpatch.pl changes from v18->v19: * merged: ccid.h: add copyright, fix define and remove non C89 comments * add qdev.desc changes from v15->v16: Behavioral changes: * fix abort on client answer after card remove * enable migration * remove side affect code from asserts * return consistent self-powered state * mask out reserved bits in ccid_set_parameters * add missing abRFU in SetParameters (no affect on linux guest) whitefixes / comments / consts defines: * remove stale comment * remove ccid_print_pending_answers if no DEBUG_CCID * replace printf's with DPRINTF, remove DEBUG_CCID, add verbosity defines * use error_report * update copyright (most of the code is not original) * reword known bug comment * add missing closing quote in comment * add missing whitespace on one line * s/CCID_SetParameter/CCID_SetParameters/ * add comments * use define for max packet size Comment for "return consistent self-powered state": the Configuration Descriptor bmAttributes claims we are self powered, but we were returning not self powered to USB_REQ_GET_STATUS control message. In practice, this message is not sent by a linux 2.6.35.10-74.fc14.x86_64 guest (not tested on other guests), unless you issue lsusb -v as root (for example). --- Makefile.objs | 1 + configure | 6 + hw/ccid.h | 54 +++ hw/usb-ccid.c | 1391 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 1452 insertions(+), 0 deletions(-) create mode 100644 hw/ccid.h create mode 100644 hw/usb-ccid.c diff --git a/Makefile.objs b/Makefile.objs index c144df1..414d206 100644 --- a/Makefile.objs +++ b/Makefile.objs @@ -197,6 +197,7 @@ hw-obj-$(CONFIG_APM) += pm_smbus.o apm.o hw-obj-$(CONFIG_DMA) += dma.o hw-obj-$(CONFIG_HPET) += hpet.o hw-obj-$(CONFIG_APPLESMC) += applesmc.o +hw-obj-$(CONFIG_SMARTCARD) += usb-ccid.o # PPC devices hw-obj-$(CONFIG_OPENPIC) += openpic.o diff --git a/configure b/configure index 791b71d..147aab3 100755 --- a/configure +++ b/configure @@ -174,6 +174,7 @@ trace_backend="nop" trace_file="trace" spice="" rbd="" +smartcard="yes" # parse CC options first for opt do @@ -2523,6 +2524,7 @@ echo "Trace output file $trace_file-<pid>" echo "spice support $spice" echo "rbd support $rbd" echo "xfsctl support $xfs" +echo "smartcard support $smartcard" if test $sdl_too_old = "yes"; then echo "-> Your SDL version is too old - please upgrade to have SDL support" @@ -2804,6 +2806,10 @@ if test "$spice" = "yes" ; then echo "CONFIG_SPICE=y" >> $config_host_mak fi +if test "$smartcard" = "yes" ; then + echo "CONFIG_SMARTCARD=y" >> $config_host_mak +fi + # XXX: suppress that if [ "$bsd" = "yes" ] ; then echo "CONFIG_BSD=y" >> $config_host_mak diff --git a/hw/ccid.h b/hw/ccid.h new file mode 100644 index 0000000..4350bc2 --- /dev/null +++ b/hw/ccid.h @@ -0,0 +1,54 @@ +/* + * CCID Passthru Card Device emulation + * + * Copyright (c) 2011 Red Hat. + * Written by Alon Levy. + * + * This code is licenced under the GNU LGPL, version 2 or later. + */ + +#ifndef CCID_H +#define CCID_H + +#include "qdev.h" + +typedef struct CCIDCardState CCIDCardState; +typedef struct CCIDCardInfo CCIDCardInfo; + +/* state of the CCID Card device (i.e. hw/ccid-card-*.c) + */ +struct CCIDCardState { + DeviceState qdev; + uint32_t slot; /* For future use with multiple slot reader. */ +}; + +/* callbacks to be used by the CCID device (hw/usb-ccid.c) to call + * into the smartcard device (hw/ccid-card-*.c) + */ +struct CCIDCardInfo { + DeviceInfo qdev; + void (*print)(Monitor *mon, CCIDCardState *card, int indent); + const uint8_t *(*get_atr)(CCIDCardState *card, uint32_t *len); + void (*apdu_from_guest)(CCIDCardState *card, + const uint8_t *apdu, + uint32_t len); + int (*exitfn)(CCIDCardState *card); + int (*initfn)(CCIDCardState *card); +}; + +/* API for smartcard calling the CCID device (used by hw/ccid-card-*.c) + */ +void ccid_card_send_apdu_to_guest(CCIDCardState *card, + uint8_t *apdu, + uint32_t len); +void ccid_card_card_removed(CCIDCardState *card); +void ccid_card_card_inserted(CCIDCardState *card); +void ccid_card_card_error(CCIDCardState *card, uint64_t error); +void ccid_card_qdev_register(CCIDCardInfo *card); + +/* support guest visible insertion/removal of ccid devices based on actual + * devices connected/removed. Called by card implementation (passthru, local) */ +int ccid_card_ccid_attach(CCIDCardState *card); +void ccid_card_ccid_detach(CCIDCardState *card); + +#endif /* CCID_H */ diff --git a/hw/usb-ccid.c b/hw/usb-ccid.c new file mode 100644 index 0000000..bf4022a --- /dev/null +++ b/hw/usb-ccid.c @@ -0,0 +1,1391 @@ +/* + * Copyright (C) 2011 Red Hat, Inc. + * + * CCID Device emulation + * + * Written by Alon Levy, with contributions from Robert Relyea. + * + * Based on usb-serial.c, see it's copyright and attributions below. + * + * This code is licenced under the GNU LGPL, version 2 or later. + * + * ------- + * + * usb-serial.c copyright and attribution: + * Copyright (c) 2006 CodeSourcery. + * Copyright (c) 2008 Samuel Thibault <samuel.thibault@ens-lyon.org> + * Written by Paul Brook, reused for FTDI by Samuel Thibault, + */ + +/* References: + * + * CCID Specification Revision 1.1 April 22nd 2005 + * "Universal Serial Bus, Device Class: Smart Card" + * Specification for Integrated Circuit(s) Cards Interface Devices + * + * Endianess note: from the spec (1.3) + * "Fields that are larger than a byte are stored in little endian" + * + * KNOWN BUGS + * 1. remove/insert can sometimes result in removed state instead of inserted. + * This is a result of the following: + * symptom: dmesg shows ERMOTEIO (-121), pcscd shows -99. This can happen + * when a short packet is sent, as seen in uhci-usb.c, resulting from a urb + * from the guest requesting SPD and us returning a smaller packet. + * Not sure which messages trigger this. + * + */ + +#include "qemu-common.h" +#include "qemu-error.h" +#include "usb.h" +#include "monitor.h" + +#include "hw/ccid.h" + +#define DPRINTF(s, lvl, fmt, ...) \ +do { \ + if (lvl <= s->debug) { \ + printf("usb-ccid: " fmt , ## __VA_ARGS__); \ + } \ +} while (0) + +#define D_WARN 1 +#define D_INFO 2 +#define D_MORE_INFO 3 +#define D_VERBOSE 4 + +#define CCID_DEV_NAME "usb-ccid" + +/* The two options for variable sized buffers: + * make them constant size, for large enough constant, + * or handle the migration complexity - VMState doesn't handle this case. + * sizes are expected never to be exceeded, unless guest misbehaves. */ +#define BULK_OUT_DATA_SIZE 65536 +#define PENDING_ANSWERS_NUM 128 + +#define BULK_IN_BUF_SIZE 384 +#define BULK_IN_PENDING_NUM 8 + +#define InterfaceOutClass \ + ((USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_INTERFACE)<<8) + +#define InterfaceInClass \ + ((USB_DIR_IN | USB_TYPE_CLASS | USB_RECIP_INTERFACE)<<8) + +#define CCID_MAX_PACKET_SIZE 64 + +#define CCID_CONTROL_ABORT 0x1 +#define CCID_CONTROL_GET_CLOCK_FREQUENCIES 0x2 +#define CCID_CONTROL_GET_DATA_RATES 0x3 + +#define CCID_PRODUCT_DESCRIPTION "QEMU USB CCID" +#define CCID_VENDOR_DESCRIPTION "QEMU " QEMU_VERSION +#define CCID_INTERFACE_NAME "CCID Interface" +#define CCID_SERIAL_NUMBER_STRING "1" +/* Using Gemplus Vendor and Product id + Effect on various drivers: + * usbccid.sys (winxp, others untested) is a class driver so it doesn't care. + * linux has a number of class drivers, but openct filters based on + vendor/product (/etc/openct.conf under fedora), hence Gemplus. + */ +#define CCID_VENDOR_ID 0x08e6 +#define CCID_PRODUCT_ID 0x4433 +#define CCID_DEVICE_VERSION 0x0000 + +/* BULK_OUT messages from PC to Reader + Defined in CCID Rev 1.1 6.1 (page 26) + */ +#define CCID_MESSAGE_TYPE_PC_to_RDR_IccPowerOn 0x62 +#define CCID_MESSAGE_TYPE_PC_to_RDR_IccPowerOff 0x63 +#define CCID_MESSAGE_TYPE_PC_to_RDR_GetSlotStatus 0x65 +#define CCID_MESSAGE_TYPE_PC_to_RDR_XfrBlock 0x6f +#define CCID_MESSAGE_TYPE_PC_to_RDR_GetParameters 0x6c +#define CCID_MESSAGE_TYPE_PC_to_RDR_ResetParameters 0x6d +#define CCID_MESSAGE_TYPE_PC_to_RDR_SetParameters 0x61 +#define CCID_MESSAGE_TYPE_PC_to_RDR_Escape 0x6b +#define CCID_MESSAGE_TYPE_PC_to_RDR_IccClock 0x6e +#define CCID_MESSAGE_TYPE_PC_to_RDR_T0APDU 0x6a +#define CCID_MESSAGE_TYPE_PC_to_RDR_Secure 0x69 +#define CCID_MESSAGE_TYPE_PC_to_RDR_Mechanical 0x71 +#define CCID_MESSAGE_TYPE_PC_to_RDR_Abort 0x72 +#define CCID_MESSAGE_TYPE_PC_to_RDR_SetDataRateAndClockFrequency 0x73 + +/* BULK_IN messages from Reader to PC + Defined in CCID Rev 1.1 6.2 (page 48) + */ +#define CCID_MESSAGE_TYPE_RDR_to_PC_DataBlock 0x80 +#define CCID_MESSAGE_TYPE_RDR_to_PC_SlotStatus 0x81 +#define CCID_MESSAGE_TYPE_RDR_to_PC_Parameters 0x82 +#define CCID_MESSAGE_TYPE_RDR_to_PC_Escape 0x83 +#define CCID_MESSAGE_TYPE_RDR_to_PC_DataRateAndClockFrequency 0x84 + +/* INTERRUPT_IN messages from Reader to PC + Defined in CCID Rev 1.1 6.3 (page 56) + */ +#define CCID_MESSAGE_TYPE_RDR_to_PC_NotifySlotChange 0x50 +#define CCID_MESSAGE_TYPE_RDR_to_PC_HardwareError 0x51 + +/* Endpoints for CCID - addresses are up to us to decide. + To support slot insertion and removal we must have an interrupt in ep + in addition we need a bulk in and bulk out ep + 5.2, page 20 + */ +#define CCID_INT_IN_EP 1 +#define CCID_BULK_IN_EP 2 +#define CCID_BULK_OUT_EP 3 + +/* bmSlotICCState masks */ +#define SLOT_0_STATE_MASK 1 +#define SLOT_0_CHANGED_MASK 2 + +/* Status codes that go in bStatus (see 6.2.6) */ +enum { + ICC_STATUS_PRESENT_ACTIVE = 0, + ICC_STATUS_PRESENT_INACTIVE, + ICC_STATUS_NOT_PRESENT +}; + +enum { + COMMAND_STATUS_NO_ERROR = 0, + COMMAND_STATUS_FAILED, + COMMAND_STATUS_TIME_EXTENSION_REQUIRED +}; + +/* Error codes that go in bError (see 6.2.6) + */ +enum { + ERROR_CMD_NOT_SUPPORTED = 0, + ERROR_CMD_ABORTED = -1, + ERROR_ICC_MUTE = -2, + ERROR_XFR_PARITY_ERROR = -3, + ERROR_XFR_OVERRUN = -4, + ERROR_HW_ERROR = -5, +}; + +/* 6.2.6 RDR_to_PC_SlotStatus definitions */ +enum { + CLOCK_STATUS_RUNNING = 0, + /* 0 - Clock Running, 1 - Clock stopped in State L, 2 - H, + 3 - unkonwn state. rest are RFU + */ +}; + +typedef struct __attribute__ ((__packed__)) CCID_Header { + uint8_t bMessageType; + uint32_t dwLength; + uint8_t bSlot; + uint8_t bSeq; +} CCID_Header; + +typedef struct __attribute__ ((__packed__)) CCID_BULK_IN { + CCID_Header hdr; + uint8_t bStatus; /* Only used in BULK_IN */ + uint8_t bError; /* Only used in BULK_IN */ +} CCID_BULK_IN; + +typedef struct __attribute__ ((__packed__)) CCID_SlotStatus { + CCID_BULK_IN b; + uint8_t bClockStatus; +} CCID_SlotStatus; + +typedef struct __attribute__ ((__packed__)) CCID_Parameter { + CCID_BULK_IN b; + uint8_t bProtocolNum; + uint8_t abProtocolDataStructure[0]; +} CCID_Parameter; + +typedef struct __attribute__ ((__packed__)) CCID_DataBlock { + CCID_BULK_IN b; + uint8_t bChainParameter; + uint8_t abData[0]; +} CCID_DataBlock; + +/* 6.1.4 PC_to_RDR_XfrBlock */ +typedef struct __attribute__ ((__packed__)) CCID_XferBlock { + CCID_Header hdr; + uint8_t bBWI; /* Block Waiting Timeout */ + uint16_t wLevelParameter; /* XXX currently unused */ + uint8_t abData[0]; +} CCID_XferBlock; + +typedef struct __attribute__ ((__packed__)) CCID_IccPowerOn { + CCID_Header hdr; + uint8_t bPowerSelect; + uint16_t abRFU; +} CCID_IccPowerOn; + +typedef struct __attribute__ ((__packed__)) CCID_IccPowerOff { + CCID_Header hdr; + uint16_t abRFU; +} CCID_IccPowerOff; + +typedef struct __attribute__ ((__packed__)) CCID_SetParameters { + CCID_Header hdr; + uint8_t bProtocolNum; + uint16_t abRFU; + uint8_t abProtocolDataStructure[0]; +} CCID_SetParameters; + +typedef struct CCID_Notify_Slot_Change { + uint8_t bMessageType; /* CCID_MESSAGE_TYPE_RDR_to_PC_NotifySlotChange */ + uint8_t bmSlotICCState; +} CCID_Notify_Slot_Change; + +/* used for DataBlock response to XferBlock */ +typedef struct Answer { + uint8_t slot; + uint8_t seq; +} Answer; + +/* pending BULK_IN messages */ +typedef struct BulkIn { + uint8_t data[BULK_IN_BUF_SIZE]; + uint32_t len; + uint32_t pos; +} BulkIn; + +enum { + MIGRATION_NONE, + MIGRATION_MIGRATED, +}; + +typedef struct CCIDBus CCIDBus; +typedef struct USBCCIDState USBCCIDState; + +#define MAX_PROTOCOL_SIZE 7 + +/** + * powered - defaults to true, changed by PowerOn/PowerOff messages + */ +struct USBCCIDState { + USBDevice dev; + CCIDBus *bus; + CCIDCardState *card; + CCIDCardInfo *cardinfo; /* caching the info pointer */ + uint8_t debug; + BulkIn bulk_in_pending[BULK_IN_PENDING_NUM]; /* circular */ + uint32_t bulk_in_pending_start; + uint32_t bulk_in_pending_end; /* first free */ + uint32_t bulk_in_pending_num; + BulkIn *current_bulk_in; + uint8_t bulk_out_data[BULK_OUT_DATA_SIZE]; + uint32_t bulk_out_pos; + uint8_t bmSlotICCState; + uint8_t powered; + uint8_t notify_slot_change; + uint64_t last_answer_error; + Answer pending_answers[PENDING_ANSWERS_NUM]; + uint32_t pending_answers_start; + uint32_t pending_answers_end; + uint32_t pending_answers_num; + uint8_t bError; + uint8_t bmCommandStatus; + uint8_t bProtocolNum; + uint8_t abProtocolDataStructure[MAX_PROTOCOL_SIZE]; + uint32_t ulProtocolDataStructureSize; + uint32_t state_vmstate; + uint8_t migration_state; + uint32_t migration_target_ip; + uint16_t migration_target_port; +}; + +/* CCID Spec chapter 4: CCID uses a standard device descriptor per Chapter 9, + * "USB Device Framework", section 9.6.1, in the Universal Serial Bus + * Specification. + * + * This device implemented based on the spec and with an Athena Smart Card + * Reader as reference: + * 0dc3:1004 Athena Smartcard Solutions, Inc. + */ + +static const uint8_t qemu_ccid_dev_descriptor[] = { + 0x12, /* u8 bLength; */ + USB_DT_DEVICE, /* u8 bDescriptorType; Device */ + 0x10, 0x01, /* u16 bcdUSB; v1.1 */ + + 0x00, /* u8 bDeviceClass; */ + 0x00, /* u8 bDeviceSubClass; */ + 0x00, /* u8 bDeviceProtocol; [ low/full speeds only ] */ + 0x40, /* u8 bMaxPacketSize0; 8 Bytes (valid: 8,16,32,64) */ + + /* Vendor and product id are arbitrary. */ + /* u16 idVendor */ + CCID_VENDOR_ID & 0xff, CCID_VENDOR_ID >> 8, + /* u16 idProduct */ + CCID_PRODUCT_ID & 0xff, CCID_PRODUCT_ID >> 8, + /* u16 bcdDevice */ + CCID_DEVICE_VERSION & 0xff, CCID_DEVICE_VERSION >> 8, + 0x01, /* u8 iManufacturer; */ + 0x02, /* u8 iProduct; */ + 0x03, /* u8 iSerialNumber; */ + 0x01, /* u8 bNumConfigurations; */ +}; + +static const uint8_t qemu_ccid_config_descriptor[] = { + + /* one configuration */ + 0x09, /* u8 bLength; */ + USB_DT_CONFIG, /* u8 bDescriptorType; Configuration */ + 0x5d, 0x00, /* u16 wTotalLength; 9+9+54+7+7+7 */ + 0x01, /* u8 bNumInterfaces; (1) */ + 0x01, /* u8 bConfigurationValue; */ + 0x00, /* u8 iConfiguration; */ + 0xe0, /* u8 bmAttributes; + Bit 7: must be set, + 6: Self-powered, + 5: Remote wakeup, + 4..0: resvd */ + 100/2, /* u8 MaxPower; 50 == 100mA */ + + /* one interface */ + 0x09, /* u8 if_bLength; */ + USB_DT_INTERFACE, /* u8 if_bDescriptorType; Interface */ + 0x00, /* u8 if_bInterfaceNumber; */ + 0x00, /* u8 if_bAlternateSetting; */ + 0x03, /* u8 if_bNumEndpoints; */ + 0x0b, /* u8 if_bInterfaceClass; Smart Card Device Class */ + 0x00, /* u8 if_bInterfaceSubClass; Subclass code */ + 0x00, /* u8 if_bInterfaceProtocol; Protocol code */ + 0x04, /* u8 if_iInterface; Index of string descriptor */ + + /* Smart Card Device Class Descriptor */ + 0x36, /* u8 bLength; */ + 0x21, /* u8 bDescriptorType; Functional */ + 0x10, 0x01, /* u16 bcdCCID; CCID Specification Release Number. */ + 0x00, /* u8 bMaxSlotIndex; The index of the highest available + slot on this device. All slots are consecutive starting + at 00h. */ + 0x07, /* u8 bVoltageSupport; 01h - 5.0v, 02h - 3.0, 03 - 1.8 */ + + 0x03, 0x00, /* u32 dwProtocols; RRRR PPPP. RRRR = 0000h.*/ + 0x00, 0x00, /* PPPP: 0001h = Protocol T=0, 0002h = Protocol T=1 */ + /* u32 dwDefaultClock; in kHZ (0x0fa0 is 4 MHz) */ + 0xa0, 0x0f, 0x00, 0x00, + /* u32 dwMaximumClock; */ + 0x00, 0x00, 0x01, 0x00, + 0x00, /* u8 bNumClockSupported; * + * 0 means just the default and max. */ + /* u32 dwDataRate ;bps. 9600 == 00002580h */ + 0x80, 0x25, 0x00, 0x00, + /* u32 dwMaxDataRate ; 11520 bps == 0001C200h */ + 0x00, 0xC2, 0x01, 0x00, + 0x00, /* u8 bNumDataRatesSupported; 00 means all rates between + * default and max */ + /* u32 dwMaxIFSD; * + * maximum IFSD supported by CCID for protocol * + * T=1 (Maximum seen from various cards) */ + 0xfe, 0x00, 0x00, 0x00, + /* u32 dwSyncProtocols; 1 - 2-wire, 2 - 3-wire, 4 - I2C */ + 0x00, 0x00, 0x00, 0x00, + /* u32 dwMechanical; 0 - no special characteristics. */ + 0x00, 0x00, 0x00, 0x00, + /* u32 dwFeatures; + * 0 - No special characteristics + * + 2 Automatic parameter configuration based on ATR data + * + 4 Automatic activation of ICC on inserting + * + 8 Automatic ICC voltage selection + * + 10 Automatic ICC clock frequency change + * + 20 Automatic baud rate change + * + 40 Automatic parameters negotiation made by the CCID + * + 80 automatic PPS made by the CCID + * 100 CCID can set ICC in clock stop mode + * 200 NAD value other then 00 accepted (T=1 protocol) + * + 400 Automatic IFSD exchange as first exchange (T=1) + * One of the following only: + * + 10000 TPDU level exchanges with CCID + * 20000 Short APDU level exchange with CCID + * 40000 Short and Extended APDU level exchange with CCID + * + * + 100000 USB Wake up signaling supported on card + * insertion and removal. Must set bit 5 in bmAttributes + * in Configuration descriptor if 100000 is set.*/ + 0xfe, 0x04, 0x11, 0x00, + /* u32 dwMaxCCIDMessageLength; For extended APDU in + * [261 + 10 , 65544 + 10]. Otherwise the minimum is + * wMaxPacketSize of the Bulk-OUT endpoint */ + 0x12, 0x00, 0x01, 0x00, + 0xFF, /* u8 bClassGetResponse; Significant only for CCID that + * offers an APDU level for exchanges. Indicates the + * default class value used by the CCID when it sends a + * Get Response command to perform the transportation of + * an APDU by T=0 protocol + * FFh indicates that the CCID echos the class of the APDU. + */ + 0xFF, /* u8 bClassEnvelope; EAPDU only. Envelope command for + * T=0 */ + 0x00, 0x00, /* u16 wLcdLayout; XXYY Number of lines (XX) and chars per + * line for LCD display used for PIN entry. 0000 - no LCD + * */ + 0x01, /* u8 bPINSupport; 01h PIN Verification, + * 02h PIN Modification */ + 0x01, /* u8 bMaxCCIDBusySlots; */ + + /* Interrupt-IN endpoint */ + 0x07, /* u8 ep_bLength; */ + /* u8 ep_bDescriptorType; Endpoint */ + USB_DT_ENDPOINT, + /* u8 ep_bEndpointAddress; IN Endpoint 1 */ + 0x80 | CCID_INT_IN_EP, + 0x03, /* u8 ep_bmAttributes; Interrupt */ + /* u16 ep_wMaxPacketSize; */ + CCID_MAX_PACKET_SIZE & 0xff, (CCID_MAX_PACKET_SIZE >> 8), + 0xff, /* u8 ep_bInterval; */ + + /* Bulk-In endpoint */ + 0x07, /* u8 ep_bLength; */ + /* u8 ep_bDescriptorType; Endpoint */ + USB_DT_ENDPOINT, + /* u8 ep_bEndpointAddress; IN Endpoint 2 */ + 0x80 | CCID_BULK_IN_EP, + 0x02, /* u8 ep_bmAttributes; Bulk */ + 0x40, 0x00, /* u16 ep_wMaxPacketSize; */ + 0x00, /* u8 ep_bInterval; */ + + /* Bulk-Out endpoint */ + 0x07, /* u8 ep_bLength; */ + /* u8 ep_bDescriptorType; Endpoint */ + USB_DT_ENDPOINT, + /* u8 ep_bEndpointAddress; OUT Endpoint 3 */ + CCID_BULK_OUT_EP, + 0x02, /* u8 ep_bmAttributes; Bulk */ + 0x40, 0x00, /* u16 ep_wMaxPacketSize; */ + 0x00, /* u8 ep_bInterval; */ + +}; + +static bool ccid_has_pending_answers(USBCCIDState *s) +{ + return s->pending_answers_num > 0; +} + +static void ccid_clear_pending_answers(USBCCIDState *s) +{ + s->pending_answers_num = 0; + s->pending_answers_start = 0; + s->pending_answers_end = 0; +} + +static void ccid_print_pending_answers(USBCCIDState *s) +{ + Answer *answer; + int i, count; + + DPRINTF(s, D_VERBOSE, "usb-ccid: pending answers:"); + if (!ccid_has_pending_answers(s)) { + DPRINTF(s, D_VERBOSE, " empty\n"); + return; + } + for (i = s->pending_answers_start, count = s->pending_answers_num ; + count > 0; count--, i++) { + answer = &s->pending_answers[i % PENDING_ANSWERS_NUM]; + if (count == 1) { + DPRINTF(s, D_VERBOSE, "%d:%d\n", answer->slot, answer->seq); + } else { + DPRINTF(s, D_VERBOSE, "%d:%d,", answer->slot, answer->seq); + } + } +} + +static void ccid_add_pending_answer(USBCCIDState *s, CCID_Header *hdr) +{ + Answer *answer; + + assert(s->pending_answers_num < PENDING_ANSWERS_NUM); + s->pending_answers_num++; + answer = + &s->pending_answers[(s->pending_answers_end++) % PENDING_ANSWERS_NUM]; + answer->slot = hdr->bSlot; + answer->seq = hdr->bSeq; + ccid_print_pending_answers(s); +} + +static void ccid_remove_pending_answer(USBCCIDState *s, + uint8_t *slot, uint8_t *seq) +{ + Answer *answer; + + assert(s->pending_answers_num > 0); + s->pending_answers_num--; + answer = + &s->pending_answers[(s->pending_answers_start++) % PENDING_ANSWERS_NUM]; + *slot = answer->slot; + *seq = answer->seq; + ccid_print_pending_answers(s); +} + +static void ccid_bulk_in_clear(USBCCIDState *s) +{ + s->bulk_in_pending_start = 0; + s->bulk_in_pending_end = 0; + s->bulk_in_pending_num = 0; +} + +static void ccid_bulk_in_release(USBCCIDState *s) +{ + assert(s->current_bulk_in != NULL); + s->current_bulk_in->pos = 0; + s->current_bulk_in = NULL; +} + +static void ccid_bulk_in_get(USBCCIDState *s) +{ + if (s->current_bulk_in != NULL || s->bulk_in_pending_num == 0) { + return; + } + assert(s->bulk_in_pending_num > 0); + s->bulk_in_pending_num--; + s->current_bulk_in = &s->bulk_in_pending[ + (s->bulk_in_pending_start++) % BULK_IN_PENDING_NUM]; +} + +static void *ccid_reserve_recv_buf(USBCCIDState *s, uint16_t len) +{ + BulkIn *bulk_in; + + DPRINTF(s, D_VERBOSE, "%s: QUEUE: reserve %d bytes\n", __func__, len); + + /* look for an existing element */ + if (len > BULK_IN_BUF_SIZE) { + DPRINTF(s, D_WARN, "usb-ccid.c: %s: len larger then max (%d>%d). " + "discarding message.\n", + __func__, len, BULK_IN_BUF_SIZE); + return NULL; + } + if (s->bulk_in_pending_num >= BULK_IN_PENDING_NUM) { + DPRINTF(s, D_WARN, "usb-ccid.c: %s: No free bulk_in buffers. " + "discarding message.\n", __func__); + return NULL; + } + bulk_in = + &s->bulk_in_pending[(s->bulk_in_pending_end++) % BULK_IN_PENDING_NUM]; + s->bulk_in_pending_num++; + bulk_in->len = len; + return bulk_in->data; +} + +static void ccid_reset(USBCCIDState *s) +{ + ccid_bulk_in_clear(s); + ccid_clear_pending_answers(s); +} + +static void ccid_detach(USBCCIDState *s) +{ + ccid_reset(s); +} + +static void ccid_handle_reset(USBDevice *dev) +{ + USBCCIDState *s = DO_UPCAST(USBCCIDState, dev, dev); + + DPRINTF(s, 1, "Reset\n"); + + ccid_reset(s); +} + +static int ccid_handle_control(USBDevice *dev, int request, int value, + int index, int length, uint8_t *data) +{ + USBCCIDState *s = DO_UPCAST(USBCCIDState, dev, dev); + int ret = 0; + + DPRINTF(s, 1, "got control %x, value %x\n", request, value); + switch (request) { + case DeviceRequest | USB_REQ_GET_STATUS: + data[0] = (1 << USB_DEVICE_SELF_POWERED) | + (dev->remote_wakeup << USB_DEVICE_REMOTE_WAKEUP); + data[1] = 0x00; + ret = 2; + break; + case DeviceOutRequest | USB_REQ_CLEAR_FEATURE: + if (value == USB_DEVICE_REMOTE_WAKEUP) { + dev->remote_wakeup = 0; + } else { + goto fail; + } + ret = 0; + break; + case DeviceOutRequest | USB_REQ_SET_FEATURE: + if (value == USB_DEVICE_REMOTE_WAKEUP) { + dev->remote_wakeup = 1; + } else { + goto fail; + } + ret = 0; + break; + case DeviceOutRequest | USB_REQ_SET_ADDRESS: + dev->addr = value; + ret = 0; + break; + case DeviceRequest | USB_REQ_GET_DESCRIPTOR: + switch (value >> 8) { + case USB_DT_DEVICE: + memcpy(data, qemu_ccid_dev_descriptor, + sizeof(qemu_ccid_dev_descriptor)); + ret = sizeof(qemu_ccid_dev_descriptor); + break; + case USB_DT_CONFIG: + memcpy(data, qemu_ccid_config_descriptor, + sizeof(qemu_ccid_config_descriptor)); + ret = sizeof(qemu_ccid_config_descriptor); + break; + case USB_DT_STRING: + switch (value & 0xff) { + case 0: + /* language ids */ + data[0] = 4; + data[1] = 3; + data[2] = 0x09; + data[3] = 0x04; + ret = 4; + break; + case 1: + /* vendor description */ + ret = set_usb_string(data, CCID_VENDOR_DESCRIPTION); + break; + case 2: + /* product description */ + ret = set_usb_string(data, CCID_PRODUCT_DESCRIPTION); + break; + case 3: + /* serial number */ + ret = set_usb_string(data, CCID_SERIAL_NUMBER_STRING); + break; + case 4: + /* interface name */ + ret = set_usb_string(data, CCID_INTERFACE_NAME); + break; + default: + goto fail; + } + break; + default: + goto fail; + } + break; + case DeviceRequest | USB_REQ_GET_CONFIGURATION: + data[0] = 1; + ret = 1; + break; + case DeviceOutRequest | USB_REQ_SET_CONFIGURATION: + /* Only one configuration - we just ignore the request */ + ret = 0; + break; + case DeviceRequest | USB_REQ_GET_INTERFACE: + data[0] = 0; + ret = 1; + break; + case InterfaceOutRequest | USB_REQ_SET_INTERFACE: + ret = 0; + break; + case EndpointOutRequest | USB_REQ_CLEAR_FEATURE: + ret = 0; + break; + + /* Class specific requests. */ + case InterfaceOutClass | CCID_CONTROL_ABORT: + DPRINTF(s, 1, "ccid_control abort UNIMPLEMENTED\n"); + ret = USB_RET_STALL; + break; + case InterfaceInClass | CCID_CONTROL_GET_CLOCK_FREQUENCIES: + DPRINTF(s, 1, "ccid_control get clock frequencies UNIMPLEMENTED\n"); + ret = USB_RET_STALL; + break; + case InterfaceInClass | CCID_CONTROL_GET_DATA_RATES: + DPRINTF(s, 1, "ccid_control get data rates UNIMPLEMENTED\n"); + ret = USB_RET_STALL; + break; + default: +fail: + DPRINTF(s, 1, "got unsupported/bogus control %x, value %x\n", + request, value); + ret = USB_RET_STALL; + break; + } + return ret; +} + +static bool ccid_card_inserted(USBCCIDState *s) +{ + return s->bmSlotICCState & SLOT_0_STATE_MASK; +} + +static uint8_t ccid_card_status(USBCCIDState *s) +{ + return ccid_card_inserted(s) + ? (s->powered ? + ICC_STATUS_PRESENT_ACTIVE + : ICC_STATUS_PRESENT_INACTIVE + ) + : ICC_STATUS_NOT_PRESENT; +} + +static uint8_t ccid_calc_status(USBCCIDState *s) +{ + /* page 55, 6.2.6, calculation of bStatus from bmICCStatus and + bmCommandStatus + */ + uint8_t ret = ccid_card_status(s) | (s->bmCommandStatus << 6); + DPRINTF(s, D_VERBOSE, "status = %d\n", ret); + return ret; +} + +static void ccid_reset_error_status(USBCCIDState *s) +{ + s->bError = ERROR_CMD_NOT_SUPPORTED; + s->bmCommandStatus = COMMAND_STATUS_NO_ERROR; +} + +static void ccid_write_slot_status(USBCCIDState *s, CCID_Header *recv) +{ + CCID_SlotStatus *h = ccid_reserve_recv_buf(s, sizeof(CCID_SlotStatus)); + if (h == NULL) { + return; + } + h->b.hdr.bMessageType = CCID_MESSAGE_TYPE_RDR_to_PC_SlotStatus; + h->b.hdr.dwLength = 0; + h->b.hdr.bSlot = recv->bSlot; + h->b.hdr.bSeq = recv->bSeq; + h->b.bStatus = ccid_calc_status(s); + h->b.bError = s->bError; + h->bClockStatus = CLOCK_STATUS_RUNNING; + ccid_reset_error_status(s); +} + +static void ccid_write_parameters(USBCCIDState *s, CCID_Header *recv) +{ + CCID_Parameter *h; + uint32_t len = s->ulProtocolDataStructureSize; + + h = ccid_reserve_recv_buf(s, sizeof(CCID_Parameter) + len); + if (h == NULL) { + return; + } + h->b.hdr.bMessageType = CCID_MESSAGE_TYPE_RDR_to_PC_Parameters; + h->b.hdr.dwLength = 0; + h->b.hdr.bSlot = recv->bSlot; + h->b.hdr.bSeq = recv->bSeq; + h->b.bStatus = ccid_calc_status(s); + h->b.bError = s->bError; + h->bProtocolNum = s->bProtocolNum; + memcpy(h->abProtocolDataStructure, s->abProtocolDataStructure, len); + ccid_reset_error_status(s); +} + +static void ccid_write_data_block( + USBCCIDState *s, uint8_t slot, uint8_t seq, + const uint8_t *data, uint32_t len) +{ + CCID_DataBlock *p = ccid_reserve_recv_buf(s, sizeof(*p) + len); + + if (p == NULL) { + return; + } + p->b.hdr.bMessageType = CCID_MESSAGE_TYPE_RDR_to_PC_DataBlock; + p->b.hdr.dwLength = cpu_to_le32(len); + p->b.hdr.bSlot = slot; + p->b.hdr.bSeq = seq; + p->b.bStatus = ccid_calc_status(s); + p->b.bError = s->bError; + if (p->b.bError) { + DPRINTF(s, D_VERBOSE, "error %d", p->b.bError); + } + memcpy(p->abData, data, len); + ccid_reset_error_status(s); +} + +static void ccid_write_data_block_answer(USBCCIDState *s, + const uint8_t *data, uint32_t len) +{ + uint8_t seq; + uint8_t slot; + + if (!ccid_has_pending_answers(s)) { + abort(); + } + ccid_remove_pending_answer(s, &slot, &seq); + ccid_write_data_block(s, slot, seq, data, len); +} + +static void ccid_write_data_block_atr(USBCCIDState *s, CCID_Header *recv) +{ + const uint8_t *atr = NULL; + uint32_t len = 0; + + if (s->card) { + atr = s->cardinfo->get_atr(s->card, &len); + } + ccid_write_data_block(s, recv->bSlot, recv->bSeq, atr, len); +} + +static void ccid_set_parameters(USBCCIDState *s, CCID_Header *recv) +{ + CCID_SetParameters *ph = (CCID_SetParameters *) recv; + uint32_t len = 0; + if ((ph->bProtocolNum & 3) == 0) { + len = 5; + } + if ((ph->bProtocolNum & 3) == 1) { + len = 7; + } + if (len == 0) { + s->bmCommandStatus = COMMAND_STATUS_FAILED; + s->bError = 7; /* Protocol invalid or not supported */ + return; + } + s->bProtocolNum = ph->bProtocolNum; + memcpy(s->abProtocolDataStructure, ph->abProtocolDataStructure, len); + s->ulProtocolDataStructureSize = len; + DPRINTF(s, 1, "%s: using len %d\n", __func__, len); +} + +/* must be 5 bytes for T=0, 7 bytes for T=1 + * See page 52 */ +static const uint8_t abDefaultProtocolDataStructure[7] = { + 0x77, 0x00, 0x00, 0x00, 0x00, 0xfe /*IFSC*/, 0x00 /*NAD*/ }; + +static void ccid_reset_parameters(USBCCIDState *s) +{ + uint32_t len = sizeof(abDefaultProtocolDataStructure); + + s->bProtocolNum = 1; /* T=1 */ + s->ulProtocolDataStructureSize = len; + memcpy(s->abProtocolDataStructure, abDefaultProtocolDataStructure, len); +} + +static void ccid_report_error_failed(USBCCIDState *s, uint8_t error) +{ + s->bmCommandStatus = COMMAND_STATUS_FAILED; + s->bError = error; +} + +/* NOTE: only a single slot is supported (SLOT_0) + */ +static void ccid_on_slot_change(USBCCIDState *s, bool full) +{ + /* RDR_to_PC_NotifySlotChange, 6.3.1 page 56 + */ + uint8_t current = s->bmSlotICCState; + if (full) { + s->bmSlotICCState |= SLOT_0_STATE_MASK; + } else { + s->bmSlotICCState &= ~SLOT_0_STATE_MASK; + } + if (current != s->bmSlotICCState) { + s->bmSlotICCState |= SLOT_0_CHANGED_MASK; + } + s->notify_slot_change = true; +} + +static void ccid_write_data_block_error( + USBCCIDState *s, uint8_t slot, uint8_t seq) +{ + ccid_write_data_block(s, slot, seq, NULL, 0); +} + +static void ccid_on_apdu_from_guest(USBCCIDState *s, CCID_XferBlock *recv) +{ + uint32_t len; + + if (ccid_card_status(s) != ICC_STATUS_PRESENT_ACTIVE) { + DPRINTF(s, 1, + "usb-ccid: not sending apdu to client, no card connected\n"); + ccid_write_data_block_error(s, recv->hdr.bSlot, recv->hdr.bSeq); + return; + } + len = le32_to_cpu(recv->hdr.dwLength); + DPRINTF(s, 1, "%s: seq %d, len %d\n", __func__, + recv->hdr.bSeq, len); + ccid_add_pending_answer(s, (CCID_Header *)recv); + if (s->card) { + s->cardinfo->apdu_from_guest(s->card, recv->abData, len); + } else { + DPRINTF(s, D_WARN, "warning: discarded apdu\n"); + } +} + +/* handle a single USB_TOKEN_OUT, return value returned to guest. + * 0 - all ok + * USB_RET_STALL - failed to handle packet */ +static int ccid_handle_bulk_out(USBCCIDState *s, USBPacket *p) +{ + CCID_Header *ccid_header; + + if (p->len + s->bulk_out_pos > BULK_OUT_DATA_SIZE) { + return USB_RET_STALL; + } + ccid_header = (CCID_Header *)s->bulk_out_data; + memcpy(s->bulk_out_data + s->bulk_out_pos, p->data, p->len); + s->bulk_out_pos += p->len; + if (p->len == CCID_MAX_PACKET_SIZE) { + DPRINTF(s, D_VERBOSE, + "usb-ccid: bulk_in: expecting more packets (%d/%d)\n", + p->len, ccid_header->dwLength); + return 0; + } + if (s->bulk_out_pos < 10) { + DPRINTF(s, 1, + "%s: bad USB_TOKEN_OUT length, should be at least 10 bytes\n", + __func__); + } else { + DPRINTF(s, D_MORE_INFO, "%s %x\n", __func__, ccid_header->bMessageType); + switch (ccid_header->bMessageType) { + case CCID_MESSAGE_TYPE_PC_to_RDR_GetSlotStatus: + ccid_write_slot_status(s, ccid_header); + break; + case CCID_MESSAGE_TYPE_PC_to_RDR_IccPowerOn: + DPRINTF(s, 1, "PowerOn: %d\n", + ((CCID_IccPowerOn *)(ccid_header))->bPowerSelect); + s->powered = true; + if (!ccid_card_inserted(s)) { + ccid_report_error_failed(s, ERROR_ICC_MUTE); + } + /* atr is written regardless of error. */ + ccid_write_data_block_atr(s, ccid_header); + break; + case CCID_MESSAGE_TYPE_PC_to_RDR_IccPowerOff: + DPRINTF(s, 1, "PowerOff\n"); + ccid_reset_error_status(s); + s->powered = false; + ccid_write_slot_status(s, ccid_header); + break; + case CCID_MESSAGE_TYPE_PC_to_RDR_XfrBlock: + ccid_on_apdu_from_guest(s, (CCID_XferBlock *)s->bulk_out_data); + break; + case CCID_MESSAGE_TYPE_PC_to_RDR_SetParameters: + ccid_reset_error_status(s); + ccid_set_parameters(s, ccid_header); + ccid_write_parameters(s, ccid_header); + break; + case CCID_MESSAGE_TYPE_PC_to_RDR_ResetParameters: + ccid_reset_error_status(s); + ccid_reset_parameters(s); + ccid_write_parameters(s, ccid_header); + break; + case CCID_MESSAGE_TYPE_PC_to_RDR_GetParameters: + ccid_reset_error_status(s); + ccid_write_parameters(s, ccid_header); + break; + default: + DPRINTF(s, 1, + "handle_data: ERROR: unhandled message type %Xh\n", + ccid_header->bMessageType); + /* the caller is expecting the device to respond, tell it we + * do't support the operation */ + ccid_report_error_failed(s, ERROR_CMD_NOT_SUPPORTED); + ccid_write_slot_status(s, ccid_header); + break; + } + } + s->bulk_out_pos = 0; + return 0; +} + +static int ccid_bulk_in_copy_to_guest(USBCCIDState *s, uint8_t *data, int len) +{ + int ret = 0; + + assert(len > 0); + ccid_bulk_in_get(s); + if (s->current_bulk_in != NULL) { + ret = MIN(s->current_bulk_in->len - s->current_bulk_in->pos, len); + memcpy(data, s->current_bulk_in->data + s->current_bulk_in->pos, ret); + s->current_bulk_in->pos += ret; + if (s->current_bulk_in->pos == s->current_bulk_in->len) { + ccid_bulk_in_release(s); + } + } else { + ret = USB_RET_NAK; /* return when device has no data - usb 2.0 spec + Table 8-4 */ + } + if (ret > 0) { + DPRINTF(s, D_MORE_INFO, + "%s: %d/%d req/act to guest (BULK_IN)\n", __func__, len, ret); + } + if (ret != USB_RET_NAK && ret < len) { + DPRINTF(s, 1, + "%s: returning short (EREMOTEIO) %d < %d\n", __func__, ret, len); + } + return ret; +} + +static int ccid_handle_data(USBDevice *dev, USBPacket *p) +{ + USBCCIDState *s = DO_UPCAST(USBCCIDState, dev, dev); + int ret = 0; + uint8_t *data = p->data; + int len = p->len; + + switch (p->pid) { + case USB_TOKEN_OUT: + ret = ccid_handle_bulk_out(s, p); + break; + + case USB_TOKEN_IN: + switch (p->devep & 0xf) { + case CCID_BULK_IN_EP: + if (!len) { + ret = USB_RET_NAK; + } else { + ret = ccid_bulk_in_copy_to_guest(s, data, len); + } + break; + case CCID_INT_IN_EP: + if (s->notify_slot_change) { + /* page 56, RDR_to_PC_NotifySlotChange */ + data[0] = CCID_MESSAGE_TYPE_RDR_to_PC_NotifySlotChange; + data[1] = s->bmSlotICCState; + ret = 2; + s->notify_slot_change = false; + s->bmSlotICCState &= ~SLOT_0_CHANGED_MASK; + DPRINTF(s, D_INFO, + "handle_data: int_in: notify_slot_change %X, " + "requested len %d\n", + s->bmSlotICCState, len); + } + break; + default: + DPRINTF(s, 1, "Bad endpoint\n"); + break; + } + break; + default: + DPRINTF(s, 1, "Bad token\n"); + ret = USB_RET_STALL; + break; + } + + return ret; +} + +static void ccid_handle_destroy(USBDevice *dev) +{ + USBCCIDState *s = DO_UPCAST(USBCCIDState, dev, dev); + + ccid_bulk_in_clear(s); +} + +static void ccid_flush_pending_answers(USBCCIDState *s) +{ + while (ccid_has_pending_answers(s)) { + ccid_write_data_block_answer(s, NULL, 0); + } +} + +static Answer *ccid_peek_next_answer(USBCCIDState *s) +{ + return s->pending_answers_num == 0 + ? NULL + : &s->pending_answers[s->pending_answers_start % PENDING_ANSWERS_NUM]; +} + +static void ccid_bus_dev_print(Monitor *mon, DeviceState *qdev, int indent) +{ + CCIDCardState *card = DO_UPCAST(CCIDCardState, qdev, qdev); + CCIDCardInfo *info = DO_UPCAST(CCIDCardInfo, qdev, qdev->info); + + if (info->print) { + info->print(mon, card, indent); + } +} + +struct CCIDBus { + BusState qbus; +}; + +static struct BusInfo ccid_bus_info = { + .name = "ccid-bus", + .size = sizeof(CCIDBus), + .print_dev = ccid_bus_dev_print, + .props = (Property[]) { + DEFINE_PROP_UINT32("slot", struct CCIDCardState, slot, 0), + DEFINE_PROP_END_OF_LIST(), + } +}; + +static CCIDBus *ccid_bus_new(DeviceState *dev) +{ + CCIDBus *bus; + + bus = FROM_QBUS(CCIDBus, qbus_create(&ccid_bus_info, dev, NULL)); + bus->qbus.allow_hotplug = 1; + + return bus; +} + +void ccid_card_send_apdu_to_guest(CCIDCardState *card, + uint8_t *apdu, uint32_t len) +{ + USBCCIDState *s = DO_UPCAST(USBCCIDState, dev.qdev, + card->qdev.parent_bus->parent); + Answer *answer; + + if (!ccid_has_pending_answers(s)) { + DPRINTF(s, 1, "CCID ERROR: got an APDU without pending answers\n"); + return; + } + s->bmCommandStatus = COMMAND_STATUS_NO_ERROR; + answer = ccid_peek_next_answer(s); + if (answer == NULL) { + abort(); + } + DPRINTF(s, 1, "APDU returned to guest %d (answer seq %d, slot %d)\n", + len, answer->seq, answer->slot); + ccid_write_data_block_answer(s, apdu, len); +} + +void ccid_card_card_removed(CCIDCardState *card) +{ + USBCCIDState *s = + DO_UPCAST(USBCCIDState, dev.qdev, card->qdev.parent_bus->parent); + + ccid_on_slot_change(s, false); + ccid_flush_pending_answers(s); + ccid_reset(s); +} + +int ccid_card_ccid_attach(CCIDCardState *card) +{ + USBCCIDState *s = + DO_UPCAST(USBCCIDState, dev.qdev, card->qdev.parent_bus->parent); + + DPRINTF(s, 1, "CCID Attach\n"); + if (s->migration_state == MIGRATION_MIGRATED) { + s->migration_state = MIGRATION_NONE; + } + return 0; +} + +void ccid_card_ccid_detach(CCIDCardState *card) +{ + USBCCIDState *s = + DO_UPCAST(USBCCIDState, dev.qdev, card->qdev.parent_bus->parent); + + DPRINTF(s, 1, "CCID Detach\n"); + if (ccid_card_inserted(s)) { + ccid_on_slot_change(s, false); + } + ccid_detach(s); +} + +void ccid_card_card_error(CCIDCardState *card, uint64_t error) +{ + USBCCIDState *s = + DO_UPCAST(USBCCIDState, dev.qdev, card->qdev.parent_bus->parent); + + s->bmCommandStatus = COMMAND_STATUS_FAILED; + s->last_answer_error = error; + DPRINTF(s, 1, "VSC_Error: %lX\n", s->last_answer_error); + /* TODO: these error's should be more verbose and propogated to the guest. + * */ + /* we flush all pending answers on CardRemove message in ccid-card-passthru, + * so check that first to not trigger abort */ + if (ccid_has_pending_answers(s)) { + ccid_write_data_block_answer(s, NULL, 0); + } +} + +void ccid_card_card_inserted(CCIDCardState *card) +{ + USBCCIDState *s = + DO_UPCAST(USBCCIDState, dev.qdev, card->qdev.parent_bus->parent); + + s->bmCommandStatus = COMMAND_STATUS_NO_ERROR; + ccid_flush_pending_answers(s); + ccid_on_slot_change(s, true); +} + +static int ccid_card_exit(DeviceState *qdev) +{ + int ret = 0; + CCIDCardState *card = DO_UPCAST(CCIDCardState, qdev, qdev); + CCIDCardInfo *info = DO_UPCAST(CCIDCardInfo, qdev, qdev->info); + USBCCIDState *s = + DO_UPCAST(USBCCIDState, dev.qdev, card->qdev.parent_bus->parent); + + if (ccid_card_inserted(s)) { + ccid_card_card_removed(card); + } + if (info->exitfn) { + ret = info->exitfn(card); + } + s->card = NULL; + s->cardinfo = NULL; + return ret; +} + +static int ccid_card_init(DeviceState *qdev, DeviceInfo *base) +{ + CCIDCardState *card = DO_UPCAST(CCIDCardState, qdev, qdev); + CCIDCardInfo *info = DO_UPCAST(CCIDCardInfo, qdev, base); + USBCCIDState *s = + DO_UPCAST(USBCCIDState, dev.qdev, card->qdev.parent_bus->parent); + int ret = 0; + + if (card->slot != 0) { + error_report("Warning: usb-ccid supports one slot, can't add %d", + card->slot); + return -1; + } + if (s->card != NULL) { + error_report("Warning: usb-ccid card already full, not adding\n"); + return -1; + } + ret = info->initfn ? info->initfn(card) : ret; + if (ret == 0) { + s->card = card; + s->cardinfo = info; + } + return ret; +} + +void ccid_card_qdev_register(CCIDCardInfo *card) +{ + card->qdev.bus_info = &ccid_bus_info; + card->qdev.init = ccid_card_init; + card->qdev.exit = ccid_card_exit; + qdev_register(&card->qdev); +} + +static int ccid_initfn(USBDevice *dev) +{ + USBCCIDState *s = DO_UPCAST(USBCCIDState, dev, dev); + + s->bus = ccid_bus_new(&dev->qdev); + s->card = NULL; + s->cardinfo = NULL; + s->migration_state = MIGRATION_NONE; + s->migration_target_ip = 0; + s->migration_target_port = 0; + s->dev.speed = USB_SPEED_FULL; + s->notify_slot_change = false; + s->powered = true; + s->pending_answers_num = 0; + s->last_answer_error = 0; + s->bulk_in_pending_start = 0; + s->bulk_in_pending_end = 0; + s->current_bulk_in = NULL; + ccid_reset_error_status(s); + s->bulk_out_pos = 0; + ccid_reset_parameters(s); + ccid_reset(s); + return 0; +} + +static int ccid_post_load(void *opaque, int version_id) +{ + USBCCIDState *s = opaque; + + /* This must be done after usb_device_attach, which sets state to ATTACHED, + * while it must be DEFAULT in order to accept packets (like it is after + * reset, but reset will reset our addr and call our reset handler which + * may change state, and we don't want to do that when migrating). */ + s->dev.state = s->state_vmstate; + return 0; +} + +static void ccid_pre_save(void *opaque) +{ + USBCCIDState *s = opaque; + + s->state_vmstate = s->dev.state; + if (s->dev.attached) { + /* migrating an open device, ignore reconnection CHR_EVENT to avoid an + * erronous detach. */ + s->migration_state = MIGRATION_MIGRATED; + } +} + +static VMStateDescription bulk_in_vmstate = { + .name = "CCID BulkIn state", + .version_id = 1, + .minimum_version_id = 1, + .fields = (VMStateField[]) { + VMSTATE_BUFFER(data, BulkIn), + VMSTATE_UINT32(len, BulkIn), + VMSTATE_UINT32(pos, BulkIn), + VMSTATE_END_OF_LIST() + } +}; + +static VMStateDescription answer_vmstate = { + .name = "CCID Answer state", + .version_id = 1, + .minimum_version_id = 1, + .fields = (VMStateField[]) { + VMSTATE_UINT8(slot, Answer), + VMSTATE_UINT8(seq, Answer), + VMSTATE_END_OF_LIST() + } +}; + +static VMStateDescription usb_device_vmstate = { + .name = "usb_device", + .version_id = 1, + .minimum_version_id = 1, + .fields = (VMStateField[]) { + VMSTATE_UINT8(addr, USBDevice), + VMSTATE_BUFFER(setup_buf, USBDevice), + VMSTATE_BUFFER(data_buf, USBDevice), + VMSTATE_END_OF_LIST() + } +}; + +static VMStateDescription ccid_vmstate = { + .name = CCID_DEV_NAME, + .version_id = 1, + .minimum_version_id = 1, + .post_load = ccid_post_load, + .pre_save = ccid_pre_save, + .fields = (VMStateField[]) { + VMSTATE_STRUCT(dev, USBCCIDState, 1, usb_device_vmstate, USBDevice), + VMSTATE_UINT8(debug, USBCCIDState), + VMSTATE_BUFFER(bulk_out_data, USBCCIDState), + VMSTATE_UINT32(bulk_out_pos, USBCCIDState), + VMSTATE_UINT8(bmSlotICCState, USBCCIDState), + VMSTATE_UINT8(powered, USBCCIDState), + VMSTATE_UINT8(notify_slot_change, USBCCIDState), + VMSTATE_UINT64(last_answer_error, USBCCIDState), + VMSTATE_UINT8(bError, USBCCIDState), + VMSTATE_UINT8(bmCommandStatus, USBCCIDState), + VMSTATE_UINT8(bProtocolNum, USBCCIDState), + VMSTATE_BUFFER(abProtocolDataStructure, USBCCIDState), + VMSTATE_UINT32(ulProtocolDataStructureSize, USBCCIDState), + VMSTATE_STRUCT_ARRAY(bulk_in_pending, USBCCIDState, + BULK_IN_PENDING_NUM, 1, bulk_in_vmstate, BulkIn), + VMSTATE_UINT32(bulk_in_pending_start, USBCCIDState), + VMSTATE_UINT32(bulk_in_pending_end, USBCCIDState), + VMSTATE_STRUCT_ARRAY(pending_answers, USBCCIDState, + PENDING_ANSWERS_NUM, 1, answer_vmstate, Answer), + VMSTATE_UINT32(pending_answers_num, USBCCIDState), + VMSTATE_UINT8(migration_state, USBCCIDState), + VMSTATE_UINT32(state_vmstate, USBCCIDState), + VMSTATE_END_OF_LIST() + } +}; + +static struct USBDeviceInfo ccid_info = { + .product_desc = "QEMU USB CCID", + .qdev.name = CCID_DEV_NAME, + .qdev.desc = "CCID Rev 1.1 smartcard reader", + .qdev.size = sizeof(USBCCIDState), + .init = ccid_initfn, + .handle_packet = usb_generic_handle_packet, + .handle_reset = ccid_handle_reset, + .handle_control = ccid_handle_control, + .handle_data = ccid_handle_data, + .handle_destroy = ccid_handle_destroy, + .usbdevice_name = "ccid", + .qdev.props = (Property[]) { + DEFINE_PROP_UINT8("debug", USBCCIDState, debug, 0), + DEFINE_PROP_END_OF_LIST(), + }, + .qdev.vmsd = &ccid_vmstate, +}; + +static void ccid_register_devices(void) +{ + usb_qdev_register(&ccid_info); +} +device_init(ccid_register_devices) -- 1.7.4.1 ^ permalink raw reply related [flat|nested] 35+ messages in thread
* Re: [Qemu-devel] [PATCH 1/7] usb-ccid: add CCID bus 2011-02-23 11:20 ` [Qemu-devel] [PATCH 1/7] usb-ccid: add CCID bus Alon Levy @ 2011-03-14 13:54 ` Jes Sorensen 2011-03-14 14:07 ` Daniel P. Berrange 2011-03-16 9:15 ` Alon Levy 0 siblings, 2 replies; 35+ messages in thread From: Jes Sorensen @ 2011-03-14 13:54 UTC (permalink / raw) To: Alon Levy; +Cc: qemu-devel On 02/23/11 12:20, Alon Levy wrote: > diff --git a/configure b/configure > index 791b71d..147aab3 100755 > --- a/configure > +++ b/configure > @@ -174,6 +174,7 @@ trace_backend="nop" > trace_file="trace" > spice="" > rbd="" > +smartcard="yes" IMHO smartcard support shouldn't be enabled per default. The userbase is limited. > diff --git a/hw/ccid.h b/hw/ccid.h > new file mode 100644 > index 0000000..4350bc2 > --- /dev/null > +++ b/hw/ccid.h > @@ -0,0 +1,54 @@ > +/* > + * CCID Passthru Card Device emulation > + * > + * Copyright (c) 2011 Red Hat. > + * Written by Alon Levy. > + * > + * This code is licenced under the GNU LGPL, version 2 or later. > + */ > + [snip] > + > +/* callbacks to be used by the CCID device (hw/usb-ccid.c) to call > + * into the smartcard device (hw/ccid-card-*.c) > + */ This is inconsistent with the comment above. Normally multi-line comments in QEMU are like this: /* * foo * bar */ > +struct CCIDCardInfo { > + DeviceInfo qdev; > + void (*print)(Monitor *mon, CCIDCardState *card, int indent); > + const uint8_t *(*get_atr)(CCIDCardState *card, uint32_t *len); > + void (*apdu_from_guest)(CCIDCardState *card, > + const uint8_t *apdu, > + uint32_t len); > + int (*exitfn)(CCIDCardState *card); > + int (*initfn)(CCIDCardState *card); > +}; > + > +/* API for smartcard calling the CCID device (used by hw/ccid-card-*.c) > + */ again here > +void ccid_card_send_apdu_to_guest(CCIDCardState *card, > + uint8_t *apdu, > + uint32_t len); > +void ccid_card_card_removed(CCIDCardState *card); > +void ccid_card_card_inserted(CCIDCardState *card); > +void ccid_card_card_error(CCIDCardState *card, uint64_t error); > +void ccid_card_qdev_register(CCIDCardInfo *card); > + > +/* support guest visible insertion/removal of ccid devices based on actual > + * devices connected/removed. Called by card implementation (passthru, local) */ and here > diff --git a/hw/usb-ccid.c b/hw/usb-ccid.c > new file mode 100644 > index 0000000..bf4022a > --- /dev/null > +++ b/hw/usb-ccid.c > +#define CCID_DEV_NAME "usb-ccid" > + > +/* The two options for variable sized buffers: > + * make them constant size, for large enough constant, > + * or handle the migration complexity - VMState doesn't handle this case. > + * sizes are expected never to be exceeded, unless guest misbehaves. */ here again [snip] > +/* Using Gemplus Vendor and Product id > + Effect on various drivers: > + * usbccid.sys (winxp, others untested) is a class driver so it doesn't care. > + * linux has a number of class drivers, but openct filters based on > + vendor/product (/etc/openct.conf under fedora), hence Gemplus. > + */ Something went totally boink with the comments there! > +/* 6.2.6 RDR_to_PC_SlotStatus definitions */ > +enum { > + CLOCK_STATUS_RUNNING = 0, > + /* 0 - Clock Running, 1 - Clock stopped in State L, 2 - H, > + 3 - unkonwn state. rest are RFU > + */ > +}; > + > +typedef struct __attribute__ ((__packed__)) CCID_Header { > + uint8_t bMessageType; > + uint32_t dwLength; > + uint8_t bSlot; > + uint8_t bSeq; > +} CCID_Header; Is this header decided upon by the CCID spec or the code? It seems suboptimal to have a uint8 in front of a uint32 like that. Inefficient structure alignment :( > + > +/* 6.1.4 PC_to_RDR_XfrBlock */ > +typedef struct __attribute__ ((__packed__)) CCID_XferBlock { > + CCID_Header hdr; > + uint8_t bBWI; /* Block Waiting Timeout */ > + uint16_t wLevelParameter; /* XXX currently unused */ > + uint8_t abData[0]; > +} CCID_XferBlock; > + > +typedef struct __attribute__ ((__packed__)) CCID_IccPowerOn { > + CCID_Header hdr; > + uint8_t bPowerSelect; > + uint16_t abRFU; > +} CCID_IccPowerOn; Same problem with the above two structs.... > +typedef struct __attribute__ ((__packed__)) CCID_IccPowerOff { > + CCID_Header hdr; > + uint16_t abRFU; > +} CCID_IccPowerOff; > + > +typedef struct __attribute__ ((__packed__)) CCID_SetParameters { > + CCID_Header hdr; > + uint8_t bProtocolNum; > + uint16_t abRFU; > + uint8_t abProtocolDataStructure[0]; > +} CCID_SetParameters; and again. > +/** > + * powered - defaults to true, changed by PowerOn/PowerOff messages > + */ > +struct USBCCIDState { > + USBDevice dev; > + CCIDBus *bus; > + CCIDCardState *card; > + CCIDCardInfo *cardinfo; /* caching the info pointer */ > + uint8_t debug; > + BulkIn bulk_in_pending[BULK_IN_PENDING_NUM]; /* circular */ > + uint32_t bulk_in_pending_start; > + uint32_t bulk_in_pending_end; /* first free */ > + uint32_t bulk_in_pending_num; > + BulkIn *current_bulk_in; > + uint8_t bulk_out_data[BULK_OUT_DATA_SIZE]; > + uint32_t bulk_out_pos; > + uint8_t bmSlotICCState; > + uint8_t powered; > + uint8_t notify_slot_change; > + uint64_t last_answer_error; > + Answer pending_answers[PENDING_ANSWERS_NUM]; > + uint32_t pending_answers_start; > + uint32_t pending_answers_end; > + uint32_t pending_answers_num; > + uint8_t bError; > + uint8_t bmCommandStatus; > + uint8_t bProtocolNum; > + uint8_t abProtocolDataStructure[MAX_PROTOCOL_SIZE]; > + uint32_t ulProtocolDataStructureSize; > + uint32_t state_vmstate; > + uint8_t migration_state; > + uint32_t migration_target_ip; > + uint16_t migration_target_port; > +}; Try to place the struct elements a little better so you don't end up with a lot of space wasted due to natural alignment by the compiler. > +static void ccid_bulk_in_get(USBCCIDState *s) > +{ > + if (s->current_bulk_in != NULL || s->bulk_in_pending_num == 0) { > + return; > + } > + assert(s->bulk_in_pending_num > 0); > + s->bulk_in_pending_num--; > + s->current_bulk_in = &s->bulk_in_pending[ > + (s->bulk_in_pending_start++) % BULK_IN_PENDING_NUM]; That line break is really not good :( Either break it after the '=' or calculate the index outside the assignment statement. > +static void ccid_write_data_block( > + USBCCIDState *s, uint8_t slot, uint8_t seq, > + const uint8_t *data, uint32_t len) Please fix this - keep some arguments on the first line, and align the following ones to match. > +/* handle a single USB_TOKEN_OUT, return value returned to guest. > + * 0 - all ok > + * USB_RET_STALL - failed to handle packet */ another badly formatted comment > +void ccid_card_card_error(CCIDCardState *card, uint64_t error) > +{ > + USBCCIDState *s = > + DO_UPCAST(USBCCIDState, dev.qdev, card->qdev.parent_bus->parent); > + > + s->bmCommandStatus = COMMAND_STATUS_FAILED; > + s->last_answer_error = error; > + DPRINTF(s, 1, "VSC_Error: %lX\n", s->last_answer_error); > + /* TODO: these error's should be more verbose and propogated to the guest. > + * */ > + /* we flush all pending answers on CardRemove message in ccid-card-passthru, > + * so check that first to not trigger abort */ !!! there's more below. Except for the mostly cosmetic stuff, it looks ok to me. Cheers, Jes ^ permalink raw reply [flat|nested] 35+ messages in thread
* Re: [Qemu-devel] [PATCH 1/7] usb-ccid: add CCID bus 2011-03-14 13:54 ` Jes Sorensen @ 2011-03-14 14:07 ` Daniel P. Berrange 2011-03-14 14:12 ` Anthony Liguori 2011-03-16 9:15 ` Alon Levy 1 sibling, 1 reply; 35+ messages in thread From: Daniel P. Berrange @ 2011-03-14 14:07 UTC (permalink / raw) To: Jes Sorensen; +Cc: Alon Levy, qemu-devel On Mon, Mar 14, 2011 at 02:54:59PM +0100, Jes Sorensen wrote: > On 02/23/11 12:20, Alon Levy wrote: > > diff --git a/configure b/configure > > index 791b71d..147aab3 100755 > > --- a/configure > > +++ b/configure > > @@ -174,6 +174,7 @@ trace_backend="nop" > > trace_file="trace" > > spice="" > > rbd="" > > +smartcard="yes" > > IMHO smartcard support shouldn't be enabled per default. The userbase is > limited. Deciding based on importance/size of userbase is rather subjective. IMHO all features should be enabled by default, but if they depend on a 3rd party library that isn't installed on the build host, they should automatically disable themselves. eg * [the default] - on, if external library is present, off otherwise * --enable-<feature> - always on, raise error if external library is missing * --disable-<feature> - always off, Regards, Daniel -- |: http://berrange.com -o- http://www.flickr.com/photos/dberrange/ :| |: http://libvirt.org -o- http://virt-manager.org :| |: http://autobuild.org -o- http://search.cpan.org/~danberr/ :| |: http://entangle-photo.org -o- http://live.gnome.org/gtk-vnc :| ^ permalink raw reply [flat|nested] 35+ messages in thread
* Re: [Qemu-devel] [PATCH 1/7] usb-ccid: add CCID bus 2011-03-14 14:07 ` Daniel P. Berrange @ 2011-03-14 14:12 ` Anthony Liguori 0 siblings, 0 replies; 35+ messages in thread From: Anthony Liguori @ 2011-03-14 14:12 UTC (permalink / raw) To: Daniel P. Berrange; +Cc: Jes Sorensen, Alon Levy, qemu-devel On 03/14/2011 09:07 AM, Daniel P. Berrange wrote: > On Mon, Mar 14, 2011 at 02:54:59PM +0100, Jes Sorensen wrote: >> On 02/23/11 12:20, Alon Levy wrote: >>> diff --git a/configure b/configure >>> index 791b71d..147aab3 100755 >>> --- a/configure >>> +++ b/configure >>> @@ -174,6 +174,7 @@ trace_backend="nop" >>> trace_file="trace" >>> spice="" >>> rbd="" >>> +smartcard="yes" >> IMHO smartcard support shouldn't be enabled per default. The userbase is >> limited. > Deciding based on importance/size of userbase is rather subjective. > IMHO all features should be enabled by default, but if they depend > on a 3rd party library that isn't installed on the build host, they > should automatically disable themselves. eg Yes, and this is the policy we've taken pretty consistently in QEMU. Features that aren't enabled by default are subject to bit-rot. Regards, Anthony Liguori > * [the default] - on, if external library is present, off otherwise > * --enable-<feature> - always on, raise error if external library is missing > * --disable-<feature> - always off, > > Regards, > Daniel ^ permalink raw reply [flat|nested] 35+ messages in thread
* Re: [Qemu-devel] [PATCH 1/7] usb-ccid: add CCID bus 2011-03-14 13:54 ` Jes Sorensen 2011-03-14 14:07 ` Daniel P. Berrange @ 2011-03-16 9:15 ` Alon Levy 2011-03-16 9:26 ` Jes Sorensen 1 sibling, 1 reply; 35+ messages in thread From: Alon Levy @ 2011-03-16 9:15 UTC (permalink / raw) To: Jes Sorensen; +Cc: qemu-devel On Mon, Mar 14, 2011 at 02:54:59PM +0100, Jes Sorensen wrote: > On 02/23/11 12:20, Alon Levy wrote: > > diff --git a/configure b/configure > > index 791b71d..147aab3 100755 > > --- a/configure > > +++ b/configure > > @@ -174,6 +174,7 @@ trace_backend="nop" > > trace_file="trace" > > spice="" > > rbd="" > > +smartcard="yes" > > IMHO smartcard support shouldn't be enabled per default. The userbase is > limited. > > > diff --git a/hw/ccid.h b/hw/ccid.h > > new file mode 100644 > > index 0000000..4350bc2 > > --- /dev/null > > +++ b/hw/ccid.h > > @@ -0,0 +1,54 @@ > > +/* > > + * CCID Passthru Card Device emulation > > + * > > + * Copyright (c) 2011 Red Hat. > > + * Written by Alon Levy. > > + * > > + * This code is licenced under the GNU LGPL, version 2 or later. > > + */ > > + > [snip] > > + > > +/* callbacks to be used by the CCID device (hw/usb-ccid.c) to call > > + * into the smartcard device (hw/ccid-card-*.c) > > + */ > > This is inconsistent with the comment above. Normally multi-line > comments in QEMU are like this: > /* > * foo > * bar > */ > > > +struct CCIDCardInfo { > > + DeviceInfo qdev; > > + void (*print)(Monitor *mon, CCIDCardState *card, int indent); > > + const uint8_t *(*get_atr)(CCIDCardState *card, uint32_t *len); > > + void (*apdu_from_guest)(CCIDCardState *card, > > + const uint8_t *apdu, > > + uint32_t len); > > + int (*exitfn)(CCIDCardState *card); > > + int (*initfn)(CCIDCardState *card); > > +}; > > + > > +/* API for smartcard calling the CCID device (used by hw/ccid-card-*.c) > > + */ > > again here > > > +void ccid_card_send_apdu_to_guest(CCIDCardState *card, > > + uint8_t *apdu, > > + uint32_t len); > > +void ccid_card_card_removed(CCIDCardState *card); > > +void ccid_card_card_inserted(CCIDCardState *card); > > +void ccid_card_card_error(CCIDCardState *card, uint64_t error); > > +void ccid_card_qdev_register(CCIDCardInfo *card); > > + > > +/* support guest visible insertion/removal of ccid devices based on actual > > + * devices connected/removed. Called by card implementation (passthru, local) */ > > and here > > > diff --git a/hw/usb-ccid.c b/hw/usb-ccid.c > > new file mode 100644 > > index 0000000..bf4022a > > --- /dev/null > > +++ b/hw/usb-ccid.c > > +#define CCID_DEV_NAME "usb-ccid" > > + > > +/* The two options for variable sized buffers: > > + * make them constant size, for large enough constant, > > + * or handle the migration complexity - VMState doesn't handle this case. > > + * sizes are expected never to be exceeded, unless guest misbehaves. */ > > here again > > [snip] > > > +/* Using Gemplus Vendor and Product id > > + Effect on various drivers: > > + * usbccid.sys (winxp, others untested) is a class driver so it doesn't care. > > + * linux has a number of class drivers, but openct filters based on > > + vendor/product (/etc/openct.conf under fedora), hence Gemplus. > > + */ > > Something went totally boink with the comments there! > > > +/* 6.2.6 RDR_to_PC_SlotStatus definitions */ > > +enum { > > + CLOCK_STATUS_RUNNING = 0, > > + /* 0 - Clock Running, 1 - Clock stopped in State L, 2 - H, > > + 3 - unkonwn state. rest are RFU > > + */ > > +}; > > + > > +typedef struct __attribute__ ((__packed__)) CCID_Header { > > + uint8_t bMessageType; > > + uint32_t dwLength; > > + uint8_t bSlot; > > + uint8_t bSeq; > > +} CCID_Header; > > Is this header decided upon by the CCID spec or the code? It seems > suboptimal to have a uint8 in front of a uint32 like that. Inefficient > structure alignment :( > In the spec. > > + > > +/* 6.1.4 PC_to_RDR_XfrBlock */ > > +typedef struct __attribute__ ((__packed__)) CCID_XferBlock { > > + CCID_Header hdr; > > + uint8_t bBWI; /* Block Waiting Timeout */ > > + uint16_t wLevelParameter; /* XXX currently unused */ > > + uint8_t abData[0]; > > +} CCID_XferBlock; > > + > > +typedef struct __attribute__ ((__packed__)) CCID_IccPowerOn { > > + CCID_Header hdr; > > + uint8_t bPowerSelect; > > + uint16_t abRFU; > > +} CCID_IccPowerOn; > > Same problem with the above two structs.... > spec spec. > > +typedef struct __attribute__ ((__packed__)) CCID_IccPowerOff { > > + CCID_Header hdr; > > + uint16_t abRFU; > > +} CCID_IccPowerOff; > > + > > +typedef struct __attribute__ ((__packed__)) CCID_SetParameters { > > + CCID_Header hdr; > > + uint8_t bProtocolNum; > > + uint16_t abRFU; > > + uint8_t abProtocolDataStructure[0]; > > +} CCID_SetParameters; > > and again. > guess ;) > > +/** > > + * powered - defaults to true, changed by PowerOn/PowerOff messages > > + */ > > +struct USBCCIDState { > > + USBDevice dev; > > + CCIDBus *bus; > > + CCIDCardState *card; > > + CCIDCardInfo *cardinfo; /* caching the info pointer */ > > + uint8_t debug; > > + BulkIn bulk_in_pending[BULK_IN_PENDING_NUM]; /* circular */ > > + uint32_t bulk_in_pending_start; > > + uint32_t bulk_in_pending_end; /* first free */ > > + uint32_t bulk_in_pending_num; > > + BulkIn *current_bulk_in; > > + uint8_t bulk_out_data[BULK_OUT_DATA_SIZE]; > > + uint32_t bulk_out_pos; > > + uint8_t bmSlotICCState; > > + uint8_t powered; > > + uint8_t notify_slot_change; > > + uint64_t last_answer_error; > > + Answer pending_answers[PENDING_ANSWERS_NUM]; > > + uint32_t pending_answers_start; > > + uint32_t pending_answers_end; > > + uint32_t pending_answers_num; > > + uint8_t bError; > > + uint8_t bmCommandStatus; > > + uint8_t bProtocolNum; > > + uint8_t abProtocolDataStructure[MAX_PROTOCOL_SIZE]; > > + uint32_t ulProtocolDataStructureSize; > > + uint32_t state_vmstate; > > + uint8_t migration_state; > > + uint32_t migration_target_ip; > > + uint16_t migration_target_port; > > +}; > > Try to place the struct elements a little better so you don't end up > with a lot of space wasted due to natural alignment by the compiler. > ok, this one's me. I'm really not sure except for stuff that goes on the wire or get's allocated a bazillion times that this is worth the change in general, but since I'm respinning anyway I'll do it. (unless you're saying it should be a habit). > > +static void ccid_bulk_in_get(USBCCIDState *s) > > +{ > > + if (s->current_bulk_in != NULL || s->bulk_in_pending_num == 0) { > > + return; > > + } > > + assert(s->bulk_in_pending_num > 0); > > + s->bulk_in_pending_num--; > > + s->current_bulk_in = &s->bulk_in_pending[ > > + (s->bulk_in_pending_start++) % BULK_IN_PENDING_NUM]; > > That line break is really not good :( Either break it after the '=' or > calculate the index outside the assignment statement. ok, after the =, but then I think the rest is >80, so it will neccessitate another break. > > > +static void ccid_write_data_block( > > + USBCCIDState *s, uint8_t slot, uint8_t seq, > > + const uint8_t *data, uint32_t len) > > Please fix this - keep some arguments on the first line, and align the > following ones to match. Is that a coding style thing I missed or personal preferance? my personal preferance here is the way it is, since it looks shorter/more readable, but I don't care that much. > > > +/* handle a single USB_TOKEN_OUT, return value returned to guest. > > + * 0 - all ok > > + * USB_RET_STALL - failed to handle packet */ > > another badly formatted comment > fixing them all. > > +void ccid_card_card_error(CCIDCardState *card, uint64_t error) > > +{ > > + USBCCIDState *s = > > + DO_UPCAST(USBCCIDState, dev.qdev, card->qdev.parent_bus->parent); > > + > > + s->bmCommandStatus = COMMAND_STATUS_FAILED; > > + s->last_answer_error = error; > > + DPRINTF(s, 1, "VSC_Error: %lX\n", s->last_answer_error); > > + /* TODO: these error's should be more verbose and propogated to the guest. > > + * */ > > + /* we flush all pending answers on CardRemove message in ccid-card-passthru, > > + * so check that first to not trigger abort */ > > !!! there's more below. ? more badly formated comments? more todos? more flushing? > > Except for the mostly cosmetic stuff, it looks ok to me. > > Cheers, > Jes > > ^ permalink raw reply [flat|nested] 35+ messages in thread
* Re: [Qemu-devel] [PATCH 1/7] usb-ccid: add CCID bus 2011-03-16 9:15 ` Alon Levy @ 2011-03-16 9:26 ` Jes Sorensen 0 siblings, 0 replies; 35+ messages in thread From: Jes Sorensen @ 2011-03-16 9:26 UTC (permalink / raw) To: Alon Levy; +Cc: qemu-devel On 03/16/11 10:15, Alon Levy wrote: > On Mon, Mar 14, 2011 at 02:54:59PM +0100, Jes Sorensen wrote: >>> +typedef struct __attribute__ ((__packed__)) CCID_Header { >>> + uint8_t bMessageType; >>> + uint32_t dwLength; >>> + uint8_t bSlot; >>> + uint8_t bSeq; >>> +} CCID_Header; >> >> Is this header decided upon by the CCID spec or the code? It seems >> suboptimal to have a uint8 in front of a uint32 like that. Inefficient >> structure alignment :( >> > > In the spec. I was afraid of that, clearly a spec written by the people doing the wire protocol, without considering the software aspects. >>> +/** >>> + * powered - defaults to true, changed by PowerOn/PowerOff messages >>> + */ >>> +struct USBCCIDState { >>> + USBDevice dev; >>> + CCIDBus *bus; >>> + CCIDCardState *card; >>> + CCIDCardInfo *cardinfo; /* caching the info pointer */ >>> + uint8_t debug; >>> + BulkIn bulk_in_pending[BULK_IN_PENDING_NUM]; /* circular */ >>> + uint32_t bulk_in_pending_start; >>> + uint32_t bulk_in_pending_end; /* first free */ >>> + uint32_t bulk_in_pending_num; >>> + BulkIn *current_bulk_in; >>> + uint8_t bulk_out_data[BULK_OUT_DATA_SIZE]; >>> + uint32_t bulk_out_pos; >>> + uint8_t bmSlotICCState; >>> + uint8_t powered; >>> + uint8_t notify_slot_change; >>> + uint64_t last_answer_error; >>> + Answer pending_answers[PENDING_ANSWERS_NUM]; >>> + uint32_t pending_answers_start; >>> + uint32_t pending_answers_end; >>> + uint32_t pending_answers_num; >>> + uint8_t bError; >>> + uint8_t bmCommandStatus; >>> + uint8_t bProtocolNum; >>> + uint8_t abProtocolDataStructure[MAX_PROTOCOL_SIZE]; >>> + uint32_t ulProtocolDataStructureSize; >>> + uint32_t state_vmstate; >>> + uint8_t migration_state; >>> + uint32_t migration_target_ip; >>> + uint16_t migration_target_port; >>> +}; >> >> Try to place the struct elements a little better so you don't end up >> with a lot of space wasted due to natural alignment by the compiler. >> > > ok, this one's me. I'm really not sure except for stuff that goes on the wire > or get's allocated a bazillion times that this is worth the change in general, > but since I'm respinning anyway I'll do it. (unless you're saying it should be > a habit). If it is a one-off allocation, it's really not a big deal, but it is a good thing to keep in mind. In particular on non-x86 64 bit entities are normally 64 bit aligned. >>> +static void ccid_bulk_in_get(USBCCIDState *s) >>> +{ >>> + if (s->current_bulk_in != NULL || s->bulk_in_pending_num == 0) { >>> + return; >>> + } >>> + assert(s->bulk_in_pending_num > 0); >>> + s->bulk_in_pending_num--; >>> + s->current_bulk_in = &s->bulk_in_pending[ >>> + (s->bulk_in_pending_start++) % BULK_IN_PENDING_NUM]; >> >> That line break is really not good :( Either break it after the '=' or >> calculate the index outside the assignment statement. > > ok, after the =, but then I think the rest is >80, so it will neccessitate another > break. If it was my code, I would calculate the index on the previous line in a tmp variable. It is a matter of personal preference of course. >>> +static void ccid_write_data_block( >>> + USBCCIDState *s, uint8_t slot, uint8_t seq, >>> + const uint8_t *data, uint32_t len) >> >> Please fix this - keep some arguments on the first line, and align the >> following ones to match. > > Is that a coding style thing I missed or personal preferance? my personal preferance > here is the way it is, since it looks shorter/more readable, but I don't care that > much. It is not written down :(, but it is common practice. I raise the issue exactly because it is much more readable the other way :) >> >>> +/* handle a single USB_TOKEN_OUT, return value returned to guest. >>> + * 0 - all ok >>> + * USB_RET_STALL - failed to handle packet */ >> >> another badly formatted comment >> > fixing them all. Excellent! >>> +void ccid_card_card_error(CCIDCardState *card, uint64_t error) >>> +{ >>> + USBCCIDState *s = >>> + DO_UPCAST(USBCCIDState, dev.qdev, card->qdev.parent_bus->parent); >>> + >>> + s->bmCommandStatus = COMMAND_STATUS_FAILED; >>> + s->last_answer_error = error; >>> + DPRINTF(s, 1, "VSC_Error: %lX\n", s->last_answer_error); >>> + /* TODO: these error's should be more verbose and propogated to the guest. >>> + * */ >>> + /* we flush all pending answers on CardRemove message in ccid-card-passthru, >>> + * so check that first to not trigger abort */ >> >> !!! there's more below. > ? more badly formated comments? more todos? more flushing? Comments yeah. It doesn't affect the code, but for a new patch it really is better to get it straightened before it goes upstream. Cheers, Jes ^ permalink raw reply [flat|nested] 35+ messages in thread
* [Qemu-devel] [PATCH 0/7] usb-ccid (v19) @ 2011-02-07 16:34 Alon Levy 2011-02-07 16:34 ` [Qemu-devel] [PATCH 1/7] usb-ccid: add CCID bus Alon Levy 0 siblings, 1 reply; 35+ messages in thread From: Alon Levy @ 2011-02-07 16:34 UTC (permalink / raw) To: qemu-devel This patchset adds three new devices, usb-ccid, ccid-card-passthru and ccid-card-emulated, providing a CCID bus, a simple passthru protocol implementing card requiring a client, and a standalone emulated card. It also introduces a new directory libcaccard with CAC card emulation, CAC is a type of ISO 7816 smart card. Tree for pull: git://anongit.freedesktop.org/~alon/qemu usb_ccid.v19 v18-v19 changes: * more merges, down to a single digit number of patches. * drop enumeration property, use string. * rebased (trivial) v17-v18 changes: * merge vscard_common.h patches. * actually provide a tree to pull. v16-v17 changes: * merged all the "v15->v16" patches * merged some more wherever it was easy (all same file commits). * added signed off by to first four patches * ccid.h: added copyright, removed underscore in defines, and replaced non C89 comments v15-v16 changes: * split vscard_common introducing patch for ease of review * sum of commit logs for the v15-v16 commits: (whitespace fixes removed for space, see original commit messages in later patches) * usb-ccid: * fix abort on client answer after card remove * enable migration * remove side affect code from asserts * return consistent self-powered state * mask out reserved bits in ccid_set_parameters * add missing abRFU in SetParameters (no affect on linux guest) * vscard_common.h protocol change: * VSCMsgInit capabilities and magic * removed ReaderResponse, will use Error instead with code==VSC_SUCCESS. * added Flush and FlushComplete, remove Reconnect. * define VSCARD_MAGIC * added error code VSC_SUCCESS. * ccid-card-passthru * return correct size * return error instead of assert if client sent too large ATR * don't assert if client sent too large a size, but add asserts for indices to buffer * reset vscard_in indices on chardev disconnect * handle init from client * error if no chardev supplied * use ntoh, hton * eradicate reader_id_t * remove Reconnect usage (removed from VSCARD protocol) * send VSC_SUCCESS on card insert/remove and reader add/remove * ccid-card-emulated * fix error reporting in initfn v14-v15 changes: * add patch with --enable-smartcard and --disable-smartcard and only disable ccid-card-emulated if nss not found. * add patch with description strings * s/libcaccard/libcacard/ in docs/ccid.txt v13-v14 changes: - support device_del/device_add on ccid-card-* and usb-ccid * usb-ccid: * lose card reference when card device deleted * check slot number and deny adding a slot if one is already added. * ccid-card-*: use qdev_simple_unplug_cb in both emulated and passthru ccid cards, the exitfn already takes care of triggering card removal in the usb dev. * libcacard: * remove double include of config-host.mak * add replay of card events to libcacard to support second and more emulation * don't initialize more then once (doesn't support it right now, so one thread, NSS thread, is left when device_del is done) * add VCARD_EMUL_INIT_ALREADY_INITED * ccid-card-emulated: * take correct mutexes on signaling to fix deadlocks on device_del * allow card insertion/removal event without proper reader insertion event v12-v13 changes: * libcacard: * fix Makefile clean to remove vscclient * fix double include of config-host in Makefile * usb-ccid: remove attach/detach logic, usb is always attached. Guest doesn't care if there is a reader attached with no card anyway. * ccid-card-passthru: don't close chr_dev on removal, makes it possible to use device_del/device_add to create remove/insertion for debugging. v11-v12 changes: * fix out of tree build v10-v11 changes: * fix last patch that removed one of the doc files. * updated flow table in docs/ccid.txt v8-v10 changes: * usb-ccid: * add slot for future use (Gerd) * ifdef ENABLE_MIGRATION for migration support on account of usb migration not being ready in general. (Gerd) * verbosified commit messages. (Gerd) * put libcacard docs in libcacard commit. (Gerd) v8-v9 changes: * Blue Swirl comments: * white space fixes * enabled by default, disabled only if missing nss * forgotten fix from v8 (don't build libcacard.so) * added a note about device being little endian * library renamed from libcaccard to libcacard * squashed both of libcacard patches, they touched different files anyway. v7-v8 changes: * Blue Swirl comments: * usb-ccid: deannonymize some structs * usb-ccid: coding style change - answer_t and bulk_in_t fixed * usb-ccid: handle endianess conversion between guest and host * usb-ccid: s/ccid_bulk_in_copy_out/ccid_bulk_in_copy_to_guest/ * ccid-card-emulated: fix segfault if backend not specified * ccid-card-emulated: let last reader inserted win * libcaccard: remove double vscard_common.h v6->v7 changes: * external libcaccard became internal directory libcaccard * statically link object files into qemu * produce libcaccard.so for usage by external projects * applied coding style to new code (please check me) - did not use the qemu options parsing for libcaccard, since it seems to draw large amounts of qemu code (monitor for instance). v5->v6 changes: * really remove static debug (I apologize for claiming to have done so before) v4->v5 changes: * rebased to latest * remove static debug in card devices * fix --enable-smartcard to link * stall instead of assert when exceeding BULK_OUT_DATA_SIZE * make ccid_reserve_recv_buf for too large len discard message, not exit * make ccid_reserve_recv_buf return void* * fix typo * remove commented code in VMState v3->v4: * remove ccid field in CCIDBus * remove static debug in bus * add back docs v2->v3: * split into bus (usb-ccid.c, uses ccid.h) and card (ccid-card-passthru.c). * removed documentation (being revised). v1->v2: * all QSIMPLEQ turned into fixed sized rings * all allocated buffers turned into fixed size buffers * added migration support * added a message to tell client qemu has migrated to ip:port * for lack of monitor commands ip:port are 0:0, which causes the updated vscclient to connect to one port higher on the same host. will add monitor commands in a separate patch. tested with current setup. Alon Levy (6): usb-ccid: add CCID bus introduce libcacard/vscard_common.h ccid: add passthru card device ccid: add ccid-card-emulated device ccid: add docs ccid: configure: improve --enable-smartcard flags Robert Relyea (1): libcacard: initial commit Makefile | 6 +- Makefile.objs | 7 + Makefile.target | 2 + configure | 60 ++ docs/ccid.txt | 135 +++++ docs/libcacard.txt | 483 +++++++++++++++ hw/ccid-card-emulated.c | 580 ++++++++++++++++++ hw/ccid-card-passthru.c | 329 +++++++++++ hw/ccid.h | 51 ++ hw/usb-ccid.c | 1353 +++++++++++++++++++++++++++++++++++++++++++ libcacard/Makefile | 14 + libcacard/cac.c | 411 +++++++++++++ libcacard/cac.h | 20 + libcacard/card_7816.c | 780 +++++++++++++++++++++++++ libcacard/card_7816.h | 60 ++ libcacard/card_7816t.h | 163 ++++++ libcacard/event.c | 112 ++++ libcacard/eventt.h | 28 + libcacard/link_test.c | 20 + libcacard/mutex.h | 59 ++ libcacard/passthru.c | 612 +++++++++++++++++++ libcacard/passthru.h | 50 ++ libcacard/vcard.c | 350 +++++++++++ libcacard/vcard.h | 85 +++ libcacard/vcard_emul.h | 62 ++ libcacard/vcard_emul_nss.c | 1192 +++++++++++++++++++++++++++++++++++++ libcacard/vcard_emul_type.c | 60 ++ libcacard/vcard_emul_type.h | 29 + libcacard/vcardt.h | 66 +++ libcacard/vevent.h | 26 + libcacard/vreader.c | 526 +++++++++++++++++ libcacard/vreader.h | 54 ++ libcacard/vreadert.h | 23 + libcacard/vscard_common.h | 167 ++++++ libcacard/vscclient.c | 743 ++++++++++++++++++++++++ 35 files changed, 8716 insertions(+), 2 deletions(-) create mode 100644 docs/ccid.txt create mode 100644 docs/libcacard.txt create mode 100644 hw/ccid-card-emulated.c create mode 100644 hw/ccid-card-passthru.c create mode 100644 hw/ccid.h create mode 100644 hw/usb-ccid.c create mode 100644 libcacard/Makefile create mode 100644 libcacard/cac.c create mode 100644 libcacard/cac.h create mode 100644 libcacard/card_7816.c create mode 100644 libcacard/card_7816.h create mode 100644 libcacard/card_7816t.h create mode 100644 libcacard/event.c create mode 100644 libcacard/eventt.h create mode 100644 libcacard/link_test.c create mode 100644 libcacard/mutex.h create mode 100644 libcacard/passthru.c create mode 100644 libcacard/passthru.h create mode 100644 libcacard/vcard.c create mode 100644 libcacard/vcard.h create mode 100644 libcacard/vcard_emul.h create mode 100644 libcacard/vcard_emul_nss.c create mode 100644 libcacard/vcard_emul_type.c create mode 100644 libcacard/vcard_emul_type.h create mode 100644 libcacard/vcardt.h create mode 100644 libcacard/vevent.h create mode 100644 libcacard/vreader.c create mode 100644 libcacard/vreader.h create mode 100644 libcacard/vreadert.h create mode 100644 libcacard/vscard_common.h create mode 100644 libcacard/vscclient.c -- 1.7.4 ^ permalink raw reply [flat|nested] 35+ messages in thread
* [Qemu-devel] [PATCH 1/7] usb-ccid: add CCID bus 2011-02-07 16:34 [Qemu-devel] [PATCH 0/7] usb-ccid (v19) Alon Levy @ 2011-02-07 16:34 ` Alon Levy 2011-02-22 16:03 ` Anthony Liguori 0 siblings, 1 reply; 35+ messages in thread From: Alon Levy @ 2011-02-07 16:34 UTC (permalink / raw) To: qemu-devel A CCID device is a smart card reader. It is a USB device, defined at [1]. This patch introduces the usb-ccid device that is a ccid bus. Next patches will introduce two card types to use it, a passthru card and an emulated card. [1] http://www.usb.org/developers/devclass_docs/DWG_Smart-Card_CCID_Rev110. Signed-off-by: Alon Levy <alevy@redhat.com> --- changes from v18->v19: * merged: ccid.h: add copyright, fix define and remove non C89 comments * add qdev.desc changes from v15->v16: Behavioral changes: * fix abort on client answer after card remove * enable migration * remove side affect code from asserts * return consistent self-powered state * mask out reserved bits in ccid_set_parameters * add missing abRFU in SetParameters (no affect on linux guest) whitefixes / comments / consts defines: * remove stale comment * remove ccid_print_pending_answers if no DEBUG_CCID * replace printf's with DPRINTF, remove DEBUG_CCID, add verbosity defines * use error_report * update copyright (most of the code is not original) * reword known bug comment * add missing closing quote in comment * add missing whitespace on one line * s/CCID_SetParameter/CCID_SetParameters/ * add comments * use define for max packet size Comment for "return consistent self-powered state": the Configuration Descriptor bmAttributes claims we are self powered, but we were returning not self powered to USB_REQ_GET_STATUS control message. In practice, this message is not sent by a linux 2.6.35.10-74.fc14.x86_64 guest (not tested on other guests), unless you issue lsusb -v as root (for example). --- Makefile.objs | 1 + configure | 6 + hw/ccid.h | 51 +++ hw/usb-ccid.c | 1353 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 1411 insertions(+), 0 deletions(-) create mode 100644 hw/ccid.h create mode 100644 hw/usb-ccid.c diff --git a/Makefile.objs b/Makefile.objs index f1c7bfe..a1f3853 100644 --- a/Makefile.objs +++ b/Makefile.objs @@ -195,6 +195,7 @@ hw-obj-$(CONFIG_FDC) += fdc.o hw-obj-$(CONFIG_ACPI) += acpi.o acpi_piix4.o hw-obj-$(CONFIG_APM) += pm_smbus.o apm.o hw-obj-$(CONFIG_DMA) += dma.o +hw-obj-$(CONFIG_SMARTCARD) += usb-ccid.o # PPC devices hw-obj-$(CONFIG_OPENPIC) += openpic.o diff --git a/configure b/configure index 598e8e1..14a035a 100755 --- a/configure +++ b/configure @@ -174,6 +174,7 @@ trace_backend="nop" trace_file="trace" spice="" rbd="" +smartcard="yes" # parse CC options first for opt do @@ -2472,6 +2473,7 @@ echo "Trace output file $trace_file-<pid>" echo "spice support $spice" echo "rbd support $rbd" echo "xfsctl support $xfs" +echo "smartcard support $smartcard" if test $sdl_too_old = "yes"; then echo "-> Your SDL version is too old - please upgrade to have SDL support" @@ -2744,6 +2746,10 @@ if test "$spice" = "yes" ; then echo "CONFIG_SPICE=y" >> $config_host_mak fi +if test "$smartcard" = "yes" ; then + echo "CONFIG_SMARTCARD=y" >> $config_host_mak +fi + # XXX: suppress that if [ "$bsd" = "yes" ] ; then echo "CONFIG_BSD=y" >> $config_host_mak diff --git a/hw/ccid.h b/hw/ccid.h new file mode 100644 index 0000000..df9af29 --- /dev/null +++ b/hw/ccid.h @@ -0,0 +1,51 @@ +/* + * CCID Passthru Card Device emulation + * + * Copyright (c) 2011 Red Hat. + * Written by Alon Levy. + * + * This code is licenced under the GNU LGPL, version 2 or later. + */ + +#ifndef CCID_H +#define CCID_H + +#include "qdev.h" + +typedef struct CCIDCardState CCIDCardState; +typedef struct CCIDCardInfo CCIDCardInfo; + +/* state of the CCID Card device (i.e. hw/ccid-card-*.c) + */ +struct CCIDCardState { + DeviceState qdev; + uint32_t slot; /* For future use with multiple slot reader. */ +}; + +/* callbacks to be used by the CCID device (hw/usb-ccid.c) to call + * into the smartcard device (hw/ccid-card-*.c) + */ +struct CCIDCardInfo { + DeviceInfo qdev; + void (*print)(Monitor *mon, CCIDCardState *card, int indent); + const uint8_t *(*get_atr)(CCIDCardState *card, uint32_t *len); + void (*apdu_from_guest)(CCIDCardState *card, const uint8_t *apdu, uint32_t len); + int (*exitfn)(CCIDCardState *card); + int (*initfn)(CCIDCardState *card); +}; + +/* API for smartcard calling the CCID device (used by hw/ccid-card-*.c) + */ +void ccid_card_send_apdu_to_guest(CCIDCardState *card, uint8_t* apdu, uint32_t len); +void ccid_card_card_removed(CCIDCardState *card); +void ccid_card_card_inserted(CCIDCardState *card); +void ccid_card_card_error(CCIDCardState *card, uint64_t error); +void ccid_card_qdev_register(CCIDCardInfo *card); + +/* support guest visible insertion/removal of ccid devices based on actual + * devices connected/removed. Called by card implementation (passthru, local) */ +int ccid_card_ccid_attach(CCIDCardState *card); +void ccid_card_ccid_detach(CCIDCardState *card); + +#endif /* CCID_H */ + diff --git a/hw/usb-ccid.c b/hw/usb-ccid.c new file mode 100644 index 0000000..27a7103 --- /dev/null +++ b/hw/usb-ccid.c @@ -0,0 +1,1353 @@ +/* + * CCID Device emulation + * + * Written by Alon Levy, with contributions from Robert Relyea. + * + * Based on usb-serial.c, see it's copyright and attributions below. + * + * This code is licenced under the GNU LGPL, version 2 or later. + * + * ------- + * + * usb-serial.c copyright and attribution: + * Copyright (c) 2006 CodeSourcery. + * Copyright (c) 2008 Samuel Thibault <samuel.thibault@ens-lyon.org> + * Written by Paul Brook, reused for FTDI by Samuel Thibault, + */ + +/* References: + * + * CCID Specification Revision 1.1 April 22nd 2005 + * "Universal Serial Bus, Device Class: Smart Card" + * Specification for Integrated Circuit(s) Cards Interface Devices + * + * Endianess note: from the spec (1.3) + * "Fields that are larger than a byte are stored in little endian" + * + * KNOWN BUGS + * 1. remove/insert can sometimes result in removed state instead of inserted. + * This is a result of the following: + * symptom: dmesg shows ERMOTEIO (-121), pcscd shows -99. This can happen + * when a short packet is sent, as seen in uhci-usb.c, resulting from a urb + * from the guest requesting SPD and us returning a smaller packet. + * Not sure which messages trigger this. + * + */ + +#include "qemu-common.h" +#include "qemu-error.h" +#include "usb.h" +#include "monitor.h" + +#include "hw/ccid.h" + +#define DPRINTF(s, lvl, fmt, ...) \ +do { if (lvl <= s->debug) { printf("usb-ccid: " fmt , ## __VA_ARGS__); } } while (0) + +#define D_WARN 1 +#define D_INFO 2 +#define D_MORE_INFO 3 +#define D_VERBOSE 4 + +#define CCID_DEV_NAME "usb-ccid" + +/* The two options for variable sized buffers: + * make them constant size, for large enough constant, + * or handle the migration complexity - VMState doesn't handle this case. + * sizes are expected never to be exceeded, unless guest misbehaves. */ +#define BULK_OUT_DATA_SIZE 65536 +#define PENDING_ANSWERS_NUM 128 + +#define BULK_IN_BUF_SIZE 384 +#define BULK_IN_PENDING_NUM 8 + +#define InterfaceOutClass ((USB_DIR_OUT|USB_TYPE_CLASS|USB_RECIP_INTERFACE)<<8) +#define InterfaceInClass ((USB_DIR_IN |USB_TYPE_CLASS|USB_RECIP_INTERFACE)<<8) + +#define CCID_MAX_PACKET_SIZE 64 + +#define CCID_CONTROL_ABORT 0x1 +#define CCID_CONTROL_GET_CLOCK_FREQUENCIES 0x2 +#define CCID_CONTROL_GET_DATA_RATES 0x3 + +#define CCID_PRODUCT_DESCRIPTION "QEMU USB CCID" +#define CCID_VENDOR_DESCRIPTION "QEMU " QEMU_VERSION +#define CCID_INTERFACE_NAME "CCID Interface" +#define CCID_SERIAL_NUMBER_STRING "1" +/* Using Gemplus Vendor and Product id + Effect on various drivers: + * usbccid.sys (winxp, others untested) is a class driver so it doesn't care. + * linux has a number of class drivers, but openct filters based on + vendor/product (/etc/openct.conf under fedora), hence Gemplus. + */ +#define CCID_VENDOR_ID 0x08e6 +#define CCID_PRODUCT_ID 0x4433 +#define CCID_DEVICE_VERSION 0x0000 + +/* BULK_OUT messages from PC to Reader + Defined in CCID Rev 1.1 6.1 (page 26) + */ +#define CCID_MESSAGE_TYPE_PC_to_RDR_IccPowerOn 0x62 +#define CCID_MESSAGE_TYPE_PC_to_RDR_IccPowerOff 0x63 +#define CCID_MESSAGE_TYPE_PC_to_RDR_GetSlotStatus 0x65 +#define CCID_MESSAGE_TYPE_PC_to_RDR_XfrBlock 0x6f +#define CCID_MESSAGE_TYPE_PC_to_RDR_GetParameters 0x6c +#define CCID_MESSAGE_TYPE_PC_to_RDR_ResetParameters 0x6d +#define CCID_MESSAGE_TYPE_PC_to_RDR_SetParameters 0x61 +#define CCID_MESSAGE_TYPE_PC_to_RDR_Escape 0x6b +#define CCID_MESSAGE_TYPE_PC_to_RDR_IccClock 0x6e +#define CCID_MESSAGE_TYPE_PC_to_RDR_T0APDU 0x6a +#define CCID_MESSAGE_TYPE_PC_to_RDR_Secure 0x69 +#define CCID_MESSAGE_TYPE_PC_to_RDR_Mechanical 0x71 +#define CCID_MESSAGE_TYPE_PC_to_RDR_Abort 0x72 +#define CCID_MESSAGE_TYPE_PC_to_RDR_SetDataRateAndClockFrequency 0x73 + +/* BULK_IN messages from Reader to PC + Defined in CCID Rev 1.1 6.2 (page 48) + */ +#define CCID_MESSAGE_TYPE_RDR_to_PC_DataBlock 0x80 +#define CCID_MESSAGE_TYPE_RDR_to_PC_SlotStatus 0x81 +#define CCID_MESSAGE_TYPE_RDR_to_PC_Parameters 0x82 +#define CCID_MESSAGE_TYPE_RDR_to_PC_Escape 0x83 +#define CCID_MESSAGE_TYPE_RDR_to_PC_DataRateAndClockFrequency 0x84 + +/* INTERRUPT_IN messages from Reader to PC + Defined in CCID Rev 1.1 6.3 (page 56) + */ +#define CCID_MESSAGE_TYPE_RDR_to_PC_NotifySlotChange 0x50 +#define CCID_MESSAGE_TYPE_RDR_to_PC_HardwareError 0x51 + +/* Endpoints for CCID - addresses are up to us to decide. + To support slot insertion and removal we must have an interrupt in ep + in addition we need a bulk in and bulk out ep + 5.2, page 20 + */ +#define CCID_INT_IN_EP 1 +#define CCID_BULK_IN_EP 2 +#define CCID_BULK_OUT_EP 3 + +/* bmSlotICCState masks */ +#define SLOT_0_STATE_MASK 1 +#define SLOT_0_CHANGED_MASK 2 + +/* Status codes that go in bStatus (see 6.2.6) */ +enum { + ICC_STATUS_PRESENT_ACTIVE = 0, + ICC_STATUS_PRESENT_INACTIVE, + ICC_STATUS_NOT_PRESENT +}; + +enum { + COMMAND_STATUS_NO_ERROR = 0, + COMMAND_STATUS_FAILED, + COMMAND_STATUS_TIME_EXTENSION_REQUIRED +}; + +/* Error codes that go in bError (see 6.2.6) + */ +enum { + ERROR_CMD_NOT_SUPPORTED = 0, + ERROR_CMD_ABORTED = -1, + ERROR_ICC_MUTE = -2, + ERROR_XFR_PARITY_ERROR = -3, + ERROR_XFR_OVERRUN = -4, + ERROR_HW_ERROR = -5, +}; + +/* 6.2.6 RDR_to_PC_SlotStatus definitions */ +enum { + CLOCK_STATUS_RUNNING = 0, + /* 0 - Clock Running, 1 - Clock stopped in State L, 2 - H, + 3 - unkonwn state. rest are RFU + */ +}; + +typedef struct __attribute__ ((__packed__)) CCID_Header { + uint8_t bMessageType; + uint32_t dwLength; + uint8_t bSlot; + uint8_t bSeq; +} CCID_Header; + +typedef struct __attribute__ ((__packed__)) CCID_BULK_IN { + CCID_Header hdr; + uint8_t bStatus; /* Only used in BULK_IN */ + uint8_t bError; /* Only used in BULK_IN */ +} CCID_BULK_IN; + +typedef struct __attribute__ ((__packed__)) CCID_SlotStatus { + CCID_BULK_IN b; + uint8_t bClockStatus; +} CCID_SlotStatus; + +typedef struct __attribute__ ((__packed__)) CCID_Parameter { + CCID_BULK_IN b; + uint8_t bProtocolNum; + uint8_t abProtocolDataStructure[0]; +} CCID_Parameter; + +typedef struct __attribute__ ((__packed__)) CCID_DataBlock { + CCID_BULK_IN b; + uint8_t bChainParameter; + uint8_t abData[0]; +} CCID_DataBlock; + +/* 6.1.4 PC_to_RDR_XfrBlock */ +typedef struct __attribute__ ((__packed__)) CCID_XferBlock { + CCID_Header hdr; + uint8_t bBWI; /* Block Waiting Timeout */ + uint16_t wLevelParameter; /* XXX currently unused */ + uint8_t abData[0]; +} CCID_XferBlock; + +typedef struct __attribute__ ((__packed__)) CCID_IccPowerOn { + CCID_Header hdr; + uint8_t bPowerSelect; + uint16_t abRFU; +} CCID_IccPowerOn; + +typedef struct __attribute__ ((__packed__)) CCID_IccPowerOff { + CCID_Header hdr; + uint16_t abRFU; +} CCID_IccPowerOff; + +typedef struct __attribute__ ((__packed__)) CCID_SetParameters { + CCID_Header hdr; + uint8_t bProtocolNum; + uint16_t abRFU; + uint8_t abProtocolDataStructure[0]; +} CCID_SetParameters; + +typedef struct CCID_Notify_Slot_Change { + uint8_t bMessageType; /* CCID_MESSAGE_TYPE_RDR_to_PC_NotifySlotChange */ + uint8_t bmSlotICCState; +} CCID_Notify_Slot_Change; + +/* used for DataBlock response to XferBlock */ +typedef struct Answer { + uint8_t slot; + uint8_t seq; +} Answer; + +/* pending BULK_IN messages */ +typedef struct BulkIn { + uint8_t data[BULK_IN_BUF_SIZE]; + uint32_t len; + uint32_t pos; +} BulkIn; + +enum { + MIGRATION_NONE, + MIGRATION_MIGRATED, +}; + +typedef struct CCIDBus CCIDBus; +typedef struct USBCCIDState USBCCIDState; + +#define MAX_PROTOCOL_SIZE 7 + +/** + * powered - defaults to true, changed by PowerOn/PowerOff messages + */ +struct USBCCIDState { + USBDevice dev; + CCIDBus *bus; + CCIDCardState *card; + CCIDCardInfo *cardinfo; /* caching the info pointer */ + uint8_t debug; + BulkIn bulk_in_pending[BULK_IN_PENDING_NUM]; /* circular */ + uint32_t bulk_in_pending_start; + uint32_t bulk_in_pending_end; /* first free */ + uint32_t bulk_in_pending_num; + BulkIn *current_bulk_in; + uint8_t bulk_out_data[BULK_OUT_DATA_SIZE]; + uint32_t bulk_out_pos; + uint8_t bmSlotICCState; + uint8_t powered; + uint8_t notify_slot_change; + uint64_t last_answer_error; + Answer pending_answers[PENDING_ANSWERS_NUM]; + uint32_t pending_answers_start; + uint32_t pending_answers_end; + uint32_t pending_answers_num; + uint8_t bError; + uint8_t bmCommandStatus; + uint8_t bProtocolNum; + uint8_t abProtocolDataStructure[MAX_PROTOCOL_SIZE]; + uint32_t ulProtocolDataStructureSize; + uint32_t state_vmstate; + uint8_t migration_state; + uint32_t migration_target_ip; + uint16_t migration_target_port; +}; + +/* CCID Spec chapter 4: CCID uses a standard device descriptor per Chapter 9, + * "USB Device Framework", section 9.6.1, in the Universal Serial Bus + * Specification. + * + * This device implemented based on the spec and with an Athena Smart Card + * Reader as reference: + * 0dc3:1004 Athena Smartcard Solutions, Inc. + */ + +static const uint8_t qemu_ccid_dev_descriptor[] = { + 0x12, /* u8 bLength; */ + USB_DT_DEVICE, /* u8 bDescriptorType; Device */ + 0x10, 0x01, /* u16 bcdUSB; v1.1 */ + + 0x00, /* u8 bDeviceClass; */ + 0x00, /* u8 bDeviceSubClass; */ + 0x00, /* u8 bDeviceProtocol; [ low/full speeds only ] */ + 0x40, /* u8 bMaxPacketSize0; 8 Bytes (valid: 8,16,32,64) */ + + /* Vendor and product id are arbitrary. */ + /* u16 idVendor */ + CCID_VENDOR_ID & 0xff, CCID_VENDOR_ID >> 8, + /* u16 idProduct */ + CCID_PRODUCT_ID & 0xff, CCID_PRODUCT_ID >> 8, + /* u16 bcdDevice */ + CCID_DEVICE_VERSION & 0xff, CCID_DEVICE_VERSION >> 8, + 0x01, /* u8 iManufacturer; */ + 0x02, /* u8 iProduct; */ + 0x03, /* u8 iSerialNumber; */ + 0x01, /* u8 bNumConfigurations; */ +}; + +static const uint8_t qemu_ccid_config_descriptor[] = { + + /* one configuration */ + 0x09, /* u8 bLength; */ + USB_DT_CONFIG, /* u8 bDescriptorType; Configuration */ + 0x5d, 0x00, /* u16 wTotalLength; 9+9+54+7+7+7 */ + 0x01, /* u8 bNumInterfaces; (1) */ + 0x01, /* u8 bConfigurationValue; */ + 0x00, /* u8 iConfiguration; */ + 0xe0, /* u8 bmAttributes; + Bit 7: must be set, + 6: Self-powered, + 5: Remote wakeup, + 4..0: resvd */ + 100/2, /* u8 MaxPower; 50 == 100mA */ + + /* one interface */ + 0x09, /* u8 if_bLength; */ + USB_DT_INTERFACE, /* u8 if_bDescriptorType; Interface */ + 0x00, /* u8 if_bInterfaceNumber; */ + 0x00, /* u8 if_bAlternateSetting; */ + 0x03, /* u8 if_bNumEndpoints; */ + 0x0b, /* u8 if_bInterfaceClass; Smart Card Device Class */ + 0x00, /* u8 if_bInterfaceSubClass; Subclass code */ + 0x00, /* u8 if_bInterfaceProtocol; Protocol code */ + 0x04, /* u8 if_iInterface; Index of string descriptor */ + + /* Smart Card Device Class Descriptor */ + 0x36, /* u8 bLength; */ + 0x21, /* u8 bDescriptorType; Functional */ + 0x10, 0x01, /* u16 bcdCCID; CCID Specification Release Number. */ + 0x00, /* u8 bMaxSlotIndex; The index of the highest available + slot on this device. All slots are consecutive starting + at 00h. */ + 0x07, /* u8 bVoltageSupport; 01h - 5.0v, 02h - 3.0, 03 - 1.8 */ + + 0x03, 0x00, /* u32 dwProtocols; RRRR PPPP. RRRR = 0000h.*/ + 0x00, 0x00, /* PPPP: 0001h = Protocol T=0, 0002h = Protocol T=1 */ + /* u32 dwDefaultClock; in kHZ (0x0fa0 is 4 MHz) */ + 0xa0, 0x0f, 0x00, 0x00, + /* u32 dwMaximumClock; */ + 0x00, 0x00, 0x01, 0x00, + 0x00, /* u8 bNumClockSupported; 0 means just the default and max. */ + /* u32 dwDataRate ;bps. 9600 == 00002580h */ + 0x80, 0x25, 0x00, 0x00, + /* u32 dwMaxDataRate ; 11520 bps == 0001C200h */ + 0x00, 0xC2, 0x01, 0x00, + 0x00, /* u8 bNumDataRatesSupported; 00 means all rates between + * default and max */ + /* u32 dwMaxIFSD; maximum IFSD supported by CCID for protocol + * T=1 (Maximum seen from various cards) */ + 0xfe, 0x00, 0x00, 0x00, + /* u32 dwSyncProtocols; 1 - 2-wire, 2 - 3-wire, 4 - I2C */ + 0x00, 0x00, 0x00, 0x00, + /* u32 dwMechanical; 0 - no special characteristics. */ + 0x00, 0x00, 0x00, 0x00, + /* u32 dwFeatures; + * 0 - No special characteristics + * + 2 Automatic parameter configuration based on ATR data + * + 4 Automatic activation of ICC on inserting + * + 8 Automatic ICC voltage selection + * + 10 Automatic ICC clock frequency change + * + 20 Automatic baud rate change + * + 40 Automatic parameters negotiation made by the CCID + * + 80 automatic PPS made by the CCID + * 100 CCID can set ICC in clock stop mode + * 200 NAD value other then 00 accepted (T=1 protocol) + * + 400 Automatic IFSD exchange as first exchange (T=1) + * One of the following only: + * + 10000 TPDU level exchanges with CCID + * 20000 Short APDU level exchange with CCID + * 40000 Short and Extended APDU level exchange with CCID + * + * + 100000 USB Wake up signaling supported on card insertion + * and removal. Must set bit 5 in bmAttributes in Configuration + * descriptor if 100000 is set.*/ + 0xfe, 0x04, 0x11, 0x00, + /* u32 dwMaxCCIDMessageLength; For extended APDU in [261 + 10 + * , 65544 + 10]. Otherwise the minimum is wMaxPacketSize of + * the Bulk-OUT endpoint */ + 0x12, 0x00, 0x01, 0x00, + 0xFF, /* u8 bClassGetResponse; Significant only for CCID that + * offers an APDU level for exchanges. Indicates the default + * class value used by the CCID when it sends a Get Response + * command to perform the transportation of an APDU by T=0 + * protocol + * FFh indicates that the CCID echos the class of the APDU. + */ + 0xFF, /* u8 bClassEnvelope; EAPDU only. Envelope command for T=0 */ + 0x00, 0x00, /* u16 wLcdLayout; XXYY Number of lines (XX) and chars per + * line for LCD display used for PIN entry. 0000 - no LCD */ + 0x01, /* u8 bPINSupport; 01h PIN Verification, + * 02h PIN Modification */ + 0x01, /* u8 bMaxCCIDBusySlots; */ + + /* Interrupt-IN endpoint */ + 0x07, /* u8 ep_bLength; */ + /* u8 ep_bDescriptorType; Endpoint */ + USB_DT_ENDPOINT, + /* u8 ep_bEndpointAddress; IN Endpoint 1 */ + 0x80 | CCID_INT_IN_EP, + 0x03, /* u8 ep_bmAttributes; Interrupt */ + /* u16 ep_wMaxPacketSize; */ + CCID_MAX_PACKET_SIZE & 0xff, (CCID_MAX_PACKET_SIZE >> 8), + 0xff, /* u8 ep_bInterval; */ + + /* Bulk-In endpoint */ + 0x07, /* u8 ep_bLength; */ + /* u8 ep_bDescriptorType; Endpoint */ + USB_DT_ENDPOINT, + /* u8 ep_bEndpointAddress; IN Endpoint 2 */ + 0x80 | CCID_BULK_IN_EP, + 0x02, /* u8 ep_bmAttributes; Bulk */ + 0x40, 0x00, /* u16 ep_wMaxPacketSize; */ + 0x00, /* u8 ep_bInterval; */ + + /* Bulk-Out endpoint */ + 0x07, /* u8 ep_bLength; */ + /* u8 ep_bDescriptorType; Endpoint */ + USB_DT_ENDPOINT, + /* u8 ep_bEndpointAddress; OUT Endpoint 3 */ + CCID_BULK_OUT_EP, + 0x02, /* u8 ep_bmAttributes; Bulk */ + 0x40, 0x00, /* u16 ep_wMaxPacketSize; */ + 0x00, /* u8 ep_bInterval; */ + +}; + +static bool ccid_has_pending_answers(USBCCIDState *s) +{ + return s->pending_answers_num > 0; +} + +static void ccid_clear_pending_answers(USBCCIDState *s) +{ + s->pending_answers_num = 0; + s->pending_answers_start = 0; + s->pending_answers_end = 0; +} + +static void ccid_print_pending_answers(USBCCIDState *s) +{ + Answer *answer; + int i, count; + + DPRINTF(s, D_VERBOSE, "usb-ccid: pending answers:"); + if (!ccid_has_pending_answers(s)) { + DPRINTF(s, D_VERBOSE, " empty\n"); + return; + } + for (i = s->pending_answers_start, count=s->pending_answers_num ; + count > 0; count--, i++) { + answer = &s->pending_answers[i % PENDING_ANSWERS_NUM]; + if (count == 1) { + DPRINTF(s, D_VERBOSE, "%d:%d\n", answer->slot, answer->seq); + } else { + DPRINTF(s, D_VERBOSE, "%d:%d,", answer->slot, answer->seq); + } + } +} + +static void ccid_add_pending_answer(USBCCIDState *s, CCID_Header *hdr) +{ + Answer* answer; + + assert(s->pending_answers_num < PENDING_ANSWERS_NUM); + s->pending_answers_num++; + answer = &s->pending_answers[(s->pending_answers_end++) % PENDING_ANSWERS_NUM]; + answer->slot = hdr->bSlot; + answer->seq = hdr->bSeq; + ccid_print_pending_answers(s); +} + +static void ccid_remove_pending_answer(USBCCIDState *s, + uint8_t *slot, uint8_t *seq) +{ + Answer *answer; + + assert(s->pending_answers_num > 0); + s->pending_answers_num--; + answer = &s->pending_answers[(s->pending_answers_start++) % PENDING_ANSWERS_NUM]; + *slot = answer->slot; + *seq = answer->seq; + ccid_print_pending_answers(s); +} + +static void ccid_bulk_in_clear(USBCCIDState *s) +{ + s->bulk_in_pending_start = 0; + s->bulk_in_pending_end = 0; + s->bulk_in_pending_num = 0; +} + +static void ccid_bulk_in_release(USBCCIDState *s) +{ + assert(s->current_bulk_in != NULL); + s->current_bulk_in->pos = 0; + s->current_bulk_in = NULL; +} + +static void ccid_bulk_in_get(USBCCIDState *s) +{ + if (s->current_bulk_in != NULL || s->bulk_in_pending_num == 0) { + return; + } + assert(s->bulk_in_pending_num > 0); + s->bulk_in_pending_num--; + s->current_bulk_in = &s->bulk_in_pending[ + (s->bulk_in_pending_start++) % BULK_IN_PENDING_NUM]; +} + +static void* ccid_reserve_recv_buf(USBCCIDState* s, uint16_t len) +{ + BulkIn* bulk_in; + + DPRINTF(s, D_VERBOSE, "%s: QUEUE: reserve %d bytes\n", __func__, len); + + /* look for an existing element */ + if (len > BULK_IN_BUF_SIZE) { + DPRINTF(s, D_WARN, "usb-ccid.c: %s: len larger then max (%d>%d). discarding message.\n", + __func__, len, BULK_IN_BUF_SIZE); + return NULL; + } + if (s->bulk_in_pending_num >= BULK_IN_PENDING_NUM) { + DPRINTF(s, D_WARN, "usb-ccid.c: %s: No free bulk_in buffers. discarding message.\n", + __func__); + return NULL; + } + bulk_in = &s->bulk_in_pending[(s->bulk_in_pending_end++) % BULK_IN_PENDING_NUM]; + s->bulk_in_pending_num++; + bulk_in->len = len; + return bulk_in->data; +} + +static void ccid_reset(USBCCIDState *s) +{ + ccid_bulk_in_clear(s); + ccid_clear_pending_answers(s); +} + +static void ccid_detach(USBCCIDState *s) +{ + ccid_reset(s); +} + +static void ccid_handle_reset(USBDevice *dev) +{ + USBCCIDState *s = DO_UPCAST(USBCCIDState, dev, dev); + + DPRINTF(s, 1, "Reset\n"); + + ccid_reset(s); +} + +static int ccid_handle_control(USBDevice *dev, int request, int value, + int index, int length, uint8_t *data) +{ + USBCCIDState *s = DO_UPCAST(USBCCIDState, dev, dev); + int ret = 0; + + DPRINTF(s, 1, "got control %x, value %x\n",request, value); + switch (request) { + case DeviceRequest | USB_REQ_GET_STATUS: + data[0] = (1 << USB_DEVICE_SELF_POWERED) | + (dev->remote_wakeup << USB_DEVICE_REMOTE_WAKEUP); + data[1] = 0x00; + ret = 2; + break; + case DeviceOutRequest | USB_REQ_CLEAR_FEATURE: + if (value == USB_DEVICE_REMOTE_WAKEUP) { + dev->remote_wakeup = 0; + } else { + goto fail; + } + ret = 0; + break; + case DeviceOutRequest | USB_REQ_SET_FEATURE: + if (value == USB_DEVICE_REMOTE_WAKEUP) { + dev->remote_wakeup = 1; + } else { + goto fail; + } + ret = 0; + break; + case DeviceOutRequest | USB_REQ_SET_ADDRESS: + dev->addr = value; + ret = 0; + break; + case DeviceRequest | USB_REQ_GET_DESCRIPTOR: + switch(value >> 8) { + case USB_DT_DEVICE: + memcpy(data, qemu_ccid_dev_descriptor, + sizeof(qemu_ccid_dev_descriptor)); + ret = sizeof(qemu_ccid_dev_descriptor); + break; + case USB_DT_CONFIG: + memcpy(data, qemu_ccid_config_descriptor, + sizeof(qemu_ccid_config_descriptor)); + ret = sizeof(qemu_ccid_config_descriptor); + break; + case USB_DT_STRING: + switch(value & 0xff) { + case 0: + /* language ids */ + data[0] = 4; + data[1] = 3; + data[2] = 0x09; + data[3] = 0x04; + ret = 4; + break; + case 1: + /* vendor description */ + ret = set_usb_string(data, CCID_VENDOR_DESCRIPTION); + break; + case 2: + /* product description */ + ret = set_usb_string(data, CCID_PRODUCT_DESCRIPTION); + break; + case 3: + /* serial number */ + ret = set_usb_string(data, CCID_SERIAL_NUMBER_STRING); + break; + case 4: + /* interface name */ + ret = set_usb_string(data, CCID_INTERFACE_NAME); + break; + default: + goto fail; + } + break; + default: + goto fail; + } + break; + case DeviceRequest | USB_REQ_GET_CONFIGURATION: + data[0] = 1; + ret = 1; + break; + case DeviceOutRequest | USB_REQ_SET_CONFIGURATION: + /* Only one configuration - we just ignore the request */ + ret = 0; + break; + case DeviceRequest | USB_REQ_GET_INTERFACE: + data[0] = 0; + ret = 1; + break; + case InterfaceOutRequest | USB_REQ_SET_INTERFACE: + ret = 0; + break; + case EndpointOutRequest | USB_REQ_CLEAR_FEATURE: + ret = 0; + break; + + /* Class specific requests. */ + case InterfaceOutClass | CCID_CONTROL_ABORT: + DPRINTF(s, 1, "ccid_control abort UNIMPLEMENTED\n"); + ret = USB_RET_STALL; + break; + case InterfaceInClass | CCID_CONTROL_GET_CLOCK_FREQUENCIES: + DPRINTF(s, 1, "ccid_control get clock frequencies UNIMPLEMENTED\n"); + ret = USB_RET_STALL; + break; + case InterfaceInClass | CCID_CONTROL_GET_DATA_RATES: + DPRINTF(s, 1, "ccid_control get data rates UNIMPLEMENTED\n"); + ret = USB_RET_STALL; + break; + default: + fail: + DPRINTF(s, 1, "got unsupported/bogus control %x, value %x\n", request, value); + ret = USB_RET_STALL; + break; + } + return ret; +} + +static bool ccid_card_inserted(USBCCIDState *s) +{ + return s->bmSlotICCState & SLOT_0_STATE_MASK; +} + +static uint8_t ccid_card_status(USBCCIDState *s) +{ + return ccid_card_inserted(s) + ? (s->powered ? + ICC_STATUS_PRESENT_ACTIVE + : ICC_STATUS_PRESENT_INACTIVE + ) + : ICC_STATUS_NOT_PRESENT; +} + +static uint8_t ccid_calc_status(USBCCIDState *s) +{ + /* page 55, 6.2.6, calculation of bStatus from bmICCStatus and + bmCommandStatus + */ + uint8_t ret = ccid_card_status(s) | (s->bmCommandStatus << 6); + DPRINTF(s, D_VERBOSE, "status = %d\n", ret); + return ret; +} + +static void ccid_reset_error_status(USBCCIDState* s) +{ + s->bError = ERROR_CMD_NOT_SUPPORTED; + s->bmCommandStatus = COMMAND_STATUS_NO_ERROR; +} + +static void ccid_write_slot_status(USBCCIDState* s, CCID_Header* recv) +{ + CCID_SlotStatus *h = ccid_reserve_recv_buf(s, sizeof(CCID_SlotStatus)); + if (h == NULL) { + return; + } + h->b.hdr.bMessageType = CCID_MESSAGE_TYPE_RDR_to_PC_SlotStatus; + h->b.hdr.dwLength = 0; + h->b.hdr.bSlot = recv->bSlot; + h->b.hdr.bSeq = recv->bSeq; + h->b.bStatus = ccid_calc_status(s); + h->b.bError = s->bError; + h->bClockStatus = CLOCK_STATUS_RUNNING; + ccid_reset_error_status(s); +} + +static void ccid_write_parameters(USBCCIDState* s, CCID_Header* recv) +{ + CCID_Parameter *h; + uint32_t len = s->ulProtocolDataStructureSize; + + h = ccid_reserve_recv_buf(s, sizeof(CCID_Parameter) + len); + if (h == NULL) { + return; + } + h->b.hdr.bMessageType = CCID_MESSAGE_TYPE_RDR_to_PC_Parameters; + h->b.hdr.dwLength = 0; + h->b.hdr.bSlot = recv->bSlot; + h->b.hdr.bSeq = recv->bSeq; + h->b.bStatus = ccid_calc_status(s); + h->b.bError = s->bError; + h->bProtocolNum = s->bProtocolNum; + memcpy(h->abProtocolDataStructure, s->abProtocolDataStructure, len); + ccid_reset_error_status(s); +} + +static void ccid_write_data_block( + USBCCIDState* s, uint8_t slot, uint8_t seq, + const uint8_t* data, uint32_t len) +{ + CCID_DataBlock *p = ccid_reserve_recv_buf(s, sizeof(*p) + len); + + if (p == NULL) { + return; + } + p->b.hdr.bMessageType = CCID_MESSAGE_TYPE_RDR_to_PC_DataBlock; + p->b.hdr.dwLength = cpu_to_le32(len); + p->b.hdr.bSlot = slot; + p->b.hdr.bSeq = seq; + p->b.bStatus = ccid_calc_status(s); + p->b.bError = s->bError; + if (p->b.bError) { + DPRINTF(s, D_VERBOSE, "error %d", p->b.bError); + } + memcpy(p->abData, data, len); + ccid_reset_error_status(s); +} + +static void ccid_write_data_block_answer(USBCCIDState* s, + const uint8_t* data, uint32_t len) +{ + uint8_t seq; + uint8_t slot; + + if (!ccid_has_pending_answers(s)) { + abort(); + } + ccid_remove_pending_answer(s, &slot, &seq); + ccid_write_data_block(s, slot, seq, data, len); +} + +static void ccid_write_data_block_atr(USBCCIDState* s, CCID_Header* recv) +{ + const uint8_t *atr = NULL; + uint32_t len = 0; + + if (s->card) { + atr = s->cardinfo->get_atr(s->card, &len); + } + ccid_write_data_block(s, recv->bSlot, recv->bSeq, atr, len); +} + +static void ccid_set_parameters(USBCCIDState *s, CCID_Header *recv) +{ + CCID_SetParameters *ph = (CCID_SetParameters *) recv; + uint32_t len = 0; + if ((ph->bProtocolNum & 3) == 0) { + len = 5; + } + if ((ph->bProtocolNum & 3) == 1) { + len = 7; + } + if (len == 0) { + s->bmCommandStatus = COMMAND_STATUS_FAILED; + s->bError = 7; /* Protocol invalid or not supported */ + return; + } + s->bProtocolNum = ph->bProtocolNum; + memcpy(s->abProtocolDataStructure, ph->abProtocolDataStructure, len); + s->ulProtocolDataStructureSize = len; + DPRINTF(s, 1, "%s: using len %d\n", __func__, len); +} + +/* must be 5 bytes for T=0, 7 bytes for T=1 + * See page 52 */ +static const uint8_t abDefaultProtocolDataStructure[7] = + { 0x77, 0x00, 0x00, 0x00, 0x00, 0xfe /*IFSC*/, 0x00 /*NAD*/ }; + +static void ccid_reset_parameters(USBCCIDState *s) +{ + uint32_t len = sizeof(abDefaultProtocolDataStructure); + + s->bProtocolNum = 1; /* T=1 */ + s->ulProtocolDataStructureSize = len; + memcpy(s->abProtocolDataStructure, abDefaultProtocolDataStructure, len); +} + +static void ccid_report_error_failed(USBCCIDState *s, uint8_t error) +{ + s->bmCommandStatus = COMMAND_STATUS_FAILED; + s->bError = error; +} + +/* NOTE: only a single slot is supported (SLOT_0) + */ +static void ccid_on_slot_change(USBCCIDState* s, bool full) +{ + /* RDR_to_PC_NotifySlotChange, 6.3.1 page 56 + */ + uint8_t current = s->bmSlotICCState; + if (full) { + s->bmSlotICCState |= SLOT_0_STATE_MASK; + } else { + s->bmSlotICCState &= ~SLOT_0_STATE_MASK; + } + if (current != s->bmSlotICCState) { + s->bmSlotICCState |= SLOT_0_CHANGED_MASK; + } + s->notify_slot_change = true; +} + +static void ccid_write_data_block_error( + USBCCIDState *s, uint8_t slot, uint8_t seq) +{ + ccid_write_data_block(s, slot, seq, NULL, 0); +} + +static void ccid_on_apdu_from_guest(USBCCIDState *s, CCID_XferBlock *recv) +{ + uint32_t len; + + if (ccid_card_status(s) != ICC_STATUS_PRESENT_ACTIVE) { + DPRINTF(s, 1, "usb-ccid: not sending apdu to client, no card connected\n"); + ccid_write_data_block_error(s, recv->hdr.bSlot, recv->hdr.bSeq); + return; + } + len = le32_to_cpu(recv->hdr.dwLength); + DPRINTF(s, 1, "%s: seq %d, len %d\n", __FUNCTION__, + recv->hdr.bSeq, len); + ccid_add_pending_answer(s, (CCID_Header*)recv); + if (s->card) { + s->cardinfo->apdu_from_guest(s->card, recv->abData, len); + } else { + DPRINTF(s, D_WARN, "warning: discarded apdu\n"); + } +} + +/* handle a single USB_TOKEN_OUT, return value returned to guest. + * 0 - all ok + * USB_RET_STALL - failed to handle packet */ +static int ccid_handle_bulk_out(USBCCIDState *s, USBPacket *p) +{ + CCID_Header* ccid_header; + + if (p->len + s->bulk_out_pos > BULK_OUT_DATA_SIZE) { + return USB_RET_STALL; + } + ccid_header = (CCID_Header*)s->bulk_out_data; + memcpy(s->bulk_out_data + s->bulk_out_pos, p->data, p->len); + s->bulk_out_pos += p->len; + if (p->len == CCID_MAX_PACKET_SIZE) { + DPRINTF(s, D_VERBOSE, "usb-ccid: bulk_in: expecting more packets (%d/%d)\n", + p->len, ccid_header->dwLength); + return 0; + } + if (s->bulk_out_pos < 10) { + DPRINTF(s, 1, "%s: bad USB_TOKEN_OUT length, should be at least 10 bytes\n", __func__); + } else { + DPRINTF(s, D_MORE_INFO, "%s %x\n", __func__, ccid_header->bMessageType); + switch (ccid_header->bMessageType) { + case CCID_MESSAGE_TYPE_PC_to_RDR_GetSlotStatus: + ccid_write_slot_status(s, ccid_header); + break; + case CCID_MESSAGE_TYPE_PC_to_RDR_IccPowerOn: + DPRINTF(s, 1, "PowerOn: %d\n", + ((CCID_IccPowerOn*)(ccid_header))->bPowerSelect); + s->powered = true; + if (!ccid_card_inserted(s)) { + ccid_report_error_failed(s, ERROR_ICC_MUTE); + } + /* atr is written regardless of error. */ + ccid_write_data_block_atr(s, ccid_header); + break; + case CCID_MESSAGE_TYPE_PC_to_RDR_IccPowerOff: + DPRINTF(s, 1, "PowerOff\n"); + ccid_reset_error_status(s); + s->powered = false; + ccid_write_slot_status(s, ccid_header); + break; + case CCID_MESSAGE_TYPE_PC_to_RDR_XfrBlock: + ccid_on_apdu_from_guest(s, (CCID_XferBlock*)s->bulk_out_data); + break; + case CCID_MESSAGE_TYPE_PC_to_RDR_SetParameters: + ccid_reset_error_status(s); + ccid_set_parameters(s, ccid_header); + ccid_write_parameters(s, ccid_header); + break; + case CCID_MESSAGE_TYPE_PC_to_RDR_ResetParameters: + ccid_reset_error_status(s); + ccid_reset_parameters(s); + ccid_write_parameters(s, ccid_header); + break; + case CCID_MESSAGE_TYPE_PC_to_RDR_GetParameters: + ccid_reset_error_status(s); + ccid_write_parameters(s, ccid_header); + break; + default: + DPRINTF(s, 1, "handle_data: ERROR: unhandled message type %Xh\n", + ccid_header->bMessageType); + /* the caller is expecting the device to respond, tell it we + * do't support the operation */ + ccid_report_error_failed(s, ERROR_CMD_NOT_SUPPORTED); + ccid_write_slot_status(s, ccid_header); + break; + } + } + s->bulk_out_pos = 0; + return 0; +} + +static int ccid_bulk_in_copy_to_guest(USBCCIDState *s, uint8_t *data, int len) +{ + int ret = 0; + + assert(len > 0); + ccid_bulk_in_get(s); + if (s->current_bulk_in != NULL) { + ret = MIN(s->current_bulk_in->len - s->current_bulk_in->pos, len); + memcpy(data, s->current_bulk_in->data + s->current_bulk_in->pos, ret); + s->current_bulk_in->pos += ret; + if (s->current_bulk_in->pos == s->current_bulk_in->len) { + ccid_bulk_in_release(s); + } + } else { + ret = USB_RET_NAK; /* return when device has no data - usb 2.0 spec Table 8-4 */ + } + if (ret > 0) { + DPRINTF(s, D_MORE_INFO, "%s: %d/%d req/act to guest (BULK_IN)\n", __func__, len, ret); + } + if (ret != USB_RET_NAK && ret < len) { + DPRINTF(s, 1, "%s: returning short (EREMOTEIO) %d < %d\n", __func__, ret, len); + } + return ret; +} + +static int ccid_handle_data(USBDevice *dev, USBPacket *p) +{ + USBCCIDState *s = DO_UPCAST(USBCCIDState, dev, dev); + int ret = 0; + uint8_t *data = p->data; + int len = p->len; + + switch (p->pid) { + case USB_TOKEN_OUT: + ret = ccid_handle_bulk_out(s, p); + break; + + case USB_TOKEN_IN: + switch (p->devep & 0xf) { + case CCID_BULK_IN_EP: + if (!len) { + ret = USB_RET_NAK; + } else { + ret = ccid_bulk_in_copy_to_guest(s, data, len); + } + break; + case CCID_INT_IN_EP: + if (s->notify_slot_change) { + /* page 56, RDR_to_PC_NotifySlotChange */ + data[0] = CCID_MESSAGE_TYPE_RDR_to_PC_NotifySlotChange; + data[1] = s->bmSlotICCState; + ret = 2; + s->notify_slot_change = false; + s->bmSlotICCState &= ~SLOT_0_CHANGED_MASK; + DPRINTF(s, D_INFO, "handle_data: int_in: notify_slot_change %X, requested len %d\n", + s->bmSlotICCState, len); + } + break; + default: + DPRINTF(s, 1, "Bad endpoint\n"); + break; + } + break; + default: + DPRINTF(s, 1, "Bad token\n"); + ret = USB_RET_STALL; + break; + } + + return ret; +} + +static void ccid_handle_destroy(USBDevice *dev) +{ + USBCCIDState *s = DO_UPCAST(USBCCIDState, dev, dev); + + ccid_bulk_in_clear(s); +} + +static void ccid_flush_pending_answers(USBCCIDState *s) { + while (ccid_has_pending_answers(s)) { + ccid_write_data_block_answer(s, NULL, 0); + } +} + +static Answer *ccid_peek_next_answer(USBCCIDState *s) +{ + return s->pending_answers_num == 0 + ? NULL + : &s->pending_answers[s->pending_answers_start % PENDING_ANSWERS_NUM]; +} + +static void ccid_bus_dev_print(Monitor *mon, DeviceState *qdev, int indent) +{ + CCIDCardState *card = DO_UPCAST(CCIDCardState, qdev, qdev); + CCIDCardInfo *info = DO_UPCAST(CCIDCardInfo, qdev, qdev->info); + + if (info->print) { + info->print(mon, card, indent); + } +} + +struct CCIDBus { + BusState qbus; +}; + +static struct BusInfo ccid_bus_info = { + .name = "ccid-bus", + .size = sizeof(CCIDBus), + .print_dev = ccid_bus_dev_print, + .props = (Property[]) { + DEFINE_PROP_UINT32("slot", struct CCIDCardState, slot, 0), + DEFINE_PROP_END_OF_LIST(), + } +}; + +static CCIDBus *ccid_bus_new(DeviceState *dev) +{ + CCIDBus *bus; + + bus = FROM_QBUS(CCIDBus, qbus_create(&ccid_bus_info, dev, NULL)); + bus->qbus.allow_hotplug = 1; + + return bus; +} + +void ccid_card_send_apdu_to_guest(CCIDCardState *card, uint8_t* apdu, uint32_t len) +{ + USBCCIDState *s = DO_UPCAST(USBCCIDState, dev.qdev, card->qdev.parent_bus->parent); + Answer *answer; + + if (!ccid_has_pending_answers(s)) { + DPRINTF(s, 1, "CCID ERROR: got an APDU without pending answers\n"); + return; + } + s->bmCommandStatus = COMMAND_STATUS_NO_ERROR; + answer = ccid_peek_next_answer(s); + if (answer == NULL) { + abort(); + } + DPRINTF(s, 1, "APDU returned to guest %d (answer seq %d, slot %d)\n", + len, answer->seq, answer->slot); + ccid_write_data_block_answer(s, apdu, len); +} + +void ccid_card_card_removed(CCIDCardState *card) +{ + USBCCIDState *s = DO_UPCAST(USBCCIDState, dev.qdev, card->qdev.parent_bus->parent); + + ccid_on_slot_change(s, false); + ccid_flush_pending_answers(s); + ccid_reset(s); +} + +int ccid_card_ccid_attach(CCIDCardState *card) +{ + USBCCIDState *s = DO_UPCAST(USBCCIDState, dev.qdev, card->qdev.parent_bus->parent); + + DPRINTF(s, 1, "CCID Attach\n"); + if (s->migration_state == MIGRATION_MIGRATED) { + s->migration_state = MIGRATION_NONE; + } + return 0; +} + +void ccid_card_ccid_detach(CCIDCardState *card) +{ + USBCCIDState *s = DO_UPCAST(USBCCIDState, dev.qdev, card->qdev.parent_bus->parent); + + DPRINTF(s, 1, "CCID Detach\n"); + if (ccid_card_inserted(s)) { + ccid_on_slot_change(s, false); + } + ccid_detach(s); +} + +void ccid_card_card_error(CCIDCardState *card, uint64_t error) +{ + USBCCIDState *s = DO_UPCAST(USBCCIDState, dev.qdev, card->qdev.parent_bus->parent); + + s->bmCommandStatus = COMMAND_STATUS_FAILED; + s->last_answer_error = error; + DPRINTF(s, 1, "VSC_Error: %lX\n", s->last_answer_error); + /* TODO: these error's should be more verbose and propogated to the guest. + * */ + /* we flush all pending answers on CardRemove message in ccid-card-passthru, + * so check that first to not trigger abort */ + if (ccid_has_pending_answers(s)) { + ccid_write_data_block_answer(s, NULL, 0); + } +} + +void ccid_card_card_inserted(CCIDCardState *card) +{ + USBCCIDState *s = DO_UPCAST(USBCCIDState, dev.qdev, card->qdev.parent_bus->parent); + + s->bmCommandStatus = COMMAND_STATUS_NO_ERROR; + ccid_flush_pending_answers(s); + ccid_on_slot_change(s, true); +} + +static int ccid_card_exit(DeviceState *qdev) +{ + int ret = 0; + CCIDCardState *card = DO_UPCAST(CCIDCardState, qdev, qdev); + CCIDCardInfo *info = DO_UPCAST(CCIDCardInfo, qdev, qdev->info); + USBCCIDState *s = DO_UPCAST(USBCCIDState, dev.qdev, card->qdev.parent_bus->parent); + + if (ccid_card_inserted(s)) { + ccid_card_card_removed(card); + } + if (info->exitfn) { + ret = info->exitfn(card); + } + s->card = NULL; + s->cardinfo = NULL; + return ret; +} + +static int ccid_card_init(DeviceState *qdev, DeviceInfo *base) +{ + CCIDCardState *card = DO_UPCAST(CCIDCardState, qdev, qdev); + CCIDCardInfo *info = DO_UPCAST(CCIDCardInfo, qdev, base); + USBCCIDState *s = DO_UPCAST(USBCCIDState, dev.qdev, card->qdev.parent_bus->parent); + int ret = 0; + + if (card->slot != 0) { + error_report("Warning: usb-ccid supports one slot, can't add %d", + card->slot); + return -1; + } + if (s->card != NULL) { + error_report("Warning: usb-ccid card already full, not adding\n"); + return -1; + } + ret = info->initfn ? info->initfn(card) : ret; + if (ret == 0) { + s->card = card; + s->cardinfo = info; + } + return ret; +} + +void ccid_card_qdev_register(CCIDCardInfo *card) +{ + card->qdev.bus_info = &ccid_bus_info; + card->qdev.init = ccid_card_init; + card->qdev.exit = ccid_card_exit; + qdev_register(&card->qdev); +} + +static int ccid_initfn(USBDevice *dev) +{ + USBCCIDState *s = DO_UPCAST(USBCCIDState, dev, dev); + + s->bus = ccid_bus_new(&dev->qdev); + s->card = NULL; + s->cardinfo = NULL; + s->migration_state = MIGRATION_NONE; + s->migration_target_ip = 0; + s->migration_target_port = 0; + s->dev.speed = USB_SPEED_FULL; + s->notify_slot_change = false; + s->powered = true; + s->pending_answers_num = 0; + s->last_answer_error = 0; + s->bulk_in_pending_start = 0; + s->bulk_in_pending_end = 0; + s->current_bulk_in = NULL; + ccid_reset_error_status(s); + s->bulk_out_pos = 0; + ccid_reset_parameters(s); + ccid_reset(s); + return 0; +} + +static int ccid_post_load(void *opaque, int version_id) +{ + USBCCIDState *s = opaque; + + // This must be done after usb_device_attach, which sets state to ATTACHED, + // while it must be DEFAULT in order to accept packets (like it is after + // reset, but reset will reset our addr and call our reset handler which + // may change state, and we don't want to do that when migrating). + s->dev.state = s->state_vmstate; + return 0; +} + +static void ccid_pre_save(void *opaque) +{ + USBCCIDState *s = opaque; + + s->state_vmstate = s->dev.state; + if (s->dev.attached) { + // migrating an open device, ignore reconnection CHR_EVENT to avoid an + // erronous detach. + s->migration_state = MIGRATION_MIGRATED; + } +} + +static VMStateDescription bulk_in_vmstate = { + .name = "CCID BulkIn state", + .version_id = 1, + .minimum_version_id = 1, + .fields = (VMStateField []) { + VMSTATE_BUFFER(data, BulkIn), + VMSTATE_UINT32(len, BulkIn), + VMSTATE_UINT32(pos, BulkIn), + VMSTATE_END_OF_LIST() + } +}; + +static VMStateDescription answer_vmstate = { + .name = "CCID Answer state", + .version_id = 1, + .minimum_version_id = 1, + .fields = (VMStateField []) { + VMSTATE_UINT8(slot, Answer), + VMSTATE_UINT8(seq, Answer), + VMSTATE_END_OF_LIST() + } +}; + +static VMStateDescription usb_device_vmstate = { + .name = "usb_device", + .version_id = 1, + .minimum_version_id = 1, + .fields = (VMStateField []) { + VMSTATE_UINT8(addr, USBDevice), + VMSTATE_BUFFER(setup_buf, USBDevice), + VMSTATE_BUFFER(data_buf, USBDevice), + VMSTATE_END_OF_LIST() + } +}; + +static VMStateDescription ccid_vmstate = { + .name = CCID_DEV_NAME, + .version_id = 1, + .minimum_version_id = 1, + .post_load = ccid_post_load, + .pre_save = ccid_pre_save, + .fields = (VMStateField []) { + VMSTATE_STRUCT(dev, USBCCIDState, 1, usb_device_vmstate, USBDevice), + VMSTATE_UINT8(debug, USBCCIDState), + VMSTATE_BUFFER(bulk_out_data, USBCCIDState), + VMSTATE_UINT32(bulk_out_pos, USBCCIDState), + VMSTATE_UINT8(bmSlotICCState, USBCCIDState), + VMSTATE_UINT8(powered, USBCCIDState), + VMSTATE_UINT8(notify_slot_change, USBCCIDState), + VMSTATE_UINT64(last_answer_error, USBCCIDState), + VMSTATE_UINT8(bError, USBCCIDState), + VMSTATE_UINT8(bmCommandStatus, USBCCIDState), + VMSTATE_UINT8(bProtocolNum, USBCCIDState), + VMSTATE_BUFFER(abProtocolDataStructure, USBCCIDState), + VMSTATE_UINT32(ulProtocolDataStructureSize, USBCCIDState), + VMSTATE_STRUCT_ARRAY(bulk_in_pending, USBCCIDState, + BULK_IN_PENDING_NUM, 1, bulk_in_vmstate, BulkIn), + VMSTATE_UINT32(bulk_in_pending_start, USBCCIDState), + VMSTATE_UINT32(bulk_in_pending_end, USBCCIDState), + VMSTATE_STRUCT_ARRAY(pending_answers, USBCCIDState, + PENDING_ANSWERS_NUM, 1, answer_vmstate, Answer), + VMSTATE_UINT32(pending_answers_num, USBCCIDState), + VMSTATE_UINT8(migration_state, USBCCIDState), + VMSTATE_UINT32(state_vmstate, USBCCIDState), + VMSTATE_END_OF_LIST() + } +}; + +static struct USBDeviceInfo ccid_info = { + .product_desc = "QEMU USB CCID", + .qdev.name = CCID_DEV_NAME, + .qdev.desc = "CCID Rev 1.1 smartcard reader", + .qdev.size = sizeof(USBCCIDState), + .init = ccid_initfn, + .handle_packet = usb_generic_handle_packet, + .handle_reset = ccid_handle_reset, + .handle_control = ccid_handle_control, + .handle_data = ccid_handle_data, + .handle_destroy = ccid_handle_destroy, + .usbdevice_name = "ccid", + .qdev.props = (Property[]) { + DEFINE_PROP_UINT8("debug", USBCCIDState, debug, 0), + DEFINE_PROP_END_OF_LIST(), + }, + .qdev.vmsd = &ccid_vmstate, +}; + +static void ccid_register_devices(void) +{ + usb_qdev_register(&ccid_info); +} +device_init(ccid_register_devices) -- 1.7.4 ^ permalink raw reply related [flat|nested] 35+ messages in thread
* Re: [Qemu-devel] [PATCH 1/7] usb-ccid: add CCID bus 2011-02-07 16:34 ` [Qemu-devel] [PATCH 1/7] usb-ccid: add CCID bus Alon Levy @ 2011-02-22 16:03 ` Anthony Liguori 2011-02-23 15:10 ` Alon Levy 0 siblings, 1 reply; 35+ messages in thread From: Anthony Liguori @ 2011-02-22 16:03 UTC (permalink / raw) To: Alon Levy; +Cc: qemu-devel On 02/07/2011 10:34 AM, Alon Levy wrote: > +static int ccid_post_load(void *opaque, int version_id) > +{ > + USBCCIDState *s = opaque; > + > + // This must be done after usb_device_attach, which sets state to ATTACHED, > + // while it must be DEFAULT in order to accept packets (like it is after > + // reset, but reset will reset our addr and call our reset handler which > + // may change state, and we don't want to do that when migrating). > + s->dev.state = s->state_vmstate; > + return 0; > +} > + > +static void ccid_pre_save(void *opaque) > +{ > + USBCCIDState *s = opaque; > + > + s->state_vmstate = s->dev.state; > + if (s->dev.attached) { > + // migrating an open device, ignore reconnection CHR_EVENT to avoid an > + // erronous detach. > + s->migration_state = MIGRATION_MIGRATED; > + } > Still using C99 comments, should be C89. Regards, Anthony Liguori ^ permalink raw reply [flat|nested] 35+ messages in thread
* Re: [Qemu-devel] [PATCH 1/7] usb-ccid: add CCID bus 2011-02-22 16:03 ` Anthony Liguori @ 2011-02-23 15:10 ` Alon Levy 0 siblings, 0 replies; 35+ messages in thread From: Alon Levy @ 2011-02-23 15:10 UTC (permalink / raw) To: Anthony Liguori; +Cc: qemu-devel On Tue, Feb 22, 2011 at 10:03:45AM -0600, Anthony Liguori wrote: > On 02/07/2011 10:34 AM, Alon Levy wrote: > >+static int ccid_post_load(void *opaque, int version_id) > >+{ > >+ USBCCIDState *s = opaque; > >+ > >+ // This must be done after usb_device_attach, which sets state to ATTACHED, > >+ // while it must be DEFAULT in order to accept packets (like it is after > >+ // reset, but reset will reset our addr and call our reset handler which > >+ // may change state, and we don't want to do that when migrating). > >+ s->dev.state = s->state_vmstate; > >+ return 0; > >+} > >+ > >+static void ccid_pre_save(void *opaque) > >+{ > >+ USBCCIDState *s = opaque; > >+ > >+ s->state_vmstate = s->dev.state; > >+ if (s->dev.attached) { > >+ // migrating an open device, ignore reconnection CHR_EVENT to avoid an > >+ // erronous detach. > >+ s->migration_state = MIGRATION_MIGRATED; > >+ } > > > Still using C99 comments, should be C89. Fixed (by checkpatch.pl iteration) in v20. > > Regards, > > Anthony Liguori > ^ permalink raw reply [flat|nested] 35+ messages in thread
* [Qemu-devel] [PATCH 0/7] usb-ccid (v14) @ 2011-01-11 8:38 Alon Levy 2011-01-11 8:38 ` [Qemu-devel] [PATCH 1/7] usb-ccid: add CCID bus Alon Levy 0 siblings, 1 reply; 35+ messages in thread From: Alon Levy @ 2011-01-11 8:38 UTC (permalink / raw) To: qemu-devel This patchset adds three new devices, usb-ccid, ccid-card-passthru and ccid-card-emulated, providing a CCID bus, a simple passthru protocol implementing card requiring a client, and a standalone emulated card. It also introduces a new directory libcaccard with CAC card emulation, CAC is a type of ISO 7816 smart card. Tree for pull: git://anongit.freedesktop.org/~alon/qemu usb_ccid.v14 v14-v15 changes: * add patch with --enable-smartcard and --disable-smartcard and only disable ccid-card-emulated if nss not found. * add patch with description strings * s/libcaccard/libcacard/ in docs/ccid.txt v13-v14 changes: - support device_del/device_add on ccid-card-* and usb-ccid * usb-ccid: * lose card reference when card device deleted * check slot number and deny adding a slot if one is already added. * ccid-card-*: use qdev_simple_unplug_cb in both emulated and passthru ccid cards, the exitfn already takes care of triggering card removal in the usb dev. * libcacard: * remove double include of config-host.mak * add replay of card events to libcacard to support second and more emulation * don't initialize more then once (doesn't support it right now, so one thread, NSS thread, is left when device_del is done) * add VCARD_EMUL_INIT_ALREADY_INITED * ccid-card-emulated: * take correct mutexes on signaling to fix deadlocks on device_del * allow card insertion/removal event without proper reader insertion event v12-v13 changes: * libcacard: * fix Makefile clean to remove vscclient * fix double include of config-host in Makefile * usb-ccid: remove attach/detach logic, usb is always attached. Guest doesn't care if there is a reader attached with no card anyway. * ccid-card-passthru: don't close chr_dev on removal, makes it possible to use device_del/device_add to create remove/insertion for debugging. v11-v12 changes: * fix out of tree build v10-v11 changes: * fix last patch that removed one of the doc files. * updated flow table in docs/ccid.txt v8-v10 changes: * usb-ccid: * add slot for future use (Gerd) * ifdef ENABLE_MIGRATION for migration support on account of usb migration not being ready in general. (Gerd) * verbosified commit messages. (Gerd) * put libcacard docs in libcacard commit. (Gerd) v8-v9 changes: * Blue Swirl comments: * white space fixes * enabled by default, disabled only if missing nss * forgotten fix from v8 (don't build libcacard.so) * added a note about device being little endian * library renamed from libcaccard to libcacard * squashed both of libcacard patches, they touched different files anyway. v7-v8 changes: * Blue Swirl comments: * usb-ccid: deannonymize some structs * usb-ccid: coding style change - answer_t and bulk_in_t fixed * usb-ccid: handle endianess conversion between guest and host * usb-ccid: s/ccid_bulk_in_copy_out/ccid_bulk_in_copy_to_guest/ * ccid-card-emulated: fix segfault if backend not specified * ccid-card-emulated: let last reader inserted win * libcaccard: remove double vscard_common.h v6->v7 changes: * external libcaccard became internal directory libcaccard * statically link object files into qemu * produce libcaccard.so for usage by external projects * applied coding style to new code (please check me) - did not use the qemu options parsing for libcaccard, since it seems to draw large amounts of qemu code (monitor for instance). v5->v6 changes: * really remove static debug (I apologize for claiming to have done so before) v4->v5 changes: * rebased to latest * remove static debug in card devices * fix --enable-smartcard to link * stall instead of assert when exceeding BULK_OUT_DATA_SIZE * make ccid_reserve_recv_buf for too large len discard message, not exit * make ccid_reserve_recv_buf return void* * fix typo * remove commented code in VMState v3->v4: * remove ccid field in CCIDBus * remove static debug in bus * add back docs v2->v3: * split into bus (usb-ccid.c, uses ccid.h) and card (ccid-card-passthru.c). * removed documentation (being revised). v1->v2: * all QSIMPLEQ turned into fixed sized rings * all allocated buffers turned into fixed size buffers * added migration support * added a message to tell client qemu has migrated to ip:port * for lack of monitor commands ip:port are 0:0, which causes the updated vscclient to connect to one port higher on the same host. will add monitor commands in a separate patch. tested with current setup. Alon Levy (6): usb-ccid: add CCID bus ccid: add passthru card device ccid: add ccid-card-emulated device (v2) ccid: add docs ccid: configure: add --enable/disable and nss only disable ccid: add qdev description strings Robert Relyea (1): libcacard: initial commit after coding style fixes Makefile | 6 +- Makefile.objs | 7 + Makefile.target | 2 + configure | 48 ++ docs/ccid.txt | 135 +++++ docs/libcacard.txt | 483 +++++++++++++++ hw/ccid-card-emulated.c | 535 +++++++++++++++++ hw/ccid-card-passthru.c | 273 +++++++++ hw/ccid.h | 35 ++ hw/usb-ccid.c | 1356 +++++++++++++++++++++++++++++++++++++++++++ libcacard/Makefile | 14 + libcacard/cac.c | 411 +++++++++++++ libcacard/cac.h | 20 + libcacard/card_7816.c | 780 +++++++++++++++++++++++++ libcacard/card_7816.h | 60 ++ libcacard/card_7816t.h | 163 ++++++ libcacard/config.h | 81 +++ libcacard/event.c | 112 ++++ libcacard/eventt.h | 28 + libcacard/link_test.c | 20 + libcacard/mutex.h | 59 ++ libcacard/passthru.c | 612 +++++++++++++++++++ libcacard/passthru.h | 50 ++ libcacard/vcard.c | 350 +++++++++++ libcacard/vcard.h | 85 +++ libcacard/vcard_emul.h | 62 ++ libcacard/vcard_emul_nss.c | 1171 +++++++++++++++++++++++++++++++++++++ libcacard/vcard_emul_type.c | 60 ++ libcacard/vcard_emul_type.h | 29 + libcacard/vcardt.h | 66 +++ libcacard/vevent.h | 26 + libcacard/vreader.c | 526 +++++++++++++++++ libcacard/vreader.h | 54 ++ libcacard/vreadert.h | 23 + libcacard/vscard_common.h | 130 ++++ libcacard/vscclient.c | 710 ++++++++++++++++++++++ 36 files changed, 8580 insertions(+), 2 deletions(-) create mode 100644 docs/ccid.txt create mode 100644 docs/libcacard.txt create mode 100644 hw/ccid-card-emulated.c create mode 100644 hw/ccid-card-passthru.c create mode 100644 hw/ccid.h create mode 100644 hw/usb-ccid.c create mode 100644 libcacard/Makefile create mode 100644 libcacard/cac.c create mode 100644 libcacard/cac.h create mode 100644 libcacard/card_7816.c create mode 100644 libcacard/card_7816.h create mode 100644 libcacard/card_7816t.h create mode 100644 libcacard/config.h create mode 100644 libcacard/event.c create mode 100644 libcacard/eventt.h create mode 100644 libcacard/link_test.c create mode 100644 libcacard/mutex.h create mode 100644 libcacard/passthru.c create mode 100644 libcacard/passthru.h create mode 100644 libcacard/vcard.c create mode 100644 libcacard/vcard.h create mode 100644 libcacard/vcard_emul.h create mode 100644 libcacard/vcard_emul_nss.c create mode 100644 libcacard/vcard_emul_type.c create mode 100644 libcacard/vcard_emul_type.h create mode 100644 libcacard/vcardt.h create mode 100644 libcacard/vevent.h create mode 100644 libcacard/vreader.c create mode 100644 libcacard/vreader.h create mode 100644 libcacard/vreadert.h create mode 100644 libcacard/vscard_common.h create mode 100644 libcacard/vscclient.c -- 1.7.3.4 ^ permalink raw reply [flat|nested] 35+ messages in thread
* [Qemu-devel] [PATCH 1/7] usb-ccid: add CCID bus 2011-01-11 8:38 [Qemu-devel] [PATCH 0/7] usb-ccid (v14) Alon Levy @ 2011-01-11 8:38 ` Alon Levy 2011-01-25 14:10 ` Anthony Liguori 0 siblings, 1 reply; 35+ messages in thread From: Alon Levy @ 2011-01-11 8:38 UTC (permalink / raw) To: qemu-devel A CCID device is a smart card reader. It is a USB device, defined at [1]. This patch introduces the usb-ccid device that is a ccid bus. Next patches will introduce two card types to use it, a passthru card and an emulated card. [1] http://www.usb.org/developers/devclass_docs/DWG_Smart-Card_CCID_Rev110. Signed-off-by: Alon Levy <alevy@redhat.com> --- Makefile.objs | 1 + configure | 6 + hw/ccid.h | 35 ++ hw/usb-ccid.c | 1355 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 1397 insertions(+), 0 deletions(-) create mode 100644 hw/ccid.h create mode 100644 hw/usb-ccid.c diff --git a/Makefile.objs b/Makefile.objs index d6b3d60..7da4771 100644 --- a/Makefile.objs +++ b/Makefile.objs @@ -197,6 +197,7 @@ hw-obj-$(CONFIG_FDC) += fdc.o hw-obj-$(CONFIG_ACPI) += acpi.o acpi_piix4.o hw-obj-$(CONFIG_APM) += pm_smbus.o apm.o hw-obj-$(CONFIG_DMA) += dma.o +hw-obj-$(CONFIG_SMARTCARD) += usb-ccid.o # PPC devices hw-obj-$(CONFIG_OPENPIC) += openpic.o diff --git a/configure b/configure index 831a741..839980c 100755 --- a/configure +++ b/configure @@ -334,6 +334,7 @@ trace_backend="nop" trace_file="trace" spice="" rbd="" +smartcard="yes" # OS specific if check_define __linux__ ; then @@ -2441,6 +2442,7 @@ echo "Trace output file $trace_file-<pid>" echo "spice support $spice" echo "rbd support $rbd" echo "xfsctl support $xfs" +echo "smartcard support $smartcard" if test $sdl_too_old = "yes"; then echo "-> Your SDL version is too old - please upgrade to have SDL support" @@ -2710,6 +2712,10 @@ if test "$spice" = "yes" ; then echo "CONFIG_SPICE=y" >> $config_host_mak fi +if test "$smartcard" = "yes" ; then + echo "CONFIG_SMARTCARD=y" >> $config_host_mak +fi + # XXX: suppress that if [ "$bsd" = "yes" ] ; then echo "CONFIG_BSD=y" >> $config_host_mak diff --git a/hw/ccid.h b/hw/ccid.h new file mode 100644 index 0000000..af59070 --- /dev/null +++ b/hw/ccid.h @@ -0,0 +1,35 @@ +#ifndef __CCID_H__ +#define __CCID_H__ + +#include "qdev.h" + +typedef struct CCIDCardState CCIDCardState; +typedef struct CCIDCardInfo CCIDCardInfo; + +struct CCIDCardState { + DeviceState qdev; + uint32_t slot; // For future use with multiple slot reader. +}; + +struct CCIDCardInfo { + DeviceInfo qdev; + void (*print)(Monitor *mon, CCIDCardState *card, int indent); + const uint8_t *(*get_atr)(CCIDCardState *card, uint32_t *len); + void (*apdu_from_guest)(CCIDCardState *card, const uint8_t *apdu, uint32_t len); + int (*exitfn)(CCIDCardState *card); + int (*initfn)(CCIDCardState *card); +}; + +void ccid_card_send_apdu_to_guest(CCIDCardState *card, uint8_t* apdu, uint32_t len); +void ccid_card_card_removed(CCIDCardState *card); +void ccid_card_card_inserted(CCIDCardState *card); +void ccid_card_card_error(CCIDCardState *card, uint64_t error); +void ccid_card_qdev_register(CCIDCardInfo *card); + +/* support guest visible insertion/removal of ccid devices based on actual + * devices connected/removed. Called by card implementation (passthru, local) */ +int ccid_card_ccid_attach(CCIDCardState *card); +void ccid_card_ccid_detach(CCIDCardState *card); + +#endif // __CCID_H__ + diff --git a/hw/usb-ccid.c b/hw/usb-ccid.c new file mode 100644 index 0000000..58f69a6 --- /dev/null +++ b/hw/usb-ccid.c @@ -0,0 +1,1355 @@ +/* + * CCID Device emulation + * + * Based on usb-serial.c: + * Copyright (c) 2006 CodeSourcery. + * Copyright (c) 2008 Samuel Thibault <samuel.thibault@ens-lyon.org> + * Written by Paul Brook, reused for FTDI by Samuel Thibault, + * Reused for CCID by Alon Levy. + * Contributed to by Robert Relyea + * Copyright (c) 2010 Red Hat. + * + * This code is licenced under the LGPL. + */ + +/* References: + * + * CCID Specification Revision 1.1 April 22nd 2005 + * "Universal Serial Bus, Device Class: Smart Card" + * Specification for Integrated Circuit(s) Cards Interface Devices + * + * Endianess note: from the spec (1.3) + * "Fields that are larger than a byte are stored in little endian + * + * KNOWN BUGS + * 1. remove/insert can sometimes result in removed state instead of inserted. + * This is a result of the following: + * symptom: dmesg shows ERMOTEIO (-121), pcscd shows -99. This happens + * when we send a too short packet, seen in uhci-usb.c, resulting from + * a urb requesting SPD and us returning a smaller packet. + * Not sure which messages trigger this. + * + * Migration note: + * + * All the VMStateDescription's are left here for future use, but + * not enabled right now since there is no support for USB migration. + * + * To enable define ENABLE_MIGRATION + */ + +#include "qemu-common.h" +#include "qemu-error.h" +#include "usb.h" +#include "monitor.h" + +#include "hw/ccid.h" + +//#define DEBUG_CCID + +#define DPRINTF(s, lvl, fmt, ...) \ +do { if (lvl <= s->debug) { printf("usb-ccid: " fmt , ## __VA_ARGS__); } } while (0) + +#define CCID_DEV_NAME "usb-ccid" + +/* The two options for variable sized buffers: + * make them constant size, for large enough constant, + * or handle the migration complexity - VMState doesn't handle this case. + * sizes are expected never to be exceeded, unless guest misbehaves. */ +#define BULK_OUT_DATA_SIZE 65536 +#define PENDING_ANSWERS_NUM 128 + +#define BULK_IN_BUF_SIZE 384 +#define BULK_IN_PENDING_NUM 8 + +#define InterfaceOutClass ((USB_DIR_OUT|USB_TYPE_CLASS|USB_RECIP_INTERFACE)<<8) +#define InterfaceInClass ((USB_DIR_IN |USB_TYPE_CLASS|USB_RECIP_INTERFACE)<<8) + +#define CCID_CONTROL_ABORT 0x1 +#define CCID_CONTROL_GET_CLOCK_FREQUENCIES 0x2 +#define CCID_CONTROL_GET_DATA_RATES 0x3 + +#define CCID_PRODUCT_DESCRIPTION "QEMU USB CCID" +#define CCID_VENDOR_DESCRIPTION "QEMU " QEMU_VERSION +#define CCID_INTERFACE_NAME "CCID Interface" +#define CCID_SERIAL_NUMBER_STRING "1" +/* Using Gemplus Vendor and Product id + Effect on various drivers: + * usbccid.sys (winxp, others untested) is a class driver so it doesn't care. + * linux has a number of class drivers, but openct filters based on + vendor/product (/etc/openct.conf under fedora), hence Gemplus. + */ +#define CCID_VENDOR_ID 0x08e6 +#define CCID_PRODUCT_ID 0x4433 +#define CCID_DEVICE_VERSION 0x0000 + +/* BULK_OUT messages from PC to Reader + Defined in CCID Rev 1.1 6.1 (page 26) + */ +#define CCID_MESSAGE_TYPE_PC_to_RDR_IccPowerOn 0x62 +#define CCID_MESSAGE_TYPE_PC_to_RDR_IccPowerOff 0x63 +#define CCID_MESSAGE_TYPE_PC_to_RDR_GetSlotStatus 0x65 +#define CCID_MESSAGE_TYPE_PC_to_RDR_XfrBlock 0x6f +#define CCID_MESSAGE_TYPE_PC_to_RDR_GetParameters 0x6c +#define CCID_MESSAGE_TYPE_PC_to_RDR_ResetParameters 0x6d +#define CCID_MESSAGE_TYPE_PC_to_RDR_SetParameters 0x61 +#define CCID_MESSAGE_TYPE_PC_to_RDR_Escape 0x6b +#define CCID_MESSAGE_TYPE_PC_to_RDR_IccClock 0x6e +#define CCID_MESSAGE_TYPE_PC_to_RDR_T0APDU 0x6a +#define CCID_MESSAGE_TYPE_PC_to_RDR_Secure 0x69 +#define CCID_MESSAGE_TYPE_PC_to_RDR_Mechanical 0x71 +#define CCID_MESSAGE_TYPE_PC_to_RDR_Abort 0x72 +#define CCID_MESSAGE_TYPE_PC_to_RDR_SetDataRateAndClockFrequency 0x73 + +/* BULK_IN messages from Reader to PC + Defined in CCID Rev 1.1 6.2 (page 48) + */ +#define CCID_MESSAGE_TYPE_RDR_to_PC_DataBlock 0x80 +#define CCID_MESSAGE_TYPE_RDR_to_PC_SlotStatus 0x81 +#define CCID_MESSAGE_TYPE_RDR_to_PC_Parameters 0x82 +#define CCID_MESSAGE_TYPE_RDR_to_PC_Escape 0x83 +#define CCID_MESSAGE_TYPE_RDR_to_PC_DataRateAndClockFrequency 0x84 + +/* INTERRUPT_IN messages from Reader to PC + Defined in CCID Rev 1.1 6.3 (page 56) + */ +#define CCID_MESSAGE_TYPE_RDR_to_PC_NotifySlotChange 0x50 +#define CCID_MESSAGE_TYPE_RDR_to_PC_HardwareError 0x51 + +/* Endpoints for CCID - addresses are up to us to decide. + To support slot insertion and removal we must have an interrupt in ep + in addition we need a bulk in and bulk out ep + 5.2, page 20 + */ +#define CCID_INT_IN_EP 1 +#define CCID_BULK_IN_EP 2 +#define CCID_BULK_OUT_EP 3 + +/* bmSlotICCState masks */ +#define SLOT_0_STATE_MASK 1 +#define SLOT_0_CHANGED_MASK 2 + +/* Status codes that go in bStatus (see 6.2.6) */ +enum { + ICC_STATUS_PRESENT_ACTIVE = 0, + ICC_STATUS_PRESENT_INACTIVE, + ICC_STATUS_NOT_PRESENT +}; + +enum { + COMMAND_STATUS_NO_ERROR = 0, + COMMAND_STATUS_FAILED, + COMMAND_STATUS_TIME_EXTENSION_REQUIRED +}; + +/* Error codes that go in bError (see 6.2.6) + */ +enum { + ERROR_CMD_NOT_SUPPORTED = 0, + ERROR_CMD_ABORTED = -1, + ERROR_ICC_MUTE = -2, + ERROR_XFR_PARITY_ERROR = -3, + ERROR_XFR_OVERRUN = -4, + ERROR_HW_ERROR = -5, +}; + +/* 6.2.6 RDR_to_PC_SlotStatus definitions */ +enum { + CLOCK_STATUS_RUNNING = 0, + /* 0 - Clock Running, 1 - Clock stopped in State L, 2 - H, + 3 - unkonwn state. rest are RFU + */ +}; + +typedef struct __attribute__ ((__packed__)) CCID_Header { + uint8_t bMessageType; + uint32_t dwLength; + uint8_t bSlot; + uint8_t bSeq; +} CCID_Header; + +typedef struct __attribute__ ((__packed__)) CCID_BULK_IN { + CCID_Header hdr; + uint8_t bStatus; /* Only used in BULK_IN */ + uint8_t bError; /* Only used in BULK_IN */ +} CCID_BULK_IN; + +typedef struct __attribute__ ((__packed__)) CCID_SlotStatus { + CCID_BULK_IN b; + uint8_t bClockStatus; +} CCID_SlotStatus; + +typedef struct __attribute__ ((__packed__)) CCID_Parameter { + CCID_BULK_IN b; + uint8_t bProtocolNum; + uint8_t abProtocolDataStructure[0]; +} CCID_Parameter; + +typedef struct __attribute__ ((__packed__)) CCID_DataBlock { + CCID_BULK_IN b; + uint8_t bChainParameter; + uint8_t abData[0]; +} CCID_DataBlock; + +/* 6.1.4 PC_to_RDR_XfrBlock */ +typedef struct __attribute__ ((__packed__)) CCID_XferBlock { + CCID_Header hdr; + uint8_t bBWI; /* Block Waiting Timeout */ + uint16_t wLevelParameter; /* XXX currently unused */ + uint8_t abData[0]; +} CCID_XferBlock; + +typedef struct __attribute__ ((__packed__)) CCID_IccPowerOn { + CCID_Header hdr; + uint8_t bPowerSelect; + uint16_t abRFU; +} CCID_IccPowerOn; + +typedef struct __attribute__ ((__packed__)) CCID_IccPowerOff { + CCID_Header hdr; + uint16_t abRFU; +} CCID_IccPowerOff; + +typedef struct __attribute__ ((__packed__)) CCID_SetParameter { + CCID_Header hdr; + uint8_t bProtocolNum; + uint8_t abProtocolDataStructure[0]; +} CCID_SetParameter; + +typedef struct CCID_Notify_Slot_Change { + uint8_t bMessageType; /* CCID_MESSAGE_TYPE_RDR_to_PC_NotifySlotChange */ + uint8_t bmSlotICCState; +} CCID_Notify_Slot_Change; + +/* used for DataBlock response to XferBlock */ +typedef struct Answer { + uint8_t slot; + uint8_t seq; +} Answer; + +/* pending BULK_IN messages */ +typedef struct BulkIn { + uint8_t data[BULK_IN_BUF_SIZE]; + uint32_t len; + uint32_t pos; +} BulkIn; + +enum { + MIGRATION_NONE, + MIGRATION_MIGRATED, +}; + +typedef struct CCIDBus CCIDBus; +typedef struct USBCCIDState USBCCIDState; + +#define MAX_PROTOCOL_SIZE 7 + +/** + * powered - defaults to true, changed by PowerOn/PowerOff messages + */ +struct USBCCIDState { + USBDevice dev; + CCIDBus *bus; + CCIDCardState *card; + CCIDCardInfo *cardinfo; /* caching the info pointer */ + uint8_t debug; + BulkIn bulk_in_pending[BULK_IN_PENDING_NUM]; /* circular */ + uint32_t bulk_in_pending_start; + uint32_t bulk_in_pending_end; /* first free */ + uint32_t bulk_in_pending_num; + BulkIn *current_bulk_in; + uint8_t bulk_out_data[BULK_OUT_DATA_SIZE]; + uint32_t bulk_out_pos; + uint8_t bmSlotICCState; + uint8_t powered; + uint8_t notify_slot_change; + uint64_t last_answer_error; + Answer pending_answers[PENDING_ANSWERS_NUM]; + uint32_t pending_answers_start; + uint32_t pending_answers_end; + uint32_t pending_answers_num; + uint8_t bError; + uint8_t bmCommandStatus; + uint8_t bProtocolNum; + uint8_t abProtocolDataStructure[MAX_PROTOCOL_SIZE]; + uint32_t ulProtocolDataStructureSize; + uint32_t state_vmstate; + uint8_t migration_state; + uint32_t migration_target_ip; + uint16_t migration_target_port; +}; + +/* Slot specific variables. We emulate a single slot card reader. + */ + + +/* CCID Spec chapter 4: CCID uses a standard device descriptor per Chapter 9, + * "USB Device Framework", section 9.6.1, in the Universal Serial Bus + * Specification. + * + * This device implemented based on the spec and with an Athena Smart Card + * Reader as reference: + * 0dc3:1004 Athena Smartcard Solutions, Inc. + */ + +static const uint8_t qemu_ccid_dev_descriptor[] = { + 0x12, /* u8 bLength; */ + USB_DT_DEVICE, /* u8 bDescriptorType; Device */ + 0x10, 0x01, /* u16 bcdUSB; v1.1 */ + + 0x00, /* u8 bDeviceClass; */ + 0x00, /* u8 bDeviceSubClass; */ + 0x00, /* u8 bDeviceProtocol; [ low/full speeds only ] */ + 0x40, /* u8 bMaxPacketSize0; 8 Bytes (valid: 8,16,32,64) */ + + /* Vendor and product id are arbitrary. */ + /* u16 idVendor */ + CCID_VENDOR_ID & 0xff, CCID_VENDOR_ID >> 8, + /* u16 idProduct */ + CCID_PRODUCT_ID & 0xff, CCID_PRODUCT_ID >> 8, + /* u16 bcdDevice */ + CCID_DEVICE_VERSION & 0xff, CCID_DEVICE_VERSION >> 8, + 0x01, /* u8 iManufacturer; */ + 0x02, /* u8 iProduct; */ + 0x03, /* u8 iSerialNumber; */ + 0x01, /* u8 bNumConfigurations; */ +}; + +static const uint8_t qemu_ccid_config_descriptor[] = { + + /* one configuration */ + 0x09, /* u8 bLength; */ + USB_DT_CONFIG, /* u8 bDescriptorType; Configuration */ + 0x5d, 0x00, /* u16 wTotalLength; 9+9+54+7+7+7 */ + 0x01, /* u8 bNumInterfaces; (1) */ + 0x01, /* u8 bConfigurationValue; */ + 0x00, /* u8 iConfiguration; */ + 0xe0, /* u8 bmAttributes; + Bit 7: must be set, + 6: Self-powered, + 5: Remote wakeup, + 4..0: resvd */ + 100/2, /* u8 MaxPower; 50 == 100mA */ + + /* one interface */ + 0x09, /* u8 if_bLength; */ + USB_DT_INTERFACE, /* u8 if_bDescriptorType; Interface */ + 0x00, /* u8 if_bInterfaceNumber; */ + 0x00, /* u8 if_bAlternateSetting; */ + 0x03, /* u8 if_bNumEndpoints; */ + 0x0b, /* u8 if_bInterfaceClass; Smart Card Device Class */ + 0x00, /* u8 if_bInterfaceSubClass; Subclass code */ + 0x00, /* u8 if_bInterfaceProtocol; Protocol code */ + 0x04, /* u8 if_iInterface; Index of string descriptor */ + + /* Smart Card Device Class Descriptor */ + 0x36, /* u8 bLength; */ + 0x21, /* u8 bDescriptorType; Functional */ + 0x10, 0x01, /* u16 bcdCCID; CCID Specification Release Number. */ + 0x00, /* u8 bMaxSlotIndex; The index of the highest available + slot on this device. All slots are consecutive starting + at 00h. */ + 0x07, /* u8 bVoltageSupport; 01h - 5.0v, 02h - 3.0, 03 - 1.8 */ + + 0x03, 0x00, /* u32 dwProtocols; RRRR PPPP. RRRR = 0000h.*/ + 0x00, 0x00, /* PPPP: 0001h = Protocol T=0, 0002h = Protocol T=1 */ + /* u32 dwDefaultClock; in kHZ (0x0fa0 is 4 MHz) */ + 0xa0, 0x0f, 0x00, 0x00, + /* u32 dwMaximumClock; */ + 0x00, 0x00, 0x01, 0x00, + 0x00, /* u8 bNumClockSupported; 0 means just the default and max. */ + /* u32 dwDataRate ;bps. 9600 == 00002580h */ + 0x80, 0x25, 0x00, 0x00, + /* u32 dwMaxDataRate ; 11520 bps == 0001C200h */ + 0x00, 0xC2, 0x01, 0x00, + 0x00, /* u8 bNumDataRatesSupported; 00 means all rates between + * default and max */ + /* u32 dwMaxIFSD; maximum IFSD supported by CCID for protocol + * T=1 (Maximum seen from various cards) */ + 0xfe, 0x00, 0x00, 0x00, + /* u32 dwSyncProtocols; 1 - 2-wire, 2 - 3-wire, 4 - I2C */ + 0x00, 0x00, 0x00, 0x00, + /* u32 dwMechanical; 0 - no special characteristics. */ + 0x00, 0x00, 0x00, 0x00, + /* u32 dwFeatures; + * 0 - No special characteristics + * + 2 Automatic parameter configuration based on ATR data + * + 4 Automatic activation of ICC on inserting + * + 8 Automatic ICC voltage selection + * + 10 Automatic ICC clock frequency change + * + 20 Automatic baud rate change + * + 40 Automatic parameters negotiation made by the CCID + * + 80 automatic PPS made by the CCID + * 100 CCID can set ICC in clock stop mode + * 200 NAD value other then 00 accepted (T=1 protocol) + * + 400 Automatic IFSD exchange as first exchange (T=1) + * One of the following only: + * + 10000 TPDU level exchanges with CCID + * 20000 Short APDU level exchange with CCID + * 40000 Short and Extended APDU level exchange with CCID + * + * + 100000 USB Wake up signaling supported on card insertion + * and removal. Must set bit 5 in bmAttributes in Configuration + * descriptor if 100000 is set.*/ + 0xfe, 0x04, 0x11, 0x00, + /* u32 dwMaxCCIDMessageLength; For extended APDU in [261 + 10 + * , 65544 + 10]. Otherwise the minimum is wMaxPacketSize of + * the Bulk-OUT endpoint */ + 0x12, 0x00, 0x01, 0x00, + 0xFF, /* u8 bClassGetResponse; Significant only for CCID that + * offers an APDU level for exchanges. Indicates the default + * class value used by the CCID when it sends a Get Response + * command to perform the transportation of an APDU by T=0 + * protocol + * FFh indicates that the CCID echos the class of the APDU. + */ + 0xFF, /* u8 bClassEnvelope; EAPDU only. Envelope command for T=0 */ + 0x00, 0x00, /* u16 wLcdLayout; XXYY Number of lines (XX) and chars per + * line for LCD display used for PIN entry. 0000 - no LCD */ + 0x01, /* u8 bPINSupport; 01h PIN Verification, + * 02h PIN Modification */ + 0x01, /* u8 bMaxCCIDBusySlots; */ + + /* Interrupt-IN endpoint */ + 0x07, /* u8 ep_bLength; */ + /* u8 ep_bDescriptorType; Endpoint */ + USB_DT_ENDPOINT, + /* u8 ep_bEndpointAddress; IN Endpoint 1 */ + 0x80 | CCID_INT_IN_EP, + 0x03, /* u8 ep_bmAttributes; Interrupt */ + 0x40, 0x00, /* u16 ep_wMaxPacketSize; */ + 0xff, /* u8 ep_bInterval; */ + + /* Bulk-In endpoint */ + 0x07, /* u8 ep_bLength; */ + /* u8 ep_bDescriptorType; Endpoint */ + USB_DT_ENDPOINT, + /* u8 ep_bEndpointAddress; IN Endpoint 2 */ + 0x80 | CCID_BULK_IN_EP, + 0x02, /* u8 ep_bmAttributes; Bulk */ + 0x40, 0x00, /* u16 ep_wMaxPacketSize; */ + 0x00, /* u8 ep_bInterval; */ + + /* Bulk-Out endpoint */ + 0x07, /* u8 ep_bLength; */ + /* u8 ep_bDescriptorType; Endpoint */ + USB_DT_ENDPOINT, + /* u8 ep_bEndpointAddress; OUT Endpoint 3 */ + CCID_BULK_OUT_EP, + 0x02, /* u8 ep_bmAttributes; Bulk */ + 0x40, 0x00, /* u16 ep_wMaxPacketSize; */ + 0x00, /* u8 ep_bInterval; */ + +}; + +static bool ccid_has_pending_answers(USBCCIDState *s) +{ + return s->pending_answers_num > 0; +} + +static void ccid_clear_pending_answers(USBCCIDState *s) +{ + s->pending_answers_num = 0; + s->pending_answers_start = 0; + s->pending_answers_end = 0; +} + +static void ccid_print_pending_answers(USBCCIDState *s) +{ +#ifdef DEBUG_CCID + Answer *answer; + int i, count; + + printf("usb-ccid: pending answers:"); + if (!ccid_has_pending_answers(s)) { + printf(" empty\n"); + return; + } + for (i = s->pending_answers_start, count=s->pending_answers_num ; + count > 0; count--, i++) { + answer = &s->pending_answers[i % PENDING_ANSWERS_NUM]; + if (count == 1) { + printf("%d:%d\n", answer->slot, answer->seq); + } else { + printf("%d:%d,", answer->slot, answer->seq); + } + } +#endif +} + +static void ccid_add_pending_answer(USBCCIDState *s, CCID_Header *hdr) +{ + Answer* answer; + + assert(s->pending_answers_num++ < PENDING_ANSWERS_NUM); + answer = &s->pending_answers[(s->pending_answers_end++) % PENDING_ANSWERS_NUM]; + answer->slot = hdr->bSlot; + answer->seq = hdr->bSeq; + ccid_print_pending_answers(s); +} + +static void ccid_remove_pending_answer(USBCCIDState *s, + uint8_t *slot, uint8_t *seq) +{ + Answer *answer; + + assert(s->pending_answers_num-- > 0); + answer = &s->pending_answers[(s->pending_answers_start++) % PENDING_ANSWERS_NUM]; + *slot = answer->slot; + *seq = answer->seq; + ccid_print_pending_answers(s); +} + +static void ccid_bulk_in_clear(USBCCIDState *s) +{ + s->bulk_in_pending_start = 0; + s->bulk_in_pending_end = 0; + s->bulk_in_pending_num = 0; +} + +static void ccid_bulk_in_release(USBCCIDState *s) +{ + assert(s->current_bulk_in != NULL); + s->current_bulk_in->pos = 0; + s->current_bulk_in = NULL; +} + +static void ccid_bulk_in_get(USBCCIDState *s) +{ + if (s->current_bulk_in != NULL || s->bulk_in_pending_num == 0) { + return; + } + assert(s->bulk_in_pending_num > 0); + s->bulk_in_pending_num--; + s->current_bulk_in = &s->bulk_in_pending[ + (s->bulk_in_pending_start++) % BULK_IN_PENDING_NUM]; +} + +static void* ccid_reserve_recv_buf(USBCCIDState* s, uint16_t len) +{ + BulkIn* bulk_in; + + DPRINTF(s, 4, "%s: QUEUE: reserve %d bytes\n", __func__, len); + + /* look for an existing element */ + if (len > BULK_IN_BUF_SIZE) { + printf("usb-ccid.c: %s: len larger then max (%d>%d). discarding message.\n", + __func__, len, BULK_IN_BUF_SIZE); + return NULL; + } + if (s->bulk_in_pending_num >= BULK_IN_PENDING_NUM) { + printf("usb-ccid.c: %s: No free bulk_in buffers. discarding message.\n", + __func__); + return NULL; + } + bulk_in = &s->bulk_in_pending[(s->bulk_in_pending_end++) % BULK_IN_PENDING_NUM]; + s->bulk_in_pending_num++; + bulk_in->len = len; + return bulk_in->data; +} + +static void ccid_reset(USBCCIDState *s) +{ + ccid_bulk_in_clear(s); + ccid_clear_pending_answers(s); +} + +static void ccid_detach(USBCCIDState *s) +{ + ccid_reset(s); +} + +static void ccid_handle_reset(USBDevice *dev) +{ + USBCCIDState *s = DO_UPCAST(USBCCIDState, dev, dev); + + DPRINTF(s, 1, "Reset\n"); + + ccid_reset(s); +} + +static int ccid_handle_control(USBDevice *dev, int request, int value, + int index, int length, uint8_t *data) +{ + USBCCIDState *s = DO_UPCAST(USBCCIDState, dev, dev); + int ret = 0; + + DPRINTF(s, 1, "got control %x, value %x\n",request, value); + switch (request) { + case DeviceRequest | USB_REQ_GET_STATUS: + data[0] = (0 << USB_DEVICE_SELF_POWERED) | + (dev->remote_wakeup << USB_DEVICE_REMOTE_WAKEUP); + data[1] = 0x00; + ret = 2; + break; + case DeviceOutRequest | USB_REQ_CLEAR_FEATURE: + if (value == USB_DEVICE_REMOTE_WAKEUP) { + dev->remote_wakeup = 0; + } else { + goto fail; + } + ret = 0; + break; + case DeviceOutRequest | USB_REQ_SET_FEATURE: + if (value == USB_DEVICE_REMOTE_WAKEUP) { + dev->remote_wakeup = 1; + } else { + goto fail; + } + ret = 0; + break; + case DeviceOutRequest | USB_REQ_SET_ADDRESS: + dev->addr = value; + ret = 0; + break; + case DeviceRequest | USB_REQ_GET_DESCRIPTOR: + switch(value >> 8) { + case USB_DT_DEVICE: + memcpy(data, qemu_ccid_dev_descriptor, + sizeof(qemu_ccid_dev_descriptor)); + ret = sizeof(qemu_ccid_dev_descriptor); + break; + case USB_DT_CONFIG: + memcpy(data, qemu_ccid_config_descriptor, + sizeof(qemu_ccid_config_descriptor)); + ret = sizeof(qemu_ccid_config_descriptor); + break; + case USB_DT_STRING: + switch(value & 0xff) { + case 0: + /* language ids */ + data[0] = 4; + data[1] = 3; + data[2] = 0x09; + data[3] = 0x04; + ret = 4; + break; + case 1: + /* vendor description */ + ret = set_usb_string(data, CCID_VENDOR_DESCRIPTION); + break; + case 2: + /* product description */ + ret = set_usb_string(data, CCID_PRODUCT_DESCRIPTION); + break; + case 3: + /* serial number */ + ret = set_usb_string(data, CCID_SERIAL_NUMBER_STRING); + break; + case 4: + /* interface name */ + ret = set_usb_string(data, CCID_INTERFACE_NAME); + break; + default: + goto fail; + } + break; + default: + goto fail; + } + break; + case DeviceRequest | USB_REQ_GET_CONFIGURATION: + data[0] = 1; + ret = 1; + break; + case DeviceOutRequest | USB_REQ_SET_CONFIGURATION: + /* Only one configuration - we just ignore the request */ + ret = 0; + break; + case DeviceRequest | USB_REQ_GET_INTERFACE: + data[0] = 0; + ret = 1; + break; + case InterfaceOutRequest | USB_REQ_SET_INTERFACE: + ret = 0; + break; + case EndpointOutRequest | USB_REQ_CLEAR_FEATURE: + ret = 0; + break; + + /* Class specific requests. */ + case InterfaceOutClass | CCID_CONTROL_ABORT: + DPRINTF(s, 1, "ccid_control abort UNIMPLEMENTED\n"); + ret = USB_RET_STALL; + break; + case InterfaceInClass | CCID_CONTROL_GET_CLOCK_FREQUENCIES: + DPRINTF(s, 1, "ccid_control get clock frequencies UNIMPLEMENTED\n"); + ret = USB_RET_STALL; + break; + case InterfaceInClass | CCID_CONTROL_GET_DATA_RATES: + DPRINTF(s, 1, "ccid_control get data rates UNIMPLEMENTED\n"); + ret = USB_RET_STALL; + break; + default: + fail: + DPRINTF(s, 1, "got unsupported/bogus control %x, value %x\n", request, value); + ret = USB_RET_STALL; + break; + } + return ret; +} + +static bool ccid_card_inserted(USBCCIDState *s) +{ + return s->bmSlotICCState & SLOT_0_STATE_MASK; +} + +static uint8_t ccid_card_status(USBCCIDState *s) +{ + return ccid_card_inserted(s) + ? (s->powered ? + ICC_STATUS_PRESENT_ACTIVE + : ICC_STATUS_PRESENT_INACTIVE + ) + : ICC_STATUS_NOT_PRESENT; +} + +static uint8_t ccid_calc_status(USBCCIDState *s) +{ + /* page 55, 6.2.6, calculation of bStatus from bmICCStatus and + bmCommandStatus + */ + uint8_t ret = ccid_card_status(s) | (s->bmCommandStatus << 6); + DPRINTF(s, 4, "status = %d\n", ret); + return ret; +} + +static void ccid_reset_error_status(USBCCIDState* s) +{ + s->bError = ERROR_CMD_NOT_SUPPORTED; + s->bmCommandStatus = COMMAND_STATUS_NO_ERROR; +} + +static void ccid_write_slot_status(USBCCIDState* s, CCID_Header* recv) +{ + CCID_SlotStatus *h = ccid_reserve_recv_buf(s, sizeof(CCID_SlotStatus)); + if (h == NULL) { + return; + } + h->b.hdr.bMessageType = CCID_MESSAGE_TYPE_RDR_to_PC_SlotStatus; + h->b.hdr.dwLength = 0; + h->b.hdr.bSlot = recv->bSlot; + h->b.hdr.bSeq = recv->bSeq; + h->b.bStatus = ccid_calc_status(s); + h->b.bError = s->bError; + h->bClockStatus = CLOCK_STATUS_RUNNING; + ccid_reset_error_status(s); +} + +static void ccid_write_parameters(USBCCIDState* s, CCID_Header* recv) +{ + CCID_Parameter *h; + uint32_t len = s->ulProtocolDataStructureSize; + + h = ccid_reserve_recv_buf(s, sizeof(CCID_Parameter) + len); + if (h == NULL) { + return; + } + h->b.hdr.bMessageType = CCID_MESSAGE_TYPE_RDR_to_PC_Parameters; + h->b.hdr.dwLength = 0; + h->b.hdr.bSlot = recv->bSlot; + h->b.hdr.bSeq = recv->bSeq; + h->b.bStatus = ccid_calc_status(s); + h->b.bError = s->bError; + h->bProtocolNum = s->bProtocolNum; + memcpy(h->abProtocolDataStructure, s->abProtocolDataStructure, len); + ccid_reset_error_status(s); +} + +static void ccid_write_data_block( + USBCCIDState* s, uint8_t slot, uint8_t seq, + const uint8_t* data, uint32_t len) +{ + CCID_DataBlock *p = ccid_reserve_recv_buf(s, sizeof(*p) + len); + + if (p == NULL) { + return; + } + p->b.hdr.bMessageType = CCID_MESSAGE_TYPE_RDR_to_PC_DataBlock; + p->b.hdr.dwLength = cpu_to_le32(len); + p->b.hdr.bSlot = slot; + p->b.hdr.bSeq = seq; + p->b.bStatus = ccid_calc_status(s); + p->b.bError = s->bError; +#ifdef DEBUG_CCID + if (p->b.bError) { + DPRINTF(s, 4, "error %d", p->b.bError); + } +#endif + memcpy(p->abData, data, len); + ccid_reset_error_status(s); +} + +static void ccid_write_data_block_answer(USBCCIDState* s, + const uint8_t* data, uint32_t len) +{ + uint8_t seq; + uint8_t slot; + + if (!ccid_has_pending_answers(s)) { + abort(); + } + ccid_remove_pending_answer(s, &slot, &seq); + ccid_write_data_block(s, slot, seq, data, len); +} + +static void ccid_write_data_block_atr(USBCCIDState* s, CCID_Header* recv) +{ + const uint8_t *atr = NULL; + uint32_t len = 0; + + if (s->card) { + atr = s->cardinfo->get_atr(s->card, &len); + } + ccid_write_data_block(s, recv->bSlot, recv->bSeq, atr, len); +} + +static void ccid_set_parameters(USBCCIDState *s, CCID_Header *recv) +{ + CCID_SetParameter *ph = (CCID_SetParameter *) recv; + uint32_t len = 0; + if (ph->bProtocolNum == 0) { + len = 5; + } + if (ph->bProtocolNum == 1) { + len = 7; + } + if (len == 0) { + s->bmCommandStatus = COMMAND_STATUS_FAILED; + s->bError = 7; /* Protocol invalid or not supported */ + return; + } + s->bProtocolNum = ph->bProtocolNum; + memcpy(s->abProtocolDataStructure, ph->abProtocolDataStructure, len); + s->ulProtocolDataStructureSize = len; + DPRINTF(s, 1, "%s: using len %d\n", __func__, len); +} + +/* must be 5 bytes for T=0, 7 bytes for T=1 + * See page 52 */ +static const uint8_t abDefaultProtocolDataStructure[7] = + { 0x77, 0x00, 0x00, 0x00, 0x00, 0xfe /*IFSC*/, 0x00 /*NAD*/ }; + +static void ccid_reset_parameters(USBCCIDState *s) +{ + uint32_t len = sizeof(abDefaultProtocolDataStructure); + + s->bProtocolNum = 1; /* T=1 */ + s->ulProtocolDataStructureSize = len; + memcpy(s->abProtocolDataStructure, abDefaultProtocolDataStructure, len); +} + +static void ccid_report_error_failed(USBCCIDState *s, uint8_t error) +{ + s->bmCommandStatus = COMMAND_STATUS_FAILED; + s->bError = error; +} + +/* NOTE: only a single slot is supported (SLOT_0) + */ +static void ccid_on_slot_change(USBCCIDState* s, bool full) +{ + /* RDR_to_PC_NotifySlotChange, 6.3.1 page 56 + */ + uint8_t current = s->bmSlotICCState; + if (full) { + s->bmSlotICCState |= SLOT_0_STATE_MASK; + } else { + s->bmSlotICCState &= ~SLOT_0_STATE_MASK; + } + if (current != s->bmSlotICCState) { + s->bmSlotICCState |= SLOT_0_CHANGED_MASK; + } + s->notify_slot_change = true; +} + +static void ccid_write_data_block_error( + USBCCIDState *s, uint8_t slot, uint8_t seq) +{ + ccid_write_data_block(s, slot, seq, NULL, 0); +} + +static void ccid_on_apdu_from_guest(USBCCIDState *s, CCID_XferBlock *recv) +{ + uint32_t len; + + if (ccid_card_status(s) != ICC_STATUS_PRESENT_ACTIVE) { + DPRINTF(s, 1, "usb-ccid: not sending apdu to client, no card connected\n"); + ccid_write_data_block_error(s, recv->hdr.bSlot, recv->hdr.bSeq); + return; + } + len = le32_to_cpu(recv->hdr.dwLength); + DPRINTF(s, 1, "%s: seq %d, len %d\n", __FUNCTION__, + recv->hdr.bSeq, len); + ccid_add_pending_answer(s, (CCID_Header*)recv); + if (s->card) { + s->cardinfo->apdu_from_guest(s->card, recv->abData, len); + } else { + printf("warning: discarded apdu\n"); + } +} + +/* handle a single USB_TOKEN_OUT, return value returned to guest. + * 0 - all ok + * USB_RET_STALL - failed to handle packet */ +static int ccid_handle_bulk_out(USBCCIDState *s, USBPacket *p) +{ + CCID_Header* ccid_header; + + if (p->len + s->bulk_out_pos > BULK_OUT_DATA_SIZE) { + return USB_RET_STALL; + } + ccid_header = (CCID_Header*)s->bulk_out_data; + memcpy(s->bulk_out_data + s->bulk_out_pos, p->data, p->len); + s->bulk_out_pos += p->len; + if (p->len == 64) { + DPRINTF(s, 4, "usb-ccid: bulk_in: expecting more packets (%d/%d)\n", + p->len, ccid_header->dwLength); + return 0; + } + if (s->bulk_out_pos < 10) { + DPRINTF(s, 1, "%s: bad USB_TOKEN_OUT length, should be at least 10 bytes\n", __func__); + } else { + DPRINTF(s, 3, "%s %x\n", __func__, ccid_header->bMessageType); + switch (ccid_header->bMessageType) { + case CCID_MESSAGE_TYPE_PC_to_RDR_GetSlotStatus: + ccid_write_slot_status(s, ccid_header); + break; + case CCID_MESSAGE_TYPE_PC_to_RDR_IccPowerOn: + DPRINTF(s, 1, "PowerOn: %d\n", + ((CCID_IccPowerOn*)(ccid_header))->bPowerSelect); + s->powered = true; + if (!ccid_card_inserted(s)) { + ccid_report_error_failed(s, ERROR_ICC_MUTE); + } + /* atr is written regardless of error. */ + ccid_write_data_block_atr(s, ccid_header); + break; + case CCID_MESSAGE_TYPE_PC_to_RDR_IccPowerOff: + DPRINTF(s, 1, "PowerOff\n"); + ccid_reset_error_status(s); + s->powered = false; + ccid_write_slot_status(s, ccid_header); + break; + case CCID_MESSAGE_TYPE_PC_to_RDR_XfrBlock: + ccid_on_apdu_from_guest(s, (CCID_XferBlock*)s->bulk_out_data); + break; + case CCID_MESSAGE_TYPE_PC_to_RDR_SetParameters: + ccid_reset_error_status(s); + ccid_set_parameters(s, ccid_header); + ccid_write_parameters(s, ccid_header); + break; + case CCID_MESSAGE_TYPE_PC_to_RDR_ResetParameters: + ccid_reset_error_status(s); + ccid_reset_parameters(s); + ccid_write_parameters(s, ccid_header); + break; + case CCID_MESSAGE_TYPE_PC_to_RDR_GetParameters: + ccid_reset_error_status(s); + ccid_write_parameters(s, ccid_header); + break; + default: + DPRINTF(s, 1, "handle_data: ERROR: unhandled message type %Xh\n", + ccid_header->bMessageType); + /* the caller is expecting the device to respond, tell it we + * do't support the operation */ + ccid_report_error_failed(s, ERROR_CMD_NOT_SUPPORTED); + ccid_write_slot_status(s, ccid_header); + break; + } + } + s->bulk_out_pos = 0; + return 0; +} + +static int ccid_bulk_in_copy_to_guest(USBCCIDState *s, uint8_t *data, int len) +{ + int ret = 0; + + assert(len>0); + ccid_bulk_in_get(s); + if (s->current_bulk_in != NULL) { + ret = MIN(s->current_bulk_in->len - s->current_bulk_in->pos, len); + memcpy(data, s->current_bulk_in->data + s->current_bulk_in->pos, ret); + s->current_bulk_in->pos += ret; + if (s->current_bulk_in->pos == s->current_bulk_in->len) { + ccid_bulk_in_release(s); + } + } else { + ret = USB_RET_NAK; /* return when device has no data - usb 2.0 spec Table 8-4 */ + } + if (ret > 0) { + DPRINTF(s, 3, "%s: %d/%d req/act to guest (BULK_IN)\n", __func__, len, ret); + } + if (ret != USB_RET_NAK && ret < len) { + DPRINTF(s, 1, "%s: returning short (EREMOTEIO) %d < %d\n", __func__, ret, len); + } + return ret; +} + +static int ccid_handle_data(USBDevice *dev, USBPacket *p) +{ + USBCCIDState *s = DO_UPCAST(USBCCIDState, dev, dev); + int ret = 0; + uint8_t *data = p->data; + int len = p->len; + + switch (p->pid) { + case USB_TOKEN_OUT: + ret = ccid_handle_bulk_out(s, p); + break; + + case USB_TOKEN_IN: + switch (p->devep & 0xf) { + case CCID_BULK_IN_EP: + if (!len) { + ret = USB_RET_NAK; + } else { + ret = ccid_bulk_in_copy_to_guest(s, data, len); + } + break; + case CCID_INT_IN_EP: + if (s->notify_slot_change) { + /* page 56, RDR_to_PC_NotifySlotChange */ + data[0] = CCID_MESSAGE_TYPE_RDR_to_PC_NotifySlotChange; + data[1] = s->bmSlotICCState; + ret = 2; + s->notify_slot_change = false; + s->bmSlotICCState &= ~SLOT_0_CHANGED_MASK; + DPRINTF(s, 2, "handle_data: int_in: notify_slot_change %X, requested len %d\n", + s->bmSlotICCState, len); + } + break; + default: + DPRINTF(s, 1, "Bad endpoint\n"); + break; + } + break; + default: + DPRINTF(s, 1, "Bad token\n"); + ret = USB_RET_STALL; + break; + } + + return ret; +} + +static void ccid_handle_destroy(USBDevice *dev) +{ + USBCCIDState *s = DO_UPCAST(USBCCIDState, dev, dev); + + ccid_bulk_in_clear(s); +} + +static void ccid_flush_pending_answers(USBCCIDState *s) { + while (ccid_has_pending_answers(s)) { + ccid_write_data_block_answer(s, NULL, 0); + } +} + +static Answer *ccid_peek_next_answer(USBCCIDState *s) +{ + return s->pending_answers_num == 0 + ? NULL + : &s->pending_answers[s->pending_answers_start % PENDING_ANSWERS_NUM]; +} + +static void ccid_bus_dev_print(Monitor *mon, DeviceState *qdev, int indent) +{ + CCIDCardState *card = DO_UPCAST(CCIDCardState, qdev, qdev); + CCIDCardInfo *info = DO_UPCAST(CCIDCardInfo, qdev, qdev->info); + + if (info->print) { + info->print(mon, card, indent); + } +} + +struct CCIDBus { + BusState qbus; +}; + +static struct BusInfo ccid_bus_info = { + .name = "ccid-bus", + .size = sizeof(CCIDBus), + .print_dev = ccid_bus_dev_print, + .props = (Property[]) { + DEFINE_PROP_UINT32("slot", struct CCIDCardState, slot, 0), + DEFINE_PROP_END_OF_LIST(), + } +}; + +static CCIDBus *ccid_bus_new(DeviceState *dev) +{ + CCIDBus *bus; + + bus = FROM_QBUS(CCIDBus, qbus_create(&ccid_bus_info, dev, NULL)); + bus->qbus.allow_hotplug = 1; + + return bus; +} + +void ccid_card_send_apdu_to_guest(CCIDCardState *card, uint8_t* apdu, uint32_t len) +{ + USBCCIDState *s = DO_UPCAST(USBCCIDState, dev.qdev, card->qdev.parent_bus->parent); + Answer *answer; + + if (!ccid_has_pending_answers(s)) { + DPRINTF(s, 1, "CCID ERROR: got an APDU without pending answers\n"); + return; + } + s->bmCommandStatus = COMMAND_STATUS_NO_ERROR; + answer = ccid_peek_next_answer(s); + if (answer == NULL) { + abort(); + } + DPRINTF(s, 1, "APDU returned to guest %d (answer seq %d, slot %d)\n", + len, answer->seq, answer->slot); + ccid_write_data_block_answer(s, apdu, len); +} + +void ccid_card_card_removed(CCIDCardState *card) +{ + USBCCIDState *s = DO_UPCAST(USBCCIDState, dev.qdev, card->qdev.parent_bus->parent); + + ccid_on_slot_change(s, false); + ccid_flush_pending_answers(s); + ccid_reset(s); +} + +int ccid_card_ccid_attach(CCIDCardState *card) +{ + USBCCIDState *s = DO_UPCAST(USBCCIDState, dev.qdev, card->qdev.parent_bus->parent); + + DPRINTF(s, 1, "CCID Attach\n"); + if (s->migration_state == MIGRATION_MIGRATED) { + s->migration_state = MIGRATION_NONE; + } + return 0; +} + +void ccid_card_ccid_detach(CCIDCardState *card) +{ + USBCCIDState *s = DO_UPCAST(USBCCIDState, dev.qdev, card->qdev.parent_bus->parent); + + DPRINTF(s, 1, "CCID Detach\n"); + if (ccid_card_inserted(s)) { + ccid_on_slot_change(s, false); + } + ccid_detach(s); +} + +void ccid_card_card_error(CCIDCardState *card, uint64_t error) +{ + USBCCIDState *s = DO_UPCAST(USBCCIDState, dev.qdev, card->qdev.parent_bus->parent); + + s->bmCommandStatus = COMMAND_STATUS_FAILED; + s->last_answer_error = error; + DPRINTF(s, 1, "VSC_Error: %lX\n", s->last_answer_error); + /* TODO: these error's should be more verbose and propogated to the guest. + * */ + ccid_write_data_block_answer(s, NULL, 0); +} + +void ccid_card_card_inserted(CCIDCardState *card) +{ + USBCCIDState *s = DO_UPCAST(USBCCIDState, dev.qdev, card->qdev.parent_bus->parent); + + s->bmCommandStatus = COMMAND_STATUS_NO_ERROR; + ccid_flush_pending_answers(s); + ccid_on_slot_change(s, true); +} + +static int ccid_card_exit(DeviceState *qdev) +{ + int ret = 0; + CCIDCardState *card = DO_UPCAST(CCIDCardState, qdev, qdev); + CCIDCardInfo *info = DO_UPCAST(CCIDCardInfo, qdev, qdev->info); + USBCCIDState *s = DO_UPCAST(USBCCIDState, dev.qdev, card->qdev.parent_bus->parent); + + if (ccid_card_inserted(s)) { + ccid_card_card_removed(card); + } + if (info->exitfn) { + ret = info->exitfn(card); + } + s->card = NULL; + s->cardinfo = NULL; + return ret; +} + +static int ccid_card_init(DeviceState *qdev, DeviceInfo *base) +{ + CCIDCardState *card = DO_UPCAST(CCIDCardState, qdev, qdev); + CCIDCardInfo *info = DO_UPCAST(CCIDCardInfo, qdev, base); + USBCCIDState *s = DO_UPCAST(USBCCIDState, dev.qdev, card->qdev.parent_bus->parent); + int ret = 0; + + if (card->slot != 0) { + fprintf(stderr, "Warning: usb-ccid supports one slot, can't add %d", + card->slot); + return -1; + } + if (s->card != NULL) { + fprintf(stderr, "Warning: usb-ccid card already full, not adding\n"); + return -1; + } + ret = info->initfn ? info->initfn(card) : ret; + if (ret == 0) { + s->card = card; + s->cardinfo = info; + } + return ret; +} + +void ccid_card_qdev_register(CCIDCardInfo *card) +{ + card->qdev.bus_info = &ccid_bus_info; + card->qdev.init = ccid_card_init; + card->qdev.exit = ccid_card_exit; + qdev_register(&card->qdev); +} + +static int ccid_initfn(USBDevice *dev) +{ + USBCCIDState *s = DO_UPCAST(USBCCIDState, dev, dev); + + s->bus = ccid_bus_new(&dev->qdev); + s->card = NULL; + s->cardinfo = NULL; + s->migration_state = MIGRATION_NONE; + s->migration_target_ip = 0; + s->migration_target_port = 0; + s->dev.speed = USB_SPEED_FULL; + s->notify_slot_change = false; + s->powered = true; + s->pending_answers_num = 0; + s->last_answer_error = 0; + s->bulk_in_pending_start = 0; + s->bulk_in_pending_end = 0; + s->current_bulk_in = NULL; + ccid_reset_error_status(s); + s->bulk_out_pos = 0; + ccid_reset_parameters(s); + ccid_reset(s); + return 0; +} + +#ifdef ENABLE_MIGRATION +static int ccid_post_load(void *opaque, int version_id) +{ + USBCCIDState *s = opaque; + + // This must be done after usb_device_attach, which sets state to ATTACHED, + // while it must be DEFAULT in order to accept packets (like it is after + // reset, but reset will reset our addr and call our reset handler which + // may change state, and we don't want to do that when migrating). + s->dev.state = s->state_vmstate; + return 0; +} + +static void ccid_pre_save(void *opaque) +{ + USBCCIDState *s = opaque; + + s->state_vmstate = s->dev.state; + if (s->dev.attached) { + // migrating an open device, ignore reconnection CHR_EVENT to avoid an + // erronous detach. + s->migration_state = MIGRATION_MIGRATED; + } +} + +static VMStateDescription bulk_in_vmstate = { + .name = "CCID BulkIn state", + .version_id = 1, + .minimum_version_id = 1, + .fields = (VMStateField []) { + VMSTATE_BUFFER(data, BulkIn), + VMSTATE_UINT32(len, BulkIn), + VMSTATE_UINT32(pos, BulkIn), + VMSTATE_END_OF_LIST() + } +}; + +static VMStateDescription answer_vmstate = { + .name = "CCID Answer state", + .version_id = 1, + .minimum_version_id = 1, + .fields = (VMStateField []) { + VMSTATE_UINT8(slot, Answer), + VMSTATE_UINT8(seq, Answer), + VMSTATE_END_OF_LIST() + } +}; + +static VMStateDescription usb_device_vmstate = { + .name = "usb_device", + .version_id = 1, + .minimum_version_id = 1, + .fields = (VMStateField []) { + VMSTATE_UINT8(addr, USBDevice), + VMSTATE_BUFFER(setup_buf, USBDevice), + VMSTATE_BUFFER(data_buf, USBDevice), + VMSTATE_END_OF_LIST() + } +}; + +static VMStateDescription ccid_vmstate = { + .name = CCID_DEV_NAME, + .version_id = 1, + .minimum_version_id = 1, + .post_load = ccid_post_load, + .pre_save = ccid_pre_save, + .fields = (VMStateField []) { + VMSTATE_STRUCT(dev, USBCCIDState, 1, usb_device_vmstate, USBDevice), + VMSTATE_UINT8(debug, USBCCIDState), + VMSTATE_BUFFER(bulk_out_data, USBCCIDState), + VMSTATE_UINT32(bulk_out_pos, USBCCIDState), + VMSTATE_UINT8(bmSlotICCState, USBCCIDState), + VMSTATE_UINT8(powered, USBCCIDState), + VMSTATE_UINT8(notify_slot_change, USBCCIDState), + VMSTATE_UINT64(last_answer_error, USBCCIDState), + VMSTATE_UINT8(bError, USBCCIDState), + VMSTATE_UINT8(bmCommandStatus, USBCCIDState), + VMSTATE_UINT8(bProtocolNum, USBCCIDState), + VMSTATE_BUFFER(abProtocolDataStructure, USBCCIDState), + VMSTATE_UINT32(ulProtocolDataStructureSize, USBCCIDState), + VMSTATE_STRUCT_ARRAY(bulk_in_pending, USBCCIDState, + BULK_IN_PENDING_NUM, 1, bulk_in_vmstate, BulkIn), + VMSTATE_UINT32(bulk_in_pending_start, USBCCIDState), + VMSTATE_UINT32(bulk_in_pending_end, USBCCIDState), + VMSTATE_STRUCT_ARRAY(pending_answers, USBCCIDState, + PENDING_ANSWERS_NUM, 1, answer_vmstate, Answer), + VMSTATE_UINT32(pending_answers_num, USBCCIDState), + VMSTATE_UINT8(migration_state, USBCCIDState), + VMSTATE_UINT32(state_vmstate, USBCCIDState), + VMSTATE_END_OF_LIST() + } +}; +#endif // ENABLE_MIGRATION + +static struct USBDeviceInfo ccid_info = { + .product_desc = "QEMU USB CCID", + .qdev.name = CCID_DEV_NAME, + .qdev.size = sizeof(USBCCIDState), + .init = ccid_initfn, + .handle_packet = usb_generic_handle_packet, + .handle_reset = ccid_handle_reset, + .handle_control = ccid_handle_control, + .handle_data = ccid_handle_data, + .handle_destroy = ccid_handle_destroy, + .usbdevice_name = "ccid", + .qdev.props = (Property[]) { + DEFINE_PROP_UINT8("debug", USBCCIDState, debug, 0), + DEFINE_PROP_END_OF_LIST(), + }, +#ifdef ENABLE_MIGRATION + .qdev.vmsd = &ccid_vmstate, +#endif +}; + + +static void ccid_register_devices(void) +{ + usb_qdev_register(&ccid_info); +} +device_init(ccid_register_devices) -- 1.7.3.4 ^ permalink raw reply related [flat|nested] 35+ messages in thread
* Re: [Qemu-devel] [PATCH 1/7] usb-ccid: add CCID bus 2011-01-11 8:38 ` [Qemu-devel] [PATCH 1/7] usb-ccid: add CCID bus Alon Levy @ 2011-01-25 14:10 ` Anthony Liguori 2011-01-25 16:10 ` Alon Levy 0 siblings, 1 reply; 35+ messages in thread From: Anthony Liguori @ 2011-01-25 14:10 UTC (permalink / raw) To: Alon Levy; +Cc: qemu-devel On 01/11/2011 02:38 AM, Alon Levy wrote: > A CCID device is a smart card reader. It is a USB device, defined at [1]. > This patch introduces the usb-ccid device that is a ccid bus. Next patches will > introduce two card types to use it, a passthru card and an emulated card. > > [1] http://www.usb.org/developers/devclass_docs/DWG_Smart-Card_CCID_Rev110. > > Signed-off-by: Alon Levy<alevy@redhat.com> > --- > Makefile.objs | 1 + > configure | 6 + > hw/ccid.h | 35 ++ > hw/usb-ccid.c | 1355 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++ > 4 files changed, 1397 insertions(+), 0 deletions(-) > create mode 100644 hw/ccid.h > create mode 100644 hw/usb-ccid.c > > diff --git a/Makefile.objs b/Makefile.objs > index d6b3d60..7da4771 100644 > --- a/Makefile.objs > +++ b/Makefile.objs > @@ -197,6 +197,7 @@ hw-obj-$(CONFIG_FDC) += fdc.o > hw-obj-$(CONFIG_ACPI) += acpi.o acpi_piix4.o > hw-obj-$(CONFIG_APM) += pm_smbus.o apm.o > hw-obj-$(CONFIG_DMA) += dma.o > +hw-obj-$(CONFIG_SMARTCARD) += usb-ccid.o > > # PPC devices > hw-obj-$(CONFIG_OPENPIC) += openpic.o > diff --git a/configure b/configure > index 831a741..839980c 100755 > --- a/configure > +++ b/configure > @@ -334,6 +334,7 @@ trace_backend="nop" > trace_file="trace" > spice="" > rbd="" > +smartcard="yes" > > # OS specific > if check_define __linux__ ; then > @@ -2441,6 +2442,7 @@ echo "Trace output file $trace_file-<pid>" > echo "spice support $spice" > echo "rbd support $rbd" > echo "xfsctl support $xfs" > +echo "smartcard support $smartcard" > Device don't get printed out in configure because they aren't probed. > diff --git a/hw/ccid.h b/hw/ccid.h > new file mode 100644 > index 0000000..af59070 > --- /dev/null > +++ b/hw/ccid.h > @@ -0,0 +1,35 @@ > +#ifndef __CCID_H__ > +#define __CCID_H__ > No copyright and this is not valid C (you're not support to use __ prefix). > +#include "qdev.h" > + > +typedef struct CCIDCardState CCIDCardState; > +typedef struct CCIDCardInfo CCIDCardInfo; > + > +struct CCIDCardState { > + DeviceState qdev; > + uint32_t slot; // For future use with multiple slot reader. > Don't use C99 comments. > +}; > + > +struct CCIDCardInfo { > + DeviceInfo qdev; > + void (*print)(Monitor *mon, CCIDCardState *card, int indent); > + const uint8_t *(*get_atr)(CCIDCardState *card, uint32_t *len); > + void (*apdu_from_guest)(CCIDCardState *card, const uint8_t *apdu, uint32_t len); > + int (*exitfn)(CCIDCardState *card); > + int (*initfn)(CCIDCardState *card); > +}; > + > +void ccid_card_send_apdu_to_guest(CCIDCardState *card, uint8_t* apdu, uint32_t len); > +void ccid_card_card_removed(CCIDCardState *card); > +void ccid_card_card_inserted(CCIDCardState *card); > +void ccid_card_card_error(CCIDCardState *card, uint64_t error); > +void ccid_card_qdev_register(CCIDCardInfo *card); > + > +/* support guest visible insertion/removal of ccid devices based on actual > + * devices connected/removed. Called by card implementation (passthru, local) */ > +int ccid_card_ccid_attach(CCIDCardState *card); > +void ccid_card_ccid_detach(CCIDCardState *card); > + > +#endif // __CCID_H__ > + > diff --git a/hw/usb-ccid.c b/hw/usb-ccid.c > new file mode 100644 > index 0000000..58f69a6 > --- /dev/null > +++ b/hw/usb-ccid.c > @@ -0,0 +1,1355 @@ > +/* > + * CCID Device emulation > + * > + * Based on usb-serial.c: > + * Copyright (c) 2006 CodeSourcery. > + * Copyright (c) 2008 Samuel Thibault<samuel.thibault@ens-lyon.org> > + * Written by Paul Brook, reused for FTDI by Samuel Thibault, > + * Reused for CCID by Alon Levy. > + * Contributed to by Robert Relyea > + * Copyright (c) 2010 Red Hat. > + * > + * This code is licenced under the LGPL. > + */ > + > +/* References: > + * > + * CCID Specification Revision 1.1 April 22nd 2005 > + * "Universal Serial Bus, Device Class: Smart Card" > + * Specification for Integrated Circuit(s) Cards Interface Devices > + * > + * Endianess note: from the spec (1.3) > + * "Fields that are larger than a byte are stored in little endian > + * > + * KNOWN BUGS > + * 1. remove/insert can sometimes result in removed state instead of inserted. > + * This is a result of the following: > + * symptom: dmesg shows ERMOTEIO (-121), pcscd shows -99. This happens > + * when we send a too short packet, seen in uhci-usb.c, resulting from > + * a urb requesting SPD and us returning a smaller packet. > + * Not sure which messages trigger this. > + * > + * Migration note: > + * > + * All the VMStateDescription's are left here for future use, but > + * not enabled right now since there is no support for USB migration. > + * > + * To enable define ENABLE_MIGRATION > + */ > + > +#include "qemu-common.h" > +#include "qemu-error.h" > +#include "usb.h" > +#include "monitor.h" > + > +#include "hw/ccid.h" > + > +//#define DEBUG_CCID > + > +#define DPRINTF(s, lvl, fmt, ...) \ > +do { if (lvl<= s->debug) { printf("usb-ccid: " fmt , ## __VA_ARGS__); } } while (0) > + > +#define CCID_DEV_NAME "usb-ccid" > + > +/* The two options for variable sized buffers: > + * make them constant size, for large enough constant, > + * or handle the migration complexity - VMState doesn't handle this case. > + * sizes are expected never to be exceeded, unless guest misbehaves. */ > +#define BULK_OUT_DATA_SIZE 65536 > +#define PENDING_ANSWERS_NUM 128 > + > +#define BULK_IN_BUF_SIZE 384 > +#define BULK_IN_PENDING_NUM 8 > + > +#define InterfaceOutClass ((USB_DIR_OUT|USB_TYPE_CLASS|USB_RECIP_INTERFACE)<<8) > +#define InterfaceInClass ((USB_DIR_IN |USB_TYPE_CLASS|USB_RECIP_INTERFACE)<<8) > + > +#define CCID_CONTROL_ABORT 0x1 > +#define CCID_CONTROL_GET_CLOCK_FREQUENCIES 0x2 > +#define CCID_CONTROL_GET_DATA_RATES 0x3 > + > +#define CCID_PRODUCT_DESCRIPTION "QEMU USB CCID" > +#define CCID_VENDOR_DESCRIPTION "QEMU " QEMU_VERSION > Note the exposing the version creates a backwards compatibility issue. > +#define CCID_INTERFACE_NAME "CCID Interface" > +#define CCID_SERIAL_NUMBER_STRING "1" > +/* Using Gemplus Vendor and Product id > + Effect on various drivers: > + * usbccid.sys (winxp, others untested) is a class driver so it doesn't care. > + * linux has a number of class drivers, but openct filters based on > + vendor/product (/etc/openct.conf under fedora), hence Gemplus. > + */ > +#define CCID_VENDOR_ID 0x08e6 > +#define CCID_PRODUCT_ID 0x4433 > +#define CCID_DEVICE_VERSION 0x0000 > + > +/* BULK_OUT messages from PC to Reader > + Defined in CCID Rev 1.1 6.1 (page 26) > + */ > +#define CCID_MESSAGE_TYPE_PC_to_RDR_IccPowerOn 0x62 > +#define CCID_MESSAGE_TYPE_PC_to_RDR_IccPowerOff 0x63 > +#define CCID_MESSAGE_TYPE_PC_to_RDR_GetSlotStatus 0x65 > +#define CCID_MESSAGE_TYPE_PC_to_RDR_XfrBlock 0x6f > +#define CCID_MESSAGE_TYPE_PC_to_RDR_GetParameters 0x6c > +#define CCID_MESSAGE_TYPE_PC_to_RDR_ResetParameters 0x6d > +#define CCID_MESSAGE_TYPE_PC_to_RDR_SetParameters 0x61 > +#define CCID_MESSAGE_TYPE_PC_to_RDR_Escape 0x6b > +#define CCID_MESSAGE_TYPE_PC_to_RDR_IccClock 0x6e > +#define CCID_MESSAGE_TYPE_PC_to_RDR_T0APDU 0x6a > +#define CCID_MESSAGE_TYPE_PC_to_RDR_Secure 0x69 > +#define CCID_MESSAGE_TYPE_PC_to_RDR_Mechanical 0x71 > +#define CCID_MESSAGE_TYPE_PC_to_RDR_Abort 0x72 > +#define CCID_MESSAGE_TYPE_PC_to_RDR_SetDataRateAndClockFrequency 0x73 > + > +/* BULK_IN messages from Reader to PC > + Defined in CCID Rev 1.1 6.2 (page 48) > + */ > +#define CCID_MESSAGE_TYPE_RDR_to_PC_DataBlock 0x80 > +#define CCID_MESSAGE_TYPE_RDR_to_PC_SlotStatus 0x81 > +#define CCID_MESSAGE_TYPE_RDR_to_PC_Parameters 0x82 > +#define CCID_MESSAGE_TYPE_RDR_to_PC_Escape 0x83 > +#define CCID_MESSAGE_TYPE_RDR_to_PC_DataRateAndClockFrequency 0x84 > + > +/* INTERRUPT_IN messages from Reader to PC > + Defined in CCID Rev 1.1 6.3 (page 56) > + */ > +#define CCID_MESSAGE_TYPE_RDR_to_PC_NotifySlotChange 0x50 > +#define CCID_MESSAGE_TYPE_RDR_to_PC_HardwareError 0x51 > + > +/* Endpoints for CCID - addresses are up to us to decide. > + To support slot insertion and removal we must have an interrupt in ep > + in addition we need a bulk in and bulk out ep > + 5.2, page 20 > + */ > +#define CCID_INT_IN_EP 1 > +#define CCID_BULK_IN_EP 2 > +#define CCID_BULK_OUT_EP 3 > + > +/* bmSlotICCState masks */ > +#define SLOT_0_STATE_MASK 1 > +#define SLOT_0_CHANGED_MASK 2 > + > +/* Status codes that go in bStatus (see 6.2.6) */ > +enum { > + ICC_STATUS_PRESENT_ACTIVE = 0, > + ICC_STATUS_PRESENT_INACTIVE, > + ICC_STATUS_NOT_PRESENT > +}; > + > +enum { > + COMMAND_STATUS_NO_ERROR = 0, > + COMMAND_STATUS_FAILED, > + COMMAND_STATUS_TIME_EXTENSION_REQUIRED > +}; > + > +/* Error codes that go in bError (see 6.2.6) > + */ > +enum { > + ERROR_CMD_NOT_SUPPORTED = 0, > + ERROR_CMD_ABORTED = -1, > + ERROR_ICC_MUTE = -2, > + ERROR_XFR_PARITY_ERROR = -3, > + ERROR_XFR_OVERRUN = -4, > + ERROR_HW_ERROR = -5, > +}; > + > +/* 6.2.6 RDR_to_PC_SlotStatus definitions */ > +enum { > + CLOCK_STATUS_RUNNING = 0, > + /* 0 - Clock Running, 1 - Clock stopped in State L, 2 - H, > + 3 - unkonwn state. rest are RFU > + */ > +}; > + > +typedef struct __attribute__ ((__packed__)) CCID_Header { > + uint8_t bMessageType; > + uint32_t dwLength; > + uint8_t bSlot; > + uint8_t bSeq; > +} CCID_Header; > + > +typedef struct __attribute__ ((__packed__)) CCID_BULK_IN { > + CCID_Header hdr; > + uint8_t bStatus; /* Only used in BULK_IN */ > + uint8_t bError; /* Only used in BULK_IN */ > +} CCID_BULK_IN; > + > +typedef struct __attribute__ ((__packed__)) CCID_SlotStatus { > + CCID_BULK_IN b; > + uint8_t bClockStatus; > +} CCID_SlotStatus; > + > +typedef struct __attribute__ ((__packed__)) CCID_Parameter { > + CCID_BULK_IN b; > + uint8_t bProtocolNum; > + uint8_t abProtocolDataStructure[0]; > +} CCID_Parameter; > + > +typedef struct __attribute__ ((__packed__)) CCID_DataBlock { > + CCID_BULK_IN b; > + uint8_t bChainParameter; > + uint8_t abData[0]; > +} CCID_DataBlock; > + > +/* 6.1.4 PC_to_RDR_XfrBlock */ > +typedef struct __attribute__ ((__packed__)) CCID_XferBlock { > + CCID_Header hdr; > + uint8_t bBWI; /* Block Waiting Timeout */ > + uint16_t wLevelParameter; /* XXX currently unused */ > + uint8_t abData[0]; > +} CCID_XferBlock; > + > +typedef struct __attribute__ ((__packed__)) CCID_IccPowerOn { > + CCID_Header hdr; > + uint8_t bPowerSelect; > + uint16_t abRFU; > +} CCID_IccPowerOn; > + > +typedef struct __attribute__ ((__packed__)) CCID_IccPowerOff { > + CCID_Header hdr; > + uint16_t abRFU; > +} CCID_IccPowerOff; > + > +typedef struct __attribute__ ((__packed__)) CCID_SetParameter { > + CCID_Header hdr; > + uint8_t bProtocolNum; > + uint8_t abProtocolDataStructure[0]; > +} CCID_SetParameter; > + > +typedef struct CCID_Notify_Slot_Change { > + uint8_t bMessageType; /* CCID_MESSAGE_TYPE_RDR_to_PC_NotifySlotChange */ > + uint8_t bmSlotICCState; > +} CCID_Notify_Slot_Change; > + > +/* used for DataBlock response to XferBlock */ > +typedef struct Answer { > + uint8_t slot; > + uint8_t seq; > +} Answer; > + > +/* pending BULK_IN messages */ > +typedef struct BulkIn { > + uint8_t data[BULK_IN_BUF_SIZE]; > + uint32_t len; > + uint32_t pos; > +} BulkIn; > + > +enum { > + MIGRATION_NONE, > + MIGRATION_MIGRATED, > +}; > + > +typedef struct CCIDBus CCIDBus; > +typedef struct USBCCIDState USBCCIDState; > + > +#define MAX_PROTOCOL_SIZE 7 > + > +/** > + * powered - defaults to true, changed by PowerOn/PowerOff messages > + */ > +struct USBCCIDState { > + USBDevice dev; > + CCIDBus *bus; > + CCIDCardState *card; > + CCIDCardInfo *cardinfo; /* caching the info pointer */ > + uint8_t debug; > + BulkIn bulk_in_pending[BULK_IN_PENDING_NUM]; /* circular */ > + uint32_t bulk_in_pending_start; > + uint32_t bulk_in_pending_end; /* first free */ > + uint32_t bulk_in_pending_num; > + BulkIn *current_bulk_in; > + uint8_t bulk_out_data[BULK_OUT_DATA_SIZE]; > + uint32_t bulk_out_pos; > + uint8_t bmSlotICCState; > + uint8_t powered; > + uint8_t notify_slot_change; > + uint64_t last_answer_error; > + Answer pending_answers[PENDING_ANSWERS_NUM]; > + uint32_t pending_answers_start; > + uint32_t pending_answers_end; > + uint32_t pending_answers_num; > + uint8_t bError; > + uint8_t bmCommandStatus; > + uint8_t bProtocolNum; > + uint8_t abProtocolDataStructure[MAX_PROTOCOL_SIZE]; > + uint32_t ulProtocolDataStructureSize; > + uint32_t state_vmstate; > + uint8_t migration_state; > + uint32_t migration_target_ip; > + uint16_t migration_target_port; > +}; > + > +/* Slot specific variables. We emulate a single slot card reader. > + */ > + > + > +/* CCID Spec chapter 4: CCID uses a standard device descriptor per Chapter 9, > + * "USB Device Framework", section 9.6.1, in the Universal Serial Bus > + * Specification. > + * > + * This device implemented based on the spec and with an Athena Smart Card > + * Reader as reference: > + * 0dc3:1004 Athena Smartcard Solutions, Inc. > + */ > + > +static const uint8_t qemu_ccid_dev_descriptor[] = { > + 0x12, /* u8 bLength; */ > + USB_DT_DEVICE, /* u8 bDescriptorType; Device */ > + 0x10, 0x01, /* u16 bcdUSB; v1.1 */ > + > + 0x00, /* u8 bDeviceClass; */ > + 0x00, /* u8 bDeviceSubClass; */ > + 0x00, /* u8 bDeviceProtocol; [ low/full speeds only ] */ > + 0x40, /* u8 bMaxPacketSize0; 8 Bytes (valid: 8,16,32,64) */ > + > + /* Vendor and product id are arbitrary. */ > + /* u16 idVendor */ > + CCID_VENDOR_ID& 0xff, CCID_VENDOR_ID>> 8, > + /* u16 idProduct */ > + CCID_PRODUCT_ID& 0xff, CCID_PRODUCT_ID>> 8, > + /* u16 bcdDevice */ > + CCID_DEVICE_VERSION& 0xff, CCID_DEVICE_VERSION>> 8, > + 0x01, /* u8 iManufacturer; */ > + 0x02, /* u8 iProduct; */ > + 0x03, /* u8 iSerialNumber; */ > + 0x01, /* u8 bNumConfigurations; */ > +}; > + > +static const uint8_t qemu_ccid_config_descriptor[] = { > + > + /* one configuration */ > + 0x09, /* u8 bLength; */ > + USB_DT_CONFIG, /* u8 bDescriptorType; Configuration */ > + 0x5d, 0x00, /* u16 wTotalLength; 9+9+54+7+7+7 */ > + 0x01, /* u8 bNumInterfaces; (1) */ > + 0x01, /* u8 bConfigurationValue; */ > + 0x00, /* u8 iConfiguration; */ > + 0xe0, /* u8 bmAttributes; > + Bit 7: must be set, > + 6: Self-powered, > + 5: Remote wakeup, > + 4..0: resvd */ > + 100/2, /* u8 MaxPower; 50 == 100mA */ > + > + /* one interface */ > + 0x09, /* u8 if_bLength; */ > + USB_DT_INTERFACE, /* u8 if_bDescriptorType; Interface */ > + 0x00, /* u8 if_bInterfaceNumber; */ > + 0x00, /* u8 if_bAlternateSetting; */ > + 0x03, /* u8 if_bNumEndpoints; */ > + 0x0b, /* u8 if_bInterfaceClass; Smart Card Device Class */ > + 0x00, /* u8 if_bInterfaceSubClass; Subclass code */ > + 0x00, /* u8 if_bInterfaceProtocol; Protocol code */ > + 0x04, /* u8 if_iInterface; Index of string descriptor */ > + > + /* Smart Card Device Class Descriptor */ > + 0x36, /* u8 bLength; */ > + 0x21, /* u8 bDescriptorType; Functional */ > + 0x10, 0x01, /* u16 bcdCCID; CCID Specification Release Number. */ > + 0x00, /* u8 bMaxSlotIndex; The index of the highest available > + slot on this device. All slots are consecutive starting > + at 00h. */ > + 0x07, /* u8 bVoltageSupport; 01h - 5.0v, 02h - 3.0, 03 - 1.8 */ > + > + 0x03, 0x00, /* u32 dwProtocols; RRRR PPPP. RRRR = 0000h.*/ > + 0x00, 0x00, /* PPPP: 0001h = Protocol T=0, 0002h = Protocol T=1 */ > + /* u32 dwDefaultClock; in kHZ (0x0fa0 is 4 MHz) */ > + 0xa0, 0x0f, 0x00, 0x00, > + /* u32 dwMaximumClock; */ > + 0x00, 0x00, 0x01, 0x00, > + 0x00, /* u8 bNumClockSupported; 0 means just the default and max. */ > + /* u32 dwDataRate ;bps. 9600 == 00002580h */ > + 0x80, 0x25, 0x00, 0x00, > + /* u32 dwMaxDataRate ; 11520 bps == 0001C200h */ > + 0x00, 0xC2, 0x01, 0x00, > + 0x00, /* u8 bNumDataRatesSupported; 00 means all rates between > + * default and max */ > + /* u32 dwMaxIFSD; maximum IFSD supported by CCID for protocol > + * T=1 (Maximum seen from various cards) */ > + 0xfe, 0x00, 0x00, 0x00, > + /* u32 dwSyncProtocols; 1 - 2-wire, 2 - 3-wire, 4 - I2C */ > + 0x00, 0x00, 0x00, 0x00, > + /* u32 dwMechanical; 0 - no special characteristics. */ > + 0x00, 0x00, 0x00, 0x00, > + /* u32 dwFeatures; > + * 0 - No special characteristics > + * + 2 Automatic parameter configuration based on ATR data > + * + 4 Automatic activation of ICC on inserting > + * + 8 Automatic ICC voltage selection > + * + 10 Automatic ICC clock frequency change > + * + 20 Automatic baud rate change > + * + 40 Automatic parameters negotiation made by the CCID > + * + 80 automatic PPS made by the CCID > + * 100 CCID can set ICC in clock stop mode > + * 200 NAD value other then 00 accepted (T=1 protocol) > + * + 400 Automatic IFSD exchange as first exchange (T=1) > + * One of the following only: > + * + 10000 TPDU level exchanges with CCID > + * 20000 Short APDU level exchange with CCID > + * 40000 Short and Extended APDU level exchange with CCID > + * > + * + 100000 USB Wake up signaling supported on card insertion > + * and removal. Must set bit 5 in bmAttributes in Configuration > + * descriptor if 100000 is set.*/ > + 0xfe, 0x04, 0x11, 0x00, > + /* u32 dwMaxCCIDMessageLength; For extended APDU in [261 + 10 > + * , 65544 + 10]. Otherwise the minimum is wMaxPacketSize of > + * the Bulk-OUT endpoint */ > + 0x12, 0x00, 0x01, 0x00, > + 0xFF, /* u8 bClassGetResponse; Significant only for CCID that > + * offers an APDU level for exchanges. Indicates the default > + * class value used by the CCID when it sends a Get Response > + * command to perform the transportation of an APDU by T=0 > + * protocol > + * FFh indicates that the CCID echos the class of the APDU. > + */ > + 0xFF, /* u8 bClassEnvelope; EAPDU only. Envelope command for T=0 */ > + 0x00, 0x00, /* u16 wLcdLayout; XXYY Number of lines (XX) and chars per > + * line for LCD display used for PIN entry. 0000 - no LCD */ > + 0x01, /* u8 bPINSupport; 01h PIN Verification, > + * 02h PIN Modification */ > + 0x01, /* u8 bMaxCCIDBusySlots; */ > + > + /* Interrupt-IN endpoint */ > + 0x07, /* u8 ep_bLength; */ > + /* u8 ep_bDescriptorType; Endpoint */ > + USB_DT_ENDPOINT, > + /* u8 ep_bEndpointAddress; IN Endpoint 1 */ > + 0x80 | CCID_INT_IN_EP, > + 0x03, /* u8 ep_bmAttributes; Interrupt */ > + 0x40, 0x00, /* u16 ep_wMaxPacketSize; */ > + 0xff, /* u8 ep_bInterval; */ > + > + /* Bulk-In endpoint */ > + 0x07, /* u8 ep_bLength; */ > + /* u8 ep_bDescriptorType; Endpoint */ > + USB_DT_ENDPOINT, > + /* u8 ep_bEndpointAddress; IN Endpoint 2 */ > + 0x80 | CCID_BULK_IN_EP, > + 0x02, /* u8 ep_bmAttributes; Bulk */ > + 0x40, 0x00, /* u16 ep_wMaxPacketSize; */ > + 0x00, /* u8 ep_bInterval; */ > + > + /* Bulk-Out endpoint */ > + 0x07, /* u8 ep_bLength; */ > + /* u8 ep_bDescriptorType; Endpoint */ > + USB_DT_ENDPOINT, > + /* u8 ep_bEndpointAddress; OUT Endpoint 3 */ > + CCID_BULK_OUT_EP, > + 0x02, /* u8 ep_bmAttributes; Bulk */ > + 0x40, 0x00, /* u16 ep_wMaxPacketSize; */ > + 0x00, /* u8 ep_bInterval; */ > + > +}; > + > +static bool ccid_has_pending_answers(USBCCIDState *s) > +{ > + return s->pending_answers_num> 0; > +} > + > +static void ccid_clear_pending_answers(USBCCIDState *s) > +{ > + s->pending_answers_num = 0; > + s->pending_answers_start = 0; > + s->pending_answers_end = 0; > +} > + > +static void ccid_print_pending_answers(USBCCIDState *s) > +{ > +#ifdef DEBUG_CCID > + Answer *answer; > + int i, count; > + > + printf("usb-ccid: pending answers:"); > + if (!ccid_has_pending_answers(s)) { > + printf(" empty\n"); > + return; > + } > + for (i = s->pending_answers_start, count=s->pending_answers_num ; > + count> 0; count--, i++) { > + answer =&s->pending_answers[i % PENDING_ANSWERS_NUM]; > + if (count == 1) { > + printf("%d:%d\n", answer->slot, answer->seq); > + } else { > + printf("%d:%d,", answer->slot, answer->seq); > + } > + } > +#endif > It's usually better to use a dprintf() instead of an #if block like this. You'll be much more likely to notice when the debug code breaks which tends to happen over time. > +} > + > +static void ccid_add_pending_answer(USBCCIDState *s, CCID_Header *hdr) > +{ > + Answer* answer; > + > + assert(s->pending_answers_num++< PENDING_ANSWERS_NUM); > Having side effects in an assert statement is a very bad idea. > + answer =&s->pending_answers[(s->pending_answers_end++) % PENDING_ANSWERS_NUM]; > + answer->slot = hdr->bSlot; > + answer->seq = hdr->bSeq; > + ccid_print_pending_answers(s); > +} > + > +static void ccid_remove_pending_answer(USBCCIDState *s, > + uint8_t *slot, uint8_t *seq) > +{ > + Answer *answer; > + > + assert(s->pending_answers_num--> 0); > + answer =&s->pending_answers[(s->pending_answers_start++) % PENDING_ANSWERS_NUM]; > + *slot = answer->slot; > + *seq = answer->seq; > + ccid_print_pending_answers(s); > +} > + > +static void ccid_bulk_in_clear(USBCCIDState *s) > +{ > + s->bulk_in_pending_start = 0; > + s->bulk_in_pending_end = 0; > + s->bulk_in_pending_num = 0; > +} > + > +static void ccid_bulk_in_release(USBCCIDState *s) > +{ > + assert(s->current_bulk_in != NULL); > + s->current_bulk_in->pos = 0; > + s->current_bulk_in = NULL; > +} > + > +static void ccid_bulk_in_get(USBCCIDState *s) > +{ > + if (s->current_bulk_in != NULL || s->bulk_in_pending_num == 0) { > + return; > + } > + assert(s->bulk_in_pending_num> 0); > + s->bulk_in_pending_num--; > + s->current_bulk_in =&s->bulk_in_pending[ > + (s->bulk_in_pending_start++) % BULK_IN_PENDING_NUM]; > +} > + > +static void* ccid_reserve_recv_buf(USBCCIDState* s, uint16_t len) > +{ > + BulkIn* bulk_in; > + > + DPRINTF(s, 4, "%s: QUEUE: reserve %d bytes\n", __func__, len); > + > + /* look for an existing element */ > + if (len> BULK_IN_BUF_SIZE) { > + printf("usb-ccid.c: %s: len larger then max (%d>%d). discarding message.\n", > + __func__, len, BULK_IN_BUF_SIZE); > + return NULL; > + } > + if (s->bulk_in_pending_num>= BULK_IN_PENDING_NUM) { > + printf("usb-ccid.c: %s: No free bulk_in buffers. discarding message.\n", > + __func__); > + return NULL; > + } > Looks like these are guest triggable printfs. You should avoid that. > + bulk_in =&s->bulk_in_pending[(s->bulk_in_pending_end++) % BULK_IN_PENDING_NUM]; > + s->bulk_in_pending_num++; > + bulk_in->len = len; > + return bulk_in->data; > +} > + > +static void ccid_reset(USBCCIDState *s) > +{ > + ccid_bulk_in_clear(s); > + ccid_clear_pending_answers(s); > +} > + > +static void ccid_detach(USBCCIDState *s) > +{ > + ccid_reset(s); > +} > + > +static void ccid_handle_reset(USBDevice *dev) > +{ > + USBCCIDState *s = DO_UPCAST(USBCCIDState, dev, dev); > + > + DPRINTF(s, 1, "Reset\n"); > + > + ccid_reset(s); > +} > + > +static int ccid_handle_control(USBDevice *dev, int request, int value, > + int index, int length, uint8_t *data) > +{ > + USBCCIDState *s = DO_UPCAST(USBCCIDState, dev, dev); > + int ret = 0; > + > + DPRINTF(s, 1, "got control %x, value %x\n",request, value); > + switch (request) { > + case DeviceRequest | USB_REQ_GET_STATUS: > + data[0] = (0<< USB_DEVICE_SELF_POWERED) | > + (dev->remote_wakeup<< USB_DEVICE_REMOTE_WAKEUP); > + data[1] = 0x00; > + ret = 2; > + break; > + case DeviceOutRequest | USB_REQ_CLEAR_FEATURE: > + if (value == USB_DEVICE_REMOTE_WAKEUP) { > + dev->remote_wakeup = 0; > + } else { > + goto fail; > + } > + ret = 0; > + break; > + case DeviceOutRequest | USB_REQ_SET_FEATURE: > + if (value == USB_DEVICE_REMOTE_WAKEUP) { > + dev->remote_wakeup = 1; > + } else { > + goto fail; > + } > + ret = 0; > + break; > + case DeviceOutRequest | USB_REQ_SET_ADDRESS: > + dev->addr = value; > + ret = 0; > + break; > + case DeviceRequest | USB_REQ_GET_DESCRIPTOR: > + switch(value>> 8) { > + case USB_DT_DEVICE: > + memcpy(data, qemu_ccid_dev_descriptor, > + sizeof(qemu_ccid_dev_descriptor)); > + ret = sizeof(qemu_ccid_dev_descriptor); > + break; > + case USB_DT_CONFIG: > + memcpy(data, qemu_ccid_config_descriptor, > + sizeof(qemu_ccid_config_descriptor)); > + ret = sizeof(qemu_ccid_config_descriptor); > + break; > + case USB_DT_STRING: > + switch(value& 0xff) { > + case 0: > + /* language ids */ > + data[0] = 4; > + data[1] = 3; > + data[2] = 0x09; > + data[3] = 0x04; > + ret = 4; > + break; > + case 1: > + /* vendor description */ > + ret = set_usb_string(data, CCID_VENDOR_DESCRIPTION); > + break; > + case 2: > + /* product description */ > + ret = set_usb_string(data, CCID_PRODUCT_DESCRIPTION); > + break; > + case 3: > + /* serial number */ > + ret = set_usb_string(data, CCID_SERIAL_NUMBER_STRING); > + break; > + case 4: > + /* interface name */ > + ret = set_usb_string(data, CCID_INTERFACE_NAME); > + break; > + default: > + goto fail; > + } > + break; > + default: > + goto fail; > + } > + break; > + case DeviceRequest | USB_REQ_GET_CONFIGURATION: > + data[0] = 1; > + ret = 1; > + break; > + case DeviceOutRequest | USB_REQ_SET_CONFIGURATION: > + /* Only one configuration - we just ignore the request */ > + ret = 0; > + break; > + case DeviceRequest | USB_REQ_GET_INTERFACE: > + data[0] = 0; > + ret = 1; > + break; > + case InterfaceOutRequest | USB_REQ_SET_INTERFACE: > + ret = 0; > + break; > + case EndpointOutRequest | USB_REQ_CLEAR_FEATURE: > + ret = 0; > + break; > + > + /* Class specific requests. */ > + case InterfaceOutClass | CCID_CONTROL_ABORT: > + DPRINTF(s, 1, "ccid_control abort UNIMPLEMENTED\n"); > + ret = USB_RET_STALL; > + break; > + case InterfaceInClass | CCID_CONTROL_GET_CLOCK_FREQUENCIES: > + DPRINTF(s, 1, "ccid_control get clock frequencies UNIMPLEMENTED\n"); > + ret = USB_RET_STALL; > + break; > + case InterfaceInClass | CCID_CONTROL_GET_DATA_RATES: > + DPRINTF(s, 1, "ccid_control get data rates UNIMPLEMENTED\n"); > + ret = USB_RET_STALL; > + break; > + default: > + fail: > + DPRINTF(s, 1, "got unsupported/bogus control %x, value %x\n", request, value); > + ret = USB_RET_STALL; > + break; > + } > + return ret; > +} > + > +static bool ccid_card_inserted(USBCCIDState *s) > +{ > + return s->bmSlotICCState& SLOT_0_STATE_MASK; > +} > + > +static uint8_t ccid_card_status(USBCCIDState *s) > +{ > + return ccid_card_inserted(s) > + ? (s->powered ? > + ICC_STATUS_PRESENT_ACTIVE > + : ICC_STATUS_PRESENT_INACTIVE > + ) > + : ICC_STATUS_NOT_PRESENT; > +} > + > +static uint8_t ccid_calc_status(USBCCIDState *s) > +{ > + /* page 55, 6.2.6, calculation of bStatus from bmICCStatus and > + bmCommandStatus > + */ > + uint8_t ret = ccid_card_status(s) | (s->bmCommandStatus<< 6); > + DPRINTF(s, 4, "status = %d\n", ret); > + return ret; > +} > + > +static void ccid_reset_error_status(USBCCIDState* s) > +{ > + s->bError = ERROR_CMD_NOT_SUPPORTED; > + s->bmCommandStatus = COMMAND_STATUS_NO_ERROR; > +} > + > +static void ccid_write_slot_status(USBCCIDState* s, CCID_Header* recv) > +{ > + CCID_SlotStatus *h = ccid_reserve_recv_buf(s, sizeof(CCID_SlotStatus)); > + if (h == NULL) { > + return; > + } > + h->b.hdr.bMessageType = CCID_MESSAGE_TYPE_RDR_to_PC_SlotStatus; > + h->b.hdr.dwLength = 0; > + h->b.hdr.bSlot = recv->bSlot; > + h->b.hdr.bSeq = recv->bSeq; > + h->b.bStatus = ccid_calc_status(s); > + h->b.bError = s->bError; > + h->bClockStatus = CLOCK_STATUS_RUNNING; > + ccid_reset_error_status(s); > +} > + > +static void ccid_write_parameters(USBCCIDState* s, CCID_Header* recv) > +{ > + CCID_Parameter *h; > + uint32_t len = s->ulProtocolDataStructureSize; > + > + h = ccid_reserve_recv_buf(s, sizeof(CCID_Parameter) + len); > + if (h == NULL) { > + return; > + } > + h->b.hdr.bMessageType = CCID_MESSAGE_TYPE_RDR_to_PC_Parameters; > + h->b.hdr.dwLength = 0; > + h->b.hdr.bSlot = recv->bSlot; > + h->b.hdr.bSeq = recv->bSeq; > + h->b.bStatus = ccid_calc_status(s); > + h->b.bError = s->bError; > + h->bProtocolNum = s->bProtocolNum; > + memcpy(h->abProtocolDataStructure, s->abProtocolDataStructure, len); > + ccid_reset_error_status(s); > +} > + > +static void ccid_write_data_block( > + USBCCIDState* s, uint8_t slot, uint8_t seq, > + const uint8_t* data, uint32_t len) > +{ > + CCID_DataBlock *p = ccid_reserve_recv_buf(s, sizeof(*p) + len); > + > + if (p == NULL) { > + return; > + } > + p->b.hdr.bMessageType = CCID_MESSAGE_TYPE_RDR_to_PC_DataBlock; > + p->b.hdr.dwLength = cpu_to_le32(len); > + p->b.hdr.bSlot = slot; > + p->b.hdr.bSeq = seq; > + p->b.bStatus = ccid_calc_status(s); > + p->b.bError = s->bError; > +#ifdef DEBUG_CCID > + if (p->b.bError) { > + DPRINTF(s, 4, "error %d", p->b.bError); > + } > +#endif > + memcpy(p->abData, data, len); > + ccid_reset_error_status(s); > +} > + > +static void ccid_write_data_block_answer(USBCCIDState* s, > + const uint8_t* data, uint32_t len) > +{ > + uint8_t seq; > + uint8_t slot; > + > + if (!ccid_has_pending_answers(s)) { > + abort(); > + } > + ccid_remove_pending_answer(s,&slot,&seq); > + ccid_write_data_block(s, slot, seq, data, len); > +} > + > +static void ccid_write_data_block_atr(USBCCIDState* s, CCID_Header* recv) > +{ > + const uint8_t *atr = NULL; > + uint32_t len = 0; > + > + if (s->card) { > + atr = s->cardinfo->get_atr(s->card,&len); > + } > + ccid_write_data_block(s, recv->bSlot, recv->bSeq, atr, len); > +} > + > +static void ccid_set_parameters(USBCCIDState *s, CCID_Header *recv) > +{ > + CCID_SetParameter *ph = (CCID_SetParameter *) recv; > + uint32_t len = 0; > + if (ph->bProtocolNum == 0) { > + len = 5; > + } > + if (ph->bProtocolNum == 1) { > + len = 7; > + } > + if (len == 0) { > + s->bmCommandStatus = COMMAND_STATUS_FAILED; > + s->bError = 7; /* Protocol invalid or not supported */ > + return; > + } > + s->bProtocolNum = ph->bProtocolNum; > + memcpy(s->abProtocolDataStructure, ph->abProtocolDataStructure, len); > + s->ulProtocolDataStructureSize = len; > + DPRINTF(s, 1, "%s: using len %d\n", __func__, len); > +} > + > +/* must be 5 bytes for T=0, 7 bytes for T=1 > + * See page 52 */ > +static const uint8_t abDefaultProtocolDataStructure[7] = > + { 0x77, 0x00, 0x00, 0x00, 0x00, 0xfe /*IFSC*/, 0x00 /*NAD*/ }; > + > +static void ccid_reset_parameters(USBCCIDState *s) > +{ > + uint32_t len = sizeof(abDefaultProtocolDataStructure); > + > + s->bProtocolNum = 1; /* T=1 */ > + s->ulProtocolDataStructureSize = len; > + memcpy(s->abProtocolDataStructure, abDefaultProtocolDataStructure, len); > +} > + > +static void ccid_report_error_failed(USBCCIDState *s, uint8_t error) > +{ > + s->bmCommandStatus = COMMAND_STATUS_FAILED; > + s->bError = error; > +} > + > +/* NOTE: only a single slot is supported (SLOT_0) > + */ > +static void ccid_on_slot_change(USBCCIDState* s, bool full) > +{ > + /* RDR_to_PC_NotifySlotChange, 6.3.1 page 56 > + */ > + uint8_t current = s->bmSlotICCState; > + if (full) { > + s->bmSlotICCState |= SLOT_0_STATE_MASK; > + } else { > + s->bmSlotICCState&= ~SLOT_0_STATE_MASK; > + } > + if (current != s->bmSlotICCState) { > + s->bmSlotICCState |= SLOT_0_CHANGED_MASK; > + } > + s->notify_slot_change = true; > +} > + > +static void ccid_write_data_block_error( > + USBCCIDState *s, uint8_t slot, uint8_t seq) > +{ > + ccid_write_data_block(s, slot, seq, NULL, 0); > +} > + > +static void ccid_on_apdu_from_guest(USBCCIDState *s, CCID_XferBlock *recv) > +{ > + uint32_t len; > + > + if (ccid_card_status(s) != ICC_STATUS_PRESENT_ACTIVE) { > + DPRINTF(s, 1, "usb-ccid: not sending apdu to client, no card connected\n"); > + ccid_write_data_block_error(s, recv->hdr.bSlot, recv->hdr.bSeq); > + return; > + } > + len = le32_to_cpu(recv->hdr.dwLength); > + DPRINTF(s, 1, "%s: seq %d, len %d\n", __FUNCTION__, > + recv->hdr.bSeq, len); > + ccid_add_pending_answer(s, (CCID_Header*)recv); > + if (s->card) { > + s->cardinfo->apdu_from_guest(s->card, recv->abData, len); > + } else { > + printf("warning: discarded apdu\n"); > Guest triggerable printf? > + } > +} > + > +/* handle a single USB_TOKEN_OUT, return value returned to guest. > + * 0 - all ok > + * USB_RET_STALL - failed to handle packet */ > +static int ccid_handle_bulk_out(USBCCIDState *s, USBPacket *p) > +{ > + CCID_Header* ccid_header; > + > + if (p->len + s->bulk_out_pos> BULK_OUT_DATA_SIZE) { > + return USB_RET_STALL; > + } > + ccid_header = (CCID_Header*)s->bulk_out_data; > + memcpy(s->bulk_out_data + s->bulk_out_pos, p->data, p->len); > + s->bulk_out_pos += p->len; > + if (p->len == 64) { > + DPRINTF(s, 4, "usb-ccid: bulk_in: expecting more packets (%d/%d)\n", > + p->len, ccid_header->dwLength); > + return 0; > + } > + if (s->bulk_out_pos< 10) { > + DPRINTF(s, 1, "%s: bad USB_TOKEN_OUT length, should be at least 10 bytes\n", __func__); > + } else { > + DPRINTF(s, 3, "%s %x\n", __func__, ccid_header->bMessageType); > + switch (ccid_header->bMessageType) { > + case CCID_MESSAGE_TYPE_PC_to_RDR_GetSlotStatus: > + ccid_write_slot_status(s, ccid_header); > + break; > + case CCID_MESSAGE_TYPE_PC_to_RDR_IccPowerOn: > + DPRINTF(s, 1, "PowerOn: %d\n", > + ((CCID_IccPowerOn*)(ccid_header))->bPowerSelect); > + s->powered = true; > + if (!ccid_card_inserted(s)) { > + ccid_report_error_failed(s, ERROR_ICC_MUTE); > + } > + /* atr is written regardless of error. */ > + ccid_write_data_block_atr(s, ccid_header); > + break; > + case CCID_MESSAGE_TYPE_PC_to_RDR_IccPowerOff: > + DPRINTF(s, 1, "PowerOff\n"); > + ccid_reset_error_status(s); > + s->powered = false; > + ccid_write_slot_status(s, ccid_header); > + break; > + case CCID_MESSAGE_TYPE_PC_to_RDR_XfrBlock: > + ccid_on_apdu_from_guest(s, (CCID_XferBlock*)s->bulk_out_data); > + break; > + case CCID_MESSAGE_TYPE_PC_to_RDR_SetParameters: > + ccid_reset_error_status(s); > + ccid_set_parameters(s, ccid_header); > + ccid_write_parameters(s, ccid_header); > + break; > + case CCID_MESSAGE_TYPE_PC_to_RDR_ResetParameters: > + ccid_reset_error_status(s); > + ccid_reset_parameters(s); > + ccid_write_parameters(s, ccid_header); > + break; > + case CCID_MESSAGE_TYPE_PC_to_RDR_GetParameters: > + ccid_reset_error_status(s); > + ccid_write_parameters(s, ccid_header); > + break; > + default: > + DPRINTF(s, 1, "handle_data: ERROR: unhandled message type %Xh\n", > + ccid_header->bMessageType); > + /* the caller is expecting the device to respond, tell it we > + * do't support the operation */ > + ccid_report_error_failed(s, ERROR_CMD_NOT_SUPPORTED); > + ccid_write_slot_status(s, ccid_header); > + break; > + } > + } > + s->bulk_out_pos = 0; > + return 0; > +} > + > +static int ccid_bulk_in_copy_to_guest(USBCCIDState *s, uint8_t *data, int len) > +{ > + int ret = 0; > + > + assert(len>0); > + ccid_bulk_in_get(s); > + if (s->current_bulk_in != NULL) { > + ret = MIN(s->current_bulk_in->len - s->current_bulk_in->pos, len); > + memcpy(data, s->current_bulk_in->data + s->current_bulk_in->pos, ret); > + s->current_bulk_in->pos += ret; > + if (s->current_bulk_in->pos == s->current_bulk_in->len) { > + ccid_bulk_in_release(s); > + } > + } else { > + ret = USB_RET_NAK; /* return when device has no data - usb 2.0 spec Table 8-4 */ > + } > + if (ret> 0) { > + DPRINTF(s, 3, "%s: %d/%d req/act to guest (BULK_IN)\n", __func__, len, ret); > + } > + if (ret != USB_RET_NAK&& ret< len) { > + DPRINTF(s, 1, "%s: returning short (EREMOTEIO) %d< %d\n", __func__, ret, len); > + } > + return ret; > +} > + > +static int ccid_handle_data(USBDevice *dev, USBPacket *p) > +{ > + USBCCIDState *s = DO_UPCAST(USBCCIDState, dev, dev); > + int ret = 0; > + uint8_t *data = p->data; > + int len = p->len; > + > + switch (p->pid) { > + case USB_TOKEN_OUT: > + ret = ccid_handle_bulk_out(s, p); > + break; > + > + case USB_TOKEN_IN: > + switch (p->devep& 0xf) { > + case CCID_BULK_IN_EP: > + if (!len) { > + ret = USB_RET_NAK; > + } else { > + ret = ccid_bulk_in_copy_to_guest(s, data, len); > + } > + break; > + case CCID_INT_IN_EP: > + if (s->notify_slot_change) { > + /* page 56, RDR_to_PC_NotifySlotChange */ > + data[0] = CCID_MESSAGE_TYPE_RDR_to_PC_NotifySlotChange; > + data[1] = s->bmSlotICCState; > + ret = 2; > + s->notify_slot_change = false; > + s->bmSlotICCState&= ~SLOT_0_CHANGED_MASK; > + DPRINTF(s, 2, "handle_data: int_in: notify_slot_change %X, requested len %d\n", > + s->bmSlotICCState, len); > + } > + break; > + default: > + DPRINTF(s, 1, "Bad endpoint\n"); > + break; > + } > + break; > + default: > + DPRINTF(s, 1, "Bad token\n"); > + ret = USB_RET_STALL; > + break; > + } > + > + return ret; > +} > + > +static void ccid_handle_destroy(USBDevice *dev) > +{ > + USBCCIDState *s = DO_UPCAST(USBCCIDState, dev, dev); > + > + ccid_bulk_in_clear(s); > +} > + > +static void ccid_flush_pending_answers(USBCCIDState *s) { > + while (ccid_has_pending_answers(s)) { > + ccid_write_data_block_answer(s, NULL, 0); > + } > +} > + > +static Answer *ccid_peek_next_answer(USBCCIDState *s) > +{ > + return s->pending_answers_num == 0 > + ? NULL > + :&s->pending_answers[s->pending_answers_start % PENDING_ANSWERS_NUM]; > +} > + > +static void ccid_bus_dev_print(Monitor *mon, DeviceState *qdev, int indent) > +{ > + CCIDCardState *card = DO_UPCAST(CCIDCardState, qdev, qdev); > + CCIDCardInfo *info = DO_UPCAST(CCIDCardInfo, qdev, qdev->info); > + > + if (info->print) { > + info->print(mon, card, indent); > + } > +} > + > +struct CCIDBus { > + BusState qbus; > +}; > + > +static struct BusInfo ccid_bus_info = { > + .name = "ccid-bus", > + .size = sizeof(CCIDBus), > + .print_dev = ccid_bus_dev_print, > + .props = (Property[]) { > + DEFINE_PROP_UINT32("slot", struct CCIDCardState, slot, 0), > + DEFINE_PROP_END_OF_LIST(), > + } > +}; > + > +static CCIDBus *ccid_bus_new(DeviceState *dev) > +{ > + CCIDBus *bus; > + > + bus = FROM_QBUS(CCIDBus, qbus_create(&ccid_bus_info, dev, NULL)); > + bus->qbus.allow_hotplug = 1; > + > + return bus; > +} > + > +void ccid_card_send_apdu_to_guest(CCIDCardState *card, uint8_t* apdu, uint32_t len) > +{ > + USBCCIDState *s = DO_UPCAST(USBCCIDState, dev.qdev, card->qdev.parent_bus->parent); > + Answer *answer; > + > + if (!ccid_has_pending_answers(s)) { > + DPRINTF(s, 1, "CCID ERROR: got an APDU without pending answers\n"); > + return; > + } > + s->bmCommandStatus = COMMAND_STATUS_NO_ERROR; > + answer = ccid_peek_next_answer(s); > + if (answer == NULL) { > + abort(); > + } > + DPRINTF(s, 1, "APDU returned to guest %d (answer seq %d, slot %d)\n", > + len, answer->seq, answer->slot); > + ccid_write_data_block_answer(s, apdu, len); > +} > + > +void ccid_card_card_removed(CCIDCardState *card) > +{ > + USBCCIDState *s = DO_UPCAST(USBCCIDState, dev.qdev, card->qdev.parent_bus->parent); > + > + ccid_on_slot_change(s, false); > + ccid_flush_pending_answers(s); > + ccid_reset(s); > +} > + > +int ccid_card_ccid_attach(CCIDCardState *card) > +{ > + USBCCIDState *s = DO_UPCAST(USBCCIDState, dev.qdev, card->qdev.parent_bus->parent); > + > + DPRINTF(s, 1, "CCID Attach\n"); > + if (s->migration_state == MIGRATION_MIGRATED) { > + s->migration_state = MIGRATION_NONE; > + } > + return 0; > +} > + > +void ccid_card_ccid_detach(CCIDCardState *card) > +{ > + USBCCIDState *s = DO_UPCAST(USBCCIDState, dev.qdev, card->qdev.parent_bus->parent); > + > + DPRINTF(s, 1, "CCID Detach\n"); > + if (ccid_card_inserted(s)) { > + ccid_on_slot_change(s, false); > + } > + ccid_detach(s); > +} > + > +void ccid_card_card_error(CCIDCardState *card, uint64_t error) > +{ > + USBCCIDState *s = DO_UPCAST(USBCCIDState, dev.qdev, card->qdev.parent_bus->parent); > + > + s->bmCommandStatus = COMMAND_STATUS_FAILED; > + s->last_answer_error = error; > + DPRINTF(s, 1, "VSC_Error: %lX\n", s->last_answer_error); > + /* TODO: these error's should be more verbose and propogated to the guest. > + * */ > + ccid_write_data_block_answer(s, NULL, 0); > +} > + > +void ccid_card_card_inserted(CCIDCardState *card) > +{ > + USBCCIDState *s = DO_UPCAST(USBCCIDState, dev.qdev, card->qdev.parent_bus->parent); > + > + s->bmCommandStatus = COMMAND_STATUS_NO_ERROR; > + ccid_flush_pending_answers(s); > + ccid_on_slot_change(s, true); > +} > + > +static int ccid_card_exit(DeviceState *qdev) > +{ > + int ret = 0; > + CCIDCardState *card = DO_UPCAST(CCIDCardState, qdev, qdev); > + CCIDCardInfo *info = DO_UPCAST(CCIDCardInfo, qdev, qdev->info); > + USBCCIDState *s = DO_UPCAST(USBCCIDState, dev.qdev, card->qdev.parent_bus->parent); > + > + if (ccid_card_inserted(s)) { > + ccid_card_card_removed(card); > + } > + if (info->exitfn) { > + ret = info->exitfn(card); > + } > + s->card = NULL; > + s->cardinfo = NULL; > + return ret; > +} > + > +static int ccid_card_init(DeviceState *qdev, DeviceInfo *base) > +{ > + CCIDCardState *card = DO_UPCAST(CCIDCardState, qdev, qdev); > + CCIDCardInfo *info = DO_UPCAST(CCIDCardInfo, qdev, base); > + USBCCIDState *s = DO_UPCAST(USBCCIDState, dev.qdev, card->qdev.parent_bus->parent); > + int ret = 0; > + > + if (card->slot != 0) { > + fprintf(stderr, "Warning: usb-ccid supports one slot, can't add %d", > + card->slot); > I think error_report is more appropriate here. That makes it visible over QMP. > + return -1; > + } > + if (s->card != NULL) { > + fprintf(stderr, "Warning: usb-ccid card already full, not adding\n"); > + return -1; > + } > + ret = info->initfn ? info->initfn(card) : ret; > + if (ret == 0) { > + s->card = card; > + s->cardinfo = info; > + } > + return ret; > +} > + > +void ccid_card_qdev_register(CCIDCardInfo *card) > +{ > + card->qdev.bus_info =&ccid_bus_info; > + card->qdev.init = ccid_card_init; > + card->qdev.exit = ccid_card_exit; > + qdev_register(&card->qdev); > +} > + > +static int ccid_initfn(USBDevice *dev) > +{ > + USBCCIDState *s = DO_UPCAST(USBCCIDState, dev, dev); > + > + s->bus = ccid_bus_new(&dev->qdev); > + s->card = NULL; > + s->cardinfo = NULL; > + s->migration_state = MIGRATION_NONE; > + s->migration_target_ip = 0; > + s->migration_target_port = 0; > + s->dev.speed = USB_SPEED_FULL; > + s->notify_slot_change = false; > + s->powered = true; > + s->pending_answers_num = 0; > + s->last_answer_error = 0; > + s->bulk_in_pending_start = 0; > + s->bulk_in_pending_end = 0; > + s->current_bulk_in = NULL; > + ccid_reset_error_status(s); > + s->bulk_out_pos = 0; > + ccid_reset_parameters(s); > + ccid_reset(s); > + return 0; > +} > + > +#ifdef ENABLE_MIGRATION > Why is migration conditional? > +static int ccid_post_load(void *opaque, int version_id) > +{ > + USBCCIDState *s = opaque; > + > + // This must be done after usb_device_attach, which sets state to ATTACHED, > + // while it must be DEFAULT in order to accept packets (like it is after > + // reset, but reset will reset our addr and call our reset handler which > + // may change state, and we don't want to do that when migrating). > + s->dev.state = s->state_vmstate; > + return 0; > +} > + > +static void ccid_pre_save(void *opaque) > +{ > + USBCCIDState *s = opaque; > + > + s->state_vmstate = s->dev.state; > + if (s->dev.attached) { > + // migrating an open device, ignore reconnection CHR_EVENT to avoid an > + // erronous detach. > + s->migration_state = MIGRATION_MIGRATED; > + } > +} > + > +static VMStateDescription bulk_in_vmstate = { > + .name = "CCID BulkIn state", > + .version_id = 1, > + .minimum_version_id = 1, > + .fields = (VMStateField []) { > + VMSTATE_BUFFER(data, BulkIn), > + VMSTATE_UINT32(len, BulkIn), > + VMSTATE_UINT32(pos, BulkIn), > + VMSTATE_END_OF_LIST() > + } > +}; > + > +static VMStateDescription answer_vmstate = { > + .name = "CCID Answer state", > + .version_id = 1, > + .minimum_version_id = 1, > + .fields = (VMStateField []) { > + VMSTATE_UINT8(slot, Answer), > + VMSTATE_UINT8(seq, Answer), > + VMSTATE_END_OF_LIST() > + } > +}; > + > +static VMStateDescription usb_device_vmstate = { > + .name = "usb_device", > + .version_id = 1, > + .minimum_version_id = 1, > + .fields = (VMStateField []) { > + VMSTATE_UINT8(addr, USBDevice), > + VMSTATE_BUFFER(setup_buf, USBDevice), > + VMSTATE_BUFFER(data_buf, USBDevice), > + VMSTATE_END_OF_LIST() > + } > +}; > + > +static VMStateDescription ccid_vmstate = { > + .name = CCID_DEV_NAME, > + .version_id = 1, > + .minimum_version_id = 1, > + .post_load = ccid_post_load, > + .pre_save = ccid_pre_save, > + .fields = (VMStateField []) { > + VMSTATE_STRUCT(dev, USBCCIDState, 1, usb_device_vmstate, USBDevice), > + VMSTATE_UINT8(debug, USBCCIDState), > + VMSTATE_BUFFER(bulk_out_data, USBCCIDState), > + VMSTATE_UINT32(bulk_out_pos, USBCCIDState), > + VMSTATE_UINT8(bmSlotICCState, USBCCIDState), > + VMSTATE_UINT8(powered, USBCCIDState), > + VMSTATE_UINT8(notify_slot_change, USBCCIDState), > + VMSTATE_UINT64(last_answer_error, USBCCIDState), > + VMSTATE_UINT8(bError, USBCCIDState), > + VMSTATE_UINT8(bmCommandStatus, USBCCIDState), > + VMSTATE_UINT8(bProtocolNum, USBCCIDState), > + VMSTATE_BUFFER(abProtocolDataStructure, USBCCIDState), > + VMSTATE_UINT32(ulProtocolDataStructureSize, USBCCIDState), > + VMSTATE_STRUCT_ARRAY(bulk_in_pending, USBCCIDState, > + BULK_IN_PENDING_NUM, 1, bulk_in_vmstate, BulkIn), > + VMSTATE_UINT32(bulk_in_pending_start, USBCCIDState), > + VMSTATE_UINT32(bulk_in_pending_end, USBCCIDState), > + VMSTATE_STRUCT_ARRAY(pending_answers, USBCCIDState, > + PENDING_ANSWERS_NUM, 1, answer_vmstate, Answer), > + VMSTATE_UINT32(pending_answers_num, USBCCIDState), > + VMSTATE_UINT8(migration_state, USBCCIDState), > + VMSTATE_UINT32(state_vmstate, USBCCIDState), > + VMSTATE_END_OF_LIST() > + } > +}; > +#endif // ENABLE_MIGRATION > + > +static struct USBDeviceInfo ccid_info = { > + .product_desc = "QEMU USB CCID", > + .qdev.name = CCID_DEV_NAME, > + .qdev.size = sizeof(USBCCIDState), > + .init = ccid_initfn, > + .handle_packet = usb_generic_handle_packet, > + .handle_reset = ccid_handle_reset, > + .handle_control = ccid_handle_control, > + .handle_data = ccid_handle_data, > + .handle_destroy = ccid_handle_destroy, > + .usbdevice_name = "ccid", > + .qdev.props = (Property[]) { > + DEFINE_PROP_UINT8("debug", USBCCIDState, debug, 0), > + DEFINE_PROP_END_OF_LIST(), > + }, > +#ifdef ENABLE_MIGRATION > + .qdev.vmsd =&ccid_vmstate, > +#endif > +}; > + > + > +static void ccid_register_devices(void) > +{ > + usb_qdev_register(&ccid_info); > +} > +device_init(ccid_register_devices) > Regards, Anthony Liguori ^ permalink raw reply [flat|nested] 35+ messages in thread
* Re: [Qemu-devel] [PATCH 1/7] usb-ccid: add CCID bus 2011-01-25 14:10 ` Anthony Liguori @ 2011-01-25 16:10 ` Alon Levy 0 siblings, 0 replies; 35+ messages in thread From: Alon Levy @ 2011-01-25 16:10 UTC (permalink / raw) To: Anthony Liguori; +Cc: qemu-devel On Tue, Jan 25, 2011 at 08:10:11AM -0600, Anthony Liguori wrote: This review is for v14 - there have been some changes in v15, perhaps you'll want to go over it. This is the change log: v14-v15 changes: * add patch with --enable-smartcard and --disable-smartcard and only disable ccid-card-emulated if nss not found. * add patch with description strings * s/libcaccard/libcacard/ in docs/ccid.txt > On 01/11/2011 02:38 AM, Alon Levy wrote: > >A CCID device is a smart card reader. It is a USB device, defined at [1]. > >This patch introduces the usb-ccid device that is a ccid bus. Next patches will > >introduce two card types to use it, a passthru card and an emulated card. > > > > [1] http://www.usb.org/developers/devclass_docs/DWG_Smart-Card_CCID_Rev110. > > > >Signed-off-by: Alon Levy<alevy@redhat.com> > >--- > > Makefile.objs | 1 + > > configure | 6 + > > hw/ccid.h | 35 ++ > > hw/usb-ccid.c | 1355 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++ > > 4 files changed, 1397 insertions(+), 0 deletions(-) > > create mode 100644 hw/ccid.h > > create mode 100644 hw/usb-ccid.c > > > >diff --git a/Makefile.objs b/Makefile.objs > >index d6b3d60..7da4771 100644 > >--- a/Makefile.objs > >+++ b/Makefile.objs > >@@ -197,6 +197,7 @@ hw-obj-$(CONFIG_FDC) += fdc.o > > hw-obj-$(CONFIG_ACPI) += acpi.o acpi_piix4.o > > hw-obj-$(CONFIG_APM) += pm_smbus.o apm.o > > hw-obj-$(CONFIG_DMA) += dma.o > >+hw-obj-$(CONFIG_SMARTCARD) += usb-ccid.o > > > > # PPC devices > > hw-obj-$(CONFIG_OPENPIC) += openpic.o > >diff --git a/configure b/configure > >index 831a741..839980c 100755 > >--- a/configure > >+++ b/configure > >@@ -334,6 +334,7 @@ trace_backend="nop" > > trace_file="trace" > > spice="" > > rbd="" > >+smartcard="yes" > > > > # OS specific > > if check_define __linux__ ; then > >@@ -2441,6 +2442,7 @@ echo "Trace output file $trace_file-<pid>" > > echo "spice support $spice" > > echo "rbd support $rbd" > > echo "xfsctl support $xfs" > >+echo "smartcard support $smartcard" > > Device don't get printed out in configure because they aren't probed. A later patch (either in the reviewed series v14 or in the newer on list v15) adds nss, and that is probed for, I guess I'll leave it as nss support then (no need to mention the smartcard). > >diff --git a/hw/ccid.h b/hw/ccid.h > >new file mode 100644 > >index 0000000..af59070 > >--- /dev/null > >+++ b/hw/ccid.h > >@@ -0,0 +1,35 @@ > >+#ifndef __CCID_H__ > >+#define __CCID_H__ > > No copyright and this is not valid C (you're not support to use __ prefix). > will fix. > >+#include "qdev.h" > >+ > >+typedef struct CCIDCardState CCIDCardState; > >+typedef struct CCIDCardInfo CCIDCardInfo; > >+ > >+struct CCIDCardState { > >+ DeviceState qdev; > >+ uint32_t slot; // For future use with multiple slot reader. > > Don't use C99 comments. > will fix. > >+}; > >+ > >+struct CCIDCardInfo { > >+ DeviceInfo qdev; > >+ void (*print)(Monitor *mon, CCIDCardState *card, int indent); > >+ const uint8_t *(*get_atr)(CCIDCardState *card, uint32_t *len); > >+ void (*apdu_from_guest)(CCIDCardState *card, const uint8_t *apdu, uint32_t len); > >+ int (*exitfn)(CCIDCardState *card); > >+ int (*initfn)(CCIDCardState *card); > >+}; > >+ > >+void ccid_card_send_apdu_to_guest(CCIDCardState *card, uint8_t* apdu, uint32_t len); > >+void ccid_card_card_removed(CCIDCardState *card); > >+void ccid_card_card_inserted(CCIDCardState *card); > >+void ccid_card_card_error(CCIDCardState *card, uint64_t error); > >+void ccid_card_qdev_register(CCIDCardInfo *card); > >+ > >+/* support guest visible insertion/removal of ccid devices based on actual > >+ * devices connected/removed. Called by card implementation (passthru, local) */ > >+int ccid_card_ccid_attach(CCIDCardState *card); > >+void ccid_card_ccid_detach(CCIDCardState *card); > >+ > >+#endif // __CCID_H__ > >+ > >diff --git a/hw/usb-ccid.c b/hw/usb-ccid.c > >new file mode 100644 > >index 0000000..58f69a6 > >--- /dev/null > >+++ b/hw/usb-ccid.c > >@@ -0,0 +1,1355 @@ > >+/* > >+ * CCID Device emulation > >+ * > >+ * Based on usb-serial.c: > >+ * Copyright (c) 2006 CodeSourcery. > >+ * Copyright (c) 2008 Samuel Thibault<samuel.thibault@ens-lyon.org> > >+ * Written by Paul Brook, reused for FTDI by Samuel Thibault, > >+ * Reused for CCID by Alon Levy. > >+ * Contributed to by Robert Relyea > >+ * Copyright (c) 2010 Red Hat. > >+ * > >+ * This code is licenced under the LGPL. > >+ */ > >+ > >+/* References: > >+ * > >+ * CCID Specification Revision 1.1 April 22nd 2005 > >+ * "Universal Serial Bus, Device Class: Smart Card" > >+ * Specification for Integrated Circuit(s) Cards Interface Devices > >+ * > >+ * Endianess note: from the spec (1.3) > >+ * "Fields that are larger than a byte are stored in little endian > >+ * > >+ * KNOWN BUGS > >+ * 1. remove/insert can sometimes result in removed state instead of inserted. > >+ * This is a result of the following: > >+ * symptom: dmesg shows ERMOTEIO (-121), pcscd shows -99. This happens > >+ * when we send a too short packet, seen in uhci-usb.c, resulting from > >+ * a urb requesting SPD and us returning a smaller packet. > >+ * Not sure which messages trigger this. > >+ * > >+ * Migration note: > >+ * > >+ * All the VMStateDescription's are left here for future use, but > >+ * not enabled right now since there is no support for USB migration. > >+ * > >+ * To enable define ENABLE_MIGRATION > >+ */ > >+ > >+#include "qemu-common.h" > >+#include "qemu-error.h" > >+#include "usb.h" > >+#include "monitor.h" > >+ > >+#include "hw/ccid.h" > >+ > >+//#define DEBUG_CCID > >+ > >+#define DPRINTF(s, lvl, fmt, ...) \ > >+do { if (lvl<= s->debug) { printf("usb-ccid: " fmt , ## __VA_ARGS__); } } while (0) > >+ > >+#define CCID_DEV_NAME "usb-ccid" > >+ > >+/* The two options for variable sized buffers: > >+ * make them constant size, for large enough constant, > >+ * or handle the migration complexity - VMState doesn't handle this case. > >+ * sizes are expected never to be exceeded, unless guest misbehaves. */ > >+#define BULK_OUT_DATA_SIZE 65536 > >+#define PENDING_ANSWERS_NUM 128 > >+ > >+#define BULK_IN_BUF_SIZE 384 > >+#define BULK_IN_PENDING_NUM 8 > >+ > >+#define InterfaceOutClass ((USB_DIR_OUT|USB_TYPE_CLASS|USB_RECIP_INTERFACE)<<8) > >+#define InterfaceInClass ((USB_DIR_IN |USB_TYPE_CLASS|USB_RECIP_INTERFACE)<<8) > >+ > >+#define CCID_CONTROL_ABORT 0x1 > >+#define CCID_CONTROL_GET_CLOCK_FREQUENCIES 0x2 > >+#define CCID_CONTROL_GET_DATA_RATES 0x3 > >+ > >+#define CCID_PRODUCT_DESCRIPTION "QEMU USB CCID" > >+#define CCID_VENDOR_DESCRIPTION "QEMU " QEMU_VERSION > > Note the exposing the version creates a backwards compatibility issue. > ok, so I guess I'll start an internal count. > >+#define CCID_INTERFACE_NAME "CCID Interface" > >+#define CCID_SERIAL_NUMBER_STRING "1" > >+/* Using Gemplus Vendor and Product id > >+ Effect on various drivers: > >+ * usbccid.sys (winxp, others untested) is a class driver so it doesn't care. > >+ * linux has a number of class drivers, but openct filters based on > >+ vendor/product (/etc/openct.conf under fedora), hence Gemplus. > >+ */ > >+#define CCID_VENDOR_ID 0x08e6 > >+#define CCID_PRODUCT_ID 0x4433 > >+#define CCID_DEVICE_VERSION 0x0000 > >+ > >+/* BULK_OUT messages from PC to Reader > >+ Defined in CCID Rev 1.1 6.1 (page 26) > >+ */ > >+#define CCID_MESSAGE_TYPE_PC_to_RDR_IccPowerOn 0x62 > >+#define CCID_MESSAGE_TYPE_PC_to_RDR_IccPowerOff 0x63 > >+#define CCID_MESSAGE_TYPE_PC_to_RDR_GetSlotStatus 0x65 > >+#define CCID_MESSAGE_TYPE_PC_to_RDR_XfrBlock 0x6f > >+#define CCID_MESSAGE_TYPE_PC_to_RDR_GetParameters 0x6c > >+#define CCID_MESSAGE_TYPE_PC_to_RDR_ResetParameters 0x6d > >+#define CCID_MESSAGE_TYPE_PC_to_RDR_SetParameters 0x61 > >+#define CCID_MESSAGE_TYPE_PC_to_RDR_Escape 0x6b > >+#define CCID_MESSAGE_TYPE_PC_to_RDR_IccClock 0x6e > >+#define CCID_MESSAGE_TYPE_PC_to_RDR_T0APDU 0x6a > >+#define CCID_MESSAGE_TYPE_PC_to_RDR_Secure 0x69 > >+#define CCID_MESSAGE_TYPE_PC_to_RDR_Mechanical 0x71 > >+#define CCID_MESSAGE_TYPE_PC_to_RDR_Abort 0x72 > >+#define CCID_MESSAGE_TYPE_PC_to_RDR_SetDataRateAndClockFrequency 0x73 > >+ > >+/* BULK_IN messages from Reader to PC > >+ Defined in CCID Rev 1.1 6.2 (page 48) > >+ */ > >+#define CCID_MESSAGE_TYPE_RDR_to_PC_DataBlock 0x80 > >+#define CCID_MESSAGE_TYPE_RDR_to_PC_SlotStatus 0x81 > >+#define CCID_MESSAGE_TYPE_RDR_to_PC_Parameters 0x82 > >+#define CCID_MESSAGE_TYPE_RDR_to_PC_Escape 0x83 > >+#define CCID_MESSAGE_TYPE_RDR_to_PC_DataRateAndClockFrequency 0x84 > >+ > >+/* INTERRUPT_IN messages from Reader to PC > >+ Defined in CCID Rev 1.1 6.3 (page 56) > >+ */ > >+#define CCID_MESSAGE_TYPE_RDR_to_PC_NotifySlotChange 0x50 > >+#define CCID_MESSAGE_TYPE_RDR_to_PC_HardwareError 0x51 > >+ > >+/* Endpoints for CCID - addresses are up to us to decide. > >+ To support slot insertion and removal we must have an interrupt in ep > >+ in addition we need a bulk in and bulk out ep > >+ 5.2, page 20 > >+ */ > >+#define CCID_INT_IN_EP 1 > >+#define CCID_BULK_IN_EP 2 > >+#define CCID_BULK_OUT_EP 3 > >+ > >+/* bmSlotICCState masks */ > >+#define SLOT_0_STATE_MASK 1 > >+#define SLOT_0_CHANGED_MASK 2 > >+ > >+/* Status codes that go in bStatus (see 6.2.6) */ > >+enum { > >+ ICC_STATUS_PRESENT_ACTIVE = 0, > >+ ICC_STATUS_PRESENT_INACTIVE, > >+ ICC_STATUS_NOT_PRESENT > >+}; > >+ > >+enum { > >+ COMMAND_STATUS_NO_ERROR = 0, > >+ COMMAND_STATUS_FAILED, > >+ COMMAND_STATUS_TIME_EXTENSION_REQUIRED > >+}; > >+ > >+/* Error codes that go in bError (see 6.2.6) > >+ */ > >+enum { > >+ ERROR_CMD_NOT_SUPPORTED = 0, > >+ ERROR_CMD_ABORTED = -1, > >+ ERROR_ICC_MUTE = -2, > >+ ERROR_XFR_PARITY_ERROR = -3, > >+ ERROR_XFR_OVERRUN = -4, > >+ ERROR_HW_ERROR = -5, > >+}; > >+ > >+/* 6.2.6 RDR_to_PC_SlotStatus definitions */ > >+enum { > >+ CLOCK_STATUS_RUNNING = 0, > >+ /* 0 - Clock Running, 1 - Clock stopped in State L, 2 - H, > >+ 3 - unkonwn state. rest are RFU > >+ */ > >+}; > >+ > >+typedef struct __attribute__ ((__packed__)) CCID_Header { > >+ uint8_t bMessageType; > >+ uint32_t dwLength; > >+ uint8_t bSlot; > >+ uint8_t bSeq; > >+} CCID_Header; > >+ > >+typedef struct __attribute__ ((__packed__)) CCID_BULK_IN { > >+ CCID_Header hdr; > >+ uint8_t bStatus; /* Only used in BULK_IN */ > >+ uint8_t bError; /* Only used in BULK_IN */ > >+} CCID_BULK_IN; > >+ > >+typedef struct __attribute__ ((__packed__)) CCID_SlotStatus { > >+ CCID_BULK_IN b; > >+ uint8_t bClockStatus; > >+} CCID_SlotStatus; > >+ > >+typedef struct __attribute__ ((__packed__)) CCID_Parameter { > >+ CCID_BULK_IN b; > >+ uint8_t bProtocolNum; > >+ uint8_t abProtocolDataStructure[0]; > >+} CCID_Parameter; > >+ > >+typedef struct __attribute__ ((__packed__)) CCID_DataBlock { > >+ CCID_BULK_IN b; > >+ uint8_t bChainParameter; > >+ uint8_t abData[0]; > >+} CCID_DataBlock; > >+ > >+/* 6.1.4 PC_to_RDR_XfrBlock */ > >+typedef struct __attribute__ ((__packed__)) CCID_XferBlock { > >+ CCID_Header hdr; > >+ uint8_t bBWI; /* Block Waiting Timeout */ > >+ uint16_t wLevelParameter; /* XXX currently unused */ > >+ uint8_t abData[0]; > >+} CCID_XferBlock; > >+ > >+typedef struct __attribute__ ((__packed__)) CCID_IccPowerOn { > >+ CCID_Header hdr; > >+ uint8_t bPowerSelect; > >+ uint16_t abRFU; > >+} CCID_IccPowerOn; > >+ > >+typedef struct __attribute__ ((__packed__)) CCID_IccPowerOff { > >+ CCID_Header hdr; > >+ uint16_t abRFU; > >+} CCID_IccPowerOff; > >+ > >+typedef struct __attribute__ ((__packed__)) CCID_SetParameter { > >+ CCID_Header hdr; > >+ uint8_t bProtocolNum; > >+ uint8_t abProtocolDataStructure[0]; > >+} CCID_SetParameter; > >+ > >+typedef struct CCID_Notify_Slot_Change { > >+ uint8_t bMessageType; /* CCID_MESSAGE_TYPE_RDR_to_PC_NotifySlotChange */ > >+ uint8_t bmSlotICCState; > >+} CCID_Notify_Slot_Change; > >+ > >+/* used for DataBlock response to XferBlock */ > >+typedef struct Answer { > >+ uint8_t slot; > >+ uint8_t seq; > >+} Answer; > >+ > >+/* pending BULK_IN messages */ > >+typedef struct BulkIn { > >+ uint8_t data[BULK_IN_BUF_SIZE]; > >+ uint32_t len; > >+ uint32_t pos; > >+} BulkIn; > >+ > >+enum { > >+ MIGRATION_NONE, > >+ MIGRATION_MIGRATED, > >+}; > >+ > >+typedef struct CCIDBus CCIDBus; > >+typedef struct USBCCIDState USBCCIDState; > >+ > >+#define MAX_PROTOCOL_SIZE 7 > >+ > >+/** > >+ * powered - defaults to true, changed by PowerOn/PowerOff messages > >+ */ > >+struct USBCCIDState { > >+ USBDevice dev; > >+ CCIDBus *bus; > >+ CCIDCardState *card; > >+ CCIDCardInfo *cardinfo; /* caching the info pointer */ > >+ uint8_t debug; > >+ BulkIn bulk_in_pending[BULK_IN_PENDING_NUM]; /* circular */ > >+ uint32_t bulk_in_pending_start; > >+ uint32_t bulk_in_pending_end; /* first free */ > >+ uint32_t bulk_in_pending_num; > >+ BulkIn *current_bulk_in; > >+ uint8_t bulk_out_data[BULK_OUT_DATA_SIZE]; > >+ uint32_t bulk_out_pos; > >+ uint8_t bmSlotICCState; > >+ uint8_t powered; > >+ uint8_t notify_slot_change; > >+ uint64_t last_answer_error; > >+ Answer pending_answers[PENDING_ANSWERS_NUM]; > >+ uint32_t pending_answers_start; > >+ uint32_t pending_answers_end; > >+ uint32_t pending_answers_num; > >+ uint8_t bError; > >+ uint8_t bmCommandStatus; > >+ uint8_t bProtocolNum; > >+ uint8_t abProtocolDataStructure[MAX_PROTOCOL_SIZE]; > >+ uint32_t ulProtocolDataStructureSize; > >+ uint32_t state_vmstate; > >+ uint8_t migration_state; > >+ uint32_t migration_target_ip; > >+ uint16_t migration_target_port; > >+}; > >+ > >+/* Slot specific variables. We emulate a single slot card reader. > >+ */ > >+ > >+ > >+/* CCID Spec chapter 4: CCID uses a standard device descriptor per Chapter 9, > >+ * "USB Device Framework", section 9.6.1, in the Universal Serial Bus > >+ * Specification. > >+ * > >+ * This device implemented based on the spec and with an Athena Smart Card > >+ * Reader as reference: > >+ * 0dc3:1004 Athena Smartcard Solutions, Inc. > >+ */ > >+ > >+static const uint8_t qemu_ccid_dev_descriptor[] = { > >+ 0x12, /* u8 bLength; */ > >+ USB_DT_DEVICE, /* u8 bDescriptorType; Device */ > >+ 0x10, 0x01, /* u16 bcdUSB; v1.1 */ > >+ > >+ 0x00, /* u8 bDeviceClass; */ > >+ 0x00, /* u8 bDeviceSubClass; */ > >+ 0x00, /* u8 bDeviceProtocol; [ low/full speeds only ] */ > >+ 0x40, /* u8 bMaxPacketSize0; 8 Bytes (valid: 8,16,32,64) */ > >+ > >+ /* Vendor and product id are arbitrary. */ > >+ /* u16 idVendor */ > >+ CCID_VENDOR_ID& 0xff, CCID_VENDOR_ID>> 8, > >+ /* u16 idProduct */ > >+ CCID_PRODUCT_ID& 0xff, CCID_PRODUCT_ID>> 8, > >+ /* u16 bcdDevice */ > >+ CCID_DEVICE_VERSION& 0xff, CCID_DEVICE_VERSION>> 8, > >+ 0x01, /* u8 iManufacturer; */ > >+ 0x02, /* u8 iProduct; */ > >+ 0x03, /* u8 iSerialNumber; */ > >+ 0x01, /* u8 bNumConfigurations; */ > >+}; > >+ > >+static const uint8_t qemu_ccid_config_descriptor[] = { > >+ > >+ /* one configuration */ > >+ 0x09, /* u8 bLength; */ > >+ USB_DT_CONFIG, /* u8 bDescriptorType; Configuration */ > >+ 0x5d, 0x00, /* u16 wTotalLength; 9+9+54+7+7+7 */ > >+ 0x01, /* u8 bNumInterfaces; (1) */ > >+ 0x01, /* u8 bConfigurationValue; */ > >+ 0x00, /* u8 iConfiguration; */ > >+ 0xe0, /* u8 bmAttributes; > >+ Bit 7: must be set, > >+ 6: Self-powered, > >+ 5: Remote wakeup, > >+ 4..0: resvd */ > >+ 100/2, /* u8 MaxPower; 50 == 100mA */ > >+ > >+ /* one interface */ > >+ 0x09, /* u8 if_bLength; */ > >+ USB_DT_INTERFACE, /* u8 if_bDescriptorType; Interface */ > >+ 0x00, /* u8 if_bInterfaceNumber; */ > >+ 0x00, /* u8 if_bAlternateSetting; */ > >+ 0x03, /* u8 if_bNumEndpoints; */ > >+ 0x0b, /* u8 if_bInterfaceClass; Smart Card Device Class */ > >+ 0x00, /* u8 if_bInterfaceSubClass; Subclass code */ > >+ 0x00, /* u8 if_bInterfaceProtocol; Protocol code */ > >+ 0x04, /* u8 if_iInterface; Index of string descriptor */ > >+ > >+ /* Smart Card Device Class Descriptor */ > >+ 0x36, /* u8 bLength; */ > >+ 0x21, /* u8 bDescriptorType; Functional */ > >+ 0x10, 0x01, /* u16 bcdCCID; CCID Specification Release Number. */ > >+ 0x00, /* u8 bMaxSlotIndex; The index of the highest available > >+ slot on this device. All slots are consecutive starting > >+ at 00h. */ > >+ 0x07, /* u8 bVoltageSupport; 01h - 5.0v, 02h - 3.0, 03 - 1.8 */ > >+ > >+ 0x03, 0x00, /* u32 dwProtocols; RRRR PPPP. RRRR = 0000h.*/ > >+ 0x00, 0x00, /* PPPP: 0001h = Protocol T=0, 0002h = Protocol T=1 */ > >+ /* u32 dwDefaultClock; in kHZ (0x0fa0 is 4 MHz) */ > >+ 0xa0, 0x0f, 0x00, 0x00, > >+ /* u32 dwMaximumClock; */ > >+ 0x00, 0x00, 0x01, 0x00, > >+ 0x00, /* u8 bNumClockSupported; 0 means just the default and max. */ > >+ /* u32 dwDataRate ;bps. 9600 == 00002580h */ > >+ 0x80, 0x25, 0x00, 0x00, > >+ /* u32 dwMaxDataRate ; 11520 bps == 0001C200h */ > >+ 0x00, 0xC2, 0x01, 0x00, > >+ 0x00, /* u8 bNumDataRatesSupported; 00 means all rates between > >+ * default and max */ > >+ /* u32 dwMaxIFSD; maximum IFSD supported by CCID for protocol > >+ * T=1 (Maximum seen from various cards) */ > >+ 0xfe, 0x00, 0x00, 0x00, > >+ /* u32 dwSyncProtocols; 1 - 2-wire, 2 - 3-wire, 4 - I2C */ > >+ 0x00, 0x00, 0x00, 0x00, > >+ /* u32 dwMechanical; 0 - no special characteristics. */ > >+ 0x00, 0x00, 0x00, 0x00, > >+ /* u32 dwFeatures; > >+ * 0 - No special characteristics > >+ * + 2 Automatic parameter configuration based on ATR data > >+ * + 4 Automatic activation of ICC on inserting > >+ * + 8 Automatic ICC voltage selection > >+ * + 10 Automatic ICC clock frequency change > >+ * + 20 Automatic baud rate change > >+ * + 40 Automatic parameters negotiation made by the CCID > >+ * + 80 automatic PPS made by the CCID > >+ * 100 CCID can set ICC in clock stop mode > >+ * 200 NAD value other then 00 accepted (T=1 protocol) > >+ * + 400 Automatic IFSD exchange as first exchange (T=1) > >+ * One of the following only: > >+ * + 10000 TPDU level exchanges with CCID > >+ * 20000 Short APDU level exchange with CCID > >+ * 40000 Short and Extended APDU level exchange with CCID > >+ * > >+ * + 100000 USB Wake up signaling supported on card insertion > >+ * and removal. Must set bit 5 in bmAttributes in Configuration > >+ * descriptor if 100000 is set.*/ > >+ 0xfe, 0x04, 0x11, 0x00, > >+ /* u32 dwMaxCCIDMessageLength; For extended APDU in [261 + 10 > >+ * , 65544 + 10]. Otherwise the minimum is wMaxPacketSize of > >+ * the Bulk-OUT endpoint */ > >+ 0x12, 0x00, 0x01, 0x00, > >+ 0xFF, /* u8 bClassGetResponse; Significant only for CCID that > >+ * offers an APDU level for exchanges. Indicates the default > >+ * class value used by the CCID when it sends a Get Response > >+ * command to perform the transportation of an APDU by T=0 > >+ * protocol > >+ * FFh indicates that the CCID echos the class of the APDU. > >+ */ > >+ 0xFF, /* u8 bClassEnvelope; EAPDU only. Envelope command for T=0 */ > >+ 0x00, 0x00, /* u16 wLcdLayout; XXYY Number of lines (XX) and chars per > >+ * line for LCD display used for PIN entry. 0000 - no LCD */ > >+ 0x01, /* u8 bPINSupport; 01h PIN Verification, > >+ * 02h PIN Modification */ > >+ 0x01, /* u8 bMaxCCIDBusySlots; */ > >+ > >+ /* Interrupt-IN endpoint */ > >+ 0x07, /* u8 ep_bLength; */ > >+ /* u8 ep_bDescriptorType; Endpoint */ > >+ USB_DT_ENDPOINT, > >+ /* u8 ep_bEndpointAddress; IN Endpoint 1 */ > >+ 0x80 | CCID_INT_IN_EP, > >+ 0x03, /* u8 ep_bmAttributes; Interrupt */ > >+ 0x40, 0x00, /* u16 ep_wMaxPacketSize; */ > >+ 0xff, /* u8 ep_bInterval; */ > >+ > >+ /* Bulk-In endpoint */ > >+ 0x07, /* u8 ep_bLength; */ > >+ /* u8 ep_bDescriptorType; Endpoint */ > >+ USB_DT_ENDPOINT, > >+ /* u8 ep_bEndpointAddress; IN Endpoint 2 */ > >+ 0x80 | CCID_BULK_IN_EP, > >+ 0x02, /* u8 ep_bmAttributes; Bulk */ > >+ 0x40, 0x00, /* u16 ep_wMaxPacketSize; */ > >+ 0x00, /* u8 ep_bInterval; */ > >+ > >+ /* Bulk-Out endpoint */ > >+ 0x07, /* u8 ep_bLength; */ > >+ /* u8 ep_bDescriptorType; Endpoint */ > >+ USB_DT_ENDPOINT, > >+ /* u8 ep_bEndpointAddress; OUT Endpoint 3 */ > >+ CCID_BULK_OUT_EP, > >+ 0x02, /* u8 ep_bmAttributes; Bulk */ > >+ 0x40, 0x00, /* u16 ep_wMaxPacketSize; */ > >+ 0x00, /* u8 ep_bInterval; */ > >+ > >+}; > >+ > >+static bool ccid_has_pending_answers(USBCCIDState *s) > >+{ > >+ return s->pending_answers_num> 0; > >+} > >+ > >+static void ccid_clear_pending_answers(USBCCIDState *s) > >+{ > >+ s->pending_answers_num = 0; > >+ s->pending_answers_start = 0; > >+ s->pending_answers_end = 0; > >+} > >+ > >+static void ccid_print_pending_answers(USBCCIDState *s) > >+{ > >+#ifdef DEBUG_CCID > >+ Answer *answer; > >+ int i, count; > >+ > >+ printf("usb-ccid: pending answers:"); > >+ if (!ccid_has_pending_answers(s)) { > >+ printf(" empty\n"); > >+ return; > >+ } > >+ for (i = s->pending_answers_start, count=s->pending_answers_num ; > >+ count> 0; count--, i++) { > >+ answer =&s->pending_answers[i % PENDING_ANSWERS_NUM]; > >+ if (count == 1) { > >+ printf("%d:%d\n", answer->slot, answer->seq); > >+ } else { > >+ printf("%d:%d,", answer->slot, answer->seq); > >+ } > >+ } > >+#endif > > It's usually better to use a dprintf() instead of an #if block like > this. You'll be much more likely to notice when the debug code > breaks which tends to happen over time. > will fix. > >+} > >+ > >+static void ccid_add_pending_answer(USBCCIDState *s, CCID_Header *hdr) > >+{ > >+ Answer* answer; > >+ > >+ assert(s->pending_answers_num++< PENDING_ANSWERS_NUM); > > Having side effects in an assert statement is a very bad idea. > yes, will fix. > >+ answer =&s->pending_answers[(s->pending_answers_end++) % PENDING_ANSWERS_NUM]; > >+ answer->slot = hdr->bSlot; > >+ answer->seq = hdr->bSeq; > >+ ccid_print_pending_answers(s); > >+} > >+ > >+static void ccid_remove_pending_answer(USBCCIDState *s, > >+ uint8_t *slot, uint8_t *seq) > >+{ > >+ Answer *answer; > >+ > >+ assert(s->pending_answers_num--> 0); > >+ answer =&s->pending_answers[(s->pending_answers_start++) % PENDING_ANSWERS_NUM]; > >+ *slot = answer->slot; > >+ *seq = answer->seq; > >+ ccid_print_pending_answers(s); > >+} > >+ > >+static void ccid_bulk_in_clear(USBCCIDState *s) > >+{ > >+ s->bulk_in_pending_start = 0; > >+ s->bulk_in_pending_end = 0; > >+ s->bulk_in_pending_num = 0; > >+} > >+ > >+static void ccid_bulk_in_release(USBCCIDState *s) > >+{ > >+ assert(s->current_bulk_in != NULL); > >+ s->current_bulk_in->pos = 0; > >+ s->current_bulk_in = NULL; > >+} > >+ > >+static void ccid_bulk_in_get(USBCCIDState *s) > >+{ > >+ if (s->current_bulk_in != NULL || s->bulk_in_pending_num == 0) { > >+ return; > >+ } > >+ assert(s->bulk_in_pending_num> 0); > >+ s->bulk_in_pending_num--; > >+ s->current_bulk_in =&s->bulk_in_pending[ > >+ (s->bulk_in_pending_start++) % BULK_IN_PENDING_NUM]; > >+} > >+ > >+static void* ccid_reserve_recv_buf(USBCCIDState* s, uint16_t len) > >+{ > >+ BulkIn* bulk_in; > >+ > >+ DPRINTF(s, 4, "%s: QUEUE: reserve %d bytes\n", __func__, len); > >+ > >+ /* look for an existing element */ > >+ if (len> BULK_IN_BUF_SIZE) { > >+ printf("usb-ccid.c: %s: len larger then max (%d>%d). discarding message.\n", > >+ __func__, len, BULK_IN_BUF_SIZE); > >+ return NULL; > >+ } > >+ if (s->bulk_in_pending_num>= BULK_IN_PENDING_NUM) { > >+ printf("usb-ccid.c: %s: No free bulk_in buffers. discarding message.\n", > >+ __func__); > >+ return NULL; > >+ } > > Looks like these are guest triggable printfs. You should avoid that. > will turn to dprintf. > >+ bulk_in =&s->bulk_in_pending[(s->bulk_in_pending_end++) % BULK_IN_PENDING_NUM]; > >+ s->bulk_in_pending_num++; > >+ bulk_in->len = len; > >+ return bulk_in->data; > >+} > >+ > >+static void ccid_reset(USBCCIDState *s) > >+{ > >+ ccid_bulk_in_clear(s); > >+ ccid_clear_pending_answers(s); > >+} > >+ > >+static void ccid_detach(USBCCIDState *s) > >+{ > >+ ccid_reset(s); > >+} > >+ > >+static void ccid_handle_reset(USBDevice *dev) > >+{ > >+ USBCCIDState *s = DO_UPCAST(USBCCIDState, dev, dev); > >+ > >+ DPRINTF(s, 1, "Reset\n"); > >+ > >+ ccid_reset(s); > >+} > >+ > >+static int ccid_handle_control(USBDevice *dev, int request, int value, > >+ int index, int length, uint8_t *data) > >+{ > >+ USBCCIDState *s = DO_UPCAST(USBCCIDState, dev, dev); > >+ int ret = 0; > >+ > >+ DPRINTF(s, 1, "got control %x, value %x\n",request, value); > >+ switch (request) { > >+ case DeviceRequest | USB_REQ_GET_STATUS: > >+ data[0] = (0<< USB_DEVICE_SELF_POWERED) | > >+ (dev->remote_wakeup<< USB_DEVICE_REMOTE_WAKEUP); > >+ data[1] = 0x00; > >+ ret = 2; > >+ break; > >+ case DeviceOutRequest | USB_REQ_CLEAR_FEATURE: > >+ if (value == USB_DEVICE_REMOTE_WAKEUP) { > >+ dev->remote_wakeup = 0; > >+ } else { > >+ goto fail; > >+ } > >+ ret = 0; > >+ break; > >+ case DeviceOutRequest | USB_REQ_SET_FEATURE: > >+ if (value == USB_DEVICE_REMOTE_WAKEUP) { > >+ dev->remote_wakeup = 1; > >+ } else { > >+ goto fail; > >+ } > >+ ret = 0; > >+ break; > >+ case DeviceOutRequest | USB_REQ_SET_ADDRESS: > >+ dev->addr = value; > >+ ret = 0; > >+ break; > >+ case DeviceRequest | USB_REQ_GET_DESCRIPTOR: > >+ switch(value>> 8) { > >+ case USB_DT_DEVICE: > >+ memcpy(data, qemu_ccid_dev_descriptor, > >+ sizeof(qemu_ccid_dev_descriptor)); > >+ ret = sizeof(qemu_ccid_dev_descriptor); > >+ break; > >+ case USB_DT_CONFIG: > >+ memcpy(data, qemu_ccid_config_descriptor, > >+ sizeof(qemu_ccid_config_descriptor)); > >+ ret = sizeof(qemu_ccid_config_descriptor); > >+ break; > >+ case USB_DT_STRING: > >+ switch(value& 0xff) { > >+ case 0: > >+ /* language ids */ > >+ data[0] = 4; > >+ data[1] = 3; > >+ data[2] = 0x09; > >+ data[3] = 0x04; > >+ ret = 4; > >+ break; > >+ case 1: > >+ /* vendor description */ > >+ ret = set_usb_string(data, CCID_VENDOR_DESCRIPTION); > >+ break; > >+ case 2: > >+ /* product description */ > >+ ret = set_usb_string(data, CCID_PRODUCT_DESCRIPTION); > >+ break; > >+ case 3: > >+ /* serial number */ > >+ ret = set_usb_string(data, CCID_SERIAL_NUMBER_STRING); > >+ break; > >+ case 4: > >+ /* interface name */ > >+ ret = set_usb_string(data, CCID_INTERFACE_NAME); > >+ break; > >+ default: > >+ goto fail; > >+ } > >+ break; > >+ default: > >+ goto fail; > >+ } > >+ break; > >+ case DeviceRequest | USB_REQ_GET_CONFIGURATION: > >+ data[0] = 1; > >+ ret = 1; > >+ break; > >+ case DeviceOutRequest | USB_REQ_SET_CONFIGURATION: > >+ /* Only one configuration - we just ignore the request */ > >+ ret = 0; > >+ break; > >+ case DeviceRequest | USB_REQ_GET_INTERFACE: > >+ data[0] = 0; > >+ ret = 1; > >+ break; > >+ case InterfaceOutRequest | USB_REQ_SET_INTERFACE: > >+ ret = 0; > >+ break; > >+ case EndpointOutRequest | USB_REQ_CLEAR_FEATURE: > >+ ret = 0; > >+ break; > >+ > >+ /* Class specific requests. */ > >+ case InterfaceOutClass | CCID_CONTROL_ABORT: > >+ DPRINTF(s, 1, "ccid_control abort UNIMPLEMENTED\n"); > >+ ret = USB_RET_STALL; > >+ break; > >+ case InterfaceInClass | CCID_CONTROL_GET_CLOCK_FREQUENCIES: > >+ DPRINTF(s, 1, "ccid_control get clock frequencies UNIMPLEMENTED\n"); > >+ ret = USB_RET_STALL; > >+ break; > >+ case InterfaceInClass | CCID_CONTROL_GET_DATA_RATES: > >+ DPRINTF(s, 1, "ccid_control get data rates UNIMPLEMENTED\n"); > >+ ret = USB_RET_STALL; > >+ break; > >+ default: > >+ fail: > >+ DPRINTF(s, 1, "got unsupported/bogus control %x, value %x\n", request, value); > >+ ret = USB_RET_STALL; > >+ break; > >+ } > >+ return ret; > >+} > >+ > >+static bool ccid_card_inserted(USBCCIDState *s) > >+{ > >+ return s->bmSlotICCState& SLOT_0_STATE_MASK; > >+} > >+ > >+static uint8_t ccid_card_status(USBCCIDState *s) > >+{ > >+ return ccid_card_inserted(s) > >+ ? (s->powered ? > >+ ICC_STATUS_PRESENT_ACTIVE > >+ : ICC_STATUS_PRESENT_INACTIVE > >+ ) > >+ : ICC_STATUS_NOT_PRESENT; > >+} > >+ > >+static uint8_t ccid_calc_status(USBCCIDState *s) > >+{ > >+ /* page 55, 6.2.6, calculation of bStatus from bmICCStatus and > >+ bmCommandStatus > >+ */ > >+ uint8_t ret = ccid_card_status(s) | (s->bmCommandStatus<< 6); > >+ DPRINTF(s, 4, "status = %d\n", ret); > >+ return ret; > >+} > >+ > >+static void ccid_reset_error_status(USBCCIDState* s) > >+{ > >+ s->bError = ERROR_CMD_NOT_SUPPORTED; > >+ s->bmCommandStatus = COMMAND_STATUS_NO_ERROR; > >+} > >+ > >+static void ccid_write_slot_status(USBCCIDState* s, CCID_Header* recv) > >+{ > >+ CCID_SlotStatus *h = ccid_reserve_recv_buf(s, sizeof(CCID_SlotStatus)); > >+ if (h == NULL) { > >+ return; > >+ } > >+ h->b.hdr.bMessageType = CCID_MESSAGE_TYPE_RDR_to_PC_SlotStatus; > >+ h->b.hdr.dwLength = 0; > >+ h->b.hdr.bSlot = recv->bSlot; > >+ h->b.hdr.bSeq = recv->bSeq; > >+ h->b.bStatus = ccid_calc_status(s); > >+ h->b.bError = s->bError; > >+ h->bClockStatus = CLOCK_STATUS_RUNNING; > >+ ccid_reset_error_status(s); > >+} > >+ > >+static void ccid_write_parameters(USBCCIDState* s, CCID_Header* recv) > >+{ > >+ CCID_Parameter *h; > >+ uint32_t len = s->ulProtocolDataStructureSize; > >+ > >+ h = ccid_reserve_recv_buf(s, sizeof(CCID_Parameter) + len); > >+ if (h == NULL) { > >+ return; > >+ } > >+ h->b.hdr.bMessageType = CCID_MESSAGE_TYPE_RDR_to_PC_Parameters; > >+ h->b.hdr.dwLength = 0; > >+ h->b.hdr.bSlot = recv->bSlot; > >+ h->b.hdr.bSeq = recv->bSeq; > >+ h->b.bStatus = ccid_calc_status(s); > >+ h->b.bError = s->bError; > >+ h->bProtocolNum = s->bProtocolNum; > >+ memcpy(h->abProtocolDataStructure, s->abProtocolDataStructure, len); > >+ ccid_reset_error_status(s); > >+} > >+ > >+static void ccid_write_data_block( > >+ USBCCIDState* s, uint8_t slot, uint8_t seq, > >+ const uint8_t* data, uint32_t len) > >+{ > >+ CCID_DataBlock *p = ccid_reserve_recv_buf(s, sizeof(*p) + len); > >+ > >+ if (p == NULL) { > >+ return; > >+ } > >+ p->b.hdr.bMessageType = CCID_MESSAGE_TYPE_RDR_to_PC_DataBlock; > >+ p->b.hdr.dwLength = cpu_to_le32(len); > >+ p->b.hdr.bSlot = slot; > >+ p->b.hdr.bSeq = seq; > >+ p->b.bStatus = ccid_calc_status(s); > >+ p->b.bError = s->bError; > >+#ifdef DEBUG_CCID > >+ if (p->b.bError) { > >+ DPRINTF(s, 4, "error %d", p->b.bError); > >+ } > >+#endif > >+ memcpy(p->abData, data, len); > >+ ccid_reset_error_status(s); > >+} > >+ > >+static void ccid_write_data_block_answer(USBCCIDState* s, > >+ const uint8_t* data, uint32_t len) > >+{ > >+ uint8_t seq; > >+ uint8_t slot; > >+ > >+ if (!ccid_has_pending_answers(s)) { > >+ abort(); > >+ } > >+ ccid_remove_pending_answer(s,&slot,&seq); > >+ ccid_write_data_block(s, slot, seq, data, len); > >+} > >+ > >+static void ccid_write_data_block_atr(USBCCIDState* s, CCID_Header* recv) > >+{ > >+ const uint8_t *atr = NULL; > >+ uint32_t len = 0; > >+ > >+ if (s->card) { > >+ atr = s->cardinfo->get_atr(s->card,&len); > >+ } > >+ ccid_write_data_block(s, recv->bSlot, recv->bSeq, atr, len); > >+} > >+ > >+static void ccid_set_parameters(USBCCIDState *s, CCID_Header *recv) > >+{ > >+ CCID_SetParameter *ph = (CCID_SetParameter *) recv; > >+ uint32_t len = 0; > >+ if (ph->bProtocolNum == 0) { > >+ len = 5; > >+ } > >+ if (ph->bProtocolNum == 1) { > >+ len = 7; > >+ } > >+ if (len == 0) { > >+ s->bmCommandStatus = COMMAND_STATUS_FAILED; > >+ s->bError = 7; /* Protocol invalid or not supported */ > >+ return; > >+ } > >+ s->bProtocolNum = ph->bProtocolNum; > >+ memcpy(s->abProtocolDataStructure, ph->abProtocolDataStructure, len); > >+ s->ulProtocolDataStructureSize = len; > >+ DPRINTF(s, 1, "%s: using len %d\n", __func__, len); > >+} > >+ > >+/* must be 5 bytes for T=0, 7 bytes for T=1 > >+ * See page 52 */ > >+static const uint8_t abDefaultProtocolDataStructure[7] = > >+ { 0x77, 0x00, 0x00, 0x00, 0x00, 0xfe /*IFSC*/, 0x00 /*NAD*/ }; > >+ > >+static void ccid_reset_parameters(USBCCIDState *s) > >+{ > >+ uint32_t len = sizeof(abDefaultProtocolDataStructure); > >+ > >+ s->bProtocolNum = 1; /* T=1 */ > >+ s->ulProtocolDataStructureSize = len; > >+ memcpy(s->abProtocolDataStructure, abDefaultProtocolDataStructure, len); > >+} > >+ > >+static void ccid_report_error_failed(USBCCIDState *s, uint8_t error) > >+{ > >+ s->bmCommandStatus = COMMAND_STATUS_FAILED; > >+ s->bError = error; > >+} > >+ > >+/* NOTE: only a single slot is supported (SLOT_0) > >+ */ > >+static void ccid_on_slot_change(USBCCIDState* s, bool full) > >+{ > >+ /* RDR_to_PC_NotifySlotChange, 6.3.1 page 56 > >+ */ > >+ uint8_t current = s->bmSlotICCState; > >+ if (full) { > >+ s->bmSlotICCState |= SLOT_0_STATE_MASK; > >+ } else { > >+ s->bmSlotICCState&= ~SLOT_0_STATE_MASK; > >+ } > >+ if (current != s->bmSlotICCState) { > >+ s->bmSlotICCState |= SLOT_0_CHANGED_MASK; > >+ } > >+ s->notify_slot_change = true; > >+} > >+ > >+static void ccid_write_data_block_error( > >+ USBCCIDState *s, uint8_t slot, uint8_t seq) > >+{ > >+ ccid_write_data_block(s, slot, seq, NULL, 0); > >+} > >+ > >+static void ccid_on_apdu_from_guest(USBCCIDState *s, CCID_XferBlock *recv) > >+{ > >+ uint32_t len; > >+ > >+ if (ccid_card_status(s) != ICC_STATUS_PRESENT_ACTIVE) { > >+ DPRINTF(s, 1, "usb-ccid: not sending apdu to client, no card connected\n"); > >+ ccid_write_data_block_error(s, recv->hdr.bSlot, recv->hdr.bSeq); > >+ return; > >+ } > >+ len = le32_to_cpu(recv->hdr.dwLength); > >+ DPRINTF(s, 1, "%s: seq %d, len %d\n", __FUNCTION__, > >+ recv->hdr.bSeq, len); > >+ ccid_add_pending_answer(s, (CCID_Header*)recv); > >+ if (s->card) { > >+ s->cardinfo->apdu_from_guest(s->card, recv->abData, len); > >+ } else { > >+ printf("warning: discarded apdu\n"); > > Guest triggerable printf? > another dprintf convert. > >+ } > >+} > >+ > >+/* handle a single USB_TOKEN_OUT, return value returned to guest. > >+ * 0 - all ok > >+ * USB_RET_STALL - failed to handle packet */ > >+static int ccid_handle_bulk_out(USBCCIDState *s, USBPacket *p) > >+{ > >+ CCID_Header* ccid_header; > >+ > >+ if (p->len + s->bulk_out_pos> BULK_OUT_DATA_SIZE) { > >+ return USB_RET_STALL; > >+ } > >+ ccid_header = (CCID_Header*)s->bulk_out_data; > >+ memcpy(s->bulk_out_data + s->bulk_out_pos, p->data, p->len); > >+ s->bulk_out_pos += p->len; > >+ if (p->len == 64) { > >+ DPRINTF(s, 4, "usb-ccid: bulk_in: expecting more packets (%d/%d)\n", > >+ p->len, ccid_header->dwLength); > >+ return 0; > >+ } > >+ if (s->bulk_out_pos< 10) { > >+ DPRINTF(s, 1, "%s: bad USB_TOKEN_OUT length, should be at least 10 bytes\n", __func__); > >+ } else { > >+ DPRINTF(s, 3, "%s %x\n", __func__, ccid_header->bMessageType); > >+ switch (ccid_header->bMessageType) { > >+ case CCID_MESSAGE_TYPE_PC_to_RDR_GetSlotStatus: > >+ ccid_write_slot_status(s, ccid_header); > >+ break; > >+ case CCID_MESSAGE_TYPE_PC_to_RDR_IccPowerOn: > >+ DPRINTF(s, 1, "PowerOn: %d\n", > >+ ((CCID_IccPowerOn*)(ccid_header))->bPowerSelect); > >+ s->powered = true; > >+ if (!ccid_card_inserted(s)) { > >+ ccid_report_error_failed(s, ERROR_ICC_MUTE); > >+ } > >+ /* atr is written regardless of error. */ > >+ ccid_write_data_block_atr(s, ccid_header); > >+ break; > >+ case CCID_MESSAGE_TYPE_PC_to_RDR_IccPowerOff: > >+ DPRINTF(s, 1, "PowerOff\n"); > >+ ccid_reset_error_status(s); > >+ s->powered = false; > >+ ccid_write_slot_status(s, ccid_header); > >+ break; > >+ case CCID_MESSAGE_TYPE_PC_to_RDR_XfrBlock: > >+ ccid_on_apdu_from_guest(s, (CCID_XferBlock*)s->bulk_out_data); > >+ break; > >+ case CCID_MESSAGE_TYPE_PC_to_RDR_SetParameters: > >+ ccid_reset_error_status(s); > >+ ccid_set_parameters(s, ccid_header); > >+ ccid_write_parameters(s, ccid_header); > >+ break; > >+ case CCID_MESSAGE_TYPE_PC_to_RDR_ResetParameters: > >+ ccid_reset_error_status(s); > >+ ccid_reset_parameters(s); > >+ ccid_write_parameters(s, ccid_header); > >+ break; > >+ case CCID_MESSAGE_TYPE_PC_to_RDR_GetParameters: > >+ ccid_reset_error_status(s); > >+ ccid_write_parameters(s, ccid_header); > >+ break; > >+ default: > >+ DPRINTF(s, 1, "handle_data: ERROR: unhandled message type %Xh\n", > >+ ccid_header->bMessageType); > >+ /* the caller is expecting the device to respond, tell it we > >+ * do't support the operation */ > >+ ccid_report_error_failed(s, ERROR_CMD_NOT_SUPPORTED); > >+ ccid_write_slot_status(s, ccid_header); > >+ break; > >+ } > >+ } > >+ s->bulk_out_pos = 0; > >+ return 0; > >+} > >+ > >+static int ccid_bulk_in_copy_to_guest(USBCCIDState *s, uint8_t *data, int len) > >+{ > >+ int ret = 0; > >+ > >+ assert(len>0); > >+ ccid_bulk_in_get(s); > >+ if (s->current_bulk_in != NULL) { > >+ ret = MIN(s->current_bulk_in->len - s->current_bulk_in->pos, len); > >+ memcpy(data, s->current_bulk_in->data + s->current_bulk_in->pos, ret); > >+ s->current_bulk_in->pos += ret; > >+ if (s->current_bulk_in->pos == s->current_bulk_in->len) { > >+ ccid_bulk_in_release(s); > >+ } > >+ } else { > >+ ret = USB_RET_NAK; /* return when device has no data - usb 2.0 spec Table 8-4 */ > >+ } > >+ if (ret> 0) { > >+ DPRINTF(s, 3, "%s: %d/%d req/act to guest (BULK_IN)\n", __func__, len, ret); > >+ } > >+ if (ret != USB_RET_NAK&& ret< len) { > >+ DPRINTF(s, 1, "%s: returning short (EREMOTEIO) %d< %d\n", __func__, ret, len); > >+ } > >+ return ret; > >+} > >+ > >+static int ccid_handle_data(USBDevice *dev, USBPacket *p) > >+{ > >+ USBCCIDState *s = DO_UPCAST(USBCCIDState, dev, dev); > >+ int ret = 0; > >+ uint8_t *data = p->data; > >+ int len = p->len; > >+ > >+ switch (p->pid) { > >+ case USB_TOKEN_OUT: > >+ ret = ccid_handle_bulk_out(s, p); > >+ break; > >+ > >+ case USB_TOKEN_IN: > >+ switch (p->devep& 0xf) { > >+ case CCID_BULK_IN_EP: > >+ if (!len) { > >+ ret = USB_RET_NAK; > >+ } else { > >+ ret = ccid_bulk_in_copy_to_guest(s, data, len); > >+ } > >+ break; > >+ case CCID_INT_IN_EP: > >+ if (s->notify_slot_change) { > >+ /* page 56, RDR_to_PC_NotifySlotChange */ > >+ data[0] = CCID_MESSAGE_TYPE_RDR_to_PC_NotifySlotChange; > >+ data[1] = s->bmSlotICCState; > >+ ret = 2; > >+ s->notify_slot_change = false; > >+ s->bmSlotICCState&= ~SLOT_0_CHANGED_MASK; > >+ DPRINTF(s, 2, "handle_data: int_in: notify_slot_change %X, requested len %d\n", > >+ s->bmSlotICCState, len); > >+ } > >+ break; > >+ default: > >+ DPRINTF(s, 1, "Bad endpoint\n"); > >+ break; > >+ } > >+ break; > >+ default: > >+ DPRINTF(s, 1, "Bad token\n"); > >+ ret = USB_RET_STALL; > >+ break; > >+ } > >+ > >+ return ret; > >+} > >+ > >+static void ccid_handle_destroy(USBDevice *dev) > >+{ > >+ USBCCIDState *s = DO_UPCAST(USBCCIDState, dev, dev); > >+ > >+ ccid_bulk_in_clear(s); > >+} > >+ > >+static void ccid_flush_pending_answers(USBCCIDState *s) { > >+ while (ccid_has_pending_answers(s)) { > >+ ccid_write_data_block_answer(s, NULL, 0); > >+ } > >+} > >+ > >+static Answer *ccid_peek_next_answer(USBCCIDState *s) > >+{ > >+ return s->pending_answers_num == 0 > >+ ? NULL > >+ :&s->pending_answers[s->pending_answers_start % PENDING_ANSWERS_NUM]; > >+} > >+ > >+static void ccid_bus_dev_print(Monitor *mon, DeviceState *qdev, int indent) > >+{ > >+ CCIDCardState *card = DO_UPCAST(CCIDCardState, qdev, qdev); > >+ CCIDCardInfo *info = DO_UPCAST(CCIDCardInfo, qdev, qdev->info); > >+ > >+ if (info->print) { > >+ info->print(mon, card, indent); > >+ } > >+} > >+ > >+struct CCIDBus { > >+ BusState qbus; > >+}; > >+ > >+static struct BusInfo ccid_bus_info = { > >+ .name = "ccid-bus", > >+ .size = sizeof(CCIDBus), > >+ .print_dev = ccid_bus_dev_print, > >+ .props = (Property[]) { > >+ DEFINE_PROP_UINT32("slot", struct CCIDCardState, slot, 0), > >+ DEFINE_PROP_END_OF_LIST(), > >+ } > >+}; > >+ > >+static CCIDBus *ccid_bus_new(DeviceState *dev) > >+{ > >+ CCIDBus *bus; > >+ > >+ bus = FROM_QBUS(CCIDBus, qbus_create(&ccid_bus_info, dev, NULL)); > >+ bus->qbus.allow_hotplug = 1; > >+ > >+ return bus; > >+} > >+ > >+void ccid_card_send_apdu_to_guest(CCIDCardState *card, uint8_t* apdu, uint32_t len) > >+{ > >+ USBCCIDState *s = DO_UPCAST(USBCCIDState, dev.qdev, card->qdev.parent_bus->parent); > >+ Answer *answer; > >+ > >+ if (!ccid_has_pending_answers(s)) { > >+ DPRINTF(s, 1, "CCID ERROR: got an APDU without pending answers\n"); > >+ return; > >+ } > >+ s->bmCommandStatus = COMMAND_STATUS_NO_ERROR; > >+ answer = ccid_peek_next_answer(s); > >+ if (answer == NULL) { > >+ abort(); > >+ } > >+ DPRINTF(s, 1, "APDU returned to guest %d (answer seq %d, slot %d)\n", > >+ len, answer->seq, answer->slot); > >+ ccid_write_data_block_answer(s, apdu, len); > >+} > >+ > >+void ccid_card_card_removed(CCIDCardState *card) > >+{ > >+ USBCCIDState *s = DO_UPCAST(USBCCIDState, dev.qdev, card->qdev.parent_bus->parent); > >+ > >+ ccid_on_slot_change(s, false); > >+ ccid_flush_pending_answers(s); > >+ ccid_reset(s); > >+} > >+ > >+int ccid_card_ccid_attach(CCIDCardState *card) > >+{ > >+ USBCCIDState *s = DO_UPCAST(USBCCIDState, dev.qdev, card->qdev.parent_bus->parent); > >+ > >+ DPRINTF(s, 1, "CCID Attach\n"); > >+ if (s->migration_state == MIGRATION_MIGRATED) { > >+ s->migration_state = MIGRATION_NONE; > >+ } > >+ return 0; > >+} > >+ > >+void ccid_card_ccid_detach(CCIDCardState *card) > >+{ > >+ USBCCIDState *s = DO_UPCAST(USBCCIDState, dev.qdev, card->qdev.parent_bus->parent); > >+ > >+ DPRINTF(s, 1, "CCID Detach\n"); > >+ if (ccid_card_inserted(s)) { > >+ ccid_on_slot_change(s, false); > >+ } > >+ ccid_detach(s); > >+} > >+ > >+void ccid_card_card_error(CCIDCardState *card, uint64_t error) > >+{ > >+ USBCCIDState *s = DO_UPCAST(USBCCIDState, dev.qdev, card->qdev.parent_bus->parent); > >+ > >+ s->bmCommandStatus = COMMAND_STATUS_FAILED; > >+ s->last_answer_error = error; > >+ DPRINTF(s, 1, "VSC_Error: %lX\n", s->last_answer_error); > >+ /* TODO: these error's should be more verbose and propogated to the guest. > >+ * */ > >+ ccid_write_data_block_answer(s, NULL, 0); > >+} > >+ > >+void ccid_card_card_inserted(CCIDCardState *card) > >+{ > >+ USBCCIDState *s = DO_UPCAST(USBCCIDState, dev.qdev, card->qdev.parent_bus->parent); > >+ > >+ s->bmCommandStatus = COMMAND_STATUS_NO_ERROR; > >+ ccid_flush_pending_answers(s); > >+ ccid_on_slot_change(s, true); > >+} > >+ > >+static int ccid_card_exit(DeviceState *qdev) > >+{ > >+ int ret = 0; > >+ CCIDCardState *card = DO_UPCAST(CCIDCardState, qdev, qdev); > >+ CCIDCardInfo *info = DO_UPCAST(CCIDCardInfo, qdev, qdev->info); > >+ USBCCIDState *s = DO_UPCAST(USBCCIDState, dev.qdev, card->qdev.parent_bus->parent); > >+ > >+ if (ccid_card_inserted(s)) { > >+ ccid_card_card_removed(card); > >+ } > >+ if (info->exitfn) { > >+ ret = info->exitfn(card); > >+ } > >+ s->card = NULL; > >+ s->cardinfo = NULL; > >+ return ret; > >+} > >+ > >+static int ccid_card_init(DeviceState *qdev, DeviceInfo *base) > >+{ > >+ CCIDCardState *card = DO_UPCAST(CCIDCardState, qdev, qdev); > >+ CCIDCardInfo *info = DO_UPCAST(CCIDCardInfo, qdev, base); > >+ USBCCIDState *s = DO_UPCAST(USBCCIDState, dev.qdev, card->qdev.parent_bus->parent); > >+ int ret = 0; > >+ > >+ if (card->slot != 0) { > >+ fprintf(stderr, "Warning: usb-ccid supports one slot, can't add %d", > >+ card->slot); > > I think error_report is more appropriate here. That makes it > visible over QMP. > will fix. > >+ return -1; > >+ } > >+ if (s->card != NULL) { > >+ fprintf(stderr, "Warning: usb-ccid card already full, not adding\n"); > >+ return -1; > >+ } > >+ ret = info->initfn ? info->initfn(card) : ret; > >+ if (ret == 0) { > >+ s->card = card; > >+ s->cardinfo = info; > >+ } > >+ return ret; > >+} > >+ > >+void ccid_card_qdev_register(CCIDCardInfo *card) > >+{ > >+ card->qdev.bus_info =&ccid_bus_info; > >+ card->qdev.init = ccid_card_init; > >+ card->qdev.exit = ccid_card_exit; > >+ qdev_register(&card->qdev); > >+} > >+ > >+static int ccid_initfn(USBDevice *dev) > >+{ > >+ USBCCIDState *s = DO_UPCAST(USBCCIDState, dev, dev); > >+ > >+ s->bus = ccid_bus_new(&dev->qdev); > >+ s->card = NULL; > >+ s->cardinfo = NULL; > >+ s->migration_state = MIGRATION_NONE; > >+ s->migration_target_ip = 0; > >+ s->migration_target_port = 0; > >+ s->dev.speed = USB_SPEED_FULL; > >+ s->notify_slot_change = false; > >+ s->powered = true; > >+ s->pending_answers_num = 0; > >+ s->last_answer_error = 0; > >+ s->bulk_in_pending_start = 0; > >+ s->bulk_in_pending_end = 0; > >+ s->current_bulk_in = NULL; > >+ ccid_reset_error_status(s); > >+ s->bulk_out_pos = 0; > >+ ccid_reset_parameters(s); > >+ ccid_reset(s); > >+ return 0; > >+} > >+ > >+#ifdef ENABLE_MIGRATION > > Why is migration conditional? > I don't remember. Can't think of any good reason, will remove. > >+static int ccid_post_load(void *opaque, int version_id) > >+{ > >+ USBCCIDState *s = opaque; > >+ > >+ // This must be done after usb_device_attach, which sets state to ATTACHED, > >+ // while it must be DEFAULT in order to accept packets (like it is after > >+ // reset, but reset will reset our addr and call our reset handler which > >+ // may change state, and we don't want to do that when migrating). > >+ s->dev.state = s->state_vmstate; > >+ return 0; > >+} > >+ > >+static void ccid_pre_save(void *opaque) > >+{ > >+ USBCCIDState *s = opaque; > >+ > >+ s->state_vmstate = s->dev.state; > >+ if (s->dev.attached) { > >+ // migrating an open device, ignore reconnection CHR_EVENT to avoid an > >+ // erronous detach. > >+ s->migration_state = MIGRATION_MIGRATED; > >+ } > >+} > >+ > >+static VMStateDescription bulk_in_vmstate = { > >+ .name = "CCID BulkIn state", > >+ .version_id = 1, > >+ .minimum_version_id = 1, > >+ .fields = (VMStateField []) { > >+ VMSTATE_BUFFER(data, BulkIn), > >+ VMSTATE_UINT32(len, BulkIn), > >+ VMSTATE_UINT32(pos, BulkIn), > >+ VMSTATE_END_OF_LIST() > >+ } > >+}; > >+ > >+static VMStateDescription answer_vmstate = { > >+ .name = "CCID Answer state", > >+ .version_id = 1, > >+ .minimum_version_id = 1, > >+ .fields = (VMStateField []) { > >+ VMSTATE_UINT8(slot, Answer), > >+ VMSTATE_UINT8(seq, Answer), > >+ VMSTATE_END_OF_LIST() > >+ } > >+}; > >+ > >+static VMStateDescription usb_device_vmstate = { > >+ .name = "usb_device", > >+ .version_id = 1, > >+ .minimum_version_id = 1, > >+ .fields = (VMStateField []) { > >+ VMSTATE_UINT8(addr, USBDevice), > >+ VMSTATE_BUFFER(setup_buf, USBDevice), > >+ VMSTATE_BUFFER(data_buf, USBDevice), > >+ VMSTATE_END_OF_LIST() > >+ } > >+}; > >+ > >+static VMStateDescription ccid_vmstate = { > >+ .name = CCID_DEV_NAME, > >+ .version_id = 1, > >+ .minimum_version_id = 1, > >+ .post_load = ccid_post_load, > >+ .pre_save = ccid_pre_save, > >+ .fields = (VMStateField []) { > >+ VMSTATE_STRUCT(dev, USBCCIDState, 1, usb_device_vmstate, USBDevice), > >+ VMSTATE_UINT8(debug, USBCCIDState), > >+ VMSTATE_BUFFER(bulk_out_data, USBCCIDState), > >+ VMSTATE_UINT32(bulk_out_pos, USBCCIDState), > >+ VMSTATE_UINT8(bmSlotICCState, USBCCIDState), > >+ VMSTATE_UINT8(powered, USBCCIDState), > >+ VMSTATE_UINT8(notify_slot_change, USBCCIDState), > >+ VMSTATE_UINT64(last_answer_error, USBCCIDState), > >+ VMSTATE_UINT8(bError, USBCCIDState), > >+ VMSTATE_UINT8(bmCommandStatus, USBCCIDState), > >+ VMSTATE_UINT8(bProtocolNum, USBCCIDState), > >+ VMSTATE_BUFFER(abProtocolDataStructure, USBCCIDState), > >+ VMSTATE_UINT32(ulProtocolDataStructureSize, USBCCIDState), > >+ VMSTATE_STRUCT_ARRAY(bulk_in_pending, USBCCIDState, > >+ BULK_IN_PENDING_NUM, 1, bulk_in_vmstate, BulkIn), > >+ VMSTATE_UINT32(bulk_in_pending_start, USBCCIDState), > >+ VMSTATE_UINT32(bulk_in_pending_end, USBCCIDState), > >+ VMSTATE_STRUCT_ARRAY(pending_answers, USBCCIDState, > >+ PENDING_ANSWERS_NUM, 1, answer_vmstate, Answer), > >+ VMSTATE_UINT32(pending_answers_num, USBCCIDState), > >+ VMSTATE_UINT8(migration_state, USBCCIDState), > >+ VMSTATE_UINT32(state_vmstate, USBCCIDState), > >+ VMSTATE_END_OF_LIST() > >+ } > >+}; > >+#endif // ENABLE_MIGRATION > >+ > >+static struct USBDeviceInfo ccid_info = { > >+ .product_desc = "QEMU USB CCID", > >+ .qdev.name = CCID_DEV_NAME, > >+ .qdev.size = sizeof(USBCCIDState), > >+ .init = ccid_initfn, > >+ .handle_packet = usb_generic_handle_packet, > >+ .handle_reset = ccid_handle_reset, > >+ .handle_control = ccid_handle_control, > >+ .handle_data = ccid_handle_data, > >+ .handle_destroy = ccid_handle_destroy, > >+ .usbdevice_name = "ccid", > >+ .qdev.props = (Property[]) { > >+ DEFINE_PROP_UINT8("debug", USBCCIDState, debug, 0), > >+ DEFINE_PROP_END_OF_LIST(), > >+ }, > >+#ifdef ENABLE_MIGRATION > >+ .qdev.vmsd =&ccid_vmstate, > >+#endif > >+}; > >+ > >+ > >+static void ccid_register_devices(void) > >+{ > >+ usb_qdev_register(&ccid_info); > >+} > >+device_init(ccid_register_devices) > > Regards, > > Anthony Liguori ^ permalink raw reply [flat|nested] 35+ messages in thread
end of thread, other threads:[~2011-03-16 9:27 UTC | newest] Thread overview: 35+ messages (download: mbox.gz / follow: Atom feed) -- links below jump to the message on this page -- 2011-01-11 8:42 [Qemu-devel] [PATCH 0/7] usb-ccid (v15) Alon Levy 2011-01-11 8:42 ` [Qemu-devel] [PATCH 1/7] usb-ccid: add CCID bus Alon Levy 2011-01-11 8:42 ` [Qemu-devel] [PATCH 2/7] ccid: add passthru card device Alon Levy 2011-01-25 14:17 ` Anthony Liguori 2011-01-25 16:21 ` Alon Levy 2011-01-25 16:24 ` Anthony Liguori 2011-01-25 16:50 ` Alon Levy 2011-01-27 21:13 ` Alon Levy 2011-01-27 21:42 ` Anthony Liguori 2011-01-30 17:35 ` Alon Levy 2011-01-11 8:42 ` [Qemu-devel] [PATCH 3/7] libcacard: initial commit after coding style fixes Alon Levy 2011-01-25 14:19 ` Anthony Liguori 2011-01-11 8:42 ` [Qemu-devel] [PATCH 4/7] ccid: add ccid-card-emulated device (v2) Alon Levy 2011-01-25 14:21 ` Anthony Liguori 2011-01-25 16:24 ` Alon Levy 2011-01-25 16:27 ` Anthony Liguori 2011-01-31 19:28 ` Alon Levy 2011-01-11 8:42 ` [Qemu-devel] [PATCH 5/7] ccid: add docs Alon Levy 2011-01-11 8:42 ` [Qemu-devel] [PATCH 6/7] ccid: configure: add --enable/disable and nss only disable Alon Levy 2011-01-11 8:42 ` [Qemu-devel] [PATCH 6/6] ccid: configure: add --enable-smartcard and --disable-smartcard Alon Levy 2011-01-11 9:03 ` Alon Levy 2011-01-11 8:42 ` [Qemu-devel] [PATCH 7/7] ccid: add qdev description strings Alon Levy 2011-01-17 15:56 ` [Qemu-devel] [PATCH 0/7] usb-ccid (v15) Alon Levy -- strict thread matches above, loose matches on Subject: below -- 2011-02-23 11:20 [Qemu-devel] [PATCH v20 0/7] usb-ccid Alon Levy 2011-02-23 11:20 ` [Qemu-devel] [PATCH 1/7] usb-ccid: add CCID bus Alon Levy 2011-03-14 13:54 ` Jes Sorensen 2011-03-14 14:07 ` Daniel P. Berrange 2011-03-14 14:12 ` Anthony Liguori 2011-03-16 9:15 ` Alon Levy 2011-03-16 9:26 ` Jes Sorensen 2011-02-07 16:34 [Qemu-devel] [PATCH 0/7] usb-ccid (v19) Alon Levy 2011-02-07 16:34 ` [Qemu-devel] [PATCH 1/7] usb-ccid: add CCID bus Alon Levy 2011-02-22 16:03 ` Anthony Liguori 2011-02-23 15:10 ` Alon Levy 2011-01-11 8:38 [Qemu-devel] [PATCH 0/7] usb-ccid (v14) Alon Levy 2011-01-11 8:38 ` [Qemu-devel] [PATCH 1/7] usb-ccid: add CCID bus Alon Levy 2011-01-25 14:10 ` Anthony Liguori 2011-01-25 16:10 ` Alon Levy
This is an external index of several public inboxes, see mirroring instructions on how to clone and mirror all data and code used by this external index.