上海 设计网站建设/中国舆情网
目录
一、 USB 协议简介
二、 Linux USB 驱动
三、 USB 设备驱动实例
一、 USB 协议简介
USB(Universal Serial Bus,通用串行总线)正如它的名字一样,是用来连接PC外设的一种通用串行总线,即插即用和易扩展是它最大的特点。所谓即插即用,是 PC 不需要断电就可以连接外设,并且不需要在硬件上通过跳线来配置设备。易扩展则是它可以很容易扩展出更多的接口来连接更多的外设。USB 的协议主要经过了 USB1.1、USB2.和USB3.0 三个阶段。目前 PC 上很多 USB 接口都支持 USB3.0,但是在嵌入式系统上要使用的还是 USB2.0 协议,所以后面也只讨论 USB2.0 协议。USB2.0 有三种标准的速率分别是低速 1.5Mb/s、全速 12Mb/s 和高速 80Mb/s,在设备接入主机时会自动协商,最后确定一个速率。相比于前面的 I2C 和 SPI,USB 协议要复杂得多,本节从驱动开发者的角度来讨论协议中相关的一些内容。下图是 USB 的拓扑结构图(引自 USB 2.0协
议,后面有些图也引自该协议)。
USB 也是主从结构的总线,整个拓扑结构呈金字塔形状,为星形连接,最上面的一层就是主机,下面各层分别为连接在主机上的设备,下面分别进行介绍。
USB 主机,Host: 由它来发起 USB 的传输,还有一个 RootHub,通常和 USB 主机控制器集成在一起。它是一根集线器,为主机控制器提供至少一个连接设备的 USB 接口。
USB 设备,分为 Hub 和 Function。Hub 是集线器,用于总线的扩展,提供更多的 USB接口。Function 是 USB 功能设备,也就是常见的 USB 外设,后面讨论的 USB 设备驱动也是针对这个 Function 而言的。
USB2.0 规定,除主机外,下面连接的 USB 设备层数最多为 6 层。每个USB 设备都有一个唯一的地址,USB 设备地址使用7 位地址,所以地址范围为 0~127。0 地址是一个特殊地址,当一个新的 USB 设备插入到 USB 接口时,使用该地址和主机进行通信,主机随后会分配一个地址给该设备。所以理论上一个 USB 主机最多可以连接 127 个 USB设备。
一个 USB 物理设备由一个或多个 USB 逻辑设备组成,一个USB 逻辑设备体现为-个“接口”,接口又是由一组“端点”所组成,接下来分别进行说明。
按照协议上的描述,端点是可唯一识别的 USB 设备的一部分,它是主机与设备间通信流的一个终点。每个端点都有一个地址,地址为 4 位宽。从主机的角度来定义,端点又有输入 (数据从设备到主机) 和输出(数据从主机到设备) 两个方向,所以一个 USB设备最多有 32 个端点。主机通过设备地址和端点地址来寻址一个 USB 设备上的具体端点。端点 0 是一个特殊的端点,必不可少,主要用于 USB 设备的枚举。USB 设备内部存在着大量的资源信息,当一个 USB 设备接入到 USB 主机后,USB 主机会主动获取这些信息,这时用到的就是端点 0。
接口是逻辑上的概念,它是若千个端点的集合,用于实现某一具体功能,如果一个USB 设备有多个接口,那么它就是一个多功能设备。
除此之外,还有一个配置的概念,它是多个接口的集合,同一时刻只能有一个配置有效。最终,多个配置构成了一个 USB 设备。大多数 USB 设备只有一个配置和一个接口。有了上面的概念后,再来理解 USB 的通信流就很容易了。
如下图所示,主机(Host) 上的客户软件(Client Software) 通过缓冲区(Bufer)和一个USB 逻辑设备 (USB Logical Device) 的一个接口 (Interface) 中的某一端点进行数据传输,客户软件的缓冲区和端点之间的通信就构成了一个管道 (Pipe),传输的类型分为以下四种。
(1) 控制传输:突发的、非周期性的、用于请求/响应的通信。主要用于命令和状态的操作,如前面提到的枚举过程中的数据传输。只有端点 0 默认用于控制传输,所以端点 0 也叫作控制端点(通常用于什么传输的端点就叫什么端点,如用于控制传输的端点就叫控制端点)。USB 协议定义了很多标准的命令(请求)以及这些命令的响应,这些数据就是通过控制传输来完成的。另外,USB 协议允许厂商自定义命令,也用控制传输来完成。
(2) 等时传输: 有的也叫作同步传输,用于主机和设备之间周期性、连续的通信,通常用于对时间性要求高,但不太关心数据正确性的场合,比如音频数据的传输,如果传输速率不能满足要求,声音会出现停顿,但少量的数据错误,并不会太影响声音所提供的信息。
(3) 中断传输:周期性的、确保延迟不超过一个规定值的传输。这里的中断并不是我们之前所说的中断,其更像是轮询。比如对于 100ms 周期的中断传输,主机会保证在100ms 内发起一次传输。键盘、鼠标等设备通常使用这种传输模式,主机会定期获取设备的按键信息。
(4) 块传输:也叫批量传输,非周期性的大数据传输。主要用于大量数据的传输且对传输的延时不特别限制的情况,比如磁盘设备等。
最后还要说明的是,协议对各种传输的最大包长都做了规定,以全速模式为例,控制传输的最大包长为 64字节,等时传输为 1023 字节,中断传输为 64字节,块传输可以在8、16、32、64字节中选择。所以一个数据大于最大包长,都要分几次来传输。另外普备厂商的各端点的最大包长还要根据具体的设备来定,它可能还会小于协议中规定的易大包长,不过这些信息都可以在枚举阶段获得。
二、 Linux USB 驱动
Linux 的 USB 驱动层次结构和前面讲解的 I2C 和 SPI都差不多,也分为主机控制器驱动和设备驱动,因为主机控制器驱动通常由 SoC 生产厂商负责实现,所以下面只讨论USB 设备驱动。我们前面说过,一个 USB 逻辑设备体现为一个接口,Linux 中代表接口的结构是
struct usb_interface,里面有一个成员 cur_altsetting,指向了主机侧对接口的描述结构 struct usb_host_interface,该结构包含了接口中所包含的端点个数和各端点的配置描述符的详细信息。这些信息是在一个 USB 设备接入到主机时,在枚举过程中获得的。这些结构的内容都比较多,在此不一一列出,在实例代码中将会对用到的成员做相应的说明。
接下来看看代表 USB 设备驱动的结构,其定义如下(只列出了驱动开发者关心的成员)。
struct usb_driver {const char *name;int (*probe) (struct usb_interface *intf, const struct usb_device_id *id);
......void (*disconnect) (struct usb_interface *intf);int (*suspend) (struct usb_interface *intf, pm_message_t message);int (*resume) (struct usb_interface *intf);
......const struct usb_device_id *id_table;
......
};
name:驱动的名字,应该在整个 USB 驱动中唯一,且应该和模块的名字一致。
probe: 驱动用于探测接口 intf 是否是要驱动的接口,返回0表示接口和驱动绑定成功。
disconnect:在驱动卸载或设备拔掉的时候调用。
suspend、resume:用于电源管理。
id_table: 驱动支持的 USB 设备 ID 列表。USB 设备内部保存了厂商ID 和设备 ID,在枚举的过程中会获得这些信息,USB 总线驱动用获得的信息来匹配 USB 驱动中的ID表,如果匹配则会调用驱动中的 probe 函数来进一步对接口进行绑定。
与 USB 设备驱动和接口相关的函数原型或宏如下。
usb_register(driver)
void usb_deregister(struct usb_driver *);
struct usb_device *interface_to_usbdev(struct usb_interface *intf);
void usb_set_intfdata(struct usb_interface *intf, void *data);
void *usb_get_intfdata(struct usb_interface *intf);
usb_register:注册 USB 设备驱动 driver。
usb_deregister: 注销 USB 设备驱动。
interface_to_usbdev: 通过接口 intf 返回包含的USB 设备结构 struct usb _device 对象
指针。
usb_set_intfdata:保存 data 到接口 intf 中。
usb_get_intfdata: 从接口 intf 中获取之前保存的数据指针。前面讲过,主机客户软件和设备端点之间是通过管道来通信的,驱动从接口中获取端点信息 (包括地址、类型和方向) 后就可以来构造这个管道,相应的宏如下。
usb_sndctrlpipe(dev, endpoint)
usb_rcvctrlpipe(dev, endpoint)
usb_sndisocpipe(dev, endpoint)
usb_rcvisocpipe(dev, endpoint)
usb_sndbulkpipe(dev, endpoint)
usb_rcvbulkpipe(dev, endpoint)
usb_sndintpipe(dev, endpoint)
usb_rcvintpipe(dev, endpoint)
另外,还可以通过下面的函数来获取管道的最大包长。
__ul6 usb_maxpacket(struct usb_device *udev, int pipe, int is_out);
有了管道之后,驱动就可以和 USB 设备的端点进行通信了。Linux 中用 struct urb 来和 USB 设备的端点进行通信,这类似于I2C 总线或 SPI 总线中的消息。该结构的成员比较多,在此就不一一列出了。围绕 struct urb 的主要函数如下。
struct urb *usb_alloc_urb(int iso_packets, gfp_t mem_flags);
void usb_free_urb(struct urb *urb);
int usb_submit_urb(struct urb *urb, gfp_t mem_flags);
int usb_unlink_urb(struct urb *urb);
void usb_kill_urb(struct urb *urb);
void usb_fill_control_urb(struct 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);
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);
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);
usb_alloc_urb;动态分配一个 struct urb 结构对象,iso_packets 是该 URB 用于等时传输包的个数,不用于等时传输则为 0。mem_flags 是内存分配的掩码。
usb_free_urb;释放 URB。
usb_submit_urb:提交一个 URB,发起USB 传输
usb_unlink_urb: 撤销一个提交的 URB,并不等待 URB 被终止后才返回,用于不能休眠的上下文中。
usb_kill_urb: 撤销一个提交的 URB,等待 URB 被终止后才返回。
usb fill_control_urb:填充一个用于控制传输的URB,urb 是待填的 urb 对象指针dev是要通信的 USB 设备对象指针,pipe 是用于通信的管道,stup_packet是协议的建立包的地址,transfer_buffer 是传输数据的缓冲区地址,bufer_length 是传输数据的缓冲区长度,complete_fn 指向 URB 完成后的回调函数,context 指向可被 USB 驱动程序设置的数据块。
usb_fill_bulk_urb 和 usb_fill_int_urb 分别用于填充块传输URB 和中断传输URB,参数的含义和上面的一致,其中中断传输的 interval 参数用于指定中断传输的时间间隔。
USB 的传输通常使用下面的步骤来进行。
(1) 使用 usb_alloc_urb 来分配一个URB。
(2) 根据传输的类型来填充一个 URB。等时传输没有相应的函数,需要手动来实现(3)使用usb_submit_urb 来提交一个URB 来发起传输。
(4)用完成量或等待队列来等待一个 URB 的完成。
(5)URB 传输完成后,完成回调函数被调用,在这里唤醒等待的进程。
(6)进程被唤醒后检查 URB 的执行结果,包括状态信息和实际完成的传输字节数等(7)如果中途需要撤销 URB,则使用 usb_unlink_urb 或usb_kill_urb。
(8)不使用 URB 可以通过 usb_free_urb 来释放。
一个分配了的 URB 可以多次使用,不需要每次分配,但要在提交前重新填充。URB的完成状态通过 status 成员来获取,实际完成的传输字节数通过 actual_length 成员来获取。
使用 URB 来完成 USB 传输可以做到比较精细的控制,但是使用比较复杂。Liux 内核封装了一些方便使用的函数,主要如下。
int usb_control_msg(struct usb_device *dev, unsigned int pipe, __u8 request, __u8 requesttype, __u16 value, __ul6 index, void *data, __ul6 size, int timeout);
int usb_interrupt_msg(struct usb_device *usb_dev, unsigned int pipe, void *data,int len, int *actual_length, int timeout);
int usb_bulk_msg(struct usb_device *usb_dev, unsigned int pipe, void *data, int len, int *actual_length, int timeout);
usb_control_msg: 用于发起控制传输。dev 是要通信的 USB 设备对象指针,pipe 是用于通信的管道,request 是协议中的请求字段,requesttype 是请求的类型,value 和 index也是协议中对应的字段。data 是指向缓冲区的指针,size 是缓冲区数据大小,timeout 是
usb_interrupt_msg 和usb_bulk_msg 分别用于发起中断传输和块传输。actual_length超时值。是实际完成的传输字节数。
三、 USB 设备驱动实例
开发板上有一片 STC89C52RC 单片机和一片 PDIUSBD12 USB 设备侧的接口芯片。PDIUSBD12 除控制端点外还有 4 个端点,本书使用开发板配套源码中的自定义设备,这4 个端点分别是中断输入(端点地址 0x81)、中断输出(端点地址 0x01)、批量输入(端点地址 0x82) 和批量输出 (端点地址 0x02)。设备应用层的通信协议如下。
(1) 中断输入端点用于返回 8 个按键的值和按键按下、释放的计数值,长度为 8个字节,每个字节的含义参见后面的代码。
(2) 中断输出端点用于控制 8个 LED 灯的亮灭,长度为 8 个字节,每个字节的含义参见后面的代码。
(3) 批量输入端点用于返回串口收到的数据。
(4) 批量输出端点用于发送数据给串口,也就是说,批量输入端点和批量输出端点完成了 USB 和串口数据的透传。
该 USB 设备的 Linux 驱动代码如下。为了尽量突出 USB 驱动的核心,并没有加入并发控制相关的代码。另外,设紧的次设备号是动态增加的,所以设备拔掉后再插入次设备号会变化,这也是为了简化代码。
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/module.h>#include <linux/cdev.h>
#include <linux/fs.h>
#include <linux/usb.h>
#include <linux/slab.h>
#include <linux/sched.h>
#include <linux/types.h>
#include <linux/errno.h>
#include <linux/uaccess.h>#include "pdiusbd12.h"#define PDIUSBD12_MAJOR 256
#define PDIUSBD12_MINOR 10
#define PDIUSBD12_DEV_NAME "pdiusbd12"struct pdiusbd12_dev {int pipe_ep1_out;int pipe_ep1_in;int pipe_ep2_out;int pipe_ep2_in;int maxp_ep1_out;int maxp_ep1_in;int maxp_ep2_out;int maxp_ep2_in;struct urb *ep2inurb;int errors;unsigned int ep2inlen;unsigned char ep1inbuf[16];unsigned char ep1outbuf[16];unsigned char ep2inbuf[64];unsigned char ep2outbuf[64];struct usb_device *usbdev;wait_queue_head_t wq;struct cdev cdev;dev_t dev;
};static unsigned int minor = PDIUSBD12_MINOR;static int pdiusbd12_open(struct inode *inode, struct file *filp)
{struct pdiusbd12_dev *pdiusbd12;pdiusbd12 = container_of(inode->i_cdev, struct pdiusbd12_dev, cdev);filp->private_data = pdiusbd12;return 0;
}static int pdiusbd12_release(struct inode *inode, struct file *filp)
{struct pdiusbd12_dev *pdiusbd12;pdiusbd12 = container_of(inode->i_cdev, struct pdiusbd12_dev, cdev);usb_kill_urb(pdiusbd12->ep2inurb);return 0;
}void usb_read_complete(struct urb * urb)
{struct pdiusbd12_dev *pdiusbd12 = urb->context;switch (urb->status) {case 0:pdiusbd12->ep2inlen = urb->actual_length;break;case -ECONNRESET:case -ENOENT:case -ESHUTDOWN:default:pdiusbd12->ep2inlen = 0;break;}pdiusbd12->errors = urb->status;wake_up_interruptible(&pdiusbd12->wq);
}static ssize_t pdiusbd12_read(struct file *filp, char __user *buf, size_t count, loff_t *f_ops)
{int ret;struct usb_device *usbdev;struct pdiusbd12_dev *pdiusbd12 = filp->private_data;count = count > sizeof(pdiusbd12->ep2inbuf) ? sizeof(pdiusbd12->ep1inbuf) : count;ret = count;usbdev = pdiusbd12->usbdev;usb_fill_bulk_urb(pdiusbd12->ep2inurb, usbdev, pdiusbd12->pipe_ep2_in, pdiusbd12->ep2inbuf, ret, usb_read_complete, pdiusbd12);if (usb_submit_urb(pdiusbd12->ep2inurb, GFP_KERNEL))return -EIO;interruptible_sleep_on(&pdiusbd12->wq);if (pdiusbd12->errors)return pdiusbd12->errors;else {if (copy_to_user(buf, pdiusbd12->ep2inbuf, pdiusbd12->ep2inlen))return -EFAULT;elsereturn pdiusbd12->ep2inlen;}
}static ssize_t pdiusbd12_write(struct file *filp, const char __user *buf, size_t count, loff_t *f_ops)
{int len;ssize_t ret = 0;struct pdiusbd12_dev *pdiusbd12 = filp->private_data;count = count > sizeof(pdiusbd12->ep2outbuf) ? sizeof(pdiusbd12->ep2outbuf) : count;if (copy_from_user(pdiusbd12->ep2outbuf, buf, count))return -EFAULT;ret = usb_bulk_msg(pdiusbd12->usbdev, pdiusbd12->pipe_ep2_out, pdiusbd12->ep2outbuf, count, &len, 10 * HZ);if (ret)return ret;elsereturn len;
}long pdiusbd12_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
{int ret;int len;struct pdiusbd12_dev *pdiusbd12 = filp->private_data;if (_IOC_TYPE(cmd) != PDIUSBD12_MAGIC)return -ENOTTY;switch (cmd) {case PDIUSBD12_GET_KEY:ret = usb_interrupt_msg(pdiusbd12->usbdev, pdiusbd12->pipe_ep1_in, pdiusbd12->ep1inbuf, 8, &len, 10 * HZ);if (ret)return ret;else {if (copy_to_user((unsigned char __user *)arg, pdiusbd12->ep1inbuf, len))return -EFAULT;elsereturn 0;}break;case PDIUSBD12_SET_LED:if (copy_from_user(pdiusbd12->ep1outbuf, (unsigned char __user *)arg, 8))return -EFAULT;ret = usb_interrupt_msg(pdiusbd12->usbdev, pdiusbd12->pipe_ep1_out, pdiusbd12->ep1outbuf, 8, &len, 10 * HZ);if (ret)return ret;elsereturn 0;default:return -ENOTTY;}return 0;
}static struct file_operations pdiusbd12_ops = {.owner = THIS_MODULE,.open = pdiusbd12_open,.release = pdiusbd12_release,.read = pdiusbd12_read,.write = pdiusbd12_write,.unlocked_ioctl = pdiusbd12_ioctl,
};int pdiusbd12_probe(struct usb_interface *intf, const struct usb_device_id *id)
{static struct pdiusbd12_dev *pdiusbd12;struct usb_device *usbdev;struct usb_host_interface *interface;struct usb_endpoint_descriptor *endpoint;int ret = 0;pdiusbd12 = kmalloc(sizeof(struct pdiusbd12_dev), GFP_KERNEL);if (!pdiusbd12)return -ENOMEM;usbdev = interface_to_usbdev(intf);interface = intf->cur_altsetting;if (interface->desc.bNumEndpoints != 4) {ret = -ENODEV;goto out_no_dev;}/* EP1 Interrupt IN */endpoint = &interface->endpoint[0].desc;if (!(endpoint->bEndpointAddress & 0x80)) { /* IN */ret = -ENODEV;goto out_no_dev;}if ((endpoint->bmAttributes & 0x7F) != 3) { /* Interrupt */ret = -ENODEV;goto out_no_dev;}pdiusbd12->pipe_ep1_in = usb_rcvintpipe(usbdev, endpoint->bEndpointAddress);pdiusbd12->maxp_ep1_in = usb_maxpacket(usbdev, pdiusbd12->pipe_ep1_in, usb_pipeout(pdiusbd12->pipe_ep1_in));/* EP1 Interrupt Out */endpoint = &interface->endpoint[1].desc;if (endpoint->bEndpointAddress & 0x80) { /* OUT */ret = -ENODEV;goto out_no_dev;}if ((endpoint->bmAttributes & 0x7F) != 3) { /* Interrupt */ret = -ENODEV;goto out_no_dev;}pdiusbd12->pipe_ep1_out = usb_sndintpipe(usbdev, endpoint->bEndpointAddress);pdiusbd12->maxp_ep1_out = usb_maxpacket(usbdev, pdiusbd12->pipe_ep1_out, usb_pipeout(pdiusbd12->pipe_ep1_out));/* EP2 Bulk IN */endpoint = &interface->endpoint[2].desc;if (!(endpoint->bEndpointAddress & 0x80)) { /* IN */ret = -ENODEV;goto out_no_dev;}if ((endpoint->bmAttributes & 0x7F) != 2) { /* Bulk */ret = -ENODEV;goto out_no_dev;}pdiusbd12->pipe_ep2_in = usb_rcvintpipe(usbdev, endpoint->bEndpointAddress);pdiusbd12->maxp_ep2_in = usb_maxpacket(usbdev, pdiusbd12->pipe_ep2_in, usb_pipeout(pdiusbd12->pipe_ep2_in));endpoint = &interface->endpoint[3].desc;if (endpoint->bEndpointAddress & 0x80) { /* OUT */ret = -ENODEV;goto out_no_dev;}if ((endpoint->bmAttributes & 0x7F) != 2) { /* Bulk */ret = -ENODEV;goto out_no_dev;}pdiusbd12->pipe_ep2_out = usb_sndintpipe(usbdev, endpoint->bEndpointAddress);pdiusbd12->maxp_ep2_out = usb_maxpacket(usbdev, pdiusbd12->pipe_ep2_out, usb_pipeout(pdiusbd12->pipe_ep2_out));pdiusbd12->ep2inurb = usb_alloc_urb(0, GFP_KERNEL);pdiusbd12->usbdev = usbdev;usb_set_intfdata(intf, pdiusbd12);pdiusbd12->dev = MKDEV(PDIUSBD12_MAJOR, minor++);ret = register_chrdev_region (pdiusbd12->dev, 1, PDIUSBD12_DEV_NAME);if (ret < 0)goto out_reg_region;cdev_init(&pdiusbd12->cdev, &pdiusbd12_ops);pdiusbd12->cdev.owner = THIS_MODULE;ret = cdev_add(&pdiusbd12->cdev, pdiusbd12->dev, 1);if (ret)goto out_cdev_add;init_waitqueue_head(&pdiusbd12->wq);return 0;out_cdev_add:unregister_chrdev_region(pdiusbd12->dev, 1);
out_reg_region:usb_free_urb(pdiusbd12->ep2inurb);
out_no_dev:kfree(pdiusbd12);return ret;
}void pdiusbd12_disconnect(struct usb_interface *intf)
{struct pdiusbd12_dev *pdiusbd12 = usb_get_intfdata(intf);cdev_del(&pdiusbd12->cdev);unregister_chrdev_region(pdiusbd12->dev, 1);usb_kill_urb(pdiusbd12->ep2inurb);usb_free_urb(pdiusbd12->ep2inurb);kfree(pdiusbd12);
}static struct usb_device_id id_table [] = {{ USB_DEVICE(0x8888, 0x000b) }, { }
};
MODULE_DEVICE_TABLE(usb, id_table);static struct usb_driver pdiusbd12_driver =
{.name = "pdiusbd12",.id_table = id_table,.probe = pdiusbd12_probe,.disconnect = pdiusbd12_disconnect,
};module_usb_driver(pdiusbd12_driver);MODULE_LICENSE("GPL");
MODULE_AUTHOR("name <e-mail>");
MODULE_DESCRIPTION("PDIUSBD12 driver");
#ifndef _PDIUSBD12_H
#define _PDIUSBD12_Hstruct d12key {unsigned char key[8];
};struct d12led {unsigned char led[8];
};#define PDIUSBD12_MAGIC 'p'#define PDIUSBD12_GET_KEY _IOR(PDIUSBD12_MAGIC, 0, struct d12key)
#define PDIUSBD12_SET_LED _IOW(PDIUSBD12_MAGIC, 1, struct d12led)#endif
代码中 struct pdiusbd12_dev 结构中的成员包含了 4 个端点对应的管道和4个端点的最大包大小,ep2inurb 是输入端点 2 使用的 urb,用于演示 urb 的使用。errors 和 ep2inler是传输完成后端点的状态信息和实际得到的字节数,接下来是 4 个端点使用的缓冲区wq 是等待队列头,用于等待输入端点 2 的传输完成。
代码第 279 行至第 293 行是USB 驱动的定义和相应的注册、注销,id_table中的0x8888和 0x000b 是设备的厂商ID 和设备 ID,这在 USB 设备插入后可以通过 lsusb 命令查看。在 pdiusbd12_probe 函数中,使用 interface_to_usbdev 通过传入的接口 intf 来获取包含该接口的 USB 设备对象指针,该对象指针在后面的 USB 传输中将会多次使用。代码第 190 行至第 200 行,将第一个端点(中断输入端点 1) 的信息获取到,然后判断其端点的方向和端点的类型是否正确,如果正确则使用 usb_rcvintpipe 创建一个管道,使用usb_maxpacket 来获取端点的最大允许的包大小。代码第 201 行到第 238 行,使用同样的方法来创建另外 3 个节点对应的管道。代码第 240 行使用 usb_alloc_urb 分配了一个批量输入端点 2 使用的 URB,接下来保存了 USB 设备对象指针并将指针 pdiusbd12 使用usb_set_intfdata 保存到了接口 intf 中。
pdiusbd12_read 函数首先调整了读取的字节数,然后使用了 usb_fill_bulk_urb 来填充URB,并指定回调函数为 usb_read_complete,接下来使用 usb_submit_urb 来提交URB并使用interruptible_sleep_on 来等待回调函数的唤醒。URB 传输完成后,usb_read_complete函数被调用,在该函数中获取了传输的状态和实际传输的字节数,然后使用wake_up_interruptible 唤醒了读进程。读进程则根据状态来决定是否复制数据,并返回错误码或实际读取到的字节数。
pdiusbd12_write 函数要简单一些,它首先将用户的数据复制到输出端点 2 的缓冲区中,然后使用 usb_bulk_msg 发起了一次传输。函数返回后,len 变量中存放了实际发送的字节数,10* HZ 则指定了超时时间为 10 秒。
pdiusbd12_ioctl 函数和 pdiusbd12_write 函数使用的方法类似,使用了 usb_interrupt_msg发起了中断传输,分别获取了按键值和对设备写入数据,控制 LED 灯的亮灭。测试代码如下。
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/ioctl.h>
#include <fcntl.h>
#include <errno.h>#include "pdiusbd12.h"int main(int argc, char *argv[])
{int fd;int ret;unsigned char key[8];unsigned char led[8];unsigned int count, i;fd = open("/dev/pdiusbd12", O_RDWR);if (fd == -1)goto fail;while (1) {ret = ioctl(fd, PDIUSBD12_GET_KEY, key);if (ret == -1) {if (errno == ETIMEDOUT)continue;elsegoto fail;}switch (key[0]) {case 0x00:puts("KEYn released");break;case 0x01:puts("KEY2 pressed");break;case 0x02:puts("KEY3 pressed");break;case 0x04:puts("KEY4 pressed");break;case 0x08:puts("KEY5 pressed");break;case 0x10:puts("KEY6 pressed");break;case 0x20:puts("KEY7 pressed");break;case 0x40:puts("KEY8 pressed");break;case 0x80:puts("KEY9 pressed");break;}if (key[0] != 0) {led[0] = key[0];ret = ioctl(fd, PDIUSBD12_SET_LED, key);if (ret == -1)goto fail;}}
fail:perror("usb test");exit(EXIT_FAILURE);
}
上面的测试代码只实现了中断端点的测试,使用 PDIUSBD12_GET_KEY 命令将会触发底层的驱动发起中断输入传输,当有按键按下时,ioctl 函数将会返回按键信息,否则在 10 秒后超时,如果超时则重新进行下一次循环。得到的按键信息存放在第一个字节,每个按键对应一个比特位,按键按下,相应的比特位置 1,其他的字节可以忽略。使用PDIUSBD12_SET_LED 命令可以发起中断输出传输,8 个字节只有第一个字节有效,第一个字节的每一个比特位控制一个 LED 灯,为 1 则点亮,为 0 则熄灭,这样刚好可以用按键值去控制 LED 灯的亮灭。所以程序实现的功能是: 按下一个按键后,一个对应的 LED灯被点亮。
下面是编译和测试的命令。
如果要测试端点 2,首先将 USB 设备上的串口接在主机上,用串口终端软件打开这个串口,波特率为 9600。然后用 echo 命令向 USB 设备写入数据,数据就会通过串口发送给串口终端软件显示。用 cat 命令读 USB 设备,则在串口终端上输入的数据就会被 cat命令读回并显示。
相关文章:

Linux驱动开发——USB设备驱动
目录 一、 USB 协议简介 二、 Linux USB 驱动 三、 USB 设备驱动实例 一、 USB 协议简介 USB(Universal Serial Bus,通用串行总线)正如它的名字一样,是用来连接PC外设的一种通用串行总线,即插即用和易扩展是它最大的特点。所谓即插即用&am…...

微服务使用指南
微服务使用指南 1.初识微服务 微服务可以认为是一种分布式架构的解决方案,提供服务的独立性和完整性,做到服务的高内聚、低耦合。 目前服务架构主要包含:单体架构和分布式架构。 1.1 单体架构 单体架构:把所有业务功能模块都…...

MYSQL运维篇(已完结)
一、日志 1. 错误日志 2. 二进制日志 😎 介绍 😎 日志格式 😎 日志查看 😎 日志删除 3. 查询日志 4. 慢查询日志 二、主从复制 1. 概述 2. 原理 3. 搭建 4. 总结 三、分库分表 1. 介绍 🍤 问题分析 🍤…...

MapReduce性能优化之小文件问题和数据倾斜问题解决方案
文章目录 MapReduce性能优化小文件问题生成SequenceFileMapFile案例 :使用SequenceFile实现小文件的存储和计算 数据倾斜问题实际案例 MapReduce性能优化 针对MapReduce的案例我们并没有讲太多,主要是因为在实际工作中真正需要我们去写MapReduce代码的场…...

面向萌新的数学建模入门指南
时间飞逝,我的大一建模生涯也告一段落。感谢建模路上帮助过我的学长和学姐们,滴水之恩当涌泉相报,写下这篇感想,希望可以给学弟学妹们一丝启发,也就完成我的想法了。拙劣的文笔,也不知道写些啥,…...

基于 golang 从零到一实现时间轮算法 (二)
Go实现单机版时间轮 上一章介绍了时间轮的相关概念,接下来我们会使用 golang 标准库的定时器工具 time ticker 结合环状数组的设计思路,实现一个单机版的单级时间轮。 首先我们先运行一下下面的源码,看一下如何使用。 https://github.com/x…...

【系统架构设计】架构核心知识: 5 系统安全性与保密性设计
目录 一 信息安全基础 1 信息安全的基本要素 2 信息安全的范围 3 网络安全...

无人零售奶柜:革新牛奶购买体验
无人零售奶柜:革新牛奶购买体验 无人零售奶柜的投放地点覆盖了社区、写字楼等靠近居民的场所,大大提升了消费者购买牛奶的体验。这一创新不仅令消费者能够享受到与电商平台相媲美的直供价格优势,还让他们能够购买更多、更丰富的知名品牌牛奶。…...

【Mybatis小白从0到90%精讲】15: Mybatis配置打印SQL日志
文章目录 前言配置日志实现前言 日志(Log)是每个程序都不可或缺的一部分,它可以帮助开发人员诊断和调试问题。Mybatis,作为一款备受赞誉的ORM框架,自然也提供了强大的日志功能。 它不仅提供了内置的标准实现,还支持集成各种主流的日志框架,让我们可以轻松地查看最终执行…...

vue3-video-play视频播放组件
安装: npm i vue3-video-play --save使用说明: https://codelife.cc/vue3-video-play/guide/install.html...

vue项目中页面遇到404报错
vue页面访问正常,但是一刷新就会404的问题解决办法: 1.解决方法: 将vue的路由模式 mode: history 修改为 mode: hash模式 //router.js文件 const router new Router({//mode: history, mode: hash,routes: [{ path: /, redirect: /login …...

快手直播弹幕websocket protobuf序列化与反序列化
系列文章目录 websocket训练地址:https://www.qiulianmao.com,正在搭建中 基础-websocket逆向基础-http拦截基础-websocket拦截基础-base64编码与解码基础-python实现protobuf序列化与反序列化基础-前端js实现protobuf序列化与反序列化基础-protobufjs实现protobuf序列化与反…...

viple入门(三)
(1)条件循环活动 条件循环活动中,必须给定条件,条件成立,则执行条件循环的后续程序。 条件不成立,则不执行后续程序。 从报错信息来看,程序提示:条件循环要和结束循环活动一起使用。…...

Vue渲染函数渲染html
版本 vue2.6 使用 domProps属性 domProps: {innerHTML: xxx},官方文档...

Odoo|“视图”和“模型”之间的数据传输
01前言 今天带领大家学习Odoo系统中“视图”与“模型”之间的数据传输。看题目我们可以知道,这篇文章是面向的是Odoo的初学者。Odoo作为当前最普遍的二开ERP系统,其开源,模块化,灵活开发的属性使得它在ERP相关领域十分受青睐。 …...

Electron进程通信的另一种方式
上一篇讲述了主进程和渲染进程之间的通信,其中是通过调用 ipcMain 和 ipcRenderer 来完成的。比如渲染进程给主进程发送一个消息,然后主进程再返回一个消息给渲染进程: 主进程的逻辑: ipcMain.on(selectDate,(e,date)>{conso…...

二次型的相关理解
...

Spring框架中用于注入构造函数参数的标签constructor-arg
一、constructor-arg的介绍 constructor-arg是Spring框架中用于注入构造函数参数的标签,它可以用于实现依赖注入的方式之一。在实际开发中,我们通常会在Spring配置文件中声明bean的时候使用constructor-arg标签注入构造函数参数。 constructor-arg标签有…...

spdlog简单介绍和使用
spdlog 是一个C的快速、可扩展的日志库,具有高性能和友好的接口。它支持多种日志输出目标,如控制台、文件、远程服务器等,并具有强大的日志格式化和异步日志记录功能。 以下是对spdlog的简单介绍和使用示例: 安装 spdlog 你可以…...

分类模型的Top 1和Top 5
分类模型的Top 1和Top 5 flyfish 模型分类的结果指标如下图 acc top1 和 acc top5这两列 关于Top 1和Top 5分两种 top 1 accuracy top 5 accuracy 和 top 1 error rate top 5 error rate 这里将需要评估的分类器称为模型 假如一共要测试N张图像,一共有1000个类…...

LinkdeList集合
1.LinkdeList集合的特点: 1.LinkedList是集合的一个实现类 2.LinkedList内部封装了一个双向链表 3.LinkedList集合的增删快,查询慢 4.线程不安全 2.LinkedList集合的方法 1.添加 1.boolean add(Object element) 将元素附加到链表末尾 2.boolean add(int…...

KaiOS APN配置文件apn.json调试验证方法(无需项目全编)
1、KaiOS 的应用就类似web应用,结合文件夹路径webapp字面意思理解。 2、KaiOS APN配置文件源代码在apn.json, (1)apn.json可以自定义路径,通过配置脚本实现拷贝APN在编译时动态选择路径在机器中生效。 (…...

【qemu逃逸】HWS2017-FastCP
前言 虚拟机用户名:root 虚拟机密码:无密码 本题有符号,所以对于设备定位啥的就不多说了,直接逆向设备吧。 设备逆向 在 realize 函数中设置一个时钟任务,并且可以看到只注册了 mmio,大小为 0x100000。…...

调节扬声器和麦克风的音量
const audioSrc require("./../../src/assets/music.mp3")// 调节扬声器音量switchYSQColumn(){//使用Audio对象创建一个新的音频元素const audioElement new Audio();//将音频元素的src属性设置为音频文件的urlaudioElement.src audioSrc;audioElement.play()//创…...

XShelll-修改快捷键-xftp-修改编辑器
文章目录 1.XShelll-修改快捷键2.Xftp-修改文本编辑器3.总结 1.XShelll-修改快捷键 工具>选项 鼠标键盘,右键编辑,新建快捷键。 复制粘贴改成shiftc,shiftv。更习惯一些。 2.Xftp-修改文本编辑器 xftp修改服务器文件默认的编辑器,是记…...

编译原理(1)----LL(1)文法(首符号集,后跟符号集,选择符号集)
一.首符号集(First()) 技巧:找最左边可能出现的终结符 例: 1.First(E) E->T,最左边为T,又因为T->F,最左边为F,F->(E)|i,则最左边为{(,i } 2.First(T):只需要看符号串最左…...

远程仓库地址改变后更换url
请按照以下步骤进行操作: 打开终端或命令提示符,并导航到你的本地仓库目录。运行以下命令,查看当前的远程仓库配置:git remote -v 这将显示当前的远程仓库地址。 如果远程仓库地址显示为192.168.1.178,请使用以下命…...

VR全景如何助力乡村振兴,乡村发展在哪些方面用到VR全景技术
引言: 乡村振兴是当今中国发展的重要战略,也是推动农村经济社会全面发展的关键举措。在这一过程中,虚拟现实(VR)全景技术正逐渐崭露头角,为乡村振兴提供了机遇。 一.VR全景技术的概念和应用 1…...

21.6 Python 构建ARP中间人数据包
ARP中间人攻击(ARP spoofing)是一种利用本地网络的ARP协议漏洞进行欺骗的攻击方式,攻击者会向目标主机发送虚假ARP响应包,使得目标主机的ARP缓存中的IP地址和MAC地址映射关系被篡改,从而使得目标主机将网络流量发送到攻…...

DVWA靶场SQL注入
本次注入的是DVWA靶场的SQL injection 1.判断是字符型注入还是数字型注入,构造SQL语句 1 and 12 由此可以判断出为字符型注入 2.考虑闭合方式,先随便丢一个单引号试试看看报错提示 You have an error in your SQL syntax; check the manual that cor…...