mirror of
https://github.com/qemu/qemu.git
synced 2024-11-25 03:43:37 +08:00
6e92c70c37
Common function measurement block is used to report zPCI internal counters of successful pcilg/stg/stb and rpcit instructions to a memory location provided by the program. This patch introduces a new ZpciFmb structure and schedules a timer callback to copy the zPCI measures to the FMB in the guest memory at an interval time set to 4s. An error while attemping to update the FMB, would generate an error event to the guest. The pcilg/stg/stb and rpcit interception handlers increase the related counter on a successful call. The guest shall pass a null FMBA (FMB address) in the FIB (Function Information Block) when it issues a Modify PCI Function Control instruction to switch off FMB and stop the corresponding timer. Signed-off-by: Yi Min Zhao <zyimin@linux.ibm.com> Signed-off-by: Pierre Morel <pmorel@linux.ibm.com> Message-Id: <1546969050-8884-2-git-send-email-pmorel@linux.ibm.com> Acked-by: David Hildenbrand <david@redhat.com> Reviewed-by: Collin Walling <walling@linux.ibm.com> Signed-off-by: Cornelia Huck <cohuck@redhat.com>
1251 lines
36 KiB
C
1251 lines
36 KiB
C
/*
|
|
* s390 PCI instructions
|
|
*
|
|
* Copyright 2014 IBM Corp.
|
|
* Author(s): Frank Blaschka <frank.blaschka@de.ibm.com>
|
|
* Hong Bo Li <lihbbj@cn.ibm.com>
|
|
* Yi Min Zhao <zyimin@cn.ibm.com>
|
|
*
|
|
* This work is licensed under the terms of the GNU GPL, version 2 or (at
|
|
* your option) any later version. See the COPYING file in the top-level
|
|
* directory.
|
|
*/
|
|
|
|
#include "qemu/osdep.h"
|
|
#include "qemu-common.h"
|
|
#include "cpu.h"
|
|
#include "s390-pci-inst.h"
|
|
#include "s390-pci-bus.h"
|
|
#include "exec/memory-internal.h"
|
|
#include "qemu/error-report.h"
|
|
#include "sysemu/hw_accel.h"
|
|
#include "hw/s390x/tod.h"
|
|
|
|
#ifndef DEBUG_S390PCI_INST
|
|
#define DEBUG_S390PCI_INST 0
|
|
#endif
|
|
|
|
#define DPRINTF(fmt, ...) \
|
|
do { \
|
|
if (DEBUG_S390PCI_INST) { \
|
|
fprintf(stderr, "s390pci-inst: " fmt, ## __VA_ARGS__); \
|
|
} \
|
|
} while (0)
|
|
|
|
static void s390_set_status_code(CPUS390XState *env,
|
|
uint8_t r, uint64_t status_code)
|
|
{
|
|
env->regs[r] &= ~0xff000000ULL;
|
|
env->regs[r] |= (status_code & 0xff) << 24;
|
|
}
|
|
|
|
static int list_pci(ClpReqRspListPci *rrb, uint8_t *cc)
|
|
{
|
|
S390PCIBusDevice *pbdev = NULL;
|
|
S390pciState *s = s390_get_phb();
|
|
uint32_t res_code, initial_l2, g_l2;
|
|
int rc, i;
|
|
uint64_t resume_token;
|
|
|
|
rc = 0;
|
|
if (lduw_p(&rrb->request.hdr.len) != 32) {
|
|
res_code = CLP_RC_LEN;
|
|
rc = -EINVAL;
|
|
goto out;
|
|
}
|
|
|
|
if ((ldl_p(&rrb->request.fmt) & CLP_MASK_FMT) != 0) {
|
|
res_code = CLP_RC_FMT;
|
|
rc = -EINVAL;
|
|
goto out;
|
|
}
|
|
|
|
if ((ldl_p(&rrb->request.fmt) & ~CLP_MASK_FMT) != 0 ||
|
|
ldq_p(&rrb->request.reserved1) != 0) {
|
|
res_code = CLP_RC_RESNOT0;
|
|
rc = -EINVAL;
|
|
goto out;
|
|
}
|
|
|
|
resume_token = ldq_p(&rrb->request.resume_token);
|
|
|
|
if (resume_token) {
|
|
pbdev = s390_pci_find_dev_by_idx(s, resume_token);
|
|
if (!pbdev) {
|
|
res_code = CLP_RC_LISTPCI_BADRT;
|
|
rc = -EINVAL;
|
|
goto out;
|
|
}
|
|
} else {
|
|
pbdev = s390_pci_find_next_avail_dev(s, NULL);
|
|
}
|
|
|
|
if (lduw_p(&rrb->response.hdr.len) < 48) {
|
|
res_code = CLP_RC_8K;
|
|
rc = -EINVAL;
|
|
goto out;
|
|
}
|
|
|
|
initial_l2 = lduw_p(&rrb->response.hdr.len);
|
|
if ((initial_l2 - LIST_PCI_HDR_LEN) % sizeof(ClpFhListEntry)
|
|
!= 0) {
|
|
res_code = CLP_RC_LEN;
|
|
rc = -EINVAL;
|
|
*cc = 3;
|
|
goto out;
|
|
}
|
|
|
|
stl_p(&rrb->response.fmt, 0);
|
|
stq_p(&rrb->response.reserved1, 0);
|
|
stl_p(&rrb->response.mdd, FH_MASK_SHM);
|
|
stw_p(&rrb->response.max_fn, PCI_MAX_FUNCTIONS);
|
|
rrb->response.flags = UID_CHECKING_ENABLED;
|
|
rrb->response.entry_size = sizeof(ClpFhListEntry);
|
|
|
|
i = 0;
|
|
g_l2 = LIST_PCI_HDR_LEN;
|
|
while (g_l2 < initial_l2 && pbdev) {
|
|
stw_p(&rrb->response.fh_list[i].device_id,
|
|
pci_get_word(pbdev->pdev->config + PCI_DEVICE_ID));
|
|
stw_p(&rrb->response.fh_list[i].vendor_id,
|
|
pci_get_word(pbdev->pdev->config + PCI_VENDOR_ID));
|
|
/* Ignore RESERVED devices. */
|
|
stl_p(&rrb->response.fh_list[i].config,
|
|
pbdev->state == ZPCI_FS_STANDBY ? 0 : 1 << 31);
|
|
stl_p(&rrb->response.fh_list[i].fid, pbdev->fid);
|
|
stl_p(&rrb->response.fh_list[i].fh, pbdev->fh);
|
|
|
|
g_l2 += sizeof(ClpFhListEntry);
|
|
/* Add endian check for DPRINTF? */
|
|
DPRINTF("g_l2 %d vendor id 0x%x device id 0x%x fid 0x%x fh 0x%x\n",
|
|
g_l2,
|
|
lduw_p(&rrb->response.fh_list[i].vendor_id),
|
|
lduw_p(&rrb->response.fh_list[i].device_id),
|
|
ldl_p(&rrb->response.fh_list[i].fid),
|
|
ldl_p(&rrb->response.fh_list[i].fh));
|
|
pbdev = s390_pci_find_next_avail_dev(s, pbdev);
|
|
i++;
|
|
}
|
|
|
|
if (!pbdev) {
|
|
resume_token = 0;
|
|
} else {
|
|
resume_token = pbdev->fh & FH_MASK_INDEX;
|
|
}
|
|
stq_p(&rrb->response.resume_token, resume_token);
|
|
stw_p(&rrb->response.hdr.len, g_l2);
|
|
stw_p(&rrb->response.hdr.rsp, CLP_RC_OK);
|
|
out:
|
|
if (rc) {
|
|
DPRINTF("list pci failed rc 0x%x\n", rc);
|
|
stw_p(&rrb->response.hdr.rsp, res_code);
|
|
}
|
|
return rc;
|
|
}
|
|
|
|
int clp_service_call(S390CPU *cpu, uint8_t r2, uintptr_t ra)
|
|
{
|
|
ClpReqHdr *reqh;
|
|
ClpRspHdr *resh;
|
|
S390PCIBusDevice *pbdev;
|
|
uint32_t req_len;
|
|
uint32_t res_len;
|
|
uint8_t buffer[4096 * 2];
|
|
uint8_t cc = 0;
|
|
CPUS390XState *env = &cpu->env;
|
|
S390pciState *s = s390_get_phb();
|
|
int i;
|
|
|
|
if (env->psw.mask & PSW_MASK_PSTATE) {
|
|
s390_program_interrupt(env, PGM_PRIVILEGED, 4, ra);
|
|
return 0;
|
|
}
|
|
|
|
if (s390_cpu_virt_mem_read(cpu, env->regs[r2], r2, buffer, sizeof(*reqh))) {
|
|
s390_cpu_virt_mem_handle_exc(cpu, ra);
|
|
return 0;
|
|
}
|
|
reqh = (ClpReqHdr *)buffer;
|
|
req_len = lduw_p(&reqh->len);
|
|
if (req_len < 16 || req_len > 8184 || (req_len % 8 != 0)) {
|
|
s390_program_interrupt(env, PGM_OPERAND, 4, ra);
|
|
return 0;
|
|
}
|
|
|
|
if (s390_cpu_virt_mem_read(cpu, env->regs[r2], r2, buffer,
|
|
req_len + sizeof(*resh))) {
|
|
s390_cpu_virt_mem_handle_exc(cpu, ra);
|
|
return 0;
|
|
}
|
|
resh = (ClpRspHdr *)(buffer + req_len);
|
|
res_len = lduw_p(&resh->len);
|
|
if (res_len < 8 || res_len > 8176 || (res_len % 8 != 0)) {
|
|
s390_program_interrupt(env, PGM_OPERAND, 4, ra);
|
|
return 0;
|
|
}
|
|
if ((req_len + res_len) > 8192) {
|
|
s390_program_interrupt(env, PGM_OPERAND, 4, ra);
|
|
return 0;
|
|
}
|
|
|
|
if (s390_cpu_virt_mem_read(cpu, env->regs[r2], r2, buffer,
|
|
req_len + res_len)) {
|
|
s390_cpu_virt_mem_handle_exc(cpu, ra);
|
|
return 0;
|
|
}
|
|
|
|
if (req_len != 32) {
|
|
stw_p(&resh->rsp, CLP_RC_LEN);
|
|
goto out;
|
|
}
|
|
|
|
switch (lduw_p(&reqh->cmd)) {
|
|
case CLP_LIST_PCI: {
|
|
ClpReqRspListPci *rrb = (ClpReqRspListPci *)buffer;
|
|
list_pci(rrb, &cc);
|
|
break;
|
|
}
|
|
case CLP_SET_PCI_FN: {
|
|
ClpReqSetPci *reqsetpci = (ClpReqSetPci *)reqh;
|
|
ClpRspSetPci *ressetpci = (ClpRspSetPci *)resh;
|
|
|
|
pbdev = s390_pci_find_dev_by_fh(s, ldl_p(&reqsetpci->fh));
|
|
if (!pbdev) {
|
|
stw_p(&ressetpci->hdr.rsp, CLP_RC_SETPCIFN_FH);
|
|
goto out;
|
|
}
|
|
|
|
switch (reqsetpci->oc) {
|
|
case CLP_SET_ENABLE_PCI_FN:
|
|
switch (reqsetpci->ndas) {
|
|
case 0:
|
|
stw_p(&ressetpci->hdr.rsp, CLP_RC_SETPCIFN_DMAAS);
|
|
goto out;
|
|
case 1:
|
|
break;
|
|
default:
|
|
stw_p(&ressetpci->hdr.rsp, CLP_RC_SETPCIFN_RES);
|
|
goto out;
|
|
}
|
|
|
|
if (pbdev->fh & FH_MASK_ENABLE) {
|
|
stw_p(&ressetpci->hdr.rsp, CLP_RC_SETPCIFN_FHOP);
|
|
goto out;
|
|
}
|
|
|
|
pbdev->fh |= FH_MASK_ENABLE;
|
|
pbdev->state = ZPCI_FS_ENABLED;
|
|
stl_p(&ressetpci->fh, pbdev->fh);
|
|
stw_p(&ressetpci->hdr.rsp, CLP_RC_OK);
|
|
break;
|
|
case CLP_SET_DISABLE_PCI_FN:
|
|
if (!(pbdev->fh & FH_MASK_ENABLE)) {
|
|
stw_p(&ressetpci->hdr.rsp, CLP_RC_SETPCIFN_FHOP);
|
|
goto out;
|
|
}
|
|
device_reset(DEVICE(pbdev));
|
|
pbdev->fh &= ~FH_MASK_ENABLE;
|
|
pbdev->state = ZPCI_FS_DISABLED;
|
|
stl_p(&ressetpci->fh, pbdev->fh);
|
|
stw_p(&ressetpci->hdr.rsp, CLP_RC_OK);
|
|
break;
|
|
default:
|
|
DPRINTF("unknown set pci command\n");
|
|
stw_p(&ressetpci->hdr.rsp, CLP_RC_SETPCIFN_FHOP);
|
|
break;
|
|
}
|
|
break;
|
|
}
|
|
case CLP_QUERY_PCI_FN: {
|
|
ClpReqQueryPci *reqquery = (ClpReqQueryPci *)reqh;
|
|
ClpRspQueryPci *resquery = (ClpRspQueryPci *)resh;
|
|
|
|
pbdev = s390_pci_find_dev_by_fh(s, ldl_p(&reqquery->fh));
|
|
if (!pbdev) {
|
|
DPRINTF("query pci no pci dev\n");
|
|
stw_p(&resquery->hdr.rsp, CLP_RC_SETPCIFN_FH);
|
|
goto out;
|
|
}
|
|
|
|
for (i = 0; i < PCI_BAR_COUNT; i++) {
|
|
uint32_t data = pci_get_long(pbdev->pdev->config +
|
|
PCI_BASE_ADDRESS_0 + (i * 4));
|
|
|
|
stl_p(&resquery->bar[i], data);
|
|
resquery->bar_size[i] = pbdev->pdev->io_regions[i].size ?
|
|
ctz64(pbdev->pdev->io_regions[i].size) : 0;
|
|
DPRINTF("bar %d addr 0x%x size 0x%" PRIx64 "barsize 0x%x\n", i,
|
|
ldl_p(&resquery->bar[i]),
|
|
pbdev->pdev->io_regions[i].size,
|
|
resquery->bar_size[i]);
|
|
}
|
|
|
|
stq_p(&resquery->sdma, ZPCI_SDMA_ADDR);
|
|
stq_p(&resquery->edma, ZPCI_EDMA_ADDR);
|
|
stl_p(&resquery->fid, pbdev->fid);
|
|
stw_p(&resquery->pchid, 0);
|
|
stw_p(&resquery->ug, 1);
|
|
stl_p(&resquery->uid, pbdev->uid);
|
|
stw_p(&resquery->hdr.rsp, CLP_RC_OK);
|
|
break;
|
|
}
|
|
case CLP_QUERY_PCI_FNGRP: {
|
|
ClpRspQueryPciGrp *resgrp = (ClpRspQueryPciGrp *)resh;
|
|
resgrp->fr = 1;
|
|
stq_p(&resgrp->dasm, 0);
|
|
stq_p(&resgrp->msia, ZPCI_MSI_ADDR);
|
|
stw_p(&resgrp->mui, DEFAULT_MUI);
|
|
stw_p(&resgrp->i, 128);
|
|
stw_p(&resgrp->maxstbl, 128);
|
|
resgrp->version = 0;
|
|
|
|
stw_p(&resgrp->hdr.rsp, CLP_RC_OK);
|
|
break;
|
|
}
|
|
default:
|
|
DPRINTF("unknown clp command\n");
|
|
stw_p(&resh->rsp, CLP_RC_CMD);
|
|
break;
|
|
}
|
|
|
|
out:
|
|
if (s390_cpu_virt_mem_write(cpu, env->regs[r2], r2, buffer,
|
|
req_len + res_len)) {
|
|
s390_cpu_virt_mem_handle_exc(cpu, ra);
|
|
return 0;
|
|
}
|
|
setcc(cpu, cc);
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* Swap data contained in s390x big endian registers to little endian
|
|
* PCI bars.
|
|
*
|
|
* @ptr: a pointer to a uint64_t data field
|
|
* @len: the length of the valid data, must be 1,2,4 or 8
|
|
*/
|
|
static int zpci_endian_swap(uint64_t *ptr, uint8_t len)
|
|
{
|
|
uint64_t data = *ptr;
|
|
|
|
switch (len) {
|
|
case 1:
|
|
break;
|
|
case 2:
|
|
data = bswap16(data);
|
|
break;
|
|
case 4:
|
|
data = bswap32(data);
|
|
break;
|
|
case 8:
|
|
data = bswap64(data);
|
|
break;
|
|
default:
|
|
return -EINVAL;
|
|
}
|
|
*ptr = data;
|
|
return 0;
|
|
}
|
|
|
|
static MemoryRegion *s390_get_subregion(MemoryRegion *mr, uint64_t offset,
|
|
uint8_t len)
|
|
{
|
|
MemoryRegion *subregion;
|
|
uint64_t subregion_size;
|
|
|
|
QTAILQ_FOREACH(subregion, &mr->subregions, subregions_link) {
|
|
subregion_size = int128_get64(subregion->size);
|
|
if ((offset >= subregion->addr) &&
|
|
(offset + len) <= (subregion->addr + subregion_size)) {
|
|
mr = subregion;
|
|
break;
|
|
}
|
|
}
|
|
return mr;
|
|
}
|
|
|
|
static MemTxResult zpci_read_bar(S390PCIBusDevice *pbdev, uint8_t pcias,
|
|
uint64_t offset, uint64_t *data, uint8_t len)
|
|
{
|
|
MemoryRegion *mr;
|
|
|
|
mr = pbdev->pdev->io_regions[pcias].memory;
|
|
mr = s390_get_subregion(mr, offset, len);
|
|
offset -= mr->addr;
|
|
return memory_region_dispatch_read(mr, offset, data, len,
|
|
MEMTXATTRS_UNSPECIFIED);
|
|
}
|
|
|
|
int pcilg_service_call(S390CPU *cpu, uint8_t r1, uint8_t r2, uintptr_t ra)
|
|
{
|
|
CPUS390XState *env = &cpu->env;
|
|
S390PCIBusDevice *pbdev;
|
|
uint64_t offset;
|
|
uint64_t data;
|
|
MemTxResult result;
|
|
uint8_t len;
|
|
uint32_t fh;
|
|
uint8_t pcias;
|
|
|
|
if (env->psw.mask & PSW_MASK_PSTATE) {
|
|
s390_program_interrupt(env, PGM_PRIVILEGED, 4, ra);
|
|
return 0;
|
|
}
|
|
|
|
if (r2 & 0x1) {
|
|
s390_program_interrupt(env, PGM_SPECIFICATION, 4, ra);
|
|
return 0;
|
|
}
|
|
|
|
fh = env->regs[r2] >> 32;
|
|
pcias = (env->regs[r2] >> 16) & 0xf;
|
|
len = env->regs[r2] & 0xf;
|
|
offset = env->regs[r2 + 1];
|
|
|
|
if (!(fh & FH_MASK_ENABLE)) {
|
|
setcc(cpu, ZPCI_PCI_LS_INVAL_HANDLE);
|
|
return 0;
|
|
}
|
|
|
|
pbdev = s390_pci_find_dev_by_fh(s390_get_phb(), fh);
|
|
if (!pbdev) {
|
|
DPRINTF("pcilg no pci dev\n");
|
|
setcc(cpu, ZPCI_PCI_LS_INVAL_HANDLE);
|
|
return 0;
|
|
}
|
|
|
|
switch (pbdev->state) {
|
|
case ZPCI_FS_PERMANENT_ERROR:
|
|
case ZPCI_FS_ERROR:
|
|
setcc(cpu, ZPCI_PCI_LS_ERR);
|
|
s390_set_status_code(env, r2, ZPCI_PCI_ST_BLOCKED);
|
|
return 0;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
switch (pcias) {
|
|
case ZPCI_IO_BAR_MIN...ZPCI_IO_BAR_MAX:
|
|
if (!len || (len > (8 - (offset & 0x7)))) {
|
|
s390_program_interrupt(env, PGM_OPERAND, 4, ra);
|
|
return 0;
|
|
}
|
|
result = zpci_read_bar(pbdev, pcias, offset, &data, len);
|
|
if (result != MEMTX_OK) {
|
|
s390_program_interrupt(env, PGM_OPERAND, 4, ra);
|
|
return 0;
|
|
}
|
|
break;
|
|
case ZPCI_CONFIG_BAR:
|
|
if (!len || (len > (4 - (offset & 0x3))) || len == 3) {
|
|
s390_program_interrupt(env, PGM_OPERAND, 4, ra);
|
|
return 0;
|
|
}
|
|
data = pci_host_config_read_common(
|
|
pbdev->pdev, offset, pci_config_size(pbdev->pdev), len);
|
|
|
|
if (zpci_endian_swap(&data, len)) {
|
|
s390_program_interrupt(env, PGM_OPERAND, 4, ra);
|
|
return 0;
|
|
}
|
|
break;
|
|
default:
|
|
DPRINTF("pcilg invalid space\n");
|
|
setcc(cpu, ZPCI_PCI_LS_ERR);
|
|
s390_set_status_code(env, r2, ZPCI_PCI_ST_INVAL_AS);
|
|
return 0;
|
|
}
|
|
|
|
pbdev->fmb.counter[ZPCI_FMB_CNT_LD]++;
|
|
|
|
env->regs[r1] = data;
|
|
setcc(cpu, ZPCI_PCI_LS_OK);
|
|
return 0;
|
|
}
|
|
|
|
static MemTxResult zpci_write_bar(S390PCIBusDevice *pbdev, uint8_t pcias,
|
|
uint64_t offset, uint64_t data, uint8_t len)
|
|
{
|
|
MemoryRegion *mr;
|
|
|
|
mr = pbdev->pdev->io_regions[pcias].memory;
|
|
mr = s390_get_subregion(mr, offset, len);
|
|
offset -= mr->addr;
|
|
return memory_region_dispatch_write(mr, offset, data, len,
|
|
MEMTXATTRS_UNSPECIFIED);
|
|
}
|
|
|
|
int pcistg_service_call(S390CPU *cpu, uint8_t r1, uint8_t r2, uintptr_t ra)
|
|
{
|
|
CPUS390XState *env = &cpu->env;
|
|
uint64_t offset, data;
|
|
S390PCIBusDevice *pbdev;
|
|
MemTxResult result;
|
|
uint8_t len;
|
|
uint32_t fh;
|
|
uint8_t pcias;
|
|
|
|
if (env->psw.mask & PSW_MASK_PSTATE) {
|
|
s390_program_interrupt(env, PGM_PRIVILEGED, 4, ra);
|
|
return 0;
|
|
}
|
|
|
|
if (r2 & 0x1) {
|
|
s390_program_interrupt(env, PGM_SPECIFICATION, 4, ra);
|
|
return 0;
|
|
}
|
|
|
|
fh = env->regs[r2] >> 32;
|
|
pcias = (env->regs[r2] >> 16) & 0xf;
|
|
len = env->regs[r2] & 0xf;
|
|
offset = env->regs[r2 + 1];
|
|
data = env->regs[r1];
|
|
|
|
if (!(fh & FH_MASK_ENABLE)) {
|
|
setcc(cpu, ZPCI_PCI_LS_INVAL_HANDLE);
|
|
return 0;
|
|
}
|
|
|
|
pbdev = s390_pci_find_dev_by_fh(s390_get_phb(), fh);
|
|
if (!pbdev) {
|
|
DPRINTF("pcistg no pci dev\n");
|
|
setcc(cpu, ZPCI_PCI_LS_INVAL_HANDLE);
|
|
return 0;
|
|
}
|
|
|
|
switch (pbdev->state) {
|
|
/* ZPCI_FS_RESERVED, ZPCI_FS_STANDBY and ZPCI_FS_DISABLED
|
|
* are already covered by the FH_MASK_ENABLE check above
|
|
*/
|
|
case ZPCI_FS_PERMANENT_ERROR:
|
|
case ZPCI_FS_ERROR:
|
|
setcc(cpu, ZPCI_PCI_LS_ERR);
|
|
s390_set_status_code(env, r2, ZPCI_PCI_ST_BLOCKED);
|
|
return 0;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
switch (pcias) {
|
|
/* A ZPCI PCI card may use any BAR from BAR 0 to BAR 5 */
|
|
case ZPCI_IO_BAR_MIN...ZPCI_IO_BAR_MAX:
|
|
/* Check length:
|
|
* A length of 0 is invalid and length should not cross a double word
|
|
*/
|
|
if (!len || (len > (8 - (offset & 0x7)))) {
|
|
s390_program_interrupt(env, PGM_OPERAND, 4, ra);
|
|
return 0;
|
|
}
|
|
|
|
result = zpci_write_bar(pbdev, pcias, offset, data, len);
|
|
if (result != MEMTX_OK) {
|
|
s390_program_interrupt(env, PGM_OPERAND, 4, ra);
|
|
return 0;
|
|
}
|
|
break;
|
|
case ZPCI_CONFIG_BAR:
|
|
/* ZPCI uses the pseudo BAR number 15 as configuration space */
|
|
/* possible access lengths are 1,2,4 and must not cross a word */
|
|
if (!len || (len > (4 - (offset & 0x3))) || len == 3) {
|
|
s390_program_interrupt(env, PGM_OPERAND, 4, ra);
|
|
return 0;
|
|
}
|
|
/* len = 1,2,4 so we do not need to test */
|
|
zpci_endian_swap(&data, len);
|
|
pci_host_config_write_common(pbdev->pdev, offset,
|
|
pci_config_size(pbdev->pdev),
|
|
data, len);
|
|
break;
|
|
default:
|
|
DPRINTF("pcistg invalid space\n");
|
|
setcc(cpu, ZPCI_PCI_LS_ERR);
|
|
s390_set_status_code(env, r2, ZPCI_PCI_ST_INVAL_AS);
|
|
return 0;
|
|
}
|
|
|
|
pbdev->fmb.counter[ZPCI_FMB_CNT_ST]++;
|
|
|
|
setcc(cpu, ZPCI_PCI_LS_OK);
|
|
return 0;
|
|
}
|
|
|
|
static void s390_pci_update_iotlb(S390PCIIOMMU *iommu, S390IOTLBEntry *entry)
|
|
{
|
|
S390IOTLBEntry *cache = g_hash_table_lookup(iommu->iotlb, &entry->iova);
|
|
IOMMUTLBEntry notify = {
|
|
.target_as = &address_space_memory,
|
|
.iova = entry->iova,
|
|
.translated_addr = entry->translated_addr,
|
|
.perm = entry->perm,
|
|
.addr_mask = ~PAGE_MASK,
|
|
};
|
|
|
|
if (entry->perm == IOMMU_NONE) {
|
|
if (!cache) {
|
|
return;
|
|
}
|
|
g_hash_table_remove(iommu->iotlb, &entry->iova);
|
|
} else {
|
|
if (cache) {
|
|
if (cache->perm == entry->perm &&
|
|
cache->translated_addr == entry->translated_addr) {
|
|
return;
|
|
}
|
|
|
|
notify.perm = IOMMU_NONE;
|
|
memory_region_notify_iommu(&iommu->iommu_mr, 0, notify);
|
|
notify.perm = entry->perm;
|
|
}
|
|
|
|
cache = g_new(S390IOTLBEntry, 1);
|
|
cache->iova = entry->iova;
|
|
cache->translated_addr = entry->translated_addr;
|
|
cache->len = PAGE_SIZE;
|
|
cache->perm = entry->perm;
|
|
g_hash_table_replace(iommu->iotlb, &cache->iova, cache);
|
|
}
|
|
|
|
memory_region_notify_iommu(&iommu->iommu_mr, 0, notify);
|
|
}
|
|
|
|
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;
|
|
|
|
if (env->psw.mask & PSW_MASK_PSTATE) {
|
|
s390_program_interrupt(env, PGM_PRIVILEGED, 4, ra);
|
|
return 0;
|
|
}
|
|
|
|
if (r2 & 0x1) {
|
|
s390_program_interrupt(env, PGM_SPECIFICATION, 4, ra);
|
|
return 0;
|
|
}
|
|
|
|
fh = env->regs[r1] >> 32;
|
|
start = env->regs[r2];
|
|
end = start + env->regs[r2 + 1];
|
|
|
|
pbdev = s390_pci_find_dev_by_fh(s390_get_phb(), fh);
|
|
if (!pbdev) {
|
|
DPRINTF("rpcit no pci dev\n");
|
|
setcc(cpu, ZPCI_PCI_LS_INVAL_HANDLE);
|
|
return 0;
|
|
}
|
|
|
|
switch (pbdev->state) {
|
|
case ZPCI_FS_RESERVED:
|
|
case ZPCI_FS_STANDBY:
|
|
case ZPCI_FS_DISABLED:
|
|
case ZPCI_FS_PERMANENT_ERROR:
|
|
setcc(cpu, ZPCI_PCI_LS_INVAL_HANDLE);
|
|
return 0;
|
|
case ZPCI_FS_ERROR:
|
|
setcc(cpu, ZPCI_PCI_LS_ERR);
|
|
s390_set_status_code(env, r1, ZPCI_MOD_ST_ERROR_RECOVER);
|
|
return 0;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
iommu = pbdev->iommu;
|
|
if (!iommu->g_iota) {
|
|
error = ERR_EVENT_INVALAS;
|
|
goto err;
|
|
}
|
|
|
|
if (end < iommu->pba || start > iommu->pal) {
|
|
error = ERR_EVENT_OORANGE;
|
|
goto err;
|
|
}
|
|
|
|
while (start < end) {
|
|
error = s390_guest_io_table_walk(iommu->g_iota, start, &entry);
|
|
if (error) {
|
|
break;
|
|
}
|
|
|
|
start += entry.len;
|
|
while (entry.iova < start && entry.iova < end) {
|
|
s390_pci_update_iotlb(iommu, &entry);
|
|
entry.iova += PAGE_SIZE;
|
|
entry.translated_addr += PAGE_SIZE;
|
|
}
|
|
}
|
|
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 {
|
|
pbdev->fmb.counter[ZPCI_FMB_CNT_RPCIT]++;
|
|
setcc(cpu, ZPCI_PCI_LS_OK);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
int pcistb_service_call(S390CPU *cpu, uint8_t r1, uint8_t r3, uint64_t gaddr,
|
|
uint8_t ar, uintptr_t ra)
|
|
{
|
|
CPUS390XState *env = &cpu->env;
|
|
S390PCIBusDevice *pbdev;
|
|
MemoryRegion *mr;
|
|
MemTxResult result;
|
|
uint64_t offset;
|
|
int i;
|
|
uint32_t fh;
|
|
uint8_t pcias;
|
|
uint8_t len;
|
|
uint8_t buffer[128];
|
|
|
|
if (env->psw.mask & PSW_MASK_PSTATE) {
|
|
s390_program_interrupt(env, PGM_PRIVILEGED, 6, ra);
|
|
return 0;
|
|
}
|
|
|
|
fh = env->regs[r1] >> 32;
|
|
pcias = (env->regs[r1] >> 16) & 0xf;
|
|
len = env->regs[r1] & 0xff;
|
|
offset = env->regs[r3];
|
|
|
|
if (!(fh & FH_MASK_ENABLE)) {
|
|
setcc(cpu, ZPCI_PCI_LS_INVAL_HANDLE);
|
|
return 0;
|
|
}
|
|
|
|
pbdev = s390_pci_find_dev_by_fh(s390_get_phb(), fh);
|
|
if (!pbdev) {
|
|
DPRINTF("pcistb no pci dev fh 0x%x\n", fh);
|
|
setcc(cpu, ZPCI_PCI_LS_INVAL_HANDLE);
|
|
return 0;
|
|
}
|
|
|
|
switch (pbdev->state) {
|
|
case ZPCI_FS_PERMANENT_ERROR:
|
|
case ZPCI_FS_ERROR:
|
|
setcc(cpu, ZPCI_PCI_LS_ERR);
|
|
s390_set_status_code(env, r1, ZPCI_PCI_ST_BLOCKED);
|
|
return 0;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
if (pcias > ZPCI_IO_BAR_MAX) {
|
|
DPRINTF("pcistb invalid space\n");
|
|
setcc(cpu, ZPCI_PCI_LS_ERR);
|
|
s390_set_status_code(env, r1, ZPCI_PCI_ST_INVAL_AS);
|
|
return 0;
|
|
}
|
|
|
|
/* Verify the address, offset and length */
|
|
/* offset must be a multiple of 8 */
|
|
if (offset % 8) {
|
|
goto specification_error;
|
|
}
|
|
/* Length must be greater than 8, a multiple of 8 */
|
|
/* and not greater than maxstbl */
|
|
if ((len <= 8) || (len % 8) || (len > pbdev->maxstbl)) {
|
|
goto specification_error;
|
|
}
|
|
/* Do not cross a 4K-byte boundary */
|
|
if (((offset & 0xfff) + len) > 0x1000) {
|
|
goto specification_error;
|
|
}
|
|
/* Guest address must be double word aligned */
|
|
if (gaddr & 0x07UL) {
|
|
goto specification_error;
|
|
}
|
|
|
|
mr = pbdev->pdev->io_regions[pcias].memory;
|
|
mr = s390_get_subregion(mr, offset, len);
|
|
offset -= mr->addr;
|
|
|
|
if (!memory_region_access_valid(mr, offset, len, true,
|
|
MEMTXATTRS_UNSPECIFIED)) {
|
|
s390_program_interrupt(env, PGM_OPERAND, 6, ra);
|
|
return 0;
|
|
}
|
|
|
|
if (s390_cpu_virt_mem_read(cpu, gaddr, ar, buffer, len)) {
|
|
s390_cpu_virt_mem_handle_exc(cpu, ra);
|
|
return 0;
|
|
}
|
|
|
|
for (i = 0; i < len / 8; i++) {
|
|
result = memory_region_dispatch_write(mr, offset + i * 8,
|
|
ldq_p(buffer + i * 8), 8,
|
|
MEMTXATTRS_UNSPECIFIED);
|
|
if (result != MEMTX_OK) {
|
|
s390_program_interrupt(env, PGM_OPERAND, 6, ra);
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
pbdev->fmb.counter[ZPCI_FMB_CNT_STB]++;
|
|
|
|
setcc(cpu, ZPCI_PCI_LS_OK);
|
|
return 0;
|
|
|
|
specification_error:
|
|
s390_program_interrupt(env, PGM_SPECIFICATION, 6, ra);
|
|
return 0;
|
|
}
|
|
|
|
static int reg_irqs(CPUS390XState *env, S390PCIBusDevice *pbdev, ZpciFib fib)
|
|
{
|
|
int ret, len;
|
|
uint8_t isc = FIB_DATA_ISC(ldl_p(&fib.data));
|
|
|
|
pbdev->routes.adapter.adapter_id = css_get_adapter_id(
|
|
CSS_IO_ADAPTER_PCI, isc);
|
|
pbdev->summary_ind = get_indicator(ldq_p(&fib.aisb), sizeof(uint64_t));
|
|
len = BITS_TO_LONGS(FIB_DATA_NOI(ldl_p(&fib.data))) * sizeof(unsigned long);
|
|
pbdev->indicator = get_indicator(ldq_p(&fib.aibv), len);
|
|
|
|
ret = map_indicator(&pbdev->routes.adapter, pbdev->summary_ind);
|
|
if (ret) {
|
|
goto out;
|
|
}
|
|
|
|
ret = map_indicator(&pbdev->routes.adapter, pbdev->indicator);
|
|
if (ret) {
|
|
goto out;
|
|
}
|
|
|
|
pbdev->routes.adapter.summary_addr = ldq_p(&fib.aisb);
|
|
pbdev->routes.adapter.summary_offset = FIB_DATA_AISBO(ldl_p(&fib.data));
|
|
pbdev->routes.adapter.ind_addr = ldq_p(&fib.aibv);
|
|
pbdev->routes.adapter.ind_offset = FIB_DATA_AIBVO(ldl_p(&fib.data));
|
|
pbdev->isc = isc;
|
|
pbdev->noi = FIB_DATA_NOI(ldl_p(&fib.data));
|
|
pbdev->sum = FIB_DATA_SUM(ldl_p(&fib.data));
|
|
|
|
DPRINTF("reg_irqs adapter id %d\n", pbdev->routes.adapter.adapter_id);
|
|
return 0;
|
|
out:
|
|
release_indicator(&pbdev->routes.adapter, pbdev->summary_ind);
|
|
release_indicator(&pbdev->routes.adapter, pbdev->indicator);
|
|
pbdev->summary_ind = NULL;
|
|
pbdev->indicator = NULL;
|
|
return ret;
|
|
}
|
|
|
|
int pci_dereg_irqs(S390PCIBusDevice *pbdev)
|
|
{
|
|
release_indicator(&pbdev->routes.adapter, pbdev->summary_ind);
|
|
release_indicator(&pbdev->routes.adapter, pbdev->indicator);
|
|
|
|
pbdev->summary_ind = NULL;
|
|
pbdev->indicator = NULL;
|
|
pbdev->routes.adapter.summary_addr = 0;
|
|
pbdev->routes.adapter.summary_offset = 0;
|
|
pbdev->routes.adapter.ind_addr = 0;
|
|
pbdev->routes.adapter.ind_offset = 0;
|
|
pbdev->isc = 0;
|
|
pbdev->noi = 0;
|
|
pbdev->sum = 0;
|
|
|
|
DPRINTF("dereg_irqs adapter id %d\n", pbdev->routes.adapter.adapter_id);
|
|
return 0;
|
|
}
|
|
|
|
static int reg_ioat(CPUS390XState *env, S390PCIIOMMU *iommu, ZpciFib fib,
|
|
uintptr_t ra)
|
|
{
|
|
uint64_t pba = ldq_p(&fib.pba);
|
|
uint64_t pal = ldq_p(&fib.pal);
|
|
uint64_t g_iota = ldq_p(&fib.iota);
|
|
uint8_t dt = (g_iota >> 2) & 0x7;
|
|
uint8_t t = (g_iota >> 11) & 0x1;
|
|
|
|
pba &= ~0xfff;
|
|
pal |= 0xfff;
|
|
if (pba > pal || pba < ZPCI_SDMA_ADDR || pal > ZPCI_EDMA_ADDR) {
|
|
s390_program_interrupt(env, PGM_OPERAND, 6, ra);
|
|
return -EINVAL;
|
|
}
|
|
|
|
/* currently we only support designation type 1 with translation */
|
|
if (!(dt == ZPCI_IOTA_RTTO && t)) {
|
|
error_report("unsupported ioat dt %d t %d", dt, t);
|
|
s390_program_interrupt(env, PGM_OPERAND, 6, ra);
|
|
return -EINVAL;
|
|
}
|
|
|
|
iommu->pba = pba;
|
|
iommu->pal = pal;
|
|
iommu->g_iota = g_iota;
|
|
|
|
s390_pci_iommu_enable(iommu);
|
|
|
|
return 0;
|
|
}
|
|
|
|
void pci_dereg_ioat(S390PCIIOMMU *iommu)
|
|
{
|
|
s390_pci_iommu_disable(iommu);
|
|
iommu->pba = 0;
|
|
iommu->pal = 0;
|
|
iommu->g_iota = 0;
|
|
}
|
|
|
|
void fmb_timer_free(S390PCIBusDevice *pbdev)
|
|
{
|
|
if (pbdev->fmb_timer) {
|
|
timer_del(pbdev->fmb_timer);
|
|
timer_free(pbdev->fmb_timer);
|
|
pbdev->fmb_timer = NULL;
|
|
}
|
|
pbdev->fmb_addr = 0;
|
|
memset(&pbdev->fmb, 0, sizeof(ZpciFmb));
|
|
}
|
|
|
|
static int fmb_do_update(S390PCIBusDevice *pbdev, int offset, uint64_t val,
|
|
int len)
|
|
{
|
|
MemTxResult ret;
|
|
uint64_t dst = pbdev->fmb_addr + offset;
|
|
|
|
switch (len) {
|
|
case 8:
|
|
address_space_stq_be(&address_space_memory, dst, val,
|
|
MEMTXATTRS_UNSPECIFIED,
|
|
&ret);
|
|
break;
|
|
case 4:
|
|
address_space_stl_be(&address_space_memory, dst, val,
|
|
MEMTXATTRS_UNSPECIFIED,
|
|
&ret);
|
|
break;
|
|
case 2:
|
|
address_space_stw_be(&address_space_memory, dst, val,
|
|
MEMTXATTRS_UNSPECIFIED,
|
|
&ret);
|
|
break;
|
|
case 1:
|
|
address_space_stb(&address_space_memory, dst, val,
|
|
MEMTXATTRS_UNSPECIFIED,
|
|
&ret);
|
|
break;
|
|
default:
|
|
ret = MEMTX_ERROR;
|
|
break;
|
|
}
|
|
if (ret != MEMTX_OK) {
|
|
s390_pci_generate_error_event(ERR_EVENT_FMBA, pbdev->fh, pbdev->fid,
|
|
pbdev->fmb_addr, 0);
|
|
fmb_timer_free(pbdev);
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
static void fmb_update(void *opaque)
|
|
{
|
|
S390PCIBusDevice *pbdev = opaque;
|
|
int64_t t = qemu_clock_get_ms(QEMU_CLOCK_VIRTUAL);
|
|
int i;
|
|
|
|
/* Update U bit */
|
|
pbdev->fmb.last_update *= 2;
|
|
pbdev->fmb.last_update |= UPDATE_U_BIT;
|
|
if (fmb_do_update(pbdev, offsetof(ZpciFmb, last_update),
|
|
pbdev->fmb.last_update,
|
|
sizeof(pbdev->fmb.last_update))) {
|
|
return;
|
|
}
|
|
|
|
/* Update FMB sample count */
|
|
if (fmb_do_update(pbdev, offsetof(ZpciFmb, sample),
|
|
pbdev->fmb.sample++,
|
|
sizeof(pbdev->fmb.sample))) {
|
|
return;
|
|
}
|
|
|
|
/* Update FMB counters */
|
|
for (i = 0; i < ZPCI_FMB_CNT_MAX; i++) {
|
|
if (fmb_do_update(pbdev, offsetof(ZpciFmb, counter[i]),
|
|
pbdev->fmb.counter[i],
|
|
sizeof(pbdev->fmb.counter[0]))) {
|
|
return;
|
|
}
|
|
}
|
|
|
|
/* Clear U bit and update the time */
|
|
pbdev->fmb.last_update = time2tod(qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL));
|
|
pbdev->fmb.last_update *= 2;
|
|
if (fmb_do_update(pbdev, offsetof(ZpciFmb, last_update),
|
|
pbdev->fmb.last_update,
|
|
sizeof(pbdev->fmb.last_update))) {
|
|
return;
|
|
}
|
|
timer_mod(pbdev->fmb_timer, t + DEFAULT_MUI);
|
|
}
|
|
|
|
int mpcifc_service_call(S390CPU *cpu, uint8_t r1, uint64_t fiba, uint8_t ar,
|
|
uintptr_t ra)
|
|
{
|
|
CPUS390XState *env = &cpu->env;
|
|
uint8_t oc, dmaas;
|
|
uint32_t fh;
|
|
ZpciFib fib;
|
|
S390PCIBusDevice *pbdev;
|
|
uint64_t cc = ZPCI_PCI_LS_OK;
|
|
|
|
if (env->psw.mask & PSW_MASK_PSTATE) {
|
|
s390_program_interrupt(env, PGM_PRIVILEGED, 6, ra);
|
|
return 0;
|
|
}
|
|
|
|
oc = env->regs[r1] & 0xff;
|
|
dmaas = (env->regs[r1] >> 16) & 0xff;
|
|
fh = env->regs[r1] >> 32;
|
|
|
|
if (fiba & 0x7) {
|
|
s390_program_interrupt(env, PGM_SPECIFICATION, 6, ra);
|
|
return 0;
|
|
}
|
|
|
|
pbdev = s390_pci_find_dev_by_fh(s390_get_phb(), fh);
|
|
if (!pbdev) {
|
|
DPRINTF("mpcifc no pci dev fh 0x%x\n", fh);
|
|
setcc(cpu, ZPCI_PCI_LS_INVAL_HANDLE);
|
|
return 0;
|
|
}
|
|
|
|
switch (pbdev->state) {
|
|
case ZPCI_FS_RESERVED:
|
|
case ZPCI_FS_STANDBY:
|
|
case ZPCI_FS_DISABLED:
|
|
case ZPCI_FS_PERMANENT_ERROR:
|
|
setcc(cpu, ZPCI_PCI_LS_INVAL_HANDLE);
|
|
return 0;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
if (s390_cpu_virt_mem_read(cpu, fiba, ar, (uint8_t *)&fib, sizeof(fib))) {
|
|
s390_cpu_virt_mem_handle_exc(cpu, ra);
|
|
return 0;
|
|
}
|
|
|
|
if (fib.fmt != 0) {
|
|
s390_program_interrupt(env, PGM_OPERAND, 6, ra);
|
|
return 0;
|
|
}
|
|
|
|
switch (oc) {
|
|
case ZPCI_MOD_FC_REG_INT:
|
|
if (pbdev->summary_ind) {
|
|
cc = ZPCI_PCI_LS_ERR;
|
|
s390_set_status_code(env, r1, ZPCI_MOD_ST_SEQUENCE);
|
|
} else if (reg_irqs(env, pbdev, fib)) {
|
|
cc = ZPCI_PCI_LS_ERR;
|
|
s390_set_status_code(env, r1, ZPCI_MOD_ST_RES_NOT_AVAIL);
|
|
}
|
|
break;
|
|
case ZPCI_MOD_FC_DEREG_INT:
|
|
if (!pbdev->summary_ind) {
|
|
cc = ZPCI_PCI_LS_ERR;
|
|
s390_set_status_code(env, r1, ZPCI_MOD_ST_SEQUENCE);
|
|
} else {
|
|
pci_dereg_irqs(pbdev);
|
|
}
|
|
break;
|
|
case ZPCI_MOD_FC_REG_IOAT:
|
|
if (dmaas != 0) {
|
|
cc = ZPCI_PCI_LS_ERR;
|
|
s390_set_status_code(env, r1, ZPCI_MOD_ST_DMAAS_INVAL);
|
|
} else if (pbdev->iommu->enabled) {
|
|
cc = ZPCI_PCI_LS_ERR;
|
|
s390_set_status_code(env, r1, ZPCI_MOD_ST_SEQUENCE);
|
|
} else if (reg_ioat(env, pbdev->iommu, fib, ra)) {
|
|
cc = ZPCI_PCI_LS_ERR;
|
|
s390_set_status_code(env, r1, ZPCI_MOD_ST_INSUF_RES);
|
|
}
|
|
break;
|
|
case ZPCI_MOD_FC_DEREG_IOAT:
|
|
if (dmaas != 0) {
|
|
cc = ZPCI_PCI_LS_ERR;
|
|
s390_set_status_code(env, r1, ZPCI_MOD_ST_DMAAS_INVAL);
|
|
} else if (!pbdev->iommu->enabled) {
|
|
cc = ZPCI_PCI_LS_ERR;
|
|
s390_set_status_code(env, r1, ZPCI_MOD_ST_SEQUENCE);
|
|
} else {
|
|
pci_dereg_ioat(pbdev->iommu);
|
|
}
|
|
break;
|
|
case ZPCI_MOD_FC_REREG_IOAT:
|
|
if (dmaas != 0) {
|
|
cc = ZPCI_PCI_LS_ERR;
|
|
s390_set_status_code(env, r1, ZPCI_MOD_ST_DMAAS_INVAL);
|
|
} else if (!pbdev->iommu->enabled) {
|
|
cc = ZPCI_PCI_LS_ERR;
|
|
s390_set_status_code(env, r1, ZPCI_MOD_ST_SEQUENCE);
|
|
} else {
|
|
pci_dereg_ioat(pbdev->iommu);
|
|
if (reg_ioat(env, pbdev->iommu, fib, ra)) {
|
|
cc = ZPCI_PCI_LS_ERR;
|
|
s390_set_status_code(env, r1, ZPCI_MOD_ST_INSUF_RES);
|
|
}
|
|
}
|
|
break;
|
|
case ZPCI_MOD_FC_RESET_ERROR:
|
|
switch (pbdev->state) {
|
|
case ZPCI_FS_BLOCKED:
|
|
case ZPCI_FS_ERROR:
|
|
pbdev->state = ZPCI_FS_ENABLED;
|
|
break;
|
|
default:
|
|
cc = ZPCI_PCI_LS_ERR;
|
|
s390_set_status_code(env, r1, ZPCI_MOD_ST_SEQUENCE);
|
|
}
|
|
break;
|
|
case ZPCI_MOD_FC_RESET_BLOCK:
|
|
switch (pbdev->state) {
|
|
case ZPCI_FS_ERROR:
|
|
pbdev->state = ZPCI_FS_BLOCKED;
|
|
break;
|
|
default:
|
|
cc = ZPCI_PCI_LS_ERR;
|
|
s390_set_status_code(env, r1, ZPCI_MOD_ST_SEQUENCE);
|
|
}
|
|
break;
|
|
case ZPCI_MOD_FC_SET_MEASURE: {
|
|
uint64_t fmb_addr = ldq_p(&fib.fmb_addr);
|
|
|
|
if (fmb_addr & FMBK_MASK) {
|
|
cc = ZPCI_PCI_LS_ERR;
|
|
s390_pci_generate_error_event(ERR_EVENT_FMBPRO, pbdev->fh,
|
|
pbdev->fid, fmb_addr, 0);
|
|
fmb_timer_free(pbdev);
|
|
break;
|
|
}
|
|
|
|
if (!fmb_addr) {
|
|
/* Stop updating FMB. */
|
|
fmb_timer_free(pbdev);
|
|
break;
|
|
}
|
|
|
|
if (!pbdev->fmb_timer) {
|
|
pbdev->fmb_timer = timer_new_ms(QEMU_CLOCK_VIRTUAL,
|
|
fmb_update, pbdev);
|
|
} else if (timer_pending(pbdev->fmb_timer)) {
|
|
/* Remove pending timer to update FMB address. */
|
|
timer_del(pbdev->fmb_timer);
|
|
}
|
|
pbdev->fmb_addr = fmb_addr;
|
|
timer_mod(pbdev->fmb_timer,
|
|
qemu_clock_get_ms(QEMU_CLOCK_VIRTUAL) + DEFAULT_MUI);
|
|
break;
|
|
}
|
|
default:
|
|
s390_program_interrupt(&cpu->env, PGM_OPERAND, 6, ra);
|
|
cc = ZPCI_PCI_LS_ERR;
|
|
}
|
|
|
|
setcc(cpu, cc);
|
|
return 0;
|
|
}
|
|
|
|
int stpcifc_service_call(S390CPU *cpu, uint8_t r1, uint64_t fiba, uint8_t ar,
|
|
uintptr_t ra)
|
|
{
|
|
CPUS390XState *env = &cpu->env;
|
|
uint8_t dmaas;
|
|
uint32_t fh;
|
|
ZpciFib fib;
|
|
S390PCIBusDevice *pbdev;
|
|
uint32_t data;
|
|
uint64_t cc = ZPCI_PCI_LS_OK;
|
|
|
|
if (env->psw.mask & PSW_MASK_PSTATE) {
|
|
s390_program_interrupt(env, PGM_PRIVILEGED, 6, ra);
|
|
return 0;
|
|
}
|
|
|
|
fh = env->regs[r1] >> 32;
|
|
dmaas = (env->regs[r1] >> 16) & 0xff;
|
|
|
|
if (dmaas) {
|
|
setcc(cpu, ZPCI_PCI_LS_ERR);
|
|
s390_set_status_code(env, r1, ZPCI_STPCIFC_ST_INVAL_DMAAS);
|
|
return 0;
|
|
}
|
|
|
|
if (fiba & 0x7) {
|
|
s390_program_interrupt(env, PGM_SPECIFICATION, 6, ra);
|
|
return 0;
|
|
}
|
|
|
|
pbdev = s390_pci_find_dev_by_idx(s390_get_phb(), fh & FH_MASK_INDEX);
|
|
if (!pbdev) {
|
|
setcc(cpu, ZPCI_PCI_LS_INVAL_HANDLE);
|
|
return 0;
|
|
}
|
|
|
|
memset(&fib, 0, sizeof(fib));
|
|
|
|
switch (pbdev->state) {
|
|
case ZPCI_FS_RESERVED:
|
|
case ZPCI_FS_STANDBY:
|
|
setcc(cpu, ZPCI_PCI_LS_INVAL_HANDLE);
|
|
return 0;
|
|
case ZPCI_FS_DISABLED:
|
|
if (fh & FH_MASK_ENABLE) {
|
|
setcc(cpu, ZPCI_PCI_LS_INVAL_HANDLE);
|
|
return 0;
|
|
}
|
|
goto out;
|
|
/* BLOCKED bit is set to one coincident with the setting of ERROR bit.
|
|
* FH Enabled bit is set to one in states of ENABLED, BLOCKED or ERROR. */
|
|
case ZPCI_FS_ERROR:
|
|
fib.fc |= 0x20;
|
|
case ZPCI_FS_BLOCKED:
|
|
fib.fc |= 0x40;
|
|
case ZPCI_FS_ENABLED:
|
|
fib.fc |= 0x80;
|
|
if (pbdev->iommu->enabled) {
|
|
fib.fc |= 0x10;
|
|
}
|
|
if (!(fh & FH_MASK_ENABLE)) {
|
|
env->regs[r1] |= 1ULL << 63;
|
|
}
|
|
break;
|
|
case ZPCI_FS_PERMANENT_ERROR:
|
|
setcc(cpu, ZPCI_PCI_LS_ERR);
|
|
s390_set_status_code(env, r1, ZPCI_STPCIFC_ST_PERM_ERROR);
|
|
return 0;
|
|
}
|
|
|
|
stq_p(&fib.pba, pbdev->iommu->pba);
|
|
stq_p(&fib.pal, pbdev->iommu->pal);
|
|
stq_p(&fib.iota, pbdev->iommu->g_iota);
|
|
stq_p(&fib.aibv, pbdev->routes.adapter.ind_addr);
|
|
stq_p(&fib.aisb, pbdev->routes.adapter.summary_addr);
|
|
stq_p(&fib.fmb_addr, pbdev->fmb_addr);
|
|
|
|
data = ((uint32_t)pbdev->isc << 28) | ((uint32_t)pbdev->noi << 16) |
|
|
((uint32_t)pbdev->routes.adapter.ind_offset << 8) |
|
|
((uint32_t)pbdev->sum << 7) | pbdev->routes.adapter.summary_offset;
|
|
stl_p(&fib.data, data);
|
|
|
|
out:
|
|
if (s390_cpu_virt_mem_write(cpu, fiba, ar, (uint8_t *)&fib, sizeof(fib))) {
|
|
s390_cpu_virt_mem_handle_exc(cpu, ra);
|
|
return 0;
|
|
}
|
|
|
|
setcc(cpu, cc);
|
|
return 0;
|
|
}
|