最近比较闲,准备玩玩xilinx的SoC,但又由于预算不够,买不起ZedBoard,所以最后入手了Z-Turn这块板子。它是米尔科技设计的,板子整体是蛮漂亮的,就是软资源不足——不能很好地玩转啊,这是个硬伤。 过年在家,闲的无聊,就准备研究一下以前 一直想了解的linux中块设备的驱动架构,好吧扯了很多废话,让我们进入正题吧。
z-turn这块板子上,系统可以从SPI flash或者TF卡中启动,我们来看一下它的uboot env中与从tf卡启动的相关的部分(此处省去不相关的东西): root=/dev/mmcblk0p2。从此我们知道系统是从编号为0的块设备的part2区域去加载linux的根文件系统的,很多人可能理解为什么是编号为0的块设备,但可能会对为什么会是part2区域产生一定的疑惑。
对此,我是这样理解的:TF卡和电脑的硬盘一样,可以分成几个partions(C盘、D盘......)。在linux里面识别为 mmcblkxpy,其中x是mmc设备的编号,y是partion的编号。比如插入一块tf卡,识别为0号,就是mmcblk0。在本系统中具体分区如下图所示:
图1.1
可以看出TF卡分成了2个分区,第一个是fat32格式的文件系统分区,linux会识别为mmcblk0p1,第二个是ext4文件系统的分区,linux会识别为mmcblk0p2。一般来说没有特殊的软件支持,windows系统只识别第一个分区,不信话你可以吧TF通过读卡器,插到电脑上试试,是可以识别fat32的分区,也就是说,你是可以拷贝来更新uboot、linux等文件的。当然你如果使用专用的软件,你也可以去更新ext4文件系统,在本系统中ext4里面就是ubuntu。
好了,前戏结束了,让我们进一步跟进,开始讨论这个mmcblk0是怎么在内核中被注册。首先我们当然想到了函数(省去了具体代码):
int register_blkdev(unsigned int major, const char *name)
{
1. 从全局变量数组 major_names找到一个是NULL(空闲)的成员
2. 通过参数major和那么,构造并初始化一个 blk_major_name类型的对象p
3. 检查major_name中是否有major相同的,如果有就挑选出来,并把对象p填充到该数组中
}
大家可能发现了,注册了不也是什么功能都实现不了吗?对啊,很多注册函数本来就是这个样子,为了混个脸熟,占个位子嘛!
我们打开设备树文件linux-xlnx/arch/arm/boot/dts/zynq-7000.dtsi。通过搜索关键词sdhci,可以搜索到TF卡的设备驱动的节点部分,源码如下:
sdhci0: sdhci@e0100000 {
compatible = "arasan,sdhci-8.9a";
status = "disabled";
clock-names = "clk_xin", "clk_ahb";
clocks = <&clkc 21>, <&clkc 32>;
interrupt-parent = <&intc>;
interrupts = <0 24 4>;
reg = <0xe0100000 0x1000>;
};
我们都知道,linux启动以后,系统过通过dts去生成各个设备驱动,并通过这些设备驱动去probe平台驱动,此处也是一样,它肯定对应了一个平台驱动,源码位于:linux-xlnx /drivers/mmc/host/sdhci-of-arasan.c。大家可能会疑问为什么是arasan,而不是xilinx呢?我查看了下,arasan这是一家可以提供各种SDIO、SPI等可以连接MMC/eMMC存储设备的接口的IP核的公司,xilixn估计就是用的它的IP核,毕竟方便易用好移植吗。
通过不停得追踪,发现此处注册一个设备驱动有如下的函数滴啊用关系: sdhci_arasan_probe-> sdhci_add_host-> mmc_add_host-> device_add,顺利地把这个设备注册到了系统中。device_add?为什么不是device_register,该设备驱动驱动是在哪里被构造的?好回去继续找吧!函数调用关系如下:sdhci_arasan_probe-> sdhci_pltfm_init-> sdhci_alloc_host-> mmc_alloc_host,就是这个函数里面构造了这个设备驱动;大家阅读的时候可能发现了,设备的是名字是 "mmc%d",转换出来就是"mmc0","mmc1"......和设备"mmcblk0p2"有一毛钱关系啊,突然感觉失去了线索。但是想想我们这里也只是注册了一个mmc驱动,和block驱动还是很大的区别的吧!所以呢我觉得这应该就是个设备驱动,需要通过总线(应该就是MMC总线了)去match正真的驱动——块设备驱动。
在文件 linux-xlnx/drivers/mmc/card/block.c中我找了函数 mmc_blk_init,该函数被 module_init引用,应该是驱动入口函数了,我们看看它具体是干嘛的。先通过 register_blkdev(MMC_BLOCK_MAJOR, "mmc")注册了一个名字是mmc的块设备;接着通过 mmc_register_driver(&mmc_driver)这个mmc设备驱动,这是里面最重要的函数。
int mmc_register_driver(struct mmc_driver *drv)
{
drv->drv.bus = &mmc_bus_type;
return driver_register(&drv->drv);
}
主要就是吧该驱动注册到mmc总线上面去。再次通过层层追踪,发现 mmc_blk_probe-> mmc_blk_alloc-> mmc_blk_alloc_req -> snprintf(md->disk->disk_name, sizeof(md->disk->disk_name), "mmcblk%d%s", md->name_idx, subname ? subname : "")将某设备的名字配置成了"mmcblk%d%s",终于找到它了。
我们简单概括一下以上的流程:
1. linux读取dts,获取各个设备
2. 匹配设备驱动,调用该设备驱动的probe程序,生成设备驱动inode文件
3. 该设备驱动通过总线去匹配正真的驱动文件,并调用该驱动的probe程序 ->在dev下生成了mmcblkXpY的inode文件
4. 该系统通过uboot的启动参数,获知需要从那个mmcblkXpY去加载根文件系统,去实现整个系统的初始化