From 22f74df7ac575c43c4dd1e2d4cbae65ff84a3595 Mon Sep 17 00:00:00 2001 From: Heiko Carstens Date: Tue, 15 Sep 2026 16:43:07 +0200 Subject: [PATCH 01/24] s390/uv: Rework page allocation for guest variable storage area Alexander Gordeev reported that the allocation of pages of the guest variable storage area via the uv_alloc_range_cb() callback of apply_to_page_range() will cause problems as soon as s390 fully supports lazy mmu mode. Problem is that uv_alloc_range_cb() allocates pages with GFP_KERNEL while preemption would be disabled. This would result in reports like this: BUG: sleeping function called from invalid context at ./include/linux/sched/mm.h:322 in_atomic(): 1, irqs_disabled(): 0, non_block: 0, pid: 6092, name: qemu-kvm preempt_count: 1, expected: 0 ... Call Trace: dump_stack_lvl+0xae/0x108 __might_resched+0x1de/0x2f0 prepare_alloc_pages+0x1ba/0x220 __alloc_frozen_pages_noprof+0xc6/0x390 alloc_pages_mpol+0xe6/0x220 alloc_frozen_pages_noprof+0x5c/0x80 alloc_pages_noprof+0x22/0x80 uv_alloc_range_cb+0x30/0x300 apply_to_pte_range+0x11a/0x3b0 apply_to_pmd_range+0x13a/0x250 __apply_to_page_range+0x232/0x4d0 apply_to_page_range+0x28/0x40 uv_alloc_stor_var+0x5e/0x90 kvm_s390_pv_alloc_vm+0x118/0x1e0 [kvm] kvm_s390_pv_init_vm+0x84/0x2e0 [kvm] kvm_s390_handle_pv+0x502/0xf70 [kvm] kvm_arch_vm_ioctl+0x234/0xdd0 [kvm] kvm_vm_ioctl+0x33a/0x890 [kvm] __s390x_sys_ioctl+0xfa/0x130 __do_syscall+0x1fc/0x700 system_call+0x72/0x90 Preallocate the pages and make use of vm_area_map_pages() to address this potential future bug. Reported-by: Alexander Gordeev Closes: https://lore.kernel.org/all/e9449c63-b8d2-4ed9-a184-7dcab3e8dfce-agordeev@linux.ibm.com Signed-off-by: Heiko Carstens Acked-by: Alexander Gordeev Signed-off-by: Janosch Frank Signed-off-by: Christian Borntraeger --- arch/s390/kernel/uv.c | 41 +++++++++++++++++++++++------------------ 1 file changed, 23 insertions(+), 18 deletions(-) diff --git a/arch/s390/kernel/uv.c b/arch/s390/kernel/uv.c index 8ea9dd7704ff..abb94720c79b 100644 --- a/arch/s390/kernel/uv.c +++ b/arch/s390/kernel/uv.c @@ -16,6 +16,7 @@ #include #include #include +#include #include #include #include @@ -242,34 +243,38 @@ void uv_free_stor_var(void *stor_var) } EXPORT_SYMBOL_FOR_MODULES(uv_free_stor_var, "kvm"); -static int uv_alloc_range_cb(pte_t *ptep, unsigned long addr, void *data) -{ - struct page *page; - pte_t pte; - - page = alloc_page(GFP_KERNEL_ACCOUNT | __GFP_ZERO); - if (!page) - return -ENOMEM; - pte = __pte(page_to_phys(page) | pgprot_val(PAGE_KERNEL)); - set_pte(ptep, pte); - return 0; -} - void *uv_alloc_stor_var(unsigned long size) { + unsigned long i, nr_pages, addr; struct vm_struct *area; - unsigned long addr; + struct page **pages; size = PAGE_ALIGN(size); + nr_pages = size >> PAGE_SHIFT; area = get_vm_area(size, VM_SPARSE); if (!area) return NULL; + pages = kvcalloc(nr_pages, sizeof(struct page *), GFP_KERNEL_ACCOUNT); + if (!pages) + goto free_area; + for (i = 0; i < nr_pages; i++) { + pages[i] = alloc_page(GFP_KERNEL_ACCOUNT | __GFP_ZERO); + if (!pages[i]) + goto free_pages; + } addr = (unsigned long)area->addr; - if (apply_to_page_range(&init_mm, addr, size, uv_alloc_range_cb, NULL)) - goto out; + if (vm_area_map_pages(area, addr, addr + size, pages)) + goto free_pages; + kvfree(pages); return area->addr; -out: - uv_free_stor_var(area->addr); +free_pages: + for (i = 0; i < nr_pages; i++) { + if (pages[i]) + __free_page(pages[i]); + } + kvfree(pages); +free_area: + free_vm_area(area); return NULL; } EXPORT_SYMBOL_FOR_MODULES(uv_alloc_stor_var, "kvm"); From b33cbce8a58383e127e44c552b0e7738e2afc091 Mon Sep 17 00:00:00 2001 From: Gabriella Seifert Date: Thu, 20 Aug 2026 12:20:35 +0000 Subject: [PATCH 02/24] KVM: s390: selftests: Add IRQ injection selftests The IRQ injection selftests haven't been added to the selftests yet so let's do that now. We start off with a couple simple error checks for invalid data. Signed-off-by: Gabriella Seifert [frankja@linux.ibm.com: Improved formatting and wording] Signed-off-by: Janosch Frank Signed-off-by: Christian Borntraeger --- tools/testing/selftests/kvm/Makefile.kvm | 1 + .../selftests/kvm/s390/irq_injection.c | 74 +++++++++++++++++++ 2 files changed, 75 insertions(+) create mode 100644 tools/testing/selftests/kvm/s390/irq_injection.c diff --git a/tools/testing/selftests/kvm/Makefile.kvm b/tools/testing/selftests/kvm/Makefile.kvm index fa10eb9e8fa7..fc409ebbde38 100644 --- a/tools/testing/selftests/kvm/Makefile.kvm +++ b/tools/testing/selftests/kvm/Makefile.kvm @@ -225,6 +225,7 @@ TEST_GEN_PROGS_s390 += rseq_test TEST_GEN_PROGS_s390 += s390/irq_routing TEST_GEN_PROGS_s390 += mmu_stress_test TEST_GEN_PROGS_s390 += pre_fault_memory_test +TEST_GEN_PROGS_s390 += s390/irq_injection TEST_GEN_PROGS_riscv = $(TEST_GEN_PROGS_COMMON) TEST_GEN_PROGS_riscv += riscv/sbi_pmu_test diff --git a/tools/testing/selftests/kvm/s390/irq_injection.c b/tools/testing/selftests/kvm/s390/irq_injection.c new file mode 100644 index 000000000000..75e0528f9b01 --- /dev/null +++ b/tools/testing/selftests/kvm/s390/irq_injection.c @@ -0,0 +1,74 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Test for KVM_S390_IRQ - s390x IRQ injection API + * + * Verifies that VM-level interrupt types are correctly rejected when + * injected via the vCPU file descriptor, and that invalid arguments + * to vCPU-level interrupts are also rejected. + * + * Copyright IBM Corp. 2026 + * Author: Gabriella Seifert + */ +#include +#include +#include +#include +#include +#include +#include "kvm_util.h" +#include "test_util.h" +#include + +static void test_vm_irq_on_vcpu_fails(struct kvm_vcpu *vcpu, __u64 type) +{ + struct kvm_s390_irq irq = { .type = type }; + int rc; + + rc = __vcpu_ioctl(vcpu, KVM_S390_IRQ, &irq); + ksft_test_result(rc == -1 && errno == EINVAL, + "VM on VCPU IRQ type 0x%llx: expected %d, got %d\n", + type, EINVAL, errno); +} + +static void test_vcpu_irq_bad_param_fails(struct kvm_vcpu *vcpu, + struct kvm_s390_irq *irq) +{ + int rc; + + rc = __vcpu_ioctl(vcpu, KVM_S390_IRQ, irq); + ksft_test_result(rc == -1 && errno == EINVAL, + "VCPU IRQ bad param type 0x%llx: expected %d got %d\n", + irq->type, EINVAL, errno); +} + +int main(void) +{ + struct kvm_s390_irq irq; + struct kvm_vcpu *vcpu; + struct kvm_vm *vm; + + ksft_print_header(); + ksft_set_plan(6); + vm = vm_create_with_one_vcpu(&vcpu, NULL); + + test_vm_irq_on_vcpu_fails(vcpu, KVM_S390_INT_VIRTIO); + test_vm_irq_on_vcpu_fails(vcpu, KVM_S390_INT_SERVICE); + test_vm_irq_on_vcpu_fails(vcpu, KVM_S390_INT_IO_MIN); + + memset(&irq, 0, sizeof(irq)); + irq.type = KVM_S390_INT_EMERGENCY; + irq.u.emerg.code = 0xffff; + test_vcpu_irq_bad_param_fails(vcpu, &irq); + + memset(&irq, 0, sizeof(irq)); + irq.type = KVM_S390_INT_EXTERNAL_CALL; + irq.u.extcall.code = 0xffff; + test_vcpu_irq_bad_param_fails(vcpu, &irq); + + memset(&irq, 0, sizeof(irq)); + irq.type = 0xdeadbeef; + test_vcpu_irq_bad_param_fails(vcpu, &irq); + + kvm_vm_free(vm); + ksft_finished(); +} From 6079170e9cb52e5cfa3efc589ef065290e3e87ad Mon Sep 17 00:00:00 2001 From: "Mike Rapoport (Microsoft)" Date: Sun, 6 Sep 2026 11:27:12 +0300 Subject: [PATCH 03/24] KVM: s390: Replace get_zeroed_page() with kzalloc() for sie_page2 and CMMA kvm_arch_init_vm() allocates struct sie_page2 that holds the facility list and the crypto control block of a VM and kvm_s390_vcpu_setup_cmma() allocates the CMMA collaborative memory management block referenced by the SIE control block. Both are passed to the hardware as physical addresses and must be page aligned. kmalloc() guarantees that a power of two sized allocation is aligned to its size, so a PAGE_SIZE allocation is page aligned as well. These buffers can be allocated with kmalloc() as there's nothing special about them to go directly to the page allocator. kmalloc() provides a better API that does not require ugly casts and kfree() does not need to know the size of the freed object. Performance difference between kmalloc() and __get_free_pages() is not measurable as both allocators take an object/page from a per-CPU list for fast path allocations. For the slow path the performance is anyway determined by the amount of reclaim involved rather than by what allocator is used. The sie_page allocated in kvm_arch_vcpu_create() is left alone because it is mapped to userspace with virt_to_page() in kvm_arch_vcpu_fault() and therefore really needs a struct page. The SIE hardware stores the offset of the next CBR entry in the low bits of cbrlo, so mask them out before converting cbrlo back to a virtual address. free_page() did not care, kfree() needs the address kzalloc() returned. Replace use of get_zeroed_page() with kzalloc() and free_page() with kfree(). Link: https://lore.kernel.org/all/635405e4-9423-4a25-a6e7-e03c8ea0bcbe@redhat.com Assisted-by: copilot:claude-opus Signed-off-by: Mike Rapoport (Microsoft) Reviewed-by: Claudio Imbrenda Reviewed-by: Janosch Frank Signed-off-by: Janosch Frank Signed-off-by: Christian Borntraeger --- arch/s390/kvm/s390/s390.c | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/arch/s390/kvm/s390/s390.c b/arch/s390/kvm/s390/s390.c index b14706c70946..305a3ccaa25a 100644 --- a/arch/s390/kvm/s390/s390.c +++ b/arch/s390/kvm/s390/s390.c @@ -3273,8 +3273,8 @@ int kvm_arch_init_vm(struct kvm *kvm, unsigned long type) goto out_err; BUILD_BUG_ON(sizeof(struct sie_page2) != 4096); - kvm->arch.sie_page2 = - (struct sie_page2 *) get_zeroed_page(GFP_KERNEL_ACCOUNT | GFP_DMA); + kvm->arch.sie_page2 = kzalloc_obj(*kvm->arch.sie_page2, + GFP_KERNEL_ACCOUNT | GFP_DMA); if (!kvm->arch.sie_page2) goto out_err; @@ -3369,7 +3369,7 @@ int kvm_arch_init_vm(struct kvm *kvm, unsigned long type) return 0; out_err: - free_page((unsigned long)kvm->arch.sie_page2); + kfree(kvm->arch.sie_page2); debug_unregister(kvm->arch.dbf); sca_dispose(kvm); KVM_EVENT(3, "creation of vm failed: %d", rc); @@ -3427,7 +3427,7 @@ void kvm_arch_destroy_vm(struct kvm *kvm) mmu_notifier_unregister(&kvm->arch.pv.mmu_notifier, kvm->mm); debug_unregister(kvm->arch.dbf); - free_page((unsigned long)kvm->arch.sie_page2); + kfree(kvm->arch.sie_page2); kvm_s390_destroy_adapters(kvm); kvm_s390_clear_float_irqs(kvm); kvm_s390_vsie_destroy(kvm); @@ -3657,13 +3657,13 @@ static void kvm_s390_vcpu_crypto_setup(struct kvm_vcpu *vcpu) void kvm_s390_vcpu_unsetup_cmma(struct kvm_vcpu *vcpu) { if (vcpu->arch.sie_block->cbrlo) - free_page((unsigned long)phys_to_virt(vcpu->arch.sie_block->cbrlo)); + kfree(phys_to_virt(vcpu->arch.sie_block->cbrlo & PAGE_MASK)); vcpu->arch.sie_block->cbrlo = 0; } int kvm_s390_vcpu_setup_cmma(struct kvm_vcpu *vcpu) { - void *cbrlo_page = (void *)get_zeroed_page(GFP_KERNEL_ACCOUNT); + void *cbrlo_page = kzalloc(PAGE_SIZE, GFP_KERNEL_ACCOUNT); if (!cbrlo_page) return -ENOMEM; From 603695afbeee031711cfa944519a5b5c00607e53 Mon Sep 17 00:00:00 2001 From: "Mike Rapoport (Microsoft)" Date: Sun, 6 Sep 2026 11:27:11 +0300 Subject: [PATCH 04/24] KVM: s390: Replace get_zeroed_page() with kzalloc() for the GIB kvm_s390_gib_init() allocates the guest information block (GIB). The GIB is passed to the hardware as a physical address and must be page aligned. kmalloc() guarantees that a power of two sized allocation is aligned to its size, so a PAGE_SIZE allocation is page aligned as well. This buffer can be allocated with kmalloc() as there's nothing special about it to go directly to the page allocator. kmalloc() provides a better API that does not require ugly casts and kfree() does not need to know the size of the freed object. Performance difference between kmalloc() and __get_free_pages() is not measurable as both allocators take an object/page from a per-CPU list for fast path allocations. For the slow path the performance is anyway determined by the amount of reclaim involved rather than by what allocator is used. Replace use of get_zeroed_page() with kzalloc() and free_page() with kfree(). Link: https://lore.kernel.org/all/635405e4-9423-4a25-a6e7-e03c8ea0bcbe@redhat.com Assisted-by: copilot:claude-opus Signed-off-by: Mike Rapoport (Microsoft) Reviewed-by: Claudio Imbrenda Reviewed-by: Janosch Frank Signed-off-by: Janosch Frank Signed-off-by: Christian Borntraeger --- arch/s390/kvm/s390/interrupt.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/arch/s390/kvm/s390/interrupt.c b/arch/s390/kvm/s390/interrupt.c index f892f4307883..27b5fdf7ee75 100644 --- a/arch/s390/kvm/s390/interrupt.c +++ b/arch/s390/kvm/s390/interrupt.c @@ -3690,7 +3690,7 @@ void kvm_s390_gib_destroy(void) } chsc_sgib(0); unregister_adapter_interrupt(&gib_alert_irq); - free_page((unsigned long)gib); + kfree(gib); gib = NULL; } @@ -3704,7 +3704,7 @@ int __init kvm_s390_gib_init(u8 nisc) goto out; } - gib = (struct kvm_s390_gib *)get_zeroed_page(GFP_KERNEL_ACCOUNT | GFP_DMA); + gib = kzalloc(PAGE_SIZE, GFP_KERNEL_ACCOUNT | GFP_DMA); if (!gib) { rc = -ENOMEM; goto out; @@ -3723,7 +3723,7 @@ int __init kvm_s390_gib_init(u8 nisc) gib_origin = virt_to_phys(gib); if (chsc_sgib(gib_origin)) { pr_err("Associating the GIB with the AIV facility failed\n"); - free_page((unsigned long)gib); + kfree(gib); gib = NULL; rc = -EIO; goto out_unreg_gal; @@ -3743,7 +3743,7 @@ int __init kvm_s390_gib_init(u8 nisc) out_unreg_gal: unregister_adapter_interrupt(&gib_alert_irq); out_free_gib: - free_page((unsigned long)gib); + kfree(gib); gib = NULL; out: return rc; From f10a817b3ab1db7b7f912a6c57e60f93ceddfa12 Mon Sep 17 00:00:00 2001 From: "Mike Rapoport (Microsoft)" Date: Sun, 6 Sep 2026 11:27:10 +0300 Subject: [PATCH 05/24] KVM: s390: Replace get_zeroed_page() with kzalloc() for the STSI buffer handle_stsi() allocates the buffer that receives the STSI response block before it is copied to the guest. This buffer can be allocated with kmalloc() as there's nothing special about it to go directly to the page allocator. kmalloc() provides a better API that does not require ugly casts and kfree() does not need to know the size of the freed object. Performance difference between kmalloc() and __get_free_pages() is not measurable as both allocators take an object/page from a per-CPU list for fast path allocations. For the slow path the performance is anyway determined by the amount of reclaim involved rather than by what allocator is used. While on it, make the buffer a void pointer to get rid of the casts. Replace use of get_zeroed_page() with kzalloc() and free_page() with kfree(). Link: https://lore.kernel.org/all/635405e4-9423-4a25-a6e7-e03c8ea0bcbe@redhat.com Assisted-by: copilot:claude-opus Signed-off-by: Mike Rapoport (Microsoft) Reviewed-by: Janosch Frank Reviewed-by: Claudio Imbrenda Signed-off-by: Janosch Frank Signed-off-by: Christian Borntraeger --- arch/s390/kvm/s390/priv.c | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/arch/s390/kvm/s390/priv.c b/arch/s390/kvm/s390/priv.c index b3cb2c2c3aa7..856939acf012 100644 --- a/arch/s390/kvm/s390/priv.c +++ b/arch/s390/kvm/s390/priv.c @@ -14,6 +14,7 @@ #include #include #include +#include #include #include #include @@ -869,7 +870,7 @@ static int handle_stsi(struct kvm_vcpu *vcpu) int fc = (vcpu->run->s.regs.gprs[0] & 0xf0000000) >> 28; int sel1 = vcpu->run->s.regs.gprs[0] & 0xff; int sel2 = vcpu->run->s.regs.gprs[1] & 0xffff; - unsigned long mem = 0; + void *mem = NULL; u64 operand2; int rc = 0; u8 ar; @@ -911,19 +912,19 @@ static int handle_stsi(struct kvm_vcpu *vcpu) switch (fc) { case 1: /* same handling for 1 and 2 */ case 2: - mem = get_zeroed_page(GFP_KERNEL_ACCOUNT); + mem = kzalloc(PAGE_SIZE, GFP_KERNEL_ACCOUNT); if (!mem) goto out_no_data; - if (stsi((void *) mem, fc, sel1, sel2)) + if (stsi(mem, fc, sel1, sel2)) goto out_no_data; break; case 3: if (sel1 != 2 || sel2 != 2) goto out_no_data; - mem = get_zeroed_page(GFP_KERNEL_ACCOUNT); + mem = kzalloc(PAGE_SIZE, GFP_KERNEL_ACCOUNT); if (!mem) goto out_no_data; - handle_stsi_3_2_2(vcpu, (void *) mem); + handle_stsi_3_2_2(vcpu, mem); break; case 15: /* fc 15 is fully handled in userspace */ insert_stsi_usr_data(vcpu, operand2, ar, fc, sel1, sel2); @@ -931,10 +932,10 @@ static int handle_stsi(struct kvm_vcpu *vcpu) return -EREMOTE; } if (kvm_s390_pv_cpu_is_protected(vcpu)) { - memcpy(sida_addr(vcpu->arch.sie_block), (void *)mem, PAGE_SIZE); + memcpy(sida_addr(vcpu->arch.sie_block), mem, PAGE_SIZE); rc = 0; } else { - rc = write_guest(vcpu, operand2, ar, (void *)mem, PAGE_SIZE); + rc = write_guest(vcpu, operand2, ar, mem, PAGE_SIZE); } if (rc) { rc = kvm_s390_inject_prog_cond(vcpu, rc); @@ -945,14 +946,14 @@ static int handle_stsi(struct kvm_vcpu *vcpu) rc = -EREMOTE; } trace_kvm_s390_handle_stsi(vcpu, fc, sel1, sel2, operand2); - free_page(mem); + kfree(mem); kvm_s390_set_psw_cc(vcpu, 0); vcpu->run->s.regs.gprs[0] = 0; return rc; out_no_data: kvm_s390_set_psw_cc(vcpu, 3); out: - free_page(mem); + kfree(mem); return rc; } From 60fbfe3af7a66e38b1ceeaa7722ba06be15e4fcc Mon Sep 17 00:00:00 2001 From: "Mike Rapoport (Microsoft)" Date: Sun, 6 Sep 2026 11:27:09 +0300 Subject: [PATCH 06/24] KVM: s390: Replace get_zeroed_page() with kzalloc() for the STHYI buffer handle_sthyi() allocates the buffer that receives the STHYI response block before it is copied to the guest. This buffer can be allocated with kmalloc() as there's nothing special about it to go directly to the page allocator. kmalloc() provides a better API that does not require ugly casts and kfree() does not need to know the size of the freed object. Performance difference between kmalloc() and __get_free_pages() is not measurable as both allocators take an object/page from a per-CPU list for fast path allocations. For the slow path the performance is anyway determined by the amount of reclaim involved rather than by what allocator is used. The only goto in that function does not unwind anything, it merely skips to the exit when the function code is invalid. Turn it into an early return so that the buffer can be freed with __free(kfree). Replace use of get_zeroed_page() with kzalloc() and free_page() with kfree(). Assisted-by: copilot:claude-opus Signed-off-by: Mike Rapoport (Microsoft) Reviewed-by: Janosch Frank Reviewed-by: Claudio Imbrenda Signed-off-by: Janosch Frank Signed-off-by: Christian Borntraeger --- arch/s390/kvm/s390/intercept.c | 23 ++++++++++------------- 1 file changed, 10 insertions(+), 13 deletions(-) diff --git a/arch/s390/kvm/s390/intercept.c b/arch/s390/kvm/s390/intercept.c index ca1205dfac8b..f7ad111f259c 100644 --- a/arch/s390/kvm/s390/intercept.c +++ b/arch/s390/kvm/s390/intercept.c @@ -11,6 +11,8 @@ #include #include #include +#include +#include #include #include @@ -411,9 +413,9 @@ static int handle_partial_execution(struct kvm_vcpu *vcpu) */ int handle_sthyi(struct kvm_vcpu *vcpu) { + struct sthyi_sctns *sctns __free(kfree) = NULL; int reg1, reg2, cc = 0, r = 0; u64 code, addr, rc = 0; - struct sthyi_sctns *sctns = NULL; if (!test_kvm_facility(vcpu->kvm, 74)) return kvm_s390_inject_program_int(vcpu, PGM_OPERATION); @@ -430,37 +432,32 @@ int handle_sthyi(struct kvm_vcpu *vcpu) return kvm_s390_inject_program_int(vcpu, PGM_SPECIFICATION); if (code & 0xffff) { - cc = 3; - rc = 4; - goto out; + vcpu->run->s.regs.gprs[reg2 + 1] = 4; + kvm_s390_set_psw_cc(vcpu, 3); + return 0; } if (!kvm_s390_pv_cpu_is_protected(vcpu) && (addr & ~PAGE_MASK)) return kvm_s390_inject_program_int(vcpu, PGM_SPECIFICATION); - sctns = (void *)get_zeroed_page(GFP_KERNEL_ACCOUNT); + sctns = kzalloc(PAGE_SIZE, GFP_KERNEL_ACCOUNT); if (!sctns) return -ENOMEM; cc = sthyi_fill(sctns, &rc); - if (cc < 0) { - free_page((unsigned long)sctns); + if (cc < 0) return cc; - } -out: + if (!cc) { if (kvm_s390_pv_cpu_is_protected(vcpu)) { memcpy(sida_addr(vcpu->arch.sie_block), sctns, PAGE_SIZE); } else { r = write_guest(vcpu, addr, reg2, sctns, PAGE_SIZE); - if (r) { - free_page((unsigned long)sctns); + if (r) return kvm_s390_inject_prog_cond(vcpu, r); - } } } - free_page((unsigned long)sctns); vcpu->run->s.regs.gprs[reg2 + 1] = rc; kvm_s390_set_psw_cc(vcpu, cc); return r; From bbfbe09b281e2bf80914ea2850dbfb4f953796b0 Mon Sep 17 00:00:00 2001 From: Eric Farman Date: Fri, 18 Sep 2026 16:51:51 +0200 Subject: [PATCH 07/24] Documentation: Add missing KVM s390 crypto attributes Device attributes to enable/disable the AP interpretive execution support for crypto were added to KVM several years ago, but they weren't added to the documentation itself. Fixes: 37940fb0b6a2 ("KVM: s390: device attrs to enable/disable AP interpretation") Signed-off-by: Eric Farman Reviewed-by: Anthony Krowiak Signed-off-by: Janosch Frank Signed-off-by: Christian Borntraeger --- Documentation/virt/kvm/devices/vm.rst | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/Documentation/virt/kvm/devices/vm.rst b/Documentation/virt/kvm/devices/vm.rst index a4d39fa1b083..78e63eb9994e 100644 --- a/Documentation/virt/kvm/devices/vm.rst +++ b/Documentation/virt/kvm/devices/vm.rst @@ -280,6 +280,24 @@ Allows user space to disable dea key wrapping, clearing the wrapping key. :Parameters: none :Returns: 0 +4.5. ATTRIBUTE: KVM_S390_VM_CRYPTO_ENABLE_APIE (w/o) +------------------------------------------------------- + +Allows user space to enable AP interpretive execution. + +:Parameters: none +:Returns: 0 for success + -EOPNOTSUPP if AP instructions not available for guest + +4.6. ATTRIBUTE: KVM_S390_VM_CRYPTO_DISABLE_APIE (w/o) +------------------------------------------------------- + +Allows user space to disable AP interpretive execution. + +:Parameters: none +:Returns: 0 for success + -EOPNOTSUPP if AP instructions not available for guest + 5. GROUP: KVM_S390_VM_MIGRATION =============================== From 370b3a89cbd95173c57e2524477a471e69c0e682 Mon Sep 17 00:00:00 2001 From: Janosch Frank Date: Tue, 18 Aug 2026 08:36:06 +0000 Subject: [PATCH 08/24] KVM: s390: Improve floating IRQ injection behavior Floating IRQs can be handled by any VCPU that opened its masks. The current design does not check if the mask is open when a floating IRQ is injected via the FLIC. It will wakeup the last VCPU that went sleeping hoping it's the correct one. Improve this by checking if the VCPU can deliver that partiqular IRQ and if not try to find another VCPU which can take the IRQ. This is not the final fix around this topic but we'll eventually inject a pending IRQ. The current code can easily deadlock a VM and with this fix we work around that. Signed-off-by: Janosch Frank Acked-by: Christian Borntraeger Signed-off-by: Christian Borntraeger --- arch/s390/kvm/s390/interrupt.c | 86 ++++++++++++++++++++++++++-------- 1 file changed, 66 insertions(+), 20 deletions(-) diff --git a/arch/s390/kvm/s390/interrupt.c b/arch/s390/kvm/s390/interrupt.c index 27b5fdf7ee75..0776e98a85ec 100644 --- a/arch/s390/kvm/s390/interrupt.c +++ b/arch/s390/kvm/s390/interrupt.c @@ -1918,49 +1918,93 @@ static int __inject_io(struct kvm *kvm, struct kvm_s390_interrupt_info *inti) return 0; } +static u64 inti_to_irq_pend_mask(u64 type, int isc) +{ + switch (type) { + case KVM_S390_MCHK: + /* Only repressible machine checks are floating */ + return BIT(IRQ_PEND_MCHK_REP); + case KVM_S390_INT_VIRTIO: + return BIT(IRQ_PEND_VIRTIO); + case KVM_S390_INT_SERVICE: + return BIT(IRQ_PEND_EXT_SERVICE) | + BIT(IRQ_PEND_EXT_SERVICE_EV); + case KVM_S390_INT_PFAULT_DONE: + return BIT(IRQ_PEND_PFAULT_DONE); + case KVM_S390_INT_IO_MIN...KVM_S390_INT_IO_MAX: + return BIT(isc_to_irq_type(isc)); + default: + return 0; + } +} + +/* + * Setup intervention masks to catch running vcpus that hopefully open + * their masks soonish and kick sleeping vcpus to motivate them to + * take IRQs. + */ +static void vcpu_intervention_kick(struct kvm_vcpu *vcpu, u64 type) +{ + /* make the VCPU drop out of the SIE, or wake it up if sleeping */ + switch (type) { + case KVM_S390_MCHK: + kvm_s390_set_cpuflags(vcpu, CPUSTAT_STOP_INT); + break; + case KVM_S390_INT_IO_MIN...KVM_S390_INT_IO_MAX: + if (!(type & KVM_S390_INT_IO_AI_MASK && + vcpu->kvm->arch.gisa_int.origin) || + kvm_s390_pv_cpu_get_handle(vcpu)) + kvm_s390_set_cpuflags(vcpu, CPUSTAT_IO_INT); + break; + default: + kvm_s390_set_cpuflags(vcpu, CPUSTAT_EXT_INT); + break; + } + kvm_s390_vcpu_wakeup(vcpu); +} + /* * Find a destination VCPU for a floating irq and kick it. */ -static void __floating_irq_kick(struct kvm *kvm, u64 type) +static void __floating_irq_kick(struct kvm *kvm, u64 type, int isc) { struct kvm_vcpu *dst_vcpu; int sigcpu, online_vcpus, nr_tries = 0; + u64 irq_pend_mask; + unsigned long i; online_vcpus = atomic_read(&kvm->online_vcpus); if (!online_vcpus) return; + irq_pend_mask = inti_to_irq_pend_mask(type, isc); for (sigcpu = kvm->arch.float_int.last_sleep_cpu; ; sigcpu++) { sigcpu %= online_vcpus; dst_vcpu = kvm_get_vcpu(kvm, sigcpu); - if (!is_vcpu_stopped(dst_vcpu)) + if (!is_vcpu_stopped(dst_vcpu) && + deliverable_irqs(dst_vcpu) & irq_pend_mask) break; /* avoid endless loops if all vcpus are stopped */ - if (nr_tries++ >= online_vcpus) - return; + if (nr_tries++ >= online_vcpus) { + dst_vcpu = NULL; + break; + } } - /* make the VCPU drop out of the SIE, or wake it up if sleeping */ - switch (type) { - case KVM_S390_MCHK: - kvm_s390_set_cpuflags(dst_vcpu, CPUSTAT_STOP_INT); - break; - case KVM_S390_INT_IO_MIN...KVM_S390_INT_IO_MAX: - if (!(type & KVM_S390_INT_IO_AI_MASK && - kvm->arch.gisa_int.origin) || - kvm_s390_pv_cpu_get_handle(dst_vcpu)) - kvm_s390_set_cpuflags(dst_vcpu, CPUSTAT_IO_INT); - break; - default: - kvm_s390_set_cpuflags(dst_vcpu, CPUSTAT_EXT_INT); - break; + /* Nobody was enabled, time to wake all of them */ + if (!dst_vcpu) { + kvm_for_each_vcpu(i, dst_vcpu, kvm) + vcpu_intervention_kick(dst_vcpu, type); + return; } - kvm_s390_vcpu_wakeup(dst_vcpu); + + vcpu_intervention_kick(dst_vcpu, type); } static int __inject_vm(struct kvm *kvm, struct kvm_s390_interrupt_info *inti) { u64 type = READ_ONCE(inti->type); + int isc = -1; int rc; switch (type) { @@ -1977,6 +2021,8 @@ static int __inject_vm(struct kvm *kvm, struct kvm_s390_interrupt_info *inti) rc = __inject_pfault_done(kvm, inti); break; case KVM_S390_INT_IO_MIN...KVM_S390_INT_IO_MAX: + /* Grab isc here since __inject_io() might free inti */ + isc = int_word_to_isc(inti->io.io_int_word); rc = __inject_io(kvm, inti); break; default: @@ -1985,7 +2031,7 @@ static int __inject_vm(struct kvm *kvm, struct kvm_s390_interrupt_info *inti) if (rc) return rc; - __floating_irq_kick(kvm, type); + __floating_irq_kick(kvm, type, isc); return 0; } From c03717417232c690534315a428e019a006502031 Mon Sep 17 00:00:00 2001 From: Janosch Frank Date: Wed, 19 Aug 2026 08:19:26 +0000 Subject: [PATCH 09/24] KVM: s390: Kick PV cpus at the right time for service irqs Service call handling is a two stage process for PV vms. First we receive the secure instruction intercept and then the secure instruction notification intercept. The secure instruction intercept (104) is analogous to the non-pv instruction intercept (4) with the difference that we're not allowed to inject an IRQ when re-entering SIE. We have to wait for the notification intercept (108) which tells us that we're allowed to inject. Unfortunately we never considered this difference and hence the IRQ injection code will try to kick on the secure instruction intercept where service IRQs are masked. It's time to move the kick to the instruction notification and skip kicking on the instruction interception path. Signed-off-by: Janosch Frank Acked-by: Christian Borntraeger Signed-off-by: Christian Borntraeger --- arch/s390/kvm/s390/intercept.c | 9 ++++++ arch/s390/kvm/s390/interrupt.c | 50 ++++++++++++++++++++++++++++------ arch/s390/kvm/s390/s390.h | 1 + 3 files changed, 52 insertions(+), 8 deletions(-) diff --git a/arch/s390/kvm/s390/intercept.c b/arch/s390/kvm/s390/intercept.c index f7ad111f259c..adfde2a656e7 100644 --- a/arch/s390/kvm/s390/intercept.c +++ b/arch/s390/kvm/s390/intercept.c @@ -533,6 +533,15 @@ static int handle_pv_sclp(struct kvm_vcpu *vcpu) set_bit(IRQ_PEND_EXT_SERVICE, &fi->pending_irqs); clear_bit(IRQ_PEND_EXT_SERVICE, &fi->masked_irqs); spin_unlock_irqrestore(&fi->lock, flags); + + /* + * We missed the floating IRQ kick since we can only inject + * when we end up here and not when the irq was injected via + * the FLIC. + * + * Now that we have cleared the masking we can kick cpus. + */ + kvm_s390_pv_sclp_kick(vcpu); return 0; } diff --git a/arch/s390/kvm/s390/interrupt.c b/arch/s390/kvm/s390/interrupt.c index 0776e98a85ec..31a6074feffc 100644 --- a/arch/s390/kvm/s390/interrupt.c +++ b/arch/s390/kvm/s390/interrupt.c @@ -1963,10 +1963,7 @@ static void vcpu_intervention_kick(struct kvm_vcpu *vcpu, u64 type) kvm_s390_vcpu_wakeup(vcpu); } -/* - * Find a destination VCPU for a floating irq and kick it. - */ -static void __floating_irq_kick(struct kvm *kvm, u64 type, int isc) +static void kick_cpu_irq(struct kvm *kvm, u64 type, u64 parm) { struct kvm_vcpu *dst_vcpu; int sigcpu, online_vcpus, nr_tries = 0; @@ -1977,7 +1974,7 @@ static void __floating_irq_kick(struct kvm *kvm, u64 type, int isc) if (!online_vcpus) return; - irq_pend_mask = inti_to_irq_pend_mask(type, isc); + irq_pend_mask = inti_to_irq_pend_mask(type, parm); for (sigcpu = kvm->arch.float_int.last_sleep_cpu; ; sigcpu++) { sigcpu %= online_vcpus; dst_vcpu = kvm_get_vcpu(kvm, sigcpu); @@ -2001,10 +1998,46 @@ static void __floating_irq_kick(struct kvm *kvm, u64 type, int isc) vcpu_intervention_kick(dst_vcpu, type); } +void kvm_s390_pv_sclp_kick(struct kvm_vcpu *vcpu) +{ + /* + * The cpu that called sclp likely will also take the IRQ, no + * need to kick anyone. + */ + if (deliverable_irqs(vcpu) & BIT(IRQ_PEND_EXT_SERVICE)) + return; + + /* + * For the other cases we might have sleeping cpus with open + * masks. Time to find and kick them. + */ + kick_cpu_irq(vcpu->kvm, KVM_S390_INT_SERVICE, -1); +} + +/* + * Find a destination VCPU for a floating irq and kick it. + */ +static void __floating_irq_kick(struct kvm *kvm, u64 type, u64 parm) +{ + struct kvm_s390_float_interrupt *fi = &kvm->arch.float_int; + + /* + * No need to kick on non-ev service IRQs for PV VMs, we're + * not allowed to inject anyway. We need to wait for the sclp + * instruction notification AFTER re-entry of the vcpu that + * handled the instruction intercept. + */ + if (type == KVM_S390_INT_SERVICE && !(parm & SCCB_EVENT_PENDING) && + test_bit(IRQ_PEND_EXT_SERVICE, &fi->masked_irqs)) + return; + + kick_cpu_irq(kvm, type, parm); +} + static int __inject_vm(struct kvm *kvm, struct kvm_s390_interrupt_info *inti) { u64 type = READ_ONCE(inti->type); - int isc = -1; + u64 parm = -1; int rc; switch (type) { @@ -2015,6 +2048,7 @@ static int __inject_vm(struct kvm *kvm, struct kvm_s390_interrupt_info *inti) rc = __inject_virtio(kvm, inti); break; case KVM_S390_INT_SERVICE: + parm = inti->ext.ext_params & SCCB_EVENT_PENDING; rc = __inject_service(kvm, inti); break; case KVM_S390_INT_PFAULT_DONE: @@ -2022,7 +2056,7 @@ static int __inject_vm(struct kvm *kvm, struct kvm_s390_interrupt_info *inti) break; case KVM_S390_INT_IO_MIN...KVM_S390_INT_IO_MAX: /* Grab isc here since __inject_io() might free inti */ - isc = int_word_to_isc(inti->io.io_int_word); + parm = int_word_to_isc(inti->io.io_int_word); rc = __inject_io(kvm, inti); break; default: @@ -2031,7 +2065,7 @@ static int __inject_vm(struct kvm *kvm, struct kvm_s390_interrupt_info *inti) if (rc) return rc; - __floating_irq_kick(kvm, type, isc); + __floating_irq_kick(kvm, type, parm); return 0; } diff --git a/arch/s390/kvm/s390/s390.h b/arch/s390/kvm/s390/s390.h index aa0d1d062f8d..9ed1af5583e3 100644 --- a/arch/s390/kvm/s390/s390.h +++ b/arch/s390/kvm/s390/s390.h @@ -375,6 +375,7 @@ enum hrtimer_restart kvm_s390_idle_wakeup(struct hrtimer *timer); int __must_check kvm_s390_deliver_pending_interrupts(struct kvm_vcpu *vcpu); void kvm_s390_clear_local_irqs(struct kvm_vcpu *vcpu); void kvm_s390_clear_float_irqs(struct kvm *kvm); +void kvm_s390_pv_sclp_kick(struct kvm_vcpu *vcpu); int __must_check kvm_s390_inject_vm(struct kvm *kvm, struct kvm_s390_interrupt *s390int, struct kvm_s390_interrupt_info *inti); From b290b5bff8505b0eafa04684b10c50cccfc64013 Mon Sep 17 00:00:00 2001 From: Steffen Eiden Date: Wed, 30 Sep 2026 19:34:27 +0200 Subject: [PATCH 10/24] s390/Kconfig: remove PCI dependency from HAS_IOMEM HAS_IOMEM was defined as def_bool PCI on s390, making it false on kernels built without PCI support. This made sense until now as the IOMEM code was only required if PCI was enabled. This will change as KVM will require IOMEM in the future. To simplify the config space, turn HAS_IOMEM on unconditionally by default. All other architectures either do not override HAS_IOMEM or set it unconditionally. s390 was the only architecture where HAS_IOMEM could be false on a real system. Since HAS_IOMEM=y now causes kernel/iomem.o and lib/devres.o to be built unconditionally, ioremap()/iounmap() must always be available. Select GENERIC_IOREMAP unconditionally to provide the generic implementations for the PCI=n case. Previously GENERIC_IOREMAP was conditioned on CONFIG_PCI in lockstep with HAS_IOMEM, so mm/ioremap.c was never built without PCI. Therefore, guard the PCI override macros ioremap_prot, iounmap, and ioremap_wc with CONFIG_PCI. Reviewed-by: Ilya Leoshkevich Signed-off-by: Steffen Eiden Signed-off-by: Christian Borntraeger --- arch/s390/Kconfig | 5 +---- arch/s390/include/asm/io.h | 12 ++++++------ 2 files changed, 7 insertions(+), 10 deletions(-) diff --git a/arch/s390/Kconfig b/arch/s390/Kconfig index 4b51bc6e8948..5a01fbd8e32b 100644 --- a/arch/s390/Kconfig +++ b/arch/s390/Kconfig @@ -181,7 +181,7 @@ config S390 select GENERIC_ENTRY select GENERIC_GETTIMEOFDAY select GENERIC_SMP_IDLE_THREAD - select GENERIC_IOREMAP if PCI + select GENERIC_IOREMAP select GLOB select HAVE_ALIGNED_STRUCT_PAGE select HAVE_ARCH_AUDITSYSCALL @@ -768,9 +768,6 @@ config PCI_NR_FUNCTIONS endif # PCI -config HAS_IOMEM - def_bool PCI - config CHSC_SCH def_tristate m prompt "Support for CHSC subchannels" diff --git a/arch/s390/include/asm/io.h b/arch/s390/include/asm/io.h index faddb9aef3b8..ef0490edeccd 100644 --- a/arch/s390/include/asm/io.h +++ b/arch/s390/include/asm/io.h @@ -27,14 +27,8 @@ void unxlate_dev_mem_ptr(phys_addr_t phys, void *addr); /* * I/O memory mapping functions. */ -#define ioremap_prot ioremap_prot -#define iounmap iounmap - #define _PAGE_IOREMAP pgprot_val(PAGE_KERNEL) -#define ioremap_wc(addr, size) \ - ioremap_prot((addr), (size), pgprot_writecombine(PAGE_KERNEL)) - static inline void __iomem *ioport_map(unsigned long port, unsigned int nr) { return NULL; @@ -46,6 +40,12 @@ static inline void ioport_unmap(void __iomem *p) #ifdef CONFIG_PCI +#define ioremap_prot ioremap_prot +#define iounmap iounmap + +#define ioremap_wc(addr, size) \ + ioremap_prot((addr), (size), pgprot_writecombine(PAGE_KERNEL)) + /* * s390 needs a private implementation of pci_iomap since ioremap with its * offset parameter isn't sufficient. That's because BAR spaces are not From 2687655bef3524bb158f9b592e9cb2397d9556b9 Mon Sep 17 00:00:00 2001 From: Steffen Eiden Date: Wed, 30 Sep 2026 14:27:35 +0200 Subject: [PATCH 11/24] KVM: s390: Use dedicated function for migration mode Use kvm_s390_is_migration_mode() instead of kvm.arch->migration_mode. The function is independent of the actual kvm_arch implementation which is different for the second parallel KVM module.. Signed-off-by: Steffen Eiden Signed-off-by: Christian Borntraeger --- arch/s390/kvm/gmap/kvm_mmu.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/arch/s390/kvm/gmap/kvm_mmu.c b/arch/s390/kvm/gmap/kvm_mmu.c index c2ffb5e59ec6..24e618f77be1 100644 --- a/arch/s390/kvm/gmap/kvm_mmu.c +++ b/arch/s390/kvm/gmap/kvm_mmu.c @@ -76,7 +76,7 @@ int s390_kvm_mmu_prepare_memory_region(struct kvm *kvm, return -EINVAL; } - if (kvm->arch.migration_mode) { + if (kvm_s390_is_migration_mode(kvm)) { /* * Turn off migration mode when: * - userspace creates a new memslot with dirty logging off, From b70113c00604ee52c1c18a0b1b498c9bcf5ddfbb Mon Sep 17 00:00:00 2001 From: Steffen Eiden Date: Mon, 22 Jun 2026 14:47:10 +0200 Subject: [PATCH 12/24] s390/tools: Use arm64 headers Introduce a flexible system for sharing ARM64 headers with s390 KVM. Selected arm64 headers or snippet of headers will be reachable by s390 code through asm/header.h. Add copy-arm64h.awk that detects ARM64_S390_COMMON markers and extracts marked section into a generated header consumable by s390. Falls back to full file copy when no markers are found. Transform copied includes by replacing '#include ' with '#include ' and similar for uapi headers to ensure correct path resolution on s390 and allow for a clear separation of s390 asm headers and arm64 asm headers. Integrate this extraction into the s390/kapi target, conditional on CONFIG_KVM. Reviewed-by: Hendrik Brueckner Signed-off-by: Steffen Eiden Signed-off-by: Christian Borntraeger --- arch/s390/include/uapi/Kbuild | 6 ++ arch/s390/tools/Makefile | 6 +- arch/s390/tools/Makefile.arm64h | 55 ++++++++++++++++++ arch/s390/tools/copy-arm64h-full.awk | 50 +++++++++++++++++ arch/s390/tools/copy-arm64h.awk | 83 ++++++++++++++++++++++++++++ 5 files changed, 199 insertions(+), 1 deletion(-) create mode 100644 arch/s390/include/uapi/Kbuild create mode 100644 arch/s390/tools/Makefile.arm64h create mode 100644 arch/s390/tools/copy-arm64h-full.awk create mode 100644 arch/s390/tools/copy-arm64h.awk diff --git a/arch/s390/include/uapi/Kbuild b/arch/s390/include/uapi/Kbuild new file mode 100644 index 000000000000..81dcaaf42c40 --- /dev/null +++ b/arch/s390/include/uapi/Kbuild @@ -0,0 +1,6 @@ +# SPDX-License-Identifier: GPL-2.0 + +# The ARM64 uapi headers under generated/uapi/arm64/ are private copies used +# internally by the s390 KVM ARM64 implementation. They contain #line +# directives and rewritten include paths and must not be shipped to userspace. +no-export-headers += arm64/% diff --git a/arch/s390/tools/Makefile b/arch/s390/tools/Makefile index f2862364fb42..d0aa7e076bad 100644 --- a/arch/s390/tools/Makefile +++ b/arch/s390/tools/Makefile @@ -3,12 +3,16 @@ # Makefile for s390 specific build tools # +include $(srctree)/arch/s390/tools/Makefile.arm64h + kapi := arch/$(ARCH)/include/generated/asm kapi-hdrs-y := $(kapi)/facility-defs.h $(kapi)/dis-defs.h +kapi-hdrs-$(CONFIG_KVM) += $(ARM64_TARGETS) + PHONY += kapi -kapi: $(kapi-hdrs-y) +kapi: $(kapi-hdrs-y) $(kapi-hdrs-m) hostprogs += gen_facilities hostprogs += gen_opcode_table diff --git a/arch/s390/tools/Makefile.arm64h b/arch/s390/tools/Makefile.arm64h new file mode 100644 index 000000000000..f3f8e9d9bdd8 --- /dev/null +++ b/arch/s390/tools/Makefile.arm64h @@ -0,0 +1,55 @@ +# SPDX-License-Identifier: GPL-2.0 +# +# Share ARM64 headers with s390 KVM +# + +arm64api := $(objtree)/arch/$(ARCH)/include/generated/arm64 +arm64uapi := $(objtree)/arch/$(ARCH)/include/generated/uapi/arm64 + +# List of ARM64 headers to share with s390 KVM +# Format: [uapi:]header.h or header-gen.h +# +# * header.h - from arch/arm64/include/asm/ to arm64/ +# * uapi:header.h - from arch/arm64/include/uapi/asm/ to uapi/arm64/ +# * header-gen.h - generated as header-gen.h from ARM64 header header.h +# If a header contains partial markers (#if ARM64_S390_COMMON == 1) only this part will be copied. +# If the file does not contain any markers the whole file will be copied. +ARM64_SHARED_HEADERS := \ + brk-imm.h \ + esr.h \ + kvm_arm.h \ + kvm_emulate-gen.h \ + kvm_host.h \ + ptrace-gen.h \ + sysreg-gen.h \ + uapi:hwcap.h \ + uapi:kvm.h \ + uapi:ptrace.h \ + uapi:sve_context.h + +filechk_gen_arm_hdr = \ + $(AWK) -f $(src)/copy-arm64h.awk $< || \ + $(AWK) -f $(src)/copy-arm64h-full.awk $< || \ + { echo "Error: Unbalanced ARM64_S390_COMMON markers in $<" >&2; exit 1; } + +arm64-type = $(firstword $(subst :, ,$(1))) +arm64-base = $(or $(word 2,$(subst :, ,$(1))),$(1)) +arm64-src-base = $(patsubst %-gen.h,%.h,$(call arm64-base,$(1))) + +arm64-src = $(srctree)/arch/arm64/include/$(if $(filter uapi,$(call arm64-type,$(1))),uapi/)asm/$(call arm64-src-base,$(1)) +arm64-dst = $(if $(filter uapi,$(call arm64-type,$(1))),$(arm64uapi),$(arm64api))/$(call arm64-base,$(1)) + +$(foreach hdr,$(ARM64_SHARED_HEADERS),\ + $(eval $(call arm64-dst,$(hdr)): $(call arm64-src,$(hdr)) $(src)/copy-arm64h.awk $(src)/copy-arm64h-full.awk FORCE)) + +ARM64_TARGETS := $(sort $(foreach hdr,$(ARM64_SHARED_HEADERS),$(call arm64-dst,$(hdr)))) +$(ARM64_TARGETS): + $(call filechk,gen_arm_hdr) + +filechk_gen_sysreg_arm64 = \ + $(AWK) -f $(srctree)/arch/arm64/tools/gen-sysreg.awk $(srctree)/arch/arm64/tools/sysreg + +$(arm64api)/sysreg-defs.h: $(srctree)/arch/arm64/tools/gen-sysreg.awk $(srctree)/arch/arm64/tools/sysreg FORCE + $(call filechk,gen_sysreg_arm64) + +ARM64_TARGETS += $(arm64api)/sysreg-defs.h diff --git a/arch/s390/tools/copy-arm64h-full.awk b/arch/s390/tools/copy-arm64h-full.awk new file mode 100644 index 000000000000..ad7bb98dc4a6 --- /dev/null +++ b/arch/s390/tools/copy-arm64h-full.awk @@ -0,0 +1,50 @@ +#!/usr/bin/awk -f +# SPDX-License-Identifier: GPL-2.0 +# +# Process entire ARM64 headers for sharing with s390 KVM +# +# Usage: copy-arm64h-full.awk +# +# This processes the entire file (unlike copy-arm64h.awk which only extracts marked sections) + +BEGIN { + max_guard_line = 25 + guard_found = 0 + header_added = 0 +} + +# Find and process the include guard in the first few lines +NR <= max_guard_line && !guard_found && /^#ifndef [A-Z_0-9]+$/ { + guard_name = $2 + guard_found = 1 + print "/* This header was copied from " FILENAME " */" + print "" + # Map subsequent lines back to the original ARM64 source file so that + # compiler diagnostics point there rather than at the generated copy. + print "#line " NR " \"" FILENAME "\"" + print + header_added = 1 + next +} + +# Transform include directives +/^#include +# +# Extracts all sections between start/end markers. If no markers found, signals for fallback. +BEGIN { + # Constants + start_pattern = "^#if ARM64_S390_COMMON == 1$" + end_pattern = "^#endif \\/\\* ARM64_S390_COMMON == 1 \\*\\/$" + max_guard_line = 25 + + # State variables + copying = found_marker = error = 0 + guard_name = "" + file_header_done = 0 + depth = 0 +} + +NR <= max_guard_line && !guard_name && /^#ifndef [A-Za-z0-9_]+$/ { + guard_name = $2 + next +} + +NR > max_guard_line && !guard_name && !file_header_done { + print "error: no include guard found in first " max_guard_line " lines" > "/dev/stderr" + file_header_done = 1 + error = 1; exit +} + +$0 ~ start_pattern { + if (copying) { error = 1; exit } + if (!found_marker) { + print "/*" + print " * This header was automatically generated from " FILENAME + print " * Do not modify this file directly." + print " */" + print "#ifndef " guard_name + print "#define " guard_name + print "" + } + depth = copying= found_marker = 1 + # Map subsequent lines back to the original ARM64 source file so that + # compiler diagnostics point there rather than at the generated copy. + print "#line " (NR + 1) " \"" FILENAME "\"" + next +} + +guard_name && !found_marker { + next +} + +$0 ~ /^[ \t]*#[ \t]*endif/ { + depth-- + if (depth == 0) { + if ($0 ~ end_pattern ) { + copying = 0 + next + } + error = 1; exit + } +} + +/ARM64_S390_COMMON/ { error = 1; exit } + +copying { + if ($0 ~ /^[ \t]*#[ \t]*if(def|ndef)?([ \t]|$)/) depth++ + gsub(/#include Date: Thu, 25 Jun 2026 13:56:16 +0200 Subject: [PATCH 13/24] KVM: s390: Use arm64 code Add the infrastructure to extract KVM/arm64 code into s390 at build time similar to the arm64 header sharing. Add copy-arm64c.awk that detects ARM64_S390_COMMON markers and extracts marked section into an .inc file that can be consumed by arm on s390 host code. If no marker is found make will fail. To ensure that no code is consumed twice by accident a guard is added during file generation. A s390 C file wanting to consume such an inc file must first define __INCL_GEN_ARM_FILE otherwise the inc file will emit a compile error. Example: #define __INCL_GEN_ARM_FILE #include #undef __INCL_GEN_ARM_FILE Signed-off-by: Steffen Eiden Signed-off-by: Christian Borntraeger --- arch/s390/kvm/arm64/.gitignore | 2 + arch/s390/kvm/arm64/Makefile.gen | 28 ++++++++++ arch/s390/kvm/arm64/copy-arm64c.awk | 82 +++++++++++++++++++++++++++++ 3 files changed, 112 insertions(+) create mode 100644 arch/s390/kvm/arm64/.gitignore create mode 100644 arch/s390/kvm/arm64/Makefile.gen create mode 100644 arch/s390/kvm/arm64/copy-arm64c.awk diff --git a/arch/s390/kvm/arm64/.gitignore b/arch/s390/kvm/arm64/.gitignore new file mode 100644 index 000000000000..bd9d90694676 --- /dev/null +++ b/arch/s390/kvm/arm64/.gitignore @@ -0,0 +1,2 @@ +# SPDX-License-Identifier: GPL-2.0-only +generated/* diff --git a/arch/s390/kvm/arm64/Makefile.gen b/arch/s390/kvm/arm64/Makefile.gen new file mode 100644 index 000000000000..9c33b068fab5 --- /dev/null +++ b/arch/s390/kvm/arm64/Makefile.gen @@ -0,0 +1,28 @@ +# SPDX-License-Identifier: GPL-2.0 +# +# Extracts ARM64 marked sections to .inc files that are included by main source +# files + +# List of ARM64 C files to extract +ARM64_CFILES := \ + arm.c \ + guest.c \ + mmio.c + +quiet_cmd_extract_inc = GEN $@ + cmd_extract_inc = mkdir -p $(dir $@); \ + $(AWK) -f $(src)/copy-arm64c.awk $< > $@ || \ + { echo "Error: No or unbalanced ARM64_S390_COMMON markers in $<" >&2; rm -f $@; exit 1; } + +ARM64_INC_FILES := $(foreach cfile,$(ARM64_CFILES),generated/$(basename $(cfile)).inc) + +$(addprefix $(obj)/,$(ARM64_INC_FILES)): $(obj)/generated/%.inc: $(srctree)/arch/arm64/kvm/%.c $(src)/copy-arm64c.awk FORCE + $(call if_changed,extract_inc) + +targets += $(ARM64_INC_FILES) +clean-files += generated/*.inc + +$(foreach cfile,$(basename $(ARM64_CFILES)),\ + $(eval $(obj)/$(cfile).o $(obj)/$(cfile).i $(obj)/$(cfile).s \ + $(obj)/$(cfile).ll $(obj)/$(cfile).lst: \ + $(obj)/generated/$(cfile).inc)) diff --git a/arch/s390/kvm/arm64/copy-arm64c.awk b/arch/s390/kvm/arm64/copy-arm64c.awk new file mode 100644 index 000000000000..640a7bcb7e23 --- /dev/null +++ b/arch/s390/kvm/arm64/copy-arm64c.awk @@ -0,0 +1,82 @@ +#!/usr/bin/awk -f +# SPDX-License-Identifier: GPL-2.0 +# +# Extract marked sections from ARM64 C files for sharing with s390 KVM +# +# Usage: copy-arm64c.awk +# +# Extracts all sections between start/end markers. If no markers found, signals failure. + +BEGIN { + # Constants + start_pattern = "^#if ARM64_S390_COMMON == 1$" + end_pattern = "^#endif /\\* ARM64_S390_COMMON == 1 \\*/$" + + # State variables + copying = found_marker = 0 + file_header_done = 0 + depth = 0 +} + +!file_header_done { + if (/^\/\*/ || /^\/\/ SPDX-License-Identifier:/) { + print + next + } + if (/[[:space:]]\*([[:space:]]|$)/) { + print + next + } + if (/\*\//) { + print " *" + } else { + print "/*" + } + + print " * This file was automatically generated from " FILENAME + print " * Do not modify this file directly." + print " */" + print "" + print "#ifndef __INCL_GEN_ARM_FILE" + print "#error do not include this file directly." + print "#endif /* __INCL_GEN_ARM_FILE */" + print "#undef __INCL_GEN_ARM_FILE" + print "" + + file_header_done = 1 +} + +$0 ~ start_pattern { + if (copying) { error = 1; exit } + depth = copying = found_marker = 1 + # Map subsequent lines back to the original ARM64 source file so that + # compiler diagnostics point there rather than at the generated copy. + print "#line " (NR + 1) " \"" FILENAME "\"" + next +} + +copying && /^[ \t]*#[ \t]*if(def|ndef)?([ \t]|$)/ { depth++ } + +$0 ~ /^[ \t]*#[ \t]*endif/ { + depth-- + if (depth == 0) { + if ($0 ~ end_pattern) { + copying = 0 + next + } + error = 1; exit + } +} + +/ARM64_S390_COMMON/ { error = 1; exit } + +copying { + gsub(/#include Date: Thu, 2 Apr 2026 06:00:00 +0200 Subject: [PATCH 14/24] s390: Introduce Start Arm Execution instruction The Start Arm Execution (SAE) instruction is the centerpiece for executing arm64 (KVM) guests on s390. Its purpose is, similar to SIE, to enable accelerated execution of arm64 virtual machines. SAE expects the physical address of a control block as the only argument. The host is responsible to save & restore - GPRs 0-13 - access registers 0-15 - breaking event register (BEAR) - vector/floating point registers between SAE executions to guarantee host consistency. GPRs and BEAR are saved and restored in the asm functions. The other registers are handled within C code. Access registers are handled in a later patch and SVEs will be handled when they are introduced in a future series. Most arm64 registers are handled by a satellite block called save_area. Some registers, frequently used by hypervisors, are placed into the SAE control block itself. Enlighten asm/kvm_host_types.h for the new header variant. The new header is chosen instead of asm/kvm_host_s390_types.h if KVM_S390_ARM64 is defined. Co-developed-by: Andreas Grapentin Signed-off-by: Andreas Grapentin Co-developed-by: Nina Schoetterl-Glausch Signed-off-by: Nina Schoetterl-Glausch Reviewed-by: Ilya Leoshkevich Signed-off-by: Steffen Eiden Signed-off-by: Christian Borntraeger --- arch/s390/include/asm/asm-prototypes.h | 1 + arch/s390/include/asm/kvm_host_arm64_types.h | 124 +++++++++++++++++++ arch/s390/include/asm/kvm_host_types.h | 4 + arch/s390/include/asm/sae.h | 49 ++++++++ arch/s390/include/asm/stacktrace.h | 5 + arch/s390/kernel/asm-offsets.c | 1 + arch/s390/kernel/entry.S | 22 ++++ arch/s390/tools/opcodes.txt | 3 + 8 files changed, 209 insertions(+) create mode 100644 arch/s390/include/asm/kvm_host_arm64_types.h create mode 100644 arch/s390/include/asm/sae.h diff --git a/arch/s390/include/asm/asm-prototypes.h b/arch/s390/include/asm/asm-prototypes.h index d4da4436d02b..faa2afb259a1 100644 --- a/arch/s390/include/asm/asm-prototypes.h +++ b/arch/s390/include/asm/asm-prototypes.h @@ -6,6 +6,7 @@ #include #include #include +#include #include #endif /* _ASM_S390_PROTOTYPES_H */ diff --git a/arch/s390/include/asm/kvm_host_arm64_types.h b/arch/s390/include/asm/kvm_host_arm64_types.h new file mode 100644 index 000000000000..8783a66d3029 --- /dev/null +++ b/arch/s390/include/asm/kvm_host_arm64_types.h @@ -0,0 +1,124 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#ifndef ASM_KVM_HOST_ARM64_TYPES_H +#define ASM_KVM_HOST_ARM64_TYPES_H + +#include +#include +#include +#include +#include + +struct kvm_sae_block { + u64 _0000[16]; /* 0x0000 */ +#define SAE_ICPTR_SPURIOUS 0x00 +#define SAE_ICPTR_VALIDITY 0x01 +#define SAE_ICPTR_HOST_ACCESS_EXCEPTION 0x02 +#define SAE_ICPTR_SYNCHRONOUS_EXCEPTION 0x03 +#define SAE_ICPTR_TIMER 0x04 +#define SAE_ICPTR_PE_INTERCOMM 0x05 +#define SAE_ICPTR_GUEST_ADDRESS_SIZE 0x06 +#define SAE_ICPTR_STOP 0x07 +#define SAE_ICPTR_MIO_ADDRESS 0x08 +#define SAE_ICPTR_PMU 0x09 +#define SAE_ICPTR_MAINTENANCE 0x0a + u8 icptr; /* 0x0080 */ + u8 _0081[7]; /* 0x0081 */ + u64 scad; /* 0x0088 */ + u64 _0090[16]; /* 0x0090 */ + u32 cntp_ctl; /* 0x0110 */ + u32 cntv_ctl; /* 0x0114 */ + u8 irq_ctl; /* 0x0118 */ + u8 _0119[7]; /* 0x0119 */ + struct { + u64 ich_hcr_el2; /* 0x0120 */ + u64 ich_vmcr_el2; /* 0x0128 */ + u64 ich_ap0r0_el2; /* 0x0130 */ + u64 ich_ap1r0_el2; /* 0x0138 */ + u64 _0140[2]; /* 0x0140 */ + u64 ich_lrn_el2[4]; /* 0x0150 */ + u64 _0170[4]; /* 0x0170 */ + } ic_regs; + u64 _0190[12]; /* 0x0190 */ + u64 contextidr_el1; /* 0x01f0 */ + u32 wip; /* 0x01f8 */ + u32 _01fc; /* 0x01fc */ +#define SAE_SD_FORMAT_0 0x00 + u8 sdf; /* 0x0200 */ + u8 _0201[7]; /* 0x0201 */ + u64 mso; /* 0x0208 */ + u64 msl; /* 0x0210 */ + u64 hbasce; /* 0x0218 */ + u64 _0220; /* 0x0220 */ + u64 gpto; /* 0x0228 */ + u64 ic; /* 0x0230 */ + u64 ec; /* 0x0238 */ + u64 save_area; /* 0x0240 */ + u64 _0248[7]; /* 0x0248 */ + u8 _0280[6]; /* 0x0280 */ + u16 lrcpua; /* 0x0286 */ + u64 pstate; /* 0x0288 */ + u64 pc; /* 0x0290 */ + u64 sp_el0; /* 0x0298 */ + u64 sp_el1; /* 0x02a0 */ + u64 _02a8; /* 0x02a8 */ + struct { /* 0x02b0 */ + u32 _fpcr_res0; + u32 fpcr; + }; + struct { /* 0x02b8 */ + u32 _fpsr_res0; + u32 fpsr; + }; + u16 sve_pregs[16]; /* 0x02c0 */ + u16 sve_ffr; /* 0x02e0 */ + u8 _02e2[6]; /* 0x02e2 */ + u64 _02e8[3]; /* 0x02e8 */ + + u64 gpr[31]; /* 0x0300 */ + u64 _03f8; /* 0x03f8 */ + + union { + u64 icptd[8]; /* 0x0400 */ + /* validity-interception reason; icptr 0x01 */ + u16 vir; /* 0x0400 */ + /* host access interception details; icptr 0x02 */ + struct { + u64 esr_el2; /* 0x0400 */ + u8 _0408[6]; /* 0x0408 */ + u16 pic; /* 0x040e */ + union teid teid; /* 0x0410 */ + gva_t far_el2; /* 0x0418 */ + gva_t vaddr; /* 0x0420 */ + u64 suppl; /* 0x0428 */ + u8 gltl; /* 0x0430 */ + u8 _0431[7]; /* 0x0431 */ + u64 _0438; /* 0x0438 */ + } hai; + /* exception-interception details; icptr 0x03 */ + struct { + u64 esr_el2; /* 0x0400 */ + u64 _0408[2]; /* 0x0408 */ + gva_t far_el2; /* 0x0418 */ + } trap; + /* timer-interception reason; icptr 0x04 */ +#define SAE_IR_TIMER_ID_VIRT BIT(6) +#define SAE_IR_TIMER_ID_PHYS BIT(7) + u8 tir; /* 0x0400 */ + }; + u64 _0440[376]; /* 0x0440 */ +} __packed __aligned(PAGE_SIZE); +static_assert(sizeof(struct kvm_sae_block) == PAGE_SIZE); + +struct kvm_sae_save_area { +#define SAE_SAVE_AREA_FORMAT_0 0x00 + u8 saf; /* 0x0000 */ + u8 _0001[5]; /* 0x0001 */ +#define SAE_SAS_VALID BIT(0) + u16 sas; /* 0x0006 */ + u64 sdo; /* 0x0008 */ + u64 _0010[2]; /* 0x0010 */ + u64 regs[508]; /* 0x0020 */ +} __packed __aligned(PAGE_SIZE); +static_assert(sizeof(struct kvm_sae_save_area) == PAGE_SIZE); + +#endif /* ASM_KVM_HOST_ARM64_TYPES_H */ diff --git a/arch/s390/include/asm/kvm_host_types.h b/arch/s390/include/asm/kvm_host_types.h index e5bdba07cab0..d308739ebcf5 100644 --- a/arch/s390/include/asm/kvm_host_types.h +++ b/arch/s390/include/asm/kvm_host_types.h @@ -3,6 +3,10 @@ #ifndef ASM_KVM_HOST_TYPES_H #define ASM_KVM_HOST_TYPES_H +#ifdef KVM_S390_ARM64 +#include +#else #include +#endif /* KVM_S390_ARM64 */ #endif /* ASM_KVM_HOST_TYPES_H */ diff --git a/arch/s390/include/asm/sae.h b/arch/s390/include/asm/sae.h new file mode 100644 index 000000000000..26d89fae0836 --- /dev/null +++ b/arch/s390/include/asm/sae.h @@ -0,0 +1,49 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#ifndef __ASM_S390_SAE_H +#define __ASM_S390_SAE_H + +#include +#include + +#ifndef __ASSEMBLER__ + +/* defined in arch/s390/kernel/entry.S */ +asmlinkage void sae(phys_addr_t sae_block_phys); + +#include +#include + +/** + * stiasrm() - STore and Invalidate Arm System Register Multiple + * @save_area: Pointer to SAE save area + * + * Store the guest system registers to the save area. + * The values in the guest are no longer valid. + */ +static __always_inline void stiasrm(struct kvm_sae_save_area *save_area) +{ + asm_inline volatile( + " .insn rre,0xb9a70000,%[r1],0\n" + : "+m" (*save_area) + : [r1] "a" (save_area) + ); +} + +/** + * lasrm() - Load Arm System Register Multiple + * + * @save_area: Pointer to SAE save area + * + * Load the system registers from save_area into the guest. + */ +static __always_inline void lasrm(struct kvm_sae_save_area *save_area) +{ + asm_inline volatile( + " .insn rre,0xb9a60000,%[r1],0\n" + : + : "m" (*save_area), [r1] "a" (save_area) + ); +} + +#endif /* !__ASSEMBLER__ */ +#endif /* __ASM_S390_SAE_H */ diff --git a/arch/s390/include/asm/stacktrace.h b/arch/s390/include/asm/stacktrace.h index ac3606c3babe..2d332d7c8145 100644 --- a/arch/s390/include/asm/stacktrace.h +++ b/arch/s390/include/asm/stacktrace.h @@ -59,6 +59,7 @@ static inline bool on_stack(struct stack_info *info, struct stack_frame { union { unsigned long empty[9]; + /* SIE stack frame */ struct { unsigned long sie_control_block; unsigned long sie_savearea; @@ -68,6 +69,10 @@ struct stack_frame { unsigned long sie_guest_asce; unsigned long sie_irq; }; + /* SAE stack frame */ + struct { + unsigned long sae_bear; + }; }; unsigned long gprs[10]; unsigned long back_chain; diff --git a/arch/s390/kernel/asm-offsets.c b/arch/s390/kernel/asm-offsets.c index f6dd2b67dcee..f9f9c2246074 100644 --- a/arch/s390/kernel/asm-offsets.c +++ b/arch/s390/kernel/asm-offsets.c @@ -70,6 +70,7 @@ int main(void) OFFSET(__SF_SIE_CONTROL_PHYS, stack_frame, sie_control_block_phys); OFFSET(__SF_SIE_GUEST_ASCE, stack_frame, sie_guest_asce); OFFSET(__SF_SIE_IRQ, stack_frame, sie_irq); + OFFSET(__SF_SAE_BEAR, stack_frame, sae_bear); DEFINE(STACK_FRAME_OVERHEAD, sizeof(struct stack_frame)); BLANK(); OFFSET(__SFUSER_BACKCHAIN, stack_frame_user, back_chain); diff --git a/arch/s390/kernel/entry.S b/arch/s390/kernel/entry.S index 10dd9bbdf985..2004fd7642d7 100644 --- a/arch/s390/kernel/entry.S +++ b/arch/s390/kernel/entry.S @@ -245,6 +245,28 @@ EXPORT_SYMBOL(__sie64a) EXPORT_SYMBOL(sie_exit) #endif +#if IS_ENABLED(CONFIG_KVM) +/* + * SAE calling convention: + * %r2 physical address of the SAE control block + */ +SYM_FUNC_START(sae) + stmg %r6,%r14,__SF_GPRS(%r15) # store kernel registers + STBEAR __SF_SAE_BEAR(%r15) # save breaking event address register + .insn rre,0xb9a50000,%r2,0 # Start Arm Execution + LBEAR __SF_SAE_BEAR(%r15) # restore breaking event address register + lmg %r6,%r14,__SF_GPRS(%r15) # restore kernel registers + xgr %r0,%r0 # clear guest registers to + xgr %r1,%r1 # prevent speculative use + xgr %r2,%r2 + xgr %r3,%r3 + xgr %r4,%r4 + xgr %r5,%r5 + BR_EX %r14 +SYM_FUNC_END(sae) +EXPORT_SYMBOL(sae) +#endif + /* * SVC interrupt handler routine. System calls are synchronous events and * are entered with interrupts disabled. diff --git a/arch/s390/tools/opcodes.txt b/arch/s390/tools/opcodes.txt index def2659f6602..0e4773c94af0 100644 --- a/arch/s390/tools/opcodes.txt +++ b/arch/s390/tools/opcodes.txt @@ -594,6 +594,9 @@ b9a0 clp RRF_U0RR b9a1 tpei RRE_RR b9a2 ptf RRE_R0 b9a4 uvc RRF_URR +b9a5 sae RRE_R0 +b9a6 lasrm RRE_R0 +b9a7 stiasrm RRE_R0 b9aa lptea RRF_RURR2 b9ab essa RRF_U0RR b9ac irbm RRE_RR From e6c329180d1d59766a651ec92397a417953b5239 Mon Sep 17 00:00:00 2001 From: Steffen Eiden Date: Thu, 2 Apr 2026 06:00:00 +0200 Subject: [PATCH 15/24] KVM: s390: arm64: Introduce host definitions Add all basic definitions the arm on s390 KVM host requires. Including, but not limited to, struct kvm*arch definitions, various functions (to be implemented in the following patches), and various defines required to run arm64 guests. Enlighten asm/kvm_host.h for the new header variant. The new header is chosen instead of asm/kvm_host_s390.h if KVM_S390_ARM64 is defined. Co-developed-by: Andreas Grapentin Signed-off-by: Andreas Grapentin Co-developed-by: Nina Schoetterl-Glausch Signed-off-by: Nina Schoetterl-Glausch Signed-off-by: Steffen Eiden [borntraeger: Rebased onto latest changes regarding KVM configuration] Signed-off-by: Christian Borntraeger --- arch/s390/include/asm/kvm.h | 6 + arch/s390/include/asm/kvm_host.h | 4 + arch/s390/include/asm/kvm_host_arm64.h | 209 +++++++++++++++++++++++++ 3 files changed, 219 insertions(+) create mode 100644 arch/s390/include/asm/kvm.h create mode 100644 arch/s390/include/asm/kvm_host_arm64.h diff --git a/arch/s390/include/asm/kvm.h b/arch/s390/include/asm/kvm.h new file mode 100644 index 000000000000..aeb11dc631d5 --- /dev/null +++ b/arch/s390/include/asm/kvm.h @@ -0,0 +1,6 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#ifdef KVM_S390_ARM64 +#include +#else +#include +#endif diff --git a/arch/s390/include/asm/kvm_host.h b/arch/s390/include/asm/kvm_host.h index 147325978225..ac3440a617c1 100644 --- a/arch/s390/include/asm/kvm_host.h +++ b/arch/s390/include/asm/kvm_host.h @@ -3,7 +3,11 @@ #ifndef ASM_KVM_HOST_H #define ASM_KVM_HOST_H +#ifdef KVM_S390_ARM64 +#include +#else #include +#endif #define PGM_OPERATION 0x01 #define PGM_PRIVILEGED_OP 0x02 diff --git a/arch/s390/include/asm/kvm_host_arm64.h b/arch/s390/include/asm/kvm_host_arm64.h new file mode 100644 index 000000000000..82343e723ebf --- /dev/null +++ b/arch/s390/include/asm/kvm_host_arm64.h @@ -0,0 +1,209 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#ifndef ASM_KVM_HOST_ARM64_H +#define ASM_KVM_HOST_ARM64_H + +#include + +#include +#include + +#define vcpu_gp_regs(v) ((v)->arch.sae_block.gpr) + +#include +#include + +#include + +#define KVM_HAVE_MMU_RWLOCK +#define KVM_MAX_VCPUS 1 +#define __KVM_HAVE_VCPU_RUN_PID_CHANGE + +#define KVM_S390_ARM64_IMPL_FEATURES 0 +#define KVM_HALT_POLL_NS_DEFAULT 50000 + +#define KVM_S390_MANAGES_S390_GUEST 0 + +/* Minimal (=no) vgic definitions */ +#define KVM_IRQCHIP_NUM_PINS 1 +#define irqchip_in_kernel(_k) false + +#define __ctxt_sys_reg(ctx, reg) NULL +struct kvm_cpu_context { + /* + * These are just for 32 bit, which we don't have, making them RES0. + * They are exposed to user space. + */ + u64 spsr_abt; + u64 spsr_und; + u64 spsr_irq; + u64 spsr_fiq; + + __vector128 __aligned(16) vregs[32]; +}; + +struct kvm_vcpu_arch { + struct kvm_sae_block sae_block; + struct kvm_sae_save_area save_area; + struct kvm_cpu_context ctxt; + + u32 host_acrs[NUM_ACRS]; + + /* Hypervisor Configuration Register */ + u64 hcr_el2; + + /* Configuration flags, set once and for all before the vcpu can run */ + u8 cflags; + + /* Input flags to the hypervisor code, potentially cleared after use */ + u8 iflags; + + /* State flags for kernel bookkeeping, unused by the hypervisor code */ + u8 sflags; + + /* + * Don't run the guest (internal implementation need). + * + * Contrary to the flags above, this is set/cleared outside of + * a vcpu context, and thus cannot be mixed with the flags + * themselves (or the flag accesses need to be made atomic). + */ + bool pause; + + /* vcpu power state */ + struct kvm_mp_state mp_state; + /* lock for mp_state & reset_state.reset */ + spinlock_t mp_state_lock; + + /* vcpu reset state */ + struct vcpu_reset_state reset_state; + + struct kvm_s390_mmu_cache *mc; + + void *debugfs_state_data; +}; + +struct kvm_vcpu_stat { + struct kvm_vcpu_stat_generic generic; + /* ARM64 stats */ + u64 hvc_exit_stat; + u64 wfe_exit_stat; + u64 wfi_exit_stat; + u64 mmio_exit_user; + u64 mmio_exit_kernel; + u64 signal_exits; + u64 exits; + /* GMAP stats */ + u64 pfault_sync; +}; + +#define kvm_has_mte(_kvm) false +#define vcpu_has_sve(_vcpu) false +#define vcpu_has_ptrauth(_vcpu) false + +struct kvm_arch_memory_slot { +}; + +struct kvm_arch { + struct gmap *gmap; + u64 guest_phys_size; + + /* VM-wide vCPU feature set */ + unsigned long flags; + + /* Protects VM-scoped configuration data */ + struct mutex config_lock; + + debug_info_t *dbf; + + DECLARE_BITMAP(vcpu_features, KVM_VCPU_MAX_FEATURES); + + unsigned long mem_limit; +}; + +static inline bool __vcpu_has_feature(const struct kvm_arch *ka, int feature) +{ + return test_bit(feature, ka->vcpu_features); +} + +struct kvm_vm_stat { + struct kvm_vm_stat_generic generic; +}; + +static inline bool system_has_full_ptr_auth(void) +{ + return true; +} + +#define kvm_vm_is_protected(_kvm) false +#define vcpu_is_protected(_vcpu) false + +#define vcpu_is_loaded(_vcpu) ((_vcpu)->cpu != -1) + +#define KVM_HVA_ERR_BAD (-1UL) +#define KVM_HVA_ERR_RO_BAD (-2UL) + +static inline bool kvm_is_error_hva(unsigned long addr) +{ + return IS_ERR_VALUE(addr); +} + +u32 get_kvm_ipa_limit(void); + +/* unused, but required functions */ +static inline void kvm_arch_free_memslot(struct kvm *kvm, struct kvm_memory_slot *slot) {} +static inline void kvm_arch_memslots_updated(struct kvm *kvm, u64 gen) {} +static inline void kvm_arch_flush_shadow_all(struct kvm *kvm) {} +static inline void kvm_arch_flush_shadow_memslot(struct kvm *kvm, struct kvm_memory_slot *slot) {} +static inline void kvm_arch_vcpu_blocking(struct kvm_vcpu *vcpu) {} +static inline void kvm_arch_vcpu_unblocking(struct kvm_vcpu *vcpu) {} +static inline void kvm_arch_sync_events(struct kvm *kvm) {} +static inline void kvm_arch_sched_in(struct kvm_vcpu *vcpu, int cpu) {} +static inline void kvm_arch_mmu_enable_log_dirty_pt_masked(struct kvm *kvm, + struct kvm_memory_slot *slot, + gfn_t gfn_offset, + unsigned long mask) {} + +int kvm_handle_guest_abort(struct kvm_vcpu *vcpu); +void kvm_reset_vcpu(struct kvm_vcpu *vcpu); + +/* arm64 guests do not use async-pf. Defined because Kbuild requires it as s390 kvm turns it on. */ +#define ASYNC_PF_PER_VCPU 0 +struct kvm_arch_async_pf { + unsigned long pfault_token; +}; + +#define __unsupp_async_call(fn) WARN_ONCE(true, "async not supported on kvm-arm64 %s", fn) + +static inline bool kvm_arch_can_dequeue_async_page_present(struct kvm_vcpu *vcpu) +{ + __unsupp_async_call(__func__); + return false; +}; + +static inline void kvm_arch_async_page_ready(struct kvm_vcpu *vcpu, + struct kvm_async_pf *work) +{ + __unsupp_async_call(__func__); +}; + +static inline bool kvm_arch_async_page_not_present(struct kvm_vcpu *vcpu, + struct kvm_async_pf *work) +{ + __unsupp_async_call(__func__); + return false; +}; + +static inline void kvm_arch_async_page_present(struct kvm_vcpu *vcpu, + struct kvm_async_pf *work) +{ + __unsupp_async_call(__func__); +}; + +static inline void kvm_arch_async_page_present_queued(struct kvm_vcpu *vcpu) +{ + __unsupp_async_call(__func__); +}; + +#define kvm_supports_32bit_el0() false + +#endif /* ASM_KVM_HOST_ARM64_H */ From dcd1a3a29250a971e6dc86a6f5e4e16749d6006d Mon Sep 17 00:00:00 2001 From: Hendrik Brueckner Date: Thu, 2 Apr 2026 06:00:00 +0200 Subject: [PATCH 16/24] s390/hwcaps: Report SAE support as hwcap Report SAE support as hwcap (and /proc/cpuinfo) Signed-off-by: Hendrik Brueckner Reviewed-by: Steffen Eiden Reviewed-by: Janosch Frank Reviewed-by: Ilya Leoshkevich Signed-off-by: Steffen Eiden Signed-off-by: Christian Borntraeger --- arch/s390/include/asm/elf.h | 2 ++ arch/s390/include/asm/sclp.h | 5 ++++- arch/s390/kernel/processor.c | 3 +++ drivers/s390/char/sclp_early.c | 1 + 4 files changed, 10 insertions(+), 1 deletion(-) diff --git a/arch/s390/include/asm/elf.h b/arch/s390/include/asm/elf.h index bb63fa4d20bb..ad3108ecfb07 100644 --- a/arch/s390/include/asm/elf.h +++ b/arch/s390/include/asm/elf.h @@ -123,6 +123,7 @@ enum { HWCAP_NR_NNPA = 20, HWCAP_NR_PCI_MIO = 21, HWCAP_NR_SIE = 22, + HWCAP_NR_SAE = 23, HWCAP_NR_MAX }; @@ -150,6 +151,7 @@ enum { #define HWCAP_NNPA BIT(HWCAP_NR_NNPA) #define HWCAP_PCI_MIO BIT(HWCAP_NR_PCI_MIO) #define HWCAP_SIE BIT(HWCAP_NR_SIE) +#define HWCAP_SAE BIT(HWCAP_NR_SAE) /* * These are used to set parameters in the core dumps. diff --git a/arch/s390/include/asm/sclp.h b/arch/s390/include/asm/sclp.h index 44066550b9b1..68f61b71a2d5 100644 --- a/arch/s390/include/asm/sclp.h +++ b/arch/s390/include/asm/sclp.h @@ -56,7 +56,9 @@ struct sclp_core_entry { u8 siif : 1; u8 sigpif : 1; u8 : 3; - u8 reserved2[3]; + u8 aef: 1; + u8 : 7; + u8 reserved2[2]; u8 : 2; u8 ib : 1; u8 cei : 1; @@ -109,6 +111,7 @@ struct sclp_info { unsigned char has_aeni : 1; unsigned char has_aisi : 1; unsigned char has_astfleie2 : 1; + unsigned char has_aef : 1; unsigned int ibc; unsigned int mtid; unsigned int mtid_cp; diff --git a/arch/s390/kernel/processor.c b/arch/s390/kernel/processor.c index e33a3eccda56..6da55a158027 100644 --- a/arch/s390/kernel/processor.c +++ b/arch/s390/kernel/processor.c @@ -150,6 +150,7 @@ static void show_cpu_summary(struct seq_file *m, void *v) [HWCAP_NR_NNPA] = "nnpa", [HWCAP_NR_PCI_MIO] = "pcimio", [HWCAP_NR_SIE] = "sie", + [HWCAP_NR_SAE] = "sae", }; int i, cpu; @@ -254,6 +255,8 @@ static int __init setup_hwcaps(void) /* virtualization support */ if (sclp.has_sief2) elf_hwcap |= HWCAP_SIE; + if (sclp.has_aef) + elf_hwcap |= HWCAP_SAE; return 0; } diff --git a/drivers/s390/char/sclp_early.c b/drivers/s390/char/sclp_early.c index 22dd797e6229..e58d24b95bd1 100644 --- a/drivers/s390/char/sclp_early.c +++ b/drivers/s390/char/sclp_early.c @@ -96,6 +96,7 @@ static void __init sclp_early_facilities_detect(void) sclp.has_ib = cpue->ib; sclp.has_cei = cpue->cei; sclp.has_skey = cpue->skey; + sclp.has_aef = cpue->aef; break; } From 761404d7acfe8c58f9bd938332c6cc9beed6bafd Mon Sep 17 00:00:00 2001 From: Steffen Eiden Date: Thu, 2 Apr 2026 06:00:00 +0200 Subject: [PATCH 17/24] KVM: s390: Add basic arm64 kvm module Add basic code for the new arm64 on s390 KVM implementation. Add kernel module boilerplate code and trivial functions. Add tracing scaffolding. Signed-off-by: Steffen Eiden [borntraeger@linux.ibm.com: add struct kvm_vcpu *vcpu to system_supported_vcpu_features] Signed-off-by: Christian Borntraeger --- arch/s390/kvm/arm64/arm.c | 147 ++++++++++++++++++++++++++++++++++++ arch/s390/kvm/arm64/arm.h | 44 +++++++++++ arch/s390/kvm/arm64/guest.c | 90 ++++++++++++++++++++++ arch/s390/kvm/arm64/trace.h | 18 +++++ 4 files changed, 299 insertions(+) create mode 100644 arch/s390/kvm/arm64/arm.c create mode 100644 arch/s390/kvm/arm64/arm.h create mode 100644 arch/s390/kvm/arm64/guest.c create mode 100644 arch/s390/kvm/arm64/trace.h diff --git a/arch/s390/kvm/arm64/arm.c b/arch/s390/kvm/arm64/arm.c new file mode 100644 index 000000000000..3e70e3c3dcbc --- /dev/null +++ b/arch/s390/kvm/arm64/arm.c @@ -0,0 +1,147 @@ +// SPDX-License-Identifier: GPL-2.0 + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include +#include +#include +#include + +#include "arm.h" + +#define CREATE_TRACE_POINTS +#include "trace.h" + +static unsigned long system_supported_vcpu_features(struct kvm_vcpu *vcpu); + +#define __INCL_GEN_ARM_FILE +#include "generated/arm.inc" +#undef __INCL_GEN_ARM_FILE + +int kvm_vm_ioctl_check_extension(struct kvm *kvm, long ext) +{ + int ret; + + switch (ext) { + case KVM_CAP_NR_VCPUS: + case KVM_CAP_MAX_VCPUS: + case KVM_CAP_MAX_VCPU_ID: + ret = KVM_MAX_VCPUS; + break; + case KVM_CAP_ARM_VM_IPA_SIZE: + ret = get_kvm_ipa_limit(); + break; + case KVM_CAP_IOEVENTFD: + case KVM_CAP_PRE_FAULT_MEMORY: + ret = 1; + break; + default: + ret = 0; + } + + return ret; +} + +static u64 kvm_max_guest_address(void) +{ + u64 max_addr; + + max_addr = min_t(u64, TASK_SIZE_MAX, sclp.hamax); + max_addr = max_t(u64, max_addr, SZ_1G - 1); + return ALIGN_DOWN(max_addr + 1, SZ_1G) - 1; +} + +vm_fault_t kvm_arch_vcpu_fault(struct kvm_vcpu *vcpu, struct vm_fault *vmf) +{ + return VM_FAULT_SIGBUS; +} + +long kvm_arch_dev_ioctl(struct file *filp, + unsigned int ioctl, unsigned long arg) +{ + return -EINVAL; +} + +u32 get_kvm_ipa_limit(void) +{ + return fls64(kvm_max_guest_address() + 1) - 1; +} + +int kvm_arch_vcpu_precreate(struct kvm *kvm, unsigned int id) +{ + return 0; +} + +void kvm_arch_vcpu_postcreate(struct kvm_vcpu *vcpu) +{ +} + +int kvm_arch_vcpu_ioctl_get_mpstate(struct kvm_vcpu *vcpu, + struct kvm_mp_state *mp_state) +{ + *mp_state = READ_ONCE(vcpu->arch.mp_state); + return 0; +} + +int kvm_arch_vcpu_ioctl_set_mpstate(struct kvm_vcpu *vcpu, + struct kvm_mp_state *mp_state) +{ + return -EINVAL; +} + +int kvm_arch_vcpu_runnable(struct kvm_vcpu *vcpu) +{ + return 0; +} + +static unsigned long system_supported_vcpu_features(struct kvm_vcpu *vcpu) +{ + unsigned long features = KVM_S390_ARM64_IMPL_FEATURES; + + return features; +} + +bool kvm_arch_irqchip_in_kernel(struct kvm *kvm) +{ + return false; +} + +int kvm_set_msi(struct kvm_kernel_irq_routing_entry *e, struct kvm *kvm, + int irq_source_id, int level, bool line_status) +{ + return -EINVAL; +} + +int kvm_set_routing_entry(struct kvm *kvm, + struct kvm_kernel_irq_routing_entry *e, + const struct kvm_irq_routing_entry *ue) +{ + return -EINVAL; +} + +int kvm_cpu_has_pending_timer(struct kvm_vcpu *vcpu) +{ + return 0; +} + +long kvm_arch_vcpu_unlocked_ioctl(struct file *filp, unsigned int ioctl, + unsigned long arg) +{ + return -ENOIOCTLCMD; +} + +static int __init kvm_s390_arm64_init(void) +{ + if (!sclp.has_aef) + return -ENODEV; + + return kvm_init(sizeof(struct kvm_vcpu), 0, THIS_MODULE); +} + +static __exit void kvm_s390_arm64_exit(void) +{ + kvm_exit(); +} + +module_init(kvm_s390_arm64_init); +module_exit(kvm_s390_arm64_exit); diff --git a/arch/s390/kvm/arm64/arm.h b/arch/s390/kvm/arm64/arm.h new file mode 100644 index 000000000000..9d903916c896 --- /dev/null +++ b/arch/s390/kvm/arm64/arm.h @@ -0,0 +1,44 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#ifndef ARCH_S390_KVM_ARM64_H +#define ARCH_S390_KVM_ARM64_H + +static __always_inline int kvm_is_ucontrol(struct kvm *kvm) +{ + return 0; +} + +static __always_inline int __kvm_s390_pv_destroy_page(struct page *page) +{ + return 0; +} + +static __always_inline void kvm_s390_vsie_gmap_notifier(struct gmap *gmap, gpa_t start, gpa_t end) +{ +} + +static __always_inline int kvm_s390_pv_get_handle(struct kvm *kvm) +{ + return 0; +} + +static __always_inline int kvm_s390_is_migration_mode(struct kvm *kvm) +{ + return false; +} + +static __always_inline bool kvm_arch_setup_async_pf(struct kvm_vcpu *vcpu) +{ + return false; +} + +static __always_inline void kvm_s390_update_cmma_dirty(struct kvm *kvm, const struct kvm_memory_slot *old) +{ +} + +/* should never be called */ +static __always_inline int kvm_s390_vm_stop_migration(struct kvm *kvm) +{ + return -EINVAL; +} + +#endif /* ARCH_S390_KVM_ARM64_H */ diff --git a/arch/s390/kvm/arm64/guest.c b/arch/s390/kvm/arm64/guest.c new file mode 100644 index 000000000000..1bf546bded81 --- /dev/null +++ b/arch/s390/kvm/arm64/guest.c @@ -0,0 +1,90 @@ +// SPDX-License-Identifier: GPL-2.0 +#include +#include + +#include +#include + +#define __INCL_GEN_ARM_FILE +#include "generated/guest.inc" +#undef __INCL_GEN_ARM_FILE + +const struct kvm_stats_desc kvm_vm_stats_desc[] = { + KVM_GENERIC_VM_STATS() +}; + +const struct kvm_stats_header kvm_vm_stats_header = { + .name_size = KVM_STATS_NAME_SIZE, + .num_desc = ARRAY_SIZE(kvm_vm_stats_desc), + .id_offset = sizeof(struct kvm_stats_header), + .desc_offset = sizeof(struct kvm_stats_header) + KVM_STATS_NAME_SIZE, + .data_offset = sizeof(struct kvm_stats_header) + KVM_STATS_NAME_SIZE + + sizeof(kvm_vm_stats_desc), +}; + +const struct kvm_stats_desc kvm_vcpu_stats_desc[] = { + KVM_GENERIC_VCPU_STATS(), + /* ARM64 stats */ + STATS_DESC_COUNTER(VCPU, hvc_exit_stat), + STATS_DESC_COUNTER(VCPU, wfe_exit_stat), + STATS_DESC_COUNTER(VCPU, wfi_exit_stat), + STATS_DESC_COUNTER(VCPU, mmio_exit_user), + STATS_DESC_COUNTER(VCPU, mmio_exit_kernel), + STATS_DESC_COUNTER(VCPU, signal_exits), + STATS_DESC_COUNTER(VCPU, exits), + /* GMAP stats */ + STATS_DESC_COUNTER(VCPU, pfault_sync), +}; + +const struct kvm_stats_header kvm_vcpu_stats_header = { + .name_size = KVM_STATS_NAME_SIZE, + .num_desc = ARRAY_SIZE(kvm_vcpu_stats_desc), + .id_offset = sizeof(struct kvm_stats_header), + .desc_offset = sizeof(struct kvm_stats_header) + KVM_STATS_NAME_SIZE, + .data_offset = sizeof(struct kvm_stats_header) + KVM_STATS_NAME_SIZE + + sizeof(kvm_vcpu_stats_desc), +}; + +int kvm_arch_vcpu_ioctl_get_regs(struct kvm_vcpu *vcpu, struct kvm_regs *regs) +{ + return -EINVAL; +} + +int kvm_arch_vcpu_ioctl_set_regs(struct kvm_vcpu *vcpu, struct kvm_regs *regs) +{ + return -EINVAL; +} + +int kvm_arch_vcpu_ioctl_get_sregs(struct kvm_vcpu *vcpu, + struct kvm_sregs *sregs) +{ + return -EINVAL; +} + +int kvm_arch_vcpu_ioctl_set_sregs(struct kvm_vcpu *vcpu, + struct kvm_sregs *sregs) +{ + return -EINVAL; +} + +int kvm_arch_vcpu_ioctl_get_fpu(struct kvm_vcpu *vcpu, struct kvm_fpu *fpu) +{ + return -EINVAL; +} + +int kvm_arch_vcpu_ioctl_set_fpu(struct kvm_vcpu *vcpu, struct kvm_fpu *fpu) +{ + return -EINVAL; +} + +int kvm_arch_vcpu_ioctl_translate(struct kvm_vcpu *vcpu, + struct kvm_translation *tr) +{ + return -EINVAL; +} + +int kvm_arch_vcpu_ioctl_set_guest_debug(struct kvm_vcpu *vcpu, + struct kvm_guest_debug *dbg) +{ + return -EINVAL; +} diff --git a/arch/s390/kvm/arm64/trace.h b/arch/s390/kvm/arm64/trace.h new file mode 100644 index 000000000000..3a5dcd039c13 --- /dev/null +++ b/arch/s390/kvm/arm64/trace.h @@ -0,0 +1,18 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#if !defined(KVM_ARM64_TRACE_KVM_H) || defined(TRACE_HEADER_MULTI_READ) +#define KVM_ARM64_TRACE_KVM_H + +#include + +#undef TRACE_SYSTEM +#define TRACE_SYSTEM kvm + +#endif /* KVM_ARM64_TRACE_KVM_H */ + +#undef TRACE_INCLUDE_PATH +#define TRACE_INCLUDE_PATH . +#undef TRACE_INCLUDE_FILE +#define TRACE_INCLUDE_FILE trace + +/* This part must be outside protection */ +#include From 3fb328f76b0f78b27ce9f4c3c1687c2c5bd15134 Mon Sep 17 00:00:00 2001 From: Steffen Eiden Date: Thu, 2 Apr 2026 06:00:00 +0200 Subject: [PATCH 18/24] KVM: s390: arm64: Implement required functions Implement the mostly trivial functions that the shared arm64 (kvm) code & headers oblige s390 to implement. MAINTAINERS: Add arch/s390/include/arm64/* to KVM/s390 Implement a very basic smccc handler that (non-compliantly) is just able to stop a vcpu. Signed-off-by: Steffen Eiden Signed-off-by: Christian Borntraeger --- MAINTAINERS | 1 + arch/s390/include/arm64/kvm_emulate.h | 130 ++++++++++++++++++++++++++ arch/s390/include/arm64/kvm_nested.h | 11 +++ arch/s390/include/arm64/ptrace.h | 11 +++ arch/s390/include/arm64/sysreg.h | 13 +++ arch/s390/kvm/arm64/handle_exit.c | 127 +++++++++++++++++++++++++ arch/s390/kvm/arm64/inject_fault.c | 21 +++++ 7 files changed, 314 insertions(+) create mode 100644 arch/s390/include/arm64/kvm_emulate.h create mode 100644 arch/s390/include/arm64/kvm_nested.h create mode 100644 arch/s390/include/arm64/ptrace.h create mode 100644 arch/s390/include/arm64/sysreg.h create mode 100644 arch/s390/kvm/arm64/handle_exit.c create mode 100644 arch/s390/kvm/arm64/inject_fault.c diff --git a/MAINTAINERS b/MAINTAINERS index c2414447892c..1d45ccf863c1 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -14372,6 +14372,7 @@ L: kvm@vger.kernel.org S: Supported T: git git://git.kernel.org/pub/scm/linux/kernel/git/kvms390/linux.git F: Documentation/virt/kvm/s390* +F: arch/s390/include/arm64/* F: arch/s390/include/asm/gmap_helpers.h F: arch/s390/include/asm/kvm* F: arch/s390/include/uapi/asm/kvm* diff --git a/arch/s390/include/arm64/kvm_emulate.h b/arch/s390/include/arm64/kvm_emulate.h new file mode 100644 index 000000000000..416c4f96db05 --- /dev/null +++ b/arch/s390/include/arm64/kvm_emulate.h @@ -0,0 +1,130 @@ +/* SPDX-License-Identifier: GPL-2.0 */ + +#ifndef __S390_ARM64_KVM_EMULATE_H__ +#define __S390_ARM64_KVM_EMULATE_H__ + +#include +#include + +#include +#include +#include +#include + +static __always_inline unsigned long *vcpu_pc(const struct kvm_vcpu *vcpu) +{ + return (unsigned long *)&vcpu->arch.sae_block.pc; +} + +static __always_inline unsigned long *vcpu_cpsr(const struct kvm_vcpu *vcpu) +{ + return (unsigned long *)&vcpu->arch.sae_block.pstate; +} + +static __always_inline unsigned long *vcpu_sp_el0(const struct kvm_vcpu *vcpu) +{ + return (unsigned long *)&vcpu->arch.sae_block.sp_el0; +} + +static __always_inline u64 *vcpu_sp_el1(struct kvm_vcpu *vcpu) +{ + return &vcpu->arch.sae_block.sp_el1; +} + +static __always_inline __vector128 *vcpu_vreg(struct kvm_vcpu *vcpu, int off) +{ + return &vcpu->arch.ctxt.vregs[off]; +} + +static __always_inline u32 *vcpu_fpsr(struct kvm_vcpu *vcpu) +{ + return &vcpu->arch.sae_block.fpsr; +} + +static __always_inline u32 *vcpu_fpcr(struct kvm_vcpu *vcpu) +{ + return &vcpu->arch.sae_block.fpcr; +} + +static __always_inline bool vcpu_mode_is_32bit(const struct kvm_vcpu *vcpu) +{ + return false; +} + +static __always_inline u64 kvm_vcpu_get_esr(const struct kvm_vcpu *vcpu) +{ + return vcpu->arch.sae_block.hai.esr_el2; +} + +static inline unsigned long *vcpu_hcr(struct kvm_vcpu *vcpu) +{ + return (unsigned long *)&vcpu->arch.hcr_el2; +} + +static __always_inline unsigned long kvm_vcpu_get_hfar(const struct kvm_vcpu *vcpu) +{ + return vcpu->arch.sae_block.hai.far_el2; +} + +static __always_inline phys_addr_t kvm_vcpu_get_fault_ipa(const struct kvm_vcpu *vcpu) +{ + return gfn_to_gpa(vcpu->arch.sae_block.hai.teid.addr); +} + +static inline u16 kvm_vcpu_fault_pic(const struct kvm_vcpu *vcpu) +{ + return vcpu->arch.sae_block.hai.pic & PGM_INT_CODE_MASK; +} + +static __always_inline +bool kvm_vcpu_trap_is_permission_fault(const struct kvm_vcpu *vcpu) +{ + return kvm_vcpu_fault_pic(vcpu) == PGM_PROTECTION; +} + +static __always_inline bool kvm_condition_valid(const struct kvm_vcpu *vcpu) +{ + return true; +} + +static __always_inline bool vcpu_el1_is_32bit(struct kvm_vcpu *vcpu) +{ + return false; +} + +static inline bool kvm_vcpu_is_be(struct kvm_vcpu *vcpu) +{ + return false; +} + +static inline int kvm_vcpu_abt_gltl(struct kvm_vcpu *vcpu) +{ + return vcpu->arch.sae_block.hai.gltl; +} + +static inline bool vcpu_mode_priv(const struct kvm_vcpu *vcpu) +{ + u32 mode = *vcpu_cpsr(vcpu) & PSR_MODE_MASK; + + return mode != PSR_MODE_EL0t; +} + +static inline void kvm_skip_instr(struct kvm_vcpu *vcpu) +{ + *vcpu_pc(vcpu) += 4; + *vcpu_cpsr(vcpu) &= ~PSR_BTYPE_MASK; + + /* advance the singlestep state machine */ + *vcpu_cpsr(vcpu) &= ~SPSR_ELx_SS; +} + +static inline void kvm_reset_fpsimd(struct kvm_vcpu *vcpu) +{ + memset(vcpu->arch.ctxt.vregs, 0, sizeof(vcpu->arch.ctxt.vregs)); + vcpu->arch.sae_block.fpsr = 0; + vcpu->arch.sae_block.fpcr = 0; +} + +#include + +#endif /* __S390_ARM64_KVM_EMULATE_H__ */ diff --git a/arch/s390/include/arm64/kvm_nested.h b/arch/s390/include/arm64/kvm_nested.h new file mode 100644 index 000000000000..e950b1a10c41 --- /dev/null +++ b/arch/s390/include/arm64/kvm_nested.h @@ -0,0 +1,11 @@ +/* SPDX-License-Identifier: GPL-2.0 */ + +#ifndef ASM_KVM_NESTED_H +#define ASM_KVM_NESTED_H + +static inline bool vcpu_has_nv(const struct kvm_vcpu *vcpu) +{ + return false; +} + +#endif /* ASM_KVM_NESTED_H */ diff --git a/arch/s390/include/arm64/ptrace.h b/arch/s390/include/arm64/ptrace.h new file mode 100644 index 000000000000..449b85582c97 --- /dev/null +++ b/arch/s390/include/arm64/ptrace.h @@ -0,0 +1,11 @@ +/* SPDX-License-Identifier: GPL-2.0 */ + +#ifndef __S390_ARM64_PTRACE_H__ +#define __S390_ARM64_PTRACE_H__ + +#include +#include + +#include + +#endif /* __S390_ARM64_PTRACE_H__ */ diff --git a/arch/s390/include/arm64/sysreg.h b/arch/s390/include/arm64/sysreg.h new file mode 100644 index 000000000000..ac6b638fef56 --- /dev/null +++ b/arch/s390/include/arm64/sysreg.h @@ -0,0 +1,13 @@ +/* SPDX-License-Identifier: GPL-2.0 */ + +#ifndef __S390_ARM64_SYSREG_H__ +#define __S390_ARM64_SYSREG_H__ + +#include +#include +#include +#include + +#include + +#endif /* __S390_ARM64_SYSREG_H__ */ diff --git a/arch/s390/kvm/arm64/handle_exit.c b/arch/s390/kvm/arm64/handle_exit.c new file mode 100644 index 000000000000..07d8d7a53702 --- /dev/null +++ b/arch/s390/kvm/arm64/handle_exit.c @@ -0,0 +1,127 @@ +// SPDX-License-Identifier: GPL-2.0 + +#include + +#include +#include + +typedef int (*exit_handle_fn)(struct kvm_vcpu *); + +#define PSCI_0_2_FN_SYSTEM_OFF 0x84000008 +#define PSCI_RET_NOT_SUPPORTED -1 +#define PSCI_RET_INTERNAL_FAILURE -6 +/* + * Temporary smc/hvc handler. Non-compliant implementation (features missing). + * Implements only system off so that test programs are able to end their execution. + * Multi-vCPU power state management and KVM_REQ_SLEEP handling are deferred + * to future SMP series. + */ +static int kvm_smccc_call_handler(struct kvm_vcpu *vcpu) +{ + u32 func_id = vcpu_get_reg(vcpu, 0); + u64 val = PSCI_RET_NOT_SUPPORTED; + int ret = 1; + + if (func_id == PSCI_0_2_FN_SYSTEM_OFF) { + scoped_guard(spinlock, &vcpu->arch.mp_state_lock) { + WRITE_ONCE(vcpu->arch.mp_state.mp_state, KVM_MP_STATE_STOPPED); + } + memset(&vcpu->run->system_event, 0, + sizeof(vcpu->run->system_event)); + vcpu->run->system_event.type = KVM_SYSTEM_EVENT_SHUTDOWN; + vcpu->run->system_event.ndata = 1; + vcpu->run->system_event.data[0] = 0; + vcpu->run->exit_reason = KVM_EXIT_SYSTEM_EVENT; + val = PSCI_RET_INTERNAL_FAILURE; + ret = 0; + } + vcpu_set_reg(vcpu, 0, val); + + return ret; +} + +static int handle_hvc(struct kvm_vcpu *vcpu) +{ + vcpu->stat.hvc_exit_stat++; + return kvm_smccc_call_handler(vcpu); +} + +static int kvm_handle_unknown_ec(struct kvm_vcpu *vcpu) +{ + u64 esr = kvm_vcpu_get_esr(vcpu); + + kvm_pr_unimpl("Unknown exception class: esr: %#016llx -- %s\n", + esr, esr_get_class_string(esr)); + + kvm_inject_undefined(vcpu); + return 1; +} + +static exit_handle_fn arm_exit_handlers[] = { + [0 ... ESR_ELx_EC_MAX] = kvm_handle_unknown_ec, + [ESR_ELx_EC_HVC64] = handle_hvc, +}; + +static exit_handle_fn kvm_get_exit_handler(struct kvm_vcpu *vcpu) +{ + u64 esr = kvm_vcpu_get_esr(vcpu); + u8 esr_ec = ESR_ELx_EC(esr); + + return arm_exit_handlers[esr_ec]; +} + +/* + * We may be single-stepping an emulated instruction. If the emulation + * has been completed in the kernel, we can return to userspace with a + * KVM_EXIT_DEBUG, otherwise userspace needs to complete its + * emulation first. + */ +static int handle_trap_exceptions(struct kvm_vcpu *vcpu) +{ + exit_handle_fn exit_handler; + + exit_handler = kvm_get_exit_handler(vcpu); + return exit_handler(vcpu); +} + +/* manually copied from arch/arm64/kernel/traps.c */ +static const char * const esr_class_str[] = { + [0 ... ESR_ELx_EC_MAX] = "UNRECOGNIZED EC", + [ESR_ELx_EC_UNKNOWN] = "Unknown/Uncategorized", + [ESR_ELx_EC_WFx] = "WFI/WFE", + [ESR_ELx_EC_FP_ASIMD] = "ASIMD", + [ESR_ELx_EC_PAC] = "PAC", + [ESR_ELx_EC_BTI] = "BTI", + [ESR_ELx_EC_ILL] = "PSTATE.IL", + [ESR_ELx_EC_SVC64] = "SVC (AArch64)", + [ESR_ELx_EC_HVC64] = "HVC (AArch64)", + [ESR_ELx_EC_SMC64] = "SMC (AArch64)", + [ESR_ELx_EC_SYS64] = "MSR/MRS (AArch64)", + [ESR_ELx_EC_SVE] = "SVE", + [ESR_ELx_EC_ERET] = "ERET/ERETAA/ERETAB", + [ESR_ELx_EC_FPAC] = "FPAC", + [ESR_ELx_EC_SME] = "SME", + [ESR_ELx_EC_IMP_DEF] = "EL3 IMP DEF", + [ESR_ELx_EC_IABT_LOW] = "IABT (lower EL)", + [ESR_ELx_EC_IABT_CUR] = "IABT (current EL)", + [ESR_ELx_EC_PC_ALIGN] = "PC Alignment", + [ESR_ELx_EC_DABT_LOW] = "DABT (lower EL)", + [ESR_ELx_EC_DABT_CUR] = "DABT (current EL)", + [ESR_ELx_EC_SP_ALIGN] = "SP Alignment", + [ESR_ELx_EC_MOPS] = "MOPS", + [ESR_ELx_EC_FP_EXC64] = "FP (AArch64)", + [ESR_ELx_EC_GCS] = "Guarded Control Stack", + [ESR_ELx_EC_SERROR] = "SError", + [ESR_ELx_EC_BREAKPT_LOW] = "Breakpoint (lower EL)", + [ESR_ELx_EC_BREAKPT_CUR] = "Breakpoint (current EL)", + [ESR_ELx_EC_SOFTSTP_LOW] = "Software Step (lower EL)", + [ESR_ELx_EC_SOFTSTP_CUR] = "Software Step (current EL)", + [ESR_ELx_EC_WATCHPT_LOW] = "Watchpoint (lower EL)", + [ESR_ELx_EC_WATCHPT_CUR] = "Watchpoint (current EL)", + [ESR_ELx_EC_BRK64] = "BRK (AArch64)", +}; + +const char *esr_get_class_string(unsigned long esr) +{ + return esr_class_str[ESR_ELx_EC(esr)]; +} diff --git a/arch/s390/kvm/arm64/inject_fault.c b/arch/s390/kvm/arm64/inject_fault.c new file mode 100644 index 000000000000..425dbeaa421c --- /dev/null +++ b/arch/s390/kvm/arm64/inject_fault.c @@ -0,0 +1,21 @@ +// SPDX-License-Identifier: GPL-2.0 + +#include + +/** + * kvm_inject_undefined - inject an undefined instruction into the guest + * @vcpu: The vCPU in which to inject the exception + * + * It is assumed that this code is called from the VCPU thread and that the + * VCPU therefore is not currently executing guest code. + */ +void kvm_inject_undefined(struct kvm_vcpu *vcpu) +{ + /* Stub until s390 supports arm64 sysregs TODO sysregs*/ +} + +int kvm_inject_sea(struct kvm_vcpu *vcpu, bool iabt, u64 addr) +{ + /* Stub until s390 supports arm64 sysregs TODO sysregs*/ + return 1; +} From 90246e7d1476b5471e7340d971e6b1800f182b05 Mon Sep 17 00:00:00 2001 From: Steffen Eiden Date: Thu, 2 Apr 2026 06:00:00 +0200 Subject: [PATCH 19/24] KVM: s390: arm64: Implement vm/vcpu create destroy Implement init and destroy IOCTLS for vcpu and vm. Implement arch vm IOCTL. Use s390 gmap and gmap plumbing. Implement function (stubs) required by gmap. Co-developed-by: Janosch Frank Signed-off-by: Janosch Frank Co-developed-by: Andreas Grapentin Signed-off-by: Andreas Grapentin Co-developed-by: Nina Schoetterl-Glausch Signed-off-by: Nina Schoetterl-Glausch Signed-off-by: Steffen Eiden Signed-off-by: Christian Borntraeger --- arch/s390/kvm/arm64/arm.c | 190 ++++++++++++++++++++++++++++++++ arch/s390/kvm/arm64/arm.h | 14 +++ arch/s390/kvm/gmap/faultin.c | 5 + arch/s390/kvm/gmap/gmap.c | 4 + arch/s390/kvm/gmap/kvm_mmu.c | 4 + arch/s390/kvm/gmap/trace_gmap.h | 13 +++ 6 files changed, 230 insertions(+) diff --git a/arch/s390/kvm/arm64/arm.c b/arch/s390/kvm/arm64/arm.c index 3e70e3c3dcbc..d5bae17d9c79 100644 --- a/arch/s390/kvm/arm64/arm.c +++ b/arch/s390/kvm/arm64/arm.c @@ -7,6 +7,9 @@ #include #include +#include +#include + #include "arm.h" #define CREATE_TRACE_POINTS @@ -18,6 +21,8 @@ static unsigned long system_supported_vcpu_features(struct kvm_vcpu *vcpu); #include "generated/arm.inc" #undef __INCL_GEN_ARM_FILE +#define KVM_PHYS_SHIFT (40) + int kvm_vm_ioctl_check_extension(struct kvm *kvm, long ext) { int ret; @@ -51,6 +56,83 @@ static u64 kvm_max_guest_address(void) return ALIGN_DOWN(max_addr + 1, SZ_1G) - 1; } +static int kvm_gmap_init(struct kvm *kvm) +{ + struct crst_table *table; + + kvm->arch.gmap = gmap_new(kvm, gpa_to_gfn(kvm->arch.guest_phys_size)); + + if (!kvm->arch.gmap) + return -ENOMEM; + + /* arm64 (on s390) do not have pfault */ + clear_bit(GMAP_FLAG_PFAULT_ENABLED, &kvm->arch.gmap->flags); + set_bit(GMAP_FLAG_ALLOW_HPAGE_1M, &kvm->arch.gmap->flags); + + table = dereference_asce(kvm->arch.gmap->asce); + crst_table_init((void *)table, _CRSTE_HOLE(table->crstes[0].h.tt).val); + + return 0; +} + +static int kvm_vm_type_ipa_size_shift(unsigned long type) +{ + int phys_shift; + + phys_shift = KVM_VM_TYPE_ARM_IPA_SIZE(type); + if (phys_shift) { + if (phys_shift > get_kvm_ipa_limit() || + phys_shift < ARM64_MIN_PARANGE_BITS) + return -EINVAL; + } else { + phys_shift = KVM_PHYS_SHIFT; + if (phys_shift > get_kvm_ipa_limit()) { + pr_warn_once("%s using unsupported default IPA limit\n", + current->comm); + return -EINVAL; + } + } + + return phys_shift; +} + +int kvm_arch_init_vm(struct kvm *kvm, unsigned long type) +{ + char debug_name[32]; + int ret; + + if (type & ~KVM_VM_TYPE_ARM_IPA_SIZE_MASK) + return -EINVAL; + + ret = kvm_vm_type_ipa_size_shift(type); + if (ret < 0) + return ret; + kvm->arch.guest_phys_size = 1UL << ret; + + mutex_init(&kvm->arch.config_lock); + bitmap_zero(kvm->arch.vcpu_features, KVM_VCPU_MAX_FEATURES); + + snprintf(debug_name, sizeof(debug_name), "kvm-arm64-%u-%u", + current->pid, get_random_u32()); + kvm->arch.dbf = debug_register(debug_name, 32, 1, 7 * sizeof(long)); + if (!kvm->arch.dbf) + return -ENOMEM; + debug_register_view(kvm->arch.dbf, &debug_sprintf_view); + + ret = kvm_gmap_init(kvm); + if (ret) + goto out_err; + kvm->arch.mem_limit = kvm->arch.guest_phys_size; + + VM_EVENT(kvm, 3, "vm created with type %lu", type); + return 0; + +out_err: + debug_unregister(kvm->arch.dbf); + + return ret; +} + vm_fault_t kvm_arch_vcpu_fault(struct kvm_vcpu *vcpu, struct vm_fault *vmf) { return VM_FAULT_SIGBUS; @@ -62,6 +144,13 @@ long kvm_arch_dev_ioctl(struct file *filp, return -EINVAL; } +void kvm_arch_destroy_vm(struct kvm *kvm) +{ + kvm_destroy_vcpus(kvm); + debug_unregister(kvm->arch.dbf); + kvm->arch.gmap = gmap_put(kvm->arch.gmap); +} + u32 get_kvm_ipa_limit(void) { return fls64(kvm_max_guest_address() + 1) - 1; @@ -72,10 +161,45 @@ int kvm_arch_vcpu_precreate(struct kvm *kvm, unsigned int id) return 0; } +int kvm_arch_vcpu_create(struct kvm_vcpu *vcpu) +{ + struct kvm_sae_block *sae_block = &vcpu->arch.sae_block; + struct kvm_sae_save_area *save_area = &vcpu->arch.save_area; + + spin_lock_init(&vcpu->arch.mp_state_lock); + + /* Force users to call KVM_ARM_VCPU_INIT */ + vcpu_clear_flag(vcpu, VCPU_INITIALIZED); + + vcpu->arch.mc = kvm_s390_new_mmu_cache(); + if (!vcpu->arch.mc) + return -ENOMEM; + + save_area->sdo = virt_to_phys(sae_block); + + sae_block->hbasce = vcpu->kvm->arch.gmap->asce.val; + sae_block->mso = 0L; + sae_block->msl = vcpu->kvm->arch.mem_limit - 1; + sae_block->save_area = virt_to_phys(save_area); + + VM_EVENT(vcpu->kvm, 3, + "create cpu %d at 0x%p, sae block at 0x%p, satellite at 0x%p", + vcpu->vcpu_id, vcpu, &vcpu->arch.sae_block, + &vcpu->arch.save_area); + return 0; +} + void kvm_arch_vcpu_postcreate(struct kvm_vcpu *vcpu) { } +void kvm_arch_vcpu_destroy(struct kvm_vcpu *vcpu) +{ + kvm_s390_free_mmu_cache(vcpu->arch.mc); + + VCPU_EVENT(vcpu, 3, "%s", "free cpu"); +} + int kvm_arch_vcpu_ioctl_get_mpstate(struct kvm_vcpu *vcpu, struct kvm_mp_state *mp_state) { @@ -101,6 +225,72 @@ static unsigned long system_supported_vcpu_features(struct kvm_vcpu *vcpu) return features; } +int kvm_vm_ioctl_get_dirty_log(struct kvm *kvm, + struct kvm_dirty_log *log) +{ + return s390_kvm_mmu_get_dirty_log(kvm, log); +} + +bool kvm_age_gfn(struct kvm *kvm, struct kvm_gfn_range *range) +{ + scoped_guard(read_lock, &kvm->mmu_lock) + return gmap_age_gfn(kvm->arch.gmap, range->start, range->end); +} + +void kvm_arch_sync_dirty_log(struct kvm *kvm, struct kvm_memory_slot *memslot) +{ + gfn_t last_gfn = memslot->base_gfn + memslot->npages; + + scoped_guard(read_lock, &kvm->mmu_lock) + gmap_sync_dirty_log(kvm->arch.gmap, memslot->base_gfn, last_gfn); +} + +int kvm_arch_prepare_memory_region(struct kvm *kvm, + const struct kvm_memory_slot *old, + struct kvm_memory_slot *new, + enum kvm_mr_change change) +{ + return s390_kvm_mmu_prepare_memory_region(kvm, old, new, change); +} + +void kvm_arch_commit_memory_region(struct kvm *kvm, struct kvm_memory_slot *old, + const struct kvm_memory_slot *new, + enum kvm_mr_change change) +{ +} + +bool kvm_unmap_gfn_range(struct kvm *kvm, struct kvm_gfn_range *range) +{ + return gmap_unmap_gfn_range(kvm->arch.gmap, range->slot, range->start, range->end); +} + +bool kvm_test_age_gfn(struct kvm *kvm, struct kvm_gfn_range *range) +{ + scoped_guard(read_lock, &kvm->mmu_lock) + return dat_test_age_gfn(kvm->arch.gmap->asce, range->start, range->end); +} + +int kvm_arch_vm_ioctl(struct file *filp, unsigned int ioctl, unsigned long arg) +{ + void __user *argp = (void __user *)arg; + + switch (ioctl) { + case KVM_ARM_PREFERRED_TARGET: { + struct kvm_vcpu_init init = { + .target = KVM_ARM_TARGET_GENERIC_V8, + }; + + if (copy_to_user(argp, &init, sizeof(init))) + return -EFAULT; + + return 0; + } + + default: + return -EINVAL; + } +} + bool kvm_arch_irqchip_in_kernel(struct kvm *kvm) { return false; diff --git a/arch/s390/kvm/arm64/arm.h b/arch/s390/kvm/arm64/arm.h index 9d903916c896..760230685ca1 100644 --- a/arch/s390/kvm/arm64/arm.h +++ b/arch/s390/kvm/arm64/arm.h @@ -2,6 +2,20 @@ #ifndef ARCH_S390_KVM_ARM64_H #define ARCH_S390_KVM_ARM64_H +#include +#include + +#define VM_EVENT(d_kvm, d_loglevel, d_string, d_args...) \ + debug_sprintf_event((d_kvm)->arch.dbf, d_loglevel, \ + __stringify(KVM_DEV_NAME) ": " d_string "\n", \ + d_args) + +#define VCPU_EVENT(d_vcpu, d_loglevel, d_string, d_args...) \ + debug_sprintf_event((d_vcpu)->kvm->arch.dbf, d_loglevel, \ + __stringify(KVM_DEV_NAME) " %02d[%016llx-%016llx]: " d_string "\n", \ + (d_vcpu)->vcpu_id, (d_vcpu)->arch.sae_block.pstate, \ + (d_vcpu)->arch.sae_block.pc, d_args) + static __always_inline int kvm_is_ucontrol(struct kvm *kvm) { return 0; diff --git a/arch/s390/kvm/gmap/faultin.c b/arch/s390/kvm/gmap/faultin.c index 1dc79807012c..2e93bd563d5c 100644 --- a/arch/s390/kvm/gmap/faultin.c +++ b/arch/s390/kvm/gmap/faultin.c @@ -10,6 +10,11 @@ #include "gmap.h" #include "faultin.h" +#ifdef KVM_S390_ARM64 +#include "arm.h" +#else +#include "s390.h" +#endif bool kvm_arch_setup_async_pf(struct kvm_vcpu *vcpu); #define CREATE_TRACE_POINTS diff --git a/arch/s390/kvm/gmap/gmap.c b/arch/s390/kvm/gmap/gmap.c index 3f3fa864cc36..bf91e3d5013b 100644 --- a/arch/s390/kvm/gmap/gmap.c +++ b/arch/s390/kvm/gmap/gmap.c @@ -21,7 +21,11 @@ #include "dat.h" #include "gmap.h" +#ifdef KVM_S390_ARM64 +#include "arm.h" +#else #include "s390.h" +#endif #include "faultin.h" static int gmap_limit_to_type(gfn_t limit) diff --git a/arch/s390/kvm/gmap/kvm_mmu.c b/arch/s390/kvm/gmap/kvm_mmu.c index 24e618f77be1..4e2da60b9859 100644 --- a/arch/s390/kvm/gmap/kvm_mmu.c +++ b/arch/s390/kvm/gmap/kvm_mmu.c @@ -3,7 +3,11 @@ #include #include +#ifdef KVM_S390_ARM64 +#include "arm.h" +#else #include "s390.h" +#endif #include "gmap.h" #include "dat.h" #include "kvm_mmu.h" diff --git a/arch/s390/kvm/gmap/trace_gmap.h b/arch/s390/kvm/gmap/trace_gmap.h index 78d26dae59f9..1dce3ef3b0d9 100644 --- a/arch/s390/kvm/gmap/trace_gmap.h +++ b/arch/s390/kvm/gmap/trace_gmap.h @@ -13,6 +13,18 @@ #undef TRACE_INCLUDE_FILE #define TRACE_INCLUDE_FILE trace_gmap +#ifdef KVM_S390_ARM64 +#define __KVM_FIELDS \ + __field(unsigned long, pstate) \ + __field(unsigned long, pc) +#define __KVM_ASSIGN ({\ + __entry->pstate = vcpu->arch.sae_block.pstate; \ + __entry->pc = vcpu->arch.sae_block.pc; \ + }) +#define __KVM_PRINT \ + __entry->pstate, \ + __entry->pc +#else #define __KVM_FIELDS \ __field(unsigned long, pswmask) \ __field(unsigned long, pswaddr) @@ -23,6 +35,7 @@ #define __KVM_PRINT \ __entry->pswmask,\ __entry->pswaddr +#endif TRACE_EVENT(kvm_s390_major_guest_pfault, TP_PROTO(struct kvm_vcpu *vcpu), From 0630ce34a00dc65ec102c6def207397537bed52c Mon Sep 17 00:00:00 2001 From: Steffen Eiden Date: Thu, 2 Apr 2026 06:00:00 +0200 Subject: [PATCH 20/24] KVM: s390: arm64: Implement vCPU IOCTLs Implement all required vCPU (arch) IOCTLs. Co-developed-by: Andreas Grapentin Signed-off-by: Andreas Grapentin Co-developed-by: Nina Schoetterl-Glausch Signed-off-by: Nina Schoetterl-Glausch Signed-off-by: Steffen Eiden Signed-off-by: Christian Borntraeger --- arch/s390/kvm/arm64/arm.c | 417 ++++++++++++++++++++++++++++++ arch/s390/kvm/arm64/guest.c | 45 ++++ arch/s390/kvm/arm64/handle_exit.c | 32 +++ arch/s390/kvm/arm64/handle_exit.h | 9 + arch/s390/kvm/arm64/reset.c | 71 +++++ arch/s390/kvm/arm64/trace.h | 36 ++- 6 files changed, 609 insertions(+), 1 deletion(-) create mode 100644 arch/s390/kvm/arm64/handle_exit.h create mode 100644 arch/s390/kvm/arm64/reset.c diff --git a/arch/s390/kvm/arm64/arm.c b/arch/s390/kvm/arm64/arm.c index d5bae17d9c79..bd9d85235f43 100644 --- a/arch/s390/kvm/arm64/arm.c +++ b/arch/s390/kvm/arm64/arm.c @@ -6,11 +6,19 @@ #include #include #include +#include +#include + +#include + +#include +#include #include #include #include "arm.h" +#include "handle_exit.h" #define CREATE_TRACE_POINTS #include "trace.h" @@ -200,6 +208,22 @@ void kvm_arch_vcpu_destroy(struct kvm_vcpu *vcpu) VCPU_EVENT(vcpu, 3, "%s", "free cpu"); } +void kvm_arch_vcpu_load(struct kvm_vcpu *vcpu, int cpu) +{ + save_access_regs(&vcpu->arch.host_acrs[0]); + vcpu->cpu = cpu; + + lasrm(&vcpu->arch.save_area); +} + +void kvm_arch_vcpu_put(struct kvm_vcpu *vcpu) +{ + stiasrm(&vcpu->arch.save_area); + + vcpu->cpu = -1; + restore_access_regs(&vcpu->arch.host_acrs[0]); +} + int kvm_arch_vcpu_ioctl_get_mpstate(struct kvm_vcpu *vcpu, struct kvm_mp_state *mp_state) { @@ -225,6 +249,399 @@ static unsigned long system_supported_vcpu_features(struct kvm_vcpu *vcpu) return features; } +bool kvm_arch_vcpu_in_kernel(struct kvm_vcpu *vcpu) +{ + return vcpu_mode_priv(vcpu); +} + +int kvm_arch_vcpu_run_pid_change(struct kvm_vcpu *vcpu) +{ + struct kvm *kvm = vcpu->kvm; + + if (!kvm_vcpu_initialized(vcpu)) + return -ENOEXEC; + + if (!kvm_arm_vcpu_is_finalized(vcpu)) + return -EPERM; + + if (likely(READ_ONCE(vcpu->pid))) + return 0; + + scoped_guard(mutex, &kvm->arch.config_lock) + set_bit(KVM_ARCH_FLAG_HAS_RAN_ONCE, &kvm->arch.flags); + + return 0; +} + +/** + * check_vcpu_requests - check and handle pending vCPU requests + * @vcpu: the VCPU pointer + * + * Return: 1 if we should enter the guest + * 0 if we should exit to userspace + * < 0 if we should exit to userspace, where the return value indicates + * an error + */ +static int check_vcpu_requests(struct kvm_vcpu *vcpu) +{ + if (kvm_request_pending(vcpu)) { + if (kvm_test_request(KVM_REQ_VM_DEAD, vcpu)) + return -EIO; + if (kvm_check_request(KVM_REQ_VCPU_RESET, vcpu)) + kvm_reset_vcpu(vcpu); + /* + * Clear IRQ_PENDING requests that were made to guarantee + * that a VCPU sees new virtual interrupts. + */ + kvm_check_request(KVM_REQ_IRQ_PENDING, vcpu); + + if (kvm_check_request(KVM_REQ_TLB_FLUSH, vcpu)) + asce_flush_tlb(vcpu->kvm->arch.gmap->asce); + } + + return 1; +} + +static int kvm_vcpu_initialize(struct kvm_vcpu *vcpu, + const struct kvm_vcpu_init *init) +{ + unsigned long features = init->features[0]; + struct kvm *kvm = vcpu->kvm; + + guard(mutex)(&kvm->arch.config_lock); + if (test_bit(KVM_ARCH_FLAG_VCPU_FEATURES_CONFIGURED, &kvm->arch.flags) && + kvm_vcpu_init_changed(vcpu, init)) + return -EINVAL; + + bitmap_copy(kvm->arch.vcpu_features, &features, KVM_VCPU_MAX_FEATURES); + + kvm_reset_vcpu(vcpu); + + set_bit(KVM_ARCH_FLAG_VCPU_FEATURES_CONFIGURED, &kvm->arch.flags); + vcpu_set_flag(vcpu, VCPU_INITIALIZED); + + return 0; +} + +static int kvm_vcpu_set_target(struct kvm_vcpu *vcpu, + const struct kvm_vcpu_init *init) +{ + int ret; + + if (init->target != KVM_ARM_TARGET_GENERIC_V8) + return -EINVAL; + + ret = kvm_vcpu_init_check_features(vcpu, init); + if (ret) + return ret; + + if (!kvm_vcpu_initialized(vcpu)) + return kvm_vcpu_initialize(vcpu, init); + + if (kvm_vcpu_init_changed(vcpu, init)) + return -EINVAL; + + kvm_reset_vcpu(vcpu); + + return 0; +} + +static int kvm_arch_vcpu_ioctl_vcpu_init(struct kvm_vcpu *vcpu, + struct kvm_vcpu_init *init) +{ + struct kvm_sae_save_area *save_area = &vcpu->arch.save_area; + struct kvm_sae_block *sae_block = &vcpu->arch.sae_block; + int ret; + + sae_block->save_area = virt_to_phys(save_area); + save_area->sdo = virt_to_phys(sae_block); + + ret = kvm_vcpu_set_target(vcpu, init); + if (ret) + return ret; + + scoped_guard(spinlock, &vcpu->arch.mp_state_lock) { + WRITE_ONCE(vcpu->arch.mp_state.mp_state, KVM_MP_STATE_RUNNABLE); + } + + return 0; +} + +int kvm_vm_ioctl_irq_line(struct kvm *kvm, struct kvm_irq_level *irq_level, + bool line_status) +{ + /* stub for now*/ + return -EINVAL; +} + +static void adjust_pc(struct kvm_vcpu *vcpu) +{ + if (vcpu_get_flag(vcpu, INCREMENT_PC)) { + kvm_skip_instr(vcpu); + vcpu_clear_flag(vcpu, INCREMENT_PC); + } +} + +static noinstr void arm_vcpu_enter_exit(phys_addr_t sae_block) +{ + guest_timing_enter_irqoff(); + guest_state_enter_irqoff(); + + /* + * External interrupts may occur while the SAE instruction is executing + * and must preempt guest execution to maintain responsiveness of the + * host. + * + * As a temporary workaround, run SAE with interrupts enabled so that any + * interrupt is taken via the normal host interrupt path rather than + * being subsumed inside the SAE window. + */ + raw_local_irq_enable(); + sae(sae_block); + raw_local_irq_disable(); + + guest_state_exit_irqoff(); + guest_timing_exit_irqoff(); +} + +/** kvm_arch_vcpu_ioctl_run() - run arm64 vCPU + * + * Execute arm64 guest instructions using SAE. + * + * Returns: + * 1 enter the guest (should not be observed by userspace) + * 0 exit to userspace + * < 0 exit to userspace, where the return value indicates an error + * + * + */ +int kvm_arch_vcpu_ioctl_run(struct kvm_vcpu *vcpu) +{ + DECLARE_KERNEL_FPU_ONSTACK32(fpu_save); + struct kvm_run *kvm_run = vcpu->run; + int ret; + + if (kvm_run->exit_reason == KVM_EXIT_MMIO) { + ret = kvm_handle_mmio_return(vcpu); + if (ret <= 0) + return ret; + } + + vcpu_load(vcpu); + + kernel_fpu_begin(&fpu_save, KERNEL_FPC | KERNEL_VXR); + load_vx_regs((vcpu->arch.ctxt.vregs)); + + if (!vcpu->wants_to_run) { + ret = -EINTR; + goto out; + } + + kvm_sigset_activate(vcpu); + + might_fault(); + + ret = 1; + kvm_run->exit_reason = KVM_EXIT_UNKNOWN; + while (ret > 0) { + ret = kvm_xfer_to_guest_mode_handle_work(vcpu); + if (!ret) + ret = 1; + + if (ret > 0) + ret = check_vcpu_requests(vcpu); + + vcpu->arch.sae_block.icptr = 0; + + local_irq_disable(); + xfer_to_guest_mode_prepare(); + /* + * The full memory barrier in smp_store_mb() orders the mode + * store against the kvm_request_pending() check below, so + * any pending kick is visible before we decide to enter the + * guest. + */ + smp_store_mb(vcpu->mode, IN_GUEST_MODE); + + if (ret <= 0 || kvm_request_pending(vcpu) || + xfer_to_guest_mode_work_pending()) { + vcpu->mode = OUTSIDE_GUEST_MODE; + local_irq_enable(); + continue; + } + + trace_kvm_entry(*vcpu_pc(vcpu)); + + adjust_pc(vcpu); + + arm_vcpu_enter_exit(virt_to_phys(&vcpu->arch.sae_block)); + + vcpu->mode = OUTSIDE_GUEST_MODE; + vcpu->stat.exits++; + + local_irq_enable(); + trace_kvm_exit(kvm_vcpu_trap_get_class(vcpu), *vcpu_pc(vcpu)); + + ret = handle_exit(vcpu); + } + + kvm_sigset_deactivate(vcpu); +out: + if (unlikely(vcpu_get_flag(vcpu, INCREMENT_PC))) + adjust_pc(vcpu); + + save_vx_regs(vcpu->arch.ctxt.vregs); + kernel_fpu_end(&fpu_save, KERNEL_FPC | KERNEL_VXR); + vcpu_put(vcpu); + + return ret; +} + +static int kvm_arm_vcpu_set_attr(struct kvm_vcpu *vcpu, + struct kvm_device_attr *attr) +{ + int ret; + + switch (attr->group) { + default: + ret = -ENXIO; + break; + } + + return ret; +} + +static int kvm_arm_vcpu_get_attr(struct kvm_vcpu *vcpu, + struct kvm_device_attr *attr) +{ + int ret; + + switch (attr->group) { + default: + ret = -ENXIO; + break; + } + + return ret; +} + +static int kvm_arm_vcpu_has_attr(struct kvm_vcpu *vcpu, + struct kvm_device_attr *attr) +{ + int ret; + + switch (attr->group) { + default: + ret = -ENXIO; + break; + } + + return ret; +} + +long kvm_arch_vcpu_ioctl(struct file *filp, unsigned int ioctl, unsigned long arg) +{ + struct kvm_vcpu *vcpu = filp->private_data; + void __user *argp = (void __user *)arg; + struct kvm_device_attr attr; + int ret; + + switch (ioctl) { + case KVM_ARM_VCPU_INIT: { + struct kvm_vcpu_init init; + + ret = -EFAULT; + if (copy_from_user(&init, argp, sizeof(init))) + break; + + ret = kvm_arch_vcpu_ioctl_vcpu_init(vcpu, &init); + break; + } + case KVM_SET_ONE_REG: + case KVM_GET_ONE_REG: { + struct kvm_one_reg reg; + + ret = -ENOEXEC; + if (unlikely(!kvm_vcpu_initialized(vcpu))) + break; + + ret = -EFAULT; + if (copy_from_user(®, argp, sizeof(reg))) + break; + + if (kvm_check_request(KVM_REQ_VCPU_RESET, vcpu)) + kvm_reset_vcpu(vcpu); + + if (ioctl == KVM_SET_ONE_REG) + ret = kvm_arm_set_reg(vcpu, ®); + else + ret = kvm_arm_get_reg(vcpu, ®); + break; + } + case KVM_GET_REG_LIST: { + struct kvm_reg_list __user *user_list = argp; + struct kvm_reg_list reg_list; + unsigned int n; + + ret = -ENOEXEC; + if (unlikely(!kvm_vcpu_initialized(vcpu))) + break; + ret = -EPERM; + if (!kvm_arm_vcpu_is_finalized(vcpu)) + break; + ret = -EFAULT; + if (copy_from_user(®_list, user_list, sizeof(reg_list))) + break; + n = reg_list.n; + reg_list.n = kvm_arm_num_regs(vcpu); + if (copy_to_user(user_list, ®_list, sizeof(reg_list))) + break; + ret = -E2BIG; + if (n < reg_list.n) + break; + ret = kvm_arm_copy_reg_indices(vcpu, user_list->reg); + break; + } + case KVM_ARM_VCPU_FINALIZE: { + int what; + + if (!kvm_vcpu_initialized(vcpu)) + return -ENOEXEC; + + if (get_user(what, (const int __user *)argp)) + return -EFAULT; + + ret = kvm_arm_vcpu_finalize(vcpu, what); + break; + } + case KVM_SET_DEVICE_ATTR: { + ret = -EFAULT; + if (copy_from_user(&attr, argp, sizeof(attr))) + break; + ret = kvm_arm_vcpu_set_attr(vcpu, &attr); + break; + } + case KVM_GET_DEVICE_ATTR: { + ret = -EFAULT; + if (copy_from_user(&attr, argp, sizeof(attr))) + break; + ret = kvm_arm_vcpu_get_attr(vcpu, &attr); + break; + } + case KVM_HAS_DEVICE_ATTR: { + ret = -EFAULT; + if (copy_from_user(&attr, argp, sizeof(attr))) + break; + ret = kvm_arm_vcpu_has_attr(vcpu, &attr); + break; + } + default: + ret = -EINVAL; + } + + return ret; +} + int kvm_vm_ioctl_get_dirty_log(struct kvm *kvm, struct kvm_dirty_log *log) { diff --git a/arch/s390/kvm/arm64/guest.c b/arch/s390/kvm/arm64/guest.c index 1bf546bded81..4ea6fe2c270d 100644 --- a/arch/s390/kvm/arm64/guest.c +++ b/arch/s390/kvm/arm64/guest.c @@ -45,6 +45,51 @@ const struct kvm_stats_header kvm_vcpu_stats_header = { sizeof(kvm_vcpu_stats_desc), }; +int kvm_arm_copy_reg_indices(struct kvm_vcpu *vcpu, u64 __user *uindices) +{ + int ret; + + ret = copy_core_reg_indices(vcpu, uindices); + if (ret < 0) + return ret; + uindices += ret; + + return 0; +} + +unsigned long kvm_arm_num_regs(struct kvm_vcpu *vcpu) +{ + return num_core_regs(vcpu); +} + +int kvm_arm_get_reg(struct kvm_vcpu *vcpu, const struct kvm_one_reg *reg) +{ + /* We currently use nothing arch-specific in upper 32 bits */ + if ((reg->id & ~KVM_REG_SIZE_MASK) >> 32 != KVM_REG_ARM64 >> 32) + return -EINVAL; + + switch (reg->id & KVM_REG_ARM_COPROC_MASK) { + case KVM_REG_ARM_CORE: + return get_core_reg(vcpu, reg); + default: + return -EINVAL; + } +} + +int kvm_arm_set_reg(struct kvm_vcpu *vcpu, const struct kvm_one_reg *reg) +{ + /* We currently use nothing arch-specific in upper 32 bits */ + if ((reg->id & ~KVM_REG_SIZE_MASK) >> 32 != KVM_REG_ARM64 >> 32) + return -EINVAL; + + switch (reg->id & KVM_REG_ARM_COPROC_MASK) { + case KVM_REG_ARM_CORE: + return set_core_reg(vcpu, reg); + default: + return -EINVAL; + } +} + int kvm_arch_vcpu_ioctl_get_regs(struct kvm_vcpu *vcpu, struct kvm_regs *regs) { return -EINVAL; diff --git a/arch/s390/kvm/arm64/handle_exit.c b/arch/s390/kvm/arm64/handle_exit.c index 07d8d7a53702..dbf6edaeefaf 100644 --- a/arch/s390/kvm/arm64/handle_exit.c +++ b/arch/s390/kvm/arm64/handle_exit.c @@ -5,6 +5,8 @@ #include #include +#include "handle_exit.h" + typedef int (*exit_handle_fn)(struct kvm_vcpu *); #define PSCI_0_2_FN_SYSTEM_OFF 0x84000008 @@ -84,6 +86,36 @@ static int handle_trap_exceptions(struct kvm_vcpu *vcpu) return exit_handler(vcpu); } +/* + * Return > 0 to return to guest, < 0 on error, 0 (and set exit_reason) on + * proper exit to userspace. + */ +int handle_exit(struct kvm_vcpu *vcpu) +{ + u8 icptr = vcpu->arch.sae_block.icptr; + int ret = 1; + + switch (icptr) { + case SAE_ICPTR_SPURIOUS: + break; + case SAE_ICPTR_VALIDITY: + WARN_ONCE(true, "SAE: validity intercept. vir: 0x%04x", + vcpu->arch.sae_block.vir); + vcpu->run->exit_reason = KVM_EXIT_FAIL_ENTRY; + ret = 0; + break; + case SAE_ICPTR_GUEST_ADDRESS_SIZE: + case SAE_ICPTR_HOST_ACCESS_EXCEPTION: + case SAE_ICPTR_SYNCHRONOUS_EXCEPTION: + ret = handle_trap_exceptions(vcpu); + break; + default: + vcpu->run->exit_reason = KVM_EXIT_FAIL_ENTRY; + ret = 0; + } + return ret; +} + /* manually copied from arch/arm64/kernel/traps.c */ static const char * const esr_class_str[] = { [0 ... ESR_ELx_EC_MAX] = "UNRECOGNIZED EC", diff --git a/arch/s390/kvm/arm64/handle_exit.h b/arch/s390/kvm/arm64/handle_exit.h new file mode 100644 index 000000000000..65ae58721537 --- /dev/null +++ b/arch/s390/kvm/arm64/handle_exit.h @@ -0,0 +1,9 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +#ifndef KVM_ARM64_HANDLE_EXIT_H +#define KVM_ARM64_HANDLE_EXIT_H + +#include + +int handle_exit(struct kvm_vcpu *vcpu); + +#endif /* KVM_ARM64_HANDLE_EXIT_H */ diff --git a/arch/s390/kvm/arm64/reset.c b/arch/s390/kvm/arm64/reset.c new file mode 100644 index 000000000000..e4cdce1dbf9d --- /dev/null +++ b/arch/s390/kvm/arm64/reset.c @@ -0,0 +1,71 @@ +// SPDX-License-Identifier: GPL-2.0 + +#include +#include + +#include +#include + +bool kvm_arm_vcpu_is_finalized(struct kvm_vcpu *vcpu) +{ + return true; +} + +static inline void vcpu_reset_hcr(struct kvm_vcpu *vcpu) +{ + vcpu->arch.hcr_el2 = HCR_EL2_E2H | HCR_EL2_RW | HCR_EL2_AMO | + HCR_EL2_IMO | HCR_EL2_FMO | HCR_EL2_PTW; + /* traps */ + vcpu->arch.hcr_el2 |= HCR_EL2_TSC | HCR_EL2_TID1 | HCR_EL2_TID2 | + HCR_EL2_TID3 | HCR_EL2_TID4 | HCR_EL2_TID5 | + HCR_EL2_TIDCP | HCR_EL2_TLOR; +} + +void kvm_reset_vcpu(struct kvm_vcpu *vcpu) +{ + struct vcpu_reset_state reset_state; + bool loaded; + + scoped_guard(spinlock, &vcpu->arch.mp_state_lock) { + reset_state = vcpu->arch.reset_state; + vcpu->arch.reset_state.reset = false; + } + + /* + * Disable preemption around the vcpu reset as we might otherwise race with + * preempt notifiers which call stiasrm/lasrm from put/load + */ + preempt_disable(); + + /* The reset must run with an unloaded save area */ + loaded = vcpu_is_loaded(vcpu); + if (loaded) + vcpu_put(vcpu); + + kvm_reset_vcpu_core(vcpu); + + /* Reset special registers */ + vcpu_reset_hcr(vcpu); + + if (reset_state.reset) { + *vcpu_pc(vcpu) = reset_state.pc; + vcpu_clear_flag(vcpu, PENDING_EXCEPTION); + vcpu_clear_flag(vcpu, EXCEPT_MASK); + vcpu_clear_flag(vcpu, INCREMENT_PC); + vcpu_set_reg(vcpu, 0, reset_state.r0); + } + + /* Load new vx-regs into HW if they are currently loaded */ + if (current->thread.kfpu_flags) + load_vx_regs(vcpu->arch.ctxt.vregs); + + if (loaded) + vcpu_load(vcpu); + + preempt_enable(); +} + +int kvm_arm_vcpu_finalize(struct kvm_vcpu *vcpu, int feature) +{ + return -EINVAL; +} diff --git a/arch/s390/kvm/arm64/trace.h b/arch/s390/kvm/arm64/trace.h index 3a5dcd039c13..0380e45fba28 100644 --- a/arch/s390/kvm/arm64/trace.h +++ b/arch/s390/kvm/arm64/trace.h @@ -5,7 +5,41 @@ #include #undef TRACE_SYSTEM -#define TRACE_SYSTEM kvm +#define TRACE_SYSTEM kvm_arm64 +#undef TRACE_SYSTEM_VAR +#define TRACE_SYSTEM_VAR kvm_arm64 + +TRACE_EVENT(kvm_entry, + TP_PROTO(unsigned long vcpu_pc), + TP_ARGS(vcpu_pc), + + TP_STRUCT__entry( + __field(unsigned long, vcpu_pc) + ), + + TP_fast_assign(__entry->vcpu_pc = vcpu_pc;), + + TP_printk("PC: 0x%016lx", __entry->vcpu_pc) +); + +TRACE_EVENT(kvm_exit, + TP_PROTO(unsigned int esr_ec, unsigned long vcpu_pc), + TP_ARGS(esr_ec, vcpu_pc), + + TP_STRUCT__entry( + __field(unsigned int, esr_ec ) + __field(unsigned long, vcpu_pc ) + ), + + TP_fast_assign( + __entry->esr_ec = esr_ec; + __entry->vcpu_pc = vcpu_pc; + ), + + TP_printk("HSR_EC: 0x%04x, PC: 0x%016lx", + __entry->esr_ec, + __entry->vcpu_pc) +); #endif /* KVM_ARM64_TRACE_KVM_H */ From 4265225bbeb46e1d63b5cc55205ab6fd2a19f6c6 Mon Sep 17 00:00:00 2001 From: Steffen Eiden Date: Thu, 2 Apr 2026 06:00:00 +0200 Subject: [PATCH 21/24] KVM: s390: arm64: Implement basic page fault handler Add host functionality to page in guest memory. If the guest does something unexpected or illegal exit to userspace abort guest execution. This behaviour will be changed to guest error injects once all sysregs are accessible for the host. Add pre-fault handler. Co-developed-by: Nina Schoetterl-Glausch Signed-off-by: Nina Schoetterl-Glausch Signed-off-by: Steffen Eiden [borntraeger@linux.ibm.com: fixup for recent arm changes] Signed-off-by: Christian Borntraeger --- arch/s390/kvm/arm64/handle_exit.c | 2 + arch/s390/kvm/arm64/mmio.c | 15 +++ arch/s390/kvm/arm64/mmu.c | 210 ++++++++++++++++++++++++++++++ arch/s390/kvm/arm64/trace.h | 49 +++++++ 4 files changed, 276 insertions(+) create mode 100644 arch/s390/kvm/arm64/mmio.c create mode 100644 arch/s390/kvm/arm64/mmu.c diff --git a/arch/s390/kvm/arm64/handle_exit.c b/arch/s390/kvm/arm64/handle_exit.c index dbf6edaeefaf..e0c7773c2165 100644 --- a/arch/s390/kvm/arm64/handle_exit.c +++ b/arch/s390/kvm/arm64/handle_exit.c @@ -62,6 +62,8 @@ static int kvm_handle_unknown_ec(struct kvm_vcpu *vcpu) static exit_handle_fn arm_exit_handlers[] = { [0 ... ESR_ELx_EC_MAX] = kvm_handle_unknown_ec, [ESR_ELx_EC_HVC64] = handle_hvc, + [ESR_ELx_EC_IABT_LOW] = kvm_handle_guest_abort, + [ESR_ELx_EC_DABT_LOW] = kvm_handle_guest_abort, }; static exit_handle_fn kvm_get_exit_handler(struct kvm_vcpu *vcpu) diff --git a/arch/s390/kvm/arm64/mmio.c b/arch/s390/kvm/arm64/mmio.c new file mode 100644 index 000000000000..908a6b43ddf7 --- /dev/null +++ b/arch/s390/kvm/arm64/mmio.c @@ -0,0 +1,15 @@ +// SPDX-License-Identifier: GPL-2.0 + +#include +#include + +#include + +#include +#include + +#include "trace.h" + +#define __INCL_GEN_ARM_FILE +#include "generated/mmio.inc" +#undef __INCL_GEN_ARM_FILE diff --git a/arch/s390/kvm/arm64/mmu.c b/arch/s390/kvm/arm64/mmu.c new file mode 100644 index 000000000000..31e0d036896f --- /dev/null +++ b/arch/s390/kvm/arm64/mmu.c @@ -0,0 +1,210 @@ +// SPDX-License-Identifier: GPL-2.0 +#include + +#include + +#include + +#include "trace.h" + +static inline bool kvm_s390_cur_gmap_fault_is_write(struct kvm_vcpu *vcpu) +{ + return (vcpu->arch.sae_block.hai.pic & PGM_INT_CODE_MASK) == PGM_PROTECTION || + vcpu->arch.sae_block.hai.teid.fsi == TEID_FSI_STORE; +} + +/* + * user_mem_abort() - handle a dat fault for the gmap of a vcpu + * + * Return: 0 on success, < 0 in case of error. + * Context: The mm lock must not be held before calling. May sleep. + */ +static int user_mem_abort(struct kvm_vcpu *vcpu, gpa_t fault_ipa, + struct kvm_memory_slot *slot, hva_t hva) +{ + struct guest_fault f = { }; + int ret; + + if (kvm_s390_cur_gmap_fault_is_write(vcpu)) + f.write_attempt = FOLL_WRITE; + f.gfn = gpa_to_gfn(fault_ipa); + + ret = kvm_s390_faultin_gfn(vcpu, NULL, &f); + if (ret <= 0) + return ret; + if (ret == PGM_ADDRESSING) + /* + * Without the relevant sysregs we cannot do anything for now. + * Go back to userspace with an error. TODO sysreg handling + */ + return -ENOEXEC; + KVM_BUG_ON(ret, vcpu->kvm); + return -EINVAL; +} + +static int kvm_handle_pic(struct kvm_vcpu *vcpu, bool *translation) +{ + switch (kvm_vcpu_fault_pic(vcpu)) { + /* expected cases: */ + case PGM_ASCE_TYPE: + case PGM_REGION_FIRST_TRANS: + case PGM_REGION_SECOND_TRANS: + case PGM_REGION_THIRD_TRANS: + case PGM_SEGMENT_TRANSLATION: + case PGM_PAGE_TRANSLATION: + *translation = true; + break; + case PGM_PROTECTION: + break; + /* unexpected cases: */ + case 0: + KVM_BUG(1, vcpu->kvm, "On MMU fault path but no fault occurred"); + return -EFAULT; + default: + KVM_BUG(1, vcpu->kvm, "Unexpected program interrupt 0x%x, TEID 0x%016lx", + vcpu->arch.sae_block.hai.pic, vcpu->arch.sae_block.hai.teid.val); + send_sig(SIGSEGV, current, 0); + return -EFAULT; + } + + return 0; +} + +int kvm_handle_guest_abort(struct kvm_vcpu *vcpu) +{ + struct kvm_memory_slot *memslot; + bool translation = false; + phys_addr_t fault_ipa; + unsigned long hva; + unsigned long esr; + bool write_fault; + bool guest_size_err; + bool writable; + bool is_iabt; + int ret; + gfn_t gfn; + + esr = kvm_vcpu_get_esr(vcpu); + fault_ipa = kvm_vcpu_get_fault_ipa(vcpu); + is_iabt = kvm_vcpu_trap_is_iabt(vcpu); + guest_size_err = vcpu->arch.sae_block.icptr == SAE_ICPTR_GUEST_ADDRESS_SIZE; + + if (guest_size_err) { + translation = true; + } else { + ret = kvm_handle_pic(vcpu, &translation); + if (ret) + return ret; + } + + if (translation) { + /* + * For both cases: + * Without the relevant sysregs we cannot do anything for now. + * Go back to userspace with an error. TODO sysreg handling + */ + if (fault_ipa >= BIT_ULL(get_kvm_ipa_limit())) + return -ENOEXEC; + + if (fault_ipa >= vcpu->kvm->arch.guest_phys_size) + return -ENOEXEC; + } + + trace_kvm_guest_fault(*vcpu_pc(vcpu), kvm_vcpu_get_esr(vcpu), + kvm_vcpu_get_hfar(vcpu), fault_ipa); + + kvm_vcpu_srcu_read_lock(vcpu); + + gfn = fault_ipa >> PAGE_SHIFT; + + memslot = gfn_to_memslot(vcpu->kvm, gfn); + hva = gfn_to_hva_memslot_prot(memslot, gfn, &writable); + write_fault = kvm_s390_cur_gmap_fault_is_write(vcpu); + if (kvm_is_error_hva(hva) || (write_fault && !writable)) { + ret = -ENOEXEC; + /* + * The guest has put either its instructions or its page-tables + * somewhere it shouldn't have. Userspace won't be able to do + * anything about this (there's no syndrome for a start). + * + * Without the relevant sysregs we cannot do anything for now. + * Go back to userspace with an error. TODO sysreg handling + */ + if (is_iabt) + goto out_unlock; + + if (kvm_vcpu_abt_iss1tw(vcpu)) { + /* + * Without the relevant sysregs we cannot do anything for now. + * Go back to userspace with an error. TODO sysreg handling + */ + goto out_unlock; + } + + /* + * Check for a cache maintenance operation. Assume the guest is + * cautious and skip instruction + */ + if (kvm_is_error_hva(hva) && esr_dabt_is_cm(esr)) { + kvm_incr_pc(vcpu); + ret = 1; + goto out_unlock; + } + + /* + * The IPA is reported as [MAX:12], so we need to + * complement it with the bottom 12 bits from the + * faulting VA. This is always 12 bits, irrespective + * of the page size. + */ + fault_ipa |= kvm_vcpu_get_hfar(vcpu) & ((1 << 12) - 1); + ret = io_mem_abort(vcpu, fault_ipa); + goto out_unlock; + } + + /* Userspace should not be able to register out-of-bounds IPAs */ + VM_BUG_ON(fault_ipa >= vcpu->kvm->arch.guest_phys_size); + /* + * Proper guest size faults have been injected. + * In theory it's fine to have device memory higher than MSL, + * even if not currently possible, but that would have been handled above. + * So if we get here with a guest size intercept, we have a bug somewhere. + */ + VM_BUG_ON(guest_size_err); + + ret = user_mem_abort(vcpu, fault_ipa, memslot, hva); + if (!ret) + ret = 1; +out_unlock: + kvm_vcpu_srcu_read_unlock(vcpu); + return ret; +} + +long kvm_arch_vcpu_pre_fault_memory(struct kvm_vcpu *vcpu, struct kvm_pre_fault_memory *range) +{ + struct guest_fault f = { .gfn = gpa_to_gfn(range->gpa), }; + gpa_t end; + int rc; + + rc = kvm_s390_faultin_gfn(vcpu, NULL, &f); + if (rc == PGM_ADDRESSING) + return -ENOENT; + if (rc > 0) + return -EIO; + if (rc < 0) + return rc; + + if (f.ptep) + return PAGE_SIZE; + + end = ALIGN(range->gpa + PAGE_SIZE, f.crste_region3 ? _REGION3_SIZE : HPAGE_SIZE); + return min(range->size, end - range->gpa); +} + +int kvm_arch_pre_fault_allowed(struct kvm_vcpu *vcpu) +{ + if (!kvm_vcpu_initialized(vcpu)) + return -ENOEXEC; + + return 0; +} diff --git a/arch/s390/kvm/arm64/trace.h b/arch/s390/kvm/arm64/trace.h index 0380e45fba28..ee232c4f5a6d 100644 --- a/arch/s390/kvm/arm64/trace.h +++ b/arch/s390/kvm/arm64/trace.h @@ -41,6 +41,55 @@ TRACE_EVENT(kvm_exit, __entry->vcpu_pc) ); +TRACE_EVENT(kvm_mmio_nisv, + TP_PROTO(unsigned long vcpu_pc, unsigned long esr, + unsigned long far, unsigned long ipa), + TP_ARGS(vcpu_pc, esr, far, ipa), + + TP_STRUCT__entry( + __field( unsigned long, vcpu_pc ) + __field( unsigned long, esr ) + __field( unsigned long, far ) + __field( unsigned long, ipa ) + ), + + TP_fast_assign( + __entry->vcpu_pc = vcpu_pc; + __entry->esr = esr; + __entry->far = far; + __entry->ipa = ipa; + ), + + TP_printk("ipa %#016lx, esr %#016lx, far %#016lx, pc %#016lx", + __entry->ipa, __entry->esr, + __entry->far, __entry->vcpu_pc) +); + +TRACE_EVENT(kvm_guest_fault, + TP_PROTO(unsigned long vcpu_pc, unsigned long hsr, + unsigned long hxfar, + unsigned long long ipa), + TP_ARGS(vcpu_pc, hsr, hxfar, ipa), + + TP_STRUCT__entry( + __field( unsigned long, vcpu_pc ) + __field( unsigned long, hsr ) + __field( unsigned long, hxfar ) + __field( unsigned long long, ipa ) + ), + + TP_fast_assign( + __entry->vcpu_pc = vcpu_pc; + __entry->hsr = hsr; + __entry->hxfar = hxfar; + __entry->ipa = ipa; + ), + + TP_printk("ipa %#llx, hsr %#08lx, hxfar %#08lx, pc %#016lx", + __entry->ipa, __entry->hsr, + __entry->hxfar, __entry->vcpu_pc) +); + #endif /* KVM_ARM64_TRACE_KVM_H */ #undef TRACE_INCLUDE_PATH From 91b87e6eea4d09a4d80f6a66515e8bc2821303ad Mon Sep 17 00:00:00 2001 From: Steffen Eiden Date: Thu, 2 Apr 2026 06:00:00 +0200 Subject: [PATCH 22/24] KVM: s390: arm64: Integrate arm on s390 Add all Kbuild/Makefile configurations to build a second KVM module on s390 implementing the arm64 KVM API. The new module is named kvm_arm64. Symbol conflicts with kvm_s390 are prevented by localising all internal symbols in kvm_arm64 when compiled as built-in (=y): kvm-unnamespaced.o holds all constituent objects. objcopy --localize-symbols then produces kvm-namespaced.o which is what kvm-arm64.o resolves to. For =m the normal module symbol scoping applies and no localisation step is needed. Because the %-arm64.o pattern targets do not match any kvm-arm64-y entry, kbuild would derive a wrong KBUILD_MODNAME (e.g. "kvm_main_arm64") and a wrong KBUILD_MODFILE for the virt/kvm and gmap copies. Co-developed-by: Andreas Grapentin Signed-off-by: Andreas Grapentin Co-developed-by: Nina Schoetterl-Glausch Signed-off-by: Nina Schoetterl-Glausch Co-developed-by: Gautam Gala Signed-off-by: Gautam Gala Signed-off-by: Steffen Eiden Signed-off-by: Christian Borntraeger --- arch/s390/kvm/Kconfig | 21 ++++--- arch/s390/kvm/Makefile | 1 + arch/s390/kvm/arm64/Makefile | 110 +++++++++++++++++++++++++++++++++++ 3 files changed, 125 insertions(+), 7 deletions(-) create mode 100644 arch/s390/kvm/arm64/Makefile diff --git a/arch/s390/kvm/Kconfig b/arch/s390/kvm/Kconfig index 9045ae20fd89..e7b525d04c6d 100644 --- a/arch/s390/kvm/Kconfig +++ b/arch/s390/kvm/Kconfig @@ -30,15 +30,22 @@ config KVM select KVM_MMU_LOCKLESS_AGING select KVM_GENERIC_PRE_FAULT_MEMORY help - Support hosting paravirtualized guest machines using the SIE - virtualization capability on the mainframe. This should work - on any 64bit machine. + Enable support for hosting virtualized, hardware-accelerated + virtual machines via two modules: kvm and kvm_arm64. - This module provides access to the hardware capabilities through - a character device node named /dev/kvm. + The kvm module provides access to host hardware-accelerated s390 + guests using the Start Interpretive Execution (SIE) instruction via a + character device node named /dev/kvm. This requires a 64-bit machine + (hardware model). - To compile this as a module, choose M here: the module - will be called kvm. + The kvm_arm64 module provides access to host hardware-accelerated + arm64 guests using the Start ARM Execution (SAE) instruction via a + character device node named /dev/kvm_arm64. This requires hardware + models that support the Arm execution facility (AEF). + + To compile this driver as a module, choose M here. The module will be + called kvm for the SIE acceleration and kvm_arm64 for the SAE + acceleration. If unsure, say N. diff --git a/arch/s390/kvm/Makefile b/arch/s390/kvm/Makefile index a4e3875b5bdd..380db443a0e9 100644 --- a/arch/s390/kvm/Makefile +++ b/arch/s390/kvm/Makefile @@ -4,3 +4,4 @@ # Copyright IBM Corp. 2008 obj-$(CONFIG_KVM) += s390/ +obj-$(CONFIG_KVM) += arm64/ diff --git a/arch/s390/kvm/arm64/Makefile b/arch/s390/kvm/arm64/Makefile new file mode 100644 index 000000000000..3f0fe5c9f0a1 --- /dev/null +++ b/arch/s390/kvm/arm64/Makefile @@ -0,0 +1,110 @@ +# SPDX-License-Identifier: GPL-2.0 + +KVM := ../../../../virt/kvm +KVM_DEV_NAME = kvm_arm64 +KVM_DEV_MINOR = MISC_DYNAMIC_MINOR +include $(srctree)/virt/kvm/Makefile.kvm +include $(srctree)/arch/$(SRCARCH)/kvm/gmap/Makefile +include $(src)/Makefile.gen + +ccflags-y += -I $(src) -I $(objtree)/$(obj) -I$(srctree)/arch/$(SRCARCH)/kvm/gmap -DKVM_S390_ARM64 + +kvm-arm64-local := \ + arm.o \ + guest.o \ + handle_exit.o \ + inject_fault.o \ + mmio.o \ + mmu.o \ + reset.o + +kvm-arm64-obj := $(kvm-arm64-local) +kvm-arm64-obj += $(patsubst %.o,%-arm64.o,$(kvm-y)) +kvm-arm64-obj += $(patsubst %.o,%-arm64.o,$(gmap-y)) + +targets += $(kvm-arm64-obj) + +$(obj)/%-arm64.o: $(src)/%.c FORCE + @mkdir -p $(dir $@) + $(call if_changed_rule,cc_o_c) + $(call cmd,force_checksrc) + +# %-arm64.o stems don't match any kvm-arm64-y entry so Kbuild derives wrong +# KBUILD_MODNAME/KBUILD_MODFILE. Force include a generated header to override. +KVM_ARM64_MODNAME_H := $(obj)/generated/kvm_arm64_modname.h +ccflags-y += -include $(KVM_ARM64_MODNAME_H) + +$(addprefix $(obj)/,$(kvm-arm64-obj)): $(KVM_ARM64_MODNAME_H) +$(foreach sfx, i s ll lst, \ + $(eval $(patsubst %.o,%.$(sfx),$(addprefix $(obj)/,$(kvm-arm64-local))): $(KVM_ARM64_MODNAME_H))) + +targets += generated/kvm_arm64_modname.h + +define filechk_kvm_arm64_modname + echo '#ifndef __KVM_ARM64_MODNAME_H'; \ + echo '#define __KVM_ARM64_MODNAME_H'; \ + echo '#undef KBUILD_MODNAME'; \ + echo '#define KBUILD_MODNAME "$(KVM_DEV_NAME)"'; \ + echo '#undef __KBUILD_MODNAME'; \ + echo '#define __KBUILD_MODNAME $(KVM_DEV_NAME)'; \ + echo '#undef KBUILD_MODFILE'; \ + echo '#define KBUILD_MODFILE "$(obj)/kvm-arm64"'; \ + echo '#endif /* __KVM_ARM64_MODNAME_H */' +endef + +$(KVM_ARM64_MODNAME_H): FORCE + $(call filechk,kvm_arm64_modname) + +# Members are not in real-obj-y. Set part-of-builtin to enable instrumentation. +$(addprefix $(obj)/,$(kvm-arm64-obj)): private part-of-builtin := y + +ifeq ($(CONFIG_KVM),m) + +kvm-arm64-y = $(kvm-arm64-obj) + +else ifeq ($(CONFIG_KVM),y) + +prereq-o-cmd = $(foreach o, $(filter %.o, $^), $(dir $(o)).$(notdir $(o)).cmd) +cmd_gen_symversions_o = \ + grep --no-filename "^\#SYMVER" $(prereq-o-cmd) >> $(dot-target).cmd || true + +cmd_ld_o_o = $(cmd_ld) +define rule_ld_o_o + $(call cmd_and_savecmd,ld) + $(call cmd,gen_symversions_o) +endef + +LDFLAGS_kvm-unnamespaced.o := -r +$(obj)/kvm-unnamespaced.o: $(addprefix $(obj)/,$(kvm-arm64-obj)) FORCE + $(call if_changed_rule,ld_o_o) + +quiet_cmd_nm_filter = NMFLTR $@ + cmd_nm_filter = $(NM) --defined-only --format=just-symbols $< | sort -u > $@ + +$(obj)/kvm_symbol_list: $(obj)/kvm-unnamespaced.o FORCE + $(call if_changed,nm_filter) + +# cmd_oc_o_o carries -DKBUILD_MODFILE so generate_builtin_ranges.awk +# can attribute kvm-namespaced.o to kvm_arm64 via its .cmd file. +cmd_oc_o_o = $(cmd_objcopy) -DKBUILD_MODFILE='"$(obj)/kvm-arm64"' +define rule_oc_o_o + $(call cmd,objcopy) + @printf '%s\n' 'savedcmd_$@ := $(call make-cmd,oc_o_o)' > $(dot-target).cmd + $(call cmd,gen_objtooldep) + $(call cmd,gen_symversions_o) +endef + +OBJCOPYFLAGS_kvm-namespaced.o := -O $(LD_BFD) --localize-symbols=$(obj)/kvm_symbol_list +$(obj)/kvm-namespaced.o: $(obj)/kvm-unnamespaced.o $(obj)/kvm_symbol_list FORCE + $(call if_changed_rule,oc_o_o) + +kvm-arm64-y = kvm-namespaced.o + +targets += kvm-unnamespaced.o kvm_symbol_list kvm-namespaced.o + +endif + +clean-files += kvm-unnamespaced.o kvm_symbol_list kvm-namespaced.o + +obj-$(CONFIG_KVM) += kvm-arm64.o + From ffae233b4d536c7eb6bbad21bc8afa41f6be2929 Mon Sep 17 00:00:00 2001 From: Steffen Eiden Date: Mon, 12 Oct 2026 17:57:25 +0200 Subject: [PATCH 23/24] MAINTAINERS: Add entry for ARM on s390 KVM Add a dedicated MAINTAINERS section for KVM ARM64 virtualization support on s390. Adding only reviewers. arm-on-s390 is primarily maintained by the KVM/s390 maintainers through inheritance. Signed-off-by: Steffen Eiden Acked-by: Andreas Grapentin Acked-by: Marc Zyngier Acked-by: Janosch Frank Signed-off-by: Christian Borntraeger --- MAINTAINERS | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/MAINTAINERS b/MAINTAINERS index 1d45ccf863c1..d9d212d06ef2 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -14385,6 +14385,22 @@ F: tools/testing/selftests/drivers/s390x/uvdevice/ F: tools/testing/selftests/kvm/*/s390/ F: tools/testing/selftests/kvm/s390/ +KERNEL VIRTUAL MACHINE for arm-on-s390 (KVM/s390) +R: Steffen Eiden +R: Andreas Grapentin +R: Marc Zyngier +L: kvm@vger.kernel.org +L: linux-s390@vger.kernel.org +S: Supported +T: git git://git.kernel.org/pub/scm/linux/kernel/git/kvms390/linux.git +F: arch/s390/include/arm64/ +F: arch/s390/include/asm/aef.h +F: arch/s390/include/asm/kvm_host_arm64* +F: arch/s390/include/asm/sae* +F: arch/s390/kernel/aef.c +F: arch/s390/kvm/arm64/ +F: arch/s390/tools/*arm64* + KERNEL VIRTUAL MACHINE FOR X86 (KVM/x86) M: Sean Christopherson M: Paolo Bonzini From e93fb70b748982fda5e6d97721df35194cacfc4a Mon Sep 17 00:00:00 2001 From: Steffen Eiden Date: Thu, 24 Sep 2026 12:57:18 +0200 Subject: [PATCH 24/24] arm64: Use proper include variant Use <> for including the sysreg header as it is not discoverable from the current directory. Acked-by: Marc Zyngier Signed-off-by: Steffen Eiden Acked-by: Mark Rutland Signed-off-by: Catalin Marinas Signed-off-by: Christian Borntraeger --- arch/arm64/include/asm/sysreg.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/arch/arm64/include/asm/sysreg.h b/arch/arm64/include/asm/sysreg.h index b4f5dedff5ae..aa65603455a2 100644 --- a/arch/arm64/include/asm/sysreg.h +++ b/arch/arm64/include/asm/sysreg.h @@ -179,7 +179,7 @@ * come from here. The header relies on the definition of sys_reg() * earlier in this file. */ -#include "asm/sysreg-defs.h" +#include /* * System registers, organised loosely by encoding but grouped together