“Qemu”的版本间的差异
来自百问网嵌入式Linux wiki
(未显示5个用户的58个中间版本) | |||
第1行: | 第1行: | ||
− | = QEMU使用手册 = | + | ={{redtext|Part A. QEMU使用手册}}= |
− | == QEMU简介 == | + | ==QEMU简介== |
− | + | : QEMU的英文单词是:QuickEmulator,它是一个小巧的模拟器。还有很多模拟器,比如VMWare、Virtual Box等。 | |
− | + | : 但是VMWare、VirtualBox只能模拟x86、AMD64/Intel64等PC系统;而QEMU可以模拟更多硬件:ARM、MIPS、PPC、x86、AMD64/Intel64。 | |
− | + | : QEMU用途广泛,比如Xen、Android模拟器等都是基于QEMU的。 | |
− | + | : 在嵌入式领域,很多人使用QEMU来深研Linux,比如研究文件系统、优化等等。 | |
− | + | : QEMU有两种模式: | |
− | + | ===用户模式(User Mode)=== | |
− | + | : 简单地说,一个使用arm-xxx-gcc编译出来的程序,是给ARM板子使用的,它无法在PC机上运行,只能放到ARM板子上去运行。 | |
− | + | : 借助qemu,可以在PC机上运行ARM程序。比如: | |
− | |||
− | |||
− | |||
− | |||
− | |||
− | === 用户模式(User Mode) === | ||
− | |||
− | |||
− | |||
− | |||
<syntaxhighlight lang="bash"> | <syntaxhighlight lang="bash"> | ||
− | $ gcc -o hello hello.c -static | + | $ gcc -o hello hello.c -static |
− | $ ./hello // 这个hello程序是使用gcc给PC机编译的,可以直接运行 | + | $ ./hello // 这个hello程序是使用gcc给PC机编译的,可以直接运行 |
− | Hello, world! | + | Hello, world! |
− | $ arm-linux-gnueabihf-gcc -o hello hello.c -static // 它是给ARM板子编译的 | + | $ arm-linux-gnueabihf-gcc -o hello hello.c -static // 它是给ARM板子编译的 |
− | $ ./hello // 所以无法在PC上运行 | + | $ ./hello // 所以无法在PC上运行 |
− | bash: ./hello: cannot execute binary file: Exec format error | + | bash: ./hello: cannot execute binary file: Exec format error |
− | $ ./qemu-arm ./hello // 我们可以用QEMU在PC上运行它 | + | $ ./qemu-arm ./hello // 我们可以用QEMU在PC上运行它 |
− | Hello, world! | + | Hello, world! |
</syntaxhighlight> | </syntaxhighlight> | ||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
+ | : 在PC上使用qemu运行单个ARM程序时,这就是使用QEMU的用户模式。 | ||
+ | : 它会把ARM指令翻译为PC的指令去运行。 | ||
+ | : '''{{redtext|注意}}''',你可能无法做上述实验,因为: | ||
+ | :: a) 你没有安装ARM交叉编译工具链 | ||
+ | :: b) 你没有安装QEMU | ||
+ | : 你根据下章《QEMU快速使用》安装QEMU后,就可以进行上述实验了。 | ||
− | + | ===系统模式(System Mode)=== | |
+ | : 很多时候我们并不满足于在PC上运行单个ARM程序,我们想模拟出整个ARM单板:在这个模拟出来的虚拟ARM单板上,运行Linux系统,在其中运行各种APP。 | ||
+ | : 这时候需要使用QEMU的系统模式。 | ||
+ | : 我们就是使用QEMU的系统模式来模拟IMX6ULL开发板,具体的使用请看下章。 | ||
+ | ===我们做的改进=== | ||
+ | : QEMU可以模拟x86,也可以模拟各种ARM板子,还可以模拟各种外设。 | ||
+ | : 百问网对QEMU做了很多改进,支持更多硬件,支持更多GUI现实,支持更方便的调试。 | ||
+ | '''100ask-qemu特点''' | ||
+ | : 1.模拟网卡 | ||
+ | : 2.模拟LCD显示功能 | ||
+ | : 3.模拟led灯、按键 | ||
+ | : 4.模拟at24cxx i2c存储芯片,直接可以通过用户态操作看到效果 | ||
+ | : 5.增加逻辑分析仪显示功能 | ||
+ | : 6.后续会逐渐增加更多的模拟硬件模块 | ||
+ | :: 6.1 温湿度传感器 | ||
+ | :: 6.2 红外 | ||
+ | :: 6.3 超声波模块 | ||
+ | :: 6.4 ADC,DAC模块 | ||
+ | :: 6.5 I2C接口的传感器 | ||
+ | :: 6.6 SPI接口的OLED | ||
+ | :: 6.7 你想加啥,跟我们说 | ||
− | + | ==QEMU快速使用== | |
+ | : 使用apt-get当然也可以安装QEMU,但是它版本太低,也不支持IMX6ULL。 | ||
+ | : 新版本QEMU已经支持IMX6ULL,我们在此基础上添加了更多功能,也修改了一些BUG。 | ||
+ | : 所以,请参考本文安装我们定制的QEMU。 | ||
+ | ===准备工作=== | ||
+ | ====Windows电脑和VMWare虚拟机==== | ||
+ | : {{redtext|必须}}的: | ||
+ | :: ①一台可以上网的windows电脑 | ||
+ | :: ②一个VMWare虚拟机,里面运行Ubuntu,也要能上网 | ||
+ | : VMWare虚拟机软件和Ubuntu映象可以从网盘中下载,打开以下链接: | ||
+ | : [[Download_link_page| 百度网盘下载地址]] | ||
+ | : 找到“韦东山升级版全系列视频开发板BSP包”,打开对应的网盘链接后,可以看到一个目录“100ask_imx6ull-qemu”,进入“01_Tools”子目录 : | ||
+ | [[File:100ask_imx6ull_qemu_081.png|600px]] | ||
− | + | ====确保Ubuntu可以上网,可以跟Windows联通==== | |
+ | : {{redtext|注意}}:很多人碰到VMWare中Ubuntu联网问题问题,可以参考此链接: | ||
+ | :: [[VMwareAndUbuntuNetworkSetupGuide | VMWare和Ubuntu网络设置]] | ||
− | + | ====安装KVM==== | |
− | + | : {{redtext|可选}}的(注意,如果想要有更快的效果,在ubuntu下可以安装KVM): | |
− | + | : 安装kvm加速qemu运行,在终端下执行如下命令: | |
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
<syntaxhighlight lang="bash"> | <syntaxhighlight lang="bash"> | ||
− | $ sudo apt-get update | + | $ sudo apt-get update |
− | $ sudo apt-get install qemu qemu-kvm libvirt-bin bridge-utils virt-manager | + | $ sudo apt-get install qemu qemu-kvm libvirt-bin bridge-utils virt-manager |
</syntaxhighlight> | </syntaxhighlight> | ||
− | === 获取镜像 === | + | ===获取镜像=== |
− | + | ====下载==== | |
− | + | : 我们只提供ubuntu 16.04和ubuntu 18.04系统的QEMU镜象文件。 | |
− | + | {{Redtext|点击如下链接下载<br>}} | |
− | + | [[download_ubuntu-18.04_imx6ul_qemu | ubuntu-18.04开发环境下qemu imx6ul系统镜像下载页面]]<br> | |
− | + | [[download_ubuntu-16.04_imx6ul_qemu | ubuntu-16.04开发环境下qemu imx6ul系统镜像下载页面]]<br> | |
− | + | : 下载完成后可以得到ubuntu-16.04_imx6ul_qemu_system、ubuntu-18.04_imx6ul_qemu_system这样的目录 | |
− | + | : 然后进入这些目录执行后文介绍的命令。 | |
− | |||
− | |||
− | |||
− | < | ||
+ | ====镜像目录结构==== | ||
+ | : 目录结构及说明如下,在后续的开发过程中,我们有可能更换红框中的文件: | ||
+ | [[File:100ask_imx6ull_qemu_082.png|800px]] | ||
− | + | ===运行QEMU系统=== | |
− | + | : 假设你已经按照上文下载、解压好了QEMU镜像文件,你需要进入QEMU的目录,执行下列命令。 | |
− | + | ====首次运行需要安装SDL环境==== | |
+ | : 使用脚本自动解压安装: | ||
<syntaxhighlight lang="bash"> | <syntaxhighlight lang="bash"> | ||
− | $ | + | $./install_sdl.sh // 提示输入用户密码,等待安装完成 |
</syntaxhighlight> | </syntaxhighlight> | ||
− | + | 如果出现报错 ''Package xxxx is not installed.'',可使用apt命令修复: | |
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
<syntaxhighlight lang="bash"> | <syntaxhighlight lang="bash"> | ||
− | $ | + | $sudo apt --fix-broken install |
</syntaxhighlight> | </syntaxhighlight> | ||
− | |||
− | + | ====运行带GUI的imx6ul模拟器==== | |
+ | : ①模拟百问网imx6ull-qemu开发板 | ||
<syntaxhighlight lang="bash"> | <syntaxhighlight lang="bash"> | ||
− | $ ./qemu- | + | $./qemu-imx6ull-gui.sh // 启动后,登录名是root,无需密码 |
</syntaxhighlight> | </syntaxhighlight> | ||
− | + | : ②模拟野火imx6ull-pro开发板 | |
<syntaxhighlight lang="bash"> | <syntaxhighlight lang="bash"> | ||
− | $ ./qemu- | + | $./qemu-imx6ull-gui.sh fire // 启动后,登录名是root,无需密码 |
</syntaxhighlight> | </syntaxhighlight> | ||
− | + | : ③模拟正点原子imx6ull-alpha开发板 | |
<syntaxhighlight lang="bash"> | <syntaxhighlight lang="bash"> | ||
− | $ ./qemu- | + | $./qemu-imx6ull-gui.sh atk // 启动后,登录名是root,无需密码 |
</syntaxhighlight> | </syntaxhighlight> | ||
− | ==== | + | ====运行不带GUI的imx6ul模拟器==== |
− | |||
− | |||
<syntaxhighlight lang="bash"> | <syntaxhighlight lang="bash"> | ||
− | $ ./qemu- | + | $./qemu-imx6ull-nogui.sh // 启动后,登录名是root,无需密码 |
</syntaxhighlight> | </syntaxhighlight> | ||
+ | ''如果在ssh字符终端下要退出QEMU,可以输入ctrl+a 抬起后,再输入'x'。'' | ||
− | ==== 参数讲解 ==== | + | ====参数讲解==== |
− | + | : 可以打开脚本文件qemu-imx6ull-gui.sh,它就是运行qemu-system-arm程序。其中用到了很多参数: | |
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
<syntaxhighlight lang="bash"> | <syntaxhighlight lang="bash"> | ||
− | + | -M mcimx6ul-evk 指定需要模拟的单板型号。 | |
+ | -m 512M 指定板子的内存大小。 | ||
+ | -kernel zImage 指定使用的内核镜像文件。 | ||
+ | -dtb 100ask_imx6ull_qemu.dtb 指定使用的设备树文件。 | ||
+ | -display sdl 指定使用那种图形显示输出。 | ||
+ | -serial mon:stdio 指定串口信息输出。 | ||
+ | -drive file=rootfs.img,format=raw,id=mysdcard名为mysdcard的drive,源为rootfs.img | ||
+ | -device sd-card,drive=mysdcard 添加一个sd-card设备,内容来自名为mysdcard的drive | ||
+ | -append “console=ttymxc0,115200 rootfstype=ext4 root=/dev/mmcblk1 rw rootwaitinit=/sbin/init loglevel=8” 指定内核的命令行参数 | ||
+ | -nic user 指定网卡为user mode | ||
</syntaxhighlight> | </syntaxhighlight> | ||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | + | : 有了内核zImage、设备树、文件系统(rootfs.img),这就是一个完整的Linux系统。 | |
+ | : {{redtext|注意}}:QEMU中没有实现bootloader,以后我们会完全模拟SD卡,在SD卡上面放置u-boot、内核、设备树、文件系统。 | ||
− | + | ===QEMU操作示例=== | |
− | + | : 先执行以下命令启动QEMU,它模拟百问网imx6ull-qemu开发板: | |
− | ==== | ||
− | |||
− | |||
<syntaxhighlight lang="bash"> | <syntaxhighlight lang="bash"> | ||
− | + | $./qemu-imx6ull-gui.sh // 启动后,登录名是root,无需密码 | |
</syntaxhighlight> | </syntaxhighlight> | ||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | + | : 它会弹出一个开发板的界面,并且运行该开发板的Linux系统,你可以在终端中操作该开发板: | |
+ | :: [[File:100ask_imx6ull_qemu_001.png|800px]] | ||
+ | : {{redtext|注意}}:当你的鼠标点击QEMU的GUI界面时,鼠标将无法移出这个GUI界面。这时可以通过快捷键“Ctrl+Alt+g”把鼠标从GUI界面中退出来。 | ||
+ | ====操作设备管理器==== | ||
+ | : 我们的计划是模拟更多的外设,为形象地操作这些外设,每个外设都会有一个GUI界面。可能会有多达几十、上百个外设,如果一下子显示那么多GUI界面,会很乱。 | ||
+ | : 所以我们实现了一个“设备管理”,如下所示。可以点击它上面的某个按钮,显示或隐藏某个外设的GUI界面。 | ||
+ | :: [[File:100ask_imx6ull_qemu_002.png|800px]] | ||
+ | ====操作LCD==== | ||
+ | : 我们模拟的IMX6ULL板子,它的Linux系统中已经带有LCD的测试命令,可以执行以下命令测试: | ||
<syntaxhighlight lang="bash"> | <syntaxhighlight lang="bash"> | ||
− | [root@qemu_imx6ul:~]# | + | [root\@qemu_imx6ul:\~]\# fb-test |
− | |||
− | |||
− | |||
</syntaxhighlight> | </syntaxhighlight> | ||
− | + | : 或 | |
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
<syntaxhighlight lang="bash"> | <syntaxhighlight lang="bash"> | ||
− | [root@qemu_imx6ul:~]# | + | [root\@qemu_imx6ul:\~]\# myfb-test /dev/fb0 |
− | |||
− | |||
− | |||
− | |||
− | |||
</syntaxhighlight> | </syntaxhighlight> | ||
− | + | : 效果如下: | |
− | + | :: [[File:100ask_imx6ull_qemu_003.png|1200px]] | |
− | + | ====操作LED==== | |
− | + | : 我们模拟的IMX6ULL板子,它的Linux系统中已经带有LED驱动和测试命令,可以执行以下命令测试: | |
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
<syntaxhighlight lang="bash"> | <syntaxhighlight lang="bash"> | ||
− | // | + | [root\@qemu_imx6ul:\~]\# cd led_driver_qemu/ |
− | [root@qemu_imx6ul:~]# | + | [root\@qemu_imx6ul:\~/led_driver_qemu]\# insmod 100ask_led.ko |
− | + | [root\@qemu_imx6ul:\~/led_driver_qemu]\# ./ledtest /dev/100ask_led0 off | |
− | [root@qemu_imx6ul:~]# | + | [root\@qemu_imx6ul:\~/led_driver_qemu]\# ./ledtest /dev/100ask_led0 on |
</syntaxhighlight> | </syntaxhighlight> | ||
− | + | : 效果如下: | |
− | + | :: [[File:100ask_imx6ull_qemu_004.png|400px]] | |
− | + | ====使用按键来控制LED==== | |
− | + | : 我们模拟的IMX6ULL板子,它的Linux系统中已经带有LED驱动、按键驱动和测试命令。 | |
− | + | : 首先在“QEMU 设备管理器”中打开按键的界面,然后执行以下命令测试: | |
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | == | ||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
<syntaxhighlight lang="bash"> | <syntaxhighlight lang="bash"> | ||
− | + | [root\@qemu_imx6ul:\~]\# cd led_driver_qemu/ | |
− | + | [root\@qemu_imx6ul:\~/led_driver_qemu]\# insmod 100ask_led.ko | |
+ | [root\@qemu_imx6ul:\~/led_driver_qemu]\# cd ../button_driver_qemu/ | ||
+ | [root\@qemu_imx6ul:\~/button_driver_qemu]\# insmod button_drv.ko | ||
+ | [root\@qemu_imx6ul:\~/button_driver_qemu]\# insmod board_100ask_qemu_imx6ull.ko | ||
+ | [root\@qemu_imx6ul:\~/button_driver_qemu]\# ./button_led_test | ||
</syntaxhighlight> | </syntaxhighlight> | ||
− | |||
− | |||
− | |||
− | + | : 效果如下图(只实现了左边的2个按键,右边的2个按键留待以后实现其他目的): | |
+ | :: [[File:100ask_imx6ull_qemu_005.png|400px]] | ||
− | + | ====读写I2C EEPROM AT24C02==== | |
+ | : 我们模拟的IMX6ULL板子,它已经实现了对I2C设备AT24C02的模拟。 | ||
+ | : 首先在“QEMU 设备管理器”中打开at24c02的界面,然后执行以下命令测试: | ||
<syntaxhighlight lang="bash"> | <syntaxhighlight lang="bash"> | ||
− | + | // 0x50是AT24C02的I2C设备地址 | |
− | + | [root\@qemu_imx6ul:\~]\# i2c_usr_test /dev/i2c-0 0x50 r 0 // 读地址0 data: , 0, 0x00 | |
− | + | [root\@qemu_imx6ul:\~]\# i2c_usr_test /dev/i2c-0 0x50 w 1 0x58 // 写地址1,写入0x58 | |
− | |||
</syntaxhighlight> | </syntaxhighlight> | ||
− | + | : 效果如下图: | |
+ | :: [[File:100ask_imx6ull_qemu_006.png|800px]] | ||
+ | ==使用QEMU进行嵌入式Linux开发== | ||
+ | : 完整的嵌入式Linux系统包含:bootloader、Linux内核、设备树、根文件系统。 | ||
+ | : QEMU可以略过bootloader而直接启动内核,这给开发带来了便利。 | ||
+ | : 但是以后我们会修改QEMU,让它支持从SD卡上启动bootloader,让bootloader来启动内核。 | ||
+ | : 在本章中,我们介绍如何编译并替换内核、编译并替换设备树,修改根文件系统。 | ||
− | + | ===获取源码=== | |
− | + | : 我们的源码都是存放在GIT中,所以需要确保你的Ubuntu可以上网。如果不能上网,请参考以下链接: | |
− | + | :: [http://wiki.100ask.org/VMwareAndUbuntuNetworkSetupGuide http://wiki.100ask.org/VMwareAndUbuntuNetworkSetupGuide] | |
− | + | ====设置git邮箱账号和用户名==== | |
− | + | : 在Ubuntu中执行如下命令: | |
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | ==== | ||
− | |||
− | |||
<syntaxhighlight lang="bash"> | <syntaxhighlight lang="bash"> | ||
− | book@100ask:~$ | + | book\@100ask:\~\$ git config --global user.email "you\@example.com" |
+ | book\@100ask:\~\$ git config --global user.name "Your Name" | ||
</syntaxhighlight> | </syntaxhighlight> | ||
− | + | : 初次使用GIT时,需要配置邮箱帐号和用户名,可以随意指定。 | |
+ | ====下载源码==== | ||
+ | : 考虑到代码仓库过多,特使用repo工具管理代码。 | ||
+ | : 先用git clone下载repo工具,再用repo工具下载源码: | ||
<syntaxhighlight lang="bash"> | <syntaxhighlight lang="bash"> | ||
− | + | book\@100ask:\~\$ git clone https://e.coding.net/codebug8/repo.git | |
− | + | book\@100ask:\~\$ mkdir -p 100ask_imx6ull-qemu && cd 100ask_imx6ull-qemu | |
− | + | book\@100ask:\~/100ask_imx6ull-qemu\$ ../repo/repo init -u https://e.coding.net/weidongshan/manifests.git -b linux-sdk -m imx6ull/100ask-imx6ull_qemu_release_v1.0.xml --no-repo-verify | |
+ | book\@100ask:\~/100ask_imx6ull-qemu\$ ../repo/repo sync -j4 | ||
</syntaxhighlight> | </syntaxhighlight> | ||
− | + | : 上面使用的repo管理的是国内coding仓库,从国内仓库下载会快很多。 | |
+ | : 如果一切正常,你在/home/book目录下创建了一个100ask_imx6ull-qemu目录,里有如下内容: | ||
+ | : [[File:100ask_imx6ull_qemu_007.png|400px]] | ||
− | + | ===百问网imx6ull-qemu开发板资料下载=== | |
+ | : 百问网imx6ull-qemu开发板,并不是真实的开发板,我们可以在它上面添加任意硬件,当然也会提供原理图。 | ||
+ | : 这款虚拟开发板的资料可以从网盘中下载,打开以下链接: | ||
+ | :: [http://wiki.100ask.org/Download_link_page http://wiki.100ask.org/Download_link_page] | ||
+ | :: 找到:找到“韦东山升级版全系列视频开发板BSP包”,打开对应的网盘链接后,可以看到一个目录“100ask_imx6ull-qemu”。 | ||
− | + | ===设置工具链=== | |
+ | : 交叉编译工具链主要是用于在ubuntu主机上编译可以在其它平台上运行的系统,比如在PC上为ARM板子编译程序。 | ||
+ | : 设置交叉编译工具主要是设置PATH,ARCH和CROSS_COMPILE三个环境变量,下面介绍具体设置方法(3种方法任选一种)。 | ||
+ | ====永久生效==== | ||
+ | : 如需永久修改,请修改用户配置文件。在Ubuntu系统下,修改如下: | ||
<syntaxhighlight lang="bash"> | <syntaxhighlight lang="bash"> | ||
− | book@100ask:~$ | + | book\@100ask:\~\$ vi ~/.bashrc |
− | + | </syntaxhighlight> | |
− | |||
− | </syntaxhighlight> | ||
− | + | : 在行尾添加或修改: | |
− | |||
− | |||
<syntaxhighlight lang="bash"> | <syntaxhighlight lang="bash"> | ||
− | + | export ARCH=arm | |
− | + | export CROSS_COMPILE=arm-linux-gnueabihf- | |
+ | export PATH=$PATH:/home/book/100ask_imx6ull-qemu/ToolChain/gcc-linaro-6.2.1-2016.11-x86_64_arm-linux-gnueabihf/bin | ||
</syntaxhighlight> | </syntaxhighlight> | ||
− | < | + | ====注意:设置完环境变量后需要执行以下命令或重启UBUNTU才能使环境变量生效:==== |
+ | <syntaxhighlight lang="bash"> | ||
+ | source ~/.bashrc | ||
+ | </syntaxhighlight> | ||
− | + | ====临时生效==== | |
− | + | : 如果你有很多单板,为了不冲突,你不能使用“永久生效”的办法。比如你有32位的ARM板,也有64位的ARM板,在使用前者时需要设置ARCH=arm,在使用后者时需要设置ARCH=arm64。 | |
− | + | : 这种情况下,可以使用“export”命令设置环境变量,这种设置方法只对当前终端有效: | |
− | |||
− | |||
− | |||
− | |||
− | === | ||
− | |||
− | |||
<syntaxhighlight lang="bash"> | <syntaxhighlight lang="bash"> | ||
− | book@100ask:~/ | + | book\@100ask:\~\$ export PATH=$PATH:/home/book/100ask_imx6ull-qemu/ToolChain/gcc-linaro-6.2.1-2016.11-x86_64_arm-linux-gnueabihf/bin |
− | book | + | book\@100ask:\~\$ export ARCH=arm |
− | + | book\@100ask:\~\$ export CROSS_COMPILE=arm-linux-gnueabihf- | |
− | book@100ask:~ | ||
− | book@100ask:~ | ||
</syntaxhighlight> | </syntaxhighlight> | ||
− | + | ====手动指定==== | |
+ | : 执行make命令时,可以手工指定ARCH架构、CROSS_COMPILE等变量: | ||
<syntaxhighlight lang="bash"> | <syntaxhighlight lang="bash"> | ||
− | + | book\@100ask:\~\$ export PATH=$PATH:/home/book/100ask_imx6ull-qemu/ToolChain/gcc-linaro-6.2.1-2016.11-x86_64_arm-linux-gnueabihf/bin | |
− | + | book\@100ask:\~\$ make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- | |
</syntaxhighlight> | </syntaxhighlight> | ||
− | + | : ''不过这种方法效率太低,不建议使用。'' | |
− | + | ===编译内核及设备树=== | |
− | + | : 编译内核需要安装一些程序,执行以下命令安装: | |
− | |||
− | |||
− | |||
− | |||
− | |||
− | = | ||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
<syntaxhighlight lang="bash"> | <syntaxhighlight lang="bash"> | ||
− | sudo | + | book\@100ask:\~/100ask_imx6ull-qemu\$ sudo apt --fix-broken install |
− | + | book\@100ask:\~/100ask_imx6ull-qemu\$ sudo apt-get install lzop | |
− | + | </syntaxhighlight> | |
− | + | : 前面我们下载了源码,设置好工具链后,即可编译: | |
<syntaxhighlight lang="bash"> | <syntaxhighlight lang="bash"> | ||
− | + | book\@100ask:\~/100ask_imx6ull-qemu\$ cd linux-4.9.88 | |
− | </syntaxhighlight> | + | book\@100ask:\~/100ask_imx6ull-qemu/linux-4.9.88\$ make mrproper |
− | + | book\@100ask:\~/100ask_imx6ull-qemu/linux-4.9.88\$ make 100ask_imx6ull_qemu_defconfig | |
+ | book\@100ask:\~/100ask_imx6ull-qemu/linux-4.9.88\$ make zImage -jN //编译zImage内核镜像,其中N参数可以根据CPU个数,来加速编译系统。 | ||
+ | book\@100ask:\~/100ask_imx6ull-qemu/linux-4.9.88\$ make dtbs //编译设备树文件 | ||
+ | </syntaxhighlight> | ||
− | + | : 编译成功后,可以得到如下文件: | |
<syntaxhighlight lang="bash"> | <syntaxhighlight lang="bash"> | ||
− | + | arch/arm/boot/zImage // 内核 | |
+ | arch/arm/boot/dts/100ask_imx6ull_qemu.dtb // 设备树 | ||
</syntaxhighlight> | </syntaxhighlight> | ||
− | + | : 如果你修改过内核,或是修改过设备树文件,那么可以用上面2个文件去替换QEMU中的zImage和100ask_imx6ull_qemu.dtb。 | |
+ | : QEMU中的zImage和100ask_imx6ull_qemu.dtb在哪?你安装我们提供的QEMU时,可以得到这样的脚本:qemu-imx6ull-gui.sh。打开它就可以知道这2个文件在哪里了。 | ||
+ | : 一般位于imx6ull-system-image目录下: | ||
+ | :: [[File:100ask_imx6ull_qemu_008.png|800px]] | ||
− | === | + | ===修改文件系统=== |
+ | : 安装好我们提供的QEMU后,你可以得到一个imx6ull-system-image目录,里面有名为rootfs.img的文件,它就是根文件系统: | ||
+ | :: [[File:100ask_imx6ull_qemu_008.png|800px]] | ||
+ | : 你可以在Ubuntu下直接修改rootfs.img,不过要先挂载,执行以下命令: | ||
+ | :: [[File:100ask_imx6ull_qemu_009.png|800px]] | ||
+ | : 主要命令就是: | ||
+ | <syntaxhighlight lang="bash"> | ||
+ | sudo mount -o loop rootfs.img /mnt | ||
+ | </syntaxhighlight> | ||
− | < | + | : 你就可以在/mnt目录下对其中的文件进行操作了,也可以把Ubuntu中的文件复制进去。 |
+ | : {{redtext|注意}}:修改完毕后,要执行以下命令: | ||
+ | <syntaxhighlight lang="bash"> | ||
+ | sudo umount /mnt | ||
+ | </syntaxhighlight> | ||
+ | ===启动模拟器后使用NFS=== | ||
+ | : 安装好我们提供的QEMU后,可以执行如下命令启动开发板: | ||
+ | <syntaxhighlight lang="bash"> | ||
+ | $./qemu-imx6ull-gui.sh // 启动后,登录名是root,无需密码 | ||
+ | </syntaxhighlight> | ||
− | + | : 等待进入系统后,就可以使用网络命令挂载Ubuntu的NFS目录了。 | |
+ | ====在Ubuntu上安装、配置NFS服务==== | ||
+ | : 如果你使用的是我们提供的Ubuntu,那么已经安装好了NFS服务。 | ||
+ | : 如果你的Ubuntu未安装NFS服务,那么在确保Ubuntu可以上网的前提下,执行以下命令: | ||
<syntaxhighlight lang="bash"> | <syntaxhighlight lang="bash"> | ||
− | sudo apt-get install nfs-kernel-server | + | sudo apt-get install nfs-kernel-server |
</syntaxhighlight> | </syntaxhighlight> | ||
− | + | : 然后,还得修改/etc/exports,添加类似以下的内容,下面的例子里允许开发板通过NFS访问Ubuntu的/home/book、/work两个目录: | |
<syntaxhighlight lang="bash"> | <syntaxhighlight lang="bash"> | ||
− | /home/book *(rw,nohide,insecure,no_subtree_check,async,no_root_squash) | + | /home/book *(rw,nohide,insecure,no_subtree_check,async,no_root_squash) |
− | /work *(rw,nohide,insecure,no_subtree_check,async,no_root_squash) | + | /work *(rw,nohide,insecure,no_subtree_check,async,no_root_squash) |
− | </syntaxhighlight> | + | </syntaxhighlight> |
− | + | : 最后,重启NFS服务,在Ubuntu上执行以下命令: | |
<syntaxhighlight lang="bash"> | <syntaxhighlight lang="bash"> | ||
− | sudo /etc/init.d/nfs-kernel-server restart | + | sudo /etc/init.d/nfs-kernel-server restart |
− | </syntaxhighlight> | + | </syntaxhighlight> |
− | + | : 可以在Ubuntu上通过NFS挂载自己,验证一下NFS可用: | |
<syntaxhighlight lang="bash"> | <syntaxhighlight lang="bash"> | ||
− | sudo mount -t nfs -o nolock,vers=3 127.0.0.1:/home/book /mnt | + | sudo mount -t nfs -o nolock,vers=3 127.0.0.1:/home/book /mnt |
− | ls /mnt | + | ls /mnt |
− | </syntaxhighlight> | + | </syntaxhighlight> |
− | |||
− | |||
− | |||
− | |||
− | |||
− | + | ====开发板获取IP地址==== | |
− | + | : QEMU运行时,Ubuntu是Host即宿主机,QEMU给它分配的IP是10.0.2.2。 | |
− | + | : QEMU模拟的imx6ull板子是Guest即客户机,它会自动获取IP,也可以自己设置。 | |
+ | : Guest可以通过10.0.2.2访问Host,Host不能访问Guest。 | ||
+ | : Guest中可以使用ifconfig命令查看IP,如果没有IP,可以如下设置: | ||
<syntaxhighlight lang="bash"> | <syntaxhighlight lang="bash"> | ||
− | [root@qemu_imx6ul:~]# ifconfig eth0 10.0.2.15 | + | [root\@qemu_imx6ul:\~]\# ifconfig eth0 10.0.2.15 |
− | </syntaxhighlight> | + | </syntaxhighlight> |
− | |||
− | + | ====挂载主机nfs目录==== | |
+ | : QEMU模拟的imx6ull开发板,可以去访问10.0.2.2,比如使用NFS挂载: | ||
<syntaxhighlight lang="bash"> | <syntaxhighlight lang="bash"> | ||
− | [root@qemu_imx6ul:~]# mount -t nfs -o nolock,vers=3 10.0.2.2:/home/book/nfs_rootfs /mnt | + | [root\@qemu_imx6ul:\~]\# mount -t nfs -o nolock,vers=3 10.0.2.2:/home/book/nfs_rootfs /mnt |
</syntaxhighlight> | </syntaxhighlight> | ||
− | + | : 如果一切正常,在开发板上就可以通过/mnt目录访问Ubuntu的/home/book/nfs_rootfs目录了。 | |
− | == 更新QEMU | + | ==更新QEMU == |
+ | : 我们使用QEMU来模拟IMX6ULL开发板,目的是学习嵌入式Linux系统。随着我们课程的陆续发布,我们也会给QEMU添加更多的功能。比如后面要讲到触摸屏时,就会修改QEMU让它支持触摸屏。 | ||
+ | : 所以你需要不断地更新QEMU,有3个方法。 | ||
+ | ===下载最新的release包=== | ||
+ | : 从下面2个地址就可以下载到最新的release包,解压开后即可使用: | ||
+ | :: ①ubuntu-18.04开发环境下qemu imx6ul系统镜像下载地址: | ||
+ | ::: [http://wiki.100ask.org/Download_ubuntu-18.04_imx6ul_qemu http://wiki.100ask.org/Download_ubuntu-18.04_imx6ul_qemu] | ||
+ | :: ②ubuntu-16.04开发环境下qemu imx6ul系统镜像下载地址: | ||
+ | ::: [http://wiki.100ask.org/Download_ubuntu-16.04_imx6ul_qemu http://wiki.100ask.org/Download_ubuntu-16.04_imx6ul_qemu] | ||
− | |||
− | |||
− | |||
− | + | : {{redtext|注意}}:如果你之前对内核、设备树或是文件系统进行过修改,想继续使用这些修改过的文件的话,需要用它们来覆盖release包中解压出来的对应文件。 | |
+ | ===下载最新的qemu-system-arm可执行程序及配置文件=== | ||
+ | : 如果你不想下载整个release包,可以只替换其中的某些文件。 | ||
+ | : 可以打开下面的某个网址(根据你的ubuntu版本号选择): | ||
+ | :: [https://dev.tencent.com/u/weidongshan/p/ubuntu-16.04_imx6ul_qemu_system/git https://dev.tencent.com/u/weidongshan/p/ubuntu-16.04_imx6ul_qemu_system/git] | ||
+ | :: [https://dev.tencent.com/u/weidongshan/p/ubuntu-18.04_imx6ul_qemu_system/git https://dev.tencent.com/u/weidongshan/p/ubuntu-18.04_imx6ul_qemu_system/git] | ||
+ | : 以ubuntu 18.04为例,进入下图所示的bin、etc目录: | ||
+ | :: [[File:100ask_imx6ull_qemu_010.png|1200px]] | ||
+ | : 对于bin目录,只需要下载其中的qemu-sysmte-arm;对于etc目录,里面所有的bmp文件都要下载。 | ||
+ | : 你之前在Ubuntu中曾经安装过我们提供的QEMU,你用下载的文件覆盖QEMU中的对应文件即可。 | ||
− | === | + | ===下载最新的qemu源码并编译=== |
+ | : 请参考后续章节,这属于对QEMU的开发了。对于嵌入式Linux初学者,没必要研究QEMU,使用我们提供的可执行程序即可。 | ||
− | + | ={{redtext|Part B. QEMU开发手册}}= | |
+ | : 重要的事情说三遍: | ||
+ | :: {{redtext|对于嵌入式Linux初学者,对于QEMU无感者,对于急于找工作的人,}} | ||
+ | :: {{redtext|不需要深入研究QEMU,不需要看本开发手册,看前面的使用手册就可以了,会用就行。}} | ||
+ | :: {{redtext|不需要深入研究QEMU,不需要看本开发手册,看前面的使用手册就可以了,会用就行。}} | ||
+ | :: {{redtext|不需要深入研究QEMU,不需要看本开发手册,看前面的使用手册就可以了,会用就行。}} | ||
− | + | : 我们使用QEMU来模拟IMX6ULL开发板,就像去制作一个开发板一样。作为初学者,你并不需要去设计开发板,会用开发板就可以了。 | |
+ | : 如果你想深入研究QEMU,想承接我们发布的QEMU外包项目,那么请阅读本手册。 | ||
− | + | QEMU框架简单,功能却很强大。 | |
+ | : 理解了它的框架之后,添加新的外设并不困难。当然这需要你对外设的原理有清楚的认识。 | ||
+ | : 我们开发QEMU,要做的事情主要有3部分: | ||
+ | : '''{{redtext|1. 添加外设}}''' | ||
+ | :: 比如添加一个LED,那么在QEMU中得有对应的源码,这些源码要监测LED对应的寄存器的值:当APP写寄存器时,这些源码要把那些值记录下来。 | ||
+ | : '''{{redtext|2. 添加外设的GUI}}''' | ||
+ | :: QEMU的界面很简陋,它默认只实现了LCD的GUI显示。你想点亮LED,在QEMU上是看不出效果的。 | ||
+ | :: 所以我们需要给外设添加GUI界面。 | ||
+ | :: 比如上面举的LED例子,APP写寄存器时,除了把寄存器的值记录下来之后,我们还要在GUI界面显示一个LED,并且把它点亮或熄灭。 | ||
− | + | : '''{{redtext|3. 在虚拟开发板上开发测试程序}}''' | |
+ | :: 这个测试程序可以是裸机,也可以是Linux驱动+应用程序。 | ||
+ | :: 我们在QEMU模拟出来的板子上运行这些测试程序时,跟真实板子应该没有差别。 | ||
− | + | : 上述列的第1、2部分,需要对QEMU有所了解,请看本文的“开发手册”。 | |
+ | : 上述列的第3部分,跟QEMU无关,跟在真实板子上写的程序是一样的。 | ||
+ | ==QEMU下载与编译== | ||
+ | ===源码下载=== | ||
+ | : 官方的QEMU对IMX6ULL的支持还太弱,没有更形象化的GUI,也没有更多的外设。我们对它进行了大量的改进。 | ||
+ | : 修改后的源码位于这2个GIT中: | ||
+ | :: [https://github.com/100askTeam/qemu.git https://github.com/100askTeam/qemu.git] | ||
+ | :: [https://gitee.com/weidongshan/qemu.git https://gitee.com/weidongshan/qemu.git] | ||
+ | : 在Ubuntu下,执行如下命令即可下载: | ||
+ | <syntaxhighlight lang="bash"> | ||
+ | $ git clone https://gitee.com/weidongshan/qemu.git | ||
+ | </syntaxhighlight> | ||
− | + | : 或 | |
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | 或 | ||
<syntaxhighlight lang="bash"> | <syntaxhighlight lang="bash"> | ||
− | $ git clone | + | $ git clone https://github.com/100askTeam/qemu.git |
</syntaxhighlight> | </syntaxhighlight> | ||
− | + | : 我们会不断更新QEMU,你下载过QEMU之后,可以进入qemu目录执行下述命令更新代码: | |
<syntaxhighlight lang="bash"> | <syntaxhighlight lang="bash"> | ||
− | $ git pull origin | + | $ git pull origin |
</syntaxhighlight> | </syntaxhighlight> | ||
− | + | ===配置、编译、安装=== | |
− | + | : 进入qemu目录,执行如下配置命令: | |
− | === 配置、编译、安装 === | ||
− | |||
− | |||
<syntaxhighlight lang="bash"> | <syntaxhighlight lang="bash"> | ||
./configure --prefix=$PWD/ --target-list="arm-softmmu arm-linux-user" --enable-debug --enable-sdl --enable-kvm --enable-tools --disable-curl | ./configure --prefix=$PWD/ --target-list="arm-softmmu arm-linux-user" --enable-debug --enable-sdl --enable-kvm --enable-tools --disable-curl | ||
− | </syntaxhighlight> | + | </syntaxhighlight> |
− | + | : 编译、安装命令如下: | |
<syntaxhighlight lang="bash"> | <syntaxhighlight lang="bash"> | ||
$ make | $ make | ||
$ make install | $ make install | ||
</syntaxhighlight> | </syntaxhighlight> | ||
− | + | : 如果一切正常,会在qemu源码目录下生成bin子目录,里面存放有各种可执行程序。 | |
− | + | : 配置、编译过程中有可能出错,一般就是缺少库文件。 | |
− | + | : 如果你的ubuntu能上网,那么使用apt-get命令即可安装这些库。 | |
− | + | : 错误示例,提示信息如下: | |
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
<syntaxhighlight lang="bash"> | <syntaxhighlight lang="bash"> | ||
− | ERROR: pixman >= 0.21.8 not present. | + | ERROR: pixman >= 0.21.8 not present. |
− | Please install the pixman devel package. | + | Please install the pixman devel package. |
</syntaxhighlight> | </syntaxhighlight> | ||
− | + | : 解决方法: | |
− | + | :: ①确定库的名称: | |
− | + | ::: 执行如下命令: | |
− | |||
− | |||
<syntaxhighlight lang="bash"> | <syntaxhighlight lang="bash"> | ||
$ apt-cache search pixman | $ apt-cache search pixman | ||
− | libpixman-1-0 - pixel-manipulation library for X and cairo | + | libpixman-1-0 - pixel-manipulation library for X and cairo |
− | libpixman-1-dev - pixel-manipulation library for X and cairo (development files) | + | libpixman-1-dev - pixel-manipulation library for X and cairo (development files) |
</syntaxhighlight> | </syntaxhighlight> | ||
− | + | ::: 根据输出信息,需要安装开发包(dev表示开发包): libpixman-1-dev。 | |
− | + | :: ② 安装开发包: | |
− | |||
− | ② 安装开发包: | ||
<syntaxhighlight lang="bash"> | <syntaxhighlight lang="bash"> | ||
$ sudo apt-get install libpixman-1-dev | $ sudo apt-get install libpixman-1-dev | ||
</syntaxhighlight> | </syntaxhighlight> | ||
− | + | ||
+ | ::: 可能你的ubuntu中已经安装了某些开发包,下面列出一些必须的包: | ||
<syntaxhighlight lang="bash"> | <syntaxhighlight lang="bash"> | ||
$ sudo apt-get install pkg-config | $ sudo apt-get install pkg-config | ||
第909行: | 第486行: | ||
</syntaxhighlight> | </syntaxhighlight> | ||
− | + | ::: 每次出错后,根据提示信息安装开发包,然后重新配置、编译、安装。 | |
− | + | ::: 如果一切正常,在当前目录下会生成bin子目录, 里面有生成的QEMU程序: | |
− | |||
<syntaxhighlight lang="bash"> | <syntaxhighlight lang="bash"> | ||
− | qemu-system-arm | + | qemu-system-arm |
</syntaxhighlight> | </syntaxhighlight> | ||
− | + | ===使用新的qemu-system-arm=== | |
− | + | : 将上面编译出来的bin/qemu-system-arm 可执行文件复制到如下目录: | |
− | === 使用新的qemu-system-arm === | ||
− | |||
− | |||
<syntaxhighlight lang="bash"> | <syntaxhighlight lang="bash"> | ||
− | ubuntu-18.04_imx6ul_qemu_system/qemu | + | ubuntu-18.04_imx6ul_qemu_system/qemu/bin |
</syntaxhighlight> | </syntaxhighlight> | ||
− | 或 | + | |
+ | : 或 | ||
<syntaxhighlight lang="bash"> | <syntaxhighlight lang="bash"> | ||
− | ubuntu-16.04_imx6ul_qemu_system/qemu | + | ubuntu-16.04_imx6ul_qemu_system/qemu/bin |
− | </syntaxhighlight> | + | </syntaxhighlight> |
− | + | : 我们也可能添加了更多的GUI显示,这些GUI所用图片位于源码目录的etc子目录下,这些图片也需要复制到如下目录去: | |
<syntaxhighlight lang="bash"> | <syntaxhighlight lang="bash"> | ||
− | ubuntu-18.04_imx6ul_qemu_system/qemu | + | ubuntu-18.04_imx6ul_qemu_system/qemu/etc |
− | </syntaxhighlight> | + | </syntaxhighlight> |
− | 或 | + | |
+ | : 或 | ||
<syntaxhighlight lang="bash"> | <syntaxhighlight lang="bash"> | ||
− | ubuntu-16.04_imx6ul_qemu_system/qemu | + | ubuntu-16.04_imx6ul_qemu_system/qemu/etc |
</syntaxhighlight> | </syntaxhighlight> | ||
− | + | : 然后就可以执行 qemu-imx6ull-gui.sh 或 | |
− | + | <syntaxhighlight lang="bash"> | |
− | + | qemu-imx6ull-nogui.sh来使用您编译出来的QEMU了。 | |
− | < | + | </syntaxhighlight> |
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | + | ===QEMU源码目录=== | |
+ | : 我们只罗列出涉及的少许文件,一般来说一个.c文件会有一个.h文件,它们的目录类似。 | ||
+ | : 比如hw/gpio/imx_gpio.c对应的头文件为include/hw/gpio/imx_gpio.h | ||
+ | :: [[File:100ask_imx6ull_qemu_012.png|400px]] | ||
+ | ==QEMU的设备创建过程== | ||
+ | ===重要结构体TypeInfo=== | ||
+ | : 一个板子上有很多硬件:IMX6ULL、LED、按键、LCD、触摸屏、网卡等等。 | ||
+ | : IMX6ULL这类芯片被称为SoC(System onChip),它里面也有很多部件,比如CPU、GPIO、SD控制器、中断控制器等等。 | ||
+ | : 这些硬件,或是部件,各有不同。怎么描述它们? | ||
+ | : 每一个都使用一个TypeInfo结构体来描述。 | ||
− | < | + | : 比如对于CPU,有这样的结构体(hw/cpu/a15mpcore.c): |
+ | :: [[File:100ask_imx6ull_qemu_013.png|400px]] | ||
+ | : 对于LCD,有这样的结构体(hw/display/100ask_qemu_fb.c): | ||
+ | :: [[File:100ask_imx6ull_qemu_014.png|400px]] | ||
+ | : 对于GPIO,也有这样的结构体(hw/gpio/imx_gpio.c): | ||
+ | :: [[File:100ask_imx6ull_qemu_015.png|400px]] | ||
+ | : 由此可见,在QEMU中,每一个硬件都由一个TypeInfo来描述。这些结构体都会被注册进程序里,在一个链表中保存着,备用。**注意**,是备用,它们并不一定会用到。 | ||
+ | : 怎么注册这些TypeInfo结构体呢?不需要我们去调用注册函数,以GPIO为例,在hw/gpio/imx_gpio.c中有如下代码: | ||
+ | :: [[File:100ask_imx6ull_qemu_016.png|400px]] | ||
+ | : 关键点在于type_init,这个宏在include/qemu/module.h中定义: | ||
+ | :: [[File:100ask_imx6ull_qemu_017.png|600px]] | ||
+ | : 对于属性为“constructor”函数,它在main函数之前被调用。 | ||
+ | : 对于上述的type_init(imx_gpio_register_types),它的宏展开如下: | ||
+ | <syntaxhighlight lang="C"> | ||
+ | static void __attribute__((constructor)) do_qemu_init_imx_gpio_register_types (void) | ||
− | + | { | |
− | + | register_module_init(imx_gpio_register_types, MODULE_INIT_QOM); | |
− | + | } | |
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | } | ||
</syntaxhighlight> | </syntaxhighlight> | ||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | === 生成TypeImpl === | + | : 这就得到了一个属性为“constructor”的函数do_qemu_init_imx_gpio_register_types,这个函数将在main函数之前被调用,它调用了register_module_init。 |
− | + | : register_module_init中构造了一个ModuleEntry结构体,并把它添加进某个链表里,这个链表是init_type_list[MODULE_INIT_QOM]: | |
− | + | :: [[File:100ask_imx6ull_qemu_018.png|600px]] | |
− | + | : 这样就得到了一个链表: | |
− | + | :: [[File:100ask_imx6ull_qemu_019.png|600px]] | |
− | <syntaxhighlight lang=" | + | ===生成TypeImpl=== |
− | a15mp_register_types | + | : 从名字上讲,TypeImpl就是TypeInfo的实现,就是使用TypeInfo的信息构造一个TypeImpl结构体。 |
− | ask100fb_register_types | + | : 在init_type_list[MODULE_INIT_QOM]链表中,有一系列的ModuleEntry结构体,每个ModuleEntry结构体中有一个init函数,它指向某个xxx_register_types,比如: |
+ | <syntaxhighlight lang="C"> | ||
+ | a15mp_register_types | ||
+ | ask100fb_register_types | ||
</syntaxhighlight> | </syntaxhighlight> | ||
− | + | : 这些ModuleEntry结构体中的init函数何时被调用? | |
− | + | <syntaxhighlight lang="C"> | |
− | + | /* vl.c */ | |
− | < | + | /* main */ |
− | |||
− | main | ||
module_call_init(MODULE_INIT_QOM); | module_call_init(MODULE_INIT_QOM); | ||
QTAILQ_FOREACH(e, l, node) { | QTAILQ_FOREACH(e, l, node) { | ||
e->init(); | e->init(); | ||
− | + | } | |
</syntaxhighlight> | </syntaxhighlight> | ||
− | + | : 这些xxx_register_types执行后,又得到了什么? | |
+ | :: ①分配一个TypeImpl结构体,使用TypeInfo来设置它: | ||
+ | ::: [[File:100ask_imx6ull_qemu_020.png|400px]] | ||
+ | :: ②把TypeImpl放入链表:type_table | ||
+ | ::: 于是,在程序中就有了这样的链表: | ||
+ | :::: [[File:100ask_imx6ull_qemu_021.png|800px]] | ||
+ | ===使用TypeImpl:实例化=== | ||
+ | : 在程序的type_table链表中,有很多TypeImpl结构体,比如CPU、GPIO、LED、LCD对应的TypeImpl结构体。 | ||
+ | : 但是这并不表示QEMU模拟的板子上有这些硬件,必竟它们只是“TypeImpl”,表示“类型”,需要在“实例化”之后,才表示板子上有了这些硬件。 | ||
+ | : 以CPU为例,代码为hw/cpu/a15mpcore.c,里面声明了一个A15MPPrivState结构体,还定义了一个TypeInfo结构体: | ||
+ | :: [[File:100ask_imx6ull_qemu_022.png|600px]] | ||
+ | : A15MPPrivState和TypeInfo、TypeImpl之间有什么关系? | ||
+ | :: TypeImpl的信息基本来自TypeInfo, | ||
+ | : 所以问题转为:A15MPPrivState和TypeInfo之间有什么关系? | ||
+ | : A15MPPrivState用来表示一个CPU,你要在板子上添加一个CPU,必须分配、设置一个A15MPPrivState结构体。 | ||
− | + | : 板子上的主芯片可能是单核CPU的,也可能是多核CPU的。 | |
+ | : 假设有2个CPU,那么应该有对应的2个A15MPPrivState结构体。这2个CPU是类似的,同属于某类:用TypeImpl来描述。 | ||
+ | : 所以,可以得到下面的图: | ||
+ | :: [[File:100ask_imx6ull_qemu_023.png|600px]] | ||
+ | : 谁来分配、设置A15MPPrivState结构体? | ||
+ | :: ①分配: | ||
+ | ::: 猜测是根据TypeInfo中的instrance_size来malloc出A15MPPrivState结构体。 | ||
+ | :: ②设置: | ||
+ | ::: 猜测是调用TypeInfo中的instrance_init函数来设置刚malloc出A15MPPrivState结构体。 | ||
− | + | : 谁来malloc、谁来调用TypeInfo中的instrance_init函数? | |
+ | :: 有2种方法: | ||
+ | ::: ①qdev_create/qdev_init_nofail | ||
+ | ::: ②sysbus_init_child_obj/object_property_set_bool | ||
− | + | ===实例化方法1:qdev_create/qdev_init_nofail=== | |
+ | : 这2个函数是成对出现的,以hw/display/100ask_qemu_fb.c为例: | ||
+ | :: [[File:100ask_imx6ull_qemu_024.png|400px]] | ||
+ | : 这2个函数的作用如下图所示: | ||
+ | :: [[File:100ask_imx6ull_qemu_025.png|1000px]] | ||
− | + | ====qdev_create分析==== | |
+ | : ①第2个参数是name,会被用来找到对应的TypeInfo结构体 | ||
+ | : ②分配instance_size大小的内存,即分配ASK100FbState结构体,这用来表示LCD | ||
+ | : ③调用TypeInfo结构体中的class_init函数 | ||
+ | :: class_init,顾名思义,这个设备属于什么类别?先初始化一下它的类别。 | ||
+ | :: 这些class_init函数都很类似,都是设置dc->realize函数,比如: | ||
+ | ::: [[File:100ask_imx6ull_qemu_026.png|600px]] | ||
+ | : ④ 调用TypeInfo结构体中的instance_init函数 | ||
+ | ====qdev_init_nofail分析==== | ||
+ | : qdev_init_nofail做的事情很简单: | ||
+ | :: [[File:100ask_imx6ull_qemu_027.png|600px]] | ||
+ | : 只是把设备的realized属性设置为true,表示可以对它进行realize(变为现实)了。这会导致dc->realize函数被调用,即设备的类里的realize函数被调用。 | ||
− | + | ====总结:怎么创建设备==== | |
+ | : 怎么定义一个设备?如下图: | ||
+ | :: [[File:100ask_imx6ull_qemu_028.png|400px]] | ||
+ | :: ①先定义一个TypeInfo结构体 | ||
+ | ::: 里面有name,表示它的类型名。 | ||
+ | ::: 有class_init,这是“类别的初始化函数”,该类下可能有多个设备。class_init函数中通常给该类设置一个realize函数。 | ||
+ | ::: 有instance_size,每一个设备都用一个结构体来表示,比如LCD用ASK100FbState来描述。Instance_size表示这个结构体的大小。 | ||
+ | ::: 有instance_init,这是“实例的初始化函数”,它会被用来初始化设备结构体,比如初始化ASK100FbState结构体。 | ||
+ | :: ②注册这个TypeInfo结构体: | ||
+ | ::: 定义一个ask100fb_register_types函数,里面会注册TypeInfo结构体。 | ||
+ | :: ③使用type_init,把ask100fb_register_types函数放入链表中 | ||
+ | :: ④调用qdev_create创建设备,这会传入type name | ||
+ | :: ⑤调用qdev_init_nofail设置设备的状态为realized,这会导致类别的realize函数被调用。 | ||
+ | : 简单地说,一个设备被创建时,这些函数被依次调用: | ||
+ | :: ①TypeInfo中的class_init:它会设置dc->realize = 某个函数 | ||
+ | :: ②TypeInfo中instance_size大小的对象被malloc | ||
+ | :: ③TypeInfo中的instance_init函数被调用,它被用来初始化步骤②中malloc出来的结构体 | ||
+ | :: ④dc->realize被调用 | ||
− | + | ===实例化方法2:object_initialize_child/object_property_set_bool=== | |
+ | : 参考hw/arm/fsl-imx6ul.c,里面大量的成对代码,比如: | ||
+ | :: [[File:100ask_imx6ull_qemu_029.png|600px]] | ||
+ | : 上述函数的内部调用过程,跟qdev_create/qdev_init_nofail是类似的: | ||
+ | :: [[File:100ask_imx6ull_qemu_030.png|1200px]] | ||
− | + | ====object_initialize_child分析==== | |
+ | : 该函数的第5个参数是type,表示type name,它会被用来找到对应的TypeImpl。 | ||
+ | : 找到后,会分配instance_size大小的结构体; | ||
+ | : 然后调用TypeImpl中的class_init函数,这一般是设置dc->realize。 | ||
+ | : 最后调用TypeImpl中的instance_init函数。 | ||
− | === | + | ====object_property_set_bool分析==== |
+ | : 比如: | ||
+ | :: [[File:100ask_imx6ull_qemu_031.png|400px]] | ||
+ | : 只是把设备的realized属性设置为true,表示可以对它进行realize(变为现实)了。这会导致dc->realize函数被调用,即设备的类里的realize函数被调用。 | ||
− | + | ====总结:怎么创建设备==== | |
+ | : 怎么定义一个设备?如下图所示: | ||
+ | :: [[File:100ask_imx6ull_qemu_032.png|600px]] | ||
+ | :: ①先定义一个TypeInfo结构体 | ||
+ | ::: 里面有name,表示它的类型名。 | ||
+ | ::: 有class_init,这是“类别的初始化函数”,该类下可能有多个设备。class_init函数中通常给该类设置一个realize函数。 | ||
+ | ::: 有instance_size,每一个设备都用一个结构体来表示,比如CPU用A15MPPrivState来描述。Instance_size表示这个结构体的大小。 | ||
+ | ::: 有instance_init,这是“实例的初始化函数”,它会被用来初始化设备结构体,比如初始化A15MPPrivState结构体。 | ||
+ | :: ②注册这个TypeInfo结构体: | ||
+ | ::: 定义一个a15mp_register_types函数,里面会注册TypeInfo结构体。 | ||
+ | :: ③使用type_init,把a15mp_register_types函数放入链表中 | ||
+ | :: ④调用object_initialize_child创建设备,这会传入type name | ||
+ | :: ⑤调用object_property_set_bool设置设备的状态为realized,这会导致类别的realize函数被调用。 | ||
− | + | ===怎么创建设备=== | |
+ | : 其实在2.4.3、2.5.3已经整理出来了,在这里只是再次总结一下。 | ||
+ | ====设置TypeInfo结构体==== | ||
+ | : 比如LCD: | ||
+ | :: [[File:100ask_imx6ull_qemu_033.png|400px]] | ||
+ | : 要注意到,里面有一个ASK100FbState结构体,这个结构体由我们自己设置。但是它的格式有一定要求: | ||
+ | :: [[File:100ask_imx6ull_qemu_034.png|600px]] | ||
+ | ====注册TypeInfo结构体==== | ||
+ | : 比如: | ||
+ | :: [[File:100ask_imx6ull_qemu_035.png|400px]] | ||
+ | ====使用TypeInfo创建设备/设置设备的realized属性为true==== | ||
+ | : 这有2种方法,前面介绍过: | ||
+ | <syntaxhighlight lang="C"> | ||
+ | qdev_create/qdev_init_nofail | ||
+ | object_initialize_child/object_property_set_bool | ||
+ | </syntaxhighlight> | ||
+ | ==QEMU的设备模拟== | ||
+ | ===QEMU模拟外设的原理=== | ||
+ | : QEMU主要是实现了CPU核的模拟,可以读写某个地址。 | ||
+ | : QEMU的模拟外设的原理很简单:硬件即内存。 | ||
+ | : 要在QEMU上模拟某个外设,思路就是: | ||
+ | :: ①CPU读某个地址时,QEMU模拟外设的行为,把数据返回给CPU | ||
+ | :: ②CPU写某个地址时,QEMU获得数据,用来模拟外设的行为。 | ||
+ | ::: 即:要模拟外设备,我们只需要针对外设的地址提供对应的读写函数即可。 | ||
+ | : 以GPIO为例: | ||
+ | :: [[File:100ask_imx6ull_qemu_036.png|800px]] | ||
− | + | :: 以LCD控制器为例,它主要有2大功能: | |
+ | ::: ①写LCD控制器,根据外接的LCD设置参数,比如分辨率、BPP、各种时序 | ||
+ | ::: ②从FrameBuffer中不断获得数据发给LCD,在LCD上显示出来。 | ||
+ | :: {{redtext|站在Linux LCD驱动的角度,上述2大功能可以分为:}} | ||
+ | ::: ①写LCD控制器的相关寄存器 | ||
+ | ::: ②分配FrameBuffer,写FrameBuffer | ||
+ | :: QEMU中要模拟LCD控制器,需要: | ||
+ | ::: ①记录驱动程序写寄存器的值,解析出分辨率等信息 | ||
+ | ::: ②记录FrameBuffer的地址,并持续不断地从中得到图像数据并显示出来 | ||
+ | : 框图如下: | ||
+ | :: [[File:100ask_imx6ull_qemu_037.png|800px]] | ||
− | < | + | : 简单地说, |
+ | :: ① 设置LCD控制器时: | ||
+ | ::: 在QEMU中可以给LCD控制器的访问地址A1~A2提供读写回调函数,比如: | ||
+ | <syntaxhighlight lang="C"> | ||
+ | qemu_a1a2_read | ||
+ | qemu_a1a2_write | ||
+ | </syntaxhighlight> | ||
+ | ::: 当LinuxLCD驱动程序写LCD控制器的寄存器时,就会导致qemu中的qemu_a1a2_write函数被调用,在函数中分析、记录这些值,得到分辨率等信息。 | ||
+ | :: ②写FrameBuffer时: | ||
+ | ::: 在QEMU中针对FrameBuffer提供一个刷新函数。 | ||
+ | ::: 当LCD驱动写FrameBuffer时,QEMU会使用这些数据更新GUI窗口的图像。 | ||
+ | ===给某段地址提供读写函数=== | ||
+ | : 怎么描述某段地址:基地址、大小;还得给这段地址提供读写函数。 | ||
+ | : 这段地址设置好后,需要添加进system_memory去。 | ||
+ | : 有2种方法。 | ||
+ | ====memory_region_init_io/memory_region_add_subregion==== | ||
+ | : 比如: | ||
+ | :: [[File:100ask_imx6ull_qemu_038.png|600px]] | ||
+ | ::上图中memory_region_init_io被用来初始化一块内存s->iomem,指定了它的读写函数、大小。 | ||
+ | :: 然后给s->iomem指定了基地址,并添加进system_memory中。 | ||
+ | :: 以后,客户机上的程序读写这块地址时,就会导致对应的读写函数被调用。 | ||
− | + | ====memory_region_init_io/sysbus_init_mmio/sysbus_mmio_map==== | |
+ | : 以hw/net/smc91c111.c为例: | ||
+ | :: [[File:100ask_imx6ull_qemu_039.png|600px]] | ||
− | < | + | ==模拟LED== |
+ | ===主要函数=== | ||
+ | : 客户机上的程序要操作LED时,会先设置GPIO为output,然后把值写入某个数据寄存器。 | ||
+ | : 所以我们只需要针对数据寄存器提供对应的write函数即可 | ||
+ | : QEMU中已经实现IMX6ULL的GPIO模拟,代码在hw\gpio\imx_gpio.c中。 | ||
+ | : 对应的write函数如下: | ||
+ | :: [[File:100ask_imx6ull_qemu_040.png|600px]] | ||
+ | ===添加一段内存=== | ||
+ | : 客户机的程序要设置GPIO5_IO03为输出引脚时,需要访问这个寄存器: | ||
+ | <syntaxhighlight lang="C"> | ||
+ | IOMUXC_SNVS_SW_MUX_CTL_PAD_SNVS_TAMPER3 | ||
+ | </syntaxhighlight> | ||
+ | : 它的基地址是0x2290014,QEMU中并没有添加这个地址,客户机访问它时就会发生硬件错误。 | ||
+ | : 所以在hw/arm/fsl-imx6ul.c中使用如下代码添加了这块空间: | ||
+ | [[File:100ask_imx6ull_qemu_041.png|600px]] | ||
− | + | ==模拟LCD== | |
− | + | : 我们添加的文件是:hw/display/100ask_qemu_fb.c,新加一个文件时要把它编进QEMU中,需要修改同目录的Makefile.objs,比如修改hw/display/Makefile.objs,添加一行: | |
− | + | <syntaxhighlight lang="C"> | |
− | + | common-obj-\$(CONFIG_FSL_IMX6UL) += 100ask_qemu_fb.o | |
− | + | </syntaxhighlight> | |
− | + | ===在Linux上编写LCD驱动程序=== | |
− | + | : 既然操作的不是真实的LCD控制器,那么LCD驱动程序可以极大精简。 | |
− | + | :: ①对于LCD控制器,只需要操作4个寄存器: | |
− | + | ::: 分别用来保存:framebuffer的物理地址、宽度、高度、BPP。 | |
− | + | ::: 你需要记住这些寄存器的物理地址(可以自己指定地址是什么)。 | |
− | + | :: ②对于FrameBuffer: | |
− | + | ::: 驱动程序分配得到FrameBuffer后,要把它的物理地址写到上述第1个寄存器里。 | |
− | + | ::: 部分代码如下,其他的时钟使能、GPIO设置等等都不再需要: | |
− | + | :::: [[File:100ask_imx6ull_qemu_042.png|800px]] | |
− | + | ===在QEMU中创建LCD控制器的设备=== | |
− | + | : [[File:100ask_imx6ull_qemu_043.png|800px]] | |
− | < | ||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | + | ===重要函数分析=== | |
+ | ====给LCD控制器的寄存器提供回调函数==== | ||
+ | : [[File:100ask_imx6ull_qemu_044.png|600px]] | ||
+ | : [[File:100ask_imx6ull_qemu_045.png|600px]] | ||
+ | =====给FrameBuffer提供更新函数===== | ||
+ | : [[File:100ask_imx6ull_qemu_046.png|600px]] | ||
+ | =====更新函数===== | ||
+ | : [[File:100ask_imx6ull_qemu_047.png|600px]] | ||
− | + | ==QEMU的输出:GUI系统== | |
+ | : QEMU的GUI系统,支持SDL、GTK等。SDL使用比较简单,我们就使用SDL来显示GUI。 | ||
+ | : SDL的显示原理跟LCD | ||
+ | : FrameBuffer是一样的,可以认为每一个SDL窗口都有一个显存。你可以在显存中修改每一个象素的颜色。 | ||
+ | : 本文以hw/display/100ask_qemu_fb.c为例进行讲解。 | ||
+ | ===创建GUI Console=== | ||
+ | : 创建GUI Console的函数有2个:graphic_console_init、graphic_console_init_hidden。 | ||
+ | : 后者是我们添加的,它创建的GUI Console默认是隐藏的,要显示的话需要在“Device Manager”中点击对应的按钮。 | ||
− | + | : LCD代码如下,主要是graphic_console_init函数: | |
+ | :: [[File:100ask_imx6ull_qemu_048.png|600px]] | ||
+ | : 调用graphic_console_init时,要传入一个GraphicHwOps结构体,如下: | ||
+ | :: [[File:100ask_imx6ull_qemu_049.png|600px]] | ||
− | + | ===GUI更新函数=== | |
+ | : ask100fb_update函数被GUI系统周期性地调用: | ||
+ | :: [[File:100ask_imx6ull_qemu_050.png|800px]] | ||
− | + | :效果如下: | |
+ | :: [[File:100ask_imx6ull_qemu_051.png|400px]] | ||
− | + | ===GUI函数详解=== | |
− | + | ====解析BMP文件得到RGB数据==== | |
− | + | : [[File:100ask_imx6ull_qemu_052.png|600px]] | |
− | + | ====显示RGB数据==== | |
− | + | : [[File:100ask_imx6ull_qemu_053.png|600px]] | |
− | + | ====显示LCD数据==== | |
− | + | : [[File:100ask_imx6ull_qemu_054.png|600px]] | |
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | = | ||
− | |||
− | ==== | ||
− | |||
− | |||
− | |||
− | |||
− | ==== | ||
− | |||
− | |||
− | |||
− | |||
− | ==== | ||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
+ | ==QEMU的输入:鼠标事件== | ||
+ | : 点击buttons界面时,对应的按钮会被按下或松开: | ||
+ | :: [[File:100ask_imx6ull_qemu_055.png|400px]] | ||
+ | : 这是怎么实现的? | ||
+ | : 我们需要给这个GUI界面添加鼠标处理函数,代码在ui/button_ui.c中。这是新加的文件,要把它编进QEMU中需要修改同目录下的Makefile.objs,比如修改ui/Makefile.objs,添加一行: | ||
+ | <syntaxhighlight lang="C"> | ||
+ | common-obj-$(CONFIG_FSL_IMX6UL) += button_ui.o | ||
+ | </syntaxhighlight> | ||
+ | ===分配、设置、注册QemuInputHandler=== | ||
+ | : [[File:100ask_imx6ull_qemu_056.png|800px]] | ||
+ | ===QemuInputHandler处理函数分析=== | ||
+ | : [[File:100ask_imx6ull_qemu_057.png|800px]] | ||
+ | ===输入事件处理流程=== | ||
+ | : 输入事件的处理源头是sdl2_2d_refresh函数: | ||
+ | :: [[File:100ask_imx6ull_qemu_058.png|800px]] | ||
+ | <syntaxhighlight lang="C"> | ||
sdl2_2d_refresh | sdl2_2d_refresh | ||
+ | sdl2_poll_events(scon); | ||
+ | SDL_PollEvent(ev) | ||
+ | handle_keydown(ev); | ||
+ | handle_keyup(ev); | ||
+ | handle_textinput(ev); | ||
+ | handle_mousemotion(ev); | ||
+ | handle_mousebutton(ev); // SDL_MOUSEBUTTONDOWN,UP | ||
+ | sdl_send_mouse_event(scon, 0, 0, bev->x, bev->y, buttonstate); | ||
+ | </syntaxhighlight> | ||
− | + | ===QEMU捕获的输入事件,可以作为ABS也可以作为REL事件上传=== | |
+ | : 作为哪类事件上传,取决于qemu_input_is_absolute()的返回值: | ||
+ | :: [[File:100ask_imx6ull_qemu_059.png|600px]] | ||
− | + | : qemu_input_is_absolute函数只会判断第1个GUI Console中的QemuInputHandler。我们的第1个GUI Console是“Device Manager”,它的代码是hw/display/device_manager.c。 | |
+ | : 对应的QemuInputHandler的mask必须设置为INPUT_EVENT_MASK_ABS: | ||
+ | :: [[File:100ask_imx6ull_qemu_060.png|600px]] | ||
− | + | ==使用中断== | |
+ | : 在QEMU中模拟一个外设时,这个外设要使用中断的话,并不复杂。 | ||
+ | : 先看看硬件框架: | ||
+ | :: [[File:100ask_imx6ull_qemu_061.png|400px]] | ||
+ | ===外设发出哪一个中断?=== | ||
+ | : GIC从多个中断源获得中断信号,它会发信号给CPU,这样CPU才会处理中断。 | ||
+ | : 作为外设备,它要发出哪一个中断呢? | ||
+ | : 在include/hw/arm/fsl-imx6ul.h中列出了各个中断源,如下: | ||
+ | :: [[File:100ask_imx6ull_qemu_062.png|300px]] | ||
+ | : 你需要查看这些中断源,确定你的外设要发出哪一个中断。 | ||
+ | : 你的外设,要跟中断控制器建立联系,即确定使用哪一个中断。 | ||
+ | ====调用sysbus_init_irq初始化qemu_irq==== | ||
+ | : 在QEMU中,用qemu_irq结构体来描述中断,需要初始化qemu_irq结构体。 | ||
+ | : 以GPIO为例,代码在hw/gpio/imx_gpio.c中: | ||
+ | :: [[File:100ask_imx6ull_qemu_063.png|600px]] | ||
− | + | : sysbus_init_irq函数很有意思: | |
+ | :: ①它会给设备dev添加一个属性prop,prop的名字是“sysbus-irq[0]”或“sysbus-irq[1]” | ||
+ | :: ②会给这个prop添加一个link,link到&s->irq[0]或&s->irq[1] | ||
+ | ====调用sysbus_connect_irq连接中断==== | ||
+ | : 在hw/arm/fsl-imx6ul.c中,给GPIO模块和GIC建立联系: | ||
+ | :: [[File:100ask_imx6ull_qemu_064.png|600px]] | ||
− | < | + | : sysbus_connect_irq也很神奇,它是通过名字“sysbus-irq[0]”或“sysbus-irq[1]”找到设备的属性。 |
+ | : 然后根据属性的link找到之前传入的&s->irq[0]或&s->irq[1],最后设置它。 | ||
+ | : 设置为什么呢?下面的函数会返回一个qemu_irq,第2个参数用来指定是哪一个中断: | ||
+ | <syntaxhighlight lang="C"> | ||
+ | qdev_get_gpio_in(DEVICE(&s->a7mpcore), FSL_IMX6UL_I2Cn_IRQ[i]) | ||
+ | </syntaxhighlight> | ||
+ | ===中断触发=== | ||
+ | : 发出中断: | ||
+ | <syntaxhighlight lang="C"> | ||
+ | qemu_set_irq(irq, 1); | ||
+ | </syntaxhighlight> | ||
+ | : 清除中断: | ||
+ | <syntaxhighlight lang="C"> | ||
+ | qemu_set_irq(irq, 0); | ||
+ | </syntaxhighlight> | ||
− | + | : 上面的2个调用必须成双出现,如果多次发出中断而没有清除中断,那后面发出的中断是无效的。 | |
+ | : 这里说的成双出现,并不是说它们必须前后出现。流程是这样的: | ||
+ | :: ①用户按下按键后,使用“qemu_set_irq(irq, 1)”发出中断 | ||
+ | :: ②客户机的程序处理完中断后,它会写ISR寄存器来清除中断: | ||
+ | : 对ISR寄存器的写操作会导致QEMU中对应的write函数被调用,在里面执行“qemu_set_irq(irq, | ||
+ | 0)”。 | ||
− | < | + | : 对于hw/gpio/imx_gpio.c,发出中断的函数流程为: |
+ | <syntaxhighlight lang="C"> | ||
+ | notify_imx_gpio_change | ||
+ | imx_gpio_set(s, pin, level); | ||
+ | imx_gpio_update_int(s); | ||
+ | qemu_set_irq(s->irq[0], 1); | ||
+ | </syntaxhighlight> | ||
− | < | + | : 清除中断的流程为: |
+ | <syntaxhighlight lang="C"> | ||
+ | imx_gpio_write | ||
+ | case ISR_ADDR: | ||
+ | s->isr &= ~value; | ||
+ | imx_gpio_set_all_int_lines(s); | ||
+ | qemu_set_irq(s->irq[0], 0); | ||
+ | </syntaxhighlight> | ||
+ | ==模拟按键== | ||
+ | : 我们模拟的按键涉及GUI、鼠标输入、中断,值得仔细研究。 | ||
+ | : GUI和鼠标的处理,都在这个文件里:ui/button_ui.c | ||
− | + | : 当用户在GUI中点击某个按键时,它对应哪个引脚? | |
+ | : 这由这个文件决定:hw/gpio/100ask_imx6ull_buttons.c | ||
− | === | + | : 上述文件确定是哪一个引脚之后,要通知hw/gpio/imx_gpio.c来处理。 |
+ | ===按键GUI=== | ||
+ | : [[File:100ask_imx6ull_qemu_065.png|800px]] | ||
+ | ===点击按键=== | ||
+ | : [[File:100ask_imx6ull_qemu_057.png|800px]] | ||
+ | ===更新按键图片的同时=== | ||
+ | : [[File:100ask_imx6ull_qemu_066.png|800px]] | ||
+ | ===单板的代码怎么处理按键=== | ||
+ | : 对应的代码在hw/gpio/100ask_imx6ull_buttons.c中: | ||
+ | :: [[File:100ask_imx6ull_qemu_067.png|600px]] | ||
+ | ===IMX6ULL的代码怎么处理输入引脚=== | ||
+ | : 对应的代码在hw/gpio/imx_gpio.c中: | ||
+ | :: [[File:100ask_imx6ull_qemu_068.png|600px]] | ||
− | + | ==QEMU启动过程== | |
+ | ===各个模块的注册=== | ||
+ | : 各个模块都定义了一个TypeInfo结构体,比如LCD: | ||
+ | :: [[File:100ask_imx6ull_qemu_043.png|800px]] | ||
− | < | + | :并且都使用type_init定义了一个属性为constructor的函数(include/qemu/module.h): |
+ | <syntaxhighlight lang="C"> | ||
+ | #define type_init(function) module_init(function, MODULE_INIT_QOM) | ||
+ | #define module_init(function, type) | ||
+ | static void __attribute__((constructor)) do_qemu_init_ ## function(void) | ||
+ | { | ||
+ | register_dso_module_init(function, type); | ||
+ | } | ||
+ | </syntaxhighlight> | ||
+ | :: [[File:100ask_imx6ull_qemu_069.png|600px]] | ||
− | < | + | : 这些属性为constructor的函数会在main函数之前被调用,这样就得到了一个链表: |
+ | :: [[File:100ask_imx6ull_qemu_021.png|800px]] | ||
+ | ===各个模块的初始化=== | ||
+ | : main函数位于vl.c中,它有如下调用: | ||
+ | <syntaxhighlight lang="C"> | ||
+ | /* main */ | ||
+ | module_call_init(MODULE_INIT_QOM); | ||
+ | QTAILQ_FOREACH(e, l, node) { | ||
+ | e->init(); | ||
+ | } | ||
+ | </syntaxhighlight> | ||
− | + | : 这些xxx_register_types执行后,又得到了什么? | |
+ | :: ①分配一个TypeImpl结构体,使用TypeInfo来设置它: | ||
+ | ::: [[File:100ask_imx6ull_qemu_020.png|400px]] | ||
+ | :: ②把TypeImpl放入链表:type_table | ||
+ | ::: 于是,在程序中就有了这样的链表: | ||
+ | :::: [[File:100ask_imx6ull_qemu_021.png|800px]] | ||
+ | ::: 程序中有一系列的TypeImpl,但是并不表示会用到它们。要用某个TypeImpl,需要创建对应的设备。 | ||
+ | ====选择machine==== | ||
+ | : 启动qemu时,会传入参数“-M mcimx6ul-evk”,对应的文件为:hw/arm/mcimx6ul-evk.c | ||
+ | :: [[File:100ask_imx6ull_qemu_070.png|600px]] | ||
+ | ====选择CPU==== | ||
+ | : [[File:100ask_imx6ull_qemu_071.png|600px]] | ||
− | + | : CPU在中hw/arm/fsl-imx6ul.c定义: | |
+ | : [[File:100ask_imx6ull_qemu_072.png|600px]] | ||
+ | ====CPU的初始化==== | ||
+ | : hw/arm/mcimx6ul-evk.c mcimx6ul_evk_init: | ||
+ | :: [[File:100ask_imx6ull_qemu_073.png|800px]] | ||
+ | : 这会导致hw/arm/fsl-imx6ul.c中fsl_imx6ul_init函数被调用: | ||
+ | :: [[File:100ask_imx6ull_qemu_074.png|600px]] | ||
+ | : 在fsl_imx6ul_init函数中,会去创建名为a7mpcore的设备,它的类型是TYPE_A15MPCORE_PRIV: | ||
+ | :: [[File:100ask_imx6ull_qemu_075.png|800px]] | ||
+ | :: [[File:100ask_imx6ull_qemu_076.png|800px]] | ||
+ | :: [[File:100ask_imx6ull_qemu_077.png|600px]] | ||
+ | : a7mpcore设备对应的realize函数被调用时,里面会实例化GIC等。 | ||
+ | ====在system memory中添加内存==== | ||
+ | : 分配system memory,代码为hw/arm/mcimx6ul-evk.c中的mcimx6ul_evk_init函数: | ||
+ | :: [[File:100ask_imx6ull_qemu_078.png|600px]] | ||
− | + | : 其中的ram_size来自QEMU运行时的参数“-m 512M”;内存基地址为0x80000000。 | |
+ | : 从这里可以知道,system memory中既含有可读可写的内存,也含有各种模块的寄存器。 | ||
− | + | ==调试== | |
+ | ===要调试,运行gdb程序之前,必须先进入源码目录=== | ||
+ | : 无论是使用gdb调试PC程序,还是使用arm-xxx-gdb调试ARM程序,运行这些gdb程序前,必须进入要调试的源码的目录。 | ||
+ | : 然后再启动gdb或arm-xxx-gdb。 | ||
+ | ===调试QEMU本身=== | ||
+ | : 先进入QEMU源码目录,执行如下命令。 | ||
+ | : 这个命令中涉及qemu-system-arm、zImage、设备树、文件系统这4个文件的路径,你要改成你的路径。 | ||
+ | <syntaxhighlight lang="bash"> | ||
+ | gdb --args /home/book/mywork/qemu/qemu_git/qemu/bin/qemu-system-arm -M \ | ||
+ | mcimx6ul-evk -m 512M -kernel /home/book/nfs_rootfs/zImage \ | ||
+ | -dtb /home/book/nfs_rootfs/mynet.dtb -serial stdio \ | ||
+ | -drive file=/home/book/mywork/imx6ull_test_image/buildroot_rootfs.img,format=raw,id=mysdcard -device sd-card,drive=mysdcard \ | ||
+ | -append "console=ttymxc0,115200 console=tty1 rootfstype=ext4 root=/dev/mmcblk1 rw rootwait init=/sbin/init loglevel=8" \ | ||
+ | -nic user | ||
+ | </syntaxhighlight> | ||
− | < | + | : 关键点在于:gdb --args ,它后面就可以跟一连串的命令及参数了。 |
+ | : 这种方法有一个缺点,就是客户机上的Linux系统启动后,你无法再在终端中输入gdb命令。可以使用下一种方法来调试。 | ||
+ | ===调试之前就运行的QEMU=== | ||
+ | : 先进入QEMU源码目录。 | ||
+ | :: ①首先确定qemu-system-arm进程的PID | ||
+ | ::: 执行如下命令: | ||
+ | <syntaxhighlight lang="bash"> | ||
+ | ps -A \| grep qemu | ||
+ | </syntaxhighlight> | ||
+ | ::: 假设得到PID为9527 | ||
+ | :: ②在QEMU源码目录下执行如下命令: | ||
+ | <syntaxhighlight lang="bash"> | ||
+ | sudo gdb -p 9527 | ||
+ | </syntaxhighlight> | ||
+ | ::: {{redtext|注意}}:一定要加上sudo | ||
+ | [[Category:imx6ull]][[Category:Qemu]] | ||
− | + | ===调试客户机上的Linux内核=== | |
+ | ====首先运行qemu-system-arm==== | ||
+ | : 执行QEMU的命令时,在最后加上参数“-s -S”。 | ||
+ | : -s是“-gdb tcp::1234”的缩写,表示QEMU将会在本机端口1234上开启gdbserver。 | ||
+ | : -S表示不启动CPU,当你使用arm-xxx-gdb连接上QEMU后,必须执行c命令才可以运行内核。 | ||
− | + | : 我用的命令如下所示。 | |
− | + | : 这个命令中涉及qemu-system-arm、zImage、设备树、文件系统这4个文件的路径,你要改成你的路径。 | |
− | + | <syntaxhighlight lang="bash"> | |
− | + | gdb --args /home/book/mywork/qemu/qemu_git/qemu/bin/qemu-system-arm -M \ | |
− | + | mcimx6ul-evk -m 512M -kernel /home/book/nfs_rootfs/zImage \ | |
− | + | -dtb /home/book/nfs_rootfs/mynet.dtb -serial stdio \ | |
− | + | -drive file=/home/book/mywork/imx6ull_test_image/buildroot_rootfs.img,format=raw,id=mysdcard -device sd-card,drive=mysdcard \ | |
− | + | -append "console=ttymxc0,115200 console=tty1 rootfstype=ext4 root=/dev/mmcblk1 rw rootwait init=/sbin/init loglevel=8" \ | |
− | + | -nic user -s -S | |
− | + | </syntaxhighlight> | |
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | < | ||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | gdb --args /home/book/mywork/qemu/qemu_git/qemu/bin/qemu-system-arm -M \ | ||
− | |||
− | mcimx6ul-evk -m 512M -kernel /home/book/nfs_rootfs/zImage \ | ||
− | |||
− | -dtb /home/book/nfs_rootfs/mynet.dtb -serial stdio \ | ||
− | |||
− | -drive file=/home/book/mywork/imx6ull_test_image/buildroot_rootfs.img,format=raw,id=mysdcard -device sd-card,drive=mysdcard \ | ||
− | |||
− | -append "console=ttymxc0,115200 console=tty1 rootfstype=ext4 root=/dev/mmcblk1 rw rootwait init=/sbin/init loglevel=8" \ | ||
− | |||
− | -nic user -s -S | ||
− | |||
− | |||
− | |||
− | + | ====进入内核源码目录,运行arm-xxx-gdb vmlinux==== | |
+ | : [[File:100ask_imx6ull_qemu_079.png|800px]] |
2021年12月13日 (一) 15:40的最新版本
目录
- 1 Part A. QEMU使用手册
-
2 Part B. QEMU开发手册
- 2.1 QEMU下载与编译
- 2.2 QEMU的设备创建过程
- 2.3 QEMU的设备模拟
- 2.4 模拟LED
- 2.5 模拟LCD
- 2.6 QEMU的输出:GUI系统
- 2.7 QEMU的输入:鼠标事件
- 2.8 使用中断
- 2.9 模拟按键
- 2.10 QEMU启动过程
- 2.11 调试
Part A. QEMU使用手册
QEMU简介
- QEMU的英文单词是:QuickEmulator,它是一个小巧的模拟器。还有很多模拟器,比如VMWare、Virtual Box等。
- 但是VMWare、VirtualBox只能模拟x86、AMD64/Intel64等PC系统;而QEMU可以模拟更多硬件:ARM、MIPS、PPC、x86、AMD64/Intel64。
- QEMU用途广泛,比如Xen、Android模拟器等都是基于QEMU的。
- 在嵌入式领域,很多人使用QEMU来深研Linux,比如研究文件系统、优化等等。
- QEMU有两种模式:
用户模式(User Mode)
- 简单地说,一个使用arm-xxx-gcc编译出来的程序,是给ARM板子使用的,它无法在PC机上运行,只能放到ARM板子上去运行。
- 借助qemu,可以在PC机上运行ARM程序。比如:
$ gcc -o hello hello.c -static
$ ./hello // 这个hello程序是使用gcc给PC机编译的,可以直接运行
Hello, world!
$ arm-linux-gnueabihf-gcc -o hello hello.c -static // 它是给ARM板子编译的
$ ./hello // 所以无法在PC上运行
bash: ./hello: cannot execute binary file: Exec format error
$ ./qemu-arm ./hello // 我们可以用QEMU在PC上运行它
Hello, world!
- 在PC上使用qemu运行单个ARM程序时,这就是使用QEMU的用户模式。
- 它会把ARM指令翻译为PC的指令去运行。
-
注意,你可能无法做上述实验,因为:
- a) 你没有安装ARM交叉编译工具链
- b) 你没有安装QEMU
- 你根据下章《QEMU快速使用》安装QEMU后,就可以进行上述实验了。
系统模式(System Mode)
- 很多时候我们并不满足于在PC上运行单个ARM程序,我们想模拟出整个ARM单板:在这个模拟出来的虚拟ARM单板上,运行Linux系统,在其中运行各种APP。
- 这时候需要使用QEMU的系统模式。
- 我们就是使用QEMU的系统模式来模拟IMX6ULL开发板,具体的使用请看下章。
我们做的改进
- QEMU可以模拟x86,也可以模拟各种ARM板子,还可以模拟各种外设。
- 百问网对QEMU做了很多改进,支持更多硬件,支持更多GUI现实,支持更方便的调试。
100ask-qemu特点
- 1.模拟网卡
- 2.模拟LCD显示功能
- 3.模拟led灯、按键
- 4.模拟at24cxx i2c存储芯片,直接可以通过用户态操作看到效果
- 5.增加逻辑分析仪显示功能
- 6.后续会逐渐增加更多的模拟硬件模块
- 6.1 温湿度传感器
- 6.2 红外
- 6.3 超声波模块
- 6.4 ADC,DAC模块
- 6.5 I2C接口的传感器
- 6.6 SPI接口的OLED
- 6.7 你想加啥,跟我们说
QEMU快速使用
- 使用apt-get当然也可以安装QEMU,但是它版本太低,也不支持IMX6ULL。
- 新版本QEMU已经支持IMX6ULL,我们在此基础上添加了更多功能,也修改了一些BUG。
- 所以,请参考本文安装我们定制的QEMU。
准备工作
Windows电脑和VMWare虚拟机
-
必须的:
- ①一台可以上网的windows电脑
- ②一个VMWare虚拟机,里面运行Ubuntu,也要能上网
- VMWare虚拟机软件和Ubuntu映象可以从网盘中下载,打开以下链接:
- 百度网盘下载地址
- 找到“韦东山升级版全系列视频开发板BSP包”,打开对应的网盘链接后,可以看到一个目录“100ask_imx6ull-qemu”,进入“01_Tools”子目录 :
确保Ubuntu可以上网,可以跟Windows联通
- 注意:很多人碰到VMWare中Ubuntu联网问题问题,可以参考此链接:
安装KVM
- 可选的(注意,如果想要有更快的效果,在ubuntu下可以安装KVM):
- 安装kvm加速qemu运行,在终端下执行如下命令:
$ sudo apt-get update
$ sudo apt-get install qemu qemu-kvm libvirt-bin bridge-utils virt-manager
获取镜像
下载
- 我们只提供ubuntu 16.04和ubuntu 18.04系统的QEMU镜象文件。
点击如下链接下载
ubuntu-18.04开发环境下qemu imx6ul系统镜像下载页面
ubuntu-16.04开发环境下qemu imx6ul系统镜像下载页面
- 下载完成后可以得到ubuntu-16.04_imx6ul_qemu_system、ubuntu-18.04_imx6ul_qemu_system这样的目录
- 然后进入这些目录执行后文介绍的命令。
运行QEMU系统
- 假设你已经按照上文下载、解压好了QEMU镜像文件,你需要进入QEMU的目录,执行下列命令。
首次运行需要安装SDL环境
- 使用脚本自动解压安装:
$./install_sdl.sh // 提示输入用户密码,等待安装完成
如果出现报错 Package xxxx is not installed.,可使用apt命令修复:
$sudo apt --fix-broken install
运行带GUI的imx6ul模拟器
- ①模拟百问网imx6ull-qemu开发板
$./qemu-imx6ull-gui.sh // 启动后,登录名是root,无需密码
- ②模拟野火imx6ull-pro开发板
$./qemu-imx6ull-gui.sh fire // 启动后,登录名是root,无需密码
- ③模拟正点原子imx6ull-alpha开发板
$./qemu-imx6ull-gui.sh atk // 启动后,登录名是root,无需密码
运行不带GUI的imx6ul模拟器
$./qemu-imx6ull-nogui.sh // 启动后,登录名是root,无需密码
如果在ssh字符终端下要退出QEMU,可以输入ctrl+a 抬起后,再输入'x'。
参数讲解
- 可以打开脚本文件qemu-imx6ull-gui.sh,它就是运行qemu-system-arm程序。其中用到了很多参数:
-M mcimx6ul-evk 指定需要模拟的单板型号。
-m 512M 指定板子的内存大小。
-kernel zImage 指定使用的内核镜像文件。
-dtb 100ask_imx6ull_qemu.dtb 指定使用的设备树文件。
-display sdl 指定使用那种图形显示输出。
-serial mon:stdio 指定串口信息输出。
-drive file=rootfs.img,format=raw,id=mysdcard名为mysdcard的drive,源为rootfs.img
-device sd-card,drive=mysdcard 添加一个sd-card设备,内容来自名为mysdcard的drive
-append “console=ttymxc0,115200 rootfstype=ext4 root=/dev/mmcblk1 rw rootwaitinit=/sbin/init loglevel=8” 指定内核的命令行参数
-nic user 指定网卡为user mode
- 有了内核zImage、设备树、文件系统(rootfs.img),这就是一个完整的Linux系统。
- 注意:QEMU中没有实现bootloader,以后我们会完全模拟SD卡,在SD卡上面放置u-boot、内核、设备树、文件系统。
QEMU操作示例
- 先执行以下命令启动QEMU,它模拟百问网imx6ull-qemu开发板:
$./qemu-imx6ull-gui.sh // 启动后,登录名是root,无需密码
- 它会弹出一个开发板的界面,并且运行该开发板的Linux系统,你可以在终端中操作该开发板:
- 注意:当你的鼠标点击QEMU的GUI界面时,鼠标将无法移出这个GUI界面。这时可以通过快捷键“Ctrl+Alt+g”把鼠标从GUI界面中退出来。
操作设备管理器
操作LCD
- 我们模拟的IMX6ULL板子,它的Linux系统中已经带有LCD的测试命令,可以执行以下命令测试:
[root\@qemu_imx6ul:\~]\# fb-test
- 或
[root\@qemu_imx6ul:\~]\# myfb-test /dev/fb0
操作LED
- 我们模拟的IMX6ULL板子,它的Linux系统中已经带有LED驱动和测试命令,可以执行以下命令测试:
[root\@qemu_imx6ul:\~]\# cd led_driver_qemu/
[root\@qemu_imx6ul:\~/led_driver_qemu]\# insmod 100ask_led.ko
[root\@qemu_imx6ul:\~/led_driver_qemu]\# ./ledtest /dev/100ask_led0 off
[root\@qemu_imx6ul:\~/led_driver_qemu]\# ./ledtest /dev/100ask_led0 on
使用按键来控制LED
- 我们模拟的IMX6ULL板子,它的Linux系统中已经带有LED驱动、按键驱动和测试命令。
- 首先在“QEMU 设备管理器”中打开按键的界面,然后执行以下命令测试:
[root\@qemu_imx6ul:\~]\# cd led_driver_qemu/
[root\@qemu_imx6ul:\~/led_driver_qemu]\# insmod 100ask_led.ko
[root\@qemu_imx6ul:\~/led_driver_qemu]\# cd ../button_driver_qemu/
[root\@qemu_imx6ul:\~/button_driver_qemu]\# insmod button_drv.ko
[root\@qemu_imx6ul:\~/button_driver_qemu]\# insmod board_100ask_qemu_imx6ull.ko
[root\@qemu_imx6ul:\~/button_driver_qemu]\# ./button_led_test
使用QEMU进行嵌入式Linux开发
- 完整的嵌入式Linux系统包含:bootloader、Linux内核、设备树、根文件系统。
- QEMU可以略过bootloader而直接启动内核,这给开发带来了便利。
- 但是以后我们会修改QEMU,让它支持从SD卡上启动bootloader,让bootloader来启动内核。
- 在本章中,我们介绍如何编译并替换内核、编译并替换设备树,修改根文件系统。
获取源码
- 我们的源码都是存放在GIT中,所以需要确保你的Ubuntu可以上网。如果不能上网,请参考以下链接:
设置git邮箱账号和用户名
- 在Ubuntu中执行如下命令:
book\@100ask:\~\$ git config --global user.email "you\@example.com"
book\@100ask:\~\$ git config --global user.name "Your Name"
- 初次使用GIT时,需要配置邮箱帐号和用户名,可以随意指定。
下载源码
- 考虑到代码仓库过多,特使用repo工具管理代码。
- 先用git clone下载repo工具,再用repo工具下载源码:
book\@100ask:\~\$ git clone https://e.coding.net/codebug8/repo.git
book\@100ask:\~\$ mkdir -p 100ask_imx6ull-qemu && cd 100ask_imx6ull-qemu
book\@100ask:\~/100ask_imx6ull-qemu\$ ../repo/repo init -u https://e.coding.net/weidongshan/manifests.git -b linux-sdk -m imx6ull/100ask-imx6ull_qemu_release_v1.0.xml --no-repo-verify
book\@100ask:\~/100ask_imx6ull-qemu\$ ../repo/repo sync -j4
百问网imx6ull-qemu开发板资料下载
- 百问网imx6ull-qemu开发板,并不是真实的开发板,我们可以在它上面添加任意硬件,当然也会提供原理图。
- 这款虚拟开发板的资料可以从网盘中下载,打开以下链接:
- http://wiki.100ask.org/Download_link_page
- 找到:找到“韦东山升级版全系列视频开发板BSP包”,打开对应的网盘链接后,可以看到一个目录“100ask_imx6ull-qemu”。
设置工具链
- 交叉编译工具链主要是用于在ubuntu主机上编译可以在其它平台上运行的系统,比如在PC上为ARM板子编译程序。
- 设置交叉编译工具主要是设置PATH,ARCH和CROSS_COMPILE三个环境变量,下面介绍具体设置方法(3种方法任选一种)。
永久生效
- 如需永久修改,请修改用户配置文件。在Ubuntu系统下,修改如下:
book\@100ask:\~\$ vi ~/.bashrc
- 在行尾添加或修改:
export ARCH=arm
export CROSS_COMPILE=arm-linux-gnueabihf-
export PATH=$PATH:/home/book/100ask_imx6ull-qemu/ToolChain/gcc-linaro-6.2.1-2016.11-x86_64_arm-linux-gnueabihf/bin
注意:设置完环境变量后需要执行以下命令或重启UBUNTU才能使环境变量生效:
source ~/.bashrc
临时生效
- 如果你有很多单板,为了不冲突,你不能使用“永久生效”的办法。比如你有32位的ARM板,也有64位的ARM板,在使用前者时需要设置ARCH=arm,在使用后者时需要设置ARCH=arm64。
- 这种情况下,可以使用“export”命令设置环境变量,这种设置方法只对当前终端有效:
book\@100ask:\~\$ export PATH=$PATH:/home/book/100ask_imx6ull-qemu/ToolChain/gcc-linaro-6.2.1-2016.11-x86_64_arm-linux-gnueabihf/bin
book\@100ask:\~\$ export ARCH=arm
book\@100ask:\~\$ export CROSS_COMPILE=arm-linux-gnueabihf-
手动指定
- 执行make命令时,可以手工指定ARCH架构、CROSS_COMPILE等变量:
book\@100ask:\~\$ export PATH=$PATH:/home/book/100ask_imx6ull-qemu/ToolChain/gcc-linaro-6.2.1-2016.11-x86_64_arm-linux-gnueabihf/bin
book\@100ask:\~\$ make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf-
- 不过这种方法效率太低,不建议使用。
编译内核及设备树
- 编译内核需要安装一些程序,执行以下命令安装:
book\@100ask:\~/100ask_imx6ull-qemu\$ sudo apt --fix-broken install
book\@100ask:\~/100ask_imx6ull-qemu\$ sudo apt-get install lzop
- 前面我们下载了源码,设置好工具链后,即可编译:
book\@100ask:\~/100ask_imx6ull-qemu\$ cd linux-4.9.88
book\@100ask:\~/100ask_imx6ull-qemu/linux-4.9.88\$ make mrproper
book\@100ask:\~/100ask_imx6ull-qemu/linux-4.9.88\$ make 100ask_imx6ull_qemu_defconfig
book\@100ask:\~/100ask_imx6ull-qemu/linux-4.9.88\$ make zImage -jN //编译zImage内核镜像,其中N参数可以根据CPU个数,来加速编译系统。
book\@100ask:\~/100ask_imx6ull-qemu/linux-4.9.88\$ make dtbs //编译设备树文件
- 编译成功后,可以得到如下文件:
arch/arm/boot/zImage // 内核
arch/arm/boot/dts/100ask_imx6ull_qemu.dtb // 设备树
修改文件系统
- 安装好我们提供的QEMU后,你可以得到一个imx6ull-system-image目录,里面有名为rootfs.img的文件,它就是根文件系统:
- 你可以在Ubuntu下直接修改rootfs.img,不过要先挂载,执行以下命令:
- 主要命令就是:
sudo mount -o loop rootfs.img /mnt
- 你就可以在/mnt目录下对其中的文件进行操作了,也可以把Ubuntu中的文件复制进去。
- 注意:修改完毕后,要执行以下命令:
sudo umount /mnt
启动模拟器后使用NFS
- 安装好我们提供的QEMU后,可以执行如下命令启动开发板:
$./qemu-imx6ull-gui.sh // 启动后,登录名是root,无需密码
- 等待进入系统后,就可以使用网络命令挂载Ubuntu的NFS目录了。
在Ubuntu上安装、配置NFS服务
- 如果你使用的是我们提供的Ubuntu,那么已经安装好了NFS服务。
- 如果你的Ubuntu未安装NFS服务,那么在确保Ubuntu可以上网的前提下,执行以下命令:
sudo apt-get install nfs-kernel-server
- 然后,还得修改/etc/exports,添加类似以下的内容,下面的例子里允许开发板通过NFS访问Ubuntu的/home/book、/work两个目录:
/home/book *(rw,nohide,insecure,no_subtree_check,async,no_root_squash)
/work *(rw,nohide,insecure,no_subtree_check,async,no_root_squash)
- 最后,重启NFS服务,在Ubuntu上执行以下命令:
sudo /etc/init.d/nfs-kernel-server restart
- 可以在Ubuntu上通过NFS挂载自己,验证一下NFS可用:
sudo mount -t nfs -o nolock,vers=3 127.0.0.1:/home/book /mnt
ls /mnt
开发板获取IP地址
- QEMU运行时,Ubuntu是Host即宿主机,QEMU给它分配的IP是10.0.2.2。
- QEMU模拟的imx6ull板子是Guest即客户机,它会自动获取IP,也可以自己设置。
- Guest可以通过10.0.2.2访问Host,Host不能访问Guest。
- Guest中可以使用ifconfig命令查看IP,如果没有IP,可以如下设置:
[root\@qemu_imx6ul:\~]\# ifconfig eth0 10.0.2.15
挂载主机nfs目录
- QEMU模拟的imx6ull开发板,可以去访问10.0.2.2,比如使用NFS挂载:
[root\@qemu_imx6ul:\~]\# mount -t nfs -o nolock,vers=3 10.0.2.2:/home/book/nfs_rootfs /mnt
- 如果一切正常,在开发板上就可以通过/mnt目录访问Ubuntu的/home/book/nfs_rootfs目录了。
更新QEMU
- 我们使用QEMU来模拟IMX6ULL开发板,目的是学习嵌入式Linux系统。随着我们课程的陆续发布,我们也会给QEMU添加更多的功能。比如后面要讲到触摸屏时,就会修改QEMU让它支持触摸屏。
- 所以你需要不断地更新QEMU,有3个方法。
下载最新的release包
- 从下面2个地址就可以下载到最新的release包,解压开后即可使用:
- ①ubuntu-18.04开发环境下qemu imx6ul系统镜像下载地址:
- ②ubuntu-16.04开发环境下qemu imx6ul系统镜像下载地址:
- 注意:如果你之前对内核、设备树或是文件系统进行过修改,想继续使用这些修改过的文件的话,需要用它们来覆盖release包中解压出来的对应文件。
下载最新的qemu-system-arm可执行程序及配置文件
- 如果你不想下载整个release包,可以只替换其中的某些文件。
- 可以打开下面的某个网址(根据你的ubuntu版本号选择):
- 以ubuntu 18.04为例,进入下图所示的bin、etc目录:
- 对于bin目录,只需要下载其中的qemu-sysmte-arm;对于etc目录,里面所有的bmp文件都要下载。
- 你之前在Ubuntu中曾经安装过我们提供的QEMU,你用下载的文件覆盖QEMU中的对应文件即可。
下载最新的qemu源码并编译
- 请参考后续章节,这属于对QEMU的开发了。对于嵌入式Linux初学者,没必要研究QEMU,使用我们提供的可执行程序即可。
Part B. QEMU开发手册
- 重要的事情说三遍:
- 对于嵌入式Linux初学者,对于QEMU无感者,对于急于找工作的人,
- 不需要深入研究QEMU,不需要看本开发手册,看前面的使用手册就可以了,会用就行。
- 不需要深入研究QEMU,不需要看本开发手册,看前面的使用手册就可以了,会用就行。
- 不需要深入研究QEMU,不需要看本开发手册,看前面的使用手册就可以了,会用就行。
- 我们使用QEMU来模拟IMX6ULL开发板,就像去制作一个开发板一样。作为初学者,你并不需要去设计开发板,会用开发板就可以了。
- 如果你想深入研究QEMU,想承接我们发布的QEMU外包项目,那么请阅读本手册。
QEMU框架简单,功能却很强大。
- 理解了它的框架之后,添加新的外设并不困难。当然这需要你对外设的原理有清楚的认识。
- 我们开发QEMU,要做的事情主要有3部分:
-
1. 添加外设
- 比如添加一个LED,那么在QEMU中得有对应的源码,这些源码要监测LED对应的寄存器的值:当APP写寄存器时,这些源码要把那些值记录下来。
-
2. 添加外设的GUI
- QEMU的界面很简陋,它默认只实现了LCD的GUI显示。你想点亮LED,在QEMU上是看不出效果的。
- 所以我们需要给外设添加GUI界面。
- 比如上面举的LED例子,APP写寄存器时,除了把寄存器的值记录下来之后,我们还要在GUI界面显示一个LED,并且把它点亮或熄灭。
-
3. 在虚拟开发板上开发测试程序
- 这个测试程序可以是裸机,也可以是Linux驱动+应用程序。
- 我们在QEMU模拟出来的板子上运行这些测试程序时,跟真实板子应该没有差别。
- 上述列的第1、2部分,需要对QEMU有所了解,请看本文的“开发手册”。
- 上述列的第3部分,跟QEMU无关,跟在真实板子上写的程序是一样的。
QEMU下载与编译
源码下载
- 官方的QEMU对IMX6ULL的支持还太弱,没有更形象化的GUI,也没有更多的外设。我们对它进行了大量的改进。
- 修改后的源码位于这2个GIT中:
- 在Ubuntu下,执行如下命令即可下载:
$ git clone https://gitee.com/weidongshan/qemu.git
- 或
$ git clone https://github.com/100askTeam/qemu.git
- 我们会不断更新QEMU,你下载过QEMU之后,可以进入qemu目录执行下述命令更新代码:
$ git pull origin
配置、编译、安装
- 进入qemu目录,执行如下配置命令:
./configure --prefix=$PWD/ --target-list="arm-softmmu arm-linux-user" --enable-debug --enable-sdl --enable-kvm --enable-tools --disable-curl
- 编译、安装命令如下:
$ make
$ make install
- 如果一切正常,会在qemu源码目录下生成bin子目录,里面存放有各种可执行程序。
- 配置、编译过程中有可能出错,一般就是缺少库文件。
- 如果你的ubuntu能上网,那么使用apt-get命令即可安装这些库。
- 错误示例,提示信息如下:
ERROR: pixman >= 0.21.8 not present.
Please install the pixman devel package.
- 解决方法:
- ①确定库的名称:
- 执行如下命令:
- ①确定库的名称:
$ apt-cache search pixman
libpixman-1-0 - pixel-manipulation library for X and cairo
libpixman-1-dev - pixel-manipulation library for X and cairo (development files)
- 根据输出信息,需要安装开发包(dev表示开发包): libpixman-1-dev。
- ② 安装开发包:
$ sudo apt-get install libpixman-1-dev
- 可能你的ubuntu中已经安装了某些开发包,下面列出一些必须的包:
$ sudo apt-get install pkg-config
$ sudo apt-get install libsdl2-dev
$ sudo apt-get install libpixman-1-dev
- 每次出错后,根据提示信息安装开发包,然后重新配置、编译、安装。
- 如果一切正常,在当前目录下会生成bin子目录, 里面有生成的QEMU程序:
qemu-system-arm
使用新的qemu-system-arm
- 将上面编译出来的bin/qemu-system-arm 可执行文件复制到如下目录:
ubuntu-18.04_imx6ul_qemu_system/qemu/bin
- 或
ubuntu-16.04_imx6ul_qemu_system/qemu/bin
- 我们也可能添加了更多的GUI显示,这些GUI所用图片位于源码目录的etc子目录下,这些图片也需要复制到如下目录去:
ubuntu-18.04_imx6ul_qemu_system/qemu/etc
- 或
ubuntu-16.04_imx6ul_qemu_system/qemu/etc
- 然后就可以执行 qemu-imx6ull-gui.sh 或
qemu-imx6ull-nogui.sh来使用您编译出来的QEMU了。
QEMU的设备创建过程
重要结构体TypeInfo
- 一个板子上有很多硬件:IMX6ULL、LED、按键、LCD、触摸屏、网卡等等。
- IMX6ULL这类芯片被称为SoC(System onChip),它里面也有很多部件,比如CPU、GPIO、SD控制器、中断控制器等等。
- 这些硬件,或是部件,各有不同。怎么描述它们?
- 每一个都使用一个TypeInfo结构体来描述。
- 比如对于CPU,有这样的结构体(hw/cpu/a15mpcore.c):
- 对于LCD,有这样的结构体(hw/display/100ask_qemu_fb.c):
- 对于GPIO,也有这样的结构体(hw/gpio/imx_gpio.c):
- 由此可见,在QEMU中,每一个硬件都由一个TypeInfo来描述。这些结构体都会被注册进程序里,在一个链表中保存着,备用。**注意**,是备用,它们并不一定会用到。
- 怎么注册这些TypeInfo结构体呢?不需要我们去调用注册函数,以GPIO为例,在hw/gpio/imx_gpio.c中有如下代码:
- 关键点在于type_init,这个宏在include/qemu/module.h中定义:
- 对于属性为“constructor”函数,它在main函数之前被调用。
- 对于上述的type_init(imx_gpio_register_types),它的宏展开如下:
static void __attribute__((constructor)) do_qemu_init_imx_gpio_register_types (void)
{
register_module_init(imx_gpio_register_types, MODULE_INIT_QOM);
}
生成TypeImpl
- 从名字上讲,TypeImpl就是TypeInfo的实现,就是使用TypeInfo的信息构造一个TypeImpl结构体。
- 在init_type_list[MODULE_INIT_QOM]链表中,有一系列的ModuleEntry结构体,每个ModuleEntry结构体中有一个init函数,它指向某个xxx_register_types,比如:
a15mp_register_types
ask100fb_register_types
- 这些ModuleEntry结构体中的init函数何时被调用?
/* vl.c */
/* main */
module_call_init(MODULE_INIT_QOM);
QTAILQ_FOREACH(e, l, node) {
e->init();
}
使用TypeImpl:实例化
- 在程序的type_table链表中,有很多TypeImpl结构体,比如CPU、GPIO、LED、LCD对应的TypeImpl结构体。
- 但是这并不表示QEMU模拟的板子上有这些硬件,必竟它们只是“TypeImpl”,表示“类型”,需要在“实例化”之后,才表示板子上有了这些硬件。
- 以CPU为例,代码为hw/cpu/a15mpcore.c,里面声明了一个A15MPPrivState结构体,还定义了一个TypeInfo结构体:
- A15MPPrivState和TypeInfo、TypeImpl之间有什么关系?
- TypeImpl的信息基本来自TypeInfo,
- 所以问题转为:A15MPPrivState和TypeInfo之间有什么关系?
- A15MPPrivState用来表示一个CPU,你要在板子上添加一个CPU,必须分配、设置一个A15MPPrivState结构体。
- 板子上的主芯片可能是单核CPU的,也可能是多核CPU的。
- 假设有2个CPU,那么应该有对应的2个A15MPPrivState结构体。这2个CPU是类似的,同属于某类:用TypeImpl来描述。
- 所以,可以得到下面的图:
- 谁来分配、设置A15MPPrivState结构体?
- ①分配:
- 猜测是根据TypeInfo中的instrance_size来malloc出A15MPPrivState结构体。
- ②设置:
- 猜测是调用TypeInfo中的instrance_init函数来设置刚malloc出A15MPPrivState结构体。
- ①分配:
- 谁来malloc、谁来调用TypeInfo中的instrance_init函数?
- 有2种方法:
- ①qdev_create/qdev_init_nofail
- ②sysbus_init_child_obj/object_property_set_bool
- 有2种方法:
实例化方法1:qdev_create/qdev_init_nofail
qdev_create分析
qdev_init_nofail分析
总结:怎么创建设备
- 怎么定义一个设备?如下图:
- ①先定义一个TypeInfo结构体
- 里面有name,表示它的类型名。
- 有class_init,这是“类别的初始化函数”,该类下可能有多个设备。class_init函数中通常给该类设置一个realize函数。
- 有instance_size,每一个设备都用一个结构体来表示,比如LCD用ASK100FbState来描述。Instance_size表示这个结构体的大小。
- 有instance_init,这是“实例的初始化函数”,它会被用来初始化设备结构体,比如初始化ASK100FbState结构体。
- ②注册这个TypeInfo结构体:
- 定义一个ask100fb_register_types函数,里面会注册TypeInfo结构体。
- ③使用type_init,把ask100fb_register_types函数放入链表中
- ④调用qdev_create创建设备,这会传入type name
- ⑤调用qdev_init_nofail设置设备的状态为realized,这会导致类别的realize函数被调用。
- 简单地说,一个设备被创建时,这些函数被依次调用:
- ①TypeInfo中的class_init:它会设置dc->realize = 某个函数
- ②TypeInfo中instance_size大小的对象被malloc
- ③TypeInfo中的instance_init函数被调用,它被用来初始化步骤②中malloc出来的结构体
- ④dc->realize被调用
实例化方法2:object_initialize_child/object_property_set_bool
object_initialize_child分析
- 该函数的第5个参数是type,表示type name,它会被用来找到对应的TypeImpl。
- 找到后,会分配instance_size大小的结构体;
- 然后调用TypeImpl中的class_init函数,这一般是设置dc->realize。
- 最后调用TypeImpl中的instance_init函数。
object_property_set_bool分析
总结:怎么创建设备
- 怎么定义一个设备?如下图所示:
- ①先定义一个TypeInfo结构体
- 里面有name,表示它的类型名。
- 有class_init,这是“类别的初始化函数”,该类下可能有多个设备。class_init函数中通常给该类设置一个realize函数。
- 有instance_size,每一个设备都用一个结构体来表示,比如CPU用A15MPPrivState来描述。Instance_size表示这个结构体的大小。
- 有instance_init,这是“实例的初始化函数”,它会被用来初始化设备结构体,比如初始化A15MPPrivState结构体。
- ②注册这个TypeInfo结构体:
- 定义一个a15mp_register_types函数,里面会注册TypeInfo结构体。
- ③使用type_init,把a15mp_register_types函数放入链表中
- ④调用object_initialize_child创建设备,这会传入type name
- ⑤调用object_property_set_bool设置设备的状态为realized,这会导致类别的realize函数被调用。
QEMU的设备模拟
QEMU模拟外设的原理
- QEMU主要是实现了CPU核的模拟,可以读写某个地址。
- QEMU的模拟外设的原理很简单:硬件即内存。
- 要在QEMU上模拟某个外设,思路就是:
- ①CPU读某个地址时,QEMU模拟外设的行为,把数据返回给CPU
- ②CPU写某个地址时,QEMU获得数据,用来模拟外设的行为。
- 即:要模拟外设备,我们只需要针对外设的地址提供对应的读写函数即可。
- 以LCD控制器为例,它主要有2大功能:
- ①写LCD控制器,根据外接的LCD设置参数,比如分辨率、BPP、各种时序
- ②从FrameBuffer中不断获得数据发给LCD,在LCD上显示出来。
-
站在Linux LCD驱动的角度,上述2大功能可以分为:
- ①写LCD控制器的相关寄存器
- ②分配FrameBuffer,写FrameBuffer
- QEMU中要模拟LCD控制器,需要:
- ①记录驱动程序写寄存器的值,解析出分辨率等信息
- ②记录FrameBuffer的地址,并持续不断地从中得到图像数据并显示出来
- 以LCD控制器为例,它主要有2大功能:
- 框图如下:
- 简单地说,
- ① 设置LCD控制器时:
- 在QEMU中可以给LCD控制器的访问地址A1~A2提供读写回调函数,比如:
- ① 设置LCD控制器时:
qemu_a1a2_read
qemu_a1a2_write
- 当LinuxLCD驱动程序写LCD控制器的寄存器时,就会导致qemu中的qemu_a1a2_write函数被调用,在函数中分析、记录这些值,得到分辨率等信息。
- ②写FrameBuffer时:
- 在QEMU中针对FrameBuffer提供一个刷新函数。
- 当LCD驱动写FrameBuffer时,QEMU会使用这些数据更新GUI窗口的图像。
给某段地址提供读写函数
- 怎么描述某段地址:基地址、大小;还得给这段地址提供读写函数。
- 这段地址设置好后,需要添加进system_memory去。
- 有2种方法。
memory_region_init_io/memory_region_add_subregion
模拟LED
主要函数
模拟LCD
- 我们添加的文件是:hw/display/100ask_qemu_fb.c,新加一个文件时要把它编进QEMU中,需要修改同目录的Makefile.objs,比如修改hw/display/Makefile.objs,添加一行:
common-obj-\$(CONFIG_FSL_IMX6UL) += 100ask_qemu_fb.o
在Linux上编写LCD驱动程序
QEMU的输出:GUI系统
- QEMU的GUI系统,支持SDL、GTK等。SDL使用比较简单,我们就使用SDL来显示GUI。
- SDL的显示原理跟LCD
- FrameBuffer是一样的,可以认为每一个SDL窗口都有一个显存。你可以在显存中修改每一个象素的颜色。
- 本文以hw/display/100ask_qemu_fb.c为例进行讲解。
创建GUI Console
- 创建GUI Console的函数有2个:graphic_console_init、graphic_console_init_hidden。
- 后者是我们添加的,它创建的GUI Console默认是隐藏的,要显示的话需要在“Device Manager”中点击对应的按钮。
QEMU的输入:鼠标事件
- 点击buttons界面时,对应的按钮会被按下或松开:
- 这是怎么实现的?
- 我们需要给这个GUI界面添加鼠标处理函数,代码在ui/button_ui.c中。这是新加的文件,要把它编进QEMU中需要修改同目录下的Makefile.objs,比如修改ui/Makefile.objs,添加一行:
common-obj-$(CONFIG_FSL_IMX6UL) += button_ui.o
输入事件处理流程
sdl2_2d_refresh
sdl2_poll_events(scon);
SDL_PollEvent(ev)
handle_keydown(ev);
handle_keyup(ev);
handle_textinput(ev);
handle_mousemotion(ev);
handle_mousebutton(ev); // SDL_MOUSEBUTTONDOWN,UP
sdl_send_mouse_event(scon, 0, 0, bev->x, bev->y, buttonstate);
使用中断
外设发出哪一个中断?
- GIC从多个中断源获得中断信号,它会发信号给CPU,这样CPU才会处理中断。
- 作为外设备,它要发出哪一个中断呢?
- 在include/hw/arm/fsl-imx6ul.h中列出了各个中断源,如下:
- 你需要查看这些中断源,确定你的外设要发出哪一个中断。
- 你的外设,要跟中断控制器建立联系,即确定使用哪一个中断。
调用sysbus_init_irq初始化qemu_irq
- sysbus_init_irq函数很有意思:
- ①它会给设备dev添加一个属性prop,prop的名字是“sysbus-irq[0]”或“sysbus-irq[1]”
- ②会给这个prop添加一个link,link到&s->irq[0]或&s->irq[1]
中断触发
- 发出中断:
qemu_set_irq(irq, 1);
- 清除中断:
qemu_set_irq(irq, 0);
- 上面的2个调用必须成双出现,如果多次发出中断而没有清除中断,那后面发出的中断是无效的。
- 这里说的成双出现,并不是说它们必须前后出现。流程是这样的:
- ①用户按下按键后,使用“qemu_set_irq(irq, 1)”发出中断
- ②客户机的程序处理完中断后,它会写ISR寄存器来清除中断:
- 对ISR寄存器的写操作会导致QEMU中对应的write函数被调用,在里面执行“qemu_set_irq(irq,
0)”。
- 对于hw/gpio/imx_gpio.c,发出中断的函数流程为:
notify_imx_gpio_change
imx_gpio_set(s, pin, level);
imx_gpio_update_int(s);
qemu_set_irq(s->irq[0], 1);
- 清除中断的流程为:
imx_gpio_write
case ISR_ADDR:
s->isr &= ~value;
imx_gpio_set_all_int_lines(s);
qemu_set_irq(s->irq[0], 0);
模拟按键
- 我们模拟的按键涉及GUI、鼠标输入、中断,值得仔细研究。
- GUI和鼠标的处理,都在这个文件里:ui/button_ui.c
- 当用户在GUI中点击某个按键时,它对应哪个引脚?
- 这由这个文件决定:hw/gpio/100ask_imx6ull_buttons.c
- 上述文件确定是哪一个引脚之后,要通知hw/gpio/imx_gpio.c来处理。
QEMU启动过程
各个模块的注册
- 并且都使用type_init定义了一个属性为constructor的函数(include/qemu/module.h):
#define type_init(function) module_init(function, MODULE_INIT_QOM)
#define module_init(function, type)
static void __attribute__((constructor)) do_qemu_init_ ## function(void)
{
register_dso_module_init(function, type);
}
各个模块的初始化
- main函数位于vl.c中,它有如下调用:
/* main */
module_call_init(MODULE_INIT_QOM);
QTAILQ_FOREACH(e, l, node) {
e->init();
}
- 这些xxx_register_types执行后,又得到了什么?
CPU的初始化
调试
要调试,运行gdb程序之前,必须先进入源码目录
- 无论是使用gdb调试PC程序,还是使用arm-xxx-gdb调试ARM程序,运行这些gdb程序前,必须进入要调试的源码的目录。
- 然后再启动gdb或arm-xxx-gdb。
调试QEMU本身
- 先进入QEMU源码目录,执行如下命令。
- 这个命令中涉及qemu-system-arm、zImage、设备树、文件系统这4个文件的路径,你要改成你的路径。
gdb --args /home/book/mywork/qemu/qemu_git/qemu/bin/qemu-system-arm -M \
mcimx6ul-evk -m 512M -kernel /home/book/nfs_rootfs/zImage \
-dtb /home/book/nfs_rootfs/mynet.dtb -serial stdio \
-drive file=/home/book/mywork/imx6ull_test_image/buildroot_rootfs.img,format=raw,id=mysdcard -device sd-card,drive=mysdcard \
-append "console=ttymxc0,115200 console=tty1 rootfstype=ext4 root=/dev/mmcblk1 rw rootwait init=/sbin/init loglevel=8" \
-nic user
- 关键点在于:gdb --args ,它后面就可以跟一连串的命令及参数了。
- 这种方法有一个缺点,就是客户机上的Linux系统启动后,你无法再在终端中输入gdb命令。可以使用下一种方法来调试。
调试之前就运行的QEMU
- 先进入QEMU源码目录。
- ①首先确定qemu-system-arm进程的PID
- 执行如下命令:
- ①首先确定qemu-system-arm进程的PID
ps -A \| grep qemu
- 假设得到PID为9527
- ②在QEMU源码目录下执行如下命令:
sudo gdb -p 9527
- 注意:一定要加上sudo
调试客户机上的Linux内核
首先运行qemu-system-arm
- 执行QEMU的命令时,在最后加上参数“-s -S”。
- -s是“-gdb tcp::1234”的缩写,表示QEMU将会在本机端口1234上开启gdbserver。
- -S表示不启动CPU,当你使用arm-xxx-gdb连接上QEMU后,必须执行c命令才可以运行内核。
- 我用的命令如下所示。
- 这个命令中涉及qemu-system-arm、zImage、设备树、文件系统这4个文件的路径,你要改成你的路径。
gdb --args /home/book/mywork/qemu/qemu_git/qemu/bin/qemu-system-arm -M \
mcimx6ul-evk -m 512M -kernel /home/book/nfs_rootfs/zImage \
-dtb /home/book/nfs_rootfs/mynet.dtb -serial stdio \
-drive file=/home/book/mywork/imx6ull_test_image/buildroot_rootfs.img,format=raw,id=mysdcard -device sd-card,drive=mysdcard \
-append "console=ttymxc0,115200 console=tty1 rootfstype=ext4 root=/dev/mmcblk1 rw rootwait init=/sbin/init loglevel=8" \
-nic user -s -S