Linux那些事儿之我是U盘(39)彼岸花的传说(七)

来源:互联网 发布:电网关系知乎 编辑:程序博客网 时间:2024/06/11 06:51

很显然,我们是把为INQUIRY命令准备的数据保存到了我们自己定义的一个结构体中,struct data_ptr[36],但是我们是为了回应一个SCSI命令,最终需要知道答案的是scsi核心层.正是它们传递了一个scsi_cmnd结构体下来,srb.struct scsi_cmnd中有两个成员, unsigned request_bufflenvoid *request_buffer,小宇宙告诉我们,应该把data数组中的数据传送到request_buffer中去,这样,scsi核心层就知道去哪里获取结果.没错,当时就是这样!

usb_stor_set_xfer_buf()这个函数来自,drivers/usb/storage/protocol.c.

281 /* Store the contents of buffer into srb's transfer buffer and set the
    282  * SCSI residue. */
    283 void usb_stor_set_xfer_buf(unsigned char *buffer,
    284         unsigned int buflen, struct scsi_cmnd *srb)
    285 {
    286         unsigned int index = 0, offset = 0;
    287
    288         usb_stor_access_xfer_buf(buffer, buflen, srb, &index, &offset,
    289                         TO_XFER_BUF);
    290         if (buflen < srb->request_bufflen)
    291                 srb->resid = srb->request_bufflen - buflen;
    292 }

主要调用的又是usb_stor_access_xfer_buf()函数,这个函数也来自同一个文件,drivers/usb/storage/protocol.c,

   185 /***********************************************************************
   186  * Scatter-gather transfer buffer access routines
   187  ***********************************************************************/
    188
    189 /* Copy a buffer of length buflen to/from the srb's transfer buffer.
    190  * (Note: for scatter-gather transfers (srb->use_sg > 0), srb->request_buffer
    191  * points to a list of s-g entries and we ignore srb->request_bufflen.
    192  * For non-scatter-gather transfers, srb->request_buffer points to the
    193  * transfer buffer itself and srb->request_bufflen is the buffer's length.)
    194  * Update the *index and *offset variables so that the next copy will
    195  * pick up from where this one left off. */
    196
    197 unsigned int usb_stor_access_xfer_buf(unsigned char *buffer,
    198         unsigned int buflen, struct scsi_cmnd *srb, unsigned int *index,
    199         unsigned int *offset, enum xfer_buf_dir dir)
    200 {
    201         unsigned int cnt;
    202
    203         /* If not using scatter-gather, just transfer the data directly.
    204          * Make certain it will fit in the available buffer space. */
    205         if (srb->use_sg == 0) {
    206                 if (*offset >= srb->request_bufflen)
    207                         return 0;
    208                 cnt = min(buflen, srb->request_bufflen - *offset);
    209                 if (dir == TO_XFER_BUF)
    210                         memcpy((unsigned char *) srb->request_buffer + *offset,
    211                                         buffer, cnt);
    212                 else
    213                         memcpy(buffer, (unsigned char *) srb->request_buffer +
    214                                         *offset, cnt);
    215                 *offset += cnt;
    216
    217         /* Using scatter-gather.  We have to go through the list one entry
    218          * at a time.  Each s-g entry contains some number of pages, and
    219          * each page has to be kmap()'ed separately.  If the page is already
    220          * in kernel-addressable memory then kmap() will return its address.
    221          * If the page is not directly accessible -- such as a user buffer
    222          * located in high memory -- then kmap() will map it to a temporary
    223          * position in the kernel's virtual address space. */
    224         } else {
    225                 struct scatterlist *sg =
    226                                 (struct scatterlist *) srb->request_buffer
    227                                 + *index;
    228
    229                 /* This loop handles a single s-g list entry, which may
    230                  * include multiple pages.  Find the initial page structure
    231                  * and the starting offset within the page, and update
    232                  * the *offset and *index values for the next loop. */
    233                 cnt = 0;
    234                 while (cnt < buflen && *index < srb->use_sg) {
    235                         struct page *page = sg->page +
    236                                         ((sg->offset + *offset) >> PAGE_SHIFT);
    237                         unsigned int poff =
    238                                         (sg->offset + *offset) & (PAGE_SIZE-1);
    239                         unsigned int sglen = sg->length - *offset;
    240
    241                         if (sglen > buflen - cnt) {
    242
    243                                 /* Transfer ends within this s-g entry */
    244                                 sglen = buflen - cnt;
    245                                 *offset += sglen;
    246                         } else {
    247
    248                                 /* Transfer continues to next s-g entry */
    249                                 *offset = 0;
    250                                 ++*index;
    251                                 ++sg;
    252                         }
    253
    254                         /* Transfer the data for all the pages in this
    255                          * s-g entry.  For each page: call kmap(), do the
    256                          * transfer, and call kunmap() immediately after. */
    257                         while (sglen > 0) {
    258                                 unsigned int plen = min(sglen, (unsigned int)
    259                                                 PAGE_SIZE - poff);
    260                                 unsigned char *ptr = kmap(page);
    261
    262                                 if (dir == TO_XFER_BUF)
    263                                         memcpy(ptr + poff, buffer + cnt, plen);
    264                                 else
    265                                         memcpy(buffer + cnt, ptr + poff, plen);
    266                                 kunmap(page);
    267
    268                                 /* Start at the beginning of the next page */
    269                                 poff = 0;
    270                                 ++page;
    271                                 cnt += plen;
    272                                 sglen -= plen;
    273                         }
    274                 }
    275         }
    276
    277         /* Return the amount actually transferred */
    278         return cnt;
    279 }

,总是在最温柔的时候醉人;

  ,总是在最纤细的时候飘逸;

,总是在将调零的时候让人惋惜;

  ,总是在最深冷的时候令人希冀;

  在编写Linux设备驱动时,在生与死的抉择中,我们总是无法逃避,我们总是要涉及内存管理.内存管理毫无疑问是Linux内核中最复杂的一部分,能不涉及我们都希望别去涉及.但生活中总是充满了无奈,该来的还是会来.而我们倒是该庆幸,毕竟它不是每个月来一次.(这里说的是水电煤气费的单子,不许误解.)更不是八点档的连续剧,每天准时来赚你的眼泪.

所以,usb_stor_access_xfer_buf()函数映入了人们的眼帘.

  首先判断srb->use_sg是否为0.

  无聊的it玩家们创建了有一个词,叫做scatter/gather,她是一种用于高性能IO的标准技术.她通常意味着一种DMA传输方式,对于一个给定的数据块,她老人家可能在内存中存在于一些离散的缓冲区,换言之,就是说一些不连续的内存缓冲区一起保存一个数据块,如果没有scatter/gather,那么当我们要建立一个从内存到磁盘的传输,那么操作系统通常会为每一个buffer做一次传输,或者干脆就是把这些不连续的buffer里边的冬冬全都移动到另一个很大的buffer里边,然后再开始传输.那么这两种方法显然都是效率不高的.毫无疑问,如果 操作系统/驱动程序/硬件 能够把这些来自内存中离散位置的数据收集起来(gather up)并转移她们到适当位置整个这个步骤是一个单一的操作的话,效率肯定就会更高.反之,如果要从磁盘向内存中传输,而有一个单一的操作能够把数据块直接分散开来(scatter)到达内存中需要的位置,而不再需要中间的那个块移动,或者别的方法,那么显然,效率总会更高.

  struct scsi_cmnd,有一个成员unsigned short use_sg,上头传下来的scsi_cmnd,use_sg是设好了的,咱们判断一下,如果她为0,那么说明没有使用scatter/gather.struct scsi_cmnd中还有两个成员,unsigned request_bufflenvoid *request_buffer,她们和use_sg是什么关系呢?

  事实上,要玩scatter/gather,就需要一个scatterlist数组,有人管她叫散列表数组.对于不同的硬件平台,定义了不同的struct scatterlist结构体,她们来自include/asm/scatterlist.h,(如果是硬件平台i386,那么就是include/asm-i386/scatterlist.h,如果是x86_64的平台,那么就在include/asm-x86_64/scatterlist.h),然后所谓的scatter/gather就是一次把整个scatterlist数组给传送掉.use_sg0就表示没有scatter gather list,或者说scatterlist,对于这种情况,数据将直接传送给request_buffer或者直接从request_buffer中取得数据.而如果use_sg大于0,那么表示scatter gather list这么一个数组就在request_buffer,而数组元素个数正是use_sg.也就是说,srb->request_buffer里边的冬冬有两种可能,一种是包含了数据本身,另一种是包含了scatter gather list.具体是哪种情况通过判断use_sg来决定.而接下来即将要讲到的srb->request_bufflen顾名思义,就是buffer的长度,但对于use_sg大于0的情况,换言之,对于使用scatter gather list的情况,request_bufflen没有意义,将被忽略.

对这些原理有了基本的了解之后,我们可以从下节开始看代码了.这里先提醒一下,要注意我们这个函数虽然看似是传输数据,可它实际上并没有和usb真正发生关系,我们只是从软件上来fix一个硬件的bug,这个bug就是我们已经说过了的,不能响应基本的SCSI命令INQUIRY,你问她,她完全不予理睬.至于为什么不能响应我就不用说了吧?人的冷漠,不是因为天生就如此,只是暗淡的心境早已将所有通向阳光的窗户关闭了.而设备的冷漠,显然是制造商做的傻事了.(我没有讽刺Sony的意思,事实上这种情况并非只有Sony他们家才出现了,别的厂家也有这样的产品.只是,Devicebug,可以让程序去补,我们80后的梦碎了,拿什么去补?)

所以对于那些不能相应INQUIRY命令的设备,当上层的驱动程序去INQUIRY的时候,实际上是调用我们的queuecommand,那么我们根本就不用和下面的硬件去打交道,就直接回复上层,即我们从软件上来准备这个一段INQUIRY数据给上层,这才是我们这个函数的目的.真正的和硬件打交道的代码在后面,我们还没走到那一步.到了那一步再说.

原创粉丝点击