/* AUTHOR: Pinus
* Creat on : 2018-11-5
* KERNEL : linux-4.4.145
* REFS : Linux USB驱动学习总结(二)---- USB设备驱动
chenliang0224的专栏
hub_thread
usb hub驱动
hub_probe()
Linux USB 驱动开发(三)—— 编写USB 驱动程序
*/
app:
-------------------------------------------
USB设备驱动程序 // 知道数据含义
内核 --------------------------------------
USB总线驱动程序 // 1. 识别, 2. 找到匹配的设备驱动, 3. 提供USB读写函数 (它不知道数据含义)
-------------------------------------------
USB主机控制器
UHCI OHCI EHCI
硬件 -----------
USB设备
| UHCI: intel, 低速(1.5Mbps)/全速(12Mbps)
| OHCI: microsoft 低速/全速
| EHCI: 高速(480Mbps)
USB总线驱动程序的作用
1. 识别USB设备
1.1 分配地址
1.2 并告诉USB设备(set address)
1.3 发出命令获取描述符
2. 查找并安装对应的设备驱动程序
3. 提供USB读写函数
===========================================================
前面学习了USB驱动的一些基础概念与重要的数据结构,那么究竟如何编写一个USB 驱动程序呢?编写与一个USB设备驱动程序的方法和其他总线驱动方式类似,驱动程序把驱动程序对象注册到USB子系统中,稍后再使用制造商和设备标识来判断是否安装了硬件。当然,这些制造商和设备标识需要我们编写进USB 驱动程序中。
USB 驱动程序依然遵循设备模型 —— 总线、设备、驱动。和其他总线设备驱动编写一样,所有的USB驱动程序都必须创建的主要结构体是 struct usb_driver,它们向USB 核心代码描述了USB 驱动程序。但这是个外壳,只是实现设备和总线的挂接,具体的USB 设备是什么样的,如何实现的,比如一个字符设备,我们还需填写相应的文件操作接口 ,下面我们从外到里进行剖析,学习如何搭建这样的一个USB驱动外壳框架:
这是根据韦东山教程编写的简化的鼠标驱动,将鼠标看做三个按键(左键,滚轮按键,右键)
/* 目标:usb鼠标用作按键
* 左:L
* 右:S
* 中:Enter
*/
static struct usb_device_id usbmouse_as_key_id_table [] = {
{ USB_INTERFACE_INFO(USB_INTERFACE_CLASS_HID, USB_INTERFACE_SUBCLASS_BOOT,
USB_INTERFACE_PROTOCOL_MOUSE) },
//{USB_DEVICE(0x1234,0x5678)},
{ } /* Terminating entry */
};
/* 1. 分配/设置usb_driver */
static struct usb_driver usbmouse_as_key_driver = {
.name = "usbmouse_as_key_",
.probe = usbmouse_as_key_probe,
.disconnect = usbmouse_as_key_disconnect,
.id_table = usbmouse_as_key_id_table,
};
static __init int usbmouse_as_key_init(void)
{
usb_register(&usbmouse_as_key_driver); // 注册驱动
return 0;
}
static __exit void usbmouse_as_key_exit(void)
{
usb_deregister(&usbmouse_as_key_driver);
}
module_init(usbmouse_as_key_init);
module_exit(usbmouse_as_key_exit);
MODULE_LICENSE("GPL");
接下来分析,这个驱动程序做了些什么?这些操作又有什么意义?
usb_register(&usbmouse_as_key_driver); // 注册驱动
根据经验,这就是一个注册的函数,简单追溯一下注册流程
usb_register_driver(driver, THIS_MODULE, KBUILD_MODNAME)
usb_register_driver(struct usb_driver *new_driver, struct module *owner, const char *mod_name)
|
driver_register(&new_driver->drvwrap.driver);
usb_create_newid_files(new_driver)
driver_register()这是让人熟悉的函数啊,在分析platform_driver_register(drv) 时也调用了这个函数,看来是内核的通用总线驱动函数啊,其操作主要是根据bus type将new drv添加进相应总线驱动链表,并进行一次driver_attach,尝试与具体设备连接(usb采用id table的方式进行匹配),如果成功挂接则调用new drv->probe。具体可以参考(3.7)一个按键所能涉及的:设备驱动分层分离的概念(platform bus)
由此,接下来分析 usbmouse_as_key_probe() 函数
首先涉及了struct usb_device ,这是USB设备的内核表示
struct usb_device {
int devnum; // USB设备号;在USB总线上的地址
char devpath[16]; // 为了在信息中使用的设备ID字符串 (e.g., /port/...)
u32 route; //与XHCI一起使用的树拓扑六进制字符串
enum usb_device_state state; //设备的状态,此时处于configured状态而不是attached状态
enum usb_device_speed speed; //表示设备是高速/全速/低速 (or error)
struct usb_tt *tt; //事务传输信息,用于低速/全速设备,以及高速hub
int ttport; //usb设备在tt hub上的port
unsigned int toggle[2]; // 0代表IN端点,1代表OUT端点
struct usb_device *parent; //代表hub,除非你是root hub
struct usb_bus *bus; //设备所属的usb总线
struct usb_host_endpoint ep0; //端点0(默认的控制pipe)
struct device dev; //通用设备接口
struct usb_device_descriptor descriptor; //usb设备描述符,对应usb协议
struct usb_host_bos *bos; //USB设备BOS描述符集
struct usb_host_config *config; //设备所对应的所有配置
struct usb_host_config *actconfig; //当前活跃的配置
struct usb_host_endpoint *ep_in[16]; //IN端点数组
struct usb_host_endpoint *ep_out[16]; //OUT端点数组
char **rawdescriptors; //每个配置的原始描述符
unsigned short bus_mA; //目前可从总线获得
u8 portnum; // 父端口号(默认是1)
u8 level; //级别:USB集线器祖先数 usb hub的数量
unsigned can_submit:1; //urb可以被提交
unsigned persist_enabled:1; // 设备持续启用
unsigned have_langid:1; // whether string_langid is valid
unsigned authorized:1; //授权:(用户空间)策略决定是否授权该设备使用或不使用。默认情况下,有线USB设备是授权的。无线USB设备不是,直到我们从用户空间授权他们。
unsigned wusb:1; //是无线usb设备
unsigned lpm_capable:1; //设备支持lpm
unsigned usb2_hw_lpm_capable:1; //设备可以执行USB2硬件LPM
unsigned usb2_hw_lpm_besl_capable:1; // 设备可以执行USB2硬件BESL LPM
unsigned usb2_hw_lpm_enabled:1; //使能执行USB2硬件LPM
unsigned usb2_hw_lpm_allowed:1; //用户空间允许USB 2.0 LPM被使能
unsigned usb3_lpm_enabled:1; //使能执行USB3硬件LPM
unsigned usb3_lpm_u1_enabled:1; //USB3硬件 U1 LPM使能
unsigned usb3_lpm_u2_enabled:1; //USB3硬件 U2 LPM使能
int string_langid; //字符串的语言ID
/* static strings from the device 从设备获得的固定字符串 */
char *product; //产品ID字符串,如果存在
char *manufacturer; //厂家ID字符串,如果存在
char *serial; //串口号字符串,如果存在
struct list_head filelist; //为着被设备而打开的usb文件系统的文件列表
int maxchild; //如果是hub的接口,总端口数
u32 quirks; // 整个装置的怪癖
atomic_t urbnum; //对整个设备来说被提交的urb的个数
unsigned long active_duration; //活跃时间,设备不被挂起的总时间
#ifdef CONFIG_PM
unsigned long connect_time; //usb设备首次连接时间
unsigned do_remote_wakeup:1; //远程唤醒使能
unsigned reset_resume:1; //复位代替重启
unsigned port_is_suspended:1; // 上游端口暂停
#endif
struct wusb_dev *wusb_dev; // 如果这是一个无线USB设备,链接到WUSB特定设备的数据。
int slot_id; // XHCI分配的时隙ID
enum usb_device_removable removable; //设备可在物理上被移除
struct usb2_lpm_parameters l1_params; //USF2 L1 LPM状态和L1超时的最佳EFF服务等待时间。
struct usb3_lpm_parameters u1_params; //退出USP3 U1 LPM状态的延迟,以及轮毂启动超时。
struct usb3_lpm_parameters u2_params; //退出USP3U2LPM状态的延迟,以及轮毂启动超时。
unsigned lpm_disable_count; // 由usb_disable_lpm()和usb_enable_lpm() 使用的REF计数,用于跟踪需要为此usb_设备禁用USB 3.0链路电源管理的函数的数量。这个计数应该只由这些函数来操纵,而带宽是互斥的。
};
第二个结构体struct usb_host_interface
/* 主机侧封装,用于一个接口设置的解析描述符 */
struct usb_host_interface {
struct usb_interface_descriptor desc; // 前文的设备描述符之一
int extralen; //额外描述符的长度
unsigned char *extra; /* Extra descriptors 额外的描述符*/
/* array of desc.bNumEndpoints endpoints associated with this
* interface setting. these will be in no particular order.
* 与此接口设置相关联的desc.bNumEndpoints端点数组。这些将没有特别的顺序。
*/
struct usb_host_endpoint *endpoint;
char *string; /* iInterface string, if present 接口字符串 */
};
其中struct usb_host_endpoint
struct usb_host_endpoint {
struct usb_endpoint_descriptor desc; // 端点描述符
struct usb_ss_ep_comp_descriptor ss_ep_comp; //这个端点的超高速伴随描述符
struct list_head urb_list; //这个端点的urb队列;有USB core维护
void *hcpriv; // 由HCD使用;通常持有硬件DMA队列头(QH),每个URB具有一个或多个传输描述符(TDS)。
struct ep_device *ep_dev; /* For sysfs info 为sysfs提供信息*/
unsigned char *extra; /* Extra descriptors */
int extralen;
int enabled; //URBs可以被提供给这个端点
int streams; //端点上分配的UB-3流数
};
在接下来的几步,
首先,采用了输入子系统结构,设置注册了input dev,以前做过很多次,看上面程序很清楚。
然后设置了urb,用于USB传输
/* d. 硬件相关操作 */
/* 数据传输3要素: 源,目的,长度 */
/* 源: USB设备的某个端点 */
pipe = usb_rcvintpipe(dev, endpoint->bEndpointAddress);
/* 长度: */
len = endpoint->wMaxPacketSize;
/* 目的: */
usb_buf = usb_alloc_coherent(dev, len, GFP_ATOMIC, &usb_buf_phys);
/* 使用"3要素" */
/* 分配usb request block */
uk_urb = usb_alloc_urb(0, GFP_KERNEL);
/* 使用"3要素设置urb" */
usb_fill_int_urb(uk_urb, dev, pipe, usb_buf, len, usbmouse_as_key_irq, NULL, endpoint->bInterval);
uk_urb->transfer_dma = usb_buf_phys;
uk_urb->transfer_flags |= URB_NO_TRANSFER_DMA_MAP;
/* 使用URB */
usb_submit_urb(uk_urb, GFP_KERNEL);
return 0;
整个probe函数很明显就是两部分,一是注册input设备,而是构建urb,用于信息传输,那么正确理解urb明显至关重要【思考一:urb的作用?】,再用函数usb_fill_int_urb设置urb时,设置了,urb完成函数函数usbmouse_as_key_irq(),其中无非就是根据上报input event,最后再次提交urb
==============================================================
在drivers\usb\core\usb.c
subsys_initcall(usb_init); // 声明子系统调用,会在内核启动时初始化usb
/*
* Init
*/
static int __init usb_init(void)
{
int retval;
if (usb_disabled()) {
pr_info("%s: USB support disabled\n", usbcore_name);
return 0;
}
usb_init_pool_max();
retval = usb_debugfs_init();
if (retval)
goto out;
usb_acpi_register();
retval = bus_register(&usb_bus_type);
if (retval)
goto bus_register_failed;
retval = bus_register_notifier(&usb_bus_type, &usb_bus_nb);
if (retval)
goto bus_notifier_failed;
retval = usb_major_init();
if (retval)
goto major_init_failed;
retval = usb_register(&usbfs_driver);
if (retval)
goto driver_register_failed;
retval = usb_devio_init();
if (retval)
goto usb_devio_init_failed;
retval = usb_hub_init();
if (retval)
goto hub_init_failed;
retval = usb_register_device_driver(&usb_generic_driver, THIS_MODULE);
if (!retval)
goto out;
usb_hub_cleanup();
hub_init_failed:
usb_devio_cleanup();
usb_devio_init_failed:
usb_deregister(&usbfs_driver);
driver_register_failed:
usb_major_cleanup();
major_init_failed:
bus_unregister_notifier(&usb_bus_type, &usb_bus_nb);
bus_notifier_failed:
bus_unregister(&usb_bus_type);
bus_register_failed:
usb_acpi_unregister();
usb_debugfs_cleanup();
out:
return retval;
}
其中现在我们只关心最需要的
int usb_hub_init(void)
{
usb_register(&hub_driver); //注册USB hub驱动
...
/* 创建工作队列,此函数在usb_hub_init中分配队列,替代了以前的thrread_run的功能 */
hub_wq = alloc_workqueue("usb_hub_wq", WQ_FREEZABLE, 0);
...
}
【思考二:聊一聊Linux中的工作队列2】
hub_probe
|
hub_configure
|
usb_fill_int_urb(hub->urb, hdev, pipe, *hub->buffer, maxp, hub_irq, //hub 中断
hub, endpoint->bInterval);
当有中断产生时将调用hub_irq
把USB设备接到开发板上,看输出信息:
/ # usb 1-1: new full-speed USB device number 2 using s3c2410-ohci
拔掉:
/ # usb 1-1: USB disconnect, device number 2
再接上:
/ # usb 1-1: new full-speed USB device number 3 using s3c2410-ohci
拔掉:
/ # usb 1-1: USB disconnect, device number 3
在内核目录drivers/下搜:
grep "USB device number" * -nR
搜到:
usb/core/hub.c:4365: "%s %s USB device number %d using %s\n",
usb/core/hub.c:4498: "%s SuperSpeed%s USB device number %d using %s\n",
由此可以知道,当某个usb设备插上,因为硬件上的某些原理,会触发某些操作,从hub.c开始分析,这也与上文分析一致
hub_irq
kickkick_hub_wq
hub_events
port_event
hub_port_connect_change
hub_port_connect
udev = usb_alloc_dev(hdev, hdev->bus, port1);
choose_devnum(udev); // 给新设备分配编号(对于usb2.0也是地址)
hub_port_init(hub, udev, port1, i); // 初始化,打印信息
usb_new_device(udev); //新建一个usb设备
device_add(&udev->dev); //添加设备
...
device_attach // 尝试匹配usb驱动
怎么写USB设备驱动程序?
1. 分配/设置usb_driver结构体
.id_table
.probe
.disconnect
2. 注册
测试1th/2th:
1. make menuconfig去掉原来的USB鼠标驱动
-> Device Drivers
-> HID Devices
<> USB Human Interface Device (full HID) support
2. make uImage 并使用新的内核启动
3. insmod usbmouse_as_key.ko
4. 在开发板上接入、拔出USB鼠标
测试3th:
1. insmod usbmouse_as_key.ko
2. ls /dev/event*
3. 接上USB鼠标
4. ls /dev/event*
5. 操作鼠标观察数据
测试4th:
1. insmod usbmouse_as_key.ko
2. ls /dev/event*
3. 接上USB鼠标
4. ls /dev/event*
5. cat /dev/tty1 然后按鼠标键
6. hexdump /dev/event0