aboutsummaryrefslogtreecommitdiff
path: root/hw/s390x
diff options
context:
space:
mode:
Diffstat (limited to 'hw/s390x')
-rw-r--r--hw/s390x/s390-pci-bus.c223
-rw-r--r--hw/s390x/s390-pci-bus.h16
-rw-r--r--hw/s390x/s390-pci-inst.c64
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;
}