站在芯片原厂角度移植最新u-boot 2020.07到jz2440开发板(1)(全部驱动使用设备树与dm设备模型)

1. 概述

1.1 缘由

最近在待业,由于之前的工作是在一个初创的芯片原厂做底层驱动开发,所以最近想着能不能以一个芯片原厂的角度移植最新的u-boot到jz2440开发板呢?于是便有了这篇文章。

整篇文章遵循着以问题为导向的讲解方式,重实践,基本没有原理性的知识,并且从零开始进行移植。我相信,如果你能认真的跟着文章实际操作一把,以后不管遇到什么类型的芯片、什么类型的开发板,同样都能完成从零移植u-boot的工作。

1.2 实现的功能

移植的u-boot 2020.07目前实现的功能如下:

  1. 如标题所述,全部驱动使用设备树与dm设备模型
  2. 实现的驱动:
  • 串口驱动
  • sdcard驱动
  • usb udc驱动
  • nand flash驱动
  • gpio子系统驱动
  • pinctrl子系统驱动
  • clock子系统驱动
  1. 硬件无关的命令可以按需添加
  2. 硬件相关的命令:
  • 一些基本的必须的命令,如nand,mmc等

  • ums命令(通过usb总线,支持在pc端查看sdcard中的文件系统,暂时由于sdcard驱动问题导致当分区文件内容比较大时会不稳定)

  • dfu命令(通过usb总线,支持在pc端下载文件到nand flash)

  1. 其他:
  • 支持从nand flash启动u-boot以及内核
  • 支持从sdcard启动u-boot以及内核,内核映像文件以及dtb文件放在sdcard的第一个vfat格式分区中,根文件系统放在第二个ext4格式分区中

1.3 后续文章写作思路

我打算把移植工作分为一系列的文章来进行讲解:

  1. 本篇文章,讲解到以最小的改动实现最初始的打印u-boot启动信息功能为止;
  2. 以驱动为分类标准,之后的每一个驱动单独用一篇文章来讲解。

另外,之后如果有时间会有相同思路的移植最新linux内核的系列文章,敬请期待!

2. spl闪亮登场

2.1 引入

在讨论u-boot之前,我们需要认识另一位兄弟——spl,这里我暂且称为spl,首先它并不是u-boot中的spl(second program loader,第二程序加载器,相对于soc中固化的bootcode代码而言),但是作用是相同的,那具体是什么呢?

我这里所谓的spl,主要是参考韦东山老师的自己写bootloader的课程中的内容,一个独立于u-boot的工程。

如果将u-boot直接放在nand flash的0地址处,我们知道如果设置开发板为nand flash启动,s3c2440上电后会自动从nand flash复制4KB代码到内部ram,然后运行。这样,u-boot的重定位代码可能会在4KB之后,那之后的u-boot的代码就不会被重定位到sdram,这样显然是不行的。

所以,spl便是我的一个解决方法:

  1. 首先初始化时钟、sdram以及外部存储设备;
  2. 然后将u-boot从外部存储设备(sdcard或者nand flash)直接复制到sdram中;
  3. 最后调整pc指针到u-boot所在的sdram地址处直接运行u-boot。

2.2 工程构成

整个spl工程构成如下图所示,
在这里插入图片描述可以看到,基本和韦东山老师自己写bootloader的课程中的内容一致,唯一不同的一点是,之前由于学习sdcard裸机驱动,我在网上找了一个s3c2440的sdcard裸机驱动代码,现在正好,如果将u-boot.bin放到sdcard中相比于nandflash,对于调试来说那将是非常的方便,只要有一个读卡器,复制一个几百k的u-boot.bin不需要一秒就可以完成。

2.3 整体思路

整个spl的思路如下:

  1. 首先初始化栈、时钟、sdram、nand flash,

    .text
    
    .global _start
    
    /*****************************************************************************/
    
    @程序入口
    
    _start:
    @ 0x00: 复位异常的向量地址
    	b Reset
    @ 0x04: 未定义指令中止模式的向量地址
    HandleUndef:
    	b HandleUndef
    @ 0x08: 管理模式的向量地址(在用户模式下,可通过SWI指令进入该模式)
    HandleSvc:
    	b HandleSvc
    @ 0x0c: 指令预取中止异常的向量地址
    HandlePrefetchAbort:
    	b HandlePrefetchAbort
    @ 0x10: 数据访问中止异常的向量地址
    HandleDataAbort:
    	b HandleDataAbort
    @ 0x14: 保留
    HandleNotUsed:
    	b HandleNotUsed
    @ 0x18: 中断模式的向量地址
    HandleIRQ:
    	b HandleIRQ
    @ 0x1c: 快中断模式的向量地址
    HandleFIQ:
    	b HandleFIQ
    
    /*****************************************************************************/
    
    @复位异常入口
    Reset:
    	ldr sp, =4096     	    	@重启默认进入SVC模式,此处设置SVC模式栈指针
    
    	bl disable_watch_dog
    	bl clock_init
    	bl sdram_init
    
    	bl nand_init		    	@初始化 NAND Flash
    
  2. 由于编译后的spl.bin大概十几k,并且s3c2440上电后只会从nandflash复制前4KB代码到内部ram,所以我首先在spl的前4k代码中将spl自己从nandflash复制到sdram的起始位置0x30000000处,然后跳到sdram中接着执行spl;

    	@nand_read函数需要3个参数:
    	ldr r1, =0x30000000     	@1.目标地址=0x30000000,这是SDRAM的起始地址
    	mov r0, #0		    		@2.源地址 = 0
    	mov r2, #(1024*20)	    	@3.复制长度 = 20KB
    	bl  nand_read		    	@调用C函数nand_read
    
       	ldr pc, =on_sdram           @跳到SDRAM中继续执行
    
    on_sdram:
        msr cpsr_c, #0xdf       @进入系统模式
        ldr sp, =0x34000000     @设置系统模式栈指针
        
    @	ldr lr, =loop
    	ldr lr, =0x33f00000
    	ldr pc, =main
    
    loop:
    	b loop
    
  3. 在c代码main函数中,初始化串口用于调试,接着初始化sdcard,最后,从sdcard的指定位置处复制提前在pc上写好的u-boot.bin到u-boot.bin的链接地址0x33f00000处;

    #include <uart.h>
    #include <sd.h>
    
    #define SD_BLOCK_SIZE			(512)
    
    void delay(void)
    {
    	/* 由于使用了-O2优化编译选项,volatile的作用在于
    	   告诉编译器空循环不要优化,否则会被优化为空语句 */
    	volatile unsigned long i, j;
    
    	for (i = 0; i < 300; ++i)
    		for (j = 0; j < 300; ++j);
    }
    
    void main(void)
    {	
    	unsigned int *uboot_addr_on_sdram = (unsigned int *)0x33f00000;		// 将u-boot读取到sdram的0x33f00000地址处
    	unsigned int uboot_addr_on_sdcard = 2 * 1024 / SD_BLOCK_SIZE;		// u-boot存放在sdcard的2KB地址处
    	unsigned int block_num = (512 * 1024) / SD_BLOCK_SIZE;				// u-boot存放在sdcard的大小为512KB
    	// void (*theKernel)(void);
    
    	uart0_init();
    	delay();
    	sd_init();
    
    	printk("\n\rBegin to copy uboot from sdcard(%dKB) to sdram(0x%x)...",
    			uboot_addr_on_sdcard * 512 / 1024, uboot_addr_on_sdram);
    
    	if (!sd_read_sector(uboot_addr_on_sdram, uboot_addr_on_sdcard, block_num)) {
    		printk("   [done]\n\r");
    	} else {
    		printk("   [failed]\n\r");
    		printk("Please check your sdcard!\n\r");
    		while (1);
    	}
    
    	printk("\n\rbooting uboot...");
    
    	// theKernel = (void (*)(void))uboot_addr_on_sdram;
    	// theKernel();
    }
    
  4. main函数返回到汇编代码start.S中,提前在跳转main前设置为0x33f00000的lr寄存器将发挥作用,将0x33f00000赋值给pc,然后就开始u-boot.bin的天下了!

    @	ldr lr, =loop
    	ldr lr, =0x33f00000
    	ldr pc, =main
    
    loop:
    	b loop
    

2.4 需要改进的地方

现在对于spl,我发现有两点需要改进:

  1. 整个spl能否小于4k?这样就不用再进行搬移了;
  2. sdcard驱动在我的4g的卡上运行没有任何问题,但是换成16g的卡就会初始化失败,现在还没去解决,当然后期调试好后也可以将u-boot.bin放到nandflash中;

3. 获取u-boot源码

好的,我们正式开始移植u-boot之旅!

正所谓巧妇难为无米之炊,u-boot的源码我是从其官方的github上克隆下来的,这样可以保证是最新的。具体我是这样做的:

  1. 将github上u-boot的工程fork到自己的github仓库:

    u-boot的github网址:https://github.com/u-boot/u-boot

    进入该网址,点击右上角的fork按钮,
    在这里插入图片描述
    ​ 这样,便将u-boot工程克隆到了自己的github仓库中,

在这里插入图片描述
2. 之后就是正常的git操作了:

点击自己github仓库中刚刚克隆下来的u-boot页面中的Clone or download这个绿色按钮复制git地址到系统剪贴板,然后在pc终端中执行如下命令:
在这里插入图片描述​ 根据网络的速度不同,需等待一段时间。完成后,便将u-boot的源码下载到了当前目录下,目录名称是u-boot。

4. 明确移植方向

源码已经有了,但是在动手之前,我们可以先大概思考一下:

我们知道,u-boot是一个裸机驱动集大成者,它能运行在各种不同架构的芯片为核心的各种不同的单板上面,那它是如何做到的呢?答案也很简单:

  • 首先,很容易想到的是,它需要有针对各种不同芯片、单板的驱动程序文件;

  • 然后,它还需要一个很好的中间框架层,以抽取各种不同架构芯片、单板的共性,向上提供相同的api给上层的各种命令,向下屏蔽这些芯片、单板不同的硬件特性,将这些硬件特性操作留给驱动程序。

很显然,这用到了分层思想,一旦A需要兼容各种不同的B、C、D等等时,分层思想总能让你达到目的。

接下来,我们看一下u-boot 2020.07的顶层目录,
在这里插入图片描述各种目录的功能这里我就不做详细介绍了,这里我想重点说明的是:

  • 针对上述的第一点,arch/、board/、drivers/ 主要描述了不同架构芯片、单板的特性;

  • 针对第二点的共性,基本包括了剩下的所有除了编译相关的目录、文件。

所以,弄清楚了上述的问题之后,我们便很容易想到移植的工作大致方向在哪:没错,就是在arch/、board/、drivers/这三个目录!

4.1 arch/目录

  1. arch,描述了各种不同架构的cpu,比如我们熟悉的arm,还有诸如mips、powerpc等等,接下来我们以arm架构为例进行说明:
    在这里插入图片描述
  2. arch/arm/cpu,描述了同一cpu架构下的不同代产品,比如s3c2440使用的arm920t,还有armv7、armv8等等:
    在这里插入图片描述
  3. arch/arm,描述了同一cpu架构下的不同的soc厂家,下图中他们都使用了arm架构,但是会有许多不同的soc芯片产品,比如三星、nxp、瑞芯微、全志等等,接下来以瑞芯微为例进行说明
    在这里插入图片描述
  4. arch/arm/mach-rockchip,是对国产芯片公司瑞芯微旗下的soc芯片的描述,比如rk3128、rk3328等等:
    在这里插入图片描述

4.2 board/目录

  1. board,描述了不同单板厂家的开发板产品,由于目录众多,下图只截取了部分:
    在这里插入图片描述
  2. board/xxx,描述了使用同一soc芯片厂家产品的不同的开发板产品,比如都是使用了三星的s3c2440,除了三星的公版开发板smdk2440外,还有jz2440、mini2440、tq2440等等:
    在这里插入图片描述

4.3 drivers/目录

该目录下便是各种soc芯片内部的各种控制器主机驱动,以及各种外设驱动的天下了。

这里顺便提一下,驱动的编写并不会关心你的cpu核是arm还是mips,是arm920t还是armv7,他们的差别只在于不同汇编启动代码以及编译器,我们都是使用c语言进行驱动程序的编写,驱动关心的是soc芯片内部的控制器寄存器以及各种不同外设的寄存器。

所以,驱动编程对应的硬件对象主要包括两点:

  1. soc内部的各种控制器,比如usb控制器、sd/mmc控制器、spi控制器、中断控制器、gpio控制器等等,在驱动程序框架中这部分一般被称为主机端驱动,基本是由soc厂家来完成驱动的编写工作,相对复杂些;
  2. 各种外设,比如led、按键、lcd、各种传感器等等,这部分一般由外设厂家或是具体的开发板厂家进行驱动的编写工作,相对简单些。
    在这里插入图片描述
  • 3
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值