场景
建模一个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设备来说不需要这些接口。
定义寄存器
使用一组宏REGxx和FIELD来定义寄存器的不同位。
比如定义一个如下规格的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,大概流程就是:
- 获取要读写的寄存器偏移
- 按照寄存器的属性来读写,除了DR寄存器,其他的寄存器直接读写。DR寄存器将数据段写入缓冲区或者从缓冲区读出
- 更新控制寄存器、状态寄存器和中断状态
将新实现的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),
};