VxWorks开发板驱动程序学习之SD卡

来源:互联网 发布:linux下wget命令 编辑:程序博客网 时间:2024/04/30 14:27


现在要开始写毕业论文了,每天学VxWorks的时间就少一点。今天开始学S3C2410操作SD卡的驱动程序,争取在三天之内完成。这一节我们首先讲一下SD卡的相关信息,再看看S3C2410的SD卡接口,最后再看看Rock的开发板上基于VxWorks操作系统,如何用S3C2410驱动SD卡。

  • SD card
  • S3C2410的MMCSDSDIO HOST CONTROLLER
  • S3C2410A 的SDMMC 接口驱动
    • 块设备
      • BLK_DEV
      • 函数接口
    • TFFS
      • Flash存储器
      • 算法分析
      • 结构分析
    • 信号量
      • semLib
        • 二进制信号量
        • 互斥信号量
      • errnoLib

SD card


其实在学习SD卡之前,大家可以先看看这一篇文章,对于SD卡的讲得非常详细。(SD卡的详细资料(开发文档))。

文章先后介绍了:

  • SD卡简介
  • 卡类型
  • SD卡协议
  • SD卡通信接口
  • SD卡内部结构
  • SD卡上电过程
  • SD卡的命令和响应以及数据传送的格式
  • SD卡的状态SD卡的操作模式
  • 是否选择宽总线
  • SD卡读数据格式
  • SD卡写数据格式


有了这些基本就会对SD卡的开发操作有个基本的了解。这里我们简单摘录一部分。
先来看看SD卡的模块图:
SD卡模块图

由图可知,SD卡可由SD总线/SPI总线接口和外部通信;内部经过一个单片控制器与FLASH模块交换数据。


再来看看SD卡的物理尺寸图:
物理尺寸图
从图中可以清晰的看到各个引脚的功能,看表:

Pin 9 1 2 3 4 5 6 7 8 SD mode CD/DAT3 CMD VSS VCC CLK VSS DAT0 DAT1 DAT2 SPI mode CS MOSI VSS VCC CLK VSS MISO NC NC


卡类型
MMC卡(MultiMedia card多媒体卡)
SD卡(Security Digital card安全数字卡) 与MMC卡兼容,除了可以存储还可以加密
TF卡:软件上和SD卡一致,硬件体积比SD卡小
SDIO卡:并非存储卡,可以理解为一个SDIO接口卡,如WIFI(SDIO接口);并非memory卡


SD协议
1.x: 小于2GB的卡(通过相关软件,可以模拟实现大于2GB)
2.0: 2G< SD < 32GB
3.0: > 32GB


SD卡通信接口
SD卡有9个PIN脚:1个VDD, 2个VSS(GND), 1个CLK, 1个CMD, DATA0-DATA3(其中,DATA3可以作为卡检测引脚)


相关的具体信息可以查看SD卡的产品手册(SanDisk Secure Digital Card Product Manual)。先看SD卡总线接口图:
总线电路图
有了这个总线接口描述,来简单说一下几个引脚信号:
CMD - Command is a bi-directional signal. (Host and card drivers are operating in push pull mode. 命令与响应都是走这条线)
DAT0-3 - Data lines are bi-directional signals. (Host and card drivers are operating in push pull mode. 真正的数据走这4条线,数据位可以配置1~4)
CLK - Clock is a host to cards signal. (CLK operates in push pull mode.)
VDD - VDD is the power supply for all cards.
VSS[1:2] - VSS are two groud lines.


SPI接口描述
看图:
SPI接口
CS: Host to card Chip Select signal
CLK: host to card clock signal
DataIn: Host to card data signal
DataOut: Card to host data signal


SD卡内部结构图:
内部结构图


再看看几个寄存器:
SD卡寄存器
按顺序来,
CID: 卡身份识别寄存器 128bit, 只读,厂家号,产品号,串号,生产日期
RCA: 卡地址寄存器,可写的16bit SD host和卡进行协商的一个地址,内核中会在代码里面记录这个地址,卡上则写到RCA寄存器
CSD: 卡专有数据寄存器, 可读写128bit, 卡容量,最大传输速率,读写操作的最大电流,电压,读写擦除块的最大长度等
SCR: 卡配置寄存器, 可写的64bit 是否用Security特性,以及数据位宽度 (1bit 或4bit)
OCR: 卡操作电压寄存器 32位, 只读,每隔0.1V占1位, 第31位标识卡上电过程是否完成,置位,上电完成。如下表所示:
这里写图片描述
这里写图片描述
这里写图片描述


上电过程
接下来,看看SD卡的上电过程。
上电过程


上电初始化需要1ms或者74 CLOCK(SD卡的clock,400KHZ)两者大致,SD卡的电压上到2.0V,SD卡开始工作,此时支持的命令非常有限(其中最主要支持ACMD41命令,sd host问卡的操作电压时多少? 卡就会从它的OCR寄存器里面读出SD卡出厂就烧录在里面的卡的操作电压值返回给sd host),当电压调整到它要求的VDD时候(SD卡的工作电压在2.7~3.6V,常见3.3V),同时SD的CLK从400KHZ调整到更高频率(比如25MHZ),这时卡就完全正常的工作了。接下来可以去获取卡的生产厂家,容量等信息。


看看SD卡内部数据组织:
Memory Array Partitioning

剩余的命令部分不再讲解,具体可以查看SD卡的产品手册,也可参考上面的链接(http://www.51hei.com/mcu/4125.html)。接下来,看看S3C2410的SD接口。

S3C2410的MMC/SD/SDIO HOST CONTROLLER


S3C2410A的SD主控制器可以支持MMC/SD 卡和SDIO设备。


来看看S3C2410A的SD主控制器特性:

  • SD Memory Card Spec.(ver.1.0)/ MMC Spec.(2.11) compatible
  • SDIO Card Spec (ver.1.0) compatilble
  • 16 words(64 bytes) FIFO (depth 16) for data Tx/Rx
  • 40-bit Command Register (SDICARG[31:0] + SDICCON[7:0])
  • 136-bit Response Register (SDIRSPn[127:0] + SDICSTA[7:0])
  • 8-bit Prescaler Logic (Freq. = System Clock / (2(P+1)) )
  • CRC7 & CRC16 Generator
  • Polling, Interrupt and DMA Data Transfer Mode (Byte or Word transfer)
  • 1-bit/ 4-bit (wide bus) Mode & Block / Stream Mode Switch support
  • Support up to 25MHz in data transfer mode for SD/SDIO
  • Support up to 20MHz in data transfer mode for MMC


S3C2410的SD接口模块图:
SD block diagram


SDI 操作
一个串行时钟线与五个数据线同步移位采样信息,可根据传输频率设置SDIPRE的相关位来调整波特率。


编程步骤:
SDI 模块可以根据以下步骤编程:

  • 1 设置SDICON 配置时钟和中断
  • 2 设置SDIPRE 配置波特率
  • 3 等74个SDCLK时钟周期初始化card


CMD Path Programming

  • 1 写32位的命令参数到 SDICARG 寄存器
  • 2 设置SDICCON[8]来决定命令类型并启动命令
  • 3 当SDICSTA的相关位置位确认SDI命令操作结束(no-response: SDICSTA[11], with-response: SDICSTA[9])
  • 4 写1到SDICSTA的标志位来清响应标志位


DAT Path Programing

  • 1 向 SDIDTIMER 寄存器写超时周期
  • 2 写块大小到 SDIBSIZE register (通常0x200字节)
  • 3 设置 SDIDCON 寄存器以配置 块模式、总线宽度、DMA等,启动数据传输
  • 4 检查 SDIFSTA, 当 Tx FIFO 有效时将 Tx-data 写入 SDIDAT 寄存器
  • 5 检查 SDIFSTA, 当 Rx FIFO 有效时将 Rx-data 从 SDIDAT 读出
  • 6 检查 SDIDSTA[4] 确认SDI数据操作传输完成
  • 7 向 SDIDSTA 的响应标志位写1以清除标志位


SDIO OPERATION
讲完了SD操作,再来看看SDIO操作。


SDIO操作有两个功能:SDIO接收中断和读等待请求产生。当 SDICON 的 RcvIOInt位、RwaitEn位被分别使能时这两个功能就可以操作了。


SDIO Interrupt
在SD 1-bit 模式中,中断可以从 SDDAT1 各个循环引脚接收;
在SD 4-bit 模式中,SDDAT1 的各个引脚被接收数据和中断共同使用。


Read Wait Request
无论是1-bit还是4-bit模式,读等待请求信号都可以在以下条件满足时传输到 SDDAT2 引脚:

  • 在 read multiple operation, request signal transmission begins in 2 clocks after the end of data block.
  • Transmission ends when the user writes one to SDIDSTA[10].


下面简单罗列一下SDI 特殊功能寄存器:

Register Address Description SDICON 0x5A00 0000 SDI control register SDIPRE 0x5A00 0004 SDI baud rate prescaler register SDICARG 0x5A00 0008 SDI command argument register SDICCON 0x5A00 000C SDI command control register SDICSTA 0x5A00 0010 SDI command status register SDIRSP0 0x5A00 0014 SDI response register 0 SDIRSP1 0x5A00 0018 SDIRSP2 0x5A00 001C SDIRSP3 0x5A00 0020 SDIDTIMER 0x5A00 0024 SDI data/ busy timer register SDIBSIZE 0x5A00 0028 SDI block size register SDIDCON 0x5A00 002C SDI data control register SDIDCNT 0x5A00 0030 SDI data remain counter register SDIDSTA 0x5A00 0034 SDI data status register SDIFSTA 0x5A00 0038 SDI FIFO status register SDIDAT 0x5A00 003C SDI data register SDIIMSK 0x5A00 0040 SDI interrupt mask register


好了,S3C2410A datasheet上的内容就看到这里, 相关寄存器的功能和相应位,编程时再查询数据手册。现在来看看驱动代码。

S3C2410A 的SD/MMC 接口驱动


在看Rock的代码前,我们先看看开发板上S3C2410A与外部SD卡的接口原理图。
S3C2410A-SD
S3C2410A-SD1


可以很清楚地看到,S3C2410A的 GPE5-GPE10 六个IO口线直接复用为 SD 卡接口,提供了 DAT0-DAT3 四个数据线, SDCLK 时钟线,SDCMD 命令线。其中除了 SDCLK通过一个电阻与SD卡的CLK连接,其他五条传输线均需要接10K上拉电阻。


有了直接复用的GPE5-GPE10 六个口线,来看看使用上有没有什么要注意的。
看看几个相关的寄存器:
这里写图片描述
这里写图片描述
这里写图片描述


总结一下,GPECON 配置E口的引脚功能,GPEDAT 存储E口引脚数值,GPEUP 配置E口各位上拉电阻使能,都与通常的GPIO口使用一样,没有特殊的。从第二张表可以看到,把GPE[21:10], 每连续两位配置为 10b 就可以复用 GPE5 - GPE10 六位为SD卡接口功能了。


好了,现在来看驱动代码,几乎全部代码出自 Rock的 开发板,S3C2410A 推塔 VxWorks 5.5, 若有侵权,请联系我删除。特别鸣谢。


这里也采用边学习,边注释的方式,有不懂的先加粗标红,后续再来理解。


在Atasd.h头文件一开始先宏定义一些常用的数据类型:

#define     U32         unsigned int#define     INT32U      unsigned int#define     INT32       int#define     U16         unsigned short#define     INT16U      unsigned short#define     INT16       short int#define     S32         int#define     S16         short int#define     U8          unsigned char#define     INT8U       unsigned char#define     INT8        char#define     S8          char#define     MAX_INITS   5   #define     INICLK      400000           // 初始化时钟 400KHz#define     NORCLK      20000000         // 正常工作时钟 20MHz#define     PCLK        50000000         // PCLK时钟  50MHz#define     MMC_SECTOR_SIZE 512          // MMC扇区大小 512字节(通常都是512字节)


再看一个枚举变量,这个枚举变量列举了所有的基本命令,具体可以查看SD卡产品手册的 Table4-3 到 Table 4-9.

enum{    MMC_CMD0_GO_IDLE_STATE=0,    MMC_CMD1_SEND_OP_COND,    MMC_CMD2_ALL_SEND_CID,    MMC_CMD3_SET_RELATIVE_ADDR,    MMC_CMD4_SET_DSR,    MMC_CMD5_RESERVED,    MMC_CMD6_RESERVED,    MMC_CMD7_SELECT_CARD,    MMC_CMD8_RESERVED,    MMC_CMD9_SEND_CSD,    MMC_CMD10_SEND_CID,    MMC_CMD11_READ_DAT_UNTIL_STOP,    MMC_CMD12_STOP_TRANSMISSION,    MMC_CMD13_SEND_STATUS,    MMC_CMD14_RESERVED,    MMC_CMD15_GO_INACTIVE_STATE,    MMC_CMD16_SET_BLOCKLEN,    /*read commands*/    MMC_CMD17_READ_SINGLE_BLOCK,    MMC_CMD18_READ_MULTIPLE_BLOCK,    MMC_CMD19_RESERVED,    /*write commands*/    MMC_CMD20_WRITE_DAT_UNTIL_STOP,    MMC_CMD21_RESERVED,    MMC_CMD22_RESERVED,    MMC_CMD23_RESERVED,    MMC_CMD24_WRITE_BLOCK,    MMC_CMD25_WRITE_MULTIPLE_BLOCK,    MMC_CMD26_PROGRAM_CID,    MMC_CMD27_PROGRAM_CSD,    /*block oriented write protection*/    MMC_CMD28_SET_WRITE_PROT,    MMC_CMD29_CLR_WRITE_PROT,    MMC_CMD30_SEND_WRITE_PROT,    MMC_CMD31_RESERVED,    /*erase commands*/    MMC_CMD32_TAG_SECTOR_START,    MMC_CMD33_TAG_SECTOR_END,    MMC_CMD34_UNTAG_SECTOR,    MMC_CMD35_ERASE_GROUP_START,    MMC_CMD36_ERASE_GROUP_END,    MMC_CMD37_UNTAG_ERASE_GROUP,    MMC_CMD38_ERASE,    /*IO mode commands*/    MMC_CMD39_FASTIO,    MMC_CMD40_GO_IRQ_STATE,    MMC_CMD41_RESERVED,    /*Lock card*/    MMC_CMD42_LOCK_UNLOCK,    /*commands 43-54 are reserved*/    /*Application specific commands*/    MMC_CMD55_APP_CMD=55,    MMC_CMD56_GEN_CMD=56,    /*commands 57 are reserved*/    MMC_CMD56_READ_OCR=58    /*commands 59 are reserved    commands 60-63 are reserved for manufacturers*/};


在看下一个结构体代码之前,我们先来看看SD卡的产品手册上关于命令是怎么介绍的。
这里写图片描述
这里写图片描述

这里介绍了控制SD卡的四种命令类型和命令格式。其中,格式长度为48-bit,我们简单看一下命令格式。命令总共48位,1位起始位+1位主机位+6位命令字位+32位参数位+7位CRC位+1位结束位,显然专门用一个位段结构体来存储一条命令是最合适不过了。来看看代码是如何实现的。

typedef struct{    // the struct defined with the MSB go out first 最高位在前    U8  b6CommandIndex:    6;    U8  b1TransmissionBit: 1;    U8  b1StartBit:        1;    union    {        U8   dwArgument[4];        struct        {            U8   wStuff[2];            U8   wArgument[2];        }b16Argument;    }Argument;                  // 32位的参数,用联合体表示    U8  b1EndBit:          1;    U8  b7CRC7:            1;}t_MMC_COMMAND;

可以看到,这个结构体类型完全实现了上面表格中的48-bit命令格式,刚好占48位,其中各位顺序并没有严格按照表中的顺序。以后就可以用类型 t_MMC_COMMAND 来定义 命令结构体变量了。


接下来,看一下CSD寄存器的介绍:
这里写图片描述
话说SD卡的所有配置信息都存储在CSD寄存器中。主机可以读CSD寄存器通用 SEND_CSD 和 PROGRAM CSD 命令更新主控数据字节。

这里写图片描述
这里写图片描述
这里写图片描述
这里写图片描述


这张表里面就把CSD寄存器的各位都详细的描述了。同样也可以用一个位段结构体来表示该寄存器,但要注意的是用位段表示时各位段赋值时,超过位数的高位要被舍去。来看代码:

typedef struct{    /* readonly part */    char b2CSDStructure     : 2;    char b4SpecVers         : 4;    char b2Reserved1        : 2;    char byTAAC;    char byNSAC;    char byTranSpeed;    int  b12CCC;    char b4ReadBLLen        : 4;    char b1ReadBLPartial    : 1;    char b1WriteBlkMisalign : 1;    char b1ReadBlkMisalign  : 1;    char b1DSRImp           : 1;    char b2Reserved2        : 2;    int  b12CSize;    char b3VDDRCurrMin      : 3;    char b3VDDRCurrMax      : 3;    char b3VDDWCurrMin      : 3;    char b3VDDWCurrMax      : 3;    char b3CSizeMult        : 3;    char b5EraseGrpSize     : 5;    char b5EraseGrpMult     : 5;    char b5WPGrpSize        : 5;    char b1WPGrpEnable      : 1;    char b2DefaultECC       : 2;    char b3R2WFactor        : 3;    char b4WriteBLLen       : 4;    char b1WriteBLPartial   : 1;    char b4Reserved3        : 4;    char b1ContentProtAPP   : 1;    /* rewriteable part */    char b1FileFormatGrp    : 1;    char b1Copy             : 1;    char b1PermWriteProtect : 1;    char b1TmpWriteProtect  : 1;    char b2FileFormat       : 2;    char b2ECC              : 2;    char b7CRC              : 7;    char b1NoUsed           : 1;    }t_MMC_CSD;

这个结构体类型共128-bit= 16字节,完全把CSD寄存器包含在内。

/* 时钟发生器控制寄存器 CLKCON *//* SDI data register *//* SDI interrupt mask register */#define rCLKCON  (*((volatile unsigned*) 0x4c00000c))#define rSDIDAT  (*((volatile unsigned*) 0x5a00003c))#define rSDIIMSK (*((volatile unsigned*) 0x5a000040))  /* SDICON, 0x5A00000 *//* GPECON, 0x5600040 */#define SD_BASE  0x5a00000                     #define IO_BASE  0x5600040                     

CLKCON
SDIDAT
SDIIMSK


再来看看S3C2410A的SD接口相关寄存器定义代码:

typedef struct SD{    UINT32   rSDICON;          // SDI control    UINT32   rSDIPRE;          // SDI baud rate prescaler    UINT32   rSDICMDARG;       // SDI command argument    UINT32   rSDICMDCON;       // SDI command control    UINT32   rSDICMDSTA;       // SDI command status    UINT32   rSDIRSP0;         // SDI response 0    UINT32   rSDIRSP1;         // SDI response 1    UINT32   rSDIRSP2;         // SDI response 2    UINT32   rSDIRSP3;         // SDI response 3    UINT32   rSDIDTIMER;       // SDI data/busy timer    UINT32   rSDIBSIZE;        // SDI block size    UINT32   rSDIDATCON;       // SDI data control    UINT32   rSDIDATCNT;       // SDI data remain counter    UINT32   rSDIDATSTA;       // SDI data status    UINT32   rSDIFSTA;         // SDI FIFO status}MMCreg;
typedef struct IOT{    UINT32   rGPECON;    UINT32   rGPEDAT;    UINT32   rGPEUP;}EIOreg;UINT32  InitSDMMC( void );void    sdDosFs( void );


以下驱动代码为S3C2410A驱动SD卡, tffs文件系统, 挂接为D盘,开发者为Rock,在这里面我们只取部分出来进行分析,若有侵权,请联系我删除,特别鸣谢。


定义一个SD设备结构类型:

typedef struct DEV{    BLK_DEV       sd_blkdev;    unsigned int  startBlkNum;    unsigned int  sd_blkOffset;    SEM_ID        sd_semMutex;}SD_DEV;


接下来定义几个全局变量:定义变量时加上了 volatile 修饰符,调用时就都具有了 volatile 特性

volatile MMCreg  *v_pSDMMCregs;  /* = (MMCreg*)SD_BASE; */volatile EIOreg  *v_pIOPregs;    /* = (EIOreg*)IO_BASE; */BOOL     g_bSDMMCIsExist;volatile int RCA;int      Wide = 1;     /* 0: 1bit, 1: 4bit */int      gMMC = 0;     /* 0: SD,   1: MMC */int      SdSize = 0;


先来看第一个函数:

// 函数名称:Chk_CMDend// 函数功能:检查命令结束位// 输入参数:cmd-命令号;  be_resp-是否需要 response// 返回值: 成功返回1, 失败返回0int Chk_CMDend(int cmd, int be_resp){    int   status;    if(!be_resp)              // no response    {        status = v_pSDMMCregs->rSDICMDSTA;     // 直接读取S3C2410A的寄存器        while((status & 0x800) != 0x800)       // check cmd end            status = v_pSDMMCregs->rSDICMDSTA; // 一直读直到CMD返回END        v_pSDMMCregs->rSDICMDSTA = status;     // clear cmd end state这里用或更好        return 1;    }    else                      // with response    {        status = v_pSDMMCregs->rSDICMDSTA;        while(!(((status&0x200)==0x200|((status&0x400)==0x400))) // check cmd/rsp end            status = v_pSDMMCregs->rSDICMDSTA;        if(cmd == 1 | cmd == 9 | cmd == 41)    // CRC no check        {            if((status&0xf00)!=0xa00)          // check error            {                v_pSDMMCregs->rSDICMDSTA = status; // clear error status                if(((status&0x400)==0x400))                    return 0;                  // timeout error            }        }        else                                   // CRC check        {            if((status&0x1f00)!=0xa00)         // check error            {                v_pSDMMCregs->rSDICMDSTA = status;                if((status&0x400)==0x400)                    return 0;                  // timeout error            }        }        return 1;    }}
// 函数名称:Chk_DATend// 函数功能:检查数据结束位// 返回值: 成功返回1,  失败返回0int Chk_DATend(void){    int  finish;    if(v_pSDMMCregs == NULL)        return 0;    finish = v_pSDMMCregs->rSDIDATSTA;    while(! (((finish&0x10)==0x10) | ((finish&0x20)==0x20)))    {        finish = v_pSDMMCregs->rSDIDATSTA;       // 是不是不太好,一直死循环等    }    if((finish&0xfc) != 0x10)    {        printf("\nDATA: finish = 0x%x.\n", finish);        v_pSDMMCregs->rSDIDATSTA = 0xec;         // clear error state        return 0;    }    return 1;}


CMD7
CMD7 is used to select one SD Card and place it in the Transfer State. Only one SD Card can be in the Transfer State at a given time. If a previously selected SD Card is the Transfer State, its connection with the host is released and it will move back to the Stand-by State. When CMD7 is issued with the reserved relative card address “0x0000”, all cards transfer back to Stand-by State. (Note that it is responsibility of the Host to reserve the RCA=0 for card de-selection—refer to Table4-3) This may be used before identifying new cards without resetting other already registered cards. Cards that already have an RCA do not respond to identification commands(ACMD41, CMD2, CMD3) in this state.


CMD7命令用来选择SD卡并将其转入 Transfer 状态。当CMD7与参数“0x0000”一起发布,所有的卡都进入到 Stand-by 状态。

// 函数名称:Card_sel_desel// 函数功能:是否选择SD卡void Card_sel_desel(char sel_desel){    if(NULL == v_pSDMMCregs)        return;    if(sel_desel)    {RECMDS7:        v_pSDMMCregs->rSDICMDARG = RCA << 16;   // CMD7(RCA, stuff bit)        v_pSDMMCregs->rSDICMDCON = (0x1<<9)|(0x1<<8)|0x47;         // sht_resp, wait_resp, start, CMD7        if(!Chk_CMDend(7,1))                goto RECMDS7;        if(v_pSDMMCregs->rSDIRSP0 & 0x1e00 != 0x800)            goto RECMDS7;    }    else    {RECMDD7:        v_pSDMMCregs->rSDICMDARG = 0<<16;     // CMD7(RCA, stuff bit)释放所有SD卡        v_psDMMCregs->rSDICMDCON = (0x1<<8)|0x47; // no_resp, start, CMD7        // check end of CMD7        if(!Chk_CMDend(7, 0))            goto RECMDD7;    }}

向SD卡发送CMD0命令,复位SD卡

// 函数名称:CMD0// 函数功能:主机向SD卡发送CMD0命令,GO_IDLE_STATE, 复位SD卡// 并在串口打印返回状态,实际驱动调试好后可以直接去掉打印部分void CMD0(void){    int  nError;    if(NULL == v_pSDMMCregs)        return;    v_pSDMMCregs->rSDICMDARG = 0x0;           // CMD0(stuff bit)    v_pSDMMCregs->rSDICMDCON = (1<<8)|0x40;   // no_resp, start, CMD0    // Check end of CMD0    nError = Chk_CMDend(0, 0);    printf("\nCMD0 chk_CMDend: %d.\n", nError);}

主机向SD卡发送CMD55命令,通知SD卡接下来的命令是一条应用命令而不是一条标准命令:ACMD command type shall always precede with CMD55

Application Specific Command—APP_CMD (CMD55)
This command, when received by the card, will cause the card to interpret the following command as an application specific command (ACMD). The ACMD has the same structure as regular MultiMediaCard standard commands and it may have the same CMD number. The card will recognize it as ACMD by the fact that it appears after APP_CMD.

// 函数名称:CMD55// 函数功能:主机S3C2410A向SD卡发送CMD55命令// 返回值:  成功-1, 失败-0int  CMD55(void){    if(NULL == v_pSDMMCregs)        return 0;    v_pSDMMCregs->rSDICMDARG = RCA << 16; // CMD55(RCA, stuff bit)    v_pSDMMCregs->rSDICMDCON = (0x1<<9)|(0x1<<8)|0x77;    // check end of CMD55    if(! Chk_CMDend(55, 1))    {    //  printf("CMD55 ERROR\n");   串口打印CMD55的错误信息,调试好驱动后就可以去掉        return 0;                   // CMD55 发送失败    }    return 1;}


RCA—Relative card address: Local system address of a card, dynamically suggested by the card and approved by the host during initialization.


SEND_CSD(CMD9)— The host issues SEND_CSD(CMD9) to obtain the Card Specific Data (CSD), e.g., block length, card storage capacity, maximum clock rate.
可以发送CMD9命令来读取CSD寄存器,以获取块长度,卡容量,最大时钟速率的信息。以下发送CMD9命令来获取SD卡的容量。

// 函数名称:Get_SD_Capacity// 函数功能:获取SD卡的容量UINT32   Get_SD_Capacity(void){    UINT32  c_size, c_size_mult, read_bl_len, num;    UINT32  r1,r2,r3,r4;repx:    v_pSDMMCregs->rSDICMDARG = RCA << 16;    v_pSDMMCregs->rSDICMDCON = (0x1<<10)|(0x1<<9)|(0x1<<8)|0x49;    if(! Chk_CMDend(9, 1))   // 等待CMD9命令返回,则相应的信息即存到相应寄存器    {        goto repx;    }    r1 = v_pSDMMCregs->rSDIRSP0;    r2 = v_pSDMMCregs->rSDIRSP1;    r3 = v_pSDMMCregs->rSDIRSP2;    r4 = v_pSDMMCregs->rSDIRSP3;        c_size = ((r2&0x3ff)<<2) + (r3>>30);    c_size_mult = (r3>>15)&0x07;    read_bl_len = (r2>>16)&0x0f;    num = (c_size+1)*(1<<(c_size_mult+2))*(1<<read_bl_len)/512; // 计算容量    if( (num>0x410000)&&(num<0x810000) )     num = 0x800000; // 4G    if( (num>0x210000)&&(num<0x410000) )     num = 0x400000; // 2G    if( (num>0x110000)&&(num<0x210000) )     num = 0x200000; // 1G    if( (num>0x90000)&&(num<0x110000) )     num = 0x100000;  // 512M    if( (num>0x70000)&&(num<0x90000) )     num = 0x80000;    // 256M    if( (num>0x30000)&&(num<0x50000) )     num = 0x40000;    // 128M    if( (num>0x19000)&&(num<0x21000) )     num = 0x20000;    // 64M    if( (num>0x9000)&&(num<0x11000) )     num = 0x10000;     // 32M/*  串口打印获取到的信息,调试完驱动后可以直接去掉,该函数只需要返回容量值 num 即可      printf("\nDATA:disk_size=0x%x.\nr1=0x%x.\nr2=0x%x.\nr3=0x%x.\n            r4=0x%x.\n", num, r1,r2,r3,r4);    printf("c_size=0x%x.\nc_size_mult=0x%x.\nread_bl_len=0x%x.\n",            c_size,c_size_mult,read_bl_len);*/      return num;}

前面已经介绍过OCR(Operating Configurations Register)操作条件寄存器用来存储SD卡的电压状态,其最高位置位后则SD卡上电完成。

ACMD41是一种特殊的同步命令,用于协商工作电压范围和轮询卡,直到它们脱离其上电序列。 除了卡的操作电压分布之外,对ACMD41的响应包含忙标志,指示卡仍然在其上电过程中工作并且还没有准备好进行识别。 该位通知主机卡尚未就绪。 主机必须等待(并继续轮询这些卡,轮到他们),直到该位被清除。 单卡上电过程最长时间不超过1秒。

// 函数名称:Chk_SD_OCR// 函数功能:读OCR寄存器以检查SD卡上电状态// 返回值 : 1-上电完成; 0-上电失败,尝试检查20次,再多也返回失败,驱动杜绝死循环/* ACMD41 is a special synchronization command used to negotiate the operation voltage range and to poll the cards until they are out of their power-up sequence. Besides the operation voltage profile of the cards, the response to ACMD41 contains a busy flag, indicating that the card is still working on its power-up procedure and is not ready for identification. This bit informs the host that the card is not ready. The host has to wait (and continue to poll the cards, each one on his turn) until this bit is cleared. The maximum period of power up procedure of single card shall not exceed 1 second. */int  Chk_SD_OCR(void){    int i,j;    for(i=0; i<20; i++)    // 尝试20次,若一直未成功,则直接返回上电失败,避免一直等待    {        CMD55();           // 主机S3C2410A向SD卡通知接下来为ACMD, make ACMD        v_pSDMMCregs->rSDICMDARG = 0xff8000;  // ACMD41 (OCR:2.7V~3.6V)        v_pSDMMCregs->rSDICMDCON = (0x1<<9)|(0x1<<8)|0x69;        //sht_resp, wait_resp, start, ACMD41        if(Chk_CMDend(41, 1) & (v_pSDMMCregs->rSDIRSP0 == 0x80ff8000) )        {            return 1;      // 上电完成        }        for(j=0; j<50000; j++);  // 等待SD卡上电状态    }    return 0;              // Failed}


CMD1— voltage recognition procedure

int Chk_MMC_OCR(void){    int i,j;    if(NULL == v_pSDMMCregs)        return 0;    for(i=0; i<15; i++)       // 尝试15次 CMD1电压检测命令    {        v_pSDMMCregs->rSDICMDARG = 0xff8000;        v_pSDMMCregs->rSDICMDCON = (0x1<<9)|(0x1<<8)|0x41;        for(j=0; j<1000; j++)            ;        if(Chk_CMDend(1,1) & v_pSDMMCregs->rSDIRSP0 == 0x80ff8000)        {            return 1;         // success        }    }    return 0;                 // failed}


Wide Bus Selection/Deselection
Wide Bus (4-bit bus width) operation mode may be selected/deselected using ACMD6. The default bus width after power up or GO_IDLE (CMD0) is 1 bit bus width. ACMD6 command is valid in ‘tran state’ only. That means that the bus width may be changed only after a card was selected (CMD7).

// 函数名称:SetBus// 函数功能:设置总线宽度,用全局变量 Widevoid SetBus(void){SET_BUS:    CMD55();                    // make ACMD    v_pSDMMCregs->rSDICMDARG = Wide<<1;   // 0: 1-bit,  1:  4-bit    v_pSDMMCregs->rSDICMDCON = (0x1<<9)|(0x1<<8)|0x46;    if(! Chk_CMDend(6, 1))      // check ACMD6        goto SET_BUTS;}void Set_1bit_bus(void){    Wide = 0;                   // 1-bit    if(!gMMC)                   // SD        SetBus();}void Set_4bit_bus(void){    Wide = 1;                   // 4-bit    SetBus();                   // ACMD6}BOOL mmc_card_init(){    return 1;}


先来看看SD Interface的寄存器列表:
SD Interface
GPECON

有了这两张图就可以很容易看懂下面两个全局指针变量初始化的过程了

// 函数名称:init_reg// 函数功能:申请内存并初始化两个全局指针变量 v_pSDMMCregs, v_pIOPregs// 若内存申请失败,则直接使用 SDICON, GPECON 作为基地址封装结构体指针// volatile EIOreg *v_pIOPregs; // volatile MMCreg *v_pSDMMCregs;void init_reg(void){    v_pSDMMCregs = NULL;    v_pIOPregs = NULL;    v_pSDMMCregs = (volatile MMCreg*)malloc(sizeof(MMCreg));    if(NULL == v_pSDMMCregs)        printf("MMCreg malloc failed!\n");    else        v_pSDMMCregs = (MMCreg*) SD_BASE;    v_pIOPregs = (volatile EIOreg*) malloc(sizeof(EIOreg));    if(NULL == v_pIOPregs)        printf("EIOreg malloc failed!\n");    else        v_pIOPregs = (EIOreg *) IO_BASE;    }


前面这一系列的函数都可以看成是对照SanDisk Secure Digital Card的产品手册写的操作SD卡的函数,接下来驱动程序就要为外部提供操作接口函数了。第一个函数即初始化S3C2410A的SD/MMC接口部分,其中加入了作者自己对SD操作的封装部分。

// 函数名称:InitSDMMC// 函数功能:初始化SD/MMC接口// 返回值 : 初始化成功的SD卡的容量信息;错误返回0UINT32  InitSDMMC(void){    int    i;    UINT32 SD_Size;    g_bSDMMCIsExist = FALSE;    printf("initializing ... ...");          // 串口打印指示初始化状态    init_reg();                              // 初始化两个全局结构体指针变量    if(v_pIOPregs == NULL)   return 0;    if(v_pSDMMCregs == NULL) return 0;    for(i=0; i<0x5000; i++)                  // 等待SD卡上电,最大250ms        ;    rCLKCON = rCLKCON | (1<<9);    v_pIOPregs->rGPEUP = 0xf83f;             // SD接口这几位要上拉    v_pIOPregs->rGPECON = 0xaaaaaaaa;        // 复用GPE接口    v_pSDMMCregs->rSDICMDSTA = 0xffff;       // 对应S3C2410A的SDICSTA,写1清标志位    v_pSDMMCregs->rSDIDATSTA = 0xffff;    v_pSDMMCregs->rSDIPRE = PCLK/(2*INICLK) - 1; // 初始化时钟频率 400KHz    v_pSDMMCregs->rSDICON = (1<<1)|1;        // Type A, FIFO reset, clk enable    v_pSDMMCregs->rSDIBSIZE = 0x200;         // 512bytes = 128 word    v_pSDMMCregs->rSDIDTIMER = 0xffff;       // timeout count    for(i=0; i<0x20000; i++)                 // wait 74SDCLK for MMC card        ;     CMD0();                                  // reset    printf("In idle.\n");    // 检查MMC上电状态    if(Chk_MMC_OCR())            {        printf("In MMC ready.\n");        gMMC = 1;        goto   RECMD2;                       // ALL_SEND_CID    }    if(Chk_SD_OCR()        printf("In SD ready.\n");    else    {        printf("Initialize fail\nNo Card assertion.\n");        return 0;    }RECMD2:    // 检测卡,参照以上上电图    // The host then issues the command ALL_SEND_CID (CMD2) to each card to get     // its unique card identification (CID) number.    v_pSDMMCregs->rSDICMDARG = 0x0;    v_pSDMMCregs->rSDICMDCON = (0x1<<10)|(0x1<<9)|(0x1<<8)|0x42;    // lng_resp, wait_resp, start, CMD2    if(!Chk_CMDend(2,1))    {        goto RECMD2;    }    printf("End id.\n");RECMD3:    // Send RCA, 询问RCA    // the host issues CMD3 (SEND_RELATIVE_ADDR) asking the card to     // publish a new relative card address (RCA), which is shorter than     // CID and which will be used to address the card in the future     // data transfer mode (typically with a higher clock rate than $f_{OD}$)    v_pSDMMCregs->rSDICMDARG = gMMC << 16; // CMD3(MMC:Set RCA,SD:Ask RCA->SBZ)    v_pSDMMCregs->rSDICMDCON = (0x1<<9)|(0x1<<8)|0x43;    // sht_resp, wait_resp, start, CMD3    if(!Chk_CMDend(3, 1))        goto RECMD3;    if(gMMC)        RCA = 1;    else        RCA = (v_pSDMMCregs->rSDIRSP0 & 0xffff0000) >> 16;    SD_Size = Get_SD_Capacity();    // State(stand-by) check    if(v_pSDMMCregs->rSDIRSP0& 0x1e00 != 0x600)        // current state check        goto    RECMD3;    printf("In stand-by\n");    v_pSDMMCregs->rSDIPRE = PCLK/(2*NORCLK)-1;         // normal clk = 25MHz    Card_sel_desel(1);                                 // select card    if(!gMMC)        set_4bit_bus();    else        Set_1bit_bus();    if(mmc_card_init())    {        g_bSDMMCIsExist = TRUE;        printf("sd/mmc_card_init OK.\n");        return SD_Size;    }    else    {        g_bSDMMCIsExist = FALSE;        printf("sd/mmc_card_init ERROR.\n");        return 0;    }}


前面我们交待了,此节的SD卡驱动搭载 tffs 文件系统,挂接为D盘,以上的函数实现了操作SD卡的底层函数,接下来我们看看作者是如何组织文件系统的,涉及TFFS文件系统的内容,我们在下一节进行学习。

块设备


现在先来看一个新的概念——块设备

块设备
块设备是支持文件系统的基础,VxWorks的块设备以块为单位传输数据,字符设备以字节为单位传输数据。块设备不能与I/O系统直接打交道,在其与I/O系统之间必须有文件系统,如dosFs, rt11Fs, rawFs, tapeFS等。这种层次关系允许同一个块设备上使用不同的文件系统。块设备的函数入口不在驱动表中注册(上层文件系统 的函数入口在驱动表中注册),块设备的描述符也不进入设备链表(文件系统的描述符进入设备链表)。


为了实现上层软件的设备无关性,所有块设备驱动都遵循统一接口规范。该接口不以单独的软件层存在,而是体现在一个统一的数据结构(BLK_DEV)上。该结构由具体的设备实例化,用来代表该设备。对上层来说,与块设备的接口就是这个统一的结构,通过具体的结构实例来操作对应的设备。该结构中包含了统一的函数接口,函数具体的实现由各块设备驱动程序完成。


xxxDevCreate用来创建设备实例,当创建块设备实例时,设备没有名称与其相联。只有建立文件系统后才有名称。块设备再经过CBIO封装文件系统使用。
块设备的类似初始化函数

~ xxxDrv xxxDevCreate ramDrv STATUS ramDrv BLK_DEV* ramDevCreate nec765Fd STATUS fdDrv BLK_DEV* fdDevCreate ideDrv STATUS ideDrv BLK_DEV* ideDevCreate ataDrv STATUS ataDrv BLK_DEV* ataDevCreate tffsDrv STATUS tffsDrv BLK_DEV* tffsDevCreate memDrv STATUS memDrv BLK_DEV* memDevCreate ramDiskCbio 无 CBIO_DEV_ID ramDiskDevCreate

BLK_DEV

BLK_DEV 对块设备的作用,与驱动表对字符设备的作用相同,都身上层提供设备的规范接口。
BLK_DEV结构在<\target\h\blkIo.h>中定义如下:

typedef struct /* BLK_DEV */{    FUNCPTR     bd_blkRd;    FUNCPTR     bd_blkWrt;    FUNCPTR     bd_ioctl;           // function to ioctl device    FUNCPTR     bd_reset;           // function to reset device    FUNCPTR     bd_statusChk;       // function to check status    BOOL        bd_removable;       // removable medium flag    ULONG       bd_nBlocks;         // number of blocks on device    ULONG       bd_bytesPerBlk;     // number of bytes per block    ULONG       bd_blksPerTrack;    // number of blocks per track    ULONG       bd_nHeads;          // number of heads    int         bd_retry;           // retry count for I/O errors    int         bd_mode;            // O_RDONLY | O_WRONLY | O_RDWR    BOOL        bd_readyChanged;    // dev ready status changed} BLK_DEV;

这个 blkIo.h 文件里面也只有这一个结构类型定义,非常简单的一个头文件。

函数接口

块设备有5个标准函数接口,具体的函数实例由块设备的驱动程序提供。

// 读取存储器上的数据,以块为单位// pDev 指向具体块设备结构指针,如ATA_DEV结构,具体设备结构的第一个成员一定是BLK_DEV// startBlk 是开始读的块的起始位置// nBlks 指需要读的块的数目// pBuf 显然是存储接收数据的缓冲区指针 STATUS xxBlkRd(DEVICE *pDev, int startBlk, int nBlks, char *pBuf);// 写数据方向相反,各参数与读数据一致STATUS xxBlkWrt(DEVICE *pDev, int startBlk, int nBlks, char *pBuf);// 多用途控制函数,一些不能归入标准函数中的功能在该函数中实现,如格式化等,相当于函数接口的// 扩展“xxIoctl_function(arg)”。iotrl函数是传递执行的,上层未解释的功能码才传递到驱动// 层,若驱动层也不能解释,就返回错误,并设置错误码为 S_ioLib_UNKNOWN_REQUEST// function 是功能码,如 FIODISKFORMAT,系统功能码在 ioLib.h 中有定义,设备自己的功能// 码最好定义在 0x1000 以上,以免与系统功能码冲突STATUS xxIoctl(DEVICE *pDev, int function, int arg);// 块设备硬件复位,当文件系统在设备上加载或读写失败时,由文件系统调用STATUS xxReset(DEVICE *pDev);// 块设备状态检查,由open或 create调用。若 xxStatusChk失败,会设置errno表明原因,open// 或 create 也失败。这对可插拔的设备来说也重要。当设备出错或被拔出,此函数返回错误,这时// 谁的系统就不会继续往下操作。而是当一个新的设备插上时,它设置BLK_DEV中的 bd_readyChanged// 为TRUE,然后返回OK, 这时open 及 create 函数可以继续操作。新的设备可以自动地加入到// 系统中。设置bd_readyChanged和dosFsReadyChanged、ioctl(FIODISKCHANGE)两函数执行// 效果一样,重新加载了文件系统。STATUS xxStatusChk(DEVICE *pDev);

TFFS


TrueFFS为各种Flash存储器提供通常的块设备接口,是M-Systems公司为VxWorks操作系统所做的定制实现。

Flash存储器


Flash存储器为固态[solid-state]设备,没有运动的机械部件,具有寿命长、耗能少和体积小等优点,并且只能进行块擦除;有限的擦除和写入次数,一般约为10万次;擦除和写入操作比较耗时,且不能同时读取;对Flash 不能像普通RAM一样直接写入,需要执行系列指令。


Intel于1988年首先开发出NOR Flash技术;紧接着,1989年,东芝公司发布了NAND Flash结构。

通常所说的Flash多指NOR型存储器,一般用于程序映象的存储和运行。NOR的特点是芯片内执行(XIP, eXecute In Place),这样应用程序可以直接在闪存内运行,不必把代码读到系统RAM中。而NAND更适合大容量的数据存储。下表是两者主要区别:

~ NOR FLASH NAND FLASH 容量 1MB~32MB 16MB-512MB XIP YES NO 性能 擦除很慢(5s),写较慢 快速擦除(3ms),写和读 可靠性 一般 低(需要EDC/ECC和坏块管理) 擦除次数 1万-10万 10万-100万 擦除块 大 小 接口 标准内存接口 仅IO, CLE、OLE和ALE信号必须变化 访问方法 随机 顺序 易用性 简单 复杂 用途 代码或小量数据 大量数据存储 价格 高 低

算法分析


Flash存储器的特性使得其对上层软件有特别的要求。

  • 平均使用:最好能均匀使用Flash每个扇区
  • 高效垃圾回收:垃圾回收也应以扇区为单位,先移动扇区数据,再擦除整个扇区
  • 掉电安全:无论程序崩溃或系统掉电,都不能影响数据的一致性和完整性
  • 低空间消耗[Low OverHead]:上导软件管理结构在Flash存储器上的空间消耗要尽可能低

结构分析


TrueFFS由3层实现:翻译层(FTL)、MTD层和Socket层
这里写图片描述


FTL为上层软件提供标准块设备接口,实现上面描述的各种算法,如块映射、平均使用、垃圾回收和数据保护等。根据Flash存储器的类型,有3种类型的FTL:NOR、NAND、SSFDC。
MTD层和具体的芯片相关,用于实现 Flash存储器存的操作指令序列。
Socket层为具体硬板提供接口,实现电压控制,基地址设置,写保护控制等。


在VxWorks中,FTL以库形式提供,包括 tffsDrv, tffsLib 以及 ftl 和fl 开头的目标模块。
MTD以源码形式提供,在“\target\src\drv\tffs”目录下,以Flash芯片型号命名,如i28f008.c
Socket层也以源码提供,在”\target\src\drv\tffs\sockets” 目录下,按硬板的具体型号命名
sysTffs.c 中还包含编译 tffsConfig.c,在“\target\src\drv\tffs”目录下,只要在 mtdTable 数组中添加自己芯片的标识函数入口即可。


在为自己的硬件定制TFFS时,可以将 xxxxmtd.c、tffsConfig.c和sysTffs.c 直接添加到自己的BSP目录中,方便代码管理和Tornado重装。


关于TFFS内容,我们后面再新开一节来专门讲解。此处就学习到这里。继续看后面的接口函数代码。学习上面的这些内容,再来看后面的接口函数就要容易很多了。


现在,来看看代码开头定义的一个类型结构:

typedef struct DEV{    BLK_DEV      sd_blkdev;    unsigned int startBlkNum;    unsigned int sd_blkOffset;   // 块偏移    SEM_ID       sd_semMutex;    // 互斥信号量结构指针,指向信号量结构,作为信号量操作的标示符}SD_DEV;

显然此结构用于放在上面给出的块设备的5个接口函数的第一个参数。

/*******************     dosFS    ********************/
// 函数名称:sdf_read_sector, 同上面提到的接口函数:STATUS xxBlkRd(..)// 函数功能:读取一个扇区数据(通常512字节)// 输入参数:pSDDev - SD卡设备指针,secaddrno - SD 卡扇区地址// 输出参数:*buf - 存储读回数据的缓存STATUS  sdf_read_sector(SD_DEV *pSDDev, U32 secaddrno, U8 *buf){    U32    addr;    U32    rd_data;    int    status, i;    int    rd_cnt = 0;    if(NULL == v_pSDMMCregs)        return ERROR;    if((pSDDev->startBlkNum+pSDDev->sd_blkOffset+secaddrno) > pSDDev->sd_blkdev.bd_nBlocks)        return ERROR;    addr = (pSDDev->startBlkNum + pSDDev->sd_blkOffset + secaddrno)            * pSDDev->sd_blkdev.bd_bytesPerBlk;    v_pSDMMCregs->rSDICON |= (1<<1);    v_pSDMMCregs->rSDIDATCON = (1<<19)|(1<<17)|(Wide<<16)|(2<<12)|(1<<0);    v_pSDMMCregs->rSDICMDARG = addr;RERDCMD:    v_pSDMMCregs->rSDICMDCON = (0x1<<9)|(0x1<<8)|0x51;    if(!Chk_CMDend(17, 1))                             // 块读取指令,block read (CMD17)        goto RERDCMD;    while(rd_cnt < (512/4))    {        if((v_pSDMMCregs->rSDIDATSTA & 0x20) == 0x20)        {            printf("\nRd_Block timeout: %d!\n", secaddrno);            v_pSDMMCregs->rSDIDATSTA = 0x1<< 0x5;      // 清除标志位            return 0;        }        status = v_pSDMMCregs->rSDIFSTA;               // SDIFSTA: SDI FIFO status register        if((status & 0x1000) == 0x1000)                // FIFO data is available        {            rd_data = rSDIDAT;                         // SDIDAT: SDI data register            buf[rd_cnt*4]   = rd_data >> 0 &  0xff;            buf[rd_cnt*4+1] = rd_data >> 8 &  0xff;            buf[rd_cnt*4+2] = rd_data >>16 &  0xff;            buf[rd_cnt*4+3] = rd_data >>24 &  0xff;    // 四个字节一个字 存入缓存            rd_cnt++;        }    }    if(!Chk_DATend())  ;        // printf("\ndata error\n");    v_pSDMMCregs->rSDIDATSTA = 0x10;    return (OK);    }


这里会涉及到SD卡读取的几个命令:
Block Read Commands


All data communication in the Data Transfer Mode is point-to point between the host and the selected SD Card (using addressed commands). All addressed commands are acknowledged with a response on the CMD line.

All data read commands may be aborted any time by the stop command (CMD12). The data transfer will terminate and the card will return to the Transfer State. The read commands are: block read (CMD17), multiple block read (CMD18), send write protect (CMD30), send scr (ACMD51) and general command in read mode (CMD56).

All data write commands can be aborted any time by the stop command (CMD12). The write commands must be stopped prior to deselecting the card by CMD7. The write commands are: block write (CMD24 and CMD25), write CSD (CMD27), lock/unlock command (CMD42) and general command in write mode (CMD56).

As soon as the data transfer is completed, the card will exit the data write state and move either to the Programming State (transfer is successful) or Transfer State (transfer failed).

If a block-write-operation is stopped and the block length and CRC of the last block are valid, the data will be programmed.

The card may provide buffering for block write. This means that the next block can be sent to the card while the previous is being programmed. If all write buffers are full, and as long as the card is in Programming State (see SD Card state diagram Figure 5-8), the DAT0 line will be kept low (BUSY).

There is no buffering option for write CSD, write protection and erase. This means that while the card is busy servicing any one of these commands, no other data transfer commands will be accepted. DAT0 line will be kept low as long as the card is busy and in the Programming State. Actually if the CMD and DAT0 lines of the cards are kept separated and the host keeps the busy DAT0 line disconnected from the other DAT0 lines (of the other cards), the host may access the other cards while the card is in busy.

Parameter set commands are not allowed while the card is programming. Parameter set commands include: set block length (CMD16), erase block start (CMD32) and erase block end (CMD33).

Read commands are not allowed while the card is programming.

Moving another card from Stand-by to Transfer State (using CMD7) will not terminate erase and programming operations. The card will switch to the Disconnect State and will release the DAT line.

A card can be reselected while in the Disconnect State, using CMD7. In this case the card will move to the Programming State and reactivate the busy indication.

Resetting a card (using CMD0 or CMD15) will terminate any pending or active programming operation. This may destroy the data contents on the card. It is the host’s responsibility to prevent this.


上面这几段话对SD卡的读写要点都作了一定介绍,上面的 sdf_read_sector 读扇区也就不难理解了。下面将读一个扇区的接口函数封装一下,就可以很容易的得到读取多个扇区的函数。

// 函数名称:sdf_multiread// 函数功能:读取多个扇区数据// 输入参数:pSDDev - SD卡设备指针, startblock - 起始扇区号, blocknum - 块数量// 输出参数:buf - 输出缓存STATUS  sdf_multiread(SD_DEV *pSDDev, U32 startblock, U32 blocknum, U8 *buf){    int    status, i;    semTake( pSDDev->sd_semMutex, WAIT_FOREVER);      // 等待二进制信号量,等待事件发生,阻塞    while(blocknum > 0)    {        status = sdf_read_sector(pSDDev, startblock, buf);        semGive( pSDDev->sd_semMutex);                // 释放信号量,通知事件发生        if(status == ERROR)             return ERROR;        startblock ++;        buf += pSDDev->sd_blkdev.bd_bytesPerBlk;        blocknum--;    }    return status;}

这个函数也很简单,没什么说的。但这里的SD_DEV结构里面封装了一个二进制信号量值得注意。这里也简单介绍一下信号量。

信号量


信号量机制是许多操作系统的标准通信机制,用于实现资源互斥和任务同步。
VxWorks提供3种信号量:二进制[Binary](同步)信号量、计数信号量和互斥[Mutex]信号量。看表:

库 说明 semLib 通用信号量库 semBLib 二进制信号量库 semCLib 计数信号量库 semMLib 互斥信号量库 semOLib VxWors 4.x 二进制信号库, 用于向下兼容 semPxLib POSIX 1003.1b 信号量库 semPxShow 提供POSIX信号量查看函数 semShow 提供信号量查看函数 semSmLib 共享内存信号量库 (VxMP) 可选产品)


此处用到了互斥信号量,我们就简单介绍一下互斥信号量,不对所有的信号量做讲解了。

STATUS  semBCreate( int options, SEM_B_STATE initialState );STATUS  semMCreate( int options );STATUS  semCCreate( int options, int initialCount );

信号量创建函数都返回一个指针类型 SEM_ID ,在 <\targe\h\semLib.h > 中有定义:

typedef struct semaphore * SEM_ID;/* options */#define  SEM_Q_MASK    0x3#define  SEM_Q_FIFO    0x0#define  SEM_Q_PRIORITY 0x1#define  SEM_DELETE_SAFE  0x4   // (互斥信号量用)#define  SEM_INVERSION_SAVE 0x8 // (互斥信号量用)

互斥信号量用于控制临界资源访问,初始时都假设资源可用,初始值都缺省为1,所以省略参数指定。创建了某种信号量后,不再区分信号量类型,都针对信号量标示符进行操作,统一操作接口由 semLib 库提供。 看看WindRiver 给出的 semLib 介绍。

semLib

NAME
semLib - general semaphore library

ROUTIENS

  • semGive() - give a semaphore
  • semTake() - take a semaphore
  • semFlush() - unblock every task pended on a semaphore
  • semDelete() - delete a semaphore

二进制信号量

二进制信号量的初始值只能为0或1,SEM_B_STATE 为两值为枚举类型(SEM_EMPTY(0), SEM_FULL(1))。可用于共享资源互斥和事件同步。由于VxWorks有专门的互斥信号量用于资源保护,二进制信号量多用于线程同步。

typedef enum {SEM_EMPTY, SEM_FULL} SEM_B_STATE;

使用二进制信号量,任务在等待事件时处于阻塞状态,不用消耗CPU时间来查询事件状态。事件由中断或任务异步触发。使用时,先用 semBCreate 创建信号量,初始值设为 SEM_EMPTY(0),以等待事件发生;用 semTake 来判断事件状态; 用 semGive 来通知事件发生。

当调用 semGive 时,如果有任务在等待这个信号量,则只有该信号量任务等待队列的第一个任务恢复到 ready 状态,信号量不可再用;如果没有任务等待这个信号量,则信号量转为可用状态。如果信号量已处于可用状态,再调用 semGive 不起作用。

当调用 semFlush 时,所有等待该信号量的任务都恢复到 ready 状态,但信号量的状态不变化。

调用 semTake 时,若信号量可用,调用立即返回,任务不会阻塞而继续运行,信号量变为不可用;若信号量不可用,任务就会阻塞在调用处等待,当信号量可用,任务变为 ready , semTake 调用返回OK, 任务继续运行。也可能信号量一直不可用,直到超时, semTake 调用则返回 ERROR.

来看看VxWorks库参考手册中说明中断和任务同步中二进制信号量的使用。

SEM_ID semSync;init(){    intConnect(.., event InterruptSvcRout, ..);    // 创建二进制信号量,等待任务按FIFO排列,初始值为0以等待事件发生    semSync = semBCreate( SEM_Q_FIFO, SEM_EMPTY );    taskSpawn( .., task1, ..);}task1()          // 事件处理任务1{    ..    semTake(semSync, WAIT_FOREVER);       // 等待事件发生,阻塞    ..                                    // 处理事件}eventInterruptSvcRout()                   // 事件通知中断函数{    ..    semGive(semSync);                     // 通知事件发生,task1由阻塞变为 ready    ..}

二进制信号量在事件处理方面有缺点,如不能等待多事件处理。信号量作为一个内核对象独立于任务存在,相比于直接与任务TCB关联的事件,效率有所降低。



互斥信号量

当多个任务分享一个公共资源(如数据结构、文件和硬件)时,可以使用互斥信号量,以防止多个任务同时访问临界资源。
使用时,先用 semMCreate 为资源创建互斥信号量,初始值缺省为 SEM_FULL(1), 表示资源可用;任务使用资源前,先调用 semTake 以获取使用权;使用使用完资源后,应调用 semGive 放弃使用权。
互斥信号量可以被嵌套获取。也就是拥有该信号量的任务可以多次调用 semTake, 而不会被阻塞。但放弃使用权时,任务必须调用同样数目的 semGive.


上面讲了读一个扇区数据的函数,现在来看看写一个扇区数据的函数。

// 函数名称:sdf_write_sector// 函数功能:写入一个扇区数据到SD卡设备STATUS sdf_write_sector(SD_DEV *pSDDev, U32 secaddrno, U8* buf){    int status;    int wt_cnt = 0;    U32 addr;    U32 *wdata = (U32*)buf;    if(NULL == v_pSDMMCregs)        return ERROR;    if((pSDDev->startBlkNum+pSDDev->sd_blkOffset+secaddrno) >         pSDDev->sd_blkdev.bd_nBlocks)        return ERROR;    addr = (pSDDev->startBlkNum + pSDDev->sd_blkOffset + secaddrno) *             pSDDev->sd_blkdev.bd_bytesPerBlk;    v_pSDMMCregs->rSDICON |= (1<<1);    v_pSDMMCregs->rSDIDATCON = (1<<20)|(1<<17)|(Wide<<16)|(3<<12)|(1<<0);    v_pSDMMCregs->rSDICMDARG = addr;REWTCMD:    v_pSDMMCregs->rSDICMDCON = (0x1<<9)|(0x1<<8)|0x58;    if(!Chk_CMDend(24,1))                              // CMD24 - block write        goto REWTCMD;    while(wt_cnt < 512/4)                              // 按字为单位统计    {        status = v_pSDMMCregs->rSDIFSTA;               // SDI FIFO status        if((status&0x2000) == 0x2000)        {            rSDIDAT = *wdata ++;                       // 32-bit 按字 自加            wt_cnt ++;        }    }    if(!Chk_DATend())  ;        // printf("\ndata error\n");    v_pSDMMCregs->rSDIDATSTA = 0x10;    return (OK);}
STATUS sdf_multiwrite(SD_DEV *pSDDev, U32 startblock, U32 blocknum, U8 *buf){    int  status;    semTake(pSDDev->sd_semMutex);                      // 获取信号量    while(blocknum > 0)    {        status = sdf_write_sector( pSDDev, startblock, buf );        semGive( pSDDev->sd_semMutex );                // 释放信号量        if(status == ERROR)             return ERROR;        startblock ++;        buf += pSDDev->sd_blkdev.bd_bytesPerBlk;        blocknum --;    }    return  status;}
LOCAL STATUS sdIoctl( SD_DEV psdDev, int function, int arg ){    FAST itn status;    switch(function)    {        case FIODISKFORMAT:            status = OK;            break;        default:            errnoSet( S_ioLib_UNKNOWN_REQUEST );            status = ERROR;    }    return (status);}

errnoLib

NAME
errnoLib - error status library
ROUTINES

  • errnoGet() - get the error status value of the calling task
  • errnoOfTaskGet() - get the error status value of a specified task
  • errnoSet() - set the error status value of the calling task
  • errnoOfSTaskSet() - set the error status value of a specified task
LOCAL STATUS sd_Reset( void ){    return (OK);}


接下来学习一个非常关键的函数,SD卡设备创建函数。

// 函数名称:sdDevCreate// 函数功能:创建SD卡设备BLK_DEV *sdDevCreate( unsigned int startBlk,                       unsigned int bytesPerBlk,                       unsigned int blksPerTrack,                      unsigned int nBlocks,                        unsigned int blkOffset,                        unsigned int disk_size ){    FAST  SD_DEV   *psdDev;                       // 函数内部创建局部变量:SD设备结构指针    FAST  BLK_DEV  *pBlkDev;    if(0 == bytesPerBlk) bytesPerBlk = MMC_SECTOR_SIZE;    if(0 == nBlocks)     nBlocks = disk_size;    if(0 == blksPerTrack)blksPerTrack = nBlocks;    psdDev = (SD_DEV*) malloc (sizeof(SD_DEV));    if(NULL == psdDev) return (NULL);             // 返回 BLK_DEV 类型的指针    psdDev->sd_semMutex = semBCreate(SEM_Q_PRIORITY, SEM_FULL); // 创建二进制信号量    pBlkDev = &pdsDev->sd_blkdev;                 // 由于设备结构第一个成员是块结构,这里强制类型转换    pBlkDev->bd_nBlocks     = nBlocks;    pBlkDev->bd_bytesPerBlk = bytesPerBlk;    pBlkDev->bd_blksPerTrack= blksPerTrack;    pBlkDev->bd_nHeads      = 1;    pBlkDev->bd_removable   = FALSE;    pBlkDev->bd_retry       = 1;    pBlkDev->bd_mode        = O_RDWR;    pBlkDev->bd_readyChanged= TRUE;    pBlkDev->bd_blkRd       = sdf_multiread;       // 接口函数指针初始化    pBlkDev->bd_blkWrt      = sdf_multiwrite;          pBlkDev->bd_ioctl       = sdIoctl;    pBlkDev->bd_reset       = sd_Reset;    pBlkDev->bd_statusChk   = NULL;                // 没有实现的接口指针要也要初始化为空 NULL    psdDev->startBlkNum  = startBlk;    psdDev->sd_blkOffset = blkOffset;    return (&psdDev->sd_blkdev);}

前面已经提到过了,SD卡在读的时候不能写,写的时候不能读,因此此函数创建SD卡设备的时候附带创建一个二进制信号量用来同步读写操作。只有信号量可用的时候,才能进行读或写。

// 函数名称:usrPartDiskFsInit// 函数功能:用户硬盘文件系统初始化 (Dos 文件系统)STATUS usrPartDiskFsInit( BLK_DEV *blkDevID ){    int   dcacheSize = 0x10000;    CBIO_DEV_IO      cbio, cbio1;    if( (cbio=dcacheDevCreate((DBIO_DEV_ID)blkDevID), NULL, dcacheSize, "/sd0"))== NULL)        return ERROR;    if(usrFdiskPartCreate(cbio, 1, 0, 0, 0) == ERROR)        return ERROR;    if( (cbio1=dpartDevCreate(cbio, 1, usrFdiskPartRead)) == NULL )        return ERROR;    if(dosFsDevCreate("/D", dpartPartGet(cbio1, 0), 8, 0) == ERROR)        return ERROR;    return OK;}
UINT32  sdDev(void){    UINT32    num;    num = InitSDMMC();                          // 初始化SD/MMC,返回SD卡容量大小    return num;}
void  sdDosFs(void){    BLK_DEV    *pDev;    unsigned int  num;    num = sdDev();    SdSize = num;    printf("d_size = 0x%x.\n", num);            // 串口打印初始化得到的SD卡容量大小    if(SdSize > 0)    {        pDev = sdDevCreate(0,0,0,0,0, num);        usrPartDiskFsInit(pDev);    }}

简单说一下最后三个函数。用户执行 sdDosFs 在SD卡上创建Dos文件系统。
sdDosFs() 首先调用 sdDev() 间接调用 InitSDMMC() 来初始化SD卡并获取卡容量,并保存在全局变量 SdSize 中;
若 SdSize 即卡容量大于0,则sdDosFs() 继续直接调用 sdDevCreate() 来在SD卡上创建块设备并保存在指针变量 pDev 中(创建块设备函数挂接用户准备的一系列(读、写、bd_ioctl、复位等) 接口函数)。
创建好块设备 pDev 后,再调用 usrPartDiskFsInit() 函数在块设备上创建 Dos 文件系统。


到这里,用S3C2410A驱动SD卡,在并SD卡上创建块设备,在块设备上创建Dos文件系统的整个驱动代码我们都分析完了。其中如何在块设备上创建Dos文件系统,我们并没有做分析。欲知如何创建文件系统,Dos文件系统、Tffs文件系统等,请听下回分解。下一节开始,我们介绍文件系统 dosFs, tffs, rawFs

1 0