IDE接口和SD卡驱动程序移植
嵌入式Linux设备驱动开发之IDE接口和SD卡驱动程序移植
《嵌入式Linux应用完全开发手册》第4篇第23章总结归纳
本章目标
- 了解IDE接口驱动程序的框架,掌握移植方法
- 了解硬盘、光盘等块设备的使用方法
- 了解MMC/SD卡驱动程序的框架
- 掌握通过补丁文件移植驱动程序的方法
IDE接口驱动程序移植
IDE接口相关概念介绍
IDE的英文全称为“Integrated Driver Electronics”,即“电子集成驱动器”,它的本意是指把“硬盘控制器”与“盘体(存储部件)”集成在一起的硬盘驱动器。IDE代表着硬盘的一种类型,但是实际的应用中,人们也习惯用IDE来称呼最早出现IDE类型的硬盘ATA-1,这种类型的接口随着技术的发展已经被淘汰了,而其后发展的ATA-2、ATA-3等更高版本的接口规范。
IDE硬盘又称为并口硬盘(与下面介绍的SATA接口硬盘相对),它的已经接口有两种:台式机中使用的40脚IDE接口、笔记本中使用的44脚接口(其中的40个引脚是一样的)。从外形看,台式机中的硬盘比较大,为3.5英寸,40个引脚的旁边还有4个很大的引脚,它们用来接12V、5V电源;笔记本中的硬盘比较小,为2.5英寸,电源等就在多出来的4个引脚中。
ATA:ATA(AT Attachment)是一个20世纪80年代由一些软硬件厂家制定的IDE驱动器接口规范,AT是指IBM PC/AT个人电脑及其总线结构。通常人们也把ATA接口称为IDE接口,但是实际上两者有着细微的区别,ATA主要指硬盘驱动器与计算机的连接规范,而IDE则主要是指硬盘驱动器本身的技术规范。经过多年发展,ATA规范逐渐升级,访问硬盘的速度逐渐提高。它们的特点简要介绍如下:
- ATA-1: 这是最初的IDE标准,ATA-1主板上只有一个插口,支持1个主设备和1个从设备,每个设备的最大容量为504MB。ATA-1支持PIO-0、PIO-1和PIO-2共3种PIO模式,传输速率只有3.3MB/s;另外还支持4种DMA模式。这种标准的硬盘在市场上基本已经看不到了。
- ATA-2:它是ATA-1的扩展,也称为EIDE(Enhanced IDE)或Fast ATA。它在ATA的基础上增加了两种PIO模式和两种DMA模式,最高传输速率达到16.7MB/s。同时引进了LBA(Logical Block Address)地址转换方式,原来的地址转换方式为CHS(Cylinder Head Sector)。主板上有两个插口,每个插口可以连接1个主设备和1个从设备,共可以支持4个设备。
- ATA-3:它没有引进更高的传输模式,在传输速度上没有任何提升。最重要的是引入了一个划时代的技术–SMART技术(Self-Monitoring Analysis and Reporting Technology),自检测、分析和报告技术。
- ATA-4:也称Ultra DMA 33或ATA33,从它开始正式支持Ultra DMA数据传输模式,增加了PIO-4传输模式,传输速率达到33MB/s。它首次采用了Double Data Rate技术,让接口在一个时钟周期内传输数据达到两次(上升沿和下降沿各一次),这样数据传输率从16.7MB/s提升到33MB/s。
- ATA-5:也称Ultra DMA 66或ATA66,传输速率达到66.6MB/s。从ATA-5开始,为防止电磁干扰,硬盘的连线开始使用40针脚80芯的电缆,就是说其中的信号线仍是40根,这与以前的接口兼容,新增的40根都是地线。打开机箱,40针脚80芯的电缆与原来的电缆相比,显得更细更密,数一下排线的数目,会发现是80根。
- ATA-6:也称ATA100,它也是使用40针脚80芯的电缆,传输速率达到100MB/s。这是目前市场上主流的IDE接口硬盘。
- ATA-7:也称ATA133,它是ATA接口的最后一个版本,传输速率达到133MB/s。只有迈拓公司推出了ATA133标准的硬盘,其他厂商则停止了对IDE接口的开发,转而生产Serial ATA接口标准的硬盘。
ATAPI:AT Attachment Packet Interface,AT附加分组接口。ATA可以使用户在PC机器上连接硬盘,但是有时这样还不够。有些用户需要通过同样方便的手段连接CDROM、磁带机、MO驱动器等设备。ATAPI标准就是为了解决在IDE/EIDE接口上连接多种设备而制定的。支持ATAPI的IDE/EIDE接口可以像连接硬盘一样连接ATAPI设备。目前几乎所有的IDE/EIDE接口都支持ATAPI。ATAPI是一个软件接口,它将SCSI/ASPI命令调整到ATA接口上,这使得光驱制造商能比较容易的将其高端的CD/DVD驱动器产品调整到ATA接口上。以光驱为例,它可以像硬盘一样接在任何一个IDE接口上,但是它的驱动程序与IDE硬盘不同。
SATA和PATA:两个都是ATA规范,PATA的全称是ParallelATA,就是并行ATA硬盘接口规范,即ATA-1、ATA-2等。PATA硬盘接口规模已经具有相当的辉煌历史了,而且从ATA33/66一直发展到ATA100/133。而SATA硬盘全称则是SerialATA,即串行ATA硬盘接口规范。当硬盘的访问速度进一步提高时,并行接口的电缆属性、连接器和信号协议都表现出了很大的技术瓶颈,在技术上突破这些瓶颈存在相当大的难度。SATA的出现就是为了取代PATA。第一代的SATA硬盘的写入速度为150MB/s,第二代的SATA硬盘的写入速度则高达300MB/s,比第一代的速度提高了1倍。SATA除了速度更快之外,另一个进步在于它的数据连线,它的体积更小,散热也更好,与硬盘的连接相当方便。与PATA相比,SATA的功耗更低,这对于笔记本而言是一个好消息,同时独有的CRC技术让数据传输也更为安全。
IDE接口驱动移植
IDE接口驱动程序框架及源码分析
IDE接口驱动程序框架
PC机上最多可以有主、次两个IDE接口,每个IDE接口又可以支持主、从共两个IDE硬盘,所以最多可以有4个IDE硬盘。
内核有个数组ide_hwifs[],数组的每个元素都是一个ide_hwif_t数组结构,代表着系统中的一个可能的IDE接口。系统初始化时如果检测到一个IDE接口,就把相应表项中的noprobe字段设置成0,表示后面要通过这个接口来检测上面是否连接了硬盘,这称为IDE枚举。
同时,ide_hwif_t数据结构中又有个ide_drive_t结构数组drivers[]。IDE枚举时如果检测到某个接口上有磁盘相连,就将相应ide_driver_t结构中的present字段也设成1,并根据检测到或从系统的CMOS芯片中读到的各项参数设置这个数据结构。也就是说,ide_hwif_t数据结构是对IDE接口的描述,而ide_driver_t数据结构是对连接在具体IDE接口上的“IDE设备”的描述。
例如,如果在系统的主(primary)IDE接口上检测到有主/从两个磁盘相连,就把这两个磁盘的参数分别填入ide_hwifs[0]中的drivers[0]和drivers[1],并把它们的present字段设置成1。再例如,如果在次(secondary)IDE接口上连接着一个Mitsumi CDROM,那就把它的参数填入ide_hwifs[1]里面的drives[0],并且把ide_hwifs[1]中的字段major设置成MITSUMI_CDROM_MAJOR。
在ide_drive_t结构中有个void指针driver_data,可以指向不同的ide_driver_t数据结构(ide_drive_t和ide_driver_t是两种不同的数据结构)。这个指针在系统初始化过程中根据枚举到的IDE设备的类型而设置成不同的数据结构。对于IDE硬盘,它指向一个ide_driver_t数据结构idedisk_driver。同类的数据结构还有idetape_driver、ide_cdrom_driver以及ide_floppy_driver,分别代表着连接到IDE接口上的不同类型的设备,如果ide_drive_t结构drivers[]非空,但是它的driver指针确是NULL,就说明初始化时虽然检测到了硬盘的存在,但是却因某种原因未能完成对设备以及数据结构的初始化。
上面涉及到了3种数据结构:ide_hwif_t、ide_drive_t和ide_driver_t,它们刚好表示了IDE驱动程序的3个层次。代码中,这3种变量的名称常写为hwif、drive、driver,前面两个表示“硬件”,IDE接口、硬盘;后面一个表示“软件”,表示这个磁盘的具体驱动程序,有idedisk_driver、detape_driver等。
为了形象,从软件人员的角度说明:
从图中可以看出驱动初始化的顺序:初始化IDE接口(hwif)、IDE枚举(识别挂接的磁盘)、挂接具体的驱动程序。
- 初始化IDE接口
简单来说,每个IDE接口(驱动中用hwif表示)就是9个地址和它们的读写函数(及中断号),对磁盘的一切访问都通过这些地址:选择磁盘(一个IDE接口上可以接两个磁盘,这两个磁盘共用这9个地址,访问它们之前需要发出选择命令)、发出命令、查询状态、读写数据。对hwif的初始化主要包括一下两点。
①确定这9个地址和中断号,即ide_hwifs[].io_ports[]。
②确定这9个地址的读/写函数:ide_hwifs[].OUTB/INB/OUTW/INW等
就软件而言,就是设置相应的ide_hwifs[]项。 - IDE枚举
确定了IDE接口的地址、读写函数和中断号后,IDE驱动即会利用它们自动设别所挂接的磁盘,包括一下3点。
①检查是否有挂接了磁盘
②识别是磁盘、光盘还是软盘
③注册中断处理函数
一个IDE接口上最多可以挂接两个磁盘,可以是硬盘、光盘或软盘等,这可以通过不同的命令序列识别出来。命令序列分两类:ATA、ATAPI。前者对应硬盘,后者对应光盘/软盘等。
把磁盘当作一个巨大的可读写的数组的话,可以想象得到,磁盘上必然有一些只读的数据来标识它:生产厂商、容量、柱面数、磁头数、扇区数、是否支持多扇区读写等,称这些信息为“磁盘ID”,大小为512字节,这些信息读出后会保存起来。
就软件而言,就是设置数据结构ide_hwifs[].drivers[0],对应主磁盘;设置数据结构ide_hwifs[].drivers[1],它对应从磁盘。
如果检测到有磁盘相连,则将ide_hwifs[].drives[].present置1,然后使用ATA/ATAPI命令序列查询所接磁盘类型(硬盘、软盘、光盘),读取“磁盘ID”,保存在ide_hwifs[].drives[].id中;并设置ide_hwifs[].drives[].media,取值有ide_disk、ide_cdrom、ide_floppy等。
注册中断:同一个IDE接口(hwif)下的主、从两个磁盘,它们共用一根中断线。识别出了一个IDE接口下所有磁盘后,发现有磁盘时才注册中断。 - 挂接具体驱动程序
步骤2已经获取了磁盘的参数,并在ide_hwifs[].drives[].media中标明了磁盘类型,不同的类型对应不同的驱动程序:硬盘驱动、光盘驱动、软盘驱动等。加载这些驱动时,它们会遍历每个磁盘,即每个ide_hwifs[].drives[]。比对ide_hwifs[].dirves[].media项,匹配的话就将ide_hwifs[].drives[].driver_data指向相应的结构。比如:对于IDE硬盘,它指向一个ide_driver_t数据结构idedisk_driver。同类的数据结构还有idetape_driver、ide_cdrom_driver以及ide_floopy_driver。以硬盘为例,以后就会利用idedisk_driver结构中提供的函数进行读写硬盘了。
然后,识别磁盘分区。磁盘分区表的表示方法并不属于IDE驱动的范畴,但是了解它有助于调试驱动。
综上所述,可以认为IDE驱动分为3层:IDE接口层(hwif)、磁盘驱动器层(hwif.drives[])、具体驱动程序(hwif.drives[].driver)。实际上,在S3C2410/S3C2440系统中移植IDE接口驱动层程序,主要的工作也就是让操作系统能识别板上的IDE接口。
IDE接口驱动程序源码分析
下面按照上述3个步骤分析驱动程序,代码在dirvers/ide
目录下,分别是ide.c、ide-generic.c、ide-disk.c(硬盘)/ide-cd.c(CDROM)/ide-floppy.c(软盘)。
- 初始化IDE接口
入口在drivers/ide/ide.c
的ide_init
函数中,它的初始化默认的IDE接口,或者调用体系结构相关的函数初始化IDE接口,即确定IDE接口的9个地址和它们的读写函数。主要的函数调用关系如下:1
2
3
4
5
6
7
8ide_init ->
init_ide_data ->
init_hwif_data ->
default_hwif_iops //确定IDE接口的默认读写函数,OUTB/INB/OUTW/INW等
default_hwif_transport //一些默认的读写函数,会调用上面确定的函数
init_hwif_default ->//确认默认的IDE接口地址,对于X86架构外的CPU,通常无用
ide_arm_init ->//确认ARM架构相关的IDE接口地址
probe_for_hwifs ->//确定各种“已知的”IDE接口,它们通常是架构相关的 - IDE枚举(识别挂接的磁盘)
入口在drivers/ide/ide-generic.c
的ide_generic_init
函数中,主要的函数调用关系如下:函数do_probe在1
2
3
4
5
6ide_generic_init ->
ideprobe_inti ->
probe_hwif ->
probe_for_drive -> //枚举磁盘
do_probe
hwif_init //将枚举到的磁盘作为块设备注册到内核中,并注册中断处理函数等dirvers/ide/ide-probe.c
中定义,它利用前面确定的IDE接口的地址发出各类命令序列检测磁盘。摘取此函数里面用到的一个函数,可以看到是它如何使用前面确定的IDE接口的:SELECT_DRIVE(hwif,drive)被用来发出选择命令,选择主从磁盘,它在drivers/ide/ide-iops.c
中的定义如下:1
2
3
4
5
6void SELECT_DRIVE (ide_drive_t *drive)
{
if (HWIF(drive)->selectproc)
HWIF(drive)->selectproc(drive);
HWIF(drive)->OUTB(drive->select.all, IDE_SELECT_REG);
}IDE_SELECT_REG
就是hwif->io_ports[IDE_SELECT_OFFSET]。这行使用OUTB向寄存器IDE_SELECT_REG输出一个字节,而OUTB通常就是前面的default_hwif_iops函数设置的ide_outb,即outb。
前面说过的每个磁盘都有一些只读信息来标识自己—“磁盘ID”,读取“磁盘ID”的函数调用顺序如下:它们最终还是通过前面确定的OUTB/INB/OUTW/INW等函数来完成,只要读出了“磁盘ID”,磁盘的枚举基本就成功了。1
2
3
4
5
6
7
8
9
10
11ide_generic_init ->
ideprobe_init ->
probe_hwif ->
probe_for_driver ->
do_probe ->
try_to_identify ->
actual_try_to_identify ->
do_identify ->
hwif->ata_input_data ->
INSW ->
INW - 挂接具体驱动程序
以硬盘驱动程序为例,代码在drivers/ide/ide-disk.c
中,入口函数为idedisk_init
,代码如下:向内核注册驱动之后,最终会调用1
2
3
4static int __init idedisk_init(void)
{
return driver_register(&idedisk_driver.gen_driver);
}drivers/ide/ide-disk.c
中的ide_disk_probe
函数来识别每个磁盘。
对于所有磁盘,如果是硬盘(ide_hwifs[].drives[].driver_req为“ide-disk”、ide_hwifs[].drives[].media等于ide_disk),则挂接硬盘驱动程序:ide_hwifs[].drives[].driver = &idedisk_driver。
为硬盘挂接具体驱动程序的主要函数调用关系如下:假设一切正常,那么系统启动后就可以访问磁盘了。1
2
3
4
5
6
7idedisk_init ->
ide_disk_probe ->
idkp->driver = &idedisk_driver;//挂接硬盘驱动程序
idedisk_setup//根据磁盘ID做一些设置,比如获取磁盘容量、确定能否多扇区操作、确定读写能否以32位进行等
set_capacity//设置容量
g->fops = &idedisk_ops;//确定文件处理函数(用户调用open/read/write等时对应的函数)
add_disk(g);//识别分区
S3C2410/S3C2440开发板上的IDE接口驱动程序移植
从前面的分析可以知道,IDE接口驱动程序的移植只有一点:确定IDE的接口(地址、中断号、读写函数)。
开发板上的IDE接口硬件连线如下图所示:
在修改代码之前,先介绍一下IDE设备的寄存器。对硬盘、光盘、软盘等IDE设备的所有操作,都是通过读写它们的寄存器来完成的。这些寄存器分为两种:命令块寄存器、控制块寄存器。前者被用来给设备发送命令或是查询状态;后者被用来控制设备,它也可以用来查询状态。这两类寄存器通过CS1、CS0、DA2-DA0来分辨,它们的功能和地址信号如下图所示:
这些寄存器都是8位的,当设置好相关寄存器之后,就可以通过“数据端口”发送或读取数据了。“数据端口”的地址与数据寄存器一样,只不过传输的是16位的。
从上面的图可知,命令块寄存器的基地址是BANK1的基地址,即0x08000000,这些寄存器的地址分别为:0x0800000、0x0800002、0x0800004、…、0x080000E;控制块寄存器的基地址为BANK2的基地址,
即0x1000000,只用到一个地址:0x1000000C。硬件连线图中,ADD3-ADD1连接到DA2-DA0,没有使用ADDR0,确定地址时需要注意到这点。另外,这些地址是物理地址,在内核中使用时需要映射为虚拟地址。
中断引脚为EINT6,上升沿有效;使用nWAIT信号;数据位宽为16。
如前所述,只要告诉内核这些地址、中断号就可以了(还要进行相关设置使它们可用)。这些地址的读写函数使用前面设置的默认函数。
只需要修改两个文件:drivers/ide/arm/ide_arm.c
、drivers/ide/Kconfig
。
在drivers/ide/arm/ide_arm.c
文件中增加ide_s3c24xx_init函数,修改后的文件如下:
1 | ... |
第[6]、[7]行定义了IDE接口CS0、CS1的物理基址和中断引脚,它们在下面会用到。
第[22]-[25]行用来设置存储控制器,IDE接口使用BANK1、BANK2,数据总线位宽为16;还使用到了nWAIT信号。
第[28]-[33]行设置BANK1、BANK2的时序参数,现在设为比较宽松的值,基本都取最大值,读者可以根据硬盘特性进行调整。
第[39]-[47]行设置IDE接口的9个地址,第[39]、[40]两行首先将物理地址映射为虚拟地址,后面就是直接赋值了。需要注意的是:由于S3C2410/S3C2440的ADDR3-ADDR1接到IDE接口的DA02-DA00,没有使用ADDR0,所以是[45]、[47]行中地址的偏移都左移了1位。
第[50]-[52]行设置中断引脚,第[50]行的s3c2410_gpio_getirq
函数的返回值就是中断号IRQ_EINT6;第[51]行用来选择引脚功能为外部中断,第[52]行用来设置中断的触发方式为上升沿触发。第[50]-[52]行的功能完全可以在调用requset_irq
函数注册中断时,通过指定参数irqflags为IRQF_TRIGGER_RISING来完成,在这里之所以不使用这种方法是为了减少修改其他文件(在dribers/ide/ide-probe.c
中注册中断处理函数)。
第[55]行调用ide_register_hw
注册IDE接口,其实就是在将上面确定的地址、中断号填入某个不用的ide_hwifs[]表项中。
后面第[58]行开始的ide_arm_init
函数直接调用ide_s3c24xx_init
函数。ide_arm_init
函数在drivers/ide/ide.c
文件中的init_ide_data
函数中被调用,需要设置配置项CONFIG_IDE_ARM。ide_arm_init
函数被调用时的代码如下:
1 | static void __init init_ide_data(void) |
配置项COFIG_IDE_ARM在drivers/ide/Kconfig
中定义,代码如下:
1 | config IDE_ARM |
在配置菜单中看不到它,它取默认值。增加一个依赖条件ARCH_S3C2410,新代码如下:
1 | config IDE_ARM |
IDE接口驱动程序测试
首先配置内核,需要增加不少配置项;还要移植一些分区、格式化的工具。
配置、编译内核
为了支持硬盘、CDROM等设备,需要在设备驱动、文件系统等方面设置相应的配置项。在内核根目录下执行“make menuconfig”后,按照下面的指示进行配置即可。
1 | Device Drivers ---> |
值得一提的是上面的“(936)Default codepage for FAT”和“(cp936) Default iocharset for FAT”。
首先介绍一下字符集的概念:计算机中使用数值来表示字符,比如使用0x41来表示字符“A”。同一个数值在不同的字符集中可能表示不同的字符,比如数值0xABB6在gb2312字符集中式“东”,在big5字符集中却是“奎”,在UNICODE字符集中没有对应的字符。
在FAT文件系统中存储一个短文件名时使用本地的字符集进行存储,这个字符集被称为“codepage”;存储长文件名时,使用UNICODE字符集。在Linux中,查看FAT文件系统的文件时,比如使用“ls”命令时,这些以“codepage”或UNICODE字符集保存的数值,还要转换为另一个字符集的数值,才发送到控制台上去显示。这个“显示用”的字符集就是“iocharset”。
在Linux下挂接FAT文件系统时,经常碰到汉字的目录名、文件名显示“?”,就是因为在挂接文件系统时,没有正确设置“codepage”或“iocharset”所致。“codepage”和“iocharset”可以相同,也可以不同,比如我们可以这样挂接硬盘:
1 | mount -o codepage=950,iocharset=cp936 /dev/hda2 /mnt |
cp950表示字符集BIG5,cp936表示字符集GB2312,这个命令使得FAT文件系统使用BIG5字符集保存的繁体文件名,可以通过GB2312正确显示为简体字。
最后一项配置“Native Language Support”表示“本地语言支持”,就是将各种字符集编译进内核,或编译为模块。
移植工具
在Busybox中已经有分区工具fdisk,还需要移植EXT2文件系统格式化工具mke2fs、FAT文件系统格式化工具mkdosfs。
- 移植EXT2文件系统格式化工具mke2fs
- FAT文件系统格式化工具mkdosfs
分区、格式化、使用IDE接口设备
开发板上只有一个IDE接口,所以最多可以接两个设备:要么是主设备(hda)、要么是从设备(hdb),可以通过设置它们的跳线来确定谁是主设备、谁是从设备。
设备文件/dev/hda、/dev/hdb表示整个磁盘,设备文件/dev/hda1、/dev/hda2、/dev/hdb1、/dev/hdb2等表示磁盘的分区。初始化硬盘时,驱动程序会自动识别它的分区。
- 创建设备文件
进行下一步操作之前,先在开发板根文件系统中创建几个设备文件,以下命令在开发板上执行。1
2
3
4
5
6
7
8
9
10
11mknod /dev/hda b 3 0
mknod /dev/hda1 b 3 1
mknod /dev/hda2 b 3 2
mknod /dev/hda3 b 3 3
mknod /dev/hda4 b 3 4
mknod /dev/hdb b 3 64
mknod /dev/hdb1 b 3 65
mknod /dev/hdb2 b 3 66
mknod /dev/hdb3 b 3 67
mknod /dev/hdb4 b 3 68 - 分区
分区工具fdisk操作的是整个设备,比如“fdisk /dev/hda”。fdisk提供字符界面的菜单供用户进行各种操作:查看、增加、删除分区,查看主扇区的数据。需要注意的是:增加、删除分区等操作只是在内存中完成,还没有写入磁盘,这需要在主菜单中选择“w”才会将变化的数据写入磁盘。 - 格式化
分区之后就是格式化了,可以使用mke2fs工具将某个分区格式化为EXT2文件系统,或是使用mkdosfs工具格式化为FAT文件系统。mkdosfs工具的默认格式为FAT16,要格式化为FAT32需要增加参数“-F 32”。使用的命令示范如下:1
2mke2fs /dev/hda1
mkdosfs -F 32 /dev/hda2 - 挂接磁盘
对于已经格式化好的磁盘,直接使用mount命令即可挂接,之后就可以使用了。挂接命令如下:如果IDE接口接上了光驱,在启动内核时会看到类似以下的信息:1
2
3
4//对于EXT2文件系统,不需要指定字符集
mount /dev/hda1 /mnt
//对于FAT文件系统,指定codepage和iocharset;可省略,因为内核已设置默认字符集为cp936
mount -o codepage=936,iocharset=cp936 /dev/hda2 /mnt表示识别到了一个光驱,它是从设备(hdb)。光盘没有分区,直接使用“整个设备”,即/dev/hdb。在光驱中装入光盘后,挂接命令如下:1
2
3
4
5
6Uniform Multi-Platform E-IDE driver Revision: 7.00alpha2
ide: Assuming 50MHz system bus speed for PIO modes; override with idebus=xx
hdb: BENQ DVD DD DW1650, ATAPI CD/DVD-ROM drive
ide0 at 0xc4872000-0xc4872007,0xc487400c on irq 50
hdb: ATAPI 48X DVD-ROM DVD-R CD-R/RW drive, 2048KB Cache
Uniform CD-ROM driver Revison: 3.201
mount -o iocharset=gb2312 /dev/hdb /mnt #要显示简体汉字,指定字符集gb2312
SD卡驱动程序移植
SD卡相关概念介绍
MMC:MMC就是MultiMediaCard的缩写,即多媒体卡。它是一种非易失性存储器件,体积小巧,类似一张邮票大小,容量大,耗电量低,传输速度快,广泛用于电子玩具、PDA、数码相机、手机、MP3等设备中。以前的MMC规范的数据传输宽度只有1位,最新的4.0版本的MMC规范拓宽了4位、8位带宽,时钟频率达到52MHz频率,从而支持50MB/s的传输速率。对于SD卡的“数据保全”特性,MMC协会接纳了具有竞争性的安全卡标准—Secure MMC 1.1版规范。
SD:SD卡的英文名为Secure Digital Memory Card,即安全数码卡。它在MMC的基础上发展而来,增加了两个主要特色:SD卡强调数据的保全,可以设定所存储数据的使用权限,防止他人复制;另一个特色就是传输速度比2.11版的MMC快了4倍。在数据传输和物理规范上,SD卡向前兼容MMC卡;外观上,SD卡尺寸为24mmX32mmX2.1mm,是比MMC卡更厚一些;这两个特点使得支持SD卡的设备也支持MMC卡。SD卡和2.11版的MMC卡完全兼容。
SDIO:SDIO是在SD标准上定义了一种外设接口,它和SD卡规范间的一个重要区别是增加了低速标准。SDIO卡只需要SPI和1位SD传输模式。低速卡的目标应用是以最小的硬件开支支持低速I/O能力。低速卡支持剋四调制解调器、条码扫描仪和GPS接收器等应用。对“组合卡”(存储器+SDIO)而言,全速和4位操作对卡内存储器和SDIO部分都是强制要求的。
MMC/SD/SDIO这3种存储卡都支持这两种接口:对于MMC卡,称为MMC接口和SPI接口;对于SD卡,SDIO卡,称为SD接口和SPI接口。SD接口有1位和4位之分,上电时默认使用1位模式,设置SD主机后可以使用4位模式。
MCI:MCI是Multimedia Card Interface的简称,即多媒体卡接口。上述的MMC、SD、SDIO卡定义的接口都属于MCI接口。MCI这个术语在驱动程序种经常使用,很多文件、函数名字都包含“mci”。
SD卡驱动程序移植
S3C2410/S3C2440中集成了一个MMC/SD/SDIO主机控制器,用于访问外接的MMC卡、SD卡或SDIO卡,它有如下特性:
- 支持SD存储卡规范1.0、MMC卡规范2.11
- 支持SDIO规范1.0
- 内部有16个字(64字节)的FIFO,用于发送、接收数据
- 40位的命令寄存器(SDICARG[31:0]+SDICCON[7:0])
- 136位的回应寄存器(SDIRSPn[127:0]+SDICSTA[7:0])
- 8位的预分频逻辑电路
对于S3C2410,Freq=System Clock/(2(P+1))
对于S3C2440,Freq=System Clock/(P+1) - CRC7和CRC16校验码产生器
- 支持查询、中断或者DMA传输模式
- 数据总线的宽度可以是1位或者4位,支持串流或区块传输模式
- 对于SD卡、SDIO卡,传输数据时最高时钟频率为25MHz
- 对于MMC卡,传输数据时最高时钟频率为20MHz
Linux2.6.22.6尚未支持S3C2410/S3C2440的MMC/SD/SDIO控制器,需要移植驱动程序。在这之前,先介绍一下MMC/SD驱动的框架。这些驱动程序在内核drivers/mmc
目录下。
内核MMC/SD驱动程序框架
内核drivers/mmc
目录下有3个子目录:card/、core/和host/,这刚好表示了MMC/SD驱动程序的3个层次,层次结构如下图所示:
- 区块层
向文件系统层、用户空间提供文件操作的接口,主要文件是card/目录下的block.c,queue.c向它提供了几个函数来操作队列。
区块层调用core/目录下的core.c、sysfs.c提供的接口来识别存储卡的分区、读写存储卡等功能。 - 核心层
核心层代码在core/目录下,它封装了MMC/SD命令,实现MMC/SD协议,它调用主机控制器层的接口完成存储卡的识别、设置、读写等。
如上图所示,core.c文件由sd.c、mmc.c两个文件支撑,core.c把MMC卡、SD卡的共性抽象出来,它们的差别由sd.c和sd_ops.c、mmc.c和mmc_ops.c来完成。
sysfs.c是MMC/SD驱动程序的sysfs文件系统的实现,它提供一些内核体系相关的函数来实现注册、调用驱动程序;在用户空间挂接sysfs文件系统后,可以从中看到MMC/SD的一些信息。 - 主机控制器层
核心层根据需要构造各种MMC/SD命令,这些命令是怎么发送给MMC/SD卡的?这通过主机控制器来实现。这层是架构相关的,里面针对各款CPU提供了一个文件,目前支持的CPU还很少。
以即将移植的s3cmci.c为例,它首先进行一些底层设置,比如设置MMC/SD/SDIO控制器使用到的GPIO引脚、使能控制器、注册中断处理函数等,然后向上面的核心层增加一个主机(Host),这样核心层就可以调用s3cmci.c提供的函数来识别、使用具体存储卡了。
在向核心层增加主机之前,s3cmci.c设置了一个mmc_host_ops结构,它实现了两个函数:发起访问请求的request函数,进行一些属性设置(时钟频率、数据线位宽)的set_ios函数。以后上层对存储卡的操作都是通过这两个函数来完成。
下面列出识别存储卡、区块层发起操作请求这两种情况下函数的主要调用关系,读者根据函数名称及所在文件,就可以了解到上面讲述的层次结构。
仍以即将移植的s3cmci.c为例:
- 识别存储卡
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17s3c2410sdi_probe(host/s3c2410mci.c)
mmc_alloc_host(core/core.c)
mmc_rescan(core/core.c)
mmc_attach_sd(core/sd.c)(SD卡的入口点)//尝试识别SD卡
mmc_sd_init_card(core/sd.c)
mmc_all_send_cid(core/sd_ops.c)//读取存储卡的CID
mmc_wait_for_cmd(core/core.c)//发起并等待请求完成
mmc_start_request(core/core.c)
host->ops->request(host, mrq);//host/s3cmci.c的s3cmci_requset
...
mmc_switch_hs(sd.c)//设置为高速模式
mmc_set_timing(core.c)//设置时钟
mmc_set_ios(core.c)
host->ops->set_ops(host,ios);//host/s3cmci.c的s3cmci_set_ios
...
mmc_attach_mmc(core/mmc.c)(MMC卡的入口点)//尝试识别MMC卡
... - 区块层发起操作请求
1
2
3
4mmc_blk_issue_req(block/block.c)
mmc_wait_for_req(core/core.c)//发起并等待请求完成
mmc_start_requst(core/core.c)
host->ops->request(host,mrq);//host/s3cmci.c中的s3cmci_request
S3C2410/S3C2440的MMC/SD/SDIO控制器驱动程序移植
开发板上MMC/SD接口的连线如下图所示,nCD接到外部中断引脚EINT16,接上或拔下存储卡时会触发中断。
移植MMC/SD/SDIO控制器驱动程序分为3个步骤:打补丁、增加MMC/SD平台设备、修改主机控制器驱动程序以指定上图中的nCD中断。
- 给内核打补丁
内核中要增加对S3C2410/S3C2440的MMC/SD/SDIO控制器的支持,需要打补丁。以下6个patch:按照上述文件的次序,执行以下命令打上补丁。1
2
3
4
5
6s3c_mci.patch
s3c_mci_platform.patch
s3cmci-dma-free.patch
s3cmci-stop-fix.patch
s3cmci-unfinished-write-fix.patch
s3cmci_dbg.patch这些补丁修改或添加的文件有:1
2
3
4
5
6
7cd linux-2.6.22.6
patch -p1 < s3c_mci.patch
patch -p1 < s3c_mci_platform.patch
patch -p1 < s3cmci-dma-free.patch
patch -p1 < s3cmci-stop-fix.patch
patch -p1 < s3cmci-unfinished-write-fix.patch
patch -p1 < s3cmci_dbg.patch然后配置内核,增加MMC块设备驱动、s3c24xx的MMC/SD卡驱动,配置如下:1
2
3
4
5
6
7
8drivers/mmc/host/Kconfig //修改
drivers/mmc/host/Makefile //修改
include/asm-arm/arch-s3c2410/regs-sdi.h //修改
include/asm-arm/arch-s3c2410/mci.h //新增
drivers/mmc/host/mmc_debug.c //新增
drivers/mmc/host/mmc_debug.h //新增
drivers/mmc/host/s3cmci.c //新增
drivers/mmc/host/s3cmci.h //新增配置后编译内核时,会发现MMC_ERR_DMA、MMC_ERR_BUSY、MMC_ERR_CANCLED这3个宏没有定义,在1
2
3
4
5Device Drivers --->
<*> MMC/SD card support --->
[*] MMC debugging
<*> MMC block device driver
<*> Samsung S3C SD/MMC Card Interface supportinclude/linux/mmc/core.c
中增加以下3行:1
2
3 - 增加MMC/SDI平台设备
drivers/mmc/s3cmci.c
文件的入口函数为s3cmci_init,代码如下:上述向内核注册3个平台驱动,本书关注的两个驱动如下:1
2
3
4
5
6
7static int __init s3cmci_init(void)
{
platform_driver_register(&s3cmci_driver_2410);
platform_driver_register(&s3cmci_driver_2412);
platform_driver_register(&s3cmci_driver_2440);
return 0;
}当发现名称为“s3c2410-sdi”或“s3c2440-sdi”的平台设备时,会调用其中的s3cmci_probe_2410或s3cmci_probe_2440函数来枚举MMC/SD设备。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15static struct platform_driver s3cmci_driver_2410 = {
.driver.name = "s3c2410-sdi",
.probe = s3cmci_probe_2410,
.remove = s3cmci_remove,
.suspend = s3cmci_suspend,
.resume = s3cmci_resume,
};
...
static struct platform_driver s3cmci_driver_2440 = {
.driver.name = "s3c2440-sdi",
.probe = s3cmci_probe_2440,
.remove = s3cmci_remove,
.suspend = s3cmci_suspend,
.resume = s3cmci_resume,
};
如上所述,要为MMC/SD驱动定义平台设备。在内核文件arch/arm/plat-s3c24xx/devs.c
中,已经有一个平台设备的数据结构s3c_device_sdi(名称为“s3c2410-sdi”),只不过还没有使用它。
现在仿照它在以下两个文件中分别增加s3c2410_device_sdi,s3c2440_device_sdi结构,并把它们加入到设备列表中(就是smdk2410_devices[]、smdk2440_devices[]数组)。
①修改arch/arm/mach-s3c2410/mach-smdk2410.c
以下是修改的代码②修改1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25/*SDI*/
static struct resource s3c2410_sdi_resource[] = {
[0] = {
.start = S3C2410_PA_SDI,
.end = S3C2410_PA_SDI + S3C24XX_SZ_SDI - 1,
.flag = IORESOURCE_MEM,
},
[1] = {
.start = IRQ_SDI,
.end = IRQ_SDI,
.flag = IORESOURCE_IRQ,
}
};
static struct platform_device s3c2410_device_sdi = {
.nane = "s3c2410-sdi",
.id = -1,
.num_resources = ARRAY_SIZE(s3c2410_sdi_resource),
.resource = s3c2410_sdi_resource,
};
static struct platform_device *smdk2410_devices[] __initdata = {
...
&s3c2410_device_sdi,
};arch/arm/mach-s3c2440/mach-smdk2440.c
③修改1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25/*SDI*/
static struct resource s3c2440_sdi_resource[] = {
[0] = {
.start = S3C2410_PA_SDI,
.end = S3C2410_PA_SDI + S3C24XX_SZ_SDI - 1,
.flag = IORESOURCE_MEM,
},
[1] = {
.start = IRQ_SDI,
.end = IRQ_SDI,
.flag = IORESOURCE_IRQ,
}
};
static struct platform_device s3c2440_device_sdi = {
.nane = "s3c2440-sdi",
.id = -1,
.num_resources = ARRAY_SIZE(s3c2440_sdi_resource),
.resource = s3c2440_sdi_resource,
};
static struct platform_device *smdk2410_devices[] __initdata = {
...
&s3c2440_device_sdi,
};drivers/mmc/host/s3cmci.c
,指定nCD中断。
只要在s3cmci_def_pdata结构中修改gpio_detect成员即可,将它从0改为S3C2410_GPG8。修改后的代码如下:s3cmci.c中的函数会将GPG8引脚设置为外部中断EINT16,设置双边沿触发。现在可以编译内核了。1
2
3
4
5static struct s3c24xx_mci_pdata s3cmci_def_pdata = {
.gpio_detect = S3C2410_GPG8,
.set_power = NULL,
.ocr_avail = MMC_VDD_32_33,
};
SD卡驱动程序测试
使用新编译的内核启动系统,可以看到如下信息:
1 | s3c2440-sdi s3c2440-sdi: powered down |
如果接上SD卡,还可以看到类似下面的信息:
这表明已经识别出了SD卡,然后就可以使用fdisk工具来分区,使用mke2fs或mkdosfs来格式化设备了。
如果根文件系统中没有使用mdev机制,在使用之前要先创建设备节点(主设备号可以通过“cat /proc/devices”命令确定),mmcblk0表示整个SD卡,mmcblk0p1等表示上面的分区。
1 | mknod /dev/mmcblk0 b 179 0 |
磁盘分区表
磁盘的分区形式表示形式有多种风格:BSD/SUN、IRIX/SGI、DOS。在DOS风格的分区表中,分区开始地址和大小是以两种不同的形式来存放的:以扇区数的绝对值来描述(占32位)和以柱面、磁头、扇区三个一组的形式(占10+8+6个位)来描述。前者编号从0开始;后者被称为C/H/S方式。
磁盘一个扇区大小为512字节,第一个扇区被称为主引导记录(MBR,Mater Boot Record)。MBR中偏移地址446-509处存放了分区表,每个表项为16字节,可以存放4个表项。MBR偏移地址中510、511处的数据为0x55、0xAA。
分布表项的数据结构如下(在include/linux/genhd.h
中定义):
1 | struct partition { |
其中的head、sector、cyl、end_head、end_sector、end_cyl在Linux中不再使用,而是使用start_sect、nr_sects来定义一个分区的开始扇区和大小。
由于MBR中只有4个分区表项,所以一个磁盘最多可以有4个主分区。如果要划分更多的分区,那么这4个分区只可以用1个来作为扩展分区(表项中sys_ind等于0x05)。当创建一个扩展分区时,扩展分区表也被创建。扩展分区就像一个独立的磁盘驱动器,它有自己的分区表,在他里面又可以进一步划分最多4个分区,也可以划分一个扩展分区。扩展分区里面进一步划分出来的分区被称为逻辑分区,与主分区相对,扩展分区的分区表完全包含在扩展分区之内。