#include "hv/config.h" #include "hv/mmio.h" #include "hv/mmio_policy.h" #include "hv/uart.h" #include "hv/vcpu.h" static const struct mmio_policy_region g_mmio_regions[] = { { CONFIG_UART_BASE, CONFIG_UART_SIZE, "pl011-uart", MMIO_POLICY_EMULATE_PL011 }, { CONFIG_GICD_BASE, CONFIG_GICD_SIZE, "gicd", MMIO_POLICY_EMULATE_READONLY_DEVICE }, { CONFIG_GITS_BASE, CONFIG_GITS_SIZE, "gits", MMIO_POLICY_EMULATE_READONLY_DEVICE }, { CONFIG_GICR_BASE, CONFIG_GICR_SIZE, "gicr", MMIO_POLICY_EMULATE_READONLY_DEVICE }, { CONFIG_RTC_BASE, CONFIG_RTC_SIZE, "pl031-rtc", MMIO_POLICY_EMULATE_READONLY_DEVICE }, { CONFIG_FW_CFG_BASE, CONFIG_FW_CFG_SIZE, "fw-cfg", MMIO_POLICY_EMULATE_READONLY_DEVICE }, { CONFIG_GPIO_BASE, CONFIG_GPIO_SIZE, "pl061-gpio", MMIO_POLICY_EMULATE_READONLY_DEVICE }, { CONFIG_PCI_ECAM_BASE, CONFIG_PCI_ECAM_SIZE, "pci-ecam", MMIO_POLICY_EMULATE_ZERO }, { 0x0a000000ull, 0x00004000ull, "virtio-mmio", MMIO_POLICY_EMULATE_VIRTIO_MMIO }, }; const struct mmio_policy_region *mmio_policy_lookup(ipa_t ipa) { for (uint32_t i = 0; i < HV_ARRAY_SIZE(g_mmio_regions); i++) { uint64_t end; if (hv_add_overflow_u64(g_mmio_regions[i].base, g_mmio_regions[i].size, &end)) { continue; } if (ipa >= g_mmio_regions[i].base && ipa < end) { return &g_mmio_regions[i]; } } return (const struct mmio_policy_region *)0; } static bool decode_abort_access(uint64_t esr, bool *is_write, uint32_t *rt) { if ((esr & HV_BIT(24)) == 0u) { return false; } *rt = (uint32_t)((esr >> 16u) & 0x1fu); *is_write = (esr & HV_BIT(6)) != 0u; return true; } static uint64_t virtio_read(uint64_t off) { if ((off & ~0x1ffull) != 0u) { return 0u; } off &= 0x1ffu; switch (off) { case 0x000u: return 0x74726976u; case 0x004u: return 2u; case 0x008u: return 3u; case 0x00cu: return 0x41524945u; case 0x010u: return 0u; case 0x034u: return 0u; case 0x070u: return 0u; default: return 0u; } } static uint64_t readonly_device_read32(ipa_t ipa) { if (!hv_is_aligned_u64(ipa, 4u)) { return 0u; } return (uint64_t)mmio_read32((uintptr_t)ipa); } bool mmio_policy_emulate(struct vcpu *vcpu, ipa_t ipa, uint64_t esr_el2) { const struct mmio_policy_region *region = mmio_policy_lookup(ipa); bool is_write; uint32_t rt; uint64_t off; uint64_t value = 0u; if (vcpu == (struct vcpu *)0 || region == (const struct mmio_policy_region *)0 || !decode_abort_access(esr_el2, &is_write, &rt)) { return false; } off = ipa - region->base; if (region->action == MMIO_POLICY_EMULATE_PL011) { if (is_write && off == 0u) { uart_putc((char)(vcpu_read_gpr(vcpu, rt) & 0xffu)); } else if (!is_write && off == 0x18u) { value = 0x10u; } } else if (region->action == MMIO_POLICY_EMULATE_VIRTIO_MMIO) { if (!is_write) { value = virtio_read(off); } } else if (region->action == MMIO_POLICY_EMULATE_READONLY_DEVICE) { if (!is_write) { value = readonly_device_read32(ipa); } } else if (region->action != MMIO_POLICY_EMULATE_ZERO) { return false; } if (!is_write) { vcpu_write_gpr(vcpu, rt, value); } return true; } void mmio_policy_dump(void) { for (uint32_t i = 0; i < HV_ARRAY_SIZE(g_mmio_regions); i++) { uart_puts("mmio name="); uart_puts(g_mmio_regions[i].name); uart_puts(" base="); uart_put_hex64(g_mmio_regions[i].base); uart_puts(" size="); uart_put_hex64(g_mmio_regions[i].size); uart_puts(" action="); uart_put_dec64((uint64_t)g_mmio_regions[i].action); uart_puts("\n"); } }