总结
usb 框架
usb 每一级都可以是 hub 或者 设备,但是 hub 最多6级,第7级只能是设备。
app 可以通过两种方法来访问硬件
- app -> usb device driver -> usb host driver -> host 硬件 -> device 硬件
- app -> libusb -> usb host driver -> host 硬件 -> device 硬件
电气信号
低速,d-
变化,全速和高速 d+
变化,
先识别到全速,然后 host 会发送一个复位,device 复位之后,会和 host 之间发送 K J 信号,如果没问题,那就是高速 host + 高速设备,然后高速设备就会取消 d+
的上拉电阻。 高速时, host 和 设备都会下拉电阻用于防止信号反射,一旦 设备拔出,信号反射变化变大,就可以被检测出来。
从 idle 的 j 状态变化为 k 状态,就是 sop 信号了,后面就是要开始传输信号了。
NRZI:反向不归零编码,波形变化表示0,波形不变表示1. 当连续传输6个1之后,必须强制插入一个0,防止双方在长期不变信号的情况下,传输错位。
协议,数据包格式
- 外层包:sop + sync + 包内容 + eop
- 包内容: pid + 地址 + 帧号 + 数据 + crc
- pid: 方向 + 类型。
一个完整的事务,含有 令牌包 + 数据包 + 握手回应包,对应了 令牌域,数据域,握手域。
- host 写,含有 host 发送的 令牌包(地址) + 数据包,以及 device 发送的 握手回应包。
- host 读,含有 host 发送的 令牌包(地址) + device 发送的数据包,以及 host 发送的 握手回应包。
数据传输形式
- 批量传输:u盘,可靠,非实时
- 中断传输:鼠标,可靠,实时
- 实时传输:摄像头,非可靠,实时
- 控制传输:识别、枚举等。
控制传输由多个事务实现,其他传输由一个事务实现。
- 批量传输:读和写都是完整的事务,另外还有一个 ping 查询包
- 中断传输: 读和写都是完整事务,但是要周期性的传输。
- 实时传输: 没有握手包。
- 控制传输:由设置阶段 + 数据阶段 + 状态阶段组成
- 设置阶段: 令牌包中指明 setup
- 数据阶段: 由一个或多个批量传输来组成
- 状态阶段:由一个批量传输组成,如果数据阶段是读或写,那么本阶段会反向;如果无数据阶段,那么本阶段就是读
体验数据格式
usbprotocolsuit 中的示例程序可以看数据格式。
高速模式,sync 4个字节,eop 1个字节,字节长度是包含他们的。
描述符
- 一个硬件设备可能有多种配置,一般情况下,只能生效一种配置。 比如:上网卡有 u盘 和 上网两种配置。
- 一个配置下,可能有多个接口也就是功能逻辑设备,一般可以同时生效。比如,耳机有音频和按键
- 一个接口,可能有多个端点,一种端点只能由一种数据方向。 比如说音频接口,有播放和录音这两个端点。
所以描述符会有:设备描述符,配置描述符,接口描述符,端点描述符。
usb设备,都有端口0,用于最开始的通讯。
linux 上面可以使用 lsusb -v
来查看当前的 usb 描述符。
枚举过程
- 全速设备在复位后,会检查是否支持高速。
- 设备初始地址为0, host 会和这个地址 0, ep0 进行通讯。设置地址之类的事情
- 启用新地址后, host 与新地址通信。
- 配置可用配置
在设置事务中,数据阶段,都是标准的 8字节数据,并且是固定格式。
枚举流程:
- 获取设备描述符
- 设置地址
- 获取设备描述符
- 获取配置描述符
- 设置配置
libusb
app 可以通过 libusb 来和 host 驱动打交道。 但是这就要求 app 需要阅读 usb 设备手册,了解设备相关细节,然后才能用 libusb 提供的接口进行操作。 本来这些 usb 设备相关的工作,应该由驱动工程师来实现。
app 有两种方式来打开 usb 设备
- 知道确切的 usb 编号,就可以通过编号直接打开
- 不知道编号,就需要遍历所有 sub 设备,然后查看设备描述符和配置描述符,确定好之后,再打开。
app 通过 libusb 使用 usb 设备的时候,还需要通过 detach 相关函数,先移除驱动,然后再 claim 函数,认领这个 usb 设备。
传输可以通过 同步传输也可以异步传输。
另外 libusb 使用的时候,也需要 init, open, detach claim 等操作以及相应的反操作。
libusb api
libusb 异步的好处是,可以针对多 ep 进行操作,并且可以取消传输。 同步只能对一个 ep 进行操作,除非用多线程。
异步传输主要是几个步骤:
- 分配
- 填充
- 提交
- 处理事件,检查结果,回调函数。
- 释放
usb 鼠标
drivers\hid\usbhid\usbmouse.c
linux 鼠标驱动
上报数据,不同鼠标不太一样,但是大体的顺序是一样的:
-
- bit0: 左键
- bit[1]: 右键
- bit[2]: 中键
- bit[3]: 侧键
- bit[4]: 额外键
libusb 鼠标
可以参考 libusb-1.0.26
自带的 example dpfp.c
去编写. 也可以参考 openocd 相关的 cmsis_dap_usb_bulk.c
整体流程:
- init
err = libusb_init(NULL);
- get device list
if ((num_devices = libusb_get_device_list(NULL, &devs)) < 0)
- for each device, get config descriptor
for (int i = 0; i < num_devices; i++) { dev = devs[i]; /* parse interface descriptor, find usb mouse */ err = libusb_get_config_descriptor(dev, 0, &config_desc);
- 遍历 interface
for (int interface = 0; interface < config_desc->bNumInterfaces; interface++) { const struct libusb_interface_descriptor *intf_desc = &config_desc->interface[interface].altsetting[0]; interface_num = intf_desc->bInterfaceNumber;
- 找到鼠标条件
if (intf_desc->bInterfaceClass != 3 || intf_desc->bInterfaceProtocol != 2) else
- 找到了输入的中断端点
for (int ep = 0; ep < intf_desc->bNumEndpoints; ep++) { if ((intf_desc->endpoint[ep].bmAttributes & 3) == LIBUSB_TRANSFER_TYPE_INTERRUPT || (intf_desc->endpoint[ep].bEndpointAddress & 0x80) == LIBUSB_ENDPOINT_IN) { /* 找到了输入的中断端点 */ fprintf(stdout, "find in int endpoint\n"); endpoint = intf_desc->endpoint[ep].bEndpointAddress; found = 1; break; } }
- 释放 config
libusb_free_config_descriptor(config_desc);
- 如果找到鼠标,就
err = libusb_open(dev, &dev_handle);
, 如果没找到需要libusb_free_device_list(devs, 1); libusb_exit(NULL);
- free device list
libusb_free_device_list(devs, 1);
- claim interface
libusb_set_auto_detach_kernel_driver(dev_handle, 1); err = libusb_claim_interface(dev_handle, interface_num);
- libusb_interrupt_transfer
while (1) { err = libusb_interrupt_transfer(dev_handle, endpoint, buffer, 16, &transferred, 5000);
- libusb_close
libusb_release_interface(dev_handle, interface_num); libusb_close(dev_handle); libusb_exit(NULL);
编译
本机编译时需要 libusb 1.0 的开发包,需要安装 libusb-1.0-0-dev, 还有链接的时候,也需要 -lusb-1.0
交叉编译,需要安装 libtool
libudev
交叉编译libusb
sudo apt-get install libtool
unzip libusb-1.0.26.zip
cd libusb-1.0.26
./autogen.sh
./configure --host=arm-buildroot-linux-gnueabihf --prefix=$PWD/tmp
make
make install
ls tmp/
include lib
安装库、头文件到工具链的目录里
libusb-1.0.26/tmp/lib$ cp * -rfd /home/book/100ask_imx6ull-sdk/ToolChain/arm-buildroot-linux-gnueabihf_sdk-buildroot/arm-buildroot-linux-gnueabihf/sysroot/usr/lib/
libusb-1.0.26/tmp/include$ cp libusb-1.0 -rf /home/book/100ask_imx6ull-sdk/ToolChain/arm-buildroot-linux-gnueabihf_sdk-buildroot/arm-buildroot-linux-gnueabihf/sysroot/usr/include/
交叉编译app
arm-buildroot-linux-gnueabihf-gcc -o readmouse.c -lusb-1.0
libusb 鼠标 异步
异步情况下,可以对多个 ep 进行处理,所以可以用链表和结构体来操作。
struct usb_mouse {
struct libusb_device_handle *handle;
int interface;
int endpoint;
unsigned char buf[16];
int transferred;
struct libusb_transfer *transfer;
struct usb_mouse *next;
};
找到鼠标后处理
err = libusb_open(dev, &dev_handle);
pmouse = malloc(sizeof(struct usb_mouse));
pmouse->endpoint = endpoint;
pmouse->interface = interface_num;
pmouse->handle = dev_handle;
pmouse->next = NULL;
if (!list)
list = pmouse;
else
{
pmouse->next = list;
list = pmouse;
}
claim interface
pmouse = usb_mouse_list;
while (pmouse)
{
libusb_set_auto_detach_kernel_driver(pmouse->handle, 1);
err = libusb_claim_interface(pmouse->handle, pmouse->interface);
pmouse = pmouse->next;
}
alloc transfer, fill transfer, submit transfer
pmouse = usb_mouse_list;
while (pmouse)
{
/* alloc transfer */
pmouse->transfer = libusb_alloc_transfer(0);
/* fill transfer */
libusb_fill_interrupt_transfer(pmouse->transfer, pmouse->handle, pmouse->endpoint, pmouse->buf, sizeof(pmouse->buf), mouse_irq, pmouse, 0);
/* submit transfer */
libusb_submit_transfer(pmouse->transfer);
pmouse = pmouse->next;
}
mouse_irq
static void mouse_irq(struct libusb_transfer *transfer)
{
static int count = 0;
if (transfer->status == LIBUSB_TRANSFER_COMPLETED)
{
/* parser data */
printf("%04d datas: ", count++);
for (int i = 0; i < transfer->actual_length; i++)
{
printf("%02x ", transfer->buffer[i]);
}
printf("\n");
}
if (libusb_submit_transfer(transfer) < 0)
{
fprintf(stderr, "libusb_submit_transfer err\n");
}
}
handle events 用于处理回调函数
while (1) {
struct timeval tv = { 5, 0 };
int r;
r = libusb_handle_events_timeout(NULL, &tv);
}
libusb_close
pmouse = usb_mouse_list;
while (pmouse)
{
libusb_release_interface(pmouse->handle, pmouse->interface);
libusb_close(pmouse->handle);
pmouse = pmouse->next;
}
列表清空
void free_usb_mouses(struct usb_mouse *usb_mouse_list)
{
struct usb_mouse *pnext;
while (usb_mouse_list)
{
pnext = usb_mouse_list->next;
free(usb_mouse_list);
usb_mouse_list = pnext;
}
}
usb 设备驱动模型
也是和 i2c, spi 一样,usb 也有自己的总线,一边是驱动,另外一边是 usb设备的 interface. 具体可以参考 drivers\hid\usbhid\usbmouse.c
include\linux\usb.h
里面是可以使用的usb 函数,这些函数的对象是 usb 设备里面的某个 ep, 成为 pipe.
- 同步传输,可以直接用相关函数
- 异步传输,需要先分配设置提交一个 URB(usb request block), 当传输完成后,回调函数会被调用。
- 一般传输中,在提交 URB 的时候,会临时分配一个 DMA buffer, 当然可以用
usb_alloc_coherent
来搞出来 DMA buffer。
- 一般传输中,在提交 URB 的时候,会临时分配一个 DMA buffer, 当然可以用
usb 设备驱动框架
在 probe 中,构造注册一个 input_dev, 获得数据通过构造提交 URB,并在回调函数中向 input 系统上报数据。
usb 设备驱动
- 分配 input
input_dev = devm_input_allocate_device(&intf->dev);
- input 上报需要拿到 usb 相关的信息,所以这些需要设置为 input 的数据,方便后续使用
struct usb_mouse_as_key_desc { struct usb_device *dev; struct usb_interface *intf; const struct usb_device_id *id; int pipe, maxp; int bInterval; void *data_buffer; dma_addr_t data_dma; struct urb *urb; }; desc = kmalloc(sizeof(struct usb_mouse_as_key_desc), GFP_KERNEL); desc->dev = dev; desc->intf = intf; desc->id = id; desc->pipe = pipe; desc->maxp = maxp; desc->bInterval = endpoint->bInterval; desc->data_buffer = usb_alloc_coherent(dev, maxp, GFP_ATOMIC, &desc->data_dma); input_set_drvdata(input_dev, desc);
- input 设置事件
/* set 1: which type event ? */ __set_bit(EV_KEY, input_dev->evbit); /* set 2: which event ? */ __set_bit(KEY_L, input_dev->keybit); __set_bit(KEY_S, input_dev->keybit); __set_bit(KEY_ENTER, input_dev->keybit);
- usb 传输,只有在 input 被 open 之后才工作
input_dev->open = usb_mouse_as_key_open; input_dev->close = usb_mouse_as_key_close;
- 注册 input
error = input_register_device(input_dev);
- input 也作为 usb 数据
usb_set_intfdata(intf, input_dev);
- open 中,分配设置 URB, 其中不变的 pipe, dev 等放在 probe 中。
static int usb_mouse_as_key_open(struct input_dev *dev) { struct urb *urb; struct usb_mouse_as_key_desc *desc = input_get_drvdata(dev); int err; printk("%s %s %d\n", __FILE__, __FUNCTION__, __LINE__); /* 分配/填充/提交 URB */ urb = usb_alloc_urb(0, GFP_KERNEL); desc->urb = urb; usb_fill_int_urb(urb, desc->dev, desc->pipe, desc->data_buffer, (desc->maxp > 8 ? 8 : desc->maxp), usb_mouse_as_key_irq, dev, desc->bInterval); urb->transfer_dma = desc->data_dma; urb->transfer_flags |= URB_NO_TRANSFER_DMA_MAP; err = usb_submit_urb (urb, GFP_ATOMIC); printk("%s %s %d, err = %d\n", __FILE__, __FUNCTION__, __LINE__, err); return err; }
- 按照
usbmouse.c
,设置 pipe等。interface = intf->cur_altsetting; if (interface->desc.bNumEndpoints != 1) return -ENODEV; endpoint = &interface->endpoint[0].desc; if (!usb_endpoint_is_int_in(endpoint)) return -ENODEV; pipe = usb_rcvintpipe(dev, endpoint->bEndpointAddress); maxp = usb_maxpacket(dev, pipe, usb_pipeout(pipe));
- close
static void usb_mouse_as_key_close(struct input_dev *dev) { struct usb_mouse_as_key_desc *desc = input_get_drvdata(dev); /* 取消/释放 URB */ usb_kill_urb(desc->urb); usb_free_urb(desc->urb); }
- urb irq 中,通过 input 上报数据
static void usb_mouse_as_key_irq(struct urb *urb) { struct input_dev *dev = urb->context; struct usb_mouse_as_key_desc *desc = input_get_drvdata(dev); signed char *data = desc->data_buffer; int status; switch (urb->status) { case 0: /* success */ break; case -ECONNRESET: /* unlink */ case -ENOENT: case -ESHUTDOWN: return; /* -EPIPE: should clear the halt */ default: /* error */ goto resubmit; } input_report_key(dev, KEY_L, data[1] & 0x01); input_report_key(dev, KEY_S, data[1] & 0x02); input_report_key(dev, KEY_ENTER, data[1] & 0x04); input_sync(dev); resubmit: status = usb_submit_urb (urb, GFP_ATOMIC); }
- usb设备拔出
static void usb_mouse_as_key_disconnect(struct usb_interface *intf) { struct input_dev *input_dev = usb_get_intfdata (intf); struct usb_mouse_as_key_desc *desc = input_get_drvdata(input_dev); usb_free_coherent(desc->dev, desc->maxp, desc->data_buffer, desc->data_dma); kfree(desc); input_unregister_device(input_dev); usb_set_intfdata(intf, NULL); }
usb 设备调试
别忘了,把内核中已经有的 usb_hid 给去掉。否则会被这个驱动先接管。
usb 鼠标,支持两种通信协议,
- 一种简单的是 boot protocol,
usbmouse.c
用的就是这种 - 另外一种复杂的是 report protocol,
hid-core.c
用的是这种
一般鼠标默认用的是 report protocol,如果想要修改,需要用控制请求 Set_Protocol 来修改。
otg
通过 id 引脚,确认自己是否是 host,如果是 host,就对电源引脚供电。