Linux usb mass-storage 阅读
usb core:实现核心的功能,为别的设备驱动程序提供服务,比如申请内存,比如实现一些所有设备都会需要的公共的函数,初始化整个usb系统,初始化root_hubusb host 控制器:控制所有的usb设备的通信,CPU不是直接和usb设备通信,而是和控制器通信,CPU要对设备做什么动作,它会告诉控制器,HC再去负责处理。
usb core:实现核心的功能,为别的设备驱动程序提供服务,比如申请内存,比如实现一些所有设备都会需要的公共的函数,初始化整个usb系统,初始化root_hub
usb host 控制器:控制所有的usb设备的通信,CPU不是直接和usb设备通信,而是和控制器通信,CPU要对设备做什么动作,它会告诉控制器,HC再去负责处理
usb device :一个普通的设备要正常工作,除了设备本身之外,还要一个控制器HC,和这个控制器连接在一起的叫root hub,控制器和hub绑定集成到一起,和控制器绑定一起的hub就是root_hub,
USB设备驱动中,不用再提device,因为每个设备驱动对应的是一种Interface,每个Interface属于一个class,class下面又分了subclass,然后subclass下面又按照各种设备通信协议继续细分;
比如mass-storage的class就是0x08;这个class下面又包含不同的subclass,不日subclass 02的CD-ROM设备,04为软盘驱动器,06为SCSI类设备;通信协议主要有CBI(controll/Bulk/Interrupt)协议和Bulk-Only协议
U盘的subclass属于US_SC_SCSI,通信协议使用Bulk-Only
设备描述符,配置描述符,接口描述符,端点描述符,这是每个设备都有的,0号端点是任何一个usb设备都必须提供的,而有些描述符可有可无,比如字符串描述符,hub的hub描述符,
URB:设备驱动要发送信息,所需要做的就是建立一个urb数据结构,并把这个数据结构提交给核心层
usb驱动程序通信——》urb赋值携带所需信息——》底层usb core——》usb host controller——》通信完成通知设备驱动程序
usb_alloc_urb:为urb申请内存,两个参数,一个是iso_packets用来等时传输时指定多少包,其他模式为0,另一个参数mem_flags,申请内存的方式,一般是GFP_KERNEL
总结:usb是一种总线,它需要通信,而我们手里只有设备,所以要用usb主机控制器HC来负责统一调度控制usb设备的通信,设备驱动程序只要为每一次请求准备一个urb结构体变量,把它填充好,然后调用usb core提供的函数,把这个urb给usb HC,HC就会把所有的urb统一规划操作,这期间usb设备驱动程序通常会进入睡眠,一旦HC把urb处理完了,它会调用函数唤醒usb设备驱动程序,然后usb设备驱动程序继续走
U盘设备和驱动
1、重点函数:
storage_probe开始,storage_disconnect结束
get_device_info、get_protocol、get_transport、get-pipes、usb_stor_Bulk_max_lun
2、lun:
lun是logical unit number,逻辑单元号,lun id的作用是扩充设备target id,一个lun就是一个device中的一个driver,比如一个读卡器有两个插槽,一个支持CF卡,一个支持SD卡,它就用两个lun来表示,U盘只有一个LUN
3、usb_stor_Bulk_max_lun:
usb_stor_control_thread——》usb_stor_acquire_resources
——》usb_stor_Bulk_max_lun——》usb_stor_control_msg ——》usb_stor_msg_common ——》usb_fill_control_urb ——》usb_stor_blocking_completion
控制传输比较简单,向usb core提交一个urb,这个urb包含了一个控制请求命令,要按照一定格式发送,usb规范规定一个控制请求格式为一个8字节的数据包,而struct usb_ctrlrequest正是专门为此准备的8字节的一个变量
wait_for_completion:使本进程进入睡眠。前面使用init_completion来初始化一个struct completion变量,是内核的一种同步机制,与之对应的一个函数是completion;先用init_completion初始化,然后使用wait_for_completion这样当前进程会进入睡眠,处于一种等待状态,另一个进程做其他事,完事之后会调用completion函数,这样刚才睡眠的这个进程就会被唤醒,这样来实现一种同步等待机制
如果一个urb处理超时了,它的timeout_handle被调用,然后调用异步的usb_unlink_urb函数去取消这个urb,还是会调用它的completion函数,设置urb->status告诉大家这个urb被cancel了
usb_stor_clear_halt:usb中,中断端点和Bulk端点,有Halt,stall,就是寄存器中的某一位,设为1,表示设置了halt特征,这个端点就不工作了,要想端点重新工作,就把这一位设为0,
对于bulk端点,每当设备在reset之后,需要清除halt这个feature端点才能正常工作
SCSI
scsi core:把scsi设备分为四类:sd.c,sr.c,st.c,sg.c
scsi host:HBA,host controller adapter,虚拟了一个scsi host,主要作用就是负责发送命令给设备
scsi的三座大山:sd_read_capacity、sd_read_write_protect_flag、sd_read_cache_type
还有从应用层来的ioctl,copy_frome_user、copy_to_user
scsi子系统使用scsi命令来通信,U盘支持的是scsi transparent command set,所有的设备至少要支持这4个命令:INQUIRY,REQUEST SENSE,SEND DIAGNOSTIC,TEST UNIT READY
对于磁盘,scsi协议中称它为 direct-access device,直接存取,
scsi_host_alloc:scsi子系统提供的函数,申请一个scsi host响应的数据结构,有个参数usb_stor_host_template, 后面和scsi相关联的代码都放在scsiglue.c文件
struct scsi_host_template:scsi host 数据结构体
1、scsc子系统被分为三层:上中下三层
上层:和操作系统打交道,sd.c,它和硬件无关,纯软件抽象出来的数据结构组建的模块
中层:这是核心层,scsi core,提供了scsi的核心数据结构和数据,scsi.c模块
底层:上层和中层和硬件无关,基本确定不用动,这底层是我们要写的驱动,核心层提供了很多接口给底层使用,比如scsi_host_alloc,通过它向核心层注册了一个scsi虚拟卡,
2、虚拟scci:
真正想要模拟一个scsi情景,需要三个函数:scsi_host_alloc、scsi_add_host、scso_scan_host
3、kernel_thread(usb_stor_control_thread, us, CLONE_VM)
,kthread_run(usb_stor_control_thread, us, “usb-storage”);
一个内核守护线程,可以理解类似fork,因为最后创建线程调用的是do_fork
执行之后会有两个进程,一个是父进程,就是当前函数usb_stor_acquire_resources ,一个是子进程,就是这里要创建的usb_stor_control_thread,us是子进程的实参,父进程使用wait_for_completion来休眠等待,子进程结束之后,会completion函数通知唤醒父进程,父进程继续执行,
usb_stor_control_thread函数,进去就是个for(; ;)死循环,在执行completion之后跳出去唤醒父进程
回到probe中,scsi_add_host函数被执行,之后起一个线程或者work执行usb_stor_scan,扫描这个scsi卡上接了什么设备,之后可以在/proc/scsi/scsi下看到U盘,/dev下面也能看到设备了,比如/dev/sda
wait_event_interruptible_timeout :等待机制,等待某个事件的发生,
4、U盘工作条件:
U盘工作需要四个模块:usbcore、scsi_mod、sd_mod、usb_storage
sd_mod就是scsi disk的驱动程序,
5、子进程usb_stor_control_thread:
死循环,除非模块卸载,不然一直运行
Bulk Only传输:三个阶段:命令传输阶段;数据传输阶段;状态传输阶段
命令传输阶段:
刚进入usb_stor_control_thread是睡眠的,需要被唤醒,卸载模块或者命令唤醒
scsi命令核心层,硬件上就是scsi命令从主机到设备,软件上是scsi核心层为每一个scsi host实现一个queuecommand命令,发送了scsi命令之后核心层那个就会调用这个queuecommand(scsi_host结构体成员 scsi_host_template的成员指针)
回到usb_stor_control_thread,它由queuecommand唤醒了,判断flag是否被设置,US_FLDX_TIMED_OUT :是否超时; DMA_BIDIRECTIONAL:表征数据阶段数据传输的方向; US_FL_SCM_MULT_TARG :表示设备支持多个target; US_FL_FIX_INQUIRY :unusal_devs.h中的设备不需要查询pid和vid,是直接定义好的,其他大多设备通过INQUIRY查询pid vid
一个scsi设备必要的三个成员:若干个channel,每个channel上有若干个targer,每个target有若干个lun,
fill_inquiry_response:INQUIRY:scsi的一个基本命令,第一次探测设备就用它来了解这是个什么设备,
usb_stor_set_xfer_buf:要玩 数据buffer的聚合scatter/gather,就需要一个 scatterlist 数组 ,散列表数组,一次传输一个scatterlist数组的数据;但它并没有实际和usb发生关系,只是从软件上fix一个硬件bug(一些设备不能响应scsi的查询命令INQUIRY),函数传输完成之后,设置srb->result = 0,之后自有scsi那边函数去检测
回到usb_stor_control_thread函数中,srb->scsi_done,剩下scsi层去处理
继续下去proto_handle这里,才是bulk传输的地方,处理scsi命令的函数指针
US_DEBUG(usb_stor_show_command(us, srb));
us->proto_handler(srb, us);
usb_mark_last_busy(us->pusb_dev);
US_DEBUG,执行usb_stor_show_command函数,把要执行的scsi命令打印出来
us->proto_handle:对于Upan,在get_protocol中被赋值usb_stor_transparent_scsi_command——》usb_stor_invoke_transport,
us->transport:在probe中被赋值usb_stor_Bulk_transport ——》usb_stor_bulk_transfer_buf ——》usb_fill_bulk_urb
bulk传输不再需要一个setup_packet,函数的参数,us->send_bulk_pipe,作为U盘,除了一个控制管道外,还有两个bulk管道,一个是IN,一个是OUT,管道就是一个unsigned int的数,还有us->recv_bulk_pipe 都是在probe中get_pipes函数获得的
interpret_urb_result :根据传进来的参数result做判断,从而采取对应行动
数据传输阶段:
CBW:command block wrapper,命令的封装包
CSW:command status wrapper,状态的封装包
U盘的bulk-only协议:首先由host给设备发送一个CBW,然后device接收到了CBW,然后解析按照CBW定义的那样去执行事情,之后给host返回一个CSW,
usb_stor_bulk_transfer_sg 函数真正执行bulk数据传输——》usb_stor_bulk_transfer_sglist函数专门为scatter-gather传输 ——》usb_stor_bulk_transfer_buf
采用sglist就是为了提高传输效率,sg的目的就是让一堆不连续的buffers一次DMA操作就全部传输
struct usb_sg_request结构体usb系统都使用,要使用scatter gather方式的话,usb core有接口直接调用:
一次bulk传输流程:
usb_sg_init——》usb_sg_wait——》usb_sg_cancel
usb-storage: Command WRITE_10 (10 bytes)
usb-storage: 2a 00 00 00 01 37 00 00 08 00
usb-storage: Bulk Command S 0x43425355 T 0x82 L 4096 F 0 Trg 0 LUN 0 CL 10
usb-storage: usb_stor_bulk_transfer_buf: xfer 31 bytes
usb-storage: Status code 0; transferred 31/31
usb-storage: – transfer complete
usb-storage: Bulk command transfer result=0
usb-storage: usb_stor_bulk_transfer_sglist: xfer 4096 bytes, 2 entries
usb-storage: Status code 0; transferred 4096/4096
usb-storage: – transfer complete
usb-storage: Bulk data transfer result 0x0
usb-storage: Attempting to get CSW…
usb-storage: usb_stor_bulk_transfer_buf: xfer 13 bytes
usb-storage: Status code 0; transferred 13/13
usb-storage: – transfer complete
usb-storage: Bulk status result = 0
usb-storage: Bulk Status S 0x53425355 T 0x82 R 3072 Stat 0x0170
usb-storage: – unexpectedly short transfer
usb-storage: scsi cmd done, result=0x10070000
SCSI error : <0 0 0 0> return code = 0x10070000
end_request: I/O error, dev sda, sector 311
fake_sense、need_auto_sense
如果设备想要发送比期望值更多的数据,设置fake_sense为1
scsi命令request sense,用来获取错误信息,返回一个sense data
厂商bug
US_FL_FIX_CAPACITY 这个 flag 的设置,意味着这种设备存在这么一个 bug,会执行fix_read_capacity——》usb_stor_access_xfer_buf函数,明明容量是N,偏要汇报时N+1
be32_to_cpu、cpu_to_le32:字节序
le:Little Endian,小端存储,顺序;地址低位存储的是低位,地址高位存储的是高位
be:Big Endian,大端存储,倒叙;地址低位存储的是高位,地址高位存储的是低位
假设变量c是这样被存储在内存地址0x0000开始的地方:
0x0000 0x12
0x0001 0x34
0x0002 0xab
0x0003 0xcd
le方式读出来:0xcdab3412
be方式读出来:0x1234abcd
usb总线上使用的是LE的字节序,所以在处理bcs、bcd的时候一直在调用lexx_to_cpu\cpu_to_lexx这种宏
scsi总线上使用的是BE的字节序,所以它和usb在request_buffer来回的数据要做大小端转换
templeta
两个结构体struct scsi_host_template、 struct usb_stor_host_template,还有queuecommand, :usb和scsi核心层沟通的关键接口
比如里面有个reset接口:device_reset——》bus_reset——》usb_stor_control_thread——》usb_stor_acquire_resources——》usb_stor_release_resources——》storage_disconnect
他们之后使用us->dev_semaphore信号量来实现进程间的同步,reset的时候不能执行control_thread,执行thread时也不能release和disconnect
更多推荐

所有评论(0)