Data Flow
CLINT — Core Local Interruptor
The CLINT provides timer interrupts and software interrupts. It is mapped at CLINT_BASE_ADDR. Reading mtimecmp and writing mtime allows the guest to schedule timer interrupts. When the host real-time clock exceeds mtimecmp, the CLINT raises MTIP in mip.
static uint32_t clint_read(void *opaque, uint32_t offset, int size_log2)
{
RISCVMachine *s = opaque;
switch(offset) {
case 0x4000 ... 0x4007:
/* mtimecmp low/high */
return ...;
case 0xbff8 ... 0xbfff:
/* mtime low/high */
return rtc_get_time(s);
}
}
static void clint_write(void *opaque, uint32_t offset,
uint32_t val, int size_log2)
{
RISCVMachine *s = opaque;
switch(offset) {
case 0x4000 ... 0x4007:
/* set mtimecmp */
s->timecmp = ...;
break;
}
}PLIC — Platform Level Interrupt Controller
The PLIC aggregates up to 32 external interrupt sources. Each source has a priority, a pending bit, and an enable bit per context. When a device raises an IRQ, plic_set_irq updates the pending register and asserts the CPU interrupt line.
static uint32_t plic_read(void *opaque, uint32_t offset, int size_log2)
{
RISCVMachine *s = opaque;
if (offset >= PLIC_HART_BASE) {
/* context threshold / claim/complete */
} else {
/* priority / pending / enable registers */
}
}
static void plic_write(void *opaque, uint32_t offset,
uint32_t val, int size_log2)
{
/* threshold, claim/complete, enable */
}
static void plic_set_irq(void *opaque, int irq_num, int level)
{
RISCVMachine *s = opaque;
if (level) {
s->plic_pending_irq |= (1 << irq_num);
} else {
s->plic_pending_irq &= ~(1 << irq_num);
}
plic_update_mip(s);
}HTIF — Host Target Interface
HTIF is a legacy mechanism for console output and system poweroff. TinyEMU uses fixed addresses (0x40008000) instead of ELF-symbol-based addresses. The bootloader was patched to support this.
static uint32_t htif_read(void *opaque, uint32_t offset, int size_log2)
{
RISCVMachine *s = opaque;
if (offset == 0) return s->htif_tohost;
if (offset == 8) return s->htif_fromhost;
return 0;
}
static void htif_write(void *opaque, uint32_t offset,
uint32_t val, int size_log2)
{
RISCVMachine *s = opaque;
if (offset == 0) {
s->htif_tohost = val;
htif_handle_cmd(s);
}
}
static void htif_handle_cmd(RISCVMachine *s)
{
uint64_t cmd = s->htif_tohost;
int device = (cmd >> 56) & 0xff;
int cmd_code = (cmd >> 48) & 0xff;
if (device == 1 && cmd_code == 1) {
/* console output */
console_write(s->common.console, (char)(cmd & 0xff));
} else if (device == 0 && cmd_code == 0) {
/* poweroff */
exit(0);
}
}FDT — Flattened Device Tree
Before boot, riscv_build_fdt() constructs a device tree blob in memory describing CPUs, memory, CLINT, PLIC, VirtIO devices, and the framebuffer. The BIOS reads this FDT to discover hardware.
int riscv_build_fdt(RISCVMachine *s, uint8_t *dst,
uint64_t kernel_start, uint64_t kernel_size,
uint64_t initrd_start, uint64_t initrd_size,
const char *cmdline)
{
FDTState *s = fdt_init();
fdt_begin_node(s, "");
fdt_prop_u32(s, "#address-cells", 2);
fdt_prop_u32(s, "#size-cells", 2);
fdt_prop_str(s, "compatible", "riscv-virtio");
/* cpus, memory, soc, virtio, chosen ... */
fdt_end_node(s);
return fdt_output(s, dst);
}