You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
721 lines
17 KiB
721 lines
17 KiB
/* |
|
* Copyright (c) 2022 Nordic Semiconductor ASA |
|
* |
|
* SPDX-License-Identifier: Apache-2.0 |
|
*/ |
|
|
|
/** |
|
* @file udc_virtual.c |
|
* @brief Virtual USB device controller (UDC) driver |
|
* |
|
* Virtual device controller does not emulate any hardware |
|
* and can only communicate with the virtual host controller |
|
* through virtual bus. |
|
*/ |
|
|
|
#include "udc_common.h" |
|
#include "../uvb/uvb.h" |
|
|
|
#include <string.h> |
|
#include <stdio.h> |
|
|
|
#include <zephyr/kernel.h> |
|
#include <zephyr/drivers/usb/udc.h> |
|
|
|
#include <zephyr/logging/log.h> |
|
LOG_MODULE_REGISTER(udc_vrt, CONFIG_UDC_DRIVER_LOG_LEVEL); |
|
|
|
struct udc_vrt_config { |
|
size_t num_of_eps; |
|
struct udc_ep_config *ep_cfg_in; |
|
struct udc_ep_config *ep_cfg_out; |
|
void (*make_thread)(const struct device *dev); |
|
struct uvb_node *dev_node; |
|
int speed_idx; |
|
const char *uhc_name; |
|
}; |
|
|
|
struct udc_vrt_data { |
|
struct k_fifo fifo; |
|
struct k_thread thread_data; |
|
uint8_t addr; |
|
}; |
|
|
|
struct udc_vrt_event { |
|
sys_snode_t node; |
|
enum uvb_event_type type; |
|
struct uvb_packet *pkt; |
|
}; |
|
|
|
K_MEM_SLAB_DEFINE(udc_vrt_slab, sizeof(struct udc_vrt_event), |
|
16, sizeof(void *)); |
|
|
|
/* Reuse request packet for reply */ |
|
static int vrt_request_reply(const struct device *dev, |
|
struct uvb_packet *const pkt, |
|
const enum uvb_reply reply) |
|
{ |
|
const struct udc_vrt_config *config = dev->config; |
|
|
|
pkt->reply = reply; |
|
|
|
return uvb_reply_pkt(config->dev_node, pkt); |
|
} |
|
|
|
static void ctrl_ep_clear_halt(const struct device *dev) |
|
{ |
|
struct udc_ep_config *cfg; |
|
|
|
cfg = udc_get_ep_cfg(dev, USB_CONTROL_EP_OUT); |
|
cfg->stat.halted = false; |
|
|
|
cfg = udc_get_ep_cfg(dev, USB_CONTROL_EP_IN); |
|
cfg->stat.halted = false; |
|
} |
|
|
|
static int vrt_ctrl_feed_dout(const struct device *dev, |
|
const size_t length) |
|
{ |
|
struct udc_ep_config *ep_cfg = udc_get_ep_cfg(dev, USB_CONTROL_EP_OUT); |
|
struct net_buf *buf; |
|
|
|
buf = udc_ctrl_alloc(dev, USB_CONTROL_EP_OUT, length); |
|
if (buf == NULL) { |
|
return -ENOMEM; |
|
} |
|
|
|
udc_buf_put(ep_cfg, buf); |
|
|
|
return 0; |
|
} |
|
|
|
static void drop_control_transfers(const struct device *dev) |
|
{ |
|
struct net_buf *buf; |
|
|
|
buf = udc_buf_get_all(udc_get_ep_cfg(dev, USB_CONTROL_EP_OUT)); |
|
if (buf != NULL) { |
|
net_buf_unref(buf); |
|
} |
|
|
|
buf = udc_buf_get_all(udc_get_ep_cfg(dev, USB_CONTROL_EP_IN)); |
|
if (buf != NULL) { |
|
net_buf_unref(buf); |
|
} |
|
} |
|
|
|
static int vrt_handle_setup(const struct device *dev, |
|
struct uvb_packet *const pkt) |
|
{ |
|
struct net_buf *buf; |
|
int err, ret; |
|
|
|
drop_control_transfers(dev); |
|
|
|
buf = udc_ctrl_alloc(dev, USB_CONTROL_EP_OUT, 8); |
|
if (buf == NULL) { |
|
return -ENOMEM; |
|
} |
|
|
|
net_buf_add_mem(buf, pkt->data, pkt->length); |
|
udc_ep_buf_set_setup(buf); |
|
ctrl_ep_clear_halt(dev); |
|
|
|
/* Update to next stage of control transfer */ |
|
udc_ctrl_update_stage(dev, buf); |
|
|
|
if (udc_ctrl_stage_is_data_out(dev)) { |
|
/* Allocate and feed buffer for data OUT stage */ |
|
LOG_DBG("s: %p | feed for -out-", buf); |
|
err = vrt_ctrl_feed_dout(dev, udc_data_stage_length(buf)); |
|
if (err == -ENOMEM) { |
|
/* |
|
* Pass it on to the higher level which will |
|
* halt control OUT endpoint. |
|
*/ |
|
err = udc_submit_ep_event(dev, buf, err); |
|
} |
|
} else if (udc_ctrl_stage_is_data_in(dev)) { |
|
LOG_DBG("s: %p | submit for -in-", buf); |
|
/* Allocate buffer for data IN and submit to upper layer */ |
|
err = udc_ctrl_submit_s_in_status(dev); |
|
} else { |
|
LOG_DBG("s:%p | submit for -status", buf); |
|
/* |
|
* For all other cases we feed with a buffer |
|
* large enough for setup packet. |
|
*/ |
|
err = udc_ctrl_submit_s_status(dev); |
|
} |
|
|
|
ret = vrt_request_reply(dev, pkt, UVB_REPLY_ACK); |
|
|
|
return ret ? ret : err; |
|
} |
|
|
|
static int vrt_handle_ctrl_out(const struct device *dev, |
|
struct net_buf *const buf) |
|
{ |
|
int err = 0; |
|
|
|
if (udc_ctrl_stage_is_status_out(dev)) { |
|
/* Status stage finished, notify upper layer */ |
|
err = udc_ctrl_submit_status(dev, buf); |
|
} |
|
|
|
/* Update to next stage of control transfer */ |
|
udc_ctrl_update_stage(dev, buf); |
|
|
|
if (udc_ctrl_stage_is_status_in(dev)) { |
|
return udc_ctrl_submit_s_out_status(dev, buf); |
|
} |
|
|
|
return err; |
|
} |
|
|
|
static int vrt_handle_out(const struct device *dev, |
|
struct uvb_packet *const pkt) |
|
{ |
|
struct udc_ep_config *ep_cfg; |
|
const uint8_t ep = pkt->ep; |
|
struct net_buf *buf; |
|
size_t min_len; |
|
int err = 0; |
|
int ret; |
|
|
|
ep_cfg = udc_get_ep_cfg(dev, ep); |
|
if (ep_cfg->stat.halted) { |
|
LOG_DBG("reply STALL ep 0x%02x", ep); |
|
return vrt_request_reply(dev, pkt, UVB_REPLY_STALL); |
|
} |
|
|
|
buf = udc_buf_peek(ep_cfg); |
|
if (buf == NULL) { |
|
LOG_DBG("reply NACK ep 0x%02x", ep); |
|
return vrt_request_reply(dev, pkt, UVB_REPLY_NACK); |
|
} |
|
|
|
min_len = MIN(pkt->length, net_buf_tailroom(buf)); |
|
net_buf_add_mem(buf, pkt->data, min_len); |
|
|
|
LOG_DBG("Handle data OUT, %zu | %zu", pkt->length, net_buf_tailroom(buf)); |
|
|
|
if (net_buf_tailroom(buf) == 0 || pkt->length < udc_mps_ep_size(ep_cfg)) { |
|
buf = udc_buf_get(ep_cfg); |
|
|
|
if (ep == USB_CONTROL_EP_OUT) { |
|
err = vrt_handle_ctrl_out(dev, buf); |
|
} else { |
|
err = udc_submit_ep_event(dev, buf, 0); |
|
} |
|
} |
|
|
|
ret = vrt_request_reply(dev, pkt, UVB_REPLY_ACK); |
|
|
|
return ret ? ret : err; |
|
} |
|
|
|
static int isr_handle_ctrl_in(const struct device *dev, |
|
struct net_buf *const buf) |
|
{ |
|
int err = 0; |
|
|
|
if (udc_ctrl_stage_is_status_in(dev) || udc_ctrl_stage_is_no_data(dev)) { |
|
/* Status stage finished, notify upper layer */ |
|
err = udc_ctrl_submit_status(dev, buf); |
|
} |
|
|
|
/* Update to next stage of control transfer */ |
|
udc_ctrl_update_stage(dev, buf); |
|
|
|
if (udc_ctrl_stage_is_status_out(dev)) { |
|
/* |
|
* IN transfer finished, release buffer, |
|
* Feed control OUT buffer for status stage. |
|
*/ |
|
net_buf_unref(buf); |
|
return vrt_ctrl_feed_dout(dev, 0); |
|
} |
|
|
|
return err; |
|
} |
|
|
|
static int vrt_handle_in(const struct device *dev, |
|
struct uvb_packet *const pkt) |
|
{ |
|
struct udc_ep_config *ep_cfg; |
|
const uint8_t ep = pkt->ep; |
|
struct net_buf *buf; |
|
size_t min_len; |
|
int err = 0; |
|
int ret; |
|
|
|
ep_cfg = udc_get_ep_cfg(dev, ep); |
|
if (ep_cfg->stat.halted) { |
|
LOG_DBG("reply STALL ep 0x%02x", ep); |
|
return vrt_request_reply(dev, pkt, UVB_REPLY_STALL); |
|
} |
|
|
|
buf = udc_buf_peek(ep_cfg); |
|
if (buf == NULL) { |
|
LOG_DBG("reply NACK ep 0x%02x", ep); |
|
return vrt_request_reply(dev, pkt, UVB_REPLY_NACK); |
|
} |
|
|
|
LOG_DBG("Handle data IN, %zu | %u | %u", |
|
pkt->length, buf->len, udc_mps_ep_size(ep_cfg)); |
|
min_len = MIN(pkt->length, buf->len); |
|
memcpy(pkt->data, buf->data, min_len); |
|
net_buf_pull(buf, min_len); |
|
pkt->length = min_len; |
|
|
|
if (buf->len == 0 || pkt->length < udc_mps_ep_size(ep_cfg)) { |
|
if (udc_ep_buf_has_zlp(buf)) { |
|
udc_ep_buf_clear_zlp(buf); |
|
goto continue_in; |
|
} |
|
|
|
LOG_DBG("Finish data IN %zu | %u", pkt->length, buf->len); |
|
buf = udc_buf_get(ep_cfg); |
|
|
|
if (ep == USB_CONTROL_EP_IN) { |
|
err = isr_handle_ctrl_in(dev, buf); |
|
} else { |
|
err = udc_submit_ep_event(dev, buf, 0); |
|
} |
|
} |
|
|
|
continue_in: |
|
ret = vrt_request_reply(dev, pkt, UVB_REPLY_ACK); |
|
|
|
return ret ? ret : err; |
|
} |
|
|
|
static int vrt_handle_request(const struct device *dev, |
|
struct uvb_packet *const pkt) |
|
{ |
|
LOG_DBG("REQUEST event for %p pkt %p", dev, pkt); |
|
|
|
if (USB_EP_GET_IDX(pkt->ep) == 0 && pkt->request == UVB_REQUEST_SETUP) { |
|
return vrt_handle_setup(dev, pkt); |
|
} |
|
|
|
if (USB_EP_DIR_IS_OUT(pkt->ep) && pkt->request == UVB_REQUEST_DATA) { |
|
return vrt_handle_out(dev, pkt); |
|
} |
|
|
|
if (USB_EP_DIR_IS_IN(pkt->ep) && pkt->request == UVB_REQUEST_DATA) { |
|
return vrt_handle_in(dev, pkt); |
|
} |
|
|
|
return -ENOTSUP; |
|
} |
|
|
|
static ALWAYS_INLINE void udc_vrt_thread_handler(void *arg) |
|
{ |
|
const struct device *dev = (const struct device *)arg; |
|
struct udc_vrt_data *priv = udc_get_private(dev); |
|
|
|
while (true) { |
|
struct udc_vrt_event *vrt_ev; |
|
int err = 0; |
|
|
|
vrt_ev = k_fifo_get(&priv->fifo, K_FOREVER); |
|
|
|
switch (vrt_ev->type) { |
|
case UVB_EVT_VBUS_REMOVED: |
|
err = udc_submit_event(dev, UDC_EVT_VBUS_REMOVED, 0); |
|
break; |
|
case UVB_EVT_VBUS_READY: |
|
err = udc_submit_event(dev, UDC_EVT_VBUS_READY, 0); |
|
break; |
|
case UVB_EVT_SUSPEND: |
|
err = udc_submit_event(dev, UDC_EVT_SUSPEND, 0); |
|
break; |
|
case UVB_EVT_RESUME: |
|
err = udc_submit_event(dev, UDC_EVT_RESUME, 0); |
|
break; |
|
case UVB_EVT_RESET: |
|
err = udc_submit_event(dev, UDC_EVT_RESET, 0); |
|
break; |
|
case UVB_EVT_REQUEST: |
|
err = vrt_handle_request(dev, vrt_ev->pkt); |
|
break; |
|
default: |
|
break; |
|
}; |
|
|
|
if (err) { |
|
udc_submit_event(dev, UDC_EVT_ERROR, err); |
|
} |
|
|
|
k_mem_slab_free(&udc_vrt_slab, (void *)vrt_ev); |
|
} |
|
} |
|
|
|
static void vrt_submit_uvb_event(const struct device *dev, |
|
const enum uvb_event_type type, |
|
struct uvb_packet *const pkt) |
|
{ |
|
struct udc_vrt_data *priv = udc_get_private(dev); |
|
struct udc_vrt_event *vrt_ev; |
|
int ret; |
|
|
|
ret = k_mem_slab_alloc(&udc_vrt_slab, (void **)&vrt_ev, K_NO_WAIT); |
|
__ASSERT(ret == 0, "Failed to allocate slab"); |
|
|
|
vrt_ev->type = type; |
|
vrt_ev->pkt = pkt; |
|
k_fifo_put(&priv->fifo, vrt_ev); |
|
} |
|
|
|
static void udc_vrt_uvb_cb(const void *const vrt_priv, |
|
const enum uvb_event_type type, |
|
const void *data) |
|
{ |
|
const struct device *dev = vrt_priv; |
|
struct udc_vrt_data *priv = udc_get_private(dev); |
|
struct uvb_packet *const pkt = (void *)data; |
|
|
|
switch (type) { |
|
case UVB_EVT_VBUS_REMOVED: |
|
__fallthrough; |
|
case UVB_EVT_VBUS_READY: |
|
if (udc_is_initialized(dev)) { |
|
vrt_submit_uvb_event(dev, type, NULL); |
|
} |
|
break; |
|
case UVB_EVT_SUSPEND: |
|
__fallthrough; |
|
case UVB_EVT_RESUME: |
|
__fallthrough; |
|
case UVB_EVT_RESET: |
|
if (udc_is_enabled(dev)) { |
|
vrt_submit_uvb_event(dev, type, NULL); |
|
} |
|
break; |
|
case UVB_EVT_REQUEST: |
|
if (udc_is_enabled(dev) && priv->addr == pkt->addr) { |
|
vrt_submit_uvb_event(dev, type, pkt); |
|
} |
|
break; |
|
default: |
|
LOG_ERR("Unknown event for %p", dev); |
|
break; |
|
}; |
|
} |
|
|
|
static int udc_vrt_ep_enqueue(const struct device *dev, |
|
struct udc_ep_config *cfg, |
|
struct net_buf *buf) |
|
{ |
|
LOG_DBG("%p enqueue %p", dev, buf); |
|
udc_buf_put(cfg, buf); |
|
|
|
if (cfg->stat.halted) { |
|
LOG_DBG("ep 0x%02x halted", cfg->addr); |
|
return 0; |
|
} |
|
|
|
return 0; |
|
} |
|
|
|
static int udc_vrt_ep_dequeue(const struct device *dev, |
|
struct udc_ep_config *cfg) |
|
{ |
|
unsigned int lock_key; |
|
struct net_buf *buf; |
|
|
|
lock_key = irq_lock(); |
|
/* Draft dequeue implementation */ |
|
buf = udc_buf_get_all(cfg); |
|
if (buf) { |
|
udc_submit_ep_event(dev, buf, -ECONNABORTED); |
|
} |
|
irq_unlock(lock_key); |
|
|
|
return 0; |
|
} |
|
|
|
static int udc_vrt_ep_enable(const struct device *dev, |
|
struct udc_ep_config *cfg) |
|
{ |
|
return 0; |
|
} |
|
|
|
static int udc_vrt_ep_disable(const struct device *dev, |
|
struct udc_ep_config *cfg) |
|
{ |
|
return 0; |
|
} |
|
|
|
static int udc_vrt_ep_set_halt(const struct device *dev, |
|
struct udc_ep_config *cfg) |
|
{ |
|
LOG_DBG("Set halt ep 0x%02x", cfg->addr); |
|
|
|
cfg->stat.halted = true; |
|
|
|
return 0; |
|
} |
|
|
|
static int udc_vrt_ep_clear_halt(const struct device *dev, |
|
struct udc_ep_config *cfg) |
|
{ |
|
cfg->stat.halted = false; |
|
|
|
return 0; |
|
} |
|
|
|
static int udc_vrt_set_address(const struct device *dev, const uint8_t addr) |
|
{ |
|
struct udc_vrt_data *priv = udc_get_private(dev); |
|
|
|
priv->addr = addr; |
|
LOG_DBG("Set new address %u for %p", priv->addr, dev); |
|
|
|
return 0; |
|
} |
|
|
|
static int udc_vrt_host_wakeup(const struct device *dev) |
|
{ |
|
|
|
const struct udc_vrt_config *config = dev->config; |
|
|
|
return uvb_to_host(config->dev_node, UVB_EVT_DEVICE_ACT, |
|
INT_TO_POINTER(UVB_DEVICE_ACT_RWUP)); |
|
} |
|
|
|
static enum udc_bus_speed udc_vrt_device_speed(const struct device *dev) |
|
{ |
|
struct udc_data *data = dev->data; |
|
|
|
/* FIXME: get actual device speed */ |
|
return data->caps.hs ? UDC_BUS_SPEED_HS : UDC_BUS_SPEED_FS; |
|
} |
|
|
|
static int udc_vrt_enable(const struct device *dev) |
|
{ |
|
const struct udc_vrt_config *config = dev->config; |
|
enum uvb_device_act act; |
|
|
|
switch (config->speed_idx) { |
|
case 1: |
|
act = UVB_DEVICE_ACT_FS; |
|
break; |
|
case 2: |
|
act = UVB_DEVICE_ACT_HS; |
|
break; |
|
case 3: |
|
act = UVB_DEVICE_ACT_SS; |
|
break; |
|
case 0: |
|
default: |
|
act = UVB_DEVICE_ACT_LS; |
|
break; |
|
} |
|
|
|
return uvb_to_host(config->dev_node, UVB_EVT_DEVICE_ACT, |
|
INT_TO_POINTER(act)); |
|
} |
|
|
|
static int udc_vrt_disable(const struct device *dev) |
|
{ |
|
const struct udc_vrt_config *config = dev->config; |
|
|
|
return uvb_to_host(config->dev_node, UVB_EVT_DEVICE_ACT, |
|
INT_TO_POINTER(UVB_DEVICE_ACT_REMOVED)); |
|
} |
|
|
|
static int udc_vrt_init(const struct device *dev) |
|
{ |
|
const struct udc_vrt_config *config = dev->config; |
|
|
|
if (udc_ep_enable_internal(dev, USB_CONTROL_EP_OUT, |
|
USB_EP_TYPE_CONTROL, 64, 0)) { |
|
LOG_ERR("Failed to enable control endpoint"); |
|
return -EIO; |
|
} |
|
|
|
if (udc_ep_enable_internal(dev, USB_CONTROL_EP_IN, |
|
USB_EP_TYPE_CONTROL, 64, 0)) { |
|
LOG_ERR("Failed to enable control endpoint"); |
|
return -EIO; |
|
} |
|
|
|
return uvb_subscribe(config->uhc_name, config->dev_node); |
|
} |
|
|
|
static int udc_vrt_shutdown(const struct device *dev) |
|
{ |
|
const struct udc_vrt_config *config = dev->config; |
|
|
|
if (udc_ep_disable_internal(dev, USB_CONTROL_EP_OUT)) { |
|
LOG_ERR("Failed to disable control endpoint"); |
|
return -EIO; |
|
} |
|
|
|
if (udc_ep_disable_internal(dev, USB_CONTROL_EP_IN)) { |
|
LOG_ERR("Failed to disable control endpoint"); |
|
return -EIO; |
|
} |
|
|
|
return uvb_unsubscribe(config->uhc_name, config->dev_node); |
|
} |
|
|
|
static int udc_vrt_driver_preinit(const struct device *dev) |
|
{ |
|
const struct udc_vrt_config *config = dev->config; |
|
struct udc_data *data = dev->data; |
|
struct udc_vrt_data *priv = data->priv; |
|
uint16_t mps = 1023; |
|
int err; |
|
|
|
k_mutex_init(&data->mutex); |
|
k_fifo_init(&priv->fifo); |
|
|
|
data->caps.rwup = true; |
|
data->caps.mps0 = UDC_MPS0_64; |
|
if (config->speed_idx == 2) { |
|
data->caps.hs = true; |
|
mps = 1024; |
|
} |
|
|
|
for (int i = 0; i < config->num_of_eps; i++) { |
|
config->ep_cfg_out[i].caps.out = 1; |
|
if (i == 0) { |
|
config->ep_cfg_out[i].caps.control = 1; |
|
config->ep_cfg_out[i].caps.mps = 64; |
|
} else { |
|
config->ep_cfg_out[i].caps.bulk = 1; |
|
config->ep_cfg_out[i].caps.interrupt = 1; |
|
config->ep_cfg_out[i].caps.iso = 1; |
|
config->ep_cfg_out[i].caps.mps = mps; |
|
} |
|
|
|
config->ep_cfg_out[i].addr = USB_EP_DIR_OUT | i; |
|
err = udc_register_ep(dev, &config->ep_cfg_out[i]); |
|
if (err != 0) { |
|
LOG_ERR("Failed to register endpoint"); |
|
return err; |
|
} |
|
} |
|
|
|
for (int i = 0; i < config->num_of_eps; i++) { |
|
config->ep_cfg_in[i].caps.in = 1; |
|
if (i == 0) { |
|
config->ep_cfg_in[i].caps.control = 1; |
|
config->ep_cfg_in[i].caps.mps = 64; |
|
} else { |
|
config->ep_cfg_in[i].caps.bulk = 1; |
|
config->ep_cfg_in[i].caps.interrupt = 1; |
|
config->ep_cfg_in[i].caps.iso = 1; |
|
config->ep_cfg_in[i].caps.mps = mps; |
|
} |
|
|
|
config->ep_cfg_in[i].addr = USB_EP_DIR_IN | i; |
|
err = udc_register_ep(dev, &config->ep_cfg_in[i]); |
|
if (err != 0) { |
|
LOG_ERR("Failed to register endpoint"); |
|
return err; |
|
} |
|
} |
|
|
|
config->dev_node->priv = dev; |
|
config->make_thread(dev); |
|
LOG_INF("Device %p (max. speed %d) belongs to %s", |
|
dev, config->speed_idx, config->uhc_name); |
|
|
|
return 0; |
|
} |
|
|
|
static void udc_vrt_lock(const struct device *dev) |
|
{ |
|
udc_lock_internal(dev, K_FOREVER); |
|
} |
|
|
|
static void udc_vrt_unlock(const struct device *dev) |
|
{ |
|
udc_unlock_internal(dev); |
|
} |
|
|
|
static const struct udc_api udc_vrt_api = { |
|
.lock = udc_vrt_lock, |
|
.unlock = udc_vrt_unlock, |
|
.device_speed = udc_vrt_device_speed, |
|
.init = udc_vrt_init, |
|
.enable = udc_vrt_enable, |
|
.disable = udc_vrt_disable, |
|
.shutdown = udc_vrt_shutdown, |
|
.set_address = udc_vrt_set_address, |
|
.host_wakeup = udc_vrt_host_wakeup, |
|
.ep_enable = udc_vrt_ep_enable, |
|
.ep_disable = udc_vrt_ep_disable, |
|
.ep_set_halt = udc_vrt_ep_set_halt, |
|
.ep_clear_halt = udc_vrt_ep_clear_halt, |
|
.ep_enqueue = udc_vrt_ep_enqueue, |
|
.ep_dequeue = udc_vrt_ep_dequeue, |
|
}; |
|
|
|
#define DT_DRV_COMPAT zephyr_udc_virtual |
|
|
|
#define UDC_VRT_DEVICE_DEFINE(n) \ |
|
K_THREAD_STACK_DEFINE(udc_vrt_stack_area_##n, \ |
|
CONFIG_UDC_VIRTUAL_STACK_SIZE); \ |
|
\ |
|
static void udc_vrt_thread_##n(void *dev, void *unused1, void *unused2) \ |
|
{ \ |
|
while (1) { \ |
|
udc_vrt_thread_handler(dev); \ |
|
} \ |
|
} \ |
|
\ |
|
static void udc_vrt_make_thread_##n(const struct device *dev) \ |
|
{ \ |
|
struct udc_vrt_data *priv = udc_get_private(dev); \ |
|
\ |
|
k_thread_create(&priv->thread_data, \ |
|
udc_vrt_stack_area_##n, \ |
|
K_THREAD_STACK_SIZEOF(udc_vrt_stack_area_##n), \ |
|
udc_vrt_thread_##n, \ |
|
(void *)dev, NULL, NULL, \ |
|
K_PRIO_COOP(CONFIG_UDC_VIRTUAL_THREAD_PRIORITY), \ |
|
K_ESSENTIAL, \ |
|
K_NO_WAIT); \ |
|
k_thread_name_set(&priv->thread_data, dev->name); \ |
|
} \ |
|
\ |
|
static struct udc_ep_config \ |
|
ep_cfg_out[DT_INST_PROP(n, num_bidir_endpoints)]; \ |
|
static struct udc_ep_config \ |
|
ep_cfg_in[DT_INST_PROP(n, num_bidir_endpoints)]; \ |
|
\ |
|
static struct uvb_node udc_vrt_dev_node##n = { \ |
|
.name = DT_NODE_FULL_NAME(DT_DRV_INST(n)), \ |
|
.notify = udc_vrt_uvb_cb, \ |
|
}; \ |
|
\ |
|
static const struct udc_vrt_config udc_vrt_config_##n = { \ |
|
.num_of_eps = DT_INST_PROP(n, num_bidir_endpoints), \ |
|
.ep_cfg_in = ep_cfg_out, \ |
|
.ep_cfg_out = ep_cfg_in, \ |
|
.make_thread = udc_vrt_make_thread_##n, \ |
|
.dev_node = &udc_vrt_dev_node##n, \ |
|
.speed_idx = DT_ENUM_IDX(DT_DRV_INST(n), maximum_speed), \ |
|
.uhc_name = DT_NODE_FULL_NAME(DT_INST_PARENT(n)), \ |
|
}; \ |
|
\ |
|
static struct udc_vrt_data udc_priv_##n = { \ |
|
}; \ |
|
\ |
|
static struct udc_data udc_data_##n = { \ |
|
.mutex = Z_MUTEX_INITIALIZER(udc_data_##n.mutex), \ |
|
.priv = &udc_priv_##n, \ |
|
}; \ |
|
\ |
|
DEVICE_DT_INST_DEFINE(n, udc_vrt_driver_preinit, NULL, \ |
|
&udc_data_##n, &udc_vrt_config_##n, \ |
|
POST_KERNEL, CONFIG_KERNEL_INIT_PRIORITY_DEVICE, \ |
|
&udc_vrt_api); |
|
|
|
DT_INST_FOREACH_STATUS_OKAY(UDC_VRT_DEVICE_DEFINE)
|
|
|