《Linux那些事儿之我是USB》我是U盘(28)彼岸花的传说(七)


 

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

 

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

 

243 /* Store the contents of buffer into srb'stransfer buffer and set the

 

244  *SCSI residue. */

 

245 void usb_stor_set_xfer_buf(unsigned char*buffer,

 

246         unsigned intbuflen, struct scsi_cmnd *srb)

 

247 {

 

248   unsigned int index = 0, offset =0;

 

249

 

250    usb_stor_access_xfer_buf(buffer,buflen, srb, &index, &offset,

 

251                        TO_XFER_BUF);

 

252   if (buflen <srb->request_bufflen)

 

253        srb->resid = srb->request_bufflen - buflen;

 

254 }

 

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

 

159 unsigned int usb_stor_access_xfer_buf(unsignedchar *buffer,

 

160         unsigned intbuflen, struct scsi_cmnd *srb, unsigned int *index,

 

161         unsigned int*offset, enum xfer_buf_dir dir)

 

162 {

 

163     unsignedint cnt;

 

164

 

165    /* If notusing scatter-gather, just transfer the data directly.

 

166    * Make certain it will fit in the available buffer space. */

 

167    if(srb->use_sg == 0) {

 

168       if (*offset >= srb->request_bufflen)

 

169            return 0;

 

170         cnt = min(buflen, srb->request_bufflen -*offset);

 

171         if (dir == TO_XFER_BUF)

 

172            memcpy((unsigned char *) srb->request_buffer+ *offset,

 

173                                        buffer,cnt);

 

174          else

 

175        memcpy(buffer, (unsigned char*) srb->request_buffer +

 

176                                        *offset, cnt);

 

177        *offset += cnt;

 

178

 

179     /*Using scatter-gather.  We have togo through the list one entry

 

180     * at a time.  Each s-g entry contains some number of pages, and

 

181     * each page has to be kmap()'edseparately.  If the page is already

 

182     * in kernel-addressable memory thenkmap() will return its address.

 

183      * If the page is not directly accessible-- such as a user buffer

 

184      * located in high memory -- then kmap()will map it to a temporary

 

185     * position in the kernel's virtualaddress space. */

 

186    } else {

 

187        struct scatterlist *sg =

 

188                         (struct scatterlist *)srb->request_buffer

 

189                           + *index;

 

190

 

191         /* This loop handles a single s-g list entry,which may

 

192          *include multiple pages.  Find theinitial page structure

 

193           *and the starting offset within the page, and update

 

194          *the *offset and *index values for the next loop. */

 

195         cnt = 0;

 

196         while (cnt < buflen && *index <srb->use_sg) {

 

197             struct page *page = sg->page +

 

198                               ((sg->offset + *offset)>> PAGE_SHIFT);

 

199              unsigned int poff =

 

200                         (sg->offset+ *offset) & (PAGE_SIZE-1);

 

201              unsigned int sglen = sg->length- *offset;

 

202

 

203             if (sglen > buflen - cnt) {

 

204

 

205                  /* Transfer ends within this s-gentry */

 

206                   sglen = buflen - cnt;

 

207                   *offset += sglen;

 

208             } else {

 

209

 

210                    /* Transfer continues to next s-g entry */

 

211                   *offset = 0;

 

212                   ++*index;

 

213                   ++sg;

 

214              }

 

215

 

216             /* Transfer the data for all thepages in this

 

217               * s-g entry.  For each page: call kmap(), do the

 

218               * transfer, and call kunmap() immediately after. */

 

219             while (sglen > 0) {

 

220                   unsigned int plen = min(sglen,(unsigned int)

 

221                                                PAGE_SIZE- poff);

 

222                   unsigned char *ptr = kmap(page);

 

223

 

224                 if (dir == TO_XFER_BUF)

 

225                       memcpy(ptr + poff, buffer + cnt,plen);

 

226                  else

 

227                         memcpy(buffer + cnt, ptr + poff, plen);

 

228                  kunmap(page);

 

229

 

230                   /* Start at the beginning of thenext page */

 

231                   poff = 0;

 

232                   ++page;

 

233                   cnt += plen;

 

234                   sglen -= plen;

 

235             }

 

236          }

 

237   }

 

238

 

239    /* Returnthe amount actually transferred */

 

240    returncnt;

 

241 }

 

在编写Linux设备驱动时,总是要涉及内存管理。内存管理毫无疑问是Linux内核中最复杂的一部分,能不涉及我们都希望别去涉及。但生活中总是充满了无奈,该来的还是会来。

 

所以,usb_stor_access_xfer_buf()函数映入了我们的眼帘。 www.2cto.com

 

首先判断srb->use_sg是否为0。IT玩家们创建了有一个词,即scatter/gather,它是一种用于高性能IO的标准技术。它通常意味着一种DMA传输方式,对于一个给定的数据块,它可能在内存中存在于一些离散的缓冲区。换而言之,就是一些不连续的内存缓冲区一起保存一个数据块。如果没有scatter/gather,那么当我们要建立一个从内存到磁盘的传输,那么操作系统通常会为每一个buffer做一次传输,或者干脆就是把这些不连续的buffer里边的东西全都移动到另一个很大的buffer里面,再开始传输。那么这两种方法显然都是效率不高的。

 

毫无疑问,如果操作系统、驱动程序或硬件能够把这些来自内存中离散位置的数据收集起来(gatherup)并转移它们到适当位置整个这个步骤是一个单一的操作的话,效率肯定就会更高。反之,如果要从磁盘向内存中传输,而有一个单一的操作能够把数据块直接分散开来(scatter)到达内存中需要的位置,而不再需要中间的那个块移动,或者别的方法,那么显然,效率总会更高。

 

在structscsi_cmnd中,有一个成员unsignedshort use_sg,上头传下来的scsi_cmnd,其use_sg是设好了的,这里判断一下。如果它为0,那么说明没有使用scatter/gather。struct scsi_cmnd中还有两个成员,unsigned request_bufflen和void *request_buffer,它们和use_sg是什么关系呢?

 

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

 

对这些原理有了基本的了解之后,我们可以从下节开始看代码了。这里先提醒一下,要注意我们这个函数虽然看似是传输数据,可它实际上并没有和USB真正发生关系,我们只是从软件上来fix一个硬件的bug,这个bug就是我们已经说过了的,不能响应基本的SCSI命令INQUIRY。

 

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

相关内容

    暂无相关文章