其实光看USB鼠标的驱动,不知道USB插到开发板上发生了什么事,感觉还是没有掌握。
一、USB插到开发板上发生了什么:
在linx 系统中,usb热插拔由两部分方面共同实现,即内核空间和用户空间,内核由一个守护进程实现,用户空间由udev 程序实现。
在内核空间里,有一个专门用于监控usb hub的状态的守护进程,守护进程通过等待队列实现,等待队列平时处理休眠状态,当usb hub上状态变化时(即有usb设备从usb hub上插入或拔出)时,便会去唤醒等待队列,然后去实现usb设备枚举,枚举成功后,向linux系统注册usb设备,并通过kobject_event通知用户空间,有设备插入或拔出,用户空间里有一个专门用于实现动态加载设备节点的程序udev,当它收到内核通知后,能够动态创建usb设备节点,这样便实现了usb的热插拔。
这是在网上搜到的比较好的描述,提到;守护进程、等待队列、设备枚举依次在linux源码中找到他们的具体实现。
1、守护进程
首先在USB子系统初始化过程,我们需要经过USB总线初始化、USB文件系统初始化、USB hub初始化、还有USB设备驱动注册,这个守护进程在USB hub实现:
int usb_hub_init(void) //drivers/usb/core.c
{
if (usb_register(&hub_driver) < 0) {
printk(KERN_ERR "%s: can't register hub driver\n",
usbcore_name);
return -1;
}
//hub_thread就是守护进程,在此处由kthread_run唤醒
khubd_task = kthread_run(hub_thread, NULL, "khubd");
if (!IS_ERR(khubd_task))
return 0;
/* Fall through if kernel_thread failed */
usb_deregister(&hub_driver);
printk(KERN_ERR "%s: can't start khubd\n", usbcore_name);
return -1;
}
static int hub_thread(void *__unused){/* khubd needs to be freezable to avoid intefering with USB-PERSIST * port handover. Otherwise it might see that a full-speed device * was gone before the EHCI controller had handed its port over to * the companion full-speed controller. */set_freezable();do {hub_events();// USB枚举过程wait_event_freezable(khubd_wait,!list_empty(&hub_event_list) ||kthread_should_stop());} while (!kthread_should_stop() || !list_empty(&hub_event_list));pr_debug("%s: khubd exiting\n", usbcore_name);return 0;}当运行完hub_events后,usb线程就会通过wait_event_freezable进入休眠状态(肯定要进入休眠状态,不然要死机),直到被信号中断或条件满足被唤醒, usb的守护进程由kick_khubd函数进行唤醒,该函数在多个地方被调用:
static void kick_khubd(struct usb_hub *hub)那么,到底发生什么事才会调用那些调用kick_khubd函数的函数?
{
unsigned longflags;
spin_lock_irqsave(&hub_event_lock, flags);
if (!hub->disconnected && list_empty(&hub->event_list)) {
list_add_tail(&hub->event_list, &hub_event_list);
/* Suppress autosuspend until khubd runs */
usb_autopm_get_interface_no_resume(
to_usb_interface(hub->intfdev));
wake_up(&khubd_wait);
}
spin_unlock_irqrestore(&hub_event_lock, flags);
}
usb设备枚举第一阶段,这部分主要工作是:遍历hub下的各个port,查看这些port是否有新设备加入,如果有新设备加入,则为这个设备申请资源,并去获取设备描述符,配置描述符去完善这个usb 设备,最后将这个port上的usb 设备注册到系统中;
当usb设备通过device_add注册到usb 总线上后,系统会去遍历usb总线上的所有驱动,寻找与usb设备驱动,通过usb_match_device进行匹配。对于usb设备,它的驱动应该是usb_xxx_driver,当驱动与设备匹配成功后,就会调用usb_xxx_driver中的xxx_probe函数,它个函数会从usb设备中找出一个合适 的usb配置,并对usb设备进行配置。
身为程序员,语言无法让我们相信,那么系统又是如何去遍历USB总线上的所有驱动?当然可以想象到
-------------------------------------------------------------------------------------------------------------------------------------------
对于USB驱动,我们最终会提交一个urb:
struct urb {这说明我们的数据就在这个urb包里边,我们在探测函数里边需要填充它,如:
/* private: usb core and host controller only fields in the urb */
struct kref kref;/* reference count of the URB */
void *hcpriv;/* private data for host controller */
atomic_t use_count;/* concurrent submissions counter */
atomic_t reject;/* submissions will fail */
int unlinked;/* unlink error code */
/* public: documented fields in the urb that can be used by drivers */
struct list_head urb_list;/* list head for use by the urb's
* current owner */
struct list_head anchor_list;/* the URB may be anchored */
struct usb_anchor *anchor;
struct usb_device *dev;/* (in) pointer to associated device */
struct usb_host_endpoint *ep;/* (internal) pointer to endpoint */
unsigned int pipe;/* (in) pipe information */
unsigned int stream_id;/* (in) stream ID */
int status;/* (return) non-ISO status */
unsigned int transfer_flags;/* (in) URB_SHORT_NOT_OK | ...*/
void *transfer_buffer;/* (in) associated data buffer */
dma_addr_t transfer_dma;/* (in) dma addr for transfer_buffer */
struct scatterlist *sg;/* (in) scatter gather buffer list */
int num_mapped_sgs;/* (internal) mapped sg entries */
int num_sgs;/* (in) number of entries in the sg list */
u32 transfer_buffer_length;/* (in) data buffer length */
u32 actual_length;/* (return) actual transfer length */
unsigned char *setup_packet;/* (in) setup packet (control only) */
dma_addr_t setup_dma;/* (in) dma addr for setup_packet */
int start_frame;/* (modify) start frame (ISO) */
int number_of_packets;/* (in) number of ISO packets */
int interval;/* (modify) transfer interval
* (INT/ISO) */
int error_count;/* (return) number of ISO errors */
void *context;/* (in) context for completion */
usb_complete_t complete;/* (in) completion routine */
struct usb_iso_packet_descriptor iso_frame_desc[0];
/* (in) ISO ONLY */
};
usb_fill_int_urb(mouse->irq, dev, pipe, mouse->data,函数具体内容如下:
(maxp > 8 ? 8 : maxp),
usb_mouse_irq, mouse, endpoint->bInterval);
static inline void usb_fill_int_urb(struct urb *urb,我们使用urb->context = mouse,最终还是走到了这里,原来这个上下文是mouse,这里也贴出mouse结构体的内容:
struct usb_device *dev,
unsigned int pipe,
void *transfer_buffer,
int buffer_length,
usb_complete_t complete_fn,
void *context,
int interval)
{
urb->dev = dev;
urb->pipe = pipe;
urb->transfer_buffer = transfer_buffer;
urb->transfer_buffer_length = buffer_length;
urb->complete = complete_fn;
urb->context = context;
if (dev->speed == USB_SPEED_HIGH || dev->speed == USB_SPEED_SUPER)
urb->interval = 1 << (interval - 1);
else
urb->interval = interval;
urb->start_frame = -1;
}
/* 每一个驱动都有一个结构体 */这个有一个成员,就是data,之前一直在想,我们使用input子系统到底在哪起到的作用,当然这个结构体里边也有一个input_dev,有当然有了,到底是怎么用的:
struct usb_mouse {
char name[128];
char phys[64];
struct usb_device *usbdev;
struct input_dev *dev;
struct urb *irq; // 用于传输数据
signed char *data; // 数据,我们最终需要在应用程序里边用到的数据
dma_addr_t data_dma;
};
input子系统只是提供了另外一种数据方法,也可以说是提供了应用程序open的文件,同时也提供了数据格式。
我们还需要把鼠标的数据整形,然后发到input里边,最后input里边的数据格式是input_event(不是函数名),这个结构体内容:
/*So ,就是这样,我们在应用成读出鼠标的数据就是上面的格式。
* The event structure itself
* 上报一个事件的格式,我们在应用层定义一个input_event,可以将上报的事件打印出来
*/
struct input_event {
struct timeval time;
__u16 type;
__u16 code;
__s32 value;
};
---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
插上鼠标,总线就检测到这个设备,经过多个函数调用后,执行到探测函数usb_mouse_probe(...),就是我们写的驱动程序里边的函数,这个函数要完成的事
是我们要写的。同时这个探测函数也带进了几个参数,这几个参数是由总线传下来的。
static int usb_mouse_probe(struct usb_interface *intf, const struct usb_device_id *id)有两个参数:intf和id
struct usb_interface {这个是由总线传下来的参数intf的内容,里边的一些成员我们需要赋给其他的东西。
/* 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 */
};
usb_fill_int_urb(mouse->irq, dev, pipe, mouse->data,我们需要填充urb这个结构体,我们就需要提前准备这些参数:
(maxp > 8 ? 8 : maxp),
usb_mouse_irq, mouse, endpoint->bInterval);
首先,每一个驱动都有一个设备,usbmouse的设备结构体:
我们的鼠标要有:名字(厂家、生产号等等)、路径、USB设备、输入设备、用于传数据的URB
struct usb_mouse {我们需要在驱动最开始定义这个结构体。
char name[128];
char phys[64];
struct usb_device *usbdev;
struct input_dev *dev;
struct urb *irq; // 用于传输数据
signed char *data;
dma_addr_t data_dma;
};
---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
内核里边提供的鼠标驱动,到这里,我还是没有搞懂的就是探测函数里边一大推数据结构的往这赋值往那赋值,卧槽,感觉太他妈乱了。
1.第一个参数
mouse->irq = usb_alloc_urb(0, GFP_KERNEL);
2.第二个参数
struct usb_device *dev = interface_to_usbdev(intf); //使这个dev连接这个USB接口