aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--hw/s390x/css.c38
-rw-r--r--hw/s390x/s390-pci-bus.c10
-rw-r--r--hw/s390x/s390-pci-inst.c8
-rw-r--r--hw/s390x/s390-skeys-kvm.c4
-rw-r--r--hw/s390x/s390-skeys.c206
-rw-r--r--hw/s390x/s390-virtio-ccw.c5
-rw-r--r--hw/s390x/sclp.c2
-rw-r--r--hw/vfio/ccw.c4
-rw-r--r--include/hw/s390x/css.h3
-rw-r--r--include/hw/s390x/s390-pci-bus.h5
-rw-r--r--include/hw/s390x/storage-keys.h65
-rw-r--r--target/s390x/gen-features.c8
-rw-r--r--target/s390x/helper.h6
-rw-r--r--target/s390x/ioinst.c2
-rw-r--r--target/s390x/mmu_helper.c70
-rw-r--r--target/s390x/s390x-internal.h3
-rw-r--r--target/s390x/tcg/excp_helper.c13
-rw-r--r--target/s390x/tcg/mem_helper.c53
-rw-r--r--target/s390x/tcg/misc_helper.c15
-rw-r--r--tests/tcg/s390x/Makefile.target17
-rw-r--r--tests/tcg/s390x/gdbstub/test-signals-s390x.py76
-rw-r--r--tests/tcg/s390x/signals-s390x.c165
22 files changed, 627 insertions, 151 deletions
diff --git a/hw/s390x/css.c b/hw/s390x/css.c
index 133ddea575..7d9523f811 100644
--- a/hw/s390x/css.c
+++ b/hw/s390x/css.c
@@ -1206,23 +1206,53 @@ static void sch_handle_start_func_virtual(SubchDev *sch)
}
-static void sch_handle_halt_func_passthrough(SubchDev *sch)
+static IOInstEnding sch_handle_halt_func_passthrough(SubchDev *sch)
{
int ret;
ret = s390_ccw_halt(sch);
if (ret == -ENOSYS) {
sch_handle_halt_func(sch);
+ return IOINST_CC_EXPECTED;
+ }
+ /*
+ * Some conditions may have been detected prior to starting the halt
+ * function; map them to the correct cc.
+ * Note that we map both -ENODEV and -EACCES to cc 3 (there's not really
+ * anything else we can do.)
+ */
+ switch (ret) {
+ case -EBUSY:
+ return IOINST_CC_BUSY;
+ case -ENODEV:
+ case -EACCES:
+ return IOINST_CC_NOT_OPERATIONAL;
+ default:
+ return IOINST_CC_EXPECTED;
}
}
-static void sch_handle_clear_func_passthrough(SubchDev *sch)
+static IOInstEnding sch_handle_clear_func_passthrough(SubchDev *sch)
{
int ret;
ret = s390_ccw_clear(sch);
if (ret == -ENOSYS) {
sch_handle_clear_func(sch);
+ return IOINST_CC_EXPECTED;
+ }
+ /*
+ * Some conditions may have been detected prior to starting the clear
+ * function; map them to the correct cc.
+ * Note that we map both -ENODEV and -EACCES to cc 3 (there's not really
+ * anything else we can do.)
+ */
+ switch (ret) {
+ case -ENODEV:
+ case -EACCES:
+ return IOINST_CC_NOT_OPERATIONAL;
+ default:
+ return IOINST_CC_EXPECTED;
}
}
@@ -1265,9 +1295,9 @@ IOInstEnding do_subchannel_work_passthrough(SubchDev *sch)
SCHIB *schib = &sch->curr_status;
if (schib->scsw.ctrl & SCSW_FCTL_CLEAR_FUNC) {
- sch_handle_clear_func_passthrough(sch);
+ return sch_handle_clear_func_passthrough(sch);
} else if (schib->scsw.ctrl & SCSW_FCTL_HALT_FUNC) {
- sch_handle_halt_func_passthrough(sch);
+ return sch_handle_halt_func_passthrough(sch);
} else if (schib->scsw.ctrl & SCSW_FCTL_START_FUNC) {
return sch_handle_start_func_passthrough(sch);
}
diff --git a/hw/s390x/s390-pci-bus.c b/hw/s390x/s390-pci-bus.c
index 7db1c5943f..6c0225c3a0 100644
--- a/hw/s390x/s390-pci-bus.c
+++ b/hw/s390x/s390-pci-bus.c
@@ -330,7 +330,7 @@ static unsigned int calc_sx(dma_addr_t ptr)
static unsigned int calc_px(dma_addr_t ptr)
{
- return ((unsigned long) ptr >> PAGE_SHIFT) & ZPCI_PT_MASK;
+ return ((unsigned long) ptr >> TARGET_PAGE_BITS) & ZPCI_PT_MASK;
}
static uint64_t get_rt_sto(uint64_t entry)
@@ -506,7 +506,7 @@ uint16_t s390_guest_io_table_walk(uint64_t g_iota, hwaddr addr,
int8_t ett = 1;
uint16_t error = 0;
- entry->iova = addr & PAGE_MASK;
+ entry->iova = addr & TARGET_PAGE_MASK;
entry->translated_addr = 0;
entry->perm = IOMMU_RW;
@@ -526,7 +526,7 @@ static IOMMUTLBEntry s390_translate_iommu(IOMMUMemoryRegion *mr, hwaddr addr,
{
S390PCIIOMMU *iommu = container_of(mr, S390PCIIOMMU, iommu_mr);
S390IOTLBEntry *entry;
- uint64_t iova = addr & PAGE_MASK;
+ uint64_t iova = addr & TARGET_PAGE_MASK;
uint16_t error = 0;
IOMMUTLBEntry ret = {
.target_as = &address_space_memory,
@@ -562,7 +562,7 @@ static IOMMUTLBEntry s390_translate_iommu(IOMMUMemoryRegion *mr, hwaddr addr,
ret.perm = entry->perm;
} else {
ret.iova = iova;
- ret.addr_mask = ~PAGE_MASK;
+ ret.addr_mask = ~TARGET_PAGE_MASK;
ret.perm = IOMMU_NONE;
}
@@ -868,7 +868,7 @@ static int s390_pci_msix_init(S390PCIBusDevice *pbdev)
name = g_strdup_printf("msix-s390-%04x", pbdev->uid);
memory_region_init_io(&pbdev->msix_notify_mr, OBJECT(pbdev),
- &s390_msi_ctrl_ops, pbdev, name, PAGE_SIZE);
+ &s390_msi_ctrl_ops, pbdev, name, TARGET_PAGE_SIZE);
memory_region_add_subregion(&pbdev->iommu->mr,
pbdev->pci_group->zpci_group.msia,
&pbdev->msix_notify_mr);
diff --git a/hw/s390x/s390-pci-inst.c b/hw/s390x/s390-pci-inst.c
index 9ec277d50e..1c8ad91175 100644
--- a/hw/s390x/s390-pci-inst.c
+++ b/hw/s390x/s390-pci-inst.c
@@ -613,7 +613,7 @@ static uint32_t s390_pci_update_iotlb(S390PCIIOMMU *iommu,
.iova = entry->iova,
.translated_addr = entry->translated_addr,
.perm = entry->perm,
- .addr_mask = ~PAGE_MASK,
+ .addr_mask = ~TARGET_PAGE_MASK,
},
};
@@ -640,7 +640,7 @@ static uint32_t s390_pci_update_iotlb(S390PCIIOMMU *iommu,
cache = g_new(S390IOTLBEntry, 1);
cache->iova = entry->iova;
cache->translated_addr = entry->translated_addr;
- cache->len = PAGE_SIZE;
+ cache->len = TARGET_PAGE_SIZE;
cache->perm = entry->perm;
g_hash_table_replace(iommu->iotlb, &cache->iova, cache);
dec_dma_avail(iommu);
@@ -725,8 +725,8 @@ int rpcit_service_call(S390CPU *cpu, uint8_t r1, uint8_t r2, uintptr_t ra)
while (entry.iova < start && entry.iova < end &&
(dma_avail > 0 || entry.perm == IOMMU_NONE)) {
dma_avail = s390_pci_update_iotlb(iommu, &entry);
- entry.iova += PAGE_SIZE;
- entry.translated_addr += PAGE_SIZE;
+ entry.iova += TARGET_PAGE_SIZE;
+ entry.translated_addr += TARGET_PAGE_SIZE;
}
}
err:
diff --git a/hw/s390x/s390-skeys-kvm.c b/hw/s390x/s390-skeys-kvm.c
index 1c4d805ad8..3ff9d94b80 100644
--- a/hw/s390x/s390-skeys-kvm.c
+++ b/hw/s390x/s390-skeys-kvm.c
@@ -15,7 +15,7 @@
#include "qemu/error-report.h"
#include "qemu/module.h"
-static int kvm_s390_skeys_enabled(S390SKeysState *ss)
+static bool kvm_s390_skeys_are_enabled(S390SKeysState *ss)
{
S390SKeysClass *skeyclass = S390_SKEYS_GET_CLASS(ss);
uint8_t single_key;
@@ -57,7 +57,7 @@ static void kvm_s390_skeys_class_init(ObjectClass *oc, void *data)
S390SKeysClass *skeyclass = S390_SKEYS_CLASS(oc);
DeviceClass *dc = DEVICE_CLASS(oc);
- skeyclass->skeys_enabled = kvm_s390_skeys_enabled;
+ skeyclass->skeys_are_enabled = kvm_s390_skeys_are_enabled;
skeyclass->get_skeys = kvm_s390_skeys_get;
skeyclass->set_skeys = kvm_s390_skeys_set;
diff --git a/hw/s390x/s390-skeys.c b/hw/s390x/s390-skeys.c
index 9a8d60d1d9..5024faf411 100644
--- a/hw/s390x/s390-skeys.c
+++ b/hw/s390x/s390-skeys.c
@@ -17,6 +17,8 @@
#include "qapi/qapi-commands-misc-target.h"
#include "qapi/qmp/qdict.h"
#include "qemu/error-report.h"
+#include "sysemu/memory_mapping.h"
+#include "exec/address-spaces.h"
#include "sysemu/kvm.h"
#include "migration/qemu-file-types.h"
#include "migration/register.h"
@@ -80,11 +82,18 @@ void hmp_info_skeys(Monitor *mon, const QDict *qdict)
int r;
/* Quick check to see if guest is using storage keys*/
- if (!skeyclass->skeys_enabled(ss)) {
+ if (!skeyclass->skeys_are_enabled(ss)) {
monitor_printf(mon, "Error: This guest is not using storage keys\n");
return;
}
+ if (!address_space_access_valid(&address_space_memory,
+ addr & TARGET_PAGE_MASK, TARGET_PAGE_SIZE,
+ false, MEMTXATTRS_UNSPECIFIED)) {
+ monitor_printf(mon, "Error: The given address is not valid\n");
+ return;
+ }
+
r = skeyclass->get_skeys(ss, addr / TARGET_PAGE_SIZE, 1, &key);
if (r < 0) {
monitor_printf(mon, "Error: %s\n", strerror(-r));
@@ -109,18 +118,17 @@ void qmp_dump_skeys(const char *filename, Error **errp)
{
S390SKeysState *ss = s390_get_skeys_device();
S390SKeysClass *skeyclass = S390_SKEYS_GET_CLASS(ss);
- MachineState *ms = MACHINE(qdev_get_machine());
- const uint64_t total_count = ms->ram_size / TARGET_PAGE_SIZE;
- uint64_t handled_count = 0, cur_count;
+ GuestPhysBlockList guest_phys_blocks;
+ GuestPhysBlock *block;
+ uint64_t pages, gfn;
Error *lerr = NULL;
- vaddr cur_gfn = 0;
uint8_t *buf;
int ret;
int fd;
FILE *f;
/* Quick check to see if guest is using storage keys*/
- if (!skeyclass->skeys_enabled(ss)) {
+ if (!skeyclass->skeys_are_enabled(ss)) {
error_setg(errp, "This guest is not using storage keys - "
"nothing to dump");
return;
@@ -144,53 +152,86 @@ void qmp_dump_skeys(const char *filename, Error **errp)
goto out;
}
- /* we'll only dump initial memory for now */
- while (handled_count < total_count) {
- /* Calculate how many keys to ask for & handle overflow case */
- cur_count = MIN(total_count - handled_count, S390_SKEYS_BUFFER_SIZE);
+ assert(qemu_mutex_iothread_locked());
+ guest_phys_blocks_init(&guest_phys_blocks);
+ guest_phys_blocks_append(&guest_phys_blocks);
- ret = skeyclass->get_skeys(ss, cur_gfn, cur_count, buf);
- if (ret < 0) {
- error_setg(errp, "get_keys error %d", ret);
- goto out_free;
- }
+ QTAILQ_FOREACH(block, &guest_phys_blocks.head, next) {
+ assert(QEMU_IS_ALIGNED(block->target_start, TARGET_PAGE_SIZE));
+ assert(QEMU_IS_ALIGNED(block->target_end, TARGET_PAGE_SIZE));
- /* write keys to stream */
- write_keys(f, buf, cur_gfn, cur_count, &lerr);
- if (lerr) {
- goto out_free;
- }
+ gfn = block->target_start / TARGET_PAGE_SIZE;
+ pages = (block->target_end - block->target_start) / TARGET_PAGE_SIZE;
+
+ while (pages) {
+ const uint64_t cur_pages = MIN(pages, S390_SKEYS_BUFFER_SIZE);
- cur_gfn += cur_count;
- handled_count += cur_count;
+ ret = skeyclass->get_skeys(ss, gfn, cur_pages, buf);
+ if (ret < 0) {
+ error_setg_errno(errp, -ret, "get_keys error");
+ goto out_free;
+ }
+
+ /* write keys to stream */
+ write_keys(f, buf, gfn, cur_pages, &lerr);
+ if (lerr) {
+ goto out_free;
+ }
+
+ gfn += cur_pages;
+ pages -= cur_pages;
+ }
}
out_free:
+ guest_phys_blocks_free(&guest_phys_blocks);
error_propagate(errp, lerr);
g_free(buf);
out:
fclose(f);
}
-static void qemu_s390_skeys_init(Object *obj)
+static bool qemu_s390_skeys_are_enabled(S390SKeysState *ss)
{
- QEMUS390SKeysState *skeys = QEMU_S390_SKEYS(obj);
- MachineState *machine = MACHINE(qdev_get_machine());
+ QEMUS390SKeysState *skeys = QEMU_S390_SKEYS(ss);
- skeys->key_count = machine->ram_size / TARGET_PAGE_SIZE;
- skeys->keydata = g_malloc0(skeys->key_count);
+ /* Lockless check is sufficient. */
+ return !!skeys->keydata;
}
-static int qemu_s390_skeys_enabled(S390SKeysState *ss)
+static bool qemu_s390_enable_skeys(S390SKeysState *ss)
{
- return 1;
+ QEMUS390SKeysState *skeys = QEMU_S390_SKEYS(ss);
+ static gsize initialized;
+
+ if (likely(skeys->keydata)) {
+ return true;
+ }
+
+ /*
+ * TODO: Modern Linux doesn't use storage keys unless running KVM guests
+ * that use storage keys. Therefore, we keep it simple for now.
+ *
+ * 1) We should initialize to "referenced+changed" for an initial
+ * over-indication. Let's avoid touching megabytes of data for now and
+ * assume that any sane user will issue a storage key instruction before
+ * actually relying on this data.
+ * 2) Relying on ram_size and allocating a big array is ugly. We should
+ * allocate and manage storage key data per RAMBlock or optimally using
+ * some sparse data structure.
+ * 3) We only ever have a single S390SKeysState, so relying on
+ * g_once_init_enter() is good enough.
+ */
+ if (g_once_init_enter(&initialized)) {
+ MachineState *machine = MACHINE(qdev_get_machine());
+
+ skeys->key_count = machine->ram_size / TARGET_PAGE_SIZE;
+ skeys->keydata = g_malloc0(skeys->key_count);
+ g_once_init_leave(&initialized, 1);
+ }
+ return false;
}
-/*
- * TODO: for memory hotplug support qemu_s390_skeys_set and qemu_s390_skeys_get
- * will have to make sure that the given gfn belongs to a memory region and not
- * a memory hole.
- */
static int qemu_s390_skeys_set(S390SKeysState *ss, uint64_t start_gfn,
uint64_t count, uint8_t *keys)
{
@@ -198,9 +239,10 @@ static int qemu_s390_skeys_set(S390SKeysState *ss, uint64_t start_gfn,
int i;
/* Check for uint64 overflow and access beyond end of key data */
- if (start_gfn + count > skeydev->key_count || start_gfn + count < count) {
- error_report("Error: Setting storage keys for page beyond the end "
- "of memory: gfn=%" PRIx64 " count=%" PRId64,
+ if (unlikely(!skeydev->keydata || start_gfn + count > skeydev->key_count ||
+ start_gfn + count < count)) {
+ error_report("Error: Setting storage keys for pages with unallocated "
+ "storage key memory: gfn=%" PRIx64 " count=%" PRId64,
start_gfn, count);
return -EINVAL;
}
@@ -218,9 +260,10 @@ static int qemu_s390_skeys_get(S390SKeysState *ss, uint64_t start_gfn,
int i;
/* Check for uint64 overflow and access beyond end of key data */
- if (start_gfn + count > skeydev->key_count || start_gfn + count < count) {
- error_report("Error: Getting storage keys for page beyond the end "
- "of memory: gfn=%" PRIx64 " count=%" PRId64,
+ if (unlikely(!skeydev->keydata || start_gfn + count > skeydev->key_count ||
+ start_gfn + count < count)) {
+ error_report("Error: Getting storage keys for pages with unallocated "
+ "storage key memory: gfn=%" PRIx64 " count=%" PRId64,
start_gfn, count);
return -EINVAL;
}
@@ -236,7 +279,8 @@ static void qemu_s390_skeys_class_init(ObjectClass *oc, void *data)
S390SKeysClass *skeyclass = S390_SKEYS_CLASS(oc);
DeviceClass *dc = DEVICE_CLASS(oc);
- skeyclass->skeys_enabled = qemu_s390_skeys_enabled;
+ skeyclass->skeys_are_enabled = qemu_s390_skeys_are_enabled;
+ skeyclass->enable_skeys = qemu_s390_enable_skeys;
skeyclass->get_skeys = qemu_s390_skeys_get;
skeyclass->set_skeys = qemu_s390_skeys_set;
@@ -247,7 +291,6 @@ static void qemu_s390_skeys_class_init(ObjectClass *oc, void *data)
static const TypeInfo qemu_s390_skeys_info = {
.name = TYPE_QEMU_S390_SKEYS,
.parent = TYPE_S390_SKEYS,
- .instance_init = qemu_s390_skeys_init,
.instance_size = sizeof(QEMUS390SKeysState),
.class_init = qemu_s390_skeys_class_init,
.class_size = sizeof(S390SKeysClass),
@@ -257,14 +300,13 @@ static void s390_storage_keys_save(QEMUFile *f, void *opaque)
{
S390SKeysState *ss = S390_SKEYS(opaque);
S390SKeysClass *skeyclass = S390_SKEYS_GET_CLASS(ss);
- MachineState *ms = MACHINE(qdev_get_machine());
- uint64_t pages_left = ms->ram_size / TARGET_PAGE_SIZE;
- uint64_t read_count, eos = S390_SKEYS_SAVE_FLAG_EOS;
- vaddr cur_gfn = 0;
+ GuestPhysBlockList guest_phys_blocks;
+ GuestPhysBlock *block;
+ uint64_t pages, gfn;
int error = 0;
uint8_t *buf;
- if (!skeyclass->skeys_enabled(ss)) {
+ if (!skeyclass->skeys_are_enabled(ss)) {
goto end_stream;
}
@@ -274,36 +316,52 @@ static void s390_storage_keys_save(QEMUFile *f, void *opaque)
goto end_stream;
}
- /* We only support initial memory. Standby memory is not handled yet. */
- qemu_put_be64(f, (cur_gfn * TARGET_PAGE_SIZE) | S390_SKEYS_SAVE_FLAG_SKEYS);
- qemu_put_be64(f, pages_left);
-
- while (pages_left) {
- read_count = MIN(pages_left, S390_SKEYS_BUFFER_SIZE);
-
- if (!error) {
- error = skeyclass->get_skeys(ss, cur_gfn, read_count, buf);
- if (error) {
- /*
- * If error: we want to fill the stream with valid data instead
- * of stopping early so we pad the stream with 0x00 values and
- * use S390_SKEYS_SAVE_FLAG_ERROR to indicate failure to the
- * reading side.
- */
- error_report("S390_GET_KEYS error %d", error);
- memset(buf, 0, S390_SKEYS_BUFFER_SIZE);
- eos = S390_SKEYS_SAVE_FLAG_ERROR;
+ guest_phys_blocks_init(&guest_phys_blocks);
+ guest_phys_blocks_append(&guest_phys_blocks);
+
+ /* Send each contiguous physical memory range separately. */
+ QTAILQ_FOREACH(block, &guest_phys_blocks.head, next) {
+ assert(QEMU_IS_ALIGNED(block->target_start, TARGET_PAGE_SIZE));
+ assert(QEMU_IS_ALIGNED(block->target_end, TARGET_PAGE_SIZE));
+
+ gfn = block->target_start / TARGET_PAGE_SIZE;
+ pages = (block->target_end - block->target_start) / TARGET_PAGE_SIZE;
+ qemu_put_be64(f, block->target_start | S390_SKEYS_SAVE_FLAG_SKEYS);
+ qemu_put_be64(f, pages);
+
+ while (pages) {
+ const uint64_t cur_pages = MIN(pages, S390_SKEYS_BUFFER_SIZE);
+
+ if (!error) {
+ error = skeyclass->get_skeys(ss, gfn, cur_pages, buf);
+ if (error) {
+ /*
+ * Create a valid stream with all 0x00 and indicate
+ * S390_SKEYS_SAVE_FLAG_ERROR to the destination.
+ */
+ error_report("S390_GET_KEYS error %d", error);
+ memset(buf, 0, S390_SKEYS_BUFFER_SIZE);
+ }
}
+
+ qemu_put_buffer(f, buf, cur_pages);
+ gfn += cur_pages;
+ pages -= cur_pages;
}
- qemu_put_buffer(f, buf, read_count);
- cur_gfn += read_count;
- pages_left -= read_count;
+ if (error) {
+ break;
+ }
}
+ guest_phys_blocks_free(&guest_phys_blocks);
g_free(buf);
end_stream:
- qemu_put_be64(f, eos);
+ if (error) {
+ qemu_put_be64(f, S390_SKEYS_SAVE_FLAG_ERROR);
+ } else {
+ qemu_put_be64(f, S390_SKEYS_SAVE_FLAG_EOS);
+ }
}
static int s390_storage_keys_load(QEMUFile *f, void *opaque, int version_id)
@@ -312,6 +370,14 @@ static int s390_storage_keys_load(QEMUFile *f, void *opaque, int version_id)
S390SKeysClass *skeyclass = S390_SKEYS_GET_CLASS(ss);
int ret = 0;
+ /*
+ * Make sure to lazy-enable if required to be done explicitly. No need to
+ * flush any TLB as the VM is not running yet.
+ */
+ if (skeyclass->enable_skeys) {
+ skeyclass->enable_skeys(ss);
+ }
+
while (!ret) {
ram_addr_t addr;
int flags;
diff --git a/hw/s390x/s390-virtio-ccw.c b/hw/s390x/s390-virtio-ccw.c
index 4d25278cf2..61aeccb163 100644
--- a/hw/s390x/s390-virtio-ccw.c
+++ b/hw/s390x/s390-virtio-ccw.c
@@ -803,6 +803,11 @@ DEFINE_CCW_MACHINE(6_2, "6.2", true);
static void ccw_machine_6_1_instance_options(MachineState *machine)
{
ccw_machine_6_2_instance_options(machine);
+ s390_cpudef_featoff_greater(16, 1, S390_FEAT_NNPA);
+ s390_cpudef_featoff_greater(16, 1, S390_FEAT_VECTOR_PACKED_DECIMAL_ENH2);
+ s390_cpudef_featoff_greater(16, 1, S390_FEAT_BEAR_ENH);
+ s390_cpudef_featoff_greater(16, 1, S390_FEAT_RDP);
+ s390_cpudef_featoff_greater(16, 1, S390_FEAT_PAI);
}
static void ccw_machine_6_1_class_options(MachineClass *mc)
diff --git a/hw/s390x/sclp.c b/hw/s390x/sclp.c
index edb6e3ea01..89c30a8a91 100644
--- a/hw/s390x/sclp.c
+++ b/hw/s390x/sclp.c
@@ -51,7 +51,7 @@ static bool sccb_verify_boundary(uint64_t sccb_addr, uint16_t sccb_len,
uint32_t code)
{
uint64_t sccb_max_addr = sccb_addr + sccb_len - 1;
- uint64_t sccb_boundary = (sccb_addr & PAGE_MASK) + PAGE_SIZE;
+ uint64_t sccb_boundary = (sccb_addr & TARGET_PAGE_MASK) + TARGET_PAGE_SIZE;
switch (code & SCLP_CMD_CODE_MASK) {
case SCLP_CMDW_READ_SCP_INFO:
diff --git a/hw/vfio/ccw.c b/hw/vfio/ccw.c
index 000992fb9f..0354737666 100644
--- a/hw/vfio/ccw.c
+++ b/hw/vfio/ccw.c
@@ -199,7 +199,7 @@ again:
case 0:
case -ENODEV:
case -EACCES:
- return 0;
+ return ret;
case -EFAULT:
default:
sch_gen_unit_exception(sch);
@@ -240,7 +240,7 @@ again:
case -EBUSY:
case -ENODEV:
case -EACCES:
- return 0;
+ return ret;
case -EFAULT:
default:
sch_gen_unit_exception(sch);
diff --git a/include/hw/s390x/css.h b/include/hw/s390x/css.h
index 10ed1df1bb..75e5381613 100644
--- a/include/hw/s390x/css.h
+++ b/include/hw/s390x/css.h
@@ -146,7 +146,8 @@ struct SubchDev {
static inline void sch_gen_unit_exception(SubchDev *sch)
{
- sch->curr_status.scsw.ctrl &= ~SCSW_ACTL_START_PEND;
+ sch->curr_status.scsw.ctrl &= ~(SCSW_ACTL_DEVICE_ACTIVE |
+ SCSW_ACTL_SUBCH_ACTIVE);
sch->curr_status.scsw.ctrl |= SCSW_STCTL_PRIMARY |
SCSW_STCTL_SECONDARY |
SCSW_STCTL_ALERT |
diff --git a/include/hw/s390x/s390-pci-bus.h b/include/hw/s390x/s390-pci-bus.h
index 49ae9f03d3..aa891c178d 100644
--- a/include/hw/s390x/s390-pci-bus.h
+++ b/include/hw/s390x/s390-pci-bus.h
@@ -81,9 +81,6 @@ OBJECT_DECLARE_SIMPLE_TYPE(S390PCIIOMMU, S390_PCI_IOMMU)
#define ZPCI_SDMA_ADDR 0x100000000ULL
#define ZPCI_EDMA_ADDR 0x1ffffffffffffffULL
-#define PAGE_SHIFT 12
-#define PAGE_SIZE (1 << PAGE_SHIFT)
-#define PAGE_MASK (~(PAGE_SIZE-1))
#define PAGE_DEFAULT_ACC 0
#define PAGE_DEFAULT_KEY (PAGE_DEFAULT_ACC << 4)
@@ -137,7 +134,7 @@ enum ZpciIoatDtype {
#define ZPCI_TABLE_BITS 11
#define ZPCI_PT_BITS 8
-#define ZPCI_ST_SHIFT (ZPCI_PT_BITS + PAGE_SHIFT)
+#define ZPCI_ST_SHIFT (ZPCI_PT_BITS + TARGET_PAGE_BITS)
#define ZPCI_RT_SHIFT (ZPCI_ST_SHIFT + ZPCI_TABLE_BITS)
#define ZPCI_RTE_FLAG_MASK 0x3fffULL
diff --git a/include/hw/s390x/storage-keys.h b/include/hw/s390x/storage-keys.h
index 2888d42d0b..aa2ec2aae5 100644
--- a/include/hw/s390x/storage-keys.h
+++ b/include/hw/s390x/storage-keys.h
@@ -28,9 +28,72 @@ struct S390SKeysState {
struct S390SKeysClass {
DeviceClass parent_class;
- int (*skeys_enabled)(S390SKeysState *ks);
+
+ /**
+ * @skeys_are_enabled:
+ *
+ * Check whether storage keys are enabled. If not enabled, they were not
+ * enabled lazily either by the guest via a storage key instruction or
+ * by the host during migration.
+ *
+ * If disabled, everything not explicitly triggered by the guest,
+ * such as outgoing migration or dirty/change tracking, should not touch
+ * storage keys and should not lazily enable it.
+ *
+ * @ks: the #S390SKeysState
+ *
+ * Returns false if not enabled and true if enabled.
+ */
+ bool (*skeys_are_enabled)(S390SKeysState *ks);
+
+ /**
+ * @enable_skeys:
+ *
+ * Lazily enable storage keys. If this function is not implemented,
+ * setting a storage key will lazily enable storage keys implicitly
+ * instead. TCG guests have to make sure to flush the TLB of all CPUs
+ * if storage keys were not enabled before this call.
+ *
+ * @ks: the #S390SKeysState
+ *
+ * Returns false if not enabled before this call, and true if already
+ * enabled.
+ */
+ bool (*enable_skeys)(S390SKeysState *ks);
+
+ /**
+ * @get_skeys:
+ *
+ * Get storage keys for the given PFN range. This call will fail if
+ * storage keys have not been lazily enabled yet.
+ *
+ * Callers have to validate that a GFN is valid before this call.
+ *
+ * @ks: the #S390SKeysState
+ * @start_gfn: the start GFN to get storage keys for
+ * @count: the number of storage keys to get
+ * @keys: the byte array where storage keys will be stored to
+ *
+ * Returns 0 on success, returns an error if getting a storage key failed.
+ */
int (*get_skeys)(S390SKeysState *ks, uint64_t start_gfn, uint64_t count,
uint8_t *keys);
+ /**
+ * @set_skeys:
+ *
+ * Set storage keys for the given PFN range. This call will fail if
+ * storage keys have not been lazily enabled yet and implicit
+ * enablement is not supported.
+ *
+ * Callers have to validate that a GFN is valid before this call.
+ *
+ * @ks: the #S390SKeysState
+ * @start_gfn: the start GFN to set storage keys for
+ * @count: the number of storage keys to set
+ * @keys: the byte array where storage keys will be read from
+ *
+ * Returns 0 on success, returns an error if setting a storage key failed.
+ */
int (*set_skeys)(S390SKeysState *ks, uint64_t start_gfn, uint64_t count,
uint8_t *keys);
};
diff --git a/target/s390x/gen-features.c b/target/s390x/gen-features.c
index 7d85322d68..7cb1a6ec10 100644
--- a/target/s390x/gen-features.c
+++ b/target/s390x/gen-features.c
@@ -663,7 +663,13 @@ static uint16_t default_GEN15_GA1[] = {
S390_FEAT_ETOKEN,
};
-#define default_GEN16_GA1 EmptyFeat
+static uint16_t default_GEN16_GA1[] = {
+ S390_FEAT_NNPA,
+ S390_FEAT_VECTOR_PACKED_DECIMAL_ENH2,
+ S390_FEAT_BEAR_ENH,
+ S390_FEAT_RDP,
+ S390_FEAT_PAI,
+};
/* QEMU (CPU model) features */
diff --git a/target/s390x/helper.h b/target/s390x/helper.h
index 6215ca00bc..271b081e8c 100644
--- a/target/s390x/helper.h
+++ b/target/s390x/helper.h
@@ -336,9 +336,9 @@ DEF_HELPER_FLAGS_4(stctl, TCG_CALL_NO_WG, void, env, i32, i64, i32)
DEF_HELPER_FLAGS_4(stctg, TCG_CALL_NO_WG, void, env, i32, i64, i32)
DEF_HELPER_FLAGS_2(testblock, TCG_CALL_NO_WG, i32, env, i64)
DEF_HELPER_FLAGS_3(tprot, TCG_CALL_NO_WG, i32, env, i64, i64)
-DEF_HELPER_FLAGS_2(iske, TCG_CALL_NO_RWG_SE, i64, env, i64)
-DEF_HELPER_FLAGS_3(sske, TCG_CALL_NO_RWG, void, env, i64, i64)
-DEF_HELPER_FLAGS_2(rrbe, TCG_CALL_NO_RWG, i32, env, i64)
+DEF_HELPER_2(iske, i64, env, i64)
+DEF_HELPER_3(sske, void, env, i64, i64)
+DEF_HELPER_2(rrbe, i32, env, i64)
DEF_HELPER_4(mvcs, i32, env, i64, i64, i64)
DEF_HELPER_4(mvcp, i32, env, i64, i64, i64)
DEF_HELPER_4(sigp, i32, env, i64, i32, i32)
diff --git a/target/s390x/ioinst.c b/target/s390x/ioinst.c
index 4eb0a7a9f8..bdae5090bc 100644
--- a/target/s390x/ioinst.c
+++ b/target/s390x/ioinst.c
@@ -123,7 +123,7 @@ static int ioinst_schib_valid(SCHIB *schib)
}
/* for MB format 1 bits 26-31 of word 11 must be 0 */
/* MBA uses words 10 and 11, it means align on 2**6 */
- if ((be16_to_cpu(schib->pmcw.chars) & PMCW_CHARS_MASK_MBFC) &&
+ if ((be32_to_cpu(schib->pmcw.chars) & PMCW_CHARS_MASK_MBFC) &&
(be64_to_cpu(schib->mba) & 0x03fUL)) {
return 0;
}
diff --git a/target/s390x/mmu_helper.c b/target/s390x/mmu_helper.c
index d779a9fc51..b04b57c235 100644
--- a/target/s390x/mmu_helper.c
+++ b/target/s390x/mmu_helper.c
@@ -94,6 +94,14 @@ target_ulong mmu_real2abs(CPUS390XState *env, target_ulong raddr)
return raddr;
}
+bool mmu_absolute_addr_valid(target_ulong addr, bool is_write)
+{
+ return address_space_access_valid(&address_space_memory,
+ addr & TARGET_PAGE_MASK,
+ TARGET_PAGE_SIZE, is_write,
+ MEMTXATTRS_UNSPECIFIED);
+}
+
static inline bool read_table_entry(CPUS390XState *env, hwaddr gaddr,
uint64_t *entry)
{
@@ -117,7 +125,7 @@ static inline bool read_table_entry(CPUS390XState *env, hwaddr gaddr,
static int mmu_translate_asce(CPUS390XState *env, target_ulong vaddr,
uint64_t asc, uint64_t asce, target_ulong *raddr,
- int *flags, int rw)
+ int *flags)
{
const bool edat1 = (env->cregs[0] & CR0_EDAT) &&
s390_has_feat(S390_FEAT_EDAT);
@@ -293,20 +301,27 @@ static void mmu_handle_skey(target_ulong addr, int rw, int *flags)
{
static S390SKeysClass *skeyclass;
static S390SKeysState *ss;
- MachineState *ms = MACHINE(qdev_get_machine());
- uint8_t key;
+ uint8_t key, old_key;
int rc;
- if (unlikely(addr >= ms->ram_size)) {
- return;
- }
-
+ /*
+ * We expect to be called with an absolute address that has already been
+ * validated, such that we can reliably use it to lookup the storage key.
+ */
if (unlikely(!ss)) {
ss = s390_get_skeys_device();
skeyclass = S390_SKEYS_GET_CLASS(ss);
}
/*
+ * Don't enable storage keys if they are still disabled, i.e., no actual
+ * storage key instruction was issued yet.
+ */
+ if (!skeyclass->skeys_are_enabled(ss)) {
+ return;
+ }
+
+ /*
* Whenever we create a new TLB entry, we set the storage key reference
* bit. In case we allow write accesses, we set the storage key change
* bit. Whenever the guest changes the storage key, we have to flush the
@@ -330,6 +345,7 @@ static void mmu_handle_skey(target_ulong addr, int rw, int *flags)
trace_get_skeys_nonzero(rc);
return;
}
+ old_key = key;
switch (rw) {
case MMU_DATA_LOAD:
@@ -353,20 +369,23 @@ static void mmu_handle_skey(target_ulong addr, int rw, int *flags)
/* Any store/fetch sets the reference bit */
key |= SK_R;
- rc = skeyclass->set_skeys(ss, addr / TARGET_PAGE_SIZE, 1, &key);
- if (rc) {
- trace_set_skeys_nonzero(rc);
+ if (key != old_key) {
+ rc = skeyclass->set_skeys(ss, addr / TARGET_PAGE_SIZE, 1, &key);
+ if (rc) {
+ trace_set_skeys_nonzero(rc);
+ }
}
}
/**
* Translate a virtual (logical) address into a physical (absolute) address.
* @param vaddr the virtual address
- * @param rw 0 = read, 1 = write, 2 = code fetch
+ * @param rw 0 = read, 1 = write, 2 = code fetch, < 0 = load real address
* @param asc address space control (one of the PSW_ASC_* modes)
* @param raddr the translated address is stored to this pointer
* @param flags the PAGE_READ/WRITE/EXEC flags are stored to this pointer
- * @param exc true = inject a program check if a fault occurred
+ * @param tec the translation exception code if stored to this pointer if
+ * there is an exception to raise
* @return 0 = success, != 0, the exception to raise
*/
int mmu_translate(CPUS390XState *env, target_ulong vaddr, int rw, uint64_t asc,
@@ -420,7 +439,7 @@ int mmu_translate(CPUS390XState *env, target_ulong vaddr, int rw, uint64_t asc,
}
/* perform the DAT translation */
- r = mmu_translate_asce(env, vaddr, asc, asce, raddr, flags, rw);
+ r = mmu_translate_asce(env, vaddr, asc, asce, raddr, flags);
if (unlikely(r)) {
return r;
}
@@ -440,10 +459,17 @@ int mmu_translate(CPUS390XState *env, target_ulong vaddr, int rw, uint64_t asc,
}
nodat:
- /* Convert real address -> absolute address */
- *raddr = mmu_real2abs(env, *raddr);
+ if (rw >= 0) {
+ /* Convert real address -> absolute address */
+ *raddr = mmu_real2abs(env, *raddr);
- mmu_handle_skey(*raddr, rw, flags);
+ if (!mmu_absolute_addr_valid(*raddr, rw == MMU_DATA_STORE)) {
+ *tec = 0; /* unused */
+ return PGM_ADDRESSING;
+ }
+
+ mmu_handle_skey(*raddr, rw, flags);
+ }
return 0;
}
@@ -464,12 +490,6 @@ static int translate_pages(S390CPU *cpu, vaddr addr, int nr_pages,
if (ret) {
return ret;
}
- if (!address_space_access_valid(&address_space_memory, pages[i],
- TARGET_PAGE_SIZE, is_write,
- MEMTXATTRS_UNSPECIFIED)) {
- *tec = 0; /* unused */
- return PGM_ADDRESSING;
- }
addr += TARGET_PAGE_SIZE;
}
@@ -579,6 +599,12 @@ int mmu_translate_real(CPUS390XState *env, target_ulong raddr, int rw,
*addr = mmu_real2abs(env, raddr & TARGET_PAGE_MASK);
+ if (!mmu_absolute_addr_valid(*addr, rw == MMU_DATA_STORE)) {
+ /* unused */
+ *tec = 0;
+ return PGM_ADDRESSING;
+ }
+
mmu_handle_skey(*addr, rw, flags);
return 0;
}
diff --git a/target/s390x/s390x-internal.h b/target/s390x/s390x-internal.h
index 5506f185e8..7a6aa4dacc 100644
--- a/target/s390x/s390x-internal.h
+++ b/target/s390x/s390x-internal.h
@@ -373,6 +373,9 @@ void probe_write_access(CPUS390XState *env, uint64_t addr, uint64_t len,
/* mmu_helper.c */
+bool mmu_absolute_addr_valid(target_ulong addr, bool is_write);
+/* Special access mode only valid for mmu_translate() */
+#define MMU_S390_LRA -1
int mmu_translate(CPUS390XState *env, target_ulong vaddr, int rw, uint64_t asc,
target_ulong *raddr, int *flags, uint64_t *tec);
int mmu_translate_real(CPUS390XState *env, target_ulong raddr, int rw,
diff --git a/target/s390x/tcg/excp_helper.c b/target/s390x/tcg/excp_helper.c
index a61917d04f..3d6662a53c 100644
--- a/target/s390x/tcg/excp_helper.c
+++ b/target/s390x/tcg/excp_helper.c
@@ -150,19 +150,6 @@ bool s390_cpu_tlb_fill(CPUState *cs, vaddr address, int size,
g_assert_not_reached();
}
- /* check out of RAM access */
- if (!excp &&
- !address_space_access_valid(&address_space_memory, raddr,
- TARGET_PAGE_SIZE, access_type,
- MEMTXATTRS_UNSPECIFIED)) {
- MachineState *ms = MACHINE(qdev_get_machine());
- qemu_log_mask(CPU_LOG_MMU,
- "%s: raddr %" PRIx64 " > ram_size %" PRIx64 "\n",
- __func__, (uint64_t)raddr, (uint64_t)ms->ram_size);
- excp = PGM_ADDRESSING;
- tec = 0; /* unused */
- }
-
env->tlb_fill_exc = excp;
env->tlb_fill_tec = tec;
diff --git a/target/s390x/tcg/mem_helper.c b/target/s390x/tcg/mem_helper.c
index 21a4de4067..0bf775a37d 100644
--- a/target/s390x/tcg/mem_helper.c
+++ b/target/s390x/tcg/mem_helper.c
@@ -28,6 +28,7 @@
#include "qemu/int128.h"
#include "qemu/atomic128.h"
#include "tcg/tcg.h"
+#include "trace.h"
#if !defined(CONFIG_USER_ONLY)
#include "hw/s390x/storage-keys.h"
@@ -2171,22 +2172,28 @@ uint32_t HELPER(tprot)(CPUS390XState *env, uint64_t a1, uint64_t a2)
/* insert storage key extended */
uint64_t HELPER(iske)(CPUS390XState *env, uint64_t r2)
{
- MachineState *ms = MACHINE(qdev_get_machine());
static S390SKeysState *ss;
static S390SKeysClass *skeyclass;
uint64_t addr = wrap_address(env, r2);
uint8_t key;
+ int rc;
- if (addr > ms->ram_size) {
- return 0;
+ addr = mmu_real2abs(env, addr);
+ if (!mmu_absolute_addr_valid(addr, false)) {
+ tcg_s390_program_interrupt(env, PGM_ADDRESSING, GETPC());
}
if (unlikely(!ss)) {
ss = s390_get_skeys_device();
skeyclass = S390_SKEYS_GET_CLASS(ss);
+ if (skeyclass->enable_skeys && !skeyclass->enable_skeys(ss)) {
+ tlb_flush_all_cpus_synced(env_cpu(env));
+ }
}
- if (skeyclass->get_skeys(ss, addr / TARGET_PAGE_SIZE, 1, &key)) {
+ rc = skeyclass->get_skeys(ss, addr / TARGET_PAGE_SIZE, 1, &key);
+ if (rc) {
+ trace_get_skeys_nonzero(rc);
return 0;
}
return key;
@@ -2195,23 +2202,30 @@ uint64_t HELPER(iske)(CPUS390XState *env, uint64_t r2)
/* set storage key extended */
void HELPER(sske)(CPUS390XState *env, uint64_t r1, uint64_t r2)
{
- MachineState *ms = MACHINE(qdev_get_machine());
static S390SKeysState *ss;
static S390SKeysClass *skeyclass;
uint64_t addr = wrap_address(env, r2);
uint8_t key;
+ int rc;
- if (addr > ms->ram_size) {
- return;
+ addr = mmu_real2abs(env, addr);
+ if (!mmu_absolute_addr_valid(addr, false)) {
+ tcg_s390_program_interrupt(env, PGM_ADDRESSING, GETPC());
}
if (unlikely(!ss)) {
ss = s390_get_skeys_device();
skeyclass = S390_SKEYS_GET_CLASS(ss);
+ if (skeyclass->enable_skeys && !skeyclass->enable_skeys(ss)) {
+ tlb_flush_all_cpus_synced(env_cpu(env));
+ }
}
- key = (uint8_t) r1;
- skeyclass->set_skeys(ss, addr / TARGET_PAGE_SIZE, 1, &key);
+ key = r1 & 0xfe;
+ rc = skeyclass->set_skeys(ss, addr / TARGET_PAGE_SIZE, 1, &key);
+ if (rc) {
+ trace_set_skeys_nonzero(rc);
+ }
/*
* As we can only flush by virtual address and not all the entries
* that point to a physical address we have to flush the whole TLB.
@@ -2222,28 +2236,37 @@ void HELPER(sske)(CPUS390XState *env, uint64_t r1, uint64_t r2)
/* reset reference bit extended */
uint32_t HELPER(rrbe)(CPUS390XState *env, uint64_t r2)
{
- MachineState *ms = MACHINE(qdev_get_machine());
+ uint64_t addr = wrap_address(env, r2);
static S390SKeysState *ss;
static S390SKeysClass *skeyclass;
uint8_t re, key;
+ int rc;
- if (r2 > ms->ram_size) {
- return 0;
+ addr = mmu_real2abs(env, addr);
+ if (!mmu_absolute_addr_valid(addr, false)) {
+ tcg_s390_program_interrupt(env, PGM_ADDRESSING, GETPC());
}
if (unlikely(!ss)) {
ss = s390_get_skeys_device();
skeyclass = S390_SKEYS_GET_CLASS(ss);
+ if (skeyclass->enable_skeys && !skeyclass->enable_skeys(ss)) {
+ tlb_flush_all_cpus_synced(env_cpu(env));
+ }
}
- if (skeyclass->get_skeys(ss, r2 / TARGET_PAGE_SIZE, 1, &key)) {
+ rc = skeyclass->get_skeys(ss, addr / TARGET_PAGE_SIZE, 1, &key);
+ if (rc) {
+ trace_get_skeys_nonzero(rc);
return 0;
}
re = key & (SK_R | SK_C);
key &= ~SK_R;
- if (skeyclass->set_skeys(ss, r2 / TARGET_PAGE_SIZE, 1, &key)) {
+ rc = skeyclass->set_skeys(ss, addr / TARGET_PAGE_SIZE, 1, &key);
+ if (rc) {
+ trace_set_skeys_nonzero(rc);
return 0;
}
/*
@@ -2441,7 +2464,7 @@ uint64_t HELPER(lra)(CPUS390XState *env, uint64_t addr)
tcg_s390_program_interrupt(env, PGM_SPECIAL_OP, GETPC());
}
- exc = mmu_translate(env, addr, 0, asc, &ret, &flags, &tec);
+ exc = mmu_translate(env, addr, MMU_S390_LRA, asc, &ret, &flags, &tec);
if (exc) {
cc = 3;
ret = exc | 0x80000000;
diff --git a/target/s390x/tcg/misc_helper.c b/target/s390x/tcg/misc_helper.c
index 33e6999e15..aab9c47747 100644
--- a/target/s390x/tcg/misc_helper.c
+++ b/target/s390x/tcg/misc_helper.c
@@ -151,13 +151,26 @@ void HELPER(diag)(CPUS390XState *env, uint32_t r1, uint32_t r3, uint32_t num)
/* Set Prefix */
void HELPER(spx)(CPUS390XState *env, uint64_t a1)
{
+ const uint32_t prefix = a1 & 0x7fffe000;
+ const uint32_t old_prefix = env->psa;
CPUState *cs = env_cpu(env);
- uint32_t prefix = a1 & 0x7fffe000;
+
+ if (prefix == old_prefix) {
+ return;
+ }
env->psa = prefix;
HELPER_LOG("prefix: %#x\n", prefix);
tlb_flush_page(cs, 0);
tlb_flush_page(cs, TARGET_PAGE_SIZE);
+ if (prefix != 0) {
+ tlb_flush_page(cs, prefix);
+ tlb_flush_page(cs, prefix + TARGET_PAGE_SIZE);
+ }
+ if (old_prefix != 0) {
+ tlb_flush_page(cs, old_prefix);
+ tlb_flush_page(cs, old_prefix + TARGET_PAGE_SIZE);
+ }
}
static void update_ckc_timer(CPUS390XState *env)
diff --git a/tests/tcg/s390x/Makefile.target b/tests/tcg/s390x/Makefile.target
index bd084c7840..cc64dd32d2 100644
--- a/tests/tcg/s390x/Makefile.target
+++ b/tests/tcg/s390x/Makefile.target
@@ -1,4 +1,5 @@
-VPATH+=$(SRC_PATH)/tests/tcg/s390x
+S390X_SRC=$(SRC_PATH)/tests/tcg/s390x
+VPATH+=$(S390X_SRC)
CFLAGS+=-march=zEC12 -m64
TESTS+=hello-s390x
TESTS+=csst
@@ -9,3 +10,17 @@ TESTS+=pack
TESTS+=mvo
TESTS+=mvc
TESTS+=trap
+TESTS+=signals-s390x
+
+ifneq ($(HAVE_GDB_BIN),)
+GDB_SCRIPT=$(SRC_PATH)/tests/guest-debug/run-test.py
+
+run-gdbstub-signals-s390x: signals-s390x
+ $(call run-test, $@, $(GDB_SCRIPT) \
+ --gdb $(HAVE_GDB_BIN) \
+ --qemu $(QEMU) --qargs "$(QEMU_OPTS)" \
+ --bin $< --test $(S390X_SRC)/gdbstub/test-signals-s390x.py, \
+ "mixing signals and debugging on s390x")
+
+EXTRA_RUNS += run-gdbstub-signals-s390x
+endif
diff --git a/tests/tcg/s390x/gdbstub/test-signals-s390x.py b/tests/tcg/s390x/gdbstub/test-signals-s390x.py
new file mode 100644
index 0000000000..80a284b475
--- /dev/null
+++ b/tests/tcg/s390x/gdbstub/test-signals-s390x.py
@@ -0,0 +1,76 @@
+from __future__ import print_function
+
+#
+# Test that signals and debugging mix well together on s390x.
+#
+# This is launched via tests/guest-debug/run-test.py
+#
+
+import gdb
+import sys
+
+failcount = 0
+
+
+def report(cond, msg):
+ """Report success/fail of test"""
+ if cond:
+ print("PASS: %s" % (msg))
+ else:
+ print("FAIL: %s" % (msg))
+ global failcount
+ failcount += 1
+
+
+def run_test():
+ """Run through the tests one by one"""
+ illegal_op = gdb.Breakpoint("illegal_op")
+ stg = gdb.Breakpoint("stg")
+ mvc_8 = gdb.Breakpoint("mvc_8")
+
+ # Expect the following events:
+ # 1x illegal_op breakpoint
+ # 2x stg breakpoint, segv, breakpoint
+ # 2x mvc_8 breakpoint, segv, breakpoint
+ for _ in range(14):
+ gdb.execute("c")
+ report(illegal_op.hit_count == 1, "illegal_op.hit_count == 1")
+ report(stg.hit_count == 4, "stg.hit_count == 4")
+ report(mvc_8.hit_count == 4, "mvc_8.hit_count == 4")
+
+ # The test must succeed.
+ gdb.Breakpoint("_exit")
+ gdb.execute("c")
+ status = int(gdb.parse_and_eval("$r2"))
+ report(status == 0, "status == 0");
+
+
+#
+# This runs as the script it sourced (via -x, via run-test.py)
+#
+try:
+ inferior = gdb.selected_inferior()
+ arch = inferior.architecture()
+ print("ATTACHED: %s" % arch.name())
+except (gdb.error, AttributeError):
+ print("SKIPPING (not connected)", file=sys.stderr)
+ exit(0)
+
+if gdb.parse_and_eval("$pc") == 0:
+ print("SKIP: PC not set")
+ exit(0)
+
+try:
+ # These are not very useful in scripts
+ gdb.execute("set pagination off")
+ gdb.execute("set confirm off")
+
+ # Run the actual tests
+ run_test()
+except (gdb.error):
+ print("GDB Exception: %s" % (sys.exc_info()[0]))
+ failcount += 1
+ pass
+
+print("All tests complete: %d failures" % failcount)
+exit(failcount)
diff --git a/tests/tcg/s390x/signals-s390x.c b/tests/tcg/s390x/signals-s390x.c
new file mode 100644
index 0000000000..dc2f8ee59a
--- /dev/null
+++ b/tests/tcg/s390x/signals-s390x.c
@@ -0,0 +1,165 @@
+#include <assert.h>
+#include <signal.h>
+#include <string.h>
+#include <sys/mman.h>
+#include <ucontext.h>
+#include <unistd.h>
+
+/*
+ * Various instructions that generate SIGILL and SIGSEGV. They could have been
+ * defined in a separate .s file, but this would complicate the build, so the
+ * inline asm is used instead.
+ */
+
+void illegal_op(void);
+void after_illegal_op(void);
+asm(".globl\tillegal_op\n"
+ "illegal_op:\t.byte\t0x00,0x00\n"
+ "\t.globl\tafter_illegal_op\n"
+ "after_illegal_op:\tbr\t%r14");
+
+void stg(void *dst, unsigned long src);
+asm(".globl\tstg\n"
+ "stg:\tstg\t%r3,0(%r2)\n"
+ "\tbr\t%r14");
+
+void mvc_8(void *dst, void *src);
+asm(".globl\tmvc_8\n"
+ "mvc_8:\tmvc\t0(8,%r2),0(%r3)\n"
+ "\tbr\t%r14");
+
+static void safe_puts(const char *s)
+{
+ write(0, s, strlen(s));
+ write(0, "\n", 1);
+}
+
+enum exception {
+ exception_operation,
+ exception_translation,
+ exception_protection,
+};
+
+static struct {
+ int sig;
+ void *addr;
+ unsigned long psw_addr;
+ enum exception exception;
+} expected;
+
+static void handle_signal(int sig, siginfo_t *info, void *ucontext)
+{
+ void *page;
+ int err;
+
+ if (sig != expected.sig) {
+ safe_puts("[ FAILED ] wrong signal");
+ _exit(1);
+ }
+
+ if (info->si_addr != expected.addr) {
+ safe_puts("[ FAILED ] wrong si_addr");
+ _exit(1);
+ }
+
+ if (((ucontext_t *)ucontext)->uc_mcontext.psw.addr != expected.psw_addr) {
+ safe_puts("[ FAILED ] wrong psw.addr");
+ _exit(1);
+ }
+
+ switch (expected.exception) {
+ case exception_translation:
+ page = mmap(expected.addr, 4096, PROT_READ | PROT_WRITE,
+ MAP_PRIVATE | MAP_ANONYMOUS | MAP_FIXED, -1, 0);
+ if (page != expected.addr) {
+ safe_puts("[ FAILED ] mmap() failed");
+ _exit(1);
+ }
+ break;
+ case exception_protection:
+ err = mprotect(expected.addr, 4096, PROT_READ | PROT_WRITE);
+ if (err != 0) {
+ safe_puts("[ FAILED ] mprotect() failed");
+ _exit(1);
+ }
+ break;
+ default:
+ break;
+ }
+}
+
+static void check_sigsegv(void *func, enum exception exception,
+ unsigned long val)
+{
+ int prot;
+ unsigned long *page;
+ unsigned long *addr;
+ int err;
+
+ prot = exception == exception_translation ? PROT_NONE : PROT_READ;
+ page = mmap(NULL, 4096, prot, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
+ assert(page != MAP_FAILED);
+ if (exception == exception_translation) {
+ /* Hopefully nothing will be mapped at this address. */
+ err = munmap(page, 4096);
+ assert(err == 0);
+ }
+ addr = page + (val & 0x1ff);
+
+ expected.sig = SIGSEGV;
+ expected.addr = page;
+ expected.psw_addr = (unsigned long)func;
+ expected.exception = exception;
+ if (func == stg) {
+ stg(addr, val);
+ } else {
+ assert(func == mvc_8);
+ mvc_8(addr, &val);
+ }
+ assert(*addr == val);
+
+ err = munmap(page, 4096);
+ assert(err == 0);
+}
+
+int main(void)
+{
+ struct sigaction act;
+ int err;
+
+ memset(&act, 0, sizeof(act));
+ act.sa_sigaction = handle_signal;
+ act.sa_flags = SA_SIGINFO;
+ err = sigaction(SIGILL, &act, NULL);
+ assert(err == 0);
+ err = sigaction(SIGSEGV, &act, NULL);
+ assert(err == 0);
+
+ safe_puts("[ RUN ] Operation exception");
+ expected.sig = SIGILL;
+ expected.addr = illegal_op;
+ expected.psw_addr = (unsigned long)after_illegal_op;
+ expected.exception = exception_operation;
+ illegal_op();
+ safe_puts("[ OK ]");
+
+ safe_puts("[ RUN ] Translation exception from stg");
+ check_sigsegv(stg, exception_translation, 42);
+ safe_puts("[ OK ]");
+
+ safe_puts("[ RUN ] Translation exception from mvc");
+ check_sigsegv(mvc_8, exception_translation, 4242);
+ safe_puts("[ OK ]");
+
+ safe_puts("[ RUN ] Protection exception from stg");
+ check_sigsegv(stg, exception_protection, 424242);
+ safe_puts("[ OK ]");
+
+ safe_puts("[ RUN ] Protection exception from mvc");
+ check_sigsegv(mvc_8, exception_protection, 42424242);
+ safe_puts("[ OK ]");
+
+ safe_puts("[ PASSED ]");
+
+ _exit(0);
+}