Skip to content

外设建模(串口)

我们以 PL011 串口为例,讲解如何建模一个外设。

L011 串口在 QEMU 中的建模完整展示了硬件外设模拟的核心流程:

  • 类型系统集成:通过 QOM 实现设备类的继承体系
  • 状态精确建模:寄存器、FIFO、中断状态的全周期管理
  • 硬件接口实现:MMIO 回调处理 CPU 访问,字符后端对接宿主 I/O
  • 中断系统集成:与平台中断控制器 (PLIC) 协同工作
  • 设备生命周期:实现重置、迁移、销毁等完整生命周期

设备类型定义

核心机制:QEMU Object Model (QOM)

关键操作:

  • 定义 TypeInfo 结构体描述设备类型
  • 实现类初始化 (class_init) 和实例初始化 (instance_init)
  • 注册到全局类型系统
 // hw/char/pl011.c
static const TypeInfo pl011_type_info = {
    .name = TYPE_PL011,
    .parent = TYPE_SYS_BUS_DEVICE,
    .instance_size = sizeof(PL011State),
    .class_init = pl011_class_init,
    .instance_init = pl011_instance_init,
};

static void pl011_register_types(void) {
    type_register_static(&pl011_type_info);
}
type_init(pl011_register_types);

设备状态结构设计

核心组件:PL011State

关键字段:

  • 寄存器状态 (lcr, imsc, 等)
  • FIFO 缓冲区
  • 中断信号线
  • 字符后端 (CharBackend)
typedef struct PL011State {
    SysBusDevice parent_obj;

    /* 寄存器状态 */
    uint32_t lcr;       // 线路控制寄存器
    uint32_t imsc;      // 中断屏蔽寄存器
    uint32_t flags;     // 状态标志位

    /* FIFO 缓冲区 */
    uint8_t read_fifo[PL011_FIFO_DEPTH];
    uint32_t read_pos;
    uint32_t read_count;

    /* 中断系统 */
    qemu_irq irq;       // 中断信号线

    /* 字符设备后端 */
    CharBackend chr;    // 连接宿主终端或文件
} PL011State;

设备类初始化

核心任务: - 设置设备属性 (properties) - 绑定 realize 和 reset 方法 - 定义设备迁移支持

static void pl011_class_init(ObjectClass *klass, void *data) {
    DeviceClass *dc = DEVICE_CLASS(klass);

    dc->realize = pl011_realize;
    dc->reset = pl011_reset;
    dc->vmsd = &vmstate_pl011;

    device_class_set_props(dc, pl011_properties);
}

static Property pl011_properties[] = {
    DEFINE_PROP_CHR("chardev", PL011State, chr),
    DEFINE_PROP_UINT32("clock", PL011State, clock, 0),
    DEFINE_PROP_END_OF_LIST(),
};

设备实例化与硬件连接

核心操作:

  • 初始化内存区域 (MemoryRegion)
  • 注册 MMIO 回调函数
  • 连接中断信号
  • 绑定字符设备后端
static void pl011_realize(DeviceState *dev, Error **errp) {
    PL011State *s = PL011(dev);
    SysBusDevice *sbd = SYS_BUS_DEVICE(dev);

    /* 1. 初始化内存区域 */
    memory_region_init_io(&s->iomem, OBJECT(s), &pl011_ops, s, "pl011", 0x1000);
    sysbus_init_mmio(sbd, &s->iomem);

    /* 2. 初始化中断 */
    sysbus_init_irq(sbd, &s->irq);

    /* 3. 连接字符设备 */
    qemu_chr_fe_set_handlers(&s->chr, pl011_can_receive, pl011_receive,
                             NULL, NULL, s, NULL, true);
}

实现设备功能逻辑

核心组件:

  • MMIO 读写回调
  • 中断生成逻辑
  • FIFO 管理
  • 字符设备通信
/* MMIO 写操作 */
static void pl011_write(void *opaque, hwaddr offset, uint64_t value, unsigned size) {
    PL011State *s = opaque;

    switch (offset) {
    case PL011_DR:  // 数据寄存器
        if (s->lcr & LCR_FEN) {
            fifo_push(s, value);  // FIFO 模式
        } else {
            qemu_chr_fe_write(&s->chr, (uint8_t*)&value, 1); // 直接发送
        }
        break;
    case PL011_IMSC: // 中断屏蔽
        s->imsc = value;
        pl011_update(s);  // 更新中断状态
        break;
    }
}

/* 字符设备接收回调 */
static void pl011_receive(void *opaque, const uint8_t *buf, int size) {
    PL011State *s = opaque;
    fifo_push(s, *buf);  // 数据放入接收 FIFO
    s->int_status |= INT_RX;  // 设置接收中断
    qemu_set_irq(s->irq, 1);  // 触发中断
}

中断处理与状态更新

核心逻辑:

  • 根据寄存器状态生成中断
  • 实现中断清除机制
  • FIFO 阈值触发
static void pl011_update(PL011State *s) {
    uint32_t int_level = 0;

    /* 检查接收中断 */
    if ((s->imsc & INT_RX) && (s->read_count > 0)) {
        int_level |= INT_RX;
    }

    /* 检查发送中断 */
    if ((s->imsc & INT_TX) && (s->xmit_count < PL011_FIFO_DEPTH)) {
        int_level |= INT_TX;
    }

    /* 更新中断线 */
    qemu_set_irq(s->irq, int_level ? 1 : 0);
}

一个简单 PL011 设备建模的代码示例:

/* hw/char/pl011.c - PL011 UART 完整实现 */

#define TYPE_PL011 "pl011"
#define PL011(obj) OBJECT_CHECK(PL011State, (obj), TYPE_PL011)

/* 寄存器偏移定义 */
enum {
    PL011_DR    = 0x00, // 数据寄存器
    PL011_FR    = 0x18, // 标志寄存器
    PL011_ILPR  = 0x20, // 低功耗寄存器
    PL011_IBRD  = 0x24, // 整数波特率
    PL011_FBRD  = 0x28, // 小数波特率
    PL011_LCRH  = 0x2C, // 线路控制
    PL011_CR    = 0x30, // 控制寄存器
    PL011_IMSC  = 0x38, // 中断屏蔽
};

/* 设备状态结构 */
typedef struct PL011State {
    SysBusDevice parent_obj;
    MemoryRegion iomem;
    CharBackend chr;
    qemu_irq irq;

    /* 寄存器状态 */
    uint32_t readbuff;
    uint32_t flags;
    uint32_t lcr;
    uint32_t cr;
    uint32_t imsc;
    uint32_t int_level;

    /* FIFO 状态 */
    uint8_t read_fifo[PL011_FIFO_DEPTH];
    uint32_t read_pos;
    uint32_t read_count;
    uint32_t read_trigger;
} PL011State;

/* MMIO 操作回调 */
static const MemoryRegionOps pl011_ops = {
    .read = pl011_read,
    .write = pl011_write,
    .endianness = DEVICE_NATIVE_ENDIAN,
    .impl.min_access_size = 4,
    .impl.max_access_size = 4,
};

/* 读操作实现 */
static uint64_t pl011_read(void *opaque, hwaddr offset, unsigned size) {
    PL011State *s = opaque;
    uint32_t ret = 0;

    switch (offset) {
    case PL011_DR:
        if (s->read_count > 0) {
            ret = s->read_fifo[s->read_pos];
            s->read_pos = (s->read_pos + 1) % PL011_FIFO_DEPTH;
            s->read_count--;
            pl011_update(s); // 更新中断状态
        }
        break;
    case PL011_FR:
        ret = s->flags;
        break;
    case PL011_IMSC:
        ret = s->imsc;
        break;
    }
    return ret;
}

/* 写操作实现 */
static void pl011_write(void *opaque, hwaddr offset, uint64_t value, unsigned size) {
    PL011State *s = opaque;

    switch (offset) {
    case PL011_DR:
        qemu_chr_fe_write(&s->chr, (uint8_t*)&value, 1);
        break;
    case PL011_LCRH:
        s->lcr = value;
        break;
    case PL011_IMSC:
        s->imsc = value;
        pl011_update(s);
        break;
    }
}

/* 字符设备接收回调 */
static void pl011_receive(void *opaque, const uint8_t *buf, int size) {
    PL011State *s = opaque;

    if (s->read_count < PL011_FIFO_DEPTH) {
        int slot = (s->read_pos + s->read_count) % PL011_FIFO_DEPTH;
        s->read_fifo[slot] = *buf;
        s->read_count++;

        if (s->read_count >= s->read_trigger) {
            s->int_level |= INT_RX;
            qemu_set_irq(s->irq, 1);
        }
    }
}

/* 中断状态更新 */
static void pl011_update(PL011State *s) {
    uint32_t int_level = 0;

    // 接收中断 (RX)
    if ((s->imsc & INT_RX) && (s->read_count > 0)) {
        int_level |= INT_RX;
    }

    // 发送中断 (TX)
    if ((s->imsc & INT_TX) && (s->xmit_count < PL011_FIFO_DEPTH)) {
        int_level |= INT_TX;
    }

    qemu_set_irq(s->irq, int_level ? 1 : 0);
}

/* 设备重置 */
static void pl011_reset(DeviceState *dev) {
    PL011State *s = PL011(dev);

    s->lcr = 0;
    s->cr = 0x300;
    s->imsc = 0;
    s->read_count = 0;
    s->read_pos = 0;
    s->int_level = 0;
    qemu_set_irq(s->irq, 0);
}

/* 设备初始化 */
static void pl011_realize(DeviceState *dev, Error **errp) {
    PL011State *s = PL011(dev);
    SysBusDevice *sbd = SYS_BUS_DEVICE(dev);

    memory_region_init_io(&s->iomem, OBJECT(s), &pl011_ops, s, "pl011", 0x1000);
    sysbus_init_mmio(sbd, &s->iomem);
    sysbus_init_irq(sbd, &s->irq);

    qemu_chr_fe_set_handlers(&s->chr, pl011_can_receive, pl011_receive,
                             NULL, NULL, s, NULL, true);
}

/* 类型注册 */
static const TypeInfo pl011_info = {
    .name = TYPE_PL011,
    .parent = TYPE_SYS_BUS_DEVICE,
    .instance_size = sizeof(PL011State),
    .class_init = pl011_class_init,
};

static void pl011_register_types(void) {
    type_register_static(&pl011_info);
}
type_init(pl011_register_types)

在 RISC-V virt 机器中实例化 PL011:

/* hw/riscv/virt.c - 机器初始化 */

static void virt_machine_init(MachineState *machine) {
    // ...

    /* 创建 PL011 串口 */
    DeviceState *dev = qdev_new(TYPE_PL011);
    qdev_prop_set_chr(dev, "chardev", serial_hd(0));

    sysbus_realize_and_unref(SYS_BUS_DEVICE(dev), &error_fatal);
    sysbus_mmio_map(SYS_BUS_DEVICE(dev), 0, 0x10000000); // 映射到 0x10000000
    sysbus_connect_irq(SYS_BUS_DEVICE(dev), 0, plic_irq[UART0_IRQ]); // 连接中断

    // 设备树添加节点
    qemu_fdt_add_subnode(fdt, "/soc/serial");
    qemu_fdt_setprop_string(fdt, "/soc/serial", "compatible", "arm,pl011");
    qemu_fdt_setprop_cells(fdt, "/soc/serial", "reg", 0x10000000, 0x1000);
    qemu_fdt_setprop_cells(fdt, "/soc/serial", "interrupts", UART0_IRQ);
}

关键实现要点

寄存器精确建模:

  • 实现所有 32 个寄存器的精确行为
  • 处理特殊位(如 FIFO 使能位、中断屏蔽位)
  • 支持波特率计算(IBRD/FBRD)

FIFO 状态机:

cstatic void fifo_push(PL011State *s, uint32_t value) {
    if (s->read_count < PL011_FIFO_DEPTH) {
        uint32_t slot = (s->read_pos + s->read_count) % PL011_FIFO_DEPTH;
        s->read_fifo[slot] = value;
        s->read_count++;

        // 触发中断条件
        if (s->read_count >= s->read_trigger) {
            s->int_level |= INT_RX;
            qemu_set_irq(s->irq, 1);
        }
    }
}

时钟域处理:

  • 实现 pl011_clock_update 回调
  • 处理波特率变化事件
  • 支持动态时钟调整

迁移支持:

cstatic const VMStateDescription vmstate_pl011 = {
    .name = "pl011",
    .version_id = 2,
    .fields = (VMStateField[]) {
        VMSTATE_UINT32(lcr, PL011State),
        VMSTATE_UINT32_ARRAY(read_fifo, PL011State, PL011_FIFO_DEPTH),
        VMSTATE_UINT32(read_pos, PL011State),
        VMSTATE_UINT32(read_count, PL011State),
        VMSTATE_END_OF_LIST()
    }
};

性能优化:

  • 使用位操作代替除法计算波特率
  • FIFO 操作用掩码替代取模运算
  • 中断状态缓存减少计算次数

这种建模方式使 PL011 在 QEMU 中能精确模拟真实硬件行为,支持从裸机程序到 Linux 内核的全栈开发。