Linux驱动开发——USB设备驱动-CSDN博客

阿里云国内75折 回扣 微信号:monov8
阿里云国际,腾讯云国际,低至75折。AWS 93折 免费开户实名账号 代冲值 优惠多多 微信号:monov8 飞机:@monov6

目录

一、 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 和 SPIUSB 协议要复杂得多本节从驱动开发者的角度来讨论协议中相关的一些内容。下图是 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:填充一个用于控制传输的URBurb 是待填的 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;
		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");
#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 灯的亮灭。测试代码如下。
 

#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命令读回并显示。

阿里云国内75折 回扣 微信号:monov8
阿里云国际,腾讯云国际,低至75折。AWS 93折 免费开户实名账号 代冲值 优惠多多 微信号:monov8 飞机:@monov6
标签: linux

“Linux驱动开发——USB设备驱动-CSDN博客” 的相关文章