U盘枚举(自己总结)_第1页
U盘枚举(自己总结)_第2页
U盘枚举(自己总结)_第3页
U盘枚举(自己总结)_第4页
U盘枚举(自己总结)_第5页
已阅读5页,还剩69页未读 继续免费阅读

下载本文档

版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领

文档简介

本文格式为Word版,下载可任意编辑——U盘枚举(自己总结)

插入U盘

直接拔出

安全拔出

A9枚举

LinuxUSBgadget设备驱动解析(2)驱动调试:刘洪涛,华清远见嵌入式学院金牌讲师。

这一节主要把在实现“linuxU盘功能〞过程中的一些调试过程记录下来,并加以解析。

一、背景知识

1、USBMassStorage类规范概述

USB组织在universalSerialBusMassStorageClassSpaceification1.1版本中定义了海量存储设备类(MassStorageClass)的规范,这个类规范包括四个

独立的子类规范,即:

1.USBMassStorageClassControl/Bulk/Interrupt(CBI)Transport2.USBMassStorageClassBulk-OnlyTransport3.USBMassStorageClassATACommandBlock

4.USBMassStorageClassUFICommandSpecification

前两个子规范定义了数据/命令/状态在USB上的传输方法。Bulk-Only传输规范仅仅使用Bulk端点传送数据/命令/状态,CBI传输规范则使用

Control/Bulk/Interrupt三种类型的端点进行数据/命令/状态传送。后两个子规范则定义了存储介质的操作命令。ATA命令规范用于硬盘,UFI命令规范是针对USB移动存储。MicrosoftWindows中提供对MassStorage协议的支持,因此USB移动设备只需要遵循MassStorage协议来组织数据和处理命令,即可实现与PC机交换数据。而Flash的存储单元组织形式采用FAT16文件系统,这样,就可以直接在Windows的浏览器中通过可移动磁盘来交换数据了,Windows负责对FAT16文件系统的管理,USB设备不需要干预FAT16文件系统操作的具体细节。

USB(Host)唯一通过描述符了解设备的有关信息,根据这些信息,建立起通信,在这些描述符中,规定了设备所使用的协议、端点状况等。因此,正确地提供描述符,是USB设备正常工作的先决条件。

Linux-2.6.26内核中在利用USBgadget驱动实现模拟U盘时主要涉及到

file_storage.c、s3c2410_udc.c等驱动文件(这些文件的具体结构,将在下一篇文章中描述)。此时我们想先从这些代码中找到USB描述描述符,从中确定使用的存储类规范,从而确定协议。确定通讯协议是我们调试的基础。存储类规范是由接口描述符决定的。接口描述符各项的定义义如下:

其中,bInteaceClass、bInterfaceSubClass、bInterfaceProtocol可以判断出设备是否是存储类,以及属于哪种存储子类和存储介质的操作命令。在file_storage.c文件中,

/*USBprotocolvalue=thetransportmethod*/

#defineUSB_PR_CBI0x00//Control/Bulk/Interrupt#defineUSB_PR_CB0x01//Control/Bulkw/ointerrupt#defineUSB_PR_BULK0x50//Bulk-only

/*USBsubclassvalue=theprotocolencapsulation*/

#defineUSB_SC_RBC0x01//ReducedBlockCommands(flash)#defineUSB_SC_80200x02//SFF-8020i,MMC-2,ATAPI(CD-ROM)

#defineUSB_SC_QIC0x03//QIC-157(tape)#defineUSB_SC_UFI0x04//UFI(floppy)

#defineUSB_SC_80700x05//SFF-8070i(removable)#defineUSB_SC_SCSI0x06//TransparentSCSI

默认的状况是:

mod_data={//Defaultvalues.transport_parm=\.protocol_parm=\??

默认的赋值如下:

bInterfaceClass=08表示:存储类

bInterfaceSubClass=0x06表示:透明的SCSI指令bInterfaceProtocol=0x50表示:bulk-only传输

2、Bulk-Only传输协议

下面看看Bulk-Only传输协议:(详细的规范请阅读《UniversalSerialBusMassStorageClassBulk-OnlyTransport》)

设备插入到USB后,USB即对设备进行探寻,并要求设备提供相应的描述符。在USBHost得到上述描述符后,即完成了设备的配置,识别出为Bulk-Only的MassStorage设备,然后即进入Bulk-Only传输方式。在此方式下,USB与设备间的所有数据均通过Bulk-In和Bulk-Out来进行传输,不再通过控制端点传输任何数据。

在这种传输方式下,有三种类型的数据在USB和设备之间传送,CBW、CSW和普通数据。CBW(CommandBlockWrapper,即命令块包)是从USBHost发送到设备的命令,命令格式遵从接口中的bInterfaceSubClass所指定的命令块,这里为SCSI传输命令集。USB设备需要将SCSI命令从CBW中提取出来,执行相应的命令,完成以后,向Host发出反映当前命令执行状态的CSW(CommandStatusWrapper),Host根据CSW来决定是否继续发送下一个CBW或是数据。Host要求USB设备执行的命令可能为发送数据,则此时需要将特定数据传送出去,完毕后发出CSW,以使Host进行下一步的操作。USB设备所执行的操作可用下图描述:

下面是利用bushound工具在出现问题时采集到的数据。

DevPhaseDataInfoTimeCmd.Phase.Ofs

26CTL80060001-000012

00GETDESCRIPTR0us1.1.0

26DI12011001-00000010-2505a5a4-12030102%4.8ms1.2.003

01..1.2.1626CTL80060002-000009

00GETDESCRIPTR14us2.1.0

26DI09022000-010104c0-

01..3.9ms2.2.0

26CTL80060002-000020

00GETDESCRIPTR16us3.1.0

26DI09022000-010104c0-01090400-00020806..4.9ms3.2.0

50050705-81024000-00070502-02400000P@@..3.2.1626CTL80060003-000002

00GETDESCRIPTR60us4.1.0

26DI09022000-010104c0-

01..3.9ms2.2.0

26DI04

03..3.9ms3.1.026CTL80060003-000004

00GETDESCRIPTR15us5.1.026DI040309

043.9ms6.1.0

26CTL80060303-090402

00GET

DESCRIPTR10us1.2.1626DI1a

034.0ms6.2.026CTL80060303-09041a

00GETDESCRIPTR18us7.1.0

26DI1a033300-37003200-30003400-31003700....9ms7.2.035003600-37003700-35

00.626CTL00090100-000000

00SETCONFIG16us8.1.026CTL010b0000-000000

00SETINTERFACE60ms9.1.026CTLa1fe0000-000001

00CLASS62ms10.1.0

26DI00.3.9ms10.2.0

26DO55534243-086080000612USBC.`..$985us11.1.0

00000024-00000000-00000000-000000...$11.1.16

26DI00800202-1f000000-4c696e75-78202020Linux1.0ms12.1.0

46696c65-2d53746f-72204761-64676574File-StorGadget12.1.16303331

32031212.1.32

26CTL80060002-000020

00GETDESCRIPTR893ms13.1.0

26DI09022000-010104c0-01090400-00020806..4.1ms13.2.0

50050705-81024000-00070502-02400000P@@..13.2.1626CTL80060002-000020

00GETDESCRIPTR2.7sc14.1.0

26DI09022000-010104c0-01090400-000208

06..4.4ms14.2.0

50050705-81024000-00070502-02400000P@@..14.2.1626USTS050000

c0noresponse2.8sc15.1.0

注意上面红色部分的代码,DO发出了55534243开始的CBW命令块,命令码是12,即Inquiry命令。要求目标返回Inquiry命令要求的数据,长度是0x24。接下来设备端通过DI返回了设备信息。依照规范,在返回完了数据后,设备端还应当通过DI向系统返回CSW的值。但实际的捕获内容并没有。所以导致不能正确出现盘符。

在file_storage.c中,发送数据时都会调用到start_transfer()函数。在此函数中参与printk调试语句,观测现象。发现只要参与的调试语句,windows端就能够正常设别设备了。于是,可以猜测是由于需要在连续两次发送之间加上一些延时。在函数中参与udelay(800)后,windows系统可以正常发现设备了。具体的代码架构,将在下一遍文章中解析。下面是程序正常后,用bushound捕获到的数据。

红色部分,可以看出设备正确的依照规范在发送完数据后,返回CSW信息。

四、总结做好USBgadget驱动、或者USBhost驱动调试需要:·把握一定的知识基础

包括:USB协议、具体的类设备规范、USB驱动程序架构、USB设备端控制器操作等。

·合理利用调试工具。

包括:USBview、bushound、及一些硬件USB信号分析仪。

一、追踪USB大容量设备的实现流程

1、从main.c开始

(1)main函数的执行流程

Set_System();//设置时钟、端口等。

Set_USBClock();//设置usb的时钟

USB_Interrupts_Config();//设置中断

Led_Config();//设置所使用的到的灯。

MSD_Init();//SD卡初始化

Get_Medium_Characteristics();//获取SD块总数、每块字节数。

USB_Init();//USB_init.c提供的初始化函数。从这里开始USB设备被主机检测到。

while(1)

{//USB的工作都是在中断中完成的,主执行流程什么也没做。

}

(2)与鼠标例程不同的地方

在中断配置中,使能了USB高优先级中断。

NVIC_InitStructure.NVIC_IRQChannel=USB_HP_CAN_TX_IRQChannel;

NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=0;

NVIC_InitStructure.NVIC_IRQChannelSubPriority=0;

NVIC_InitStructure.NVIC_IRQChannelCmd=ENABLE;

NVIC_Init(

用到了几个灯指示,这个我的开发板上用不到,就不详细看了。

MSD_Init(),是对SD卡进行初始化。该函数在msd.c中,我看了一下它的SD卡实现的代码,比我的SD函数代码齐整多了。以后有时间要把我的USB驱动好好的整理一下。不过现在就先不管了。

接下来这个函数获取SD卡的容量,这样的函数我在SD卡驱动中也实现了,改变一下调用方式就行了。

USB_Init()函数在usb_init.c库函数中,但它最终会调用user_prop.c宏的用户初始化例程。下面就追踪进去看一看。

(3)大容量存储设备的初始化

voidMASS_init()

{

pInformation->Current_Configuration=0;

PowerOn();连接电缆主机很快发总线复位。

_SetISTR(0);

wInterrupt_Mask=IMR_MSK;

_SetCNTR(wInterrupt_Mask);开启复位和传输中断。

pInformation->Current_Feature=MASS_ConfigDescriptor[7];

while(pInformation->Current_Configuration==0)

{

NOP_Process();

}

bDeviceState=CONFIGURED;//这句执行完成后,设备处于已配置状态。我先在这里加一句调试语句。

#ifusb_debug

Uart_PutString(“设备已配置〞);

#endif

}

2、进入复位中断

(1)先列出中断处理代码

发生总线复位中断以后,处理是在usb_prop.c的Mass_Reset()函数中完成的。

voidMASS_Reset()

{

Device_Info.Current_Configuration=0;

SetBTABLE(BTABLE_ADDRESS);

SetEPType(ENDP0,EP_CONTROL);//端点0控制端点

SetEPTxStatus(ENDP0,EP_TX_NAK);//不响应IN

SetEPRxAddr(ENDP0,ENDP0_RXADDR);//设置接收缓冲区(OUT)

SetEPRxCount(ENDP0,Device_Property.MaxPacketSize);接收长度。

SetEPTxAddr(ENDP0,ENDP0_TXADDR);//发送缓冲区(IN)

Clear_Status_Out(ENDP0);

SetEPRxValid(ENDP0);//使能端点0的接收。

SetEPType(ENDP1,EP_BULK);//端点1批量模式

SetEPTxAddr(ENDP1,ENDP1_TXADDR);//设置发送缓冲区(IN)

SetEPTxStatus(ENDP1,EP_TX_NAK);发送不响应。

SetEPRxStatus(ENDP1,EP_RX_DIS);//接收无效。对OUT无效

SetEPType(ENDP2,EP_BULK);//端点2批量模式

SetEPRxAddr(ENDP2,ENDP2_RXADDR);//设置接收缓冲区OUT

SetEPRxCount(ENDP2,Device_Property.MaxPacketSize);

SetEPRxStatus(ENDP2,EP_RX_VALID);

SetEPTxStatus(ENDP2,EP_TX_DIS);//发送无效,对IN无效

SetDeviceAddress(0);//使能USB接口模块。

CBW.dSignature=BOT_CBW_SIGNATURE;

Bot_State=BOT_IDLE;//命令状态机初始化为空闲状态

}

在这里没有我没有看到将批量端点设置为双缓冲模式的迹象,莫非这个例程没有用它?

-3、进入枚举过程

由于在鼠标例程中已经详细分析过枚举过程,这里主要是大容量设备枚举过程中不同的地方做一下分析。

(1)获取设备描述符、设置地址。

(2)获取配置描述符

(3)获取配置描述符集合,这里主要讲接口描述符分析一下。

0x09,/*bLength:InterfaceDescriptorsize*/

0x04,/*bDescriptorType:*/

0x00,/*bInterfaceNumber:NumberofInterface*/

0x00,/*bAlternateSetting:Alternatesetting*/

0x02,/*bNumEndpoints*/使用两个端点

0x08,/*bInterfaceClass:MASSSTORAGEClass,大容量存储类*/

0x06,/*bInterfaceSubClass:SCSItransparent,SCSI传输*/

0x50,/*nInterfaceProtocol,仅批量传输*/

4,/*iInterface:*/

(4)获取字符串描述符

(5)类请求实现:

类获取规律盘:

一般返回0

类请求复位:

ClearDTOG_TX(ENDP1);

ClearDTOG_RX(ENDP2);

CBW.dSignature=BOT_CBW_SIGNATURE;

Bot_State=BOT_IDLE;

(6)设置配置

在用户设置的回调函数中,又调用

voidMass_Storage_SetConfiguration(void)

{

if(pInformation->Current_Configuration)

{

ClearDTOG_TX(ENDP1);

ClearDTOG_RX(ENDP2);

Bot_State=BOT_IDLE;}

}

这个工作前面已经做过了。

4、主机发命令INQUIRY

(1)首先进入批量输出中断

该中断的回调函数调用Mass_Storage_Out()进行处理。

(2)追踪进入Mass_Storage_Out()

voidMass_Storage_Out(void)

{

u8CMD;

CMD=CBW.CB[0];//

Data_Len=GetEPRxCount(ENDP2);

PMAToUserBufferCopy(Bulk_Data_Buff,ENDP2_RXADDR,Data_Len);

switch(Bot_State)

{

caseBOT_IDLE:

CBW_Decode();//第一次收到命令确定调用这个解码函数。

break;//它的作用应当是填充CBW命令块封包结构

caseBOT_DATA_OUT:

if(CMD==SCSI_WRITE10)

{

SCSI_Write10_Cmd();

break;

}

}

(3)追踪进入CBW_Decode()

这个函数的代码较长,我就不列举在这里了,我就分析一下本次的主要工作。

首先将用户缓冲区的数据复制到命令封包结构里面。

然后准备好状态封包结构:

CSW.dTag=CBW.dTag;//这个标志由主机生成,可以用于检查设备是否正确收到该命令。

CSW.dDataResidue=CBW.dDataLength;

然后主要是根据命令操作码,调用相应的SCSI命令处理函数。

switch(CBW.CB[0])

{

caseSCSI_REQUEST_SENSE:

SCSI_RequestSense_Cmd();

break;

caseSCSI_INQUIRY:

SCSI_Inquiry_Cmd();

break;

我这里就列出了两项,实际的命令是好多的。本次主要是查询处理。

(4)追踪进入SCSI_Inquiry_Cmd()

以上函数是在usb_bot.c里面,现在跳转到usb_scsi.c里面。

这个函数的主要工作是调用Transfer_Data_Request(Inquiry_Data,Inquiry_Data_Length)来完成。

这个Inquiry_Data=Standard_Inquiry_Data,后面这个Standard_Inquiry_Data是scsi_data.c里面定义的一个数据结构,专门用于inquiry命令的返回。

u8Standard_Inquiry_Data[]=

{

0x00,/*DirectAccessDevice*/

0x80,/*RMB=1:RemovableMedium*/

0x02,/*Version:Noconformanceclaimtostandard*/

0x02,//这里圈圈的书上说应当为0x01

36-4,//这里圈圈的书上说应当为31

0x00,0x00,0x00,/*SCCS=1:StorageControllerComponent*/

'S','T','M','','','','','',//厂商信息

'S','T','R','','','F','l','a','s','h','','D','i','s','k','',//产品信息

'1','.','0',''

//版本信息。

};

(5)追踪进入Transfer_Data_Request()

voidTransfer_Data_Request(u8*Data_Pointer,u16Data_Len)

{

UserToPMABufferCopy(Data_Pointer,ENDP1_TXADDR,Data_Len);

SetEPTxCount(ENDP1,Data_Len);

SetEPTxStatus(ENDP1,EP_TX_VALID);

Bot_State=BOT_DATA_IN_LAST;

CSW.dDataResidue-=Data_Len;

CSW.bStatus=CSW_CMD_PASSED;//设置好命令状态封包信息。

}

(6)接下来,主机遇发IN,取走查询信息。并进入批量输入中断。

在该中断中,将调用函数Mass_Storage_In(void)

voidMass_Storage_In(void)

{

switch(Bot_State)

{

caseBOT_CSW_Send:

caseBOT_ERROR:

Bot_State=BOT_IDLE;

SetEPRxStatus(ENDP2,EP_RX_VALID);/*enabletheEndpointtorecivethenextcmd*/

break;

caseBOT_DATA_IN_LAST:

Set_CSW(CSW_CMD_PASSED,SEND_CSW_ENABLE);

SetEPRxStatus(ENDP2,EP_RX_VALID);

break;

default:

break;

}

}

然后设备的命令状态机状态变为Set_CSW()这个函数所设置的状态,一般为BOT_CSW_Send。

(7)追踪进入Set_CSW()

在该函数中:

voidSet_CSW(u8CSW_Status,u8Send_Permission)

{

CSW.dSignature=BOT_CSW_SIGNATURE;

CSW.bStatus=CSW_Status;//命令状态封包数据已经准备好

UserToPMABufferCopy((

SetEPTxCount(ENDP1,CSW_DATA_LENGTH);

Bot_State=BOT_ERROR;

if(Send_Permission)

{

Bot_State=BOT_CSW_Send;

SetEPTxStatus(ENDP1,EP_TX_VALID);

}

}

然后,主机再次发IN令牌包,取走命令状态封包。

caseBOT_ERROR:

Bot_State=BOT_IDLE;

SetEPRxStatus(ENDP2,EP_RX_VALID);

端点2的接收又被使能,重新进入接收命令状态

二、追踪USB大容量设备的实现流程

5、主机发命令READFORMATCATPACITIES

再次分析一次命令执行的流程

(1)首先在批量输出端点2产生RX中断

在中断中调用Mass_Storage_Out()。

在处理过程中先将接收缓冲区的数据、长度保存。

由于此时,命令处理状态机处于BOT_IDLE状态,所以调用命令解码函数CBW_Decode()。

(2)解码函数所做的工作

把接收到的数据先赋值给命令封包结构CBW。同时开始准备填充命令状态封包结构CSW。

switch(CBW.CB[0]),根据命令操作码进行命令处理散转。

(3)操作码0x23,进入相应处理函数SCSI_ReadFormatCapacity_Cmd()

这个命令处理主要是填充用户返回的容量数据结构体,然后调用另外一个函数Transfer_Data_Request()来完成数据的传输。

这个函数接收发送数据缓冲区的起始地址、长度,首先把数据复制到数据输出批量端点1:

UserToPMABufferCopy(Data_Pointer,ENDP1_TXADDR,Data_Len);

SetEPTxCount(ENDP1,Data_Len);

SetEPTxStatus(ENDP1,EP_TX_VALID);

Bot_State=BOT_DATA_IN_LAST;//设置命令处理的新状态

CSW.dDataResidue-=Data_Len;

CSW.bStatus=CSW_CMD_PASSED;//填充命令状态封包。

接下来主机遇连发两个“IN〞,第一次将容量数据结构体返回。

然后在端点1输入中断中,又将命令状态封包结构复制到批量端点1输出缓冲区。主机的其次个“IN“将取走这个数据。

在其次次输入中断处理程序中,命令状态重新回到“BOT_IDLE〞,于是又可以接收新的命令。

6、读容量命令

这个跟上个命令返回的数据差不多,具体什么区别,等到移植调试的时候再看。

7、READ(10)命令

这是一个十分重要的命令,我们获取U盘的文件主要就靠它了。

(1)前面的过程忽略,直接进入读命令解码SCSI_Read10_Cmd()

处理中,主要存在两种状况:

在BOT_IDLE时:

if((CBW.bmFlags

Read_Memory();

}

在BOT_DATA_IN时:

直接Read_Memory();

(2)进入Read_Memory()处理函数。

voidRead_Memory(void)

{

if(!Block_Read_count)

{//读入一个扇区512字节,但是一次只能发送64个字节。

MSD_ReadBlock(Data_Buffer,Memory_Offset,512);

UserToPMABufferCopy(Data_Buffer,ENDP1_TXADDR,BULK_MAX_PACKET_SIZE);

Block_Read_count=512-BULK_MAX_PACKET_SIZE;

Block_offset=BULK_MAX_PACKET_SIZE;

}

else

{

UserToPMABufferCopy(Data_Buffer+Block_offset,ENDP1_TXADDR,BULK_MAX_PACKET_SIZE);

Block_Read_count-=BULK_MAX_PACKET_SIZE;

Block_offset+=BULK_MAX_PACKET_SIZE;

}

SetEPTxCount(ENDP1,BULK_MAX_PACKET_SIZE);

SetEPTxStatus(ENDP1,EP_TX_VALID);

Memory_Offset+=BULK_MAX_PACKET_SIZE;

Transfer_Length-=BULK_MAX_PACKET_SIZE;//剩下的需要传输的字节数。

CSW.dDataResidue-=BULK_MAX_PACKET_SIZE;

Led_RW_ON();

}

这里不明白的是主机是一次把512字节读完,还是每次发一个命令读取64字节。我觉得应当是发一次读命令,8次“IN〞读取一个扇区,再发一个“IN〞读取命令状态封包。

8、写命令WRITE(10)

这个命令的处理过程跟读命令的处理差不多,只是最终它会调用Write_Memory()进行处理。

i=0;

for(;Counter这三个函数我以前实际上都实现了,但是用到这个例程中,还需要改变一些参数的对应问题。

2、为了更明了的了解U盘的整个工作流程,在关键的地方加一些调试函数,向串口输出信息。

二、开始移植

1、准备源文件

在usb目录下新建udisk目录,在其下建src和inc两个子目录。

将usb_istr.c、usb_pwr.c、usb_desc.c、usb_prop.c、hw_config.c,usb_endp.c、usb_bot.c、usb_scsi.c、scsi_data.c、memory.c、msd.c,main.c等共12个文件复制如src目录。

将相应的头文件复制入inc目录。

在工程里新建文件组,把所有c源文件参与工程。

2、添加命令udisk

当在串口输入udisk命令时,整个开发板将成为一个读卡器。

3、修改各个源文件的包含关系、单独编译

(1)主程序main.c改变成“udisk〞命令处理程序。

voidUartCmdUsbMouse(u8argc,void**argv){

Uart_PutString(\进入U盘实现过程!\\r\\n\

USB_Connect_Init();

USB_Interrupts_Config();

Set_USBClock();

Get_Medium_Characteristics();

USB_Init();

while(1)

}

在这个过程中,去掉了Set_System()函数、led等配置函数等,这些函数都在hw_config.c文件中,该文件编译通过以后,再修改hw_config.c的函数实现。

(2)修改hw_config.c

其它函数的修改在鼠标例程中已经分析过了,这里主要是以下这个函数:

voidGet_Medium_Characteristics(void)

{

u8res;

CardInfoMsdInfo;

res=SD_GetCardInfo(

if(res==0){

Mass_Block_Count=MsdInfo.BlockNumber;

Mass_Block_Size=MsdInfo.BlockLength;

Mass_Memory_Size=MsdInfo.Capacity;

}

}

获取SD卡容量的函数重新修改,也不需要头文件msd.h了。

(3)有9个文件只要修改头文件包含关系就行了

(4)修改文件msd.c

由于我在sduser.c文件中已经实现了大部分的SD操作函数,所以这里实际上读卡、写卡函数调用以前编写的函数就行了。

当然,在入口参数有不一致的地方必需做调整。

u8MSD_WriteBlock(u8*pBuffer,u32WriteAddr,u16NumByteToWrite)

{

u8res;

rea=SD_WriteBlock(WriteAddr>>9,pBuffer);

if(res==0)returnres;

return0xFF;

}

读扇区的修改跟这个类似。

三、下载、测试和修改

1、整个工程编译,生成Hex文件,下载到开发板。

2、输入命令udisk

PC识别了该设备,但是磁盘为空。

这是串口发回来的消息。

Sh>udisk

进入U盘实现过程!

setup中断8006000100004000获取设备描述符

设备准备发送12字节:120100020000004083042057000101020301

IN令牌04中断

OUT状态中断

setup中断0005020000000000设置地址

IN状态中断

setup中断8006000100001200获取设备描述符

设备准备发送12字节:120100020000004083042057000101020301

IN令牌04中断

OUT状态中断

setup中断8006000200000900获取配置描述符

设备准备发送09字节:090220000101008032

IN令牌04中断

OUT状态中断

setup中断800600030000FF00获取字符串描述符

设备准备发送04字节:04030904

IN令牌04中断

OUT状态中断

setup中断800600020000FF00获取配置描述符

设备准备发送20字节:0902200001010080320904000002080650040705810240000007050202400000

IN令牌04中断

OUT状态中断

setup中断8006000600000A00这是与高速有关的,这里不支持。

一、USB设备驱动入门

1、学习目的

(1)了解windows系统硬件驱动的一些基本知识。从应用程序给出要求、驱动程序假使处理、底层硬件工作的大致状况有个基本了解。

(2)使用自定义的应用程序、自定义的驱动程序来控制与设备的交互。

2、学习工具

我手边有去年买的一本《windows驱动开发技术详解》,这本书写的挺好的。不过去年我买的时候,看着像天书。到现在,反复看了两遍以后,心里对windows底层的工作原理已经有了那么一点点概念了。

像PCI、USB类型的设备,都是属于WDM驱动模型。特点是即插即用、分层、面向对象、数据驱动(IRP)。

3、WDM驱动特征分析

(1)即插即用

譬如对一个USB设备来说,就能够做到即插即用。

USB主机控制器是PCI总线上的一个设备,在windows启动的时候就已经安装好的驱动。系统可以驱动USBHC,其下游端口有设备接入的时候,HCD也有相应的程序进行处理。

譬如现在插入一个u盘,在主机的根集线器端口。它向HC报告了这个事件后,有一个叫做即插即用管理器的组件,根据USB总线驱动对象创立一个PDO设备对象。

然后总线驱动获取设备的VID、PID、设备类型等信息,这是通过设备枚举取得的。根据这些信息,windows系统查找相应的功能驱动,譬如u盘就是大容量设备类驱动。然后这个驱动再创立一个FDO设备对象。这样这个设备就可以供用户使用了。

(2)分层

分层是现代操作系统的特征,也是我们编写软件时提高可读性、灵活性、可重用性的方法。

分层使得设备对用户提供了统一的操作接口。

譬如用户要打卡设备,都是调用CreateFile()函数、读写用ReadFile()和WriteFile函数、控制用DeviceIOCtrl()函数。

这些函数都是win32子系统实现的,windows系统将这些函数调用转化为系统调用,进入windows内核。

内核服务函数调用windows的执行组件,一般设备操作是用过IO管理器完成的。IO管理器根据用户传递下来的设备名称,找到相应的驱动对象和设备对象。

IO管理器把用户的要求组合成一个用户输入输出请求包(IRP),然后利用IRP调用相应的驱动程序。这里时间上使用的回调函数的概念,根据具体请求(读、写或其它要求),调用驱动程序的相应函数进行处理。

驱动一般也是多层。上层的驱动程序完成一些工作后,将IRP传递到下一层。譬如USB设备的操作,经过功能层次的处理,创立URB请求包附加到IRP中,最终由总线驱动和HCD驱动转换为USB总线上的数据包。

二、USB设备驱动开发

1、开发过程简介

这次只是了解一下windows驱动开发的过程,并没有详细学习windows驱动开发的计划,我连VC都已经不熟悉了。

本次操作是根据《圈圈教你玩USB》第九章的源程序,做一些稍微的修改,使它适合智林开发板的驱动。

主要实现按键信息的读取、led灯的控制两个内容,跟上次实现的自定义HID设备功能一样。只是现在采用自定义设备而不是HID设备类型、使用自定义的文件读写而不是从HID驱动获取的报告描述符中获取数据了。

2、驱动框架的建立

(1)编译vdw_wdm.lib

(2)根据向导建立一个USBWDM驱动程序。

工程名“usbdevice〞、“WDM〞类型驱动、“功能驱动〞、“USB驱动〞

“使用端点1的中断输入、中断输出〞、“使用缓冲IO〞、“VID=8888,PID=1111〞。

(3)去掉link选项里的ntstrsafe.lib,整个工程编译成功。

3、驱动程序的修改

主要是在读写函数里修改:

//在这里构建读数据的URB

PURBpUrb=EP1_WRITE.BuildInterruptTransfer(

pBuffer,writeSize,TRUE,NULL,NULL,FALSE);

if(pUrb==NULL){

status=STATUS_INSUFFICIENT_RESOURCES;

}

else{

status=EP1_WRITE.SubmitUrb(pUrb,NULL,NULL,0);

bytesSent=pUrb->UrbInterruptTransfer.TransferBufferLengt

h;

deletepUrb;

}

依照圈圈书里的描述进行修改,实际修改的地方很少。

4、设备固件的修改

将设备描述符里的设备类改为“0xFF〞。

将设备的VID、PID改为“8888〞和“1111〞。

将HID类描述符删除。

将报告描述符删除。

编译、下载,windows弹出安装驱动的界面。安装好以后,在设备管理器可

以看到如下设备。

MassStorage设备,即大容量存储设备,最典型的莫过于U盘了,而U盘一般以BulkOnly传输方式实现。

四、USBMassStorage设备的描述符及枚举过程

描述符就是对应标准请求的那些描述符,与HID设备不同,MassStorage设备没有自己的类描述符。描述符在USBMassStorageClassBulk-OnlyTransport文档中有详细的一对一的描述。所以此处不再赘述,仅举一例:

(设备描述符略,通用定义,与设备类无关)(配置描述符略,通用定义,与设备类无关)

_Interface_Descriptor:

.dw0x09//bLength:0x09byte

.dw0x04//bDescriptorType:INTERFACE.dw0x00//bInterfaceNumber:interface0

.dw0x00//bAlternateSetting:alternatesetting0

.dw0x02//bNumEndpoints:3endpoints(EP0,EP1,EP2).dw0x08//bInterfaceClass:MassStorageDevicesClass.dw0x06//bInterfaceSubClass:.dw0x50//bInterfaceProtocol

.dw0x02//iInterface:indexofstring_Interface_Descriptor_End:

_Endpoint1:

.dw0x07//bLength:0x07byte

.dw0x05//bDescriptorType:ENDPOINT.dw0x81//bEndpointAddress:INendpoint1.dw0x02//bmAttributes:Bulk

.dw0x40,0x00//wMaxPacketSize:64byte.dw0x00//bInterval:ignored

_Endpoint2:

//Endpoint2(0x07byte)

.dw0x07//bLength:0x07byte

.dw0x05//bDescriptorType:ENDPOINT

.dw0x02//bEndpointAddress:OUTendpoint2.dw0x02//bmAttributes:Bulk

.dw0x40,0x00//wMaxPacketSize:64byte.dw0x00//bInterval:ignored

关于请求:

第一,主机首先会发出一系列标准请求。其次,在标准请求完成之后,会发出两个类请求:Bulk-OnlyMassStorageReset请求和GetMaxLUN请求。这两个请求的格式可以在USBMassStorageClassBulk-OnlyTransport文档中查询。

Bulk-OnlyMassStorageReset没有数据阶段,只在状态阶段告诉主机设备的Reset过程完成与否。假使在状态阶段返回ACK,那么主机就认为设备已经Reset完毕并准备好接收CBW了。GetMaxLUN要求设备返回一个字节的数据给主机,以说明此USB设备有多少个规律设备。返回的这个数据就是最大的设备规律号(LogicUnitNumber),范围是0到15。例如,假使返回2,那么代表有0、1、2三个规律设备。

2、USBMassStorage设备的Bulk数据交换流程

通过bulk端点进行的数据传输,都遵循这样一个过程,即三个阶段:CBW->DATA->CSW

CBW是一个数据块,携带主机发给设备的SCSI命令。接收了CBW后,设备就可以从中知道在接下来的DATA阶段中该干什么。DATA阶段有三种状况:无数据需要传输,IN传输(设备到主机)或OUT传输(主机到设备)。CSW阶段反馈这次传输的结果给主机。

其中值得注意的是:

-在设备枚举完成之后,主机发出的第一个bulkOUT事务就是请求向设备发出CBW。所以设备可以通过这第一次的bulkOUT事务来判定第一次bulk数据传输的开始。此后的bulk数据传输就依照上述的三个阶段反复执行。也就是说,第一次传输CBW后,假使有数据要传输,那么就会经历DATA阶段,然后进入CSW阶段;假使没有数据要传输,则直接进入CSW阶段,就此一次传输终止。接下来,假使又有传输,那么再发出CBW。因此,设备可以认为CSW完成后收到的下一个bulkOUT事务就是主机请求传输新的CBW。

-CBW[12](CBW数据块的第13个字节)指明白传输方向,CBW[8-11]指明白传输的数据长度。实际上,CBW中的SCSI命令就暗含了数据要传输的方向和数据长度,由于SCSI规范中已明确规定这个命令所对应的数据格式。(在完整的应用中,要将CBW中的传输方向、数据长度与SCSI命令所说明的传输方向和数据长度做比较,不对应就要进行错误处理(MassStorageBulk-Only文档中有相关描述),不过正常状况下二者是匹配的,试验的时候可以暂时不理)。

-CSW[12](CSW数据块的第13个字节)这个字节很重要,它为0则表示此次传输成功,非0就是不成功。在DATA阶段的数据传完(或者无需数据传输)之后,主机遇发出IN事务请求设备返回CSW。假使CSW传送

温馨提示

  • 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
  • 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
  • 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
  • 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
  • 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
  • 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
  • 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。

评论

0/150

提交评论