diff options
Diffstat (limited to 'hw/s390x')
-rw-r--r-- | hw/s390x/s390-pci-bus.c | 223 | ||||
-rw-r--r-- | hw/s390x/s390-pci-bus.h | 16 | ||||
-rw-r--r-- | hw/s390x/s390-pci-inst.c | 64 |
3 files changed, 225 insertions, 78 deletions
diff --git a/hw/s390x/s390-pci-bus.c b/hw/s390x/s390-pci-bus.c index 7d9c65e719..e7ef7d28d9 100644 --- a/hw/s390x/s390-pci-bus.c +++ b/hw/s390x/s390-pci-bus.c @@ -309,49 +309,186 @@ static uint64_t get_st_pto(uint64_t entry) : 0; } -static uint64_t s390_guest_io_table_walk(uint64_t guest_iota, - uint64_t guest_dma_address) +static bool rt_entry_isvalid(uint64_t entry) { - uint64_t sto_a, pto_a, px_a; - uint64_t sto, pto, pte; - uint32_t rtx, sx, px; - - rtx = calc_rtx(guest_dma_address); - sx = calc_sx(guest_dma_address); - px = calc_px(guest_dma_address); - - sto_a = guest_iota + rtx * sizeof(uint64_t); - sto = address_space_ldq(&address_space_memory, sto_a, - MEMTXATTRS_UNSPECIFIED, NULL); - sto = get_rt_sto(sto); - if (!sto) { - pte = 0; + return (entry & ZPCI_TABLE_VALID_MASK) == ZPCI_TABLE_VALID; +} + +static bool pt_entry_isvalid(uint64_t entry) +{ + return (entry & ZPCI_PTE_VALID_MASK) == ZPCI_PTE_VALID; +} + +static bool entry_isprotected(uint64_t entry) +{ + return (entry & ZPCI_TABLE_PROT_MASK) == ZPCI_TABLE_PROTECTED; +} + +/* ett is expected table type, -1 page table, 0 segment table, 1 region table */ +static uint64_t get_table_index(uint64_t iova, int8_t ett) +{ + switch (ett) { + case ZPCI_ETT_PT: + return calc_px(iova); + case ZPCI_ETT_ST: + return calc_sx(iova); + case ZPCI_ETT_RT: + return calc_rtx(iova); + } + + return -1; +} + +static bool entry_isvalid(uint64_t entry, int8_t ett) +{ + switch (ett) { + case ZPCI_ETT_PT: + return pt_entry_isvalid(entry); + case ZPCI_ETT_ST: + case ZPCI_ETT_RT: + return rt_entry_isvalid(entry); + } + + return false; +} + +/* Return true if address translation is done */ +static bool translate_iscomplete(uint64_t entry, int8_t ett) +{ + switch (ett) { + case 0: + return (entry & ZPCI_TABLE_FC) ? true : false; + case 1: + return false; + } + + return true; +} + +static uint64_t get_frame_size(int8_t ett) +{ + switch (ett) { + case ZPCI_ETT_PT: + return 1ULL << 12; + case ZPCI_ETT_ST: + return 1ULL << 20; + case ZPCI_ETT_RT: + return 1ULL << 31; + } + + return 0; +} + +static uint64_t get_next_table_origin(uint64_t entry, int8_t ett) +{ + switch (ett) { + case ZPCI_ETT_PT: + return entry & ZPCI_PTE_ADDR_MASK; + case ZPCI_ETT_ST: + return get_st_pto(entry); + case ZPCI_ETT_RT: + return get_rt_sto(entry); + } + + return 0; +} + +/** + * table_translate: do translation within one table and return the following + * table origin + * + * @entry: the entry being translated, the result is stored in this. + * @to: the address of table origin. + * @ett: expected table type, 1 region table, 0 segment table and -1 page table. + * @error: error code + */ +static uint64_t table_translate(S390IOTLBEntry *entry, uint64_t to, int8_t ett, + uint16_t *error) +{ + uint64_t tx, te, nto = 0; + uint16_t err = 0; + + tx = get_table_index(entry->iova, ett); + te = address_space_ldq(&address_space_memory, to + tx * sizeof(uint64_t), + MEMTXATTRS_UNSPECIFIED, NULL); + + if (!te) { + err = ERR_EVENT_INVALTE; goto out; } - pto_a = sto + sx * sizeof(uint64_t); - pto = address_space_ldq(&address_space_memory, pto_a, - MEMTXATTRS_UNSPECIFIED, NULL); - pto = get_st_pto(pto); - if (!pto) { - pte = 0; + if (!entry_isvalid(te, ett)) { + entry->perm &= IOMMU_NONE; goto out; } - px_a = pto + px * sizeof(uint64_t); - pte = address_space_ldq(&address_space_memory, px_a, - MEMTXATTRS_UNSPECIFIED, NULL); + if (ett == ZPCI_ETT_RT && ((te & ZPCI_TABLE_LEN_RTX) != ZPCI_TABLE_LEN_RTX + || te & ZPCI_TABLE_OFFSET_MASK)) { + err = ERR_EVENT_INVALTL; + goto out; + } + nto = get_next_table_origin(te, ett); + if (!nto) { + err = ERR_EVENT_TT; + goto out; + } + + if (entry_isprotected(te)) { + entry->perm &= IOMMU_RO; + } else { + entry->perm &= IOMMU_RW; + } + + if (translate_iscomplete(te, ett)) { + switch (ett) { + case ZPCI_ETT_PT: + entry->translated_addr = te & ZPCI_PTE_ADDR_MASK; + break; + case ZPCI_ETT_ST: + entry->translated_addr = (te & ZPCI_SFAA_MASK) | + (entry->iova & ~ZPCI_SFAA_MASK); + break; + } + nto = 0; + } out: - return pte; + if (err) { + entry->perm = IOMMU_NONE; + *error = err; + } + entry->len = get_frame_size(ett); + return nto; +} + +uint16_t s390_guest_io_table_walk(uint64_t g_iota, hwaddr addr, + S390IOTLBEntry *entry) +{ + uint64_t to = s390_pci_get_table_origin(g_iota); + int8_t ett = 1; + uint16_t error = 0; + + entry->iova = addr & PAGE_MASK; + entry->translated_addr = 0; + entry->perm = IOMMU_RW; + + if (entry_isprotected(g_iota)) { + entry->perm &= IOMMU_RO; + } + + while (to) { + to = table_translate(entry, to, ett--, &error); + } + + return error; } static IOMMUTLBEntry s390_translate_iommu(IOMMUMemoryRegion *mr, hwaddr addr, IOMMUAccessFlags flag) { - uint64_t pte; - uint32_t flags; S390PCIIOMMU *iommu = container_of(mr, S390PCIIOMMU, iommu_mr); + S390IOTLBEntry entry; + uint16_t error = 0; IOMMUTLBEntry ret = { .target_as = &address_space_memory, .iova = 0, @@ -374,26 +511,26 @@ static IOMMUTLBEntry s390_translate_iommu(IOMMUMemoryRegion *mr, hwaddr addr, DPRINTF("iommu trans addr 0x%" PRIx64 "\n", addr); if (addr < iommu->pba || addr > iommu->pal) { - return ret; + error = ERR_EVENT_OORANGE; + goto err; } - pte = s390_guest_io_table_walk(s390_pci_get_table_origin(iommu->g_iota), - addr); - if (!pte) { - return ret; - } + error = s390_guest_io_table_walk(iommu->g_iota, addr, &entry); - flags = pte & ZPCI_PTE_FLAG_MASK; - ret.iova = addr; - ret.translated_addr = pte & ZPCI_PTE_ADDR_MASK; - ret.addr_mask = 0xfff; + ret.iova = entry.iova; + ret.translated_addr = entry.translated_addr; + ret.addr_mask = entry.len - 1; + ret.perm = entry.perm; - if (flags & ZPCI_PTE_INVALID) { - ret.perm = IOMMU_NONE; - } else { - ret.perm = IOMMU_RW; + if (flag != IOMMU_NONE && !(flag & ret.perm)) { + error = ERR_EVENT_TPROTE; + } +err: + if (error) { + iommu->pbdev->state = ZPCI_FS_ERROR; + s390_pci_generate_error_event(error, iommu->pbdev->fh, + iommu->pbdev->fid, addr, 0); } - return ret; } diff --git a/hw/s390x/s390-pci-bus.h b/hw/s390x/s390-pci-bus.h index 2993f0ddef..7ed577c806 100644 --- a/hw/s390x/s390-pci-bus.h +++ b/hw/s390x/s390-pci-bus.h @@ -148,6 +148,8 @@ enum ZpciIoatDtype { #define ZPCI_STE_FLAG_MASK 0x7ffULL #define ZPCI_STE_ADDR_MASK (~ZPCI_STE_FLAG_MASK) +#define ZPCI_SFAA_MASK (~((1ULL << 20) - 1)) + /* I/O Page tables */ #define ZPCI_PTE_VALID_MASK 0x400 #define ZPCI_PTE_INVALID 0x400 @@ -165,10 +167,15 @@ enum ZpciIoatDtype { #define ZPCI_TABLE_INVALID 0x20 #define ZPCI_TABLE_PROTECTED 0x200 #define ZPCI_TABLE_UNPROTECTED 0x000 +#define ZPCI_TABLE_FC 0x400 #define ZPCI_TABLE_VALID_MASK 0x20 #define ZPCI_TABLE_PROT_MASK 0x200 +#define ZPCI_ETT_RT 1 +#define ZPCI_ETT_ST 0 +#define ZPCI_ETT_PT -1 + /* PCI Function States * * reserved: default; device has just been plugged or is in progress of being @@ -253,6 +260,13 @@ typedef struct S390MsixInfo { uint32_t pba_offset; } S390MsixInfo; +typedef struct S390IOTLBEntry { + uint64_t iova; + uint64_t translated_addr; + uint64_t len; + uint64_t perm; +} S390IOTLBEntry; + typedef struct S390PCIBusDevice S390PCIBusDevice; typedef struct S390PCIIOMMU { Object parent_obj; @@ -320,6 +334,8 @@ void s390_pci_iommu_enable(S390PCIIOMMU *iommu); void s390_pci_iommu_disable(S390PCIIOMMU *iommu); void s390_pci_generate_error_event(uint16_t pec, uint32_t fh, uint32_t fid, uint64_t faddr, uint32_t e); +uint16_t s390_guest_io_table_walk(uint64_t g_iota, hwaddr addr, + S390IOTLBEntry *entry); S390PCIBusDevice *s390_pci_find_dev_by_idx(S390pciState *s, uint32_t idx); S390PCIBusDevice *s390_pci_find_dev_by_fh(S390pciState *s, uint32_t fh); S390PCIBusDevice *s390_pci_find_dev_by_fid(S390pciState *s, uint32_t fid); diff --git a/hw/s390x/s390-pci-inst.c b/hw/s390x/s390-pci-inst.c index be449210d9..1d33a89351 100644 --- a/hw/s390x/s390-pci-inst.c +++ b/hw/s390x/s390-pci-inst.c @@ -575,23 +575,23 @@ int rpcit_service_call(S390CPU *cpu, uint8_t r1, uint8_t r2, uintptr_t ra) { CPUS390XState *env = &cpu->env; uint32_t fh; + uint16_t error = 0; S390PCIBusDevice *pbdev; S390PCIIOMMU *iommu; + S390IOTLBEntry entry; hwaddr start, end; - IOMMUTLBEntry entry; - IOMMUMemoryRegion *iommu_mr; - IOMMUMemoryRegionClass *imrc; + IOMMUTLBEntry notify; cpu_synchronize_state(CPU(cpu)); if (env->psw.mask & PSW_MASK_PSTATE) { s390_program_interrupt(env, PGM_PRIVILEGED, 4, ra); - goto out; + return 0; } if (r2 & 0x1) { s390_program_interrupt(env, PGM_SPECIFICATION, 4, ra); - goto out; + return 0; } fh = env->regs[r1] >> 32; @@ -602,7 +602,7 @@ int rpcit_service_call(S390CPU *cpu, uint8_t r1, uint8_t r2, uintptr_t ra) if (!pbdev) { DPRINTF("rpcit no pci dev\n"); setcc(cpu, ZPCI_PCI_LS_INVAL_HANDLE); - goto out; + return 0; } switch (pbdev->state) { @@ -622,44 +622,38 @@ int rpcit_service_call(S390CPU *cpu, uint8_t r1, uint8_t r2, uintptr_t ra) iommu = pbdev->iommu; if (!iommu->g_iota) { - pbdev->state = ZPCI_FS_ERROR; - setcc(cpu, ZPCI_PCI_LS_ERR); - s390_set_status_code(env, r1, ZPCI_PCI_ST_INSUF_RES); - s390_pci_generate_error_event(ERR_EVENT_INVALAS, pbdev->fh, pbdev->fid, - start, 0); - goto out; + error = ERR_EVENT_INVALAS; + goto err; } if (end < iommu->pba || start > iommu->pal) { - pbdev->state = ZPCI_FS_ERROR; - setcc(cpu, ZPCI_PCI_LS_ERR); - s390_set_status_code(env, r1, ZPCI_PCI_ST_INSUF_RES); - s390_pci_generate_error_event(ERR_EVENT_OORANGE, pbdev->fh, pbdev->fid, - start, 0); - goto out; + error = ERR_EVENT_OORANGE; + goto err; } - iommu_mr = &iommu->iommu_mr; - imrc = IOMMU_MEMORY_REGION_GET_CLASS(iommu_mr); - while (start < end) { - entry = imrc->translate(iommu_mr, start, IOMMU_NONE); - - if (!entry.translated_addr) { - pbdev->state = ZPCI_FS_ERROR; - setcc(cpu, ZPCI_PCI_LS_ERR); - s390_set_status_code(env, r1, ZPCI_PCI_ST_INSUF_RES); - s390_pci_generate_error_event(ERR_EVENT_SERR, pbdev->fh, pbdev->fid, - start, ERR_EVENT_Q_BIT); - goto out; + error = s390_guest_io_table_walk(iommu->g_iota, start, &entry); + if (error) { + break; } - - memory_region_notify_iommu(iommu_mr, entry); - start += entry.addr_mask + 1; + notify.target_as = &address_space_memory; + notify.iova = entry.iova; + notify.translated_addr = entry.translated_addr; + notify.addr_mask = entry.len - 1; + notify.perm = entry.perm; + memory_region_notify_iommu(&iommu->iommu_mr, notify); + start += entry.len; } - setcc(cpu, ZPCI_PCI_LS_OK); -out: +err: + if (error) { + pbdev->state = ZPCI_FS_ERROR; + setcc(cpu, ZPCI_PCI_LS_ERR); + s390_set_status_code(env, r1, ZPCI_PCI_ST_FUNC_IN_ERR); + s390_pci_generate_error_event(error, pbdev->fh, pbdev->fid, start, 0); + } else { + setcc(cpu, ZPCI_PCI_LS_OK); + } return 0; } |