| /* |
| * s390 storage key device |
| * |
| * Copyright 2015 IBM Corp. |
| * Author(s): Jason J. Herne <jjherne@linux.vnet.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/units.h" |
| #include "hw/boards.h" |
| #include "hw/s390x/storage-keys.h" |
| #include "qapi/error.h" |
| #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" |
| |
| #define S390_SKEYS_BUFFER_SIZE (128 * KiB) /* Room for 128k storage keys */ |
| #define S390_SKEYS_SAVE_FLAG_EOS 0x01 |
| #define S390_SKEYS_SAVE_FLAG_SKEYS 0x02 |
| #define S390_SKEYS_SAVE_FLAG_ERROR 0x04 |
| |
| S390SKeysState *s390_get_skeys_device(void) |
| { |
| S390SKeysState *ss; |
| |
| ss = S390_SKEYS(object_resolve_path_type("", TYPE_S390_SKEYS, NULL)); |
| assert(ss); |
| return ss; |
| } |
| |
| void s390_skeys_init(void) |
| { |
| Object *obj; |
| |
| if (kvm_enabled()) { |
| obj = object_new(TYPE_KVM_S390_SKEYS); |
| } else { |
| obj = object_new(TYPE_QEMU_S390_SKEYS); |
| } |
| object_property_add_child(qdev_get_machine(), TYPE_S390_SKEYS, |
| obj); |
| object_unref(obj); |
| |
| qdev_realize(DEVICE(obj), NULL, &error_fatal); |
| } |
| |
| static void write_keys(FILE *f, uint8_t *keys, uint64_t startgfn, |
| uint64_t count, Error **errp) |
| { |
| uint64_t curpage = startgfn; |
| uint64_t maxpage = curpage + count - 1; |
| |
| for (; curpage <= maxpage; curpage++) { |
| uint8_t acc = (*keys & 0xF0) >> 4; |
| int fp = (*keys & 0x08); |
| int ref = (*keys & 0x04); |
| int ch = (*keys & 0x02); |
| int res = (*keys & 0x01); |
| |
| fprintf(f, "page=%03" PRIx64 ": key(%d) => ACC=%X, FP=%d, REF=%d," |
| " ch=%d, reserved=%d\n", |
| curpage, *keys, acc, fp, ref, ch, res); |
| keys++; |
| } |
| } |
| |
| void hmp_info_skeys(Monitor *mon, const QDict *qdict) |
| { |
| S390SKeysState *ss = s390_get_skeys_device(); |
| S390SKeysClass *skeyclass = S390_SKEYS_GET_CLASS(ss); |
| uint64_t addr = qdict_get_int(qdict, "addr"); |
| uint8_t key; |
| int r; |
| |
| /* Quick check to see if guest is using storage keys*/ |
| 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)); |
| return; |
| } |
| |
| monitor_printf(mon, " key: 0x%X\n", key); |
| } |
| |
| void hmp_dump_skeys(Monitor *mon, const QDict *qdict) |
| { |
| const char *filename = qdict_get_str(qdict, "filename"); |
| Error *err = NULL; |
| |
| qmp_dump_skeys(filename, &err); |
| if (err) { |
| error_report_err(err); |
| } |
| } |
| |
| void qmp_dump_skeys(const char *filename, Error **errp) |
| { |
| S390SKeysState *ss = s390_get_skeys_device(); |
| S390SKeysClass *skeyclass = S390_SKEYS_GET_CLASS(ss); |
| GuestPhysBlockList guest_phys_blocks; |
| GuestPhysBlock *block; |
| uint64_t pages, gfn; |
| Error *lerr = NULL; |
| uint8_t *buf; |
| int ret; |
| int fd; |
| FILE *f; |
| |
| /* Quick check to see if guest is using storage keys*/ |
| if (!skeyclass->skeys_are_enabled(ss)) { |
| error_setg(errp, "This guest is not using storage keys - " |
| "nothing to dump"); |
| return; |
| } |
| |
| fd = qemu_open_old(filename, O_WRONLY | O_CREAT | O_TRUNC, 0600); |
| if (fd < 0) { |
| error_setg_file_open(errp, errno, filename); |
| return; |
| } |
| f = fdopen(fd, "wb"); |
| if (!f) { |
| close(fd); |
| error_setg_file_open(errp, errno, filename); |
| return; |
| } |
| |
| buf = g_try_malloc(S390_SKEYS_BUFFER_SIZE); |
| if (!buf) { |
| error_setg(errp, "Could not allocate memory"); |
| goto out; |
| } |
| |
| assert(qemu_mutex_iothread_locked()); |
| guest_phys_blocks_init(&guest_phys_blocks); |
| guest_phys_blocks_append(&guest_phys_blocks); |
| |
| 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; |
| |
| while (pages) { |
| const uint64_t cur_pages = MIN(pages, S390_SKEYS_BUFFER_SIZE); |
| |
| 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 bool qemu_s390_skeys_are_enabled(S390SKeysState *ss) |
| { |
| QEMUS390SKeysState *skeys = QEMU_S390_SKEYS(ss); |
| |
| /* Lockless check is sufficient. */ |
| return !!skeys->keydata; |
| } |
| |
| static bool qemu_s390_enable_skeys(S390SKeysState *ss) |
| { |
| 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; |
| } |
| |
| static int qemu_s390_skeys_set(S390SKeysState *ss, uint64_t start_gfn, |
| uint64_t count, uint8_t *keys) |
| { |
| QEMUS390SKeysState *skeydev = QEMU_S390_SKEYS(ss); |
| int i; |
| |
| /* Check for uint64 overflow and access beyond end of key data */ |
| 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; |
| } |
| |
| for (i = 0; i < count; i++) { |
| skeydev->keydata[start_gfn + i] = keys[i]; |
| } |
| return 0; |
| } |
| |
| static int qemu_s390_skeys_get(S390SKeysState *ss, uint64_t start_gfn, |
| uint64_t count, uint8_t *keys) |
| { |
| QEMUS390SKeysState *skeydev = QEMU_S390_SKEYS(ss); |
| int i; |
| |
| /* Check for uint64 overflow and access beyond end of key data */ |
| 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; |
| } |
| |
| for (i = 0; i < count; i++) { |
| keys[i] = skeydev->keydata[start_gfn + i]; |
| } |
| return 0; |
| } |
| |
| static void qemu_s390_skeys_class_init(ObjectClass *oc, void *data) |
| { |
| S390SKeysClass *skeyclass = S390_SKEYS_CLASS(oc); |
| DeviceClass *dc = DEVICE_CLASS(oc); |
| |
| 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; |
| |
| /* Reason: Internal device (only one skeys device for the whole memory) */ |
| dc->user_creatable = false; |
| } |
| |
| static const TypeInfo qemu_s390_skeys_info = { |
| .name = TYPE_QEMU_S390_SKEYS, |
| .parent = TYPE_S390_SKEYS, |
| .instance_size = sizeof(QEMUS390SKeysState), |
| .class_init = qemu_s390_skeys_class_init, |
| .class_size = sizeof(S390SKeysClass), |
| }; |
| |
| static void s390_storage_keys_save(QEMUFile *f, void *opaque) |
| { |
| S390SKeysState *ss = S390_SKEYS(opaque); |
| S390SKeysClass *skeyclass = S390_SKEYS_GET_CLASS(ss); |
| GuestPhysBlockList guest_phys_blocks; |
| GuestPhysBlock *block; |
| uint64_t pages, gfn; |
| int error = 0; |
| uint8_t *buf; |
| |
| if (!skeyclass->skeys_are_enabled(ss)) { |
| goto end_stream; |
| } |
| |
| buf = g_try_malloc(S390_SKEYS_BUFFER_SIZE); |
| if (!buf) { |
| error_report("storage key save could not allocate memory"); |
| goto end_stream; |
| } |
| |
| 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; |
| } |
| |
| if (error) { |
| break; |
| } |
| } |
| |
| guest_phys_blocks_free(&guest_phys_blocks); |
| g_free(buf); |
| end_stream: |
| 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) |
| { |
| S390SKeysState *ss = S390_SKEYS(opaque); |
| 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; |
| |
| addr = qemu_get_be64(f); |
| flags = addr & ~TARGET_PAGE_MASK; |
| addr &= TARGET_PAGE_MASK; |
| |
| switch (flags) { |
| case S390_SKEYS_SAVE_FLAG_SKEYS: { |
| const uint64_t total_count = qemu_get_be64(f); |
| uint64_t handled_count = 0, cur_count; |
| uint64_t cur_gfn = addr / TARGET_PAGE_SIZE; |
| uint8_t *buf = g_try_malloc(S390_SKEYS_BUFFER_SIZE); |
| |
| if (!buf) { |
| error_report("storage key load could not allocate memory"); |
| ret = -ENOMEM; |
| break; |
| } |
| |
| while (handled_count < total_count) { |
| cur_count = MIN(total_count - handled_count, |
| S390_SKEYS_BUFFER_SIZE); |
| qemu_get_buffer(f, buf, cur_count); |
| |
| ret = skeyclass->set_skeys(ss, cur_gfn, cur_count, buf); |
| if (ret < 0) { |
| error_report("S390_SET_KEYS error %d", ret); |
| break; |
| } |
| handled_count += cur_count; |
| cur_gfn += cur_count; |
| } |
| g_free(buf); |
| break; |
| } |
| case S390_SKEYS_SAVE_FLAG_ERROR: { |
| error_report("Storage key data is incomplete"); |
| ret = -EINVAL; |
| break; |
| } |
| case S390_SKEYS_SAVE_FLAG_EOS: |
| /* normal exit */ |
| return 0; |
| default: |
| error_report("Unexpected storage key flag data: %#x", flags); |
| ret = -EINVAL; |
| } |
| } |
| |
| return ret; |
| } |
| |
| static inline bool s390_skeys_get_migration_enabled(Object *obj, Error **errp) |
| { |
| S390SKeysState *ss = S390_SKEYS(obj); |
| |
| return ss->migration_enabled; |
| } |
| |
| static SaveVMHandlers savevm_s390_storage_keys = { |
| .save_state = s390_storage_keys_save, |
| .load_state = s390_storage_keys_load, |
| }; |
| |
| static inline void s390_skeys_set_migration_enabled(Object *obj, bool value, |
| Error **errp) |
| { |
| S390SKeysState *ss = S390_SKEYS(obj); |
| |
| /* Prevent double registration of savevm handler */ |
| if (ss->migration_enabled == value) { |
| return; |
| } |
| |
| ss->migration_enabled = value; |
| |
| if (ss->migration_enabled) { |
| register_savevm_live(TYPE_S390_SKEYS, 0, 1, |
| &savevm_s390_storage_keys, ss); |
| } else { |
| unregister_savevm(VMSTATE_IF(ss), TYPE_S390_SKEYS, ss); |
| } |
| } |
| |
| static void s390_skeys_instance_init(Object *obj) |
| { |
| object_property_add_bool(obj, "migration-enabled", |
| s390_skeys_get_migration_enabled, |
| s390_skeys_set_migration_enabled); |
| object_property_set_bool(obj, "migration-enabled", true, NULL); |
| } |
| |
| static void s390_skeys_class_init(ObjectClass *oc, void *data) |
| { |
| DeviceClass *dc = DEVICE_CLASS(oc); |
| |
| dc->hotpluggable = false; |
| set_bit(DEVICE_CATEGORY_MISC, dc->categories); |
| } |
| |
| static const TypeInfo s390_skeys_info = { |
| .name = TYPE_S390_SKEYS, |
| .parent = TYPE_DEVICE, |
| .instance_init = s390_skeys_instance_init, |
| .instance_size = sizeof(S390SKeysState), |
| .class_init = s390_skeys_class_init, |
| .class_size = sizeof(S390SKeysClass), |
| .abstract = true, |
| }; |
| |
| static void qemu_s390_skeys_register_types(void) |
| { |
| type_register_static(&s390_skeys_info); |
| type_register_static(&qemu_s390_skeys_info); |
| } |
| |
| type_init(qemu_s390_skeys_register_types) |