QEMU 建模硬件

news/2025/10/25 12:57:01/文章来源:https://www.cnblogs.com/yexuanyang/p/19165170

场景

建模一个RISCV架构的虚拟板卡G233

G233的datasheet如下
https://gevico.github.io/learning-qemu-docs/ch4/g233-board-datasheet/

前置

[[QEMU 初始化流程]]

看完初始化流程之后再研究怎么建模新硬件会比较清晰。

建模

新文件位置

对硬件建模的.c文件放在hw/riscv下面,同级目录下面有一些可以参考的其他机器的建模,比如sifive_e.c, sifive_u.c, virt.c

新文件需要实现的内容

硬件总线地址划分

首先是一个有关新硬件的总线地址空间的划分,一般使用一个全局变量xxx_memmap表示,这是一个数组,每一个元素存储基址和大小,表示一个地址区域。

比如virt_memmap

static const MemMapEntry virt_memmap[] = {[VIRT_DEBUG] =        {        0x0,         0x100 },[VIRT_MROM] =         {     0x1000,        0xf000 },[VIRT_TEST] =         {   0x100000,        0x1000 },[VIRT_RTC] =          {   0x101000,        0x1000 },[VIRT_CLINT] =        {  0x2000000,       0x10000 },[VIRT_ACLINT_SSWI] =  {  0x2F00000,        0x4000 },[VIRT_PCIE_PIO] =     {  0x3000000,       0x10000 },[VIRT_IOMMU_SYS] =    {  0x3010000,        0x1000 },[VIRT_PLATFORM_BUS] = {  0x4000000,     0x2000000 },[VIRT_PLIC] =         {  0xc000000, VIRT_PLIC_SIZE(VIRT_CPUS_MAX * 2) },[VIRT_APLIC_M] =      {  0xc000000, APLIC_SIZE(VIRT_CPUS_MAX) },[VIRT_APLIC_S] =      {  0xd000000, APLIC_SIZE(VIRT_CPUS_MAX) },[VIRT_UART0] =        { 0x10000000,         0x100 },[VIRT_VIRTIO] =       { 0x10001000,        0x1000 },[VIRT_FW_CFG] =       { 0x10100000,          0x18 },[VIRT_FLASH] =        { 0x20000000,     0x4000000 },[VIRT_IMSIC_M] =      { 0x24000000, VIRT_IMSIC_MAX_SIZE },[VIRT_IMSIC_S] =      { 0x28000000, VIRT_IMSIC_MAX_SIZE },[VIRT_PCIE_ECAM] =    { 0x30000000,    0x10000000 },[VIRT_PCIE_MMIO] =    { 0x40000000,    0x40000000 },[VIRT_DRAM] =         { 0x80000000,           0x0 },
};

类似我们可以根据datasheet定义g233_memmap

static const MemMapEntry g233_memmap[] = {[G233_DEV_MROM] =     {     0x1000,     0x2000 },[G233_DEV_CLINT] =    {  0x2000000,     0xC000 },[G233_DEV_PLIC] =     {  0xc000000,  0x4000000 },[G233_DEV_UART0] =    { 0x10000000,     0x1000 },[G233_DEV_GPIO0] =    { 0x10012000,      0x100 },[G233_DEV_PWM0] =     { 0x10015000,     0x1000 },[G233_DEV_SPI] =      { 0x10018000,       0x14 },[G233_DEV_DRAM] =     { 0x80000000, 0x40000000 },
};

G233_DEV_SPI是扩展外设,没有在datasheet的图片里面显示地址

定义machine和device

想新增一个新的machine,需要注册一个新的machine类型
比如virt_machine添加了一个这样的Type,这个类型的TypeInfo如下

static const TypeInfo virt_machine_typeinfo = {.name       = MACHINE_TYPE_NAME("virt"),.parent     = TYPE_MACHINE,.class_init = virt_machine_class_init,.instance_init = virt_machine_instance_init,.instance_size = sizeof(RISCVVirtState),.interfaces = (const InterfaceInfo[]) {{ TYPE_HOTPLUG_HANDLER },{ }},
};static void virt_machine_init_register_types(void)
{type_register_static(&virt_machine_typeinfo);
}type_init(virt_machine_init_register_types)

系统指定virt开始跑的时候会依次执行class_init,instance_init来初始化这个类型。

但是virt没有创建device类型(不清楚为什么)

sifive_e.c里面创建了device类型和machine类型

device:

static const TypeInfo sifive_e_soc_type_info = {.name = TYPE_RISCV_E_SOC,.parent = TYPE_DEVICE,.instance_size = sizeof(SiFiveESoCState),.instance_init = sifive_e_soc_init,.class_init = sifive_e_soc_class_init,
};static void sifive_e_soc_register_types(void)
{type_register_static(&sifive_e_soc_type_info);
}type_init(sifive_e_soc_register_types)

machine:

static const TypeInfo sifive_e_machine_typeinfo = {.name       = MACHINE_TYPE_NAME("sifive_e"),.parent     = TYPE_MACHINE,.class_init = sifive_e_machine_class_init,.instance_init = sifive_e_machine_instance_init,.instance_size = sizeof(SiFiveEState),
};static void sifive_e_machine_init_register_types(void)
{type_register_static(&sifive_e_machine_typeinfo);
}type_init(sifive_e_machine_init_register_types)

类似,我们给g233创建一个machine和device的TypeInfo,然后实现这个类型

static const TypeInfo g233_soc_type_info = {.name = TYPE_RISCV_G233_SOC,.parent = TYPE_DEVICE,.instance_size = sizeof(G233SoCState),.instance_init = g233_soc_init,.class_init = g233_soc_class_init,
};static void g233_soc_register_types(void)
{type_register_static(&g233_soc_type_info);
}type_init(g233_soc_register_types)...static const TypeInfo g233_machine_typeinfo = {.name       = TYPE_RISCV_G233_MACHINE,.parent     = TYPE_MACHINE,.class_init = g233_machine_class_init,.instance_init = g233_machine_instance_init,.instance_size = sizeof(G233MachineState),
};static void g233_machine_init_register_types(void)
{type_register_static(&g233_machine_typeinfo);
}type_init(g233_machine_init_register_types)

type_register_static做了啥

static TypeImpl *type_register_internal(const TypeInfo *info)
{TypeImpl *ti;if (!type_name_is_valid(info->name)) {fprintf(stderr, "Registering '%s' with illegal type name\n", info->name);abort();}ti = type_new(info);type_table_add(ti);return ti;
}TypeImpl *type_register_static(const TypeInfo *info)
{assert(info->parent);return type_register_internal(info);
}

上面的type_new函数利用TypeInfo里面的信息创建一个TypeImpl

static TypeImpl *type_new(const TypeInfo *info)
{TypeImpl *ti = g_malloc0(sizeof(*ti));int i;g_assert(info->name != NULL);if (type_table_lookup(info->name) != NULL) {fprintf(stderr, "Registering `%s' which already exists\n", info->name);abort();}ti->name = g_strdup(info->name);ti->parent = g_strdup(info->parent);ti->class_size = info->class_size;ti->instance_size = info->instance_size;ti->instance_align = info->instance_align;ti->class_init = info->class_init;ti->class_base_init = info->class_base_init;ti->class_data = info->class_data;ti->instance_init = info->instance_init;ti->instance_post_init = info->instance_post_init;ti->instance_finalize = info->instance_finalize;ti->abstract = info->abstract;for (i = 0; info->interfaces && info->interfaces[i].type; i++) {ti->interfaces[i].typename = g_strdup(info->interfaces[i].type);}ti->num_interfaces = i;return ti;
}

type_table_add(ti)将类型添加到全局的类型哈希表里面

static void type_table_add(TypeImpl *ti)
{assert(!enumerating_types);g_hash_table_insert(type_table_get(), (void *)ti->name, ti);
}

g_hash_table_insert的函数签名

gboolean g_hash_table_insert(GHashTable *hash_table, gpointer key,  
gpointer value)

以ti->name为key,ti为value插入全局哈希表

类型初始化的过程(class_init)

真正初始化的地方是type_initialize

下面是g233_soc_class_init的调用链

(gdb) bt
#0  g233_soc_class_init (oc=0x55555744a3a0, data=0x0) at ../hw/riscv/g233.c:190
#1  0x0000555555c044a4 in type_initialize (ti=0x5555573e2390) at ../qom/object.c:417
#2  0x0000555555c05f6c in object_class_foreach_tramp (key=0x5555573e2510, value=0x5555573e2390, opaque=0x7fffffffcb60) at ../qom/object.c:1110
#3  0x00007ffff6e7fc5b in g_hash_table_foreach (hash_table=0x55555739b920 = {...}, func=0x555555c05f34 <object_class_foreach_tramp>, user_data=0x7fffffffcb60)at ../glib/ghash.c:2128
#4  0x0000555555c06063 in object_class_foreach(fn=0x555555c061ff <object_class_get_list_tramp>, implements_type=0x5555563a6017 "machine", include_abstract=false, opaque=0x7fffffffcbb0)at ../qom/object.c:1132
#5  0x0000555555c06282 in object_class_get_list (implements_type=0x5555563a6017 "machine", include_abstract=false) at ../qom/object.c:1189
#6  0x000055555586ffa9 in select_machine (qdict=0x5555573f0030, errp=0x7fffffffcc00) at ../system/vl.c:1675
#7  0x000055555587155d in qemu_create_machine (qdict=0x5555573f0030) at ../system/vl.c:2187
#8  0x0000555555876406 in qemu_init (argc=4, argv=0x7fffffffcf98) at ../system/vl.c:3759
#9  0x0000555555d5f471 in main (argc=4, argv=0x7fffffffcf98) at ../system/main.c:71

关键调用如下:

qemu_init -> qemu_create_machine -> select_machine -> object_class_foreach_tramp -> type_initialize

感觉是QEMU启动的时候解析-M参数选择对应的machine,之后对这个machine的所有对象做class_init

示例初始化的过程(instance_init)

调用object_initialize_xxx的时候会调用到对象的instance_init`方法

g233_machine_instance_init的调用链:

(gdb) bt
#0  g233_machine_instance_init (obj=0x5555575f2c60) at ../hw/riscv/g233.c:258
#1  0x0000555555c04516 in object_init_with_type (obj=0x5555575f2c60, ti=0x5555573e2550) at ../qom/object.c:428
#2  0x0000555555c04b09 in object_initialize_with_type (obj=0x5555575f2c60, size=4608, type=0x5555573e2550) at ../qom/object.c:570
#3  0x0000555555c05336 in object_new_with_type (type=0x5555573e2550) at ../qom/object.c:774
#4  0x0000555555c0536e in object_new_with_class (klass=0x55555753c8a0) at ../qom/object.c:782
#5  0x0000555555871580 in qemu_create_machine (qdict=0x5555573f0030) at ../system/vl.c:2190
#6  0x0000555555876406 in qemu_init (argc=4, argv=0x7fffffffcf98) at ../system/vl.c:3759
#7  0x0000555555d5f471 in main (argc=4, argv=0x7fffffffcf98) at ../system/main.c:71

关键调用链

qemu_init -> object_init_with_type -> object_initialize_with_type -> g233_machine_instance_init

g233_soc_init的调用链

(gdb) bt
#0  g233_soc_init (obj=0x5555575f2df0) at ../hw/riscv/g233.c:63
#1  0x0000555555c04516 in object_init_with_type (obj=0x5555575f2df0, ti=0x5555573e2390) at ../qom/object.c:428
#2  0x0000555555c04b09 in object_initialize_with_type (obj=0x5555575f2df0, size=4208, type=0x5555573e2390) at ../qom/object.c:570
#3  0x0000555555c04b71 in object_initialize (data=0x5555575f2df0, size=4208, typename=0x5555563fa7f3 "riscv.gevico.g233.soc") at ../qom/object.c:578
#4  0x0000555555c04cf1 in object_initialize_child_with_propsv(parentobj=0x5555575f2c60, propname=0x5555563fa8cf "soc", childobj=0x5555575f2df0, size=4208, type=0x5555563fa7f3 "riscv.gevico.g233.soc", errp=0x555556a43bf8 <error_abort>, vargs=0x7fffffffc9e0) at ../qom/object.c:608
#5  0x0000555555c04c6e in object_initialize_child_with_props(parentobj=0x5555575f2c60, propname=0x5555563fa8cf "soc", childobj=0x5555575f2df0, size=4208, type=0x5555563fa7f3 "riscv.gevico.g233.soc", errp=0x555556a43bf8 <error_abort>) at ../qom/object.c:591
#6  0x0000555555c04dee in object_initialize_child_internal(parent=0x5555575f2c60, propname=0x5555563fa8cf "soc", child=0x5555575f2df0, size=4208, type=0x5555563fa7f3 "riscv.gevico.g233.soc") at ../qom/object.c:645
#7  0x0000555555a74e0b in g233_machine_init (machine=0x5555575f2c60) at ../hw/riscv/g233.c:228
#8  0x000055555563da01 in machine_run_board_init (machine=0x5555575f2c60, mem_path=0x0, errp=0x7fffffffcbe0) at ../hw/core/machine.c:1694
#9  0x0000555555872df0 in qemu_init_board () at ../system/vl.c:2710
#10 0x0000555555873183 in qmp_x_exit_preconfig (errp=0x555556a43c00 <error_fatal>) at ../system/vl.c:2804
#11 0x00005555558765c1 in qemu_init (argc=4, argv=0x7fffffffcf98) at ../system/vl.c:3840
#12 0x0000555555d5f471 in main (argc=4, argv=0x7fffffffcf98) at ../system/main.c:71

关键调用链:

qemu_init -> qemu_init_board -> g233_machine_init -> object_initialize_child_internal -> object_initialize_with_type -> g233_soc_init

调用object_initialize_child宏会调用object_initialize_child_internal,初始化一个子对象并且将这个子对象设置为父对象的一个属性。
object_initialize_child(parent, propname, child, type)
初始化child对象,类型是type,设置这个对象为parent的propname属性

比如

G233SoCState *s = RISCV_G233_SOC(obj);object_initialize_child(obj, "riscv.g233.cpu", &s->cpus, TYPE_RISCV_HART_ARRAY);

这里的TYPE_RISCV_HART_ARRAY就是RISCVHartArrayState类型的标识符

riscv_hart.h里面

#define TYPE_RISCV_HART_ARRAY "riscv.hart_array"OBJECT_DECLARE_SIMPLE_TYPE(RISCVHartArrayState, RISCV_HART_ARRAY)struct RISCVHartArrayState {/*< private >*/SysBusDevice parent_obj;/*< public >*/uint32_t num_harts;uint32_t hartid_base;char *cpu_type;uint64_t resetvec;uint32_t num_rnmi_irqvec;uint64_t *rnmi_irqvec;uint32_t num_rnmi_excpvec;uint64_t *rnmi_excpvec;RISCVCPU *harts;
};

OBJECT_DECLARE_SIMPLE_TYPE宏展开,它定义了一个类型转换的函数

typedef struct RISCVHartArrayState RISCVHartArrayState;
G_DEFINE_AUTOPTR_CLEANUP_FUNC(RISCVHartArrayState, object_unref)
static inline G_GNUC_UNUSEDRISCVHartArrayState *RISCV_HART_ARRAY(const void *obj) {return ((RISCVHartArrayState *)object_dynamic_cast_assert(((Object *)(obj)), ("riscv.hart_array"),"/home/yyx/workspaces/learning-qemu-2025-yexuanyang/include/hw/riscv/""riscv_hart.h",30, __func__));
}

使用RISCV_HART_ARRAY(obj)将对象obj转换成RISCVHartArrayState

riscv_hart.c里面定义了TypeInfo

static const TypeInfo riscv_harts_info = {.name          = TYPE_RISCV_HART_ARRAY,.parent        = TYPE_SYS_BUS_DEVICE,.instance_size = sizeof(RISCVHartArrayState),.class_init    = riscv_harts_class_init,
};static void riscv_harts_register_types(void)
{type_register_static(&riscv_harts_info);
}type_init(riscv_harts_register_types)

object_initialize_with_type

这个函数会初始化子对象的类型,然后实例化这个类型。也就是会先做class_init然后做instance_init.
函数如下:

static void object_initialize_with_type(Object *obj, size_t size, TypeImpl *type)
{type_initialize(type);g_assert(type->instance_size >= sizeof(Object));g_assert(type->abstract == false);g_assert(size >= type->instance_size);memset(obj, 0, type->instance_size);obj->class = type->class;object_ref(obj);object_class_property_init_all(obj);obj->properties = g_hash_table_new_full(g_str_hash, g_str_equal,NULL, object_property_free);object_init_with_type(obj, type);object_post_init_with_type(obj, type);
}

在type_initialize函数里面初始化类型,会调用到class_init。在object_init_with_type函数里面实例化类型,会调用到instance_init。

static void object_init_with_type(Object *obj, TypeImpl *ti)
{if (type_has_parent(ti)) {object_init_with_type(obj, type_get_parent(ti));}if (ti->instance_init) {ti->instance_init(obj);}
}
static void type_initialize(TypeImpl *ti)
{TypeImpl *parent;// 递归初始化父类型以及设置一些属性....if (ti->class_init) {ti->class_init(ti->class, ti->class_data);}
}

machine_init调用过程

g233_machine_init的调用链

(gdb) bt
#0  g233_machine_init (machine=0x5555575f2c60) at ../hw/riscv/g233.c:211
#1  0x000055555563da01 in machine_run_board_init (machine=0x5555575f2c60, mem_path=0x0, errp=0x7fffffffcbe0) at ../hw/core/machine.c:1694
#2  0x0000555555872df0 in qemu_init_board () at ../system/vl.c:2710
#3  0x0000555555873183 in qmp_x_exit_preconfig (errp=0x555556a43c00 <error_fatal>) at ../system/vl.c:2804
#4  0x00005555558765c1 in qemu_init (argc=4, argv=0x7fffffffcf98) at ../system/vl.c:3840
#5  0x0000555555d5f471 in main (argc=4, argv=0x7fffffffcf98) at ../system/main.c:71

关键调用链:

qemu_init -> qemu_init_board -> g233_machine_init

在初始化板卡的时候调用machine_init

realize的调用过程

调用qdev_realize的时候会调用到DeviceState里面定义的realize方法

g233_soc_realize的调用链如下:

(gdb) bt
#0  g233_soc_realize (dev=0x5555575f2df0, errp=0x7fffffffc8f0) at ../hw/riscv/g233.c:75
#1  0x0000555555bff4ca in device_set_realized (obj=0x5555575f2df0, value=true, errp=0x7fffffffca00) at ../hw/core/qdev.c:494
#2  0x0000555555c093b0 in property_set_bool (obj=0x5555575f2df0, v=0x555557601300, name=0x55555641e601 "realized", opaque=0x5555573f4cb0, errp=0x7fffffffca00)at ../qom/object.c:2375
#3  0x0000555555c06e27 in object_property_set (obj=0x5555575f2df0, name=0x55555641e601 "realized", v=0x555557601300, errp=0x7fffffffca00) at ../qom/object.c:1450
#4  0x0000555555c0bff0 in object_property_set_qobject(obj=0x5555575f2df0, name=0x55555641e601 "realized", value=0x5555573f0ea0, errp=0x555556a43c00 <error_fatal>) at ../qom/qom-qobject.c:28
#5  0x0000555555c071da in object_property_set_bool (obj=0x5555575f2df0, name=0x55555641e601 "realized", value=true, errp=0x555556a43c00 <error_fatal>)at ../qom/object.c:1520
#6  0x0000555555bfebc2 in qdev_realize (dev=0x5555575f2df0, bus=0x0, errp=0x555556a43c00 <error_fatal>) at ../hw/core/qdev.c:276
#7  0x0000555555a74e37 in g233_machine_init (machine=0x5555575f2c60) at ../hw/riscv/g233.c:229
#8  0x000055555563da01 in machine_run_board_init (machine=0x5555575f2c60, mem_path=0x0, errp=0x7fffffffcbe0) at ../hw/core/machine.c:1694
#9  0x0000555555872df0 in qemu_init_board () at ../system/vl.c:2710
#10 0x0000555555873183 in qmp_x_exit_preconfig (errp=0x555556a43c00 <error_fatal>) at ../system/vl.c:2804
#11 0x00005555558765c1 in qemu_init (argc=4, argv=0x7fffffffcf98) at ../system/vl.c:3840
#12 0x0000555555d5f471 in main (argc=4, argv=0x7fffffffcf98) at ../system/main.c:71

关键调用链:

qemu_init -> qemu_init_board -> g233_machine_init -> qdev_realize -> g233_soc_realize

其他组件的realize

调用sysbus_realize可以实现以sysbus作为父对象的类型。

sysbus_realize函数

bool sysbus_realize(SysBusDevice *dev, Error **errp)
{return qdev_realize(DEVICE(dev), sysbus_get_default(), errp);
}

实际调用的是API qdev_realize,额外传入了一个bus参数sysbus

对于qdev类的API,查阅 https://www.qemu.org/docs/master/devel/qdev-api.html
里面有对qdev_realize和qdev_realize_and_unref等函数的解释。

建模SPI扩展外设

SPI扩展外设的参数信息: https://gevico.github.io/learning-qemu-docs/ch4/g233-board-datasheet/#spi

我对g233 SPI扩展外设的建模放在Github, https://github.com/gevico/learning-qemu-2025-yexuanyang

建模扩展外设需要定义新的硬件,SPI设备的建模放在hw/ssi目录下。对这些设备的建模是和架构无关的。
对g233建模需要放在hw/riscv下是因为g233启动需要用到QEMU封装的riscv接口,比如riscv_boot_info_init, riscv_load_kernel等等,还有一些设备的创建也需要用到riscv接口,比如riscv_aclint_swi_create, riscv_aclint_mtimer_create。对SPI设备来说不需要这些接口。

定义寄存器

使用一组宏REGxxFIELD来定义寄存器的不同位。

比如定义一个如下规格的SR寄存器

SPI_SR - 状态寄存器 (0x08)
复位值是0x00000002

位域 名称 访问 复位值 描述
31:8 保留 R 0 保留位
7 BSY R 0 忙标志位
0: SPI 空闲
1: SPI 忙
6:4 保留 R 0 保留位
3 OVERRUN R/W 0 溢出错误标志
0: 无溢出
1: 发生溢出(写 1 清除)
2 UNDERRUN R/W 0 下溢错误标志
0: 无下溢
1: 发生下溢(写 1 清除)
1 TXE R 1 发送缓冲区空标志
0: 发送缓冲区满
1: 发送缓冲区空
0 RXNE R 0 接收缓冲区非空标志
0: 接收缓冲区为空
1: 接收缓冲区非空,数据可读

那么可以这样定义每一位

REG32(SR, 0x08)FIELD(SR, RESERVED_FLD1, 8, 24)FIELD(SR, BSY, 7, 1)FIELD(SR, RESERVED_FLD2, 4, 3)FIELD(SR, OVERRUN, 3, 1)FIELD(SR, UNDERRUN, 2, 1)FIELD(SR, TXE, 1, 1)FIELD(SR, RXNE, 0, 1)

FIELD第一个参数是寄存器,第二个参数是名称,第三个参数是shift,表示起始位置,第四个参数是size,表示区域大小。
注意一个寄存器的不同区域名称是不能相同的

类似的可以定义其他寄存器的bitfields

REG32(CR1, 0x00)FIELD(CR1, RESERVED_FLD1, 7, 25)FIELD(CR1, SPE, 6, 1)FIELD(CR1, RESERVED_FLD2, 3, 3)FIELD(CR1, MSTR, 2, 1)FIELD(CR1, RESERVED_FLD3, 0, 2)
REG32(CR2, 0x04)FIELD(CR2, RESERVED_FLD1, 8, 24)FIELD(CR2, TXEIE, 7, 1)FIELD(CR2, RXNEIE, 6, 1)FIELD(CR2, ERRIE, 5, 1)FIELD(CR2, SSOE, 4, 1)FIELD(CR2, RESERVED_FLD2, 0, 4)
REG32(SR, 0x08)FIELD(SR, RESERVED_FLD1, 8, 24)FIELD(SR, BSY, 7, 1)FIELD(SR, RESERVED_FLD2, 4, 3)FIELD(SR, OVERRUN, 3, 1)FIELD(SR, UNDERRUN, 2, 1)FIELD(SR, TXE, 1, 1)FIELD(SR, RXNE, 0, 1)
REG32(DR, 0x0C)FIELD(DR, RESERVED_FLD1, 8, 24)FIELD(DR, DATA, 0, 8)
REG32(CSCTRL, 0x10)FIELD(CSCTRL, RESERVED_FLD2, 8, 24)FIELD(CSCTRL, CS3_ACT, 7, 1)FIELD(CSCTRL, CS2_ACT, 6, 1)FIELD(CSCTRL, CS1_ACT, 5, 1)FIELD(CSCTRL, CS0_ACT, 4, 1)FIELD(CSCTRL, CS3_EN, 3, 1)FIELD(CSCTRL, CS2_EN, 2, 1)FIELD(CSCTRL, CS1_EN, 1, 1)FIELD(CSCTRL, CS0_EN, 0, 1)

REGxx和FIELD宏展开

展开REG32宏

REG32(SR, 0x08)

原型和展开如下:

#define REG32(reg, addr)                                                       \enum { A_##reg = (addr) };                                                   \enum { R_##reg = (addr) / 4 };// Expands to
enum { A_SR = (0x08) };
enum { R_SR = (0x08) / 4 };

R_SR是寄存器的index,A_SR是寄存器的地址偏移

展开FIELD宏

FIELD(SR, BSY, 7, 1)

原型和展开如下:

#define FIELD(reg, field, shift, length)                                       \enum { R_##reg##_##field##_SHIFT = (shift) };                                \enum { R_##reg##_##field##_LENGTH = (length) };                              \enum { R_##reg##_##field##_MASK = MAKE_64BIT_MASK(shift, length) };// Expands to
enum { R_SR_BSY_SHIFT = (7) };
enum { R_SR_BSY_LENGTH = (1) };
enum { R_SR_BSY_MASK = (((~0ULL) >> (64 - (1))) << (7)) };

如果想编辑这个bitfields,使用布尔运算和MASK就可以。

// set
s->regs[R_SR] |= R_SR_BSY_MASK
// unset
s->regs[R_SR] &= ~R_SR_BSY_MASK

对于多位的bitfields也类似
比如DR寄存器的DATA bitfields

REG32(DR, 0x0C)FIELD(DR, RESERVED_FLD1, 8, 24)FIELD(DR, DATA, 0, 8)

这个时候R_DR_DATA_MASK就是0xff

// set
s->regs[R_DR] = value & R_DR_DATA_MASK;
// clear
s->regs[R_DR] &= ~R_DR_DATA_MASK;

定义SPIState对象

SPI传输使用rx和tx,全双工传输。接受一个bytes的同时输出一个bytes。我们仿照其他spi的实现,使用fifo8作为传输缓冲区

下面的代码在g233_spi.h头文件中定义

#define G233_SPI_REG_NUM  (0x10 / 4 + 1)#define TYPE_G233_SPI "g233.spi"
#define G233_SPI(obj) OBJECT_CHECK(G233SPIState, (obj), TYPE_G233_SPI)typedef struct G233SPIState {SysBusDevice parent_obj;MemoryRegion mmio;qemu_irq irq;uint32_t num_cs;qemu_irq *cs_lines;SSIBus *spi;Fifo8 tx_fifo;Fifo8 rx_fifo;uint32_t regs[G233_SPI_REG_NUM];
} G233SPIState;

解释上面的代码:
G233_SPI_REG_NUM是根据SPI扩展外设的规格定义的,每一个寄存器占32位,最后一个寄存器的偏移是0x10,那么一共有(0x10 / 4 + 1)个寄存器,也就是5个。
TYPE_G233_SPI是类型G233SPIState的标识符
G233_SPI(obj)宏用于类型转换,使用G233_SPI(obj)可以将obj转换成G233SPIState对象

前面提到了OBJECT_DECLARE_SIMPLE_TYPE宏也可以定义类型转换函数,这里是另外一种写法,本质最后都会调用到API object_dynamic_cast_assert

OBJECT_CHECK展开如下

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

G233SPIState里面

  • SysBusDevice是父类
  • mmio是用于映射寄存器到虚拟地址的内存区域
  • irq是SPI中断
  • num_cs是片选线数量
  • cs_lines是片选中断线数组
  • spi是SSI总线,可以连接到SSI总线的其他设备比如flash或者sd卡
  • tx_fifo和rx_fifo分别是发送侧和接收侧的缓冲区
  • regs是SPI外设的寄存器列表

定义完这个类型之后需要定义TypeInfo,在g233_spi.c中定义

static const TypeInfo g233_spi_info = {.name           = TYPE_G233_SPI,.parent         = TYPE_SYS_BUS_DEVICE,.instance_size  = sizeof(G233SPIState),.class_init     = g233_spi_class_init,
};static void g233_spi_register_types(void)
{type_register_static(&g233_spi_info);
}type_init(g233_spi_register_types)

然后class_init实现如下:

static const Property g233_spi_properties[] = {DEFINE_PROP_UINT32("num-cs", G233SPIState, num_cs, 4),
};static void g233_spi_class_init(ObjectClass *klass, const void *data)
{DeviceClass *dc = DEVICE_CLASS(klass);device_class_set_props(dc, g233_spi_properties);device_class_set_legacy_reset(dc, g233_spi_reset);dc->realize = g233_spi_realize;
}

设置了spi的属性num-cs是4,然后设置了reset方法和realize方法

spi_reset如下

static void g233_spi_reset(DeviceState *dev)
{G233SPIState *s = G233_SPI(dev);memset(s->regs, 0, sizeof(s->regs));s->regs[R_SR] = 0x02;s->regs[R_DR] = 0x0C;g233_spi_update_cs(s);g233_spi_update_irq(s);g233_spi_txfifo_reset(s);g233_spi_rxfifo_reset(s);
}

这个函数就是复位SPI的所有寄存器,然后更新片选和中断信息,重置tx/rx_fifo缓冲区。这些操作根据datasheet里面对于SPI的寄存器定义来实现。具体实现参考github代码。

spi_realize如下

static void g233_spi_realize(DeviceState *dev, Error **errp)
{SysBusDevice *sbd = SYS_BUS_DEVICE(dev);G233SPIState *s = G233_SPI(dev);int i;s->spi = ssi_create_bus(dev, "spi");sysbus_init_irq(sbd, &s->irq);s->cs_lines = g_new0(qemu_irq, s->num_cs);for (i = 0; i < s->num_cs; i++) {sysbus_init_irq(sbd, &s->cs_lines[i]);}memory_region_init_io(&s->mmio, OBJECT(s), &g233_spi_ops, s,TYPE_G233_SPI, 0x1000);sysbus_init_mmio(sbd, &s->mmio);fifo8_create(&s->tx_fifo, FIFO_CAPACITY);fifo8_create(&s->rx_fifo, FIFO_CAPACITY);
}

这个函数做了这些事:

  • 注册ssi总线,设置为s->spi
  • 注册sysbus中断
  • 创建mmio内存映射,对应读写操作被g233_spi_ops路由
  • 创建rx/tx_fifo缓冲区,大小为FIFO_CAPACITY

这里的g233_spi_ops定义了读写操作实际执行的函数

static const MemoryRegionOps g233_spi_ops = {.read = g233_spi_read,.write = g233_spi_write,.endianness = DEVICE_LITTLE_ENDIAN,.valid = {.min_access_size = 4,.max_access_size = 4}
};

QEMU对MemoryRegion的操作抽象了一个接口MemoryRegionOps,里面的read/write是读写函数的具体实现,然后其他的属性是一些操作的限制或者操作的属性。比如endianness定义大小端,valid定义了访问的最大和最小大小,字节为单位。因为SPI寄存器都是32位,这里设置为4字节。

spi_read和spi_write操作的具体实现参考Github,大概流程就是:

  1. 获取要读写的寄存器偏移
  2. 按照寄存器的属性来读写,除了DR寄存器,其他的寄存器直接读写。DR寄存器将数据段写入缓冲区或者从缓冲区读出
  3. 更新控制寄存器、状态寄存器和中断状态

将新实现的SPI设备添加到构建系统里面

QEMU使用Kconfig来配置构建项

首先在hw/ssi/Kconfig里面增加一条设置

config G233_SPIboolselect SSI

然后在板卡的设置里面将这个G233_SPI默认选上,在hw/riscv/Kconfig添加select G233_SPI

config GEVICO_G233booldefault ydepends on RISCV32 || RISCV64select RISCV_ACLINTselect SIFIVE_PLICselect SIFIVE_GPIOselect SIFIVE_PWMselect PL011
+    select G233_SPI

设置添加完了,还需要将源文件添加到构建系统。在hw/ssi/meson.build里面添加

system_ss.add(when: 'CONFIG_G233_SPI', if_true: files('g233_spi.c'))

这样g233_spi.c就被加入到整个QEMU的构建中了。

在原先g233板卡中添加SPI外设

增加内存段的映射信息

static const MemMapEntry g233_memmap[] = {[G233_DEV_MROM] =     {     0x1000,     0x2000 },[G233_DEV_CLINT] =    {  0x2000000,     0xC000 },[G233_DEV_PLIC] =     {  0xc000000,  0x4000000 },[G233_DEV_UART0] =    { 0x10000000,     0x1000 },[G233_DEV_GPIO0] =    { 0x10012000,      0x100 },[G233_DEV_PWM0] =     { 0x10015000,     0x1000 },// newline[G233_DEV_SPI] =      { 0x10018000,       0x14 },[G233_DEV_DRAM] =     { 0x80000000, 0x40000000 },
};

增加对spi子对象的初始化

static void g233_soc_init(Object *obj)
{/** You can add more devices here(e.g. cpu, gpio)* Attention: The cpu resetvec is 0x1004*/MachineState *ms = MACHINE(qdev_get_machine());G233SoCState *s = RISCV_G233_SOC(obj);object_initialize_child(obj, "riscv.g233.cpu", &s->cpus, TYPE_RISCV_HART_ARRAY);object_property_set_int(OBJECT(&s->cpus), "num-harts", ms->smp.cpus, &error_abort);object_property_set_int(OBJECT(&s->cpus), "resetvec", 0x1004, &error_abort);object_initialize_child(obj, "sifive_soc.gpio", &s->gpio, TYPE_SIFIVE_GPIO);// newlineobject_initialize_child(obj, "g233.spi", &s->spi, TYPE_G233_SPI);
}

在 g233_soc_realize末尾增加对SPI控制器的实现

static void g233_soc_realize(DeviceState *dev, Error **errp)
{.../* G233 SPI Controller */if (!sysbus_realize(SYS_BUS_DEVICE(&s->spi), errp)) {return;}/* Map SPI registers */sysbus_mmio_map(SYS_BUS_DEVICE(&s->spi), 0, memmap[G233_DEV_SPI].base);/* Connect SPI interrupt to PLIC */sysbus_connect_irq(SYS_BUS_DEVICE(&s->spi), 0,qdev_get_gpio_in(DEVICE(s->plic), G233_SPI0_IRQ));
}

添加flash设备

在 g233_soc_realize末尾增加添加flash设备的代码

static void g233_soc_realize(DeviceState *dev, Error **errp)
{...
/* Flash 0: w25x16 on CS0 */flash0_dev = qdev_new("w25x16");qdev_prop_set_uint8(flash0_dev, "cs", 0);  /* Set CS index to 0 */dinfo = drive_get(IF_MTD, 0, 0);if (dinfo) {qdev_prop_set_drive_err(flash0_dev, "drive",blk_by_legacy_dinfo(dinfo),&error_abort);}qdev_realize_and_unref(flash0_dev, BUS(s->spi.spi), &error_fatal);/* Connect CS0 line from SPI controller to Flash 0 */flash0_cs = qdev_get_gpio_in_named(flash0_dev, SSI_GPIO_CS, 0);sysbus_connect_irq(SYS_BUS_DEVICE(&s->spi), 1, flash0_cs);/* Flash 1: w25x32 on CS1 */flash1_dev = qdev_new("w25x32");qdev_prop_set_uint8(flash1_dev, "cs", 1);  /* Set CS index to 1 */dinfo = drive_get(IF_MTD, 0, 1);  /* Use index 1 for second flash */if (dinfo) {qdev_prop_set_drive_err(flash1_dev, "drive",blk_by_legacy_dinfo(dinfo),&error_abort);}qdev_realize_and_unref(flash1_dev, BUS(s->spi.spi), &error_fatal);/* Connect CS1 line from SPI controller to Flash 1 */flash1_cs = qdev_get_gpio_in_named(flash1_dev, SSI_GPIO_CS, 0);sysbus_connect_irq(SYS_BUS_DEVICE(&s->spi), 2, flash1_cs);  /* Use IRQ 2 for CS1 */
}

上面的qdev_new传入的参数是设备的名字,在m25p80.c中有对w25x16设备的定义

static const FlashPartInfo known_devices[] = {.../* Winbond -- w25x "blocks" are 64k, "sectors" are 4KiB */{ INFO("w25x10",      0xef3011,      0,  64 << 10,   2, ER_4K) },{ INFO("w25x20",      0xef3012,      0,  64 << 10,   4, ER_4K) },{ INFO("w25x40",      0xef3013,      0,  64 << 10,   8, ER_4K) },{ INFO("w25x80",      0xef3014,      0,  64 << 10,  16, ER_4K) },{ INFO("w25x16",      0xef3015,      0,  64 << 10,  32, ER_4K) },{ INFO("w25x32",      0xef3016,      0,  64 << 10,  64, ER_4K) },{ INFO("w25q32",      0xef4016,      0,  64 << 10,  64, ER_4K) },...
}

我们只使用w25x16和w25x32

使用qdev_prop_set_uint8将w25x16的片选设置为0,w25x32的片选设置为1。

cs属性定义如下,表示当前使用的SPI片选线下标,对应SSIPeripheral的cs_index。SSIPeripheral的cs表示当前是否被选中。

// hw/ssi/ssi.h
struct SSIPeripheral {DeviceState parent_obj;/* cache the class */SSIPeripheralClass *spc;/* Chip select state */bool cs;/* Chip select index */uint8_t cs_index;
};// hw/ssi/ssi.c
static const Property ssi_peripheral_properties[] = {DEFINE_PROP_UINT8("cs", SSIPeripheral, cs_index, 0),
};

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/946100.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

P14304 【MX-J27-T1】分块

P14304 【MX-J27-T1】分块 题解题目传送门 My Blog 我们发现这个题是 T1,但它的 \(n\) 很大,于是我们可以合理推断这是个结论题或诈骗题。 我们考虑 \([m^2,(m+1)^2)\) 这个区间里有哪些数满足题中所述的条件。显然 …

实现安卓scrollview里的多个按钮实现的每个按钮单选功能

实现安卓scrollview里的多个按钮实现的每个按钮单选功能void initPatterns(LinearLayout PatternsRoot){for(int i=0;i<512;i++){Button patternButton = new Button(PianoRollActivity.this);patternButton.setTex…

ABP - 懒加载 [ILazyServiceProvider、DefaultLazyServiceProvider、LazyServiceProvider]

懒加载 对于 ABP 框架的懒加载机制,核心是围绕 ILazyServiceProvider 接口及其实现类展开的。作为新手,你可以简单理解为:它是 ABP 提供的“延迟获取服务的工具”,能让你在需要时才创建服务实例,而不是一开始就初…

三角函数的2倍角公式

根据 \(\cos(x + y) = \cos x\cos y - \sin x\sin y\) 和 \(\sin(x + y) = \sin x\cos y + \cos x\sin y\)(具体看这里)。 我们可以得到 \(\cos 2x = \cos(x + x) = \cos^2x - \sin^2 y = 1 - 2\sin^2y = 2\cos^2y -…

FFmpeg开发笔记(八十五)基于PyQt和FFmpeg的开源视频剪辑器OpenShot

《FFmpeg开发实战:从零基础到短视频上线》一书的“第 12 章 FFmpeg的移动开发”介绍了如何使用FFmpeg在手机上剪辑视频,方便开发者更好地开发类似剪映那样的视频剪辑软件。那么在桌面系统上还有一款开源视频剪辑框架…

2025年铁氟龙高温线厂家权威推荐榜:极细铁氟龙/UL10064铁氟龙/UL1332铁氟龙/UL1867铁氟龙/UL10064极细铁氟龙/UL1332极细铁氟龙/UL1867极细铁氟龙专业供应商

2025年铁氟龙高温线厂家权威推荐榜:极细铁氟龙/UL10064铁氟龙/UL1332铁氟龙/UL1867铁氟龙/UL10064极细铁氟龙/UL1332极细铁氟龙/UL1867极细铁氟龙专业供应商 随着电子设备向小型化、高性能化方向发展,铁氟龙高温线作…

2025年工业风扇厂家权威推荐榜:直流风扇、显卡散热风扇、一体机风扇、轴流风扇及散热风扇专业选购指南

2025年工业风扇厂家权威推荐榜:直流风扇、显卡散热风扇、一体机风扇、轴流风扇及散热风扇专业选购指南 随着工业4.0和智能制造的深入推进,散热风扇作为关键热管理组件,其技术含量和市场要求正经历显著提升。工业风扇…

2025年除尘设备厂家权威推荐榜:除尘器/脉冲除尘器/中央脉冲除尘器/工业除尘器源头企业综合测评与选购指南

2025年除尘设备厂家权威推荐榜:除尘器/脉冲除尘器/中央脉冲除尘器/工业除尘器源头企业综合测评与选购指南 随着环保政策的持续收紧和工业制造升级的加速推进,除尘设备行业正迎来技术革新的关键时期。作为工业生产环境…

2025年提升机厂家权威推荐榜:自动提升机专业选购指南,高效稳定与安全性能深度解析

2025年提升机厂家权威推荐榜:自动提升机专业选购指南,高效稳定与安全性能深度解析 在工业自动化快速发展的今天,提升机作为物料输送系统的核心设备,其性能直接影响生产效率和运营成本。自动提升机凭借智能化控制、…

2025年冷水机/冷冻机/冰水机厂家权威推荐榜:工业制冷设备实力解析与高效节能口碑之选

2025年冷水机/冷冻机/冰水机厂家权威推荐榜:工业制冷设备实力解析与高效节能口碑之选 工业制冷设备作为现代制造业的重要基础设施,在塑料成型、食品加工、电子制造、医药化工等众多领域发挥着不可替代的作用。随着&q…

2025年服装厂家推荐排行榜:棒球帽,卫衣,羽绒服,春秋季运动休闲服饰源头厂家精选

2025年服装厂家推荐排行榜:棒球帽,卫衣,羽绒服,春秋季运动休闲服饰源头厂家精选 随着运动休闲服饰市场的持续升温,棒球帽、卫衣和羽绒服作为三大核心品类,在消费升级和个性化需求的双重驱动下呈现出蓬勃发展的态…

2025女丘

设 \[f(x)=\frac{x^2-3x+3}{x^2-x+1} \]对于所有正整数 \(n\),求 \(f^{(n)}(x)\)。 设 \(A,B\) 是实对称矩阵,证明:\(tr(ABAB)\le tr(A^2B^2)\),并求出等号成立的充分必要条件。 设 \(a,z,w\) 为复数,其中 \(|a|\…

详细介绍:OpenLayers的OGC服务 -- 章节一:WMS服务详解

pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !important; font-family: "Consolas", "Monaco", "Courier New", …

netcore vue grpc、http grpc

netcore vue grpc、http grpc vue 前端 一、项目准备创建 vue 项目$ cd E:\code# 创建 vue 项目 $ vue create apricot-grpc-> 1、选择 Vue3安装依赖$ npm install grpc-web --save$ npm install google-protobuf -…

Go 的跨平台编译详解 - 指南

Go 的跨平台编译详解 - 指南pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !important; font-family: "Consolas", "Monaco",…

2025年上海久宙集团深度解析:技术护城河与标准话语权的双重验证

引言 本文从“技术领先与专利护城”这一核心维度切入,结合行业标准制定、质量认证体系、下游应用反馈与第三方检测数据,为读者提供一份可验证、可复盘的客观参考。全文不展开宏大叙事,只聚焦“技术—专利—标准”闭…

2025年上海久宙集团:深度解析其技术护城河与行业话语权

引言 本文聚焦“技术领先与标准制定”维度,拆解上海久宙集团如何在分子筛与活性氧化铝赛道构建专利壁垒、主导行业规则,为采购方、投资机构及监管部门提供一份可验证的客观参照。 背景与概况 上海久宙集团成立于2002…

墨尔本迎来第六届PancakesCon网络安全大会

作者Lesley Carhart宣布第六届PancakesCon免费网络安全教育大会将于2025年9月21日在墨尔本举行。该会议通过YouTube直播,不仅提供网络安全教育内容,还展示演讲者的业余爱好,过去五年已成功举办并广受好评。我在墨尔…

爬虫逆向——RPC技术 - 教程

爬虫逆向——RPC技术 - 教程2025-10-25 11:57 tlnshuju 阅读(0) 评论(0) 收藏 举报pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !important;…

完整教程:三维计算机视觉:从2D图像到3D理解的跨越

pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !important; font-family: "Consolas", "Monaco", "Courier New", …