547 lines
16 KiB
C
547 lines
16 KiB
C
|
|
#include <osmocom/core/linuxlist.h>
|
|
#include <osmocom/core/msgb.h>
|
|
#include <libsimtrace/usb_buffered_ep.h>
|
|
#include <libsimtrace/card_emu.h>
|
|
|
|
#include "tusb.h"
|
|
#include "device/usbd_pvt.h"
|
|
#include "shared.h"
|
|
|
|
/* endpoint indexes so we can use them as look-up into arrays */
|
|
enum tusb_cardem_epidx {
|
|
EPIDX_OUT = 0,
|
|
EPIDX_IN = 1,
|
|
EPIDX_IRQ = 2,
|
|
_NUM_EPIDX
|
|
};
|
|
|
|
/* per-interface/instance structure */
|
|
struct tusb_cardem_inst {
|
|
/* the interface to which this driver has been bound */
|
|
int itf_num;
|
|
struct usb_buffered_ep bep[_NUM_EPIDX];
|
|
/* handle for card_emu.c core */
|
|
struct card_handle *ch;
|
|
};
|
|
|
|
/* must have at least as many elements as there could be cardem interfaces */
|
|
static struct tusb_cardem_inst gtci[MAX_CARDEM_ITF];
|
|
|
|
|
|
/****************************************************************************
|
|
* endpoint_nr -> cardem_inst + ep_idx map (used for dispatch tusb -> cardem)
|
|
****************************************************************************/
|
|
|
|
/* must have at least as many elements as there could be USB endpoint numbers */
|
|
#define MAX_EP 16
|
|
struct tusb_ep_map {
|
|
struct tusb_cardem_inst *tci;
|
|
uint8_t ep_idx;
|
|
};
|
|
/* same endpoint number can be used for IN and OUT, so we need two arrays */
|
|
static struct tusb_ep_map g_tem_in[MAX_EP];
|
|
static struct tusb_ep_map g_tem_out[MAX_EP];
|
|
|
|
/* resolve the tusb_ep_map entry for a given endpoint address */
|
|
static struct tusb_ep_map *tem_for_epaddr(uint8_t epaddr)
|
|
{
|
|
struct tusb_ep_map *map;
|
|
|
|
/* determine map based on input/output direction */
|
|
if (epaddr & 0x80)
|
|
map = g_tem_in;
|
|
else
|
|
map = g_tem_out;
|
|
|
|
/* resolve tusb_ep_map by indexing the array */
|
|
uint8_t epnr = tu_edpt_number(epaddr);
|
|
OSMO_ASSERT(epnr < MAX_EP);
|
|
|
|
return &map[epnr];
|
|
}
|
|
|
|
/* return the usb_buffered_ep (+ optionally endpoint idx) for a given endpoint address */
|
|
static struct usb_buffered_ep *bep_for_epaddr(uint8_t epaddr, uint8_t *epidx)
|
|
{
|
|
struct tusb_ep_map *tem = tem_for_epaddr(epaddr);
|
|
|
|
if (epidx)
|
|
*epidx = tem->ep_idx;
|
|
|
|
return &tem->tci->bep[tem->ep_idx];
|
|
}
|
|
|
|
|
|
|
|
/****************************************************************************
|
|
* actual tinyusb driver code
|
|
****************************************************************************/
|
|
|
|
/* called during tud_init() time. */
|
|
static void cardemd_init(void)
|
|
{
|
|
printf("%s\r\n", __func__);
|
|
for (unsigned int i = 0; i < ARRAY_SIZE(gtci); i++) {
|
|
struct tusb_cardem_inst *tci = >ci[i];
|
|
for (unsigned int j = 0; j < ARRAY_SIZE(tci->bep); j++) {
|
|
/* endpoint numbers are written in cardemd_open(), so we pass 0 here */
|
|
bep_init(&tci->bep[j], 0);
|
|
}
|
|
}
|
|
}
|
|
|
|
/* called during usdbd_reset(); happens during bus reset or USB unplug */
|
|
static void cardemd_reset(uint8_t __unused rhport)
|
|
{
|
|
printf("%s\r\n", __func__);
|
|
for (unsigned int i = 0; i < ARRAY_SIZE(gtci); i++) {
|
|
gtci[i].itf_num = -1;
|
|
/* TODO: ? */
|
|
}
|
|
|
|
for (unsigned int i = 0; i < ARRAY_SIZE(g_tem_in); i++)
|
|
g_tem_in[i].tci = NULL;
|
|
|
|
for (unsigned int i = 0; i < ARRAY_SIZE(g_tem_out); i++)
|
|
g_tem_out[i].tci = NULL;
|
|
|
|
/* FIXME free all the buffered msgb */
|
|
}
|
|
|
|
/* called during process_set_config() / process_control_request() */
|
|
static uint16_t cardemd_open(uint8_t rhport, tusb_desc_interface_t const *itf_desc, uint16_t max_len)
|
|
{
|
|
struct tusb_cardem_inst *tci = >ci[0]; // FIXME: multiple instances
|
|
|
|
printf("%s\r\n", __func__);
|
|
|
|
TU_VERIFY(TUSB_CLASS_VENDOR_SPECIFIC == itf_desc->bInterfaceClass &&
|
|
2 == itf_desc->bInterfaceSubClass &&
|
|
0 == itf_desc->bInterfaceProtocol, 0);
|
|
|
|
uint16_t drv_len = sizeof(tusb_desc_interface_t);
|
|
TU_VERIFY(max_len >= drv_len, 0);
|
|
|
|
tci->itf_num = itf_desc->bInterfaceNumber;
|
|
|
|
uint8_t const *p_desc = tu_desc_next(itf_desc);
|
|
uint8_t const * desc_end = p_desc + max_len;
|
|
if (itf_desc->bNumEndpoints) {
|
|
/* skip non-endpoint descriptors */
|
|
while ((TUSB_DESC_ENDPOINT != tu_desc_type(p_desc)) && (p_desc < desc_end))
|
|
p_desc = tu_desc_next(p_desc);
|
|
|
|
/* iterate over endpoints */
|
|
for (int i = 0; i < itf_desc->bNumEndpoints; i++) {
|
|
tusb_desc_endpoint_t const * desc_ep = (tusb_desc_endpoint_t const *) p_desc;
|
|
TU_ASSERT(TUSB_DESC_ENDPOINT == desc_ep->bDescriptorType);
|
|
/* open each endpoint */
|
|
printf("opening EP 0x%02x\r\n", desc_ep->bEndpointAddress);
|
|
TU_ASSERT(usbd_edpt_open(rhport, desc_ep));
|
|
|
|
uint8_t ep_nr = tu_edpt_number(desc_ep->bEndpointAddress);
|
|
|
|
if (tu_edpt_dir(desc_ep->bEndpointAddress) == TUSB_DIR_IN) {
|
|
g_tem_in[ep_nr].tci = tci;
|
|
if (TUSB_XFER_INTERRUPT == desc_ep->bmAttributes.xfer) {
|
|
g_tem_in[ep_nr].ep_idx = EPIDX_IRQ;
|
|
tci->bep[EPIDX_IRQ].ep = desc_ep->bEndpointAddress;
|
|
printf("EPIDX_IRQ = 0x%02x\r\n", tci->bep[EPIDX_IRQ].ep);
|
|
} else {
|
|
g_tem_in[ep_nr].ep_idx = EPIDX_IN;
|
|
tci->bep[EPIDX_IN].ep = desc_ep->bEndpointAddress;
|
|
printf("EPIDX_IN = 0x%02x\r\n", tci->bep[EPIDX_IN].ep);
|
|
}
|
|
/* FIXME: tud_vendor_n_write_flush() for each IN EP */
|
|
} else {
|
|
g_tem_out[ep_nr].tci = tci;
|
|
g_tem_out[ep_nr].ep_idx = EPIDX_OUT;
|
|
tci->bep[EPIDX_OUT].ep = desc_ep->bEndpointAddress;
|
|
printf("EPIDX_OUT = 0x%02x\r\n", tci->bep[EPIDX_OUT].ep);
|
|
|
|
/* enqueue a buffer for receiving data from OUT ep */
|
|
struct msgb *msg = bep_msgb_alloc(&tci->bep[EPIDX_OUT]);
|
|
if (msg) {
|
|
bep_enqueue_msgb(msg);
|
|
bep_refill_to_host(&tci->bep[EPIDX_OUT], true);
|
|
}
|
|
}
|
|
|
|
p_desc = tu_desc_next(p_desc);
|
|
drv_len += sizeof(tusb_desc_endpoint_t);
|
|
}
|
|
}
|
|
|
|
tci->ch = card_emu_init(tci->itf_num, 0 /*fixme*/, &tci->bep[EPIDX_IN], &tci->bep[EPIDX_IRQ],
|
|
false, false, false);
|
|
card_emu_drv_init(tci->ch, 0 /* fixme */);
|
|
|
|
|
|
return drv_len;
|
|
}
|
|
|
|
extern void dcd_edpt_reset_data_toggle(uint8_t rhport, uint8_t ep_addr);
|
|
extern void dcd_edpt_close_all (uint8_t rhport);
|
|
|
|
/* inbound control request from host */
|
|
static bool cardemd_control_xfer_cb(uint8_t __unused rhport, uint8_t __unused stage, tusb_control_request_t const *request)
|
|
{
|
|
switch (request->bmRequestType_bit.type) {
|
|
case TUSB_REQ_TYPE_STANDARD:
|
|
switch (request->bRequest) {
|
|
case TUSB_REQ_SET_INTERFACE:
|
|
printf("SET_INTERFACE Stage: %u\r\n", stage);
|
|
switch (stage) {
|
|
case CONTROL_STAGE_SETUP:
|
|
tud_control_status(rhport, request);
|
|
return true;
|
|
case CONTROL_STAGE_ACK:
|
|
if (request->wIndex != 0) {
|
|
// return RequestError
|
|
return false;
|
|
}
|
|
if (request->wValue != 0) {
|
|
// return RequestError
|
|
return false;
|
|
}
|
|
/* from here: permitted alternate setting */
|
|
#if 0 /* global disable of any SET_INTERFACE */
|
|
/* reset data toggle to DATA0 after configuration operation */
|
|
printf("SET_INTERFACE: RESTTING DATA TOGGLE TO DATA0\r\n");
|
|
#if 0 /* try it via reset of data toggle */
|
|
dcd_edpt_reset_data_toggle(0, 0x01);
|
|
dcd_edpt_reset_data_toggle(0, 0x81);
|
|
dcd_edpt_reset_data_toggle(0, 0x82);
|
|
#else /* try it via closing of endpoints */
|
|
dcd_edpt_close_all(rhport);
|
|
#endif
|
|
return true;
|
|
#else /* global disable of any SET_INTERFACE */
|
|
/* return false here so that the calling function will cause a
|
|
* STALL during the ACK (Status) phase of the control transfer,
|
|
* making the entire request fail */
|
|
return false;
|
|
#endif
|
|
default:
|
|
return true;
|
|
}
|
|
case TUSB_REQ_GET_INTERFACE:
|
|
/* let generic tinyusb usbd care about it */
|
|
return false;
|
|
default:
|
|
/* let generic tinyusb usbd care about it */
|
|
return false;
|
|
}
|
|
break;
|
|
default:
|
|
/* we have no class specific control requests */
|
|
return false;
|
|
}
|
|
}
|
|
|
|
#include <libsimtrace/simtrace_prot.h>
|
|
static const struct simtrace_board_info g_board_info = {
|
|
.hardware = {
|
|
.manufacturer = "sysmocom",
|
|
.model = "rp2040-cardem-breakout",
|
|
.version = "0.1",
|
|
},
|
|
.software = {
|
|
.provider = "sysmocom",
|
|
.name = "rp2040-cardem-composite",
|
|
.version = "FIXME",
|
|
.buildhost = "FIXME",
|
|
.crc = 0, // FIXME
|
|
},
|
|
.speed = {
|
|
.max_baud_rate = 9600,
|
|
},
|
|
.cap_generic_bytes = 0, // FIXME
|
|
};
|
|
|
|
/* push (prepend) the generic simtrace_msg_hdr in front of msg and enqueue it for tx to USB host */
|
|
static int stp_push_hdr_and_send(struct msgb *msg, uint8_t msg_class, uint8_t msg_type, uint8_t seq_nr, uint8_t slot_nr)
|
|
{
|
|
struct simtrace_msg_hdr *sth = (struct simtrace_msg_hdr *) msgb_push(msg, sizeof(*sth));
|
|
struct usb_buffered_ep *bep = msg->dst;
|
|
int rc;
|
|
|
|
sth->msg_class = msg_class;
|
|
sth->msg_type = msg_type;
|
|
sth->seq_nr = seq_nr;
|
|
sth->slot_nr = slot_nr;
|
|
sth->msg_len = msgb_length(msg);
|
|
|
|
rc = bep_enqueue_msgb(msg);
|
|
if (rc >= 0)
|
|
bep_refill_to_host(bep, false);
|
|
return rc;
|
|
}
|
|
|
|
static void stp_tx_error(struct usb_buffered_ep *bep, uint8_t seq_nr, uint8_t slot_nr, uint8_t severity,
|
|
uint8_t subsystem, uint16_t code, const char *errmsg)
|
|
{
|
|
struct msgb *msg = bep_msgb_alloc(bep);
|
|
struct cardemu_usb_msg_error *err;
|
|
|
|
LOGBEP(bep, "(slot_nr=%u, severity=%u, subsys=%u, code=0x%04x, msg=%s)\r\n",
|
|
slot_nr, severity, subsystem, code, errmsg);
|
|
|
|
if (!msg)
|
|
return;
|
|
|
|
err = (struct cardemu_usb_msg_error *) msgb_put(msg, sizeof(*err));
|
|
err->severity = severity;
|
|
err->subsystem = subsystem;
|
|
err->code = code;
|
|
err->msg_len = strlen(errmsg);
|
|
if (err->msg_len) {
|
|
uint8_t *out = msgb_put(msg, err->msg_len);
|
|
memcpy(out, errmsg, err->msg_len);
|
|
}
|
|
|
|
stp_push_hdr_and_send(msg, SIMTRACE_MSGC_GENERIC, SIMTRACE_CMD_DO_ERROR, seq_nr, slot_nr);
|
|
|
|
}
|
|
|
|
/* handle SIMTRACE_MSGC_GENERIC received from USB host */
|
|
static int my_stp_generic_handler(struct tusb_cardem_inst *tci, const struct simtrace_msg_hdr *smh, struct usb_buffered_ep *bep)
|
|
{
|
|
struct simtrace_board_info *bdinfo;
|
|
struct msgb *msg;
|
|
|
|
switch (smh->msg_type) {
|
|
case SIMTRACE_CMD_BD_BOARD_INFO:
|
|
LOGBEP(bep, "responding to BOARD_INFO\r\n");
|
|
msg = bep_msgb_alloc(&tci->bep[EPIDX_IN]);
|
|
if (!msg)
|
|
break;
|
|
bdinfo = (struct simtrace_board_info *) msgb_put(msg, sizeof(*bdinfo));
|
|
memcpy(bdinfo, &g_board_info, sizeof(g_board_info));
|
|
stp_push_hdr_and_send(msg, SIMTRACE_MSGC_GENERIC, SIMTRACE_CMD_BD_BOARD_INFO,
|
|
smh->seq_nr, smh->slot_nr);
|
|
break;
|
|
default:
|
|
stp_tx_error(&tci->bep[EPIDX_IN], smh->seq_nr, smh->slot_nr, 1, 1, 0x0003, "unknown message type");
|
|
return -1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* handler function for CARDEM class simtrace protocol messages arriving on OUT endpoint */
|
|
int stp_cardem_handler(struct tusb_cardem_inst *tci, const struct simtrace_msg_hdr *smh, struct msgb *msg)
|
|
{
|
|
struct cardemu_usb_msg_set_atr *atr;
|
|
//struct cardemu_usb_msg_cardinsert *cardins;
|
|
struct cardemu_usb_msg_config *cfg;
|
|
struct llist_head *queue;
|
|
|
|
int rc;
|
|
|
|
switch (smh->msg_type) {
|
|
case SIMTRACE_MSGT_DT_CEMU_TX_DATA:
|
|
queue = card_emu_get_uart_tx_queue(tci->ch);
|
|
llist_add_tail(&msg->list, queue);
|
|
card_emu_have_new_uart_tx(tci->ch);
|
|
return 1; /* tell caller to not free the msgb! */
|
|
case SIMTRACE_MSGT_DT_CEMU_SET_ATR:
|
|
atr = (struct cardemu_usb_msg_set_atr *) msg->l2h;
|
|
if (msgb_l2len(msg) < sizeof(*atr))
|
|
rc = -1;
|
|
else if (msgb_l2len(msg) < sizeof(*atr) + atr->atr_len)
|
|
rc = -1;
|
|
else
|
|
rc = card_emu_set_atr(tci->ch, atr->atr, atr->atr_len);
|
|
break;
|
|
case SIMTRACE_MSGT_BD_CEMU_STATUS:
|
|
rc = card_emu_report_status(tci->ch, false);
|
|
break;
|
|
case SIMTRACE_MSGT_BD_CEMU_CONFIG:
|
|
cfg = (struct cardemu_usb_msg_config *) msg->l2h;
|
|
rc = card_emu_set_config(tci->ch, cfg, msgb_l2len(msg));
|
|
break;
|
|
case SIMTRACE_MSGT_BD_CEMU_STATS:
|
|
case SIMTRACE_MSGT_DT_CEMU_CARDINSERT:
|
|
stp_tx_error(&tci->bep[EPIDX_IN], smh->seq_nr, smh->slot_nr, 1, 2, 0x0005, "unsupported message type");
|
|
break;
|
|
default:
|
|
stp_tx_error(&tci->bep[EPIDX_IN], smh->seq_nr, smh->slot_nr, 1, 2, 0x0003, "unknown message type");
|
|
return -1;
|
|
}
|
|
|
|
if (rc)
|
|
stp_tx_error(&tci->bep[EPIDX_IN], smh->seq_nr, smh->slot_nr, 1, 2, 0x0006, "error processing request");
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* handler function for simtrace protocol messages arriving on OUT endpoint */
|
|
static void my_ep_out_handler(struct tusb_cardem_inst *tci, struct msgb *msg, struct usb_buffered_ep *bep)
|
|
{
|
|
struct simtrace_msg_hdr *smh;
|
|
int rc = 0;
|
|
|
|
if (msgb_length(msg) < sizeof(*smh)) {
|
|
stp_tx_error(bep, 0, 0 /*slot_nr*/, 1, 1, 0x0001, "short message header");
|
|
goto out;
|
|
}
|
|
|
|
smh = (struct simtrace_msg_hdr *) msg->data;
|
|
|
|
if (msgb_length(msg) < smh->msg_len) {
|
|
stp_tx_error(bep, smh->seq_nr, smh->slot_nr, 1, 1, 0x0002, "short message");
|
|
goto out;
|
|
}
|
|
msg->l2h = msg->data + sizeof(*smh);
|
|
|
|
LOGBEP(bep, "(slot_nr=%u, class=%u, msg_type=%u, seq_nr=%u, len=%u)\r\n",
|
|
smh->slot_nr, smh->msg_class, smh->msg_type, smh->seq_nr, smh->msg_len);
|
|
|
|
switch (smh->msg_class) {
|
|
case SIMTRACE_MSGC_GENERIC:
|
|
rc = my_stp_generic_handler(tci, smh, bep);
|
|
break;
|
|
case SIMTRACE_MSGC_CARDEM:
|
|
rc = stp_cardem_handler(tci, smh, msg);
|
|
break;
|
|
case SIMTRACE_MSGC_MODEM:
|
|
case SIMTRACE_MSGC_SNIFF:
|
|
default:
|
|
/* send error in return */
|
|
stp_tx_error(&tci->bep[EPIDX_IN], smh->seq_nr, smh->slot_nr, 1, 0, 0x0004, "unknown message class");
|
|
goto out;
|
|
}
|
|
|
|
out:
|
|
if (rc != 1)
|
|
bep_msgb_free(msg);
|
|
}
|
|
|
|
/* USB transfer has completed. */
|
|
static bool cardemd_xfer_cb(uint8_t __unused rhport, uint8_t ep_addr, xfer_result_t result, uint32_t xferred_bytes)
|
|
{
|
|
struct msgb *msg;
|
|
uint8_t epidx;
|
|
struct tusb_cardem_inst *tci = >ci[0]; // FIXME: multiple instances
|
|
printf("%s(ep=0x%02x, result=%u, bytes=%u\r\n", __func__, ep_addr, result, xferred_bytes);
|
|
|
|
struct usb_buffered_ep *bep = bep_for_epaddr(ep_addr, &epidx);
|
|
if (!bep)
|
|
return false;
|
|
|
|
switch (epidx) {
|
|
case EPIDX_OUT:
|
|
/* 1) resolve last submitted buffer + dispatch it to handler */
|
|
msg = bep_get_completed(bep);
|
|
/* update our msgb structure with amount of bytes received */
|
|
msgb_put(msg, xferred_bytes);
|
|
/* dispatch to handler; transfers ownership */
|
|
my_ep_out_handler(tci, msg, bep);
|
|
/* 2) allocate a new buffer */
|
|
msg = bep_msgb_alloc(bep);
|
|
if (!msg)
|
|
break;
|
|
bep_enqueue_msgb(msg);
|
|
/* 3) submit that new buffer */
|
|
bep_refill_to_host(bep, true);
|
|
break;
|
|
case EPIDX_IN:
|
|
/* resolve last submitted buffer + release it back to allocator/pool */
|
|
bep_complete_in_progress(bep);
|
|
/* submit next pending buffer, if any */
|
|
bep_refill_to_host(bep, false);
|
|
break;
|
|
case EPIDX_IRQ:
|
|
/* resolve last submitted buffer + release it back to allocator/pool */
|
|
bep_complete_in_progress(bep);
|
|
/* submit next pending buffer, if any */
|
|
bep_refill_to_host(bep, false);
|
|
break;
|
|
default:
|
|
OSMO_ASSERT(0);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
static usbd_class_driver_t const _cardemd_driver = {
|
|
#if CFG_TUSB_DEBUG >= 2
|
|
.name = "cardem",
|
|
#endif
|
|
.init = cardemd_init,
|
|
.reset = cardemd_reset,
|
|
.open = cardemd_open,
|
|
.control_xfer_cb = cardemd_control_xfer_cb,
|
|
.xfer_cb = cardemd_xfer_cb,
|
|
.sof = NULL
|
|
};
|
|
|
|
/* Implement callback to add our custom driver. Called during tud_init() */
|
|
usbd_class_driver_t const *usbd_app_driver_get_cb(uint8_t *driver_count)
|
|
{
|
|
*driver_count = 1;
|
|
return &_cardemd_driver;
|
|
}
|
|
|
|
/* notes:
|
|
* - endpoint busy condition can be checked via usbd_edpt_busy()
|
|
* - IN/IRQ transfers to the host are initiated with usbd_edpt_xfer()
|
|
* - transfer completion signaled via xfer_cb()
|
|
* - OUT transfers to the host are initiated via usbd_edpt_xfer()
|
|
* - transfer completion signaled via xfer_cb()
|
|
* - memory used is allocated 'out of band'
|
|
*
|
|
* - usbd_edpt_claim() / usbd_edpt_release() called around usbd_edpt_xfer()
|
|
* - claim blocks the endpoint from other users
|
|
* - if usbd_edpt_xfer() is successful, *DO NOT* release it
|
|
* - tusb itself releases it before calling xfer_cb()
|
|
*/
|
|
|
|
/* = do we have to have a write-queue of buffers to the host?
|
|
*
|
|
* IN EP:
|
|
* - traffic-carrying messages triggered by ISO7816
|
|
* - RX_DATA: waits for answer from host; only one in flight
|
|
* - PTS: waits fro answer from host; only one in flight
|
|
*
|
|
* - responses to host requests:
|
|
* - STATS
|
|
* - STATUS
|
|
* - CONFIG
|
|
* either of those might happen in response to a host request, in parallel
|
|
* to ongoing DATA/PTS traffic. Only one of them at a time, as answers are immediate
|
|
*
|
|
* => write queue is needed, but usually will have only one entry backlog (beyond the
|
|
* current IN transfer)
|
|
*
|
|
* IRQ IN EP:
|
|
* - we probably want to have a queue of depth of at least 1 (beyond ongoing IN xfer)
|
|
* - queue depth should be limited to avoid noisy GPIO changes from causing OOM
|
|
*
|
|
* = execution context of call-backs (process? IRQ?)
|
|
*
|
|
* It seems that it's up to the application to regularly/repeatedly call tud_task()
|
|
* and we'll be doing that from normal process context.
|
|
*
|
|
*/
|
|
|
|
/* General High Level Flow
|
|
*
|
|
* = xfer_cb() for USB IN EP:
|
|
*
|
|
* - process any generic requests in-line (no alloc+memcpy)
|
|
* - process any CARDEM STATS/STATUS/CONFIG requests in-line (no malloc+memcpy)
|
|
* - process SET_ATR in-line (no malloc+memcpy)
|
|
* - allocate+memcpy for TX_DATA, put into queue
|
|
*
|
|
* All responses are dynamically allocated + enqueued!
|
|
*
|
|
* = xfer_cb() for USB OUT EP:
|
|
*
|
|
* - resolve msgb from endpoint / pointer (completion should happen in-order for each EP!)
|
|
* - release buffer back to pool
|
|
*
|
|
*/
|