2020年1月

使用 QEMU 运行 RT-Thread
qemu-system-arm仿真vexpress-a9踩坑记

buildroot 编译:

修改 qemu_arm_vexpress_defconfig, 添加 linux kernel git 地址:

# Architecture
BR2_arm=y
BR2_cortex_a9=y
BR2_ARM_ENABLE_NEON=y
BR2_ARM_ENABLE_VFP=y
BR2_ARM_FPU_VFPV3D16=y

# System
BR2_SYSTEM_DHCP="eth0"
BR2_TARGET_GENERIC_GETTY_PORT="ttyAMA0"

# Filesystem
BR2_TARGET_ROOTFS_EXT2=y
# BR2_TARGET_ROOTFS_TAR is not set

# Linux headers same as kernel, a 4.19 series
BR2_PACKAGE_HOST_LINUX_HEADERS_CUSTOM_4_19=y

# Kernel
BR2_LINUX_KERNEL=y

#BR2_LINUX_KERNEL_CUSTOM_VERSION=y
#BR2_LINUX_KERNEL_CUSTOM_VERSION_VALUE="4.19.90"
BR2_LINUX_KERNEL_CUSTOM_GIT=y
BR2_LINUX_KERNEL_CUSTOM_REPO_URL="http://172.17.0.1:3000/<path>/linux-4.19.90.git"
BR2_LINUX_KERNEL_CUSTOM_REPO_VERSION="b27c50698362fa3269acbafea0006053883cf696"

BR2_LINUX_KERNEL_DEFCONFIG="vexpress"
BR2_LINUX_KERNEL_DTS_SUPPORT=y
BR2_LINUX_KERNEL_INTREE_DTS_NAME="vexpress-v2p-ca9"

# host-qemu for gitlab testing
BR2_PACKAGE_HOST_QEMU=y
BR2_PACKAGE_HOST_QEMU_SYSTEM_MODE=y

开始编译:

make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- qemu_arm_vexpress_defconfig
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf-

输出目录文件:

  • ./output/images
  • rootfs.ext2
  • vexpress-v2p-ca9.dtb
  • zImage

根文件目录:

  • ./output/target

qemu 运行

  • -M: 指定模拟器类型
  • -cpu:指定 CPU 类型
  • -m: 指定内存大小
  • -kernel:指定内核文件
  • -dtb: dtb 文件
  • -append:kernel 内核参数, "root=/dev/mmcblk0 console=ttyAMA0"
  • -serial: 指定串行接口数据
  • -sd: 指定 SD 镜像文件挂载
  • -net nic -net tap,ifname=tap:tap 虚拟网卡
  • -display: 显示模式,例如: "-display sdl"
  • -S: Do not start CPU at startup (you must type ’c’ in the monitor).
@echo off
if exist sd.raw goto run
qemu-img create -f raw sd.raw 32M

:run
qemu-system-arm -M vexpress-a9 -cpu cortex-a9 -m 256 -kernel zImage -s -serial stdio -sd rootfs.ext2 -dtb vexpress-v2p-ca9.dtb -append "root=/dev/mmcblk0 console=ttyAMA0" -net nic -net tap,ifname=tap -display sdl

制作 ext2 格式的 sd 卡
dd if=/dev/zero of=a9rootfs.ext2 bs=1M count=32
mkfs.ext2 a9rootfs.ext2
sudo mkdir tmpfs
sudo mount -t ext2 a9rootfs.ext2 tmpfs/ -o loop
sudo cp zImage.img //将用mkimage制作好的内核文件拷到此处
sudo umount tmpfs

https://www.veryarm.com/115692.html

编译uboot

编译内核

用mkimage制作文件

mkimage -n 'linux-2.6.14' -A arm -O linux -T kernel -C none -a 0x60008000 -e 0x60008000 -d zImage zImage.img

制作ext2格式的sd卡

dd if=/dev/zero of=a9rootfs.ext2 bs=1M count=32
mkfs.ext2 a9rootfs.ext2
sudo mkdir tmpfs
sudo mount -t ext2 a9rootfs.ext2 tmpfs/ -o loop
sudo cp zImage.img //将用mkimage制作好的内核文件拷到此处
sudo umount tmpfs

启动qemu,

qemu-system-arm -M vexpress-a9 -m 256M -nographic -kernel u-boot -sd a9rootfs.ext2

在qemu环境下:

=> ext2load mmc 0 60008000 zImage.img
1965048 bytes read in 1225 ms (1.5 MiB/s)

=> bootm 60008000

Booting kernel from Legacy Image at 60008000 ...

Image Name: linux-2.6.14
Image Type: ARM Linux Kernel Image (uncompressed)
Data Size: 1964984 Bytes = 1.9 MiB
Load Address: 60008000
Entry Point: 60008000
Verifying Checksum ... OK
Loading Kernel Image ... OK

Starting kernel ...

https://www.cnblogs.com/zhuangquan/category/1519777.html

1.uboot中的环境变量

bootdelay:执行自动启动的等候秒数
baudrate:串口控制台的波特率
netmask: 以太网接口的掩码
ethaddr:  以太网卡的网卡物理地址
bootfile:   缺省的下载文件
bootargs:传递给内核的启动参数
bootcmd:自动启动时执行的命令
serverip: 服务器端的ip地址
ipaddr:    本地ip 地址
stdin:      标准输入设备
stdout:    标准输出设备
stderr:    标准出错设备

  以上是一些基本的环境变量。uboot中一般会有一些缺省的环境变量。在启动uboot后会将参数放在特定的FLASH区域,之后由kernel去获取解析。还有另一种方法设置环境变量就是在uboot启动后进入命令行模式,设置环境变量,然后执行saveenv后,会将设置的环境变量保存到特定区域的FLASH中,由kernel去获取解析。

  其中bootargs和bootcmd相对比较重要。

2.bootargs解析

root:

  目前很多新的开发板都是使用FLASH作为存储。因为很多都直接使用MTD驱动程序。MTD 驱动程序的主要优点在于 MTD 驱动程序是专门为基于闪存的设备所设计的,所以它们通常有更好的支持、更好的管理和基于扇区的擦除和读写操作的更好的接口。Linux 下的 MTD 驱动程序接口被划分为两类模块:用户模块和硬件模块。

  有两个流行的用户模块可启用对闪存的访问: MTD_CHAR 和 MTD_BLOCK 。
  MTD_CHAR 提供对闪存的原始字符访问,而 MTD_BLOCK 将闪存设计为可以在上面创建文件系统的常规块设备(象 IDE 磁盘)。与 MTD_CHAR 关联的设备是 /dev/mtd0、mtd1、mtd2(等等),而与 MTD_BLOCK 关联的设备是 /dev/mtdblock0、mtdblock1(等等)。由于 MTD_BLOCK 设备提供象块设备那样的模拟,通常更可取的是在这个模拟基础上创建象 FTL 和 JFFS2 那样的文件系统。

  本人经常使用的是直接将root挂载/dev/mtdblock2中。

  root=/dev/mtdblockx rw(x=0,1,2..这个是根据自己mtd的分区,看自己要将文件系统放在哪个分区中)

  还有其他格式:比如root=/dev/mtdx rw,root=/dev/ram rw。这个是根据自己内核和FLASH不同改变的。

  还有一种比较特殊的挂载文件系统的方法就是文件系统不在开发板上。而是用NFS共享的服务器上的。当然指定root=/dev/nfs之后,还需要指定服务器的IP地址。nfsroot=serverip:nfs_dir,serverip是服务器的IP,dir即指明文件系统存在那个主机的那个目录下面。

  root=/dev/nfs

  eg:root=/dev/nfs rw nfsroot=10.103.4.216:/nfsroot/rootfs ip=10.103.4.211

  root=/dev/ram rw

  root=/dev/ram0 rw

rootfstype:

  指明挂载哪种文件系统

  eg: rootfstype=jffs2

console:

  console=ttySAC0,115200 控制台使用串口0,波特率115200.

  区分一下:

串行端口终端(/dev/ttySn )
伪终端(/dev/pty/ )
控制终端(/dev/tty )
控制台终端(/dev/ttyn, /dev/console )

ttyS、ttySAC、tty、ttyn的区别:https://blog.csdn.net/qq_21792169/article/details/51034165

mem:

  mem=xxM 指定内存的大小,不是必须的。

  eg:mem=128M

ramdisk:

  首先介绍一下ramdisk,Ramdisk是虚拟于RAM中的盘(Disk)。对于用户来说,能把RAM disk和通常的硬盘分区(如/dev/hda1)同等对待来使用

  注意:在2.6版本后,Ramdisk的这一作用开始被tmpfs(Virtual memory file system support)取代。

  为了能够使用RAMdisk你的内核必须要支持RAM disk,即:在编译内核时,要选中RAM disk support这一选项,会在配置文件中定义CONFIG_BLK_DEV_RAM。为了让内核有能力在内核加载阶段就能装入RAMDISK,并运行其中的内容,要选中initial RAM disk(initrd) support选项,会在配置文件中定义CONFIG_BLK_DEV_INITRD。

  ramdisk一个作用就是用来解决boot过程中mount根文件系统的“先有鸡还是先有蛋”的问题的。一般来说,根文件系统在形形色色的存储设备上,不同的设备又要不同的硬件厂商的驱动,比如intel的南桥自然需要intel的ide/sata驱动,VIA的南桥需要VIA的ide/sata驱动,根文件系统也有不同的文件系统的可能,比如ubuntu发行版可能一般用ext3,suse可能就不是了,不同的文件系统也需要不同的文件系统模块;假如把所有驱动/模块都编译进内核(注:即编一个通用的、万能的内核),那自然没问题,但是这样就违背了“内核”的精神或本质,所以一般来说驱动/模块都驻留在根文件系统本身上/lib/modules/xxx,那么“鸡蛋”问题就来了,现在要mount根文件系统却需要根文件系统上的模块文件,怎么办?于是,就想出ramdisk,内核总是能安装ramdisk的(注:这是因为ramdisk临时文件系统和内核一样,也是由bootloader通过低级读写命令(如uboot用nand read,而不用通过文件系统层提供的高级读写接口)加载进内存,因此内核可以挂载内存里ramdisk文件系统),然后把所有可能需要的驱动/模块都放在ramdisk上,首先,让内核将ramdisk当作根文件系统来安装,然后再用这个根文件系统上的驱动来安装真正的根文件系统,就将这个矛盾问题解决了 .

  linux中RamDisk的三种实现方式:https://blog.csdn.net/alextanghao/article/details/2660656  

  ramdisk=xxxxx 不推荐
  ramdisk_size=xxxxx 推荐

  上 面这两个都可以告诉ramdisk 驱动,创建的ramdisk的size,你可以查看Documentation/ramdisk.txt找到相关的描述,不过ramdisk=xxxxx在新版的内核都已经没有提了,不推荐使用

 

initrd, noinitrd:

  当你没有使用ramdisk启动系统的时候,你需要使用noinitrd这个参数,但是如果使用了的话,就需要指定initrd=r_addr,size,r_addr表示initrd在内存中的位置,size表示initrd的大小。

  initrd使用来初始化ramdisk用的。

init:

  init 指定的是内核启起来后,进入系统中运行的第一个脚本,一般init=/linuxrc,或者init=/etc/preinit,preinit的内容一般是创建console,null设备节点,运行init程序,挂载一些文件系统等等操作。请注意,很多初学者以为init=/linuxrc是固定写法,其实不然,/linuxrc指的是/目录下面的linuxrc脚本,一般是一个连接罢了。

mtdparts:

  mtdparts=fc000000.nor_flash:1920k(linux),128k(fdt),20M(ramdisk),4M(jffs2),38272k(user),256k(env),384k(uboot)要 想这个参数起作用,内核中的mtd驱动必须要支持,即内核配置时需要选上Device Drivers ---> Memory Technology Device (MTD) support---> Command line partition table parsing.

  其中fc000000.nor_flash是mtd-id ,必须要跟你当前平台的flash的mtd-id一致,不然整个mtdparts会失效。(可以在设备树中找到mtd-id)

  mtdparts=sa1100:256k(ARMboot)ro,-(root) → 有两个分区。其中 ‘-’表示剩余的空间。

说完常见的几种bootargs,那么我们来讨论平常我经常使用的几种组合:

1). 假设文件系统是ramdisk,且直接就在内存中,bootargs的设置应该如下:
setenv bootargs ‘initrd=0x32000000,0xa00000 root=/dev/ram0 console=ttySAC0 mem=64M init=/linuxrc’

2). 假设文件系统是ramdisk,且在flash中,bootargs的设置应该如下:
setenv bootargs ‘mem=32M console=ttyS0,115200 root=/dev/ram rw init=/linuxrc’
注意这种情况下你应该要在bootm命令中指定ramdisk在flash中的地址,如bootm
kernel_addr ramdisk_addr (fdt_addr)

3). 假设文件系统是jffs2类型的,且在flash中,bootargs的设置应该如下
setenv bootargs ‘mem=32M console=ttyS0,115200 noinitrd root=/dev/mtdblock2 rw rootfstype=jffs2 init=/linuxrc’

4). 假设文件系统是基于nfs的,bootargs的设置应该如下
setenv bootargs ‘noinitrd mem=64M console=ttySAC0 root=/dev/nfs nfsroot=192.168.0.3:/nfs ip=192.168.0.5:192.168.0.3:192.168.0.3:255.255.255.0::eth0:off’
或者
setenv bootargs ‘noinitrd mem=64M console=ttySAC0 root=/dev/nfs nfsroot=192.168.0.3:/nfs ip=192.168.0.5’

3.bootcmd

  eg:setenv bootcmd 'nand read 0x42000000 0x100000 0x400000;bootm 0x42000000'

   指令 Flash内地址 擦除长度
  nand erase 0x100000 0x200000

    指令                内存中地址          Flash内地址    写入长度

  nand write 0x20000000 0x100000 0x200000

    nand read       0x20000000        0x100000        0x200000

  有待确认。。。。