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写入
写入数据的思路是一样的, 我这里就不罗嗦了.

参考:

posted @ 2020-12-30 17:42  luoyuna  阅读(4077)  评论(0编辑  收藏  举报