通用串行总线(USB)是主机和外围设备之间的一种连接。最新USB规范修订增加了理论上高达480Mbps的高速连接。
从拓扑上看,USB子系统并不是以总线的方式来布置的,它是一颗由几个点对点的连接构建而成的树。
USB是四线缆:地线、电源线、两根信号线
- USB主控制器负责询问每一个USB设备是否有数据需要发送。
- 一个USB设备在没有主控制器要求的情况下是不能发送数据的。
- 方便搭建即插即用的系统。
- 只担当设备和主控制器之间通信通道的角色,对它所发送的数据没有任何特殊的内容和结构上的要求。
- USB协议规范定义了一套任何特定类型的设备都可以遵循的标准。
- 不同特定类型称为类(class),包括存储设备、键盘、鼠标、游戏杆、网络设备和调制解调器,和其他需要驱动的设备。
Linux内核支持两种USB驱动程序:
- 宿主(host)系统上的驱动程序
- 设备(device)上的驱动程序
- 宿主系统的USB驱动程序控制插入其中的USB设备,而USB设备的驱动程序控制该设备如何作为一个USB设备和主机通信。
一、USB设备基础
USB设备非常复杂,官网www.usb.org中有详细描述
Linux内部有USB核心(USB core)的子系统来处理大部分的复杂性。
端点
USB通信最基本的形式是通过一个名为端点的东西。 USB端点只能往一个方向传输数据,可以看做单向管道。
USB端点四种不同类型,具有不同的传输数据的方式:
- 控制:控制端点用来控制对USB设备不同部分的访问。
- 中断:每当USB宿主要求设备传输数据时,中断端点就以一个固定的速率来传输少量的数据。
- 批量:批量端点传输大批量的数据。
- 等时:可以传送大批量的数据,但数据是否到达是没有保证的。
内核中使用struct usb_host_endpoint来描述USB端点:
struct usb_host_endpoint { struct usb_endpoint_descriptor desc; struct usb_ss_ep_comp_descriptor ss_ep_comp; struct list_head urb_list; void *hcpriv; struct ep_device *ep_dev; /* For sysfs info */ unsigned char *extra; /* Extra descriptors */ int extralen; int enabled; };
结构中包含struct usb_endpoint_descriptor包含了真正的端点信息,所有的USB特定数据。这些数据时设备自己定义的。
/* USB_DT_ENDPOINT: Endpoint descriptor */ struct usb_endpoint_descriptor { __u8 bLength; __u8 bDescriptorType; __u8 bEndpointAddress; /* 这是特定端点USB地址,端点方向 */ __u8 bmAttributes; /* 这是端点的类型 */ __le16 wMaxPacketSize; /* 端点一次可以处理的最大字节 */ __u8 bInterval; /* 如果端点是中断类型 */ /* NOTE: these two are _only_ in audio endpoints. */ /* use USB_DT_ENDPOINT*_SIZE in bLength, not sizeof. */ __u8 bRefresh; __u8 bSynchAddress; } __attribute__ ((packed));
接口
USB端点被捆绑为接口。USB接口值处理一种USB逻辑连接,一些USB设备具有多个接口,Linux需要不同的驱动程序来处理。
USB接口可以有其他的设置,这些是和接口的参数不同的选择。
内核使用struct usb_interface结构体来描述USB接口,USB核心把该结构体传递给USB驱动程序。
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 */ /* If there is an interface association descriptor then it will list * the associated interfaces */ struct usb_interface_assoc_descriptor *intf_assoc; int minor; /* minor number this interface is * bound to */ enum usb_interface_condition condition; /* state of binding */ unsigned sysfs_files_created:1; /* the sysfs attributes exist */ unsigned ep_devs_created:1; /* endpoint "devices" exist */ unsigned unregistering:1; /* unregistration is in progress */ unsigned needs_remote_wakeup:1; /* driver requires remote wakeup */ unsigned needs_altsetting0:1; /* switch to altsetting 0 is pending */ unsigned needs_binding:1; /* needs delayed unbind/rebind */ unsigned reset_running:1; unsigned resetting_device:1; /* true: bandwidth alloc after reset */ struct device dev; /* interface specific device info */ struct device *usb_dev; atomic_t pm_usage_cnt; /* usage counter for autosuspend */ struct work_struct reset_ws; /* for resets in atomic context */ }; /* struct usb_host_interface *altsetting 一个接口结构体数组,包含了所有可能用于该接口的可选设置 */ /* unsigned num_altsetting:altsetting指针所指的可选设置的数量 */ /* struct usb_host_interface *cur_altsetting:指向altsetting数组内部的指针,表示该接口的当前活动设置 */ /* int minor:如果捆绑到该接口的USB驱动程序使用USB主设备号 */ /* struct usb_interface 结构体中还有其他的字段,不过USB驱动程序不需要考虑它们 */
配置
USB接口本身被捆绑为配置,一个USB设备可以有多个配置,而且可以在配置之间切换以改变设备的状态。
Linux使用struct usb_host_config结构体来描述USB配置,使用struct usb_device结构体来描述整个USB设备。
struct usb_host_config { struct usb_config_descriptor desc; char *string; /* iConfiguration string, if present */ /* List of any Interface Association Descriptors in this * configuration. */ struct usb_interface_assoc_descriptor *intf_assoc[USB_MAXIADS]; /* the interfaces associated with this configuration, * stored in no particular order */ struct usb_interface *interface[USB_MAXINTERFACES]; /* Interface information available even when this is not the * active configuration */ struct usb_interface_cache *intf_cache[USB_MAXINTERFACES]; unsigned char *extra; /* Extra descriptors */ int extralen; }; struct usb_device { int devnum; char devpath[16]; u32 route; enum usb_device_state state; enum usb_device_speed speed; struct usb_tt *tt; int ttport; unsigned int toggle[2]; struct usb_device *parent; struct usb_bus *bus; struct usb_host_endpoint ep0; struct device dev; struct usb_device_descriptor descriptor; struct usb_host_config *config; struct usb_host_config *actconfig; struct usb_host_endpoint *ep_in[16]; struct usb_host_endpoint *ep_out[16]; char **rawdescriptors; unsigned short bus_mA; u8 portnum; u8 level; unsigned can_submit:1; unsigned persist_enabled:1; unsigned have_langid:1; unsigned authorized:1; unsigned authenticated:1; unsigned wusb:1; int string_langid; /* static strings from the device */ char *product; char *manufacturer; char *serial; struct list_head filelist; #ifdef CONFIG_USB_DEVICE_CLASS struct device *usb_classdev; #endif #ifdef CONFIG_USB_DEVICEFS struct dentry *usbfs_dentry; #endif int maxchild; struct usb_device *children[USB_MAXCHILDREN]; u32 quirks; atomic_t urbnum; unsigned long active_duration; #ifdef CONFIG_PM unsigned long connect_time; #ifdef CONFIG_SMM6260_MODEM int autosuspend_delay; #endif unsigned do_remote_wakeup:1; unsigned reset_resume:1; #endif struct wusb_dev *wusb_dev; int slot_id; };
USB设备驱动程序通常需要把一个给定的struct usb_interface结构体数据转换为struct usb_device结构体,USB核心在很多函数调用汇总都需要该结构体。
interface_to_usbdev用于该转换功能的函数。
简而言之,USB之间的逻辑单元之间的关系可以如下描述:
- 设备通常具有一个或更多的配置
- 配置经常具有一个或更多的接口
- 接口通常具有一个或更多的设置
- 接口没有或具有一个以上的端点
USB和sysfs
USB sysfs设备命名方案为:
根集线器 - 集线器端口号 : 配置 . 接口
对于一个两层的树,其设备名类似于:
根集线器 - 集线器端口号 - 集线器端口号 : 配置 . 接口
sysfs并没有展示USB设备所有的不同部分,它只限于接口级别。不过可以通过/proc/bus/usb目录中,从/proc/bus/usb/device文件,
可以知道系统中存在的所有USB设备的可选配置。
USB urb
Linux内核中的USB代码通过一个称为Urb的东西和所有的USB设备通信。
struct urb结构体来描述这个请求块,可以在include/linux/usb.h文件中找到。
urb被用来以一种异步的方式往/从特定的USB设备上的特定USB端点发送/接收数据。
创建和销毁urb
struct urb 接口体不能在驱动程序中或者另一个结构体中静态地创建,因为这样会破坏USB核心对urb所使用的引用计数机制。
struct urb *usb_alloc_urb(int iso_packets, int mem_flags); iso_packets:是该urb应该包含的等时数据包的数量,如果不是等时的,设置为0 mem_flags:传递给用于从内核分配内存的kmalloc函数 返回:urb的指针,NULL则是错误 void usb_free_urb(struct urb *urb); 释放urb指针
中断urb
正确地初始化即将被发送到USB设备的中断端点的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); struct urb *urb:指向需要初始化的urb指针 struct usb_device *dev:该urb所发送的目标USB设备 unsigned int pipe:该urb所发送的目标USB设备的特定端点 void *transger_buffer:用于保存外发数据或者接受数据的缓冲区的指针 int buffer_length:transfer_buffer指针所指向的缓冲区的大小 usb_complete_t complete:指向当该urb结束之后调用的结束处理例程的指针 void *context:指向一个小数据块,将被添加到urb结构体中以便进行结束处理例程后面的查找 int interval:该urb应该被调度的间隔
批量urb
void usb_fill_bulk_urb(struct urb *urb, struct usb_device *dev, usngined int pipe, void *transfer_buffer, int buffer_length, usb_complete_t complete, void *context);
控制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, void *context); 参数和usb_fill_bulk_urb完全一样,除了一个新参数 unsgined char *setup_packet:指向即将被发送到端点的设置数据包的数据
等时urb
等时urb没有中断、控制和批量urb类似的初始化函数,需要手动的进行初始化
urb->dev = dev; urb->context = uvd; urb->pipe = urb_rcvisopipe(dev, uvd->video_endp-1); urb->interval = 1; urb->transfer_flags = URB_ISO_ASAP; urb->transfer_buffer = cam->sts_buf[i]; urb->complete = konicawc_isoc_irq; urb->number_of_packets = FRAMES_PER_DESC; urb->transfer_buffer_length = FRAMES_PER_DESC; for(j=0;j<FRAMES_PER_DESC;j++) { urb->iso_frame_desc[j].offset = j; urb->iso_frame_desc[j].length = 1; }
提交urb
int usb_submit_urb(struct urb *urb, int mem_flags); urb:指向即将被发送到设备的urb指针 mem_flags:传递给kmalloc的同一个参数 GFP_ATOMIC: GFP_NOIO:在所有存储类型的设备的错误处理路径中也应该使用它 GFP_KERNEL:该值应该在前述类别之外的所有情况中使用
结束urb:结束回调处理例程
取消urb
int usb_kill_urb(struct urb *urb); int usb_unlink_urb(struct urb *urb);
二、编写USB驱动程序
对于PCI设备,有许多用来初始化该结构体的宏:
USB_DEVICE(vendor, product) 创建一个struct usb_device_id结构体,仅指定制造商和产品ID匹配 USB_DEVICE_VER(vendor, product, lo, hi) 创建一个struct usb_device_id结构体,指定制造商和产品ID值相匹配 USB_DEVICE_INFO(class, subclass, protocol) 创建一个struct usb_device_id结构体,仅和USB设备的指定类型相匹配 USB_INTERFACE_INFO(class, subclass, protocol) 创建一个struct usb_device_id结构体,仅和USB接口的指定类型相匹配 struct usb_device_id表将被定义为: /* 该驱动程序支持的设备列表 */ static struct usb_device_id skel_table [ ] = { { USB_DEVICE(USB_SKEL_VENDOR_ID, USB_SKEL_PRODUCT_ID) }, { } }; MODULE_DEVICE_TABLE(usb, skel_table);
注册USB驱动程序
创建一个有效的struct usb_driver结构体只需要初始化五个字段:
static struct usb_driver skel_driver = { .owner = THIS_MODULE, .name = "skeleton", .id_table = skel_table, .probe = skel_probe, .disconnect = skel_disconnect, };
接口函数原型:
int (*probe)(struct usb_interface *intf, const struct usb_device_id *id) 探测函数 void (*disconnect)(struct usb_interface *intf) 断开函数 int (*ioctl)(struct usb_interface *intf, unsigned int code, void *buf) ioctl函数 int (*suspend)(struct usb_interface *intf, u32 state) 挂起函数 int (*resume)(struct usb_interface *intf) 恢复函数
探测和断开的细节
/* 设置端点信息 */ /* 只使用第一个批量IN和批量OUT端点 */ iface_desc = interface->cur_altsetting; for(i=0;i<iface_desc->desc.bNumEndpoints;++i) { endpoint = &iface_desc->endpoint[i].desc; if(!dev->bulk_in_endpointAddr && (endpoint->bEndpointAddress & USB_DIR_IN) && ((endpoint->bmAttributes & USB_ENDPOINT_XFERTYPE_MASK) == USB_ENDPOINT_XFER_BULK)) { /* 发现一个批量IN类型的端点 */ buffer_size = 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_DIR_IN) && ((endpoint->bmAttributes & USB_ENDPOINT_XFERTYPE_MASK) == USB_ENDPOINT_XFER_BULK)) { /* 发现一个批量OUT类型的端点 */ 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; }
提交和控制urb
/* 分配urb来把数据传输给设备 */ urb = usb_alloc_urb(0, GFP_KERNEL); if(!urb) { retval = -ENOMEM; goto error; } /* 创建DMA缓冲区来以最高效的方式发送数据到设备 */ buf = usb_buffer_alloc(dev->udev, count, GFP_KERNEL, &urb->transfer_dma); if(!buf) { retval = -ENOMEM; goto error; } if(copy_from_user(buf, user_buffer, count)) { retval = -EFAULT; goto error; } /* 数据被复制到局部缓冲区中,urb必须可以提交给USB核心之前被正确的初始化 */ usb_fill_bulk_urb(urb, dev->udev, usb_sndbulkpipe(dev->udev, dev->bulk_out_endpointAddr), buf, count, skel_write_bulk_callback, dev); urb->transfer_flags |= URB_NO_TRANSFER_DMA_MAP; /* urb正确分配,数据被正确复制,urb正确初始化,然后可以提交给USB核心以传输到设备 */ /* 把数据从批量端口发出 */ retval = usb_submit_urb(urb, GFP_KERNEL); if(retval) { err("%s - failed submitting write urb, error %d", __FUNCTION__, retval); goto error; } /* urb被成功地传输到USB设备之后,urb毁掉函数将被USB核心调用 */ static void skel_write_bulk_callback(struct urb *urb, struct pt_regs *regs) { /* sync/async解连接故障不是错误 */ if(urb->status && !(urb->status == -ENOENT || urb->status == -ECONNRESET || urb->status == -ESHUTDOWN)) { dbg("%s - nonzero write bulk status received: %d", __FUNCTION__, urb->status); } /* 释放已分配的缓冲区 */ usb_buffer_free(urb->dev, urb->transfer_buffer_length, urb->transfer_buffer, urb->transfer_dma); }
不使用urb的USB传输
有时候USB驱动程序只是要发送或者接受一些简单的USB数据,而不想把上面的流程走一遍,有两个函数提供了更简单接口和函数的使用。
usb_bulk_msg
int usb_bulk_msg(struct usb_device *usb_dev, unsigned int pipe, void *data, int len, int *actual_length, int timeout); struct usb_device *usb_dev:发送的目标USB指针 unsigned int pipe:目标USB的特定端点 void *data:如果是OUT端点,指向即将发送到设备的数据的指针 int len:data参数所指缓冲区的大小 int *actual_length:指向保存实际传输字节数的位置的指针 int timeout:等待的超时时间
函数调用例子:
/* 进行阻塞的批量读以设别获取数据 */ retval = usb_bulk_msg(dev->udev, usb_rcvbulkpipe(dev->udev, dev->bulk_in_endpointAddr), dev->bulk_in_buffer, min(dev->bulk_in_size, count), &count, HZ*10); /* 如果读取成功,复制数据到用户空间 */ if(!retval) { if(copy_to_user(buffer, dev->bulk_in_buffer, count)) retval = -EFAULT; else retval = count; }
usb_control_msg
int usb_control_msg(struct usb_device *dev, unsigned int pipe, __u8 request, __u8 requesttype, __u16 value, __u16 index, void *data, __u16 size, int timeout); struct usb_device *dev:控制消息所发送的目标USB设备的指针 unsigned int pipe:该控制消息所发送的目标USB设备的特定端点 __u8 request:控制消息的USB请求值 __u8 requesttype:控制消息的USB请求类型值 __u16 value:USB消息值 __u16 index:USB消息索引值 void *data:一个OUT端点,指向即将发送到设备的数据指针 __u16 size:data参数所指缓冲区的大小 int timeout:等待的超时时间
其他USB数据函数
int usb_get_descriptor(struct usb_device *dev, unsigned char type, unsigned char index, void *buf, int size); struct usb_device *usb_dev:指向想要获取描述符的目标USB设备的指针 unsigned char type:描述符的类型 unsigned char index:应该从设备获取的描述符的编号 void *buf:指向复制描述符到其中的缓冲区的指针 int size:buf变量所指的内存大小