552 lines
12 KiB
C
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)
|