409 lines
12 KiB
C
409 lines
12 KiB
C
|
|
#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)
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
/* qualifier:FS 下不是必须,但留着不影响 */
|
|||
|
|
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;
|
|||
|
|
}
|