13.4. 編寫一個 USB 驅動

2018-02-24 15:50 更新

13.4.?編寫一個 USB 驅動

編寫一個 USB 設備驅動的方法類似于一個 pci 驅動: 驅動注冊它的驅動對象到 USB 子系統(tǒng)并且之后使用供應商和設備標識來告知是否它的硬件已經(jīng)安裝.

13.4.1.?驅動支持什么設備

struct usb_device_id 結構提供了這個驅動支持的一個不同類型 USB 設備的列表. 這個列表被USB 核心用來決定給設備哪個驅動, 并且通過熱插拔腳本來決定哪個驅動自動加載, 當特定設備被插入系統(tǒng)時.

struct usb_device_id 結構定義有下面的成員:

__u16 match_flags
決定設備應當匹配結構中下列的哪個成員. 這是一個位成員, 由在 include/linux/mod_devicetable.h 文件中指定的不同的 USB_DEVICE_IDMATCH* 值所定義. 這個成員常常從不直接設置, 但是由 USB_DEVICE 類型宏來初始化.

__u16 idVendor
這個設備的 USB 供應商 ID. 這個數(shù)由 USB 論壇分配給它的成員并且不能由任何別的構成.

__u16 idProduct
這個設備的 USB 產(chǎn)品 ID. 所有的有分配給他們的供應商 ID 的供應商可以隨意管理它們的產(chǎn)品 ID.

__u16 bcdDevice_lo__u16 bcdDevice_hi
定義供應商分配的產(chǎn)品版本號的高低范圍. bcdDevice_hi 值包括其中; 它的值是最高編號的設備號. 這 2 個值以BCD 方式編碼. 這些變量, 連同 idVendor 和 idProduct, 用來定義一個特定的設備版本.

u8 bDeviceClassu8 bDeviceSubClass__u8 bDeviceProtocol
定義類, 子類, 和設備協(xié)議, 分別地. 這些值被 USB 論壇分配并且定義在 USB 規(guī)范中. 這些值指定這個設備的行為, 包括設備上所有的接口.

u8 bInterfaceClassu8 bInterfaceSubClass__u8 bInterfaceProtocol
非常象上面的設備特定值, 這些定義了類, 子類, 和單個接口協(xié)議, 分別地. 這些值由 USB 論壇指定并且定義在 USB 規(guī)范中.

kernel_ulong_t driver_info
這個值不用來匹配, 但是它持有信息, 驅動可用來在 USB 驅動的探測回調(diào)函數(shù)區(qū)分不同的設備.

至于 PCI 設備, 有幾個宏可用來初始化這個結構:

USB_DEVICE(vendor, product)
創(chuàng)建一個 struct usb_device_id, 可用來只匹配特定供應商和產(chǎn)品 ID 值. 這是非常普遍用的, 對于需要特定驅動的 USB 設備.

USB_DEVICE_VER(vendor, product, lo, hi)
創(chuàng)建一個 struct usb_device_id, 用來在一個版本范圍中只匹配特定供應商和產(chǎn)品 ID 值.

USB_DEVICE_INFO(class, subclass, protocol)
創(chuàng)建一個 struct usb_device_id, 可用來只匹配一個特定類的 USB 設備.

USB_INTERFACE_INFO(class, subclass, protocol)
創(chuàng)建一個 struct usb_device_id, 可用來只匹配一個特定類的 USB 接口.

對于一個簡單的 USB 設備驅動, 只控制來自一個供應商的一個單一 USB 設備, struct usb_device_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 */
};
MODULE_DEVICE_TABLE (usb, skel_table);

至于 PCI 驅動, MODULE_DEVICE_TABLE 宏有必要允許用戶空間工具來發(fā)現(xiàn)這個驅動可控制什么設備. 但是對于 USB 驅動, 字符串 usb 必須是在這個宏中的第一個值.

13.4.2.?注冊一個 USB 驅動

所有 USB 驅動必須創(chuàng)建的主要結構是 struct usb_driver. 這個結構必須被 USB 驅動填充并且包含多個函數(shù)回調(diào)和變量, 來向 USB 核心代碼描述 USB 驅動:

struct module *owner
指向這個驅動的模塊擁有者的指針. USB 核心使用它正確地引用計數(shù)這個 USB 驅動, 以便它不被在不合適的時刻卸載. 這個變量應當設置到 THIS_MODULE 宏.

const char *name
指向驅動名子的指針. 它必須在內(nèi)核 USB 驅動中是唯一的并且通常被設置為和驅動的模塊名相同. 它出現(xiàn)在 sysfs 中在 /sys/bus/usb/drivers/ 之下, 當驅動在內(nèi)核中時.

const struct usb_device_id *id_table
指向 struct usb_device_id 表的指針, 包含這個驅動可接受的所有不同類型 USB 設備的列表. 如果這個變量沒被設置, USB 驅動中的探測回調(diào)函數(shù)不會被調(diào)用. 如果你需要你的驅動給系統(tǒng)中每個 USB 設備一直被調(diào)用, 創(chuàng)建一個只設置這個 driver_info 成員的入口項:


static struct usb_device_id usb_ids[] = {
 {.driver_info = 42},
    {} 
};

int (probe) (struct usb_interface intf, const struct usb_device_id *id)
指向 USB 驅動中探測函數(shù)的指針. 這個函數(shù)(在"探測和去連接的細節(jié)"一節(jié)中描述)被 USB 核心調(diào)用當它認為它有一個這個驅動可處理的 struct usb_interface. 一個指向 USB 核心用來做決定的 struct usb_device_id 的指針也被傳遞到這個函數(shù). 如果這個 USB 驅動主張傳遞給它的 struct usb_interface, 它應當正確地初始化設備并且返回 0. 如果驅動不想主張這個設備, 或者發(fā)生一個錯誤, 它應當返回一個負錯誤值.

void (disconnect) (struct usb_interface intf)
指向 USB 驅動的去連接函數(shù)的指針. 這個函數(shù)(在"探測和去連接的細節(jié)"一節(jié)中描述)被 USB 核心調(diào)用, 當 struct usb_interface 已被從系統(tǒng)中清除或者當驅動被從 USB 核心卸載.

為創(chuàng)建一個值 struct usb_driver 結構, 只有 5 個成員需要被初始化:


static struct usb_driver skel_driver = {
 .owner = THIS_MODULE,
 .name = "skeleton",
 .id_table = skel_table,
 .probe = skel_probe,
 .disconnect = skel_disconnect, 
}; 

struct usb_driver 確實包含更多幾個回調(diào), 它們通常不經(jīng)常用到, 并且不被要求使 USB 驅動正確工作:

int (ioctl) (struct usb_interface intf, unsigned int code, void *buf)
指向 USB 驅動的 ioctl 函數(shù)的指針. 如果它出現(xiàn), 在用戶空間程序對一個關聯(lián)到 USB 設備的 usbfs 文件系統(tǒng)設備入口, 做一個 ioctl 調(diào)用時被調(diào)用. 實際上, 只有 USB 集線器驅動使用這個 ioctl, 因為沒有其他的真實需要對于任何其他 USB 驅動要使用.

int (suspend) (struct usb_interface intf, u32 state)
指向 USB 驅動中的懸掛函數(shù)的指針. 當設備要被 USB 核心懸掛時被調(diào)用.

int (resume) (struct usb_interface intf)
指向 USB 驅動中的恢復函數(shù)的指針. 當設備正被 USB 核心恢復時被調(diào)用.

為注冊 struct usb_driver 到 USB 核心, 一個調(diào)用 usb_register_driver 帶一個指向 struct usb_driver 的指針. 傳統(tǒng)上在 USB 驅動的模塊初始化代碼做這個:


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

當 USB 驅動被卸載, struct usb_driver 需要從內(nèi)核注銷. 使用對 usb_deregister_driver 的調(diào)用做這個. 當這個調(diào)用發(fā)生, 任何當前綁定到這個驅動的 USB 接口被去連接, 并且去連接函數(shù)為它們而被調(diào)用.


static void __exit usb_skel_exit(void)
{
        /* deregister this driver with the USB subsystem */
        usb_deregister(&skel_driver);
}

13.4.2.1.?探測和去連接的細節(jié)

在之前章節(jié)描述的 struct usb_driver 結構中, 驅動指定 2 個 USB 核心在合適的時候調(diào)用的函數(shù). 探測函數(shù)被調(diào)用, 當設備被安裝時, USB 核心認為這個驅動應當處理; 探測函數(shù)應當進行檢查傳遞給它的關于設備的信息, 并且決定是否驅動真正合適那個設備. 去連接函數(shù)被調(diào)用當驅動應當不再控制設備, 由于某些理由, 并且可做清理.

探測和去連接函數(shù)回調(diào)都在 USB 集線器內(nèi)核線程上下文中被調(diào)用, 因此它們中睡眠是合法的. 但是, 建議如果有可能大部分工作應當在設備被用戶打開時完成. 為了保持 USB 探測時間為最小. 這是因為 USB 核心處理 USB 設備的添加和去除在一個線程中, 因此任何慢設備驅動可導致 USB 設備探測時間慢下來并且用戶可注意到.

在探測函數(shù)回調(diào)中, USB 驅動應當初始化任何它可能使用來管理 USB 設備的本地結構. 它還應當保存任何它需要的關于設備的信息到本地結構, 因為在此時做這些通常更容易. 作為一個例子, USB 驅動常常想為設備探測端點地址和緩沖大小是什么, 因為和設備通訊需要它們. 這里是一些例子代碼, 它探測 BULK 類型的 IN 和 OUT 端點, 并且保存一些關于它們的信息在一個本地設備結構中:


/* set up the endpoint information */
/* use only the first bulk-in and bulk-out endpoints */
iface_desc = interface->cur_altsetting;
for (i = 0; i < iface_desc->desc.bNumEndpoints; ++i)
{

        endpoint = &iface_desc->endpoint[i].desc;
        if (!dev->bulk_in_endpointAddr &&
                        (endpoint->bEndpointAddress & USB_DIR_IN) &&
                        ((endpoint->bmAttributes & USB_ENDPOINT_XFERTYPE_MASK)

                         == USB_ENDPOINT_XFER_BULK)) { /* we found a bulk in endpoint */ buffer_size = endpoint->wMaxPacketSize;
                dev->bulk_in_size = buffer_size;
                dev->bulk_in_endpointAddr = endpoint->bEndpointAddress;
                dev->bulk_in_buffer = kmalloc(buffer_size, GFP_KERNEL);
                if (!dev->bulk_in_buffer) {
                        err("Could not allocate bulk_in_buffer");
                        goto error;
                }
        }

        if (!dev->bulk_out_endpointAddr &&
                        !(endpoint->bEndpointAddress & USB_DIR_IN) &&
                        ((endpoint->bmAttributes & USB_ENDPOINT_XFERTYPE_MASK)
                         == USB_ENDPOINT_XFER_BULK)) { /* we found a bulk out endpoint */ dev->bulk_out_endpointAddr = endpoint->bEndpointAddress;
        }
}
if (!(dev->bulk_in_endpointAddr && dev->bulk_out_endpointAddr))
{

        err("Could not find both bulk-in and bulk-out endpoints");
        goto error;
}

這塊代碼首先循環(huán)在這個接口中出現(xiàn)的每個端點, 并且分配一個本地指針到端點結構來使它之后容易存取:


for (i = 0; i < iface_desc->desc.bNumEndpoints; ++i) {
 endpoint = &iface_desc->endpoint[i].desc;

那么, 在我們有了一個端點后, 我們還沒有發(fā)現(xiàn)一個塊 IN 類型端點, 我們看是否這個端點的方向是 IN. 那個可被測試通過看是否位掩碼 USB_DIR_IN 被包含在 bEndpointAddress 端點變量中. 如果這是真, 我們決定是否端點類型是塊, 通過使用 USB_ENDPOINT_XFERTYPE_MASK 位掩碼首先掩去 bmAttributes 變量, 并且接著檢查是否它匹配值 USB_ENDPOINT_XFER_BULK:


if (!dev->bulk_in_endpointAddr &&
                (endpoint->bEndpointAddress & USB_DIR_IN) &&
                ((endpoint->bmAttributes & USB_ENDPOINT_XFERTYPE_MASK)

                 == USB_ENDPOINT_XFER_BULK))
{

如果所有的這些檢查都是真, 驅動知道它發(fā)現(xiàn)了正確的端點類型, 并且可保存關于端點的信息到本地結構中, 它后來將需要這些信息和它通訊.


/* we found a bulk in endpoint */
buffer_size = endpoint->wMaxPacketSize;
dev->bulk_in_size = buffer_size;
dev->bulk_in_endpointAddr = endpoint->bEndpointAddress;
dev->bulk_in_buffer = kmalloc(buffer_size, GFP_KERNEL);
if (!dev->bulk_in_buffer)
{
        err("Could not allocate bulk_in_buffer");
        goto error;
}

因為 USB 驅動需要獲取在設備的生命周期后期和這個 struct usb_interface 關聯(lián)的本地數(shù)據(jù)結構, 函數(shù) usb_set_intfdata 可被調(diào)用:


/* save our data pointer in this interface device */
usb_set_intfdata(interface, dev);

這個函數(shù)接受一個指向任何數(shù)據(jù)類型的指針, 并且保存它到 struct usb_interface 結構為后面的存取. 為獲取這個數(shù)據(jù), 函數(shù) usb_get_intfdata 應當被調(diào)用:


struct usb_skel *dev;
struct usb_interface *interface;
int subminor;
int retval = 0;

subminor = iminor(inode);
interface = usb_find_interface(&skel_driver, subminor);
if (!interface)
{
        err ("%s - error, can't find device for minor %d",
             __FUNCTION__, subminor);
        retval = -ENODEV;
        goto exit;
}

dev = usb_get_intfdata(interface);
if (!dev)
{
        retval = -ENODEV;
        goto exit;
}

usb_get_intfdata 常常被調(diào)用, 在 USB 驅動的 open 函數(shù)和在去連接函數(shù). 感謝這 2 個函數(shù), USB 驅動不需要保持一個靜態(tài)指針數(shù)組來保存單個設備結構為系統(tǒng)中所有當前的設備. 對設備信息的非直接引用允許一個無限數(shù)目的設備被任何 USB 驅動支持.

如果 USB 驅動沒有和另一種處理用戶和設備交互的子系統(tǒng)(例如 input, tty, video, 等待)關聯(lián), 驅動可使用 USB 主編號為了使用傳統(tǒng)的和用戶空間之間的字符驅動接口. 為此, USB 驅動必須在探測函數(shù)中調(diào)用 usb_register_dev 函數(shù), 當它想注冊一個設備到 USB 核心. 確認設備和驅動處于正確的狀態(tài), 來處理一個想在調(diào)用這個函數(shù)時盡快存取這個設備的用戶.


/* we can register the device now, as it is ready */
retval = usb_register_dev(interface, &skel_class);
if (retval)
{
        /* something prevented us from registering this driver */
        err("Not able to get a minor for this device.");
        usb_set_intfdata(interface, NULL);
        goto error;
}

usb_register_dev 函數(shù)要求一個指向 struct usb_interface 的指針和指向 struct usb_class_driver 的指針. struct -usb_class_driver 用來定義許多不同的參數(shù), 當注冊一個次編號USB 驅動要 USB 核心知道這些參數(shù). 這個結構包括下列變量:.

char *name
sysfs 用來描述設備的名子. 一個前導路徑名, 如果存在, 只用在 devfs 并且本書不涉及. 如果設備號需要在這個名子中, 字符 %d 應當在名子串中. 例如, 位創(chuàng)建 devfs 名子 usb/foo1 和 sysfs 類名 foo1, 名子串應當設置為 usb/foo%d.

struct file_operations *fops;
指向 struct file_operations 的結構的指針, 這個驅動已定義來注冊為字符設備. 這個結構的更多信息見第 3 章.

mode_t mode;
給這個驅動的要被創(chuàng)建的 devfs 文件的模式; 否則不使用. 這個變量的典型設置是值 S_IRUSR 和 值 S_IWUSR 的結合, 它將只提供這個設備文件的擁有者讀和寫存取.

int minor_base;
這是給這個驅動安排的次編號的開始. 所有和這個驅動相關的設備被創(chuàng)建為從這個值開始的唯一的, 遞增的次編號. 只有 16 個設備被允許在任何時刻和這個驅動關聯(lián), 除非 CONFIG_USB_DYNAMIC_MINORS 配置選項被打開. 如果這樣, 忽略這個變量, 并且這個設備的所有的次編號被以先來先服務的方式分配. 建議打開了這個選項的系統(tǒng)使用一個程序例如 udev 來關聯(lián)系統(tǒng)中的設備節(jié)點, 因為一個靜態(tài)的 /dev 樹不會正確工作.

當 USB 設備斷開, 所有的關聯(lián)到這個設備的資源應當被清除, 如果可能. 在此時, 如果 usb_register_dev 已被在探測函數(shù)中調(diào)用來分配一個 USB 設備的次編號, 函數(shù) usb_deregister_dev 必須被調(diào)用來將次編號給回 USB 核心.

在斷開函數(shù)中, 也重要的是從接口獲取之前調(diào)用 usb_set_intfdata 所設置的數(shù)據(jù). 接著設置數(shù)據(jù)指針在 struct us_interface 結構為 NULL 來阻止在不正確存取數(shù)據(jù)中的任何進一步的錯誤.


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_set_intfdata(interface, NULL);
        /* give back our minor */
        usb_deregister_dev(interface, &skel_class);

        unlock_kernel(); /* decrement our usage count */

        kref_put(&dev->kref, skel_delete);
        info("USB Skeleton #%d now disconnected", minor);
}

注意在之前代碼片段中的調(diào)用 lock_kernel. 它獲取了 bigkernel 鎖, 以至于 disconnect 回調(diào)不會遇到一個競爭情況, 在使用 open 調(diào)用試圖獲取一個指向正確接口數(shù)據(jù)結構的指針. 因為 open 在 bigkernel 鎖獲取情況下被調(diào)用, 如果 disconnect 也獲取同一個鎖, 只有驅動的一部分可存取并且接著設置接口數(shù)據(jù)指針.

就在 disconnect 函數(shù)為一個 USB 設備被調(diào)用, 所有的當前在被傳送的 urb 可被 USB 核心取消, 因此驅動不必明確為這些 urb 調(diào)用 usb_kill_urb. 如果一個驅動試圖提交一個 urb 給 USB 設備, 在調(diào)用 usb_submit_urb 被斷開之后, 這個任務會失敗, 錯誤值為-EPIPE.

13.4.3.?提交和控制一個 urb

當驅動有數(shù)據(jù)發(fā)送到 USB 設備(如同在驅動的 write 函數(shù)中發(fā)生的), 一個 urb 必須被分配來傳送數(shù)據(jù)到設備.


urb = usb_alloc_urb(0, GFP_KERNEL);
if (!urb)
{
        retval = -ENOMEM;
        goto error;
}

在 urb 被成功分配后, 一個 DMA 緩沖也應當被創(chuàng)建來發(fā)送數(shù)據(jù)到設備以最有效的方式, 并且被傳遞到驅動的數(shù)據(jù)應當被拷貝到緩沖:


buf = usb_buffer_alloc(dev->udev, count, GFP_KERNEL, &urb->transfer_dma);
if (!buf)
{
        retval = -ENOMEM;
        goto error;
}
if (copy_from_user(buf, user_buffer, count))
{
        retval = -EFAULT;
        goto error;
}

應當數(shù)據(jù)被正確地從用戶空間拷貝到本地緩沖, urb 在它可被提交給 USB 核心之前必須被正確初始化:


/* initialize the urb properly */
usb_fill_bulk_urb(urb, dev->udev,
                  usb_sndbulkpipe(dev->udev, dev->bulk_out_endpointAddr),
                  buf, count, skel_write_bulk_callback, dev);
urb->transfer_flags |= URB_NO_TRANSFER_DMA_MAP;

現(xiàn)在 urb 被正確分配, 數(shù)據(jù)被正確拷貝, 并且 urb 被正確初始化, 它可被提交給 USB 核心來傳遞給設備.


/* send the data out the bulk port */
retval = usb_submit_urb(urb, GFP_KERNEL);
if (retval)
{
        err("%s - failed submitting write urb, error %d", __FUNCTION__, retval);
        goto error;
}

在urb被成功傳遞到 USB 設備(或者在傳輸中發(fā)生了什么), urb 回調(diào)被 USB 核心調(diào)用. 在我們的例子中, 我們初始化 urb 來指向函數(shù) skel_write_bulk_callback, 并且那就是被調(diào)用的函數(shù):


static void skel_write_bulk_callback(struct urb *urb, struct pt_regs *regs)
{
        /* sync/async unlink faults aren't errors */
        if (urb->status &&

                        !(urb->status == -ENOENT ||
                          urb->status == -ECONNRESET ||
                          urb->status == -ESHUTDOWN)){
                dbg("%s - nonzero write bulk status received: %d",
                    __FUNCTION__, urb->status);
        }

        /* free up our allocated buffer */
        usb_buffer_free(urb->dev, urb->transfer_buffer_length,
                        urb->transfer_buffer, urb->transfer_dma);
}

回調(diào)函數(shù)做的第一件事是檢查 urb 的狀態(tài)來決定是否這個 urb 成功完成或沒有. 錯誤值, -ENOENT, -ECONNRESET, 和 -ESHUTDOWN 不是真正的傳送錯誤, 只是報告伴隨成功傳送的情況. (見 urb 的可能錯誤的列表, 在"結構 struct urb"一節(jié)中詳細列出). 接著這個回調(diào)釋放安排給這個 urb 傳送的已分配的緩沖.

在 urb 的回調(diào)函數(shù)在運行時另一個 urb 被提交給設備是普遍的. 當流數(shù)據(jù)到設備時是有用的. 記住 urb 回調(diào)是在中斷上下文運行, 因此它不應當做任何內(nèi)存分配, 持有任何旗標, 或者任何可導致進程睡眠的事情. 當從回調(diào)中提交 urb, 使用 GFP_ATOMIC 標志來告知 USB 核心不要睡眠, 如果它需要分配新內(nèi)存塊在提交過程中.

以上內(nèi)容是否對您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號
微信公眾號

編程獅公眾號