Linux驱动开发——USB设备驱动

目录

[一、 USB 协议简介](#一、 USB 协议简介)

[二、 Linux USB 驱动](#二、 Linux USB 驱动)

[三、 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 设备驱动的结构,其定义如下(只列出了驱动开发者关心的成员)。

cpp 复制代码
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 设备驱动和接口相关的函数原型或宏如下。

cpp 复制代码
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 中获取之前保存的数据指针。前面讲过,主机客户软件和设备端点之间是通过管道来通信的,驱动从接口中获取端点信息 (包括地址、类型和方向) 后就可以来构造这个管道,相应的宏如下。

cpp 复制代码
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)

另外,还可以通过下面的函数来获取管道的最大包长。

cpp 复制代码
__ul6 usb_maxpacket(struct usb_device *udev, int pipe, int is_out);

有了管道之后,驱动就可以和 USB 设备的端点进行通信了。Linux 中用 struct urb 来和 USB 设备的端点进行通信,这类似于I2C 总线或 SPI 总线中的消息。该结构的成员比较多,在此就不一一列出了。围绕 struct urb 的主要函数如下。

cpp 复制代码
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 内核封装了一些方便使用的函数,主要如下。

cpp 复制代码
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 驱动的核心,并没有加入并发控制相关的代码。另外,设紧的次设备号是动态增加的,所以设备拔掉后再插入次设备号会变化,这也是为了简化代码。

cpp 复制代码
#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;
		else
			return 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;
	else
		return 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;
			else
				return 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;
		else
			return 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");
cpp 复制代码
#ifndef _PDIUSBD12_H
#define _PDIUSBD12_H

struct 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 灯的亮灭。测试代码如下。

cpp 复制代码
#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;
			else
				goto 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命令读回并显示。

相关推荐
XMAIPC_Robot2 分钟前
基于RK3588 ARM+FPGA电火花数控机床控制系统设计,兼顾ethercat软硬件实时
linux·arm开发·人工智能·嵌入式硬件·fpga开发
青梅橘子皮3 分钟前
Linux---进程切换与调度
linux·运维·服务器
底层开发智库9 分钟前
C1-Ultra FVP调试并运行Linux kernel全程记录,硬核演示如何解决启动问题
linux·arm开发·内核·嵌入式·arm
楼兰公子11 分钟前
RK3588** 平台(瑞芯微旗舰 SoC,四核 A76 + 四核 A55,Mali-G610,支持 8K 编解码)整理一份系统性的驱动开发实战指南
驱动开发
承渊政道19 分钟前
Linux系统学习【进程控制:进程创建、终止与等待、进程程序替换、自主shell命令行解释器详解】
linux·服务器·c++·学习·ubuntu·bash·远程工作
Kurisu57524 分钟前
深度拆解:从 Linux 内核 Namespace 与 Cgroups 洞察容器技术的底层本质
java·linux·运维
liulilittle30 分钟前
Linux SS快速诊断命令
linux·运维·智能路由器
晚风吹红霞1 小时前
Linux下的趣味编程 —— 进度条、Git版本控制与GDB调试实战
linux·运维·git
nan madol1 小时前
Rocky Linux 9.5 部署 Percona XtraDB Cluster (PXC) 集群
linux·运维·服务器
zincsweet1 小时前
Linux 命名管道(FIFO)详解:原理分析、源码封装与通信流程图解
linux·服务器·c++·流程图