zephyr/drivers/usb/uhc/uhc_virtual.c
Johann Fischer 9cb777b95e drivers: uhc: rework transfer buffer handling
The current approach is a bit impractical in the upper layer.
This patch removes the two fifos that hold the transfer buffers
and replaces them with a byte array for the setup packet and
a pointer to a data buffer. The data buffer is mandatory for
all types of transfers except control without a data stage.
The waste of eight unused bytes for non-control transfers should
be insignificant, since an additional pointer would be at least
half of it, and then there would be the overhead of handling it.

This patch also clean up the transfer flags, rename owner to callback
as it reflects the upper layer use case, and add an additional member
to hold the pointer to the USB device (peripheral on the bus).

Signed-off-by: Johann Fischer <johann.fischer@nordicsemi.no>
2023-10-01 09:26:07 +03:00

552 lines
12 KiB
C

/*
* Copyright (c) 2022 Nordic Semiconductor ASA
*
* SPDX-License-Identifier: Apache-2.0
*/
/*
* @file uhc_virtual.c
* @brief Virtual USB host controller (UHC) driver
*
* Virtual device controller does not emulate any hardware
* and can only communicate with the virtual device controllers
* through virtual bus.
*/
#include "uhc_common.h"
#include "../uvb/uvb.h"
#include <string.h>
#include <zephyr/kernel.h>
#include <zephyr/init.h>
#include <zephyr/drivers/usb/uhc.h>
#include <zephyr/logging/log.h>
LOG_MODULE_REGISTER(uhc_vrt, CONFIG_UHC_DRIVER_LOG_LEVEL);
struct uhc_vrt_config {
};
struct uhc_vrt_data {
const struct device *dev;
struct uvb_node *host_node;
struct k_work work;
struct k_fifo fifo;
struct uhc_transfer *last_xfer;
struct k_timer sof_timer;
bool busy;
uint8_t req;
};
enum uhc_vrt_event_type {
/* Trigger next transfer */
UHC_VRT_EVT_XFER,
/* SoF generator event */
UHC_VRT_EVT_SOF,
/* Request reply received */
UHC_VRT_EVT_REPLY,
};
/* Structure for driver's endpoint events */
struct uhc_vrt_event {
sys_snode_t node;
enum uhc_vrt_event_type type;
struct uvb_packet *pkt;
};
K_MEM_SLAB_DEFINE(uhc_vrt_slab, sizeof(struct uhc_vrt_event),
16, sizeof(void *));
static void vrt_event_submit(const struct device *dev,
const enum uhc_vrt_event_type type,
const void *data)
{
struct uhc_vrt_data *priv = uhc_get_private(dev);
struct uhc_vrt_event *event;
int ret;
ret = k_mem_slab_alloc(&uhc_vrt_slab, (void **)&event, K_NO_WAIT);
__ASSERT(ret == 0, "Failed to allocate slab");
event->type = type;
event->pkt = (struct uvb_packet *const)data;
k_fifo_put(&priv->fifo, event);
k_work_submit(&priv->work);
}
static int vrt_xfer_control(const struct device *dev,
struct uhc_transfer *const xfer)
{
struct uhc_vrt_data *priv = uhc_get_private(dev);
struct net_buf *buf = xfer->buf;
struct uvb_packet *uvb_pkt;
uint8_t *data = NULL;
size_t length = 0;
if (xfer->stage == UHC_CONTROL_STAGE_SETUP) {
LOG_DBG("Handle SETUP stage");
uvb_pkt = uvb_alloc_pkt(UVB_REQUEST_SETUP,
xfer->addr, USB_CONTROL_EP_OUT,
xfer->setup_pkt, sizeof(xfer->setup_pkt));
if (uvb_pkt == NULL) {
LOG_ERR("Failed to allocate UVB packet");
return -ENOMEM;
}
priv->req = UVB_REQUEST_SETUP;
priv->busy = true;
return uvb_advert_pkt(priv->host_node, uvb_pkt);
}
if (buf != NULL && xfer->stage == UHC_CONTROL_STAGE_DATA) {
if (USB_EP_DIR_IS_IN(xfer->ep)) {
length = MIN(net_buf_tailroom(buf), xfer->mps);
data = net_buf_tail(buf);
} else {
length = MIN(buf->len, xfer->mps);
data = buf->data;
}
LOG_DBG("Handle DATA stage");
uvb_pkt = uvb_alloc_pkt(UVB_REQUEST_DATA,
xfer->addr, xfer->ep,
data, length);
if (uvb_pkt == NULL) {
LOG_ERR("Failed to allocate UVB packet");
return -ENOMEM;
}
priv->req = UVB_REQUEST_DATA;
priv->busy = true;
return uvb_advert_pkt(priv->host_node, uvb_pkt);
}
if (xfer->stage == UHC_CONTROL_STAGE_STATUS) {
uint8_t ep;
LOG_DBG("Handle STATUS stage");
if (USB_EP_DIR_IS_IN(xfer->ep)) {
ep = USB_CONTROL_EP_OUT;
} else {
ep = USB_CONTROL_EP_IN;
}
uvb_pkt = uvb_alloc_pkt(UVB_REQUEST_DATA,
xfer->addr, ep,
NULL, 0);
if (uvb_pkt == NULL) {
LOG_ERR("Failed to allocate UVB packet");
return -ENOMEM;
}
priv->req = UVB_REQUEST_DATA;
priv->busy = true;
return uvb_advert_pkt(priv->host_node, uvb_pkt);
}
return -EINVAL;
}
static int vrt_xfer_bulk(const struct device *dev,
struct uhc_transfer *const xfer)
{
struct uhc_vrt_data *priv = uhc_get_private(dev);
struct net_buf *buf = xfer->buf;
struct uvb_packet *uvb_pkt;
uint8_t *data;
size_t length;
if (USB_EP_DIR_IS_IN(xfer->ep)) {
length = MIN(net_buf_tailroom(buf), xfer->mps);
data = net_buf_tail(buf);
} else {
length = MIN(buf->len, xfer->mps);
data = buf->data;
}
uvb_pkt = uvb_alloc_pkt(UVB_REQUEST_DATA, xfer->addr, xfer->ep,
data, length);
if (uvb_pkt == NULL) {
LOG_ERR("Failed to allocate UVB packet");
return -ENOMEM;
}
return uvb_advert_pkt(priv->host_node, uvb_pkt);
}
static int vrt_schedule_xfer(const struct device *dev)
{
struct uhc_vrt_data *priv = uhc_get_private(dev);
if (priv->last_xfer == NULL) {
priv->last_xfer = uhc_xfer_get_next(dev);
if (priv->last_xfer == NULL) {
LOG_DBG("Nothing to transfer");
return 0;
}
LOG_DBG("Next transfer is %p", priv->last_xfer);
}
if (USB_EP_GET_IDX(priv->last_xfer->ep) == 0) {
return vrt_xfer_control(dev, priv->last_xfer);
}
/* TODO: Isochronous transfers */
return vrt_xfer_bulk(dev, priv->last_xfer);
}
static void vrt_hrslt_success(const struct device *dev,
struct uvb_packet *const pkt)
{
struct uhc_vrt_data *priv = uhc_get_private(dev);
struct uhc_transfer *const xfer = priv->last_xfer;
struct net_buf *buf = xfer->buf;
bool finished = false;
size_t length;
switch (pkt->request) {
case UVB_REQUEST_SETUP:
if (xfer->buf != NULL) {
xfer->stage = UHC_CONTROL_STAGE_DATA;
} else {
xfer->stage = UHC_CONTROL_STAGE_STATUS;
}
break;
case UVB_REQUEST_DATA:
if (xfer->stage == UHC_CONTROL_STAGE_STATUS) {
LOG_DBG("Status stage finished");
finished = true;
break;
}
if (USB_EP_DIR_IS_OUT(pkt->ep)) {
length = MIN(buf->len, xfer->mps);
net_buf_pull(buf, length);
LOG_DBG("OUT chunk %zu out of %u", length, buf->len);
if (buf->len == 0) {
if (pkt->ep == USB_CONTROL_EP_OUT) {
xfer->stage = UHC_CONTROL_STAGE_STATUS;
} else {
finished = true;
}
}
} else {
length = MIN(net_buf_tailroom(buf), pkt->length);
net_buf_add(buf, length);
if (pkt->length > xfer->mps) {
LOG_ERR("Ambiguous packet with the length %zu",
pkt->length);
}
LOG_DBG("IN chunk %zu out of %zu", length, net_buf_tailroom(buf));
if (pkt->length < xfer->mps || !net_buf_tailroom(buf)) {
if (pkt->ep == USB_CONTROL_EP_IN) {
xfer->stage = UHC_CONTROL_STAGE_STATUS;
} else {
finished = true;
}
}
}
break;
}
if (finished) {
LOG_DBG("Transfer finished");
uhc_xfer_return(dev, xfer, 0);
priv->last_xfer = NULL;
}
}
static void vrt_xfer_drop_active(const struct device *dev, int err)
{
struct uhc_vrt_data *priv = uhc_get_private(dev);
if (priv->last_xfer) {
uhc_xfer_return(dev, priv->last_xfer, err);
priv->last_xfer = NULL;
}
}
static int vrt_handle_reply(const struct device *dev,
struct uvb_packet *const pkt)
{
struct uhc_vrt_data *priv = uhc_get_private(dev);
struct uhc_transfer *const xfer = priv->last_xfer;
int ret = 0;
if (xfer == NULL) {
LOG_ERR("No transfers to handle");
ret = -ENODATA;
goto handle_reply_err;
}
priv->busy = false;
switch (pkt->reply) {
case UVB_REPLY_NACK:
/* Restart last transaction */
break;
case UVB_REPLY_STALL:
vrt_xfer_drop_active(dev, -EPIPE);
break;
case UVB_REPLY_ACK:
vrt_hrslt_success(dev, pkt);
break;
default:
vrt_xfer_drop_active(dev, -EINVAL);
ret = -EINVAL;
break;
}
handle_reply_err:
uvb_free_pkt(pkt);
return ret;
}
static void xfer_work_handler(struct k_work *work)
{
struct uhc_vrt_data *priv = CONTAINER_OF(work, struct uhc_vrt_data, work);
const struct device *dev = priv->dev;
struct uhc_vrt_event *ev;
while ((ev = k_fifo_get(&priv->fifo, K_NO_WAIT)) != NULL) {
bool schedule = false;
int err;
switch (ev->type) {
case UHC_VRT_EVT_REPLY:
err = vrt_handle_reply(dev, ev->pkt);
if (unlikely(err)) {
uhc_submit_event(dev, UHC_EVT_ERROR, err);
}
schedule = true;
break;
case UHC_VRT_EVT_XFER:
LOG_DBG("Transfer triggered for %p", dev);
schedule = true;
break;
case UHC_VRT_EVT_SOF:
if (priv->last_xfer != NULL) {
if (priv->last_xfer->timeout) {
priv->last_xfer->timeout--;
} else {
vrt_xfer_drop_active(dev, -ETIMEDOUT);
priv->busy = false;
LOG_WRN("Transfer timeout");
}
}
break;
default:
break;
}
if (schedule && !priv->busy) {
err = vrt_schedule_xfer(dev);
if (unlikely(err)) {
uhc_submit_event(dev, UHC_EVT_ERROR, err);
}
}
k_mem_slab_free(&uhc_vrt_slab, (void *)ev);
}
}
static void sof_timer_handler(struct k_timer *timer)
{
struct uhc_vrt_data *priv = CONTAINER_OF(timer, struct uhc_vrt_data, sof_timer);
vrt_event_submit(priv->dev, UHC_VRT_EVT_SOF, NULL);
}
static void vrt_device_act(const struct device *dev,
const enum uvb_device_act act)
{
enum uhc_event_type type;
switch (act) {
case UVB_DEVICE_ACT_RWUP:
type = UHC_EVT_RWUP;
break;
case UVB_DEVICE_ACT_FS:
type = UHC_EVT_DEV_CONNECTED_FS;
break;
case UVB_DEVICE_ACT_HS:
type = UHC_EVT_DEV_CONNECTED_HS;
break;
case UVB_DEVICE_ACT_REMOVED:
type = UHC_EVT_DEV_REMOVED;
break;
default:
type = UHC_EVT_ERROR;
}
uhc_submit_event(dev, type, 0);
}
static void uhc_vrt_uvb_cb(const void *const vrt_priv,
const enum uvb_event_type type,
const void *data)
{
const struct device *dev = vrt_priv;
if (type == UVB_EVT_REPLY) {
vrt_event_submit(dev, UHC_VRT_EVT_REPLY, data);
} else if (type == UVB_EVT_DEVICE_ACT) {
vrt_device_act(dev, POINTER_TO_INT(data));
} else {
LOG_ERR("Unknown event %d for %p", type, dev);
}
}
static int uhc_vrt_sof_enable(const struct device *dev)
{
/* TODO */
return 0;
}
/* Disable SOF generator and suspend bus */
static int uhc_vrt_bus_suspend(const struct device *dev)
{
struct uhc_vrt_data *priv = uhc_get_private(dev);
k_timer_stop(&priv->sof_timer);
return uvb_advert(priv->host_node, UVB_EVT_SUSPEND, NULL);
}
static int uhc_vrt_bus_reset(const struct device *dev)
{
struct uhc_vrt_data *priv = uhc_get_private(dev);
k_timer_stop(&priv->sof_timer);
return uvb_advert(priv->host_node, UVB_EVT_RESET, NULL);
}
static int uhc_vrt_bus_resume(const struct device *dev)
{
struct uhc_vrt_data *priv = uhc_get_private(dev);
k_timer_init(&priv->sof_timer, sof_timer_handler, NULL);
k_timer_start(&priv->sof_timer, K_MSEC(1), K_MSEC(1));
return uvb_advert(priv->host_node, UVB_EVT_RESUME, NULL);
}
static int uhc_vrt_enqueue(const struct device *dev,
struct uhc_transfer *const xfer)
{
uhc_xfer_append(dev, xfer);
vrt_event_submit(dev, UHC_VRT_EVT_XFER, NULL);
return 0;
}
static int uhc_vrt_dequeue(const struct device *dev,
struct uhc_transfer *const xfer)
{
/* TODO */
return 0;
}
static int uhc_vrt_init(const struct device *dev)
{
return 0;
}
static int uhc_vrt_enable(const struct device *dev)
{
struct uhc_vrt_data *priv = uhc_get_private(dev);
return uvb_advert(priv->host_node, UVB_EVT_VBUS_READY, NULL);
}
static int uhc_vrt_disable(const struct device *dev)
{
struct uhc_vrt_data *priv = uhc_get_private(dev);
return uvb_advert(priv->host_node, UVB_EVT_VBUS_REMOVED, NULL);
}
static int uhc_vrt_shutdown(const struct device *dev)
{
return 0;
}
static int uhc_vrt_lock(const struct device *dev)
{
return uhc_lock_internal(dev, K_FOREVER);
}
static int uhc_vrt_unlock(const struct device *dev)
{
return uhc_unlock_internal(dev);
}
static int uhc_vrt_driver_preinit(const struct device *dev)
{
struct uhc_vrt_data *priv = uhc_get_private(dev);
struct uhc_data *data = dev->data;
priv->dev = dev;
k_mutex_init(&data->mutex);
priv->host_node->priv = dev;
k_fifo_init(&priv->fifo);
k_work_init(&priv->work, xfer_work_handler);
k_timer_init(&priv->sof_timer, sof_timer_handler, NULL);
LOG_DBG("Virtual UHC pre-initialized");
return 0;
}
static const struct uhc_api uhc_vrt_api = {
.lock = uhc_vrt_lock,
.unlock = uhc_vrt_unlock,
.init = uhc_vrt_init,
.enable = uhc_vrt_enable,
.disable = uhc_vrt_disable,
.shutdown = uhc_vrt_shutdown,
.bus_reset = uhc_vrt_bus_reset,
.sof_enable = uhc_vrt_sof_enable,
.bus_suspend = uhc_vrt_bus_suspend,
.bus_resume = uhc_vrt_bus_resume,
.ep_enqueue = uhc_vrt_enqueue,
.ep_dequeue = uhc_vrt_dequeue,
};
#define DT_DRV_COMPAT zephyr_uhc_virtual
#define UHC_VRT_DEVICE_DEFINE(n) \
UVB_HOST_NODE_DEFINE(uhc_bc_##n, \
DT_NODE_FULL_NAME(DT_DRV_INST(n)), \
uhc_vrt_uvb_cb); \
\
static const struct uhc_vrt_config uhc_vrt_config_##n = { \
}; \
\
static struct uhc_vrt_data uhc_priv_##n = { \
.host_node = &uhc_bc_##n, \
}; \
\
static struct uhc_data uhc_data_##n = { \
.priv = &uhc_priv_##n, \
}; \
\
DEVICE_DT_INST_DEFINE(n, uhc_vrt_driver_preinit, NULL, \
&uhc_data_##n, &uhc_vrt_config_##n, \
POST_KERNEL, CONFIG_KERNEL_INIT_PRIORITY_DEVICE, \
&uhc_vrt_api);
DT_INST_FOREACH_STATUS_OKAY(UHC_VRT_DEVICE_DEFINE)