免费注册 查看新帖 |

Chinaunix

  平台 论坛 博客 文库
最近访问板块 发新帖
楼主: projl
打印 上一主题 下一主题

《Linux 那些事儿之我是U盘》 [复制链接]

论坛徽章:
0
81 [报告]
发表于 2008-06-06 14:17 |只看该作者
第一次亲密接触(三)

让我们单刀直入,进入usb_stor_msg_common() 函数.

首先看145 行,让us->flags 和ABORTING_OR_DISCONNECTING 相
与,ABORTING_OR_DISCONNECTING 宏定义于drivers/usb/storage/usb.h 中:

78 /* Dynamic flag definitions: used in set_bit() etc. */
79 #define US_FLIDX_URB_ACTIVE 18 /* 0x00040000 current_urb is in use */
80 #define US_FLIDX_SG_ACTIVE 19 /* 0x00080000 current_sg is in use */
81 #define US_FLIDX_ABORTING 20 /* 0x00100000 abort is in progress */
82 #define US_FLIDX_DISCONNECTING 21 /* 0x00200000 disconnect in progress
*/
83 #define ABORTING_OR_DISCONNECTING ((1UL << US_FLIDX_ABORTING) |
\
84 (1UL << US_FLIDX_DISCONNECTING))
85 #define US_FLIDX_RESETTING 22 /* 0x00400000 device reset in progress
*/
86 #define US_FLIDX_TIMED_OUT 23 /* 0x00800000 SCSI midlayer timed
out */

她只是一个flag,咱们知道,每一个usb mass storage 设备,会有一个struct us_data 的数据结构,即
us,所以,在整个probe 的过程来看,她相当于一个"全局"的变量,因此咱们可以使用一些flags 来标记一些
事情.比如,此处,对于提交urb 的函数来说,显然她不希望设备此时已经处于放弃或者断开的状态,因为那样
就没有必要提交urb 了嘛不是.

而下一个函数init_completion(), 只是一个队列操作函数,她被定义于include/linux/completion.h
中:

13 struct completion {
14 unsigned int done;
15 wait_queue_head_t wait;
16 };
24 static inline void init_completion(struct completion *x)
25 {
26 x->done = 0;
27

init_waitqueue_head(&x->wait);
28 }
她只是调用了init_waitqueue_head 去初始化一个等待队列.而struct completion 的定义也在上面已
经列出.关于init_waitqueue_head 咱们将在下面的故事中专门进行描述.

而接下来,都是在设置us 的current_urb 结构,咱们看161 行,transfer_flags 被设置成了
URB_ASYNC_UNLINK | URB_NO_SETUP_DMA_MAP, 其中URB_ASYNC_UNLINK 表明
usb_unlink_urb() 函数将被异步调用,不懂异步调用也没有关系,因为内核的发展是如此的迅速,这一点完
全可以和上海的发展速度有得一拼,最新的内核中,人们已经很少用这个宏了,因为usb_unlink_urb 已经是

论坛徽章:
0
82 [报告]
发表于 2008-06-06 14:18 |只看该作者
异步调用的函数了,如果是同步调用,则可以使用另一个函数usb_kill_urb(). 这两个函数的作用就是取消
一个urb 请求.同步调用用于这样一种情况,即函数执行过程中可以进入睡眠,满足一定条件再醒来继续执行,
而异步调用则不会睡眠,那么这里之所以要设置URB_ASYNC_UNLINK, 其目的就在于让
usb_unlink_urb 是异步的执行,原因是,有的时候我们取消一个urb request 的时候是处在一种不能睡眠
的上下文,比如后面我们会看到的处理超时的函数.我们在互联网上下载一个很大文件的时候,常常会遇到超
时的情况,然后我们的请求就被中止了.对于usb 系统同样有这个问题,我们会给一个urb 设置超时,提交上
去到了一定时间还没能传输好的话就意味着可能传输有问题,这种情况我们通常也会把这个urb 给取消掉.
于是这里有两种情况,第一,我们提交了urb 之后就不管事了,我们等待,怎么等待?睡眠,我们的进程进入睡
眠,换句话说在我们这个上下文情景中也就是storage_probe 函数睡了.如果不超时,那么万事大吉,urb 执
行完了之后我们的进程被唤醒,继续往下走.那么天下平安.但是第二种情况是,我们进入了睡眠,可是时间到
了,urb 还没有执行完,那么超时函数会被执行,它就会去取消这个urb,如果说这个超时函数也可以睡眠,那
就不得了了,天下大乱了,都睡了,谁干活?整个驱动不就瘫痪了!所以,这种情况下我们要调用的函数是不能
睡眠的,也就是说必须是异步的.而usb_unlink_urb 内部会通过判断是否有URB_ASYNC_UNLINK 这么
一个flag 被设置在了urb->transfer_flags 中,来选择是异步执行还是同步执行,实际上它的同步执行就是
调用usb_kill_urb 而已.不过刚才说的,最新版的内核,比如2.6.21, 它里边就把这两个函数彻底分开了,不
用设这么一个flag,usb_unlink_urb 就是异步,usb_kill_urb 就是同步.

而URB_NO_SETUP_DMA_MAP 表明,如果使用DMA 传输,则urb 中setup_dma 指针所指向的缓冲
区是DMA 缓冲区,而不是setup_packet 所指向的缓冲区.接下来再或上
URB_NO_TRANSFER_DMA_MAP 则表明,如果本urb 有一个DMA 缓冲区需要传输,则该缓冲区是
transfer_dma 指针所指向的那个缓冲区,而不是transfer_buffer 指针所指向的那一个缓冲区.换句话说,
如果没设置这两个DMA 的flag,那么usb core 就会使用setup_packet 和transfer_buffer 作为数据传
输的缓冲区,然后下面两行就是把us 的iobuf_dma 和cr_dma 赋给了urb 的transfer_dma 和
setup_dma.(157 至160的注释表明,只要transfer_buffer 被赋了值,那就假设有DMA 缓冲区需要传输,
于是就去设URB_NO_TRANSFER_DMA_MAP.) 关于DMA 这一段,因为比较难理解,所以我们多说几句.

首先,这里是两个DMA 相关的flag,一个是URB_NO_SETUP_DMA_MAP, 而另一个是
URB_NO_TRANSFER_DMA_MAP. 主意这两个是不一样的,前一个是专门为控制传输准备的,因为只有控
制传输需要有这么一个setup 阶段需要准备一个setup packet.而我们把让setup_packet 指向了
us->cr 别忘了我们当初为us->cr 申请内存的时候用的是下面这句:

449 /* Allocate the device-related DMA-mapped buffers */

450 us->cr = usb_buffer_alloc(us->pusb_dev, sizeof(*us->cr),

451 GFP_KERNEL, &us->cr_dma);

别忘了这里的us->cr_dma,这个函数虽然返回值是赋给了us->cr,但与此同时,在us->cr_dma 中记
录的可以该地址所映射的dma 地址,那么刚才这里设置了URB_NO_SETUP_DMA_MAP 这么一个flag,
就说明,如果是DMA 方式的传输,那么usb core 就应该使用us->cr_dma 里边的冬冬去进行dma 传输,
而不要用us->cr 里边的冬冬了.换句话说,也就是urb 里边的setup_dma 而不是setup_buffer.

同样transfer_buffer 和transfer_dma 的关系也是如此,我们当初同样用类似的方法申请了
us->iobuf 的内存:

457

us->iobuf = usb_buffer_alloc(us->pusb_dev, US_IOBUF_SIZE,
458


GFP_KERNEL, &us->iobuf_dma);

论坛徽章:
0
83 [报告]
发表于 2008-06-06 14:18 |只看该作者
这里就有us->iobuf 和us->iobuf_dma 这两个咚咚,但是我们注意到,163 和164 行,我们在设置
URB_NO_TRANSFER_DMA_MAP 这个flag 的时候,先做了一次判断,判断
us->current_urb->transfer_buffer 是否等于us->iobuf, 这是什么意思呢?我们在什么地方对
transfer_buffer 赋过值? 答案是usb_fill_control_urb 中,我们把us->iobuf 传递了过去,它被赋给了
urb->transfer_buffer,这样做就意味着我们这里将使用DMA 传输,所以这里就设置了这个flag,倘若我们
不希望进行DMA 传输,那很简单,我们在调用usb_stor_msg_common 之前,不让
urb->transfer_buffer 指向us->iobuf 就是了,反正这都是我们自己设置的,别人管不着.需要知道的
是,transfer_buffer 是给各种传输方式中真正用来数据传输的,而setup_packet 仅仅是在控制传输中发
送setup 的包的,控制传输除了setup 阶段之外,也会有数据传输阶段,这一阶段要传输数据还是得靠
transfer_buffer, 而如果使用dma 方式,那么就是使用transfer_dma.

Ok,下一句,169 行,终于到了提交urb 这一步了,usb_submit_urb 得到调用,作为usb 设备驱动程序,
我们不需要知道这个函数究竟在做什么,只要知道怎么使用就可以了,无需关注代码背后的哲学.它的定义在
drivers/usb/core/urb.c 中,我们得知道它有两个参数,一个就是要提交的urb,一个是内存申请的flag, 这
里我们使用的是GFP_NOIO, 意思就是不能在申请内存的时候进行IO 操作,道理很简单,咱们这个是存储设
备,调用usb_submit_urb 很可能是因为我们要读些磁盘或者U盘,那这种情况如果申请内存的函数又再一
次去读写磁盘,那就有问题了,什么问题?嵌套呗.什么叫申请内存的函数也会读写磁盘?玩过Linux 的人不会
不知道swap 吧,交换分区,干嘛要交换啊,可不就是因为内存不够么.使用磁盘作为交换分区不就方便了,所
以申请内存的时候可能要的内存在磁盘上,那就得交换回来.这不就读写磁盘了么?所以我们为了读写硬盘
而提交urb,那么这个过程中就不能再次有IO 操作了,这样做的目的是为了杜绝嵌套死循环.

于是我们调用了169 行就可以往下走了,剩下的事情usb core 和usb host 会去处理,至于这个函数本
身的返回值,如果一切正常,status 将是0.所以这里判断如果status 不为0 那么就算出错了.

177 行,一个urb 被提交了之后,通常我们会把us->flags 中置上一个flag, US_FLIDX_URB_ACTIVE,
让我们记录下这个urb 的状态是活着的.

180 行,这里我们再次判断us->flags, 看是不是谁置了aborting 或者disconnected 的flag.稍后我们
会看到谁会置这些flag,显然如果已经置了这些flag 的话,咱们就没必要往下了,这个urb 可以cancel 了.

190 行,一个新的故事将被引出,这就是伟大的时间机制.

能冲刷一切的,除了眼泪,就是时间.所以Linux 中引入了时间机制.

第一次亲密接触(四)

金城武说:不知道从什么时候开始,在什么东西上面都有个日期,秋刀鱼会过期,肉罐头会过期,连保鲜纸
都会过期,我开始怀疑,在这个世界上,还有什么东西是不会过期的?

有时候我也被这个问题所困扰,我不知道是我不明白还是这世界变化太快.连Linux 中都引入了过期这么
一个概念.说文雅一点就是超时.设置一个时间,如果时间到了该做的事情还没有做完,那么某些事情就会发
生.

论坛徽章:
0
84 [报告]
发表于 2008-06-06 14:19 |只看该作者
比如,咱们需要做这样一些事情,定个闹钟,比如咱们需要烤蛋糕,现在是8 点30,而咱们要烤45分钟,所以
咱们希望闹钟9 点一刻响,然后当时间到了,闹钟就如咱们期待的一样,响个不停.在计算机中,咱们也需要做
这样的事情,有些事情,咱们需要时间控制,特别是网络,通信,等等,凡是涉及数据传输的事儿,就得考虑超时,
换句话说,定个闹钟,你要是在这个给定的时间里还没做好你该做的事情,那么停下来,别做了,肯定有问题,
比如,咱们如果烤蛋糕45 分钟,发现蛋糕一点香味都没有,颜色也没变,那肯定有问题,别烤了,先检查一下烤
箱是不是坏了,是不是停电了,等等.而具体到咱们这里,需要用一个闹钟,或者叫专业一点,定时器,如果时间
到了,就执行某个函数,这个功能Linux 内核的时间机制已经实现了,咱们只需要按"说明书"调用相应的接口
函数即可.看代码,190 行,如果timeout>0, 也就是说需要设置闹钟,那么首先需要定义一个struct
timer_list 结构体的变量,咱们这里定义的变量叫做to_timer( 在usb_stor_msg_common 一开始就定
义了的),然后用init_timer() 函数和add_timer() 函数来真正实现设置闹钟,init_timer() 是初始化,然后
设置好之后调用add_timer 才能让闹钟生效.具体怎么设置的呢?在add_timer() 之前,为
to_timer.expires 赋值为jiffies+timeout,to_timer.function 赋值为
timeout_handler,to_timer.data 赋值为us. 这表示,超时时间点为当前时间加上一个
timeout,(jiffies,Linux 内核中赫赫有名的全局变量,表示当前时间),timeout 咱们前面调用
usb_stor_msg_common 的时候给设置成了HZ,也就是1 秒.当时间到了之后,timeout_handler 函数
会被执行,而us 作为参数传递给她.不妨来看一下timeout_handler 函数吧,她定义于
drivers/usb/storage/transport.c 中:

119 /* This is the timeout handler which will cancel an URB when its timeout
120 * expires.
121 *
/
122 static void timeout_handler(unsigned long us_
)
123
{
124 struct us_data *us = (struct us_data *) us_
;
125
126 if (test_and_clear_bit(US_FLIDX_URB_ACTIVE, &us->flags))
{
127 US_DEBUGP("Timeout -- cancelling URB\n")
;
128 usb_unlink_urb(us->current_urb)
;
129
}
130
}


看得出,其实也没做什么,就是清除US_FLIDX_URB_ACTIVE flag, 然后调用usb_unlink_urb() 函数
撤销当前这个urb.还记得刚才说的那个同步异步了吗?这正是刚才说的那个异步的情形,显然此刻这个函数
不能睡眠,否则整个driver 就挂了...

紧接着,199 行,非常重要的一句wait_for_completion(&urb_done), 这句话会使本进程进入睡眠.别
忘了刚才我们那句init_completion(&urb_done),urb_done 是一个struct completion 结构体变量,
这个定义在usb_stor_msg_common() 函数的第一行就出现了.显然completion 是Linux 中同步机制
的一个很重要的结构体.与wait_for_completion 对应的一个函数是complete(). 其用法和作用是这样的:
首先我们要用init_completion 初始化一个struct completion 的结构体变量,然后调用
wait_for_completion() 这样当前进程就会进入睡眠,处于一种等待状态,而另一个进程可能会去做某事,
当它做完了某件事情之后,它会调用complete() 函数,一旦它调用这个complete 函数,那么刚才睡眠的这
个进程就会被唤醒.这样就实现了一种同步机制,或者叫等待机制.那么我们来看complete 函数在哪里被调
用的,换句话说,咱们这里一旦睡去,何时才能醒来.

论坛徽章:
0
85 [报告]
发表于 2008-06-06 14:20 |只看该作者
还记得在调用usb_fill_control_urb() 填充urb 的时候咱们设置了一个urb->complete 指针吗?没错,
当时咱们就看到了,urb->complete=usb_stor_blocking_completion, 这相当于向usb host
controller driver 传达了一个信息.所以,当urb 传输完成了之后,usb host controller 会唤醒她,但不会直
接唤醒她,而是通过执行之前设定的urb 的complete 函数指针所指向的函数,即调用
usb_stor_blocking_completion() 函数去真正唤醒她.usb_stor_blocking_completion() 函数定义于
drivers/usb/storage/transport.c 中:

109 /* This is the completion handler which will wake us up when an URB

110 * completes.

111 */

112 static void usb_stor_blocking_completion(struct urb *urb, struct pt_regs *regs)

113 {

114 struct completion *urb_done_ptr = (struct completion *)urb->context;

115

116 complete(urb_done_ptr);

117 }

这个函数就两句话,但她调用了complete() 函数,urb_done_ptr 就被赋为urb->context, 而
urb->context 是什么? usb_stor_msg_common() 函数中,152 行,可不就是把刚初始化好的
urb_done 赋给了它么?这个函数可是Linux 内核的核心函数,不要问她从哪里来,她会告诉你她来自内核底
层,没错,她的户口在kernel/sched.c, 很显然,她就是唤醒刚才睡眠的那个进程.换言之,到
这,wait_for_completion() 将醒来,从而继续往下走.

如果你足够好奇,你会问如果超时,那么timeout_handler 会被调用,于是usb_unlink_urb 会被调用,
然后呢?其实usb_stor_blocking_completion 还是会被调用,而且会设置urb->status 以告诉大家这个
urb 被cancel 了.

下面只剩下几行代码了.首先是clear_bit() 清除US_FLIDX_URB_ACTIVE, 表明这个urb 不再是
active 了.因为该干的事都干完了,就好比您的包裹已经寄到了,那显然您填的那个单子就没有用了.至少她
上面应该有标志表明这份单子对应的包裹已经送过了,不要再送了.如果是超时了,那么也是一样的,urb 都
被cancel 了,当然就不用设为active 了.

然后下一行,如果这时timeout 还大于0,那么说明刚才您设的那个超时闹钟还没到过期,而您该做的事
情却已经做完了,所以这个闹钟就不需要设了,就好比邮局承诺您三天寄到,完了您记住了,三天她要没寄到,
您就去索赔,所以您自个儿就订了个闹钟,三天真到期了您就可以去索赔,但是如果人家两天就给您寄到了,
那您这个闹钟就没意义了嘛不是,所以这样您就得取消这个闹钟,省得她那弦老紧绷着,这里您也得删除刚才
那个to_timer, 这样您可以调用Linux 内核为您提供的函数del_timer_sync(), 她的参数就是刚才这个
to_timer 的地址.最后一句,usb_stor_msg_common() 函数终于该返回了,return
us->current_urb->status,返回的就是urb 的status.于是我们总算是可以离开这个函数了.

返回之后,又回到usb_stor_control_msg() 中来,如果status 是0,那么说明成功传输了,对于成功传输
的情况,urb 的actual_length 将被赋值为实际传输长度,然后usb_stor_control_msg() 也返回了,要么
是实际长度,要么就是不成功的具体status.因此我们也不得不离开这个函数.诚然,快乐要有悲伤作陪,雨过
应该就有天晴.但是如果雨后还是雨,如果忧伤之后还是忧伤.请让我们从容面对这离别之后的离别.

论坛徽章:
0
86 [报告]
发表于 2008-06-06 14:20 |只看该作者
于是,走过了千山万水,经历了千辛万苦,咱们再一次回到了久违的usb_stor_Bulk_max_lun() 函数.
这样一次真正的控制传输就这么开始就这么结束了.

接下来,我们继续看,控制传输的结果返回给了result, 我们说过,单纯的U 盘一般来说这个结果总是0,即
它必然只有一个lun. 这里判断的是result 大于0,道理很简单,result 是一个int 型的数据,而返回的给它的
实际上是iobuf[0] 这么一个char 类型的变量,所以是字符’0’,保存成int 型当然就大于0 了,所以这里打印
出来结果是0,但是result 实际上是大于0 的.而945 行我们同样注意到,如果result 是一些奇怪的值,正如
注释所说,有的设备它就不认GetMaxLUN 这个命令,就像在她的心里潜伏着一个深渊,扔下巨石也发不出
声音来,或者它干脆就返回一个0 长度的结果来,那么这种情况那么我们就只能把这种设备当作只有一个
LUN 了,所以也就返回0 得了.

不过933 到935 这三行需要说一下.这也是专门为一些变态的设备准备的.一般的设备用不着.只是
usb_stor_clear_halt 这个函数是我们自己定义的,并且今后我们也会用到,所以我们还是讲一下.不过,在
这个函数中,我们将再一次见到控制传输,但是毕竟不再是我们的第一次亲密接触了,所以,虽然我们依然还
是在usb_stor_Bulk_max_lun 中,但还是让我们下一节在讲吧.

最后需要解释一下的是,像init_timer(),add_timer(),del_timer_sync() 这几个函数都是Linux 内核
中的核心函数,包括结构体struct timer_list, 他们都来自include/linux/timer.h 和kernel/timer.c 中,
我们只需要知道调用就可以了,不用知道究竟怎么实现的,只需要知道这样设置了超时的话,我们注册的超时
处理函数就会被执行.而至于它究竟如何去调用的,如何计时如何去判断超时这些内核自会处理,不用我们担
心,我们瞎操心也没用.对于内核来说,时间是怎样划破她的皮肤,只有她自己最清楚.而对于写设备驱动的人
来说,这些核心代码就像是天空中的云朵,你看着它往某一个方向飘,却什么也做不了.正如令狐冲所说的那
样:”有些事情本身我们无法控制,只好控制自己.”

将控制传输进行到底

其实usb_stor_clear_halt 这个函数的作用很简单,就是spec 里边规定了,usb 设备中,有两类端点,必
须具有一个叫做Halt 的特征,啥是Halt?查金山词霸去,中断,停止,暂停,怎么解释呢,你把手机关了,就不能
给超级女生发短信投票了吧,你把电脑关了,就不能上黄色网站了吧,你把电视机关了,就不能看中国之队在
亚洲杯上的精彩表演了吧.对于usb 设备来说,其中端端点和Bulk 端点就有这么一个特征,叫做Halt,其实
就是寄存器里的某一位,设为1,就表示设置了Halt 的特征,那就是表示这个端点不工作了.要想让端点重新
工作,很简单,把这一位设置为0 就可以了.

关于Halt,用我们行话说,这叫做一个feature,其实就是一个特征,坊间更喜欢说feature.而usb 设备实
际上有很多feature.确切的说,有的feature 是算device 的feature,有些是interface 的feature,有些
是endpoint 的feature.而Halt是endpoint 的feature. usb spec规定了一些请求,比如SET_FEATURE,
以及CLEAR_FEATURE,顾名思义,就是设置一个feature 或者清除一个feature.那么我们这里发生的是
clear halt, 实际上就是执行CLEAR_FEATURE,清除halt 这个feature.刚才通过
usb_stor_Bulk_max_lun() 函数我们已经看到了对于一次控制传输,我们作为设备驱动需要做哪些工作,
这里和刚才的区别仅仅在于,刚才发送的请求是GET MAX LUN, 而现在要发送的请求是CLEAR FEATURE.
另一方面呢,GET MAX LUN 是usb mass storage spec 专门给它们这一小类设备定义的,而CLEAR
FEATURE 那是所有的usb 设备都通用的,因为它是usb spec 所规定的.

论坛徽章:
0
87 [报告]
发表于 2008-06-06 14:21 |只看该作者
那么什么时候我们需要调用这个函数呢?先不说我们这里的上下文,实际上usb spec 规定了,对于设备的
bulk 端点,每当设备在reset 之后,需要清除halt 这个feature 然后端点才能正常工作.所以之后我们会看
到,在reset 相关的函数里我们会调用这个函数,那么我们此刻所遇到的这个函数是处于什么情景呢?如果不
看注释你就能看懂,那么我只能说,你他妈的太有才了!开源社区需要你这样伟大的自由主义战士!注释里说
得很清楚,有些变态的设备,它就是不跟你按常理出牌,人家能正常响应GetMaxLUN 这个request, 它偏要
耍个性,就是不认spec,你发送GetMaxLUN 请求过来,它不予回复,它出现STALL 的特点,什么是STALL?
其实就是Halt, 端点挂起,或者通俗一点理解,就是死机了.所以,毫无疑问,我们要把这个halt 给清掉,否则设
别没有办法工作了.

Ok,是时候该看看函数内部了,这是一个定义于drivers/usb/storage/transport.c 中的函
数.usb_stor_clear_halt():

243 /* This is a version of usb_clear_halt() that allows early termination and
244 * doesn't read the status from the device -- this is because some devices
245 * crash their internal firmware when the status is requested after a halt.
246 *
247 * A definitive list of these 'bad' devices is too difficult to maintain or
248 * make complete enough to be useful. This problem was first observed on the
249 * Hagiwara FlashGate DUAL unit. However, bus traces reveal that neither
250 * MacOS nor Windows checks the status after clearing a halt.
251 *
252 * Since many vendors in this space limit their testing to interoperability
253 * with these two OSes, specification violations like this one are common.
254 */
255 int usb_stor_clear_halt(struct us_data *us, unsigned int pipe)
256 {
257 int result;
258 int endp = usb_pipeendpoint(pipe);
259
260 if (usb_pipein (pipe))
261 endp |= USB_DIR_IN;
262
263 result = usb_stor_control_msg(us, us->send_ctrl_pipe,
264 USB_REQ_CLEAR_FEATURE, USB_RECIP_ENDPOINT,
265 USB_ENDPOINT_HALT, endp,
266 NULL, 0, 3*HZ);
267
268

/* reset the endpoint toggle *
/
269


usb_settoggle(us->pusb_dev, usb_pipeendpoint(pipe)
,
270


usb_pipeout(pipe), 0)
;
271
272


US_DEBUGP("%s: result = %d\n", __FUNCTION__, result)
;
273 return result;
274
}

论坛徽章:
0
88 [报告]
发表于 2008-06-06 14:21 |只看该作者
258 行,usb_pipeendpoint, 定义于include/linux/usb.h 中,

1091 #define usb_pipeendpoint(pipe) (((pipe) >> 15) & 0xf)

很简单,右移15 位,然后与0xf 相与,得到的自然就是原来pipe 里边的15 至18 位. 我们曾经讲过,一个
pipe 的15 位至18 位是endpoint 号,(一共16 个endpoint,) 所以很显然,这里就是得到endpoint 号.
然后把她赋给了endp. 然后usb_pipein() 也定义于同一文件中,

1088 #define usb_pipein(pipe) ((pipe) & USB_DIR_IN)

1089 #define usb_pipeout(pipe) (!usb_pipein(pipe))

显然,就是判断她是不是IN 的管道.如果是IN,那么她返回1,反之,返回0.usb_pipeout 则相反.261 行,
如果是,就或上.

263 行,再一次调用usb_stor_control_msg 来传递信息了.USB_REQ_CLEAR_FEATURE 对应的
usb spec 的一个标准请求命令CLEAR_FEATURE( 即凡是usb 设备就应该支持的命令),表示清除一个设
备的某种特征,而USB_ENDPOINT_HALT 则对应usb 的端点特征,每个端点都有这么一个特
征,ENDPOINT_HALT, 她指出端点是否处于停止状态.CLEAR_FEATURE 命令用来清除该端点的停止状
态.说明了CLEAR_FEATURE 清除的是端点的特征.结合usb_stor_control_msg 形参实参来看,usb
spec(Table 9-3) 规定对于这个请求,wValue 要被设置为被Feature Selector, 赋值为
USB_ENDPOINT_HALT,即选择的Feature 是ENDPOINT_HALT, 而wIndex 要被设置为指定一个
Endpoint, 参考usb2.0 规范,在指定一个Endpoint 时wIndex 的格式,可知,低四位为端点号
(D3~D0),D7 为方向,(IN/OUT),其余各位为保留位.实际上赋值为endp, 正是包含了方向和端点号这两
个信息.wLength 要求被设置为0,data 设置为NULL,这些都没错.超时设了3s.酱紫,就可以清除这个
Endpoint 的ENDPOINT_HALT 这个flag.关于usb_stor_control_msg 我们当然就不用再讲了,忘记了
的回头去看吧,反正一样的天一样的脸,一样的函数就在你面前.唯一不同的只是传递的参数不同罢了,也许
这就是曾经沧海难为水吧,我们的人生也是如此,只能是一条不归路,走上去,就回不了头,谁也没有办法重走
一遍曾经的路.

需要特别注意一下,上次GETMaxLUN 调用usb_stor_control_msg 的时候,我们倒数第四个参数是设
了0,而这里我们传递了一个endp,这是因为不同的请求spec 里边规定好了的,虽然这两个命令控制的对象
不一样,但是作为控制传输,主机总是和控制端点在发生关系.并不因为这里是清楚bulk 端点的Halt
Feature 就要发送给bulk 端点,控制传输永远都只是发生在主机和控制端点之间.而真正要控制bulk 端点,
正是通过我们这里这个endp 这么一传递,设备自然就知道该干嘛了.

接下来,269 行,又是一个定义于include/linux/usb.h 中的宏,

1101 #define usb_settoggle(dev, ep, out, bit) ((dev)->toggle[out] =
((dev)->toggle[out] & ~(1 << (ep))) | ((bit) << (ep)))

dev 是struct usb_device 结构体指针,直到现在才知道,struct usb_device 结构体中有unsigned
int toggle[2] 这么一个数组,这个数组有两个元素,对应endpoint 的IN 和OUT.拿OUT 来说,每一个
endpoint 在这里占一位触发位,在usb 控制传输的数据传输时,每一个包的头部是交替的,有两种包
头,DATA0 和DATA1, 为了保证传输的正确性,一次用DATA0, 一次用DATA1, 一旦哪次没有交叉,host 就
知道出错了.这里所谓的usb_settoggle, 就是对指定的ep 所对应的那个toggle 位reset 成0,然后如果

论坛徽章:
0
89 [报告]
发表于 2008-06-06 14:22 |只看该作者
bit 不为0,则把bit 左移到ep 对应的那位再和toggle 或上,也就是说,把这个toggle 位reset 成'bit', 比如
bit 为1,那么就是reset 成1,如果bit 为0,那么就是reset 成0,大多数情况下reset 都是复位成0,但有
时也会不是0,这些都得看心情而定了.((dev)->toggle[out] & ~(1<<(ep))) 就是把1 左移ep 位,比如
ep 为3,那么就是得到了1000, 然后取反,得到0111,( 当然高位还有更多个1),然后(dev)->toggle[out]
和0111 相与,这就是使得toggle[out] 的第3 位清零而其她位都不变.然后咱们这里bit 传递进来的是0,
所以就不起什么作用,还是reset 成0.总之,269 行做的事情就是把指定的Endpoint 的和指定的pipe 对
应的那位toggle 位给清零.)

当然细心的人会看一下spec,spec 里面说了,对于使用data toggle 的endpoint, 不管其halt feature
是否被设置了,总之只要你调用Clear Feature, 那么其data toggle 总是会被初始化为Data0). 所以有人
就奇怪了,既然调用Clear Feature 就已经把data toggle 位初始化为0 了,那这里为什么还要再次作一次
set toggle 呢?

事实上是这样的,其实这个世界上有两个toggle bits, 不是两个toggle bit, 是两个toggle bit_s_, 单复
数别看错了,其实设备里边是有一个toggle bits, 而我们这里软件层次上,也定义了toggle bits, 这个
toggle bits 是给host 用的,设备里边的那个toggle bits 在clear feature 之后,没错,是被初始化成Data0
了,但是host这边他也想记录下这么一个序列,所以写代码的哥们儿就定义了这个一个数组,而这里调用set
toggle 的目的无非就是想让这个数组和设备中物理上的那个toggle bits 保持同步.

到这里这个函数也就结束了,返回的是这次控制传输的结果,不过我们注意到调用这个函数的上下文,并没
有人会care 这个返回值,也许这里再判断返回值的意义不大了吧,本来就是在处理出错的代码中.

至此, usb_stor_Bulk_max_lun 这个函数也终于要返回了.于是我们终于,终于再一次回到了
usb_stor_acquire_resources 函数中,我容易吗?别说我了,歌神张学友在看到Linux 内核代码如此复杂
也不得不感慨说,这代码是一张无边无际的网,轻易就将我困在网中央,我越陷越深越迷惘,路越走越远越漫
长,如何我才能锁住这个函数...

776 行,令us->max_lun 等于刚才usb_stor_Bulk_max_lun() 的返回值.接下来,我们将看到一行具
有划时代意义的代码.从此我们唱着东方红,走进新时代,这就是伟大的S-C-S-I.

横空出世的scsi

世界上最遥远的距离
,
不是生与死
,
而是我就站在你面前
,
你却不知道我爱你
.
世界上最遥远的距离
,
不是我就站在你面前
,
你却不知道我爱你
,
而是明明知道彼此相爱
,
却不能在一起
.
世界上最遥远的距离
,
不是明明知道彼此相爱
,
却不能在一起
,

论坛徽章:
0
90 [报告]
发表于 2008-06-06 14:22 |只看该作者
而是明明无法抵挡这股想念,

却还得故意装作丝毫没有把你放在心里.

世界上最遥远的距离,

不是明明无法抵挡这股想念,

却还得故意装作丝毫没有把你放在心里,

而是用自己冷默的心,

对爱你的人掘了一条无法跨越的沟渠.

曾经天真的以为代码看到这里,该出场的函数也都出场了,该出场的数据结构也都出场了,不会有什么新鲜
的冬冬了.曾经天真的以为我们即将知道整个驱动是如何工作的了.未曾想到,我们距离完全了解整个故事还
有一光年. 挡在我们面前的,是scsi. 的确,看一个U 盘驱动不仅仅是要了解usb 协议,还要懂scsi 协议,要
知道U 盘它不仅仅是usb 设备,它还是”盘”,它要存储数据,所以才叫它usb mass storage, 所以才叫它海
量存储,而U 盘,它所遵循的传输协议叫bulk-only 传输,它所遵循的指令集叫做SCSI transparent
command sets. 换句话说,U 盘究竟怎么通信?使用scsi 命令.你不懂scsi 协议行吗?

没办法,如果你对scsi协议完全不了解.那么对不起,先抽点空熟悉一下scsi 协议,熟悉一下scsi命令集吧.
不要说你没有时间,雷锋同志说的好,时间就像乳沟,只要肯挤,总是有的.去google 一把吧,去百度一把吧.
我们在这里等你.

如果你真的不去看,那好吧,我假设你了解一点吧,什么是scsi? 无非也是一类总线.不过我们通常大多数普
通人并不会接触scsi, 公司里边用得多,比如scsi 硬盘,校园里边大学生通常不用scsi 硬盘,用ide 硬盘.每种
硬盘都有它的市场.就像每个明星都有它的fans 一样.于是我们知道这个世界上有玉米,有凉粉,有盒饭.那
么常见的硬盘就是scsi 硬盘和ide 硬盘.scsi 硬盘属于scsi 设备中的一种,有设备就有总线,有总线就有协
议,所以我们知道了这个世界上有一种协议叫做scsi 协议,就好比我们usb 世界里有usb 协议一样.时下流
行的是SCSI-2 协议.Linux 内核代码中自然也按这个协议来为scsi 设备准备设备驱动程序.

关于scsi,drivers 目录下面当然也有一个子目录是属于它的,那就是drivers/scsi 目录.如果你有雅兴用
ls 命令看一下,你会发现这下面的文件那是相当的多.如果你真的很感兴趣,那么请从Kconfig 文件和
Makefile 文件开始看起.去深入了解一下Linux 整个scsi 子系统是怎么工作的.我就不奉陪了.不过正如我
们曾经介绍过的2.6 中伟大的设备模型实现了这么一件事情,不管你是pci 还是usb 还是scsi, 都给你定义
一条总线,然后总线上面两棵树,一棵是设备,一棵是驱动,对于设备这棵树,pci 有pci 的扫描方法,usb 有
usb 的扫描方法,scsi有scsi 的扫描方法,总之这个过程被称为总线枚举,枚举完了之后设备这棵树就建立好
了,同时drivers 这棵树也会一步一步建立.每类设备有它自己的比较方法,要是合适,就把一个设备和一个
驱动绑定起来,这样子,驱动程序提供的函数自然就会在需要的时候被调用,那么,谁来调用?

Ok,如果你是代码设计者,你会怎么处理?你打算如何为整个scsi 系统规划代码?不知道?真的不知道?那
么我真的羡慕你这么年轻就认识我了.不过,可惜,我也不知道.经过在复旦四年的大学教育,我已经被训练成
了一名合格的人渣.这几年里我们关注的只是璩美凤的被偷拍事件,只是赵忠祥的录音事件,只是李金斗的嫖
娼事件,只是阿丘的包二奶事件,却从未曾关注过自己应该学点什么,作为一名生在红旗下长在新中国的共产
主义接班人,惭愧啊!

算了,不知道就不知道吧,让我们来思考一下.就像usb 子系统那边一样,usb 那边有一部分核心代码,被称
为usb core,那么scsi 这边自然也应该有这么一部分代码吧,也叫scsi core, 这你没意见吧.usb 那边弄了
一个usb host 目录,然后各种设备也分了类,比如storage 设备,比如input 设备,比如serial 设备,比如
image 设备,那是因为usb 的世界里有两个角色,一个是host,一个是设备,那scsi 这边是不是也可以这样
您需要登录后才可以回帖 登录 | 注册

本版积分规则 发表回复

  

北京盛拓优讯信息技术有限公司. 版权所有 京ICP备16024965号-6 北京市公安局海淀分局网监中心备案编号:11010802020122 niuxiaotong@pcpop.com 17352615567
未成年举报专区
中国互联网协会会员  联系我们:huangweiwei@itpub.net
感谢所有关心和支持过ChinaUnix的朋友们 转载本站内容请注明原作者名及出处

清除 Cookies - ChinaUnix - Archiver - WAP - TOP