在WIN从零开始在QMUE上添加一块自己的开发板(一)

在WIN从零开始在QMUE上添加一块自己的开发板(一)笔者这篇博客作为平时学习时的笔记记录 如有不对还望指正 本博客大量借鉴资料 笔者只是拾人牙慧的小屁孩

大家好,欢迎来到IT知识分享网。

一、前言

二、源码编译

笔者是在Win系统上利用Msys2进行的QUME源码编译。

(一)安装Msys2

打开 https://www.msys2.org/ ,下载最新Msys2的安装包并安装。

MSYS2的安装
完成安装后,我们先进行更新源。
(笔者的安装路径为:C:\msys64
进入目录C:\msys64\etc\pacman.d


  • 在文件mirrorlist.msys的前面插入
    Server = http://mirrors.ustc.edu.cn/msys2/msys/$arch
  • 在文件mirrorlist.mingw32的前面插入
    Server = http://mirrors.ustc.edu.cn/msys2/mingw/i686
  • 在文件mirrorlist.mingw64的前面插入
    Server = http://mirrors.ustc.edu.cn/msys2/mingw/x86_64

然后我们启动 MSYS2 终端(MSYS2 MINGW64),进行更新:

pacman -Syu pacman -Su 

(二)配置GCC工具链

pacman -Sy mingw-w64-x86_64-toolchain 

(三)安装QEMU构建依赖

pacman -Sy mingw-w64-x86_64-meson mingw-w64-x86_64-ninja \ mingw-w64-x86_64-python \ mingw-w64-x86_64-python-sphinx \ mingw-w64-x86_64-python-sphinx_rtd_theme \ mingw-w64-x86_64-autotools \ mingw-w64-x86_64-tools-git \ mingw-w64-x86_64-cc \ mingw-w64-x86_64-angleproject \ mingw-w64-x86_64-capstone \ mingw-w64-x86_64-curl \ mingw-w64-x86_64-cyrus-sasl \ mingw-w64-x86_64-expat \ mingw-w64-x86_64-fontconfig \ mingw-w64-x86_64-freetype \ mingw-w64-x86_64-fribidi \ mingw-w64-x86_64-gcc-libs \ mingw-w64-x86_64-gdk-pixbuf2 \ mingw-w64-x86_64-gettext \ mingw-w64-x86_64-glib2 \ mingw-w64-x86_64-gmp \ mingw-w64-x86_64-gnutls \ mingw-w64-x86_64-graphite2 \ mingw-w64-x86_64-gst-plugins-base \ mingw-w64-x86_64-gstreamer \ mingw-w64-x86_64-gtk3 \ mingw-w64-x86_64-harfbuzz \ mingw-w64-x86_64-jbigkit \ mingw-w64-x86_64-lerc \ mingw-w64-x86_64-libc++ \ mingw-w64-x86_64-libdatrie \ mingw-w64-x86_64-libdeflate \ mingw-w64-x86_64-libepoxy \ mingw-w64-x86_64-libffi \ mingw-w64-x86_64-libiconv \ mingw-w64-x86_64-libidn2 \ mingw-w64-x86_64-libjpeg-turbo \ mingw-w64-x86_64-libnfs \ mingw-w64-x86_64-libpng \ mingw-w64-x86_64-libpsl \ mingw-w64-x86_64-libslirp \ mingw-w64-x86_64-libssh \ mingw-w64-x86_64-libssh2 \ mingw-w64-x86_64-libtasn1 \ mingw-w64-x86_64-libthai \ mingw-w64-x86_64-libtiff \ mingw-w64-x86_64-libunistring \ mingw-w64-x86_64-libunwind \ mingw-w64-x86_64-libusb \ mingw-w64-x86_64-libwebp \ mingw-w64-x86_64-libwinpthread-git \ mingw-w64-x86_64-lz4 \ mingw-w64-x86_64-lzo2 \ mingw-w64-x86_64-nettle \ mingw-w64-x86_64-openssl \ mingw-w64-x86_64-opus \ mingw-w64-x86_64-orc \ mingw-w64-x86_64-p11-kit \ mingw-w64-x86_64-pango \ mingw-w64-x86_64-pixman \ mingw-w64-x86_64-SDL2 \ mingw-w64-x86_64-SDL2_image \ mingw-w64-x86_64-snappy \ mingw-w64-x86_64-spice \ mingw-w64-x86_64-usbredir \ mingw-w64-x86_64-xz \ mingw-w64-x86_64-zlib \ mingw-w64-x86_64-zstd 

(四)下载编译QEMU源码

mkdir qemu cd qemu/ 

下载QUME的版本为8.2.0
QEMU源码

wget https://download.qemu.org/qemu-8.2.0.tar.xz tar xvJf qemu-8.2.0.tar.xz cd qemu-8.2.0/ ./configure make -j8 

编译完成后会生成一个./build目录

 cd build/ make install 

之后我们测试一下——查看QEMU的版本号:

Whisky@LAPTOP-ILRB6MKK MINGW64 ~/qemu/qemu-8.2.0/build $ ./qemu-img -V qemu-img version 8.2.0 Copyright (c) 2003-2023 Fabrice Bellard and the QEMU Project developers 

二、QUME编程基础

(一)QOM机制

QOM——The QEMU Object Model
QEMU提供了一套面向对象编程的模型——QOM,即QEMU Object Module,几乎所有的设备如CPU、内存、总线等都是利用这一面向对象的模型来实现的。
QEMU对象模型提供了一个注册用户可创建类型并从这些类型实例化对象的框架。
其实也就是一种OOP IN C(C上实现面对对象)。
一段面对对象的程序代码(C++语言)



class MyClass { 
    public: int a; void set_A(int a); } 

切换为C语言也就是:

struct MyClass { 
    int a; void (*set_A)(MyClass *this, int a); } 
  1. TypeInfo 注册 TypeImpl
  2. 实例化 ObjectClass
  3. 实例化 Object
  4. 添加 Property

QOM模型的实现代码位于qom/文件夹下的文件中,这涉及了几个结构TypeImpl, ObjectClass, ObjectTypeInfo。看了下它们的定义都在/include/qom/object.h可以找到,只有TypeImpl的具体结构是在/qom/object.c中。

ObjectClass: 是所有类对象的基类,第一个成员变量为类型typedef struct TypeImpl *type
Object: 是所有对象的 基类Base Object , 第一个成员变量为指向 ObjectClass类型的指针。
TypeInfo:是用户用来定义一个 Type 的工具型的数据结构。
TypeImpl:对数据类型的抽象数据结构,TypeInfo的属性与TypeImpl的属性对应。


(二)将 TypeInfo 注册 TypeImpl

struct TypeInfo { 
    const char *name; const char *parent; size_t instance_size; void (*instance_init)(Object *obj); void (*instance_post_init)(Object *obj); void (*instance_finalize)(Object *obj); bool abstract; size_t class_size; void (*class_init)(ObjectClass *klass, void *data); void (*class_base_init)(ObjectClass *klass, void *data); void *class_data; InterfaceInfo *interfaces; }; 

其中的重点有:

  1. Name :包含了自己的名字name和parent的名字的parent
  2. Class(针对ObjectClass) : ObjectClass的信息包括,class_sizeclass_data,class相关函数:class_base_initclass_initclass_finalize等。
    这些函数都是为了初始化,释放结构体ObjectClass。
  3. Instance(针对的是Object): 对象Object信息包括:instance_size,instance相关函数:instance_post_initinstance_initinstance_finalize
    这些函数都是为了初始化,释放结构体Object。
  4. 其他信息:abstract是否为抽象。interface数组。

一般是定义一个TypeInfo,然后调用 type_register(TypeInfo) 或者 type_register_static(TypeInfo) 函数(使用type_register_static比较多),就会生成相应的TypeImpl实例,将这个TypeInfo注册到全局的TypeImpl的hash表中。
我们来看一个例程:

#define TYPE_MY_DEVICE "my-device" static void my_device_class_init(ObjectClass *oc, void *data) { 
    } static void my_device_init(Object *obj) { 
    } typedef struct MyDeviceClass { 
    DeviceClass parent; void (*init) (MyDevice *obj); } MyDeviceClass; typedef struct MyDevice { 
    DeviceState parent; int reg0, reg1, reg2; }MyDevice; static const TypeInfo my_device_info = { 
    .name = TYPE_MY_DEVICE, .parent = TYPE_DEVICE, .instance_size = sizeof(MyDevice), .instance_init = my_device_init, .class_size = sizeof(MyDeviceClass), .class_init = my_device_class_init, }; static void my_device_register_types(void) { 
    type_register_static(&my_device_info); } type_init(my_device_register_types) 

当然,其中的代码

static void my_device_register_types(void) { 
    type_register_static(&my_device_info); } type_init(my_device_register_types) 

也可以简化为

DEFINE_TYPES(my_device_infos) 

举个实际的例子

  1. 定义设备
/* SOC state定义 */ #define TYPE_NUCLEI_HBIRD_SOC "riscv.nuclei.hbird.soc" #define RISCV_NUCLEI_HBIRD_SOC(obj) \ OBJECT_CHECK(NucleiHBSoCState, (obj), TYPE_NUCLEI_HBIRD_SO C) typedef struct NucleiHBSoCState { 
    /*< private >*/ SysBusDevice parent_obj; /*< public >*/ } NucleiHBSoCState; /* Machine state定义 */ #define TYPE_HBIRD_FPGA_MACHINE MACHINE_TYPE_NAME("hbird_fpga") #define HBIRD_FPGA_MACHINE(obj) \ OBJECT_CHECK(NucleiHBState, (obj), TYPE_HBIRD_FPGA_MACHINE) typedef struct { 
    /*< private >*/ SysBusDevice parent_obj; /*< public >*/ NucleiHBSoCState soc; } NucleiHBState; 
  1. SOC设备注册
static void nuclei_soc_init(Object *obj) { 
    qemu_log(">>nuclei_soc_init \n"); } static void nuclei_soc_realize(DeviceState *dev, Error **errp) { 
    qemu_log(">>nuclei_soc_realize \n"); } static void nuclei_soc_class_init(ObjectClass *oc, void *data) { 
    qemu_log(">>nuclei_soc_class_init \n"); DeviceClass *dc = DEVICE_CLASS(oc); dc->realize = nuclei_soc_realize; dc->user_creatable = false; } static const TypeInfo nuclei_soc_type_info = { 
    .name = TYPE_NUCLEI_HBIRD_SOC, .parent = TYPE_DEVICE, .instance_size = sizeof(NucleiHBSoCState), .instance_init = nuclei_soc_init, .class_init = nuclei_soc_class_init, }; static void nuclei_soc_register_types(void) { 
    type_register_static(&nuclei_soc_type_info); } type_init(nuclei_soc_register_types) 

可以看见我们是在nuclei_soc_class_init设定了实例的成员函数实现nuclei_soc_realize
这里是需要理清的关系。

  1. Machine设备注册
static void nuclei_board_init(MachineState *machine) { 
    NucleiHBState *s = HBIRD_FPGA_MACHINE(machine); qemu_log(">>nuclei_board_init \n"); /* Initialize SOC */ object_initialize_child(OBJECT(machine), "soc", &s->soc, TYPE_NUCLEI_HBIRD_SOC); qdev_realize(DEVICE(&s->soc), NULL, &error_abort); } static void nuclei_machine_instance_init(Object *obj) { 
    qemu_log(">>nuclei_machine_instance_init \n"); } static void nuclei_machine_class_init(ObjectClass *oc, void *data) { 
    qemu_log(">>nuclei_machine_class_init \n"); MachineClass *mc = MACHINE_CLASS(oc); mc->desc = "Nuclei HummingBird Evaluation Kit"; mc->init = nuclei_board_init; } static const TypeInfo nuclei_machine_typeinfo = { 
    .name = MACHINE_TYPE_NAME("hbird_fpga"), .parent = TYPE_MACHINE, .class_init = nuclei_machine_class_init, .instance_init = nuclei_machine_instance_init, .instance_size = sizeof(NucleiHBState), }; static void nuclei_machine_init_register_types(void) { 
    type_register_static(&nuclei_machine_typeinfo); } type_init(nuclei_machine_init_register_types) 
  1. 修改编译文件

hw/riscv/Kconfig:

config NUCLEI_N bool select MSI_NONBROKEN select UNIMP 

hw/riscv/meson.build:

riscv_ss = ss.source_set() riscv_ss.add(files('boot.c'), fdt) riscv_ss.add(files('numa.c')) riscv_ss.add(files('riscv_hart.c')) ... riscv_ss.add(when: 'CONFIG_NUCLEI_N', if_true: files('nuclei_n.c')) hw_arch += { 
   'riscv': riscv_ss} 

configs\devices\riscv32-softmmu\default.mak:

... CONFIG_NUCLEI_N=y 

编译参数:

./configure --target-list=riscv32-softmmu make -j16 

(三)测试

编译完成后,我们进行安装(Msys2在管理员权限下运行)

make install 

当然,为了方便我们测试,也可以编写脚本,然后不混用build文件夹,保证我们自己平时也能使用qume纯净版:

build.sh:

# 获取当前脚本文件所在的目录 SHELL_FOLDER=$(cd "$(dirname "$0")";pwd) if [ ! -d "$SHELL_FOLDER/output/qemu" ]; then ./configure --prefix=$SHELL_FOLDER/output/qemu --target-list=riscv32-softmmu fi make -j8 make install cd .. 

run.sh:

SHELL_FOLDER=$(cd "$(dirname "$0")";pwd) $SHELL_FOLDER/output/qemu/qemu-system-riscv32.exe \ -M hbird_fpga 
./qemu-system-riscv32.exe -M ? 

得到的板子列表中有我们刚刚编写的板子:

Supported machines are: hbird_fpga Nuclei HummingBird Evaluation Kit none empty machine opentitan RISC-V Board compatible with OpenTitan sifive_e RISC-V Board compatible with SiFive E SDK sifive_u RISC-V Board compatible with SiFive U SDK spike RISC-V Spike board (default) virt RISC-V VirtIO board 

我们直接运行这块板子:

./qemu-system-riscv32.exe -M hbird_fpga 
>>nuclei_soc_class_init >>nuclei_machine_class_init >>nuclei_machine_instance_init >>nuclei_board_init >>nuclei_soc_init >>nuclei_soc_realize 

测试结果

(四)从结果中的反思

ObjectClass的初始化
在测试结果中,我们还可以回味整个QUME的运行流程。
首先在我们注册TypeInfo时,其类的构造函数会在其创建其类的时候执行,也就是在TypeImpl的hash表已经有了之后,下一步要初始化每个type的时候。(这一步可以看成是class的初始化,可以理解成每一个type对应了一个class,接下来会初始化class)
main函数中的module_call_init(MODULE_INIT_QOM);调用了MODULE_INIT_QOM类型的ModuleTypeList中的所有ModuleEntry中的init()函数,也就是第一步type_init的第一个参数XXX_register_types函数指针。(__attribute__((constructor))的修饰让type_initmain之前执行,type_init的参数是XXX_register_types函数指针,将函数指针传递到ModuleEntryinit函数指针,最后就是将这个ModuleEntry插入到ModuleTypeList)那接下来就是XXX_register_types函数的操作了,就是一个个创建完TypeImpl的哈希表。


如果这里有看不懂,可以深究QEMU 的一些基础知识及QOM(Qemu Object Model)的部分相关源码阅读。

之后main函数会调用machine_class = select_machine();在里面的调用链中将会有ti->class_init初始化的实现。
所以,会首先看见

>>nuclei_soc_class_init >>nuclei_machine_class_init 

实例化 Instance(Object)
其次,我们发现main函数接下来调用了qemu_opts_foreach,循环查找参数(options):

qemu_opts_foreach(qemu_find_opts("device"), default_driver_check, NULL, NULL); qemu_opts_foreach(qemu_find_opts("device"), device_help_func, NULL, NULL) ... qemu_opts_foreach(qemu_find_opts("device"), device_init_func, NULL, &error_fatal); 

前二者default_driver_checkdevice_help_func参数的qemu_opts_foreach输出driver的help信息,还有那些option什么的。
重点在device_init_func参数的qemu_opts_foreach,在其中调用了qdev_device_add。而在qdev_device_add里面,重要的一行是调用了dev = DEVICE(object_new(driver));,而且上一行有个注释——/* create device */
DEVICE是一个宏,实际是OBJECT_CHECK,主要是是看看obj是否是TYPE_DEVICE的一个实例:

#define DEVICE(obj) OBJECT_CHECK(DeviceState, (obj), TYPE_DEVICE) #define OBJECT_CHECK(type, obj, name) \ ((type *)object_dynamic_cast_assert(OBJECT(obj), (name), \ __FILE__, __LINE__, __func__)) 

更重要的是object_new(driver),它利用object_new_with_type进行实例:
它调用type_initialize,在其中调用parentclass_base_init进行初始化,最后调用自己class_init进行初始化。
其次调用object_init_with_type函数首先判断ti是否有parent(即type->parent != NULL),有parent就会递归调用object_init_with_type,最终就是调用ti->instance_init函数。

所以,再接着是

>>nuclei_machine_instance_init 

之后又因为我们在nuclei_machine_class_init中赋值mc->init = nuclei_board_init;,所以执行ti->instance_init

>>nuclei_board_init 

当然我们知道,在nuclei_board_init里面,我们进行了SOC的实例化:

 object_initialize_child(OBJECT(machine), "soc", &s->soc, TYPE_NUCLEI_HBIRD_SOC); qdev_realize(DEVICE(&s->soc), NULL, &error_abort); 

所以最后:

>>nuclei_soc_init >>nuclei_soc_realize 

参考资料

  1. 如何在 Windows 10/11 上构建 QEMU
  2. 在Windows上编译QEMU
  3. 从源码构建Qemu
  4. [完结]从零开始的RISC-V模拟器开发·第一季·2021春季
  5. QEMU 的一些基础知识及QOM(Qemu Object Model)的部分相关源码阅读

免责声明:本站所有文章内容,图片,视频等均是来源于用户投稿和互联网及文摘转载整编而成,不代表本站观点,不承担相关法律责任。其著作权各归其原作者或其出版社所有。如发现本站有涉嫌抄袭侵权/违法违规的内容,侵犯到您的权益,请在线联系站长,一经查实,本站将立刻删除。 本文来自网络,若有侵权,请联系删除,如若转载,请注明出处:https://haidsoft.com/115485.html

(0)
上一篇 2025-12-02 22:33
下一篇 2025-05-24 19:45

相关推荐

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

关注微信