2009年5月12日星期二

Linux下USB驱动基础

作者:Sam (甄峰) sam_code@hotmail.com

USB是主机和外围设备之间的一种连接。USB最初是为了替代各种各样的不同的接口的低速总线而设计的。(例如:串口,并口,键盘连接等)。它以单一类型的总线连接各种不同类型的设备。

USB拓扑机构不是以总线方式的。而是一棵由几个点对点的连接构成的树。连接线由4根电缆组成(电源,地线,两个数据线)

USB主控制器(Host Controller)负责询问每一个USB设备是否有数据需要发送。也就是说:一个USB设备在没有主控制器要求的情况下是不能发送数据的。

USB协议规范定义了一套任何特定类型的设备都可以遵循的标准。如果一个设备遵循该设备,就不需要一个特殊的驱动程序。这些不同的特定类型称之为类(class).例如:存储设备,键盘,鼠标,游戏杆,网络设备等。对于不符合这些类的其他设备。则需要对此设备编写特定driver.



USB设备构成:
Linux Kernel提供了USB Core来处理大部分USB的复杂性。写USB驱动,Sam觉得就是把USB硬件设备和USB Core之间给沟通起来。

USB协议把一个硬件USB设备用以下各个定义勾画出来。

概念一. USB 端点(endpoint)
USB endpoint只能往一个方向传送数据。从主机到设备(输出Endpoint)或从设备到主机(输入Endpoint)。 一个Endpoint可以看作一个单向的管道。

有四种类型Endpoint,他们的区别在于传送数据的方式:
控制Endpoint:
用来控制对USB设备不同部分的访问。他们通常用于配置设备,获取设备信息,发送命令到设备,或者获取设备的状态报告。每个USB设备都有一个名为:Endpoint0的控制Endpoint。USB Core使用该Endpoint0在插入时进行设备的配置。

中断Endpoint:
每当USB主控制器要求设备传输数据时,中断Endpoint就以一个固定的速率来传送少量的数据。
USB Keyboard和Mouse通常使用中断Endpoint。
请注意,中断Endpoint和中断不同,它还是无法主动向USB主控制器发送数据。二是需要等待USB主控制器轮询。

批量Endpoint:
Bulk Endpoint用来传输大批量的数据。USB规范不保证数据能在规定时间内传输完成。但保证数据完整性。通常打印机,存储设备和网络设备使用之。

等时Endpoint:
用来传输大批量数据,但数据是否能够到达,怎无法保证。
通常数据收集设备会使用之。

Sam觉得,其实一个设备有多少个以及什么类型的Endpoint。其实是硬件设备在制造阶段已经定好的。 USB Core只是去读取了这些信息,并把这些信息传送给USB driver.

Linux Kernel中使用struct usb_host_endpoint来描述USB Endpoint。但其实其中的struct usb_endpoint_descriptor才是真正的描述Endpoint的。
struct usb_endpoint_descriptor
{
__u8 bLength;
__u8 bDescriptorType;
__u8 bEndpointAddress;
__u8 bmAttributes;
__le16 wMaxPacketSize;
__u8 bInterval;
__u8 bRefresh;
__u8 bSynchAddress;
} __attribute__ ((packed));

bEndpointAddress:
//此Endpoint USB地址。它还包含了Endpoint方向信息。通过掩码USB_DIR_OUT和USB_DIR_IN判断是输出Endpoint还是输入Endpoint。

bmAttributes;
Endpoint Type,也可以通过掩码:USB_ENDPOINT_XFER_ISOC等判断此Endpoint是中断,等时,控制还是批量Endpoint。

wMaxPacketSize;
该Endpoint一次可以处理的最大字节数。虽然driver可以传送更大的数据,但实际传送时,还是会分割成这个大小。

bInterval:
如果是中断Endpoint,它就是Endpoint的间隔设置。也就是说,中断请求间隔时间。以毫秒为单位。


概念二:接口(Interface)
数个Endpoint被捆绑为一个USB Interface。
一个 USB Interface只对应一个逻辑连接,例如鼠标,键盘或者音频流。一个USB设备可以对应多个Interface。例如Sam见过的鼠标键盘一起的设备,就有2个Interface,一个键盘,一个鼠标。
另外,有些USB扬声器有2个Interface,一个键盘,一个音频流。

注意:每个USB drver只处理一个USB Interface。所以,一个设备也许会对应多个driver.
所以,USB Core在处理USB设备插入时,会针对不同的Interface唤醒它认为合适的driver。并以参数的形式把interface传递给drver.

Linux Kernel使用struct usb_interface来描述USB Interface。但Interface参数照例是其中的usb_interface_descriptor。

struct usb_interface
{
struct usb_host_interface *altsetting;
struct usb_host_interface *cur_altsetting;

unsigned num_altsetting;
struct usb_interface_assoc_descriptor *intf_assoc;
int minor;
enum usb_interface_condition condition;
unsigned is_active:1;
unsigned needs_remote_wakeup:1;
struct device dev;
struct device *usb_dev;
int pm_usage_cnt;
};

struct usb_host_interface *altsetting;
Interface结构体数组,包含了所有可能用于该Interface的可选配置。
struct usb_host_interface *cur_altsetting;
可选配置数
unsigned num_altsetting;
指向altsetting的指针。当前的Active 设置。


概念三:配置
一个或多个USB Interface被捆绑为配置。一个USB设备可以有多个配置,并且可以在多个配置之间切换。
配置:struct usb_host_config
USB设备:usb_device.

综上所述:
1个USB设备有一个或多个配置
1个配置有一个或多个Interface
一个Interface有一个或多个设置
Interface没有或有多个Endpoint



USB URB
Linux Kernel中的USB代码通过 urb(USB 请求快)与所有的USB设备通信。
urb被用来以异步方式从特定的USB设备的特定USB Endpoint上接收数据,或往特定的USB设备的特定USB Endpoint上发送数据。

urb是由USB driver创建的。并分配给特定USB设备的特定Endpoint。并由USB driver提交给USB Core。

一:创建urb.
urb不能在driver中静态的定义。因为这样会破坏USB Core对urb的计数机制。所以必须使用:
usb_alloc_urb函数来创建。
struct urb *usb_alloc_urb(int iso_packets, gfp_t mem_flags)
第一个参数是:等时数据包的数量。
第二个参数是:memory申请类型。

二:初始化urb:
针对中断Endpoint的urb:usb_fill_int_urb()
针对批量Endpoint的urb: usb_fill_bulk_urb()
针对控制Endpoint的urb: usb_fill_control_urb()
针对等时Endpoint的urb: 需要手动初始化。


三:提交urb:
usb_smbmit_urb();
提交urb到USB Core

2009年5月5日星期二

Linux下USB HID device driver研究(二)

作者:Sam(甄峰) sam_code@hotmail.com

在drivers/hid/usbhid/hid-core.c中,有如下语句:
module_init(hid_init);
表明当hid-usb.o(hid-core.o等三个组成)添加入kernel core时,会调用hid_init.


1. hid_init分析:
hid_init首先调用usbhid_quirks_init();
1.1. usbhid_quirks_init() 解析:
其实就是查找insmod 时给的pid,vid参数在quirks列表中是否有,如果有,就替换。没有就创建。

1.2. hiddev_init();
此function只有在选中CONFIG_USB_HIDDEV才会真正做事。
也就是说:只有在配置kernel时选中下面条目才有效.
config USB_HIDDEV
bool "/dev/hiddev raw HID device support"
它只是简单的注册一个USB设备。但这个设备在USB 硬件插入时什么都不作。

1.3 usb_register(&hid_driver);
注册一个USB driver. 从这个driver的id_table来看,只要是hid设备,这个driver就会被唤醒。


2. HID USB设备被插入时的状况:
分析hid_driver->probe
第一个参数为USB Core传过来的USB设备Interface。第二个参数为本driver的id_table.






背景知识一:模块参数:
当使用insmod或modprobe安装模块时,可以通过模块参数给模块传递一些数值。这增加了模块的灵活性。但在使用模块参数之前,必须要让这些参数对insmod可见,则可以使用如下方式,让insmod知道参数名:
module_param_named(name, value, type, perm)
name是参数的名称
value是参数在模块中对应的变量
type是参数的类型
perm是参数的权限(其实就是/sys/module/[module_name]/parameters的权限)

例如:
int disk_size = 1024;
module_param_named(size, disk_size, int, S_IRUGO);
则给模块加上名称为"size"的参数,如果在加载模块是使用
insmod thismodule size=100,
那么在模块代码中disk_size的值就是100。相反,如果加载模块时没有指定参数,那么模块代码中disk_size的值仍是默认的1024。
注意,所有模块参数,都应该给定一个默认值。

Linux下USB HID device driver研究(一)

作者: Sam (甄峰) sam_code@hotmail.com

Sam需要研究一下USB Mouse device driver. 在Kernel 2.4时代,这块东西是由Input,event等配合组成的。而在kernel2.6中。Sam还不知道是如何运作的。

首先介绍HID:
HID是Human Interface Devices的缩写.翻译成中文即为人机交互设备.这里的人机交互设备是一个宏观上面的概念,任何设备,只要符合HID spec,都可以称之为HID设备

在make menuconfig中,选中USB Human Interface Device(full HID) support。则所有USB HID都会被驱动,其中包括USB Mouse。
在drivers/hid/usbhid/Kconfig看到这项对应的为:CONFIG_USB_HID
又在drivers/hid/usbhid/Makefile中看到:obj-$(CONFIG_USB_HID) += usbhid.o
也就是说:如果选中USB Human Interface Device(full HID) support为built-in.则usbhid.o会被built-in。
再在drivers/hid/usbhid/.usbhid.o.cmd中看到usbhid.o其实是由:hid-core.o,hid-quirks.o,hiddev.o组成的。
也就是说:当在Menuconfig中选中那一项后,这三个.o都会被built-in.

下一篇从driver角度学习。

2009年5月4日星期一

Linux kernel 2.6下的modules编译与KBuild

作者: Sam (甄峰) sam_code@hotmail.com


Sam之前在Linux kernel 2.4下写过一些driver.但自从转到kernel 2.6之后,再也没有写过driver.所以很多具体的东西并不清楚。今天看了看文档,觉得变化挺大的。记下来。

在2.4内核中,只要自己写了driver,最多需要kernel头文件来配合编译。通常的CFLAGS为:
=-D__KERNEL__ -I$(KDIR) /include -DMODULE

但在2.6下面,不再是这样。模块的编译需要依赖配置过的内核源码,编译过程首先会到内核源码目标下,读取顶层的Makefile文件
Module编译模板如下:
#Makefile for linux2.6
ifneq ($(KERNELRELEASE),)
# call from kernel build system
scull-objs := main.o pipe.o access.o
obj-m := scull.o
else
KERNELDIR ?= /lib/modules/$(shell uname -r)/build
PWD := $(shell pwd)
modules:
$(MAKE) -C $(KERNELDIR) M=$(PWD)
clean: rm -rf *.o *~ core .depend .*.cmd *.ko *.mod.c .tmp_versions
endif

解释:
KERNELRELEASE:由Kernel顶层Makefile中定义。
第一次运行中,KERNELRELEASE没有被定义,所以进入else
$(MAKE) -C $(KERNELDIR) M=$(PWD)
此处读取KERNELDIR中的顶层Makefile。读取后又返回本目录。继续执行本目录Makefile。
则此时KERNELRELEASE被定义。进入if.
if和else之间的为kbuild语法的语句.