Linux: USB Gadget 驱动简介

文章目录

  • 1. 前言
  • 2. 背景
  • 3. USB Gadget 驱动
    • 3.1 什么是 USB Gadget 驱动?
    • 3.2 USB Gadget 驱动框架
    • 3.3 USB 设备控制器(UDC) 驱动
      • 3.3.1 USB 设备控制器(UDC) 驱动 概述
      • 3.3.2 USB 设备控制器(UDC) 驱动示例
    • 3.4 USB Gadget Function 驱动
    • 3.5 USB Gadget 驱动
      • 3.5.1 USB Gadget 驱动的加载
        • 3.5.1.1 启动 UDC: 上拉 D+
        • 3.5.1.2 设备枚举
      • 3.5.2 USB Gadget 驱动数据交流过程
      • 3.5.3 USB Gadget 驱动的卸载

1. 前言

限于作者能力水平,本文可能存在谬误,因此而给读者带来的损失,作者不做任何承诺。

2. 背景

本文所有分析基于 Linux 4.14 内核代码。

3. USB Gadget 驱动

3.1 什么是 USB Gadget 驱动?

USB 设备驱动,按照设备端关联的 USB 控制器 是工作在 主模式 还是 从模式,分为 USB 设备主机侧驱动 (主模式),或者 USB 设备从机侧驱动 (从模式)。同时,工作在 主模式USB 控制器,称为 USB 主机控制器 (UHC: USB Host Controller),工作在 从模式USB 控制器,称为 USB 设备控制器 (UDC: USB Device Controller)。有的 USB 控制器,只能工作在 主模式从模式 中的某一种;而有的则既可以工作在 主模式,也可以工作在 从模式,模式通过 OTG 切换。当然,在同一时刻,USB 控制器 要么工作在 主模式,要么工作在 从模式
本文的重点是 USB 设备从机侧驱动 (从模式),Linux 下将 USB 设备从机侧驱动 ,称为 USB Gadget 驱动USB Gadget 驱动 是通过 USB 来模拟其它类型的设备,如 USB Gadget UAC 驱动 用来模拟声卡外设;USB Gadget Serial 驱动 用来模拟串口外设,等等等等。这里所谓模拟,是指通过 USB 来模拟这些设备的行为,而这些对于连接对端的 USB 主机 是透明的。对于 USB Gadget 驱动 ,类似于譬如像 U 盘设备的固件,但它们并不完全等同,因为毕竟只是通过 USB 模拟设备行为。

3.2 USB Gadget 驱动框架

正如本小节中框图所示,USB Gadget 驱动,包括 USB 设备控制器(UDC) 驱动Gadget 功能(function)驱动 两大部分。其中 USB 设备控制器(UDC) 驱动 负责 USB 设备控制器(UDC) 和 主机侧 USB 控制器(UHC) 之间的数据传输;而 Gadget 功能驱动(function) 负责实现功能协议(如 UDC 等)。USB 设备控制器(UDC) 驱动Gadget 功能驱动(function) 彼此之间也会进行数据交互。
在进一步对 USB Gadget 驱动 做更细节的描述前,我们通过下图,先让大家对 USB Gadget 驱动框架 做一个初步认识(图片来自于网络,具体出处已不可考):
在这里插入图片描述实际上本文重点只涉及上图中右侧红框中的部分,之所有列出左边部分,是想让大家对整个 USB 驱动框架有一个整体认识。左边的部分,不仅限于 Linux,可以是任何支持 USB 主机侧 系统,如 Windows, Mac OS 等其它系统。上图中我们列出的是 Linux 系统,Linux 系统本身既包含 USB 设备主机侧驱动,又包含 USB 设备从机侧驱动(即 USB Gadget 驱动),所以 Linux 自身就形成一个完整的 USB 驱动框架闭环。
接下来内容,我们将按上图中右半部分所示,逐个来讲述框架中的每个部分。

3.3 USB 设备控制器(UDC) 驱动

3.3.1 USB 设备控制器(UDC) 驱动 概述

所有的 USB Gadget 驱动,最终都是通过 USB 设备控制器(UDC) ,和 主机侧的 USB 控制器(UHC) 进行交互;而且 UDC 是独占的,一旦它被某个 USB Gadget 驱动 使用,直到该 USB Gadget 驱动 被卸载之前,其它的 USB Gadget 驱动 就不能使用它。
先来了解下 UDC 的硬件实现梗概 和 驱动代码组织。我们以常见的 双角色(既支持 主模式,又支持 从模式)USB 控制器 为例,来了解 UDC 的硬件实现。绝大多数芯片厂商,不会自己实现 USB 控制器的所有部分,而是购买 USB 控制器 IP 库,然后再以某个 IP 库为基础来实现 UDC 硬件。如 全志 H3 用 明导国际(Mentor Graphics)MUSB 实现 UDC ,它的 硬件实现 和 驱动代码组织 梗概如下:

      硬件           |         驱动-----------------|---------------------------------------------------UDC 的 厂商实现 | drivers/usb/musb/sunxi.c (UDC 厂商驱动部分)|        |           |V        |           VMUSB IP 库   | drivers/usb/musb/musb* (UDC MUSB IP 库驱动部分)|           ||           V| drivers/usb/gadget/udc/core.c (UDC core 公共代码)

上面举例的是 全志 H3 对 MUSB 的实现,其它各个厂商对各类 USB IP 的实现类似。如 DesignWareDWC3 也是经常被芯片厂商用来实现双角色 USB 控制器的 USB IP 库。DWC3 双角色 USB IP 的驱动实现于代码文件 drivers/usb/dwc3/dwc3* 中,而 UDC 驱动的其它部分类似于 全志 H3 的实现。对于单单支持 USB 设备控制器(UDC) 角色(从模式) 的驱动,代码实现于目录 drivers/usb/gadget/udc 下。

3.3.2 USB 设备控制器(UDC) 驱动示例

接下来,我们以 全志 H3 以 MUSB IP 库为基础,实现的 USB 设备控制器 驱动为例,来进行 UDC 驱动 的说明。先看下 MUSB 的 DTS 配置:

usb_otg: usb@01c19000 {compatible = "allwinner,sun8i-h3-musb";...phys = <&usbphy 0>; // 关联的 USB PHYphy-names = "usb";extcon = <&usbphy 0>;dr_mode = "otg"; // 设备模式配置为 OTG:即可以动态的切换 UHC 和 UDC 角色status = "okay";
};usbphy: phy@01c19400 {compatible = "allwinner,sun8i-h3-usb-phy";...// PG12 用于 OTG 角色检测usb0_id_det-gpios = <&pio 6 12 GPIO_ACTIVE_HIGH>; /* PG12 */usb0_vbus-supply = <&reg_usb0_vbus>;status = "okay";
};

DTS 配置的 usb_otg 指代 H3 实现的 双角色 USB 控制器usbphy 指代 USB 控制器使用的 USB PHY 设备。先看 USB 控制器的驱动:

/* drivers/usb/musb/sunxi.c */
// MUSB 全志厂商驱动入口
static int sunxi_musb_probe(struct platform_device *pdev)
{struct musb_hdrc_platform_data pdata;struct platform_device_info pinfo;struct sunxi_glue  *glue;...glue = devm_kzalloc(&pdev->dev, sizeof(*glue), GFP_KERNEL);switch (usb_get_dr_mode(&pdev->dev)) { // 获取 DTS 配置的模式...
#ifdef CONFIG_USB_MUSB_DUAL_ROLEcase USB_DR_MODE_OTG: // DTS 配置为 "otg" 模式,即双角色模式pdata.mode = MUSB_PORT_MODE_DUAL_ROLE;glue->phy_mode = PHY_MODE_USB_OTG;break;
#endif...}// 平台特定的接口: 初始化,寄存器读写,DMA, 模式设置, VBUS控制, ...pdata.platform_ops = &sunxi_musb_ops;if (!of_device_is_compatible(np, "allwinner,sun8i-h3-musb"))// 厂商硬件实现的端点(EndPoint)配置数据: 端点数目、方向(IN,OUT,IN&OUT), FIFO 缓冲深度 等pdata.config = &sunxi_musb_hdrc_config;else......memset(&pinfo, 0, sizeof(pinfo));pinfo.name  = "musb-hdrc"; // MUSB IP 驱动匹配关键字...// 加载 MUSB IP 驱动: 厂商驱动 -> MUSB IP 驱动glue->musb_pdev = platform_device_register_full(&pinfo);...
}/* drivers/usb/musb/musb_core.c */
// MUSB IP 驱动入口,共实现厂商调用
static int musb_probe(struct platform_device *pdev)
{...return musb_init_controller(dev, irq, base);
}// 初始化 MUSB。这里只重点关注 从设模式 相关的初始化。
static int
musb_init_controller(struct device *dev, int nIrq, void __iomem *ctrl)
{struct musb  *musb;musb = allocate_instance(dev, plat->config, ctrl);...musb->ops = plat->platform_ops; // 设置 MUSB 的厂商接口 (&sunxi_musb_ops)musb->port_mode = plat->mode;...status = musb_platform_init(musb); // 厂商特定初始化: sunxi_musb_init()musb->ops->init(musb) = sunxi_musb_init()...musb->isr = sunxi_musb_interrupt; // 厂商中断入口....../* setup musb parts of the core (especially endpoints) */// MUSB IP 核心初始化,特别是 USB 端点(EndPoint)数据初始化status = musb_core_init(plat->config->multipoint? MUSB_CONTROLLER_MHDRC: MUSB_CONTROLLER_HDRC, musb);...// 注册厂商初始化设定的中断处理接口 sunxi_musb_interrupt()// sunxi_musb_interrupt() -> musb_interrupt()// 厂商中断处理接口最终会调用 MUSB IP 的中断接口 musb_interrupt()if (request_irq(nIrq, musb->isr, IRQF_SHARED, dev_name(dev), musb)) {...}...// 按配置的模式, 初始化 MUSB IP 。// 再一次的,我们只关注 从设模式的相关初始化。switch (musb->port_mode) {...case MUSB_PORT_MODE_DUAL_ROLE: // 双角色模式, 我们只关注 从设模式 相关部分...// MUSB 从设模式 初始化.// 其中最重要的是会注册 UDC 对象到系统,其将和 USB Gadget Function 驱动组合到一起,// 形成一个完整的 USB Gadget 驱动.status = musb_gadget_setup(musb);...status = musb_platform_set_mode(musb, MUSB_OTG);break;}...
}// MUSB IP 核心初始化,特别是 USB 端点(EndPoint)数据初始化
static int musb_core_init(u16 musb_type, struct musb *musb)
{.../* configure ep0 */musb_configure_ep0(musb);/* discover endpoint configuration */musb->nr_endpoints = 1;musb->epmask = 1;// 端点数目、数据搜集 + 端点初始化(FIFO 配置等)if (musb->dyn_fifo)status = ep_config_from_table(musb);elsestatus = ep_config_from_hw(musb);...
}/* drivers/usb/musb/musb_gadget.c */
// MUSB 从设模式 初始化.
int musb_gadget_setup(struct musb *musb)
{...musb->g.ops = &musb_gadget_operations; // MUSB 从设模式 操作接口musb->g.max_speed = USB_SPEED_HIGH;musb->g.speed = USB_SPEED_UNKNOWN;musb->g.name = musb_driver_name; // "musb-hdrc"// MUSB 端点(EndPoint) 数据初始化,【包括】控制端点(EP0)musb_g_init_endpoints(musb);// 注册 UDC 设备对象 到系统status = usb_add_gadget_udc(musb->controller, &musb->g);...
}// 所有 MUSB 端点(EndPoint) 数据初始化,【包括】控制端点(EP0)
static inline void musb_g_init_endpoints(struct musb *musb)
{.../* initialize endpoint list just once */INIT_LIST_HEAD(&(musb->g.ep_list));for (epnum = 0, hw_ep = musb->endpoints;epnum < musb->nr_endpoints;epnum++, hw_ep++) {if (hw_ep->is_shared_fifo /* || !epnum */) { // 输入输出共享 FIFO,端点同时支持【输入&输出(IN & OUT)】,典型的如 EP0 端点init_peripheral_ep(musb, &hw_ep->ep_in, epnum, 0);count++;} else { // 只支持一个方向(IN 或 OUT)的端点if (hw_ep->max_packet_sz_tx) { // 只支持【输出】的端点: OUTinit_peripheral_ep(musb, &hw_ep->ep_in,epnum, 1);count++;}if (hw_ep->max_packet_sz_rx) { // 只支持【输入】的端点: INinit_peripheral_ep(musb, &hw_ep->ep_out,epnum, 0);count++;}}}
}// 初始化一个端点
static void
init_peripheral_ep(struct musb *musb, struct musb_ep *ep, u8 epnum, int is_in)
{...ep->current_epnum = epnum; // 端点编号...ep->is_in = is_in; // 标记端点方向:是输入(IN)还是出输出(OUT)INIT_LIST_HEAD(&ep->req_list); // 初始端点上数据请求包(usb_request)列表...// 配置端点接口和能力if (!epnum) { // 控制端点 EP0usb_ep_set_maxpacket_limit(&ep->end_point, 64);ep->end_point.caps.type_control = true;ep->end_point.ops = &musb_g_ep0_ops; // 控制端点 EP0 操作接口musb->g.ep0 = &ep->end_point;}  else {if (is_in)usb_ep_set_maxpacket_limit(&ep->end_point, hw_ep->max_packet_sz_tx);elseusb_ep_set_maxpacket_limit(&ep->end_point, hw_ep->max_packet_sz_rx);ep->end_point.caps.type_iso = true;ep->end_point.caps.type_bulk = true;ep->end_point.caps.type_int = true;ep->end_point.ops = &musb_ep_ops; // 普通端点 操作接口list_add_tail(&ep->end_point.ep_list, &musb->g.ep_list);}// 修正端点方向设置if (!epnum || hw_ep->is_shared_fifo) {ep->end_point.caps.dir_in = true;ep->end_point.caps.dir_out = true;} else if (is_in)ep->end_point.caps.dir_in = true;elseep->end_point.caps.dir_out = true;
}

最后来看 UDC 设备对象的注册过程:

/* drivers/usb/gadget/udc/core.c */
int usb_add_gadget_udc(struct device *parent, struct usb_gadget *gadget)
{return usb_add_gadget_udc_release(parent, gadget, NULL);
}int usb_add_gadget_udc_release(struct device *parent, struct usb_gadget *gadget,void (*release)(struct device *dev))
{struct usb_udc  *udc;...udc = kzalloc(sizeof(*udc), GFP_KERNEL); // 分配一个 UDC 设备对象...list_add_tail(&udc->list, &udc_list); // 添加 UDC 设备对象到全局列表...usb_gadget_set_state(gadget, USB_STATE_NOTATTACHED);udc->vbus = true;/* pick up one of pending gadget drivers */// 可能发生的、将 UDC 绑定到某个 USB Gadget Function 驱动。// 这不是通常的情形,后续我们看后一种绑定时机的细节,它们逻辑上是一样的。ret = check_pending_gadget_drivers(udc);...return 0;
}

对于 UDC 驱动 的讨论,就先到这里,等到后面涉及到 UDCUSB Gadget Function 绑定的时候,再讨论剩余的相关细节。

3.4 USB Gadget Function 驱动

单独把 USB Gadget Function 驱动 拿出来讲,没有太多意义。关于 USB Gadget Function 驱动 的细节,我们将在 3.5 小节中一起讲述。

3.5 USB Gadget 驱动

本小节将讲述如何将 UDC 驱动USB Gadget Function 驱动 组合到一起,形成一个完整的 USB Gadget 驱动
顺便提一下,USB Gadget 驱动代码组织在目录 drivers/usb/gadget 目录下:

drivers/usb/gadget/*.c, *.h: USB Gadget 驱动核心公共代码
drivers/usb/gadget/function/*.c, *.h: USB Gadget 驱动各功能(serial,UDC,...)代码
drivers/usb/gadget/legacy/*.c, *.h: USB Gadget 驱动

解下来,我们以 Gadget UAC 驱动 为例,来讲述 USB Gadget 驱动 的 记载、卸载,以及工作过程。其它功能的 USB Gadget 驱动 ,除了协议特定部分,其它部分的流程是类似的,读者可参考本文自行分析。

3.5.1 USB Gadget 驱动的加载

3.5.1.1 启动 UDC: 上拉 D+

// 我们假设将 Gadget UAC 驱动编译程模块形式,即 g_audio.ko 。
// 当以 insmod g_audio.ko 指令加载模块时,将进入驱动入口 usb_composite_probe()/* drivers/usb/gadget/legacy/audio.c */
static struct usb_composite_driver audio_driver = {.name  = "g_audio",.dev  = &device_desc,.strings = audio_strings,.max_speed = USB_SPEED_HIGH,.bind  = audio_bind,.unbind  = audio_unbind,
};/** include/linux/usb/composite.h** #define module_usb_composite_driver(__usb_composite_driver) \* 	module_driver(__usb_composite_driver, usb_composite_probe, \*        		usb_composite_unregister)*/
// 定义 USB Gadget 驱动 
module_usb_composite_driver(audio_driver);

在进一步讨论前,先看看 module_usb_composite_driver()usb_composite_driver 的定义:

#define module_driver(__driver, __register, __unregister, ...) \
static int __init __driver##_init(void) \
{ \return __register(&(__driver) , ##__VA_ARGS__); \
} \
module_init(__driver##_init); \
static void __exit __driver##_exit(void) \
{ \__unregister(&(__driver) , ##__VA_ARGS__); \
} \
module_exit(__driver##_exit);#define module_usb_composite_driver(__usb_composite_driver) \module_driver(__usb_composite_driver, usb_composite_probe, \usb_composite_unregister)

可以看到,module_usb_composite_driver() 定义了两个函数。具体到我们的 UAC 例子,就是定义了函数 audio_driver_init()audio_driver_exit()

static int __init audio_driver_init(void)
{return usb_composite_probe(&audio_driver);
}
module_init(audio_driver_init);static void __exit audio_driver_exit(void)
{usb_composite_unregister(&audio_driver);
}
module_exit(audio_driver_exit);

再看下 usb_composite_driver 的定义:

struct usb_composite_driver {const char    *name;const struct usb_device_descriptor *dev;struct usb_gadget_strings  **strings;enum usb_device_speed   max_speed;unsigned  needs_serial:1;int   (*bind)(struct usb_composite_dev *cdev); // audio_bind()int   (*unbind)(struct usb_composite_dev *); // audio_unbind()void   (*disconnect)(struct usb_composite_dev *);/* global suspend hooks */void   (*suspend)(struct usb_composite_dev *);void   (*resume)(struct usb_composite_dev *);struct usb_gadget_driver  gadget_driver;
};struct usb_gadget_driver {char   *function;enum usb_device_speed max_speed;int   (*bind)(struct usb_gadget *gadget,struct usb_gadget_driver *driver); // composite_bind()void   (*unbind)(struct usb_gadget *); // composite_unbind()int   (*setup)(struct usb_gadget *,const struct usb_ctrlrequest *); // composite_setup()void   (*disconnect)(struct usb_gadget *);void   (*suspend)(struct usb_gadget *);void   (*resume)(struct usb_gadget *);void   (*reset)(struct usb_gadget *);struct device_driver driver;// 可以显式指定 USB Gadget 驱动关联的 UDC。// 但绝大多数情形都会将 udc_name 设为 NULL, 这指示直接使用系统中能找到的 UDC,// 因为通常系统中不会有超过一个 UDC 存在。char   *udc_name;...
};

好,继续看 Gadget UAC 驱动的加载过程:

/* drivers/usb/gadget/composite.c */
int usb_composite_probe(struct usb_composite_driver *driver)
{struct usb_gadget_driver *gadget_driver;driver->gadget_driver = composite_driver_template;gadget_driver = &driver->gadget_driver;...return usb_gadget_probe_driver(gadget_driver); // 绑定 UDC
}/* drivers/usb/gadget/udc/core.c */
int usb_gadget_probe_driver(struct usb_gadget_driver *driver)
{...mutex_lock(&udc_lock);if (driver->udc_name) {...} else {list_for_each_entry(udc, &udc_list, list) {/* For now we take the first one */if (!udc->driver)goto found;}}...found:ret = udc_bind_to_driver(udc, driver); // 绑定 UDC 和 usb_gadget_drivermutex_unlock(&udc_lock);return ret;
}static int udc_bind_to_driver(struct usb_udc *udc, struct usb_gadget_driver *driver)
{...// 绑定 UDC 和 usb_gadget_driverudc->driver = driver;udc->dev.driver = &driver->driver;udc->gadget->dev.driver = &driver->driver;// 间接触发 UAC 驱动的 bind:// composite_bind() -> audio_bind()ret = driver->bind(udc->gadget, driver);...status = composite->bind(cdev) = audio_bind()......ret = usb_gadget_udc_start(udc); /* 启动 UDC: 上电 */...usb_udc_connect_control(udc); /* D+ 上拉: 触发枚举过程 */...return 0;
}

重点看一下 audio_bind(),看它是如何将 Gadget Fuction(UAC 1.0 Function) 绑定上来的:

/* drivers/usb/gadget/legacy/audio.c */
static struct usb_function_instance *fi_uac1;
static struct usb_function *f_uac1static int audio_bind(struct usb_composite_dev *cdev)
{struct f_uac1_legacy_opts *uac1_opts;fi_uac1 = usb_get_function_instance("uac1");try_get_usb_function_instance(name)...list_for_each_entry(fd, &func_list, list) {if (strcmp(name, fd->name)) // 按名字查找 function drivercontinue;...fi = fd->alloc_inst(); // f_audio_alloc_inst()if (IS_ERR(fi))module_put(fd->mod);elsefi->fd = fd;break;}...return fi;...status = usb_add_config(cdev, &audio_config_driver, audio_do_config);...status = usb_add_config_only(cdev, config); // 添加配置 @config...status = bind(config); // function 绑定:audio_do_config(), .........
}static int audio_do_config(struct usb_configuration *c)
{...f_uac1 = usb_get_function(fi_uac1); // 获取 functionf = fi->fd->alloc_func(fi); // f_audio_alloc()struct f_uac1 *uac1;...uac1 = kzalloc(sizeof(*uac1), GFP_KERNEL); // 分配 function 对象...// 设置 function 接口uac1->g_audio.func.name = "uac1_func";uac1->g_audio.func.bind = f_audio_bind;uac1->g_audio.func.unbind = f_audio_unbind;uac1->g_audio.func.set_alt = f_audio_set_alt;uac1->g_audio.func.get_alt = f_audio_get_alt;uac1->g_audio.func.setup = f_audio_setup; // 枚举阶段处理各种 UAC 协议特定的 setup 包uac1->g_audio.func.disable = f_audio_disable;uac1->g_audio.func.free_func = f_audio_free;return &uac1->g_audio.func;...f->fi = fi;return f;...status = usb_add_function(c, f_uac1); // 绑定 function...function->config = config;list_add_tail(&function->list, &config->functions);...if (function->bind) {// function 的初始化: 各类 USB 描述符设置 等等value = function->bind(config, function); // f_audio_bind(), ......}...
}

补充说明下,usb_get_function_instance() 查找的 Gadget Function 列表 func_list 是怎么构建的:

/* drivers/usb/gadget/function/f_uac1.c */
DECLARE_USB_FUNCTION_INIT(uac1, f_audio_alloc_inst, f_audio_alloc);/* drivers/usb/gadget/function/f_serial.c */
DECLARE_USB_FUNCTION_INIT(gser, gser_alloc_inst, gser_alloc);// 更多 drivers/usb/gadget/function/f_*.c 注册的 function
#define DECLARE_USB_FUNCTION_INIT(_name, _inst_alloc, _func_alloc) \DECLARE_USB_FUNCTION(_name, _inst_alloc, _func_alloc)  \static int __init _name ## mod_init(void)   \{        \return usb_function_register(&_name ## usb_func); \}        \static void __exit _name ## mod_exit(void)   \{        \usb_function_unregister(&_name ## usb_func);  \}        \module_init(_name ## mod_init);     \module_exit(_name ## mod_exit)usb_function_register()...list_add_tail(&newf->list, &func_list);

到此,已经完成了 UDC 和 Function 的绑定 和 初始化过程。此时,UDC 已经触发了枚举过程,接下来看枚举期间发生了什么。

3.5.1.2 设备枚举

USB 的通信,总是从主机一侧开始。当 USB 设备控制器收到 主机发过来的数据包 (UHC -> UDC),UDC 就是产生一个中断,所以我们的分析,从中断接口 sunxi_musb_interrupt() 开始:

/* drivers/usb/musb/sunxi.c */
static irqreturn_t sunxi_musb_interrupt(int irq, void *__hci)
{// 厂商特定的中断处理musb->int_usb = readb(musb->mregs + SUNXI_MUSB_INTRUSB);if (musb->int_usb)writeb(musb->int_usb, musb->mregs + SUNXI_MUSB_INTRUSB);...musb->int_tx = readw(musb->mregs + SUNXI_MUSB_INTRTX); // 读各端点 TX 中断状态if (musb->int_tx)writew(musb->int_tx, musb->mregs + SUNXI_MUSB_INTRTX);musb->int_rx = readw(musb->mregs + SUNXI_MUSB_INTRRX); // 读各端点 RX 中断状态if (musb->int_rx)writew(musb->int_rx, musb->mregs + SUNXI_MUSB_INTRRX);// MUSB IP 公共中断处理musb_interrupt(musb);...return IRQ_HANDLED;
}irqreturn_t musb_interrupt(struct musb *musb)
{...if (musb->int_usb)retval |= musb_stage0_irq(musb, musb->int_usb, devctl);// EP0 中断处理if (musb->int_tx & 1) {if (is_host_active(musb))retval |= musb_h_ep0_irq(musb);elseretval |= musb_g_ep0_irq(musb);/* we have just handled endpoint 0 IRQ, clear it */musb->int_tx &= ~BIT(0); // EP0 的事件处理了,清除 EP0 的位码}// 普通端点 TX 中断处理status = musb->int_tx;for_each_set_bit(epnum, &status, 16) {retval = IRQ_HANDLED;if (is_host_active(musb)) // 主机模式musb_host_tx(musb, epnum);else // 从设模式musb_g_tx(musb, epnum);}// 普通端点 TX 中断处理status = musb->int_rx;for_each_set_bit(epnum, &status, 16) {retval = IRQ_HANDLED;if (is_host_active(musb))musb_host_rx(musb, epnum);elsemusb_g_rx(musb, epnum);}return retval;
}

中段涉及的数据不光包含枚举或断开连接这些期间的 setup 包,还有其它数据的 RX, TX 传输。本小节讲述的枚举,所以只描述枚举相关的通信做简要说明:

sunxi_musb_interrupt()musb_interrupt()musb_g_ep0_irq()switch (musb->ep0_state) {...case MUSB_EP0_STAGE_SETUP:if (csr & MUSB_CSR0_RXPKTRDY) { /* 收包就绪 */struct usb_ctrlrequest setup;...musb_read_setup(musb, &setup); /* 从 FIFO 读取 setup 包的内容 */...handled = forward_to_driver(musb, &setup);musb->gadget_driver->setup(&musb->g, ctrlrequest) = composite_setup()...}...}/* drivers/usb/gadget/composite.c */
int
composite_setup(struct usb_gadget *gadget, const struct usb_ctrlrequest *ctrl)
{...// 非特定于 Gadget Function 的 setup 包处理switch (ctrl->bRequest) {case USB_REQ_GET_DESCRIPTOR: // 获取 USB 设备描述符...break;case USB_REQ_SET_CONFIGURATION: // 设置 USB 设备配置描述符...value = set_config(cdev, ctrl, w_value);...break;case USB_REQ_GET_CONFIGURATION: // 获取 USB 设备配置描述符...break;case USB_REQ_SET_INTERFACE:...value = f->set_alt(f, w_index, w_value); // f_audio_set_alt(), ......break;case USB_REQ_GET_INTERFACE:...value = f->get_alt ? f->get_alt(f, w_index) : 0; // f_audio_get_alt(), ......break;...default: // 特定于 Gadget Function 的 setup 包处理
unknown:...
try_fun_setup:if (f && f->setup)// 进行 Gadget Function 特定的 setup 包处理value = f->setup(f, ctrl); // f_audio_setup()else...goto done; // 特定于 Gadget Function 的 setup 包处理完毕,直接跳到函数末尾}/* respond with data transfer before status phase? */// 非特定于 Gadget Function 的 setup 包处理 回馈if (value >= 0 && value != USB_GADGET_DELAYED_STATUS) {req->length = value;req->context = cdev;req->zero = value < w_length;value = composite_ep0_queue(cdev, req, GFP_ATOMIC);...}done:/* device either stalls (value < 0) or reports success */return value;
}

正确完成上述枚举过程,主机端接下来就可以和从设进行数据通信了。

3.5.2 USB Gadget 驱动数据交流过程

以 UAC 播放过程为例,来说明 USB Gadget 驱动 和 主机侧的 数据交流过程。在 Windows 开启播放器程序,它首先会发 SET_INTERFACE 给从设,以告知 USB Gadget 驱动做播放前的准备工作,以及启动对应的 USB 端点进行播放数据传输:

sunxi_musb_interrupt()...composite_setup()...f_audio_set_alt()...if (intf == uac1->as_out_intf) { // 准备处理播放数据uac1->as_out_alt = alt;if (alt)ret = u_audio_start_capture(&uac1->g_audio);...usb_ep_enable(ep); // 激活 USB 端点for (i = 0; i < params->req_number; i++) {// 分配 USB 数据包处理对象req = usb_ep_alloc_request(ep, GFP_ATOMIC);...req->complete = u_audio_iso_complete;...// 将 USB 数据包处理对象加入到 USB 端点if (usb_ep_queue(ep, prm->ureq[i].req, GFP_ATOMIC))...}else...}

接下来是收取播放数据包过程:

sunxi_musb_interrupt()...musb_g_giveback()...list_del(&req->list); // 含有数据的 usb_request 出 端点数据包队列...usb_gadget_giveback_request(&req->ep->end_point, &req->request)...req->complete(ep, req); // u_audio_iso_complete(), ...u_audio_iso_complete()...if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {...}  else { // 拷贝 主机侧 发送的 播放数据 到 UAC 声卡 DMA 缓冲if (unlikely(pending < req->actual)) {memcpy(runtime->dma_area + hw_ptr, req->buf, pending);memcpy(runtime->dma_area, req->buf + pending, req->actual - pending);} else {memcpy(runtime->dma_area + hw_ptr, req->buf, req->actual);}...// usb_request 重新加入端点队列,以处理后续数据if (usb_ep_queue(ep, req, GFP_ATOMIC))...}

3.5.3 USB Gadget 驱动的卸载

仍然以 Gadget UAC 驱动的卸载为例,说明 USB Gadget 驱动 的卸载过程:

// 通过 rmmod g_audio 卸载模块
sys_delete_module()audio_driver_exit()usb_composite_unregister()usb_gadget_unregister_driver()usb_gadget_remove_driver()composite_unbind()__composite_unbind()remove_config()usb_remove_function()f_audio_unbind()

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/28978.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Vector - CANoe - VCDL与SomeIP

目录 一、基础介绍 二、vCDL介绍 1、vCDL工程创建 2、 vCDL编辑器关键字介绍 3、创建命名空间Datatype 接口示例 4、创建命名空间ICalculate 5、创建命名空间Participants 一、基础介绍 SomeIP作为车载以太网一个重要的组成部分&#xff0c;因为它的测试也是我们作为总…

Socket--七层协议与Socket原生框架

Socket 一、网络七层协议 / OSI 七层模型 2.1 物理层 解决两个硬件之间怎么通信的问题,常见的物理媒介有光纤、电缆、中继器等。它主要定义物理设备标准,如网线的接口类型、光纤的接口类型、各种传输介质的传输速率等。 它的主要作用是传输比特流(就是由1、0转化为电流强…

C#中的HashTable和Dictionary之间的区别

面试基础&#xff1a;C#中的HashTable和Dictionary之间的区别 HashTable和Dictionary都是用于存储数据的数据结构的类型。这两个数据结构都将存储的数据保存为键值对。

视频融合平台EasyCVR级联后上级平台播放失败的问题排查与优化

EasyCVR视频融合平台基于云边端智能协同架构&#xff0c;具有强大的数据接入、处理及分发能力&#xff0c;平台可提供视频监控直播、云端录像、云存储、录像检索与回看、智能告警、平台级联、云台控制等视频能力与服务&#xff0c;可支持多协议、多类型的海量设备接入与分发。 …

TPS61021A 具有 0.5V 超低输入电压的3A升压转换器

1 特性 1 • 输入电压范围&#xff1a;0.5V至4.4V • 启动时的最小输入电压为0.9V • 可设置的输出电压范围&#xff1a;1.8V到4.0V • 效率高达91%&#xff08;VIN2.4V、VOUT3.3V 且IOUT 1.5A 时&#xff09; • 2.0MHz 开关频率 • IOUT > 1.5A&#xff0c;VOUT 3.…

JavaWeb(3)——HTML、CSS、JS 快速入门

一、JavaScript 运算符 • 赋值运算符&#xff08; &#xff09; 赋值运算符执行过程&#xff1f; 将等号右边的值赋予给左边, 要求左边必须是一个容器 出现是为了简化代码, 比如让 let age 18 &#xff0c;age 加 2 怎么写呢 let age 18age 2console.log(age)age * 2con…

Android Studio Flutter 开发配置

近来比较闲&#xff0c;就研究下Flutter 开发&#xff0c;在此记录下studio 配置过程&#xff0c;时间是2023.7.19 在 Windows 操作系统上安装和配置 Flutter 开发环境 1.首先下载 Flutter SDKhttps://storage.flutter-io.cn/flutter_infra_release/releases/stable/windows/…

STM32MP157驱动开发——设备树知识

文章目录 概述开发板中设备树文件目录结构设备树文件语法设备树文件参考实例&#xff1a;Devicetree 格式DTS 文件的格式node 的格式properties 的格式 dts 文件包含 dtsi 文件常用的属性#address-cells 、#size-cellscompatiblemodelstatusreginterruptgpio属性name( 过时了&a…

二、DDL-1.数据库操作

一、查询 1、查询所有数据库 show databases; MySQL自带的默认的数据库有四个&#xff1a; 二、创建 1、创建一个新的数据库itcast&#xff08;不区分大小写&#xff09;&#xff1a; create database itcast; 查询所有数据库&#xff1a;多了itcast 2、再创建同名的数据库…

[高通平台][WLAN] IEEE802.11mc 介绍

IEEE802.11mcWi-Fi协议(即Wi-FiRound-Trip-Time,RTT),利用此项技术及可以进行室内定位,因此为了使用此项技术,只有在硬件支持的设备上,应用才可以使用最新的RTT API以测量附近具有RTT功能的Wi-FiAP。 单面RTT :  距离是通过发送的分组和接收到的ACK之间的时间差来计算的…

【广州华锐互动】发动机零件拆装VR虚拟学习平台

随着科技的飞速发展&#xff0c;虚拟现实(VR)技术正在各行各业中发挥越来越重要的作用。在工业教育领域&#xff0c;发动机零件拆装VR虚拟学习平台为学生提供了一种全新的学习方式&#xff0c;使他们能够在安全、低成本的环境中进行实践操作&#xff0c;从而提高技能&#xff0…

数据结构--图的存储邻接表法

数据结构–图的存储邻接表法 邻接矩阵&#xff1a; 数组实现的顺序存储&#xff0c;空间复杂度高&#xff0c;不适合存储稀疏图 邻接表&#xff1a; 顺序链式存储 邻接表法&#xff08;顺序链式存储&#xff09; //边/弧 typedef struct ArcNode {int adjvex; //边/弧指向哪个…