本文转自网络
从实用的角度出发,主要分为两个基本方面就行介绍,USB协议(和linux内核中的重要数据结构进行理解)和USB设备驱动程序的编写流程。不侧重于分析USB子系统
第一:
USB协议
1. USB驱动程序一般分为两种,一种是USB总线驱动程序,一种是USB设备驱动程序。
2. USB设备接入电脑以后,usb总线驱动程序会发出命令来获取设备的描述符,,USB设备必须返回描述符。
3. USB设备接入PC时,USB总线驱动程序都会给他们分配一个编号,接在USB总线的上的每一个设备都有自己的编号,PC机访问某个USB设备时,发出的命令都含有对应的编号
4. USB设备接入PC后,PC要将分配的编号传递给设备,但是设备此时没有编号,那么只能使用默认的编号0
5. USB是主从结构,所有的USB传输,都是从USB主机发起的,USB设备没有“主动”通知主机的能力,靠的USB控制器不断的查询,如果发现有数据就去中断CPU
6. USB传输类型
(1)U盘:批量传输(可靠,没有时间保证)
(2)鼠标:中断传输(可靠,实时)
(3)摄像头:实时传输(不可靠,实时)
(4)读取描述符:USB设备的识别过程
7. USB传输的对象:端点,除了端点0以外,每个端点只支持一个方向的传输,端点0用于控制传输,每一个端点都有类型(只有一个)和传输方向.
8. 程序里的输入输出是针对主机来说的
9. usb驱动的整体框架
app:
-------------------------------------------
USB设备驱动程序 // 知道数据含义
内核 --------------------------------------
USB总线驱动程序 // 1. 识别, 2. 找到匹配的设备驱动, 3. 提供USB读写函数 (它不知道数据含义)
-------------------------------------------
USB主机控制器
UHCI OHCI EHCI
硬件 --------------------
USB设备
10. USB总线驱动程序作用
三点:
(1)识别
(2)查找驱动匹配
(3)提供读写函数,不了解数据具体含义
11. USB传输的分类:
低速:1.5Mbps
全速:12Mbps
高速:480Mbps
12. USB总线驱动程序的过程,当USB设备接入时,硬件设备会产生中断
中断的处理流程如下:
hub_irq
kick_khubd
hub_thread
hub_events
hub_port_connect_change
udev = usb_alloc_dev(hdev, hdev->bus, port1);//为刚加进来的设备分配并填充一个usb_device结构体,下面会将device加入到设备驱动模型中
dev->dev.bus = &usb_bus_type;
choose_address(udev); //在1-128中找到没有使用的地址
hub_port_init
hub_set_address //把地址发送给设备
usb_get_device_descriptor(udev, 8); // 获取8个字节的设备描述符(描述符一共有四类)
retval = usb_get_device_descriptor(udev, USB_DT_DEVICE_SIZE);得到最大传输量后,再次获得完整设备描述符
usb_new_device(udev)
err = usb_get_configuration(udev); 得到各种描述符
usb_parse_configuration 解析描述符得到各种信息
device_add //从usb中线中取出driver,用match进行匹配,如果匹配成功,调用driver的probe函数。注意,这里的match不是单纯比较名称,而是在driver中有一张表,里面包含了它所支持设备的信息,从这里我们可以看出,linux的USB驱动程序提供给我们的框架已经将设备模型中的总线和设备部分做好了,我们要实现的就是driver
13. USB子系统中重要的数据结构
第一:逻辑结构
1. 设备usb_device 代表一个具有复合功能的设备
2. 配置usb_host_config 功能的组合
3. 接口usb_interface 一种功能
struct usb_interface {
/* array of alternate settings for this interface,
* stored in no particular order */
struct usb_host_interface *altsetting;
struct usb_host_interface *cur_altsetting; /* the currently
* active alternate setting */
unsigned num_altsetting; /* number of alternate settings */
/* If there is an interface association descriptor then it will list
* the associated interfaces */
struct usb_interface_assoc_descriptor *intf_assoc;
int minor; /* minor number this interface is bound to */
enum usb_interface_condition condition; /* state of binding */
unsigned is_active:1; /* the interface is not suspended */
unsigned sysfs_files_created:1; /* the sysfs attributes exist */
unsigned ep_devs_created:1; /* endpoint "devices" exist */
unsigned unregistering:1; /* unregistration is in progress */
unsigned needs_remote_wakeup:1; /* driver requires remote wakeup */
unsigned needs_altsetting0:1; /* switch to altsetting 0 is pending */
unsigned needs_binding:1; /* needs delayed unbind/rebind */
unsigned reset_running:1;
struct device dev; /* interface specific device info */
struct device *usb_dev;
atomic_t pm_usage_cnt; /* usage counter for autosuspend */
struct work_struct reset_ws; /* for resets in atomic context */
};
这里有一个重要的成员struct usb_host_interface *cur_altsetting;
struct usb_host_interface的意思是设置,一旦配置定下来以后,例如,铃声是一种配置,那么铃声的大小就是一种设置
struct usb_host_interface {
struct usb_interface_descriptor desc;
/* array of desc.bNumEndpoint endpoints associated with this
* interface setting. these will be in no particular order.
*/
struct usb_host_endpoint *endpoint;
char *string; /* iInterface string, if present */
unsigned char *extra; /* Extra descriptors */
int extralen;
};
这里存在着接口描述符和设备端点
4. 端点 usb _host_endpoint 单向的传输管道
struct usb_host_endpoint {
struct usb_endpoint_descriptor desc;
struct list_head urb_list;
void *hcpriv;
struct ep_device *ep_dev; /* For sysfs info */
struct usb_host_ss_ep_comp *ss_ep_comp; /* For SS devices */
unsigned char *extra; /* Extra descriptors */
int extralen;
int enabled;
};
第二:描述符
1. 设备描述符struct usb_device_descriptor
2. 配置描述符struct usb_config_descriptor
3. 接口描述符struct usb_interface_descriptor
struct usb_interface_descriptor {
__u8 bLength;
__u8 bDescriptorType;
__u8 bInterfaceNumber;
__u8 bAlternateSetting;
__u8 bNumEndpoints;
__u8 bInterfaceClass;
__u8 bInterfaceSubClass;
__u8 bInterfaceProtocol;
__u8 iInterface;
} __attribute__ ((packed))
以后就是用这里面的信息与驱动中idtable进行对比
4. 端点描述符
struct usb_endpoint_descriptor {
__u8 bLength; //端点描述符长度,B为单位
__u8 bDescriptorType;
__u8 bEndpointAddress; //这个成员包含很多信息
__u8 bmAttributes; //端点类型,四种传输类型
__le16 wMaxPacketSize;
__u8 bInterval; //主机对设备的查询时间
/* NOTE: these two are _only_ in audio endpoints. */
/* use USB_DT_ENDPOINT*_SIZE in bLength, not sizeof. */
__u8 bRefresh;
__u8 bSynchAddress;
} __attribute__ ((packed));
注意:0号端点是特殊的端点,它没有自己的端点描述符
bEndpointAddress:包含端点方向(最高位,使用USB_ENDPOINT_DIR_MASK,它的含义就是0x80,判断端点方向),端点地址,端点号码(0-3位,与0xff进行与操作)
总结:这8个数据结构的基本关系是
通过usb_device 可以找到 usb_host_config和struct usb_device_descriptor
通过usb_host_config可以找到usb_interface和truct usb_config_descriptor
通过usb_interface可以找到usb_host_interface
通过usb_host_interface可以找到truct usb_interface_descriptor 和 usb _host_endpoint
通过 usb _host_endpoint可以找到usb_endpoint_descriptor
第二:
具体USB鼠标驱动
1. 设备模型
USB设备驱动程序的编写要遵循设备驱动总线的模型,linux系统已经提供好了总线和设备,我们要实现的就是驱动的编写了。
所以,顺利成章,上来先要分配一个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 int usbmouse_as_key_init(void)
{
/* 2. 注册 */
usb_register(&usbmouse_as_key_driver);
return 0;
}
static 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");
这几句话我我想正常人都能写的出来,呵呵
接下来实现简单的probe和disconnect函数
static int usbmouse_as_key_probe(struct usb_interface *intf, const struct usb_device_id *id)
{
printk("found usbmouse!\n");
return 0;
}
static void usbmouse_as_key_disconnect(struct usb_interface *intf)
{
printk("disconnect usbmouse!\n");
}
比较肯定要提供那张表格啊
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) },
{ }
};
到这里要解释一下
struct usb_device_id {
/* which fields to match against? */
__u16 match_flags;
/* Used for product specific matches; range is inclusive */
__u16 idVendor;
__u16 idProduct;
__u16 bcdDevice_lo;
__u16 bcdDevice_hi;
/* Used for device class matches */
__u8 bDeviceClass;
__u8 bDeviceSubClass;
__u8 bDeviceProtocol;
/* Used for interface class matches */
__u8 bInterfaceClass;
__u8 bInterfaceSubClass;
__u8 bInterfaceProtocol;
/* not matched against */
kernel_ulong_t driver_info;
};
这就是这张表格具备的信息
#define USB_INTERFACE_INFO(cl, sc, pr) \
.match_flags = USB_DEVICE_ID_MATCH_INT_INFO, \
.bInterfaceClass = (cl), \
.bInterfaceSubClass = (sc), \
.bInterfaceProtocol = (pr)
所以刚才的定义等价于
static struct usb_device_id usbmouse_as_key_id_table [] = {
{
.match_flags = USB_DEVICE_ID_MATCH_INT_INFO,
.bInterfaceClass = USB_INTERFACE_CLASS_HID,
.bInterfaceSubClass = USB_INTERFACE_SUBCLASS_BOOT,
.bInterfaceProtocol = USB_INTERFACE_PROTOCOL_MOUSE,
}
{}//表示结束
}
满足这些个条件,那么这个driver就支持这个设备,从而调用probe函数
还有其他用来定义这个表格的宏
#define USB_DEVICE(vend,prod) \
.match_flags = USB_DEVICE_ID_MATCH_DEVICE, \
.idVendor = (vend), \
.idProduct = (prod)
#define USB_DEVICE_VER(vend, prod, lo, hi) \
.match_flags = USB_DEVICE_ID_MATCH_DEVICE_AND_VERSION, \
.idVendor = (vend), \
.idProduct = (prod), \
.bcdDevice_lo = (lo), \
.bcdDevice_hi = (hi)
match_flags的作用:因为struct usb_device_id中有很多匹配项,match_flags的作用就是告诉内核需要比较哪些项
2. input子系统
接下来,因为鼠标是个输入设备,自然要用到input子系统了,input子系统三要素,分配,设置,注册,上报。其中的分配和设置那没有悬念的要在probe函数中完成了
下面看probe函数
static int usbmouse_as_key_probe(struct usb_interface *intf, const struct usb_device_id *id)
{
struct usb_device *dev = interface_to_usbdev(intf);
struct usb_host_interface *interface;
struct usb_endpoint_descriptor *endpoint;
int pipe;
interface = intf->cur_altsetting;
endpoint = &interface->endpoint[0].desc;
/* a. 分配一个input_dev */
uk_dev = input_allocate_device();
/* b. 设置 */
/* b.1 能产生哪类事件 */
set_bit(EV_KEY, uk_dev->evbit);
set_bit(EV_REP, uk_dev->evbit);
/* b.2 能产生哪些事件 */
set_bit(KEY_L, uk_dev->keybit);
set_bit(KEY_S, uk_dev->keybit);
set_bit(KEY_ENTER, uk_dev->keybit);
/* c. 注册 */
input_register_device(uk_dev);
}
3. USB传输问题
接下来的要讨论的就是USB设备驱动程序的重点问题了我,究竟怎么将数据通过USB总线进行传输呢,这里是要必须知道的就是三个基本要素和一个核心数据结构,就是传说中的struct urb,要进行USB传输,必须分配并填充一个URB结构,然后提交到usb core,在恰当的时候调用注册好的函数就是数据的处理
这里的三个基本要素就是源址,长度,目的地址
源址:
pipe = usb_rcvintpipe(dev, endpoint->bEndpointAddress);
这里引出一个新的概念,就是管道,pipe,它是传输数据的虚拟载体。在linux中用unsigned int表示。在linux中一条管道只能进行一个方向上的传输,所以USB通信中,一般需要两条管道,一个用于接收数据,一个用于发送数据。
这样,linux中提供宏来创建管道
/* Create various pipes... */
#define usb_sndctrlpipe(dev,endpoint) \
((PIPE_CONTROL << 30) | __create_pipe(dev, endpoint)) //创建发送控制管道
#define usb_rcvctrlpipe(dev,endpoint) \
((PIPE_CONTROL << 30) | __create_pipe(dev, endpoint) | USB_DIR_IN) //创建接收控制管道
#define usb_sndisocpipe(dev,endpoint) \
((PIPE_ISOCHRONOUS << 30) | __create_pipe(dev, endpoint))
#define usb_rcvisocpipe(dev,endpoint) \
((PIPE_ISOCHRONOUS << 30) | __create_pipe(dev, endpoint) | USB_DIR_IN)
#define usb_sndbulkpipe(dev,endpoint) \
((PIPE_BULK << 30) | __create_pipe(dev, endpoint)) //创建发送批量管道
#define usb_rcvbulkpipe(dev,endpoint) \
((PIPE_BULK << 30) | __create_pipe(dev, endpoint) | USB_DIR_IN) //创建发送批量管道
#define usb_sndintpipe(dev,endpoint) \
((PIPE_INTERRUPT << 30) | __create_pipe(dev, endpoint)) //创建发送中断管道
#define usb_rcvintpipe(dev,endpoint) \
((PIPE_INTERRUPT << 30) | __create_pipe(dev, endpoint) | USB_DIR_IN) //创建接收中断管道
注意:这里的发送和接收都都是针对USB主机的,管道和端点一一对应
上面可以看出,由于pipe是一个unsigned int的数,调用__create_pipe(dev, endpoint)创建管道其实就是利用设备号和端点号填充pipe中的位
static inline unsigned int __create_pipe(struct usb_device *dev, unsigned int endpoint)
{
return (dev->devnum << 8) | (endpoint << 15);
}
由此可见管道的7位为管道方向,8-14位为设备号,15-18位为端点号,30,31为管道类型
长度
len = endpoint->wMaxPacketSize;//代表端点一次传输的最大数据量,字节表示,大于这个最大量最分批次传输
目的
usb_buf = usb_alloc_coherent(dev, len, GFP_ATOMIC, &usb_buf_phys);
将三要素完成后,下面就开始进行urb的相关工作了,老样子,先申请urb,接着填充,最后提交
1).申请urb
uk_urb = usb_alloc_urb(0, GFP_KERNEL);
2).填充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;
3).提交
usb_submit_urb(uk_urb, GFP_KERNEL);
4. 注册函数进行数据处理
我们看到在填充urb时候,制定了一个函数,那就是usbmouse_as_key_irq,当有数据产生的时候,这个函数就会唤醒进程,实际上这里利用了一个完成量的实现
static void usbmouse_as_key_irq(struct urb *urb)
{
static unsigned char pre_val;
/* USB鼠标数据含义
* data[0]: bit0-左键, 1-按下, 0-松开
* bit1-右键, 1-按下, 0-松开
* bit2-中键, 1-按下, 0-松开
*/
if ((pre_val & (1<<0)) != (usb_buf[0] & (1<<0)))
{
/* 左键发生了变化 */
input_event(uk_dev, EV_KEY, KEY_L, (usb_buf[0] & (1<<0)) ? 1 : 0);
input_sync(uk_dev);
}
if ((pre_val & (1<<1)) != (usb_buf[0] & (1<<1)))
{
/* 右键发生了变化 */
input_event(uk_dev, EV_KEY, KEY_S, (usb_buf[0] & (1<<1)) ? 1 : 0);
input_sync(uk_dev);
}
if ((pre_val & (1<<2)) != (usb_buf[0] & (1<<2)))
{
/* 中键发生了变化 */
input_event(uk_dev, EV_KEY, KEY_ENTER, (usb_buf[0] & (1<<2)) ? 1 : 0);
input_sync(uk_dev);
}
pre_val = usb_buf[0];
/* 重新提交urb */
usb_submit_urb(uk_urb, GFP_KERNEL);
}
这里主要进行数据处理,提交事件和重新提交ur
阅读(1088) | 评论(0) | 转发(0) |