Data Flow
Physical Memory Map
iomem.c manages the PhysMemoryMap: an array of up to PHYS_MEM_RANGE_MAX ranges. Each range is either RAM (is_ram = TRUE) or a device (is_ram = FALSE). The map provides function pointers for RAM registration, freeing, dirty-bit retrieval, and address changes.
PhysMemoryMap *phys_mem_map_init(void)
{
PhysMemoryMap *s;
s = mallocz(sizeof(*s));
s->register_ram = default_register_ram;
s->free_ram = default_free_ram;
s->get_dirty_bits = default_get_dirty_bits;
s->set_ram_addr = default_set_addr;
return s;
}Memory Range Lookup
get_phys_mem_range() linearly searches the array for a range containing the physical address. This is called on every TLB miss and MMIO access.
PhysMemoryRange *get_phys_mem_range(PhysMemoryMap *s, uint64_t paddr)
{
PhysMemoryRange *pr;
int i;
for(i = 0; i < s->n_phys_mem_range; i++) {
pr = &s->phys_mem_range[i];
if (paddr >= pr->addr && paddr < pr->addr + pr->size)
return pr;
}
return NULL;
}RAM Registration
cpu_register_ram() allocates host memory for a guest RAM region. The DEVRAM_FLAG_DIRTY_BITS flag enables tracking of modified pages via a bitmap, used by the display subsystem to detect framebuffer changes.
static PhysMemoryRange *default_register_ram(PhysMemoryMap *s, uint64_t addr,
uint64_t size, int devram_flags)
{
PhysMemoryRange *pr;
pr = register_ram_entry(s, addr, size, devram_flags);
pr->phys_mem = mallocz(size);
if (!pr->phys_mem) {
fprintf(stderr, "Could not allocate VM memory\n");
exit(1);
}
if (devram_flags & DEVRAM_FLAG_DIRTY_BITS) {
size_t nb_pages = size >> DEVRAM_PAGE_SIZE_LOG2;
pr->dirty_bits_size = ((nb_pages + 31) / 32) * sizeof(uint32_t);
pr->dirty_bits_index = 0;
for(i = 0; i < 2; i++) {
pr->dirty_bits_tab[i] = mallocz(pr->dirty_bits_size);
}
pr->dirty_bits = pr->dirty_bits_tab[pr->dirty_bits_index];
}
return pr;
}MMIO Device Registration
cpu_register_device() maps a physical address range to callback functions. When the CPU accesses this range, the read/write callbacks are invoked with the offset and access size.
PhysMemoryRange *cpu_register_device(PhysMemoryMap *s, uint64_t addr,
uint64_t size, void *opaque,
DeviceReadFunc *read_func,
DeviceWriteFunc *write_func,
int devio_flags)
{
PhysMemoryRange *pr;
assert(s->n_phys_mem_range < PHYS_MEM_RANGE_MAX);
assert(size <= 0xffffffff);
pr = &s->phys_mem_range[s->n_phys_mem_range++];
pr->map = s;
pr->addr = addr;
pr->org_size = size;
pr->size = (devio_flags & DEVIO_DISABLED) ? 0 : pr->org_size;
pr->is_ram = FALSE;
pr->opaque = opaque;
pr->read_func = read_func;
pr->write_func = write_func;
pr->devio_flags = devio_flags;
return pr;
}Dirty Bits
Dirty bits track which pages have been written by the CPU. The display code calls phys_mem_get_dirty_bits() to retrieve and reset the bitmap, then flushes only changed regions to the screen.
static const uint32_t *default_get_dirty_bits(PhysMemoryMap *map,
PhysMemoryRange *pr)
{
uint32_t *dirty_bits = pr->dirty_bits;
BOOL has_dirty_bits = FALSE;
size_t n = pr->dirty_bits_size / sizeof(uint32_t);
for(i = 0; i < n; i++) {
if (dirty_bits[i] != 0) { has_dirty_bits = TRUE; break; }
}
if (has_dirty_bits && pr->size != 0) {
map->flush_tlb_write_range(map->opaque, pr->phys_mem, pr->org_size);
}
pr->dirty_bits_index ^= 1;
pr->dirty_bits = pr->dirty_bits_tab[pr->dirty_bits_index];
memset(pr->dirty_bits, 0, pr->dirty_bits_size);
return dirty_bits;
}IRQ Wiring
The IRQSignal abstraction connects devices to interrupt controllers. irq_init() binds a SetIRQFunc callback, and set_irq() raises or lowers the line. On RISC-V, PLIC IRQs are wired to the CPU's mip via plic_set_irq.
typedef void SetIRQFunc(void *opaque, int irq_num, int level);
typedef struct {
SetIRQFunc *set_irq;
void *opaque;
int irq_num;
} IRQSignal;
void irq_init(IRQSignal *irq, SetIRQFunc *set_irq, void *opaque, int irq_num);
static inline void set_irq(IRQSignal *irq, int level)
{
irq->set_irq(irq->opaque, irq->irq_num, level);
}