/* * 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 #include #include #include #include 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)