(二)USB驅動程式_USB裝置驅動(Host)
USB裝置驅動(Host)
深入,並且廣泛
-沉默犀牛
有了第一篇文章的基礎,我們這篇文章來看一下USB裝置驅動的原始碼。與其他的Driver一樣,USB的driver也表現為一個結構體:struct usb_driver
驅動整體結構
在編寫新的USB裝置驅動時,主要應該完成的工作是probe()和disconnect()函式,它們分別在Device被插入和拔出的時候呼叫,用於初始化和釋放軟硬體資源。usb_driver結構體中的id_table成員描述了這個USB驅動所支援的USB裝置列表,它指向一個usb_device_id陣列,例項如下:
static const struct usb_device_id skel_table[] = { { USB_DEVICE(USB_SKEL_VENDOR_ID, USB_SKEL_PRODUCT_ID) }, { } /* Terminating entry */ }; MODULE_DEVICE_TABLE(usb, skel_table); static struct usb_driver skel_driver = { .name = "skeleton", .probe = skel_probe, //probe .disconnect = skel_disconnect, //disconnent .suspend = skel_suspend, .resume = skel_resume, .pre_reset = skel_pre_reset, .post_reset = skel_post_reset, .id_table = skel_table, //id_table .supports_autosuspend = 1, };
USB_DEVICE這個巨集是可以根據Vendor ID和Product ID生成一個usb_device_id結構體的例項化。 當USB core檢測到某個Device的屬性和某個Drivre的usb_device_id結構體所攜帶的資訊一致的時候,這個Driver的probe()函式就被執行(如果這個USB驅動是個模組的話,相關的.ko還應被Linux自動載入)。拔掉Device或者解除安裝Drivre模組後,USB核心就執行disconnect()函式來響應這個動作。
usb_driver結構體中的函式是USB裝置驅動中與USB相關的部分,而USB只是一個匯流排,USB裝置驅動真正的主體工作仍然是USB裝置 本身所屬型別的驅動,比如字元裝置、tty裝置、塊裝置、輸入裝置等。 因此USB裝置驅動包含其作為 1.總線上掛接裝置的驅動 2.本身所屬裝置型別的驅動兩部分
儘管USB本身所屬裝置驅動的結構與其掛不掛在USB總線上沒什麼關係,但是據此在訪問方式上卻有很大的變化,例如,對於USB的TP而言,儘管仍然是中斷服務函式、韌體升級這些函式,但是在這些函式中,貫穿始終的是稱為URB的USB請求塊
舉個書中的例子: 我們把整個USB構架(Host側)看做一顆大叔,HostController是樹根,樹葉比作具體的USB裝置,樹幹和樹枝就是USB匯流排樹葉本身與樹枝是通過usb_driver結構體連線,而樹葉本身的驅動(讀寫、控制)則需要通過樹葉裝置本身所屬類裝置驅動來完成。樹根和樹葉之間的“通訊”依靠在樹幹和樹枝中“流淌”的URB完成
由此可見,usb_driver本身只是有找到USB裝置,管理USB裝置連線和斷開的作用,也就是說,它是公司入口處的“打卡機”,可以獲得員工(USB裝置)的上下班情況。樹葉和員工一樣,可以是研發工程師,也可以是銷售,而作為USB裝置的樹葉可以是字元樹葉、網路樹葉或塊樹葉,因此必須實現相應裝置類的驅動
URB介紹
URB結構體
根據上面的描述,我們知道了URB承載著USB裝置和Host controller通訊的核心資料,使用struct urb來描述: 僅註釋了重要成員
struct urb {
/* private: usb core and host controller only fields in the urb */
struct kref kref;
void *hcpriv;
atomic_t use_count;
atomic_t reject;
int unlinked;
/* public: documented fields in the urb that can be used by drivers */
struct list_head urb_list;
struct list_head anchor_list;
struct usb_anchor *anchor;
struct usb_device *dev; //urb所要傳送的目標 struct usb_device指標。該變數在urb可以被髮送到
USB core之前,必須由USB驅動程式初始化
struct usb_host_endpoint *ep;
unsigned int pipe; //urb所要傳送的特定目標struct usb_device的端點資訊。該變數在urb可以
被髮送到USB core之前,必須由USB驅動程式初始化
unsigned int stream_id;
int status; //當urb結束之後,或者正在被USB core處理時,該變數被設定為urb的當前狀態
這是為了防止當urb被USB core處理時競態的發生。
unsigned int transfer_flags; //該變數可以設為許多不同的位值,取決與USB驅動程式對urb的具體操作
詳情見LDD3 P333
void *transfer_buffer; //指向用於傳送資料到裝置(OUT urb)或者從裝置接受資料(IN urb)的緩衝區
的指標,為了使Host controller可以正確的訪問該緩衝區,必須使用kmalloc
來建立它,而不是在棧中或者靜態記憶體中。對於控制端點,該緩衝區用於傳輸數
據的中轉
dma_addr_t transfer_dma; // 用於以DMA方式傳輸資料到USB裝置的緩衝區
struct scatterlist *sg;
int num_mapped_sgs;
int num_sgs;
u32 transfer_buffer_length; //transfer_buffer或者transfer_dma變數所指向的緩衝區的大小(因為一個
urb 只能使用其中的一個)。若值為0,兩個傳輸緩衝區都沒有被USB core使用
u32 actual_length; //當urb結束之後,該變數被設定為urb所發出的資料(OUT urb)或者urb所接收
的資料(IN urb)的實際長度。對於IN urb,必須使用該變數而不是
transfer_buffer_length變數,因為所接收的資料可能小於整個緩衝區的尺寸
unsigned char *setup_packet; //指向控制urb的設定資料包的指標。它在傳輸緩衝區中的資料之前被傳送。該變數
只對控制urb有效
dma_addr_t setup_dma; //控制urb用於設定資料包的DMA緩衝區。它在普通傳輸 緩衝區中的資料之前被傳送
該變數只對控制urb有效
int start_frame; //設定或者返回初始的幀數量,用於等時傳輸
int number_of_packets; //僅對等時urb有效,指定該urb所助理的等時傳輸緩衝區的數量。對於等時urb,在
urb被髮送到USB core之前,USB驅動程式必須設定該值
int interval; //urb被輪詢的時間間隔。僅對中斷或者等時urb有效
int error_count; //有USB core設定,僅用於等時urb結束之後。它表示報告了任何一種型別錯誤的等
時傳輸的數量
void *context; //指向一個可以被USB驅動程式設定的資料塊。它可以在結束處理程式中,當urb被返
回到驅動程式時使用。
usb_complete_t complete; //指向一個結束處理程式的指標,讓urb被完全傳輸 或者發生錯誤時,USB core將調
用該函式。在該函式中,USB驅動程式可以檢查urb,釋放它,或者把它重新提交到
另一個傳輸中去
struct usb_iso_packet_descriptor iso_frame_desc[0];
//僅對等時urb有效。
};
URB處理流程
一個典型的urb生命週期如下: 1.由USB裝置驅動程式建立 2.分配給一個特定的USB裝置的特定端點 3.由USB裝置驅動程式遞交給USB core 4.由USB core遞交到特定裝置的特定USB Host controller驅動程式 5.由USB Host controller驅動程式處理,它從裝置進行USB傳送 6.當urb結束之後,USB Host controller驅動程式通知USB裝置驅動程式
urb可以在任何時候被遞交該urb的驅動程式取消掉,或者被USB核心取消,如果該裝置已從系統中移除。urb被動態的建立,它包含一個內部引用計數,使得它們可以在最後一個使用者釋放它們時自動地銷燬
例項分析
我們剛剛分析了USB驅動的整體結構,也說明了URB的用途和處理流程,接下來我們就找一個實際的例子來看看以下是kernel/drivers/input/touchscreen/usbtouchscreen.c檔案中的probe函式 (只保留了有關input 和 urb處理的部分)
static int usbtouch_probe(struct usb_interface *intf,
const struct usb_device_id *id)
{
struct usbtouch_usb *usbtouch;
struct input_dev *input_dev;
struct usb_endpoint_descriptor *endpoint;
struct usb_device *udev = interface_to_usbdev(intf);
struct usbtouch_device_info *type;
int err = -ENOMEM;
...
input_dev = input_allocate_device(); //input
...
usbtouch->type = type;
if (!type->process_pkt)
type->process_pkt = usbtouch_process_pkt; //之後會在urb完成處理函式中呼叫
...
usbtouch->irq = usb_alloc_urb(0, GFP_KERNEL); //urb的建立,(urb流程的第一步)
...
input_dev->name = usbtouch->name;
input_dev->phys = usbtouch->phys;
usb_to_input_id(udev, &input_dev->id);
input_dev->dev.parent = &intf->dev;
input_set_drvdata(input_dev, usbtouch);
input_dev->open = usbtouch_open;
input_dev->close = usbtouch_close;
input_dev->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_ABS);
input_dev->keybit[BIT_WORD(BTN_TOUCH)] = BIT_MASK(BTN_TOUCH);
input_set_abs_params(input_dev, ABS_X, type->min_xc, type->max_xc, 0, 0);
input_set_abs_params(input_dev, ABS_Y, type->min_yc, type->max_yc, 0, 0);
if (type->max_press)
input_set_abs_params(input_dev, ABS_PRESSURE, type->min_press,
type->max_press, 0, 0)
...
//中間這一部分是關於input的設定
if (usb_endpoint_type(endpoint) == USB_ENDPOINT_XFER_INT)
usb_fill_int_urb(usbtouch->irq, udev, //分配urb給特定USB的特定端點(urb處理流程第二步)
usb_rcvintpipe(udev, endpoint->bEndpointAddress),
usbtouch->data, usbtouch->data_size,
usbtouch_irq, usbtouch, endpoint->bInterval); //usbtouch_irq就是urb處理完成函式
else
usb_fill_bulk_urb(usbtouch->irq, udev,
usb_rcvbulkpipe(udev, endpoint->bEndpointAddress),
usbtouch->data, usbtouch->data_size,
usbtouch_irq, usbtouch);
...
err = input_register_device(usbtouch->input); //註冊到input子系統
...
if (usbtouch->type->irq_always) { //this can‘t fail 一定會進這個if
/* this can't fail */
usb_autopm_get_interface(intf);
err = usb_submit_urb(usbtouch->irq, GFP_KERNEL); //遞交給USB core (urb處理流程第四步)
...
}
return 0;
...
}
上述urb處理流程的第4、 5步由USB core來處理,等到urb處理完,會呼叫urb處理完成函式,如下:
static void usbtouch_irq(struct urb *urb)
{
struct usbtouch_usb *usbtouch = urb->context;
struct device *dev = &usbtouch->interface->dev;
int retval;
switch (urb->status) { //在完成處理函式中,一般都會判斷一下urb的狀態
case 0:
/* success */
break;
case -ETIME:
/* this urb is timing out */
dev_dbg(dev,
"%s - urb timed out - was the device unplugged?\n",
__func__);
return;
case -ECONNRESET:
case -ENOENT:
case -ESHUTDOWN:
case -EPIPE:
/* this urb is terminated, clean up */
dev_dbg(dev, "%s - urb shutting down with status: %d\n",
__func__, urb->status);
return;
default:
dev_dbg(dev, "%s - nonzero urb status received: %d\n",
__func__, urb->status);
goto exit;
}
usbtouch->type->process_pkt(usbtouch, usbtouch->data, urb->actual_length);
//還記得上面說的拿個(要被urb完成處理函式呼叫的)函式嗎?
...
}
以下就是usbtouch->type->process_pkt所指向的函式
static void usbtouch_process_pkt(struct usbtouch_usb *usbtouch,
unsigned char *pkt, int len)
{ //明顯就是上報點的函式
struct usbtouch_device_info *type = usbtouch->type;
if (!type->read_data(usbtouch, pkt))
return;
input_report_key(usbtouch->input, BTN_TOUCH, usbtouch->touch);
if (swap_xy) {
input_report_abs(usbtouch->input, ABS_X, usbtouch->y);
input_report_abs(usbtouch->input, ABS_Y, usbtouch->x);
} else {
input_report_abs(usbtouch->input, ABS_X, usbtouch->x);
input_report_abs(usbtouch->input, ABS_Y, usbtouch->y);
}
if (type->max_press)
input_report_abs(usbtouch->input, ABS_PRESSURE, usbtouch->press);
input_sync(usbtouch->input);
}
這樣我們就用這個usbtouchscreen.c檔案中的程式碼理清了USB裝置驅動的用法,一般的掛載在i2c總線上的TP是通過觸發中斷來執行中斷處理函式,在中斷處理函式的下半部完成報點,而掛載在USB匯流排後,是通過urb處理完成,來出發urb完成處理函式,在urb完成處理函式中,完成報點。