From: Kunkun Jiang jiangkunkun@huawei.com
virt inclusion category: feature bugzilla: https://gitee.com/openeuler/kernel/issues/I8K89F CVE: NA
------------------------------------------------------------------
This device is designed as the accessory device of mbigen to assist vtimer to support irqbypass capability. It can translate the level vtimer irq which percpu vtimer triggered to MSG interrupt (routed by ITS, and inject to cpu).
Multiple mbigens may exist on one CPU Die and not all mbigens are related to vtimer. Use MBIX_VPPI_ITS_TA to determin target peri mbigen related to the vtimer. For each cpu, we calculate the offset of the corresponding GICR phys addr to get the correct MBIGEN pin.
This implemented: (1) vtimer mbigen device probed and inited, which indicate the device as the vtimer accessory dev.
(2) open some device configuration interfaces for hyper to configure.
Signed-off-by: Nianyao Tang tangnianyao@huawei.com Signed-off-by: Zenghui Yu yuzenghui@huawei.com Signed-off-by: Kunkun Jiang jiangkunkun@huawei.com Signed-off-by: Dongxu Sun sundongxu3@huawei.com --- drivers/irqchip/irq-gic-v3.c | 5 + drivers/irqchip/irq-mbigen.c | 358 ++++++++++++++++++++++++++- include/clocksource/arm_arch_timer.h | 17 ++ include/linux/irqchip/arm-gic-v3.h | 2 + 4 files changed, 379 insertions(+), 3 deletions(-)
diff --git a/drivers/irqchip/irq-gic-v3.c b/drivers/irqchip/irq-gic-v3.c index 2015ee5f3f0c..e1faf99d0c1a 100644 --- a/drivers/irqchip/irq-gic-v3.c +++ b/drivers/irqchip/irq-gic-v3.c @@ -165,6 +165,11 @@ static enum gic_intid_range __get_intid_range(irq_hw_number_t hwirq) } }
+phys_addr_t get_gicr_paddr(int cpu) +{ + return (per_cpu_ptr(gic_data.rdists.rdist, cpu))->phys_base; +} + static enum gic_intid_range get_intid_range(struct irq_data *d) { return __get_intid_range(d->hwirq); diff --git a/drivers/irqchip/irq-mbigen.c b/drivers/irqchip/irq-mbigen.c index fc05e23938cd..da77ef7d6198 100644 --- a/drivers/irqchip/irq-mbigen.c +++ b/drivers/irqchip/irq-mbigen.c @@ -15,6 +15,14 @@ #include <linux/of_platform.h> #include <linux/platform_device.h> #include <linux/slab.h> +#include <linux/list.h> +#include <linux/cpumask.h> +#include <asm/smp_plat.h> +#include <asm/cputype.h> +#include <asm/barrier.h> + +#include <clocksource/arm_arch_timer.h> +#include <linux/irqchip/arm-gic-v3.h>
/* Interrupt numbers per mbigen node supported */ #define IRQS_PER_MBIGEN_NODE 128 @@ -55,6 +63,61 @@ #define REG_MBIGEN_SPI_TYPE_OFFSET 0x400 #define REG_MBIGEN_LPI_TYPE_OFFSET 0x0
+/** + * MBIX control register + * bit[31:26] SocketID + * bit[25:24] DieID + */ +#define MBIGEN_CTLR 0x0 +#define MBIGEN_AFF3_MASK 0xff000000 +#define MBIGEN_AFF3_SHIFT 24 + +/** + * MBIX config register + * bit[31:24] mbi_type: + * - 0x10b support vTimer irqbypass + */ +#define MBIGEN_NODE_CFG_OFFSET 0x0004 +#define MBIGEN_TYPE_MASK 0x03000000 +#define MBIGEN_TYPE_SHIFT 24 +#define TYPE_VITMER_ENABLED 0x02 + +#define REG_VTIMER_MBIGEN_WIDTH 0x0004 +#define PPIS_PER_MBIGEN_NODE 32 +#define REG_VTIMER_MBIGEN_TYPE_OFFSET 0x1000 +#define REG_VTIMER_MBIGEN_SET_AUTO_CLR_OFFSET 0x1100 +#define REG_VTIMER_MBIGEN_CLR_AUTO_CLR_OFFSET 0x1110 +#define REG_VTIMER_MBIGEN_ATV_STAT_OFFSET 0x1120 +#define REG_VTIMER_MBIGEN_VEC_OFFSET 0x1200 +#define REG_VTIMER_MBIGEN_ATV_CLR_OFFSET 0xa008 + +/** + * Due to the existence of hyper-threading technology, We need to get the + * absolute offset of a cpu relative to the base cpu. + */ +#define GICR_LENGTH 0x40000 +#define get_abs_offset(cpu, cpu_base) \ + ((get_gicr_paddr(cpu) - get_gicr_paddr(cpu_base)) / GICR_LENGTH) + +/** + * struct vtimer_mbigen_device - holds the information of vtimer mbigen device. + * + * @base: mapped address of this mbigen chip. + * @cpu_base : the base cpu_id attached to the mbigen chip. + * @cpu_num : the num of the cpus attached to the mbigen chip. + * @mpidr_aff3 : [socket_id : die_id] of the mbigen chip. + * @entry: list_head connecting this vtimer_mbigen to the full list. + * @vmgn_lock: spinlock for set type. + */ +struct vtimer_mbigen_device { + void __iomem *base; + int cpu_base; + int cpu_num; + int mpidr_aff3; + struct list_head entry; + spinlock_t vmgn_lock; +}; + /** * struct mbigen_device - holds the information of mbigen device. * @@ -62,10 +125,154 @@ * @base: mapped address of this mbigen chip. */ struct mbigen_device { - struct platform_device *pdev; - void __iomem *base; + struct platform_device *pdev; + void __iomem *base; + bool vtimer_bypass_enabled; + struct vtimer_mbigen_device *vtimer_mbigen_chip; };
+static struct arch_timer_kvm_info *vtimer_kvm_info; + +static LIST_HEAD(vtimer_mgn_list); + +cpumask_t vtimer_cpu_mask; + +static struct vtimer_mbigen_device *get_vtimer_mbigen(int cpu_id) +{ + unsigned int mpidr_aff3; + struct vtimer_mbigen_device *chip; + + mpidr_aff3 = MPIDR_AFFINITY_LEVEL(cpu_logical_map(cpu_id), 3); + + list_for_each_entry(chip, &vtimer_mgn_list, entry) { + if (chip->mpidr_aff3 == mpidr_aff3) + return chip; + } + + pr_debug("Failed to get vtimer mbigen of cpu%d!\n", cpu_id); + return NULL; +} + +void vtimer_mbigen_set_vector(int cpu_id, u16 vpeid) +{ + + struct vtimer_mbigen_device *chip; + void __iomem *addr; + int cpu_abs_offset, count = 100; + + chip = get_vtimer_mbigen(cpu_id); + if (!chip) + return; + + cpu_abs_offset = get_abs_offset(cpu_id, chip->cpu_base); + addr = chip->base + REG_VTIMER_MBIGEN_VEC_OFFSET + + cpu_abs_offset * REG_VTIMER_MBIGEN_WIDTH; + + writel_relaxed(vpeid, addr); + + /* Make sure correct vpeid set */ + do { + if (readl_relaxed(addr) == vpeid) + break; + } while (count--); + + if (!count) + pr_err("Failed to set mbigen vector of CPU%d!\n", cpu_id); +} + +bool vtimer_mbigen_get_active(int cpu_id) +{ + struct vtimer_mbigen_device *chip; + void __iomem *addr; + int cpu_abs_offset; + u32 val; + + chip = get_vtimer_mbigen(cpu_id); + if (!chip) + return false; + + cpu_abs_offset = get_abs_offset(cpu_id, chip->cpu_base); + addr = chip->base + REG_VTIMER_MBIGEN_ATV_STAT_OFFSET + + (cpu_abs_offset / PPIS_PER_MBIGEN_NODE) * REG_VTIMER_MBIGEN_WIDTH; + + dsb(sy); + val = readl_relaxed(addr); + return (!!(val & (1 << (cpu_abs_offset % PPIS_PER_MBIGEN_NODE)))); +} + +void vtimer_mbigen_set_auto_clr(int cpu_id, bool set) +{ + struct vtimer_mbigen_device *chip; + void __iomem *addr; + int cpu_abs_offset; + u64 offset; + u32 val; + + chip = get_vtimer_mbigen(cpu_id); + if (!chip) + return; + + cpu_abs_offset = get_abs_offset(cpu_id, chip->cpu_base); + offset = set ? REG_VTIMER_MBIGEN_SET_AUTO_CLR_OFFSET : + REG_VTIMER_MBIGEN_CLR_AUTO_CLR_OFFSET; + addr = chip->base + offset + + (cpu_abs_offset / PPIS_PER_MBIGEN_NODE) * REG_VTIMER_MBIGEN_WIDTH; + val = 1 << (cpu_abs_offset % PPIS_PER_MBIGEN_NODE); + + writel_relaxed(val, addr); + dsb(sy); +} + +void vtimer_mbigen_set_active(int cpu_id, bool set) +{ + struct vtimer_mbigen_device *chip; + void __iomem *addr; + int cpu_abs_offset; + u64 offset; + u32 val; + + chip = get_vtimer_mbigen(cpu_id); + if (!chip) + return; + + cpu_abs_offset = get_abs_offset(cpu_id, chip->cpu_base); + offset = set ? REG_VTIMER_MBIGEN_ATV_STAT_OFFSET : + REG_VTIMER_MBIGEN_ATV_CLR_OFFSET; + addr = chip->base + offset + + (cpu_abs_offset / PPIS_PER_MBIGEN_NODE) * REG_VTIMER_MBIGEN_WIDTH; + val = 1 << (cpu_abs_offset % PPIS_PER_MBIGEN_NODE); + + writel_relaxed(val, addr); + dsb(sy); +} + +static int vtimer_mbigen_set_type(unsigned int cpu_id) +{ + struct vtimer_mbigen_device *chip = get_vtimer_mbigen(cpu_id); + void __iomem *addr; + int cpu_abs_offset; + u32 val, mask; + + chip = get_vtimer_mbigen(cpu_id); + if (!chip) + return -EINVAL; + + cpu_abs_offset = get_abs_offset(cpu_id, chip->cpu_base); + addr = chip->base + REG_VTIMER_MBIGEN_TYPE_OFFSET + + (cpu_abs_offset / PPIS_PER_MBIGEN_NODE) * REG_VTIMER_MBIGEN_WIDTH; + + mask = 1 << (cpu_abs_offset % PPIS_PER_MBIGEN_NODE); + + spin_lock(&chip->vmgn_lock); + val = readl_relaxed(addr); + val |= mask; + writel_relaxed(val, addr); + dsb(sy); + spin_unlock(&chip->vmgn_lock); + return 0; +} + + static inline unsigned int get_mbigen_vec_reg(irq_hw_number_t hwirq) { unsigned int nid, pin; @@ -341,6 +548,150 @@ static inline int mbigen_acpi_create_domain(struct platform_device *pdev, } #endif
+static void vtimer_mbigen_set_kvm_info(void) +{ + vtimer_kvm_info = arch_timer_get_kvm_info(); + + vtimer_kvm_info->irqbypass_flag |= VT_EXPANDDEV_PROBED; +} + +static int vtimer_mbigen_chip_get_aff3(struct vtimer_mbigen_device *chip) +{ + void __iomem *base = chip->base; + void __iomem *addr = base + MBIGEN_CTLR; + u32 val = readl_relaxed(addr); + + return ((val & MBIGEN_AFF3_MASK) >> MBIGEN_AFF3_SHIFT); +} + +static int vtimer_mbigen_chip_match_cpu(struct vtimer_mbigen_device *chip) +{ + int cpu; + chip->cpu_base = -1; + chip->cpu_num = 0; + + for_each_possible_cpu(cpu) { + int mpidr_aff3 = MPIDR_AFFINITY_LEVEL(cpu_logical_map(cpu), 3); + if (chip->mpidr_aff3 == mpidr_aff3) { + /* get the first cpu attached to the mbigen */ + if (chip->cpu_base == -1) { + /* Make sure cpu_base is attached to PIN0 */ + u64 mpidr = cpu_logical_map(cpu); + if (MPIDR_AFFINITY_LEVEL(mpidr, 2) + || MPIDR_AFFINITY_LEVEL(mpidr, 1) + || MPIDR_AFFINITY_LEVEL(mpidr, 0)) + return -EINVAL; + + chip->cpu_base = cpu; + } + + chip->cpu_num++; + /* + * check if this cpu has already + * attached to another mbigen + */ + if (cpumask_test_and_set_cpu(cpu, &vtimer_cpu_mask)) + return -EINVAL; + } + } + + if (chip->cpu_base == -1 || chip->cpu_num > IRQS_PER_MBIGEN_NODE) + return -EINVAL; + + return 0; +} + +static void vtimer_mbigen_chip_set_type(struct vtimer_mbigen_device *chip) +{ + int cpuid_start, cpuid_end, cpuid; + + cpuid_start = chip->cpu_base; + cpuid_end = chip->cpu_base + chip->cpu_num; + + for (cpuid = cpuid_start; cpuid < cpuid_end; cpuid++) + vtimer_mbigen_set_type(cpuid); +} + +static bool is_mbigen_vtimer_bypass_enabled(struct mbigen_device *mgn_chip) +{ + void __iomem *base = mgn_chip->base; + void __iomem *addr = base + MBIGEN_NODE_CFG_OFFSET; + u32 val = readl_relaxed(addr); + + return ((val & MBIGEN_TYPE_MASK) >> MBIGEN_TYPE_SHIFT) + == TYPE_VITMER_ENABLED; +} + +/** + * MBIX_VPPI_ITS_TA: Indicates the address of the ITS corresponding + * to the mbigen. + */ +#define MBIX_VPPI_ITS_TA 0x0038 +static bool vtimer_mbigen_should_probe(struct mbigen_device *mgn_chip) +{ + unsigned int mpidr_aff3; + struct vtimer_mbigen_device *chip; + void __iomem *addr; + u32 val; + + addr = mgn_chip->base + MBIX_VPPI_ITS_TA; + val = readl_relaxed(addr); + if (!val) + return false; + + addr = mgn_chip->base + MBIGEN_CTLR; + val = readl_relaxed(addr); + mpidr_aff3 = (val & MBIGEN_AFF3_MASK) >> MBIGEN_AFF3_SHIFT; + list_for_each_entry(chip, &vtimer_mgn_list, entry) { + if (chip->mpidr_aff3 == mpidr_aff3) + return false; + } + + return true; +} + +static int vtimer_mbigen_device_probe(struct platform_device *pdev) +{ + struct mbigen_device *mgn_chip = platform_get_drvdata(pdev); + struct vtimer_mbigen_device *vtimer_mgn_chip; + int err; + + mgn_chip->vtimer_bypass_enabled = + is_mbigen_vtimer_bypass_enabled(mgn_chip); + + if (!mgn_chip->vtimer_bypass_enabled || + !vtimer_mbigen_should_probe(mgn_chip)) + return 0; + + vtimer_mgn_chip = kzalloc(sizeof(*vtimer_mgn_chip), GFP_KERNEL); + if (!vtimer_mgn_chip) + return -ENOMEM; + + mgn_chip->vtimer_mbigen_chip = vtimer_mgn_chip; + vtimer_mgn_chip->base = mgn_chip->base; + vtimer_mgn_chip->mpidr_aff3 = vtimer_mbigen_chip_get_aff3(vtimer_mgn_chip); + err = vtimer_mbigen_chip_match_cpu(vtimer_mgn_chip); + if (err) { + dev_err(&pdev->dev, + "Fail to match vtimer mbigen device with cpu\n"); + goto out; + } + + spin_lock_init(&vtimer_mgn_chip->vmgn_lock); + list_add(&vtimer_mgn_chip->entry, &vtimer_mgn_list); + vtimer_mbigen_set_kvm_info(); + vtimer_mbigen_chip_set_type(vtimer_mgn_chip); + + pr_info("vtimer mbigen device @%p probed success!\n", mgn_chip->base); + return 0; + +out: + kfree(vtimer_mgn_chip); + dev_err(&pdev->dev, "vtimer mbigen device @%p probed failed\n", + mgn_chip->base); + return err; +} + static int mbigen_device_probe(struct platform_device *pdev) { struct mbigen_device *mgn_chip; @@ -377,7 +728,8 @@ static int mbigen_device_probe(struct platform_device *pdev) }
platform_set_drvdata(pdev, mgn_chip); - return 0; + + return vtimer_mbigen_device_probe(pdev); }
static const struct of_device_id mbigen_of_match[] = { diff --git a/include/clocksource/arm_arch_timer.h b/include/clocksource/arm_arch_timer.h index 6b085710ec68..ea2179cfe1c3 100644 --- a/include/clocksource/arm_arch_timer.h +++ b/include/clocksource/arm_arch_timer.h @@ -64,6 +64,10 @@ struct arch_timer_kvm_info { struct timecounter timecounter; int virtual_irq; int physical_irq; + +/* vtimer expand device probed flag */ +#define VT_EXPANDDEV_PROBED (1 << 0) + unsigned long irqbypass_flag; };
struct arch_timer_mem_frame { @@ -106,4 +110,17 @@ static inline bool arch_timer_evtstrm_available(void)
#endif
+static inline bool vtimer_irqbypass_hw_support(struct arch_timer_kvm_info *info) +{ + if (!(info->irqbypass_flag & VT_EXPANDDEV_PROBED)) + return false; + + return true; +} + +void vtimer_mbigen_set_vector(int cpu_id, u16 vpeid); +bool vtimer_mbigen_get_active(int cpu_id); +void vtimer_mbigen_set_auto_clr(int cpu_id, bool set); +void vtimer_mbigen_set_active(int cpu_id, bool set); + #endif diff --git a/include/linux/irqchip/arm-gic-v3.h b/include/linux/irqchip/arm-gic-v3.h index 380c83b7a89f..2f7983a7f876 100644 --- a/include/linux/irqchip/arm-gic-v3.h +++ b/include/linux/irqchip/arm-gic-v3.h @@ -698,6 +698,8 @@ int its_init(struct fwnode_handle *handle, struct rdists *rdists, struct irq_domain *domain); int mbi_init(struct fwnode_handle *fwnode, struct irq_domain *parent);
+phys_addr_t get_gicr_paddr(int cpu); + static inline bool gic_enable_sre(void) { u32 val;