usb host 驱动之 urb

1、URB 处理流程

(1)usb 设备驱动程序创建并初始化一个访问特定usb设备特定端点的 urb,并提交给 usb core;
(2)usb core 提交该 urb 到 usb 主控制器驱动程序;
(3)usb 主控制器驱动程序根据 urb 描述的信息,来访问 usb 设备;
(4)当设备访问结束后,usb 主控制器驱动程序通知 usb core(调用这个函数usb_complete_t complete;)然后其再通知usb设备驱动程序。

1.1 创建URB

struct urb *usb_alloc_urb(int iso_packets, int mem_flags)
// iso_packets: urb 所包含的等时数据包的个数,若不是等时传输,则为0.
// mem_flags: 内存分配标识(如GFP_KERNEL),参考kmalloc

1.2 初始化URB

(1)对于中断urb,使用usb_fill_int_urb函数来初始化:

static inline void usb_fill_int_urb (struct urb *urb,	//要初始化的urb指针。
                     struct usb_device *dev,	//所要访问的设备
                     unsigned int      pipe,	//要访问的端点所对应的管道,使用usb_sndintpipe()或usb_rcvintpipe()创建
                     void              *transfer_buffer,	//要传输的数据缓冲区
                     int               buffer_length,		//缓冲区长度
                     usb_complete_t    complete_fn,			//当完成该urb所请求的操作时,要调用的回调函数
                     void              *context,			//complet_fn函数所需的上下文,通常取值为dev
                     int               interval)			//urb被调度的时间间隔

(2)对于批量 urb,使用usb_fill_bulk_urb函数来初始化。
(3)对于控制 urb,使用usb_fill_control_urb函数来初始化。
(4)等时 urb 没有像中断,控制和批量 urb 那样的初始化函数,我们只能手动的初始化 urb。

1.2.1 管道 pipe

管道:驱动程序的数据缓冲区与一个端点的连接,它代表了一种在两者之间移动数据的能力。

/*
 * For various legacy reasons, Linux has a small cookie that's paired with
 * a struct usb_device to identify an endpoint queue.  Queue characteristics
 * are defined by the endpoint's descriptor.  This cookie is called a "pipe",
 * an unsigned int encoded as:
 *
 *  - direction:	bit 7		(0 = Host-to-Device [Out],
 *					 1 = Device-to-Host [In] ...
 *					like endpoint bEndpointAddress)
 *  - device address:	bits 8-14       ... bit positions known to uhci-hcd
 *  - endpoint:		bits 15-18      ... bit positions known to uhci-hcd
 *  - pipe type:	bits 30-31	(00 = isochronous, 01 = interrupt,
 *					 10 = control, 11 = bulk)
 *
 * Given the device address and endpoint descriptor, pipes are redundant.
 */

1.2.2 端点 endpoint

端点:设备的固有属性,用来与主机通信。

USB 外设本身应包含一定数量的独立的寄存器端口,并能由 USB 设备驱动程序直接操作。这些寄存器也就是 USB 设备的端点。一个设备可以有多个端点,但所有的 USB 设备都必须有一个零端点以用于设置,完成 Control 类型传送。

端点数量:低速设备3对;全速、高速16对(32个)。

1.3 提交URB

在完成urb 的创建和初始化后,urb 便可以通过usb_submit_urb函数来提交给usb 核心:

int usb_submit_urb(struct urb *urb,gfp_t mem_flags)
// urb:指向urb的指针;
// mem_flags:内存分配标识,它用于告知usb核心如何分配内存缓冲区。

1.4 处理URB

URB 被提交到 USB 核心后,usb 核心指定 usb 主控制器驱动程序来处理该 urb,在 3 种情况下,urb会被认为处理完成:
(1)urb 被成功发送给设备,并且设备返回成功确认。如果urb->status为0,意味着对于一个输出urb,数据被成功发送;对于一个输入urb,请求的数据被成功收到。
(2)如果发送数据到设备或从设备接受数据时发生了错误,urb->status将记录错误值。
(3)urb 被“取消”,这发生在驱动通过usb_unlink_urb()usb_kill_urb()函数取消 urb,或 urb 虽已提交,而 usb 设备被拔出的情况下。

当urb处理完成后,urb 完成函数将被调用。

2、urb submit and giveback

2.1 submit urb

urb 的创建,初始化,提交都是usb 设备驱动程序完成的,之后usb 设备驱动程序会调用usb_submit_urb()函数提交给usb core

usb_submit_urb()是异步函数,usb_fill_bulk_urb()有注册完成函数,urb 提交usb_submit_urb()立马返回,usb 设备处理完后 usb core 就会回调urb->complete()

以下以手机内存 cp 数据到外设 U盘 urb 过程为例。

2.1.1 设备驱动submit urb 到hcd

SUBMIT_CMD_URB :
== uas_submit_urbs(struct scsi_cmnd *cmnd, struct uas_dev_info *devinfo)
	== usb_submit_urb(cmdinfo->cmd_urb, GFP_ATOMIC);
		== usb_hcd_submit_urb(struct urb *urb, gfp_t mem_flags)
			hcd->driver->urb_enqueue(hcd, urb, mem_flags);
			== xhci_urb_enqueue();				trace_xhci_urb_enqueue(urb);
				== xhci_queue_bulk_tx();		//批量传输
					== prepare_transfer();
						== usb_hcd_link_urb_to_ep()		//add an URB to its endpoint queue
							== list_add_tail(&urb->urb_list, &urb->ep->urb_list);
SUBMIT_DATA_IN_URB:
== uas_submit_urbs(struct scsi_cmnd *cmnd, struct uas_dev_info *devinfo)
	== usb_submit_urb(cmdinfo->data_in_urb, GFP_ATOMIC);

usb_hcd_link_urb_to_ep()usb core 的 api,目的是 hcd 主机控制器把 urb 放入端点队列中,等待底层发送。
与之对应的是调用usb_hcd_unlink_urb_from_ep()usb_hcd_giveback_urb()将 urb 从 usb core 归还给设备驱动,完成一次 urb 的处理。

2.1.2 hcd 发送urb

== xhci_urb_enqueue()						trace_xhci_urb_enqueue(urb);
	== xhci_queue_bulk_tx()
		== prepare_transfer()	
		== queue_trb()						trace_xhci_queue_trb(ring, trb);	//queueing a TRB on a ring
			== inc_enq()					trace_xhci_inc_enq(ring);
		== giveback_first_trb()		//Pass all the TRBs to the hardware at once and make sure this write isn't reordered.
			== xhci_ring_ep_doorbell()		trace_xhci_ring_ep_doorbell(slot_id, DB_VALUE(ep_index, stream_id));
				== writel(DB_VALUE(ep_index, stream_id), db_addr);

2.1.3 hcd 接收中断 giveback urb 给设备驱动

data urb:

inc_deq														trace_xhci_inc_deq(ring);
== xhci_irq(struct usb_hcd *hcd)
	== xhci_handle_event(struct xhci_hcd *xhci)				trace_xhci_handle_event(xhci->event_ring, &event->generic);
		== handle_tx_event()								trace_xhci_handle_transfer(ep_ring, (struct xhci_generic_trb *) ep_trb)
			== process_bulk_intr_td()		//Process bulk and interrupt tds, update urb status and actual_length.
				== finish_td()
					== xhci_td_cleanup()
						== xhci_giveback_urb_in_irq()		trace_xhci_urb_giveback(urb);
							== usb_hcd_unlink_urb_from_ep(hcd, urb);
							== usb_hcd_giveback_urb(hcd, urb, status);

cmd urb:(cmd completion 控制器接收后处理,不用giveback 给设备驱动)

inc_deq()									trace_xhci_inc_deq(ring);
== xhci_irq()	
	== xhci_handle_event()					trace_xhci_handle_event(xhci->event_ring, &event->generic);
		== handle_cmd_completion()			trace_xhci_handle_command(xhci->cmd_ring, &cmd_trb->generic);
			== xhci_handle_cmd_set_deq()	trace_xhci_handle_cmd_set_deq(slot_ctx);
											trace_xhci_handle_cmd_set_deq_ep(ep_ctx);
				== xhci_dbg_trace(xhci, trace_xhci_dbg_cancel_urb,
					"Successful Set TR Deq Ptr cmd, deq = @%08llx", deq);

2.2 kill urb

== usb_kill_urb(struct urb *urb)
	== usb_hcd_unlink_urb(struct urb *urb, int status)
		== unlink1(hcd, urb, status);
			hcd->driver->urb_dequeue(hcd, urb, status);
			== xhci_urb_dequeue(struct usb_hcd *hcd, struct urb *urb, int status)
			   //Remove the URB's TD from the endpoint ring.
			   == list_add_tail(&td->cancelled_td_list, &ep->cancelled_td_list);
			   
//如果dequeue 失败,则直接giveback urb,不用等xhci_irq
usb_hcd_unlink_urb_from_ep()——usb_hcd_giveback_urb()——tasklet_hi_schedule(&bh->bh);——usb_giveback_urb_bh()——__usb_hcd_giveback_urb()

usb_add_hcd()——init_giveback_urb_bh()——tasklet_setup(&bh->bh);——tasklet_schedule(&bh->bh);

2.3 dequeue urb

== xhci_urb_dequeue()	trace_xhci_urb_dequeue(urb);
	== xhci_dbg_trace(xhci, trace_xhci_dbg_cancel_urb,
				"Cancel URB %p, dev %s, ep 0x%x, "
				"starting at offset 0x%llx", ...)
	== xhci_queue_stop_endpoint()		//发送一个stop ep 的cmd

//dequeue 成功之后,产生中断cmd stop ep
inc_deq				trace_xhci_inc_deq(ring);
== xhci_irq()	
	== xhci_handle_event()							trace_xhci_handle_event(xhci->event_ring, &event->generic);
		== handle_cmd_completion()					trace_xhci_handle_command(xhci->cmd_ring, &cmd_trb->generic);
			== xhci_handle_cmd_stop_ep()			trace_xhci_handle_cmd_stop_ep(ep_ctx);
				== xhci_invalidate_cancelled_tds()	xhci_dbg_trace(xhci, trace_xhci_dbg_cancel_urb,
					"Removing canceled TD starting at 0x%llx (dma).",
					== xhci_move_dequeue_past_td()	xhci_dbg_trace(xhci, trace_xhci_dbg_cancel_urb,
		       			"Set TR Deq ptr 0x%llx, cycle %u\n", addr, new_cycle);
						== xhci_ring_cmd_db()		trace_xhci_ring_host_doorbell(0, DB_VALUE_HOST);
				== xhci_giveback_invalidated_tds(ep);

3、hub urb

3.1 hub urb 初始化

   //root hub(3-1:1.0 2-1:1.0)作为struct usb_device *hdev的一个interface注册后:
== hub_probe(struct usb_interface *intf, const struct usb_device_id *id)
	   //配置hub,interface的端点0:
	== hub_configure(hub, &desc->endpoint[0].desc);
		== usb_fill_int_urb(hub->urb, hdev, pipe, *hub->buffer, maxp, hub_irq,
			hub, endpoint->bInterval);			//初始化hub->urb和完成函数hub_irq
		== hub_activate(hub, HUB_INIT);			//启用hub
			== usb_submit_urb(hub->urb, GFP_NOIO);		//提交hub->urb
			
   //提交后若hub上有设备插入等端口状态变化事件发生,则会调用完成函数hub_irq
== hub_irq(struct urb *urb)
	== kick_hub_wq(hub);	/* Something happened, let hub_wq figure it out */
		== queue_work(hub_wq, &hub->events)
			== hub_event(struct work_struct *work)
				== port_event(struct usb_hub *hub, int port1)
//根据hub->change_bits、hub->event_bits、hub->wakeup_bits检测是哪个port发生了事件,并根据portchange和portstatus判断发生了什么事件。

3.2 USB设备插入调用urb

root hub 检测到有 usb 设备插入,最后是怎么加载U盘驱动的?我做了简单的代码走读,通过枚举到的 PID/VID 信息匹配到 U盘驱动或者 HID 键鼠驱动等,就回调相应驱动的 probe 驱动入口了,最后就能看到 /dev/sda 或者 /dev/input/even0了:

== usb_submit_urb(hub->urb, GFP_NOIO);		//提交hub->urb
	== usb_hcd_submit_urb()
		== rh_urb_enqueue(hcd, urb);		//root hub
			== rh_call_control()			//control urb
				== usb_hcd_link_urb_to_ep(hcd, urb);
				== hcd->driver->hub_control()	// non-generic request
					== xhci_hub_control()
						== xhci_get_port_status()
				== usb_hcd_unlink_urb_from_ep(hcd, urb);
				== usb_hcd_giveback_urb(hcd, urb, status);		// 返回给设备,即rhdev
			== rh_queue_status(hcd, urb);		//int urb 循环检测hub 的状态
				== usb_hcd_link_urb_to_ep(hcd, urb);
				== mod_timer(&hcd->rh_timer, jiffies);
					== timer_setup(&hcd->rh_timer, rh_timer_func, 0);
					== rh_timer_func()
						== usb_hcd_poll_rh_status(_hcd);
							== hcd->driver->hub_status_data(hcd, buffer);
							== xhci_hub_status_data(struct usb_hcd *hcd, char *buf)
							== usb_hcd_unlink_urb_from_ep(hcd, urb);	// 如果有change,返回给设备即rhdev
							== usb_hcd_giveback_urb(hcd, urb, 0);
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值