| /* SPDX-License-Identifier: GPL-2.0-only */ |
| /* |
| * Ultravisor related functionality |
| * |
| * Copyright 2020 IBM Corp. |
| * |
| * Authors: |
| * Janosch Frank <frankja@linux.ibm.com> |
| */ |
| #include <libcflat.h> |
| #include <bitops.h> |
| #include <alloc.h> |
| #include <alloc_page.h> |
| #include <asm/page.h> |
| #include <asm/arch_def.h> |
| |
| #include <asm/facility.h> |
| #include <asm/uv.h> |
| #include <uv.h> |
| #include <sie.h> |
| #include <snippet.h> |
| |
| static struct uv_cb_qui uvcb_qui = { |
| .header.cmd = UVC_CMD_QUI, |
| .header.len = sizeof(uvcb_qui), |
| }; |
| static uint64_t uv_init_mem; |
| |
| |
| bool uv_os_is_guest(void) |
| { |
| return test_facility(158) && |
| uv_query_test_call(BIT_UVC_CMD_SET_SHARED_ACCESS) && |
| uv_query_test_call(BIT_UVC_CMD_REMOVE_SHARED_ACCESS); |
| } |
| |
| bool uv_os_is_host(void) |
| { |
| return test_facility(158) && uv_query_test_call(BIT_UVC_CMD_INIT_UV); |
| } |
| |
| bool uv_host_requirement_checks(void) |
| { |
| if (!test_facility(158)) { |
| report_skip("UV Call facility unavailable"); |
| return false; |
| } |
| if (!sclp_facilities.has_sief2) { |
| report_skip("SIEF2 facility unavailable"); |
| return false; |
| } |
| if (get_ram_size() < SNIPPET_PV_MIN_MEM_SIZE) { |
| report_skip("Not enough memory. This test needs about %ld MB of memory", |
| SNIPPET_PV_MIN_MEM_SIZE / SZ_1M); |
| return false; |
| } |
| |
| return true; |
| } |
| |
| bool uv_query_test_call(unsigned int nr) |
| { |
| /* Query needs to be called first */ |
| assert(uvcb_qui.header.rc); |
| assert(nr < BITS_PER_LONG * ARRAY_SIZE(uvcb_qui.inst_calls_list)); |
| |
| return test_bit_inv(nr, uvcb_qui.inst_calls_list); |
| } |
| |
| const struct uv_cb_qui *uv_get_query_data(void) |
| { |
| /* Query needs to be called first */ |
| assert(uvcb_qui.header.rc == 1 || uvcb_qui.header.rc == 0x100); |
| |
| return &uvcb_qui; |
| } |
| |
| int uv_setup(void) |
| { |
| if (!test_facility(158)) |
| return 0; |
| |
| uv_call(0, (u64)&uvcb_qui); |
| |
| assert(uvcb_qui.header.rc == 1 || uvcb_qui.header.rc == 0x100); |
| return 1; |
| } |
| |
| void uv_init(void) |
| { |
| struct uv_cb_init uvcb_init = { |
| .header.len = sizeof(uvcb_init), |
| .header.cmd = UVC_CMD_INIT_UV, |
| }; |
| static bool initialized; |
| int cc; |
| |
| /* Let's not do this twice */ |
| if (initialized) |
| return; |
| /* Query is done on initialization but let's check anyway */ |
| assert(uvcb_qui.header.rc == 1 || uvcb_qui.header.rc == 0x100); |
| |
| /* Donated storage needs to be over 2GB aligned to 1MB */ |
| uv_init_mem = (uint64_t)memalign_pages_flags(HPAGE_SIZE, uvcb_qui.uv_base_stor_len, AREA_NORMAL); |
| uvcb_init.stor_origin = uv_init_mem; |
| uvcb_init.stor_len = uvcb_qui.uv_base_stor_len; |
| |
| cc = uv_call(0, (uint64_t)&uvcb_init); |
| assert(cc == 0); |
| initialized = true; |
| } |
| |
| /* |
| * Create a new ASCE for the UV config because they can't be shared |
| * for security reasons. We just simply copy the top most table into a |
| * fresh set of allocated pages and use those pages as the asce. |
| */ |
| static uint64_t create_asce(void) |
| { |
| void *pgd_new, *pgd_old; |
| uint64_t asce = stctg(1); |
| |
| pgd_new = memalign_pages(PAGE_SIZE, PAGE_SIZE * 4); |
| pgd_old = (void *)(asce & PAGE_MASK); |
| |
| memcpy(pgd_new, pgd_old, PAGE_SIZE * 4); |
| |
| asce = __pa(pgd_new) | ASCE_P | (asce & (ASCE_DT | ASCE_TL)); |
| return asce; |
| } |
| |
| void uv_create_guest(struct vm *vm) |
| { |
| struct uv_cb_cgc uvcb_cgc = { |
| .header.cmd = UVC_CMD_CREATE_SEC_CONF, |
| .header.len = sizeof(uvcb_cgc), |
| }; |
| struct uv_cb_csc uvcb_csc = { |
| .header.len = sizeof(uvcb_csc), |
| .header.cmd = UVC_CMD_CREATE_SEC_CPU, |
| .state_origin = (uint64_t)vm->sblk, |
| .num = 0, |
| }; |
| unsigned long vsize; |
| int cc; |
| |
| uvcb_cgc.guest_stor_origin = vm->sblk->mso; |
| uvcb_cgc.guest_stor_len = vm->sblk->msl - vm->sblk->mso + SZ_1M; |
| |
| /* Config allocation */ |
| vsize = uvcb_qui.conf_base_virt_stor_len + |
| ((uvcb_cgc.guest_stor_len / HPAGE_SIZE) * uvcb_qui.conf_virt_var_stor_len); |
| |
| vm->uv.conf_base_stor = memalign_pages_flags(PAGE_SIZE * 4, uvcb_qui.conf_base_phys_stor_len, 0); |
| /* |
| * This allocation needs to be below the max guest storage |
| * address so let's simply put it into the physical memory |
| */ |
| vm->uv.conf_var_stor = memalign_pages_flags(PAGE_SIZE, vsize,0); |
| uvcb_cgc.conf_base_stor_origin = (uint64_t)vm->uv.conf_base_stor; |
| uvcb_cgc.conf_var_stor_origin = (uint64_t)vm->uv.conf_var_stor; |
| |
| /* CPU allocation */ |
| vm->uv.cpu_stor = memalign_pages_flags(PAGE_SIZE, uvcb_qui.cpu_stor_len, 0); |
| uvcb_csc.stor_origin = (uint64_t)vm->uv.cpu_stor; |
| |
| uvcb_cgc.guest_asce = create_asce(); |
| vm->save_area.guest.asce = uvcb_cgc.guest_asce; |
| uvcb_cgc.guest_sca = (uint64_t)vm->sca; |
| |
| cc = uv_call(0, (uint64_t)&uvcb_cgc); |
| assert(!cc); |
| |
| vm->uv.vm_handle = uvcb_cgc.guest_handle; |
| uvcb_csc.guest_handle = uvcb_cgc.guest_handle; |
| cc = uv_call(0, (uint64_t)&uvcb_csc); |
| vm->uv.vcpu_handle = uvcb_csc.cpu_handle; |
| assert(!cc); |
| |
| /* |
| * Convert guest to format 4: |
| * |
| * - Set format 4 |
| * - Write UV handles into sblk |
| * - Allocate and set SIDA |
| */ |
| vm->sblk->sdf = 2; |
| vm->sblk->sidad = (uint64_t)alloc_page(); |
| vm->sblk->pv_handle_cpu = uvcb_csc.cpu_handle; |
| vm->sblk->pv_handle_config = uvcb_cgc.guest_handle; |
| } |
| |
| void uv_destroy_guest(struct vm *vm) |
| { |
| int cc; |
| u16 rc, rrc; |
| |
| cc = uv_cmd_nodata(vm->sblk->pv_handle_cpu, |
| UVC_CMD_DESTROY_SEC_CPU, &rc, &rrc); |
| assert(cc == 0); |
| free_page((void *)vm->sblk->sidad); |
| free_pages(vm->uv.cpu_stor); |
| |
| cc = uv_cmd_nodata(vm->sblk->pv_handle_config, |
| UVC_CMD_DESTROY_SEC_CONF, &rc, &rrc); |
| assert(cc == 0); |
| free_pages(vm->uv.conf_base_stor); |
| free_pages(vm->uv.conf_var_stor); |
| |
| free_pages((void *)(vm->uv.asce & PAGE_MASK)); |
| memset(&vm->uv, 0, sizeof(vm->uv)); |
| |
| /* Convert the sblk back to non-PV */ |
| vm->save_area.guest.asce = stctg(1); |
| vm->sblk->sdf = 0; |
| vm->sblk->sidad = 0; |
| vm->sblk->pv_handle_cpu = 0; |
| vm->sblk->pv_handle_config = 0; |
| } |
| |
| int uv_unpack(struct vm *vm, uint64_t addr, uint64_t len, uint64_t tweak) |
| { |
| int i, cc; |
| |
| for (i = 0; i < len / PAGE_SIZE; i++) { |
| cc = uv_unp_page(vm->uv.vm_handle, addr, tweak, i * PAGE_SIZE); |
| assert(!cc); |
| addr += PAGE_SIZE; |
| } |
| return cc; |
| } |
| |
| void uv_verify_load(struct vm *vm) |
| { |
| uint16_t rc, rrc; |
| int cc; |
| |
| cc = uv_cmd_nodata(vm->uv.vm_handle, UVC_CMD_VERIFY_IMG, &rc, &rrc); |
| assert(!cc); |
| cc = uv_set_cpu_state(vm->uv.vcpu_handle, PV_CPU_STATE_OPR_LOAD); |
| assert(!cc); |
| } |