diff options
-rw-r--r-- | hw/s390x/css.c | 38 | ||||
-rw-r--r-- | hw/s390x/s390-pci-bus.c | 10 | ||||
-rw-r--r-- | hw/s390x/s390-pci-inst.c | 8 | ||||
-rw-r--r-- | hw/s390x/s390-skeys-kvm.c | 4 | ||||
-rw-r--r-- | hw/s390x/s390-skeys.c | 206 | ||||
-rw-r--r-- | hw/s390x/s390-virtio-ccw.c | 5 | ||||
-rw-r--r-- | hw/s390x/sclp.c | 2 | ||||
-rw-r--r-- | hw/vfio/ccw.c | 4 | ||||
-rw-r--r-- | include/hw/s390x/css.h | 3 | ||||
-rw-r--r-- | include/hw/s390x/s390-pci-bus.h | 5 | ||||
-rw-r--r-- | include/hw/s390x/storage-keys.h | 65 | ||||
-rw-r--r-- | target/s390x/gen-features.c | 8 | ||||
-rw-r--r-- | target/s390x/helper.h | 6 | ||||
-rw-r--r-- | target/s390x/ioinst.c | 2 | ||||
-rw-r--r-- | target/s390x/mmu_helper.c | 70 | ||||
-rw-r--r-- | target/s390x/s390x-internal.h | 3 | ||||
-rw-r--r-- | target/s390x/tcg/excp_helper.c | 13 | ||||
-rw-r--r-- | target/s390x/tcg/mem_helper.c | 53 | ||||
-rw-r--r-- | target/s390x/tcg/misc_helper.c | 15 | ||||
-rw-r--r-- | tests/tcg/s390x/Makefile.target | 17 | ||||
-rw-r--r-- | tests/tcg/s390x/gdbstub/test-signals-s390x.py | 76 | ||||
-rw-r--r-- | tests/tcg/s390x/signals-s390x.c | 165 |
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); +} |