This patch improves PAM emulation. PAM defines 4 memory access redirection modes. In mode 1 reads are directed to RAM and writes are directed to PCI. In mode 2 it is contrary. In mode 0 all access is directed to PCI. In mode 3 it is directed to RAM. Modes 0 and 3 are well emulated but modes 1 and 2 are not. The cause is: aliases are used while more complicated logic is required.
The idea is to use ROM device like memory regions for mode 1 and 2 emulation instead of aliases. Writes are directed to proper destination region by specified I/O callback. Read redirection depends on type of source region. In most cases source region is RAM (or ROM), so ram_addr of PAM region is set to ram_addr of source region with offset. Otherwise, when source region is an I/O region, reading is redirected to source region read callback by PAM region one. Read source and write destination regions are updated by the memory commit callback. Note that we cannot use I/O region for PAM as it will violate "trying to execute code outside RAM or ROM" assertion. Signed-off-by: Efimov Vasily <r...@ispras.ru> --- hw/pci-host/pam.c | 238 +++++++++++++++++++++++++++++++++++++++++----- include/hw/pci-host/pam.h | 10 +- 2 files changed, 223 insertions(+), 25 deletions(-) diff --git a/hw/pci-host/pam.c b/hw/pci-host/pam.c index 17d826c..9729b2b 100644 --- a/hw/pci-host/pam.c +++ b/hw/pci-host/pam.c @@ -27,43 +27,233 @@ * THE SOFTWARE. */ -#include "qom/object.h" -#include "sysemu/sysemu.h" #include "hw/pci-host/pam.h" +#include "exec/address-spaces.h" +#include "exec/memory-internal.h" +#include "qemu/bswap.h" + +static void pam_write(void *opaque, hwaddr addr, uint64_t data, + unsigned size) +{ + PAMMemoryRegion *pam = (PAMMemoryRegion *) opaque; + void *ptr; + + /* Destination region can be both RAM and IO. */ + if (!memory_access_is_direct(pam->mr_write_to, true)) { + memory_region_dispatch_write(pam->mr_write_to, + addr + pam->write_offset, data, size, + MEMTXATTRS_UNSPECIFIED); + } else { + ptr = memory_region_get_ram_ptr(pam->mr_write_to) + addr + + pam->write_offset; + + switch (size) { + case 1: + stb_p(ptr, data); + break; + case 2: + stw_he_p(ptr, data); + break; + case 4: + stl_he_p(ptr, data); + break; + case 8: + stq_he_p(ptr, data); + break; + default: + abort(); + } + + invalidate_and_set_dirty(pam->mr_write_to, addr + pam->pam_offset, + size); + } +} + +static uint64_t pam_read(void *opaque, hwaddr addr, unsigned size) +{ + PAMMemoryRegion *pam = (PAMMemoryRegion *) opaque; + uint64_t ret = 0; + + /* Source region can be IO only. */ + memory_region_dispatch_read(pam->mr_read_from, pam->read_offset + addr, + &ret, size, MEMTXATTRS_UNSPECIFIED); + + return ret; +} + +static MemoryRegionOps pam_ops = { + .write = pam_write, + .read = pam_read, + .endianness = DEVICE_NATIVE_ENDIAN, +}; + +static void pam_set_current(PAMMemoryRegion *pam, unsigned new_current) +{ + assert(new_current <= 3); + + if (new_current != pam->current) { +#ifdef DEBUG_PAM + printf("PAM 0x"TARGET_FMT_plx": %d <=> %s\n", pam->pam_offset, + new_current, pam->region[new_current].name); +#endif + memory_region_set_enabled(&pam->region[pam->current], false); + pam->current = new_current; + memory_region_set_enabled(&pam->region[new_current], true); + } +} + +static void pam_leaf_mr_lookup(MemoryRegion *mr, hwaddr offset, + MemoryRegion **leaf, hwaddr *offset_within_leaf) +{ + MemoryRegion *other; + if (mr->alias) { + pam_leaf_mr_lookup(mr->alias, offset + mr->alias_offset, leaf, + offset_within_leaf); + } else { + if (QTAILQ_EMPTY(&mr->subregions) + && int128_lt(int128_make64(offset), mr->size)) { + *leaf = mr; + *offset_within_leaf = offset; + } else { + QTAILQ_FOREACH(other, &mr->subregions, subregions_link) { + if (!other->enabled) { + continue; + } + if (other->addr <= offset && int128_lt(int128_make64(offset), + int128_add(int128_make64(other->addr), other->size))) { + pam_leaf_mr_lookup(other, offset - other->addr, leaf, + offset_within_leaf); + if (*leaf) { + return; + } + } + } + *leaf = NULL; + } + } +} + +static bool mr_has_mr(MemoryRegion *container, MemoryRegion *mr, + hwaddr *offset) +{ + hwaddr tmp_offset; + MemoryRegion *other; + + if (container == mr) { + *offset = 0; + return true; + } + if (container->alias) { + if (mr_has_mr(container->alias, mr, &tmp_offset)) { + *offset = tmp_offset - container->alias_offset; + return true; + } else { + return false; + } + } + QTAILQ_FOREACH(other, &container->subregions, subregions_link) { + if (mr_has_mr(other, mr, &tmp_offset)) { + *offset = tmp_offset + other->addr; + return true; + } + } + return false; +} + +static void pam_mem_commit(MemoryListener *listener) +{ + PAMMemoryRegion *pam = container_of(listener, PAMMemoryRegion, listener); + MemoryRegion *cur, *read_src, *write_dst; + hwaddr offset_within_leaf; + + if (pam->current == 1) { + /* Read from RAM and write to PCI */ + /* Note pam->region[3].alias points to RAM by creation. */ + read_src = pam->region[3].alias; + pam->read_offset = pam->pam_offset; + + pam_leaf_mr_lookup(&pam->region[0], 0, &write_dst, &offset_within_leaf); + + assert(write_dst); + + pam->write_offset = offset_within_leaf; + } else if (pam->current == 2) { + /* Read from PCI and write to RAM */ + pam_leaf_mr_lookup(&pam->region[0], 0, &read_src, &offset_within_leaf); + + assert(read_src); + + pam->read_offset = offset_within_leaf; + + write_dst = pam->region[3].alias; + pam->write_offset = pam->pam_offset; + } else { + return; + } + + cur = &pam->region[pam->current]; + + if (memory_region_is_ram(read_src)) { + /* Current region parent is system_memory by creation. */ + assert(mr_has_mr(cur->container, read_src, &offset_within_leaf)); + cur->ram_addr = read_src->ram_addr + pam->pam_offset + - offset_within_leaf; + cur->rom_device = true; + cur->romd_mode = true; + } else { + cur->ram_addr = ~(ram_addr_t)0; + cur->rom_device = false; + cur->romd_mode = false; + } + + pam->mr_read_from = read_src; + pam->mr_write_to = write_dst; +} void init_pam(DeviceState *dev, MemoryRegion *ram_memory, MemoryRegion *system_memory, MemoryRegion *pci_address_space, - PAMMemoryRegion *mem, uint32_t start, uint32_t size) + PAMMemoryRegion *pam, uint32_t start, uint32_t size) { int i; - /* RAM */ - memory_region_init_alias(&mem->alias[3], OBJECT(dev), "pam-ram", ram_memory, - start, size); - /* ROM (XXX: not quite correct) */ - memory_region_init_alias(&mem->alias[1], OBJECT(dev), "pam-rom", ram_memory, - start, size); - memory_region_set_readonly(&mem->alias[1], true); - - /* XXX: should distinguish read/write cases */ - memory_region_init_alias(&mem->alias[0], OBJECT(dev), "pam-pci", pci_address_space, - start, size); - memory_region_init_alias(&mem->alias[2], OBJECT(dev), "pam-pci", ram_memory, - start, size); - - for (i = 0; i < 4; ++i) { - memory_region_set_enabled(&mem->alias[i], false); + /* All access to PCI */ + memory_region_init_alias(&pam->region[0], OBJECT(dev), "pam-pci", + pci_address_space, start, size); + + /* Read from RAM and write to PCI */ + memory_region_init_io(&pam->region[1], OBJECT(dev), &pam_ops, pam, + "pam-r-ram-w-pci", size); + + /* Read from PCI and write to RAM */ + memory_region_init_io(&pam->region[2], OBJECT(dev), &pam_ops, pam, + "pam-r-pci-w-ram", size); + + /* All access to RAM */ + memory_region_init_alias(&pam->region[3], OBJECT(dev), "pam-ram", + ram_memory, start, size); + + memory_region_add_subregion_overlap(system_memory, start, + &pam->region[0], 1); + memory_region_set_enabled(&pam->region[0], true); + for (i = 1; i < 4; i++) { + memory_region_set_enabled(&pam->region[i], false); memory_region_add_subregion_overlap(system_memory, start, - &mem->alias[i], 1); + &pam->region[i], 1); } - mem->current = 0; + + pam->current = 0; + pam->pam_offset = start; + + pam->listener = (MemoryListener) { + .commit = pam_mem_commit, + }; + + memory_listener_register(&pam->listener, &address_space_memory); } void pam_update(PAMMemoryRegion *pam, int idx, uint8_t val) { assert(0 <= idx && idx <= 12); - memory_region_set_enabled(&pam->alias[pam->current], false); - pam->current = (val >> ((!(idx & 1)) * 4)) & PAM_ATTR_MASK; - memory_region_set_enabled(&pam->alias[pam->current], true); + pam_set_current(pam, (val >> ((!(idx & 1)) * 4)) & PAM_ATTR_MASK); } diff --git a/include/hw/pci-host/pam.h b/include/hw/pci-host/pam.h index 6116c63..b918029 100644 --- a/include/hw/pci-host/pam.h +++ b/include/hw/pci-host/pam.h @@ -53,6 +53,8 @@ #include "qemu-common.h" #include "exec/memory.h" +/* #define DEBUG_PAM */ + #define SMRAM_C_BASE 0xa0000 #define SMRAM_C_END 0xc0000 #define SMRAM_C_SIZE 0x20000 @@ -82,8 +84,14 @@ #define SMRAM_C_BASE_SEG ((uint8_t)0x2) /* hardwired to b010 */ typedef struct PAMMemoryRegion { - MemoryRegion alias[4]; /* index = PAM value */ + MemoryRegion region[4]; /* index = PAM value */ unsigned current; + hwaddr pam_offset; + MemoryRegion *mr_write_to; + hwaddr write_offset; + MemoryRegion *mr_read_from; + hwaddr read_offset; + MemoryListener listener; } PAMMemoryRegion; void init_pam(DeviceState *dev, MemoryRegion *ram, MemoryRegion *system, -- 1.9.1