您的位置:首页 > 其它

usb驱动资料收集

2012-12-19 15:22 281 查看
几个重要的结构

struct--接口

[plain] view
plaincopy

struct usb_interface

{

/* array of alternate settings for this interface,

* stored in no particular order */

struct usb_host_interface *altsetting;

struct usb_host_interface *cur_altsetting; /* the currently

* active alternate setting */

unsigned num_altsetting; /* number of alternate settings */

int minor; /* minor number this interface is

* bound to */

enum usb_interface_condition condition; /* state of binding */

unsigned is_active:1; /* the interface is not suspended */

unsigned needs_remote_wakeup:1; /* driver requires remote wakeup */

struct device dev; /* interface specific device info */

struct device *usb_dev; /* pointer to the usb class's device, if any */

int pm_usage_cnt; /* usage counter for autosuspend */

};

结构体struct usb_host_interface就代表一个设置

struct usb_interface中的struct usb_host_interface *cur_altsetting成员,表示当前正在使用的设置

struct--设置

[plain] view
plaincopy

struct usb_host_interface

{

struct usb_interface_descriptor desc;//usb描述符,主要有四种usb描述符,设备描述符,配置描述符,接口描述符和端点描述符,协议里规定一个usb设备是必须支持这四大描述符的。

//usb描述符放在usb设备的eeprom里边

/* array of desc.bNumEndpoint endpoints associated with this

* interface setting. these will be in no particular order.

*/

struct usb_host_endpoint *endpoint;//这个设置所使用的端点

char *string; /* iInterface string, if present */

unsigned char *extra; /* Extra descriptors */关于额外描述符

int extralen;

};

具体到接口描述符,它当然就是描述接口本身的信息的。一个接口可以有多个设置,使用不同的设置,描述接口的信息会有些不同,所以接口描述符并没有放在struct usb_interface结构里,而是放在表示接口设置的struct usb_host_interface结构里。

struct--接口描述符

[plain] view
plaincopy

struct usb_interface_descriptor

{

__u8 bLength;//接口描述符长度

__u8 bDescriptorType;//接口描述符类型

__u8 bInterfaceNumber;//接口号。每个配置可以包含多个接口,这个值就是它们的索引值。

__u8 bAlternateSetting;//接口使用的是哪个可选设置。协议里规定,接口默认使用的设置总为0号设置。

__u8 bNumEndpoints;//接口拥有的端点数量。这里并不包括端点0,因为端点0是控制传输,是所有的设备都必须提供的,所以这里就没必要多此一举的包括它了。对于hub,因为它的传输是中断传输,所以此值为1(不包括端点0)

__u8 bInterfaceClass;

__u8 bInterfaceSubClass;//对于hub,这个值是零

__u8 bInterfaceProtocol;

__u8 iInterface;

} __attribute__ ((packed));

struct--端点

[plain] view
plaincopy

struct usb_host_endpoint

{

struct usb_endpoint_descriptor desc;

struct list_head urb_list;//端点要处理的urb队列.urb是usb通信的主角,设备中的每个端点都可以处理一个urb队列.要想和你的usb通信,就得创建一个urb,并且为它赋好值,

//交给咱们的usb core,它会找到合适的host controller,从而进行具体的数据传输

void *hcpriv;//这是提供给HCD(host controller driver)用的

struct ep_device *ep_dev; /* For sysfs info */

unsigned char *extra; /* Extra descriptors */

int extralen;

};

struct--端点描述符

[plain] view
plaincopy

struct usb_endpoint_descriptor {

__u8 bLength;

__u8 bDescriptorType;//接口类型

__u8 bEndpointAddress;//它的bits 0~3表示的就是端点号,USB_ENDPOINT_NUMBER_MASK;bit7是方向,USB_ENDPOINT_DIR_MASK

__u8 bmAttributes;//bmAttributes,属性,总共8位,其中bit1和bit0 共同称为Transfer Type,即传输类型, 00 表示控制,01 表示等时,10 表示批量,11 表示中断。USB_ENDPOINT_XFERTYPE_MASK

__le16 wMaxPacketSize;//端点一次可以处理的最大字节数,如果你发送的数据量大于端点的这个值,也会分成多次一次一次来传输

__u8 bInterval;//USB是轮询式的总线,这个值表达了端点一种美好的期待,希望主机轮询自己的时间间隔,但实际上批准不批准就是host的事了

/* NOTE: these two are _only_ in audio endpoints. */

/* use USB_DT_ENDPOINT*_SIZE in bLength, not sizeof. */

__u8 bRefresh;

__u8 bSynchAddress;

} __attribute__ ((packed));

注:0号端点没有自己的端点描述符

struct--设备

[plain] view
plaincopy

struct usb_device {

int devnum; //devnum只是usb设备在一条usb总线上的编号.一条usb_bus_type类型的总线上最多可以连上128个设备

char devpath [16]; /* Use in messages: /port/port/*/ //对于root hub.会将dev->devpath[0]=’0’

enum usb_device_state state; //设备的状态Attached,Powered,Default,Address,Configured,Suspended;

//Attached表示设备已经连接到usb接口上了,是hub检测到设备时的初始状态。那么这里所谓的USB_STATE_NOTATTACHED就是表示设备并没有Attached。

//Address状态表示主机分配了一个唯一的地址给设备,此时设备可以使用缺省管道响应主机的请求

//Configured状态表示设备已经被主机配置过了,也就是协议里说的处理了一个带有非0值的SetConfiguration()请求,此时主机可以使用设备提供的所有功能

//Suspended挂起状态,为了省电,设备在指定的时间内,3ms吧,如果没有发生总线传输,就要进入挂起状态。此时,usb设备要自己维护包括地址、配置在内的信息

enum usb_device_speed speed; /* high/full/low (or error) */

struct usb_tt *tt; //如果一个高速设备里有这么一个TT,那么就可以连接低速/全速设备,如不然,那低速/全速设备没法用,只能连接到OHCI/UHCI那边出来的hub口里。

int ttport; //如果一个高速设备里有这么一个TT,那么就可以连接低速/全速设备,如不然,那低速/全速设备没法用,只能连接到OHCI/UHCI那边出来的hub口里。

unsigned int toggle[2]; /* one bit for each endpoint //他实际上就是一个位图.IN方向的是toggle[0].OUT方向的是toggle[1].其实,这个数组中的每一位表示ep的toggle值

* ([0] = IN, [1] = OUT) */它里面的每一位表示的就是每个端点当前发送或接收的数据包是DATA0还是DATA1

struct usb_device *parent; /* our hub, unless we're the root */

//USB设备是从Root Hub开始,一个一个往外面连的,比如Root Hub有4个口,每个口连一个USB设备,比如其中有一个是Hub,那么这个Hub有可以继续有多个口,于是一级一级的往下连,

//最终连成了一棵树。

struct usb_bus *bus; /* Bus we're part of */设备所在的总线

struct usb_host_endpoint ep0; //端点0的特殊地位决定了她必将受到特殊的待遇,在struct usb_device对象产生的时候它就要初始化

struct device dev; /* Generic device interface */嵌入到struct usb_device结构里的struct device结构

struct usb_device_descriptor descriptor;/* Descriptor */设备描述符,此结构体的bMaxPacketSize0 filed保存了端点0的maximum packet size

struct usb_host_config *config; //设备拥有的所有配置

struct usb_host_config *actconfig;//设备正在使用的配置

struct usb_host_endpoint *ep_in[16];//ep_in[16],359行,ep_out[16],除了端点0,一个设备即使在高速模式下也最多只能再有15个IN端点和15个OUT端点,端点0太特殊了,

struct usb_host_endpoint *ep_out[16];//对应的管道是Message管道,又能进又能出特能屈能伸的那种,所以这里的ep_in和ep_out数组都有16个值

char **rawdescriptors; /* Raw descriptors for each config */

unsigned short bus_mA; /* Current available from the bus */这个值是在host controller的驱动程序中设置的,通常来讲,计算机的usb端口可以提供500mA的电流

u8 portnum; //不管是root hub还是一般的hub,你的USB设备总归要插在一个hub的端口上才能用,portnum就是那个端口号。

u8 level; //层次,也可以说是级别,表征usb设备树的级连关系。Root Hub的level当然就是0,其下面一层就是level 1,再下面一层就是level 2,依此类推

unsigned discon_suspended:1; /* Disconnected while suspended */

unsigned have_langid:1; /* whether string_langid is valid */

int string_langid; /* language ID for strings */

/* static strings from the device */

char *product; /* iProduct string, if present */

char *manufacturer; /* iManufacturer string, if present */

char *serial; /* iSerialNumber string, if present */

//分别用来保存产品、厂商和序列号对应的字符串描述符信息

struct list_head filelist;

#ifdef CONFIG_USB_DEVICE_CLASS

struct device *usb_classdev;

#endif

#ifdef CONFIG_USB_DEVICEFS

struct dentry *usbfs_dentry; /* usbfs dentry entry for the device */

#endif

/*

* Child devices - these can be either new devices

* (if this is a hub device), or different instances

* of this same device.

*

* Each instance needs its own set of data structures.

*/

int maxchild; /* Number of ports if hub */

struct usb_device *children[USB_MAXCHILDREN];

int pm_usage_cnt; /* usage counter for autosuspend */

u32 quirks; //quirk就是用来判断这些有毛病的产品啥毛病的

#ifdef CONFIG_PM

struct delayed_work autosuspend; /* for delayed autosuspends */

struct mutex pm_mutex; /* protects PM operations */

unsigned long last_busy; /* time of last use */

int autosuspend_delay; /* in jiffies */

unsigned auto_pm:1; /* autosuspend/resume in progress */

unsigned do_remote_wakeup:1; /* remote wakeup should be enabled */

unsigned autosuspend_disabled:1; /* autosuspend and autoresume */

unsigned autoresume_disabled:1; /* disabled by the user */

#endif

};

struct--设备描述符

[plain] view
plaincopy

struct usb_device_descriptor {

__u8 bLength;

__u8 bDescriptorType;

__le16 bcdUSB; //USB spec的版本号,一个设备如果能够进行高速传输,那么它设备描述符里的bcdUSB这一项就应该为0200H。

__u8 bDeviceClass; //

__u8 bDeviceSubClass;

__u8 bDeviceProtocol; //为了设置tt。full/low speed的hub的bDeviceProtocol是0;对于high speed的hub,其bDeviceProtocol为1表示是single tt,为2表示是multiple tt;

__u8 bMaxPacketSize0; //端点0一次可以处理的最大字节数

__le16 idVendor; //厂商id

__le16 idProduct; //产品id

__le16 bcdDevice; //设备版本号

__u8 iManufacturer;

__u8 iProduct;

__u8 iSerialNumber;

__u8 bNumConfigurations; //设备当前速度模式下支持的配置数量。有的设备可以在多个速度模式下操作,这里包括的只是当前速度模式下的配置数目,不是总的配置数目

} __attribute__ ((packed));

#define USB_DT_DEVICE_SIZE

18

注:为什么端点0的属性bMaxPacketSize0要放到设备描述符里边呢 ?

首先表明了这是一个共性的东西。

前面说端点的时候说了端点0并没有一个专门的端点描述符,因为不需要,基本上它所有的特性都在spec里规定好了的,然而,别忘了这里说的是“基本上”,有一个特性则是不一样的,这叫做maximum packet size,每个端点都有这么一个特性,即告诉你该端点能够发送或者接收的包的最大值。对于通常的端点来说,这个值被保存在该端点描述符中的wMaxPacketSize这一个field,而对于端点0就不一样了,由于它自己没有一个描述符,而每个设备又都有这么一个端点,所以这个信息被保存在了设备描述符里,所以我们在设备描述符里可以看到这么一项,bMaxPacketSize0。而且spec还规定了,这个值只能是8,16,32或者64这四者之一,如果一个设备工作在高速模式,这个值还只能是64,如果是工作在低速模式,则只能是8,取别的值都不行。

struct--配置

[plain] view
plaincopy

struct usb_config_descriptor {

__u8 bLength;

__u8 bDescriptorType; //这里的值并不仅仅可以为USB_DT_CONFIG,还可以为USB_DT_OTHER_SPEED_CONFIG

__le16 wTotalLength; //使用GET_DESCRIPTOR请求从设备里获得配置描述符信息时,返回的数据长度

__u8 bNumInterfaces; //这个配置包含的接口数量

__u8 bConfigurationValue; //对于拥有多个配置的幸运设备来说,可以拿这个值为参数,使用SET_CONFIGURATION请求来改变正在被使用的 USB配置,bConfigurationValue就指明了将要激活哪个配置。

//咱们的设备虽然可以有多个配置,但同一时间却也只能有一个配置被激活。捎带着提一下,SET_CONFIGURATION请求也是标准的设备请求之一,专门用来设置设备的配置。

__u8 iConfiguration; //描述配置信息的字符串描述符的索引值

__u8 bmAttributes; //这个字段表征了配置的一些特点,比如bit 6为1表示self-powered,bit 5为1表示这个配置支持远程唤醒。另外,它的bit 7必须为1

__u8 bMaxPower; //设备正常运转时,从总线那里分得的最大电流值,以2mA为单位。设备可以使用这个字段向hub表明自己需要的的电流,但如果设备需求过于旺盛,请求的超出了hub所能给予的,hub就会直接拒绝

//还记得struct usb_device结构里的bus_mA吗?它就表示hub所能够给予的。计算机的usb端口可以提供最多500mA的电流

} __attribute__ ((packed));

struct--配置描述符

[plain] view
plaincopy

struct usb_config_descriptor {

__u8 bLength;

__u8 bDescriptorType; //这里的值并不仅仅可以为USB_DT_CONFIG,还可以为USB_DT_OTHER_SPEED_CONFIG

__le16 wTotalLength; //使用GET_DESCRIPTOR请求从设备里获得配置描述符信息时,返回的数据长度

__u8 bNumInterfaces; //这个配置包含的接口数量

__u8 bConfigurationValue; //对于拥有多个配置的幸运设备来说,可以拿这个值为参数,使用SET_CONFIGURATION请求来改变正在被使用的 USB配置,bConfigurationValue就指明了将要激活哪个配置。

//咱们的设备虽然可以有多个配置,但同一时间却也只能有一个配置被激活。捎带着提一下,SET_CONFIGURATION请求也是标准的设备请求之一,专门用来设置设备的配置。

__u8 iConfiguration; //描述配置信息的字符串描述符的索引值

__u8 bmAttributes; //这个字段表征了配置的一些特点,比如bit 6为1表示self-powered,bit 5为1表示这个配置支持远程唤醒。另外,它的bit 7必须为1

__u8 bMaxPower; //设备正常运转时,从总线那里分得的最大电流值,以2mA为单位。设备可以使用这个字段向hub表明自己需要的的电流,但如果设备需求过于旺盛,请求的超出了hub所能给予的,hub就会直接拒绝

//还记得struct usb_device结构里的bus_mA吗?它就表示hub所能够给予的。计算机的usb端口可以提供最多500mA的电流

} __attribute__ ((packed));

struct--usb接口的缓存

[plain] view
plaincopy

struct usb_interface_cache {

unsigned num_altsetting; /* number of alternate settings */

struct kref ref; /* reference counter */

/* variable-length array of alternate settings for this interface,

* stored in no particular order */

struct usb_host_interface altsetting[0];

};

struct--hub描述符

[plain] view
plaincopy

struct usb_hub_descriptor {

__u8 bDescLength;

__u8 bDescriptorType;

__u8 bNbrPorts; //Number of downstream facing ports that this hub supports,就是说这个hub所支持的下行端口,这个值不能比31大

__le16 wHubCharacteristics; //它记录了很多信息

__u8 bPwrOn2PwrGood;

__u8 bHubContrCurrent; //bHubContrCurrent是Hub控制器的最大电流需求

/* add 1 bit for hub status change; round to bytes */

__u8 DeviceRemovable[(USB_MAXCHILDREN + 1 + 7) / 8];//用来判断这个端口连接的设备是否是可以移除的,每一个bit代表一个端口,如果该bit为0,则说明可以被移除,为1,就说明不可以移除

__u8 PortPwrCtrlMask[(USB_MAXCHILDREN + 1 + 7) / 8];

} __attribute__ ((packed));

struct--hub结构体

[plain] view
plaincopy

struct usb_hub {

struct device *intfdev; /* the "interface" device */

struct usb_device *hdev;

struct kref kref;

struct urb *urb; /* for interrupt polling pipe */

/* buffer for urb with extra space in case of babble */

char (*buffer)[8];

dma_addr_t buffer_dma; /* DMA address for buffer */

union {

struct usb_hub_status hub;

struct usb_port_status port;

} *status; /* buffer for status reports */

struct mutex status_mutex; /* for the status buffer */

int error; /* last reported error */

int nerrors; /* track consecutive errors */

struct list_head event_list; /* hubs w/data or errs ready */

unsigned long event_bits[1]; /* status change bitmask */

unsigned long change_bits[1]; /* ports with logical connect status change */

unsigned long busy_bits[1]; /* ports being reset or

//resumed */这个flag只有在reset和resume的函数内部才会设置,所以可以通过测试busy_bits的相应位来检测某个端口是否在执行reset或resume操作。

#if USB_MAXCHILDREN > 31 /* 8*sizeof(unsigned long) - 1 */

#error event_bits[] is too short!

#endif

struct usb_hub_descriptor *descriptor; /* class descriptor */

struct usb_tt tt; /* Transaction Translator */

unsigned mA_per_port; //提供给每一个port的电流,一般为500mA,但可能会因为hub提供不了这么大的电流或者host controller那边提供不了这么大的电流,而导致不足500mA

unsigned limited_power:1; //对于host controller那边限制了电流的情况下,这个值被设置为1

unsigned quiescing:1; //quiescing是停止的意思,在reset的时候我们会设置它为1,在suspend的时候我们也会把它设置为1,一旦把它设置成了1,那么hub驱动程序就不会再提交任何URB

unsigned activating:1; //如果activating为1,那么hub驱动程序就会给每个端口发送一个叫做Get Port Status的请求,通常情况下,hub驱动只有在一个端口发生了状态变化的情况下才会去发送

//Get Port Status从而去获得端口的状态。正常情况下,quiescing和activating都应该为0.

unsigned disconnected:1;

unsigned has_indicators:1;

u8 indicator[USB_MAXCHILDREN];

struct delayed_work leds;

};

struct--usb_bus结构体

[plain] view
plaincopy

struct usb_bus {

struct device *controller; /* host/master side hardware */

int busnum; /* Bus number (in order of reg) */

char *bus_name; /* stable id (PCI slot_name etc) */

u8 uses_dma; /* Does the host controller use DMA? */

u8 otg_port; /* 0, or number of OTG/HNP port */

unsigned is_b_host:1; /* true during some HNP roleswitches */

unsigned b_hnp_enable:1; /* OTG: did A-Host enable HNP? */

int devnum_next; /* Next open device number in // 在总线初始化的时候,其devnum_next被设置为1

* round-robin allocation */

struct usb_devmap devmap; /* device address allocation map */ 每条总线设有一个地址映射表

struct usb_device *root_hub; /* Root hub */

struct list_head bus_list; /* list of busses */

int bandwidth_allocated; /* on this bus: how much of the time

* reserved for periodic (intr/iso)

* requests is used, on average?

* Units: microseconds/frame.

* Limits: Full/low speed reserve 90%,

* while high speed reserves 80%.

*/

int bandwidth_int_reqs; /* number of Interrupt requests */

int bandwidth_isoc_reqs; /* number of Isoc. requests */

#ifdef CONFIG_USB_DEVICEFS

struct dentry *usbfs_dentry; /* usbfs dentry entry for the bus */

#endif

struct device *dev; /* device for this bus */

#if defined(CONFIG_USB_MON)

struct mon_bus *mon_bus; /* non-null when associated */

int monitored; /* non-zero when monitored */

#endif

};

struct--usb_devmap

struct usb_devmap {

unsigned long devicemap[128 / (8*sizeof(unsigned long))];

};

几个重要的函数分析usb_hub_init所做的工作:

代码原型:

[plain] view
plaincopy

khubd_task = kthread_run(hub_thread, NULL, "khubd");

建立一个内核级线程:hub_threadhub_thread所做的工作:

代码原型:

[plain] view
plaincopy

do {

hub_events();

wait_event_freezable(khubd_wait,

!list_empty(&hub_event_list) ||

kthread_should_stop()); //一般来说,只要hub_event_list不为空,就会一直调用hub_events()函数,hub_event_list将所有的usb_hub的list_head类型的event_list对象连在一起

} while (!kthread_should_stop() || !list_empty(&hub_event_list));

调用规则:

hub_events函数所做的工作:

对每个端口号(共计bNbrPorts个端口,bNbrPorts这个值从hub描述符里边得到,因为此值描述了hub所用用的端口的情况),假如满足下列条件则调用hub_port_connect_change:

1.连接有变化

2.端口本身重新使能,即所谓的enable,这种情况通常就是为了对付电磁干扰的,正如我们前面的判断中所说的那样

3.在复位一个设备的时候发现其描述符变了,这通常对应的是硬件本身有了升级.很显然,第一种情况是真正的物理变化,后两者就算是逻辑变化

代码模型如下:

[plain] view
plaincopy

for (i = 1; i <= hub->descriptor->bNbrPorts; i++) {

if (connect_change) //表示满足如上条件之一

hub_port_connect_change(hub, i,

portstatus, portchange);



个人感觉,此处的i没有什么实际意义,只是为了保证有“几个端口,才能接几个设备”这样的一个逻辑。虽然说usb_bus最多可以接128个设备,hub最多可以接31个设备,但是,假如hub没有那么多的端口,你怎么接设备?hub_port_connect_change的调用规则:

代码模型:

[plain] view
plaincopy

for (i = 0; i < SET_CONFIG_TRIES; i++) //在此驱动里边,SET_CONFIG_TRIES=4,新策略尝试两次,旧策略尝试两次

{

udev = usb_alloc_dev(hdev, hdev->bus, port1);

udev->speed = USB_SPEED_UNKNOWN;

//进行一些必要的设置

choose_address(udev); //设置了udev->devnum

//错误判断

status = hub_port_init(hub, udev, port1, i); //先进行两次新的策略(i=0和=1时),如果不行就再进行两次旧的策略(i=2和i=3时).所有这一切只有一个目的,就是为了获得设备的描述符

//设置了udev->tt、udev->ttport和udev->ep0.desc.wMaxPacketSize,设置udev->status= USB_STATE_ADDRESS



hub_port_init功能:

hub_port_init里边为udev->tt和udev->ttport和udev->ep0.desc.wMaxPacketSize赋了值,设置udev->status= USB_STATE_ADDRESS.

它的长篇大论只是为了讨论如何确定赋给udev->ep0.desc.wMaxPacketSize的值,这个值是由设备描述符的bMaxPacketSize0字段确定的.每调用一次hub_port_init,使用一种策略。

hub_port_init的原型:

[plain] view
plaincopy

static int hub_port_init (struct usb_hub *hub, struct usb_device *udev, int port1,int retry_counter)



retval = hub_port_reset(hub, port1, udev, delay); //将hub hub上的udev设备重启,并设置udev->speed的值

//根据udev->speed的值设置udev->ep0.desc.wMaxPacketSize的值,但此处的值仅仅是个猜测,最终端点0的maxinum packet size的值要根据设备描述符的bMaxPacketSize0字段得到

//设置udev->tt和udev->ttport的值

for (i = 0; i < GET_DESCRIPTOR_TRIES; (++i, msleep(100))) //GET_DESCRIPTOR_TRIES=2,是为了多执行几次保证正确

{

if (USE_NEW_SCHEME(retry_counter)) //USE_NEW_SCHEME判断使用什么策略,USB_NEW_SCHEME(i)将在i为0和1的时候为1,在i为2和3的时候为0

{ //当retry_counter=0,1时,USE_NEW_SCHEME(retry_counter)=1,表示使用新策略;

//当retry_counter=2,3时,USE_NEW_SCHEME(retry_counter)=0,表示使用旧策略

#define GET_DESCRIPTOR_BUFSIZE 64

buf = kmalloc(GET_DESCRIPTOR_BUFSIZE, GFP_NOIO); //首先定义一个struct usb_device_descriptor的指针buf,然后申请64个字节的空间

for (j = 0; j < 3; ++j) {

buf->bMaxPacketSize0 = 0;

r = usb_control_msg(udev, usb_rcvaddr0pipe(), //发送一个64字节的控制传输的请求

USB_REQ_GET_DESCRIPTOR, USB_DIR_IN,

USB_DT_DEVICE << 8, 0,

buf, GET_DESCRIPTOR_BUFSIZE,

USB_CTRL_GET_TIMEOUT);

switch (buf->bMaxPacketSize0) {

case 8: case 16: case 32: case 64: case 255:

if (buf->bDescriptorType ==

USB_DT_DEVICE) {

r = 0;

break;

}

/* FALL THROUGH */

default:

if (r == 0)

r = -EPROTO;

break;

}

if (r == 0)

break;

}

udev->descriptor.bMaxPacketSize0 = buf->bMaxPacketSize0;//得到udev->descriptor.bMaxPacketSize0

kfree(buf);

retval = hub_port_reset(hub, port1, udev, delay); //将hub hub上的udev设备重启,并设置udev->speed的值

#undef GET_DESCRIPTOR_BUFSIZE

}

for (j = 0; j < SET_ADDRESS_TRIES; ++j) { //SET_ADDRESS_TRIES=2,多试几次

retval = hub_set_address(udev, devnum); //告知设备它的总线地址,设置udev->status= USB_STATE_ADDRESS

if (retval >= 0) //表示正确,退出循环

break;

msleep(200);

}

if (retval < 0) {

dev_err(&udev->dev,

"device not accepting address %d, error %d\n", //我的驱动打印出了这个信息,表示hub_set_address函数出错了

devnum, retval);

goto fail;

}

if (USE_NEW_SCHEME(retry_counter)) //假如这是新策略,执行到这里,表示得到了正确的设备描述符,退出大循环

break;

retval = usb_get_device_descriptor(udev, 8); //执行到这里,说明使用的是旧策略,先一次读取8个字节

//假如返回出错,那么go to fail吧,两种策略都失败了

} //到此为止,应该成功读取到了设备描述符,并可以从中得到bMaxPacketSize0的值

//根据bMaxPacketSize0的值重新设置udev->ep0.desc.wMaxPacketSize的值

retval = usb_get_device_descriptor(udev, USB_DT_DEVICE_SIZE); //再一次读取设备描述符来测试可不可以正确读取,注意这次读取的长度为USB_DT_DEVICE_SIZE

//由于知道了endpoint 0的max packet size,所以可以进行正常的控制传输了

//假如retval == sizeof(udev->descriptor),那么说明控制传输ok了,也验证了端点0的maxinum packet size的值是正确的,那么happy的返回把

}

hub_port_reset功能:
hub_port_reset的原型:

[plain] view
plaincopy

static int hub_port_reset(struct usb_hub *hub, int port1,

struct usb_device *udev, unsigned int delay)

{

int i, status;

/* Block EHCI CF initialization during the port reset.

* Some companion controllers don't like it when they mix.

*/

down_read(&ehci_cf_port_reset_rwsem);

/* Reset the port */

for (i = 0; i < PORT_RESET_TRIES; i++) { //PORT_RESET_TRIES=5

status = set_port_feature(hub->hdev, //发送一个hub类型的控制请求,设置一个feature,传递进来的feature是USB_PORT_FEAT_RESET,即对应于usb spec中的reset

port1, USB_PORT_FEAT_RESET);

if (status)

dev_err(hub->intfdev,

"cannot reset port %d (err = %d)\n",

port1, status);

else {

status = hub_port_wait_reset(hub, port1, udev, delay); //取得端口的状态,然后判断reset是否成功,如果成功,则根据端口的状态字设置udev->speed的值,否则,出错

if (status && status != -ENOTCONN)

dev_dbg(hub->intfdev,

"port_wait_reset: err = %d\n",

status);

}

//在我的例子理,hub_port_wait_reset返回了-110

/* return on disconnect or reset */

switch (status) {

case 0:

/* TRSTRCY = 10 ms; plus some extra */

msleep(10 + 40);

udev->devnum = 0; /* Device now at address 0 */

/* FALL THROUGH */

case -ENOTCONN: &n,bsp; //ENOTCONN=107,transport endpoint is not connected

case -ENODEV: //ENODEV=19,no such device

clear_port_feature(hub->hdev, //status的值为0,-107,-19的时候才会执行clear_port_feature和usb_set_device

port1, USB_PORT_FEAT_C_RESET);

/* FIXME need disconnect() for NOTATTACHED device */

usb_set_device_state(udev, status //status为0的情况,属于正常情况,从这时候开始,struct usb_device结构体的状态就将记录为USB_STATE_DEFAULT

? USB_STATE_NOTATTACHED

: USB_STATE_DEFAULT);

goto done;

}

dev_dbg (hub->intfdev, //status的值不为0,-107,-19的时候才会执行到这里,比如,我的驱动里为-110

"port %d not enabled, trying reset again\n",

port1);

delay = HUB_LONG_RESET_TIME;

}

dev_err (hub->intfdev, //假如执行到这里,说明几次尝试重启都失败了

"Cannot enable port %i. Maybe the USB cable is bad?\n",

port1);

done:

up_read(&ehci_cf_port_reset_rwsem);

return status;

}

hub_port_wait_reset功能:

取得端口的状态,然后判断reset是否成功,如果成功,则根据端口的状态字设置udev->speed的值,否则,出错

hub_port_wait_reset函数原型:

[plain] view
plaincopy

static int hub_port_wait_reset(struct usb_hub *hub, int port1,

struct usb_device *udev, unsigned int delay)

{

//超时重试控制

ret = hub_port_status(hub, port1, &portstatus, &portchange); //得到端口的状态

..

if (!(portstatus & USB_PORT_STAT_CONNECTION)) //如果在reset期间设备都被撤掉了,那就返回吧

return -ENOTCONN;

if ((portchange & USB_PORT_STAT_C_CONNECTION)) //如果又一次汇报说有设备插入,那就是见鬼了.返回错误

return -ENOTCONN;

if (!(portstatus & USB_PORT_STAT_RESET) &&

(portstatus & USB_PORT_STAT_ENABLE)) { //reset真正完成以后,status就应该是enabled,所以if如果满足就说明reset好了

if (hub_is_wusb(hub))

udev->speed = USB_SPEED_VARIABLE;

else if (portstatus & USB_PORT_STAT_HIGH_SPEED)

udev->speed = USB_SPEED_HIGH;

else if (portstatus & USB_PORT_STAT_LOW_SPEED)

udev->speed = USB_SPEED_LOW;

else

udev->speed = USB_SPEED_FULL;

return 0; //reset好了以后,设置udev->speed字段,然后返回

//问题:怎么能从端口的状态里边得到设备的speed字段呢?有点疑惑

}

//超时重试控制

}

hub_port_status其实就是调用了get_port_status(hub->hdev, port1, &hub->status->port);

get_port_status的实现如下:

static int get_port_status(struct usb_device *hdev, int port1,

struct usb_port_status *data)

{

int i, status = -ETIMEDOUT; //ETIMEDOUT=110

for (i = 0; i < USB_STS_RETRIES && status == -ETIMEDOUT; i++) {

status = usb_control_msg(hdev, usb_rcvctrlpipe(hdev, 0),

USB_REQ_GET_STATUS, USB_DIR_IN | USB_RT_PORT, 0, port1,

data, sizeof(*data), USB_STS_TIMEOUT);

}

return status;

}

在我的驱动里边,这个函数出错,返回值是-110,

关于usb驱动的若干基本知识:

1.什么是tt电路?

知道tt干嘛的吗?tt叫做transaction translator.你可以把它想成一块特殊的电路,是hub里面的电路,确切的说是高速hub中的电路,我们知道usb设备有三种速度的,分别是low speed,full speed,high speed.即所谓的低速/全速/高速,抗日战争那会儿,这个世界上只有low speed/full speed的设备,没有high speed的设备,后来解放后,国民生产力的大幅度提升催生了一种high speed的设备,包括主机控制器,以前只有两种接口的,OHCI/UHCI,这都是在usb
spec 1.0的时候,后来2.0推出了EHCI,高速设备应运而生.Hub也有高速hub和过去的hub,但是这里就有一个兼容性问题了,高速的hub是否能够支持低速/全速的设备呢?一般来说是不支持的,于是有了一个叫做TT的电路,它就负责高速和低速/全速的数据转换,于是,如果一个高速设备里有这么一个TT,那么就可以连接低速/全速设备,如不然,那低速/全速设备没法用,只能连接到OHCI/UHCI那边出来的hub口里。tt有两种,一种是single tt,一种是multi tt.前者表示整个hub就是一个TT,而multi
tt表示每个端口都配了一个TT.大多数hub是single TT,因为一个hub一个TT就够了,国家资源那么紧张,何必铺张浪费.使用single TT就是支持国家反腐倡廉!

2.

我们知道usb-storage里面最常见的传输方式就是control/bulk传输,而对于hub,它的传输方式就是control/interrupt,而最有特色的正是它的中断传输.

3.

USB_PORT_STAT_CONNECTION的意思的确是表征是否有设备连接在这个端口上,我们不妨假设有,那么portstatus和它相与的结果就是1,在usb spec里面,这一位叫做Current Connect Status位,于是这里我们会看到connect_change被设置成了1.而接下来,USB_PORT_STAT_C_CONNECTION则是表征这个端口的Current Connect Status位是否有变化,如果有变化,那么portchange和USB_PORT_STAT_C_CONNECTION相与的结果就是1,对于这种情况,我们需要发送另一个请求以清除这个flag,并且将connect_change也设置为1.这个请求叫做Clear
Port Feature.这个请求也是Hub的标准请求。

usb的接口驱动的数据结构为:struct usb_drive,它和usb接口对等;

usb设备驱动的数据结构为:struct usb_device_driver,它和整个usb设备对等

4.配置和设置的区别:

配置还有设置有什么区别,起码我平时即使是再无聊也不会去想这个,但老外不一样,他们不知道老子也不知道郑板桥,所以说他们挺较真儿这个,还分了两个词,配置是configuration,设置是setting。先说配置,一个手机可以有多种配置,比如可以摄像,可以接在电脑里当做一个U盘,那么这两种情况就属于不同的配置,在手机里面有相应的选择菜单,你选择了哪种它就按哪种配置进行工作,供你选择的这个就叫做配置。很显然,当你摄像的时候你不可以访问这块U盘,当你访问这块U盘的时候你不可以摄像,因为你做了选择。第二,既然一个配置代表一种不同的功能,那么很显然,不同的配置可能需要的接口就不一样,我假设你的手机里从硬件上来说一共有5个接口,那么可能当你配置成U盘的时候它只需要用到某一个接口,当你配置成摄像的时候,它可能只需要用到另外两个接口,可能你还有别的配置,然后你可能就会用到剩下那两个接口,也就是说一个配置可能包括到若干个接口

再说说设置,一个手机可能各种配置都确定了,是振动还是铃声已经确定了,各种功能都确定了,但是声音的大小还可以变吧,通常手机的音量是一格一格的变动,大概也就5、6格,那么这个可以算一个setting吧。

5.端点、接口、设置、配置

• 设备通常有一个或多个配置.

• 配置常常有一个或多个接口

• 接口常常有一个或多个设置.

• 接口有零或多个端点.

6.一般来说,主设备号表明了设备的种类,也表明了设备对应着哪个驱动程序,而次设备号则是因为一个驱动程序要支持多个设备而为了让驱动程序区分它们而设置的。也就是说,主设备号用来帮你找到对应的驱动程序,次设备号给你的驱动用来决定对哪个设备进行操作

7.

每个端点都有一个叫做maximum packet size的filed,即告诉你该端点能够发送或者接收的包的最大值.对于通常的端点来说,这个值被保存在该端点描述符中的wMaxPacketSize这一个field,而对于端点0就不一样了,由于它自己没有一个描述符,而每个设备又都有这么一个端点,所以这个信息被保存在了设备描述符里,所以我们在设备描述符里可以看到这么一项,bMaxPacketSize0,而且spec还规定了,这个值只能是8,16,32或者64这四者之一,而且,如果一个设备工作在高速模式,这个值还只能是64,取别的值都不行

8.

在usb_hcd_submit_urt()中,Root Hub, rh_urb_enqueue会被执行,对于非Root Hub,即一般的Hub,driver->urb_enqueue会被执行,对于uhci来说,就是uhci_urb_enqueue会被执行.

对于一般的hub,会执行usb/host/ixp4xx_ehci_hcd.c中的ehci_urb_enqueue(),而ehci_urb_enqueue()则会调用submit_async()(对于设备请求,调用到了这里)

对于一般的hub

ehci_watchdog-> ehci_work()出现ehci_work(),就开始返回了

给出一个请求,会执行如下过程:rh_timer_func-> usb_hcd_poll_rh_status-> usb_hcd_giveback_urb-> urb->complete

9.对于root hub的情况,为什么不用对传输缓存区进行DMA映射呢?

在后面的处理中我们可以看到,其实对于root hub ,它不需要进行实际的物理传输,linux按照spec上的规定,将它静态放置在内存中,在进行相关操作的时候,只要直接copy过去就可以了.

usb_hcd_giveback_urb函数里边会调用urb->complete(urb);

10.urb发送处理过程

(1).假如一个usb_control_msg函数被调用

(2).usb_control_msg会调用usb_internal_control_msg函数

(3).usb_internal_control_msg会调用usb_alloc_urb分配一个urb结构,然后调用usb_fill_control_urb函数来填充这个urb结构,然后调用usb_start_wait_urb函数

(4).usb_start_wait_urb会调用usb_submit_urb(urb,GFP_NOIP)将这个urb发送给usb core,完成过程如下:

(4.1).usb_submit_urb对urb进行一些设置,然后交给usb主控制器驱动来出作进一步的处理,调用的函数为usb_hcd_submit_urb

(4.2).在usb_hcd_submit_urb函数中,判断urb要发送到的设备,假如此设备为 root hub,那么调用rh_urb_enqueue(hcd, urb),否则,调用hcd->driver->urb_enqueue(hcd, urb, mem_flags);

在我的主控制器驱动里注册的urb_enqueue函数为ehci_urb_enqueue

[plain] view
plaincopy

if (is_root_hub(urb->dev))

status = rh_urb_enqueue(hcd, urb);

else

//如果是一般的设备

status = hcd->driver->urb_enqueue(hcd, urb, mem_flags);

因为我分析的是一个普通的usb设备,所以调用ehci_urb_enqueue

(4.3).ehci_urb_enqueue将来自usbcore层的urb的传输请求转换成ehci 可识别的传输描述结构(iTD,siTD,qTD等),然后安排到echi的periodic schedule list或者asynchronous schedule list的合适位置。

对于urb 所要发送到endpoint的类型的control的情况下,此函数将依次调用两个函数:qh_urb_transaction (ehci, urb, &qtd_list, mem_flags)和submit_async (ehci, ep, urb, &qtd_list, mem_flags)

(4.4).调用qh_urb_transaction (ehci, urb, &qtd_list, mem_flags)从urb生成一系列qTD结构,并将这些结构连接到qtd_list;

(4.5).调用submit_async (ehci, ep, urb, &qtd_list, mem_flags)将qtd_list链接的qTD结构分配到ep对应的QH, 将该QH安排到ehci asynchronous schedule list中;

(5)usb_start_wait_urb调用usb_submit_urb将urb最终发送给主控制器驱动之后,它开始满怀信息的等待usb core给它的结果。而它呢,在usb core给它结果以前,它没什么事情可干了把。于是,它调用wait_for_completion_timeout进行非中断的等待(休眠了自己)。

11.urb的完成

(1).在usb_create_hcd函数内有一句:

[plain] view
plaincopy

hcd->rh_timer.function = rh_timer_func;

(2).rh_timer_func函数实现如下:

[plain] view
plaincopy

static void rh_timer_func (unsigned long _hcd)

{

usb_hcd_poll_rh_status((struct usb_hcd *) _hcd);

}

(3).usb_hcd_poll_rh_status函数实现如下:

root hub的中断传输使用polling方式(使用一个定时器),当驱动请求它的时候。也就是说,当有事件发生的时候,驱动负责调用usb_hcd_poll_rh_status

[plain] view
plaincopy

void usb_hcd_poll_rh_status(struct usb_hcd *hcd)

{

struct urb *urb;

int length;

unsigned long flags;

char buffer[4]; /* Any root hubs with > 31 ports? */

//检测主机控制器驱动是否已经注册

if (unlikely(!hcd->rh_registered))

return;

if (!hcd->uses_new_polling && !hcd->status_urb)

return;

//负责检测端口和td队列的状态

length = hcd->driver->hub_status_data(hcd, buffer);

//端口有设备

if (length > 0)

{

/* try to complete the status urb */

spin_lock_irqsave(&hcd_root_hub_lock, flags);

urb = hcd->status_urb;

//检测urb是否存在

if (urb)

{

hcd->poll_pending = 0;

//清除hcd的状态urb

hcd->status_urb = NULL;

//置实际传输长度为1

urb->actual_length = length;

//拷贝端口状态描述组到urb中

memcpy(urb->transfer_buffer, buffer, length);

//卸载urb与节点的连接

usb_hcd_unlink_urb_from_ep(hcd, urb);

spin_unlock(&hcd_root_hub_lock);

//返回urb给驱动程序

usb_hcd_giveback_urb(hcd, urb, 0);

spin_lock(&hcd_root_hub_lock);

}

else

{

length = 0;

hcd->poll_pending = 1;

}

spin_unlock_irqrestore(&hcd_root_hub_lock, flags);

}

/* The USB 2.0 spec says 256 ms. This is close enough and won't

* exceed that limit if HZ is 100. The math is more clunky than

* maybe expected, this is to make sure that all timers for USB devices

* fire at the same time to give the CPU a break inbetween */

//每间隔HZ/4就执行一次hcd->rh_timer中指定的函数

if (hcd->uses_new_polling ? hcd->poll_rh :(length == 0 && hcd->status_urb != NULL))

mod_timer (&hcd->rh_timer, (jiffies/(HZ/4) + 1) * (HZ/4));

}

usb_hcd_poll_rh_status首先调用hcd->driver->hub_status_data(hcd, buffer);在我的ehci中为ehci_hub_status_data

ehci_hub_status_data负责检测端口和td队列的状态。假如ehci_hub_status_data返回值大于0,表明存在已经完成的urb,判断hcd->status_urb是否为空,

假如非空,那么设置hcd结构,然后调用usb_hcd_giveback_urb.

(4).usb_hcd_giveback_urb设置urb->status,然后调用urb->complete,这将导致urb的完成处理函数被执行。

(5).在urb的完成处理函数中会调用complete(&ctx->done)或类似的函数。

(6).complete(&ctx->done)将会唤醒usb_start_wait_urb函数.从usb_start_wait_urb函数的wait_for_completion_timeout处继续执行。

如果超时,也会返回usb_start_wait_urb,然后进行错误处理;如果一切正常,那么恭喜,此次通信圆满成功。

12.

ehci_irq

ehci_work

scan_saync

qh_completions

在qh_competions函数里,urb->status的值在此函数里发生了变化,

ehci_urb_done

usb_hcd_giveback_urb

13.

系统中断入口:

asm_do_IRQ

handle_level_irq

handle_IRQ_event

usb控制器中断

usb_hcd_irq

ehci_irq

ehci_wrok

scan_async

qh_completions

qtd_copy_status

consistent_asny

Linux USB驱动框架分析(一)

初次接触和OS相关的设备驱动编写,感觉还挺有意思的,为了不至于忘掉看过的东西,笔记跟总结当然不可缺,更何况我决定为嵌入式卖命了。好,言归正传,我说一说这段时间的收获,跟大家分享一下Linux的驱动研发。但这次只先针对Linux的USB子系统作分析,因为周五研讨老板催货。当然,还会顺带提一下其他的驱动程式写法。

事实上,Linux的设备驱动都遵循一个惯例——表征驱动程式(用driver更贴切一些,应该称为驱动器比较好吧)的结构体,结构体里面应该包含了驱动程式所需要的任何资源。用术语来说,就是这个驱动器对象所拥有的属性及成员。由于Linux的内核用c来编写,所以我们也按照这种结构化的思想来分析代码,但我还是希望从OO的角度来阐述这些细节。这个结构体的名字有驱动研发人员决定,比如说,鼠标可能有一个叫做mouse_dev的struct,键盘可能由一个keyboard_dev的struct(dev
for device,我们做的只是设备驱动)。而这次我们来分析一下Linux内核源码中的一个usb-skeleton(就是usb驱动的骨架咯),自然,他定义的设备结构体就叫做usb-skel:

struct usb_skel {

struct usb_device * udev; /* the usb device for this device */

struct usb_interface * interface; /* the interface for this device */

struct semaphore limit_sem; /* limiting the number of writes in progress */

unsigned char * bulk_in_buffer; /* the buffer to receive data */

size_t bulk_in_size; /* the size of the receive buffer */

__u8 bulk_in_endpointAddr; /* the address of the bulk in endpoint */

__u8 bulk_out_endpointAddr; /* the address of the bulk out endpoint */

struct kref kref;

};

这里我们得补充说明一下一些USB的协议规范细节。USB能够自动监测设备,并调用相应得驱动程式处理设备,所以其规范实际上是相当复杂的,幸好,我们不必理会大部分细节问题,因为Linux已提供相应的解决方案。就我现在的理解来说,USB的驱动分为两块,一块是USB的bus驱动,这个东西,Linux内核已做好了,我们能够不管,但我们至少要了解他的功能。形象得说,USB的bus驱动相当于铺出一条路来,让任何的信息都能够通过这条USB通道到达该到的地方,这部分工作由usb_core来完成。当USB设备接到USB控制器接口时,usb_core就检测该设备的一些信息,例如生产厂商ID和产品的ID,或是设备所属的class、subclass跟protocol,以便确定应该调用哪一个驱动处理该设备。里面复杂细节我们不用管,我们要做的是另一块工作——usb的设备驱动。也就是说,我们就等着usb_core告诉我们要工作了,我们才工作。



从研发人员的角度看,每一个usb设备有若干个配置(configuration)组成,每个配置又能够有多个接口(interface),每个接口又有多个配置(setting图中没有给出),而接口本身可能没有端点或多个端点(end point)。USB的数据交换通过端点来进行,主机和各个端点之间建立起单向的管道来传输数据。而这些接口能够分为四类:

控制(control

用于配置设备、获取设备信息、发送命令或获取设备的状态报告

中断(interrupt

当USB宿主需要设备传输数据时,中断端点会以一个固定的速率传送少量数据,还用于发送数据到USB设备以控制设备,一般不用于传送大量数据。

批量(bulk

用于大量数据的可靠传输,假如总线上的空间不足以发送整个批量包,他会被分割成多个包传输。

等时(isochronous

大量数据的不可靠传输,不确保数据的到达,但确保恒定的数据流,多用于数据采集。

Linux中用struct usb_host_endpoint来描述USB端点,每个usb_host_endpoint中包含一个struct usb_endpoint_descriptor结构体,当中包含该端点的信息连同设备自定义的各种信息,这些信息包括:

bEndpointAddress(b for byte)

8位端点地址,其地址还隐藏了端点方向的信息(之前说过,端点是单向的),能够用掩码USB_DIR_OUT和USB_DIR_IN来确定。

bmAttributes

端点的类型,结合USB_ENDPOINT_XFERTYPE_MASK能够确定端点是USB_ENDPOINT_XFER_ISOC(等时)、USB_ENDPOINT_XFER_BULK(批量)还是USB_ENDPOINT_XFER_INT(中断)。

wMaxPacketSize

端点一次处理的最大字节数。发送的BULK包能够大于这个数值,但会被分割传送。

bInterval

假如端点是中断类型,该值是端点的间隔配置,以毫秒为单位。

在逻辑上,一个USB设备的功能划分是通过接口来完成的。比如说一个USB扬声器,可能会包括有两个接口:一个用于键盘控制,另外一个用于音频流传输。而事实上,这种设备需要用到不同的两个驱动程式来操作,一个控制键盘,一个控制音频流。但也有例外,比如蓝牙设备,需要有两个接口,第一用于ACL跟EVENT的传输,另外一个用于SCO链路,但两者通过一个驱动控制。在Linux上,接口使用

struct usb_interface来描述,以下是该结构体中比较重要的字段:

struct usb_host_interface *altsetting(注意不是usb_interface)

其实据我理解,他应该是每个接口的配置,虽然名字上有点奇怪。该字段是个配置的数组(一个接口能够有多个配置),每个usb_host_interface都包含一套由struct usb_host_endpoint定义的端点配置。但这些配置次序是不定的。

unsigned num_altstting

可选配置的数量,即altsetting所指数组的元素个数。

struct usb_host_interface *cur_altsetting

当前活动的配置,指向altsetting数组中的一个。

int minor

当捆绑到该接口的USB驱动程式使用USB主设备号时,USB core分配的次设备号。仅在成功调用usb_register_dev之后才有效。

除了他能够用struct usb_host_config来描述之外,到现在为止,我对配置的了解不多。而整个USB设备则能够用struct usb_device来描述,但基本上只会用他来初始化函数的接口,真正用到的应该是我们之前所提到的自定义的一个结构体。

Linux USB驱动框架分析(二)

好,了解过USB一些规范细节之后,我们现在来看看Linux的驱动框架。事实上,Linux的设备驱动,特别是这种hotplug的USB设备驱动,会被编译成模块,然后在需要时挂在到内核。要写一个Linux的模块并不复杂,以一个helloworld为例:

#include

#include

MODULE_LICENSE(“GPL”);

static int hello_init(void)


{

printk(KERN_ALERT “Hello World!\n”);

return 0;

}

static int hello_exit(void)

{

printk(KERN_ALERT “GOODBYE!\n”);

}

module_init(hello_init);

module_exit(hello_exit);

这个简单的程式告诉大家应该怎么写一个模块,MODULE_LICENSE告诉内核该模块的版权信息,很多情况下,用GPL或BSD,或两个,因为一个私有模块一般很难得到社区的帮助。module_init和module_exit用于向内核注册模块的初始化函数和模块推出函数。如程式所示,初始化函数是hello_init,而退出函数是hello_exit。

另外,要编译一个模块通常还需要用到内核源码树中的makefile,所以模块的Makefile能够写成:

ifneq ($(KERNELRELEASE),)

obj-m:= hello.o#usb-dongle.o

else

KDIR:= /usr/src/linux-headers-$(shell uname -r)

BDIR:= $(shell pwd)

default:

$(MAKE) -C $(KDIR) M=$(PWD) modules

.PHONY: clean

clean:

make -C $(KDIR) M=$(BDIR) clean

endif

能够用insmod跟rmmod来验证模块的挂在跟卸载,但必须用root的身份登陆命令行,用普通用户加su或sudo在Ubuntu上的测试是不行的。

Linux USB驱动框架分析(三)

下面分析一下usb-skeleton的源码。这个范例程式能够在linux-2.6.17/drivers/usb下找到,其他版本的内核程式源码可能有所不同,但相差不大。大家能够先找到源码看一看,先有个整体印象。

之前已提到,模块先要向内核注册初始化跟销毁函数:

static int __init usb_skel_init(void)

{

int result;

/* register this driver with the USB subsystem */

result = usb_register(&skel_driver);

if (result)

err("usb_register failed. Error number %d", result);

return result;

}

static void __exit usb_skel_exit(void)

{

/* deregister this driver with the USB subsystem */

usb_deregister(&skel_driver);

}

module_init (usb_skel_init);

module_exit (usb_skel_exit);


MODULE_LICENSE("GPL");

从代码开来,这个init跟exit函数的作用只是用来注册驱动程式,这个描述驱动程式的结构体是系统定义的标准结构struct usb_driver,注册和注销的方法很简单,usb_register(struct *usb_driver), usb_deregister(struct *usb_driver)。那这个结构体需要做些什么呢?他要向系统提供几个函数入口,跟驱动的名字:

static struct usb_driver skel_driver = {

.name = "skeleton",

.probe = skel_probe,

.disconnect = skel_disconnect,

.id_table = skel_table,

};

从代码看来,usb_driver需要初始化四个东西:模块的名字skeleton,probe函数skel_probe,disconnect函数skel_disconnect,连同id_table。

在解释skel_driver各个成员之前,我们先来看看另外一个结构体。这个结构体的名字有研发人员自定义,他描述的是该驱动拥有的任何资源及状态:

struct usb_skel {

struct usb_device * udev; /* the usb device for this device */

struct usb_interface * interface; /* the interface for this device */

struct semaphore limit_sem; /* limiting the number of writes in progress */

unsigned char * bulk_in_buffer; /* the buffer to receive data */

size_t bulk_in_size; /* the size of the receive buffer */

__u8 bulk_in_endpointAddr; /* the address of the bulk in endpoint */

__u8 bulk_out_endpointAddr; /* the address of the bulk out endpoint */

struct kref kref;

};

我们先来对这个usb_skel作个简单分析,他拥有一个描述usb设备的结构体udev,一个接口interface,用于并发访问控制的semaphore(信号量) limit_sem,用于接收数据的缓冲bulk_in_buffer及其尺寸bulk_in_size,然后是批量输入输出端口地址bulk_in_endpointAddr、bulk_out_endpointAddr,最后是个内核使用的引用计数器。他们的作用我们将在后面的代码中看到。

我们再回过头来看看skel_driver。

name用来告诉内核模块的名字是什么,这个注册之后有系统来使用,跟我们关系不大。

id_table用来告诉内核该模块支持的设备。usb子系统通过设备的production ID和vendor ID的组合或设备的class、subclass跟protocol的组合来识别设备,并调用相关的驱动程式作处理。我们能够看看这个id_table到底是什么东西:

/* Define these values to match your devices */

#define USB_SKEL_VENDOR_ID 0xfff0

#define USB_SKEL_PRODUCT_ID 0xfff0

/* table of devices that work with this driver */

static struct usb_device_id skel_table [] = {

{ USB_DEVICE(USB_SKEL_VENDOR_ID, USB_SKEL_PRODUCT_ID) },

{ } /* Terminating entry */

};

MODULE_DEVICE_TABLE (usb, skel_table);

MODULE_DEVICE_TABLE的第一个参数是设备的类型,假如是USB设备,那自然是usb(假如是PCI设备,那将是pci,这两个子系统用同一个宏来注册所支持的设备。这涉及PCI设备的驱动了,在此先不深究)。后面一个参数是设备表,这个设备表的最后一个元素是空的,用于标识结束。代码定义了USB_SKEL_VENDOR_ID是0xfff0,USB_SKEL_PRODUCT_ID是0xfff0,也就是说,当有一个设备接到集线器时,usb子系统就会检查这个设备的vendor
ID和product ID,假如他们的值是0xfff0时,那么子系统就会调用这个skeleton模块作为设备的驱动。

Linux USB驱动框架分析(四)

probe是usb子系统自动调用的一个函数,有USB设备接到硬件集线器时,usb子系统会根据production ID和vendor ID的组合或设备的class、subclass跟protocol的组合来识别设备调用相应驱动程式的probe(探测)函数,对于skeleton来说,就是skel_probe。系统会传递给探测函数一个usb_interface *跟一个struct usb_device_id *作为参数。他们分别是该USB设备的接口描述(一般会是该设备的第0号接口,该接口的默认配置也是第0号配置)跟他的设备ID描述(包括Vendor
ID、Production ID等)。probe函数比较长,我们分段来分析这个函数:

dev->udev = usb_get_dev(interface_to_usbdev(interface));

dev->interface = interface;

在初始化了一些资源之后,能够看到第一个关键的函数调用——interface_to_usbdev。他同uo一个usb_interface来得到该接口所在设备的设备描述结构。本来,要得到一个usb_device只要用interface_to_usbdev就够了,但因为要增加对该usb_device的引用计数,我们应该在做一个usb_get_dev的操作,来增加引用计数,并在释放设备时用usb_put_dev来减少引用计数。这里要解释的是,该引用计数值是对该usb_device的计数,并不是对本模块的计数,本模块的计数要由kref来维护。所以,probe一开始就有初始化kref。事实上,kref_init操作不单只初始化kref,还将其置设成1。所以在出错处理代码中有kref_put,他把kref的计数减1,假如kref计数已为0,那么kref会被释放。kref_put的第二个参数是个函数指针,指向一个清理函数。注意,该指针不能为空,或kfree。该函数会在最后一个对kref的引用释放时被调用(假如我的理解不准确,请指正)。下面是内核源码中的一段注释及代码:

/**

* kref_put - decrement refcount for object.

* @kref: object.

* @release: pointer to the function that will clean up the object when the

* last reference to the object is released.

* This pointer is required, and it is not acceptable to pass kfree

* in as this function.

*

* Decrement the refcount, and if 0, call release().

* Return 1 if the object was removed, otherwise return 0. Beware, if this

* function returns 0, you still can not count on the kref from remaining in

* memory. Only use the return value if you want to see if the kref is now

* gone, not present.

*/

int kref_put(struct kref *kref, void (*release)(struct kref *kref))

{

WARN_ON(release == NULL);

WARN_ON(release == (void (*)(struct kref *))kfree);

/*

* if current count is one, we are the last user and can release object

* right now, avoiding an atomic operation on 'refcount'

*/

if ((atomic_read(&kref->refcount) == 1) ||

(atomic_dec_and_test(&kref->refcount))) {

release(kref);

return 1;

}

return 0;

}

当我们执行打开操作时,我们要增加kref的计数,我们能够用kref_get,来完成。任何对struct kref的操作都有内核代码确保其原子性。

得到了该usb_device之后,我们要对我们自定义的usb_skel各个状态跟资源作初始化。这部分工作的任务主要是向usb_skel注册该usb设备的端点。这里可能要补充以下一些关于usb_interface_descriptor的知识,但因为内核源码对该结构体的注释不多,所以只能靠个人猜测。在一个usb_host_interface结构里面有一个usb_interface_descriptor叫做desc的成员,他应该是用于描述该interface的一些属性,其中bNumEndpoints是个8位(b
for byte)的数字,他代表了该接口的端点数。probe然后遍历任何的端点,检查他们的类型跟方向,注册到usb_skel中。

/* set up the endpoint information */

/* use only the first bulk-in and bulk-out endpoints */

iface_desc = interface->cur_altsetting;

for (i = 0; i desc.bNumEndpoints; i) {

endpoint = &iface_desc->endpoint.desc;

if ( !dev->bulk_in_endpointAddr &&

((endpoint->bEndpointAddress & USB_ENDPOINT_DIR_MASK) = = USB_DIR_IN) &&

((endpoint->bmAttributes & USB_ENDPOINT_XFERTYPE_MASK) = = USB_ENDPOINT_XFER_BULK)) {

/* we found a bulk in endpoint */

buffer_size = le16_to_cpu(endpoint->wMaxPacketSize);

dev->bulk_in_size = buffer_size;

dev->bulk_in_endpointAddr = endpoint->bEndpointAddress;

dev->bulk_in_buffer = kmalloc(buffer_size, GFP_KERNEL);

if (!dev->bulk_in_buffer) {

err("Could not allocate bulk_in_buffer");

goto error;

}

}

if (!dev->bulk_out_endpointAddr &&

((endpoint->bEndpointAddress & USB_ENDPOINT_DIR_MASK)= =USB_DIR_OUT) &&

((endpoint->bmAttributes & USB_ENDPOINT_XFERTYPE_MASK)= = USB_ENDPOINT_XFER_BULK)) {

/* we found a bulk out endpoint */

dev->bulk_out_endpointAddr = endpoint->bEndpointAddress;

}

}

if (!(dev->bulk_in_endpointAddr && dev->bulk_out_endpointAddr)) {

err("Could not find both bulk-in and bulk-out endpoints");

goto error;

}

Linux USB驱动框架分析(五)

接下来的工作是向系统注册一些以后会用的的信息。首先我们来说明一下usb_set_intfdata(),他向内核注册一个data,这个data的结构能够是任意的,这段程式向内核注册了一个usb_skel结构,就是我们刚刚看到的被初始化的那个,这个data能够在以后用usb_get_intfdata来得到。

usb_set_intfdata(interface, dev);

retval = usb_register_dev(interface, &skel_class);

然后我们向这个interface注册一个skel_class结构。这个结构又是什么?我们就来看看这到底是个什么东西:

static struct usb_class_driver skel_class = {

.name = "skel%d",

.fops = &skel_fops,

.minor_base = USB_SKEL_MINOR_BASE,

};

他其实是个系统定义的结构,里面包含了一名字、一个文档操作结构体更有一个次设备号的基准值。事实上他才是定义 真正完成对设备IO操作的函数。所以他的核心内容应该是skel_fops。这里补充一些我个人的估计:因为usb设备能够有多个interface,每个interface所定义的IO操作可能不相同,所以向系统注册的usb_class_driver需要注册到某一个interface,而不是device,因此,usb_register_dev的第一个参数才是interface,而第二个参数就是某一个usb_class_driver。通常情况下,linux系统用主设备号来识别某类设备的驱动程式,用次设备号管理识别具体的设备,驱动程式能够依照次设备号来区分不同的设备,所以,这里的次设备好其实是用来管理不同的interface的,但由于这个范例只有一个interface,在代码上无法求证这个猜想。

static struct file_operations skel_fops = {

.owner = THIS_MODULE,

.read = skel_read,

.write = skel_write,

.open = skel_open,

.release = skel_release,

};

这个文档操作结构中定义了对设备的读写、打开、释放(USB设备通常使用这个术语release)。他们都是函数指针,分别指向skel_read、skel_write、skel_open、skel_release这四个函数,这四个函数应该有研发人员自己实现。

当设备被拔出集线器时,usb子系统会自动地调用disconnect,他做的事情不多,最重要的是注销class_driver(交还次设备号)和interface的data:

dev = usb_get_intfdata(interface);

usb_set_intfdata(interface, NULL);

/* give back our minor */

usb_deregister_dev(interface, &skel_class);

然后他会用kref_put(&dev->kref, skel_delete)进行清理,kref_put的细节参见前文。

到现在为止,我们已分析完usb子系统需要的各个主要操作,下一部分我们在讨论一下对USB设备的IO操作。

Linux USB驱动框架分析(六)

说到usb子系统的IO操作,不得不说usb request block,简称urb。事实上,能够打一个这样的比喻,usb总线就像一条高速公路,货物、人流之类的能够看成是系统和设备交互的数据,而urb就能够看成是汽车。在一开始对USB规范细节的介绍,我们就说过USB的endpoint有4种不同类型,也就是说能在这条高速公路上流动的数据就有四种。但是这对汽车是没有需要的,所以urb能够运载四种数据,但是您要先告诉司机您要运什么,目的地是什么。我们现在就看看struct
urb的具体内容。他的内容很多,为了不让我的理解误导各位,大家最好还是看一看内核源码的注释,具体内容参见源码树下include/linux/usb.h。

在这里我们重点介绍程式中出现的几个关键字段:

struct usb_device *dev

urb所发送的目标设备。

unsigned int pipe

一个管道号码,该管道记录了目标设备的端点连同管道的类型。每个管道只有一种类型和一个方向,他和他的目标设备的端点相对应,我们能够通过以下几个函数来获得管道号并配置管道类型:

unsigned int usb_sndctrlpipe(struct usb_device *dev, unsigned int endpoint)

把指定USB设备的指定端点配置为一个控制OUT端点。

unsigned int usb_rcvctrlpipe(struct usb_device *dev, unsigned int endpoint)

把指定USB设备的指定端点配置为一个控制IN端点。

unsigned int usb_sndbulkpipe(struct usb_device *dev, unsigned int endpoint)

把指定USB设备的指定端点配置为一个批量OUT端点。

unsigned int usb_rcvbulkpipe(struct usb_device *dev, unsigned int endpoint)

把指定USB设备的指定端点配置为一个批量OUT端点。

unsigned int usb_sndintpipe(struct usb_device *dev, unsigned int endpoint)

把指定USB设备的指定端点配置为一个中断OUT端点。

unsigned int usb_rcvintpipe(struct usb_device *dev, unsigned int endpoint)

把指定USB设备的指定端点配置为一个中断OUT端点。

unsigned int usb_sndisocpipe(struct usb_device *dev, unsigned int endpoint)

把指定USB设备的指定端点配置为一个等时OUT端点。

unsigned int usb_rcvisocpipe(struct usb_device *dev, unsigned int endpoint)

把指定USB设备的指定端点配置为一个等时OUT端点。

unsigned int transfer_flags

当不使用DMA时,应该transfer_flags |= URB_NO_TRANSFER_DMA_MAP(按照代码的理解,希望没有错)。

int status

当一个urb把数据送到设备时,这个urb会由系统返回给驱动程式,并调用驱动程式的urb完成回调函数处理。这时,status记录了这次数据传输的有关状态,例如传送成功和否。成功的话会是0。

要能够运货当然首先要有车,所以第一步当然要创建urb:

struct urb *usb_alloc_urb(int isoc_packets, int mem_flags);

第一个参数是等时包的数量,假如不是乘载等时包,应该为0,第二个参数和kmalloc的标志相同。

要释放一个urb能够用:

void usb_free_urb(struct urb *urb);

要承载数据,还要告诉司机目的地信息跟要运的货物,对于不同的数据,系统提供了不同的函数,对于中断urb,我们用

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, void *context, int interval);

这里要解释一下,transfer_buffer是个要送/收的数据的缓冲,buffer_length是他的长度,complete是urb完成回调函数的入口,context由用户定义,可能会在回调函数中使用的数据,interval就是urb被调度的间隔。

对于批量urb和控制urb,我们用:

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,

void *context);

void usb_fill_bulk_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,void *context);

控制包有一个特别参数setup_packet,他指向即将被发送到端点的配置数据报的数据。

对于等时urb,系统没有专门的fill函数,只能对各urb字段显示赋值。

有了汽车,有了司机,下一步就是要开始运货了,我们能够用下面的函数来提交urb

int usb_submit_urb(struct urb *urb, int mem_flags);

mem_flags有几种:GFP_ATOMIC、GFP_NOIO、GFP_KERNEL,通常在中断上下文环境我们会用GFP_ATOMIC。

当我们的卡车运货之后,系统会把他调回来,并调用urb完成回调函数,并把这辆车作为函数传递给驱动程式。我们应该在回调函数里面检查status字段,以确定数据的成功传输和否。下面是用urb来传送数据的细节。

/* initialize the urb properly */

usb_fill_bulk_urb(urb, dev->udev,

usb_sndbulkpipe(dev->udev, dev->bulk_out_endpointAddr),

buf, writesize, skel_write_bulk_callback, dev);

urb->transfer_flags |= URB_NO_TRANSFER_DMA_MAP;

/* send the data out the bulk port */

retval = usb_submit_urb(urb, GFP_KERNEL);

这里skel_write_bulk_callback就是个完成回调函数,而他做的主要事情就是检查数据传输状态和释放urb:

dev = (struct usb_skel *)urb->context;

/* sync/async unlink faults aren't errors */

if (urb->status && !(urb->status = = -ENOENT || urb->status == -ECONNRESET || urb->status = = -ESHUTDOWN)) {

dbg("%s - nonzero write bulk status received: %d", __FUNCTION__, urb->status);

}

/* free up our allocated buffer */

usb_buffer_free(urb->dev, urb->transfer_buffer_length,

urb->transfer_buffer, urb->transfer_dma);

事实上,假如数据的量不大,那么能够不一定用卡车来运货,系统还提供了一种不用urb的传输方式,而usb-skeleton的读操作正是采用这种方式实现:

/* do a blocking bulk read to get data from the device */

retval = usb_bulk_msg(dev->udev,

usb_rcvbulkpipe(dev->udev, dev->bulk_in_endpointAddr),

dev->bulk_in_buffer,

min(dev->bulk_in_size, count),

&bytes_read, 10000);

/* if the read was successful, copy the data to userspace */

if (!retval) {

if (copy_to_user(buffer, dev->bulk_in_buffer, bytes_read))

retval = -EFAULT;

else

retval = bytes_read;

}

程式使用了usb_bulk_msg来传送数据,他的原型如下:

int usb_bulk_msg(struct usb_device *usb_dev, unsigned int pipe,void *data,

int len, int *actual length, int timeout)

这个函数会阻塞等待数据传输完成或等到超时,data是输入/输出缓冲,len是他的大小,actual length是实际传送的数据大小,timeout是阻塞超时。

对于控制数据,系统提供了另外一个函数,他的原型是:

Int usb_contrl_msg(struct usb_device *dev, unsigned int pipe, __u8 request,

__u8 requesttype, __u16 value, __u16 index, void *data,

__u16 size, int timeout);

request是控制消息的USB请求值、requesttype是控制消息的USB请求类型,value是控制消息的USB消息值,index是控制消息的USB消息索引。具体是什么,暂时不是很清楚,希望大家提供说明。

至此,Linux下的USB驱动框架分析基本完成了。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: