rp2040-playground/composite/tusb_cardem.c

575 lines
16 KiB
C

//#include <libsimtrace/usb_buf.h>
/****************************************************************************
* buffered endpoint
****************************************************************************/
/* USB buffer library
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*/
//#pragma once
#include "tusb.h"
#include "device/usbd_pvt.h"
#include "trace.h"
#include <errno.h>
#include <osmocom/core/linuxlist.h>
#include <osmocom/core/msgb.h>
/* buffered USB endpoint (with queue of msgb) */
struct usb_buffered_ep {
/* endpoint address */
uint8_t ep;
/* currently any transfer in progress? */
struct msgb * msg_in_progress;
/* Tx queue (IN) / Rx queue (OUT) */
struct llist_head queue;
/* current length of queue */
unsigned int queue_len;
};
#define USB_MAX_QLEN 3
#define USB_ALLOC_SIZE 280
void bep_init(struct usb_buffered_ep *bep, uint8_t ep)
{
bep->ep = ep;
bep->msg_in_progress = NULL;
INIT_LLIST_HEAD(&bep->queue);
bep->queue_len = 0;
}
struct msgb *bep_msgb_alloc(struct usb_buffered_ep *bep)
{
struct msgb *msg = msgb_alloc(USB_ALLOC_SIZE, "USB");
if (!msg)
return NULL;
msg->dst = bep;
return msg;
}
void bep_msgb_free(struct msgb *msg)
{
msgb_free(msg);
}
#define LOGBEP(bep, fmt, args ...) \
printf("%s(%02x): " fmt, __func__, (bep)->ep, ## args)
/* IN/IRQ EP: dequeue next pending message and send it to host */
int bep_refill_to_host(struct usb_buffered_ep *bep)
{
unsigned long x;
const uint8_t rhport = 0;
if (usbd_edpt_busy(rhport, bep->ep)) {
LOGBEP(bep, "skipping: edpt_busy\r\n");
return 0;
}
local_irq_save(x);
if (bep->msg_in_progress) {
local_irq_restore(x);
LOGBEP(bep, "skipping: msg_in_progress\r\n");
return 0;
}
if (llist_empty(&bep->queue)) {
local_irq_restore(x);
LOGBEP(bep, "skipping: queue empty\r\n");
return 0;
}
bep->msg_in_progress = msgb_dequeue_count(&bep->queue, &bep->queue_len);
local_irq_restore(x);
TU_VERIFY(usbd_edpt_xfer(rhport, bep->ep, msgb_data(bep->msg_in_progress),
msgb_length(bep->msg_in_progress)));
LOGBEP(bep, "success (msg=%p, data=%p/%u)!\r\n", bep->msg_in_progress, msgb_data(bep->msg_in_progress), msgb_length(bep->msg_in_progress));
return 1;
}
/* assume the last in-progress msgb has completed + return it */
struct msgb *bep_get_completed(struct usb_buffered_ep *bep)
{
unsigned long x;
struct msgb *msg;
local_irq_save(x);
msg = bep->msg_in_progress;
bep->msg_in_progress = NULL;
local_irq_restore(x);
LOGBEP(bep, "(msg=%p)\r\n", msg);
return msg;
}
/* assume the last in-progress msgb has completed + free it */
void bep_complete_in_progress(struct usb_buffered_ep *bep)
{
struct msgb *msg = bep_get_completed(bep);
LOGBEP(bep, "(msg=%p)\r\n", msg);
bep_msgb_free(msg);
}
/* enqueue a USB buffer for transmission to host */
int bep_enqueue_msgb(struct msgb *msg)
{
struct usb_buffered_ep *ep = msg->dst;
if (!msg->dst) {
TRACE_ERROR("%s: msg without dst\r\n", __func__);
bep_msgb_free(msg);
return -EINVAL;
}
/* no need for irqsafe operation, as the usb_tx_queue is
* processed only by the main loop context */
if (ep->queue_len >= USB_MAX_QLEN) {
struct msgb *evict;
/* free the first pending buffer in the queue */
TRACE_INFO("EP%02x: dropping first queue element (qlen=%u)\r\n",
ep->ep, ep->queue_len);
evict = msgb_dequeue_count(&ep->queue, &ep->queue_len);
OSMO_ASSERT(evict);
bep_msgb_free(evict);
}
LOGBEP(ep, "(msg=%p)\r\n", msg);
msgb_enqueue_count(&ep->queue, msg, &ep->queue_len);
return 0;
}
#include "tusb.h"
#include "device/usbd_pvt.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];
};
/* must have at least as many elements as there could be cardem interfaces */
#define MAX_IF 1
static struct tusb_cardem_inst gtci[MAX_IF];
/****************************************************************************
* 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 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 = &gtci[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 = &gtci[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;
} else {
g_tem_in[ep_nr].ep_idx = EPIDX_IN;
tci->bep[EPIDX_IN].ep = desc_ep->bEndpointAddress;
}
/* 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;
/* 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);
}
p_desc = tu_desc_next(p_desc);
drv_len += sizeof(tusb_desc_endpoint_t);
}
}
return drv_len;
}
/* 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)
{
/* 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));
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);
return bep_enqueue_msgb(msg);
}
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_push(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(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:
msg = bep_msgb_alloc(bep);
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(bep, smh->seq_nr, smh->slot_nr, 1, 1, 0x0003, "unknown message type");
return -1;
}
return 0;
}
static void my_ep_out_handler(struct msgb *msg, struct usb_buffered_ep *bep)
{
struct simtrace_msg_hdr *smh;
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;
}
LOGBEP(bep, "(slot_nr=%u, class=%u, msg_type=%u, seq_nr=%u, len=%u)\r\n", __func__,
smh->slot_nr, smh->msg_class, smh->msg_type, smh->seq_nr, smh->msg_len);
switch (smh->msg_class) {
case SIMTRACE_MSGC_GENERIC:
my_stp_generic_handler(smh, bep);
break;
case SIMTRACE_MSGC_CARDEM:
case SIMTRACE_MSGC_MODEM:
case SIMTRACE_MSGC_SNIFF:
default:
/* FIXME: send error in return */
goto out;
}
out:
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;
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);
/* dispatch to handler; transfers ownership */
my_ep_out_handler(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);
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);
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);
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
*
*/