USBIP源碼分析

簡介

在普通的電腦上,想使用USB設備,必須將插入到主機。USBIP卻能夠經過網絡,讓主機訪問其餘主機上的外部設備,而用戶程序徹底感知不到區別。linux

usbip的文章在這裏:https://pdfs.semanticscholar.org/c7c4/cb054d75810fdb0a2affa11c288b7687267f.pdf數組

本文基於linux內核4.4.3,分析USBIP這部分的源碼。先介紹總體結構,而後分兩部分介紹USBIP源碼網絡

USBIP總體架構

從體系機構的角度上來講,USB的設備和總線都是經過Host分出,host叫作主機控制器,一個主機控制器會有一個root hub,而後root hub再經過接口分出多個hub,最後,一個hub的一個端口均可以用來鏈接一個外部設備或是寧一個hub,造成一個以host爲跟的樹狀結構。而Host最終是做爲一個PCI的設備,鏈接在PCI總線上。架構

在linux內核中,USB驅動的架構能夠分紅3層。app

  • 最靠近用戶層的是USB設備驅動,這是每一個USB設備有獨有的,在它之上能夠直接與VFS交互,好比鍵盤和鼠標這些設備,USB設備驅動就足以處理外部設備的交互任務,由於他們發送過來的就只有是一些很簡單的數據,可是若是是一個插在USB上的網卡等設備,則須要在USB設備驅動之上再覆蓋一層網卡等驅動,用來解析USB總線傳輸過來的數據。它與下層的USB Core經過urb交換數據。
  • USB Core則是承上啓下的一層,USB設備驅動經過urb的抽次昂概念來和底層交互,USB Core就用來解析urb,同時,還會包含Hub驅動等等一些USB驅動框架的其餘主要部分。
  • 最下層的是主機控制器驅動,前面看到USB外部設備最終經過Host和總線、CPU交互,也就是說最底層還須要有一個主機控制器的驅動,它主要負責Host的相關工做,將USB設備的信息經過主機控制器向上傳遞軟件層。這部分會有一個PCI驅動,而後控制Host硬件

 理解了USB驅動的框架,USBIP的架構就比價容易了,主要部分也是兩個,讀取設備的主機端,設置一個虛擬的主機控制器接口VHCI,它不操縱底層的主機控制器,而是將上層的消息經過網絡轉發到另外一個主機,在另外一側,實現一個USB設備驅動,它不是將USB Core的內容向上傳遞,一樣是經過網絡發送出去,叫作Stub端。框架

在Linux內核中,USBIP的源碼寫在drivers/usb/usbip目錄下,在這些文件中,以stub開頭的都是server端的代碼,vhci開頭的是client端的代碼,其他是公共部分的代碼。socket

下面從Stub和VHCI的角度來分析。tcp

Stub端

主要結構體

usbip_common中有一個usbip_device結構,這是stub和vhci兩邊設備從後向出來的公共部分。這裏有3個內核線程,一個socket,以及和eh相關的等待隊列和操做集合。usbip_common中其餘幾個結構就再也不敘述,比較簡單ide

struct usbip_device {
    enum usbip_side side;
    enum usbip_device_status status;

    /* lock for status */
    spinlock_t lock;

    struct socket *tcp_socket;//用來通訊的socket

    struct task_struct *tcp_rx;//收消息的線程
    struct task_struct *tcp_tx;//發送消息的線程

    unsigned long event;//記錄事件
    struct task_struct *eh;//內核線程
    wait_queue_head_t eh_waitq;//eh等待隊列

    struct eh_ops {
        void (*shutdown)(struct usbip_device *);
        void (*reset)(struct usbip_device *);
        void (*unusable)(struct usbip_device *);
    } eh_ops;
};

stub.h中定義了關鍵的結構體,他們的內容和含義以下:函數

stub_device是stub端的設備抽象,表明以一個外部設備

struct stub_device {
    struct usb_interface *interface;//接口描述符指針
    struct usb_device *udev;
    struct usbip_device ud;//對於stub和vhci兩端的設備都作了抽象,用來表示兩端交互中都須要的內容
    __u32 devid;
    spinlock_t priv_lock;
    struct list_head priv_init;//urb初始隊列
    struct list_head priv_tx;//urb被提交以後
    struct list_head priv_free;//urb的內容被髮送給了chci
    struct list_head unlink_tx;//unlink請求隊列
    struct list_head unlink_free;//unlink請求被處理
    wait_queue_head_t tx_waitq;//等待隊列
};

stub_priv被賦值給urb->priv字段,主要是給stub端來管理urb結構體

struct stub_priv {
    unsigned long seqnum;//給每一個urb都有一個序列號
    struct list_head list;
    struct stub_device *sdev;
    struct urb *urb;
    int unlinking;//是否被unlink
};

stub_unlink表示一個urb的unlink請求

struct stub_unlink {
    unsigned long seqnum;//和stub_priv對於的序列號
    struct list_head list;
    __u32 status;//unlink是否成功
};

stub_main中有一個全局變量的數組,用來管理全部的設備

static struct bus_id_priv busid_table[MAX_BUSID];

其中一個設備用bus_id_priv來表示

struct bus_id_priv {
    char name[BUSID_SIZE];
    char status;
    int interf_count;
    struct stub_device *sdev;
    struct usb_device *udev;
    char shutdown_busid;
};

模塊的初始化函數

先看stub_main中最後幾行,有模塊的的初始和卸載函數

module_init(usbip_host_init);
module_exit(usbip_host_exit);

usbip_host_init中主要函數有4個,作的工做以下

一、初始化全局變量busid_table
二、分配一個slab用來作stub_priv的分配工做
三、註冊一個usb驅動,這裏是stub的初始化,這裏註冊的驅動也是位於設備端,USB核心上層的USB驅動
四、建立兩個sysfs文件,這兩個文件的操做就在上面,rebind_store和store_match_busid、show_match_busid。

下面的退出函數usbip_host_exit同理,只是作了這幾個函數的清理工做

 先看看它建立的兩個sysfs文件。這是給用戶態的接口。一個是match_busid

static DRIVER_ATTR(match_busid, S_IRUSR | S_IWUSR, show_match_busid,store_match_busid);

涉及的函數是show_match_busid和store_match_busid,show_match_busid用來輸出名字,store_match_busid則用來增長或是刪除busid_table中的條目

另外一個文件則是用來設置設備綁定的驅動。

這幾個文件的操做都是圍繞busid_table和它的幾個操做函數開展開,也比較簡單。整個文件的內容也分析完了。下面就還有一個關鍵的usb驅動,也就是Stub驅動,很顯然,主要的工做都是經過這個驅動來開展的。

Stub驅動

stub_dev文件最底部有這個驅動

struct usb_device_driver stub_driver = {
    .name        = "usbip-host",
    .probe        = stub_probe,
    .disconnect    = stub_disconnect,
#ifdef CONFIG_PM
    .suspend    = stub_suspend,
    .resume        = stub_resume,
#endif
    .supports_autosuspend    =    0,
};

從probe函數開始,這裏只說一些和Stub驅動自身邏輯相關的重要部分,proe函數經過stub_device_alloc分配了一個stub_device函數。而這個stub_device_alloc函數中還有一個usbip_start_eh函數。用來建立了一個內核線程eh。

int usbip_start_eh(struct usbip_device *ud)
{
    init_waitqueue_head(&ud->eh_waitq);
    ud->event = 0;

    ud->eh = kthread_run(event_handler_loop, ud, "usbip_eh");
    if (IS_ERR(ud->eh)) {
        pr_warn("Unable to start control thread\n");
        return PTR_ERR(ud->eh);
    }

    return 0;
}
EXPORT_SYMBOL_GPL(usbip_start_eh);

前面看到usbip_device結構中有3個內核線程,這是其中一個,二且eh_waitq就是用來給eh睡眠的,出發事件寫在usbip_event_happened函數中,只是檢查usbip_device上是否有事件產生,event字段會否爲0。

好,接着回到probe函數,裏面有一個stub_add_files函數。能夠知道這個函數一樣建立了幾個sysfs文件,他們爲dev_attr_usbip_status、dev_attr_usbip_sockfd、dev_attr_usbip_debug。

其中比較關鍵的是dev_attr_usbip_sockfd,當寫入數據爲-1時執行關閉操做,另外一條分支則會經過寫入數據打開socket,賦值給tcp_socket字段,接着建立了兩個內核線程。

看看這個stub_dev文件,剩下的代碼都是處理斷開和釋放的函數了,這裏就不分析,剩下的兩個文件stub_rx和stub_tx能夠知道這是和內核線程相關的了。因此說剩下的主要內容就是這兩個內核線程。

rx和tx兩個內核線程和stub_device中的5個鏈表

先看rx,主函數以下,沿着這個邏輯往下看

int stub_rx_loop(void *data)
{
    struct usbip_device *ud = data;

    while (!kthread_should_stop()) {
        if (usbip_event_happened(ud))
            break;

        stub_rx_pdu(ud);
    }

    return 0;
}

stub_rx_pdu函數中,調用usbip_recv收一個消息,usbip_header_correct_endian轉換成小端,而後根據收到的消息來作不一樣的處理,若是是unlink消息則調用stub_recv_cmd_unlink,若是是urb的提交消息則調用stub_recv_cmd_submit。

說一下priv_init、priv_tx、priv_free這3個隊列。stub中的urb經過stub_priv來進行管理。經過stub_priv,給每一個urb分配了序列號,同時,經過stub_priv中的list字段,將urb鏈接到上述3個隊列中,當urb被提交給USB Core,直到完成前,放在priv_init,提交完成後,還須要經過網絡發送給vhci,發送以前放在priv_tx,發送以後放在priv_free。

因此說,stub_recv_cmd_unlink的行爲就是遍歷priv_init,找到那些尚未完成的urb,調用usb_unlink_urb讓USB Core去取消他們。

stub_recv_cmd_submit函數,調用了stub_priv_alloc,這個函數的末尾將urb加入到了priv_init中。最後調用usb_submit_urb提交urb,注意這裏的回調函數,是stub_complete。這個函數在urb執行完後執行下面的語句,將urb移入priv_tx

list_move_tail(&priv->list, &sdev->priv_tx);

而後,調用了wake_up,喚醒tx內核線程

wake_up(&sdev->tx_waitq);

接着看rx線程,主要是兩個函數stub_send_ret_submit和stub_send_ret_unlink,由於這是一個發送消息的線程,因此發送的消息有兩種,一個是USB設備的交互內容,一種是vhci發送的unlink消息的回覆。

stub_send_ret_submit處理的就是USB設備的返回內容,dequeue_from_priv_tx將urb從priv_tx取下,放入priv_free。stub_send_ret_submit最後再將內容封裝成usbip協議規定的樣子,經過網絡發送出去。

下面再介紹剩下的兩個隊列,unlink_tx和unlink_free,這是用來處理unlink的兩個隊列,一個stub_unlink結構表示一個unlink請求,unlink_tx存放尚未被回覆的unlink請求,而unlink_free則是存放已經回覆了的。

因此stub_send_ret_unlink中的dequeue_from_unlink_tx用來完成unlink的隊列轉換,剩下的代碼就會經過網絡發送回覆消息

這樣,stub這邊的幾個函數就都已經完了

VHCI端

這邊的代碼和上面已經很是類似了,抓住3個點

一、模塊的初始化時註冊的驅動程序,在usb_add_hcd中會調用reset和start函數,接着抓住urb的幾個操做函數就能夠了

static struct hc_driver vhci_hc_driver = {
    .description    = driver_name,
    .product_desc    = driver_desc,
    .hcd_priv_size    = sizeof(struct vhci_hcd),

    .flags        = HCD_USB2,

    .start        = vhci_start,
    .stop        = vhci_stop,

    .urb_enqueue    = vhci_urb_enqueue,
    .urb_dequeue    = vhci_urb_dequeue,

    .get_frame_number = vhci_get_frame_number,

    .hub_status_data = vhci_hub_status,
    .hub_control    = vhci_hub_control,
    .bus_suspend    = vhci_bus_suspend,
    .bus_resume    = vhci_bus_resume,
};

二、建立的幾個sysfs文件。

三、3個內核線程的工做

就不贅述了

相關文章
相關標籤/搜索