initial commit related to CCID device/firmware implementation

The CCID core here shall be rather generic, and combined with the
hardware specific bits for (initially) the sysmoOCTSIM target.

Change-Id: I2789f7fcdb1b24c6ef47c7f37f4889f90acfd698
diff --git a/ccid/Makefile b/ccid/Makefile
new file mode 100644
index 0000000..34df8c9
--- /dev/null
+++ b/ccid/Makefile
@@ -0,0 +1,10 @@
+
+
+ccid_descriptors: ccid_descriptors.o
+	$(CC) -lasan -losmocore -laio -o $@ $^
+
+%.o: %.c
+	$(CC) -o $@ -c $^
+
+clean:
+	rm ccid_descriptors *.o
diff --git a/ccid/ccid_descriptors.c b/ccid/ccid_descriptors.c
new file mode 100644
index 0000000..7117fbb
--- /dev/null
+++ b/ccid/ccid_descriptors.c
@@ -0,0 +1,358 @@
+
+#include <stdint.h>
+#include <endian.h>
+#include <sys/types.h>
+#include <linux/usb/functionfs.h>
+
+#include "ccid_proto.h"
+
+
+#if __BYTE_ORDER == __LITTLE_ENDIAN
+#define cpu_to_le16(x)  (x)
+#define cpu_to_le32(x)  (x)
+#else
+#define cpu_to_le16(x)  ((((x) >> 8) & 0xffu) | (((x) & 0xffu) << 8))
+#define cpu_to_le32(x)  \
+	((((x) & 0xff000000u) >> 24) | (((x) & 0x00ff0000u) >>  8) | \
+	(((x) & 0x0000ff00u) <<  8) | (((x) & 0x000000ffu) << 24))
+#endif
+
+#define le32_to_cpu(x)  le32toh(x)
+#define le16_to_cpu(x)  le16toh(x)
+
+/***********************************************************************
+ * Actual USB CCID Descriptors
+ ***********************************************************************/
+
+static const struct {
+	struct usb_functionfs_descs_head_v2 header;
+	__le32 fs_count;
+	struct {
+		struct usb_interface_descriptor intf;
+		struct usb_ccid_class_descriptor ccid;
+		struct usb_endpoint_descriptor_no_audio ep_irq;
+		struct usb_endpoint_descriptor_no_audio ep_out;
+		struct usb_endpoint_descriptor_no_audio ep_in;
+	} __attribute__ ((packed)) fs_descs;
+} __attribute__ ((packed)) descriptors = {
+	.header = {
+		.magic = cpu_to_le32(FUNCTIONFS_DESCRIPTORS_MAGIC_V2),
+		.flags = cpu_to_le32(FUNCTIONFS_HAS_FS_DESC),
+		.length = cpu_to_le32(sizeof(descriptors)),
+	},
+	.fs_count = cpu_to_le32(5),
+	.fs_descs = {
+		.intf = {
+			.bLength = sizeof(descriptors.fs_descs.intf),
+			.bDescriptorType = USB_DT_INTERFACE,
+			.bNumEndpoints = 3,
+			.bInterfaceClass = 11,
+			.iInterface = 1,
+		},
+		.ccid = {
+			.bLength = sizeof(descriptors.fs_descs.ccid),
+			.bDescriptorType = 33,
+			.bcdCCID = cpu_to_le16(0x0110),
+			.bMaxSlotIndex = 7,
+			.bVoltageSupport = 0x07, /* 5/3/1.8V */
+			.dwProtocols = cpu_to_le32(1), /* T=0 only */
+			.dwDefaultClock = cpu_to_le32(5000),
+			.dwMaximumClock = cpu_to_le32(20000),
+			.bNumClockSupported = 0,
+			.dwDataRate = cpu_to_le32(9600),
+			.dwMaxDataRate = cpu_to_le32(921600),
+			.bNumDataRatesSupported = 0,
+			.dwMaxIFSD = cpu_to_le32(0),
+			.dwSynchProtocols = cpu_to_le32(0),
+			.dwMechanical = cpu_to_le32(0),
+			.dwFeatures = cpu_to_le32(0x10),
+			.dwMaxCCIDMessageLength = 272,
+			.bClassGetResponse = 0xff,
+			.bClassEnvelope = 0xff,
+			.wLcdLayout = cpu_to_le16(0),
+			.bPINSupport = 0,
+			.bMaxCCIDBusySlots = 8,
+		},
+		.ep_irq = {
+			.bLength = sizeof(descriptors.fs_descs.ep_irq),
+			.bDescriptorType = USB_DT_ENDPOINT,
+			.bEndpointAddress = 1 | USB_DIR_IN,
+			.bmAttributes = USB_ENDPOINT_XFER_INT,
+			.wMaxPacketSize = 64,
+		},
+		.ep_out = {
+			.bLength = sizeof(descriptors.fs_descs.ep_out),
+			.bDescriptorType = USB_DT_ENDPOINT,
+			.bEndpointAddress = 2 | USB_DIR_OUT,
+			.bmAttributes = USB_ENDPOINT_XFER_BULK,
+			/* .wMaxPacketSize = autoconfiguration (kernel) */
+		},
+		.ep_in = {
+			.bLength = sizeof(descriptors.fs_descs.ep_in),
+			.bDescriptorType = USB_DT_ENDPOINT,
+			.bEndpointAddress = 3 | USB_DIR_IN,
+			.bmAttributes = USB_ENDPOINT_XFER_BULK,
+			/* .wMaxPacketSize = autoconfiguration (kernel) */
+		},
+	},
+};
+
+#define STR_INTERFACE_ "Osmocom CCID Interface"
+
+static const struct {
+	struct usb_functionfs_strings_head header;
+	struct {
+		__le16 code;
+		const char str1[sizeof(STR_INTERFACE_)];
+	} __attribute__((packed)) lang0;
+} __attribute__((packed)) strings = {
+	.header = {
+		.magic = cpu_to_le32(FUNCTIONFS_STRINGS_MAGIC),
+		.length = cpu_to_le32(sizeof(strings)),
+		.str_count = cpu_to_le32(1),
+		.lang_count = cpu_to_le32(1),
+	},
+	.lang0 = {
+		cpu_to_le16(0x0409), /* en-us */
+		STR_INTERFACE_,
+	},
+};
+
+
+
+/***********************************************************************
+ * USB FunctionFS interface
+ ***********************************************************************/
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <unistd.h>
+#include <assert.h>
+#include <fcntl.h>
+#include <sys/stat.h>
+#include <osmocom/core/select.h>
+#include <osmocom/core/utils.h>
+
+#ifndef FUNCTIONFS_SUPPORTS_POLL
+#include <libaio.h>
+struct aio_help {
+	uint8_t buf[64];
+	struct iocb *iocb;
+};
+#endif
+
+/* usb function handle */
+struct ufunc_handle {
+	struct osmo_fd ep0;
+	struct osmo_fd ep_in;
+	struct osmo_fd ep_out;
+	struct osmo_fd ep_int;
+#ifndef FUNCTIONFS_SUPPORTS_POLL
+	struct osmo_fd aio_evfd;
+	io_context_t aio_ctx;
+	struct aio_help aio_in;
+	struct aio_help aio_out;
+	struct aio_help aio_int;
+#endif
+};
+
+static int ep_int_cb(struct osmo_fd *ofd, unsigned int what)
+{
+	printf("INT\n");
+	return 0;
+}
+
+static int ep_out_cb(struct osmo_fd *ofd, unsigned int what)
+{
+	uint8_t buf[64];
+	int rc;
+
+	printf("OUT\n");
+	if (what & BSC_FD_READ) {
+		rc = read(ofd->fd, buf, sizeof(buf));
+		ccid_handle_out(uh->ccid_handle, buf, rc);
+	}
+	return 0;
+}
+
+static int ep_in_cb(struct osmo_fd *ofd, unsigned int what)
+{
+	printf("IN\n");
+	if (what & BSC_FD_WRITE) {
+		/* write what we have to write */
+	}
+	return 0;
+}
+
+const struct value_string ffs_evt_type_names[] = {
+	{ FUNCTIONFS_BIND,	"BIND" },
+	{ FUNCTIONFS_UNBIND,	"UNBIND" },
+	{ FUNCTIONFS_ENABLE,	"ENABLE" },
+	{ FUNCTIONFS_DISABLE,	"DISABLE" },
+	{ FUNCTIONFS_SETUP,	"SETUP" },
+	{ FUNCTIONFS_SUSPEND,	"SUSPEND" },
+	{ FUNCTIONFS_RESUME,	"RESUME" },
+	{ 0, NULL }
+};
+
+static void handle_setup(const struct usb_ctrlrequest *setup)
+{
+	printf("bRequestType = %d\n", setup->bRequestType);
+	printf("bRequest     = %d\n", setup->bRequest);
+	printf("wValue       = %d\n", le16_to_cpu(setup->wValue));
+	printf("wIndex       = %d\n", le16_to_cpu(setup->wIndex));
+	printf("wLength      = %d\n", le16_to_cpu(setup->wLength));
+}
+
+static void aio_refill_out(struct ufunc_handle *uh);
+
+static int ep_0_cb(struct osmo_fd *ofd, unsigned int what)
+{
+	struct ufunc_handle *uh = (struct ufunc_handle *) ofd->data;
+	int rc;
+
+	printf("EP0\n");
+
+	if (what & BSC_FD_READ) {
+		struct usb_functionfs_event evt;
+		rc = read(ofd->fd, (uint8_t *)&evt, sizeof(evt));
+		if (rc < sizeof(evt))
+			return -23;
+		printf("\t%s\n", get_value_string(ffs_evt_type_names, evt.type));
+		switch (evt.type) {
+		case FUNCTIONFS_ENABLE:
+			aio_refill_out(uh);
+			break;
+		case FUNCTIONFS_SETUP:
+			handle_setup(&evt.u.setup);
+			break;
+		}
+
+	}
+	return 0;
+}
+
+#ifndef FUNCTIONFS_SUPPORTS_POLL
+
+static void aio_refill_out(struct ufunc_handle *uh)
+{
+	int rc;
+	struct aio_help *ah = &uh->aio_out;
+	io_prep_pread(ah->iocb, uh->ep_out.fd, ah->buf, sizeof(ah->buf), 0);
+	io_set_eventfd(ah->iocb, uh->aio_evfd.fd);
+	rc = io_submit(uh->aio_ctx, 1, &ah->iocb);
+	OSMO_ASSERT(rc >= 0);
+}
+
+static int evfd_cb(struct osmo_fd *ofd, unsigned int what)
+{
+	struct ufunc_handle *uh = (struct ufunc_handle *) ofd->data;
+	struct io_event evt[3];
+	uint64_t ev_cnt;
+	int i, rc;
+
+	rc = read(ofd->fd, &ev_cnt, sizeof(ev_cnt));
+	assert(rc == sizeof(ev_cnt));
+
+	rc = io_getevents(uh->aio_ctx, 1, 3, evt, NULL);
+	if (rc <= 0)
+		return rc;
+
+	for (i = 0; i < rc; i++) {
+		int fd = evt[i].obj->aio_fildes;
+		if (fd == uh->ep_int.fd) {
+			/* interrupt endpoint AIO has completed. This means the IRQ transfer
+			 * which we generated has reached the host */
+		} else if (fd == uh->ep_in.fd) {
+			/* IN endpoint AIO has completed. This means the IN transfer which
+			 * we sent to the host has completed */
+		} else if (fd == uh->ep_out.fd) {
+			/* IN endpoint AIO has completed. This means the host has sent us
+			 * some OUT data */
+			//printf("\t%s\n", osmo_hexdump(uh->aio_out.buf, evt[i].res));
+			ccid_handle_out(uh->ccid_handle, uh->aio_out.buf, evt[i].res);
+			aio_refill_out(uh);
+		}
+	}
+}
+#endif
+
+
+static int ep0_init(struct ufunc_handle *uh)
+{
+	int rc;
+
+	/* open control endpoint and write descriptors to it */
+	rc = open("ep0", O_RDWR);
+	assert(rc >= 0);
+	osmo_fd_setup(&uh->ep0, rc, BSC_FD_READ, &ep_0_cb, uh, 0);
+	osmo_fd_register(&uh->ep0);
+	rc = write(uh->ep0.fd, &descriptors, sizeof(descriptors));
+	if (rc != sizeof(descriptors))
+		return -1;
+	rc = write(uh->ep0.fd, &strings, sizeof(strings));
+	if (rc != sizeof(strings))
+		return -1;
+
+	/* open other endpoint file descriptors */
+	rc = open("ep1", O_RDWR);
+	assert(rc >= 0);
+	osmo_fd_setup(&uh->ep_int, rc, 0, &ep_int_cb, uh, 1);
+#ifdef FUNCTIONFS_SUPPORTS_POLL
+	osmo_fd_register(&uh->ep_int);
+#endif
+
+	rc = open("ep2", O_RDWR);
+	assert(rc >= 0);
+	osmo_fd_setup(&uh->ep_out, rc, BSC_FD_READ, &ep_out_cb, uh, 2);
+#ifdef FUNCTIONFS_SUPPORTS_POLL
+	osmo_fd_register(&uh->ep_out);
+#endif
+
+	rc = open("ep3", O_RDWR);
+	assert(rc >= 0);
+	osmo_fd_setup(&uh->ep_in, rc, 0, &ep_in_cb, uh, 3);
+#ifdef FUNCTIONFS_SUPPORTS_POLL
+	osmo_fd_register(&uh->ep_in);
+#endif
+
+#ifndef FUNCTIONFS_SUPPORTS_POLL
+#include <sys/eventfd.h>
+	/* for some absolutely weird reason, gadgetfs+functionfs don't support
+	 * the standard methods of non-blocking I/o (select/poll).  We need to
+	 * work around using Linux AIO, which is not to be confused with POSIX AIO! */
+
+	memset(&uh->aio_ctx, 0, sizeof(uh->aio_ctx));
+	rc = io_setup(3, &uh->aio_ctx);
+	OSMO_ASSERT(rc >= 0);
+
+	/* create an eventfd, which will be marked readable once some AIO completes */
+	rc = eventfd(0, 0);
+	OSMO_ASSERT(rc >= 0);
+	osmo_fd_setup(&uh->aio_evfd, rc, BSC_FD_READ, &evfd_cb, uh, 0);
+	osmo_fd_register(&uh->aio_evfd);
+
+	uh->aio_out.iocb = malloc(sizeof(struct iocb));
+
+#endif
+
+	return 0;
+}
+
+
+int main(int argc, char **argv)
+{
+	struct ufunc_handle ufh = (struct ufunc_handle) { 0, };
+	int rc;
+
+	chdir(argv[1]);
+	rc = ep0_init(&ufh);
+	if (rc < 0) {
+		fprintf(stderr, "Error %d\n", rc);
+		exit(2);
+	}
+
+	while (1) {
+		osmo_select_main(0);
+	}
+}
diff --git a/ccid/ccid_device.c b/ccid/ccid_device.c
new file mode 100644
index 0000000..c81f177
--- /dev/null
+++ b/ccid/ccid_device.c
@@ -0,0 +1,370 @@
+#include <stdlib.h>
+#include <stdio.h>
+#include <unistd.h>
+#include <assert.h>
+
+#include <osmocom/core/msgb.h>
+#include <osmocom/core/utils.h>
+
+#include "ccid_proto.h"
+
+#define NR_SLOTS	8
+
+struct ccid_slot {
+	struct ccid_instance *ci;
+	uint8_t slot_nr;
+	bool icc_present;
+	bool icc_powered;
+	bool icc_in_reset;
+};
+
+struct ccid_ops {
+	int (*send)(struct ccid_instance *ci, struct msgb *msg);
+};
+
+struct ccid_instance {
+	struct ccid_slot slot[NR_SLOTS];
+	struct ccid_ops ops;
+};
+
+#define msgb_ccid_out(x) (union ccid_pc_to_rdr *)msgb_data(x)
+#define msgb_ccid_in(x) (union ccid_rdr_to_pc *)msgb_data(x)
+
+static struct ccid_slot *get_ccid_slot(struct ccid_instance *ci, uint8_t slot_nr)
+{
+	if (slot_nr >= sizeof(ci->slot))
+		return NULL;
+	else
+		return &ci->slot[slot_nr]
+}
+
+static uint8_t get_icc_status(const struct ccid_slot *cs)
+{
+	if (cs->icc_present && cs->icc_powered && !cs->icc_in_reset)
+		return CCID_ICC_STATUS_PRES_ACT;
+	else if (!cs->icc_present)
+		return CCID_ICC_STATUS_NO_ICC;
+	else
+		return CCID_ICC_STATUS_PRES_INACT;
+}
+
+#define SET_HDR(x, msg_type, slot, seq) do {	\
+	(x)->hdr.bMessageType = msg_type;	\
+	(x)->hdr.dwLength = 0;		\
+	(x)->hdr.bSlot = slot;			\
+	(x)->hdr.bSeq = seq;			\
+	} while (0)
+
+#define SET_HDR_IN(x, msg_type, slot, seq, status, error) do {	\
+	SET_HDR(&(x)->hdr, msg_type, slot, seq);		\
+	(x)->hdr.bStatus = status;				\
+	(x)->hdr.bError = error;				\
+	} while (0)
+
+/***********************************************************************
+ * Message generation / sending
+ ***********************************************************************/
+
+static struct msgb *ccid_msgb_alloc(void)
+{
+	struct msgb *msg = msgb_alloc("ccid");
+	OSMO_ASSERT(msg);
+	return msg;
+}
+
+static int ccid_send(struct ccid_instance *ci, struct msgb *msg)
+{
+	return ci->ops.send_in(ci, msg);
+}
+
+static int ccid_slot_send(struct ccid_slot *cs, struct msgb *msg)
+{
+	const struct ccid_header *ch = (const struct ccid_header *) msgb_ccid_in(msg);
+
+	/* patch bSlotNr into message */
+	ch->hdr.bSlot = cs->slot_nr;
+	return ccid_send(cs->ci, msg);
+}
+
+
+/* Section 6.2.1 */
+static struct msgb *ccid_gen_data_block(struct ccid_slot *cs, uint8_t seq, uint8_t cmd_sts,
+					 enum ccid_error_code err, const uint8_t *data,
+					 uint32_t data_len)
+{
+	struct msgb *msg = ccid_msgb_alloc();
+	struct ccid_rdr_to_pc_data_block *db = msgb_put(msg, sizeof(*db) + data_len);
+	uint8_t sts = (cmd_sts & CCID_CMD_STATUS_MASK) | get_icc_status(cs);
+
+	SET_HDR_IN(db, RDR_to_PC_DataBlock, cs->slot_nr, seq, sts, err);
+	db->hdr.dwLength = cpu_to_le32(data_len);
+	memcpy(db->abData, data, data_len);
+	return msg;
+}
+
+/* Section 6.2.2 */
+static struct msgb *ccid_gen_slot_status(struct ccid_slot *cs, uint8_t seq, uint8_t cmd_sts,
+					 enum ccid_error_code err)
+{
+	struct msgb *msg = ccid_msgb_alloc();
+	struct ccid_rdr_to_pc_slot_status *ss = msgb_put(msg, sizeof(*ss));
+	uint8_t sts = (cmd_sts & CCID_CMD_STATUS_MASK) | get_icc_status(cs);
+
+	SET_HDR_IN(ss, RDR_to_PC_SlotStatus, cs->slot_nr, seq, sts, err);
+	return msg;
+}
+
+/* Section 6.2.3 */
+/* TODO */
+
+/* Section 6.2.4 */
+static struct msgb *ccid_gen_escape(struct ccid_slot *cs, uint8_t seq, uint8_t cmd_sts,
+				    enum ccid_error_code err, const uint8_t *data,
+				    uint32_t data_len)
+{
+	struct msgb *msg = ccid_msgb_alloc();
+	struct ccid_rdr_to_pc_escape *esc = msgb_put(msg, sizeof(*esc) + data_len);
+	uint8_t sts = (cmd_sts & CCID_CMD_STATUS_MASK) | get_icc_status(cs);
+
+	SET_HDR_IN(esc, RDR_to_PC_Escape, cs->slot_nr, seq, sts, err);
+	esc->hdr.dwLength = cpu_to_le32(data_len);
+	memcpy(esc->abData, data, data_len);
+	return msg;
+}
+
+/* Section 6.2.5 */
+static struct msgb *ccid_gen_clock_and_rate(struct ccid_slot *cs, uint8_t seq, uint8_t cmd_sts,
+					    enum ccid_error_code err, uint32_t clock_khz, uint32_t rate_bps)
+{
+	struct msgb *msg = ccid_msgb_alloc();
+	struct ccid_rdr_to_pc_data_rate_and_clock *drc = msgb_put(msg, sizeof(*drc));
+	uint8_t sts = (cmd_sts & CCID_CMD_STATUS_MASK) | get_icc_status(cs);
+
+	SET_HDR_IN(drc, RDR_to_PC_DataRateAndClockFrequency, cs->slot_nr, seq, sts, err);
+	drc->dwLength = cpu_to_le32(8); /* Message-specific data length (wtf?) */
+	drc->dwClockFrequency = cpu_to_le32(clock_khz); /* kHz */
+	drc->dwDataRate = cpu_to_le32(rate_bps); /* bps */
+	return msg;
+}
+
+
+
+#if 0
+static struct msgb *gen_err_resp(struct ccid_instance *ci, enum ccid_msg_type msg_type,
+				 enum ccid_error_code err_code)
+{
+	struct c
+}
+#endif
+
+/***********************************************************************
+ * Message reception / parsing
+ ***********************************************************************/
+
+/* Section 6.1.3 */
+static int ccid_handle_get_slot_status(struct ccid_slot *cs, struct msgb *msg)
+{
+	const union ccid_pc_to_rdr *u = msgb_ccid_out(msg);
+	struct msgb *resp;
+
+	resp = ccid_gen_slot_status(cs, u->get_slot_status.hdr.bSeq, CCID_CMD_STATUS_OK, 0);
+
+	return ccid_send(cs->ci, resp);
+}
+
+
+/* Section 6.1.1 */
+static int ccid_handle_icc_power_on(struct ccid_slot *cs, struct msgb *msg)
+{
+	const union ccid_pc_to_rdr *u = msgb_ccid_out(msg);
+	struct msgb *resp;
+
+	/* TODO: send actual ATR; handle error cases */
+	/* TODO: handle this asynchronously */
+	resp = ccid_gen_data_block(cs, u->icc_power_on.hdr.hSeq, CCID_CMD_STATUS_OK, 0, NULL, 0);
+
+	return ccid_send(cs->ci, resp);
+}
+
+/* Section 6.1.2 */
+static int ccid_handle_icc_power_off(struct ccid_slot *cs, struct msgb *msg)
+{
+	const union ccid_pc_to_rdr *u = msgb_ccid_out(msg);
+	resp = ccid_gen_slot_status(cs, u->get_slot_status.hdr.bSeq, CCID_CMD_STATUS_OK, 0);
+	return ccid_send(cs->ci, resp);
+}
+
+/* Section 6.1.4 */
+static int ccid_handle_xfr_block(struct ccid_slot *cs, struct msgb *msg)
+{
+	const union ccid_pc_to_rdr *u = msgb_ccid_out(msg);
+	resp = ccid_gen_data_block(cs, u->icc_power_on.hdr.hSeq, CCID_CMD_STATUS_OK, 0, NULL, 0);
+	return ccid_send(cs->ci, resp);
+}
+
+/* Section 6.1.5 */
+static int ccid_handle_get_parameters(struct ccid_slot *cs, struct msgb *msg)
+{
+	const union ccid_pc_to_rdr *u = msgb_ccid_out(msg);
+}
+
+/* Section 6.1.6 */
+static int ccid_handle_reset_parameters(struct ccid_slot *cs, struct msgb *msg)
+{
+	const union ccid_pc_to_rdr *u = msgb_ccid_out(msg);
+}
+
+/* Section 6.1.7 */
+static int ccid_handle_set_parameters(struct ccid_slot *cs, struct msgb *msg)
+{
+	const union ccid_pc_to_rdr *u = msgb_ccid_out(msg);
+}
+
+/* Section 6.1.8 */
+static int ccid_handle_escape(struct ccid_slot *cs, struct msgb *msg)
+{
+	const union ccid_pc_to_rdr *u = msgb_ccid_out(msg);
+}
+
+/* Section 6.1.9 */
+static int ccid_handle_icc_clock(struct ccid_slot *cs, struct msgb *msg)
+{
+	const union ccid_pc_to_rdr *u = msgb_ccid_out(msg);
+	resp = ccid_gen_slot_status(cs, u->get_slot_status.hdr.bSeq, CCID_CMD_STATUS_OK, 0);
+	return ccid_send(cs->ci, resp);
+}
+
+/* Section 6.1.10 */
+static int ccid_handle_t0apdu(struct ccid_slot *cs, struct msgb *msg)
+{
+	const union ccid_pc_to_rdr *u = msgb_ccid_out(msg);
+	resp = ccid_gen_slot_status(cs, u->get_slot_status.hdr.bSeq, CCID_CMD_STATUS_OK, 0);
+	return ccid_send(cs->ci, resp);
+}
+
+/* Section 6.1.11 */
+static int ccid_handle_secure(struct ccid_slot *cs, struct msgb *msg)
+{
+	const union ccid_pc_to_rdr *u = msgb_ccid_out(msg);
+}
+
+/* Section 6.1.12 */
+static int ccid_handle_mechanical(struct ccid_slot *cs, struct msgb *msg)
+{
+	const union ccid_pc_to_rdr *u = msgb_ccid_out(msg);
+	resp = ccid_gen_slot_status(cs, u->get_slot_status.hdr.bSeq, CCID_CMD_STATUS_OK, 0);
+	return ccid_send(cs->ci, resp);
+}
+
+/* Section 6.1.13 */
+static int ccid_handle_abort(struct ccid_slot *cs, struct msgb *msg)
+{
+	const union ccid_pc_to_rdr *u = msgb_ccid_out(msg);
+	resp = ccid_gen_slot_status(cs, u->get_slot_status.hdr.bSeq, CCID_CMD_STATUS_OK, 0);
+	return ccid_send(cs->ci, resp);
+}
+
+/* Section 6.1.14 */
+static int ccid_handle_set_rate_and_clock(struct ccid_slot *cs, struct msgb *msg)
+{
+	const union ccid_pc_to_rdr *u = msgb_ccid(msg);
+}
+
+/* handle data arriving from the host on the OUT endpoint */
+int ccid_handle_out(struct ccid_instance *ci, struct msgb *msg)
+{
+	const union ccid_pc_to_rdr *u = msgb_ccid_out(msg);
+	const struct ccid_header *ch = (const struct ccid_header *) u;
+	unsigned int len = msgb_length(msg);
+	struct ccid_slot *cs;
+
+	if (len < sizeof(*ch)) {
+		/* FIXME */
+		return -1;
+	}
+
+	cs = get_ccid_slot(ci, ch->hdr.bSlot);
+	if (!cs) {
+		/* FIXME */
+		return -1;
+	}
+
+	switch (ch->bMessageType) {
+	case PC_to_RDR_GetSlotStatus:
+		if (len != sizeof(u->get_slot_status))
+			goto short_msg;
+		rc = ccid_handle_get_slot_status(cs, msg);
+		break;
+	case PC_to_RDR_IccPowerOn:
+		if (len != sizeof(u->icc_power_on))
+			goto short_msg;
+		rc = ccid_handle_icc_power_on(cs, msg);
+		break;
+	case PC_to_RDR_IccPowerOff:
+		if (len != sizeof(u->icc_power_off))
+			goto short_msg;
+		rc = ccid_handle_icc_power_off(cs, msg);
+		break;
+	case PC_to_RDR_XfrBlock:
+		if (len < sizeof(u->xfr_block))
+			goto short_msg;
+		rc = ccid_handle_xfr_block(cs, msg);
+		break;
+	case PC_to_RDR_GetParameters:
+		if (len != sizeof(u->get_parameters))
+			goto short_msg;
+		rc = ccid_handle_get_parameters(cs, msg);
+		break;
+	case PC_to_RDR_ResetParameters:
+		if (len != sizeof(u->reset_parameters))
+			goto short_msg;
+		rc = ccid_handle_reset_parameters(cs, msg);
+		break;
+	case PC_to_RDR_SetParameters:
+		if (len != sizeof(u->set_parameters))
+			goto short_msg;
+		rc = ccid_handle_set_parameters(cs, msg);
+		break;
+	case PC_to_RDR_Escape:
+		if (len < sizeof(u->escape))
+			goto short_msg;
+		rc = ccid_handle_escape(cs, msg);
+		break;
+	case PC_to_RDR_IccClock:
+		if (len != sizeof(u->icc_clock))
+			goto short_msg;
+		rc = ccid_handle_icc_clock(cs, msg);
+		break;
+	case PC_to_RDR_T0APDU:
+		if (len != /*FIXME*/ sizeof(u->t0apdu))
+			goto short_msg;
+		rc = ccid_handle_t0_apdu(cs, msg);
+		break;
+	case PC_to_RDR_Secure:
+		if (len < sizeof(u->secure))
+			goto short_msg;
+		rc = ccid_handle_secure(cs, msg);
+		break;
+	case PC_to_RDR_Mechanical:
+		if (len != sizeof(u->mechanical))
+			goto short_msg;
+		rc = ccid_handle_mechanical(cs, msg);
+		break;
+	case PC_to_RDR_Abort:
+		if (len != sizeof(u->abort))
+			goto short_msg;
+		rc = ccid_handle_abort(cs, msg);
+		break;
+	case PC_to_RDR_SetDataRateAndClockFrequency:
+		if (len != sizeof(u->set_rate_and_clock))
+			goto short_msg;
+		rc = ccid_handle_set_rate_and_clock(cs, msg);
+		break;
+	default:
+		FIXME
+		break;
+	}
+	FIXME
+short_msg:
+	FIXME
+}
diff --git a/ccid/ccid_proto.h b/ccid/ccid_proto.h
new file mode 100644
index 0000000..c3c740c
--- /dev/null
+++ b/ccid/ccid_proto.h
@@ -0,0 +1,367 @@
+#pragma once
+#include <stdint.h>
+
+/* Identifies the length of type of subordinate descriptors of a CCID device
+ * Table 5.1-1 Smart Card Device Class descriptors */
+struct usb_ccid_class_descriptor {
+	uint8_t  bLength;
+	uint8_t  bDescriptorType;
+	uint16_t bcdCCID;
+	uint8_t  bMaxSlotIndex;
+	uint8_t  bVoltageSupport;
+	uint32_t dwProtocols;
+	uint32_t dwDefaultClock;
+	uint32_t dwMaximumClock;
+	uint8_t  bNumClockSupported;
+	uint32_t dwDataRate;
+	uint32_t dwMaxDataRate;
+	uint8_t  bNumDataRatesSupported;
+	uint32_t dwMaxIFSD;
+	uint32_t dwSynchProtocols;
+	uint32_t dwMechanical;
+	uint32_t dwFeatures;
+	uint32_t dwMaxCCIDMessageLength;
+	uint8_t  bClassGetResponse;
+	uint8_t  bClassEnvelope;
+	uint16_t wLcdLayout;
+	uint8_t  bPINSupport;
+	uint8_t  bMaxCCIDBusySlots;
+} __attribute__((packed));
+/* handling of bulk out from host */
+
+enum ccid_msg_type {
+	/* Section 6.3 / Table 6.3-1: Interrupt IN */
+	RDR_to_PC_NotifySlotChange		= 0x50,
+	RDR_to_PC_HardwareError			= 0x51,
+
+	/* Section 6.1 / Table 6.1-1: Bulk OUT */
+	PC_to_RDR_IccPowerOn			= 0x62,
+	PC_to_RDR_IccPowerOff			= 0x63,
+	PC_to_RDR_GetSlotStatus			= 0x65,
+	PC_to_RDR_XfrBlock			= 0x6f,
+	PC_to_RDR_GetParameters			= 0x6c,
+	PC_to_RDR_ResetParameters		= 0x6d,
+	PC_to_RDR_SetParameters			= 0x61,
+	PC_to_RDR_Escape			= 0x6b,
+	PC_to_RDR_IccClock			= 0x6e,
+	PC_to_RDR_T0APDU			= 0x6a,
+	PC_to_RDR_Secure			= 0x69,
+	PC_to_RDR_Mechanical			= 0x71,
+	PC_to_RDR_Abort				= 0x72,
+	PC_to_RDR_SetDataRateAndClockFrequency	= 0x73,
+
+	/* Section 6.2 / Table 6.2-1: Bulk IN */
+	RDR_to_PC_DataBlock			= 0x80,
+	RDR_to_PC_SlotStatus			= 0x81,
+	RDR_to_PC_Parameters			= 0x82,
+	RDR_to_PC_Escape			= 0x83,
+	RDR_to_PC_DataRateAndClockFrequency	= 0x84,
+};
+
+/* CCID message header on BULK-OUT endpoint */
+struct ccid_header {
+	uint8_t		bMessageType;
+	uint32_t	dwLength;
+	uint8_t		bSlot;
+	uint8_t		bSeq;
+} __attribute__ ((packed));
+
+/***********************************************************************
+ * Bulk OUT
+ ***********************************************************************/
+
+/* Section 6.1.1 */
+enum ccid_power_select {
+	CCID_PWRSEL_AUTO	= 0x00,
+	CCID_PWRSEL_5V0		= 0x01,
+	CCID_PWRSEL_3V0		= 0x02,
+	CCID_PWRSEL_1V8		= 0x03,
+};
+
+/* Section 6.1.1 */
+struct ccid_pc_to_rdr_icc_power_on {
+	struct ccid_header hdr;
+	uint8_t bPowerSelect;
+	uint8_t abRFU[2];
+} __attribute__ ((packed));
+/* Response: RDR_to_PC_DataBlock */
+
+/* Section 6.1.2 */
+struct ccid_pc_to_rdr_icc_power_off {
+	struct ccid_header hdr;
+	uint8_t abRFU[3];
+} __attribute__ ((packed));
+/* Response: RDR_to_PC_SlotStatus */
+
+/* Section 6.1.3 */
+struct ccid_pc_to_rdr_get_slot_status {
+	struct ccid_header hdr;
+	uint8_t abRFU[3];
+} __attribute__ ((packed));
+/* Response: RDR_to_PC_SlotStatus */
+
+/* Section 6.1.4 */
+struct ccid_pc_to_rdr_xfr_block {
+	struct ccid_header hdr;
+	uint8_t bBWI;
+	uint16_t wLevelParameter;
+	uint8_t abData[0];
+} __attribute__ ((packed));
+/* Response: RDR_to_PC_DataBlock */
+
+/* Section 6.1.5 */
+struct ccid_pc_to_rdr_get_parameters {
+	struct ccid_header hdr;
+	uint8_t abRFU[3];
+} __attribute__ ((packed));
+/* Response: RDR_to_PC_Parameters */
+
+/* Section 6.1.6 */
+struct ccid_pc_to_rdr_reset_parameters {
+	struct ccid_header hdr;
+	uint8_t abRFU[3];
+} __attribute__ ((packed));
+/* Response: RDR_to_PC_Parameters */
+
+/* Section 6.1.7 */
+struct ccid_proto_data_t0 {
+	uint8_t bmFindexDindex;
+	uint8_t bmTCCKST0;
+	uint8_t bGuardTimeT0;
+	uint8_t bWaitingIntegerT0;
+	uint8_t bClockStop;
+} __attribute__ ((packed));
+struct ccid_proto_data_t1 {
+	uint8_t bmFindexDindex;
+	uint8_t bmTCCKST1;
+	uint8_t bGuardTimeT1;
+	uint8_t bWaitingIntegersT1;
+	uint8_t bClockStop;
+	uint8_t bFSC;
+	uint8_t bNadValue;
+} __attribute__ ((packed));
+struct ccid_pc_to_rdr_set_parameters {
+	struct ccid_header hdr;
+	uint8_t bProtocolNum;
+	uint8_t abRFU[2];
+	union {
+		struct ccid_proto_data_t0 t0;
+		struct ccid_proto_data_t1 t1;
+	} abProtocolData;
+} __attribute__ ((packed));
+/* Response: RDR_to_PC_Parameters */
+
+/* Section 6.1.8 */
+struct ccid_pc_to_rdr_escape {
+	struct ccid_header hdr;
+	uint8_t abRFU[3];
+	uint8_t abData[0];
+} __attribute__ ((packed));
+/* Response: RDR_to_PC_Escape */
+
+/* Section 6.1.9 */
+enum ccid_clock_command {
+	CCID_CLOCK_CMD_RESTART		= 0x00,
+	CCID_CLOCK_CMD_STOP		= 0x01,
+};
+struct ccid_pc_to_rdr_icc_clock {
+	struct ccid_header hdr;
+	uint8_t bClockCommand;
+	uint8_t abRFU[2];
+} __attribute__ ((packed));
+/* response: RDR_to_PC_SlotStatus */
+
+/* Section 6.1.10 */
+struct ccid_pc_to_rdr_t0apdu {
+	struct ccid_header hdr;
+	uint8_t bmChanges;
+	uint8_t bClassGetResponse;
+	uint8_t bClassEnvelope;
+} __attribute__ ((packed));
+/* Response: RDR_to_PC_SlotStatus */
+
+/* Section 6.1.11 */
+struct ccid_pc_to_rdr_secure {
+	struct ccid_header hdr;
+	uint8_t bBWI;
+	uint16_t wLevelParameter;
+	uint8_t abData[0];
+} __attribute__ ((packed));
+struct ccid_pin_operation_data {
+	uint8_t bPINOperation;
+	uint8_t abPNDataStructure[0];
+} __attribute__ ((packed));
+struct ccid_pin_verification_data {
+	uint8_t bTimeOut;
+	uint8_t bmFormatString;
+	uint8_t bmPINBlockString;
+	uint8_t bmPINLengthFormat;
+	uint16_t wPINMaxExtraDigit;
+	uint8_t bEntryValidationCondition;
+	uint8_t bNumberMessage;
+	uint16_t wLangId;
+	uint8_t bMsgIndex;
+	uint8_t bTecPrologue;
+	uint8_t abPINApdu[0];
+} __attribute__ ((packed));
+/* Response: RDR_to_PC_DataBlock */
+
+/* Section 6.1.12 */
+struct ccid_pc_to_rdr_mechanical {
+	struct ccid_header hdr;
+	uint8_t bFunction; /* ccid_mech_function */
+	uint8_t abRFU[2];
+} __attribute__ ((packed));
+enum ccid_mech_function {
+	CCID_MECH_FN_ACCEPT_CARD	= 0x01,
+	CCID_MECH_FN_EJECT_CARD		= 0x02,
+	CCID_MECH_FN_CAPTURE_CARD	= 0x03,
+	CCID_MECH_FN_LOCK_CARD		= 0x04,
+	CCID_MECH_FN_UNLOCK_CARD	= 0x05,
+};
+/* Response: RDR_to_PC_SlotStatus */
+
+/* Section 6.1.13 */
+struct ccid_pc_to_rdr_abort {
+	struct ccid_header hdr;
+	uint8_t abRFU[3];
+} __attribute__ ((packed));
+/* Response: RDR_to_PC_SlotStatus */
+
+/* Section 6.1.14 */
+struct ccid_pc_to_rdr_set_rate_and_clock {
+	struct ccid_header hdr;
+	uint8_t abRFU[3];
+	uint32_t dwClockFrequency;
+	uint32_t dwDataRate;
+} __attribute__ ((packed));
+/* Response: RDR_to_PC_DataRateAndClockFrequency */
+
+union ccid_pc_to_rdr {
+	struct ccid_pc_to_rdr_icc_power_on		icc_power_on;
+	struct ccid_pc_to_rdr_icc_power_off		icc_power_off;
+	struct ccid_pc_to_rdr_get_slot_status		get_slot_status;
+	struct ccid_pc_to_rdr_xfr_block			xfr_block;
+	struct ccid_pc_to_rdr_get_parameters		get_parameters;
+	struct ccid_pc_to_rdr_reset_parameters		reset_parameters;
+	struct ccid_pc_to_rdr_set_parameters		set_parameters;
+	struct ccid_pc_to_rdr_escape			escape;
+	struct ccid_pc_to_rdr_icc_clock			icc_clock;
+	struct ccid_pc_to_rdr_t0apdu			t0apdu;
+	struct ccid_pc_to_rdr_secure			secure;
+	struct ccid_pc_to_rdr_mechanical		mechanical;
+	struct ccid_pc_to_rdr_abort			abort;
+	struct ccid_pc_to_rdr_set_rate_and_clock	set_rate_and_clock;
+};
+
+/***********************************************************************
+ * Bulk IN
+ ***********************************************************************/
+
+/* CCID message header on BULK-IN endpoint */
+struct ccid_header_in {
+	struct ccid_header hdr;
+	uint8_t		bStatus;
+	uint8_t		bError;
+} __attribute__ ((packed));
+
+/* Section 6.2.1 RDR_to_PC_DataBlock */
+struct ccid_rdr_to_pc_data_block {
+	struct ccid_header_in hdr;
+	uint8_t bChainParameter;
+	uint8_t abData[0];
+} __attribute__ ((packed));
+
+/* Section 6.2.2 RDR_to_PC_SlotStatus */
+struct ccid_rdr_to_pc_slot_status {
+	struct ccid_header_in hdr;
+	uint8_t bClockStatus;
+} __attribute__ ((packed));
+enum ccid_clock_status {
+	CCID_CLOCK_STATUS_RUNNING	= 0x00,
+	CCID_CLOCK_STATUS_STOPPED_L	= 0x01,
+	CCID_CLOCK_STATUS_STOPPED_H	= 0x02,
+	CCID_CLOCK_STATUS_STOPPED_UNKN	= 0x03,
+};
+
+/* Section 6.2.3 RDR_to_PC_Parameters */
+struct ccid_rdr_to_pc_parameters {
+	struct ccid_header_in hdr;
+	union {
+		struct ccid_proto_data_t0 t0;
+		struct ccid_proto_data_t1 t1;
+	} abProtocolData;
+} __attribute__ ((packed));
+
+/* Section 6.2.4 RDR_to_PC_Escape */
+struct ccid_rdr_to_pc_escape {
+	struct ccid_header hdr;
+	uint8_t bRFU;
+	uint8_t abData[0];
+} __attribute__ ((packed));
+
+/* Section 6.2.5 RDR_to_PC_DataRateAndClockFrequency */
+struct ccid_rdr_to_pc_data_rate_and_clock {
+	struct ccid_header hdr;
+	uint8_t bRFU;
+	uint32_t dwClockFrequency;
+	uint32_t dwDataRate;
+} __attribute__ ((packed));
+
+/* Section 6.2.6 */
+#define CCID_ICC_STATUS_MASK		0x03
+#define CCID_ICC_STATUS_PRES_ACT	0x00
+#define CCID_ICC_STATUS_PRES_INACT	0x01
+#define CCID_ICC_STATUS_NO_ICC		0x02
+#define CCID_CMD_STATUS_MASK		0xC0
+#define CCID_CMD_STATUS_OK		0x00
+#define CCID_CMD_STATUS_FAILED		0x40
+#define CCID_CMD_STATUS_TIME_EXT	0x80
+/* Table 6.2-2: Slot Error value when bmCommandStatus == 1 */
+enum ccid_error_code {
+	CCID_ERR_CMD_ABORTED			= 0xff,
+	CCID_ERR_ICC_MUTE			= 0xfe,
+	CCID_ERR_XFR_PARITY_ERROR		= 0xfd,
+	CCID_ERR_XFR_OVERRUN			= 0xfc,
+	CCID_ERR_HW_ERROR			= 0xfb,
+	CCID_ERR_BAD_ATR_TS			= 0xf8,
+	CCID_ERR_BAD_ATR_TCK			= 0xf7,
+	CCID_ERR_ICC_PROTOCOL_NOT_SUPPORTED	= 0xf6,
+	CCID_ERR_ICC_CLASS_NOT_SUPPORTED	= 0xf5,
+	CCID_ERR_PROCEDURE_BYTE_CONFLICT	= 0xf4,
+	CCID_ERR_DEACTIVATED_PROTOCOL		= 0xf3,
+	CCID_ERR_BUSY_WITH_AUTO_SEQUENCE	= 0xf2,
+	CCID_ERR_PIN_TIMEOUT			= 0xf0,
+	CCID_ERR_PIN_CANCELLED			= 0xef,
+	CCID_ERR_CMD_SLOT_BUSY			= 0xe0,
+	CCID_ERR_CMD_NOT_SUPPORTED		= 0x00
+};
+
+union ccid_rdr_to_pc {
+	struct ccid_rdr_to_pc_data_block		data_block;
+	struct ccid_rdr_to_pc_slot_status		slot_status;
+	struct ccid_rdr_to_pc_parameters		parameters;
+	struct ccid_rdr_to_pc_escape			escape;
+	struct ccid_rdr_to_pc_data_rate_and_clock	rate_and_clock;
+};
+
+/***********************************************************************
+ * Interupt IN
+ ***********************************************************************/
+
+/* Section 6.3.1 */
+struct ccid_rdr_to_pc_notify_slot_change {
+	uint8_t bMessageType;
+	uint8_t bmSlotCCState[0];	/* as long as bNumSlots/4 padded to next byte */
+} __attribute__ ((packed));
+
+/* Section 6.3.1 */
+struct ccid_rdr_to_pc_hardware_error {
+	struct ccid_header hdr;
+	uint8_t bHardwareErrorCode;
+} __attribute__ ((packed));
+
+union ccid_rdr_to_pc_irq {
+	struct ccid_rdr_to_pc_notify_slot_change	slot_change;
+	struct ccid_rdr_to_pc_hardware_error		hw_error;
+};
diff --git a/ccid/create_ccid_gadget.sh b/ccid/create_ccid_gadget.sh
new file mode 100755
index 0000000..aafb152
--- /dev/null
+++ b/ccid/create_ccid_gadget.sh
@@ -0,0 +1,46 @@
+#!/bin/bash
+
+GADGET_NAME=osmo-ccid
+
+
+
+GADGET_CONFIGFS=/sys/kernel/config/usb_gadget
+
+die() {
+	echo ERROR: $1
+	exit 2
+}
+
+modprobe configfs
+modprobe usb_f_fs
+modprobe dummy_hcd is_high_speed=0 is_super_speed=0
+
+[ -d $GADGET_CONFIGFS ] || die "usb_gadget configfs not mounted"
+
+gadgetdir="$GADGET_CONFIGFS/$GADGET_NAME"
+
+# create gadget
+[ -d $gadgetdir ] || mkdir $gadgetdir || die "Cannot create $gadgetdir. Permission problem?"
+set -e -x
+cd $gadgetdir
+echo 0x2342 > idVendor
+echo 0x2342 > idProduct
+[ -d strings/0x409 ] || mkdir strings/0x409
+echo 2342 > strings/0x409/serialnumber
+echo "sysmocom GmbH" > strings/0x409/manufacturer
+echo "sysmoOCTSIM" > strings/0x409/product
+
+# create config
+[ -d configs/c.1 ] || mkdir configs/c.1
+[ -d configs/c.1/strings/0x409 ] || mkdir configs/c.1/strings/0x409
+echo "sysmoOCTSIM config" > configs/c.1/strings/0x409/configuration
+
+[ -d functions/ffs.usb0 ] || mkdir functions/ffs.usb0
+[ -e configs/c.1/ffs.usb0 ] || ln -s functions/ffs.usb0 configs/c.1
+
+[ -d /dev/ffs-ccid ] || mkdir /dev/ffs-ccid
+[ -e /dev/ffs-ccid/ep0 ] || mount -t functionfs usb0 /dev/ffs-ccid/
+
+# enable device, only works after program has opened EP FDs
+#echo dummy_udc.0 > UDC
+