s390x/pci: fixup the code walking IOMMU tables

Current s390x PCI IOMMU code is lack of flags' checking, including:
1) protection bit
2) table length
3) table offset
4) intermediate tables' invalid bit
5) format control bit

This patch introduces a new struct named S390IOTLBEntry, and makes up
these missed checkings. At the same time, inform the guest with the
corresponding error number when the check fails. Finally, in order to
get the error number, we export s390_guest_io_table_walk().

Reviewed-by: Pierre Morel <pmorel@linux.vnet.ibm.com>
Signed-off-by: Yi Min Zhao <zyimin@linux.vnet.ibm.com>
Message-Id: <20180205072258.5968-2-zyimin@linux.vnet.ibm.com>
Signed-off-by: Cornelia Huck <cohuck@redhat.com>
master
Yi Min Zhao 2018-02-05 15:22:56 +08:00 committed by Cornelia Huck
parent 46a99c9f73
commit 0125861eac
3 changed files with 224 additions and 77 deletions

View File

@ -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;
return (entry & ZPCI_TABLE_VALID_MASK) == ZPCI_TABLE_VALID;
}
rtx = calc_rtx(guest_dma_address);
sx = calc_sx(guest_dma_address);
px = calc_px(guest_dma_address);
static bool pt_entry_isvalid(uint64_t entry)
{
return (entry & ZPCI_PTE_VALID_MASK) == ZPCI_PTE_VALID;
}
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;
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);
ret.iova = entry.iova;
ret.translated_addr = entry.translated_addr;
ret.addr_mask = entry.len - 1;
ret.perm = entry.perm;
if (flag != IOMMU_NONE && !(flag & ret.perm)) {
error = ERR_EVENT_TPROTE;
}
flags = pte & ZPCI_PTE_FLAG_MASK;
ret.iova = addr;
ret.translated_addr = pte & ZPCI_PTE_ADDR_MASK;
ret.addr_mask = 0xfff;
if (flags & ZPCI_PTE_INVALID) {
ret.perm = IOMMU_NONE;
} else {
ret.perm = IOMMU_RW;
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;
}

View File

@ -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);

View File

@ -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;
}