QEMU添加设备相关-中断控制器、串口、pflash支持

嵌入式系统添加串口、中断与pflash支持

QEMU添加设备相关-中断控制器、串口、pflash支持

一、添加串口支持:

真实板卡的初期调试中,串口打印可谓神器,几乎所有的SOC都会带有串口这个外设,因此我们也需要添加串口到我们的定义中。

  • 首先添加几个串口的基地址,添加多个串口的目的是为了后面在读个权限空间内跑不同的系统因此需要不同的权限的串口打印输出。

    [SIFIVE_U_UART0] = { 0x10013000, 0x1000 },

    [SIFIVE_U_UART1] = { 0x10023000, 0x1000 },

  • 在初始化函数中调用serial_mm_init创建串口实例,这个串口仿真ns16550a的定义,在后续opensbi、u-boot、kernel中有这个串口的驱动,方面移植。在sifive_u模拟板中,我们使用的是sifive_uart_create函数来实现uart实例的实现,它的仿真驱动文件位于同级的sifive_uart.c中。

serial_mm_init(system_memory,
memmap[QUARD_STAR_UART0].base, 0, qdev_get_gpio_in(DEVICE(mmio_plic),
QUARD_STAR_UART0_IRQ), 399193, serial_hd(0),
DEVICE_LITTLE_ENDIAN); serial_mm_init(system_memory,
memmap[QUARD_STAR_UART1].base, 0, qdev_get_gpio_in(DEVICE(mmio_plic),
QUARD_STAR_UART1_IRQ), 399193, serial_hd(1),
DEVICE_LITTLE_ENDIAN);

其中,qdev_get_gpio_in函数用来配置串口中断信号,必须要添加中断控制器相关的函数才能正确使用串口。

二、添加中断控制器:

外设是需要中断器的,所以说中断控制器是必不可少的。

RISCV标准的中断控制器分为两部分内核中断CLINT(Core
Local Interrupt)和外设中断控制器Platform-Level Interrupt
Controller(PLIC),CLINT在每一个smp架构下每个core都各有自己私有的中断,PLIC则是所以core共用的外部中断控制器。

[SIFIVE_U_CLINT] =    {  0x2000000,    0x10000 },

[SIFIVE_U_PLIC] =     {  0xc000000,  0x4000000 },

在初始化函数内,分别使用下面的两个函数创建相关的ip,注意要根据内核中smp个数为每个core创建对应的资源。

sifive_clint_create

sifive_plic_create

添加上述ip后,就有了中断控制器和串口,就可以进行简单的串口功能测试,现在需要固件的载体使其工作,这里使用到qemu中对于flash的仿真设备,也就是pflash。

三、添加pflash支持

pflash是并行flash,目前真实的器件已经很少见了,市面上常见的是qspi的nor flash作为低阶固件的载体,而nor flash一般都支持XIP运行,那么基本上等同于pflash了,那么为了方便我们就使用qemu提供的plash作为早期固件的载体。

依然是增加pflash的基地址,这里不是寄存器,而是pflash的数据起始地址,大小是32M,完全足够我们存放固件了。

{

[SIFIVE_U_FLASH] = {

0x20000000, 0x2000000 },

}

创建pflash器件并映射到我们的系统总线对应地址上,最后将其关联到“-drive if=pflash,bus=0,unit=0,…………”的qemu参数上,这样启动仿真时就可以加载固件文件到这片flash上,以下这些代码都不难理解,这里不在赘述

static PFlashCFI01 *sfive_u_flash_create(RISCVVirtState *s,

                                   const

char *name,

                                   const

char *alias_prop_name)

{

DeviceState *dev =

qdev_new(TYPE_PFLASH_CFI01);

qdev_prop_set_uint64(dev,

“sector-length”, QUARD_STAR_FLASH_SECTOR_SIZE);

qdev_prop_set_uint8(dev, "width",

4);

qdev_prop_set_uint8(dev,

“device-width”, 2);

qdev_prop_set_bit(dev,

“big-endian”, false);

qdev_prop_set_uint16(dev, "id0",

0x89);

qdev_prop_set_uint16(dev, "id1",

0x18);

qdev_prop_set_uint16(dev, "id2",

0x00);

qdev_prop_set_uint16(dev, "id3",

0x00);

qdev_prop_set_string(dev, "name",

name);

object_property_add_child(OBJECT(s), name,

OBJECT(dev));

object_property_add_alias(OBJECT(s),

alias_prop_name,

                          OBJECT(dev),

“drive”);

return PFLASH_CFI01(dev);

}

static
void sfive_u_flash_map(PFlashCFI01 *flash,

                        hwaddr base, hwaddr

size,

                        MemoryRegion

*sysmem)

{

DeviceState *dev = DEVICE(flash);

assert(QEMU_IS_ALIGNED(size,

QUARD_STAR_FLASH_SECTOR_SIZE));

assert(size / QUARD_STAR_FLASH_SECTOR_SIZE

<= UINT32_MAX);

qdev_prop_set_uint32(dev,

“num-blocks”, size / QUARD_STAR_FLASH_SECTOR_SIZE);

sysbus_realize_and_unref(SYS_BUS_DEVICE(dev), &error_fatal);

memory_region_add_subregion(sysmem, base,

                            sysbus_mmio_get_region(SYS_BUS_DEVICE(dev),

                                                   0));

}

s->flash = sfive_u_flash_create(s, “sifive_u.flash0”, “pflash0”);

pflash_cfi01_legacy_drive(s->flash,
drive_get(IF_PFLASH, 0, 0));

sifiv_u_flash_map(s->flash, virt_memmap[SIFIVE_U_FLASH].base,

                    virt_memmap[SIFIVE_U_FLASH].size, system_memory);
在 ESP-IDF 中,使用 `idf.py qemu --qemu-extra-args` 设置多串口时,需通过 QEMU 的 `-serial` 参数指定多个串口设备,并确保 ESP32 代码正确初始化对应的 UART 端口。以下是详细步骤和示例: --- ### **1. 启动 QEMU 并配置多串口** 通过 `--qemu-extra-args` 传递多个 `-serial` 参数,每个参数对应一个虚拟串口: ```bash idf.py qemu --qemu-extra-args "-serial pty -serial file:uart1.log -serial mon:stdio" ``` - **参数说明**: - `-serial pty`:创建 PTY(伪终端),可用于交互式通信(如 `screen /dev/pts/X 115200`)。 - `-serial file:uart1.log`:将第二个串口输出保存到文件 `uart1.log`。 - `-serial mon:stdio`:将第三个串口绑定到 QEMU 监控器(通常用于调试)。 --- ### **2. 在 ESP32 代码中初始化多个 UART** 确保代码中配置的 UART 端口与 QEMU串口顺序一致(如 UART0 对应第一个 `-serial`,UART1 对应第二个): ```c #include "driver/uart.h" void app_main() { // 初始化 UART0(默认用于日志,对应 QEMU 的第一个 -serial pty) uart_config_t uart_cfg = { .baud_rate = 115200, .data_bits = UART_DATA_8_BITS, .parity = UART_PARITY_DISABLE, .stop_bits = UART_STOP_BITS_1, }; uart_param_config(UART_NUM_0, &uart_cfg); uart_driver_install(UART_NUM_0, 256, 0, 0, NULL, 0); // 初始化 UART1(对应 QEMU 的第二个 -serial file:uart1.log) uart_param_config(UART_NUM_1, &uart_cfg); uart_set_pin(UART_NUM_1, 17, 16, UART_PIN_NO_CHANGE, UART_PIN_NO_CHANGE); // 自定义引脚 uart_driver_install(UART_NUM_1, 256, 0, 0, NULL, 0); while (1) { // 通过 UART0 输出 uart_write_bytes(UART_NUM_0, "UART0: Hello\n", 13); // 通过 UART1 输出 uart_write_bytes(UART_NUM_1, "UART1: World\n", 13); vTaskDelay(1000 / portTICK_PERIOD_MS); } } ``` --- ### **3. 连接虚拟串口** - **PTY 终端**: QEMU 启动后会输出类似 `char device redirected to /dev/pts/X`,通过以下命令连接: ```bash screen /dev/pts/X 115200 ``` - **文件日志**: 直接查看 `uart1.log`: ```bash tail -f uart1.log ``` --- ### **4. 双向通信(发送数据到 ESP32)** 若需通过串口向 ESP32 发送数据,可使用命名管道(FIFO)或 PTY: #### **方法 1:使用命名管道** ```bash # 创建命名管道 mkfifo uart_pipe # 启动 QEMU(将第一个串口绑定到管道) idf.py qemu --qemu-extra-args "-serial pipe:uart_pipe" # 在另一终端中发送数据到 ESP32 echo "Hello ESP32" > uart_pipe ``` 在 ESP32 代码中,通过 `uart_read_bytes()` 读取数据: ```c uint8_t data[128]; int len = uart_read_bytes(UART_NUM_0, data, sizeof(data), 100 / portTICK_PERIOD_MS); if (len > 0) { printf("Received: %.*s\n", len, data); } ``` #### **方法 2:使用 PTY 交互** 若第一个串口配置为 `-serial pty`,直接在连接 PTY 的终端中输入数据即可(ESP32 需通过 `uart_read_bytes()` 读取)。 --- ### **5. 常见问题与调试** - **问题 1:串口无输出** - 检查 `idf.py qemu` 启动日志,确认 QEMU 是否成功创建串口设备(如 `/dev/pts/X`)。 - 确保 ESP32 代码中调用了 `uart_driver_install()`。 - **问题 2:多串口顺序混乱** - QEMU 的 `-serial` 参数顺序与 ESP32 的 `UART_NUM_X` 严格对应(第一个 `-serial` 对应 `UART_NUM_0`)。 - **问题 3:波特率无效** - QEMU 虚拟串口不依赖实际波特率,但代码中的配置需一致(如 `115200`)。 ---
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值