首页 > 系统 > Linux >

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

2011-11-22

很显然,我们是把为INQUIRY命令准备的数据保存到了我们自己定义的一个结构体中,即structdata_ptr[36],但是我们是为了回应一个SCSI命令,最终需要知道答案的是SCSI核心层。正是它们传递了一个scsi_cmnd结构体下...

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

相关文章
最新文章
热点推荐