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

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

你可能感兴趣的:(linux)