Linux USB 3.0驅動分析(三)—— 分析USB 儲存驅動程序


前面學習了USB驅動的一些基礎概念與重要的數據結構,那么究竟如何編寫一個USB 驅動程序呢?編寫與一個USB設備驅動程序的方法和其他總線驅動方式類似,驅動程序把驅動程序對象注冊到USB子系統中,稍后再使用制造商和設備標識來判斷是否安裝了硬件。當然,這些制造商和設備標識需要我們編寫進USB 驅動程序中。

  USB 驅動程序依然遵循設備模型 —— 總線、設備、驅動。和I2C 總線設備驅動編寫一樣,所有的USB驅動程序都必須創建的主要結構體是 struct usb_driver,它們向USB 核心代碼描述了USB 驅動程序。

"./drivers/usb/usb-skeleton.c"是內核提供給usb設備驅動開發者的海量存儲usb設備的模板程序, 程序不長, 通用性卻很強,十分經典, 深入理解這個文件可以幫助我們更好的理解usb子系統以及usb設備驅動框架, 寫出更好的usb海量存儲設備驅動。


一、注冊USB驅動程序

  Linux的設備驅動,特別是這種hotplug的USB設備驅動,會被編譯成模塊,然后在需要時掛在到內核。所以USB驅動和注冊與正常的模塊注冊、卸載是一樣的,下面是USB驅動的注冊與卸載:
static struct usb_driver skel_driver = {
    .name =        "skeleton",
    .probe =    skel_probe,
    .disconnect =    skel_disconnect,
    .suspend =    skel_suspend,
    .resume =    skel_resume,
    .pre_reset =    skel_pre_reset,
    .post_reset =    skel_post_reset,
    .id_table =    skel_table,
    .supports_autosuspend = 1,
};

module_usb_driver(skel_driver);

MODULE_LICENSE("GPL v2");

1.module_usb_driver
其中module_usb_driver是對usb_register和usb_deregister的封裝的宏,用於注冊和卸載usb驅動。
/**
 * module_usb_driver() - Helper macro for registering a USB driver
 * @__usb_driver: usb_driver struct
 *
 * Helper macro for USB drivers which do not do anything special in module
 * init/exit. This eliminates a lot of boilerplate. Each module may only
 * use this macro once, and calling it replaces module_init() and module_exit()
 */
#define module_usb_driver(__usb_driver) \
    module_driver(__usb_driver, usb_register, \
               usb_deregister)

      USB設備驅動的模塊加載函數通用的方法是在I2C設備驅動的模塊加載函數中使用usb_register(struct *usb_driver)函數添加usb_driver的工作,而在模塊卸載函數中利用usb_deregister(struct *usb_driver)做相反的工作。 對比I2C設備驅動中的 i2c_add_driver(&i2c_driver)i2c_del_driver(&i2c_driver)

    

2.struct usb_driver

從代碼看來,usb_driver需要初始化幾個字段:

模塊的名字  skeleton
probe函數   skel_probe
disconnect函數skel_disconnect



3.id_table

    最重要的當然是probe函數與disconnect函數,這個在后面詳細介紹,先談一下id_table:

    id_table 是struct usb_device_id 類型,包含了一列該驅動程序可以支持的所有不同類型的USB設備。如果沒有設置該變量,USB驅動程序中的探測回調該函數將不會被調用。對比I2C中struct i2c_device_id *id_table,一個驅動程序可以對應多個設備,i2c 示例:

static const struct i2c_device_id mpu6050_id[] = {
     { "mpu6050", 0},  
   	 {}    
};  

    usb子系統通過設備的production ID和vendor ID的組合或者設備的class、subclass跟protocol的組合來識別設備,並調用相關的驅動程序作處理。我們可以看看這個id_table到底是什么東西:

/* Define these values to match your devices */
#define USB_SKEL_VENDOR_ID    0xfff0
#define USB_SKEL_PRODUCT_ID    0xfff0

/* table of devices that work with this driver */
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);

   MODULE_DEVICE_TABLE的第一個參數是  設備的類型 ,如果是USB設備,那自然是usb。后面一個參數是  設備表   這個設備表的最后一個元素是空的,用於標識結束 。代碼定義了USB_SKEL_VENDOR_ID是0xfff0,USB_SKEL_PRODUCT_ID是0xfff0,也就是說,當有一個設備接到集線器時,usb子系統就會檢查這個設備的vendor ID和product ID,如果它們的值是0xfff0時,那么子系統就會調用這個skeleton模塊作為設備的驅動。

   當USB設備接到USB控制器接口時,usb_core就檢測該設備的一些信息,例如生產廠商ID和產品的ID,或者是設備所屬的class、subclass跟protocol,以便確定應該調用哪一個驅動處理該設備
  

     

二、USB驅動程序中重要數據結構

我們下面所要做的就是對probe函數與disconnect函數的填充了,但是在對probe函數與disconnect函數填充之前,有必要先學習三個重要的數據結構,這在我們后面probe函數與disconnect函數中有很大的作用:


1、usb-skeleton

       usb-skeleton 是一個局部結構體,用於與端點進行通信。下面先看一下Linux內核源碼中的一個usb-skeleton(就是usb驅動的骨架咯),其定義的設備結構體就叫做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_device對象
	struct usb_interface	*interface;		/* the interface for this device */ //驅動操作的usb_interface對象
	struct semaphore	limit_sem;		/* limiting the number of writes in progress */ 
	struct usb_anchor	submitted;		/* in case we need to retract our submissions */
	struct urb		*bulk_in_urb;		/* the urb to read data with */ //使用的urb對象
	unsigned char           *bulk_in_buffer;	/* the buffer to receive data */ //用於接收數據的buf指針
	size_t			bulk_in_size;		/* the size of the receive buffer */  //用於接收數據的buf指針
	size_t			bulk_in_filled;		/* number of bytes in the buffer */   //標識當前緩沖區有多少有效數據的域
	size_t			bulk_in_copied;		/* already copied to user space */ //標識當前緩沖區已經被拷貝走多少數據的域
	__u8			bulk_in_endpointAddr;	/* the address of the bulk in endpoint */ //bulk設備的輸入端點
	__u8			bulk_out_endpointAddr;	/* the address of the bulk out endpoint */ //bulk設備的輸出端點
	int			errors;			/* the last request tanked */
	bool			ongoing_read;		/* a read is going on */ //設備可讀標志位,0表示可讀,1表示不可讀
	spinlock_t		err_lock;		/* lock for errors */
	struct kref		kref; //kref供內核引用計數用
	struct mutex		io_mutex;		/* synchronize I/O with disconnect */
	unsigned long		disconnected:1;
	wait_queue_head_t	bulk_in_wait;		/* to wait for an ongoing read */
};



三.probe函數分析

當platform driver與device platform匹配成功的時候,就會調用probe。這里調用skel_probe,下面流程簡化了

static int skel_probe(struct usb_interface *interface,const struct usb_device_id *id)
{
    /* allocate memory for our device state and initialize it */
    dev = kzalloc(sizeof(*dev), GFP_KERNEL);

    /* set up the endpoint information */
    /* use only the first bulk-in and bulk-out endpoints */
    retval = usb_find_common_endpoints(interface->cur_altsetting,
            &bulk_in, &bulk_out, NULL, NULL); //現在使用的接口描述符中的端點探測函數,只使用第一個批量端點,后面詳細分析a。
    /*把相關信息保存到一個局部設備結構體中*/
    dev->bulk_in_size = usb_endpoint_maxp(bulk_in); //獲取端點的最大數據包大小
    dev->bulk_in_endpointAddr = bulk_in->bEndpointAddress; //bit0~3表示端點地址,bit8 表示方向,輸入還是輸出
    dev->bulk_in_buffer = kmalloc(dev->bulk_in_size, GFP_KERNEL); //分配用於接收數據的緩沖尺寸bulk_in_size

    dev->bulk_in_urb = usb_alloc_urb(0, GFP_KERNEL); //創建一個新的urb供驅動程序使用
    dev->bulk_out_endpointAddr = bulk_out->bEndpointAddress;
    /* save our data pointer in this interface device */
    usb_set_intfdata(interface, dev); //后面詳細分析b

    /* we can register the device now, as it is ready */
    retval = usb_register_dev(interface, &skel_class); //注冊usb設備, 后面詳細分析c
}


三、探測和斷開函數分析

        USB驅動程序指定了兩個USB核心在適當時間調用的函數。


1、探測函數usb_find_common_endpoints

        當一個設備被安裝而USB核心認為該驅動程序應該處理時,探測函數被調用;

        探測函數應該檢查傳遞給他的設備信息確定驅動程序是否真的適合該設備。當驅動程序因為某種原因不應控制設備時,斷開函數被調用,它可以做一些清潔的工作。

      系統會傳遞給探測函數的信息是什么呢?一個usb_interface * 跟一個struct usb_device_id *作為參數。他們分別是該USB設備的接口描述(一般會是該設備的第0號接口,該接口的默認設置也是第0號設置)跟它的設備ID描述(包括Vendor ID、Production ID等)。

       USB驅動程序應該初始化任何可能用於控制USB設備的局部結構體,它還應該把所需的任何設備相關信息保存到局部結構體中。例如,USB驅動程序通常需要探測設備對的端點地址和緩沖區大小,因為需要他們才能和端點通信

       下面具體分析探測函數做了哪些事情:

a -- 探測設備的端點地址、緩沖區大小,初始化任何可能用於控制USB設備的數據結構

       下面是一個實例代碼,他們探測批量類型的IN和OUT端點

具體流程如下:   

     該代碼塊首先循環訪問該接口中存在的每一個端點,賦予該端點結構體的局部指針以使稍后的訪問更加容易

int usb_find_common_endpoints(struct usb_host_interface *alt,
        struct usb_endpoint_descriptor **bulk_in,
        struct usb_endpoint_descriptor **bulk_out,
        struct usb_endpoint_descriptor **int_in,
        struct usb_endpoint_descriptor **int_out)
{
    for (i = 0; i < alt->desc.bNumEndpoints; ++i) {
        epd = &alt->endpoint[i].desc;
        if (match_endpoint(epd, bulk_in, bulk_out, int_in, int_out))
            return 0;
    }

       我們有了一個端點,我們測定該端點類型是否批量,這首先通過USB_ENDPOINT_XFERTYPE_MASK 位掩碼來取bmAttributes變量的值,然后檢查它是否和USB_ENDPOINT_XFER_BULK 的值匹配來完成。  然后查看該端點的方向是否為IN。這可以通過檢查位掩碼 USB_DIR_IN 是否包含在bEndpointAddress 端點變量中來確定。

static bool match_endpoint(struct usb_endpoint_descriptor *epd,
        struct usb_endpoint_descriptor **bulk_in,
        struct usb_endpoint_descriptor **bulk_out,
        struct usb_endpoint_descriptor **int_in,
        struct usb_endpoint_descriptor **int_out)
{
    switch (usb_endpoint_type(epd)) { //獲取端點的傳輸類型
    case USB_ENDPOINT_XFER_BULK:  //如果是批量傳輸類型端點
        if (usb_endpoint_dir_in(epd)) { //查看該端點的方向是否為IN
            if (bulk_in && !*bulk_in) { //這里是bulk_in是不能是空,不然就是非法指針了。*bulk_in需要空,我們這里只是用第一個ep的信息,后面的epx都不需要了
                *bulk_in = epd;
                break;
            }
        } else {
            if (bulk_out && !*bulk_out) {
                *bulk_out = epd;
                break;
            }
        }
		return false;

    case USB_ENDPOINT_XFER_INT: //如果是終端傳輸類型端點
        if (usb_endpoint_dir_in(epd)) { //查看該端點的方向是否為IN
            if (int_in && !*int_in) { //因為int_in是NULL,所以我們不使用中斷ep
                *int_in = epd;
                break;
            }
        } else {
            if (int_out && !*int_out) {
                *int_out = epd;
                break;
            }
        }
        return false;
    default:
        return false;
    }
	return (!bulk_in || *bulk_in) && (!bulk_out || *bulk_out) &&
            (!int_in || *int_in) && (!int_out || *int_out);
}

        如果這些都通過了,驅動程序就知道它已經發現了正確的端點類型,可以把該端點相關的信息保存到一個局部結構體中,就是我們前面的usb_skel ,以便稍后使用它和端點進行通信.


b -- 把已經初始化數據結構的指針保存到接口設備中

      接下來的工作是向系統注冊一些以后會用的的信息。首先我們來說明一下usb_set_intfdata()他向內核注冊一個data,這個data的結構可以是任意的,這段程序向內核注冊了一個usb_skel結構,就是我們剛剛看到的被初始化的那個,這個data可以在以后用usb_get_intfdata來得到

usb_set_intfdata(interface, dev);


c -- 注冊USB設備

       如果USB驅動程序沒有和處理設備與用戶交互(例如輸入、tty、視頻等)的另一種類型的子系統相關聯,驅動程序可以使用USB主設備號,以便在用戶空間使用傳統的字符驅動程序接口。如果要這樣做,USB驅動程序必須在探測函數中調用 usb_resgister_dev 函數來把設備注冊到USB核心。只要該函數被調用,就要確保設備和驅動程序都處於可以處理用戶訪問設備的要求的恰當狀態

retval = usb_register_dev(interface, &skel_class);

skel_class結構。這個結構又是什么?我們就來看看這到底是個什么東西:

/*
 * 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,
};

        它其實是一個系統定義的結構,里面包含了一名字、一個文件操作結構體還有一個次設備號的基准值。事實上它才是定義真正完成對設備IO操作的函數。所以他的核心內容應該是skel_fops。

   因為usb設備可以有多個interface,每個interface所定義的IO操作可能不一樣,所以向系統注冊的usb_class_driver要求注冊到某一個interface,而不是device,因此,usb_register_dev的第一個參數才是interface,而第二個參數就是某一個usb_class_driver。

   通常情況下,linux系統用主設備號來識別某類設備的驅動程序,用次設備號管理識別具體的設備,驅動程序可以依照次設備號來區分不同的設備,所以,這里的次設備號其實是用來管理不同的interface的,但由於這個范例只有一個interface,在代碼上無法求證這個猜想。

static const struct file_operations skel_fops = {
    .owner =    THIS_MODULE,
    .read =        skel_read,
    .write =    skel_write,
    .open =        skel_open,
    .release =    skel_release,
    .flush =    skel_flush,
    .llseek =    noop_llseek,
};


2、斷開函數

      當設備被拔出集線器時,usb子系統會自動地調用disconnect,他做的事情不多,最重要的是注銷class_driver(交還次設備號)和interface的data,取消:

	/* give back our minor */
    usb_deregister_dev(interface, &skel_class);

    /* prevent more I/O from starting */
    mutex_lock(&dev->io_mutex);
    dev->disconnected = 1;
    mutex_unlock(&dev->io_mutex);

    usb_kill_urb(dev->bulk_in_urb); //取消傳輸請求並等待它完成
    usb_kill_anchored_urbs(&dev->submitted) //取消全部傳送請求,
        
    /* decrement our usage count */
    kref_put(&dev->kref, skel_delete);


四、USB請求塊

       USB 設備驅動代碼通過urb和所有的 USB 設備通訊。urb用 struct urb 結構描述(include/linux/usb.h )。

       urb 以一種異步的方式同一個特定USB設備的特定端點發送或接受數據。一個 USB 設備驅動可根據驅動的需要,分配多個 urb 給一個端點或重用單個 urb 給多個不同的端點。設備中的每個端點都處理一個 urb 隊列, 所以多個 urb 可在隊列清空之前被發送到相同的端點。

 一個 urb 的典型生命循環如下:

 (1)被創建;
 (2)被分配給一個特定 USB 設備的特定端點;
 (3)被提交給 USB 核心;
 (4)被 USB 核心提交給特定設備的特定 USB 主機控制器驅動;
 (5)被 USB 主機控制器驅動處理, 並傳送到設備;
 (6)以上操作完成后,USB主機控制器驅動通知 USB 設備驅動。

 
   urb 也可被提交它的驅動在任何時間取消;如果設備被移除,urb 可以被USB核心取消。urb 被動態創建並包含一個內部引用計數,使它們可以在最后一個用戶釋放它們時被自動釋放。

struct urb
{
    /* 私有的:只能由usb核心和主機控制器訪問的字段 */
    struct kref kref; /*urb引用計數 */
    spinlock_t lock; /* urb鎖 */
    void *hcpriv; /* 主機控制器私有數據 */
    int bandwidth; /* int/iso請求的帶寬 */
    atomic_t use_count; /* 並發傳輸計數 */
    u8 reject; /* 傳輸將失敗*/

    /* 公共的: 可以被驅動使用的字段 */
    struct list_head urb_list; /* 鏈表頭*/
    struct usb_device *dev; /* 關聯的usb設備 */
    unsigned int pipe; /* 管道信息 */
    int status; /* urb的當前狀態 */
    unsigned int transfer_flags; /* urb_short_not_ok | ...*/
    void *transfer_buffer; /* 發送數據到設備或從設備接收數據的緩沖區 */
    dma_addr_t transfer_dma; /*用來以dma方式向設備傳輸數據的緩沖區 */
    int transfer_buffer_length;/*transfer_buffer或transfer_dma 指向緩沖區的大小 */
    int actual_length; /* urb結束后,發送或接收數據的實際長度 */
    unsigned char *setup_packet; /* 指向控制urb的設置數據包的指針*/
    dma_addr_t setup_dma; /*控制urb的設置數據包的dma緩沖區*/
    int start_frame; /*等時傳輸中用於設置或返回初始幀*/
    int number_of_packets; /*等時傳輸中等時緩沖區數據 */
    int interval; /* urb被輪詢到的時間間隔(對中斷和等時urb有效) */
    int error_count;  /* 等時傳輸錯誤數量 */
    void *context; /* completion函數上下文 */
    usb_complete_t complete; /* 當urb被完全傳輸或發生錯誤時,被調用 */
    struct usb_iso_packet_descriptor iso_frame_desc[0];
    /*單個urb一次可定義多個等時傳輸時,描述各個等時傳輸 */
};


1、創建和注銷 urb

   struct urb 結構不能靜態創建,必須使用 usb_alloc_urb 函數創建. 函數原型:

struct urb *usb_alloc_urb(int iso_packets, gfp_t mem_flags);
//int iso_packets : urb 包含等時數據包的數目。如果不使用等時urb,則為0
//gfp_t mem_flags : 與傳遞給 kmalloc 函數調用來從內核分配內存的標志類型相同
//返回值          : 如果成功分配足夠內存給 urb , 返回值為指向 urb 的指針. 如果返回值是 NULL, 則在 USB 核心中發生了錯誤, 且驅動需要進行適當清理

如果驅動已經對 urb 使用完畢, 必須調用 usb_free_urb 函數,釋放urb。函數原型:

void usb_free_urb(struct urb *urb);
//struct urb *urb : 要釋放的 struct urb 指針


2、初始化 urb

/*中斷 urb 初始化函數*/
static inline void usb_fill_int_urb(struct urb *urb,                                                                                                       
                 struct usb_device *dev,
                 unsigned int pipe,
                 void *transfer_buffer,
                 int buffer_length,
                 usb_complete_t complete_fn,
                 void *context,
                 int interval);

/*批量 urb 初始化函數*/
static inline void usb_fill_bulk_urb(struct urb *urb,
                 struct usb_device *dev,
                 unsigned int pipe,
                 void *transfer_buffer,
                 int buffer_length,
                 usb_complete_t complete_fn,
                 void *context);

/*控制 urb 初始化函數*/
static inline void usb_fill_control_urb(struct urb *urb,
                    struct usb_device *dev,
                    unsigned int pipe,
                    unsigned char *setup_packet,
                    void *transfer_buffer,
                    int buffer_length,
                    usb_complete_t complete_fn,
                    void *context);

//struct urb *urb :指向要被初始化的 urb 的指針
//struct usb_device *dev :指向 urb 要發送到的 USB 設備.
//unsigned int pipe : urb 要被發送到的 USB 設備的特定端點. 必須使用前面提過的 usb_******pipe 函數創建
//void *transfer_buffer :指向外發數據或接收數據的緩沖區的指針.注意:不能是靜態緩沖,必須使用 kmalloc 來創建.
//int buffer_length :transfer_buffer 指針指向的緩沖區的大小
//usb_complete_t complete :指向 urb 結束處理例程函數指針
//void *context :指向一個小數據塊的指針, 被添加到 urb 結構中,以便被結束處理例程函數獲取使用.
//int interval :中斷 urb 被調度的間隔.
//函數不設置 urb 中的 transfer_flags 變量, 因此對這個成員的修改必須由驅動手動完成

/*等時 urb 沒有初始化函數,必須手動初始化,以下為一個例子*/
urb->dev = dev;
urb->context = uvd;
urb->pipe = usb_rcvisocpipe(dev, uvd->video_endp-1);
urb->interval = 1;
urb->transfer_flags = URB_ISO_ASAP;
urb->transfer_buffer = cam->sts_buf[i];
urb->complete = konicawc_isoc_irq;
urb->number_of_packets = FRAMES_PER_DESC;
urb->transfer_buffer_length = FRAMES_PER_DESC;
for (j=0; j < FRAMES_PER_DESC; j++) {
        urb->iso_frame_desc[j].offset = j;
        urb->iso_frame_desc[j].length = 1;
}


3、提交 urb

    一旦 urb 被正確地創建並初始化, 它就可以提交給 USB 核心以發送出到 USB 設備. 這通過調用函數 usb_submit_urb 實現:

int usb_submit_urb(struct urb *urb, gfp_t mem_flags);
//struct urb *urb :指向被提交的 urb 的指針 
//gfp_t mem_flags :使用傳遞給 kmalloc 調用同樣的參數, 用來告訴 USB 核心如何及時分配內存緩沖

/*因為函數 usb_submit_urb 可被在任何時候被調用(包括從一個中斷上下文), mem_flags 變量必須正確設置. 根據 usb_submit_urb 被調用的時間,只有 3 個有效值可用:
GFP_ATOMIC 
只要滿足以下條件,就應當使用此值:
1.調用者處於一個 urb 結束處理例程,中斷處理例程,底半部,tasklet或者一個定時器回調函數.
2.調用者持有自旋鎖或者讀寫鎖. 注意如果正持有一個信號量, 這個值不必要.
3.current->state 不是 TASK_RUNNING. 除非驅動已自己改變 current 狀態,否則狀態應該一直是 TASK_RUNNING .

GFP_NOIO 
驅動處於塊 I/O 處理過程中. 它還應當用在所有的存儲類型的錯誤處理過程中.

GFP_KERNEL 
所有不屬於之前提到的其他情況
*/

在 urb 被成功提交給 USB 核心之后, 直到結束處理例程函數被調用前,都不能訪問 urb 結構的任何成員.


4、urb結束處理例程

      如果 usb_submit_urb 被成功調用, 並把對 urb 的控制權傳遞給 USB 核心, 函數返回 0; 否則返回一個負的錯誤代碼. 如果函數調用成功, 當 urb 被結束的時候結束處理例程會被調用一次.當這個函數被調用時, USB 核心就完成了這個urb, 並將它的控制權返回給設備驅動.

只有 3 種結束urb並調用結束處理例程的情況:

(1)urb 被成功發送給設備, 且設備返回正確的確認.如果這樣, urb 中的status變量被設置為 0.
(2)發生錯誤, 錯誤值記錄在 urb 結構中的 status 變量.
(3)urb 從 USB 核心unlink. 這發生在要么當驅動通過調用 usb_unlink_urb 或者 usb_kill_urb告知 USB 核心取消一個已提交的 urb,或者在一個 urb 已經被提交給它時設備從系統中去除.

5、取消 urb

使用以下函數停止一個已經提交給 USB 核心的 urb:

void usb_kill_urb(struct urb *urb)
int usb_unlink_urb(struct urb *urb);

如果調用usb_kill_urb函數,則 urb 的生命周期將被終止. 這通常在設備從系統移除時,在斷開回調函數(disconnect callback)中調用.

對一些驅動, 應當調用 usb_unlink_urb 函數來使 USB 核心停止 urb. 這個函數不會等待 urb 完全停止才返回. 這對於在中斷處理例程中或者持有一個自旋鎖時去停止 urb 是很有用的, 因為等待一個 urb 完全停止需要 USB 核心有使調用進程休眠的能力(wait_event()函數).



五.usb讀取

在probe函數中注冊了file_operations結構體skel_fops用於應用訪問驅動。應用open之后就可以調用read和write對設備進行讀寫了。


1.skel_read

首先是skel_read(),這個函數是應用層讀設備時回調的函數,它試圖實現這樣一個功能: 如果內核緩沖區有數據就將適當的數據拷貝給應用層, 如果沒有就調用skel_do_read_io來向設備請求數據。

static ssize_t skel_read(struct file *file, char *buffer, size_t count,
             loff_t *ppos)
{
    dev = file->private_data; //先將藏在file_private_data中的資源對象拿出來

    /* no concurrent readers */
    rv = mutex_lock_interruptible(&dev->io_mutex); //不能並行去讀

    /* if IO is under way, we must not touch things */ //如果IO在進行中,我們就不能讀取數據
retry:
    /*資源對象中的可讀標志位,不可讀的時候,判斷IO是否允許阻塞,如果不允許就直接返回,允許阻塞就使用資源對象中的等待隊列頭,
     * 將進程加入等待隊列,使用的是interruptible版本的wait,如果睡眠中的進程是被中斷喚醒的,那么rv==-1,函數直接返回。*/
    if (ongoing_io) {
        /* nonblocking IO shall not wait */
        if (file->f_flags & O_NONBLOCK) { //如果IO在進行中,不等待直接返回
            rv = -EAGAIN;
            goto exit;
        }
        /*
         * IO may take forever
         * hence wait in an interruptible state
         */
        rv = wait_event_interruptible(dev->bulk_in_wait, (!dev->ongoing_read)); //IO可能需要很長時間,因此在可中斷狀態中等待
        if (rv < 0)
            goto exit;
    }

    /*
     * if the buffer is filled we may satisfy the read
     * else we need to start IO
     */
    if (dev->bulk_in_filled) { //執行到這一行只有一個情況:設備可讀了!如果緩沖區滿執行第一個語句塊,否則執行下面的語句塊
        /* we had read data */
        size_t available = dev->bulk_in_filled - dev->bulk_in_copied; //緩沖區滿時, 獲取可拷貝的數據的大小.
        size_t chunk = min(available, count); //在可拷貝的大小和期望拷貝的大小中取小者給chunk

        if (!available) { //可拷貝的數據為0, 而usb_skel->bulk_in_filled被置位才能進入這里, 所以只有一種情況: 緩沖區的數據已經拷貝完了
            /*
             * all data has been used
             * actual IO needs to be done
             */
            rv = skel_do_read_io(dev, count);
            if (rv < 0)
                goto exit;
            else
                goto retry; //既然數據已經拷貝完畢, 調用skel_do_read_io發起請求
        }
        /*
         * data is available
         * chunk tells us how much shall be copied
         */
        if (copy_to_user(buffer,
                 dev->bulk_in_buffer + dev->bulk_in_copied,
                 chunk)) //從內核緩沖區usb_skel->bulk_in_buffer + usb_skel->bulk_in_copied開始(就是剩余未拷貝數據的首地址)拷貝chunk byte的數據到應用層
            rv = -EFAULT;
        else
            rv = chunk;

        dev->bulk_in_copied += chunk; //更新usb_skel->bulk_in_copied的值

        /*
         * if we are asked for more than we have,
         * we start IO but don't wait
         */
        if (available < count) //如果可拷貝數據的大小<期望拷貝的大小, 那么顯然剛才chunk=availible, 已經將所有的數據拷貝到應用層, 但是還不能滿足應用層的需求, 
            //調用skel_do_read_io來繼續向設備索取數據, 當然, 索取的大小是沒滿足的部分, 即count-chunk
            skel_do_read_io(dev, count - chunk);

    } else { //usb_skel->bulk_in_filled沒有被置位, 表示內核緩沖區沒有數據, 調用skel_do_read_io索取數據, 當然, 索取的大小是全部數據, 即count
        /* no data in the buffer */
        rv = skel_do_read_io(dev, count); 
        if (rv < 0)
            goto exit;
        else
            goto retry;
    }

exit:
    mutex_unlock(&dev->io_mutex);
    return rv;
}

2. skel_do_read_io
剛才也說了, 如果緩沖區不能滿足應用層需求的時候, 就會調用下面這個函數向bulk usb設備請求數據, 得到數據后將數據放到緩沖區並將相應的標志位置1/置0
static int skel_do_read_io(struct usb_skel *dev, size_t count)
{
    /* prepare a read */
    /*向usb核心提交一個urb, 將資源對象dev藏在urb->context中隨着urb實參傳入回調函數, 和usb_fill_int_urb不同, usb_fill_bulk_urb注冊的時候
    需要將緩沖區首地址和請求數據的大小和urb綁定到一起一同提交, 這樣才知道向bulk設備請求的數據的大小, bulk設備有數據返回的時候才知道放哪.*/
    usb_fill_bulk_urb(dev->bulk_in_urb, dev->udev, usb_rcvbulkpipe(dev->udev, dev->bulk_in_endpointAddr), dev->bulk_in_buffer,
            min(dev->bulk_in_size, count), skel_read_bulk_callback, dev);
    /* tell everybody to leave the URB alone */
    spin_lock_irq(&dev->err_lock);
    dev->ongoing_read = 1; //將usb_skel->ongoing_read置1, 表示沒有數據可讀
    spin_unlock_irq(&dev->err_lock);

    /* submit bulk in urb, which means no data to deliver */
    dev->bulk_in_filled = 0; //將usb_skel->bulk_in_filled置0, 表示內核緩沖區沒有數據可讀
    dev->bulk_in_copied = 0; //將usb_skel->bulk_in_copied置0, 表示沒有任何數據已被拷貝

    /* do it */
    rv = usb_submit_urb(dev->bulk_in_urb, GFP_KERNEL); //做好准備工作之后, 命令usb核心發送urb
}

3.skel_read_bulk_callback
請求被發出后, usb總線就會靜待設備的反饋, 設備有反饋后就會回調urb的注冊函數, 我們看看這個回調函數都做了什么
static void skel_read_bulk_callback(struct urb *urb)
{
    dev->bulk_in_filled = urb->actual_length; //將表示設備反饋的數據長度urb->actual_length賦值給usb_skel->bulk_in_filled, 表示緩沖區有數據了
    dev->ongoing_read = 0; //將usb_skel->ongoing_read置0, 表示可讀了!
 
    wake_up_interruptible(&dev->bulk_in_wait); //喚醒因為沒有數據可讀而陷入睡眠的進程
}


六.USB寫入
寫入數據的思路是一樣的, 我這里就不羅嗦了.

參考:


免責聲明!

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



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