QEMU ARM64 仿真
东东 Lv4

本文主要介绍如何通过 QEMU 仿真 ARM64 板子

QEMU ARM64A

测试环境 Win10 VMWare: Ubuntu18.04

1. 创建工作目录

1
2
3
mkdir ~/lfs -p
export LFS=~/lfs
mkdir $LFS/src $LFS/rootfs $LFS/dst -p

2. 构建 Kernel

2.1 下载源码
1
2
3
4
cd $LFS/src
wget https://mirrors.tuna.tsinghua.edu.cn/kernel/v5.x/linux-5.18.2.tar.xz
tar -xvf linux-5.18.2.tar.xz
cd linux-5.18.2
2.2 内核配置
1
2
3
4
5
export ARCH=arm64 
export CROSS_COMPILE=aarch64-linux-gnu-

make defconfig
make menuconfig

Linux arm 和 arm64 的 “defconfig” 应该包括 virtio 和 PCI 控制器的正确设备驱动程序;一些较旧的内核版本,尤其是 32 位 Arm,默认情况下并未启用所有功能。如果您没有看到您期望的 PCI 设备,请检查您的配置是否具有:

1
2
3
CONFIG_PCI=y
CONFIG_VIRTIO_PCI=y
CONFIG_PCI_HOST_GENERIC=y

2.3 虚拟机图形支持

图形也可用,但与 x86 不同,没有启用默认显示设备:您应该从 “-device ?” 的显示设备部分选择打开。一个好的选择是使用:

1
-device virtio-gpu-pci
并启用以下 Linux 内核选项(以及前面列出的通用 “virtio PCI for the virt machine” 选项):
1
2
CONFIG_DRM=y 
CONFIG_DRM_VIRTIO_GPU=y

2.4 编译导出内核镜像
1
cp arch/arm64/boot/Image $LFS/dst

3. 制作根文件系统

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
# 工具下载
sudo apt-get install arch-install-scripts binfmt-support qemu-system-arm qemu-user-binfmt qemu-user-static

# 制作镜像
dd if=/dev/zero of=$LFS/dst/rootfs.img bs=1G count=4
mkfs.ext4 $LFS/dst/rootfs.img
sudo mount -o loop $LFS/dst/rootfs.img /mnt

# 解压 Ubuntu base
wget -P $LFS/src -c https://cdimage.ubuntu.com/ubuntu-base/releases/18.04/release/ubuntu-base-18.04.5-base-armhf.tar.gz
sudo tar -pxf $LFS/src/ubuntu-base-18.04.5-base-armhf.tar.gz -C /mnt

# 进入 chroot >>>
sudo arch-chroot /mnt

# 必备软件下载
sed -i 's/ports.ubuntu.com/mirrors.ustc.edu.cn/g' /etc/apt/sources.list
apt-get update
apt-get install systemd vim net-tools dhcpcd5 kmod

# 可选下载 (可在启动后再安装)
apt-get install qt5-defeault libsdl2-dev libsdl-dev libdrm-tests git gcc make openssh-server

# 添加串口服务
ln -s /lib/systemd/system/getty@.service /etc/systemd/system/getty.target.wants/getty@ttyAMA0.service

# 设置默认登录用户为 root 且免密登录
vim /lib/systemd/system/getty@.service
# 替换该行 ExecStart=-/sbin/agetty --autologin root --noclear %I $TERM

vim /etc/hosts
# 127.0.0.1 localhost

# 离开 chroot <<<
sudo umount /mnt

4. 启动参数

基本启动参数

1
2
3
4
5
6
7
8
9
10
11
qemu-system-aarch64         \
-M virt \
-cpu cortex-a53 \
-smp 2 \
-m 4096M \
-kernel $LFS/dst/Image \
-nographic \
-net nic -net user,hostfwd=tcp::10021-:22 \
-append "root=/dev/vda rw rootfstype=ext4 console=ttyAMA0 ignore_loglevel" \
-drive if=none,file=$LFS/dst/rootfs.img,id=hd0 \
-device virtio-blk-device,drive=hd0

可选参数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 需要连接网络或通过 ssh 远程操作时
-net nic -net user,hostfwd=tcp::10021-:22

# 启动图形时, 将当前终端作为模拟串口
-serial stdio

-usb
-device nec-usb-xhci
-device usb-host,hostbus=2,hostaddr=1
-device usb-mouse
-device usb-kbd
-show-cursor
-fsdev local,security_model=passthrough,id=fsdev0,path=/nfsroot
-device virtio-9p-pci,id=fs0,fsdev=fsdev0,mount_tag=hostshare

5. 远程登录

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# 确保 QEMU 启动时带如下参数, 并且 root 设置了密码 passwd
-net nic -net user,hostfwd=tcp::10021-:22

# 安装 openssh-server 和配置 sshd
apt-get install openssh-server

# 将 PermitRootLogin 改成 yes
vim /etc/ssh/sshd_config

# 然后重启 sshd, 如果出错使用 sshd -T 查看原因
service sshd restart

# 远程登录
ssh root@localhost -p 10021

# 上传文件
scp -P 10021 xxx root@localhost:/root

# 免密登录 cat ~/.ssh/id_rsa.pub
ssh-keygen
vim authorized_keys

6. 本机模块编译

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# 要在本机编译模块, 需拷贝整个源码 (此方法待商榷)
sudo mount -o loop $LFS/dst/rootfs.img /mnt
cd $LFS/src/linux-5.18.2
make modules
make modules_install INSTALL_MOD_PATH=./outlib
make headers_install INSTALL_HDR_PATH=./outheader
sudo cp -a outlib/lib/modules /mnt/lib
sudo cp -a outheader/include /mnt/usr/include
make clean
sudo cp -r $LFS/src/linux-5.18.2 /mnt/usr/src
# >>> 在新的 host 中重新生成脚本
ln -s /usr/src/linux-5.18.2 /lib/modules/`uname -r`/build
apt-get install flex bison bc
make scripts
# 还是需要 make 一下的, 要生成一些 hostcc 的东西, scripts 执行生成不全. 执行到内核源码编译时就可以直接停止掉了
make

# 可以直接从源代码中执行, 但是需要从之前编译中获取 .config 和 Module.symvers 放在源码目录中
# 参考 https://unix.stackexchange.com/questions/270123/how-to-create-usr-src-linux-headers-version-files
# 然后执行
make modules_prepare

7. 测试图形

1. 测试 libdrm
1
2
apt-get install libdrm-tests
modetest -M virtio_gpu -s 36@35:640x480
2. 测试 frambuffer

fbtest.c

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
#include <stdio.h>
#include <sys/ioctl.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdint.h>
#include <errno.h>
#include <string.h>
#include <linux/fb.h>
#include <sys/mman.h>

int main(int argc, char const *argv[])
{
int i, j, fd, var;

struct fb_var_screeninfo vinfo;
struct fb_fix_screeninfo finfo;

if (argc < 2)
{
fprintf(stderr, "Example: %s /dev/fb0\n", argv[0]);
return -1;
}

fd = open(argv[1], O_RDWR);
if (fd < 0)
{
fprintf(stderr, "Can't open file %s: %s\n", argv[1], strerror(errno));
return -1;
}

/* Get variable screen information */
if (ioctl(fd, FBIOGET_VSCREENINFO, &vinfo))
{
perror("Can't get FBIOGET_VSCREENINFO");
return -1;
}

/* Get fixed screen information */
if (ioctl(fd, FBIOGET_FSCREENINFO, &finfo))
{
perror("Can't get FBIOGET_VSCREENINFO");
return -1;
}

/* show these information*/
printf("vinfo.xres = %d\n", vinfo.xres);
printf("vinfo.yres = %d\n", vinfo.yres);
printf("vinfo.bits_per_bits = %d\n", vinfo.bits_per_pixel);
printf("vinfo.xoffset = %d\n", vinfo.xoffset);
printf("vinfo.yoffset = %d\n", vinfo.yoffset);
printf("finfo.line_length = %d\n", finfo.line_length);

/* Figure out the size of the screen in bytes */
int screensize = vinfo.xres * vinfo.yres * vinfo.bits_per_pixel / 8;

/* Map the device to memory */
char *fbp = mmap(0, screensize, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
if (fbp == MAP_FAILED)
{
perror("mmap error");
return -1;
}

memset(fbp, 0xff, screensize);

/* Where we are going to put the pixel */
for (int x = 0; x < vinfo.xres; x++)
{
for (int y = 0; y < vinfo.yres; y++)
{
int location = (x + vinfo.xoffset) * (vinfo.bits_per_pixel / 8) +
(y + vinfo.yoffset) * finfo.line_length;

*(fbp + location) = 0xff; /* blue */
*(fbp + location + 1) = 0x00;
}
}

/* release the memory */
munmap(fbp, screensize);
close(fd);
printf("all ok\n");

return 0;
}

编译运行

1
2
3
apt-get install gcc
gcc fbtest.c
./a.out
3. 测试 QT (基于 frambuffer)

main.cpp

1
2
3
4
5
6
7
8
9
10
11
12
#include <QApplication>
#include <QLabel>

int main(int argc, char *argv[])
{
QApplication a(argc, argv);

QLabel *label = new QLabel("Hello, world!");
label->show();

return a.exec();
}

demo.pro

1
2
3
QT += core gui widgets
TARGET = qtdemo
SOURCES += main.cpp

编译运行

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
apt-get install qt5-default g++ make
qmake && make -j8
export QT_QPA_PLATFORM=linuxfb:tty=/dev/fb0
export QT_QPA_GENERIC_PLUGINS=evdevmouse:/dev/input/event2,evdevkeyboard:/dev/input/event1
./qtdemo

# 更多 QT 选项
export QTDIR=/usr/lib/qt5
export QT_QPA_PLATFORM_PLUGIN_PATH=$QTDIR/plugins
export QT_QPA_PLATFORM=eglfs:fb=/dev/fb0
# 触摸设备支持 evdevtouch 或 tslib
# evdevtouch:/dev/input/event1
# tslib:/dev/input/ts0
export QT_QPAFONTDIR=/usr/lib/fonts
export QML2_IMPORT_PATH=$QTDIR/qml

参考文献

  • https://wiki.qemu.org/Documentation/Platforms/ARM
  • https://www.cnblogs.com/pengdonglin137/p/6431234.html

源文件来自于

 评论