| // SPDX-License-Identifier: GPL-2.0 |
| /* |
| * Copyright (C) 2021 Western Digital Corporation or its affiliates. |
| * Copyright (C) 2022 Ventana Micro Systems Inc. |
| * |
| * Authors: |
| * Anup Patel <apatel@ventanamicro.com> |
| */ |
| |
| #include <linux/atomic.h> |
| #include <linux/bitmap.h> |
| #include <linux/irqchip/riscv-imsic.h> |
| #include <linux/kvm_host.h> |
| #include <linux/math.h> |
| #include <linux/spinlock.h> |
| #include <linux/swab.h> |
| #include <kvm/iodev.h> |
| #include <asm/csr.h> |
| |
| #define IMSIC_MAX_EIX (IMSIC_MAX_ID / BITS_PER_TYPE(u64)) |
| |
| struct imsic_mrif_eix { |
| unsigned long eip[BITS_PER_TYPE(u64) / BITS_PER_LONG]; |
| unsigned long eie[BITS_PER_TYPE(u64) / BITS_PER_LONG]; |
| }; |
| |
| struct imsic_mrif { |
| struct imsic_mrif_eix eix[IMSIC_MAX_EIX]; |
| unsigned long eithreshold; |
| unsigned long eidelivery; |
| }; |
| |
| struct imsic { |
| struct kvm_io_device iodev; |
| |
| u32 nr_msis; |
| u32 nr_eix; |
| u32 nr_hw_eix; |
| |
| /* |
| * At any point in time, the register state is in |
| * one of the following places: |
| * |
| * 1) Hardware: IMSIC VS-file (vsfile_cpu >= 0) |
| * 2) Software: IMSIC SW-file (vsfile_cpu < 0) |
| */ |
| |
| /* IMSIC VS-file */ |
| rwlock_t vsfile_lock; |
| int vsfile_cpu; |
| int vsfile_hgei; |
| void __iomem *vsfile_va; |
| phys_addr_t vsfile_pa; |
| |
| /* IMSIC SW-file */ |
| struct imsic_mrif *swfile; |
| phys_addr_t swfile_pa; |
| raw_spinlock_t swfile_extirq_lock; |
| }; |
| |
| #define imsic_vs_csr_read(__c) \ |
| ({ \ |
| unsigned long __r; \ |
| csr_write(CSR_VSISELECT, __c); \ |
| __r = csr_read(CSR_VSIREG); \ |
| __r; \ |
| }) |
| |
| #define imsic_read_switchcase(__ireg) \ |
| case __ireg: \ |
| return imsic_vs_csr_read(__ireg); |
| #define imsic_read_switchcase_2(__ireg) \ |
| imsic_read_switchcase(__ireg + 0) \ |
| imsic_read_switchcase(__ireg + 1) |
| #define imsic_read_switchcase_4(__ireg) \ |
| imsic_read_switchcase_2(__ireg + 0) \ |
| imsic_read_switchcase_2(__ireg + 2) |
| #define imsic_read_switchcase_8(__ireg) \ |
| imsic_read_switchcase_4(__ireg + 0) \ |
| imsic_read_switchcase_4(__ireg + 4) |
| #define imsic_read_switchcase_16(__ireg) \ |
| imsic_read_switchcase_8(__ireg + 0) \ |
| imsic_read_switchcase_8(__ireg + 8) |
| #define imsic_read_switchcase_32(__ireg) \ |
| imsic_read_switchcase_16(__ireg + 0) \ |
| imsic_read_switchcase_16(__ireg + 16) |
| #define imsic_read_switchcase_64(__ireg) \ |
| imsic_read_switchcase_32(__ireg + 0) \ |
| imsic_read_switchcase_32(__ireg + 32) |
| |
| static unsigned long imsic_eix_read(int ireg) |
| { |
| switch (ireg) { |
| imsic_read_switchcase_64(IMSIC_EIP0) |
| imsic_read_switchcase_64(IMSIC_EIE0) |
| } |
| |
| return 0; |
| } |
| |
| #define imsic_vs_csr_swap(__c, __v) \ |
| ({ \ |
| unsigned long __r; \ |
| csr_write(CSR_VSISELECT, __c); \ |
| __r = csr_swap(CSR_VSIREG, __v); \ |
| __r; \ |
| }) |
| |
| #define imsic_swap_switchcase(__ireg, __v) \ |
| case __ireg: \ |
| return imsic_vs_csr_swap(__ireg, __v); |
| #define imsic_swap_switchcase_2(__ireg, __v) \ |
| imsic_swap_switchcase(__ireg + 0, __v) \ |
| imsic_swap_switchcase(__ireg + 1, __v) |
| #define imsic_swap_switchcase_4(__ireg, __v) \ |
| imsic_swap_switchcase_2(__ireg + 0, __v) \ |
| imsic_swap_switchcase_2(__ireg + 2, __v) |
| #define imsic_swap_switchcase_8(__ireg, __v) \ |
| imsic_swap_switchcase_4(__ireg + 0, __v) \ |
| imsic_swap_switchcase_4(__ireg + 4, __v) |
| #define imsic_swap_switchcase_16(__ireg, __v) \ |
| imsic_swap_switchcase_8(__ireg + 0, __v) \ |
| imsic_swap_switchcase_8(__ireg + 8, __v) |
| #define imsic_swap_switchcase_32(__ireg, __v) \ |
| imsic_swap_switchcase_16(__ireg + 0, __v) \ |
| imsic_swap_switchcase_16(__ireg + 16, __v) |
| #define imsic_swap_switchcase_64(__ireg, __v) \ |
| imsic_swap_switchcase_32(__ireg + 0, __v) \ |
| imsic_swap_switchcase_32(__ireg + 32, __v) |
| |
| static unsigned long imsic_eix_swap(int ireg, unsigned long val) |
| { |
| switch (ireg) { |
| imsic_swap_switchcase_64(IMSIC_EIP0, val) |
| imsic_swap_switchcase_64(IMSIC_EIE0, val) |
| } |
| |
| return 0; |
| } |
| |
| #define imsic_vs_csr_write(__c, __v) \ |
| do { \ |
| csr_write(CSR_VSISELECT, __c); \ |
| csr_write(CSR_VSIREG, __v); \ |
| } while (0) |
| |
| #define imsic_write_switchcase(__ireg, __v) \ |
| case __ireg: \ |
| imsic_vs_csr_write(__ireg, __v); \ |
| break; |
| #define imsic_write_switchcase_2(__ireg, __v) \ |
| imsic_write_switchcase(__ireg + 0, __v) \ |
| imsic_write_switchcase(__ireg + 1, __v) |
| #define imsic_write_switchcase_4(__ireg, __v) \ |
| imsic_write_switchcase_2(__ireg + 0, __v) \ |
| imsic_write_switchcase_2(__ireg + 2, __v) |
| #define imsic_write_switchcase_8(__ireg, __v) \ |
| imsic_write_switchcase_4(__ireg + 0, __v) \ |
| imsic_write_switchcase_4(__ireg + 4, __v) |
| #define imsic_write_switchcase_16(__ireg, __v) \ |
| imsic_write_switchcase_8(__ireg + 0, __v) \ |
| imsic_write_switchcase_8(__ireg + 8, __v) |
| #define imsic_write_switchcase_32(__ireg, __v) \ |
| imsic_write_switchcase_16(__ireg + 0, __v) \ |
| imsic_write_switchcase_16(__ireg + 16, __v) |
| #define imsic_write_switchcase_64(__ireg, __v) \ |
| imsic_write_switchcase_32(__ireg + 0, __v) \ |
| imsic_write_switchcase_32(__ireg + 32, __v) |
| |
| static void imsic_eix_write(int ireg, unsigned long val) |
| { |
| switch (ireg) { |
| imsic_write_switchcase_64(IMSIC_EIP0, val) |
| imsic_write_switchcase_64(IMSIC_EIE0, val) |
| } |
| } |
| |
| #define imsic_vs_csr_set(__c, __v) \ |
| do { \ |
| csr_write(CSR_VSISELECT, __c); \ |
| csr_set(CSR_VSIREG, __v); \ |
| } while (0) |
| |
| #define imsic_set_switchcase(__ireg, __v) \ |
| case __ireg: \ |
| imsic_vs_csr_set(__ireg, __v); \ |
| break; |
| #define imsic_set_switchcase_2(__ireg, __v) \ |
| imsic_set_switchcase(__ireg + 0, __v) \ |
| imsic_set_switchcase(__ireg + 1, __v) |
| #define imsic_set_switchcase_4(__ireg, __v) \ |
| imsic_set_switchcase_2(__ireg + 0, __v) \ |
| imsic_set_switchcase_2(__ireg + 2, __v) |
| #define imsic_set_switchcase_8(__ireg, __v) \ |
| imsic_set_switchcase_4(__ireg + 0, __v) \ |
| imsic_set_switchcase_4(__ireg + 4, __v) |
| #define imsic_set_switchcase_16(__ireg, __v) \ |
| imsic_set_switchcase_8(__ireg + 0, __v) \ |
| imsic_set_switchcase_8(__ireg + 8, __v) |
| #define imsic_set_switchcase_32(__ireg, __v) \ |
| imsic_set_switchcase_16(__ireg + 0, __v) \ |
| imsic_set_switchcase_16(__ireg + 16, __v) |
| #define imsic_set_switchcase_64(__ireg, __v) \ |
| imsic_set_switchcase_32(__ireg + 0, __v) \ |
| imsic_set_switchcase_32(__ireg + 32, __v) |
| |
| static void imsic_eix_set(int ireg, unsigned long val) |
| { |
| switch (ireg) { |
| imsic_set_switchcase_64(IMSIC_EIP0, val) |
| imsic_set_switchcase_64(IMSIC_EIE0, val) |
| } |
| } |
| |
| static unsigned long imsic_mrif_atomic_rmw(struct imsic_mrif *mrif, |
| unsigned long *ptr, |
| unsigned long new_val, |
| unsigned long wr_mask) |
| { |
| unsigned long old_val = 0, tmp = 0; |
| |
| __asm__ __volatile__ ( |
| "0: lr.w.aq %1, %0\n" |
| " and %2, %1, %3\n" |
| " or %2, %2, %4\n" |
| " sc.w.rl %2, %2, %0\n" |
| " bnez %2, 0b" |
| : "+A" (*ptr), "+r" (old_val), "+r" (tmp) |
| : "r" (~wr_mask), "r" (new_val & wr_mask) |
| : "memory"); |
| |
| return old_val; |
| } |
| |
| static unsigned long imsic_mrif_atomic_or(struct imsic_mrif *mrif, |
| unsigned long *ptr, |
| unsigned long val) |
| { |
| return atomic_long_fetch_or(val, (atomic_long_t *)ptr); |
| } |
| |
| #define imsic_mrif_atomic_write(__mrif, __ptr, __new_val) \ |
| imsic_mrif_atomic_rmw(__mrif, __ptr, __new_val, -1UL) |
| #define imsic_mrif_atomic_read(__mrif, __ptr) \ |
| imsic_mrif_atomic_or(__mrif, __ptr, 0) |
| |
| static u32 imsic_mrif_topei(struct imsic_mrif *mrif, u32 nr_eix, u32 nr_msis) |
| { |
| struct imsic_mrif_eix *eix; |
| u32 i, imin, imax, ei, max_msi; |
| unsigned long eipend[BITS_PER_TYPE(u64) / BITS_PER_LONG]; |
| unsigned long eithreshold = imsic_mrif_atomic_read(mrif, |
| &mrif->eithreshold); |
| |
| max_msi = (eithreshold && (eithreshold <= nr_msis)) ? |
| eithreshold : nr_msis; |
| for (ei = 0; ei < nr_eix; ei++) { |
| eix = &mrif->eix[ei]; |
| eipend[0] = imsic_mrif_atomic_read(mrif, &eix->eie[0]) & |
| imsic_mrif_atomic_read(mrif, &eix->eip[0]); |
| #ifdef CONFIG_32BIT |
| eipend[1] = imsic_mrif_atomic_read(mrif, &eix->eie[1]) & |
| imsic_mrif_atomic_read(mrif, &eix->eip[1]); |
| if (!eipend[0] && !eipend[1]) |
| #else |
| if (!eipend[0]) |
| #endif |
| continue; |
| |
| imin = ei * BITS_PER_TYPE(u64); |
| imax = ((imin + BITS_PER_TYPE(u64)) < max_msi) ? |
| imin + BITS_PER_TYPE(u64) : max_msi; |
| for (i = (!imin) ? 1 : imin; i < imax; i++) { |
| if (test_bit(i - imin, eipend)) |
| return (i << TOPEI_ID_SHIFT) | i; |
| } |
| } |
| |
| return 0; |
| } |
| |
| static int imsic_mrif_isel_check(u32 nr_eix, unsigned long isel) |
| { |
| u32 num = 0; |
| |
| switch (isel) { |
| case IMSIC_EIDELIVERY: |
| case IMSIC_EITHRESHOLD: |
| break; |
| case IMSIC_EIP0 ... IMSIC_EIP63: |
| num = isel - IMSIC_EIP0; |
| break; |
| case IMSIC_EIE0 ... IMSIC_EIE63: |
| num = isel - IMSIC_EIE0; |
| break; |
| default: |
| return -ENOENT; |
| } |
| #ifndef CONFIG_32BIT |
| if (num & 0x1) |
| return -EINVAL; |
| #endif |
| if ((num / 2) >= nr_eix) |
| return -EINVAL; |
| |
| return 0; |
| } |
| |
| static int imsic_mrif_rmw(struct imsic_mrif *mrif, u32 nr_eix, |
| unsigned long isel, unsigned long *val, |
| unsigned long new_val, unsigned long wr_mask) |
| { |
| bool pend; |
| struct imsic_mrif_eix *eix; |
| unsigned long *ei, num, old_val = 0; |
| |
| switch (isel) { |
| case IMSIC_EIDELIVERY: |
| old_val = imsic_mrif_atomic_rmw(mrif, &mrif->eidelivery, |
| new_val, wr_mask & 0x1); |
| break; |
| case IMSIC_EITHRESHOLD: |
| old_val = imsic_mrif_atomic_rmw(mrif, &mrif->eithreshold, |
| new_val, wr_mask & (IMSIC_MAX_ID - 1)); |
| break; |
| case IMSIC_EIP0 ... IMSIC_EIP63: |
| case IMSIC_EIE0 ... IMSIC_EIE63: |
| if (isel >= IMSIC_EIP0 && isel <= IMSIC_EIP63) { |
| pend = true; |
| num = isel - IMSIC_EIP0; |
| } else { |
| pend = false; |
| num = isel - IMSIC_EIE0; |
| } |
| |
| if ((num / 2) >= nr_eix) |
| return -EINVAL; |
| eix = &mrif->eix[num / 2]; |
| |
| #ifndef CONFIG_32BIT |
| if (num & 0x1) |
| return -EINVAL; |
| ei = (pend) ? &eix->eip[0] : &eix->eie[0]; |
| #else |
| ei = (pend) ? &eix->eip[num & 0x1] : &eix->eie[num & 0x1]; |
| #endif |
| |
| /* Bit0 of EIP0 or EIE0 is read-only */ |
| if (!num) |
| wr_mask &= ~BIT(0); |
| |
| old_val = imsic_mrif_atomic_rmw(mrif, ei, new_val, wr_mask); |
| break; |
| default: |
| return -ENOENT; |
| } |
| |
| if (val) |
| *val = old_val; |
| |
| return 0; |
| } |
| |
| struct imsic_vsfile_read_data { |
| int hgei; |
| u32 nr_eix; |
| bool clear; |
| struct imsic_mrif *mrif; |
| }; |
| |
| static void imsic_vsfile_local_read(void *data) |
| { |
| u32 i; |
| struct imsic_mrif_eix *eix; |
| struct imsic_vsfile_read_data *idata = data; |
| struct imsic_mrif *mrif = idata->mrif; |
| unsigned long new_hstatus, old_hstatus, old_vsiselect; |
| |
| old_vsiselect = csr_read(CSR_VSISELECT); |
| old_hstatus = csr_read(CSR_HSTATUS); |
| new_hstatus = old_hstatus & ~HSTATUS_VGEIN; |
| new_hstatus |= ((unsigned long)idata->hgei) << HSTATUS_VGEIN_SHIFT; |
| csr_write(CSR_HSTATUS, new_hstatus); |
| |
| /* |
| * We don't use imsic_mrif_atomic_xyz() functions to store |
| * values in MRIF because imsic_vsfile_read() is always called |
| * with pointer to temporary MRIF on stack. |
| */ |
| |
| if (idata->clear) { |
| mrif->eidelivery = imsic_vs_csr_swap(IMSIC_EIDELIVERY, 0); |
| mrif->eithreshold = imsic_vs_csr_swap(IMSIC_EITHRESHOLD, 0); |
| for (i = 0; i < idata->nr_eix; i++) { |
| eix = &mrif->eix[i]; |
| eix->eip[0] = imsic_eix_swap(IMSIC_EIP0 + i * 2, 0); |
| eix->eie[0] = imsic_eix_swap(IMSIC_EIE0 + i * 2, 0); |
| #ifdef CONFIG_32BIT |
| eix->eip[1] = imsic_eix_swap(IMSIC_EIP0 + i * 2 + 1, 0); |
| eix->eie[1] = imsic_eix_swap(IMSIC_EIE0 + i * 2 + 1, 0); |
| #endif |
| } |
| } else { |
| mrif->eidelivery = imsic_vs_csr_read(IMSIC_EIDELIVERY); |
| mrif->eithreshold = imsic_vs_csr_read(IMSIC_EITHRESHOLD); |
| for (i = 0; i < idata->nr_eix; i++) { |
| eix = &mrif->eix[i]; |
| eix->eip[0] = imsic_eix_read(IMSIC_EIP0 + i * 2); |
| eix->eie[0] = imsic_eix_read(IMSIC_EIE0 + i * 2); |
| #ifdef CONFIG_32BIT |
| eix->eip[1] = imsic_eix_read(IMSIC_EIP0 + i * 2 + 1); |
| eix->eie[1] = imsic_eix_read(IMSIC_EIE0 + i * 2 + 1); |
| #endif |
| } |
| } |
| |
| csr_write(CSR_HSTATUS, old_hstatus); |
| csr_write(CSR_VSISELECT, old_vsiselect); |
| } |
| |
| static void imsic_vsfile_read(int vsfile_hgei, int vsfile_cpu, u32 nr_eix, |
| bool clear, struct imsic_mrif *mrif) |
| { |
| struct imsic_vsfile_read_data idata; |
| |
| /* We can only read clear if we have a IMSIC VS-file */ |
| if (vsfile_cpu < 0 || vsfile_hgei <= 0) |
| return; |
| |
| /* We can only read clear on local CPU */ |
| idata.hgei = vsfile_hgei; |
| idata.nr_eix = nr_eix; |
| idata.clear = clear; |
| idata.mrif = mrif; |
| on_each_cpu_mask(cpumask_of(vsfile_cpu), |
| imsic_vsfile_local_read, &idata, 1); |
| } |
| |
| struct imsic_vsfile_rw_data { |
| int hgei; |
| int isel; |
| bool write; |
| unsigned long val; |
| }; |
| |
| static void imsic_vsfile_local_rw(void *data) |
| { |
| struct imsic_vsfile_rw_data *idata = data; |
| unsigned long new_hstatus, old_hstatus, old_vsiselect; |
| |
| old_vsiselect = csr_read(CSR_VSISELECT); |
| old_hstatus = csr_read(CSR_HSTATUS); |
| new_hstatus = old_hstatus & ~HSTATUS_VGEIN; |
| new_hstatus |= ((unsigned long)idata->hgei) << HSTATUS_VGEIN_SHIFT; |
| csr_write(CSR_HSTATUS, new_hstatus); |
| |
| switch (idata->isel) { |
| case IMSIC_EIDELIVERY: |
| if (idata->write) |
| imsic_vs_csr_write(IMSIC_EIDELIVERY, idata->val); |
| else |
| idata->val = imsic_vs_csr_read(IMSIC_EIDELIVERY); |
| break; |
| case IMSIC_EITHRESHOLD: |
| if (idata->write) |
| imsic_vs_csr_write(IMSIC_EITHRESHOLD, idata->val); |
| else |
| idata->val = imsic_vs_csr_read(IMSIC_EITHRESHOLD); |
| break; |
| case IMSIC_EIP0 ... IMSIC_EIP63: |
| case IMSIC_EIE0 ... IMSIC_EIE63: |
| #ifndef CONFIG_32BIT |
| if (idata->isel & 0x1) |
| break; |
| #endif |
| if (idata->write) |
| imsic_eix_write(idata->isel, idata->val); |
| else |
| idata->val = imsic_eix_read(idata->isel); |
| break; |
| default: |
| break; |
| } |
| |
| csr_write(CSR_HSTATUS, old_hstatus); |
| csr_write(CSR_VSISELECT, old_vsiselect); |
| } |
| |
| static int imsic_vsfile_rw(int vsfile_hgei, int vsfile_cpu, u32 nr_eix, |
| unsigned long isel, bool write, |
| unsigned long *val) |
| { |
| int rc; |
| struct imsic_vsfile_rw_data rdata; |
| |
| /* We can only access register if we have a IMSIC VS-file */ |
| if (vsfile_cpu < 0 || vsfile_hgei <= 0) |
| return -EINVAL; |
| |
| /* Check IMSIC register iselect */ |
| rc = imsic_mrif_isel_check(nr_eix, isel); |
| if (rc) |
| return rc; |
| |
| /* We can only access register on local CPU */ |
| rdata.hgei = vsfile_hgei; |
| rdata.isel = isel; |
| rdata.write = write; |
| rdata.val = (write) ? *val : 0; |
| on_each_cpu_mask(cpumask_of(vsfile_cpu), |
| imsic_vsfile_local_rw, &rdata, 1); |
| |
| if (!write) |
| *val = rdata.val; |
| |
| return 0; |
| } |
| |
| static void imsic_vsfile_local_clear(int vsfile_hgei, u32 nr_eix) |
| { |
| u32 i; |
| unsigned long new_hstatus, old_hstatus, old_vsiselect; |
| |
| /* We can only zero-out if we have a IMSIC VS-file */ |
| if (vsfile_hgei <= 0) |
| return; |
| |
| old_vsiselect = csr_read(CSR_VSISELECT); |
| old_hstatus = csr_read(CSR_HSTATUS); |
| new_hstatus = old_hstatus & ~HSTATUS_VGEIN; |
| new_hstatus |= ((unsigned long)vsfile_hgei) << HSTATUS_VGEIN_SHIFT; |
| csr_write(CSR_HSTATUS, new_hstatus); |
| |
| imsic_vs_csr_write(IMSIC_EIDELIVERY, 0); |
| imsic_vs_csr_write(IMSIC_EITHRESHOLD, 0); |
| for (i = 0; i < nr_eix; i++) { |
| imsic_eix_write(IMSIC_EIP0 + i * 2, 0); |
| imsic_eix_write(IMSIC_EIE0 + i * 2, 0); |
| #ifdef CONFIG_32BIT |
| imsic_eix_write(IMSIC_EIP0 + i * 2 + 1, 0); |
| imsic_eix_write(IMSIC_EIE0 + i * 2 + 1, 0); |
| #endif |
| } |
| |
| csr_write(CSR_HSTATUS, old_hstatus); |
| csr_write(CSR_VSISELECT, old_vsiselect); |
| } |
| |
| static void imsic_vsfile_local_update(int vsfile_hgei, u32 nr_eix, |
| struct imsic_mrif *mrif) |
| { |
| u32 i; |
| struct imsic_mrif_eix *eix; |
| unsigned long new_hstatus, old_hstatus, old_vsiselect; |
| |
| /* We can only update if we have a HW IMSIC context */ |
| if (vsfile_hgei <= 0) |
| return; |
| |
| /* |
| * We don't use imsic_mrif_atomic_xyz() functions to read values |
| * from MRIF in this function because it is always called with |
| * pointer to temporary MRIF on stack. |
| */ |
| |
| old_vsiselect = csr_read(CSR_VSISELECT); |
| old_hstatus = csr_read(CSR_HSTATUS); |
| new_hstatus = old_hstatus & ~HSTATUS_VGEIN; |
| new_hstatus |= ((unsigned long)vsfile_hgei) << HSTATUS_VGEIN_SHIFT; |
| csr_write(CSR_HSTATUS, new_hstatus); |
| |
| for (i = 0; i < nr_eix; i++) { |
| eix = &mrif->eix[i]; |
| imsic_eix_set(IMSIC_EIP0 + i * 2, eix->eip[0]); |
| imsic_eix_set(IMSIC_EIE0 + i * 2, eix->eie[0]); |
| #ifdef CONFIG_32BIT |
| imsic_eix_set(IMSIC_EIP0 + i * 2 + 1, eix->eip[1]); |
| imsic_eix_set(IMSIC_EIE0 + i * 2 + 1, eix->eie[1]); |
| #endif |
| } |
| imsic_vs_csr_write(IMSIC_EITHRESHOLD, mrif->eithreshold); |
| imsic_vs_csr_write(IMSIC_EIDELIVERY, mrif->eidelivery); |
| |
| csr_write(CSR_HSTATUS, old_hstatus); |
| csr_write(CSR_VSISELECT, old_vsiselect); |
| } |
| |
| static void imsic_vsfile_cleanup(struct imsic *imsic) |
| { |
| int old_vsfile_hgei, old_vsfile_cpu; |
| unsigned long flags; |
| |
| /* |
| * We don't use imsic_mrif_atomic_xyz() functions to clear the |
| * SW-file in this function because it is always called when the |
| * VCPU is being destroyed. |
| */ |
| |
| write_lock_irqsave(&imsic->vsfile_lock, flags); |
| old_vsfile_hgei = imsic->vsfile_hgei; |
| old_vsfile_cpu = imsic->vsfile_cpu; |
| imsic->vsfile_cpu = imsic->vsfile_hgei = -1; |
| imsic->vsfile_va = NULL; |
| imsic->vsfile_pa = 0; |
| write_unlock_irqrestore(&imsic->vsfile_lock, flags); |
| |
| memset(imsic->swfile, 0, sizeof(*imsic->swfile)); |
| |
| if (old_vsfile_cpu >= 0) |
| kvm_riscv_aia_free_hgei(old_vsfile_cpu, old_vsfile_hgei); |
| } |
| |
| static void imsic_swfile_extirq_update(struct kvm_vcpu *vcpu) |
| { |
| struct imsic *imsic = vcpu->arch.aia_context.imsic_state; |
| struct imsic_mrif *mrif = imsic->swfile; |
| unsigned long flags; |
| |
| /* |
| * The critical section is necessary during external interrupt |
| * updates to avoid the risk of losing interrupts due to potential |
| * interruptions between reading topei and updating pending status. |
| */ |
| |
| raw_spin_lock_irqsave(&imsic->swfile_extirq_lock, flags); |
| |
| if (imsic_mrif_atomic_read(mrif, &mrif->eidelivery) && |
| imsic_mrif_topei(mrif, imsic->nr_eix, imsic->nr_msis)) |
| kvm_riscv_vcpu_set_interrupt(vcpu, IRQ_VS_EXT); |
| else |
| kvm_riscv_vcpu_unset_interrupt(vcpu, IRQ_VS_EXT); |
| |
| raw_spin_unlock_irqrestore(&imsic->swfile_extirq_lock, flags); |
| } |
| |
| static void imsic_swfile_read(struct kvm_vcpu *vcpu, bool clear, |
| struct imsic_mrif *mrif) |
| { |
| struct imsic *imsic = vcpu->arch.aia_context.imsic_state; |
| |
| /* |
| * We don't use imsic_mrif_atomic_xyz() functions to read and |
| * write SW-file and MRIF in this function because it is always |
| * called when VCPU is not using SW-file and the MRIF points to |
| * a temporary MRIF on stack. |
| */ |
| |
| memcpy(mrif, imsic->swfile, sizeof(*mrif)); |
| if (clear) { |
| memset(imsic->swfile, 0, sizeof(*imsic->swfile)); |
| kvm_riscv_vcpu_unset_interrupt(vcpu, IRQ_VS_EXT); |
| } |
| } |
| |
| static void imsic_swfile_update(struct kvm_vcpu *vcpu, |
| struct imsic_mrif *mrif) |
| { |
| u32 i; |
| struct imsic_mrif_eix *seix, *eix; |
| struct imsic *imsic = vcpu->arch.aia_context.imsic_state; |
| struct imsic_mrif *smrif = imsic->swfile; |
| |
| imsic_mrif_atomic_write(smrif, &smrif->eidelivery, mrif->eidelivery); |
| imsic_mrif_atomic_write(smrif, &smrif->eithreshold, mrif->eithreshold); |
| for (i = 0; i < imsic->nr_eix; i++) { |
| seix = &smrif->eix[i]; |
| eix = &mrif->eix[i]; |
| imsic_mrif_atomic_or(smrif, &seix->eip[0], eix->eip[0]); |
| imsic_mrif_atomic_or(smrif, &seix->eie[0], eix->eie[0]); |
| #ifdef CONFIG_32BIT |
| imsic_mrif_atomic_or(smrif, &seix->eip[1], eix->eip[1]); |
| imsic_mrif_atomic_or(smrif, &seix->eie[1], eix->eie[1]); |
| #endif |
| } |
| |
| imsic_swfile_extirq_update(vcpu); |
| } |
| |
| void kvm_riscv_vcpu_aia_imsic_release(struct kvm_vcpu *vcpu) |
| { |
| unsigned long flags; |
| struct imsic_mrif tmrif; |
| int old_vsfile_hgei, old_vsfile_cpu; |
| struct imsic *imsic = vcpu->arch.aia_context.imsic_state; |
| |
| /* Read and clear IMSIC VS-file details */ |
| write_lock_irqsave(&imsic->vsfile_lock, flags); |
| old_vsfile_hgei = imsic->vsfile_hgei; |
| old_vsfile_cpu = imsic->vsfile_cpu; |
| imsic->vsfile_cpu = imsic->vsfile_hgei = -1; |
| imsic->vsfile_va = NULL; |
| imsic->vsfile_pa = 0; |
| write_unlock_irqrestore(&imsic->vsfile_lock, flags); |
| |
| /* Do nothing, if no IMSIC VS-file to release */ |
| if (old_vsfile_cpu < 0) |
| return; |
| |
| /* |
| * At this point, all interrupt producers are still using |
| * the old IMSIC VS-file so we first re-direct all interrupt |
| * producers. |
| */ |
| |
| /* Purge the G-stage mapping */ |
| kvm_riscv_gstage_iounmap(vcpu->kvm, |
| vcpu->arch.aia_context.imsic_addr, |
| IMSIC_MMIO_PAGE_SZ); |
| |
| /* TODO: Purge the IOMMU mapping ??? */ |
| |
| /* |
| * At this point, all interrupt producers have been re-directed |
| * to somewhere else so we move register state from the old IMSIC |
| * VS-file to the IMSIC SW-file. |
| */ |
| |
| /* Read and clear register state from old IMSIC VS-file */ |
| memset(&tmrif, 0, sizeof(tmrif)); |
| imsic_vsfile_read(old_vsfile_hgei, old_vsfile_cpu, imsic->nr_hw_eix, |
| true, &tmrif); |
| |
| /* Update register state in IMSIC SW-file */ |
| imsic_swfile_update(vcpu, &tmrif); |
| |
| /* Free-up old IMSIC VS-file */ |
| kvm_riscv_aia_free_hgei(old_vsfile_cpu, old_vsfile_hgei); |
| } |
| |
| int kvm_riscv_vcpu_aia_imsic_update(struct kvm_vcpu *vcpu) |
| { |
| unsigned long flags; |
| phys_addr_t new_vsfile_pa; |
| struct imsic_mrif tmrif; |
| void __iomem *new_vsfile_va; |
| struct kvm *kvm = vcpu->kvm; |
| struct kvm_run *run = vcpu->run; |
| struct kvm_vcpu_aia *vaia = &vcpu->arch.aia_context; |
| struct imsic *imsic = vaia->imsic_state; |
| int ret = 0, new_vsfile_hgei = -1, old_vsfile_hgei, old_vsfile_cpu; |
| |
| /* Do nothing for emulation mode */ |
| if (kvm->arch.aia.mode == KVM_DEV_RISCV_AIA_MODE_EMUL) |
| return 1; |
| |
| /* Read old IMSIC VS-file details */ |
| read_lock_irqsave(&imsic->vsfile_lock, flags); |
| old_vsfile_hgei = imsic->vsfile_hgei; |
| old_vsfile_cpu = imsic->vsfile_cpu; |
| read_unlock_irqrestore(&imsic->vsfile_lock, flags); |
| |
| /* Do nothing if we are continuing on same CPU */ |
| if (old_vsfile_cpu == vcpu->cpu) |
| return 1; |
| |
| /* Allocate new IMSIC VS-file */ |
| ret = kvm_riscv_aia_alloc_hgei(vcpu->cpu, vcpu, |
| &new_vsfile_va, &new_vsfile_pa); |
| if (ret <= 0) { |
| /* For HW acceleration mode, we can't continue */ |
| if (kvm->arch.aia.mode == KVM_DEV_RISCV_AIA_MODE_HWACCEL) { |
| run->fail_entry.hardware_entry_failure_reason = |
| CSR_HSTATUS; |
| run->fail_entry.cpu = vcpu->cpu; |
| run->exit_reason = KVM_EXIT_FAIL_ENTRY; |
| return 0; |
| } |
| |
| /* Release old IMSIC VS-file */ |
| if (old_vsfile_cpu >= 0) |
| kvm_riscv_vcpu_aia_imsic_release(vcpu); |
| |
| /* For automatic mode, we continue */ |
| goto done; |
| } |
| new_vsfile_hgei = ret; |
| |
| /* |
| * At this point, all interrupt producers are still using |
| * to the old IMSIC VS-file so we first move all interrupt |
| * producers to the new IMSIC VS-file. |
| */ |
| |
| /* Zero-out new IMSIC VS-file */ |
| imsic_vsfile_local_clear(new_vsfile_hgei, imsic->nr_hw_eix); |
| |
| /* Update G-stage mapping for the new IMSIC VS-file */ |
| ret = kvm_riscv_gstage_ioremap(kvm, vcpu->arch.aia_context.imsic_addr, |
| new_vsfile_pa, IMSIC_MMIO_PAGE_SZ, |
| true, true); |
| if (ret) |
| goto fail_free_vsfile_hgei; |
| |
| /* TODO: Update the IOMMU mapping ??? */ |
| |
| /* Update new IMSIC VS-file details in IMSIC context */ |
| write_lock_irqsave(&imsic->vsfile_lock, flags); |
| imsic->vsfile_hgei = new_vsfile_hgei; |
| imsic->vsfile_cpu = vcpu->cpu; |
| imsic->vsfile_va = new_vsfile_va; |
| imsic->vsfile_pa = new_vsfile_pa; |
| write_unlock_irqrestore(&imsic->vsfile_lock, flags); |
| |
| /* |
| * At this point, all interrupt producers have been moved |
| * to the new IMSIC VS-file so we move register state from |
| * the old IMSIC VS/SW-file to the new IMSIC VS-file. |
| */ |
| |
| memset(&tmrif, 0, sizeof(tmrif)); |
| if (old_vsfile_cpu >= 0) { |
| /* Read and clear register state from old IMSIC VS-file */ |
| imsic_vsfile_read(old_vsfile_hgei, old_vsfile_cpu, |
| imsic->nr_hw_eix, true, &tmrif); |
| |
| /* Free-up old IMSIC VS-file */ |
| kvm_riscv_aia_free_hgei(old_vsfile_cpu, old_vsfile_hgei); |
| } else { |
| /* Read and clear register state from IMSIC SW-file */ |
| imsic_swfile_read(vcpu, true, &tmrif); |
| } |
| |
| /* Restore register state in the new IMSIC VS-file */ |
| imsic_vsfile_local_update(new_vsfile_hgei, imsic->nr_hw_eix, &tmrif); |
| |
| done: |
| /* Set VCPU HSTATUS.VGEIN to new IMSIC VS-file */ |
| vcpu->arch.guest_context.hstatus &= ~HSTATUS_VGEIN; |
| if (new_vsfile_hgei > 0) |
| vcpu->arch.guest_context.hstatus |= |
| ((unsigned long)new_vsfile_hgei) << HSTATUS_VGEIN_SHIFT; |
| |
| /* Continue run-loop */ |
| return 1; |
| |
| fail_free_vsfile_hgei: |
| kvm_riscv_aia_free_hgei(vcpu->cpu, new_vsfile_hgei); |
| return ret; |
| } |
| |
| int kvm_riscv_vcpu_aia_imsic_rmw(struct kvm_vcpu *vcpu, unsigned long isel, |
| unsigned long *val, unsigned long new_val, |
| unsigned long wr_mask) |
| { |
| u32 topei; |
| struct imsic_mrif_eix *eix; |
| int r, rc = KVM_INSN_CONTINUE_NEXT_SEPC; |
| struct imsic *imsic = vcpu->arch.aia_context.imsic_state; |
| |
| if (isel == KVM_RISCV_AIA_IMSIC_TOPEI) { |
| /* Read pending and enabled interrupt with highest priority */ |
| topei = imsic_mrif_topei(imsic->swfile, imsic->nr_eix, |
| imsic->nr_msis); |
| if (val) |
| *val = topei; |
| |
| /* Writes ignore value and clear top pending interrupt */ |
| if (topei && wr_mask) { |
| topei >>= TOPEI_ID_SHIFT; |
| if (topei) { |
| eix = &imsic->swfile->eix[topei / |
| BITS_PER_TYPE(u64)]; |
| clear_bit(topei & (BITS_PER_TYPE(u64) - 1), |
| eix->eip); |
| } |
| } |
| } else { |
| r = imsic_mrif_rmw(imsic->swfile, imsic->nr_eix, isel, |
| val, new_val, wr_mask); |
| /* Forward unknown IMSIC register to user-space */ |
| if (r) |
| rc = (r == -ENOENT) ? 0 : KVM_INSN_ILLEGAL_TRAP; |
| } |
| |
| if (wr_mask) |
| imsic_swfile_extirq_update(vcpu); |
| |
| return rc; |
| } |
| |
| int kvm_riscv_aia_imsic_rw_attr(struct kvm *kvm, unsigned long type, |
| bool write, unsigned long *val) |
| { |
| u32 isel, vcpu_id; |
| unsigned long flags; |
| struct imsic *imsic; |
| struct kvm_vcpu *vcpu; |
| int rc, vsfile_hgei, vsfile_cpu; |
| |
| if (!kvm_riscv_aia_initialized(kvm)) |
| return -ENODEV; |
| |
| vcpu_id = KVM_DEV_RISCV_AIA_IMSIC_GET_VCPU(type); |
| vcpu = kvm_get_vcpu_by_id(kvm, vcpu_id); |
| if (!vcpu) |
| return -ENODEV; |
| |
| isel = KVM_DEV_RISCV_AIA_IMSIC_GET_ISEL(type); |
| imsic = vcpu->arch.aia_context.imsic_state; |
| |
| read_lock_irqsave(&imsic->vsfile_lock, flags); |
| |
| rc = 0; |
| vsfile_hgei = imsic->vsfile_hgei; |
| vsfile_cpu = imsic->vsfile_cpu; |
| if (vsfile_cpu < 0) { |
| if (write) { |
| rc = imsic_mrif_rmw(imsic->swfile, imsic->nr_eix, |
| isel, NULL, *val, -1UL); |
| imsic_swfile_extirq_update(vcpu); |
| } else |
| rc = imsic_mrif_rmw(imsic->swfile, imsic->nr_eix, |
| isel, val, 0, 0); |
| } |
| |
| read_unlock_irqrestore(&imsic->vsfile_lock, flags); |
| |
| if (!rc && vsfile_cpu >= 0) |
| rc = imsic_vsfile_rw(vsfile_hgei, vsfile_cpu, imsic->nr_eix, |
| isel, write, val); |
| |
| return rc; |
| } |
| |
| int kvm_riscv_aia_imsic_has_attr(struct kvm *kvm, unsigned long type) |
| { |
| u32 isel, vcpu_id; |
| struct imsic *imsic; |
| struct kvm_vcpu *vcpu; |
| |
| if (!kvm_riscv_aia_initialized(kvm)) |
| return -ENODEV; |
| |
| vcpu_id = KVM_DEV_RISCV_AIA_IMSIC_GET_VCPU(type); |
| vcpu = kvm_get_vcpu_by_id(kvm, vcpu_id); |
| if (!vcpu) |
| return -ENODEV; |
| |
| isel = KVM_DEV_RISCV_AIA_IMSIC_GET_ISEL(type); |
| imsic = vcpu->arch.aia_context.imsic_state; |
| return imsic_mrif_isel_check(imsic->nr_eix, isel); |
| } |
| |
| void kvm_riscv_vcpu_aia_imsic_reset(struct kvm_vcpu *vcpu) |
| { |
| struct imsic *imsic = vcpu->arch.aia_context.imsic_state; |
| |
| if (!imsic) |
| return; |
| |
| kvm_riscv_vcpu_aia_imsic_release(vcpu); |
| |
| memset(imsic->swfile, 0, sizeof(*imsic->swfile)); |
| } |
| |
| int kvm_riscv_vcpu_aia_imsic_inject(struct kvm_vcpu *vcpu, |
| u32 guest_index, u32 offset, u32 iid) |
| { |
| unsigned long flags; |
| struct imsic_mrif_eix *eix; |
| struct imsic *imsic = vcpu->arch.aia_context.imsic_state; |
| |
| /* We only emulate one IMSIC MMIO page for each Guest VCPU */ |
| if (!imsic || !iid || guest_index || |
| (offset != IMSIC_MMIO_SETIPNUM_LE && |
| offset != IMSIC_MMIO_SETIPNUM_BE)) |
| return -ENODEV; |
| |
| iid = (offset == IMSIC_MMIO_SETIPNUM_BE) ? __swab32(iid) : iid; |
| if (imsic->nr_msis <= iid) |
| return -EINVAL; |
| |
| read_lock_irqsave(&imsic->vsfile_lock, flags); |
| |
| if (imsic->vsfile_cpu >= 0) { |
| writel(iid, imsic->vsfile_va + IMSIC_MMIO_SETIPNUM_LE); |
| kvm_vcpu_kick(vcpu); |
| } else { |
| eix = &imsic->swfile->eix[iid / BITS_PER_TYPE(u64)]; |
| set_bit(iid & (BITS_PER_TYPE(u64) - 1), eix->eip); |
| imsic_swfile_extirq_update(vcpu); |
| } |
| |
| read_unlock_irqrestore(&imsic->vsfile_lock, flags); |
| |
| return 0; |
| } |
| |
| static int imsic_mmio_read(struct kvm_vcpu *vcpu, struct kvm_io_device *dev, |
| gpa_t addr, int len, void *val) |
| { |
| if (len != 4 || (addr & 0x3) != 0) |
| return -EOPNOTSUPP; |
| |
| *((u32 *)val) = 0; |
| |
| return 0; |
| } |
| |
| static int imsic_mmio_write(struct kvm_vcpu *vcpu, struct kvm_io_device *dev, |
| gpa_t addr, int len, const void *val) |
| { |
| struct kvm_msi msi = { 0 }; |
| |
| if (len != 4 || (addr & 0x3) != 0) |
| return -EOPNOTSUPP; |
| |
| msi.address_hi = addr >> 32; |
| msi.address_lo = (u32)addr; |
| msi.data = *((const u32 *)val); |
| kvm_riscv_aia_inject_msi(vcpu->kvm, &msi); |
| |
| return 0; |
| }; |
| |
| static struct kvm_io_device_ops imsic_iodoev_ops = { |
| .read = imsic_mmio_read, |
| .write = imsic_mmio_write, |
| }; |
| |
| int kvm_riscv_vcpu_aia_imsic_init(struct kvm_vcpu *vcpu) |
| { |
| int ret = 0; |
| struct imsic *imsic; |
| struct page *swfile_page; |
| struct kvm *kvm = vcpu->kvm; |
| |
| /* Fail if we have zero IDs */ |
| if (!kvm->arch.aia.nr_ids) |
| return -EINVAL; |
| |
| /* Allocate IMSIC context */ |
| imsic = kzalloc(sizeof(*imsic), GFP_KERNEL); |
| if (!imsic) |
| return -ENOMEM; |
| vcpu->arch.aia_context.imsic_state = imsic; |
| |
| /* Setup IMSIC context */ |
| imsic->nr_msis = kvm->arch.aia.nr_ids + 1; |
| rwlock_init(&imsic->vsfile_lock); |
| imsic->nr_eix = BITS_TO_U64(imsic->nr_msis); |
| imsic->nr_hw_eix = BITS_TO_U64(kvm_riscv_aia_max_ids); |
| imsic->vsfile_hgei = imsic->vsfile_cpu = -1; |
| |
| /* Setup IMSIC SW-file */ |
| swfile_page = alloc_pages(GFP_KERNEL | __GFP_ZERO, |
| get_order(sizeof(*imsic->swfile))); |
| if (!swfile_page) { |
| ret = -ENOMEM; |
| goto fail_free_imsic; |
| } |
| imsic->swfile = page_to_virt(swfile_page); |
| imsic->swfile_pa = page_to_phys(swfile_page); |
| raw_spin_lock_init(&imsic->swfile_extirq_lock); |
| |
| /* Setup IO device */ |
| kvm_iodevice_init(&imsic->iodev, &imsic_iodoev_ops); |
| mutex_lock(&kvm->slots_lock); |
| ret = kvm_io_bus_register_dev(kvm, KVM_MMIO_BUS, |
| vcpu->arch.aia_context.imsic_addr, |
| KVM_DEV_RISCV_IMSIC_SIZE, |
| &imsic->iodev); |
| mutex_unlock(&kvm->slots_lock); |
| if (ret) |
| goto fail_free_swfile; |
| |
| return 0; |
| |
| fail_free_swfile: |
| free_pages((unsigned long)imsic->swfile, |
| get_order(sizeof(*imsic->swfile))); |
| fail_free_imsic: |
| vcpu->arch.aia_context.imsic_state = NULL; |
| kfree(imsic); |
| return ret; |
| } |
| |
| void kvm_riscv_vcpu_aia_imsic_cleanup(struct kvm_vcpu *vcpu) |
| { |
| struct kvm *kvm = vcpu->kvm; |
| struct imsic *imsic = vcpu->arch.aia_context.imsic_state; |
| |
| if (!imsic) |
| return; |
| |
| imsic_vsfile_cleanup(imsic); |
| |
| mutex_lock(&kvm->slots_lock); |
| kvm_io_bus_unregister_dev(kvm, KVM_MMIO_BUS, &imsic->iodev); |
| mutex_unlock(&kvm->slots_lock); |
| |
| free_pages((unsigned long)imsic->swfile, |
| get_order(sizeof(*imsic->swfile))); |
| |
| vcpu->arch.aia_context.imsic_state = NULL; |
| kfree(imsic); |
| } |