SD3.0协议解读
一





lwj103862095 于2014-08-06 10:14:33发布 50487 收藏 34分类专栏: Linux存储技术相关版权Linux存储技术相关专栏收录该内容9 篇文章5 订阅订阅专栏
前言:
老衲我近期研究的是SD/MMC卡驱动,研究过的SD/MMC驱动的贫僧们都应该知道SD/MMC协议是必不可少的一部分,除非你不想研究透SD/MMC驱动,那你大可只研究driver/mmc/host目录下的文件即可。说到SD/MMC协议,网上一搜,SD3.0的协议只有英文版的资料,要想真正理解协议,英文水平差的贫僧就可吃力了,老衲英文水平实在是一般,但是网上对SD3.0协议的解读相关的中文资料实在是少的可怜。老衲怒想写写对SD3.0协议的理解,于是这一系列的SD3.0协议解读将会陆续问世.....
系统特征:
卡容量:
1.标准容量卡(SDSC):最大容量为2GB
2.高容量卡(SDHC):容量大小为2~32GB的卡
3.扩展容量卡(SDXC):容量大小为32GB~2TB的卡
问:这有什么用呢?在哪里会用到?
答:SD卡一般作为外置扩展容量,那么不同用户用的卡肯定不一样,那为了区分不同的卡,SD3.0协议中在初始化和识别卡的过程中会判断用户插入的卡是SDSC/SDHC/SDXC中的哪一种卡,比如在R3中的第38Bit的CCS = 0b时,表示插入的卡为SDSC卡,而CCS = 1b时,表示插入的卡为SDHC或者SDXC卡。不同的卡在Cammand和Response中有微小的区别,具体用到的时候再回头再说。
电压范围:
工作电压范围为:2.7~3.6V
问:告诉你电压范围有什么用呢?
答:卡的工作不是单边性的,它是和CPU理的卡控制器(host)之间互动的,那么host端也有自己支持的电压,card端也有自己支持的电压,而这二者支持的电压或许不一样。比如:host支持1.8~3.8V,而card支持2.7~3.6V,而协议就是完成取交集的作用,帮助host和card协调一个大家都支持的电压。
卡的属性:
卡可以设计为只读卡和可读/可写的卡
四线总线速率模式:

大家都应该看的懂什么意思吧?英文水平不至于比老衲还差吧?实在太差的就装个金山词霸呗~~这里要解释二个缩写,SDR的意思是Single Data Rate(单边数据采样,换句话说就是,要么上升沿采样,要么下降沿采样),DDR的意思是Double Data Rate(双边数据采样,换句话说,双边沿采样)。
注意了,这些总线速率模式是在四线的模式下才支持的,如果是1线的模式下,那可能就不一样了,而且并不是所有的host都支持所有的模式,比如并不是所有的CPU都支持SDR104模式,这个需要很强的IO输入输出能力,即便你的主频可以跑1.8GHz,但是到了SD卡这边的IO可不一定支持208MHz,这个具体要看CPU的spec。
好了,这篇就写这么多,注意了,老衲并不是单纯的将SD3.0的英文协议翻译过来,翻译只是对英文的解析,这并不是对协议的理解,这里是解读协议!
SD3.0协议解读二





lwj103862095 于2014-08-06 13:23:41发布 32805 收藏 15分类专栏: Linux存储技术相关版权Linux存储技术相关专栏收录该内容9 篇文章5 订阅订阅专栏
在阅读本文章之前,请先思考一下什么是总线,总线的作用是什么?相信大家都学过I2C总线,它由SCL和SDA两条线组成,通过这两条线就能完成各种通信。同样地,SD卡通信也需要有自己的总线模式。SD卡还比较牛逼,支持SD总线和SPI总线,老衲接触的比较多的是SD总线,所以这篇文章仅介绍SD总线,对于SPI总线老衲以后有机会再介绍。
SD总线:

大家都知道总线一般支持多种频率,在默认的频率下,SD总线支持一(主)对多(从)的模式,即支持一个HOST对多个SD卡的模式。但是,在高速和UHS-I,SD总线只能支持一对一的模式,即支持一个HOST对一个SD卡的模式。
问:那么对于一对多的模式,总线是如何找到相应的SD卡呢?
答:在SD卡初始化识别模式下,通过发送CMD3来获取RCA,即卡的逻辑地址,如果卡发现跟自己的逻辑地址是一致的,就会通过R6来响应。
SD总线协议:
SD总线是基于命令和数据的,开始于起始位,结束于结束位。Command和Response都是通过CMD线来传输的,Data是通过数据线来传输的。Command都是由主机(HOST)发起,Response都是由卡发出响应的。

数据的传输是以(Block)为单位的,数据块后面带有CRC校验位,图3-4描述了单块和多块的数据传输操作,多块数据传输通过Stop命令来结束数据传输操作。

因为HOST的速度总是比SD的速度要快,假如你往卡里面拼命写数据,这会出现什么问题?问题可大了,假如SD卡第一次都还没成功写完整,你就马上发第二次第三次的数据过来,那必将会造成数据的丢失,对吧。这就引申出了等待机制。HOST必须等待Card写完了,你才能继续发第二次数据传输。
那么,在SD总线的块写操作里,是通过Data0的信号状态指示卡是否Busy还是Ready的。

Command的内部编码格式:
Command命令都是由48bit组成的,每个命令的开始都是以'0'开始,以'1'结束的。每个命令都包含有CRC校验位,它的作用是,当数据传输发生错误时,可以重新重新发起操作。

Response的内部编码格式:
Response有四种编码模式,这取决于响应内容,长度为48bit或者136bit。由图3-7可知,当Response为R1、R3、R6时,响应长度为48bit,而Response为R2时,响应长度为136bit。并且在CMD线上传输都是先发送MSB,最后发送LSB,由于Command和Response都是在CMD线上传输的,所以它们都必须按照先发送MSB,最后发送LSB。这些规定都会在驱动里面被一个结构体封装掉,你知道是这么一回事就好了,不用特别在意。

SD卡的数据包格式:
1.Usual data (8 bit width):在字节之间先传输低字节后传输高字节,在字节里面先传输高位后传输低位。
2.Wide width data(SD卡寄存器):共有512bit,先传高位后传低位。
相应的数据格式这里就不贴图了,有兴趣的读者请查阅“Part_1_Physical_Layer_Specification_Ver3.00_Final_090416”的P28和P29,OK,这篇文章暂时聊到这里,精彩在后面哦。
SD3.0协议解读三





lwj103862095 于2014-08-06 15:11:53发布 49976 收藏 28分类专栏: Linux存储技术相关版权Linux存储技术相关专栏收录该内容9 篇文章5 订阅订阅专栏
SD卡功能描述
所有主机和SD卡间的通信都是由主机控制的,这和USB是一致的,例如:U盘并没有主动通知USB控制器的能力,USB鼠标也没有主动通知USB控制器的能力,当然,SD卡也是没有主动通知SD控制器的能力的。
主机发送的命令有两种,一种是一对多,另一种自然是一对一了,他们分别是:
1.广播命令:广播命令发送给所有挂在SD总线上的SD卡,有一些广播命令需要SD卡作出响应。
2.寻址(点对点)命令:寻址命令只发送给具有相应地址的卡,并需要找到的那张卡返回一个响应。
SD卡有两种模式,一种是卡识别模式,另一种是数据传输模式。
1.卡识别模式:在重置(reset)后,当主机查找总线上的新卡时,处于卡识别模式。重置后SD卡将始终处于该模式下,直到收到SEND_RCA命令(CMD3)。
2.数据传输模式:当卡收到RCA(CMD3)后,卡就会进入数据传输模式。
总的来说,卡二种操作模式,有10种状态,这两种操作模式十种状态贯穿了整个协议的精华,所以我们有必要来认识一下。

下面我们将对SD3.0的卡初始化识别模式的流程进行翻译,这段翻译比较枯燥无味,但是没办法,确实比较复杂,不仔细啃啃英文就难以理解这流程是怎么搞的。
卡识别模式:
在卡识别模式,主机重置所有处于卡识别模式的SD卡,检验操作电压范围,识别卡,并请求卡发送相对卡地址(RCA)。这些操作都是在各自的CMD线上完成的。在卡识别的过程中,卡的操作频率应该在Fod下。
卡复位:
发送GO_IDLE_STATE(CMD0)到SD卡后,除处于非活动状态(Inactive state)之外的SD卡都会进入空闲状态(Idle state);在Idle状态,SD卡的CMD线处于输入模式,默认相对地址为0x0000,默认驱动寄存器设定为最低速度,最大驱动电流能力。
工作条件检测:
在控制器和SD卡进行任何通信之前,控制器不清楚SD卡支持的工作电压范围,故而控制器首先使用默认的电压发送一条reset指令(CMD0),紧跟着的CMD8指令,用于取得SD卡支持工作电压范围数据。SD卡通过检测CMD8的参数部分来检查控制器使用的工作电压,控制器通过分析回传的CMD8参数部分来校验SD卡是否可以在所给电压下工作。如果SD卡可以在指定电压下工作,则它回送CMD8的命令响应字,其中包含check voltage, check pattern。如果SD卡不支持所给电压,则SD卡不会给出任何响应信息,并继续处于Idle状态。在PLV2.0(physical layer version2.0)下,在首次执行ACMD41之前,必须执行CMD8指令,用以初始化SDHC卡,SDHC卡根据是否接收到CMD8指令来鉴别控制器是否支持PLV2.0协议。使用低电压的控制器也必须在ACMD41命令之前发送CMD8,避免可以工作在两种电压模式下的SD卡因为没有接收到CMD8, 而默认工作在高电压环境下,被误认为是只支持高电压工作模式。 SD_SEND_OP_COND(ACMD41)命令的目的是给予SD卡控制器一个识别SD卡是否可以在所给Vdd范围下工作的机制,如果SD卡无法在指定Vdd范围内工作,则它会进入非活动状态(Inactive state)。要注意的是,ACMD41是应用相关型命令,因而,每次发出的ACMD41命令都必须紧跟在一条APP_CMD(CMD55)命令之后。在空闲态(Idle State)下使用的CMD55命令使用默认的卡相对地址(RCA)0x0000。
卡初始化和识别过程:
SD卡的初始化开始于接收到ACMD41指令之后,ACMD指令的HCS(Host Capacity Support)位如果设定为1的话,表明控制器支持SDHC卡,否则表示不支持。 在CMD8命令发送之后的ACMD41指令其功能有所扩展,在参数里多了HCS部分,在响应里面多了CCS(Card Capacity Status)部分。HCS参数会被不响应CMD8命令的SD卡所抛弃。控制器向不响应CMD8的卡发送ACMD41指令时,HCS位应该设置为零0。如果向SDHC卡发送HCS位为0的ACMD41命令,SDHC卡返回的响应,其busy标识位永远为0,代表忙状态。HCS标识位用来表明SD卡是否已经完成初始化,如果未完成,HCS为零,否则为1,如果HCS为0,控制器会重复发送ACMD41指令,SD卡只检查首次接收到的ACMD41指令的HCS位。 响应CMD8的SD卡发送的对于ACMD41指令响应会包含CCS部分,控制器只检查HCS标志位为1的响应所包含的CCS位。CCS=1表明其为SDHC卡,否则为标准SD卡。 控制器随后发送ALL_SEND_CID(CMD2)命令,查询各个卡的CID(unique card identification)值,还没有被识别的SD卡(处于Ready状态)会发送CID值作为响应,发送完CID值之后,SD卡进入识别状态(Identification state),然后控制器发送CMD3(SEND_RELATIVE_ADDR)命令,要求各个SD卡发送一个新的相对地址(RCA),RCA在之后的数据传输模式中用于寻址。RCA发送完之后。SD卡进入Stand-by状态,在这个状态,如果控制器想要给SD卡分配一个新的RCA,它可以发送另一条CMD3命令给SD卡。最后发布的RCA为SD卡的真实RCA。
卡的初始化和识别流程具体如下(SD模式):

总结一下流程:
当host上电后,使所有的卡设备处于卡识别模式,完成设置有效操作电压范围,卡识别和请求卡相对地址等操作。 1、发送指令CMD0使卡设备处于idle状态;
2、发送指令CMD8,如果卡设备有response,说明此卡为SD2.0以上;
3、发送指令CMD55+ACMD41,该指令是用来探测卡设备的工作电压是否符合host端的要求;在发送ACMD41这类指令之前需要先 发送CMD55指令,在SDIO中ACMD41指令被CMD5替代。
4、发送指令CMD11转换工作电压到1.8V;
5、发送指令CMD2获取CID;
6、发送指令CMD3获取RCA(relative card address
SD3.0协议解读四





lwj103862095 于2014-08-07 10:50:13发布 25255 收藏 9分类专栏: Linux存储技术相关版权Linux存储技术相关专栏收录该内容9 篇文章5 订阅订阅专栏
前面的文章提到过SD卡主要分为两个操作模式,一是初始化和识别操作模式,另一种就是这篇文章需要分析的数据传输模式啦。
数据传输模式:
数据传输模式主要有六种状态,分别是Stand-by状态、Transfer状态、Sending-data状态、Receive-data状态、Programming状态、Disconnect状态。这六种状态通过不同的Command就可以切换到某种状态,换句话说,这六种状态贯穿了整个数据传输模式。
要理解数据传输模式的流程,老衲认为除了理解这六种状态,还需要对Commands有一定的了解,越熟悉越好,当然,这并不是叫你去背Commands。好了,我们来看看数据传输模式的流程框图:

老衲我第一次看到这图的时候,简直看晕了,有木有!!太恶心了吧,谁定义的协议,敢不敢再复杂一点?没办法,做IT的就是苦逼,只能慢慢一点点啃,在理解流程之前,我们必须讲协议里的英文翻译一遍,然后结合流程图理解到底是怎么一回事,最后结合代码来看,这才能真正明白数据传输的过程是怎么一回事,下面是对英文协议里数据传输模式的翻译:
数据传输模式(翻译):
在SD卡识别模式结束之前,控制器使用的时钟频率均为Fod。在数据传输模式,控制器可能会使用Fpp频率。控制器发送一条SSEND_CSD(CMD9)命令来获取SD卡CSD寄存器(CardSpecificData)里面的描述值,譬如,块长度,卡容量信息等。广播命令SET_DSR(CMD4)配置所有识别卡的驱动段。对应于应用总线LayOut(长度),卡的数量和数据传输频率,这个命令设置DSR寄存器。时钟频率在那个点上也应该从Fod切换Fpp。SET_DSR命令对Host和卡都是可选的。
CMD7用来选择一个卡并将它置于传输状态(Transferstate),在任何时间只能有一个卡处于传输状态。如果已有一个卡处于传输状态,它和主机的连接将释放,并返回到Stand-by状态。当CMD7以保留相对地址“0x0000”发送时,所有卡将返回到Stand-by状态。这可以用来识别新的卡而不重置其他已注册的卡。在这种状态下已有一个RCA地址的卡不响应识别命令(ACMD41,CMD2,CMD3)。
数据传输模式下各个状态的转换关系总结如下:
·所有的数据读命令都可以被停止命令(CMD12)在任意时刻终止。数据传输会终止,SD卡返回Transfer状态。读命令有:块读操作(CMD17)、多块读操作(CMD18)、发送写保护(CMD30)、发送scr(ACMD51)以及读模式下的普通命令(CMD56)。
·所有的数据写命令都可以被停止命令(CMD12)在任意时刻终止。写命令也会在取消选择命令(CMD7)之前停止。写命令有:块写操作(CMD24,CMD25)、编程命令(CMD27)、锁定/解锁命令(CMD42)以及写模式下的普通命令(CMD56)。
·数据传输一旦完成,SD卡会退出数据写状态,进入Programming状态(传输成功)或者Transfer状态(传输失败)如果块写操作被叫停,但是写操作包含的最终块其长度和CRC校验是正确的话,数据会被编程到SD卡(从缓存写入到Flash)。
·卡可能提供块写缓冲。 这意味着在前一块数据被操作时,下一块数据可以传送给卡。如果所有卡写缓冲已满, 只要卡在 Programming State, DAT0 将保持低电平(BUSY)。
·写CSD、CID、写保护和擦除时没有缓冲。这表明在卡因这些命令而处于忙时,不再接收其他数据传输命令。 在卡忙时 DAT0 保持低电平, 并处于 Programming State。实际上如果 CMD 和 DAT0 线分离,而且主机占有的忙 DAT0 线和其他 DAT0 线分开,那么在卡忙时,主机可以访问其他卡。
·在卡被编程(programming)时,禁止参数设置命令。参数设置命令包括:设置块长度(CMD16),擦除块开始(CMD32)和擦除块结束(CMD33)。卡在操作时不允许读命令。 ·使用 CMD7 指令把另一个卡从 Stand-by 状态转移到 Transfer 状态不会中止擦除和编程(programming)操作。卡将切换到 Disconnect 状态并释放 DAT 线。 ·使用 CMD7 指令可以不选中处于 Disconnect 状态的卡。卡将进入 Programming 状态,重新激活忙指示。 ·使用 CMD0 或 CMD15 重置卡将中止所有挂起和活动的编程(programming)操作。这可能会破坏卡上的数据内容,需要主机保证避免这样的操作。CMD34-37 CMD50,CMD57保留。
看起来是挺晕的对吧,确实挺晕的,最好的办法就是结合代码去分析读写过程了,这个嘛,后面有机会再详细分析。)
好了,这篇文章就聊到这里,是否对SD卡的初始化和识别过程有了更清晰的了解呢?这只是协议上的流程,具体的代码实现我们后面再慢慢分析,千万不能急呀~~
SD/MMC/SDIO基础概念的介绍





lwj103862095 于2014-08-01 14:01:30发布 58070 收藏 36分类专栏: Linux存储技术相关版权Linux存储技术相关专栏收录该内容9 篇文章5 订阅订阅专栏
以下内容大多摘自网络,自己看了觉的不错,自己整理了一番。
1.1.什么是MMC卡
MMC:MMC就是MultiMediaCard的缩写,即多媒体卡。它是一种非易失性存储器件,体积小巧(24mm*32mm*1.4mm),容量大,耗电量低,传输速度快,广泛应用于消费类电子产品中。
1.2.什么是SD卡
SD:SD卡为Secure Digital Memory Card,即安全数码卡。它在MMC的基础上发展而来,增加了两个主要特色:SD卡强调数据的安全安全,可以设定所储存的 使用权限,防止数据被他人复制;另外一个特色就是传输速度比2.11版的MMC卡快。在数据传输和物理规范上,SD卡(24mm*32mm*2.1mm,比MMC卡更厚一点),向前兼容了MMC卡.所有支持SD卡的设备也支持MMC卡。SD卡和2.11版的MMC卡完全兼容。
1.3.什么是SDIO
SDIO:SDIO是在SD标准上定义了一种外设接口,它和SD卡规范间的一个重要区别是增加了低速标准。在SDIO卡只需要SPI和1位SD传输模式。低速卡的目标应用是以最小的硬件开销支持低速IO能力。
现在已经有非常多的手机或是手持装置都支持SDIO的功能(SD标准原本就是针对mobile device而制定),而且许多SDIO外围也都被开发出来,让手机外接外围更加容易,并且开发上更有弹性(不需要内建外围)。目前常见的SDIO外围(SDIO卡)有:
· Wi-Fi card(无线网络卡)
· CMOS sensor card(照相模块)
· GPS card
· GSM/GPRS modem card
· Bluetooth card
· Radio/TV card(很好玩)
SDIO的应用将是未来嵌入式系统最重要的接口技术之一,并且也会取代目前GPIO式的SPI接口。
1.4.什么是MCI
MCI:MCI是Multimedia Card Interface的简称,即多媒体卡接口。上述的MMC,SD,SDI卡定义的接口都属于MCI接口。MCI这个术语在驱动程序中经常使用,很多文件,函数名字都包括”mci”.
1.5.MMC/SD/SDIO卡的区别
1.5.SD/SDIO的传输模式
SD传输模式有以下3种:
· SPI mode(required)
· 1-bit mode
· 4-bit mode
SDIO同样也支持以上3种传输模式。依据SD标准,所有的SD(记忆卡)与SDIO(外围)都必须支持SPI mode,因此SPI mode是「required」。此外,早期的MMC卡(使用SPI传输)也能接到SD插糟(SD slot),并且使用SPI mode或1-bit mode来读取。
Secure digital I/Ocard,pin out
SD与MMC的区别





lwj103862095 于2014-08-01 13:05:45发布 32547 收藏 4分类专栏: Linux存储技术相关版权Linux存储技术相关专栏收录该内容9 篇文章5 订阅订阅专栏
该文章转自:http://www.imhan.com/archives/12/
经常看到SD/MMC这样的写法,在这里稍微总结一下SD卡和MMC卡的异同点吧。
首先,两者在外型的规格上是几乎一致的。而且两都的接口是兼容的。也就是说,两者可以用同一个卡座来进行读取。而且,两者在时序上也是一致的,读写命令控制也完全一样,这就是为什么经常把两者混在一起写的原因。
不过,虽说外型几乎一致,但还是有点差异的。MMC比SD卡要薄一些,并且,长度只有SD卡的一半。下面从主机驱动方面谈谈两者的差异。
一。在数据位宽方面,MMC卡最大支持8BIT,而SD卡只能支持4BIT传输。
二、在卡的激活过程,MMC使用CMD1来进行激活,而SD卡使用ACMD41来进行激活的。于是,这两条命令也成了主机区分两种卡的类型的关键。在获取卡的RCA地址时,MMC卡是由主机分配RCA给设备,而SD卡则是由设备返回RCA给主机。
三、在CMD6的使用方法上也有很大不同。MMC有EXT_CSD的概念,主要用CMD8进行读取,CMD6进行设置。而SD卡则只用CMD6进行UserFunction的设置。SD卡的CMD8主要用于区别SD1.0和SD2.0。
四、MMC还支持CMD14和CMD19进行主线测试,从而选择合适总线进行通信。SD则不支持。另外,MMC卡还支持CMD11、CMD20这类数据流操作,因此较多用于媒体设备上。MMC还支持Boot等高级的用法。
SD/MMC的Commands和Responses的总结





lwj103862095 于2014-08-01 17:35:02发布 22595 收藏 2分类专栏: Linux存储技术相关版权Linux存储技术相关专栏收录该内容9 篇文章5 订阅订阅专栏 SD总线通信是基于指令和数据比特流,起始位开始和停止位结束。SD总线通信有三个元素: 1.Command:由host发送到卡设备,使用CMD线发送;
2.Response:从card端发送到host端,作为对前一个CMD的相应,通过CMD线发送;
3.Data:即能从host传输到card,也能从card传输到host,通过data线传输。
一、Commands 以下是四种用于控制卡设备的指令类型,每个command都是固定的48位长度: 1、broadcast commands(bc), no response:广播类型的指令,不需要有响应; 2、broadcast commands with response(bcr):广播类型的指令且需要响应; 3、addressed(point-to-point) commands(ac):由HOST发送到指定的卡设备,没有数据的传输; 4、address(point-to-point) data transfercommands(adtc):由HOST发送到指定的卡设备且伴随有数据传输。
Command format如下:
Byte1:0 1 x x x x x x(命令号,由指令标志定义CMD39为100111即16进制0x27,那么完整的CMD39第一字节为01100111,即0x27+0x40)。 Byte2-5:Command Arguments,命令参数,有些命令没有参数。 Byte6:前7位为CRC(Cyclic Redundacy Check,循环冗余校验)校验位,最后一位为停止位0。
其中bit[45:40]共6bit组成了Command index,换句话说,总共有2^6次方(64)个CMD,这些CMD伴随在卡的初始化、识别、读写、擦除数据都会用到。
关于命令的详细描述参考《Part_1_Physical_Layer_Specification_Ver3.00_Final_090416》的4.7.4 Detailed Command Description,在P88可以找到相应的CMD的详细介绍。
二、Responses 所有的response都通过CMD线发送到host端,R4和R5响应类型是SDIO中特有的: 1、R1(normal response command):用来响应常用指令; 2、R2(CID,CSD register):用来响应CMD2和CMD10或CMD9,并把CID或CSD寄存器作为响应数据; 3、R3(OCR register):用来响应ACMD41指令,并把OCR寄存器作为响应数据; 4、R6(published RCA response):分配相对卡地址的响应; 5、R7(card interface condition):响应CMD8,返回卡支持的电压信息; 6、R4(CMD5):响应CMD5,并把OCR寄存器作为响应数据; 7、R5(CMD52):CMD52是一个读写寄存器的指令,R5用于CMD52的响应;
Response的格式如下:
关于响应的详细描述参考《Part_1_Physical_Layer_Specification_Ver3.00_Final_090416》的4.9 Responses 在P98可以找到相应的Responses的详细介绍。
SD/MMC相关寄存器的介绍





lwj103862095 于2014-08-01 15:15:54发布 38862 收藏 12分类专栏: Linux存储技术相关版权Linux存储技术相关专栏收录该内容9 篇文章5 订阅订阅专栏
熟悉SD/MMC的相关寄存器对协议的理解有一定的辅助作用,所以这篇文章来介绍一下SD/MMC相关的寄存器有哪些呢?
1.SD卡内部架构
在熟悉SD/MMC相关寄存器之前,我们先来看看SD卡的内部架构是怎么样的,如下图所示:

2.SD/MMC相关寄存器的介绍

从上图中总结出:SD卡内部有7个寄存器.
一、OCR,CID,CSD和SCR寄存器保存卡的配置信息;
二、RCA寄存器保存着通信过程中卡当前暂时分配的地址(只适合SD模式);
三、CSR寄存器卡状态(Card Status)和SSR寄存器SD状态(SD Status)寄存器保存着卡的状态(例如,是否写成功,通信的CRC校验是否正确等),这两个寄存器的内容与通信模式(SD模式或SPI模式)相关.
四、MMC卡没有SCR和SSR寄存器.
下面分别对7个寄存器中比较重要的寄存器详细解释一下,分别是CID、CSD、SCR、OCR、RCA这5个寄存器。
2.1.Card Identification Register(CID)
这个 CID 寄存器有 16 字节长,如下表所示,它包含了本卡的特别识别码(ID 号)。 这些信息是在卡的生产期间被编程(烧录),主控制器不 能修改它们的内容。 注意:SD卡的 CID 寄存器和 MMC 卡的 CID 寄存器在记录结构上是不同的。

2.2.Card Specific Data Register(CSD)
这个描述数据寄存器(CSD)有 128 字节长,如下表所示,此卡的包含了访问该卡数据时的必要配置信息。“cell type”栏内定义了CSD的区域是只读(R)、一次编程(R/W)或可擦除的(R/W/E)[“R/W”是指可以多次擦写,“R/W(1)”是指只能一次写入,不可擦除]。该张表中所显示的值都对应真实的CSD结构中的各自区域和编码。CSD区域的样式是依照栏标记(和一个复选标记√)的样式。注意SD卡内的 CSD寄存器和MultiMedia卡的CSD寄存器有着不同的结构。
在SD3.0协议中,CSD分为版本1.0和版本2.0,版本1.0对应标准容量的SD卡,版本2.0对应高容量和超高容量的SD卡。

CSD Version 2.0的如下:

2.3.SD card Configuration Register (SCR)
除了 CSD 寄存器外,还有一个配置寄存器的名字是:SD 卡配置寄存器(SCR)。SCR 提供了SD 卡的一些特殊特性在这张卡内。它的大小是64 位。这个寄存器内容由制造商在生产厂内设置,MMC卡没有SCR。

SCR_STRUCTURE关于SD卡内的物理级说明中SCR结构的版本号。
SD_SPEC描述这张SD卡在物理级上所支持的说明版本。
DATA_STAT_AFTER_ERASE定义了数据在擦除后的状态。是“0”或“1”中的任何一个(这要依赖卡的供应商)。
SD_SECURITY描述了该卡所支持的安全算法。0:无 1:安全协议1.0 安全说明版本 0.96 2:安全协议2.0 安全说明版本 1.0 - 1.01。其他保留
SD_BUS_WIDTHS描述该卡所支持的所有数据总线宽度。从SD 卡支持最少1 位或4 位宽度这两种总线模式开始,任何SD 卡都将最少要设置0 和2 这两个位(即SD_BUS_WIDTH = 0101 ),1.4位保留。
2.4.Operating Conditions Register (OCR)
这个 32 位的工作条件寄存器储存了卡的 VDD 电压轮廓图。任何标准的 SD 卡主控制器可以使用 2V 至 3.6V 的工作电压来让 SD 卡能执行这个电压识别操作(CMD1)。而访问存储器的阵列操作无论如何都需要 2.7V 至 3.6V 的工作电压。OCR 寄存器显示了在访问卡的数据时所需要的电压范围。OCR 寄存器的结构描述:

2.5.RCA寄存器 该16位卡地址寄存器保存了在卡识别过程中卡发布的器件地址。该地址用于在卡识别后主机利用该地址与卡进行通信。该寄存器只有在SD总线模式下才有效。
Read 系统调用在用户空间中的处理过程
Linux系统调用(SCI,system call interface)的实现机制实际上是一个多路汇聚以及分解的过程,该汇聚点就是 0x80 中断这个入口点(X86 系统结构)。也就是说,所有系统调用都从用户空间中汇聚到 0x80 中断点,同时保存具体的系统调用号。当 0x80 中断处理程序运行时,将根据系统调用号对不同的系统调用分别处理(调用不同的内核函数处理)。系统调用的更多内容,请参见参考资料。
Read 系统调用也不例外,当调用发生时,库函数在保存 read 系统调用号以及参数后,陷入 0x80 中断。这时库函数工作结束。Read 系统调用在用户空间中的处理也就完成了。
Read 系统调用在核心空间中的处理过程
0x80 中断处理程序接管执行后,先检察其系统调用号,然后根据系统调用号查找系统调用表,并从系统调用表中得到处理 read 系统调用的内核函数 sys_read ,最后传递参数并运行 sys_read 函数。至此,内核真正开始处理 read 系统调用(sys_read 是 read 系统调用的内核入口)。
在讲解 read 系统调用在核心空间中的处理部分中,首先介绍了内核处理磁盘请求的层次模型,然后再按该层次模型从上到下的顺序依次介绍磁盘读请求在各层的处理过程。
Read 系统调用在核心空间中处理的层次模型
图1显示了 read 系统调用在核心空间中所要经历的层次模型。从图中看出:对于磁盘的一次读请求,首先经过虚拟文件系统层(vfs layer),其次是具体的文件系统层(例如 ext2),接下来是 cache 层(page cache 层)、通用块层(generic block layer)、IO 调度层(I/O scheduler layer)、块设备驱动层(block device driver layer),最后是物理块设备层(block device layer)
图1 read 系统调用在核心空间中的处理层次
- 虚拟文件系统层的作用:屏蔽下层具体文件系统操作的差异,为上层的操作提供一个统一的接口。正是因为有了这个层次,所以可以把设备抽象成文件,使得操作设备就像操作文件一样简单。
- 在具体的文件系统层中,不同的文件系统(例如 ext2 和 NTFS)具体的操作过程也是不同的。每种文件系统定义了自己的操作集合。关于文件系统的更多内容,请参见参考资料。
- 引入 cache 层的目的是为了提高 linux 操作系统对磁盘访问的性能。 Cache 层在内存中缓存了磁盘上的部分数据。当数据的请求到达时,如果在 cache 中存在该数据且是最新的,则直接将数据传递给用户程序,免除了对底层磁盘的操作,提高了性能。
- 通用块层的主要工作是:接收上层发出的磁盘请求,并最终发出 IO 请求。该层隐藏了底层硬件块设备的特性,为块设备提供了一个通用的抽象视图。
- IO 调度层的功能:接收通用块层发出的 IO 请求,缓存请求并试图合并相邻的请求(如果这两个请求的数据在磁盘上是相邻的)。并根据设置好的调度算法,回调驱动层提供的请求处理函数,以处理具体的 IO 请求。
- 驱动层中的驱动程序对应具体的物理块设备。它从上层中取出 IO 请求,并根据该 IO 请求中指定的信息,通过向具体块设备的设备控制器发送命令的方式,来操纵设备传输数据。
- 设备层中都是具体的物理设备。定义了操作具体设备的规范。
相关的内核数据结构:
- Dentry : 联系了文件名和文件的 i 节点
- inode : 文件 i 节点,保存文件标识、权限和内容等信息
- file : 保存文件的相关信息和各种操作文件的函数指针集合
- file_operations :操作文件的函数接口集合
- address_space :描述文件的 page cache 结构以及相关信息,并包含有操作 page cache 的函数指针集合
- address_space_operations :操作 page cache 的函数接口集合
- bio : IO 请求的描述
数据结构之间的关系:
图2示意性地展示了上述各个数据结构(除了 bio)之间的关系。可以看出:由 dentry 对象可以找到 inode 对象,从 inode 对象中可以取出 address_space 对象,再由 address_space 对象找到 address_space_operations 对象。
File 对象可以根据当前进程描述符中提供的信息取得,进而可以找到 dentry 对象、 address_space 对象和 file_operations 对象。
图2 数据结构关系图:
前提条件:
对于具体的一次 read 调用,内核中可能遇到的处理情况很多。这里举例其中的一种情况:
- 要读取的文件已经存在
- 文件经过 page cache
- 要读的是普通文件
- 磁盘上文件系统为 ext2 文件系统,有关 ext2 文件系统的相关内容,参见参考资料
准备:
注:所有清单中代码均来自 linux2.6.11 内核原代码
读数据之前,必须先打开文件。处理 open 系统调用的内核函数为 sys_open 。 所以我们先来看一下该函数都作了哪些事。清单1显示了 sys_open 的代码(省略了部分内容,以后的程序清单同样方式处理)
清单1 sys_open 函数代码
asmlinkage long sys_open(const char __user * filename, int flags, int mode) { …… fd = get_unused_fd(); if (fd >= 0) { struct file *f = filp_open(tmp, flags, mode); fd_install(fd, f); } …… return fd; …… }
代码解释:
- get_unuesed_fd() :取回一个未被使用的文件描述符(每次都会选取最小的未被使用的文件描述符)。
- filp_open() :调用 open_namei() 函数取出和该文件相关的 dentry 和 inode (因为前提指明了文件已经存在,所以 dentry 和 inode 能够查找到,不用创建),然后调用 dentry_open() 函数创建新的 file 对象,并用 dentry 和 inode 中的信息初始化 file 对象(文件当前的读写位置在 file 对象中保存)。注意到 dentry_open() 中有一条语句:
f->f_op = fops_get(inode->i_fop);
这个赋值语句把和具体文件系统相关的,操作文件的函数指针集合赋给了 file 对象的 f _op 变量(这个指针集合是保存在 inode 对象中的),在接下来的 sys_read 函数中将会调用 file->f_op 中的成员 read 。
- fd_install() :以文件描述符为索引,关联当前进程描述符和上述的 file 对象,为之后的 read 和 write 等操作作准备。
- 函数最后返回该文件描述符。
图3显示了 sys_open 函数返回后, file 对象和当前进程描述符之间的关联关系,以及 file 对象中操作文件的函数指针集合的来源(inode 对象中的成员 i_fop)。
图3 file 对象和当前进程描述符之间的关系
到此为止,所有的准备工作已经全部结束了,下面开始介绍 read 系统调用在图1所示的各个层次中的处理过程。
虚拟文件系统层的处理:
内核函数 sys_read() 是 read 系统调用在该层的入口点,清单2显示了该函数的代码。
清单2 sys_read 函数的代码
asmlinkage ssize_t sys_read(unsigned int fd, char __user * buf, size_t count) { struct file *file; ssize_t ret = -EBADF; int fput_needed; file = fget_light(fd, &fput_needed); if (file) { loff_t pos = file_pos_read(file); ret = vfs_read(file, buf, count, &pos); file_pos_write(file, pos); fput_light(file, fput_needed); } return ret; }
代码解析:
- fget_light() :根据 fd 指定的索引,从当前进程描述符中取出相应的 file 对象(见图3)。
- 如果没找到指定的 file 对象,则返回错误
- 如果找到了指定的 file 对象:
- 调用 file_pos_read() 函数取出此次读写文件的当前位置。
- 调用 vfs_read() 执行文件读取操作,而这个函数最终调用 file->f_op.read() 指向的函数,代码如下:
if (file->f_op->read) ret = file->f_op->read(file, buf, count, pos);
- 调用 file_pos_write() 更新文件的当前读写位置。
- 调用 fput_light() 更新文件的引用计数。
- 最后返回读取数据的字节数。
到此,虚拟文件系统层所做的处理就完成了,控制权交给了 ext2 文件系统层。
在解析 ext2 文件系统层的操作之前,先让我们看一下 file 对象中 read 指针来源。
File 对象中 read 函数指针的来源:
从前面对 sys_open 内核函数的分析来看, file->f_op 来自于 inode->i_fop 。那么 inode->i_fop 来自于哪里呢?在初始化 inode 对象时赋予的。见清单3。
清单3 ext2_read_inode() 函数部分代码
void ext2_read_inode (struct inode * inode) { …… if (S_ISREG(inode->i_mode)) { inode->i_op = &ext2_file_inode_operations; inode->i_fop = &ext2_file_operations; if (test_opt(inode->i_sb, NOBH)) inode->i_mapping->a_ops = &ext2_nobh_aops; else inode->i_mapping->a_ops = &ext2_aops; } …… }
从代码中可以看出,如果该 inode 所关联的文件是普通文件,则将变量 ext2_file_operations 的地址赋予 inode 对象的 i_fop 成员。所以可以知道: inode->i_fop.read 函数指针所指向的函数为 ext2_file_operations 变量的成员 read 所指向的函数。下面来看一下 ext2_file_operations 变量的初始化过程,如清单4。
清单4 ext2_file_operations 的初始化
struct file_operations ext2_file_operations = { .llseek = generic_file_llseek, .read = generic_file_read, .write = generic_file_write, .aio_read = generic_file_aio_read, .aio_write = generic_file_aio_write, .ioctl = ext2_ioctl, .mmap = generic_file_mmap, .open = generic_file_open, .release = ext2_release_file, .fsync = ext2_sync_file, .readv = generic_file_readv, .writev = generic_file_writev, .sendfile = generic_file_sendfile, };
该成员 read 指向函数 generic_file_read 。所以, inode->i_fop.read 指向 generic_file_read 函数,进而 file->f_op.read 指向 generic_file_read 函数。最终得出结论: generic_file_read 函数才是 ext2 层的真实入口。
Ext2 文件系统层的处理
图4 read 系统调用在 ext2 层中处理时函数调用关系
由图 4 可知,该层入口函数 generic_file_read 调用函数 __generic_file_aio_read ,后者判断本次读请求的访问方式,如果是直接 io (filp->f_flags 被设置了 O_DIRECT 标志,即不经过 cache)的方式,则调用 generic_file_direct_IO 函数;如果是 page cache 的方式,则调用 do_generic_file_read 函数。函数 do_generic_file_read 仅仅是一个包装函数,它又调用 do_generic_mapping_read 函数。
在讲解 do_generic_mapping_read 函数都作了哪些工作之前,我们再来看一下文件在内存中的缓存区域是被怎么组织起来的。
文件的 page cache 结构
图5显示了一个文件的 page cache 结构。文件被分割为一个个以 page 大小为单元的数据块,这些数据块(页)被组织成一个多叉树(称为 radix 树)。树中所有叶子节点为一个个页帧结构(struct page),表示了用于缓存该文件的每一个页。在叶子层最左端的第一个页保存着该文件的前4096个字节(如果页的大小为4096字节),接下来的页保存着文件第二个4096个字节,依次类推。树中的所有中间节点为组织节点,指示某一地址上的数据所在的页。此树的层次可以从0层到6层,所支持的文件大小从0字节到16 T 个字节。树的根节点指针可以从和文件相关的 address_space 对象(该对象保存在和文件关联的 inode 对象中)中取得(更多关于 page cache 的结构内容请参见参考资料)。
图5 文件的 page cache 结构
现在,我们来看看函数 do_generic_mapping_read 都作了哪些工作, do_generic_mapping_read 函数代码较长,本文简要介绍下它的主要流程:
- 根据文件当前的读写位置,在 page cache 中找到缓存请求数据的 page
- 如果该页已经最新,将请求的数据拷贝到用户空间
- 否则, Lock 该页
- 调用 readpage 函数向磁盘发出添页请求(当下层完成该 IO 操作时会解锁该页),代码:
error = mapping->a_ops->readpage(filp, page);
- 再一次 lock 该页,操作成功时,说明数据已经在 page cache 中了,因为只有 IO 操作完成后才可能解锁该页。此处是一个同步点,用于同步数据从磁盘到内存的过程。
- 解锁该页
- 到此为止数据已经在 page cache 中了,再将其拷贝到用户空间中(之后 read 调用可以在用户空间返回了)
到此,我们知道:当页上的数据不是最新的时候,该函数调用 mapping->a_ops->readpage 所指向的函数(变量 mapping 为 inode 对象中的 address_space 对象),那么这个函数到底是什么呢?
Readpage 函数的由来
address_space 对象是嵌入在 inode 对象之中的,那么不难想象: address_space 对象成员 a_ops 的初始化工作将会在初始化 inode 对象时进行。如清单3中后半部所显示。
if (test_opt(inode->i_sb, NOBH)) inode->i_mapping->a_ops = &ext2_nobh_aops; else inode->i_mapping->a_ops = &ext2_aops;
可以知道 address_space 对象的成员 a_ops 指向变量 ext2_aops 或者变量 ext2_nobh_aops 。这两个变量的初始化如清单5所示。
清单5 变量 ext2_aops 和变量 ext2_nobh_aops 的初始化
struct address_space_operations ext2_aops = { .readpage = ext2_readpage, .readpages = ext2_readpages, .writepage = ext2_writepage, .sync_page = block_sync_page, .prepare_write = ext2_prepare_write, .commit_write = generic_commit_write, .bmap = ext2_bmap, .direct_IO = ext2_direct_IO, .writepages = ext2_writepages, }; struct address_space_operations ext2_nobh_aops = { .readpage = ext2_readpage, .readpages = ext2_readpages, .writepage = ext2_writepage, .sync_page = block_sync_page, .prepare_write = ext2_nobh_prepare_write, .commit_write = nobh_commit_write, .bmap = ext2_bmap, .direct_IO = ext2_direct_IO, .writepages = ext2_writepages, };
从上述代码中可以看出,不论是哪个变量,其中的 readpage 成员都指向函数 ext2_readpage 。所以可以断定:函数 do_generic_mapping_read 最终调用 ext2_readpage 函数处理读数据请求。
到此为止, ext2 文件系统层的工作结束。
Page cache 层的处理
从上文得知:ext2_readpage 函数是该层的入口点。该函数调用 mpage_readpage 函数,清单6显示了 mpage_readpage 函数的代码。
清单6 mpage_readpage 函数的代码
int mpage_readpage(struct page *page, get_block_t get_block) { struct bio *bio = NULL; sector_t last_block_in_bio = 0; bio = do_mpage_readpage(bio, page, 1, &last_block_in_bio, get_block); if (bio) mpage_bio_submit(READ, bio); return 0; }
该函数首先调用函数 do_mpage_readpage 函数创建了一个 bio 请求,该请求指明了要读取的数据块所在磁盘的位置、数据块的数量以及拷贝该数据的目标位置——缓存区中 page 的信息。然后调用 mpage_bio_submit 函数处理请求。 mpage_bio_submit 函数则调用 submit_bio 函数处理该请求,后者最终将请求传递给函数 generic_make_request ,并由 generic_make_request 函数将请求提交给通用块层处理。
到此为止, page cache 层的处理结束。
通用块层的处理
generic_make_request 函数是该层的入口点,该层只有这一个函数处理请求。清单7显示了函数的部分代码
清单7 generic_make_request 函数部分代码
void generic_make_request(struct bio *bio) { …… do { char b[BDEVNAME_SIZE]; q = bdev_get_queue(bio->bi_bdev); …… block_wait_queue_running(q); /* * If this device has partitions, remap block n * of partition p to block n+start(p) of the disk. */ blk_partition_remap(bio); ret = q->make_request_fn(q, bio); } while (ret); }
主要操作:
- 根据 bio 中保存的块设备号取得请求队列 q
- 检测当前 IO 调度器是否可用,如果可用,则继续;否则等待调度器可用
- 调用 q->make_request_fn 所指向的函数将该请求(bio)加入到请求队列中
到此为止,通用块层的操作结束。
IO 调度层的处理
对 make_request_fn 函数的调用可以认为是 IO 调度层的入口,该函数用于向请求队列中添加请求。该函数是在创建请求队列时指定的,代码如下(blk_init_queue 函数中):
q->request_fn = rfn; blk_queue_make_request(q, __make_request);
函数 blk_queue_make_request 将函数 __make_request 的地址赋予了请求队列 q 的 make_request_fn 成员,那么, __make_request 函数才是 IO 调度层的真实入口。
__make_request 函数的主要工作为:
- 检测请求队列是否为空,若是,延缓驱动程序处理当前请求(其目的是想积累更多的请求,这样就有机会对相邻的请求进行合并,从而提高处理的性能),并跳到3,否则跳到2
- 试图将当前请求同请求队列中现有的请求合并,如果合并成功,则函数返回,否则跳到3
- 该请求是一个新请求,创建新的请求描述符,并初始化相应的域,并将该请求描述符加入到请求队列中,函数返回
将请求放入到请求队列中后,何时被处理就由 IO 调度器的调度算法决定了(有关 IO 调度器的算法内容请参见参考资料)。一旦该请求能够被处理,便调用请求队列中成员 request_fn 所指向的函数处理。这个成员的初始化也是在创建请求队列时设置的:
q->request_fn = rfn; blk_queue_make_request(q, __make_request);
第一行是将请求处理函数 rfn 指针赋给了请求队列的 request_fn 成员。而 rfn 则是在创建请求队列时通过参数传入的。
对请求处理函数 request_fn 的调用意味着 IO 调度层的处理结束了。
块设备驱动层的处理
request_fn 函数是块设备驱动层的入口。它是在驱动程序创建请求队列时由驱动程序传递给 IO 调度层的。
IO 调度层通过回调 request_fn 函数的方式,把请求交给了驱动程序。而驱动程序从该函数的参数中获得上层发出的 IO 请求,并根据请求中指定的信息操作设备控制器(这一请求的发出需要依据物理设备指定的规范进行)。
到此为止,块设备驱动层的操作结束。
块设备层的处理
接受来自驱动层的请求,完成实际的数据拷贝工作等等。同时规定了一系列规范,驱动程序必须按照这个规范操作硬件。
后续工作
当设备完成了 IO 请求之后,通过中断的方式通知 cpu ,而中断处理程序又会调用 request_fn 函数进行处理。
当驱动再次处理该请求时,会根据本次数据传输的结果通知上层函数本次 IO 操作是否成功,如果成功,上层函数解锁 IO 操作所涉及的页面(在 do_generic_mapping_read 函数中加的锁)。
该页被解锁后, do_generic_mapping_read() 函数就可以再次成功获得该锁(数据的同步点),并继续执行程序了。之后,函数 sys_read 可以返回了。最终 read 系统调用也可以返回了。
至此, read 系统调用从发出到结束的整个处理过程就全部结束了。
总结
本文介绍了 linux 系统调用 read 的处理全过程。该过程分为两个部分:用户空间的处理和核心空间的处理。在用户空间中通过 0x80 中断的方式将控制权交给内核处理,内核接管后,经过6个层次的处理最后将请求交给磁盘,由磁盘完成最终的数据拷贝操作。在这个过程中,调用了一系列的内核函数。如图 6
图6 read 系统调用在内核中所经历的函数调用层次
























































