首页 > 代码库 > DFU工作过程中USB机制
DFU工作过程中USB机制
在一级bootloader运行进入USB启动方式之后,设备进行枚举。枚举过程中会通过PC端发送命令对连接的USB设备进行枚举。当枚举成功之后,在PC端能够看到设备的盘符。
当设备能够被PC正确识别之后,接下来就可以通过烧写工具完成设备的扫描如果成功找到设备,则可以通过USB传输数据到SRAM中,这时候的数据主要包括2k infor文件。一级bootloader在成功的解析2kinfor 之后,PC端会将DFU文件传输到初始化好的DRAM中。并且PC指针跳转到DFU地址处执行,执行过程中会跳转到USB初始化程序过程中。
USB的初始化过程主要分为两个部分,一个部分是初始化UDC设备,第二部分是等待PC端发送的命令并响应。这个过程的描述如下:
在USB的初始化过程中会调用VIM_CLKRST_UotgExitRst();函数。
此函数中,
VOID VIM_CLKRST_UotgExitRst(VOID)
{
__CLKRST_UotgSWRst();//为了防止global rst之后,来不了中断导致挂死
__CLKRST_UotgExitRst();
//waitfor uotg swrstdone irq
while(!__CLKRST_CheckUotgSwrst())//poolling
{
//donothing
};
__CLKRST_ClearUotgSwrstIrq();
}
会首先设置0x604寄存器的bit25,UOTG_SW_RESET。接着设置0x610寄存器的SW_CFG_UOTG_DONE,bit12。uotg software configuration done flag, high active
然后死等UOTG_RSTDONE_IRQ中断的到来。最后写清中断。
在初始化函数的最后,会调用
UDC_DFU_Init(VIM_USB_DataProcess,VIM_USB_ArmSpeedBinProcess);函数,函数中会将两个函数指针进行赋值给全局变量g_Usb_Process_Call以及g_Usb_SpeedBin_Call,函数指针的定义为typedef VIM_RESULT(* PFatCallBack)(UINT8 *, UINT32);
void UDC_DFU_Init(PFatCallBackp_data_process,PFatCallBack p_speedbin_process)
{
VIM_HAL_PrintStr("UDC_DFU_Init:\r\n");
g_Usb_Process_Call= p_data_process;//处理收到的512字节,就是一个sector
//其中g_Usb_Process_Call用于USB工具下载IMAGE时对一个sector的处理
g_Usb_SpeedBin_Call= p_speedbin_process;//装载speed sorting镜像
g_FatCtx.pcall= p_data_process;
//g_FatCtx.pcall用于U盘拷贝方式对一个sector的处理
g_FatCtx.fat_total_sec= ChkFatBoot(DFU_SIZE);
udc_mass_init(&g_DfuMedOp, 1);
udc_mass_start();
}
此函数中会对fat设备的cluster等变量值进行计算,并且初始化mass设备。初始化的过程主要是对设备操作过程中记录状态机等信息的全局变量this_usb_udc进行赋初始值。
void udc_mass_init(PUSB_MED_IF pMedIf,UINT8 lun_num)
{
VIM_HAL_Memset(&g_mass_ctx,0, sizeof(g_mass_ctx));
g_mass_ctx.maxlun= 0;
// g_DataShared= (UINT8*)VIM_USB_DATA_BUF;
g_mass_ctx.lun[0].dma_addr[0]= (void *) (((UINT32)g_DmaNotMallocAddr+0x1000)&0xfffff000);
g_mass_ctx.lun[0].pUmdIf= pMedIf;
udc_init();
}
在udc_init()中会完成对设备赋初值的操作,主要包括设备的初始状态值,DMA地址等信息以及如下结构体变量值,
USB_MED_IF g_DfuMedOp =
{
NULL,
MedBoot_rd_sec,
MedBoot_wr_sec,
MedBoot_get_status,
MedBoot_get_sec_num,
NULL,
{
{‘o‘,‘o‘, ‘o‘, ‘o‘, ‘o‘, ‘o‘, ‘o‘, 0x20},//VID
{‘S‘,‘U‘, ‘P‘, ‘E‘, ‘R‘, 0x20, ‘D‘, ‘I‘, ‘S‘, ‘K‘, 0x20, 0x20, 0x20, 0x20, 0x20,0x20},//PID
{‘1‘,‘.‘,‘0‘,‘0‘}//VERSIIN
}
};
在整个初始化过程的最后会通过配置寄存器连接并使能设备为高速设备。
Udc_SetUsbPower(UDC_M_POWER_SOFTCONN|UDC_M_POWER_HSENAB);
至此设备的初始化过程完成,接下来会进入循环等待设备与host端进行交互的操作过程。
在udc_mass_process函数中会注册“查询处理usb数据传输状态,进行数据处理”的中断,接受中断并进行处理。
void udc_mass_process(void)
{
udc_int_usb();//查询处理usb数据传输状态,进行数据处理
if(g_mass_ctx.usb_state!= USB_STATE_CONFIGURED)
return;
if(g_mass_ctx.umb_state== UMB_IDLE)
{
req_cbw();
}
elseif(g_mass_ctx.umb_state == UMB_CBW_OK)
{
do_cbw();
}
}
在设备接收到的CBW状态为UMB_CBW_OK时,会通过do_cbw对数据进行分发和处理,这个过程中,主要是通过UmscProcess函数。在case中会看到在PC端程序中传递下来的子命令。
static void do_cbw(void)
{
UINT32 reqlen = 0;
pCsw->Tag= pCbw->Tag;
reqlen= UdcGet32Lbuf( (UINT8*)(&pCbw->TransferLength) );
g_mass_ctx.umb_state= UMB_CBW_PROCESS;
UmscProcess(&g_mass_ctx.lun[pCbw->Resv_Lun], pCbw->CbOpt, pCbw->CbData,pCbw->Flags, reqlen );
}
根据上面的描述大体上了解了USB设备注册完成之后设备的一些状态问题。在上面的描述中可以看到打开了USB的中断,这时候设备会进行重新枚举的过程,重新枚举的过程是会首先发送reset命令。在DFU中等待设备reset的过程显得比较漫长。这个过程中需要注意是否能够进行优化。
此处还是没有查明到底reset是从代码的那个位置传送下来,还是在udc_mass_process完成注册之后会一直扫描中断,但是后者看起来不像。
当接收到reset中断后,会对端点进行重新设置,并且通过调用udc_nuke_req()函数完成实际的操作。
/***************************************************************************************
总线RESET后执行的操作
****************************************************************************************/
static void udc_usbreset(void)
{
UINT32val;
UINT8 i;
VIM_DBG_Print(USB_MSG_DEVICE_RESET);
//Udc_SetIntUnmask(UDC_INT_USB|UDC_INT_DMA);
//usbint , all without sof
val= ~UDC_M_INTR_SOF;
Udc_SetUsbIrqEn(val);
Udc_SetInIrqEn((0x1<<EP_CTRL)|(0x1<<EP_BULK_IN)|(0x1<<EP_INTR_IN) );
Udc_SetOutIrqEn((0x1<<EP_CTRL)|(0x1<<EP_BULK_OUT)|(0x1<<EP_INTR_OUT) );//重新使能相应的端点
this_usb_udc.state= USB_STATE_DEFAULT;
this_usb_udc.highspeed= 1;
//各个端点注册的RESET函数,在udc_ttc.c和udc_bulkonly中定义了this_usb_udc.driver指向的实体结构体
//g_ttc_driver and g_mass_driver
if(this_usb_udc.driver != NULL )//在TTC中,将命令的各个管道的USBstate设置成usb_state =USB_STATE_DEFAULT
this_usb_udc.driver->reset();
udc_nuke_req();//??????????????????????????????????????????
//OUT和中断端点设置BUSY位,为什么?这个BUSY的用处是什么?
//这两位为1表示不能使能RX端点,即清除它的RXPKTRDY位,不能接收下笔数据
//这两位在第一次REQ_CBW时被清除,由于系统默认是可以接收数据的,这次就不必
//人工使能端点接收了
this_usb_udc.ep[EP_BULK_OUT].busy= 1;//此时g_ep_out=EP_IDX_1
this_usb_udc.ep[EP_INTR_OUT].busy= 1;//此时EP_INTR_OUT=EP_IDX_3
//清除端点的FIFO和TOG,释放停止状态标志
for(i=0;i<EP_GT_NUM; i++)
{
Udc_EpRst(i, USB_EP_RET_DIR_OUT);//out
Udc_EpRst(i,USB_EP_RET_DIR_IN); //in
this_usb_udc.ep[i].halt= 0;
}
}
上面的代码中调用了this_usb_udc.driver->reset();这个函数的赋值是通过对结构体进行整体赋值完成的,
struct usb_driver g_mass_driver =
{
UsbMassClsReq,
Usb_MassGetDescBuf,
UsbMassSetCfg,
UsbMassReset,
UsbMassSuspend,
UsbMassSetIntf
};
usb_driver结构体的定义为:
struct usb_driver {
void (*udc_vendor_class_req)(structusb_fifo *);
UINT8* (*get_desc)(UINT8 highspeed, UINT8type, UINT16* len, UINT8 strIdx);
void (*set_cfg)(void);
void (*reset)(void);
void (*suspend)(void);
void (*set_interface)(UINT16intface, UINT16 altset);
};
实际过程中UsbMassReset函数完成的操作仅仅是对g_mass_ctx.usb_state进行赋值为 USB_STATE_DEFAULT,设置这个状态会让设备。目前能够看到的在DFU中状态机的操作是在GetConfig的过程中作为控制端点是否可用的状态判断符,
if(this_usb_udc.state == USB_STATE_DEFAULT )
{
udc_CtrlCmdNotSupport();
return;
}
如果不支持这样的控制命令那么就会stall endpoint为停止状态。
在udc_nuke_req()函数中,会根据选择的传输方式不同进行不同的控制。通常情况下,控制传输使用FIFO方式,而在需要大量数据传输的bulk传输过程中会使用DMA方式。