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