【驅動】USB驅動·入門

Preface

   USB是目前最流行的系統總線之一。隨着計算機周圍硬件的不斷擴展,各種設備使用不同的總線接口,導致計算機外部總線種類繁多,管理困難。USB總線正是因此而誕生的。

USB總線提供了所有外部設備的統一連接方式,並且支持熱插拔,方便了廠商開發設備和用戶使用設備。



USB遵循原則

   USB的設計目標是對現有的PC機體系進行擴充,但是目前不僅是PC機,許多的嵌入式系統都開始支持USB總線和接口標準。USB設計主要遵循下面幾個原則

    • 易於擴充外部設備:USB支持一個接口最多127個設備。

    • 靈活的傳輸協議: 支持同步和異步數據傳輸。

    • 設備兼容性好: 可以兼容不同類型的設備。

    • 接口標準統一:不同的設備之間使用相同的設備接口。



USB體系概述

   USB接口標準支持主機和外部設備之間進行數據傳輸。

   在USB體系結構中,主機預定了各種類型外部設備使用的總線帶寬。當外部設備和主機在運行時,USB總線允許添加、設置、使用和拆除外設。

   在USB體系結構中,一個USB系統可以分成USB互聯、USB設備和USB主機三個部分。

   USB互聯是USB設備和USB主機之間進行連接通信的操作,主要包括:

    • 總線拓撲結構:USB主機和USB設備之間的連接方式。

    • 數據流模式:描述USB通信系統中數據如何從產生方傳遞到使用方。

    • USB調度:USB總線是一個共享連接,對可以使用的連接進行了調試以支持同步數據傳輸,並且避免優先級判定的開銷。

   USB的物理連接是一個有層次的星形結構。

   在一個節點上連接多個設備需要使用 USB集線器(USB HUB)。

   USB體系結構規定,在一個 USB系統中,只有唯一的一個主機。USB和主機系統的接口稱做主機控制器,主機控制器由主機控制器芯片、固件程序和軟件共同實現的。

   USB設備包括USB集線器和功能器件。其中USB集線器的作用是擴展總線端點,向總線提供更多的連接點;功能器件是用戶使用的外部設備,如鍵盤,鼠標等。

   USB設備需要支持 USB總線協議,對主機的操作提供反饋並且提供設備性能的描述信息。



USB體系工作流程

   USB總線採用輪詢方式控制,主機控制設置初始化所有的數據傳輸。

   USB總線每次執行傳輸動作最多可以傳輸三個數據包。每次開始傳輸時,主機控制器發送一個描述符描述傳輸動作的種類和方向,這個數據包稱作標誌數據包(Token Packet)。USB設備收到主機發送的標誌數據包後解析出數據自己的數據。

   USB數據傳輸的方向只有兩種:主機到設備或者設備到主機。

   在一個數據傳輸開始時,由標誌包標示數據的傳輸方向,然後發送端開始發送包含信息的數據。接收端發送一個握手的數據包表明數據是否傳送成功。

   在主機和設備之間的USB數據傳輸可以看做一個通道。USB數據傳輸有流和消息兩種通道。消息是有格式的數據,而流是沒有數據格式的。

   USB有一個缺省的控制消息通道,在設備啓動的時候被創建,因此設備的設置查詢和輸入控制信息都可以使用缺省消息控制通道完成。



USB驅動程序框架

   Linux內核提供了完整的USB驅動程序框架。

   USB總線採用樹形結構,在一條總線上只能有唯一的主機設備。

   Linux內核從主機和設備兩個角度觀察USB總線結構。



Linux內核USB驅動框架



   左側是主機驅動結構。

   主機驅動的最底層是 USB主機控制器,提供了 OHCI/EHCI/UHCI這3種類型的總線控制功能。

   在USB控制器的上一層是主機控制器的驅動,分別對應OHCI/EHCI/UHCI這3種類型的總線接口。

   USB核心部分連接了 USB控制器驅動和設備驅動,是兩者之間的轉換接口。

   USB設備驅動層提供了各種設備的驅動程序。

   所有類型的 USB設備都是用相同的電氣接口,使用的傳輸協議也基本相同。

   向用戶提供某種特定類型的 USB設備時,需要處理 USB總線協議。內核完成所有的 USB總線協議處理,並且向用戶提供編程接口。


   右側是設備驅動結構。

   與USB主機類似,USB設備提供了相同的層次結構與之對應。但是在 USB設備一側使用名爲 Gadget API的結構作爲核心。

   Gadget API是 Linux內核實現的對應 USB設備的核心結構。Gadget API屏蔽了 USB設備控制器的細節,控制具體的 USB設備實現。



設備

   每個 USB設備提供了不同級別的配置信息。

   一個 USB設備可以包含一個或多個配置,不同的配置使設備表現出不同的特點。其中,設備的配置是通過接口組成的。

   Linux內核定義了 USB設備描述結構如下:

//源定義在Usb_ch9.h
/* USB_DT_DEVICE: Device descriptor */
struct usb_device_descriptor {
    __u8  bLength;  //設備描述符長度
    __u8  bDescriptorType;  //設備類型
    __le16 bcdUSB;  // USB版本號(使用 BCD編碼)
    __u8  bDeviceClass; //  USB設備類型
    __u8  bDeviceSubClass;  //  USB設備子類型
    __u8  bDeviceProtocol;  //  USB設備協議號
    __u8  bMaxPacketSize0;  //傳輸數據的最大包長
    __le16 idVendor;    //廠商編號
    __le16 idProduct;   //產品編號
    __le16 bcdDevice;   //設備出廠號
    __u8  iManufacturer;    //廠商字符串索引
    __u8  iProduct; //產品字符串索引
    __u8  iSerialNumber;    //產品序列號索引
    __u8  bNumConfigurations;   //最大的配置數量
} __attribute__ ((packed));


   從 usb_device_descrptor結構定義看出,一個設備描述定義了與 USB設備有關的所有信息。



接口

   在 USB體系中,接口是由多個端點組成的。

   一個接口代表一個基本的功能,是 USB設備驅動程序控制的對象。

   一個 USB設備最少有一個接口,功能複雜的 USB設備可以有多個接口。接口描述定義如下:Usb

//源定義在 Usb_ch9.h
/* USB_DT_INTERFACE: Interface descriptor */
struct usb_interface_descriptor {
    __u8  bLength;  //描述符長度
    __u8  bDescriptorType;  //描述符類型
    __u8  bInterfaceNumber; //接口編號
    __u8  bAlternateSetting;    //備用接口編號
    __u8  bNumEndpoints;    //端點數量
    __u8  bInterfaceClass;  //接口類型
    __u8  bInterfaceSubClass;   //接口子類型
    __u8  bInterfaceProtocol;   //接口使用的協議
    __u8  iInterface;   //接口索引字符串數值
} __attribute__ ((packed));



端點

   端點是 USB總線通信的基本形式,每個 USB設備接口可以認爲是端點的集合。

   主機只能通過端點與設備通信。

   USB體系結構規定每個端點都有一個唯一的地址,由設備地址和端點號決定端點地址。

   端點還包括了與主機通信用到的屬性,如傳輸方式、總線訪問頻率、帶寬和端點號等。

   端點的通信是單向的,通過端點傳輸的數據只能是從主機到設備或者從設備到主機。

   端點的定義描述如下:

/* USB_DT_ENDPOINT: Endpoint descriptor */
struct usb_endpoint_descriptor {
    __u8  bLength;  //描述符長度
    __u8  bDescriptorType;  //描述符類型
    __u8  bEndpointAddress; //端點地址
    __u8  bmAttributes; //端點屬性
    __le16 wMaxPacketSize;  //端點接收的最大數據包長度
    __u8  bInterval;
    /* NOTE:  these two are _only_ in audio endpoints. */
    /* use USB_DT_ENDPOINT*_SIZE in bLength, not sizeof. */
    __u8  bRefresh;
    __u8  bSynchAddress;
} __attribute__ ((packed));



配置

   配置是一個接口的集合。

   Linux內核配置的定義如下:

struct usb_config_descriptor {
    __u8  bLength;  //描述符長度
    __u8  bDescriptorType;  //描述符類型
    __le16 wTotalLength;    //配置返回數據長度
    __u8  bNumInterfaces;   //最大接口數
    __u8  bConfigurationValue;  //配置參數值
    __u8  iConfiguration;   //配置描述字符串索引
    __u8  bmAttributes; //供電模式
    __u8  bMaxPower;    //接口的最大電流
} __attribute__ ((packed));



主機驅動結構

   USB主機控制器有三種類型:

    • OHCI,英文全稱是Open Host Controller Interface。OHCI是用於SiS和Ali芯片組的USB控制器。

    • UHCI,英文全稱是Universal Host Controller Interface。UHCI用於Intel和AMD芯片組的USB控制器。UHCI類型的控制器比OHCI控制器硬件結構要簡單,但是需要額外的驅動支持,因此從理論上說速度要慢。

    • EHCI,USB2.0規範提出的一種控制器標準,可以兼容UHCI和OHCI。



USB主機控制器驅動

   Linux內核使用 usb_hcd結構描述 USB主機控制器驅動。

   usb_hcd結構描述了 USB主機控制器的硬件信息、狀態和操作函數。定義如下:

//源定義在Hcd.h
struct usb_hcd {    /* usb_bus.hcpriv points to this */
    /*
     * housekeeping //控制器基本信息
     */
    struct usb_bus      self;       /* hcd is-a bus */
    const char      *product_desc;  /* product/vendor string */ //廠商名稱字符串
    char            irq_descr[24];  /* driver + bus # */    //驅動和總線類型
    struct timer_list   rh_timer;   /* drives root-hub polling */   //根 hub輪詢時間間隔
    struct urb      *status_urb;    /* the current status urb */    //當前 urb狀態
    /*
     * hardware info/state  //硬件信息和狀態
     */
    const struct hc_driver  *driver;    /* hw-specific hooks */ //控制器驅動使用的回調函數
    /* Flags that need to be manipulated atomically */
    unsigned long       flags;
#define HCD_FLAG_HW_ACCESSIBLE  0x00000001
#define HCD_FLAG_SAW_IRQ    0x00000002
    unsigned        rh_registered:1;/* is root hub registered? */   //是否註冊根 hub
    /* The next flag is a stopgap, to be removed when all the HCDs
     * support the new root-hub polling mechanism. */
    unsigned        uses_new_polling:1; //是否允許輪詢根 hub狀態
    unsigned        poll_rh:1;  /* poll for rh status? */
    unsigned        poll_pending:1; /* status has changed? */   //狀態是否改變
    int         irq;        /* irq allocated */ //控制器的中斷請求號
    void __iomem        *regs;      /* device memory/io */  //控制器使用的內存和 I/O
    u64         rsrc_start; /* memory/io resource start */  //控制器使用的內存和 I/O起始地址
    u64         rsrc_len;   /* memory/io resource length */ //控制器使用的內存和 I/O資源長度
    unsigned        power_budget;   /* in mA, 0 = no limit */
#define HCD_BUFFER_POOLS    4
    struct dma_pool     *pool [HCD_BUFFER_POOLS];
    int         state;
#   define  __ACTIVE        0x01
#   define  __SUSPEND       0x04
#   define  __TRANSIENT     0x80
#   define  HC_STATE_HALT       0
#   define  HC_STATE_RUNNING    (__ACTIVE)
#   define  HC_STATE_QUIESCING  (__SUSPEND|__TRANSIENT|__ACTIVE)
#   define  HC_STATE_RESUMING   (__SUSPEND|__TRANSIENT)
#   define  HC_STATE_SUSPENDED  (__SUSPEND)
#define HC_IS_RUNNING(state) ((state) & __ACTIVE)
#define HC_IS_SUSPENDED(state) ((state) & __SUSPEND)
    /* more shared queuing code would be good; it should support
     * smarter scheduling, handle transaction translators, etc;
     * input size of periodic table to an interrupt scheduler.
     * (ohci 32, uhci 1024, ehci 256/512/1024).
     */
    /* The HC driver's private data is stored at the end of
     * this structure.
     */
    unsigned long hcd_priv[0]
            __attribute__ ((aligned (sizeof(unsigned long))));
};



OHCI控制器驅動

   usb_hcd結構可以理解爲一個通用的 USB控制器描述結構,OHCI主機控制器是 usb_hcd結構的具體實現。

   內核使用 ohci_hcd結構描述 OHCI主機控制器,定義如下:

struct ohci_hcd {
    spinlock_t      lock;
    /*
     * I/O memory used to communicate with the HC (dma-consistent)  //用於 HC通信的 I/O內存地址
     */
    struct ohci_regs __iomem *regs;
    /*
     * main memory used to communicate with the HC (dma-consistent)。    //用於 HC 通告的主內存地址
     * hcd adds to schedule for a live hc any time, but removals finish
     * only at the start of the next frame.
     */
    struct ohci_hcca    *hcca;
    dma_addr_t      hcca_dma;
    struct ed       *ed_rm_list;        /* to be removed */ //將被移除列表
    struct ed       *ed_bulktail;       /* last in bulk list */ //列表最後一項
    struct ed       *ed_controltail;    /* last in ctrl list */ //控制列表最後一項
    struct ed       *periodic [NUM_INTS];   /* shadow int_table */
    /*
     * OTG controllers and transceivers need software interaction;
     * other external transceivers should be software-transparent
     */
    struct otg_transceiver  *transceiver;
    /*
     * memory management for queue data structures  //內存管理隊列使用的數據結構
     */
    struct dma_pool     *td_cache;
    struct dma_pool     *ed_cache;
    struct td       *td_hash [TD_HASH_SIZE];
    struct list_head    pending;
    /*
     * driver state
     */
    int         num_ports;
    int         load [NUM_INTS];
    u32             hc_control; /* copy of hc control reg */    // HC控制寄存器複製
    unsigned long       next_statechange;   /* suspend/resume */    //掛起 恢復
    u32         fminterval;     /* saved register */    //保存的寄存器
    struct notifier_block   reboot_notifier;
    unsigned long       flags;      /* for HC bugs */
#define OHCI_QUIRK_AMD756   0x01            /* erratum #4 */
#define OHCI_QUIRK_SUPERIO  0x02            /* natsemi */
#define OHCI_QUIRK_INITRESET    0x04            /* SiS, OPTi, ... */
#define OHCI_BIG_ENDIAN     0x08            /* big endian HC */
#define OHCI_QUIRK_ZFMICRO  0x10            /* Compaq ZFMicro chipset*/
    // there are also chip quirks/bugs in init logic    //芯片的初始化邏輯裏也同樣會有怪異的 Bug
};

   OHCI主機控制器是嵌入式系統最常用的一種 USB主機控制器。



設備驅動結構

   USB協議規定了許多種USB設備類型。Linux內核實現了音頻設備、通信設備、人機接口、存儲設備、電源設備、打印設備等幾種USB設備類。



基本概念

   Linux內核實現的 USB設備驅動都是針對通用的設備類型設計的。

   只要 USB存儲設備是按照標準的 USB存儲設備規範實現的,就可以直接被內核 USB存儲設備驅動。如果一個 USB設備是非標準的,則需要編寫對應設備的驅動程序。



設備驅動結構

   內核使用 usb_driver結構體描述 USB設備驅動,定義如下:

struct usb_driver {
    const char *name;
    int (*probe) (struct usb_interface *intf,
              const struct usb_device_id *id);  //探測函數
    void (*disconnect) (struct usb_interface *intf);    //斷開連接函數
    int (*ioctl) (struct usb_interface *intf, unsigned int code,
            void *buf); // I/O控制函數
    int (*suspend) (struct usb_interface *intf, pm_message_t message);  //掛起函數
    int (*resume) (struct usb_interface *intf); //恢複函數
    void (*pre_reset) (struct usb_interface *intf);
    void (*post_reset) (struct usb_interface *intf);
    const struct usb_device_id *id_table;
    struct usb_dynids dynids;
    struct device_driver driver;
    unsigned int no_dynamic_id:1;
};
    • 實現一個 USB設備的驅動主要是實現 probe()和 disconnect()函數接口。

    • probe()函數在插入 USB設備的時候被調用,disconnect()函數在拔出 USB設備的時候被調用。



USB請求塊

   USB請求塊(USB request block,urb)的功能類似於網絡設備中的 sk_buff,用於描述 USB設備與主機通信的基本數據結構。

   urb結構在內核中定義如下:

//源定義在 Usb.h
struct urb
{
    /* private: usb core and host controller only fields in the urb */
    struct kref kref;       /* reference count of the URB */    // urb引用計數
    spinlock_t lock;        /* lock for the URB */  // urb鎖
    void *hcpriv;           /* private data for host controller */  //主機控制器私有數據
    int bandwidth;          /* bandwidth for INT/ISO request */ //請求帶寬
    atomic_t use_count;     /* concurrent submissions counter */    //併發傳輸計數
    u8 reject;          /* submissions will fail */ //傳輸即將失敗標誌
    /* 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 usb_device *dev;     /* (in) pointer to associated device */ //關聯的 USB設備
    unsigned int pipe;      /* (in) pipe information */ //管道信息
    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 */ //DMA使用的緩衝區
    int transfer_buffer_length; /* (in) data buffer length */   //緩衝區大小
    int 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 */
};

   內核提供了一組函數 urb類型的結構變量。urb的使用流程如下:

    1. 創建 urb。在使用之前,USB設備驅動需要調用 usb_alloc_urb()函數創建一個 urb;內核還提供釋放 urb的函數,在不使用 urb的時候(退出驅動種馬或者掛起驅動),需要使用 usb_free_urb()函數釋放 urb。

    2. 初始化 urb。設置 USB設備的端點。使用內核提供的 usb_init_urb()函數設置 urb初始結構。

    3. 提交 urb到 USB核心。在分配並設置 urb完畢後,使用 urb_submit_urb()函數把新的 urb提交到 USB核心。



USB驅動程序框架

   Linux內核代碼driver/usb/usb-skeleton.c文件是一個標準的USB設備驅動程序。

   編寫一個USB設備的驅動可以參考usb-skeleton.c文件,實際上,可以直接修改該文件驅動新的USB設備。



基本數據結構

   usb-skel設備使用自定義結構 usb_skel記錄設備驅動用到的所有描述符,該結構定義如下:

/* Structure to hold all of our device specific stuff */
struct usb_skel {
    struct usb_device * udev;           /* the usb device for this device */
        // USB設備描述符
    struct usb_interface *  interface;      /* the interface for this device */
        // USB接口描述符
    struct semaphore    limit_sem;      /* limiting the number of writes in progress */
        // 互斥信號量
    unsigned char *     bulk_in_buffer;     /* the buffer to receive data */
        // 數據接收緩衝區
    size_t          bulk_in_size;       /* the size of the receive buffer */
        // 數據接收緩衝區大小
    __u8            bulk_in_endpointAddr;   /* the address of the bulk in endpoint */
        // 入端點地址
    __u8            bulk_out_endpointAddr;  /* the address of the bulk out endpoint */
        // 出端點地址
    struct kref     kref;
};

   usb-skel設備驅動把 usb_skel結構存放在了 urb結構的 context指針裏。通過 urb,設備的所有操作函數都可以訪問到 usb_skel結構。

   其中,limit_sem成員是一個信號量,當多個 usb-skel類型的設備存在於系統中的時候,需要控制設備之間的數據同步。



驅動程序初始化和註銷

   與其他所有的 Linux設備驅動程序一樣,usb-skel驅動使用 module_init()宏設置初始化函數,使用 module_exit()宏設置註銷函數。

   usb-skel驅動的初始化函數是 usb_skel_init()函數,定義如下:

static int __init usb_skel_init(void)
{
    int result;
    /* register this driver with the USB subsystem */
    result = usb_register(&skel_driver);    //註冊 USB設備驅動
    if (result)
        err("usb_register failed. Error number %d", result);
    return result;
}

   usb_skel_init()函數調用內核提供的 usb_register()函數註冊了一個 usb_driver類型的結構變量,該變量定義如下:

static struct usb_driver skel_driver = {
    .name =     "skeleton", // USB設備名稱
    .probe =    skel_probe, // USB設備初始化函數
    .disconnect =   skel_disconnect,    // USB設備註銷函數
    .id_table = skel_table, // USB設備 ID映射表
};

   skel_driver結構變量中,定義了 usb-skel設備的名、設備初始化函數、設備註銷函數和 USB ID映射表。

   其中 usb-skel設備的 USB ID映射表定義如下:

/* table of devices that work with this driver */
static struct usb_device_id skel_table [] = {
    { USB_DEVICE(USB_SKEL_VENDOR_ID, USB_SKEL_PRODUCT_ID) },
    { }                 /* Terminating entry */
};

   skel_table中只有一項,定義了一個默認的 usb-skel設備的 ID,其中,USB_SKEL_VENDOR_ID是 USB設備的廠商 ID,USB_SKEL_PRODUCT_ID是 USB設備 ID。



設備初始化

   從 skel_driver結構可以知道 usb-skel設備的初始化函數是 skel_probe()函數。

設備初始化主要是探測設備類型,分配 USB設備用到的 urb資源,註冊 USB設備操作函數等。

skel_class結構變量記錄了 usb-skel設備信息,定義如下:

/*
 * usb class driver info in order to get a minor number from the usb core,
 * and to have the device registered with the driver core
 */
static struct usb_class_driver skel_class = {
    .name =     "skel%d",   //設備名稱
    .fops =     &skel_fops, //設備操作函數
    .minor_base =   USB_SKEL_MINOR_BASE,
};

   name變量使用 %d通配符表示一個整形變量,當一個 usb-skel類型的設備連接到 USB總線後會按照子設備編號自動設置設備名稱。

   fops是設備操作函數結構變量,定義如下:

static struct file_operations skel_fops = {
    .owner =    THIS_MODULE,
    .read =     skel_read,  //讀操作
    .write =    skel_write, //寫操作
    .open =     skel_open,  //打開操作
    .release =  skel_release,   //關閉操作
};

   skel_ops定義了 usb-skel設備的操作函數。當在 usb-skel設備上發生相關事件時,USB文件系統會調用對應的函數處理。



設備註銷

   skel_disconnect()函數在註銷設備的時候被調用,定義如下:

static void skel_disconnect(struct usb_interface *interface)
{
    struct usb_skel *dev;
    int minor = interface->minor;
    /* prevent skel_open() from racing skel_disconnect() */
    lock_kernel();  //在操作之前加鎖
    dev = usb_get_intfdata(interface);  //獲得 USB設備接口描述
    usb_set_intfdata(interface, NULL);  //設置 USB設備接口描述無效
    /* give back our minor */
    usb_deregister_dev(interface, &skel_class); //註銷 USB設備操作供述
    unlock_kernel();    //操作完畢解鎖
    /* decrement our usage count */
    kref_put(&dev->kref, skel_delete);   //減小引用計數
    info("USB Skeleton #%d now disconnected", minor);
}
static struct usb_driver skel_driver = {
    .name =     "skeleton", // USB設備名稱
    .probe =    skel_probe, // USB設備初始化函數
    .disconnect =   skel_disconnect,    // USB設備註銷函數
    .id_table = skel_table, // USB設備 ID映射表
};

   skel_disconnect()函數釋放 usb-skel設備用到的資源。

   首先獲取 USB設備接口描述,之後設置爲無效;然後調用 usb_deregister_dev()函數註銷 USB設備的操作描述符,註銷操作本身需要加鎖;註銷設備描述符後,更新內核對 usb-skel設備的引用計數。


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章