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

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

站在芯片原厂角度移植最新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子系统驱动

          3. 硬件无关的命令可以按需添加

      4. 硬件相关的命令:

  •             - 一些基本的必须的命令,如nand,mmc等

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

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

           5. 其他:

  •             - 支持从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工程构成如下图所示,

attachments-2020-06-yuyoq3V35ee11167c168d.jpg

可以看到,基本和韦东山老师自己写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按钮,

attachments-2020-06-z2HAjvrj5ee0f5de9ac20.jpg

这样,便将u-boot工程克隆到了自己的github仓库中,

attachments-2020-06-SQPbBYV45ee0f61120647.jpg


  1. 之后就是正常的git操作了:

    点击自己github仓库中刚刚克隆下来的u-boot页面中的Clone or download这个绿色按钮复制git地址到系统剪贴板,然后在pc终端中执行如下命令:

attachments-2020-06-eMEvzdwP5ee0f62c8e5f1.jpg

        根据网络的速度不同,需等待一段时间。完成后,便将u-boot的源码下载到了当前目录下,目录名称是u-boot。

4. 明确移植方向

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

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

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

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

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

接下来,我们看一下u-boot 2020.07的顶层目录,

attachments-2020-06-A6tPXYi45ee0f6ad6e078.jpg

各种目录的功能这里我就不做详细介绍了,这里我想重点说明的是:

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

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

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

4.1 arch/目录

  1. arch,描述了各种不同架构的cpu,比如我们熟悉的arm,还有诸如mips、powerpc等等,接下来我们以arm架构为例进行说明:

attachments-2020-06-BhjDbtoG5ee0f6dda5728.jpg

    
     2.arch/arm/cpu,描述了同一cpu架构下的不同代产品,比如s3c2440使用的arm920t,还有armv7、armv8等等:

                    attachments-2020-06-0GYInX7v5ee1126456335.jpg

    3. arch/arm,描述了同一cpu架构下的不同的soc厂家,下图中他们都使用了arm架构,但是会有许多不同的soc芯片产品,比如三星、nxp、瑞芯微、全志等等,接下来以瑞芯微为例进行说明

attachments-2020-06-wBYiXkT65ee0f72484343.jpg


    4. arch/arm/mach-rockchip,是对国产芯片公司瑞芯微旗下的soc芯片的描述,比如rk3128、rk3328等等:

attachments-2020-06-Yxv0EAXm5ee112916ea77.jpg

4.2 board/目录

  1. board,描述了不同单板厂家的开发板产品,由于目录众多,下图只截取了部分:

    attachments-2020-06-VrM4TT4q5ee0f73ac701c.jpg

  2. board/xxx,描述了使用同一soc芯片厂家产品的不同的开发板产品,比如都是使用了三星的s3c2440,除了三星的公版开发板smdk2440外,还有jz2440、mini2440、tq2440等等:

    attachments-2020-06-7Azr7mHf5ee0f7be6005f.jpg

4.3 drivers/目录

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

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

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

  1. soc内部的各种控制器,比如usb控制器、sd/mmc控制器、spi控制器、中断控制器、gpio控制器等等,在驱动程序框架中这部分一般被称为主机端驱动,基本是由soc厂家来完成驱动的编写工作,相对复杂些;

  2. 各种外设,比如led、按键、lcd、各种传感器等等,这部分一般由外设厂家或是具体的开发板厂家进行驱动的编写工作,相对简单些。

attachments-2020-06-JZkDHD7B5ee0f764ac9ec.jpg



  • 发表于 2020-06-11 01:09
  • 阅读 ( 2268 )
  • 分类:经验分享

3 条评论&回复

请先 登录 后评论
渐进
渐进

12 篇文章

作家榜 »

  1. 百问网-周老师 18 文章
  2. st_ashang 14 文章
  3. 渐进 12 文章
  4. zxq 11 文章
  5. helloworld 8 文章
  6. 谢工 5 文章
  7. Litchi_Zheng 5 文章
  8. DebugArm 5 文章