Data Flow
Framebuffer Architecture
TinyEMU abstracts the display via FBDevice (machine.h). The device-specific code (simplefb or VGA) fills fb_data, width, height, and stride. The SDL frontend polls for dirty pages and redraws only changed regions.
struct FBDevice {
int width;
int height;
int stride;
uint8_t *fb_data;
int fb_size;
void *device_opaque;
void (*refresh)(struct FBDevice *fb_dev,
SimpleFBDrawFunc *redraw_func, void *opaque);
};Simple Framebuffer
simplefb.c registers a RAM region with DEVRAM_FLAG_DIRTY_BITS at FRAMEBUFFER_BASE_ADDR. The guest writes pixels directly into RAM. The host detects changes via the dirty-bit bitmap and calls the registered redraw_func.
SimpleFBState *simplefb_init(PhysMemoryMap *map, uint64_t phys_addr,
FBDevice *fb_dev, int width, int height)
{
SimpleFBState *s = mallocz(sizeof(*s));
fb_dev->width = width;
fb_dev->height = height;
fb_dev->stride = width * 4;
fb_dev->fb_size = (height * fb_dev->stride + FB_ALLOC_ALIGN - 1)
& ~(FB_ALLOC_ALIGN - 1);
s->fb_page_count = fb_dev->fb_size >> DEVRAM_PAGE_SIZE_LOG2;
s->mem_range = cpu_register_ram(map, phys_addr, fb_dev->fb_size,
DEVRAM_FLAG_DIRTY_BITS);
fb_dev->fb_data = s->mem_range->phys_mem;
fb_dev->device_opaque = s;
fb_dev->refresh = simplefb_refresh1;
return s;
}SDL Frontend
sdl.c creates an SDL window and surface. During sdl_refresh() it calls fb_dev->refresh(), which invokes sdl_update() to blit dirty rectangles to the screen.
static void sdl_update(FBDevice *fb_dev, void *opaque,
int x, int y, int w, int h)
{
SDL_Rect r;
r.x = x; r.y = y; r.w = w; r.h = h;
SDL_BlitSurface(fb_surface, &r, screen, &r);
SDL_UpdateRect(screen, r.x, r.y, r.w, r.h);
}
void sdl_refresh(VirtMachine *m)
{
if (m->fb_dev) {
m->fb_dev->refresh(m->fb_dev, sdl_update, NULL);
}
}VGA / PCI Display
For x86 machines, TinyEMU includes a minimal VGA device (vga.c) and a PCI VGA card (pci.c). It exposes Bochs VBE registers and a linear framebuffer. The same FBDevice abstraction is used to forward updates to SDL.
VGAState *pci_vga_init(PCIBus *bus, FBDevice *fb_dev,
int width, int height,
const uint8_t *vga_rom_buf, int vga_rom_size)
{
VGAState *s = mallocz(sizeof(*s));
s->fb_dev = fb_dev;
/* register PCI BARs, VBE IO ports, VGA memory */
return s;
}Input Event Routing
Keyboard and mouse events from SDL are routed through the machine class vtable to VirtIO input devices. For RISC-V, riscv_vm_send_key_event and riscv_vm_send_mouse_event forward to the respective VIRTIODevice.
static void riscv_vm_send_key_event(VirtMachine *s1, BOOL is_down,
uint16_t key_code)
{
RISCVMachine *s = (RISCVMachine *)s1;
if (s->keyboard_dev) {
virtio_input_send_key_event(s->keyboard_dev, is_down, key_code);
}
}
static void riscv_vm_send_mouse_event(VirtMachine *s1, int dx, int dy, int dz,
unsigned int buttons)
{
RISCVMachine *s = (RISCVMachine *)s1;
if (s->mouse_dev) {
virtio_input_send_mouse_event(s->mouse_dev, dx, dy, dz, buttons);
}
}Console Resize
When the terminal window changes size, the STDIODevice sets resize_pending. The next iteration of virt_machine_run() reads the new dimensions and notifies the VirtIO console via virtio_console_resize_event().
if (s->resize_pending) {
int width, height;
console_get_size(s, &width, &height);
virtio_console_resize_event(m->console_dev, width, height);
s->resize_pending = FALSE;
}