linux驱动(第十七课,USBBUS)

USB也是主从结构的总线。整体呈树状结构。
USBHOST,由它发起传输。
USB设备,分为HUB和FUNCTION。我们所用的外设,就是FUNCTION。
USB树中,最多能有6层。
USB设备地址,是7位的。其中,地址0是一个特殊地址,SFA(special function address)。所以,理论上,USBBUS上,只有127个独立地址。
USB设备,大体上可以分为物理设备和逻辑设备。
一个USB物理设备,可以由一个或者多个逻辑设备组成。
一个逻辑设备,体现为一个或者多个接口(Interface),一个接口则由一组Endpoint组成。每个EP都有一个地址,地址是4位的。从HOST的角度看,EP由分为INPUTEP和OUTPUTEP,注意,INEP和OUTEP的方向定义,是从HOST的角度看的,如果HOST发送数据给设备,那么就是OUTEP,如果设备发送数据给HOST,那么就是INEP。
EP0是一个特殊端点,SFE(special function endpoint)。
用户程序中的Buffer和一个EP之间建立了通信连接后,就构成了一个pipe。
USB传输分为四类:
1)控制传输。突发的,非周期性的,主要用于命令和状态的操作。EP0默认用于控制传输,所以EP0也叫做控制端点。
2)等时传输,也叫做同步传输。周期性,连续的,对实时有要求,但是对数据正确性要求不高的场合,例如音频,如果不能满足实时要求,声音会卡顿,但是如果数据少量错误,声音信号并不会过多受损。
3)中断传输,也叫做间隔传输。通常用于键盘鼠标。少量的数据,穿插在其他的传输业务中间传输。
4)块传输。也叫做批量传输。非周期性,大量数据,主要用于对传输延时要求宽松的情况,比如存储设备。
每一个EP都有自己的传输类型。所以对应的,就有控制端点,等时端点,中断端点,块端点的称谓。

来看看linux中的usb驱动架构。
自底向上,
usb驱动分为USBHOSTDRIVER,USBCORE,USBDEVIECEDRIVER。
类似于I2C和SPI,USB的CORE提供API,为USBDEVICE提供服务。而USBCORE又调用USBHOST的驱动,使用USBHOST的服务。

如果linux管理的硬件平台是HOST,那么就是上述架构了。

但是还有一种情况,linux管理的硬件平台中,不是USBHOSTController,而是USBDEVICEController。那么就是另外一种驱动架构了。
自底向上,
USB驱动分为UDC驱动,GadgetFunctionAPI,GadgetFunctionDriver.

这里,我们讨论第一种,也是最常见的情况,USBHOSTController。

首先来看看linux中的描述符。
struct usb_device是描述USB设备的,但是它是一个控制块。
另外有几个描述块,
struct usb_device_descriptor;
struct usb_config_descriptor;
struct usb_interface_descriptor;
struct usb_endpoint_descriptor;
struct usb_string_descriptor;
之所以他们被成为DB而不是CB,是因为他们的成员都是单纯的数据,他们只用来记录信息。
当用户在SHELL中使用lsusb命令查看usb设备的信息时,显示的就是关联的这些描述符中记录的信息。

来看看UHC的驱动。
linux中,用usb_hcd结构体描述USB主控器。

struct usb_hcd{
	struct usb_bus self;
	
	struct urb* status_urb;
	struct timer_list rh_timer;
	
	struct usb_phy* usb_phy;
	struct phy * phy;
	unsigned long flags;

	const struct hc_driver* driver;
	void* hcd_priv;
	...
};

从中可以看出,usb_hcd中,关联着hc_driver。它是对应的驱动接口。
内核提供了相关的API。
usb_create_hcd,
usb_add_hcd,
usb_remove_hcd,
用来向内核注册注销hcd。

struct hc_driver{
	...
	(*irq)(...);
	(*start)(...);
	(*stop)(...);
	(*urb_enqueue)(...);
	(*urb_dequeue)(...);
	(*add_endpoint)(...);
	(*drop_endpoint)(...);
	...
};

其中有一个关键的函数指针,urb_enqueue,当上层通过usb_submit_urb()提交一个URB请求后,该函数会调用usb_hcd_submit_urb(),最终,会调用hc_driver中的urb_enqueue函数。

usb_hcd中有一个成员,hcd_priv,这是一个通配句柄,关联到hcd的私有数据对象。
(这里简单介绍以下SOD中的这个技巧。
我们会在很多结构体中看到xxxpriv或者xxxprivate_data这样的句柄。被称为私有数据。
实际上,我们并不是用它来存放数据,而是用它来存放一个句柄,作为实体标签。
所谓的私有,也并不是这个对象是结构体的一部分,而是说,这个被标记的对象,是本对象在实际运行时,特别感兴趣,需要经常使用到的对象。
之所以不能作为一个明确的成员,赋予特定的类型,而必须赋予通配类型,是因为这个特别关注的对象,并不一定是某种类型的。
最常见的,就是利用私有数据这个通配句柄,来完成回溯引用,形成环路。或者标记到整个子系统中,最关键的对象上,该对象拥有最强大的索引能力,完成源端引用。
例如,FILE中的private_data,总是被填充为某个衍生设备的句柄。这样,当内核调用操作函数时,会传入FILE的句柄,那么操作函数,就可以利用FILE索引到衍生设备的控制块,从而可以从衍生设备的控制块中获取感兴趣的数据。)

ehci_hcd通常被作为usb_hcd的私有数据而被标记。
drivers/usb/host/ehci-hcd.c文件中,实现了绝大多数EHCI主机驱动的工作。
EHCI驱动,被填充到一个hc_driver中,最终注册到内核中。

static const struct hc_driver ehci_hc_driver = {
	...
	.reset = ehci_setup,
	.start = ehci_run,
	.stop = ehci_stop,
	.shutdown = ehci_shutdown,
	...
};

UHC的驱动,通常由SOC厂商提供,我们并不需要关心。
我们需要关心的是,如何设计USBDEV。
USBDEV,对应于USBBUS上的设备。
在linux中,USBDEV请求内核的USBCORE的服务,而USBCORE则调用UHCDRIVER的服务,在USBBUS上产生操作时序。

linux实现了几类通用的USBDEV的DRIVER,例如:
音频设备,通信设备,HID设备,显示设备,存储设备,电源设备,打印设备,HUB设备等。

大多数的通用的USBDEV,不需要再编写驱动,而特定厂商,特定芯片的驱动,往往也可以参考内核中已有的驱动模板。

类似于I2CBUS,SPIBUS,
USBBUS中的usb_device,也对应有一个usb_driver。

struct usb_driver{
	const char* name;
	...
	(*probe)(...);
	(*disconnect)(...);
	...
	const struct usb_device_id* id_table;
	...
};

主要就是probe和disconnet,当有设备被探测到时,调用probe,用于初始化软硬件资源。当设备拔出时,释放资源。

id_table指向一个usb_device_id数组。
内核提供了一组宏,来生成usb_device_id。
USB_DEVICE(vendor, product),
USB_DEVICE_VER(vendor, product, low, high),
USB_DEVICE_INFO(class, subclass, protocol),
USB_INTERFACE_INFO(class, subclass, protocol)

来看一个实例

static struct usb_device_id id_table[] ={
	{
		USB_DEVICE(VENDOR_ID, PRODUCT_ID)
	},
	{}
};
MODULE_DEVICE_TABLE(usb, id_table);

当USBCORE检测到某个设备的属性和某个驱动的id_table中的属性一致时,驱动的probe就会被调用。在probe里,再完成衍生设备的UADEV的注册,UADEV的DRIVER的注册等。

类似于SPIBUS中的msg,
USBDEV请求内核服务,使用的载子,被定义为URB。URB也类似于NDEV中使用的载子skbuff。

struct urb{
	struct list_head urb_list;
	struct usb_host_endpoint* ep;
	
	unsigned int pipe;
	unsigned int stream_id;

	unsigned char* setup_packet;

	int number_of_packets;
	int interval;
	
	void* transfer_buffer;
	u32 transfer_buffer_length;
	u32 actual_length;
	unsigned int transfer_flags;
	
	usb_complete_t complete;
	void* context;

	int status;
	...
};

从中可以看出,urb内嵌链节,所以可以构成链表。
ep关联到一个EP的控制块。
pipe是EP和用户程序建立的管道的IDNUM。
streamID是EP和用户程序建立的stream的IDNUM。

USBDEV中的每个EP,都处理一个URB队列。
内核提供的操作URB的API如下:
struct urb* usb_alloc_urb(int iso_packets, gfp_t mem_flags);
创建一个urb对象。
void usb_free_urb(struct urb* 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_len, usb_complete_t complete_fn, void* context);
用来初始化填充一个控制型URB。
void usb_fill_int_urb(struct urb* urb, struct usb_device* dev, unsigned int pipe, void* transfer_buffer, int buffer_len, usb_complete_t complete_fn, void* context, int interval)
用来初始化填充一个中断型URB。
void usb_fill_bulk_urb(struct urb* urb, struct usb_device* dev, unsigned int pipe, void* transfer_buffer, int buffer_len, usb_complete_t complete_fn, void* context)
用来初始化填充一个批量型URB。

注意,等时型URB,没有对应的API,所以,等时型URB只能在创建后,手工填充。

int usb_submit_urb(struct urb* urb, gfp_t mem_flags);
提交给USBCORE,USBCORE会根据URB来分配缓冲区。
提交后,当前进程被休眠,使用完成量或者等待队列来同步。
USBHOST的驱动负责处理提交的URB,当URB完成时,USBHOST的驱动通知USBDEV。
URB完成,有三种可能情况。
1)URB成功发送给设备,并返回正确的确认。urb->status ==0
2)URB传输时,发生了错误。urb->status != 0
3)URB在排队时,被USBCORE删除。例如驱动使用了urb_unlink_urb()或者urb_kill_urb()函数。
这三种情况,都会让内核调用“完成回调函数”。
urb->complete(context)。
URB传输完成后,内核会调用“完成回调函数”,通常在Callback里唤醒被阻塞休眠的进程。
当进程被唤醒后,继续执行,会检查URB的status。
整个流程总结如下:
usb_alloc_urb(),==>
usb_fill_xxx_urb(),==>
usb_submit_urb(),==>
urb->complete().

有一些简单的API,完成整个简易的流程。
int usb_bulk_msg(struct usb_device* usb_dev, unsigned int pipe, void*data, int len, int * actual_length, int timeout);
调用这个函数的进程,中间会被休眠,并被唤醒,当函数返回时,如果返回值为0,则调用成功,否则,返回一个负数。
int usb_control_msg(struct usb_device* usb_dev, unsigned int pipe, __u8 request, __u8 request_type, __u16 value, __u16 index, void* data, int len, __u16 size, int timeout);
调用这个函数的进程,中间会被休眠,并被唤醒,当函数返回时,如果返回值为0,则调用成功,否则,返回一个负数。
int usb_interrupt_msg(struct usb_device* usb_dev, unsigned int pipe, void*data, int len, int * actual_length, int timeout);
调用这个函数的进程,中间会被休眠,并被唤醒,当函数返回时,如果返回值为0,则调用成功,否则,返回一个负数。

用户程序和USBDEV的EP之间,是通过pipe来通信的。当USBDRIVER从Interface中获取了EP的信息后,就可以创建管道了。
内核提供了与pipe相关的宏拟函数API。
usb_sndctrlpipe(dev, endpoint)
usb_sndisopipe(dev, endpoint)
usb_sndintpipe(dev, endpoint)
usb_sndbulkpipe(dev, endpoint)
usb_rcvctrlpipe(dev, endpoint)
usb_rcvisopipe(dev, endpoint)
usb_rcvintpipe(dev, endpoint)
usb_rcvbulkpipe(dev, endpoint)

usb_driver中probe函数,应该完成对象创建,填充,并注册到内核等工作。
通常要用到的API有:
void usb_set_intfdata(struct usb_interface* intf, void* data);
设置usb_interface的私有数据。
void* usb_get_intfdata(struct usb_interface* intf);
获取usb_interface的私有数据。
struct usb_device* interface_to_usbdev(struct usb_interface* intf);
从usb_interface中获取关联的usb_device的句柄。

整个驱动分为几个部分:
1)Derived_DEV_CB定义,并实例化。
2)UADEV的DRIVER的实例化,并填充。
3)UADEV的相关驱动操作函数编写,分为机制性函数的编写和事务性函数的编写。
4)USBDRIVER的实例化,并填充,注册到内核中。
5)IDTABLE的实例化,注册到内核中。
6)probe函数编写,在其中注册用户可访问设备(User Accessable Device),如CDEV,BDEV,NDEV等。及其对应的DRIVER。
7)remove函数编写,在其中逆操作。
可以看出,与常规的UADEV的编写相比,多了几个部分,就是与BUSDEV相关的DRIVER。

drivers/usb/usb-skeleton.c中,为我们提供了一个USB驱动的骨架程序。

来看一个具体的实例。

#define PDIUSBD12_MAJOR 256
#define PDIUSBD12_MINOR 10
#define PDIUSBD12_DEV_NAME "pdiusbd12"

struct pdiusbd12_dev{
	struct cdev dev;
	dev_t dev;
	struct usb_device* usbdev;
	
	struct urb* ep2inurb;
	int errors;
	
	wait_queue_head_t * wq;

	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;
	
	unsigned int ep2inlen;
	unsigned char ep1inbuf[16];
	unsigned char ep1outbuf[16];
	unsigned char ep2inbuf[64];
	unsigned char ep2outbuf[64];
};
static struct pdiusbd12_dev* pdiusbd12;
static unsigned int minor = PDIUSBD12_MINOR;


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,
};

static int pidusbd12_open(struct inode* inode, struct file* filp)
{
	struct pdiusbd12_dev* pdiusbd12 = container_of(inode->i_cdev, struct pdiusbd12_dev, cdev);
	filp->private_data = pdiusbd12;
	return 0;
}

static int pidusbd12_release(struct inode* inode, struct file* filp)
{
	struct pdiusbd12_dev* pdiusbd12 = container_of(inode->i_cdev, struct pdiusbd12_dev, cdev);
	usb_kill_urb(pdiusbd12->ep2inurb);
	return 0;
}

static ssize_t pdiusbd12_read(struct file* filp, char __user * buf, size_t count, loff_t* f_ops)
{
	int ret;
	struct pdiusbd12_dev* pdiusbd12 = filp->private_data;
	struct usb_device* usbdev = pdiusbd12->usbdev;
	
	ret = count;
	usb_fill_bulk_urb(pdiusbd12->ep2inurb, usbdev, 
					pdiusbd12->pipe_ep2_in, pdiusbd12->ep2inbuf,
					ret, usb_read_complete, pdiusbd12);

	ret = usb_submit_urb(pdiusbd12->ep2inurb, GFP_KERNEL);
	interruptable_sleep_on(&pdiusbd12->wq);
	
	ret = copy_to_user(buf, pdiusbd12->ep2inbuf, pdiusbd12->ep2inlen);

	return ret;
}

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_write(struct file* filp, const char __user *buf, size_t count, loff_t* f_pos)
{
	int ret;
	int len;
	struct pdiusbd12_dev* pdiusbd12 = filp->private_data;

	ret = copy_from_user(pdiusbd12->ep2outbuf, buf, count);
	
	ret = usb_bulk_msg(pdiusbd12->usbdev, pdiusbd12->pipe_ep2_out, pdiusbd12->ep22outbuf, count, &len, 10*HZ);
	
	return ret;
}

long pdiusbd12_ioctl(struct file* filp, unsigned int cmd, unsigned long arg)
{
	int ret;
	int len;
	
	struct pdiusbd12_dev* pdiusbd12 = filp->private_data;

	switch(cmd){
	case PDIUSBD12_GET_KEY:
		ret = usb_interrupt_msg(pdiusbd12->usbdev, pdiusbd12->pipe_ep1_in, 
								pdiusbd12->ep1inbuf, 8,
								&len, 10*HZ);

		ret = copy_to_user((unsigned char __user *)arg, pdiusbd12->ep1inbuf, len);

		break;
	case PDIUSBD12_SET_LED:
		ret = copy_from_user(pdiusbd12->ep1outbuf, (unsigned char __user *)arg, 8);

		ret = usb_interrupt_msg(pdiusbd12->usbdev, pdiusbd12->pipe_ep1_out,
								pdiusbd12->ep1outbuf, 8
								&len, 10*HZ);
		break;
	default:
		break;
	}
	return 0;
}

static struct usb_driver pdiusbd12_driver = {
	.name = "pdiusbd12",
	.probe = pdiusbd12_probe,
	.disconnect = pdiusbd12_disconnect,
	.id_table = id_table,
};
module_usb_driver(pdiusbd12_driver);

static struct usb_device_id id_table[] = {
	{USB_DEVICE(0x8888, 0x000b)},
	{}
};
MODULE_DEVICE_TABLE(usb, id_table);




int pdiusbd12_probe(struct usb_interface *intf, const struct usb_device_id* id)
{
	struct usb_device* usbdev;
	struct usb_host_interface* interface;
	struct usb_endpoint_descriptor* ep_desc;
	
	int ret = 0;

	pdiusbd12 = kmalloc(sizeof(struct pdiusbd12_dev), GFP_KERNEL);

	usbdev = interface_to_usbdev(intf);
	interface = intf->cur_altsetting;
	
	ep_desc = &interface->endpoint[0].desc;
	pdiusbd12->pipe_ep1_in = usb_rcvintpipe(usbdev, ep_desc->bEndpointAddress);
	pdiusbd12->maxp_ep1_in = usb_maxpacket(usbdev, 
										pdiusbd12->pipe_ep1_in, 
										usb_pipeout(pdiusbd12->pipe_ep1_in));

	ep_desc = &interface->endpoint[1].desc;
	pdiusbd12->pipe_ep1_out = usb_sndintpipe(usbdev, ep_desc->bEndpointAddress);
	pdiusbd12->maxp_ep1_out = usb_maxpacket(usbdev, 
										pdiusbd12->pipe_ep1_out, 
										usb_pipeout(pdiusbd12->pipe_ep1_out));


	ep_desc = &interface->endpoint[2].desc;
	pdiusbd12->pipe_ep2_in = usb_rcvbulkpipe(usbdev, ep_desc->bEndpointAddress);
	pdiusbd12->maxp_ep2_in = usb_maxpacket(usbdev, 
										pdiusbd12->pipe_ep2_in, 
										usb_pipeout(pdiusbd12->pipe_ep2_in));


	ep_desc = &interface->endpoint[3].desc;
	pdiusbd12->pipe_ep2_out = usb_sndbulkpipe(usbdev, ep_desc->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(pdi_usbd12->dev, 1, PDIUSBD12_DEV_NAME);

	cdev_init(&pdiusbd12->cdev, &pdiusbd12_ops);
	pdiusbd12->cdev.owner = THIS_MODULE;
	ret = cdev_add(&pdiusbd12->cdev, pdiusbd12->dev, 1);

	init_waitqueue_head(&pdiusbd12->wq);

	return ;
}

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);

	return;
}

在SHELL中输入命令可以测试。

#make
#make modules_install
#depmod
#modprobe pdiusbd12
#mknod /dev/pdiusbd12 c 256 10 
#gcc -o test test.c
#./test
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值