USB 协议栈与主机/设备驱动
AD23N SDK 的 USB 子系统完整参考:包含设备(Device/Slave)协议栈、主机(Host)协议栈、BSP 配置适配层与 PHY 传输抽象层,覆盖 USB 枚举、控制传输、Bulk/中断/同步端点传输、设备类接口与挂载/卸载生命周期管理。
Purpose and Scope
本文档描述 AD23N 平台 USB 子系统从底层 PHY 到上层设备类接口的完整实现机制,包括:
- 设备模式(USB Device/Slave):
usb_stack.h定义的 EP0 控制传输状态机、接口 handler 分发与描述符拼接机制; - 主机模式(USB Host):
usb_host.h定义的挂载/卸载流程、设备类驱动接口(mass_storage、adb、hid、aoa)与信号量同步; - PHY 传输层:
usb_phy.h暴露的 Bulk/中断/同步端点读写 API 与字节序转换宏; - BSP 配置层:
usb_config.h提供的模式配置、释放与中断注册接口。
本文不深入具体应用层业务(如 USB 音频类应用细节、mbox_flash 项目专用逻辑),这些属于各自应用/业务页面;本文只说明协议栈如何被它们使用。
Overview
AD23N 是杰理科技(Jieli)面向嵌入式场景的 MCU,其 USB 控制器(SIE)可通过 OTG 方式在主机模式与设备模式之间切换。SDK 将 USB 子系统拆分为两个相互独立的协议栈(设备栈与主机栈),二者共享同一套 PHY 传输层 API,由 BSP 层负责模式选择与中断配置。这种"双栈 + 共享 PHY"的结构使得同一份芯片资源可以灵活承载不同应用:
- 设备模式:让 AD23N 作为 USB 外设(如 U 盘、声卡、ADB 设备)连接 PC/手机,核心是 EP0 的 setup 状态机与各类描述符的组织;
- 主机模式:让 AD23N 作为 USB 主机(如读 U 盘、连接 HID 键鼠、AOA 手机),核心是总线复位、枚举、设备地址分配与设备类驱动的挂载。
两个协议栈都以 C 语言回调(函数指针)作为扩展点:设备栈通过 itf_hander / desc_config 把不同的 USB 接口(audio、mass storage 等)挂接到枚举流程;主机栈通过 interface_ctrl 把具体设备类驱动(disk/adb/hid/aoa)注册到接口描述符之上。
Architecture
USB 子系统整体分层如下(各层命名均取自仓库中的实际头文件):
flowchart TD
subgraph sg_App["应用层 (sdk/app)"]
App["应用任务<br/>(mbox_flash/usb_slave.c 等)"]
AudioItf["USB Audio 接口<br/>(usr/usb_audio_interface.h)"]
end
subgraph sg_Stack["协议栈层 (sdk/include_lib/device/usb)"]
DevStack["设备栈 usb_stack.h<br/>usb_device_t / usb_setup_t / EP0 状态机"]
HostStack["主机栈 usb_host.h<br/>usb_host_device / interface_ctrl / 挂载流程"]
end
subgraph sg_Bsp["BSP 配置层 (sdk/app/bsp/common/usb)"]
UsbConfig["usb_config.h<br/>usb_config / usb_release<br/>usb_h_isr_reg / usb_g_isr_reg / usb_sof_isr_reg"]
end
subgraph sg_Phy["PHY 抽象层 (usb_phy.h)"]
Phy["bulk / intr / iso 读写 API<br/>字节序宏与寄存器辅助函数"]
end
subgraph sg_Hw["硬件层 (asm/usb.h)"]
Hw["USB SIE 控制器 / DP-DM 引脚"]
end
App --> DevStack
App --> HostStack
AudioItf --> DevStack
DevStack --> UsbConfig
HostStack --> UsbConfig
DevStack --> Phy
HostStack --> Phy
UsbConfig --> Phy
Phy --> Hw
各层职责与设计意图:
- 设备栈(usb_stack.h):维护
struct usb_device_t(含 EP0 setup 状态机字段)和struct usb_setup_t(设备 + 控制请求 + 最多 5 个接口 handler 的聚合)。设计意图是让"协议状态机"与"接口业务"解耦——状态机由栈驱动,具体接口如何响应请求、如何拼接描述符由注册的itf_hander/desc_config回调决定。 - 主机栈(usb_host.h):以
struct usb_host_device为宿主设备抽象,通过struct interface_ctrl(set_power/get_power/ioctl)和struct usb_interface_info的联合体(mass_storage/adb/hid/aoa)把底层枚举与上层设备类驱动解耦;用OS_SEM(信号量)做挂载/传输的同步原语。 - BSP 配置层(usb_config.h):是栈与硬件之间的"装配车间"——
usb_config()/usb_release()完成模式配置与资源释放,usb_h_isr_reg/usb_g_isr_reg/usb_sof_isr_reg负责把中断服务例程绑定到指定优先级与 CPU 核(AD23N 为多核架构)。 - PHY 抽象层(usb_phy.h):对上层提供对称的传输 API——设备侧
usb_g_*(g = gadget/device),主机侧usb_h_*;同时提供cpu_to_be16/be32、le16_to_cpu等字节序宏与LOWORD/HIBYTE等位域宏,供构造 USB 描述符与解析请求使用。 - 硬件层(asm/usb.h):芯片寄存器定义,由 BSP/PHY 层间接引用。
设备模式(USB Device/Slave)实现
核心数据结构:struct usb_device_t
设备栈以 usb_device_t 为每次 USB 会话的"心脏",它把 EP0 控制传输所需的全部状态内聚在一个结构体中:
struct usb_device_t {
u8 baddr;
u8 bsetup_phase; //ep0 setup状态机
u16 wDataLength; //ep0 setup data stage数据长度
u8 *setup_buffer; //本次传输的bufer地址
u8 *setup_ptr; //当前传输的位置
u32(*setup_hook)(struct usb_device_t *, struct usb_ctrlrequest *);
u32(*setup_recv)(struct usb_device_t *, struct usb_ctrlrequest *);
u8 bDeviceStates;
u8 bDataOverFlag; //ep0 0包标识
u8 bRemoteWakup: 1;
u8 res: 7;
u8 wDeviceClass; // 设备类
};
Source: usb_stack.h
设计要点:
bsetup_phase+wDataLength+setup_ptr共同构成 EP0 setup 状态机:控制传输被拆分为 setup / data / status 三个阶段,栈通过这几个字段记住"当前处于哪个阶段、还差多少数据"。setup_hook与setup_recv是两个可替换的回调:setup_hook在收到 setup 包后、正式处理前被调用(可做过滤/预处理);setup_recv负责接收请求后的分发。user_setup_filter_install()提供了安装用户级 setup 过滤器的入口。bDataOverFlag标记 EP0 的 0 长度包(用于 data 阶段结束时的短包收尾,USB 规范要求短包标识传输结束)。bRemoteWakup是位域字段,支持远程唤醒特性。
设备状态机
设备栈显式枚举了 USB 规范定义的标准设备状态:
enum {
USB_ATTACHED,
USB_POWERED,
USB_DEFAULT,
USB_ADDRESS,
USB_CONFIGURED,
USB_SUSPENDED
};
Source: usb_stack.h
bDeviceStates 字段记录当前状态。状态迁移由中断/事件驱动:上电进入 USB_ATTACHED/USB_POWERED,总线复位进入 USB_DEFAULT,收到 SET_ADDRESS 进入 USB_ADDRESS,收到 SET_CONFIGURATION 进入 USB_CONFIGURED,之后接口 handler 才真正开始业务收发;总线挂起则进入 USB_SUSPENDED。
接口注册与描述符拼接
设备栈用一组函数指针类型把"协议处理"与"具体接口"解耦:
typedef u32(*itf_hander)(struct usb_device_t *usb_device, struct usb_ctrlrequest *);
typedef void(*itf_reset_hander)(struct usb_device_t *, u32 itf);
typedef void(*usb_interrupt)(struct usb_device_t *, u32 ep);
typedef u32(*desc_config)(const usb_dev usb_id, u8 *ptr, u32 *cur_itf_num);
Source: usb_stack.h
itf_hander:接口控制请求处理函数,收到SET_INTERFACE/ 类请求时被调用;itf_reset_hander:接口复位回调,总线复位或usb_reset_interface()时触发;usb_interrupt:端点中断回调,端点有数据或传输完成时触发;desc_config:配置描述符拼接回调,usb_add_desc_config()可注册多个(按index排序),栈在收到GET_DESCRIPTOR(Configuration)时依次调用它们把设备/配置/接口/端点描述符连续写入返回缓冲区,cur_itf_num用于跨回调传递当前接口编号。
聚合结构 usb_setup_t 把设备、请求和两组回调数组绑在一起:
struct usb_setup_t {
struct usb_device_t usb_device;
struct usb_ctrlrequest request;
itf_hander interface_hander[MAX_INTERFACE_NUM];
itf_reset_hander reset_hander[MAX_INTERFACE_NUM];
} __attribute__((aligned(4)));
Source: usb_stack.h
MAX_INTERFACE_NUM 为 5,即一个复合设备最多支持 5 个接口(如同时做 U 盘 + 声卡 + ADB);__attribute__((aligned(4))) 保证 DMA/控制器访问对齐。usb_setup_init() 初始化该结构并绑定 setup 缓冲区(USB_SETUP_SIZE = 512 字节,覆盖最大控制传输 data 阶段),usb_setup_release() 释放。
设备模式枚举时序
sequenceDiagram
participant H as USB 主机 (PC/手机)
participant D as usb_device_t 状态机
participant S as usb_setup_t / EP0
participant I as itf_hander / desc_config
H->>D: 总线复位 (RESET)
D->>S: 状态 -> USB_DEFAULT
H->>S: GET_DESCRIPTOR(Device)
S->>D: setup_hook 预处理
D-->>H: 返回设备描述符
H->>S: SET_ADDRESS(addr)
S->>D: baddr=addr, 状态 -> USB_ADDRESS
H->>S: GET_DESCRIPTOR(Configuration)
S->>I: 依次调用 desc_config 拼接
I-->>S: 设备/配置/接口/端点描述符
H->>S: SET_CONFIGURATION
S->>D: 状态 -> USB_CONFIGURED
Note over I: 应用接口开始 bulk/intr 收发
该流程完全由栈驱动:usb_control_transfer() 是 EP0 控制传输的驱动器,usb_set_data_payload() 把 data 阶段数据装入 setup 缓冲区并返回指针供继续填充,usb_set_setup_phase() 显式推进 setup 状态机。设备类通过 usb_device_set_class() 设定,usb_device_mode() 选择具体设备模式(class 参数决定启用哪些类逻辑)。
主机模式(USB Host)实现
核心数据结构:usb_host_device 与接口驱动
主机栈以 struct usb_host_device 抽象一个已挂载的 USB 从设备,内部包含私有数据、同步信号量与接口驱动信息:
struct usb_private_data {
usb_dev usb_id;
u8 status;
u8 devnum;
u8 ep0_max_packet_size;
};
struct usb_host_device {
OS_SEM *sem;
struct usb_private_data private_data;
const struct usb_interface_info *interface_info[MAX_HOST_INTERFACE];
};
Source: usb_host.h
usb_private_data记录主机分配的devnum(设备地址)与ep0_max_packet_size(EP0 最大包长,枚举时从设备描述符读出);sem是OS_SEM信号量,用于挂载/传输同步;interface_info数组容量MAX_HOST_INTERFACE= 1(当前平台单接口主机设备),指向接口驱动信息。
设备类驱动的抽象是 interface_ctrl 与 usb_interface_info:
struct interface_ctrl {
u8 interface_class;
int (*set_power)(struct usb_host_device *host_dev, u32 value);
int (*get_power)(struct usb_host_device *host_dev, u32 value);
int (*ioctl)(struct usb_host_device *host_dev, u32 cmd, u32 arg);
};
struct usb_interface_info {
struct interface_ctrl *ctrl;
union {
struct mass_storage *disk;
struct adb_device_t *adb;
struct hid_device_t *hid;
struct aoa_device_t *aoa;
void *p;
} dev;
};
Source: usb_host.h
设计意图:interface_ctrl 提供与具体设备类无关的电源/IOCTL 操作面(主机栈可统一管理设备上下电),dev 联合体按 interface_class 关联到具体类驱动实例(U 盘 mass storage、ADB、HID、AOA)。新增设备类只需扩展联合体并实现 interface_ctrl 回调即可接入,无需改动枚举核心。
挂载/卸载生命周期
主机栈提供完整的生命周期 API:usb_host_init() 初始化控制器,usb_host_mount() 挂载,usb_host_unmount() 卸载,usb_host_remount() 带重试/通知的重新挂载,usb_host_suspend() / usb_host_resume() 管理总线挂起与恢复,usb_h_force_reset() 强制复位总线。
flowchart TD
Start([usb_host_mount usb_id, retry, reset_delay, mount_timeout]) --> Init["usb_host_init / 复位总线"]
Init --> Enum["枚举: 读设备描述符<br/>分配 devnum / ep0_max_packet_size"]
Enum --> Chk{"ret == 0 ?"}
Chk -->|"是"| Done["挂载成功, 返回 0<br/>interface_info 可访问"]
Chk -->|"否且 DEV_ERR_OFFLINE"| Offline["check_usb_mount:<br/>log_info + goto __exit_fail"]
Chk -->|"其它错误"| Retry["继续重试 (retry 次)<br/>reset_delay / mount_timeout 控制节奏"]
Retry --> Enum
Offline --> Unmount["usb_host_unmount"]
Done --> Use["上层使用: bulk/intr/iso 传输"]
挂载失败处理由 check_usb_mount 宏统一收敛——它把 -DEV_ERR_OFFLINE(设备离线/拔出)与其它错误区分开:离线直接跳转到 __exit_fail 标签(调用方负责清理),其它错误则打印日志后 continue 重试:
#define check_usb_mount(ret) \
if(ret == -DEV_ERR_OFFLINE){\
log_info("%s() @ %d DEV_ERR_OFFLINE\n", __func__, __LINE__);\
goto __exit_fail;\
} else if(ret){\
log_info("%s() @ %d %x\n", __func__, __LINE__, ret);\
continue;\
}
Source: usb_host.h
该宏的 goto/continue 设计依赖调用方所在循环/标签上下文,属于嵌入式 SDK 常见的"约定式错误处理",使用时需按相同模式组织挂载循环。
信号量与中断回调
主机栈用四个信号量封装函数替代裸 OS 调用,保证与协议栈的抽象边界:
int usb_sem_init(struct usb_host_device *host_dev);
int usb_sem_pend(struct usb_host_device *host_dev, u32 timeout);
int usb_sem_post(struct usb_host_device *host_dev);
int usb_sem_del(struct usb_host_device *host_dev);
Source: usb_host.h
端点中断通过 usb_h_set_ep_isr()(绑定 handler 与私有参数 p)或 usb_h_set_intr_hander()(按 usb_dev 注册)挂接,中断回调类型为 usb_h_interrupt。传输完成的唤醒通常就是"ISR → usb_sem_post → 等待者 usb_sem_pend 返回",这是典型的嵌入式中断-任务同步模式。
PHY 传输层(usb_phy.h)
PHY 层为设备/主机两侧提供对称的传输 API,并统一处理字节序与超时。
设备侧(slave api)
/* slave api */
u32 usb_g_bulk_read64byte_fast(const usb_dev usb_id, u32 ep, u8 *ptr, u32 len);
u32 usb_g_bulk_read(const usb_dev usb_id, u32 ep, u8 *ptr, u32 len, u32 block);
u32 usb_g_bulk_write(const usb_dev usb_id, u32 ep, const u8 *ptr, u32 len);
u32 usb_g_intr_read(const usb_dev usb_id, u32 ep, u8 *ptr, u32 len, u32 block);
u32 usb_g_intr_write(const usb_dev usb_id, u32 ep, const u8 *ptr, u32 len);
u32 usb_g_iso_read(const usb_dev usb_id, u32 ep, u8 *ptr, u32 len, u32 block);
u32 usb_g_iso_write(const usb_dev usb_id, u32 ep, const u8 *ptr, u32 len);
void usb_slave_init(const usb_dev usb_id);
Source: usb_phy.h
主机侧(host api)
/* host api */
u32 usb_h_bulk_read(const usb_dev usb_id, u8 host_ep, u16 rxmaxp, u8 target_ep, u8 *ptr, u32 len);
u32 usb_h_bulk_write(const usb_dev usb_id, u8 host_ep, u16 txmaxp, u8 target_ep, u8 *ptr, u32 len);
u32 usb_h_intr_read(const usb_dev usb_id, u8 host_ep, u16 rxmaxp, u8 target_ep, u8 *ptr, u32 len);
u32 usb_h_intr_write(const usb_dev usb_id, u8 host_ep, u16 txmaxp, u8 target_ep, u8 *ptr, u32 len);
u32 usb_h_iso_read(const usb_dev usb_id, u8 host_ep, u16 rxmaxp, u8 target_ep, u8 *ptr, u32 len);
u32 usb_h_iso_write(const usb_dev usb_id, u8 host_ep, u16 txmaxp, u8 target_ep, u8 *ptr, u32 len);
void usb_h_entry_suspend(const usb_dev usb_id);
void usb_h_resume(const usb_dev usb_id);
u32 usb_host_init(const usb_dev usb_id, u32 reset_delay, u32 timeout);
u32 usb_host_reset(const usb_dev usb_id, u32 reset_delay, u32 timeout);
u32 usb_h_force_reset(const usb_dev usb_id);
Source: usb_phy.h
主机侧传输函数多出 host_ep / rxmaxp(或 txmaxp) / target_ep 三个参数:主机需要显式指定"本地用于传输的端点号、本地端点最大包长、目标从设备的端点号";而设备侧只需本地 ep。block 参数控制阻塞/非阻塞行为。
字节序与位域辅助宏
USB 描述符多字段为小端序、SOF 帧号等为大端场景,PHY 层提供统一宏:
#define cpu_to_be16(v16) ___ntohl(v16)
#define cpu_to_be32(v32) ___ntohs(v32)
#define be16_to_cpu(v16) cpu_to_be16(v16)
#define cpu_to_le16(v16) (v16)
#define cpu_to_le32(v32) (v32)
#define le16_to_cpu(v16) cpu_to_le16(v16)
#define LOBYTE(w) ((u8)(w))
#define HIBYTE(w) ((u8)(((u16)(w) >> 8) & 0xFF))
#define DW1BYTE(dw) (LOBYTE(LOWORD(dw)))
Source: usb_phy.h
小端宏为恒等映射(MCU 本身小端),大端宏执行字节交换;LOWORD/HIWORD/LOBYTE/HIBYTE/DW1-4BYTE 系列用于从 16/32 位值中抽取字段,是构造 wMaxPacketSize、bcdUSB 等描述符字段的常用工具。usb_host_timeout() 与 get_jiffies() 提供超时计算基础,usb_read_sofframe() / usb_read_dp_se() / usb_read_dm_se() 读取 SOF 帧号与 DP/DM 线状态(用于 OTG 检测与调试)。
BSP 配置层(usb_config.h)
usb_config.h 是协议栈与板级代码之间的装配接口,同时引用设备栈与主机栈头文件,向应用暴露统一的配置入口:
void usb_host_config(usb_dev usb_id);
void usb_host_free(usb_dev usb_id);
void *usb_h_get_ep_buffer(const usb_dev usb_id, u32 ep);
void usb_h_isr_reg(const usb_dev usb_id, u8 priority, u8 cpu_id);
void usb_g_isr_reg(const usb_dev usb_id, u8 priority, u8 cpu_id);
void usb_sof_isr_reg(const usb_dev usb_id, u8 priority, u8 cpu_id);
void *usb_get_ep_buffer(const usb_dev usb_id, u32 ep);
u32 usb_config(const usb_dev usb_id);
u32 usb_release(const usb_dev usb_id);
void usb_otg_sof_check_init(const usb_dev id);
Source: usb_config.h
关键点:
usb_config()/usb_release():设备模式下的配置与释放(对应设备栈的 setup 初始化/释放),应用切换模式前必须先 release 再 config;usb_host_config()/usb_host_free():主机模式下的配置与释放;usb_g_isr_reg()/usb_h_isr_reg()/usb_sof_isr_reg():分别注册设备中断、主机中断与 SOF 中断,均带priority与cpu_id参数——AD23N 多核架构要求中断显式绑定到某个核,这是低延迟 USB 传输的关键配置;usb_get_ep_buffer()/usb_h_get_ep_buffer():获取端点专用 DMA 缓冲区(设备侧/主机侧),批量传输的数据指针通常要求落在这些缓冲区中。
使用示例
示例 1:设备模式注册接口 handler 与描述符
设备模式应用(如 sdk/app/src/mbox_flash/usb_slave/usb_slave.c)的典型装配流程是:usb_device_mode() 选择设备类 → usb_set_interface_hander() 注册各接口 handler → usb_add_desc_config() 注册描述符拼接回调 → usb_setup_init() 启动 EP0。以下回调类型与注册接口来自设备栈:
typedef u32(*itf_hander)(struct usb_device_t *usb_device, struct usb_ctrlrequest *);
typedef void(*itf_reset_hander)(struct usb_device_t *, u32 itf);
typedef u32(*desc_config)(const usb_dev usb_id, u8 *ptr, u32 *cur_itf_num);
u32 usb_g_set_intr_hander(const usb_dev usb_id, u32 ep, usb_interrupt hander);
u32 usb_set_interface_hander(const usb_dev usb_id, u32 itf_num, itf_hander hander);
void usb_add_desc_config(const usb_dev usb_id, u32 index, const desc_config desc);
u32 usb_set_reset_hander(const usb_dev usb_id, u32 itf_num, itf_reset_hander hander);
Source: usb_stack.h
示例 2:主机模式挂载循环
主机模式应用按"retry + check_usb_mount"模式组织挂载,配合 usb_host_remount() 实现热插拔重挂载:
u32 usb_host_mount(const usb_dev usb_id, u32 retry, u32 reset_delay, u32 mount_timeout);
u32 usb_host_unmount(const usb_dev usb_id);
u32 usb_host_remount(const usb_dev usb_id, u32 retry, u32 delay, u32 ot, u8 notify);
#define check_usb_mount(ret) \
if(ret == -DEV_ERR_OFFLINE){\
log_info("%s() @ %d DEV_ERR_OFFLINE\n", __func__, __LINE__);\
goto __exit_fail;\
} else if(ret){\
log_info("%s() @ %d %x\n", __func__, __LINE__, ret);\
continue;\
}
Sources: usb_host.h、usb_host.h
示例 3:主机批量读写
主机向从设备端点 target_ep 发起批量传输,需同时给出本地 host_ep 与最大包长:
u32 usb_h_bulk_read(const usb_dev usb_id, u8 host_ep, u16 rxmaxp, u8 target_ep, u8 *ptr, u32 len);
u32 usb_h_bulk_write(const usb_dev usb_id, u8 host_ep, u16 txmaxp, u8 target_ep, u8 *ptr, u32 len);
Source: usb_phy.h
Configuration Options
USB 子系统无集中式配置文件,行为通过 API 参数与常量控制:
| 配置项 | 类型 | 默认值 | 说明 |
|---|---|---|---|
MAX_INTERFACE_NUM | 宏常量 | 5 | 设备栈最大接口数(interface_hander / reset_hander 数组容量) |
USB_SETUP_SIZE | 宏常量 | 512 | EP0 setup 缓冲区大小(覆盖最大控制传输) |
MAX_HOST_INTERFACE | 宏常量 | 1 | 主机设备支持的接口数上限 |
HUSB_MODE | 宏常量 | 0 | 主机模式标识 |
usb_config(usb_id) | 函数 | — | 设备模式配置入口 |
usb_release(usb_id) | 函数 | — | 设备模式资源释放 |
usb_host_config(usb_id) | 函数 | — | 主机模式配置入口 |
usb_host_free(usb_id) | 函数 | — | 主机模式资源释放 |
usb_h_isr_reg(id, prio, cpu) | 函数 | — | 主机中断注册(优先级 + CPU 核绑定) |
usb_g_isr_reg(id, prio, cpu) | 函数 | — | 设备中断注册(优先级 + CPU 核绑定) |
usb_sof_isr_reg(id, prio, cpu) | 函数 | — | SOF 中断注册 |
usb_host_mount(id, retry, reset_delay, timeout) | 函数 | 由调用方决定 | 挂载重试次数、复位延时与超时 |
usb_host_remount(id, retry, delay, ot, notify) | 函数 | 由调用方决定 | 重挂载参数与通知开关 |
usb_g_bulk_read(..., block) | 函数参数 | 由调用方决定 | 设备侧批量读是否阻塞 |
usb_h_bulk_read(..., rxmaxp, ...) | 函数参数 | 由调用方决定 | 主机侧端点最大包长 |
API Reference
设备栈(usb_stack.h)
usb_dev usb_device2id(const struct usb_device_t *usb_device)
设备结构体 → 设备 ID 的反向映射(供中断/API 定位控制器)。
struct usb_device_t *usb_id2device(const usb_dev usb_id)
设备 ID → 设备结构体,中断处理中由 usb_dev 快速取回 usb_device_t。
void usb_control_transfer(struct usb_device_t *usb_device)
EP0 控制传输驱动器:根据 bsetup_phase 推进 setup/data/status 三阶段,是设备枚举的核心执行函数。
void usb_device_set_class(struct usb_device_t *usb_device, u32 class_config)
设置设备类(写入 wDeviceClass),决定设备以何种类向主机呈现。
u32 usb_g_set_intr_hander(const usb_dev usb_id, u32 ep, usb_interrupt hander)
注册指定端点的中断回调(设备侧)。hander 为 void(*)(struct usb_device_t *, u32 ep)。
u32 usb_set_interface_hander(const usb_dev usb_id, u32 itf_num, itf_hander hander)
注册接口控制请求 handler,itf_num 取值范围 0..MAX_INTERFACE_NUM-1。
void usb_add_desc_config(const usb_dev usb_id, u32 index, const desc_config desc)
按 index 顺序注册配置描述符拼接回调;多个回调依次执行,通过 cur_itf_num 传递接口编号。
u32 usb_set_reset_hander(const usb_dev usb_id, u32 itf_num, itf_reset_hander hander)
注册接口复位回调,总线复位时由 usb_reset_interface() 触发。
void usb_set_setup_hook / usb_set_setup_recv(struct usb_device_t *, void *hook/recv)
安装 setup 预处理钩子与接收分发回调(setup_hook / setup_recv 字段)。
int usb_device_mode(const usb_dev usb_id, const u32 class)
进入指定设备模式(class 选择设备类组合)。
void usb_setup_init(const usb_dev usb_id, void *ptr, u8 *setup_buffer) / u32 usb_setup_release(const usb_dev usb_id)
初始化/释放 usb_setup_t 与 EP0 setup 缓冲区(USB_SETUP_SIZE)。
u8 *usb_set_data_payload(struct usb_device_t *usb_device, struct usb_ctrlrequest *req, const void *data, u32 len)
装载 EP0 data 阶段数据:把 data/len 拷贝入 setup 缓冲区并更新传输指针,返回当前写入位置。
void usb_set_setup_phase(struct usb_device_t *usb_device, u8 setup_phase)
显式推进 EP0 setup 状态机阶段。
void usb_ep_enable(const usb_dev usb_id, u32 ep, u32 is_enable)
使能/禁用指定端点。
void usb_start() / usb_stop() / usb_pause()
全局 USB 启动/停止/暂停控制。
主机栈(usb_host.h)
int host_dev_status(const struct usb_host_device *host_dev)
查询宿主设备当前状态(来自 private_data.status)。
const struct usb_host_device *host_id2device(const usb_dev id)
主机设备 ID → usb_host_device 结构体映射。
u32 usb_host_mount(const usb_dev usb_id, u32 retry, u32 reset_delay, u32 mount_timeout)
挂载 USB 从设备。返回: 0 成功,-DEV_ERR_OFFLINE 设备离线,其它负值错误。retry 控制重试次数,reset_delay 为复位间隔,mount_timeout 为挂载超时。
u32 usb_host_unmount(const usb_dev usb_id)
卸载并从总线上摘除设备。
u32 usb_host_remount(const usb_dev usb_id, u32 retry, u32 delay, u32 ot, u8 notify)
带参数的重挂载;ot 为超时,notify 控制是否通知上层。
void usb_host_suspend(const usb_dev usb_id) / void usb_host_resume(const usb_dev usb_id)
总线挂起/恢复(配合 usb_h_entry_suspend / usb_h_resume PHY 层实现)。
void usb_h_set_ep_isr(struct usb_host_device *host_dev, u32 ep, usb_h_interrupt hander, void *p) / u32 usb_h_set_intr_hander(const usb_dev usb_id, u32 ep, usb_h_interrupt hander)
注册主机端点中断回调,usb_h_set_ep_isr 额外携带私有参数 p。
PHY 层(usb_phy.h)
usb_g_bulk_read/write、usb_g_intr_read/write、usb_g_iso_read/write:设备侧批量/中断/同步端点传输,read 变体带block阻塞标志;usb_g_bulk_read64byte_fast:64 字节快速批量读,面向高吞吐热路径;usb_h_bulk_read/write、usb_h_intr_read/write、usb_h_iso_read/write:主机侧对应传输,需指定本地/目标端点与最大包长;usb_host_init(usb_id, reset_delay, timeout)/usb_host_reset(usb_id, reset_delay, timeout)/usb_h_force_reset(usb_id):主机初始化与总线复位;usb_read_sofframe(id)、usb_read_dp_se(id)、usb_read_dm_se(id):读取 SOF 帧号与 DP/DM 线状态。
故障模式、边界情况与并发
设备离线与挂载失败
主机模式下最常见的故障是设备在传输中途被拔出。SDK 的约定处理是 check_usb_mount 宏:返回 -DEV_ERR_OFFLINE 时记录日志并 goto __exit_fail 走清理路径;其它错误记录日志后 continue 继续重试。因此应用层挂载循环必须预留 __exit_fail 标签并保证循环结构匹配,否则宏展开会编译失败。usb_host_remount() 的 notify 参数允许在重挂载成功后通知上层刷新状态(如重新打开文件系统)。
EP0 短包与 0 包
控制传输 data 阶段要求"数据长度恰好是最大包长整数倍时补发 0 长度包"以标识传输结束,设备栈用 bDataOverFlag 字段跟踪。usb_set_data_payload() 负责把待发送数据写入 setup 缓冲区,若应用手工推进 bsetup_phase(usb_set_setup_phase())而非使用栈内建流程,必须自行处理短包规则,否则主机可能挂起等待。
中断并发与信号量同步
- 主机栈的
OS_SEM信号量由 ISR 与任务共享:usb_sem_post通常在中断上下文调用(传输完成),usb_sem_pend(timeout)在任务上下文等待。timeout必须合理设置,避免挂载超时/传输超时时任务永久阻塞。 usb_h_set_ep_isr携带私有指针p,回调在中断上下文执行,禁止在回调内做耗时操作(如日志、内存分配)。block参数区分阻塞/非阻塞传输:非阻塞模式下返回值语义为"是否已提交",上层需要配合中断回调与信号量判断完成。
多核中断绑定
AD23N 为多核架构,usb_g_isr_reg / usb_h_isr_reg / usb_sof_isr_reg 均要求 cpu_id 参数。若中断绑定核与等待信号量的任务所在核不一致,需确保 OS 信号量支持跨核唤醒;否则应把 USB 中断与处理任务绑定到同一核以减少跨核开销与竞态。
模式切换竞态
设备/主机模式切换必须遵循"先 release 后 config"顺序:usb_release() 释放设备栈资源后再 usb_host_config() 进入主机模式(或反之)。切换期间应暂停相关任务,避免在 setup 状态机或挂载流程进行中被 usb_stop()/usb_pause() 打断产生悬挂状态。
性能与运维注意事项
- DMA 缓冲区对齐与归属:端点传输的数据指针应取自
usb_get_ep_buffer()/usb_h_get_ep_buffer(),这些缓冲区位于 DMA 可访问内存且按端点对齐;直接使用栈/堆随机地址可能触发复制路径或传输失败。 - 64 字节快速路径:
usb_g_bulk_read64byte_fast()是面向 64 字节包的热路径优化,音频/小包场景应优先使用;大块数据传输使用usb_g_bulk_read(..., block)。 - 超时参数:
usb_host_mount的reset_delay/mount_timeout、usb_host_timeout()计算应匹配总线拓扑与设备响应能力,U 盘类设备枚举通常需要数百 ms 级超时。 - 中断优先级:USB 中断优先级应高于易失数据的任务,低于时钟/异常关键中断;多核绑定时选择负载较低且与 USB 消费任务同核的 CPU。
扩展点
USB 子系统的扩展完全基于回调与注册机制,无需修改协议栈内核:
| 扩展点 | 接口 | 用途 |
|---|---|---|
| 设备类接口 | usb_set_interface_hander(usb_id, itf_num, itf_hander) | 新增/替换接口的控制请求处理 |
| 描述符拼接 | usb_add_desc_config(usb_id, index, desc_config) | 自定义设备/配置/接口/端点描述符内容 |
| 接口复位 | usb_set_reset_hander(usb_id, itf_num, itf_reset_hander) | 总线复位时重置接口状态 |
| 端点中断 | usb_g_set_intr_hander / usb_h_set_intr_hander | 端点事件通知 |
| setup 过滤 | user_setup_filter_install(usb_device) | 拦截/修改标准请求处理 |
| 主机设备类驱动 | struct interface_ctrl + usb_interface_info.dev 联合体 | 扩展 mass_storage/adb/hid/aoa 之外的新设备类 |
| 设备类实例 | usb_device_set_class() / usb_device_mode() | 组合启用多个设备类 |
新增主机设备类的步骤(源自 usb_host.h 的结构设计):在 usb_interface_info.dev 联合体增加驱动实例指针 → 实现 interface_ctrl 的 set_power/get_power/ioctl → 挂载流程中根据 interface_class 绑定并初始化驱动。
测试与验证要点
仓库未提供独立 USB 单元测试目录;sdk/app/src/mbox_flash/usb_slave/ 下的 usb_slave.c / usb_slave_key.c 是设备模式的应用级验证(mbox_flash 项目通过 USB 从机与主机通信,含按键触发逻辑),doc/stuff/usb updater.pdf 记录了基于 USB 的升级流程。验证建议:
- 设备模式:用 PC 的 USB 分析仪/
dmesg观察枚举过程,重点检查GET_DESCRIPTOR返回与SET_CONFIGURATION后的端点收发; - 主机模式:热插拔 U 盘验证
check_usb_mount离线分支与usb_host_remount重挂载路径; - 低功耗:验证
usb_host_suspend/usb_host_resume与 SOF 中断(usb_sof_isr_reg)配合下的总线挂起恢复。