读书笔记--uboot与linux驱动
1、uboot本身是裸机程序
(1)裸机本来是没有驱动的概念的(狭义的驱动的概念就是操作系统中用来具体操控硬件的那部分代码叫驱动)
(2)裸机程序中是直接操控硬件的,操作系统中必须通过驱动来操控硬件。这两个有什么区别?本质区别就是分层。
2、uboot的虚拟地址对硬件操作的影响
(1)操作系统(指的是linux)下MMU肯定是开启的,也就是说linux驱动中肯定都使用的是虚拟地址。
而纯裸机程序中根本不会开MMU,全部使用的是物理地址。
这是裸机下和驱动中操控硬件的一个重要区别。
(2)uboot早期也是纯物理地址工作的,但是现在的uboot开启了MMU做了虚拟地址映射,这个东西驱动也必须考虑。
查uboot中的虚拟地址映射表,发现除了0x30000000-0x3FFFFFFF映射到了0xC0000000-0xCFFFFFFF之外,其余的虚拟地址空间全是原样映射的。
而我们驱动中主要是操控硬件寄存器,而S5PV210的SFR都在0xExxxxxx地址空间,因此驱动中不必考虑虚拟地址。
3、uboot借用(移植)了linux驱动
(1)linux驱动本身做了模块化设计。linux驱动本身和linux内核不是强耦合的,这是linux驱动可以被uboot借用(移植)的关键。
(2)uboot移植了linux驱动源代码。uboot是从源代码级别去移植linux驱动的,这就是linux系统的开源性。
(3)uboot中的硬件驱动比linux简单。
linux驱动本身有更复杂的框架,需要实现更多的附带功能,
uboot本质上只是个裸机程序,uboot移植linux驱动时只是借用了linux驱动的一部分而已。
4、iNand/SD驱动解析
4.1、从start_armboot开始
4.2、mmc_initialize
(1)函数位于:uboot/drivers/mmc/mmc.c。
1 int mmc_initialize(bd_t *bis) 2 { 3 struct mmc *mmc; 4 int err; 5 6 INIT_LIST_HEAD(&mmc_devices); 7 cur_dev_num = 0; 8 9 if (board_mmc_init(bis) < 0) 10 cpu_mmc_init(bis); 11 12 #if defined(DEBUG_S3C_HSMMC) 13 print_mmc_devices(','); 14 #endif 15 16 #ifdef CONFIG_CHECK_X210CV3 17 mmc = find_mmc_device(1);//lqm 18 #else 19 mmc = find_mmc_device(0); 20 #endif 21 if (mmc) { 22 err = mmc_init(mmc); 23 if (err) 24 err = mmc_init(mmc); 25 if (err) { 26 printf("Card init fail!\n"); 27 return err; 28 } 29 } 30 printf("%ldMB\n", (mmc->capacity/(1024*1024/(1<<9)))); 31 return 0; 32 }
(2)从名字可以看出,这个函数的作用就是初始化开发板上MMC系统。MMC系统的初始化应该包含这么几部分:
SoC里的MMC控制器初始化(MMC系统时钟的初始化、SFR初始化)、SoC里MMC相关的GPIO的初始化、SD卡/iNand芯片的初始化。
(3)mmc_devices链表全局变量,
static struct list_head mmc_devices;
用来记录系统中所有已经注册的SD/iNand设备。
所以向系统中插入一个SD卡/iNand设备,则系统驱动就会向mmc_devices链表中插入一个数据结构表示这个设备。
4.3、cpu_mmc_init
(1)函数位于:uboot/cpu/s5pc11x/cpu.c中。实质是通过调用3个函数来完成的。
(1)setup_hsmmc_clock,在uboot/cpu/s5pc11x/setup_hsmmc.c中。看名字函数是用来初始化SoC中MMC控制器中的时钟部分的。
(2)setup_hsmmc_cfg_gpio,在uboot/cpu/s5pc11x/setup_hsmmc.c中。看名字函数是用来配置SoC中MMC控制器相关的GPIO的。
4.4 、smdk_s3c_hsmmc_init
int smdk_s3c_hsmmc_init(void) { #ifdef OM_PIN if(OM_PIN == SDMMC_CHANNEL0) { int err; printk("SD/MMC channel0 is selected for booting device.\n"); err = s3c_hsmmc_initialize(0); return err; } else if (OM_PIN == SDMMC_CHANNEL1) { int err; printk("SD/MMC channel1 is selected for booting device.\n"); err = s3c_hsmmc_initialize(1); return err; } else printk("SD/MMC isn't selected for booting device.\n"); #endif int err; #ifdef USE_MMC0 err = s3c_hsmmc_initialize(0); if(err) return err; #endif #ifdef USE_MMC1 err = s3c_hsmmc_initialize(1); if(err) return err; #endif #ifdef USE_MMC2 err = s3c_hsmmc_initialize(2); if(err) return err; #endif #ifdef USE_MMC3 err = s3c_hsmmc_initialize(3); if(err) return err; #endif return -1; }
(1)函数位于:uboot/drivers/mmc/s3c_hsmmc.c中。
(2)函数内部通过宏定义USE_MMCx来决定是否调用s3c_hsmmc_initialize来进行具体的初始化操作。
4.5、s3c_hsmmc_initialize
1 static int s3c_hsmmc_initialize(int channel) 2 { 3 struct mmc *mmc; 4 5 mmc = &mmc_channel[channel]; 6 7 sprintf(mmc->name, "S3C_HSMMC%d", channel); 8 mmc->priv = &mmc_host[channel]; 9 mmc->send_cmd = s3c_hsmmc_send_command; 10 mmc->set_ios = s3c_hsmmc_set_ios; 11 mmc->init = s3c_hsmmc_init; 12 13 mmc->voltages = MMC_VDD_32_33 | MMC_VDD_33_34; 14 mmc->host_caps = MMC_MODE_4BIT | MMC_MODE_HS_52MHz | MMC_MODE_HS; 15 #if defined(USE_MMC0_8BIT) || defined(USE_MMC2_8BIT) 16 mmc->host_caps |= MMC_MODE_8BIT; 17 #endif 18 19 mmc->f_min = 400000; 20 mmc->f_max = 52000000; 21 22 mmc_host[channel].clock = 0; 23 24 switch(channel) { 25 case 0: 26 mmc_host[channel].ioaddr = (void *)ELFIN_HSMMC_0_BASE; 27 break; 28 case 1: 29 mmc_host[channel].ioaddr = (void *)ELFIN_HSMMC_1_BASE; 30 break; 31 case 2: 32 mmc_host[channel].ioaddr = (void *)ELFIN_HSMMC_2_BASE; 33 break; 34 #ifdef USE_MMC3 35 case 3: 36 mmc_host[channel].ioaddr = (void *)ELFIN_HSMMC_3_BASE; 37 break; 38 #endif 39 default: 40 printk("mmc err: not supported channel %d\n", channel); 41 } 42 43 return mmc_register(mmc); 44 }
1 int mmc_register(struct mmc *mmc) 2 { 3 /* Setup the universal parts of the block interface just once */ 4 mmc->block_dev.if_type = IF_TYPE_MMC; 5 mmc->block_dev.dev = cur_dev_num++; 6 mmc->block_dev.removable = 1; 7 mmc->block_dev.block_read = mmc_bread; 8 mmc->block_dev.block_write = mmc_bwrite; 9 10 INIT_LIST_HEAD(&mmc->link); 11 12 list_add_tail(&mmc->link, &mmc_devices); 13 14 return 0; 15 }
(1)函数位于:uboot/drivers/mmc/s3c_hsmmc.c中。
(2)定义并且实例化一个struct mmc类型的对象(定义了一个指针,并且给指针指向有意义的内存,或者说给指针分配内存),然后填充它的各种成员,最后调用mmc_register函数来向驱动框架注册这个mmc设备驱动。
(3)mmc_register功能是进行mmc设备的注册,注册方法其实就是将当前这个struct mmc使用链表连接到mmc_devices这个全局变量中去。
(4)我们在X210中定义了USE_MMC0和USE_MMC2,因此在我们的uboot初始化时会调用2次s3c_hsmmc_initialize函数,传递参数分别是0和2,因此完成之后系统中会注册上2个mmc设备,表示当前系统中有2个mmc通道在工作。
(5)至此cpu_mmc_init函数分析完成。
4.6、find_mmc_device
1 struct mmc *find_mmc_device(int dev_num) 2 { 3 struct mmc *m; 4 struct list_head *entry; 5 6 list_for_each(entry, &mmc_devices) { 7 m = list_entry(entry, struct mmc, link); 8 9 if (m->block_dev.dev == dev_num) 10 return m; 11 } 12 13 printf("MMC Device %d not found\n", dev_num); 14 15 return NULL; 16 }
(1)这个函数位于:uboot/drivers/mmc/mmc.c中。
(2)这个函数其实就是通过mmc设备编号来在系统中查找对应的mmc设备(struct mmc的对象,根据上面分析系统中有2个,编号分别是0和2)。
(3)函数工作原理就是通过遍历mmc_devices链表,去依次寻找系统中注册的mmc设备,然后对比其设备编号和我们当前要查找的设备编号,如果相同则就找到了要找的设备。找到了后调用mmc_init函数来初始化它。
4.6、mmc_init
1 int mmc_init(struct mmc *host) 2 { 3 int err; 4 5 err = host->init(host); 6 7 if (err) 8 return err; 9 10 /* Reset the Card */ 11 err = mmc_go_idle(host); 12 13 if (err) 14 return err; 15 16 /* Test for SD version 2 */ 17 err = mmc_send_if_cond(host); 18 19 /* Now try to get the SD card's operating condition */ 20 err = mmc_send_app_op_cond(host); 21 22 /* If the command timed out, we check for an MMC card */ 23 if (err == TIMEOUT) { 24 err = mmc_send_op_cond(host); 25 26 if (err) 27 return UNUSABLE_ERR; 28 } else 29 if (err) 30 return UNUSABLE_ERR; 31 32 return mmc_startup(host); 33 }
(1)函数位于:drivers/mmc/mmc.c中。
(2)分析猜测这个函数应该要进行mmc卡的初始化了(前面已经进行了SoC端控制器的初始化)
(3)函数的调用关系为:
mmc_init
mmc_go_idle
mmc_send_cmd
mmc_send_if_cond
mmc_send_cmd
······
具体分析可以看出,mmc_init函数内部就是依次通过向mmc卡发送命令码(CMD0、CMD2那些)来初始化SD卡/iNand内部的控制器,以达到初始化SD卡的目的。
至此整个MMC系统初始化结束。
[总结]
(1)整个MMC系统初始化分为2大部分:
SoC这一端的MMC控制器的初始化,主要是在cpu_mmc_init函数中完成.
SD卡这一端卡本身的初始化,主要是在mmc_init函数中完成。
(2)整个初始化完成后去使用sd卡/iNand时,操作方法和mmc_init函数中初始化SD卡的操作一样的方式。读写sd卡时也是通过总线向SD卡发送命令、读取/写入数据来完成的。
(3)顺着操作追下去,到了mmc_send_cmd函数处就断了,真正的向SD卡发送命令的硬件操作的函数在MMC结构体中。
(4)struct mmc结构体是关键。两部分初始化之间用mmc结构体来链接的,初始化完了后对mmc卡的常规读写操作也是通过mmc结构体来链接的。
(5)struct mmc
1)驱动的设计中有一个关键数据结构。譬如MMC驱动的结构体就是struct mmc这些结构体中包含一些变量和一些函数指针,变量用来记录驱动相关的一些属性,函数指针用来记录驱动相关的操作方法。这些变量和函数指针加起来就构成了驱动。驱动就被抽象为这个结构体。
2)一个驱动工作时主要就分几部分:
驱动构建(构建一个struct mmc然后填充它)、
驱动运行(调用这些函数指针指针的函数和变量)
(6)分离思想
1)分离思想就是说在驱动中将操作方法和数据分开。
2)操作方法就是函数,数据就是变量。所谓操作方法和数据分离的意思就是:在不同的地方来存储和管理驱动的操作方法和变量,这样的优势就是驱动便于移植。
(7)分层思想
1)分层思想是指一个整个的驱动分为好多个层次。简单理解就是驱动分为很多个源文件,放在很多个文件夹中。譬如mmc的驱动涉及到drivers/mmc下面的2个文件和cpu/s5pc11x下的好几个文件。
2)以mmc驱动为例来分析各个文件的作用:
uboot/drivers/mmc/mmc.c:本文件的主要内容是和MMC卡操作有关的方法,譬如MMC卡设置空闲状态的、卡读写数据等。但是本文件中并没有具体的硬件操作函数,操作最终指向的是struct mmc结构体中的函数指针,这些函数指针是在驱动构建的时候和真正硬件操作的函数挂接的(真正的硬件操作的函数在别的文件中)。
uboot/drivers/mmc/s3c_hsmmc.c:本文件中是SoC内部MMC控制器的硬件操作的方法,譬如向SD卡发送命令的函数(s3c_hsmmc_send_command),譬如和SD卡读写数据的函数(s3c_hsmmc_set_ios),这些函数就是具体操作硬件的函数,也就是mmc.c中需要的那些硬件操作函数。这些函数在mmc驱动初始化构建时(s3c_hsmmc_initialize函数中)和struct mmc挂接起来备用。
分析:mmc.c和s3c_hsmmc.c构成了一个分层,mmc.c中调用了s3c_hsmmc.中的函数,所以mmc.c在上层,s3c_hsmmc.c在下层。这两个分层后我们发现mmc.c中不涉及具体硬件的操作,s3c_hsmmc.c中不涉及驱动工程时的时序操作。因此移植的时候就有好处:譬如我们要把这一套mmc驱动移植到别的SoC上mmc.c就不用动,s3c_hsmmc.c动就可以了;譬如SoC没变但是SD卡升级了,这时候只需要更换mmc.c,不需要更换s3c_hsmmc.即可。
3)cpu/s5pc11x/下面还有一个setup_hsmmc.c,也和MMC驱动有关。但是这些代码为什么不能放到drivers目录下去,而要放到cpu目录下去?因为这里面的2个函数(setup_hsmmc_clock和setup_hsmmc_cfg_gpio)都是和SoC有关的初始化函数,这两个函数不能放到drivers目录下去。实际上如果非把这两个函数放在uboot/drivers/mmc/s3c_hsmmc.c文件中也凑活能说过去。
int mmc_register(struct mmc *mmc){/* Setup the universal parts of the block interface just once */mmc->block_dev.if_type = IF_TYPE_MMC;mmc->block_dev.dev = cur_dev_num++;mmc->block_dev.removable = 1;mmc->block_dev.block_read = mmc_bread;mmc->block_dev.block_write = mmc_bwrite;
INIT_LIST_HEAD(&mmc->link);
list_add_tail(&mmc->link, &mmc_devices);
return 0;}
浙公网安备 33010602011771号