0%

Linux驱动学习日记(31) USB 设备学习(2)

USB 设备驱动模型

  编写 USB 设备驱动框架模型也是根据 BUS/DEV/DRV 模型来做的。
  USB 设备驱动框架图如下

图 1 USB框架图

  一个 USB 设备可以包含一个或多个配置,每个配置下又包含一个或多个接口。在 Linux USB 驱动模型里,很多驱动并不是直接绑定整个 usb_device,而是绑定某个 usb_interface,所以 usb_interface 往往是 USB 功能驱动实际匹配和操作的对象。

  若要发起 USB 传输,则要访问 USB 设备的某一个端点,将设备和端点组合起来就称为一个管道(pipe)。调用以下这些宏以后就得到了几个信息:端点类型、端点方向、端点地址、属于设备。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#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)

  使用管道有两种方式,一种是同步传输,另一种是异步传输。
  **同步传输:**对于控制传输、批量传输、中断传输,有3个同步函数可以用来直接发起传输。这些函数内部会创建、填充、提交一个 URB(“usb request block”),并等待它完成或超时。

1
2
3
4
5
6
7
8
extern int usb_control_msg(struct usb_device *dev, unsigned int pipe,
__u8 request, __u8 requesttype, __u16 value, __u16 index,
void *data, __u16 size, int timeout);
extern int usb_interrupt_msg(struct usb_device *usb_dev, unsigned int pipe,
void *data, int len, int *actual_length, int timeout);
extern int usb_bulk_msg(struct usb_device *usb_dev, unsigned int pipe,
void *data, int len, int *actual_length,
int timeout);

  **异步传输:**对于异步传输,我们需要先分配、构造、提交一个 URB,当传输完成后,其回调函数会被调用。

  分配和释放 URB:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
/**
* usb_alloc_urb - creates a new urb for a USB driver to use
* @iso_packets: number of iso packets for this urb
* @mem_flags: the type of memory to allocate, see kmalloc() for a list of
* valid options for this.
*
* Creates an urb for the USB driver to use, initializes a few internal
* structures, increments the usage counter, and returns a pointer to it.
*
* If the driver want to use this urb for interrupt, control, or bulk
* endpoints, pass '0' as the number of iso packets.
*
* The driver must call usb_free_urb() when it is finished with the urb.
*
* Return: A pointer to the new urb, or %NULL if no memory is available.
*/
struct urb *usb_alloc_urb(int iso_packets, gfp_t mem_flags);


/**
* usb_free_urb - frees the memory used by a urb when all users of it are finished
* @urb: pointer to the urb to free, may be NULL
*
* Must be called when a user of a urb is finished with it. When the last user
* of the urb calls this function, the memory of the urb is freed.
*
* Note: The transfer buffer associated with the urb is not freed unless the
* URB_FREE_BUFFER transfer flag is set.
*/
void usb_free_urb(struct urb *urb);

  填充 URB,对于控制传输、批量传输、中断传输分别有如下函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
static inline void usb_fill_control_urb(struct urb *urb,  // 要填充的 URB 
struct usb_device *dev, // 目标设备
unsigned int pipe, // 目标管道
unsigned char *setup_packet, // 建立令牌包
void *transfer_buffer, // 发送数据
int buffer_length, // 数据长度
usb_complete_t complete_fn, // 回调函数
void *context);

static inline void usb_fill_bulk_urb(struct urb *urb,
struct usb_device *dev,
unsigned int pipe,
void *transfer_buffer,
int buffer_length,
usb_complete_t complete_fn,
void *context);

static inline void usb_fill_int_urb(struct urb *urb,
struct usb_device *dev,
unsigned int pipe,
void *transfer_buffer,
int buffer_length,
usb_complete_t complete_fn,
void *context,
int interval);

  提交 URB 使用第一个函数,在 URB 提交以后也可以取消。

1
2
3
int usb_submit_urb(struct urb *urb, gfp_t mem_flags);
void usb_kill_urb(struct urb *urb); // 同步,会等待 URB 结束
int usb_unlink_urb(struct urb *urb); // 异步,不会等待 URB 结束

  分配/释放 DMA Buffer
  发起 USB 传输时,数据保存在 buffer 里。这个 buffer 可以是一般的buffer,也可以是 DMA Buffer。对于一般的 buffer,在提交 URB 的时候会临时分配一个 DMA Buffer。发送数据时:函数内部会先从一般 buffer 中把数据复制到 DMA Buffer,在提交给 USB 控制器。读取数据时:USB 控制器先把数据传到DMA Buffer,函数内部在把DMA Buffer的数据复制到一般 buffer。中间增加了一次数据的拷贝,效率低。
  我们可以直接使用DMA Buffer,函数原型如下:

1
2
void *usb_alloc_coherent(struct usb_device *dev, size_t size, gfp_t mem_flags, dma_addr_t *dma);
void usb_free_coherent(struct usb_device *dev, size_t size, void *addr, dma_addr_t dma);

  若 URB 使用 DMA Buffer,那么填充 URB 时还需要设置一个 flag 表明这一点

1
2
urb->transfer_dma = DMA address of buffer; // usb_alloc_coherent的输出参数
urb->transfer_flags |= URB_NO_TRANSFER_DMA_MAP;

编写 USB 鼠标驱动程序

  回想一下,我们在编写 GPIO 按键驱动的时候,我们是直接构造并注册了一个 input_dev 结构体,在 GPIO 中断函数里获取数据并上报。
  现在我们要做 USB 鼠标的驱动,数据来自 USB 设备,需要做的事情如下:

  • 构造并注册 usb_driver。
  • 在 probe 函数中构造并注册 input_dev 结构体
  • 获取到数据后构造并提交 URB,在 URB 的回调函数中向 input 系统上报数据。

  接下来,我们来尝试编写一个 USB 鼠标做按键使用的程序:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
/*
* @Author: 胡城玮
* @FilePath: usbmouse.c
* @Date: 2026-03-26
* @Description:
* @Version: 0.1
*/

#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/errno.h>
#include <linux/usb.h>
#include <linux/input.h>

struct usb_mouse_as_key_desc{
struct usb_device *dev;
struct usb_interface *intf;
struct usb_device_id *id;
unsigned int pipe, maxp;
void *transfer_buffer;
dma_addr_t data_dma;
unsigned char interval;
struct urb *urb;
};

/* 1、构造一个 usb driver 结构体 */

static struct usb_device_id usb_mouse_as_key_id_table[] = {
{ USB_INTERFACE_INFO(3, 1, 2) },
{}
};

static void usb_mosu_as_key_irq(struct urb *urb)
{
struct input_dev *input_dev = urb->context;
struct usb_mouse_as_key_desc *desc= input_get_drvdata(input_dev);
signed char *data = desc->transfer_buffer;

switch(urb->status)
{
case 0:break;
case -ECONNRESET:
case -ENOENT:
case -ESHUTDOWN:
return;
default:
goto resubmit;
}

input_report_key(input_dev, KEY_L, data[0]&0x01);
input_report_key(input_dev, KEY_S, data[0]&0x02);
input_report_key(input_dev, KEY_ENTER, data[0]&0x04);

input_sync(input_dev);

resubmit:
usb_submit_urb (urb, GFP_ATOMIC);


}

static int usb_mouse_as_key_open(struct input_dev *dev){
/* 分配填充提交 URB */
printk("usb mouse input open\n");
struct urb *urb;
struct usb_mouse_as_key_desc *desc= input_get_drvdata(dev);
urb = usb_alloc_urb(0, GFP_KERNEL);

desc->urb = urb;

usb_fill_int_urb(urb, desc->dev, desc->pipe, desc->transfer_buffer, desc->maxp, usb_mosu_as_key_irq, dev,desc->interval);
urb->transfer_dma = desc->data_dma; // usb_alloc_coherent的输出参数
urb->transfer_flags |= URB_NO_TRANSFER_DMA_MAP;
usb_submit_urb(urb, GFP_ATOMIC);
return 0;
}

static void usb_mouse_as_key_close(struct input_dev *dev){
/* 取消释放 URB */
struct usb_mouse_as_key_desc *desc = input_get_drvdata(dev);
usb_kill_urb(desc->urb);
}

static int usb_mouse_as_key_probe(struct usb_interface *intf, const struct usb_device_id *id)
{
printk("matched!!!\r\n");
struct usb_device *dev = interface_to_usbdev(intf);
struct input_dev *input_dev;
struct usb_mouse_as_key_desc *desc;
int ret;
struct usb_host_interface *interface;
int pipe, maxp;
struct usb_endpoint_descriptor *endpoint;

/* 记录设备信息 */
interface = intf->cur_altsetting;

if(interface->desc.bNumEndpoints != 1)
return -ENODEV;

endpoint = &interface->endpoint[0].desc;

pipe = usb_rcvintpipe(dev, endpoint->bEndpointAddress);
maxp = usb_maxpacket(dev,pipe);

/* 分配设置注册 input_dev */
input_dev = devm_input_allocate_device(&intf->dev);
desc = kmalloc(sizeof(struct usb_mouse_as_key_desc), GFP_KERNEL);
desc->intf = intf;
desc->id = id;
desc->interval = endpoint->bInterval;
desc->dev = interface_to_usbdev(intf); // 接口转换为 USBDEV
desc->pipe = pipe;
desc->maxp = maxp;
desc->transfer_buffer = usb_alloc_coherent(dev, maxp, GFP_ATOMIC, &desc->data_dma);

input_set_drvdata(input_dev, desc);

__set_bit(EV_KEY, input_dev->evbit);
__set_bit(KEY_L, input_dev->keybit);
__set_bit(KEY_S, input_dev->keybit);
__set_bit(KEY_ENTER, input_dev->keybit);

input_dev->open = usb_mouse_as_key_open;
input_dev->close = usb_mouse_as_key_close;

ret = input_register_device(input_dev);

usb_set_intfdata(intf, input_dev);

return 0;
}

static void usb_mouse_as_key_id_disconnect(struct usb_interface *intf)
{
struct input_dev *inputdev = usb_get_intfdata(intf);
struct usb_mouse_as_key_desc *desc = input_get_drvdata(inputdev);

usb_free_urb(desc->urb);
usb_free_coherent(interface_to_usbdev(intf), desc->maxp, desc->transfer_buffer, desc->data_dma);
kfree(desc);

input_unregister_device(inputdev);
}


static struct usb_driver usb_mouse_as_key_driver = {
.name = "usbmouse_as_key",
.probe = usb_mouse_as_key_probe,
.id_table = usb_mouse_as_key_id_table,
.disconnect = usb_mouse_as_key_id_disconnect
};

/* 2、注册 */


/* 入口出口函数 */
module_usb_driver(usb_mouse_as_key_driver);
MODULE_AUTHOR("hcw");
MODULE_LICENSE("GPL");

  我们需要重新配置内核,去掉内核自带的驱动程序。在内核目录下执行 make menuconfig:

1
2
3
4
Device Drivers  --->
HID support --->
USB HID support --->
< > USB HID transport layer // 不要选中
图 2 实现成功