USBIP源碼分析


簡介

在普通的電腦上,想使用USB設備,必須將插入到主機。USBIP卻可以通過網絡,讓主機訪問其他主機上的外部設備,而用戶程序完全感知不到區別。

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層。

  • 最靠近用戶層的是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端的代碼,其余是公共部分的代碼。

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

Stub端

主要結構體

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

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個,做的工作如下

1、初始化全局變量busid_table
2、分配一個slab用來做stub_priv的分配工作
3、注冊一個usb驅動,這里是stub的初始化,這里注冊的驅動也是位於設備端,USB核心上層的USB驅動
4、創建兩個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個點

1、模塊的初始化時注冊的驅動程序,在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,
};

2、創建的幾個sysfs文件。

3、3個內核線程的工作

就不贅述了


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM