diff options
Diffstat (limited to 'hw/ppc/spapr_iommu.c')
| -rw-r--r-- | hw/ppc/spapr_iommu.c | 181 |
1 files changed, 137 insertions, 44 deletions
diff --git a/hw/ppc/spapr_iommu.c b/hw/ppc/spapr_iommu.c index 2887ad4827..9e49ec4a5c 100644 --- a/hw/ppc/spapr_iommu.c +++ b/hw/ppc/spapr_iommu.c @@ -35,6 +35,9 @@ enum sPAPRTCEAccess { SPAPR_TCE_RW = 3, }; +#define IOMMU_PAGE_SIZE(shift) (1ULL << (shift)) +#define IOMMU_PAGE_MASK(shift) (~(IOMMU_PAGE_SIZE(shift) - 1)) + static QLIST_HEAD(spapr_tce_tables, sPAPRTCETable) spapr_tce_tables; static sPAPRTCETable *spapr_tce_find_by_liobn(uint32_t liobn) @@ -70,12 +73,14 @@ static IOMMUTLBEntry spapr_tce_translate_iommu(MemoryRegion *iommu, hwaddr addr) if (tcet->bypass) { ret.perm = IOMMU_RW; - } else if (addr < tcet->window_size) { + } else if ((addr >> tcet->page_shift) < tcet->nb_table) { /* Check if we are in bound */ - tce = tcet->table[addr >> SPAPR_TCE_PAGE_SHIFT]; - ret.iova = addr & ~SPAPR_TCE_PAGE_MASK; - ret.translated_addr = tce & ~SPAPR_TCE_PAGE_MASK; - ret.addr_mask = SPAPR_TCE_PAGE_MASK; + hwaddr page_mask = IOMMU_PAGE_MASK(tcet->page_shift); + + tce = tcet->table[addr >> tcet->page_shift]; + ret.iova = addr & page_mask; + ret.translated_addr = tce & page_mask; + ret.addr_mask = ~page_mask; ret.perm = tce; } trace_spapr_iommu_xlate(tcet->liobn, addr, ret.iova, ret.perm, @@ -84,24 +89,14 @@ static IOMMUTLBEntry spapr_tce_translate_iommu(MemoryRegion *iommu, hwaddr addr) return ret; } -static int spapr_tce_table_pre_load(void *opaque) -{ - sPAPRTCETable *tcet = SPAPR_TCE_TABLE(opaque); - - tcet->nb_table = tcet->window_size >> SPAPR_TCE_PAGE_SHIFT; - - return 0; -} - static const VMStateDescription vmstate_spapr_tce_table = { .name = "spapr_iommu", - .version_id = 1, - .minimum_version_id = 1, - .pre_load = spapr_tce_table_pre_load, - .fields = (VMStateField[]) { + .version_id = 2, + .minimum_version_id = 2, + .fields = (VMStateField []) { /* Sanity check */ VMSTATE_UINT32_EQUAL(liobn, sPAPRTCETable), - VMSTATE_UINT32_EQUAL(window_size, sPAPRTCETable), + VMSTATE_UINT32_EQUAL(nb_table, sPAPRTCETable), /* IOMMU state */ VMSTATE_BOOL(bypass, sPAPRTCETable), @@ -121,28 +116,33 @@ static int spapr_tce_table_realize(DeviceState *dev) if (kvm_enabled()) { tcet->table = kvmppc_create_spapr_tce(tcet->liobn, - tcet->window_size, + tcet->nb_table << + tcet->page_shift, &tcet->fd); } if (!tcet->table) { - size_t table_size = (tcet->window_size >> SPAPR_TCE_PAGE_SHIFT) - * sizeof(uint64_t); + size_t table_size = tcet->nb_table * sizeof(uint64_t); tcet->table = g_malloc0(table_size); } - tcet->nb_table = tcet->window_size >> SPAPR_TCE_PAGE_SHIFT; trace_spapr_iommu_new_table(tcet->liobn, tcet, tcet->table, tcet->fd); memory_region_init_iommu(&tcet->iommu, OBJECT(dev), &spapr_iommu_ops, - "iommu-spapr", UINT64_MAX); + "iommu-spapr", ram_size); QLIST_INSERT_HEAD(&spapr_tce_tables, tcet, list); + vmstate_register(DEVICE(tcet), tcet->liobn, &vmstate_spapr_tce_table, + tcet); + return 0; } -sPAPRTCETable *spapr_tce_new_table(DeviceState *owner, uint32_t liobn, size_t window_size) +sPAPRTCETable *spapr_tce_new_table(DeviceState *owner, uint32_t liobn, + uint64_t bus_offset, + uint32_t page_shift, + uint32_t nb_table) { sPAPRTCETable *tcet; @@ -152,17 +152,19 @@ sPAPRTCETable *spapr_tce_new_table(DeviceState *owner, uint32_t liobn, size_t wi return NULL; } - if (!window_size) { + if (!nb_table) { return NULL; } tcet = SPAPR_TCE_TABLE(object_new(TYPE_SPAPR_TCE_TABLE)); tcet->liobn = liobn; - tcet->window_size = window_size; + tcet->bus_offset = bus_offset; + tcet->page_shift = page_shift; + tcet->nb_table = nb_table; object_property_add_child(OBJECT(owner), "tce-table", OBJECT(tcet), NULL); - qdev_init_nofail(DEVICE(tcet)); + object_property_set_bool(OBJECT(tcet), true, "realized", NULL); return tcet; } @@ -175,7 +177,7 @@ static void spapr_tce_table_finalize(Object *obj) if (!kvm_enabled() || (kvmppc_remove_spapr_tce(tcet->table, tcet->fd, - tcet->window_size) != 0)) { + tcet->nb_table) != 0)) { g_free(tcet->table); } } @@ -193,8 +195,7 @@ void spapr_tce_set_bypass(sPAPRTCETable *tcet, bool bypass) static void spapr_tce_reset(DeviceState *dev) { sPAPRTCETable *tcet = SPAPR_TCE_TABLE(dev); - size_t table_size = (tcet->window_size >> SPAPR_TCE_PAGE_SHIFT) - * sizeof(uint64_t); + size_t table_size = tcet->nb_table * sizeof(uint64_t); tcet->bypass = false; memset(tcet->table, 0, table_size); @@ -204,25 +205,110 @@ static target_ulong put_tce_emu(sPAPRTCETable *tcet, target_ulong ioba, target_ulong tce) { IOMMUTLBEntry entry; + hwaddr page_mask = IOMMU_PAGE_MASK(tcet->page_shift); + unsigned long index = (ioba - tcet->bus_offset) >> tcet->page_shift; - if (ioba >= tcet->window_size) { + if (index >= tcet->nb_table) { hcall_dprintf("spapr_vio_put_tce on out-of-bounds IOBA 0x" TARGET_FMT_lx "\n", ioba); return H_PARAMETER; } - tcet->table[ioba >> SPAPR_TCE_PAGE_SHIFT] = tce; + tcet->table[index] = tce; entry.target_as = &address_space_memory, - entry.iova = ioba & ~SPAPR_TCE_PAGE_MASK; - entry.translated_addr = tce & ~SPAPR_TCE_PAGE_MASK; - entry.addr_mask = SPAPR_TCE_PAGE_MASK; + entry.iova = ioba & page_mask; + entry.translated_addr = tce & page_mask; + entry.addr_mask = ~page_mask; entry.perm = tce; memory_region_notify_iommu(&tcet->iommu, entry); return H_SUCCESS; } +static target_ulong h_put_tce_indirect(PowerPCCPU *cpu, + sPAPREnvironment *spapr, + target_ulong opcode, target_ulong *args) +{ + int i; + target_ulong liobn = args[0]; + target_ulong ioba = args[1]; + target_ulong ioba1 = ioba; + target_ulong tce_list = args[2]; + target_ulong npages = args[3]; + target_ulong ret = H_PARAMETER; + sPAPRTCETable *tcet = spapr_tce_find_by_liobn(liobn); + CPUState *cs = CPU(cpu); + hwaddr page_mask, page_size; + + if (!tcet) { + return H_PARAMETER; + } + + if ((npages > 512) || (tce_list & SPAPR_TCE_PAGE_MASK)) { + return H_PARAMETER; + } + + page_mask = IOMMU_PAGE_MASK(tcet->page_shift); + page_size = IOMMU_PAGE_SIZE(tcet->page_shift); + ioba &= page_mask; + + for (i = 0; i < npages; ++i, ioba += page_size) { + target_ulong off = (tce_list & ~SPAPR_TCE_RW) + + i * sizeof(target_ulong); + target_ulong tce = ldq_phys(cs->as, off); + + ret = put_tce_emu(tcet, ioba, tce); + if (ret) { + break; + } + } + + /* Trace last successful or the first problematic entry */ + i = i ? (i - 1) : 0; + trace_spapr_iommu_indirect(liobn, ioba1, tce_list, i, + ldq_phys(cs->as, + tce_list + i * sizeof(target_ulong)), + ret); + + return ret; +} + +static target_ulong h_stuff_tce(PowerPCCPU *cpu, sPAPREnvironment *spapr, + target_ulong opcode, target_ulong *args) +{ + int i; + target_ulong liobn = args[0]; + target_ulong ioba = args[1]; + target_ulong tce_value = args[2]; + target_ulong npages = args[3]; + target_ulong ret = H_PARAMETER; + sPAPRTCETable *tcet = spapr_tce_find_by_liobn(liobn); + hwaddr page_mask, page_size; + + if (!tcet) { + return H_PARAMETER; + } + + if (npages > tcet->nb_table) { + return H_PARAMETER; + } + + page_mask = IOMMU_PAGE_MASK(tcet->page_shift); + page_size = IOMMU_PAGE_SIZE(tcet->page_shift); + ioba &= page_mask; + + for (i = 0; i < npages; ++i, ioba += page_size) { + ret = put_tce_emu(tcet, ioba, tce_value); + if (ret) { + break; + } + } + trace_spapr_iommu_stuff(liobn, ioba, tce_value, npages, ret); + + return ret; +} + static target_ulong h_put_tce(PowerPCCPU *cpu, sPAPREnvironment *spapr, target_ulong opcode, target_ulong *args) { @@ -232,9 +318,11 @@ static target_ulong h_put_tce(PowerPCCPU *cpu, sPAPREnvironment *spapr, target_ulong ret = H_PARAMETER; sPAPRTCETable *tcet = spapr_tce_find_by_liobn(liobn); - ioba &= ~(SPAPR_TCE_PAGE_SIZE - 1); - if (tcet) { + hwaddr page_mask = IOMMU_PAGE_MASK(tcet->page_shift); + + ioba &= page_mask; + ret = put_tce_emu(tcet, ioba, tce); } trace_spapr_iommu_put(liobn, ioba, tce, ret); @@ -245,13 +333,15 @@ static target_ulong h_put_tce(PowerPCCPU *cpu, sPAPREnvironment *spapr, static target_ulong get_tce_emu(sPAPRTCETable *tcet, target_ulong ioba, target_ulong *tce) { - if (ioba >= tcet->window_size) { + unsigned long index = (ioba - tcet->bus_offset) >> tcet->page_shift; + + if (index >= tcet->nb_table) { hcall_dprintf("spapr_iommu_get_tce on out-of-bounds IOBA 0x" TARGET_FMT_lx "\n", ioba); return H_PARAMETER; } - *tce = tcet->table[ioba >> SPAPR_TCE_PAGE_SHIFT]; + *tce = tcet->table[index]; return H_SUCCESS; } @@ -265,9 +355,11 @@ static target_ulong h_get_tce(PowerPCCPU *cpu, sPAPREnvironment *spapr, target_ulong ret = H_PARAMETER; sPAPRTCETable *tcet = spapr_tce_find_by_liobn(liobn); - ioba &= ~(SPAPR_TCE_PAGE_SIZE - 1); - if (tcet) { + hwaddr page_mask = IOMMU_PAGE_MASK(tcet->page_shift); + + ioba &= page_mask; + ret = get_tce_emu(tcet, ioba, &tce); if (!ret) { args[0] = tce; @@ -316,13 +408,12 @@ int spapr_tcet_dma_dt(void *fdt, int node_off, const char *propname, } return spapr_dma_dt(fdt, node_off, propname, - tcet->liobn, 0, tcet->window_size); + tcet->liobn, 0, tcet->nb_table << tcet->page_shift); } static void spapr_tce_table_class_init(ObjectClass *klass, void *data) { DeviceClass *dc = DEVICE_CLASS(klass); - dc->vmsd = &vmstate_spapr_tce_table; dc->init = spapr_tce_table_realize; dc->reset = spapr_tce_reset; @@ -331,6 +422,8 @@ static void spapr_tce_table_class_init(ObjectClass *klass, void *data) /* hcall-tce */ spapr_register_hypercall(H_PUT_TCE, h_put_tce); spapr_register_hypercall(H_GET_TCE, h_get_tce); + spapr_register_hypercall(H_PUT_TCE_INDIRECT, h_put_tce_indirect); + spapr_register_hypercall(H_STUFF_TCE, h_stuff_tce); } static TypeInfo spapr_tce_table_info = { |