cmvr_head/board/ports/usb/usb_cdc_char_device.c

409 lines
12 KiB
C
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#include "usb_cdc_char_device.h"
#include <rtthread.h>
#include <rtdevice.h>
#include <string.h>
#include <stdbool.h>
#include <stdint.h>
#include "usbd_core.h"
#include "usbd_cdc_acm.h"
#if !defined(RT_USING_DEVICE_OPS)
#error "RT_USING_DEVICE_OPS must be enabled"
#endif
#if !defined(CONFIG_USBDEV_ADVANCE_DESC)
#error "Please enable CONFIG_USBDEV_ADVANCE_DESC"
#endif
/* -------------------- FS fixed config -------------------- */
enum
{
CDC_IN_EP = 0x81,
CDC_OUT_EP = 0x02,
CDC_INT_EP = 0x83,
CDC_VID = 0xFFFF,
CDC_PID = 0xFFFF,
CDC_MAX_POWER_MA = 100,
CDC_MAX_MPS = 64, /* FS */
CDC_RX_DMA_SIZE = 2048, /* OUT DMA buffer */
CDC_TX_DMA_SIZE = 2048, /* IN DMA buffer */
CDC_RX_RING_SIZE = 8192, /* software RX ringbuffer */
CDC_CONFIG_SIZE = (9 + CDC_ACM_DESCRIPTOR_LEN),
/* 保险丝:防止上层误传超大 size比如把负数当无符号 */
CDC_WRITE_SIZE_GUARD = 64 * 1024, /* 64KB */
};
/* -------------------- DMA buffers in nocache RAM -------------------- */
USB_NOCACHE_RAM_SECTION USB_MEM_ALIGNX static uint8_t g_rx_dma[CDC_RX_DMA_SIZE];
USB_NOCACHE_RAM_SECTION USB_MEM_ALIGNX static uint8_t g_tx_dma[CDC_TX_DMA_SIZE];
/* -------------------- descriptors (advance-desc callbacks) -------------------- */
static const uint8_t g_device_desc[] = {
USB_DEVICE_DESCRIPTOR_INIT(USB_2_0, 0xEF, 0x02, 0x01,
CDC_VID, CDC_PID,
0x0100, 0x01)
};
static const uint8_t g_config_desc[] = {
USB_CONFIG_DESCRIPTOR_INIT(CDC_CONFIG_SIZE, 0x02, 0x01,
USB_CONFIG_BUS_POWERED, CDC_MAX_POWER_MA),
CDC_ACM_DESCRIPTOR_INIT(0x00,
CDC_INT_EP,
CDC_OUT_EP,
CDC_IN_EP,
CDC_MAX_MPS,
0x02)
};
/* qualifierFS 下不是必须,但留着不影响 */
static const uint8_t g_device_qual_desc[] = {
0x0a, USB_DESCRIPTOR_TYPE_DEVICE_QUALIFIER,
0x00, 0x02,
0x00, 0x00, 0x00,
0x40,
0x00, 0x00,
};
static const char g_langid_bytes[] = { 0x09, 0x04 };
static const char *g_string_desc[] = {
g_langid_bytes,
"CherryUSB",
"RTT CDC ACM FS",
"000000000001",
};
static const uint8_t *device_descriptor_cb(uint8_t speed) { (void)speed; return g_device_desc; }
static const uint8_t *config_descriptor_cb(uint8_t speed) { (void)speed; return g_config_desc; }
static const uint8_t *device_qual_descriptor_cb(uint8_t speed) { (void)speed; return g_device_qual_desc; }
static const char *string_descriptor_cb(uint8_t speed, uint8_t index)
{
(void)speed;
if (index > 3) return RT_NULL;
return g_string_desc[index];
}
static const struct usb_descriptor g_cdc_desc = {
.device_descriptor_callback = device_descriptor_cb,
.config_descriptor_callback = config_descriptor_cb,
.device_quality_descriptor_callback = device_qual_descriptor_cb,
.string_descriptor_callback = string_descriptor_cb
};
/* -------------------- driver object -------------------- */
struct cdc_char_dev
{
struct rt_device dev; /* must be first */
uint8_t busid;
uintptr_t reg_base;
volatile rt_uint8_t dtr_enable;
volatile rt_uint8_t connected;
volatile rt_uint8_t configured;
struct rt_ringbuffer *rx_rb;
rt_sem_t tx_sem;
rt_mutex_t tx_lock;
uint32_t tx_timeout_ms;
uint8_t require_dtr_tx;
struct usbd_interface intf0;
struct usbd_interface intf1;
struct usbd_endpoint out_ep;
struct usbd_endpoint in_ep;
};
static struct cdc_char_dev g_cdc;
/* -------------------- forward declarations -------------------- */
static void usbd_event_handler(uint8_t busid, uint8_t event);
static void cdc_bulk_out(uint8_t busid, uint8_t ep, uint32_t nbytes);
static void cdc_bulk_in(uint8_t busid, uint8_t ep, uint32_t nbytes);
/* -------------------- RT device ops -------------------- */
static rt_err_t cdc_init(rt_device_t dev) { (void)dev; return RT_EOK; }
static rt_err_t cdc_open(rt_device_t dev, rt_uint16_t oflag) { (void)dev; (void)oflag; return RT_EOK; }
static rt_err_t cdc_close(rt_device_t dev) { (void)dev; return RT_EOK; }
static rt_ssize_t cdc_read(rt_device_t dev, rt_off_t pos, void *buffer, rt_size_t size)
{
(void)pos;
if (!dev || !buffer) return -RT_EINVAL;
if (size == 0) return 0;
struct cdc_char_dev *c = (struct cdc_char_dev *)dev;
/* 未枚举配置完成,不允许读(避免上层误用) */
if (!c->configured) return -RT_EIO;
if (!c->rx_rb) return -RT_ERROR;
rt_base_t level = rt_hw_interrupt_disable();
rt_size_t got = rt_ringbuffer_get(c->rx_rb, (rt_uint8_t *)buffer, size);
rt_hw_interrupt_enable(level);
return (rt_ssize_t)got; /* ringbuffer 空则返回 0 */
}
static rt_ssize_t cdc_write(rt_device_t dev, rt_off_t pos, const void *buffer, rt_size_t size)
{
(void)pos;
if (!dev || !buffer) return -RT_EINVAL;
if (size == 0) return 0;
/* 保险丝:防止上层把负数当 size 传下来 */
if (size > CDC_WRITE_SIZE_GUARD) return -RT_EINVAL;
struct cdc_char_dev *c = (struct cdc_char_dev *)dev;
/* 未配置/断开,不允许写 */
if (!c->configured) return -RT_EIO;
if (c->require_dtr_tx && !c->dtr_enable) return -RT_EIO;
if (!c->tx_sem || !c->tx_lock) return -RT_ERROR;
rt_mutex_take(c->tx_lock, RT_WAITING_FOREVER);
rt_size_t sent_total = 0;
const rt_uint8_t *p = (const rt_uint8_t *)buffer;
while (sent_total < size)
{
/* 如果中途断开/重置,立刻失败 */
if (!c->configured)
{
rt_mutex_release(c->tx_lock);
return (sent_total > 0) ? (rt_ssize_t)sent_total : -RT_EIO;
}
rt_size_t chunk = size - sent_total;
if (chunk > CDC_TX_DMA_SIZE) chunk = CDC_TX_DMA_SIZE;
memcpy(g_tx_dma, p + sent_total, chunk);
/* 清掉旧 token */
while (rt_sem_trytake(c->tx_sem) == RT_EOK) {}
usbd_ep_start_write(c->busid, CDC_IN_EP, g_tx_dma, (uint32_t)chunk);
rt_tick_t to = rt_tick_from_millisecond(c->tx_timeout_ms);
if (rt_sem_take(c->tx_sem, to) != RT_EOK)
{
rt_mutex_release(c->tx_lock);
return (sent_total > 0) ? (rt_ssize_t)sent_total : -RT_ETIMEOUT;
}
/* 被唤醒后再次确认仍处于 configured避免断开时“假完成” */
if (!c->configured)
{
rt_mutex_release(c->tx_lock);
return (sent_total > 0) ? (rt_ssize_t)sent_total : -RT_EIO;
}
sent_total += chunk;
}
rt_mutex_release(c->tx_lock);
return (rt_ssize_t)sent_total;
}
static rt_err_t cdc_control(rt_device_t dev, int cmd, void *args)
{
if (!dev) return -RT_EINVAL;
struct cdc_char_dev *c = (struct cdc_char_dev *)dev;
switch (cmd)
{
case CDC_CTRL_SET_TX_TIMEOUT_MS:
if (args) c->tx_timeout_ms = *(uint32_t *)args;
return RT_EOK;
case CDC_CTRL_GET_TX_TIMEOUT_MS:
if (args) *(uint32_t *)args = c->tx_timeout_ms;
return RT_EOK;
case CDC_CTRL_GET_DTR:
if (args) *(uint8_t *)args = c->dtr_enable;
return RT_EOK;
case CDC_CTRL_SET_REQUIRE_DTR_TX:
if (args) c->require_dtr_tx = (*(uint8_t *)args) ? 1 : 0;
return RT_EOK;
case CDC_CTRL_GET_CONFIGURED: /* ✅ 新增:查询是否已枚举配置 */
if (args) *(uint8_t *)args = c->configured;
return RT_EOK;
default:
return -RT_ENOSYS;
}
}
/* ops table */
static const struct rt_device_ops cdc_ops =
{
.init = cdc_init,
.open = cdc_open,
.close = cdc_close,
.read = cdc_read,
.write = cdc_write,
.control = cdc_control,
};
/* -------------------- CherryUSB callbacks -------------------- */
static void usbd_event_handler(uint8_t busid, uint8_t event)
{
(void)busid;
switch (event)
{
case USBD_EVENT_RESET:
g_cdc.configured = 0;
g_cdc.connected = 0;
/* 可选:唤醒等待线程,让它能尽快感知状态变化 */
if (g_cdc.dev.rx_indicate)
g_cdc.dev.rx_indicate((rt_device_t)&g_cdc.dev, 0);
break;
case USBD_EVENT_CONNECTED:
g_cdc.connected = 1;
break;
case USBD_EVENT_DISCONNECTED:
g_cdc.configured = 0;
g_cdc.connected = 0;
/* 可选:唤醒等待线程 */
if (g_cdc.dev.rx_indicate)
g_cdc.dev.rx_indicate((rt_device_t)&g_cdc.dev, 0);
break;
case USBD_EVENT_CONFIGURED:
g_cdc.connected = 1;
g_cdc.configured = 1;
/* 重新启动 OUT 读(防止 reset 后没读) */
usbd_ep_start_read(g_cdc.busid, CDC_OUT_EP, g_rx_dma, CDC_RX_DMA_SIZE);
/* 可选:唤醒等待线程,让“先启动线程再插线”也能快速进入工作状态 */
if (g_cdc.dev.rx_indicate)
g_cdc.dev.rx_indicate((rt_device_t)&g_cdc.dev, 0);
break;
default:
break;
}
}
static void cdc_bulk_out(uint8_t busid, uint8_t ep, uint32_t nbytes)
{
(void)busid;
(void)ep;
/* 未配置时忽略数据,并继续挂下一次读 */
if (!g_cdc.configured)
{
usbd_ep_start_read(g_cdc.busid, CDC_OUT_EP, g_rx_dma, CDC_RX_DMA_SIZE);
return;
}
if (nbytes > 0 && nbytes <= CDC_RX_DMA_SIZE && g_cdc.rx_rb)
{
(void)rt_ringbuffer_put(g_cdc.rx_rb, g_rx_dma, (rt_size_t)nbytes);
if (g_cdc.dev.rx_indicate)
g_cdc.dev.rx_indicate((rt_device_t)&g_cdc.dev, (rt_size_t)nbytes);
}
usbd_ep_start_read(g_cdc.busid, CDC_OUT_EP, g_rx_dma, CDC_RX_DMA_SIZE);
}
static void cdc_bulk_in(uint8_t busid, uint8_t ep, uint32_t nbytes)
{
(void)busid;
/* ZLP长度是 MPS 整倍数时补 0 包 */
if ((nbytes % usbd_get_ep_mps(g_cdc.busid, ep)) == 0 && nbytes)
{
usbd_ep_start_write(g_cdc.busid, CDC_IN_EP, RT_NULL, 0);
return;
}
if (g_cdc.tx_sem) rt_sem_release(g_cdc.tx_sem);
if (g_cdc.dev.tx_complete)
g_cdc.dev.tx_complete((rt_device_t)&g_cdc.dev, RT_NULL);
}
/* DTR hook */
void usbd_cdc_acm_set_dtr(uint8_t busid, uint8_t intf, bool dtr)
{
(void)busid;
(void)intf;
g_cdc.dtr_enable = dtr ? 1 : 0;
}
/* endpoints */
static struct usbd_endpoint g_out_ep = { .ep_addr = CDC_OUT_EP, .ep_cb = cdc_bulk_out };
static struct usbd_endpoint g_in_ep = { .ep_addr = CDC_IN_EP, .ep_cb = cdc_bulk_in };
/* -------------------- public init -------------------- */
int rt_hw_usbd_cdc_char_init(const char *dev_name, uint8_t busid, uintptr_t reg_base)
{
if (!dev_name) return -RT_EINVAL;
memset(&g_cdc, 0, sizeof(g_cdc));
g_cdc.busid = busid;
g_cdc.reg_base = reg_base;
g_cdc.tx_timeout_ms = 2000;
g_cdc.require_dtr_tx = 0;
g_cdc.connected = 0;
g_cdc.configured = 0;
g_cdc.rx_rb = rt_ringbuffer_create(CDC_RX_RING_SIZE);
if (!g_cdc.rx_rb) return -RT_ENOMEM;
g_cdc.tx_sem = rt_sem_create("cdc_tx", 0, RT_IPC_FLAG_FIFO);
if (!g_cdc.tx_sem) return -RT_ENOMEM;
g_cdc.tx_lock = rt_mutex_create("cdc_txL", RT_IPC_FLAG_FIFO);
if (!g_cdc.tx_lock) return -RT_ENOMEM;
/* register char device */
g_cdc.dev.type = RT_Device_Class_Char;
g_cdc.dev.rx_indicate = RT_NULL;
g_cdc.dev.tx_complete = RT_NULL;
g_cdc.dev.ops = &cdc_ops;
g_cdc.dev.user_data = RT_NULL;
rt_err_t r = rt_device_register((rt_device_t)&g_cdc.dev, dev_name, RT_DEVICE_FLAG_RDWR);
if (r != RT_EOK) return r;
/* init CherryUSB CDC */
usbd_desc_register(g_cdc.busid, &g_cdc_desc);
usbd_add_interface(g_cdc.busid, usbd_cdc_acm_init_intf(g_cdc.busid, &g_cdc.intf0));
usbd_add_interface(g_cdc.busid, usbd_cdc_acm_init_intf(g_cdc.busid, &g_cdc.intf1));
g_cdc.out_ep = g_out_ep;
g_cdc.in_ep = g_in_ep;
usbd_add_endpoint(g_cdc.busid, &g_cdc.out_ep);
usbd_add_endpoint(g_cdc.busid, &g_cdc.in_ep);
usbd_initialize(g_cdc.busid, g_cdc.reg_base, usbd_event_handler);
return 0;
}