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

时间:2021-03-05 04:01:26

很显然,我们是把为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()函数映入了我们的眼帘。

首先判断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数据给上层,这才是我们这个函数的目的。真正的和硬件打交道的代码在后面,我们还没走到那一步。