diff --git a/arch/sw_64/Kconfig b/arch/sw_64/Kconfig index e684eee875f3cc0c7bf5ad20b885a21d1f3d32db..5d14dde720f5cd6ac350fdab6f897d99e763402d 100644 --- a/arch/sw_64/Kconfig +++ b/arch/sw_64/Kconfig @@ -624,7 +624,7 @@ endchoice config FORCE_MAX_ZONEORDER int - default "16" if (HUGETLB_PAGE) + default "16" if (HUGETLB_PAGE && SUBARCH_C3B) default "11" help The kernel memory allocator divides physically contiguous memory diff --git a/arch/sw_64/include/asm/Kbuild b/arch/sw_64/include/asm/Kbuild index d08f0b08918efb2a28d0caaa2eb420c9864e6bff..e6096a105ab8ff13f806c1251b2458d3ef0d9d17 100644 --- a/arch/sw_64/include/asm/Kbuild +++ b/arch/sw_64/include/asm/Kbuild @@ -2,7 +2,6 @@ generic-y += clkdev.h generic-y += export.h -generic-y += kvm_types.h generic-y += local64.h generic-y += mcs_spinlock.h generic-y += param.h diff --git a/arch/sw_64/include/asm/irq_impl.h b/arch/sw_64/include/asm/irq_impl.h index 23cade0b5d7376b56f73d15272e567612e450828..9fe780660329384a4cd4b16c4a00e9d1a999e6c6 100644 --- a/arch/sw_64/include/asm/irq_impl.h +++ b/arch/sw_64/include/asm/irq_impl.h @@ -37,6 +37,7 @@ enum sw64_irq_type { INT_FAULT = 10, INT_VT_SERIAL = 12, INT_VT_HOTPLUG = 13, + INT_VT_GPIOA_PIN0 = 15, INT_DEV = 17, INT_NMI = 18, INT_LEGACY = 31, diff --git a/arch/sw_64/include/asm/kexec.h b/arch/sw_64/include/asm/kexec.h index 25e0d8da84f8dbe98908179bb061ea5f4759aa6e..1a63899f9c11437c3f46b0483233b8c89be58c78 100644 --- a/arch/sw_64/include/asm/kexec.h +++ b/arch/sw_64/include/asm/kexec.h @@ -72,11 +72,21 @@ static inline void crash_setup_regs(struct pt_regs *newregs, /* Function pointer to optional machine-specific reinitialization */ extern void (*kexec_reinit)(void); +extern void __init reserve_crashkernel(void); +extern void __init kexec_control_page_init(void); + #endif /* __ASSEMBLY__ */ struct kimage; extern unsigned long kexec_args[4]; +#else /* CONFIG_KEXEC */ + +#ifndef __ASSEMBLY__ +static inline void __init reserve_crashkernel(void) {} +static inline void __init kexec_control_page_init(void) {} +#endif + #endif /* CONFIG_KEXEC */ #endif /* _ASM_SW64_KEXEC_H */ diff --git a/arch/sw_64/include/asm/kvm.h b/arch/sw_64/include/asm/kvm.h index 9242d58352f0cf788057711673a233541137e210..c728504d219e3d38a06a4ba7abfff9f3868b8c9e 100644 --- a/arch/sw_64/include/asm/kvm.h +++ b/arch/sw_64/include/asm/kvm.h @@ -8,7 +8,7 @@ */ #define SWVM_IRQS 256 #define IRQ_PENDING_INTX_SHIFT 16 -#define IRQ_PENDING_MSI_VECTORS_SHIFT 17 +#define IRQ_PENDING_MSI_VECTORS_SHIFT 18 #define SWVM_NUM_NUMA_MEMBANKS 1 diff --git a/arch/sw_64/include/asm/kvm_host.h b/arch/sw_64/include/asm/kvm_host.h index acaa42d1ec607c1e5a8245f99175a51be999cacf..c664bf232ccc53b797cabe23ffc979bebb683474 100644 --- a/arch/sw_64/include/asm/kvm_host.h +++ b/arch/sw_64/include/asm/kvm_host.h @@ -76,17 +76,6 @@ struct kvm_arch { pgd_t *pgd; }; -#define KVM_NR_MEM_OBJS 40 - -/* - * We don't want allocation failures within the mmu code, so we preallocate - * enough memory for a single page fault in a cache. - */ -struct kvm_mmu_memory_cache { - int nobjs; - void *objects[KVM_NR_MEM_OBJS]; -}; - struct kvm_vcpu_arch { struct kvm_regs regs __attribute__((__aligned__(32))); struct vcpucb vcb; diff --git a/arch/sw_64/include/asm/kvm_mmu.h b/arch/sw_64/include/asm/kvm_mmu.h index f137e1c39e0c935da03b2c02d7da7ee45c582781..94fe863ccafd4da119d842b6081ca766b0d48576 100644 --- a/arch/sw_64/include/asm/kvm_mmu.h +++ b/arch/sw_64/include/asm/kvm_mmu.h @@ -29,7 +29,7 @@ #define AF_STATUS_FOE 0x8 #define AF_STATUS_INV 0x10 -#define KVM_MMU_CACHE_MIN_PAGES 2 +#define KVM_MMU_CACHE_MIN_PAGES 3 static inline void kvm_set_aptpte_readonly(pte_t *pte) { diff --git a/arch/sw_64/include/asm/kvm_types.h b/arch/sw_64/include/asm/kvm_types.h new file mode 100644 index 0000000000000000000000000000000000000000..c527d02eaddbe04661ecaeed533873d2b60f59f0 --- /dev/null +++ b/arch/sw_64/include/asm/kvm_types.h @@ -0,0 +1,7 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#ifndef _ASM_SW64_KVM_TYPES_H +#define _ASM_SW64_KVM_TYPES_H + +#define KVM_ARCH_NR_OBJS_PER_MEMORY_CACHE 40 + +#endif /* _ASM_SW64_KVM_TYPES_H */ diff --git a/arch/sw_64/include/asm/memory.h b/arch/sw_64/include/asm/memory.h index d3191165c7b5df0aabbe2a81479d508b38736668..f0a20cbb096cd9396b003730f0bd77c81c45e1be 100644 --- a/arch/sw_64/include/asm/memory.h +++ b/arch/sw_64/include/asm/memory.h @@ -25,7 +25,6 @@ struct numa_node_desc_t { extern struct numa_node_desc_t numa_nodes_desc[]; void __init callback_init(void); -void __init mem_detect(void); void __init sw64_memblock_init(void); void __init zone_sizes_init(void); void __init sw64_numa_init(void); diff --git a/arch/sw_64/include/asm/msi.h b/arch/sw_64/include/asm/msi.h index ce3edf7de80365a24b98ab86127a2dade54c20b8..0573856f7bc3f59557f1735a7a345579d7008c18 100644 --- a/arch/sw_64/include/asm/msi.h +++ b/arch/sw_64/include/asm/msi.h @@ -23,10 +23,14 @@ #define VT_MSIX_ADDR_DEST_ID(dest) \ (((dest) << VT_MSIX_ADDR_DEST_ID_SHIFT) & VT_MSIX_ADDR_DEST_ID_MASK) +enum irq_alloc_type; #ifdef CONFIG_PCI_MSI -extern void vt_sw64_vector_free_irqs(unsigned int virq, unsigned int nr_irqs); -extern int sw64_setup_vt_msi_irqs(struct pci_dev *dev, int nvec, int type); +extern void vt_handle_pci_msi_interrupt(unsigned long type, + unsigned long vector, + unsigned long pci_msi1_addr); +extern void sw64_init_vt_msi_domain(struct irq_domain *parent); +extern int sw64_setup_vt_msi_irqs(struct pci_dev *pdev, int nvec, int type); extern bool find_free_cpu_vector(const struct cpumask *search_mask, int *found_cpu, int *found_vector); extern int msi_compose_msg(unsigned int irq, struct msi_msg *msg); @@ -49,8 +53,7 @@ struct sw64_msi_chip_data { unsigned long msi_config; unsigned long msiaddr; }; - unsigned long rc_node; - unsigned long rc_index; + struct pci_controller *hose; unsigned int msi_config_index; unsigned int dst_cpu; unsigned int vector; diff --git a/arch/sw_64/include/asm/pci.h b/arch/sw_64/include/asm/pci.h index f3d8a25190f0b21d3236f9e607eb03edb74f694e..5d6fe555c5d9d2ca0bffa86022a136154338f07d 100644 --- a/arch/sw_64/include/asm/pci.h +++ b/arch/sw_64/include/asm/pci.h @@ -51,6 +51,8 @@ struct pci_controller { /* This one's for the kernel only. It's in KSEG somewhere. */ void __iomem *ep_config_space_base; void __iomem *rc_config_space_base; + void __iomem *piu_ior0_base; + void __iomem *piu_ior1_base; unsigned long index; unsigned long node; @@ -83,6 +85,7 @@ struct pci_controller { extern void __init sw64_init_pci(void); extern void __init sw64_device_interrupt(unsigned long vector); +extern void setup_intx_irqs(struct pci_controller *hose); extern void __init sw64_init_irq(void); extern void __init sw64_init_arch(void); extern struct pci_ops sw64_pci_ops; @@ -163,6 +166,10 @@ extern void pci_remove_resource_files(struct pci_dev *dev); extern void __init reserve_mem_for_pci(void); extern int chip_pcie_configure(struct pci_controller *hose); +#define PCI_INTX_ENABLE ((1UL) << 62) +#define PCI_INTX_DISABLE ~((1UL) << 62) +#define PCI_INTX_VALID (1UL << 63) + #define PCI_VENDOR_ID_JN 0x5656 #define PCI_DEVICE_ID_SW64_ROOT_BRIDGE 0x3231 #define PCI_DEVICE_ID_JN_PCIESW 0x1000 diff --git a/arch/sw_64/include/asm/sw64_init.h b/arch/sw_64/include/asm/sw64_init.h index 546be1a3525023327fbcc17d6ebcc40c6a764a97..2d553242487d86e37e69faac451483d57ca53f05 100644 --- a/arch/sw_64/include/asm/sw64_init.h +++ b/arch/sw_64/include/asm/sw64_init.h @@ -17,10 +17,8 @@ struct sw64_pci_init_ops { int (*map_irq)(const struct pci_dev *dev, u8 slot, u8 pin); unsigned long (*get_rc_enable)(unsigned long node); void (*hose_init)(struct pci_controller *hose); - void (*set_rc_piu)(unsigned long node, unsigned long index); - int (*check_pci_linkup)(unsigned long node, unsigned long index); - void (*set_intx)(unsigned long node, unsigned long index, - unsigned long int_conf); + void (*set_rc_piu)(struct pci_controller *hose); + int (*check_pci_linkup)(struct pci_controller *hose); }; diff --git a/arch/sw_64/include/asm/sw64io.h b/arch/sw_64/include/asm/sw64io.h index d52cd8cc86bf24aa3a7b45356bcbbf2651cf7b01..e9c4a3be95ef08ba088d76496f4656586d90d965 100644 --- a/arch/sw_64/include/asm/sw64io.h +++ b/arch/sw_64/include/asm/sw64io.h @@ -20,66 +20,6 @@ #define MK_PIU_IOR1(nid, idx) \ (SW64_PCI_IO_BASE((nid), (idx)) | PCI_IOR1_BASE) -static inline unsigned int -read_rc_conf(unsigned long node, unsigned long rc, - unsigned int offset) -{ - void __iomem *addr; - - addr = __va(MK_RC_CFG(node, rc) | offset); - return readl(addr); -} - -static inline void -write_rc_conf(unsigned long node, unsigned long rc, - unsigned int offset, unsigned int data) -{ - void __iomem *addr; - - addr = __va(MK_RC_CFG(node, rc) | offset); - writel(data, addr); -} - -static inline unsigned long -read_piu_ior0(unsigned long node, unsigned long rc, - unsigned int reg) -{ - void __iomem *addr; - - addr = __va(MK_PIU_IOR0(node, rc) + reg); - return readq(addr); -} - -static inline void -write_piu_ior0(unsigned long node, unsigned long rc, - unsigned int reg, unsigned long data) -{ - void __iomem *addr; - - addr = __va(MK_PIU_IOR0(node, rc) + reg); - writeq(data, addr); -} - -static inline unsigned long -read_piu_ior1(unsigned long node, unsigned long rc, - unsigned int reg) -{ - void __iomem *addr; - - addr = __va(MK_PIU_IOR1(node, rc) + reg); - return readq(addr); -} - -static inline void -write_piu_ior1(unsigned long node, unsigned long rc, - unsigned int reg, unsigned long data) -{ - void __iomem *addr; - - addr = __va(MK_PIU_IOR1(node, rc) + reg); - writeq(data, addr); -} - static inline unsigned long sw64_io_read(unsigned long node, unsigned long reg) { diff --git a/arch/sw_64/include/asm/uncore_io_junzhang.h b/arch/sw_64/include/asm/uncore_io_junzhang.h index 2f745fe353658e2c838cf616a40ca5008f26b5d0..8c14890e5c1dd585c83e2d04cb4a92b3160643e8 100644 --- a/arch/sw_64/include/asm/uncore_io_junzhang.h +++ b/arch/sw_64/include/asm/uncore_io_junzhang.h @@ -64,6 +64,7 @@ #define PME_ENABLE_INTD_CORE0 (0x1UL << 62 | 0x8UL << 10) #define AER_ENABLE_INTD_CORE0 (0x1UL << 62 | 0x8UL << 10) +#define HP_ENABLE_INTD_CORE0 (0x1UL << 62 | 0x8UL << 10) #define PIUCONFIG0_INIT_VAL 0x38016 @@ -112,12 +113,15 @@ enum { SI_FAULT_STAT_EN = SPBU_BASE | 0x3180UL, SI_FAULT_INT_EN = SPBU_BASE | 0x3200UL, ADR_CTL = SPBU_BASE | 0x3600UL, + PIUH_CTRL = SPBU_BASE | 0x3680UL, MC_ONLINE = SPBU_BASE | 0x3780UL, CLK_CTL = SPBU_BASE | 0x3b80UL, CLU_LV2_SELH = SPBU_BASE | 0x3a00UL, CLU_LV2_SELL = SPBU_BASE | 0x3b00UL, PIU_TOP0_CONFIG = SPBU_BASE | 0x4c80UL, PIU_TOP1_CONFIG = SPBU_BASE | 0x4d00UL, + PIU_PHY_SRST_H = SPBU_BASE | 0x6280UL, + BUTTON_RST_N_PCIE0 = SPBU_BASE | 0x6a80UL, SOFT_INFO0 = SPBU_BASE | 0xa000UL, }; @@ -140,6 +144,8 @@ enum { PMEMSICONFIG = 0xa580UL, HPINTCONFIG = 0xa600UL, HPMSICONFIG = 0xa680UL, + HP_CTRL = 0xac80UL, + HP_WATCHOUT = 0xae00UL, DTBASEADDR = 0xb000UL, DTLB_FLUSHALL = 0xb080UL, DTLB_FLUSHDEV = 0xb100UL, @@ -165,6 +171,7 @@ enum { /* PIU IOR1 */ enum { PIUCONFIG1 = 0x0UL, + NEWLTSSMSTATE0 = 0x300UL, ERRENABLE = 0x880UL, RCDEBUGINF1 = 0xc80UL, DCACONTROL = 0x1a00UL, diff --git a/arch/sw_64/include/uapi/asm/kvm.h b/arch/sw_64/include/uapi/asm/kvm.h index 2ae0cc74bb2bf1b613564c31d354b008999322b4..3215aaa5f2eb0ea7478d4a08275c676ced1cde65 100644 --- a/arch/sw_64/include/uapi/asm/kvm.h +++ b/arch/sw_64/include/uapi/asm/kvm.h @@ -7,7 +7,7 @@ */ #define SWVM_IRQS 256 #define IRQ_PENDING_INTX_SHIFT 16 -#define IRQ_PENDING_MSI_VECTORS_SHIFT 17 +#define IRQ_PENDING_MSI_VECTORS_SHIFT 18 enum SW64_KVM_IRQ { SW64_KVM_IRQ_IPI = 27, diff --git a/arch/sw_64/kernel/acpi.c b/arch/sw_64/kernel/acpi.c index 0aa21e4d98a64170b320db32377914cb0217dbd4..70be2cf59f856994779d50574a02f01d4147b7ad 100644 --- a/arch/sw_64/kernel/acpi.c +++ b/arch/sw_64/kernel/acpi.c @@ -8,19 +8,19 @@ #include -int acpi_disabled = 1; +int acpi_disabled; EXPORT_SYMBOL(acpi_disabled); -int acpi_noirq = 1; /* skip ACPI IRQ initialization */ -int acpi_pci_disabled = 1; /* skip ACPI PCI scan and IRQ initialization */ +int acpi_noirq; /* skip ACPI IRQ initialization */ +int acpi_pci_disabled; /* skip ACPI PCI scan and IRQ initialization */ EXPORT_SYMBOL(acpi_pci_disabled); static bool param_acpi_on __initdata; static bool param_acpi_off __initdata; -static unsigned int possible_cores = 1; /* number of possible cores(at least boot core) */ -static unsigned int present_cores = 1; /* number of present cores(at least boot core) */ -static unsigned int disabled_cores; /* number of disabled cores */ +static unsigned int possible_cores; /* number of possible cores */ +static unsigned int present_cores; /* number of present cores */ +static unsigned int disabled_cores; /* number of disabled cores */ int acpi_strict; u64 arch_acpi_wakeup_start; @@ -257,12 +257,6 @@ setup_rcid_and_core_mask(struct acpi_madt_sw_cintc *sw_cintc) return -EINVAL; } - /* We can never disable the boot core, whose rcid is 0 */ - if ((rcid == 0) && !is_core_enabled(sw_cintc->flags)) { - pr_err(PREFIX "Boot core disabled in MADT\n"); - return -EINVAL; - } - /* Online capable makes core possible */ if (!is_core_enabled(sw_cintc->flags) && !is_core_online_capable(sw_cintc->flags)) { @@ -270,13 +264,9 @@ setup_rcid_and_core_mask(struct acpi_madt_sw_cintc *sw_cintc) return 0; } - rcid_information_init(sw_cintc->version); + logical_core_id = possible_cores++; - /* The logical core ID of the boot core must be 0 */ - if (rcid == 0) - logical_core_id = 0; - else - logical_core_id = possible_cores++; + rcid_information_init(sw_cintc->version); set_rcid_map(logical_core_id, rcid); set_cpu_possible(logical_core_id, true); @@ -291,8 +281,7 @@ setup_rcid_and_core_mask(struct acpi_madt_sw_cintc *sw_cintc) if (is_core_enabled(sw_cintc->flags) && !cpumask_test_cpu(logical_core_id, &cpu_offline)) { set_cpu_present(logical_core_id, true); - if (logical_core_id != 0) - present_cores++; + present_cores++; } return 0; @@ -365,12 +354,18 @@ void __init acpi_boot_table_init(void) } /** - * ACPI is disabled by default. - * ACPI is only enabled when firmware passes ACPI table - * and sets boot parameter "acpi=on". + * ACPI is enabled by default. + * + * ACPI is disabled only when firmware explicitly passes + * the boot cmdline "acpi=off". + * + * Note: If no valid ACPI table is found, it will eventually + * be disabled. */ if (param_acpi_on) enable_acpi(); + else if (param_acpi_off) + disable_acpi(); /* * If acpi_disabled, bail out diff --git a/arch/sw_64/kernel/chip_setup.c b/arch/sw_64/kernel/chip_setup.c index 60373429a64ea989ae1ee7a98985842c33b3f6e9..c6374ae18138c302fd8862211c192977f93b4ff6 100644 --- a/arch/sw_64/kernel/chip_setup.c +++ b/arch/sw_64/kernel/chip_setup.c @@ -78,38 +78,39 @@ static void pcie_save(void) { struct pci_controller *hose; struct piu_saved *piu_save; - unsigned long node, index; unsigned long i; + void __iomem *piu_ior0_base; + void __iomem *piu_ior1_base; for (hose = hose_head; hose; hose = hose->next) { - piu_save = kzalloc(sizeof(*piu_save), GFP_KERNEL); + piu_ior0_base = hose->piu_ior0_base; + piu_ior1_base = hose->piu_ior1_base; - node = hose->node; - index = hose->index; + piu_save = kzalloc(sizeof(*piu_save), GFP_KERNEL); hose->sysdata = piu_save; - piu_save->piuconfig0 = read_piu_ior0(node, index, PIUCONFIG0); - piu_save->piuconfig1 = read_piu_ior1(node, index, PIUCONFIG1); - piu_save->epdmabar = read_piu_ior0(node, index, EPDMABAR); - piu_save->msiaddr = read_piu_ior0(node, index, MSIADDR); + piu_save->piuconfig0 = readq(piu_ior0_base + PIUCONFIG0); + piu_save->piuconfig1 = readq(piu_ior1_base + PIUCONFIG1); + piu_save->epdmabar = readq(piu_ior0_base + EPDMABAR); + piu_save->msiaddr = readq(piu_ior1_base + MSIADDR); if (IS_ENABLED(CONFIG_UNCORE_XUELANG)) { for (i = 0; i < 256; i++) { - piu_save->msiconfig[i] = read_piu_ior0(node, index, - MSICONFIG0 + (i << 7)); + piu_save->msiconfig[i] = + readq(piu_ior0_base + MSICONFIG0 + (i << 7)); } } - piu_save->iommuexcpt_ctrl = read_piu_ior0(node, index, IOMMUEXCPT_CTRL); - piu_save->dtbaseaddr = read_piu_ior0(node, index, DTBASEADDR); + piu_save->iommuexcpt_ctrl = readq(piu_ior0_base + IOMMUEXCPT_CTRL); + piu_save->dtbaseaddr = readq(piu_ior0_base + DTBASEADDR); - piu_save->intaconfig = read_piu_ior0(node, index, INTACONFIG); - piu_save->intbconfig = read_piu_ior0(node, index, INTBCONFIG); - piu_save->intcconfig = read_piu_ior0(node, index, INTCCONFIG); - piu_save->intdconfig = read_piu_ior0(node, index, INTDCONFIG); - piu_save->pmeintconfig = read_piu_ior0(node, index, PMEINTCONFIG); - piu_save->aererrintconfig = read_piu_ior0(node, index, AERERRINTCONFIG); - piu_save->hpintconfig = read_piu_ior0(node, index, HPINTCONFIG); + piu_save->intaconfig = readq(piu_ior0_base + INTACONFIG); + piu_save->intbconfig = readq(piu_ior0_base + INTBCONFIG); + piu_save->intcconfig = readq(piu_ior0_base + INTCCONFIG); + piu_save->intdconfig = readq(piu_ior0_base + INTDCONFIG); + piu_save->pmeintconfig = readq(piu_ior0_base + PMEINTCONFIG); + piu_save->aererrintconfig = readq(piu_ior0_base + AERERRINTCONFIG); + piu_save->hpintconfig = readq(piu_ior0_base + HPINTCONFIG); } } @@ -118,53 +119,57 @@ static void pcie_restore(void) { struct pci_controller *hose; struct piu_saved *piu_save; - unsigned long node, index; u32 rc_misc_ctrl; unsigned int value; unsigned long i; + void __iomem *rc_config_space_base; + void __iomem *piu_ior0_base; + void __iomem *piu_ior1_base; for (hose = hose_head; hose; hose = hose->next) { - node = hose->node; - index = hose->index; + rc_config_space_base = hose->rc_config_space_base; + piu_ior0_base = hose->piu_ior0_base; + piu_ior1_base = hose->piu_ior1_base; piu_save = hose->sysdata; - write_piu_ior0(node, index, PIUCONFIG0, piu_save->piuconfig0); - write_piu_ior1(node, index, PIUCONFIG1, piu_save->piuconfig1); - write_piu_ior0(node, index, EPDMABAR, piu_save->epdmabar); - write_piu_ior0(node, index, MSIADDR, piu_save->msiaddr); + writeq(piu_save->piuconfig0, (piu_ior0_base + PIUCONFIG0)); + writeq(piu_save->piuconfig1, (piu_ior1_base + PIUCONFIG1)); + writeq(piu_save->epdmabar, (piu_ior0_base + EPDMABAR)); + writeq(piu_save->msiaddr, (piu_ior0_base + MSIADDR)); + if (IS_ENABLED(CONFIG_UNCORE_XUELANG)) { for (i = 0; i < 256; i++) { - write_piu_ior0(node, index, MSICONFIG0 + (i << 7), - piu_save->msiconfig[i]); + writeq(piu_save->msiconfig[i], + (piu_ior0_base + (MSICONFIG0 + (i << 7)))); } } - write_piu_ior0(node, index, IOMMUEXCPT_CTRL, piu_save->iommuexcpt_ctrl); - write_piu_ior0(node, index, DTBASEADDR, piu_save->dtbaseaddr); + writeq(piu_save->iommuexcpt_ctrl, (piu_ior0_base + IOMMUEXCPT_CTRL)); + writeq(piu_save->dtbaseaddr, (piu_ior0_base + DTBASEADDR)); - write_piu_ior0(node, index, INTACONFIG, piu_save->intaconfig); - write_piu_ior0(node, index, INTBCONFIG, piu_save->intbconfig); - write_piu_ior0(node, index, INTCCONFIG, piu_save->intcconfig); - write_piu_ior0(node, index, INTDCONFIG, piu_save->intdconfig); - write_piu_ior0(node, index, PMEINTCONFIG, piu_save->pmeintconfig); - write_piu_ior0(node, index, AERERRINTCONFIG, piu_save->aererrintconfig); - write_piu_ior0(node, index, HPINTCONFIG, piu_save->hpintconfig); + writeq(piu_save->intaconfig, (piu_ior0_base + INTACONFIG)); + writeq(piu_save->intbconfig, (piu_ior0_base + INTBCONFIG)); + writeq(piu_save->intcconfig, (piu_ior0_base + INTCCONFIG)); + writeq(piu_save->intdconfig, (piu_ior0_base + INTDCONFIG)); + writeq(piu_save->pmeintconfig, (piu_ior0_base + PMEINTCONFIG)); + writeq(piu_save->aererrintconfig, (piu_ior0_base + AERERRINTCONFIG)); + writeq(piu_save->hpintconfig, (piu_ior0_base + HPINTCONFIG)); /* Enable DBI_RO_WR_EN */ - rc_misc_ctrl = read_rc_conf(node, index, RC_MISC_CONTROL_1); - write_rc_conf(node, index, RC_MISC_CONTROL_1, rc_misc_ctrl | 0x1); + rc_misc_ctrl = readl(rc_config_space_base + RC_MISC_CONTROL_1); + writel((rc_misc_ctrl | 0x1), (rc_config_space_base + RC_MISC_CONTROL_1)); /* Fix up DEVICE_ID_VENDOR_ID register */ value = (PCI_DEVICE_ID_SW64_ROOT_BRIDGE << 16) | PCI_VENDOR_ID_JN; - write_rc_conf(node, index, RC_VENDOR_ID, value); + writel(value, (rc_config_space_base + RC_VENDOR_ID)); /* Set PCI-E root class code */ - value = read_rc_conf(node, index, RC_REVISION_ID); - write_rc_conf(node, index, RC_REVISION_ID, (PCI_CLASS_BRIDGE_HOST << 16) | value); + value = readl(rc_config_space_base + RC_REVISION_ID); + writel((PCI_CLASS_BRIDGE_HOST << 16) | value, (rc_config_space_base + RC_REVISION_ID)); /* Disable DBI_RO_WR_EN */ - write_rc_conf(node, index, RC_MISC_CONTROL_1, rc_misc_ctrl); + writel(rc_misc_ctrl, (rc_config_space_base + RC_MISC_CONTROL_1)); } } diff --git a/arch/sw_64/kernel/head.S b/arch/sw_64/kernel/head.S index 15265fa4a0ea316121c5fd10ebebf464b275c172..03da6f80dd0d6c6dee1521775eefb0735875941e 100644 --- a/arch/sw_64/kernel/head.S +++ b/arch/sw_64/kernel/head.S @@ -12,14 +12,6 @@ #include #include - .macro SAVE_KTP -#ifdef CONFIG_SUBARCH_C3B - sys_call HMC_wrktp -#else - csrw $8, CSR_KTP -#endif - .endm - __HEAD .globl _stext .set noreorder @@ -120,7 +112,6 @@ __smp_callin: ldi $2, idle_task_pointer s8addl $0, $2, $2 ldl $8, 0($2) # Get ksp of idle thread - SAVE_KTP ldl $30, TASK_STACK($8) ldi $30, ASM_THREAD_SIZE($30) diff --git a/arch/sw_64/kernel/irq.c b/arch/sw_64/kernel/irq.c index bdc92b40c4c07c3f124e304718d1d6136bda7cd2..b0f15d81fab9707f2c7ac13e5c12af79b6cc22eb 100644 --- a/arch/sw_64/kernel/irq.c +++ b/arch/sw_64/kernel/irq.c @@ -108,6 +108,55 @@ void fixup_irqs(void) { irq_migrate_all_off_this_cpu(); } + +#ifdef CONFIG_SW64_IRQ_MSI +static int cpu_vector_available(int cpu) +{ + int vector, max_vector = 256; + int avl_vector = 0; + + for (vector = 0; vector < max_vector; vector++) + if (per_cpu(vector_irq, cpu)[vector] == 0) + avl_vector++; + + return avl_vector; +} + +static int cpu_vector_tomove(int cpu) +{ + int max_vector = 256; + + return max_vector - cpu_vector_available(cpu); +} + +static int vector_available(void) +{ + int cpu, avl_vector = 0; + + for_each_online_cpu(cpu) + avl_vector += cpu_vector_available(cpu); + + return avl_vector; +} + +int can_unplug_cpu(void) +{ + unsigned int free, tomove; + unsigned int cpu = smp_processor_id(); + + tomove = cpu_vector_tomove(cpu); + free = vector_available(); + if (free < tomove) { + pr_info("CPU %u has %u vectors, %u available, Cannot disable CPU\n", + cpu, tomove, free); + return -ENOSPC; + } + + return 0; +} +#else +int can_unplug_cpu(void) { return 0; } +#endif #endif void __init init_IRQ(void) diff --git a/arch/sw_64/kernel/machine_kexec.c b/arch/sw_64/kernel/machine_kexec.c index dc1b2b4f5949ff10a7ddca5f9168b4fafedf079c..a85cca14444bc8927e1ec6e0ea7acac394f18967 100644 --- a/arch/sw_64/kernel/machine_kexec.c +++ b/arch/sw_64/kernel/machine_kexec.c @@ -14,11 +14,11 @@ #include #include #include +#include #include #include -extern void *kexec_control_page; extern const unsigned char relocate_new_kernel[]; extern const size_t relocate_new_kernel_size; @@ -26,6 +26,7 @@ extern unsigned long kexec_start_address; extern unsigned long kexec_indirection_page; static atomic_t waiting_for_crash_ipi; +static void *kexec_control_page; #ifdef CONFIG_SMP extern struct smp_rcb_struct *smp_rcb; @@ -46,6 +47,72 @@ static void kexec_smp_down(void *ignored) } #endif +#define KTEXT_MAX KERNEL_IMAGE_SIZE + +void __init kexec_control_page_init(void) +{ + phys_addr_t addr; + + addr = memblock_phys_alloc_range(KEXEC_CONTROL_PAGE_SIZE, PAGE_SIZE, + 0, KTEXT_MAX); + kexec_control_page = (void *)(__START_KERNEL_map + addr); +} + +/* + * reserve_crashkernel() - reserves memory are for crash kernel + * + * This function reserves memory area given in "crashkernel=" kernel command + * line parameter. The memory reserved is used by a dump capture kernel when + * primary kernel is crashing. + */ +void __init reserve_crashkernel(void) +{ + unsigned long long crash_size, crash_base; + unsigned long long mem_size = memblock_phys_mem_size(); + int ret; + + ret = parse_crashkernel(boot_command_line, mem_size, + &crash_size, &crash_base); + if (ret || !crash_size) + return; + + if (!crash_size) { + pr_warn("size of crash kernel memory unspecified, no memory reserved for crash kernel\n"); + return; + } + if (!crash_base) { + pr_warn("base of crash kernel memory unspecified, no memory reserved for crash kernel\n"); + return; + } + + if (!memblock_is_region_memory(crash_base, crash_size)) + memblock_add(crash_base, crash_size); + + ret = memblock_reserve(crash_base, crash_size); + if (ret < 0) { + pr_warn("crashkernel reservation failed - memory is in use [mem %#018llx-%#018llx]\n", + crash_base, crash_base + crash_size - 1); + return; + } + + pr_info("Reserving %ldMB of memory at %ldMB for crashkernel (System RAM: %ldMB)\n", + (unsigned long)(crash_size >> 20), + (unsigned long)(crash_base >> 20), + (unsigned long)(mem_size >> 20)); + + ret = add_memmap_region(crash_base, crash_size, memmap_crashkernel); + if (ret) + pr_warn("Add crash kernel area [mem %#018llx-%#018llx] to memmap region failed.\n", + crash_base, crash_base + crash_size - 1); + + if (crash_base >= KERNEL_IMAGE_SIZE) + pr_warn("Crash base should be less than %#x\n", KERNEL_IMAGE_SIZE); + + crashk_res.start = crash_base; + crashk_res.end = crash_base + crash_size - 1; + insert_resource(&iomem_resource, &crashk_res); +} + int machine_kexec_prepare(struct kimage *kimage) { return 0; diff --git a/arch/sw_64/kernel/perf_event.c b/arch/sw_64/kernel/perf_event.c index a7143c40d4f24070b5c4abf2c8fd5c3d66dd4217..c0aede9321b47d8c0f6fa74d5986e61347383a2d 100644 --- a/arch/sw_64/kernel/perf_event.c +++ b/arch/sw_64/kernel/perf_event.c @@ -503,15 +503,6 @@ static int sw64_pmu_event_init(struct perf_event *event) if (has_branch_stack(event)) return -EOPNOTSUPP; - /* - * SW64 does not have per-counter usr/os/guest/host bits, - * we can distinguish exclude_user and exclude_kernel by - * sample mode. - */ - if (attr->exclude_hv || attr->exclude_idle || - attr->exclude_host || attr->exclude_guest) - return -EINVAL; - if (attr->exclude_user && attr->exclude_kernel) return -EOPNOTSUPP; /* @@ -548,6 +539,15 @@ static int sw64_pmu_event_init(struct perf_event *event) return -ENOENT; } + /* + * SW64 does not have per-counter usr/os/guest/host bits, + * we can distinguish exclude_user and exclude_kernel by + * sample mode. + */ + if (attr->exclude_hv || attr->exclude_idle || + attr->exclude_host || attr->exclude_guest) + return -EINVAL; + /* Do the real initialisation work. */ __hw_perf_event_init(event); diff --git a/arch/sw_64/kernel/perf_event_c4.c b/arch/sw_64/kernel/perf_event_c4.c index 05e8b645593377a6ce8157d98a545284a0dce26b..7a5e8deba0ee2f1e17b4e4498a9212c5695955c2 100644 --- a/arch/sw_64/kernel/perf_event_c4.c +++ b/arch/sw_64/kernel/perf_event_c4.c @@ -543,13 +543,6 @@ static int sw64_pmu_event_init(struct perf_event *event) if (has_branch_stack(event)) return -EOPNOTSUPP; - /* - * SW64 does not have per-counter usr/os/guest/host bits - */ - if (attr->exclude_hv || attr->exclude_idle || - attr->exclude_host || attr->exclude_guest) - return -EINVAL; - /* * SW64 does not support precise ip feature, and system hang when * detecting precise_ip by perf_event_attr__set_max_precise_ip @@ -581,6 +574,13 @@ static int sw64_pmu_event_init(struct perf_event *event) if (config < 0) return config; + /* + * SW64 does not have per-counter usr/os/guest/host bits + */ + if (attr->exclude_hv || attr->exclude_idle || + attr->exclude_host || attr->exclude_guest) + return -EINVAL; + hwc->config = config; /* Do the real initialisation work. */ __hw_perf_event_init(event); diff --git a/arch/sw_64/kernel/reset.c b/arch/sw_64/kernel/reset.c index 955339557a7a1d8eec504f0f85ad0241e82fc833..3f1961ce85dee6cb8f172fabd3dd48f3881b957e 100644 --- a/arch/sw_64/kernel/reset.c +++ b/arch/sw_64/kernel/reset.c @@ -27,12 +27,10 @@ void fix_jm585_reset(void) 0x0585, NULL); if (pdev) { hose = pci_bus_to_pci_controller(pdev->bus); - val = read_rc_conf(hose->node, hose->index, - RC_PORT_LINK_CTL); - write_rc_conf(hose->node, hose->index, - RC_PORT_LINK_CTL, val | 0x8); - write_rc_conf(hose->node, hose->index, - RC_PORT_LINK_CTL, val); + val = readl(hose->rc_config_space_base + RC_PORT_LINK_CTL); + writel((val | 0x8), (hose->rc_config_space_base + RC_PORT_LINK_CTL)); + writel(val, (hose->rc_config_space_base + RC_PORT_LINK_CTL)); + } } diff --git a/arch/sw_64/kernel/setup.c b/arch/sw_64/kernel/setup.c index 914f13cf0489f166f871aa346c08580218945963..7d373fc67c6255741e129cfca2a144c100550429 100644 --- a/arch/sw_64/kernel/setup.c +++ b/arch/sw_64/kernel/setup.c @@ -25,17 +25,16 @@ #include #include #include -#include #include #include #include #include -#include #include #include #include #include +#include #include "proto.h" @@ -51,19 +50,6 @@ EXPORT_SYMBOL(__cpu_to_rcid); DEFINE_PER_CPU(unsigned long, hard_node_id) = { 0 }; -#ifdef CONFIG_SUBARCH_C3B -#if defined(CONFIG_KVM) || defined(CONFIG_KVM_MODULE) -struct cma *sw64_kvm_cma; -EXPORT_SYMBOL(sw64_kvm_cma); - -static phys_addr_t kvm_mem_size; -static phys_addr_t kvm_mem_base; - -struct gen_pool *sw64_kvm_pool; -EXPORT_SYMBOL(sw64_kvm_pool); -#endif -#endif - static inline int phys_addr_valid(unsigned long addr) { /* @@ -164,79 +150,6 @@ void store_cpu_data(int cpu) cpu_data[cpu].last_asid = ASID_FIRST_VERSION; } -#ifdef CONFIG_KEXEC - -void *kexec_control_page; - -#define KTEXT_MAX KERNEL_IMAGE_SIZE - -static void __init kexec_control_page_init(void) -{ - phys_addr_t addr; - - addr = memblock_phys_alloc_range(KEXEC_CONTROL_PAGE_SIZE, PAGE_SIZE, - 0, KTEXT_MAX); - kexec_control_page = (void *)(__START_KERNEL_map + addr); -} - -/* - * reserve_crashkernel() - reserves memory are for crash kernel - * - * This function reserves memory area given in "crashkernel=" kernel command - * line parameter. The memory reserved is used by a dump capture kernel when - * primary kernel is crashing. - */ -static void __init reserve_crashkernel(void) -{ - unsigned long long crash_size, crash_base; - int ret; - - ret = parse_crashkernel(boot_command_line, mem_desc.size, - &crash_size, &crash_base); - if (ret || !crash_size) - return; - - if (!crash_size) { - pr_warn("size of crash kernel memory unspecified, no memory reserved for crash kernel\n"); - return; - } - if (!crash_base) { - pr_warn("base of crash kernel memory unspecified, no memory reserved for crash kernel\n"); - return; - } - - if (!memblock_is_region_memory(crash_base, crash_size)) - memblock_add(crash_base, crash_size); - - ret = memblock_reserve(crash_base, crash_size); - if (ret < 0) { - pr_warn("crashkernel reservation failed - memory is in use [mem %#018llx-%#018llx]\n", - crash_base, crash_base + crash_size - 1); - return; - } - - pr_info("Reserving %ldMB of memory at %ldMB for crashkernel (System RAM: %ldMB)\n", - (unsigned long)(crash_size >> 20), - (unsigned long)(crash_base >> 20), - (unsigned long)(mem_desc.size >> 20)); - - ret = add_memmap_region(crash_base, crash_size, memmap_crashkernel); - if (ret) - pr_warn("Add crash kernel area [mem %#018llx-%#018llx] to memmap region failed.\n", - crash_base, crash_base + crash_size - 1); - - if (crash_base >= KERNEL_IMAGE_SIZE) - pr_warn("Crash base should be less than %#x\n", KERNEL_IMAGE_SIZE); - - crashk_res.start = crash_base; - crashk_res.end = crash_base + crash_size - 1; - insert_resource(&iomem_resource, &crashk_res); -} -#else /* !defined(CONFIG_KEXEC) */ -static void __init reserve_crashkernel(void) {} -static void __init kexec_control_page_init(void) {} -#endif /* !defined(CONFIG_KEXEC) */ - /* * I/O resources inherited from PeeCees. Except for perhaps the * turbochannel SWs, everyone has these on some sort of SuperIO chip. @@ -493,33 +406,6 @@ subsys_initcall(request_standard_resources); extern void cpu_set_node(void); #endif -static void __init show_socket_mem_layout(void) -{ - int i; - phys_addr_t base, size, end; - - base = 0; - - pr_info("Socket memory layout:\n"); - for (i = 0; i < MAX_NUMSOCKETS; i++) { - if (socket_desc[i].is_online) { - size = socket_desc[i].socket_mem; - end = base + size - 1; - pr_info("Socket %d: [mem %#018llx-%#018llx], size %llu\n", - i, base, end, size); - base = end + 1; - } - } - pr_info("Reserved memory size for Socket 0: %#lx\n", NODE0_START); -} - -int page_is_ram(unsigned long pfn) -{ - pfn <<= PAGE_SHIFT; - - return pfn >= mem_desc.base && pfn < (mem_desc.base + mem_desc.size); -} - static int __init topology_init(void) { int i; @@ -602,13 +488,19 @@ static void __init setup_firmware_fdt(void) dt_virt = (void *)sunway_boot_params->dtb_start; } else { /** - * Use DTB provided by firmware for early initialization, - * regardless of whether a Built-in DTB configured or not. - * Since we need the boot params from firmware when using - * new method to pass boot params. + * Regardless of whether built-in DTB is configured or not, + * we always: + * + * 1. Use DTB provided by firmware for early initialization, + * since we need the boot params from firmware when using + * new method to pass boot params. + * + * 2. reserve the DTB from firmware in case it is used later. */ pr_info("Parse boot params in DTB chosen node\n"); dt_virt = (void *)sunway_dtb_address; + memblock_reserve(__boot_pa(sunway_dtb_address), + fdt_totalsize(sunway_dtb_address)); } if (!arch_dtb_verify(dt_virt, true) || @@ -800,45 +692,6 @@ static void __init setup_run_mode(void) } #endif -static void __init setup_socket_info(void) -{ - int i; - int numsockets = sw64_chip->get_cpu_num(); - - memset(socket_desc, 0, MAX_NUMSOCKETS * sizeof(struct socket_desc_t)); - - for (i = 0; i < numsockets; i++) { - socket_desc[i].is_online = 1; - if (sw64_chip_init->early_init.get_node_mem) - socket_desc[i].socket_mem = sw64_chip_init->early_init.get_node_mem(i); - } -} - -#ifdef CONFIG_SUBARCH_C3B -#if defined(CONFIG_KVM) || defined(CONFIG_KVM_MODULE) -static int __init early_kvm_reserved_mem(char *p) -{ - if (!p) { - pr_err("Config string not provided\n"); - return -EINVAL; - } - - kvm_mem_size = memparse(p, &p); - if (*p != '@') - return -EINVAL; - kvm_mem_base = memparse(p + 1, &p); - return 0; -} -early_param("kvm_mem", early_kvm_reserved_mem); - -void __init sw64_kvm_reserve(void) -{ - kvm_cma_declare_contiguous(kvm_mem_base, kvm_mem_size, 0, - PAGE_SIZE, 0, "sw64_kvm_cma", &sw64_kvm_cma); -} -#endif -#endif - void __init setup_arch(char **cmdline_p) { @@ -852,8 +705,6 @@ setup_arch(char **cmdline_p) setup_cpu_info(); setup_run_mode(); setup_chip_ops(); - setup_socket_info(); - show_socket_mem_layout(); sw64_chip_init->early_init.setup_core_map(&core_start); if (is_guest_or_emul()) get_vt_smp_info(); @@ -877,30 +728,19 @@ setup_arch(char **cmdline_p) */ parse_early_param(); - /* Find our memory. */ - mem_detect(); - -#ifdef CONFIG_PCI - reserve_mem_for_pci(); -#endif - - sw64_memblock_init(); - - reserve_crashkernel(); - - /* Reserve large chunks of memory for use by CMA for KVM. */ -#ifdef CONFIG_SUBARCH_C3B -#if defined(CONFIG_KVM) || defined(CONFIG_KVM_MODULE) - sw64_kvm_reserve(); -#endif -#endif - efi_init(); - /* After efi initialization, switch to Builtin-in DTB if configured */ + /** + * Switch to builtin-in DTB if configured. + * Must be placed after efi_init(), Since + * efi_init() may parse boot params from DTB + * provided by firmware. + */ if (IS_ENABLED(CONFIG_BUILTIN_DTB)) setup_builtin_fdt(); + sw64_memblock_init(); + /* Try to upgrade ACPI tables via initrd */ acpi_table_upgrade(); @@ -1123,49 +963,3 @@ static int __init sw64_of_init(void) core_initcall(sw64_of_init); #endif -#ifdef CONFIG_SUBARCH_C3B -#if defined(CONFIG_KVM) || defined(CONFIG_KVM_MODULE) -static int __init sw64_kvm_pool_init(void) -{ - int status = 0; - unsigned long kvm_pool_virt; - struct page *base_page, *end_page, *p; - - if (!sw64_kvm_cma) - goto out; - - kvm_pool_virt = (unsigned long)kvm_mem_base; - - sw64_kvm_pool = gen_pool_create(PAGE_SHIFT, -1); - if (!sw64_kvm_pool) - goto out; - - status = gen_pool_add_virt(sw64_kvm_pool, kvm_pool_virt, kvm_mem_base, - kvm_mem_size, -1); - if (status < 0) { - pr_err("failed to add memory chunks to sw64 kvm pool\n"); - gen_pool_destroy(sw64_kvm_pool); - sw64_kvm_pool = NULL; - goto out; - } - gen_pool_set_algo(sw64_kvm_pool, gen_pool_best_fit, NULL); - - base_page = pfn_to_page(kvm_mem_base >> PAGE_SHIFT); - end_page = pfn_to_page((kvm_mem_base + kvm_mem_size - 1) >> PAGE_SHIFT); - - p = base_page; - while (p <= end_page && page_ref_count(p) == 0) { - set_page_count(p, 1); - page_mapcount_reset(p); - SetPageReserved(p); - p++; - } - - return status; - -out: - return -ENOMEM; -} -core_initcall_sync(sw64_kvm_pool_init); -#endif -#endif diff --git a/arch/sw_64/kernel/smp.c b/arch/sw_64/kernel/smp.c index 83f46a8baa6b1dc69ab31c65f725ba560ad6065f..2ee8424867cfa70dc8fb613a52429330e2729d32 100644 --- a/arch/sw_64/kernel/smp.c +++ b/arch/sw_64/kernel/smp.c @@ -56,26 +56,55 @@ enum core_version { CORE_VERSION_RESERVED = 3 /* 3 and greater are reserved */ }; -/* - * Where secondaries begin a life of C. - */ -void smp_callin(void) -{ - int cpuid = smp_processor_id(); - #ifdef CONFIG_SUBARCH_C4 - /* LV2 select PLL1 */ +static void upshift_freq(void) +{ int i, cpu_num; + if (is_guest_or_emul()) + return; cpu_num = sw64_chip->get_cpu_num(); - for (i = 0; i < cpu_num; i++) { sw64_io_write(i, CLU_LV2_SELH, -1UL); sw64_io_write(i, CLU_LV2_SELL, -1UL); udelay(1000); } +} + +static void downshift_freq(void) +{ + unsigned long value; + int cpuid, core_id, node_id; + + if (is_guest_or_emul()) + return; + cpuid = smp_processor_id(); + core_id = rcid_to_core_id(cpu_to_rcid(cpuid)); + node_id = rcid_to_domain_id(cpu_to_rcid(cpuid)); + + if (core_id > 31) { + value = 1UL << (2 * (core_id - 32)); + sw64_io_write(node_id, CLU_LV2_SELH, value); + } else { + value = 1UL << (2 * core_id); + sw64_io_write(node_id, CLU_LV2_SELL, value); + } +} +#else +static void upshift_freq(void) { } +static void downshift_freq(void) { } #endif + +/* + * Where secondaries begin a life of C. + */ +void smp_callin(void) +{ + int cpuid; + save_ktp(); + upshift_freq(); + cpuid = smp_processor_id(); local_irq_disable(); if (cpu_online(cpuid)) { @@ -591,9 +620,17 @@ void flush_tlb_kernel_range(unsigned long start, unsigned long end) EXPORT_SYMBOL(flush_tlb_kernel_range); #ifdef CONFIG_HOTPLUG_CPU +extern int can_unplug_cpu(void); int __cpu_disable(void) { int cpu = smp_processor_id(); + int ret; + + if (is_in_host()) { + ret = can_unplug_cpu(); + if (ret) + return ret; + } set_cpu_online(cpu, false); remove_cpu_topology(cpu); @@ -622,22 +659,7 @@ void __cpu_die(unsigned int cpu) void arch_cpu_idle_dead(void) { -#ifdef CONFIG_SUBARCH_C4 - /* LV2 select PLL0 */ - int cpuid = smp_processor_id(); - int core_id = rcid_to_core_id(cpu_to_rcid(cpuid)); - int node_id = rcid_to_domain_id(cpu_to_rcid(cpuid)); - unsigned long value; - - if (core_id > 31) { - value = 1UL << (2 * (core_id - 32)); - sw64_io_write(node_id, CLU_LV2_SELH, value); - } else { - value = 1UL << (2 * core_id); - sw64_io_write(node_id, CLU_LV2_SELL, value); - } -#endif - + downshift_freq(); idle_task_exit(); mb(); __this_cpu_write(cpu_state, CPU_DEAD); diff --git a/arch/sw_64/kvm/mmu.c b/arch/sw_64/kvm/mmu.c index 737d57406e79cfb3c516ca9392fa22630c756f4c..1212c14e375d1e32b63d60e8ec3657bdae884b21 100644 --- a/arch/sw_64/kvm/mmu.c +++ b/arch/sw_64/kvm/mmu.c @@ -101,43 +101,6 @@ static void apt_dissolve_pud(struct kvm *kvm, phys_addr_t addr, pud_t *pudp) put_page(virt_to_page(pudp)); } -static int mmu_topup_memory_cache(struct kvm_mmu_memory_cache *cache, - int min, int max) -{ - void *page; - - BUG_ON(max > KVM_NR_MEM_OBJS); - if (cache->nobjs >= min) - return 0; - while (cache->nobjs < max) { - page = (void *)__get_free_page(GFP_KERNEL | __GFP_ZERO); - if (!page) - return -ENOMEM; - cache->objects[cache->nobjs++] = page; - } - return 0; -} - -static void mmu_free_memory_cache(struct kvm_mmu_memory_cache *mc) -{ - while (mc->nobjs) - free_page((unsigned long)mc->objects[--mc->nobjs]); -} - -void kvm_mmu_free_memory_caches(struct kvm_vcpu *vcpu) -{ - mmu_free_memory_cache(&vcpu->arch.mmu_page_cache); -} - -static void *mmu_memory_cache_alloc(struct kvm_mmu_memory_cache *mc) -{ - void *p; - - BUG_ON(!mc || !mc->nobjs); - p = mc->objects[--mc->nobjs]; - return p; -} - static void unmap_apt_ptes(struct kvm *kvm, pmd_t *pmd, phys_addr_t addr, phys_addr_t end) { @@ -360,7 +323,7 @@ static pud_t *apt_get_pud(pgd_t *pgd, struct kvm_mmu_memory_cache *cache, if (p4d_none(*p4d)) { if (!cache) return NULL; - pud = mmu_memory_cache_alloc(cache); + pud = kvm_mmu_memory_cache_alloc(cache); p4d_populate(NULL, p4d, pud); get_page(virt_to_page(p4d)); } @@ -380,7 +343,7 @@ static pmd_t *apt_get_pmd(struct kvm *kvm, struct kvm_mmu_memory_cache *cache, if (pud_none(*pud)) { if (!cache) return NULL; - pmd = mmu_memory_cache_alloc(cache); + pmd = kvm_mmu_memory_cache_alloc(cache); pud_populate(NULL, pud, pmd); get_page(virt_to_page(pud)); } @@ -840,7 +803,7 @@ static int apt_set_pte_fast(struct kvm_vcpu *vcpu, if (pud_none(*pud)) { if (!cache) return 0; /* ignore calls from kvm_set_spte_hva */ - pmd = mmu_memory_cache_alloc(cache); + pmd = kvm_mmu_memory_cache_alloc(cache); pud_populate(NULL, pud, pmd); get_page(virt_to_page(pud)); } @@ -866,7 +829,7 @@ static int apt_set_pte_fast(struct kvm_vcpu *vcpu, if (pmd_none(*pmd)) { if (!cache) return 0; /* ignore calls from kvm_set_spte_hva */ - pte = mmu_memory_cache_alloc(cache); + pte = kvm_mmu_memory_cache_alloc(cache); pmd_populate_kernel(NULL, pmd, pte); get_page(virt_to_page(pmd)); } @@ -928,7 +891,7 @@ static int apt_set_pte(struct kvm *kvm, struct kvm_mmu_memory_cache *cache, if (pud_none(*pud)) { if (!cache) return 0; /* ignore calls from kvm_set_spte_hva */ - pmd = mmu_memory_cache_alloc(cache); + pmd = kvm_mmu_memory_cache_alloc(cache); pud_populate(NULL, pud, pmd); get_page(virt_to_page(pud)); } @@ -953,7 +916,7 @@ static int apt_set_pte(struct kvm *kvm, struct kvm_mmu_memory_cache *cache, if (pmd_none(*pmd)) { if (!cache) return 0; /* ignore calls from kvm_set_spte_hva */ - pte = mmu_memory_cache_alloc(cache); + pte = kvm_mmu_memory_cache_alloc(cache); pmd_populate_kernel(NULL, pmd, pte); get_page(virt_to_page(pmd)); } @@ -1199,8 +1162,8 @@ static int user_mem_abort(struct kvm_vcpu *vcpu, gfn = (fault_gpa & huge_page_mask(hstate_vma(vma))) >> PAGE_SHIFT; up_read(¤t->mm->mmap_lock); /* We need minimum second+third level pages */ - ret = mmu_topup_memory_cache(memcache, KVM_MMU_CACHE_MIN_PAGES, - KVM_NR_MEM_OBJS); + ret = kvm_mmu_topup_memory_cache(memcache, KVM_MMU_CACHE_MIN_PAGES); + if (ret) return ret; diff --git a/arch/sw_64/kvm/sw64.c b/arch/sw_64/kvm/sw64.c index fa249289918cb597e50c60c0c47a8b4822a213b4..3d57d6b7a0487e61f544977df59ae26bf96a1b87 100644 --- a/arch/sw_64/kvm/sw64.c +++ b/arch/sw_64/kvm/sw64.c @@ -258,7 +258,7 @@ int kvm_arch_create_memslot(struct kvm *kvm, struct kvm_memory_slot *slot, void kvm_arch_vcpu_free(struct kvm_vcpu *vcpu) { - kvm_mmu_free_memory_caches(vcpu); + kvm_mmu_free_memory_cache(&vcpu->arch.mmu_page_cache); hrtimer_cancel(&vcpu->arch.hrt); kfree(vcpu); } @@ -273,6 +273,7 @@ int kvm_arch_vcpu_create(struct kvm_vcpu *vcpu) /* Set up the timer for Guest */ pr_info("vcpu: [%d], regs addr = %#lx, vcpucb = %#lx\n", vcpu->vcpu_id, (unsigned long)&vcpu->arch.regs, (unsigned long)&vcpu->arch.vcb); + vcpu->arch.mmu_page_cache.gfp_zero = __GFP_ZERO; vcpu->arch.vtimer_freq = cpuid(GET_CPU_FREQ, 0) * 1000UL * 1000UL; hrtimer_init(&vcpu->arch.hrt, CLOCK_REALTIME, HRTIMER_MODE_ABS); vcpu->arch.hrt.function = clockdev_fn; diff --git a/arch/sw_64/mm/init.c b/arch/sw_64/mm/init.c index e51383b3b62dcb1db30f3594a95b7a21fb068b4e..5bb0dc4cbc5418becdf058bfad8c62a548fae5bd 100644 --- a/arch/sw_64/mm/init.c +++ b/arch/sw_64/mm/init.c @@ -14,10 +14,14 @@ #include #include #include +#include #include #include #include +#include +#include +#include struct mem_desc_t mem_desc; #ifndef CONFIG_NUMA @@ -62,6 +66,13 @@ static int __init setup_mem_size(char *p) mem_start = start; mem_size_limit = size; + + if (mem_start < NODE0_START) { + mem_start = NODE0_START; + mem_size_limit -= min(mem_size_limit, + NODE0_START - mem_start); + } + return 0; } early_param("mem", setup_mem_size); @@ -135,7 +146,41 @@ void __init paging_init(void) { } -void __init mem_detect(void) +static void __init setup_socket_info(void) +{ + int i; + int numsockets = sw64_chip->get_cpu_num(); + + memset(socket_desc, 0, MAX_NUMSOCKETS * sizeof(struct socket_desc_t)); + + for (i = 0; i < numsockets; i++) { + socket_desc[i].is_online = 1; + if (sw64_chip_init->early_init.get_node_mem) + socket_desc[i].socket_mem = sw64_chip_init->early_init.get_node_mem(i); + } +} + +static void __init show_socket_mem_layout(void) +{ + int i; + phys_addr_t base, size, end; + + base = 0; + + pr_info("Socket memory layout:\n"); + for (i = 0; i < MAX_NUMSOCKETS; i++) { + if (socket_desc[i].is_online) { + size = socket_desc[i].socket_mem; + end = base + size - 1; + pr_info("Socket %d: [mem %#018llx-%#018llx], size %llu\n", + i, base, end, size); + base = end + 1; + } + } + pr_info("Reserved memory size for Socket 0: %#lx\n", NODE0_START); +} + +static void __init mem_detect(void) { int i; @@ -145,17 +190,8 @@ void __init mem_detect(void) mem_desc.phys_size += socket_desc[i].socket_mem; } - if (mem_start >= NODE0_START) { - mem_desc.base = mem_start; - } else { - mem_desc.base = NODE0_START; - mem_size_limit -= NODE0_START - mem_start; - } - - if (mem_size_limit && mem_size_limit < mem_desc.phys_size - NODE0_START) - mem_desc.size = mem_size_limit; - else - mem_desc.size = mem_desc.phys_size - NODE0_START; + mem_desc.base = NODE0_START; + mem_desc.size = mem_desc.phys_size - NODE0_START; } #ifdef CONFIG_BLK_DEV_INITRD @@ -213,14 +249,110 @@ static void __init reserve_mem_for_initrd(void) } #endif /* CONFIG_BLK_DEV_INITRD */ +#ifdef CONFIG_SUBARCH_C3B +#if defined(CONFIG_KVM) || defined(CONFIG_KVM_MODULE) +struct cma *sw64_kvm_cma; +EXPORT_SYMBOL(sw64_kvm_cma); + +static phys_addr_t kvm_mem_size; +static phys_addr_t kvm_mem_base; + +struct gen_pool *sw64_kvm_pool; +EXPORT_SYMBOL(sw64_kvm_pool); + +static int __init early_kvm_reserved_mem(char *p) +{ + if (!p) { + pr_err("Config string not provided\n"); + return -EINVAL; + } + + kvm_mem_size = memparse(p, &p); + if (*p != '@') + return -EINVAL; + kvm_mem_base = memparse(p + 1, &p); + return 0; +} +early_param("kvm_mem", early_kvm_reserved_mem); + +void __init sw64_kvm_reserve(void) +{ + kvm_cma_declare_contiguous(kvm_mem_base, kvm_mem_size, 0, + PAGE_SIZE, 0, "sw64_kvm_cma", &sw64_kvm_cma); +} + +static int __init sw64_kvm_pool_init(void) +{ + int status = 0; + unsigned long kvm_pool_virt; + struct page *base_page, *end_page, *p; + + if (!sw64_kvm_cma) + goto out; + + kvm_pool_virt = (unsigned long)kvm_mem_base; + + sw64_kvm_pool = gen_pool_create(PAGE_SHIFT, -1); + if (!sw64_kvm_pool) + goto out; + + status = gen_pool_add_virt(sw64_kvm_pool, kvm_pool_virt, kvm_mem_base, + kvm_mem_size, -1); + if (status < 0) { + pr_err("failed to add memory chunks to sw64 kvm pool\n"); + gen_pool_destroy(sw64_kvm_pool); + sw64_kvm_pool = NULL; + goto out; + } + gen_pool_set_algo(sw64_kvm_pool, gen_pool_best_fit, NULL); + + base_page = pfn_to_page(kvm_mem_base >> PAGE_SHIFT); + end_page = pfn_to_page((kvm_mem_base + kvm_mem_size - 1) >> PAGE_SHIFT); + + p = base_page; + while (p <= end_page && page_ref_count(p) == 0) { + set_page_count(p, 1); + page_mapcount_reset(p); + SetPageReserved(p); + p++; + } + + return status; + +out: + return -ENOMEM; +} +core_initcall_sync(sw64_kvm_pool_init); +#endif +#endif + void __init sw64_memblock_init(void) { - memblock_add(mem_desc.base, mem_desc.size); + /** + * Detect all memory on all nodes, used in the following + * cases: + * 1. Legacy memory detect + * 2. Legacy NUMA initialization + */ + setup_socket_info(); + show_socket_mem_layout(); + + if (sunway_boot_magic != 0xDEED2024UL) { + /* Find our usable memory */ + mem_detect(); + + /* Add usable memory */ + memblock_add(mem_desc.base, mem_desc.size); + } memblock_remove(1ULL << MAX_PHYSMEM_BITS, PHYS_ADDR_MAX); max_pfn = max_low_pfn = PFN_DOWN(memblock_end_of_DRAM()); +#ifdef CONFIG_PCI + reserve_mem_for_pci(); +#endif + memblock_allow_resize(); memblock_initialized = true; process_memmap(); @@ -233,17 +365,25 @@ void __init sw64_memblock_init(void) /* Make sure initrd is in memory range. */ reserve_mem_for_initrd(); #endif - /** - * When using non built-in DTB, we need to reserve - * it but avoid using early_init_fdt_reserve_self() - * since __pa() does not work for some old firmware. - * - * We can use early_init_fdt_reserve_self() instead - * when kernel no longer support C3B. - */ - if (!IS_ENABLED(CONFIG_BUILTIN_DTB)) - memblock_reserve(__boot_pa(initial_boot_params), - fdt_totalsize(initial_boot_params)); + +#ifdef CONFIG_SUBARCH_C3B +#if defined(CONFIG_KVM) || defined(CONFIG_KVM_MODULE) + /* Reserve large chunks of memory for use by CMA for KVM. */ + sw64_kvm_reserve(); +#endif +#endif + + reserve_crashkernel(); + + /* All memory has been added, it's time to handle memory limitation */ + if (mem_size_limit) { + memblock_remove(0, mem_start); + memblock_remove(mem_start + mem_size_limit, PHYS_ADDR_MAX); + if (sunway_boot_magic != 0xDEED2024UL) { + mem_desc.base = mem_start; + mem_desc.size = memblock_phys_mem_size(); + } + } /* end of DRAM range may have been changed */ max_pfn = max_low_pfn = PFN_DOWN(memblock_end_of_DRAM()); @@ -252,12 +392,14 @@ void __init sw64_memblock_init(void) #ifndef CONFIG_NUMA void __init sw64_numa_init(void) { + phys_addr_t mem_base = memblock_start_of_DRAM(); + phys_addr_t mem_size = memblock_phys_mem_size(); const size_t nd_size = roundup(sizeof(pg_data_t), SMP_CACHE_BYTES); u64 nd_pa; void *nd; int tnid; - memblock_set_node(mem_desc.base, mem_desc.size, &memblock.memory, 0); + memblock_set_node(mem_base, mem_size, &memblock.memory, 0); nd_pa = memblock_phys_alloc(nd_size, SMP_CACHE_BYTES); nd = __va(nd_pa); @@ -271,8 +413,8 @@ void __init sw64_numa_init(void) node_data[0] = nd; memset(NODE_DATA(0), 0, sizeof(pg_data_t)); NODE_DATA(0)->node_id = 0; - NODE_DATA(0)->node_start_pfn = mem_desc.base >> PAGE_SHIFT; - NODE_DATA(0)->node_spanned_pages = mem_desc.size >> PAGE_SHIFT; + NODE_DATA(0)->node_start_pfn = mem_base >> PAGE_SHIFT; + NODE_DATA(0)->node_spanned_pages = mem_size >> PAGE_SHIFT; node_set_online(0); } #endif /* CONFIG_NUMA */ @@ -302,59 +444,6 @@ void vmemmap_free(unsigned long start, unsigned long end, } #endif -#ifdef CONFIG_HAVE_MEMBLOCK -#ifndef MIN_MEMBLOCK_ADDR -#define MIN_MEMBLOCK_ADDR __pa(PAGE_OFFSET) -#endif -#ifndef MAX_MEMBLOCK_ADDR -#define MAX_MEMBLOCK_ADDR ((phys_addr_t)~0) -#endif -void __init early_init_dt_add_memory_arch(u64 base, u64 size) -{ - const u64 phys_offset = MIN_MEMBLOCK_ADDR; - - if (acpi_disabled) { - if (!PAGE_ALIGNED(base)) { - if (size < PAGE_SIZE - (base & ~PAGE_MASK)) { - pr_warn("Ignoring memory block 0x%llx - 0x%llx\n", - base, base + size); - return; - } - size -= PAGE_SIZE - (base & ~PAGE_MASK); - base = PAGE_ALIGN(base); - } - size &= PAGE_MASK; - - if (base > MAX_MEMBLOCK_ADDR) { - pr_warn("Ignoring memory block 0x%llx - 0x%llx\n", - base, base + size); - return; - } - - if (base + size - 1 > MAX_MEMBLOCK_ADDR) { - pr_warn("Ignoring memory range 0x%llx - 0x%llx\n", - ((u64)MAX_MEMBLOCK_ADDR) + 1, base + size); - size = MAX_MEMBLOCK_ADDR - base + 1; - } - - if (base + size < phys_offset) { - pr_warn("Ignoring memory block 0x%llx - 0x%llx\n", - base, base + size); - return; - } - - if (base < phys_offset) { - pr_warn("Ignoring memory range 0x%llx - 0x%llx\n", - base, phys_offset); - size -= phys_offset - base; - base = phys_offset; - } - memblock_add(base, size); - } else - return; -} -#endif - #ifdef CONFIG_MEMORY_HOTPLUG int arch_add_memory(int nid, u64 start, u64 size, struct mhp_params *params) { diff --git a/arch/sw_64/pci/acpi.c b/arch/sw_64/pci/acpi.c index f1223dd700bd2c75ae819aa2887267aa7bdbe476..cd577c2854aa0d4935b2a3b685a6561492769a97 100644 --- a/arch/sw_64/pci/acpi.c +++ b/arch/sw_64/pci/acpi.c @@ -5,6 +5,8 @@ #include #include +#define UPPER_32_BITS_OF_U64(u64_val) ((u64_val) & 0xFFFFFFFF00000000ULL) + struct pci_root_info { struct acpi_pci_root_info info; struct pci_config_window *cfg; @@ -88,25 +90,25 @@ pci_acpi_setup_ecam_mapping(struct acpi_pci_root *root) static int pci_acpi_prepare_root_resources(struct acpi_pci_root_info *ci) { int status = 0; - acpi_status rc; - unsigned long long memh = 0; + u64 memh; struct resource_entry *entry = NULL, *tmp = NULL; struct acpi_device *device = ci->bridge; /** * To distinguish between mem and pre_mem, firmware - * only pass the lower 32bits of mem via acpi and - * use vendor specific "MEMH" to record the upper - * 32 bits of mem. + * only pass the lower 32bits of mem via _CRS method. * * Get the upper 32 bits here. */ - rc = acpi_evaluate_integer(ci->bridge->handle, "MEMH", NULL, &memh); - if (rc != AE_OK) { - dev_err(&device->dev, "unable to retrieve MEMH\n"); - return -EEXIST; + status = fwnode_property_read_u64_array(&device->fwnode, + "sw64,ep_mem_32_base", &memh, 1); + if (status) { + dev_err(&device->dev, "unable to retrieve \"sw64,ep_mem_32_base\"\n"); + return status; } + memh = UPPER_32_BITS_OF_U64(memh); + /** * Get host bridge resources via _CRS method, the return value * is the num of resource parsed. @@ -129,10 +131,10 @@ static int pci_acpi_prepare_root_resources(struct acpi_pci_root_info *ci) resource_list_for_each_entry_safe(entry, tmp, &ci->resources) { if (entry->res->flags & IORESOURCE_MEM) { - if (!(entry->res->end & 0xFFFFFFFF00000000ULL)) { + if (!UPPER_32_BITS_OF_U64(entry->res->end)) { /* Patch mem res with upper 32 bits */ - entry->res->start |= (memh << 32); - entry->res->end |= (memh << 32); + entry->res->start |= memh; + entry->res->end |= memh; } else { /** * Add PREFETCH and MEM_64 flags for diff --git a/arch/sw_64/pci/pci-legacy.c b/arch/sw_64/pci/pci-legacy.c index 228f58031626f5416ce8c37d51ef0b53ce947947..18a49f150be71474323fab61049e01ec8e5daf48 100644 --- a/arch/sw_64/pci/pci-legacy.c +++ b/arch/sw_64/pci/pci-legacy.c @@ -84,7 +84,7 @@ void __init common_init_pci(void) continue; hose->busn_space->start = last_bus; init_busnr = (0xff << 16) + ((last_bus + 1) << 8) + (last_bus); - write_rc_conf(hose->node, hose->index, RC_PRIMARY_BUS, init_busnr); + writel(init_busnr, (hose->rc_config_space_base + RC_PRIMARY_BUS)); offset = hose->mem_space->start - PCI_32BIT_MEMIO; if (is_in_host()) hose->first_busno = last_bus + 1; @@ -117,10 +117,10 @@ void __init common_init_pci(void) last_bus++; hose->last_busno = hose->busn_space->end = last_bus; - init_busnr = read_rc_conf(hose->node, hose->index, RC_PRIMARY_BUS); + init_busnr = readl(hose->rc_config_space_base + RC_PRIMARY_BUS); init_busnr &= ~(0xff << 16); init_busnr |= last_bus << 16; - write_rc_conf(hose->node, hose->index, RC_PRIMARY_BUS, init_busnr); + writel(init_busnr, (hose->rc_config_space_base + RC_PRIMARY_BUS)); pci_bus_update_busn_res_end(bus, last_bus); last_bus++; } @@ -228,9 +228,9 @@ sw64_init_host(unsigned long node, unsigned long index) sw64_chip_init->pci_init.hose_init(hose); if (sw64_chip_init->pci_init.set_rc_piu) - sw64_chip_init->pci_init.set_rc_piu(node, index); + sw64_chip_init->pci_init.set_rc_piu(hose); - ret = sw64_chip_init->pci_init.check_pci_linkup(node, index); + ret = sw64_chip_init->pci_init.check_pci_linkup(hose); if (ret == 0) { /* Root Complex downstream port is link up */ pci_mark_rc_linkup(node, index); // 8-bit per node @@ -304,47 +304,6 @@ void __init sw64_init_arch(void) void __weak set_pcieport_service_irq(int node, int index) {} -static void __init sw64_init_intx(struct pci_controller *hose) -{ - unsigned long int_conf, node, val_node; - unsigned long index, irq; - int rcid; - - node = hose->node; - index = hose->index; - - if (!node_online(node)) - val_node = next_node_in(node, node_online_map); - else - val_node = node; - irq = irq_alloc_descs_from(NR_IRQS_LEGACY, 2, val_node); - WARN_ON(irq < 0); - irq_set_chip_and_handler(irq, &dummy_irq_chip, handle_level_irq); - irq_set_status_flags(irq, IRQ_LEVEL); - hose->int_irq = irq; - irq_set_chip_and_handler(irq + 1, &dummy_irq_chip, handle_level_irq); - hose->service_irq = irq + 1; - rcid = cpu_to_rcid(0); - - printk_once(KERN_INFO "INTx are directed to node %d core %d.\n", - ((rcid >> 6) & 0x3), (rcid & 0x1f)); - int_conf = 1UL << 62 | rcid; /* rebase all intx on the first logical cpu */ - if (sw64_chip_init->pci_init.set_intx) - sw64_chip_init->pci_init.set_intx(node, index, int_conf); - - set_pcieport_service_irq(node, index); -} - -void __init sw64_init_irq(void) -{ - struct pci_controller *hose; - - /* Scan all of the recorded PCI controllers. */ - hose = hose_head; - for (hose = hose_head; hose; hose = hose->next) - sw64_init_intx(hose); -} - void __init sw64_init_pci(void) { diff --git a/arch/sw_64/pci/pci.c b/arch/sw_64/pci/pci.c index 11ff6df3505c6d71e0791936ad37b9cbd67c7251..79bd81dd6d132ec26f9dd7d919d067f143e04689 100644 --- a/arch/sw_64/pci/pci.c +++ b/arch/sw_64/pci/pci.c @@ -201,9 +201,9 @@ static void fixup_root_complex(struct pci_dev *dev) hose->self_busno = hose->busn_space->start; if (likely(bus->number == hose->self_busno)) { - if (IS_ENABLED(CONFIG_HOTPLUG_PCI_PCIE)) { + if (IS_ENABLED(CONFIG_HOTPLUG_PCI_PCIE_SUNWAY)) { /* Check Root Complex port again */ - dev->is_hotplug_bridge = 0; + dev->is_hotplug_bridge = 1; dev->current_state = PCI_D0; } @@ -244,23 +244,22 @@ static void enable_sw_dca(struct pci_dev *dev) rc_index = hose->index; for (i = 0; i < 256; i++) { - dca_conf = read_piu_ior1(node, rc_index, DEVICEID0 + (i << 7)); + dca_conf = readq(hose->piu_ior1_base + DEVICEID0 + (i << 7)); if (dca_conf >> 63) continue; else { dca_conf = (1UL << 63) | (dev->bus->number << 8) | dev->devfn; pr_info("dca device index %d, dca_conf = %#lx\n", i, dca_conf); - write_piu_ior1(node, rc_index, - DEVICEID0 + (i << 7), dca_conf); + writeq(dca_conf, (hose->piu_ior1_base + DEVICEID0 + (i << 7))); break; } } - dca_ctl = read_piu_ior1(node, rc_index, DCACONTROL); + dca_ctl = readq(hose->piu_ior1_base + DCACONTROL); if (dca_ctl & 0x1) { dca_ctl = 0x2; - write_piu_ior1(node, rc_index, DCACONTROL, dca_ctl); + writeq(dca_ctl, (hose->piu_ior1_base + DCACONTROL)); pr_info("Node %ld RC %ld enable DCA 1.0\n", node, rc_index); } } @@ -337,7 +336,7 @@ void sw64_pci_root_bridge_prepare(struct pci_host_bridge *bridge) bridge->map_irq = sw64_pci_map_irq; init_busnr = (0xff << 16) + ((last_bus + 1) << 8) + (last_bus); - write_rc_conf(hose->node, hose->index, RC_PRIMARY_BUS, init_busnr); + writel(init_busnr, (hose->rc_config_space_base + RC_PRIMARY_BUS)); hose->first_busno = last_bus + (is_in_host() ? 1 : 0); @@ -395,10 +394,10 @@ void sw64_pci_root_bridge_scan_finish_up(struct pci_host_bridge *bridge) hose->last_busno = last_bus; hose->busn_space->end = last_bus; - init_busnr = read_rc_conf(hose->node, hose->index, RC_PRIMARY_BUS); + init_busnr = readl(hose->rc_config_space_base + RC_PRIMARY_BUS); init_busnr &= ~(0xff << 16); init_busnr |= last_bus << 16; - write_rc_conf(hose->node, hose->index, RC_PRIMARY_BUS, init_busnr); + writel(init_busnr, (hose->rc_config_space_base + RC_PRIMARY_BUS)); pci_bus_update_busn_res_end(bus, last_bus); last_bus++; diff --git a/drivers/gpio/gpio-sunway.c b/drivers/gpio/gpio-sunway.c index 8db99635d97081f7114d82d1c6ca6017c31be582..47d757ad9fc34a7403af198111a9d80b4b91efb2 100644 --- a/drivers/gpio/gpio-sunway.c +++ b/drivers/gpio/gpio-sunway.c @@ -730,7 +730,7 @@ static int sunway_gpio_probe(struct platform_device *pdev) } platform_set_drvdata(pdev, gpio); - def_info(&pdev->dev, "GPIO probe succeed\n"); + dev_info(&pdev->dev, "GPIO probe succeed\n"); return 0; diff --git a/drivers/gpu/drm/amd/pm/swsmu/smu_cmn.c b/drivers/gpu/drm/amd/pm/swsmu/smu_cmn.c index 92b2ea4c197b801a7610e0f7a076ce35cb1ac355..33e734dd10ce16d8e66758066e8b8275461ef089 100644 --- a/drivers/gpu/drm/amd/pm/swsmu/smu_cmn.c +++ b/drivers/gpu/drm/amd/pm/swsmu/smu_cmn.c @@ -593,7 +593,11 @@ int smu_cmn_update_table(struct smu_context *smu, table_size = smu_table->tables[table_index].size; if (drv2smu) { +#if IS_ENABLED(CONFIG_SW64) + memcpy_toio(table->cpu_addr, table_data, table_size); +#else memcpy(table->cpu_addr, table_data, table_size); +#endif /* * Flush hdp cache: to guard the content seen by * GPU is consitent with CPU. @@ -611,7 +615,11 @@ int smu_cmn_update_table(struct smu_context *smu, if (!drv2smu) { amdgpu_asic_flush_hdp(adev, NULL); +#if IS_ENABLED(CONFIG_SW64) + memcpy_fromio(table_data, table->cpu_addr, table_size); +#else memcpy(table_data, table->cpu_addr, table_size); +#endif } return 0; @@ -667,7 +675,11 @@ int smu_cmn_get_metrics_table_locked(struct smu_context *smu, } if (metrics_table) +#if IS_ENABLED(CONFIG_SW64) + memcpy_toio(metrics_table, smu_table->metrics_table, table_size); +#else memcpy(metrics_table, smu_table->metrics_table, table_size); +#endif return 0; } diff --git a/drivers/irqchip/Kconfig b/drivers/irqchip/Kconfig index 72120298edd678de6ca97e5b765a223eb670d476..5370a6a1b47a03b4c2e89c90ae82423fa22d574f 100644 --- a/drivers/irqchip/Kconfig +++ b/drivers/irqchip/Kconfig @@ -37,6 +37,11 @@ config SW64_IRQ_CPU depends on SW64 default y +config SW64_PCI_INTX + bool + depends on SW64 && PCI + default y + config SW64_IRQ_MSI bool depends on SW64 && PCI_MSI diff --git a/drivers/irqchip/Makefile b/drivers/irqchip/Makefile index c1ffb90cfff145bbb904cffa81d9f8dcd0ad1f48..46893d5cf2ec483d6e8b26b5ac778090092cf153 100644 --- a/drivers/irqchip/Makefile +++ b/drivers/irqchip/Makefile @@ -29,6 +29,7 @@ obj-$(CONFIG_ARCH_SPEAR3XX) += spear-shirq.o obj-$(CONFIG_ARM_GIC) += irq-gic.o irq-gic-common.o obj-$(CONFIG_SW64_PINTC) += irq-sunway-pintc.o obj-$(CONFIG_SW64_LPC_INTC) += irq-sunway-lpc-intc.o +obj-$(CONFIG_SW64_PCI_INTX) += irq-sunway-pci-intx.o obj-$(CONFIG_SW64_IRQ_CPU) += irq-sunway-cpu.o ifeq ($(CONFIG_UNCORE_XUELANG),y) diff --git a/drivers/irqchip/irq-sunway-cpu.c b/drivers/irqchip/irq-sunway-cpu.c index ec2a698afbdb12f2b025dfa7430b8e15d61a6c67..e1c270f1bb10a6dda0349ecf293dbb0375a70685 100644 --- a/drivers/irqchip/irq-sunway-cpu.c +++ b/drivers/irqchip/irq-sunway-cpu.c @@ -43,36 +43,49 @@ static void handle_intx(unsigned int offset) { struct pci_controller *hose; unsigned long value; + void __iomem *piu_ior0_base; hose = hose_head; + offset <<= 7; for (hose = hose_head; hose; hose = hose->next) { - value = read_piu_ior0(hose->node, hose->index, INTACONFIG + (offset << 7)); + piu_ior0_base = hose->piu_ior0_base; + + value = readq(piu_ior0_base + INTACONFIG + offset); if (value >> 63) { value = value & (~(1UL << 62)); - write_piu_ior0(hose->node, hose->index, INTACONFIG + (offset << 7), value); + writeq(value, (piu_ior0_base + INTACONFIG + offset)); handle_irq(hose->int_irq); value = value | (1UL << 62); - write_piu_ior0(hose->node, hose->index, INTACONFIG + (offset << 7), value); + writeq(value, (piu_ior0_base + INTACONFIG + offset)); } if (IS_ENABLED(CONFIG_PCIE_PME)) { - value = read_piu_ior0(hose->node, hose->index, PMEINTCONFIG); + value = readq(piu_ior0_base + PMEINTCONFIG); if (value >> 63) { handle_irq(hose->service_irq); - write_piu_ior0(hose->node, hose->index, PMEINTCONFIG, value); + writeq(value, (piu_ior0_base + PMEINTCONFIG)); } } if (IS_ENABLED(CONFIG_PCIEAER)) { - value = read_piu_ior0(hose->node, hose->index, AERERRINTCONFIG); + value = readq(piu_ior0_base + AERERRINTCONFIG); if (value >> 63) { handle_irq(hose->service_irq); - write_piu_ior0(hose->node, hose->index, AERERRINTCONFIG, value); + writeq(value, (piu_ior0_base + AERERRINTCONFIG)); } } + if (IS_ENABLED(CONFIG_HOTPLUG_PCI_PCIE_SUNWAY)) { + value = readq(piu_ior0_base + HPINTCONFIG); + if (value >> 63) { + handle_irq(hose->service_irq); + writeq(value, (piu_ior0_base + HPINTCONFIG)); + } + + } + if (hose->iommu_enable) { - value = read_piu_ior0(hose->node, hose->index, IOMMUEXCPT_STATUS); + value = readq(piu_ior0_base + IOMMUEXCPT_STATUS); if (value >> 63) handle_irq(hose->int_irq); } @@ -185,7 +198,7 @@ asmlinkage void do_entInt(unsigned long type, unsigned long vector, if (is_guest_or_emul()) { if ((type & 0xffff) > 15) { vector = type; - if (vector == 16) + if (vector == 16 || vector == 17) type = INT_INTx; else type = INT_MSI; @@ -199,7 +212,10 @@ asmlinkage void do_entInt(unsigned long type, unsigned long vector, switch (type & 0xffff) { case INT_MSI: old_regs = set_irq_regs(regs); - handle_pci_msi_interrupt(type, vector, irq_arg); + if (is_guest_or_emul()) + vt_handle_pci_msi_interrupt(type, vector, irq_arg); + else + handle_pci_msi_interrupt(type, vector, irq_arg); set_irq_regs(old_regs); return; case INT_INTx: @@ -223,11 +239,8 @@ asmlinkage void do_entInt(unsigned long type, unsigned long vector, set_irq_regs(old_regs); return; case INT_VT_SERIAL: - old_regs = set_irq_regs(regs); - handle_irq(type); - set_irq_regs(old_regs); - return; case INT_VT_HOTPLUG: + case INT_VT_GPIOA_PIN0: old_regs = set_irq_regs(regs); handle_irq(type); set_irq_regs(old_regs); diff --git a/drivers/irqchip/irq-sunway-msi-v2.c b/drivers/irqchip/irq-sunway-msi-v2.c index 6bbef142f2b225f139d13865c9c0ad2d54655f05..a46a7ac5a57765354248a03802cbe25bcd4df677 100644 --- a/drivers/irqchip/irq-sunway-msi-v2.c +++ b/drivers/irqchip/irq-sunway-msi-v2.c @@ -52,23 +52,19 @@ bool find_free_cpu_vector(const struct cpumask *search_mask, cpu = cpumask_first(search_mask); try_again: - if (is_guest_or_emul()) { - vector = IRQ_PENDING_MSI_VECTORS_SHIFT; - max_vector = SWVM_IRQS; - } else { - vector = 0; - max_vector = 256; - } + vector = 0; + max_vector = 256; for (; vector < max_vector; vector++) { while (per_cpu(vector_irq, cpu)[vector]) { cpu = cpumask_next(cpu, search_mask); if (cpu >= nr_cpu_ids) { if (vector == 255) { if (find_once_global) { - printk("No global free vector\n"); + pr_info("No global free vector\n"); return false; } - printk("No local free vector\n"); + pr_info("No local free vector, search_mask:%*pbl\n", + cpumask_pr_args(search_mask)); search_mask = cpu_online_mask; cpu = cpumask_first(search_mask); find_once_global = true; @@ -112,10 +108,11 @@ static bool find_free_cpu_vectors(const struct cpumask *search_mask, int *found_ goto try_again; else { if (find_once_global) { - printk("No global free vectors\n"); + pr_info("No global free vectors\n"); return found; } - printk("No local free vectors\n"); + pr_info("No local free vectors, search_mask:%*pbl\n", + cpumask_pr_args(search_mask)); search_mask = cpu_online_mask; cpu = cpumask_first(search_mask); find_once_global = true; @@ -147,6 +144,9 @@ static int sw64_set_affinity(struct irq_data *d, const struct cpumask *cpumask, if (!cdata) return -ENOMEM; + if (cdata->move_in_progress) + return -EBUSY; + /* * If existing target cpu is already in the new mask and is online * then do nothing. @@ -173,13 +173,20 @@ static int sw64_set_affinity(struct irq_data *d, const struct cpumask *cpumask, /* update new setting */ entry = irq_get_msi_desc(irqd->irq); spin_lock(&cdata->cdata_lock); + if (cpu_online(cdata->dst_cpu)) { + cdata->move_in_progress = true; + cdata->prev_vector = cdata->vector; + cdata->prev_cpu = cdata->dst_cpu; + } else { + for (i = 0; i < cdata->multi_msi; i++) + per_cpu(vector_irq, cdata->dst_cpu)[cdata->vector] = 0; + } + + cdata->vector = vector; + cdata->dst_cpu = cpu; for (i = 0; i < cdata->multi_msi; i++) per_cpu(vector_irq, cpu)[vector + i] = entry->irq + i; - cdata->prev_vector = cdata->vector; - cdata->prev_cpu = cdata->dst_cpu; - cdata->dst_cpu = cpu; - cdata->vector = vector; - cdata->move_in_progress = true; + irq_msi_compose_msg(d, &msg); __pci_write_msi_msg(entry, &msg); spin_unlock(&cdata->cdata_lock); @@ -239,7 +246,7 @@ static int __assign_irq_vector(int virq, unsigned int nr_irqs, cdata = alloc_sw_msi_chip_data(irq_data); if (!cdata) { - printk("error alloc irq chip data\n"); + pr_info("error alloc irq chip data\n"); return -ENOMEM; } @@ -275,7 +282,7 @@ static int __assign_irq_vector(int virq, unsigned int nr_irqs, cdata = alloc_sw_msi_chip_data(irq_data); if (!cdata) { - printk("error alloc irq chip data\n"); + pr_info("error alloc irq chip data\n"); return -ENOMEM; } @@ -335,11 +342,6 @@ static void sw64_vector_free_irqs(struct irq_domain *domain, static void sw64_irq_free_descs(unsigned int virq, unsigned int nr_irqs) { - if (is_guest_or_emul()) { - vt_sw64_vector_free_irqs(virq, nr_irqs); - return irq_free_descs(virq, nr_irqs); - } - return irq_domain_free_irqs(virq, nr_irqs); } @@ -437,7 +439,7 @@ void arch_init_msi_domain(struct irq_domain *parent) struct irq_domain *sw64_irq_domain; if (is_guest_or_emul()) - return; + return sw64_init_vt_msi_domain(parent); sw64_irq_domain = irq_domain_add_tree(NULL, &sw64_msi_domain_ops, NULL); BUG_ON(sw64_irq_domain == NULL); @@ -475,13 +477,6 @@ void handle_pci_msi_interrupt(unsigned long type, unsigned long vector, unsigned struct irq_data *irq_data; struct sw64_msi_chip_data *cdata; - if (is_guest_or_emul()) { - cpu = smp_processor_id(); - irq = per_cpu(vector_irq, cpu)[vector]; - handle_irq(irq); - return; - } - ptr = (unsigned long *)pci_msi1_addr; int_pci_msi[0] = *ptr; int_pci_msi[1] = *(ptr + 1); @@ -492,8 +487,6 @@ void handle_pci_msi_interrupt(unsigned long type, unsigned long vector, unsigned for (i = 0; i < 4; i++) { vector_index = i * 64; while (vector != 0) { - int irq = 0; - msi_index = find_next_bit(&vector, 64, msi_index); if (msi_index == 64) { msi_index = 0; diff --git a/drivers/irqchip/irq-sunway-msi-vt.c b/drivers/irqchip/irq-sunway-msi-vt.c index 219afa54333e1308f710695069354d8aa1c56d73..d5213725c918ffa9e4644029e366832f47cd2f34 100644 --- a/drivers/irqchip/irq-sunway-msi-vt.c +++ b/drivers/irqchip/irq-sunway-msi-vt.c @@ -1,11 +1,101 @@ // SPDX-License-Identifier: GPL-2.0 -#include #include -#include +#include #include +#include +#include +#include +#include +#include + +static struct irq_domain *vt_msi_default_domain; static DEFINE_RAW_SPINLOCK(vector_lock); +static void vt_irq_move_complete(struct sw64_msi_chip_data *cdata, int cpu) +{ + if (likely(!cdata->move_in_progress)) + return; + if (cdata->dst_cpu == cpu) { + raw_spin_lock(&vector_lock); + cdata->move_in_progress = false; + per_cpu(vector_irq, cdata->prev_cpu)[cdata->prev_vector] = 0; + raw_spin_unlock(&vector_lock); + } +} + +void vt_handle_pci_msi_interrupt(unsigned long type, unsigned long vector, + unsigned long pci_msi1_addr) +{ + int irq, cpu; + struct irq_data *irq_data; + struct sw64_msi_chip_data *cdata; + + cpu = smp_processor_id(); + irq = per_cpu(vector_irq, cpu)[vector]; + irq_data = irq_domain_get_irq_data(vt_msi_default_domain->parent, irq); + cdata = irq_data_get_irq_chip_data(irq_data); + + spin_lock(&cdata->cdata_lock); + vt_irq_move_complete(cdata, cpu); + spin_unlock(&cdata->cdata_lock); + + handle_irq(irq); +} + +static bool vt_find_free_cpu_vector(const struct cpumask *search_mask, + int *found_cpu, int *found_vector, struct irq_data *d) +{ + int vector, max_vector, cpu; + bool find_once_global = false; + + cpu = cpumask_first(search_mask); +try_again: + vector = IRQ_PENDING_MSI_VECTORS_SHIFT; + max_vector = SWVM_IRQS; + + for (; vector < max_vector; vector++) { + while (per_cpu(vector_irq, cpu)[vector]) { + if (per_cpu(vector_irq, cpu)[vector] == d->irq) + break; + + if (!irqd_affinity_is_managed(d)) + cpu = cpumask_next(cpu, search_mask); + else + vector++; + + if (vector >= max_vector) { + cpu = cpumask_next(cpu, search_mask); + vector = IRQ_PENDING_MSI_VECTORS_SHIFT; + } + + if (cpu >= nr_cpu_ids) { + if (vector == max_vector-1) { + if (find_once_global) { + pr_err("No global free vector\n"); + return false; + } + pr_err("No local free vector\n"); + search_mask = cpu_online_mask; + cpu = cpumask_first(search_mask); + find_once_global = true; + goto try_again; + } + cpu = cpumask_first(search_mask); + break; + } + } + if (per_cpu(vector_irq, cpu)[vector] == d->irq) + break; + if (!per_cpu(vector_irq, cpu)[vector]) + break; + } + + *found_cpu = cpu; + *found_vector = vector; + return true; +} + static void __vt_irq_msi_compose_msg(struct sw64_msi_chip_data *cdata, struct msi_msg *msg) { @@ -18,8 +108,11 @@ static void __vt_irq_msi_compose_msg(struct sw64_msi_chip_data *cdata, static void vt_irq_msi_compose_msg(struct irq_data *irqd, struct msi_msg *msg) { struct sw64_msi_chip_data *cdata; + struct irq_data *d; + + d = irq_domain_get_irq_data(vt_msi_default_domain->parent, irqd->irq); + cdata = d->chip_data; - cdata = irqd->chip_data; __vt_irq_msi_compose_msg(cdata, msg); } @@ -33,10 +126,11 @@ static void vt_irq_msi_update_msg(struct irq_data *irqd, } static int -vt_set_affinity(struct irq_data *irqd, const struct cpumask *cpumask, +vt_set_affinity(struct irq_data *d, const struct cpumask *cpumask, bool force) { struct sw64_msi_chip_data *cdata; + struct irq_data *irqd; struct cpumask searchmask; int cpu, vector; @@ -44,6 +138,7 @@ vt_set_affinity(struct irq_data *irqd, const struct cpumask *cpumask, if (cpumask_any_and(cpumask, cpu_online_mask) >= nr_cpu_ids) return -EINVAL; + irqd = irq_domain_get_irq_data(vt_msi_default_domain->parent, d->irq); if (!irqd_is_started(irqd)) return IRQ_SET_MASK_OK; @@ -59,15 +154,15 @@ vt_set_affinity(struct irq_data *irqd, const struct cpumask *cpumask, return IRQ_SET_MASK_OK; cpumask_and(&searchmask, cpumask, cpu_online_mask); - if (!find_free_cpu_vector(&searchmask, &cpu, &vector)) + if (!vt_find_free_cpu_vector(&searchmask, &cpu, &vector, irqd)) return -ENOSPC; per_cpu(vector_irq, cpu)[vector] = irqd->irq; spin_lock(&cdata->cdata_lock); - cdata->dst_cpu = cpu; - cdata->vector = vector; cdata->prev_cpu = cdata->dst_cpu; cdata->prev_vector = cdata->vector; + cdata->dst_cpu = cpu; + cdata->vector = vector; cdata->move_in_progress = true; spin_unlock(&cdata->cdata_lock); cpumask_copy(irq_data_get_affinity_mask(irqd), &searchmask); @@ -77,45 +172,35 @@ vt_set_affinity(struct irq_data *irqd, const struct cpumask *cpumask, } static struct irq_chip vt_pci_msi_controller = { - .name = "PCI-MSI", - .irq_unmask = pci_msi_unmask_irq, - .irq_mask = pci_msi_mask_irq, - .irq_ack = sw64_irq_noop, - .irq_compose_msi_msg = vt_irq_msi_compose_msg, - .irq_set_affinity = vt_set_affinity, + .name = "PCI-MSI", + .irq_unmask = pci_msi_unmask_irq, + .irq_mask = pci_msi_mask_irq, + .irq_ack = sw64_irq_noop, + .irq_compose_msi_msg = vt_irq_msi_compose_msg, + .flags = IRQCHIP_SKIP_SET_WAKE, + .irq_set_affinity = vt_set_affinity, }; -int chip_setup_vt_msix_irq(struct pci_dev *dev, struct msi_desc *desc) +int chip_setup_vt_msi_irqs(int virq, unsigned int nr_irqs, + struct irq_domain *domain, enum irq_alloc_type type) { - int virq, val_node = 0; struct irq_data *irq_data; struct sw64_msi_chip_data *cdata; - struct pci_controller *hose = pci_bus_to_pci_controller(dev->bus); - unsigned long flags, node, rc_index; - const struct cpumask *mask; + unsigned long node; + const struct cpumask *mask; struct cpumask searchmask; - int cpu, vector; - - node = hose->node; - rc_index = hose->index; - mask = cpumask_of_node(node); - - raw_spin_lock_irqsave(&vector_lock, flags); - /* Find unused msi config reg in PIU-IOR0 */ - if (!node_online(node)) - val_node = next_node_in(node, node_online_map); - else - val_node = node; + int i, vector, cpu; - virq = irq_alloc_descs_from(NR_IRQS_LEGACY, desc->nvec_used, val_node); - if (virq < 0) { - pr_err("Failed to allocate IRQ(base 16, count %d)\n", desc->nvec_used); - raw_spin_unlock_irqrestore(&vector_lock, flags); - return virq; + if (type != IRQ_ALLOC_TYPE_MSI && type != IRQ_ALLOC_TYPE_MSIX) { + pr_info("SW arch do not identify ID:%d\n", type); + return -ENOMEM; } - irq_data = irq_get_irq_data(virq); + irq_data = irq_domain_get_irq_data(domain, virq); + if (!irq_data) + return -EINVAL; + irq_data->chip = &vt_pci_msi_controller; if (irqd_affinity_is_managed(irq_data)) { mask = irq_data_get_affinity_mask(irq_data); @@ -127,157 +212,150 @@ int chip_setup_vt_msix_irq(struct pci_dev *dev, struct msi_desc *desc) if (cpumask_first(&searchmask) >= nr_cpu_ids) cpumask_copy(&searchmask, cpu_online_mask); - if (!find_free_cpu_vector(&searchmask, &cpu, &vector)) - return -ENOSPC; - - cdata = kzalloc(sizeof(*cdata), GFP_KERNEL); - if (!cdata) - return -ENOMEM; - - per_cpu(vector_irq, cpu)[vector] = virq; - - irq_set_msi_desc(virq, desc); - irq_set_chip_and_handler_name(virq, &vt_pci_msi_controller, - handle_edge_irq, "edge"); - - cdata->dst_cpu = cpu; - cdata->vector = vector; - cdata->rc_index = hose->index; - cdata->rc_node = hose->node; - cdata->prev_cpu = cpu; - cdata->prev_vector = vector; - - irq_data->chip_data = cdata; - - vt_irq_msi_update_msg(irq_data, irq_data->chip_data); - raw_spin_unlock_irqrestore(&vector_lock, flags); - return 0; -} -EXPORT_SYMBOL(chip_setup_vt_msix_irq); - -int chip_setup_vt_msi_irqs(struct pci_dev *dev, int nvec, int type) -{ - struct msi_desc *desc; - struct pci_controller *hose = pci_bus_to_pci_controller(dev->bus); - struct irq_data *irq_data; - struct sw64_msi_chip_data *cdata; - unsigned long node, rc_index; - int virq = -1, val_node = 0; - unsigned long flags; - - const struct cpumask *mask; - struct cpumask searchmask; - int i, vector, cpu; - - if (type == PCI_CAP_ID_MSI && nvec > 32) - return 1; - - node = hose->node; - rc_index = hose->index; - raw_spin_lock_irqsave(&vector_lock, flags); - for_each_msi_entry(desc, &(dev->dev)) { - /* Find unused msi config reg in PIU-IOR0 */ - if (!node_online(node)) - val_node = next_node_in(node, node_online_map); - else - val_node = node; - virq = irq_alloc_descs_from(NR_IRQS_LEGACY, desc->nvec_used, val_node); - if (virq < 0) { - pr_err("Failed to allocate IRQ(base 16, count %d)\n", desc->nvec_used); - raw_spin_unlock_irqrestore(&vector_lock, flags); - return virq; - } - - irq_data = irq_get_irq_data(virq); - if (irqd_affinity_is_managed(irq_data)) { - mask = irq_data_get_affinity_mask(irq_data); - cpumask_and(&searchmask, mask, cpu_online_mask); - } else { - node = irq_data_get_node(irq_data); - cpumask_copy(&searchmask, cpumask_of_node(node)); + for (i = 0; i < nr_irqs; i++) { + if (i) { + irq_data = irq_domain_get_irq_data(domain, virq + i); + irq_data->chip = &vt_pci_msi_controller; } - if (cpumask_first(&searchmask) >= nr_cpu_ids) - cpumask_copy(&searchmask, cpu_online_mask); - for (i = 0; i < desc->nvec_used; i++) { - if (!find_free_cpu_vector(&searchmask, &cpu, &vector)) - return -ENOSPC; + if (!vt_find_free_cpu_vector(&searchmask, &cpu, &vector, irq_data)) + return -ENOSPC; - cdata = kzalloc(sizeof(*cdata), GFP_KERNEL); - if (!cdata) - return -ENOMEM; + cdata = kzalloc(sizeof(*cdata), GFP_KERNEL); + if (!cdata) + return -ENOMEM; - per_cpu(vector_irq, cpu)[vector] = virq + i; - irq_set_msi_desc_off(virq, i, desc); - desc->msi_attrib.multiple = ilog2(__roundup_pow_of_two(nvec)); - irq_set_chip_and_handler_name(virq + i, &vt_pci_msi_controller, handle_edge_irq, "edge"); - irq_data = irq_get_irq_data(virq + i); + per_cpu(vector_irq, cpu)[vector] = virq + i; - cdata->dst_cpu = cpu; - cdata->vector = vector; - cdata->rc_index = hose->index; - cdata->rc_node = hose->node; - cdata->prev_cpu = cpu; - cdata->prev_vector = vector; + cdata->dst_cpu = cpu; + cdata->vector = vector; + cdata->prev_cpu = cpu; + cdata->prev_vector = vector; + cdata->move_in_progress = false; - irq_data->chip_data = cdata; - - vt_irq_msi_update_msg(irq_data, irq_data->chip_data); - } + irq_data->chip_data = cdata; } - raw_spin_unlock_irqrestore(&vector_lock, flags); return 0; } EXPORT_SYMBOL(chip_setup_vt_msi_irqs); -void vt_sw64_vector_free_irqs(unsigned int virq, unsigned int nr_irqs) +static void sw64_vt_vector_free_irqs(struct irq_domain *domain, + unsigned int virq, unsigned int nr_irqs) { int i; - unsigned long flags; struct irq_data *irq_data; - struct sw64_msi_chip_data *cdata; + unsigned long flags; for (i = 0; i < nr_irqs; i++) { - irq_data = irq_get_irq_data(virq + i); + irq_data = irq_domain_get_irq_data(domain, virq + i); if (irq_data && irq_data->chip_data) { + struct sw64_msi_chip_data *cdata; + raw_spin_lock_irqsave(&vector_lock, flags); cdata = irq_data->chip_data; - irq_data->hwirq = 0; - irq_data->chip = &no_irq_chip; - irq_data->chip_data = NULL; + irq_domain_reset_irq_data(irq_data); per_cpu(vector_irq, cdata->dst_cpu)[cdata->vector] = 0; kfree(cdata); + raw_spin_unlock_irqrestore(&vector_lock, flags); } } } -int __arch_setup_vt_msix_irqs(struct pci_dev *dev, int nvec, int type) +static int assign_vt_irq_vector(int irq, unsigned int nr_irqs, + struct irq_domain *domain, enum irq_alloc_type type) { - struct msi_desc *entry; - int ret; + int err; + unsigned long flags; - list_for_each_entry(entry, &dev->dev.msi_list, list) { - ret = chip_setup_vt_msix_irq(dev, entry); - if (ret) - return ret; - } + raw_spin_lock_irqsave(&vector_lock, flags); + err = chip_setup_vt_msi_irqs(irq, nr_irqs, domain, type); + raw_spin_unlock_irqrestore(&vector_lock, flags); + return err; +} +static int sw64_vt_vector_alloc_irqs(struct irq_domain *domain, + unsigned int virq, unsigned int nr_irqs, void *arg) +{ + int err; + struct irq_alloc_info *info = arg; + enum irq_alloc_type msi_type; + + if (arg == NULL) + return -ENODEV; + msi_type = info->type; + err = assign_vt_irq_vector(virq, nr_irqs, domain, msi_type); + if (err) + goto error; return 0; +error: + sw64_vt_vector_free_irqs(domain, virq, nr_irqs); + return err; } -int sw64_setup_vt_msi_irqs(struct pci_dev *dev, int nvec, int type) + +static int vt_pci_msi_prepare(struct irq_domain *domain, struct device *dev, + int nvec, msi_alloc_info_t *arg) { - int ret = 0; + struct pci_dev *pdev = to_pci_dev(dev); + struct msi_desc *desc = first_pci_msi_entry(pdev); - if (type == PCI_CAP_ID_MSI) - ret = chip_setup_vt_msi_irqs(dev, nvec, type); - else if (type == PCI_CAP_ID_MSIX) - ret = __arch_setup_vt_msix_irqs(dev, nvec, type); + memset(arg, 0, sizeof(*arg)); + arg->msi_dev = pdev; + if (desc->msi_attrib.is_msix) + arg->type = IRQ_ALLOC_TYPE_MSIX; else - pr_info("SW arch do not identify ID:%d\n", type); + arg->type = IRQ_ALLOC_TYPE_MSI; + return 0; +} + +static struct msi_domain_ops vt_pci_msi_domain_ops = { + .msi_prepare = vt_pci_msi_prepare, +}; + +static struct msi_domain_info pci_vt_msi_domain_info = { + .flags = MSI_FLAG_USE_DEF_DOM_OPS | MSI_FLAG_USE_DEF_CHIP_OPS | + MSI_FLAG_MULTI_PCI_MSI | MSI_FLAG_PCI_MSIX, + .ops = &vt_pci_msi_domain_ops, + .chip = &vt_pci_msi_controller, + .handler = handle_edge_irq, + .handler_name = "edge", +}; + +static int sw64_vt_irq_map(struct irq_domain *d, unsigned int virq, irq_hw_number_t hw) +{ + irq_set_chip_and_handler(virq, &sw64_irq_chip, handle_level_irq); + irq_set_status_flags(virq, IRQ_LEVEL); + return 0; +} - return ret; +const struct irq_domain_ops sw64_vt_msi_domain_ops = { + .map = sw64_vt_irq_map, + .alloc = sw64_vt_vector_alloc_irqs, + .free = sw64_vt_vector_free_irqs, +}; + +int sw64_setup_vt_msi_irqs(struct pci_dev *pdev, int nvec, int type) +{ + struct irq_domain *domain; + int err; + + domain = vt_msi_default_domain; + if (domain == NULL) + return -EIO; + err = msi_domain_alloc_irqs(domain, &pdev->dev, nvec); + return err; +} + +void sw64_init_vt_msi_domain(struct irq_domain *parent) +{ + struct irq_domain *sw64_irq_domain; + + sw64_irq_domain = irq_domain_add_tree(NULL, &sw64_vt_msi_domain_ops, NULL); + irq_set_default_host(sw64_irq_domain); + vt_msi_default_domain = pci_msi_create_irq_domain(NULL, + &pci_vt_msi_domain_info, sw64_irq_domain); + if (!vt_msi_default_domain) + pr_warn("failed to initialize irqdomain for MSI/MSI-x.\n"); } -MODULE_LICENSE("GPL v2"); diff --git a/drivers/irqchip/irq-sunway-msi.c b/drivers/irqchip/irq-sunway-msi.c index f5d0a19e95835f23564082c929be124f58a5eed0..4ef7cfb8396ac1eff976e1e60f4c09cfcc3cee75 100644 --- a/drivers/irqchip/irq-sunway-msi.c +++ b/drivers/irqchip/irq-sunway-msi.c @@ -48,23 +48,19 @@ bool find_free_cpu_vector(const struct cpumask *search_mask, cpu = cpumask_first(search_mask); try_again: - if (is_guest_or_emul()) { - vector = IRQ_PENDING_MSI_VECTORS_SHIFT; - max_vector = SWVM_IRQS; - } else { - vector = 0; - max_vector = 256; - } + vector = 0; + max_vector = 256; for (; vector < max_vector; vector++) { while (per_cpu(vector_irq, cpu)[vector]) { cpu = cpumask_next(cpu, search_mask); if (cpu >= nr_cpu_ids) { if (vector == 255) { if (find_once_global) { - printk("No global free vector\n"); + pr_info("No global free vector\n"); return false; } - printk("No local free vector\n"); + pr_info("No local free vector, search_mask:%*pbl\n", + cpumask_pr_args(search_mask)); search_mask = cpu_online_mask; cpu = cpumask_first(search_mask); find_once_global = true; @@ -94,8 +90,8 @@ static unsigned long set_piu_msi_config(struct pci_controller *hose, int cpu, phy_cpu = cpu_to_rcid(cpu); msi_config |= ((phy_cpu >> 5) << 6) | (phy_cpu & 0x1f); reg = MSICONFIG0 + (unsigned long)(msiconf_index << 7); - write_piu_ior0(hose->node, hose->index, reg, msi_config); - msi_config = read_piu_ior0(hose->node, hose->index, reg); + writeq(msi_config, (hose->piu_ior0_base + reg)); + msi_config = readq(hose->piu_ior0_base + reg); set_bit(msiconf_index, hose->piu_msiconfig); return msi_config; @@ -124,6 +120,9 @@ static int sw64_set_affinity(struct irq_data *d, const struct cpumask *cpumask, if (!cdata) return -ENOMEM; + if (cdata->move_in_progress) + return -EBUSY; + /* * If existing target cpu is already in the new mask and is online * then do nothing. @@ -143,14 +142,20 @@ static int sw64_set_affinity(struct irq_data *d, const struct cpumask *cpumask, entry = irq_get_msi_desc(irqd->irq); hose = pci_bus_to_pci_controller(msi_desc_to_pci_dev(entry)->bus); spin_lock(&cdata->cdata_lock); + if (cpu_online(cdata->dst_cpu)) { + cdata->move_in_progress = true; + cdata->prev_vector = cdata->vector; + cdata->prev_cpu = cdata->dst_cpu; + } else { + per_cpu(vector_irq, cdata->dst_cpu)[cdata->vector] = 0; + } + + cdata->vector = vector; + cdata->dst_cpu = cpu; per_cpu(vector_irq, cpu)[vector] = irqd->irq; + msi_config = set_piu_msi_config(hose, cpu, cdata->msi_config_index, vector); - cdata->prev_vector = cdata->vector; - cdata->prev_cpu = cdata->dst_cpu; - cdata->dst_cpu = cpu; - cdata->vector = vector; cdata->msi_config = msi_config; - cdata->move_in_progress = true; spin_unlock(&cdata->cdata_lock); cpumask_copy(irq_data_get_affinity_mask(irqd), &searchmask); @@ -192,7 +197,8 @@ static int __assign_irq_vector(int virq, unsigned int nr_irqs, nr_irqs, nr_irqs - 1); if (msiconf_index >= 256) { - printk("No free msi on PIU!\n"); + pr_info("No free msi on PIU! node:%ld index:%ld\n", + hose->node, hose->index); return -ENOSPC; } @@ -225,7 +231,7 @@ static int __assign_irq_vector(int virq, unsigned int nr_irqs, cdata = alloc_sw_msi_chip_data(irq_data); if (!cdata) { - printk("error alloc irq chip data\n"); + pr_info("error alloc irq chip data\n"); return -ENOMEM; } @@ -235,8 +241,7 @@ static int __assign_irq_vector(int virq, unsigned int nr_irqs, cdata->dst_cpu = cpu; cdata->vector = vector; - cdata->rc_index = hose->index; - cdata->rc_node = hose->node; + cdata->hose = hose; cdata->msi_config = msi_config; cdata->msi_config_index = msiconf_index; cdata->prev_cpu = cpu; @@ -289,11 +294,6 @@ static void sw64_vector_free_irqs(struct irq_domain *domain, static void sw64_irq_free_descs(unsigned int virq, unsigned int nr_irqs) { - if (is_guest_or_emul()) { - vt_sw64_vector_free_irqs(virq, nr_irqs); - return irq_free_descs(virq, nr_irqs); - } - return irq_domain_free_irqs(virq, nr_irqs); } @@ -392,7 +392,7 @@ void arch_init_msi_domain(struct irq_domain *parent) struct irq_domain *sw64_irq_domain; if (is_guest_or_emul()) - return; + return sw64_init_vt_msi_domain(parent); sw64_irq_domain = irq_domain_add_tree(NULL, &sw64_msi_domain_ops, NULL); BUG_ON(sw64_irq_domain == NULL); @@ -425,13 +425,6 @@ void handle_pci_msi_interrupt(unsigned long type, unsigned long vector, unsigned struct irq_data *irq_data; struct sw64_msi_chip_data *cdata; - if (is_guest_or_emul()) { - cpu = smp_processor_id(); - irq = per_cpu(vector_irq, cpu)[vector]; - handle_irq(irq); - return; - } - ptr = (unsigned long *)pci_msi1_addr; int_pci_msi[0] = *ptr; int_pci_msi[1] = *(ptr + 1); @@ -455,7 +448,7 @@ void handle_pci_msi_interrupt(unsigned long type, unsigned long vector, unsigned irq_move_complete(cdata, cpu, vector_index + msi_index); piu_index = cdata->msi_config_index; value = cdata->msi_config | (1UL << 63); - write_piu_ior0(cdata->rc_node, cdata->rc_index, MSICONFIG0 + (piu_index << 7), value); + writeq(value, (cdata->hose->piu_ior0_base + MSICONFIG0 + (piu_index << 7))); spin_unlock(&cdata->cdata_lock); handle_irq(irq); diff --git a/drivers/irqchip/irq-sunway-pci-intx.c b/drivers/irqchip/irq-sunway-pci-intx.c new file mode 100644 index 0000000000000000000000000000000000000000..434a0c1156defe7b857b679da0361c44be99bbf2 --- /dev/null +++ b/drivers/irqchip/irq-sunway-pci-intx.c @@ -0,0 +1,183 @@ +#include +#include +#include + +#include +#include +#include +#include +#include + +static DEFINE_RAW_SPINLOCK(legacy_lock); +static void lock_legacy_lock(void) +{ + raw_spin_lock(&legacy_lock); +} + +static void unlock_legacy_lock(void) +{ + raw_spin_unlock(&legacy_lock); +} + +static void set_intx(struct pci_controller *hose, unsigned long intx_conf) +{ + void __iomem *piu_ior0_base; + + if (is_guest_or_emul()) + return; + + piu_ior0_base = hose->piu_ior0_base; + + writeq(intx_conf | (0x8UL << 10), (piu_ior0_base + INTACONFIG)); + writeq(intx_conf | (0x4UL << 10), (piu_ior0_base + INTBCONFIG)); + writeq(intx_conf | (0x2UL << 10), (piu_ior0_base + INTCCONFIG)); + writeq(intx_conf | (0x1UL << 10), (piu_ior0_base + INTDCONFIG)); +} + +static int __assign_piu_intx_config(struct pci_controller *hose, cpumask_t *targets) +{ + unsigned long intx_conf; + unsigned int cpu; + int phy_cpu; + + /* Use the last cpu in valid cpus to avoid core 0. */ + cpu = cpumask_last(targets); + phy_cpu = cpu_to_rcid(cpu); + + intx_conf = ((phy_cpu >> 5) << 6) | (phy_cpu & 0x1f); + set_intx(hose, intx_conf); + + return 0; +} + +static int assign_piu_intx_config(struct pci_controller *hose, cpumask_t *targets) +{ + int ret; + + lock_legacy_lock(); + ret = __assign_piu_intx_config(hose, targets); + unlock_legacy_lock(); + + return ret; +} + +static void intx_irq_enable(struct irq_data *irq_data) +{ + struct pci_controller *hose = irq_data->chip_data; + unsigned long intx_conf; + void __iomem *piu_ior0_base; + + if (is_guest_or_emul()) + return; + BUG_ON(!hose); + + piu_ior0_base = hose->piu_ior0_base; + + intx_conf = readq(piu_ior0_base + INTACONFIG); + intx_conf |= PCI_INTX_ENABLE; + writeq(intx_conf, (piu_ior0_base + INTACONFIG)); + + intx_conf = readq(piu_ior0_base + INTBCONFIG); + intx_conf |= PCI_INTX_ENABLE; + writeq(intx_conf, (piu_ior0_base + INTBCONFIG)); + + intx_conf = readq(piu_ior0_base + INTCCONFIG); + intx_conf |= PCI_INTX_ENABLE; + writeq(intx_conf, (piu_ior0_base + INTCCONFIG)); + + intx_conf = readq(piu_ior0_base + INTDCONFIG); + intx_conf |= PCI_INTX_ENABLE; + writeq(intx_conf, (piu_ior0_base + INTDCONFIG)); +} + +static void intx_irq_disable(struct irq_data *irq_data) +{ + struct pci_controller *hose = irq_data->chip_data; + unsigned long intx_conf; + void __iomem *piu_ior0_base; + + if (is_guest_or_emul()) + return; + + BUG_ON(!hose); + piu_ior0_base = hose->piu_ior0_base; + + intx_conf = readq(piu_ior0_base + INTACONFIG); + intx_conf &= PCI_INTX_DISABLE; + writeq(intx_conf, (piu_ior0_base + INTACONFIG)); + + intx_conf = readq(piu_ior0_base + INTBCONFIG); + intx_conf &= PCI_INTX_DISABLE; + writeq(intx_conf, (piu_ior0_base + INTBCONFIG)); + + intx_conf = readq(piu_ior0_base + INTCCONFIG); + intx_conf &= PCI_INTX_DISABLE; + writeq(intx_conf, (piu_ior0_base + INTCCONFIG)); + + intx_conf = readq(piu_ior0_base + INTDCONFIG); + intx_conf &= PCI_INTX_DISABLE; + writeq(intx_conf, (piu_ior0_base + INTDCONFIG)); +} + +static int intx_set_affinity(struct irq_data *irq_data, + const struct cpumask *dest, bool force) +{ + struct pci_controller *hose = irq_data->chip_data; + cpumask_t targets; + int ret = 0; + + if (cpumask_any_and(dest, cpu_online_mask) >= nr_cpu_ids) + return -EINVAL; + + cpumask_copy(&targets, dest); + + intx_irq_disable(irq_data); + ret = assign_piu_intx_config(hose, &targets); + intx_irq_enable(irq_data); + + return ret; +} + +static void noop(struct irq_data *d) {} + +static struct irq_chip sw64_intx_chip = { + .name = "PCI_INTX", + .irq_enable = intx_irq_enable, + .irq_disable = intx_irq_disable, + .irq_set_affinity = intx_set_affinity, + .irq_ack = noop, + .flags = IRQCHIP_SKIP_SET_WAKE, +}; + +void __weak set_pcieport_service_irq(struct pci_controller *hose) {} + +void setup_intx_irqs(struct pci_controller *hose) +{ + unsigned long irq, node, val_node; + + node = hose->node; + + if (!node_online(node)) + val_node = next_node_in(node, node_online_map); + else + val_node = node; + + irq = irq_alloc_descs_from(NR_IRQS_LEGACY, 2, val_node); + WARN_ON(irq < 0); + irq_set_chip_and_handler(irq, &sw64_intx_chip, handle_level_irq); + irq_set_status_flags(irq, IRQ_LEVEL); + irq_set_chip_data(irq, hose); + hose->int_irq = irq; + irq_set_chip_and_handler(irq + 1, &dummy_irq_chip, handle_level_irq); + hose->service_irq = irq + 1; + + set_pcieport_service_irq(hose); +} + +void __init sw64_init_irq(void) +{ + struct pci_controller *hose = hose_head; + + for (hose = hose_head; hose; hose = hose->next) + setup_intx_irqs(hose); +} diff --git a/drivers/mfd/sunway_ast2400.c b/drivers/mfd/sunway_ast2400.c index 3552daf06e90a66459c3ed6fa059f12fb5591e91..d729184c7a27402296cb472c328423b99365f947 100644 --- a/drivers/mfd/sunway_ast2400.c +++ b/drivers/mfd/sunway_ast2400.c @@ -20,6 +20,7 @@ #include #include +#define DRIVER_NAME "sunway_superio_ast2400" static int superio_uart0_irq; static int superio_uart1_irq; @@ -197,7 +198,7 @@ static struct platform_driver superio_nuvoton_ast2400_driver = { .probe = superio_ast2400_probe, .remove = superio_ast2400_remove, .driver = { - .name = "sunway_superio_ast2400" + .name = DRIVER_NAME }, }; diff --git a/drivers/misc/sunway-ged.c b/drivers/misc/sunway-ged.c index 557608a78e292d1d3cdbe92a6e5d3d8f34caab69..5167700369622d2498d7c8e347386d3b93462373 100644 --- a/drivers/misc/sunway-ged.c +++ b/drivers/misc/sunway-ged.c @@ -2,6 +2,7 @@ /* Generic Event Device for ACPI. */ +#include #include #include #include @@ -58,7 +59,7 @@ static int sunway_memory_enable_device(struct sunway_memory_device *mem_device) lock_device_hotplug(); /* suppose node = 0, fix me! */ - result = __add_memory(0, mem_device->start_addr, mem_device->length); + result = __add_memory(0, mem_device->start_addr, mem_device->length, MHP_NONE); unlock_device_hotplug(); /* * If the memory block has been used by the kernel, add_memory() @@ -236,10 +237,19 @@ static const struct of_device_id sunwayged_of_match[] = { }; MODULE_DEVICE_TABLE(of, sunwayged_of_match); +#ifdef CONFIG_ACPI +static const struct acpi_device_id sunwayged_acpi_match[] = { + { "SUNW1000", 0 }, + { } +}; +MODULE_DEVICE_TABLE(acpi, sunwayged_acpi_match); +#endif + static struct platform_driver sunwayged_platform_driver = { .driver = { .name = "sunway-ged", .of_match_table = sunwayged_of_match, + .acpi_match_table = ACPI_PTR(sunwayged_acpi_match), }, .probe = sunwayged_probe, .remove = sunwayged_remove, diff --git a/drivers/pci/controller/pci-sunway.c b/drivers/pci/controller/pci-sunway.c index f84f61fae96be9157eec0a58344f095dc5665f82..5914d3cfcbf00926a94c7a1adf871d427498385a 100644 --- a/drivers/pci/controller/pci-sunway.c +++ b/drivers/pci/controller/pci-sunway.c @@ -24,15 +24,29 @@ void set_adr_int(int node) } #endif -void set_pcieport_service_irq(int node, int index) +void set_pcieport_service_irq(struct pci_controller *hose) { if (IS_ENABLED(CONFIG_PCIE_PME)) - write_piu_ior0(node, index, - PMEINTCONFIG, PME_ENABLE_INTD_CORE0); + writeq(PME_ENABLE_INTD_CORE0, (hose->piu_ior0_base + PMEINTCONFIG)); if (IS_ENABLED(CONFIG_PCIEAER)) - write_piu_ior0(node, index, - AERERRINTCONFIG, AER_ENABLE_INTD_CORE0); + writeq(AER_ENABLE_INTD_CORE0, (hose->piu_ior0_base + AERERRINTCONFIG)); + +#ifdef CONFIG_UNCORE_JUNZHANG + if (IS_ENABLED(CONFIG_HOTPLUG_PCI_PCIE_SUNWAY)) + writeq(HP_ENABLE_INTD_CORE0, (hose->piu_ior0_base + HPINTCONFIG)); +#endif +} + +int pcibios_enable_device(struct pci_dev *dev, int bars) +{ + struct pci_bus *bus = dev->bus; + struct pci_controller *hose = pci_bus_to_pci_controller(bus); + + if (!is_guest_or_emul() && unlikely(bus->number == hose->self_busno)) + return 0; + else + return pci_enable_resources(dev, bars); } int chip_pcie_configure(struct pci_controller *hose) @@ -42,17 +56,16 @@ int chip_pcie_configure(struct pci_controller *hose) struct list_head *next; unsigned int max_read_size, smallest_max_payload; int max_payloadsize; - unsigned long rc_index, node; unsigned long piuconfig0, value; unsigned int pcie_caps_offset; unsigned int rc_conf_value; u16 devctl, new_values; bool rc_ari_disabled = false, found = false; unsigned char bus_max_num; + void __iomem *rc_config_space_base; - node = hose->node; - rc_index = hose->index; - smallest_max_payload = read_rc_conf(node, rc_index, RC_EXP_DEVCAP); + rc_config_space_base = hose->rc_config_space_base; + smallest_max_payload = readl(rc_config_space_base + RC_EXP_DEVCAP); smallest_max_payload &= PCI_EXP_DEVCAP_PAYLOAD; bus_max_num = hose->busn_space->start; @@ -103,40 +116,40 @@ int chip_pcie_configure(struct pci_controller *hose) } if (rc_ari_disabled) { - rc_conf_value = read_rc_conf(node, rc_index, RC_EXP_DEVCTL2); + rc_conf_value = readl(rc_config_space_base + RC_EXP_DEVCTL2); rc_conf_value &= ~PCI_EXP_DEVCTL2_ARI; - write_rc_conf(node, rc_index, RC_EXP_DEVCTL2, rc_conf_value); + writel(rc_conf_value, (rc_config_space_base + RC_EXP_DEVCTL2)); } else { - rc_conf_value = read_rc_conf(node, rc_index, RC_EXP_DEVCTL2); + rc_conf_value = readl(rc_config_space_base + RC_EXP_DEVCTL2); rc_conf_value |= PCI_EXP_DEVCTL2_ARI; - write_rc_conf(node, rc_index, RC_EXP_DEVCTL2, rc_conf_value); + writel(rc_conf_value, (rc_config_space_base + RC_EXP_DEVCTL2)); } - rc_conf_value = read_rc_conf(node, rc_index, RC_EXP_DEVCAP); + rc_conf_value = readl(rc_config_space_base + RC_EXP_DEVCAP); rc_conf_value &= PCI_EXP_DEVCAP_PAYLOAD; max_payloadsize = rc_conf_value; if (max_payloadsize < smallest_max_payload) smallest_max_payload = max_payloadsize; max_read_size = 0x2; /* Limit to 512B */ - value = read_rc_conf(node, rc_index, RC_EXP_DEVCTL); + value = readl(rc_config_space_base + RC_EXP_DEVCTL); value &= ~(PCI_EXP_DEVCTL_PAYLOAD | PCI_EXP_DEVCTL_READRQ); value |= (max_read_size << 12) | (smallest_max_payload << 5); - write_rc_conf(node, rc_index, RC_EXP_DEVCTL, value); + writel(value, (rc_config_space_base + RC_EXP_DEVCTL)); new_values = (max_read_size << 12) | (smallest_max_payload << 5); - piuconfig0 = read_piu_ior0(node, rc_index, PIUCONFIG0); + piuconfig0 = readq(hose->piu_ior0_base + PIUCONFIG0); piuconfig0 &= ~(0x7fUL << 9); if (smallest_max_payload == 0x2) { piuconfig0 |= (0x20UL << 9); - write_piu_ior0(node, rc_index, PIUCONFIG0, piuconfig0); + writeq(piuconfig0, (hose->piu_ior0_base + PIUCONFIG0)); } else { piuconfig0 |= (0x40UL << 9); - write_piu_ior0(node, rc_index, PIUCONFIG0, piuconfig0); + writeq(piuconfig0, (hose->piu_ior0_base + PIUCONFIG0)); } pr_info("Node%ld RC%ld MPSS %luB, MRRS %luB, Piuconfig0 %#lx, ARI %s\n", - node, rc_index, (1UL << smallest_max_payload) << 7, + hose->node, hose->index, (1UL << smallest_max_payload) << 7, (1UL << max_read_size) << 7, piuconfig0, rc_ari_disabled ? "disabled" : "enabled"); @@ -175,92 +188,78 @@ int chip_pcie_configure(struct pci_controller *hose) return bus_max_num; } -static int check_pci_linkup(unsigned long node, unsigned long index) +static int check_pci_linkup(struct pci_controller *hose) { unsigned long rc_debug; if (is_guest_or_emul()) { - if (node == 0 && index == 0) + if (hose->node == 0 && hose->index == 0) return 0; else return 1; - } else { - rc_debug = read_piu_ior1(node, index, RCDEBUGINF1); } - return !(rc_debug == 0x111); + rc_debug = readq(hose->piu_ior1_base + RCDEBUGINF1); + + return !((rc_debug & 0x3fful) == 0x111); } -static void set_rc_piu(unsigned long node, unsigned long index) +static void set_rc_piu(struct pci_controller *hose) { unsigned int i __maybe_unused; unsigned int value; u32 rc_misc_ctrl; + void __iomem *rc_config_space_base; + void __iomem *piu_ior0_base; + void __iomem *piu_ior1_base; if (is_guest_or_emul()) return; + rc_config_space_base = hose->rc_config_space_base; + piu_ior0_base = hose->piu_ior0_base; + piu_ior1_base = hose->piu_ior1_base; + /* configure RC, set PCI-E root controller */ - write_rc_conf(node, index, RC_COMMAND, 0x00100007); - write_rc_conf(node, index, RC_PORT_LINK_CTL, 0x1f0020); - write_rc_conf(node, index, RC_EXP_DEVCTL, 0x2850); - write_rc_conf(node, index, RC_EXP_DEVCTL2, 0x6); + writel(0x00100007, (rc_config_space_base + RC_COMMAND)); + writel(0x1f0020, (rc_config_space_base + RC_PORT_LINK_CTL)); + writel(0x2850, (rc_config_space_base + RC_EXP_DEVCTL)); + writel(0x6, (rc_config_space_base + RC_EXP_DEVCTL2)); #ifdef CONFIG_UNCORE_XUELANG - write_rc_conf(node, index, RC_ORDER_RULE_CTL, 0x0100); + writel(0x0100, (rc_config_space_base + RC_ORDER_RULE_CTL)); #endif /* enable DBI_RO_WR_EN */ - rc_misc_ctrl = read_rc_conf(node, index, RC_MISC_CONTROL_1); - write_rc_conf(node, index, RC_MISC_CONTROL_1, rc_misc_ctrl | 0x1); + rc_misc_ctrl = readl(rc_config_space_base + RC_MISC_CONTROL_1); + writel(rc_misc_ctrl | 0x1, (rc_config_space_base + RC_MISC_CONTROL_1)); /* fix up DEVICE_ID_VENDOR_ID register */ value = (PCI_DEVICE_ID_SW64_ROOT_BRIDGE << 16) | PCI_VENDOR_ID_JN; - write_rc_conf(node, index, RC_VENDOR_ID, value); + writel(value, (rc_config_space_base + RC_VENDOR_ID)); /* set PCI-E root class code */ - value = read_rc_conf(node, index, RC_REVISION_ID); - write_rc_conf(node, index, RC_REVISION_ID, - (PCI_CLASS_BRIDGE_HOST << 16) | value); + value = readl(rc_config_space_base + RC_REVISION_ID); + writel((PCI_CLASS_BRIDGE_HOST << 16) | value, (rc_config_space_base + RC_REVISION_ID)); /* disable DBI_RO_WR_EN */ - write_rc_conf(node, index, RC_MISC_CONTROL_1, rc_misc_ctrl); + writel(rc_misc_ctrl, (rc_config_space_base + RC_MISC_CONTROL_1)); - write_rc_conf(node, index, RC_PRIMARY_BUS, 0xffffff); - write_piu_ior0(node, index, PIUCONFIG0, PIUCONFIG0_INIT_VAL); + writeq(PIUCONFIG0_INIT_VAL, (piu_ior0_base + PIUCONFIG0)); - write_piu_ior1(node, index, PIUCONFIG1, 0x2); - write_piu_ior1(node, index, ERRENABLE, -1); + writeq(0x2, (piu_ior1_base + PIUCONFIG1)); + writeq(-1, (piu_ior1_base + ERRENABLE)); /* set DMA offset value PCITODMA_OFFSET */ - write_piu_ior0(node, index, EPDMABAR, PCITODMA_OFFSET); + writeq(PCITODMA_OFFSET, (piu_ior0_base + EPDMABAR)); if (IS_ENABLED(CONFIG_PCI_MSI)) { - write_piu_ior0(node, index, MSIADDR, MSIX_MSG_ADDR); + writeq(MSIX_MSG_ADDR, (piu_ior0_base + MSIADDR)); #ifdef CONFIG_UNCORE_XUELANG for (i = 0; i < 256; i++) - write_piu_ior0(node, index, MSICONFIG0 + (i << 7), 0); + writeq(0, (piu_ior0_base + MSICONFIG0 + (i << 7))); #endif } } -static void set_intx(unsigned long node, unsigned long index, - unsigned long int_conf) -{ - if (is_guest_or_emul()) - return; - -#if defined(CONFIG_UNCORE_XUELANG) - write_piu_ior0(node, index, INTACONFIG, int_conf | (0x8UL << 10)); - write_piu_ior0(node, index, INTBCONFIG, int_conf | (0x4UL << 10)); - write_piu_ior0(node, index, INTCCONFIG, int_conf | (0x2UL << 10)); - write_piu_ior0(node, index, INTDCONFIG, int_conf | (0x1UL << 10)); -#elif defined(CONFIG_UNCORE_JUNZHANG) - write_piu_ior0(node, index, INTACONFIG, int_conf | (0x1UL << 10)); - write_piu_ior0(node, index, INTBCONFIG, int_conf | (0x2UL << 10)); - write_piu_ior0(node, index, INTCCONFIG, int_conf | (0x4UL << 10)); - write_piu_ior0(node, index, INTDCONFIG, int_conf | (0x8UL << 10)); -#endif -} - static unsigned long get_rc_enable(unsigned long node) { unsigned long rc_enable; @@ -296,6 +295,8 @@ static void hose_init(struct pci_controller *hose) hose->dense_io_base = pci_io_base | PCI_LEGACY_IO; hose->ep_config_space_base = __va(pci_io_base | PCI_EP_CFG); hose->rc_config_space_base = __va(pci_io_base | PCI_RC_CFG); + hose->piu_ior0_base = __va(MK_PIU_IOR0(hose->node, hose->index)); + hose->piu_ior1_base = __va(MK_PIU_IOR1(hose->node, hose->index)); hose->mem_space->start = pci_io_base + PCI_32BIT_MEMIO; hose->mem_space->end = hose->mem_space->start + PCI_32BIT_MEMIO_SIZE - 1; @@ -338,7 +339,6 @@ static struct sw64_pci_init_ops chip_pci_init_ops = { .hose_init = hose_init, .set_rc_piu = set_rc_piu, .check_pci_linkup = check_pci_linkup, - .set_intx = set_intx, }; void __init setup_chip_pci_ops(void) @@ -637,101 +637,62 @@ int sw64_pci_map_irq(const struct pci_dev *dev, u8 slot, u8 pin) } #ifdef CONFIG_ACPI -static void setup_intx_irqs(struct pci_controller *hose) -{ - unsigned long int_conf, node, val_node; - unsigned long index, irq; - int rcid; - node = hose->node; - index = hose->index; +enum pci_props { + PROP_RC_CONFIG_BASE = 0, + PROP_EP_CONFIG_BASE, + PROP_EP_MEM_32_BASE, + PROP_EP_MEM_64_BASE, + PROP_EP_IO_BASE, + PROP_PIU_IOR0_BASE, + PROP_PIU_IOR1_BASE, + PROP_RC_INDEX, + PROP_PCIE_IO_BASE, + PROP_NUM +}; - if (!node_online(node)) - val_node = next_node_in(node, node_online_map); - else - val_node = node; - irq = irq_alloc_descs_from(NR_IRQS_LEGACY, 2, val_node); - WARN_ON(irq < 0); - irq_set_chip_and_handler(irq, &dummy_irq_chip, handle_level_irq); - irq_set_status_flags(irq, IRQ_LEVEL); - hose->int_irq = irq; - irq_set_chip_and_handler(irq + 1, &dummy_irq_chip, handle_level_irq); - hose->service_irq = irq + 1; - rcid = cpu_to_rcid(0); - - printk_once(KERN_INFO "INTx are directed to node %d core %d.\n", - ((rcid >> 6) & 0x3), (rcid & 0x1f)); - int_conf = 1UL << 62 | rcid; /* rebase all intx on the first logical cpu */ - - set_intx(node, index, int_conf); - - set_pcieport_service_irq(node, index); -} +const char *prop_names[PROP_NUM] = { + "sw64,rc_config_base", + "sw64,ep_config_base", + "sw64,ep_mem_32_base", + "sw64,ep_mem_64_base", + "sw64,ep_io_base", + "sw64,piu_ior0_base", + "sw64,piu_ior1_base", + "sw64,rc_index", + "sw64,pcie_io_base" +}; static int sw64_pci_prepare_controller(struct pci_controller *hose, - struct acpi_device *adev) + struct fwnode_handle *fwnode) { - unsigned long long index, node; - unsigned long long rc_config_base_addr; - unsigned long long pci_io_base_addr; - unsigned long long ep_io_base_addr; - acpi_status rc; - - /* Get node from ACPI namespace */ - node = acpi_get_node(adev->handle); - if (node == NUMA_NO_NODE) { - dev_err(&adev->dev, "unable to get node ID\n"); - return -EEXIST; - } - - /* Get index from ACPI namespace */ - rc = acpi_evaluate_integer(adev->handle, "INDX", NULL, &index); - if (rc != AE_OK) { - dev_err(&adev->dev, "unable to retrieve INDX\n"); - return -EEXIST; - } - - /** - * Get Root Complex config space base address. - * - * For sw64, Root Complex config space base addr is different - * from Endpoint config space base address. Use MCFG table to - * pass Endpoint config space base address, and define Root Complex - * config space base address("RCCB") separately in the ACPI namespace. - */ - rc = acpi_evaluate_integer(adev->handle, - "RCCB", NULL, &rc_config_base_addr); - if (rc != AE_OK) { - dev_err(&adev->dev, "unable to retrieve RCCB\n"); - return -EEXIST; - } - - /* Get Root Complex I/O space base addr from ACPI namespace */ - rc = acpi_evaluate_integer(adev->handle, - "RCIO", NULL, &pci_io_base_addr); - if (rc != AE_OK) { - dev_err(&adev->dev, "unable to retrieve RCIO\n"); - return -EEXIST; - } - - /* Get Endpoint I/O space base addr from ACPI namespace */ - rc = acpi_evaluate_integer(adev->handle, - "EPIO", NULL, &ep_io_base_addr); - if (rc != AE_OK) { - dev_err(&adev->dev, "unable to retrieve EPIO\n"); - return -EEXIST; + u64 props[PROP_NUM]; + int i, ret; + + /* Get properties of Root Complex */ + for (i = 0; i < PROP_NUM; ++i) { + ret = fwnode_property_read_u64_array(fwnode, prop_names[i], + &props[i], 1); + if (ret) { + pr_err("unable to retrieve \"%s\"\n", + prop_names[i]); + return ret; + } } hose->iommu_enable = false; - hose->index = index; - hose->node = node; + + hose->index = props[PROP_RC_INDEX]; hose->sparse_mem_base = 0; hose->sparse_io_base = 0; - hose->dense_mem_base = pci_io_base_addr; - hose->dense_io_base = ep_io_base_addr; + hose->dense_mem_base = props[PROP_PCIE_IO_BASE]; + hose->dense_io_base = props[PROP_EP_IO_BASE]; - hose->rc_config_space_base = __va(rc_config_base_addr); + hose->rc_config_space_base = __va(props[PROP_RC_CONFIG_BASE]); + hose->ep_config_space_base = __va(props[PROP_EP_CONFIG_BASE]); + hose->piu_ior0_base = __va(props[PROP_PIU_IOR0_BASE]); + hose->piu_ior1_base = __va(props[PROP_PIU_IOR1_BASE]); hose->first_busno = 0xff; hose->last_busno = 0xff; @@ -750,17 +711,17 @@ static int sw64_pci_prepare_controller(struct pci_controller *hose, * 1. Root Complex enable * 2. Root Complex link up */ - set_rc_piu(hose->node, hose->index); - if (check_pci_linkup(hose->node, hose->index)) { + set_rc_piu(hose); + if (check_pci_linkup(hose)) { /** * Root Complex link up failed. * This usually means that no device on the slot. */ - dev_info(&adev->dev, ": failed to link up\n", + pr_info(": link down\n", hose->node, hose->index); } else { pci_mark_rc_linkup(hose->node, hose->index); - dev_info(&adev->dev, ": successfully link up\n", + pr_info(": successfully link up\n", hose->node, hose->index); } @@ -779,7 +740,6 @@ static int sw64_pci_ecam_init(struct pci_config_window *cfg) struct pci_controller *hose = NULL; struct device *dev = cfg->parent; struct acpi_device *adev = to_acpi_device(dev); - phys_addr_t mcfg_addr; int ret; /** @@ -807,17 +767,16 @@ static int sw64_pci_ecam_init(struct pci_config_window *cfg) if (!hose) return -ENOMEM; - /* Get Endpoint config space base address from MCFG table */ - mcfg_addr = cfg->res.start - (cfg->busr.start << cfg->ops->bus_shift); - - /** - * "__va(mcfg_addr)" is equal to "cfg->win", so we can also use - * "hose->ep_config_space_base = cfg->win" here - */ - hose->ep_config_space_base = __va(mcfg_addr); + /* Get node from ACPI namespace (_PXM) */ + hose->node = acpi_get_node(adev->handle); + if (hose->node == NUMA_NO_NODE) { + kfree(hose); + dev_err(&adev->dev, "unable to get node ID\n"); + return -EINVAL; + } /* Init pci_controller */ - ret = sw64_pci_prepare_controller(hose, adev); + ret = sw64_pci_prepare_controller(hose, &adev->fwnode); if (ret) { kfree(hose); dev_err(&adev->dev, "failed to init pci controller\n"); diff --git a/drivers/pci/hotplug/Kconfig b/drivers/pci/hotplug/Kconfig index 840a84bb5ee26e54ad6ec4abab52de5e43053715..db68cf727cb536915ae055066249c343eb3bf91b 100644 --- a/drivers/pci/hotplug/Kconfig +++ b/drivers/pci/hotplug/Kconfig @@ -111,6 +111,15 @@ config HOTPLUG_PCI_SHPC When in doubt, say N. +config HOTPLUG_PCI_PCIE_SUNWAY + bool "SUNWAY PCI Express Hotplug driver" + depends on SW64 && SUBARCH_C4 && !HOTPLUG_PCI_PCIE + help + Say Y here if you have a motherboard with a SUNWAY PCI Express Hotplug + controller. + + When in doubt, say N. + config HOTPLUG_PCI_POWERNV tristate "PowerPC PowerNV PCI Hotplug driver" depends on PPC_POWERNV && EEH diff --git a/drivers/pci/hotplug/Makefile b/drivers/pci/hotplug/Makefile index 5196983220df6bd529ef4dc7d3c1f622c71c3ef1..e7193995acdeaa74df815fbc99747cdca937526d 100644 --- a/drivers/pci/hotplug/Makefile +++ b/drivers/pci/hotplug/Makefile @@ -12,6 +12,7 @@ obj-$(CONFIG_HOTPLUG_PCI_IBM) += ibmphp.o # generic support. obj-$(CONFIG_HOTPLUG_PCI_PCIE) += pciehp.o +obj-$(CONFIG_HOTPLUG_PCI_PCIE_SUNWAY) += sunway_pciehp.o obj-$(CONFIG_HOTPLUG_PCI_CPCI_ZT5550) += cpcihp_zt5550.o obj-$(CONFIG_HOTPLUG_PCI_CPCI_GENERIC) += cpcihp_generic.o obj-$(CONFIG_HOTPLUG_PCI_SHPC) += shpchp.o @@ -70,3 +71,8 @@ shpchp-objs := shpchp_core.o \ shpchp_pci.o \ shpchp_sysfs.o \ shpchp_hpc.o + +sunway_pciehp-objs := sunway_pciehp_core.o \ + sunway_pciehp_ctrl.o \ + sunway_pciehp_pci.o \ + sunway_pciehp_hpc.o diff --git a/drivers/pci/hotplug/sunway_pciehp.h b/drivers/pci/hotplug/sunway_pciehp.h new file mode 100644 index 0000000000000000000000000000000000000000..5ef5e745543b546c5db9343982f0597b7c4aad28 --- /dev/null +++ b/drivers/pci/hotplug/sunway_pciehp.h @@ -0,0 +1,215 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * Sunway PCI Express Hot Plug Controller Driver + */ +#ifndef _SUNWAYPCIEHP_H +#define _SUNWAYPCIEHP_H + +#include +#include +#include +#include +#include /* signal_pending() */ +#include +#include +#include + +#include "../pcie/portdrv.h" + +#define MY_NAME "sunway_pciehp" + +extern bool sunway_pciehp_poll_mode; +extern int sunway_pciehp_poll_time; + +/* + * Set CONFIG_DYNAMIC_DEBUG=y and boot with 'dyndbg="file sunway_pciehp* +p"' to + * enable debug messages. + */ +#define ctrl_dbg(ctrl, format, arg...) \ + pci_dbg(ctrl->pci_dev, format, ## arg) +#define ctrl_err(ctrl, format, arg...) \ + pci_err(ctrl->pci_dev, format, ## arg) +#define ctrl_info(ctrl, format, arg...) \ + pci_info(ctrl->pci_dev, format, ## arg) +#define ctrl_warn(ctrl, format, arg...) \ + pci_warn(ctrl->pci_dev, format, ## arg) + +#define SLOT_NAME_SIZE 10 + +struct saved_piu_space { + unsigned long epdmabar; + unsigned long msiaddr; + unsigned long iommuexcpt_ctrl; + unsigned long dtbaseaddr; + unsigned long intaconfig; + unsigned long intbconfig; + unsigned long intcconfig; + unsigned long intdconfig; + unsigned long pmeintconfig; + unsigned long aererrintconfig; + unsigned long hpintconfig; + unsigned int state_saved:1; +}; + +/** + * struct controller - PCIe hotplug controller + * @slot_cap: cached copy of the Slot Capabilities register + * @slot_ctrl: cached copy of the Slot Control register + * @ctrl_lock: serializes writes to the Slot Control register + * @cmd_started: jiffies when the Slot Control register was last written; + * the next write is allowed 1 second later, absent a Command Completed + * interrupt (PCIe r4.0, sec 6.7.3.2) + * @cmd_busy: flag set on Slot Control register write, cleared by IRQ handler + * on reception of a Command Completed event + * @queue: wait queue to wake up on reception of a Command Completed event, + * used for synchronous writes to the Slot Control register + * @pending_events: used by the IRQ handler to save events retrieved from the + * Slot Status register for later consumption by the IRQ thread + * @notification_enabled: whether the IRQ was requested successfully + * @power_fault_detected: whether a power fault was detected by the hardware + * that has not yet been cleared by the user + * @poll_thread: thread to poll for slot events if no IRQ is available, + * enabled with pciehp_poll_mode module parameter + * @state: current state machine position + * @state_lock: protects reads and writes of @state; + * protects scheduling, execution and cancellation of @button_work + * @button_work: work item to turn the slot on or off after 5 seconds + * in response to an Attention Button press + * @hotplug_slot: structure registered with the PCI hotplug core + * @reset_lock: prevents access to the Data Link Layer Link Active bit in the + * Link Status register and to the Presence Detect State bit in the Slot + * Status register during a slot reset which may cause them to flap + * @ist_running: flag to keep user request waiting while IRQ thread is running + * @request_result: result of last user request submitted to the IRQ thread + * @requester: wait queue to wake up on completion of user request, + * used for synchronous slot enable/disable request via sysfs + * + * PCIe hotplug has a 1:1 relationship between controller and slot, hence + * unlike other drivers, the two aren't represented by separate structures. + */ +struct controller { + struct pci_dev *pci_dev; + + u32 slot_cap; /* capabilities and quirks */ + unsigned int inband_presence_disabled:1; + u16 slot_ctrl; /* control register access */ + struct mutex ctrl_lock; + unsigned long cmd_started; + unsigned int cmd_busy:1; + wait_queue_head_t queue; + + atomic_t pending_events; /* event handling */ + unsigned int notification_enabled:1; + unsigned int power_fault_detected; + struct task_struct *poll_thread; + + u8 state; /* state machine */ + struct mutex state_lock; + struct delayed_work button_work; + + struct hotplug_slot hotplug_slot; /* hotplug core interface */ + struct rw_semaphore reset_lock; + unsigned int ist_running; + int request_result; + wait_queue_head_t requester; + + struct saved_piu_space saved_piu; +}; + +/** + * DOC: Slot state + * + * @OFF_STATE: slot is powered off, no subordinate devices are enumerated + * @BLINKINGON_STATE: slot will be powered on after the 5 second delay, + * Power Indicator is blinking + * @BLINKINGOFF_STATE: slot will be powered off after the 5 second delay, + * Power Indicator is blinking + * @POWERON_STATE: slot is currently powering on + * @POWEROFF_STATE: slot is currently powering off + * @ON_STATE: slot is powered on, subordinate devices have been enumerated + */ +#define OFF_STATE 0 +#define BLINKINGON_STATE 1 +#define BLINKINGOFF_STATE 2 +#define POWERON_STATE 3 +#define POWEROFF_STATE 4 +#define ON_STATE 5 + +/** + * DOC: Flags to request an action from the IRQ thread + * + * These are stored together with events read from the Slot Status register, + * hence must be greater than its 16-bit width. + * + * %DISABLE_SLOT: Disable the slot in response to a user request via sysfs or + * an Attention Button press after the 5 second delay + * %RERUN_ISR: Used by the IRQ handler to inform the IRQ thread that the + * hotplug port was inaccessible when the interrupt occurred, requiring + * that the IRQ handler is rerun by the IRQ thread after it has made the + * hotplug port accessible by runtime resuming its parents to D0 + */ +#define DISABLE_SLOT (1 << 16) +#define RERUN_ISR (1 << 17) +#define SW64_POLL_DISABLE_SLOT (1 << 18) +#define SW64_POLL_ENABLE_SLOT (1 << 19) + +#define ATTN_BUTTN(ctrl) ((ctrl)->slot_cap & PCI_EXP_SLTCAP_ABP) +#define POWER_CTRL(ctrl) ((ctrl)->slot_cap & PCI_EXP_SLTCAP_PCP) +#define MRL_SENS(ctrl) ((ctrl)->slot_cap & PCI_EXP_SLTCAP_MRLSP) +#define ATTN_LED(ctrl) ((ctrl)->slot_cap & PCI_EXP_SLTCAP_AIP) +#define PWR_LED(ctrl) ((ctrl)->slot_cap & PCI_EXP_SLTCAP_PIP) +#define NO_CMD_CMPL(ctrl) ((ctrl)->slot_cap & PCI_EXP_SLTCAP_NCCS) +#define PSN(ctrl) (((ctrl)->slot_cap & PCI_EXP_SLTCAP_PSN) >> 19) + +#define HP_CTRL_FINISH 0x0 +#define HP_CTRL_INSERT 0x1 +#define HP_CTRL_REMOVE 0x2 + +void sunway_pciehp_request(struct controller *ctrl, int action); +void sunway_pciehp_handle_button_press(struct controller *ctrl); +void sunway_pciehp_handle_disable_request(struct controller *ctrl); +void sunway_pciehp_handle_presence_or_link_change(struct controller *ctrl, u32 events); +int sunway_pciehp_configure_device(struct controller *ctrl); +void sunway_pciehp_unconfigure_device(struct controller *ctrl, bool presence); +void sunway_pciehp_queue_pushbutton_work(struct work_struct *work); +struct controller *sunwayhpc_init(struct pci_dev *dev); +int sunway_pcie_init_notification(struct controller *ctrl); +void sunway_pcie_shutdown_notification(struct controller *ctrl); +void sunway_pcie_clear_hotplug_events(struct controller *ctrl); +int sunway_pciehp_power_on_slot(struct controller *ctrl); +void sunway_pciehp_power_off_slot(struct controller *ctrl); +void sunway_pciehp_get_power_status(struct controller *ctrl, u8 *status); + +#define INDICATOR_NOOP -1 /* Leave indicator unchanged */ +void sunway_pciehp_set_indicators(struct controller *ctrl, int pwr, int attn); + +void sunway_pciehp_get_latch_status(struct controller *ctrl, u8 *status); +int sunway_pciehp_query_power_fault(struct controller *ctrl); +int sunway_pciehp_card_present(struct controller *ctrl); +int sunway_pciehp_card_present_or_link_active(struct controller *ctrl); +int sunway_pciehp_check_link_status(struct controller *ctrl); +int sunway_pciehp_check_link_active(struct controller *ctrl); +void sunway_pciehp_release_ctrl(struct controller *ctrl); + +int sunway_pciehp_sysfs_enable_slot(struct hotplug_slot *hotplug_slot); +int sunway_pciehp_sysfs_disable_slot(struct hotplug_slot *hotplug_slot); +int sunway_pciehp_reset_slot(struct hotplug_slot *hotplug_slot, int probe); +int sunway_pciehp_get_attention_status(struct hotplug_slot *hotplug_slot, u8 *status); +int sunway_pciehp_set_raw_indicator_status(struct hotplug_slot *hotplug_slot, u8 status); +int sunway_pciehp_get_raw_indicator_status(struct hotplug_slot *hotplug_slot, u8 *status); + +int sunway_pciehp_link_enable(struct controller *ctrl); +int sunway_pciehp_link_disable(struct controller *ctrl); +void sunway_pciehp_restore_rc_piu(struct controller *ctrl); + +static inline const char *slot_name(struct controller *ctrl) +{ + return hotplug_slot_name(&ctrl->hotplug_slot); +} + +static inline struct controller *to_ctrl(struct hotplug_slot *hotplug_slot) +{ + return container_of(hotplug_slot, struct controller, hotplug_slot); +} + +#endif /* _PCIEHP_H */ diff --git a/drivers/pci/hotplug/sunway_pciehp_core.c b/drivers/pci/hotplug/sunway_pciehp_core.c new file mode 100644 index 0000000000000000000000000000000000000000..e79a074d25576e13f2e5f1970dd0fa238acb7c77 --- /dev/null +++ b/drivers/pci/hotplug/sunway_pciehp_core.c @@ -0,0 +1,278 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Sunway PCI Express Hot Plug Controller Driver + */ + +#define pr_fmt(fmt) "sunway_pciehp: " fmt +#define dev_fmt pr_fmt + +#include +#include +#include +#include +#include +#include + +#include "../pci.h" +#include "sunway_pciehp.h" + +/* Global variables */ +bool sunway_pciehp_poll_mode; +int sunway_pciehp_poll_time; + +#define DRIVER_VERSION "0.1" +#define DRIVER_DESC "Sunway PCI Express Hot Plug Controller Driver" + +MODULE_DESCRIPTION(DRIVER_DESC); +MODULE_LICENSE("GPL"); + +/* + * not really modular, but the easiest way to keep compat with existing + * bootargs behaviour is to continue using module_param here. + */ +module_param(sunway_pciehp_poll_mode, bool, 0644); +module_param(sunway_pciehp_poll_time, int, 0644); +MODULE_PARM_DESC(sunway_pciehp_poll_mode, "Using polling mechanism for hot-plug events or not"); +MODULE_PARM_DESC(sunway_pciehp_poll_time, "Polling mechanism frequency, in seconds"); + +#define PCIE_MODULE_NAME "sunway_pciehp" + +static int set_attention_status(struct hotplug_slot *slot, u8 value); +static int get_power_status(struct hotplug_slot *slot, u8 *value); +static int get_latch_status(struct hotplug_slot *slot, u8 *value); +static int get_adapter_status(struct hotplug_slot *slot, u8 *value); + +static int init_slot(struct controller *ctrl) +{ + struct hotplug_slot_ops *ops; + char name[SLOT_NAME_SIZE]; + int retval; + + /* Setup hotplug slot ops */ + ops = kzalloc(sizeof(*ops), GFP_KERNEL); + if (!ops) + return -ENOMEM; + + ops->enable_slot = sunway_pciehp_sysfs_enable_slot; + ops->disable_slot = sunway_pciehp_sysfs_disable_slot; + ops->get_power_status = get_power_status; + ops->get_adapter_status = get_adapter_status; + ops->reset_slot = sunway_pciehp_reset_slot; + if (MRL_SENS(ctrl)) + ops->get_latch_status = get_latch_status; + if (ATTN_LED(ctrl)) { + ops->get_attention_status = sunway_pciehp_get_attention_status; + ops->set_attention_status = set_attention_status; + } else if (ctrl->pci_dev->hotplug_user_indicators) { + ops->get_attention_status = sunway_pciehp_get_raw_indicator_status; + ops->set_attention_status = sunway_pciehp_set_raw_indicator_status; + } + + /* register this slot with the hotplug pci core */ + ctrl->hotplug_slot.ops = ops; + snprintf(name, SLOT_NAME_SIZE, "%u", PSN(ctrl)); + + retval = pci_hp_initialize(&ctrl->hotplug_slot, + ctrl->pci_dev->subordinate, 0, name); + if (retval) { + ctrl_err(ctrl, "pci_hp_initialize failed: error %d\n", retval); + kfree(ops); + } + return retval; +} + +static void cleanup_slot(struct controller *ctrl) +{ + struct hotplug_slot *hotplug_slot = &ctrl->hotplug_slot; + + pci_hp_destroy(hotplug_slot); + kfree(hotplug_slot->ops); +} + +/* + * set_attention_status - Turns the Attention Indicator on, off or blinking + */ +static int set_attention_status(struct hotplug_slot *hotplug_slot, u8 status) +{ + struct controller *ctrl = to_ctrl(hotplug_slot); + struct pci_dev *pdev = ctrl->pci_dev; + + if (status) + status <<= PCI_EXP_SLTCTL_ATTN_IND_SHIFT; + else + status = PCI_EXP_SLTCTL_ATTN_IND_OFF; + + pci_config_pm_runtime_get(pdev); + sunway_pciehp_set_indicators(ctrl, INDICATOR_NOOP, status); + pci_config_pm_runtime_put(pdev); + return 0; +} + +static int get_power_status(struct hotplug_slot *hotplug_slot, u8 *value) +{ + struct controller *ctrl = to_ctrl(hotplug_slot); + struct pci_dev *pdev = ctrl->pci_dev; + + pci_config_pm_runtime_get(pdev); + sunway_pciehp_get_power_status(ctrl, value); + pci_config_pm_runtime_put(pdev); + return 0; +} + +static int get_latch_status(struct hotplug_slot *hotplug_slot, u8 *value) +{ + struct controller *ctrl = to_ctrl(hotplug_slot); + struct pci_dev *pdev = ctrl->pci_dev; + + pci_config_pm_runtime_get(pdev); + sunway_pciehp_get_latch_status(ctrl, value); + pci_config_pm_runtime_put(pdev); + return 0; +} + +static int get_adapter_status(struct hotplug_slot *hotplug_slot, u8 *value) +{ + struct controller *ctrl = to_ctrl(hotplug_slot); + struct pci_dev *pdev = ctrl->pci_dev; + int ret; + + pci_config_pm_runtime_get(pdev); + ret = sunway_pciehp_card_present_or_link_active(ctrl); + pci_config_pm_runtime_put(pdev); + + if (ret < 0) + return ret; + + *value = ret; + return 0; +} + +/** + * sunway_pciehp_check_presence() - synthesize event if presence has changed + * + * On probe and resume, an explicit presence check is necessary to bring up an + * occupied slot or bring down an unoccupied slot. This can't be triggered by + * events in the Slot Status register, they may be stale and are therefore + * cleared. Secondly, sending an interrupt for "events that occur while + * interrupt generation is disabled [when] interrupt generation is subsequently + * enabled" is optional per PCIe r4.0, sec 6.7.3.4. + */ +static void sunway_pciehp_check_presence(struct controller *ctrl) +{ + int occupied; + + down_read(&ctrl->reset_lock); + mutex_lock(&ctrl->state_lock); + + occupied = sunway_pciehp_card_present_or_link_active(ctrl); + if ((occupied > 0 && (ctrl->state == OFF_STATE || + ctrl->state == BLINKINGON_STATE)) || + (!occupied && (ctrl->state == ON_STATE || + ctrl->state == BLINKINGOFF_STATE))) + sunway_pciehp_request(ctrl, PCI_EXP_SLTSTA_PDC); + + mutex_unlock(&ctrl->state_lock); + up_read(&ctrl->reset_lock); +} + +static int sunwayhp_init(struct pci_dev *dev) +{ + int rc; + struct controller *ctrl; + + if (!dev->subordinate) { + /* Can happen if we run out of bus numbers during probe */ + dev_err(&dev->dev, + "Hotplug bridge without secondary bus, ignoring\n"); + return -ENODEV; + } + + ctrl = sunwayhpc_init(dev); + if (!ctrl) { + dev_err(&dev->dev, "Controller initialization failed\n"); + return -ENODEV; + } + pci_set_drvdata(dev, ctrl); + + /* Setup the slot information structures */ + rc = init_slot(ctrl); + if (rc) { + if (rc == -EBUSY) + ctrl_warn(ctrl, "Slot already registered by another hotplug driver\n"); + else + ctrl_err(ctrl, "Slot initialization failed (%d)\n", rc); + goto err_out_release_ctlr; + } + + /* Enable events after we have setup the data structures */ + rc = sunway_pcie_init_notification(ctrl); + if (rc) { + ctrl_err(ctrl, "Notification initialization failed (%d)\n", rc); + goto err_out_free_ctrl_slot; + } + + /* Publish to user space */ + rc = pci_hp_add(&ctrl->hotplug_slot); + if (rc) { + ctrl_err(ctrl, "Publication to user space failed (%d)\n", rc); + goto err_out_shutdown_notification; + } + + sunway_pciehp_check_presence(ctrl); + + return 0; + +err_out_shutdown_notification: + sunway_pcie_shutdown_notification(ctrl); +err_out_free_ctrl_slot: + cleanup_slot(ctrl); +err_out_release_ctlr: + sunway_pciehp_release_ctrl(ctrl); + return -ENODEV; +} + +static void sunwayhp_remove(struct pci_dev *dev) +{ + struct controller *ctrl = pci_get_drvdata(dev); + + pci_hp_del(&ctrl->hotplug_slot); + sunway_pcie_shutdown_notification(ctrl); + cleanup_slot(ctrl); + sunway_pciehp_release_ctrl(ctrl); +} + +extern struct pci_controller *hose_head, **hose_tail; +static int __init sunway_pciehp_init(void) +{ + int retval; + struct pci_dev *pdev = NULL; + struct pci_controller *hose = NULL; + + pr_info(DRIVER_DESC " version: " DRIVER_VERSION "\n"); + + for (hose = hose_head; hose; hose = hose->next) { + pdev = pci_get_device(PCI_VENDOR_ID_JN, PCI_DEVICE_ID_SW64_ROOT_BRIDGE, pdev); + + retval = sunwayhp_init(pdev); + } + + return retval; +} + +static void __exit sunway_pciehp_exit(void) +{ + struct pci_dev *pdev = NULL; + struct pci_controller *hose = NULL; + + pr_info(DRIVER_DESC " version: " DRIVER_VERSION " unloaded\n"); + + for (hose = hose_head; hose; hose = hose->next) { + pdev = pci_get_device(PCI_VENDOR_ID_JN, PCI_DEVICE_ID_SW64_ROOT_BRIDGE, pdev); + + sunwayhp_remove(pdev); + } + +} + +module_init(sunway_pciehp_init); +module_exit(sunway_pciehp_exit); diff --git a/drivers/pci/hotplug/sunway_pciehp_ctrl.c b/drivers/pci/hotplug/sunway_pciehp_ctrl.c new file mode 100644 index 0000000000000000000000000000000000000000..52e79bfe7dd6ecb483dc82ff1ffabcd7c6d00ee2 --- /dev/null +++ b/drivers/pci/hotplug/sunway_pciehp_ctrl.c @@ -0,0 +1,671 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Sunway PCI Express Hot Plug Controller Driver + */ + +#define dev_fmt(fmt) "sunway_pciehp: " fmt + +#include +#include +#include +#include +#include +#include + +#include "sunway_pciehp.h" +#include "../pci.h" + +/* The following routines constitute the bulk of the + * hotplug controller logic + */ + +#define SAFE_REMOVAL true +#define SURPRISE_REMOVAL false + +static void set_slot_off(struct controller *ctrl) +{ + /* + * Turn off slot, turn on attention indicator, turn off power + * indicator + */ + if (POWER_CTRL(ctrl)) { + sunway_pciehp_power_off_slot(ctrl); + + /* + * After turning power off, we must wait for at least 1 second + * before taking any action that relies on power having been + * removed from the slot/adapter. + */ + msleep(1000); + } + + sunway_pciehp_set_indicators(ctrl, PCI_EXP_SLTCTL_PWR_IND_OFF, + PCI_EXP_SLTCTL_ATTN_IND_ON); +} + +/** + * board_added - Called after a board has been added to the system. + * @ctrl: PCIe hotplug controller where board is added + * + * Turns power on for the board. + * Configures board. + */ +static int board_added(struct controller *ctrl) +{ + int retval = 0; + struct pci_bus *parent = ctrl->pci_dev->subordinate; + + if (POWER_CTRL(ctrl)) { + /* Power on slot */ + retval = sunway_pciehp_power_on_slot(ctrl); + if (retval) + return retval; + } + + sunway_pciehp_set_indicators(ctrl, PCI_EXP_SLTCTL_PWR_IND_BLINK, + INDICATOR_NOOP); + + /* Check link training status */ + retval = sunway_pciehp_check_link_status(ctrl); + if (retval) + goto err_exit; + + /* Check for a power fault */ + if (ctrl->power_fault_detected || sunway_pciehp_query_power_fault(ctrl)) { + ctrl_err(ctrl, "Slot(%s): Power fault\n", slot_name(ctrl)); + retval = -EIO; + goto err_exit; + } + + retval = sunway_pciehp_configure_device(ctrl); + if (retval) { + if (retval != -EEXIST) { + ctrl_err(ctrl, "Cannot add device at %04x:%02x:00\n", + pci_domain_nr(parent), parent->number); + goto err_exit; + } + } + + sunway_pciehp_set_indicators(ctrl, PCI_EXP_SLTCTL_PWR_IND_ON, + PCI_EXP_SLTCTL_ATTN_IND_OFF); + return 0; + +err_exit: + set_slot_off(ctrl); + return retval; +} + +/** + * remove_board - Turn off slot and Power Indicator + * @ctrl: PCIe hotplug controller where board is being removed + * @safe_removal: whether the board is safely removed (versus surprise removed) + */ +static void remove_board(struct controller *ctrl, bool safe_removal) +{ + sunway_pciehp_unconfigure_device(ctrl, safe_removal); + + if (POWER_CTRL(ctrl)) { + sunway_pciehp_power_off_slot(ctrl); + + /* + * After turning power off, we must wait for at least 1 second + * before taking any action that relies on power having been + * removed from the slot/adapter. + */ + msleep(1000); + + /* Ignore link or presence changes caused by power off */ + atomic_and(~(PCI_EXP_SLTSTA_DLLSC | PCI_EXP_SLTSTA_PDC), + &ctrl->pending_events); + } + + /* turn off Green LED */ + sunway_pciehp_set_indicators(ctrl, PCI_EXP_SLTCTL_PWR_IND_OFF, + INDICATOR_NOOP); +} + +static int sunway_pciehp_enable_slot(struct controller *ctrl); +static int sunway_pciehp_disable_slot(struct controller *ctrl, bool safe_removal); + +void sunway_pciehp_request(struct controller *ctrl, int action) +{ + atomic_or(action, &ctrl->pending_events); + if (!sunway_pciehp_poll_mode) + irq_wake_thread(ctrl->pci_dev->irq, ctrl); +} + +void sunway_pciehp_queue_pushbutton_work(struct work_struct *work) +{ + struct controller *ctrl = container_of(work, struct controller, + button_work.work); + + mutex_lock(&ctrl->state_lock); + switch (ctrl->state) { + case BLINKINGOFF_STATE: + sunway_pciehp_request(ctrl, DISABLE_SLOT); + break; + case BLINKINGON_STATE: + sunway_pciehp_request(ctrl, PCI_EXP_SLTSTA_PDC); + break; + default: + break; + } + mutex_unlock(&ctrl->state_lock); +} + +void sunway_pciehp_handle_button_press(struct controller *ctrl) +{ + mutex_lock(&ctrl->state_lock); + switch (ctrl->state) { + case OFF_STATE: + case ON_STATE: + if (ctrl->state == ON_STATE) { + ctrl->state = BLINKINGOFF_STATE; + ctrl_info(ctrl, "Slot(%s): Powering off due to button press\n", + slot_name(ctrl)); + } else { + ctrl->state = BLINKINGON_STATE; + ctrl_info(ctrl, "Slot(%s) Powering on due to button press\n", + slot_name(ctrl)); + } + /* blink power indicator and turn off attention */ + sunway_pciehp_set_indicators(ctrl, PCI_EXP_SLTCTL_PWR_IND_BLINK, + PCI_EXP_SLTCTL_ATTN_IND_OFF); + schedule_delayed_work(&ctrl->button_work, 5 * HZ); + break; + case BLINKINGOFF_STATE: + case BLINKINGON_STATE: + /* + * Cancel if we are still blinking; this means that we + * press the attention again before the 5 sec. limit + * expires to cancel hot-add or hot-remove + */ + ctrl_info(ctrl, "Slot(%s): Button cancel\n", slot_name(ctrl)); + cancel_delayed_work(&ctrl->button_work); + if (ctrl->state == BLINKINGOFF_STATE) { + ctrl->state = ON_STATE; + sunway_pciehp_set_indicators(ctrl, PCI_EXP_SLTCTL_PWR_IND_ON, + PCI_EXP_SLTCTL_ATTN_IND_OFF); + } else { + ctrl->state = OFF_STATE; + sunway_pciehp_set_indicators(ctrl, PCI_EXP_SLTCTL_PWR_IND_OFF, + PCI_EXP_SLTCTL_ATTN_IND_OFF); + } + ctrl_info(ctrl, "Slot(%s): Action canceled due to button press\n", + slot_name(ctrl)); + break; + default: + ctrl_err(ctrl, "Slot(%s): Ignoring invalid state %#x\n", + slot_name(ctrl), ctrl->state); + break; + } + mutex_unlock(&ctrl->state_lock); +} + +void sunway_pciehp_handle_disable_request(struct controller *ctrl) +{ + mutex_lock(&ctrl->state_lock); + switch (ctrl->state) { + case BLINKINGON_STATE: + case BLINKINGOFF_STATE: + cancel_delayed_work(&ctrl->button_work); + break; + } + ctrl->state = POWEROFF_STATE; + mutex_unlock(&ctrl->state_lock); + + ctrl->request_result = sunway_pciehp_disable_slot(ctrl, SAFE_REMOVAL); +} + +void sunway_pciehp_save_config_space(struct controller *ctrl) +{ + struct pci_dev *pdev = ctrl->pci_dev; + int i; + + if (!pdev->state_saved) { + for (i = 0; i < 16; i++) + pci_read_config_dword(pdev, i * 4, &pdev->saved_config_space[i]); + pdev->state_saved = true; + } +} + +void sunway_pciehp_save_rc_piu(struct controller *ctrl) +{ + struct pci_bus *bus = ctrl->pci_dev->bus; + struct pci_controller *hose = pci_bus_to_pci_controller(bus); + void __iomem *piu_ior0_base; + + piu_ior0_base = hose->piu_ior0_base; + + sunway_pciehp_save_config_space(ctrl); + + if (!ctrl->saved_piu.state_saved) { + ctrl->saved_piu.epdmabar = readq(piu_ior0_base + EPDMABAR); + ctrl->saved_piu.msiaddr = readq(piu_ior0_base + MSIADDR); + ctrl->saved_piu.iommuexcpt_ctrl = readq(piu_ior0_base + IOMMUEXCPT_CTRL); + ctrl->saved_piu.dtbaseaddr = readq(piu_ior0_base + DTBASEADDR); + + ctrl->saved_piu.intaconfig = readq(piu_ior0_base + INTACONFIG); + ctrl->saved_piu.intbconfig = readq(piu_ior0_base + INTBCONFIG); + ctrl->saved_piu.intcconfig = readq(piu_ior0_base + INTCCONFIG); + ctrl->saved_piu.intdconfig = readq(piu_ior0_base + INTDCONFIG); + ctrl->saved_piu.pmeintconfig = readq(piu_ior0_base + PMEINTCONFIG); + ctrl->saved_piu.aererrintconfig = readq(piu_ior0_base + AERERRINTCONFIG); + ctrl->saved_piu.hpintconfig = readq(piu_ior0_base + HPINTCONFIG); + + ctrl->saved_piu.state_saved = true; + } +} + +void sunway_pciehp_start(struct hotplug_slot *hotplug_slot) +{ + struct controller *ctrl = to_ctrl(hotplug_slot); + struct pci_dev *pdev = ctrl->pci_dev; + struct pci_bus *bus = pdev->bus; + struct pci_controller *hose = pci_bus_to_pci_controller(bus); + unsigned long piu_value; + bool hardware_auto = true; + u16 slot_ctrl; + void __iomem *piu_ior0_base; + void __iomem *piu_ior1_base; + + piu_ior0_base = hose->piu_ior0_base; + piu_ior1_base = hose->piu_ior1_base; + + switch (ctrl->state) { + case OFF_STATE: + if (sunway_pciehp_poll_mode) { + ctrl_dbg(ctrl, "%s: poll mode\n", __func__); + pcie_capability_read_word(pdev, PCI_EXP_SLTCTL, &slot_ctrl); + /* poll mode */ + slot_ctrl &= ~PCI_EXP_SLTCTL_HPIE; + pcie_capability_write_word(pdev, PCI_EXP_SLTCTL, slot_ctrl); + + sunway_pciehp_request(ctrl, SW64_POLL_ENABLE_SLOT); + } else { + pcie_capability_read_word(pdev, PCI_EXP_SLTCTL, &slot_ctrl); + /* interrupt mode */ + if (hardware_auto) { + ctrl_dbg(ctrl, "%s: hardware auto enable\n", __func__); + slot_ctrl &= ~(PCI_EXP_SLTCTL_ABPE | + PCI_EXP_SLTCTL_MRLSCE | + PCI_EXP_SLTCTL_PDCE | + PCI_EXP_SLTCTL_CCIE); + slot_ctrl |= (PCI_EXP_SLTCTL_PFDE | + PCI_EXP_SLTCTL_HPIE | + PCI_EXP_SLTCTL_DLLSCE); + } else { + ctrl_dbg(ctrl, "%s: hardware auto disable\n", __func__); + slot_ctrl &= ~(PCI_EXP_SLTCTL_ABPE | PCI_EXP_SLTCTL_MRLSCE); + slot_ctrl |= (PCI_EXP_SLTCTL_PFDE | + PCI_EXP_SLTCTL_PDCE | + PCI_EXP_SLTCTL_CCIE | + PCI_EXP_SLTCTL_HPIE | + PCI_EXP_SLTCTL_DLLSCE); + } + pcie_capability_write_word(pdev, PCI_EXP_SLTCTL, slot_ctrl); + + sunway_pciehp_set_indicators(ctrl, INDICATOR_NOOP, + PCI_EXP_SLTCTL_ATTN_IND_BLINK); + + writeq(HP_CTRL_INSERT, (piu_ior0_base + HP_CTRL)); + } + break; + case ON_STATE: + sunway_pciehp_save_rc_piu(ctrl); + if (sunway_pciehp_poll_mode) { + ctrl_dbg(ctrl, "%s: poll mode\n", __func__); + pcie_capability_read_word(pdev, PCI_EXP_SLTCTL, &slot_ctrl); + /* poll mode */ + slot_ctrl &= ~PCI_EXP_SLTCTL_HPIE; + pcie_capability_write_word(pdev, PCI_EXP_SLTCTL, slot_ctrl); + + sunway_pciehp_request(ctrl, SW64_POLL_DISABLE_SLOT); + } else { + ctrl_dbg(ctrl, "%s: int mode\n", __func__); + pcie_capability_read_word(pdev, PCI_EXP_SLTCTL, &slot_ctrl); + /* interrupt mode */ + slot_ctrl &= ~(PCI_EXP_SLTCTL_ABPE | PCI_EXP_SLTCTL_MRLSCE); + slot_ctrl |= (PCI_EXP_SLTCTL_PFDE | + PCI_EXP_SLTCTL_PDCE | + PCI_EXP_SLTCTL_CCIE | + PCI_EXP_SLTCTL_HPIE | + PCI_EXP_SLTCTL_DLLSCE); + pcie_capability_write_word(pdev, PCI_EXP_SLTCTL, slot_ctrl); + + sunway_pciehp_set_indicators(ctrl, INDICATOR_NOOP, + PCI_EXP_SLTCTL_ATTN_IND_BLINK); + sunway_pciehp_link_disable(ctrl); + + while (1) { + piu_value = readq(piu_ior1_base + NEWLTSSMSTATE0); + piu_value &= 0xff; + + if (piu_value == 0x19) + break; + + udelay(10); + } + + writeq(HP_CTRL_REMOVE, (piu_ior0_base + HP_CTRL)); + + sunway_pciehp_request(ctrl, DISABLE_SLOT); + } + break; + default: + break; + } +} + +static void pciehp_restore_config_space_range(struct pci_dev *pdev, + int start, int end) +{ + int index; + + for (index = end; index >= start; index--) { + pci_write_config_dword(pdev, 4 * index, + pdev->saved_config_space[index]); + } +} + +void sunway_pciehp_restore_config_space(struct controller *ctrl) +{ + struct pci_dev *pdev = ctrl->pci_dev; + + if (pdev->state_saved) { + pciehp_restore_config_space_range(pdev, 12, 15); + pciehp_restore_config_space_range(pdev, 9, 11); + pciehp_restore_config_space_range(pdev, 0, 8); + pdev->state_saved = false; + } +} + +void sunway_pciehp_restore_rc_piu(struct controller *ctrl) +{ + struct pci_bus *bus = ctrl->pci_dev->bus; + struct pci_controller *hose = pci_bus_to_pci_controller(bus); + void __iomem *piu_ior0_base; + + piu_ior0_base = hose->piu_ior0_base; + + sunway_pciehp_restore_config_space(ctrl); + + if (ctrl->saved_piu.state_saved) { + writeq(ctrl->saved_piu.epdmabar, (piu_ior0_base + EPDMABAR)); + writeq(ctrl->saved_piu.msiaddr, (piu_ior0_base + MSIADDR)); + writeq(ctrl->saved_piu.iommuexcpt_ctrl, (piu_ior0_base + IOMMUEXCPT_CTRL)); + writeq(ctrl->saved_piu.dtbaseaddr, (piu_ior0_base + DTBASEADDR)); + + writeq(ctrl->saved_piu.intaconfig, (piu_ior0_base + INTACONFIG)); + writeq(ctrl->saved_piu.intbconfig, (piu_ior0_base + INTBCONFIG)); + writeq(ctrl->saved_piu.intcconfig, (piu_ior0_base + INTCCONFIG)); + writeq(ctrl->saved_piu.intdconfig, (piu_ior0_base + INTDCONFIG)); + writeq(ctrl->saved_piu.pmeintconfig, (piu_ior0_base + PMEINTCONFIG)); + writeq(ctrl->saved_piu.aererrintconfig, (piu_ior0_base + AERERRINTCONFIG)); + writeq(ctrl->saved_piu.hpintconfig, (piu_ior0_base + HPINTCONFIG)); + + ctrl->saved_piu.state_saved = false; + } +} + +void sunway_pciehp_end(struct controller *ctrl, bool insert) +{ + struct pci_dev *pdev = ctrl->pci_dev; + struct pci_bus *bus = pdev->bus; + struct pci_controller *hose = pci_bus_to_pci_controller(bus); + unsigned long piu_value; + u16 slot_ctrl; + void __iomem *piu_ior0_base; + void __iomem *piu_ior1_base; + + piu_ior0_base = hose->piu_ior0_base; + piu_ior1_base = hose->piu_ior1_base; + + if (insert) { + writeq(HP_CTRL_FINISH, (piu_ior0_base + HP_CTRL)); + } else { + sunway_pciehp_set_indicators(ctrl, INDICATOR_NOOP, + PCI_EXP_SLTCTL_ATTN_IND_OFF); + sunway_pciehp_link_enable(ctrl); + + mdelay(100); + + while (1) { + piu_value = readq(piu_ior1_base + NEWLTSSMSTATE0); + piu_value &= 0xff; + + if (piu_value == 0x0) + break; + + udelay(10); + } + + writeq(HP_ENABLE_INTD_CORE0, (piu_ior0_base + HPINTCONFIG)); + + pcie_capability_read_word(pdev, PCI_EXP_SLTCTL, &slot_ctrl); + slot_ctrl |= (PCI_EXP_SLTCTL_PFDE | + PCI_EXP_SLTCTL_PDCE | + PCI_EXP_SLTCTL_CCIE | + PCI_EXP_SLTCTL_HPIE | + PCI_EXP_SLTCTL_PCC | + PCI_EXP_SLTCTL_DLLSCE); + pcie_capability_write_word(pdev, PCI_EXP_SLTCTL, slot_ctrl); + + writeq(HP_CTRL_FINISH, (piu_ior0_base + HP_CTRL)); + } +} + +void sunway_pciehp_handle_presence_or_link_change(struct controller *ctrl, u32 events) +{ + int present, link_active; + + /* + * If the slot is on and presence or link has changed, turn it off. + * Even if it's occupied again, we cannot assume the card is the same. + */ + mutex_lock(&ctrl->state_lock); + switch (ctrl->state) { + case BLINKINGOFF_STATE: + cancel_delayed_work(&ctrl->button_work); + fallthrough; + case ON_STATE: + ctrl->state = POWEROFF_STATE; + mutex_unlock(&ctrl->state_lock); + if (events & PCI_EXP_SLTSTA_DLLSC) + ctrl_info(ctrl, "Slot(%s): Link Down\n", + slot_name(ctrl)); + if (events & PCI_EXP_SLTSTA_PDC) + ctrl_info(ctrl, "Slot(%s): Card not present\n", + slot_name(ctrl)); + sunway_pciehp_disable_slot(ctrl, SURPRISE_REMOVAL); + break; + default: + mutex_unlock(&ctrl->state_lock); + break; + } + + /* Turn the slot on if it's occupied or link is up */ + mutex_lock(&ctrl->state_lock); + present = sunway_pciehp_card_present(ctrl); + link_active = sunway_pciehp_check_link_active(ctrl); + if (present <= 0 && link_active <= 0) { + sunway_pciehp_end(ctrl, false); + mutex_unlock(&ctrl->state_lock); + return; + } + + switch (ctrl->state) { + case BLINKINGON_STATE: + cancel_delayed_work(&ctrl->button_work); + fallthrough; + case OFF_STATE: + ctrl->state = POWERON_STATE; + mutex_unlock(&ctrl->state_lock); + if (present) + ctrl_info(ctrl, "Slot(%s): Card present\n", + slot_name(ctrl)); + if (link_active) + ctrl_info(ctrl, "Slot(%s): Link Up\n", + slot_name(ctrl)); + ctrl->request_result = sunway_pciehp_enable_slot(ctrl); + sunway_pciehp_end(ctrl, true); + break; + default: + mutex_unlock(&ctrl->state_lock); + break; + } +} + +static int __sunway_pciehp_enable_slot(struct controller *ctrl) +{ + u8 getstatus = 0; + + if (MRL_SENS(ctrl)) { + sunway_pciehp_get_latch_status(ctrl, &getstatus); + if (getstatus) { + ctrl_info(ctrl, "Slot(%s): Latch open\n", + slot_name(ctrl)); + return -ENODEV; + } + } + + if (POWER_CTRL(ctrl)) { + sunway_pciehp_get_power_status(ctrl, &getstatus); + if (getstatus) { + ctrl_info(ctrl, "Slot(%s): Already enabled\n", + slot_name(ctrl)); + return 0; + } + } + + return board_added(ctrl); +} + +static int sunway_pciehp_enable_slot(struct controller *ctrl) +{ + int ret; + + pm_runtime_get_sync(&ctrl->pci_dev->dev); + ret = __sunway_pciehp_enable_slot(ctrl); + if (ret && ATTN_BUTTN(ctrl)) + /* may be blinking */ + sunway_pciehp_set_indicators(ctrl, PCI_EXP_SLTCTL_PWR_IND_OFF, + INDICATOR_NOOP); + pm_runtime_put(&ctrl->pci_dev->dev); + + mutex_lock(&ctrl->state_lock); + ctrl->state = ret ? OFF_STATE : ON_STATE; + mutex_unlock(&ctrl->state_lock); + + return ret; +} + +static int __sunway_pciehp_disable_slot(struct controller *ctrl, bool safe_removal) +{ + u8 getstatus = 0; + + if (POWER_CTRL(ctrl)) { + sunway_pciehp_get_power_status(ctrl, &getstatus); + if (!getstatus) { + ctrl_info(ctrl, "Slot(%s): Already disabled\n", + slot_name(ctrl)); + return -EINVAL; + } + } + + remove_board(ctrl, safe_removal); + return 0; +} + +static int sunway_pciehp_disable_slot(struct controller *ctrl, bool safe_removal) +{ + int ret; + + pm_runtime_get_sync(&ctrl->pci_dev->dev); + ret = __sunway_pciehp_disable_slot(ctrl, safe_removal); + pm_runtime_put(&ctrl->pci_dev->dev); + + mutex_lock(&ctrl->state_lock); + ctrl->state = OFF_STATE; + mutex_unlock(&ctrl->state_lock); + + return ret; +} + +int sunway_pciehp_sysfs_enable_slot(struct hotplug_slot *hotplug_slot) +{ + struct controller *ctrl = to_ctrl(hotplug_slot); + + mutex_lock(&ctrl->state_lock); + switch (ctrl->state) { + case BLINKINGON_STATE: + case OFF_STATE: + mutex_unlock(&ctrl->state_lock); + /* + * The IRQ thread becomes a no-op if the user pulls out the + * card before the thread wakes up, so initialize to -ENODEV. + */ + ctrl->request_result = -ENODEV; + sunway_pciehp_request(ctrl, PCI_EXP_SLTSTA_PDC); + + wait_event(ctrl->requester, + !atomic_read(&ctrl->pending_events) && + !ctrl->ist_running); + + sunway_pciehp_start(hotplug_slot); + return ctrl->request_result; + case POWERON_STATE: + ctrl_info(ctrl, "Slot(%s): Already in powering on state\n", + slot_name(ctrl)); + break; + case BLINKINGOFF_STATE: + case ON_STATE: + case POWEROFF_STATE: + ctrl_info(ctrl, "Slot(%s): Already enabled\n", + slot_name(ctrl)); + break; + default: + ctrl_err(ctrl, "Slot(%s): Invalid state %#x\n", + slot_name(ctrl), ctrl->state); + break; + } + mutex_unlock(&ctrl->state_lock); + + return -ENODEV; +} + +int sunway_pciehp_sysfs_disable_slot(struct hotplug_slot *hotplug_slot) +{ + struct controller *ctrl = to_ctrl(hotplug_slot); + + mutex_lock(&ctrl->state_lock); + switch (ctrl->state) { + case BLINKINGOFF_STATE: + case ON_STATE: + sunway_pciehp_start(hotplug_slot); + + mutex_unlock(&ctrl->state_lock); + wait_event(ctrl->requester, + !atomic_read(&ctrl->pending_events) && + !ctrl->ist_running); + + return ctrl->request_result; + case POWEROFF_STATE: + ctrl_info(ctrl, "Slot(%s): Already in powering off state\n", + slot_name(ctrl)); + break; + case BLINKINGON_STATE: + case OFF_STATE: + case POWERON_STATE: + ctrl_info(ctrl, "Slot(%s): Already disabled\n", + slot_name(ctrl)); + break; + default: + ctrl_err(ctrl, "Slot(%s): Invalid state %#x\n", + slot_name(ctrl), ctrl->state); + break; + } + mutex_unlock(&ctrl->state_lock); + + return -ENODEV; +} diff --git a/drivers/pci/hotplug/sunway_pciehp_hpc.c b/drivers/pci/hotplug/sunway_pciehp_hpc.c new file mode 100644 index 0000000000000000000000000000000000000000..bd4556dbc3d430e09cd1c0a9f330b7ae7857edc7 --- /dev/null +++ b/drivers/pci/hotplug/sunway_pciehp_hpc.c @@ -0,0 +1,1153 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Sunway PCI Express PCI Hot Plug Driver + */ + +#define dev_fmt(fmt) "sunway_pciehp: " fmt + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "../pci.h" +#include "sunway_pciehp.h" + +#include +#include + +static const struct dmi_system_id inband_presence_disabled_dmi_table[] = { + /* + * Match all Dell systems, as some Dell systems have inband + * presence disabled on NVMe slots (but don't support the bit to + * report it). Setting inband presence disabled should have no + * negative effect, except on broken hotplug slots that never + * assert presence detect--and those will still work, they will + * just have a bit of extra delay before being probed. + */ + { + .ident = "Dell System", + .matches = { + DMI_MATCH(DMI_OEM_STRING, "Dell System"), + }, + }, + {} +}; + +static inline struct pci_dev *ctrl_dev(struct controller *ctrl) +{ + return ctrl->pci_dev; +} + +static irqreturn_t sunway_pciehp_isr(int irq, void *dev_id); +static irqreturn_t sunway_pciehp_ist(int irq, void *dev_id); +static int sunway_pciehp_poll_start(void *data); + +static inline int sunway_pciehp_request_irq(struct controller *ctrl) +{ + int retval, irq = ctrl->pci_dev->irq; + + if (sunway_pciehp_poll_mode) { + ctrl->poll_thread = kthread_run(&sunway_pciehp_poll_start, ctrl, + "sunway_pciehp_poll_start-%s", + slot_name(ctrl)); + return PTR_ERR_OR_ZERO(ctrl->poll_thread); + } + + /* Installs the interrupt handler */ + retval = request_threaded_irq(irq, sunway_pciehp_isr, sunway_pciehp_ist, + IRQF_SHARED, MY_NAME, ctrl); + if (retval) + ctrl_err(ctrl, "Cannot get irq %d for the hotplug controller\n", + irq); + return retval; +} + +static inline void sunway_pciehp_free_irq(struct controller *ctrl) +{ + if (sunway_pciehp_poll_mode) + kthread_stop(ctrl->poll_thread); + else + free_irq(ctrl->pci_dev->irq, ctrl); +} + +static int pcie_poll_cmd(struct controller *ctrl, int timeout) +{ + struct pci_dev *pdev = ctrl_dev(ctrl); + u16 slot_status; + + do { + pcie_capability_read_word(pdev, PCI_EXP_SLTSTA, &slot_status); + if (slot_status == (u16) ~0) { + ctrl_info(ctrl, "%s: no response from device\n", + __func__); + return 0; + } + + if (slot_status & PCI_EXP_SLTSTA_CC) { + pcie_capability_write_word(pdev, PCI_EXP_SLTSTA, + PCI_EXP_SLTSTA_CC); + return 1; + } + msleep(10); + timeout -= 10; + } while (timeout >= 0); + return 0; /* timeout */ +} + +static void pcie_wait_cmd(struct controller *ctrl) +{ + unsigned int msecs = sunway_pciehp_poll_mode ? 2500 : 1000; + unsigned long duration = msecs_to_jiffies(msecs); + unsigned long cmd_timeout = ctrl->cmd_started + duration; + unsigned long now, timeout; + int rc; + + /* + * If the controller does not generate notifications for command + * completions, we never need to wait between writes. + */ + if (NO_CMD_CMPL(ctrl)) + return; + + if (!ctrl->cmd_busy) + return; + + /* + * Even if the command has already timed out, we want to call + * pcie_poll_cmd() so it can clear PCI_EXP_SLTSTA_CC. + */ + now = jiffies; + if (time_before_eq(cmd_timeout, now)) + timeout = 1; + else + timeout = cmd_timeout - now; + + if (ctrl->slot_ctrl & PCI_EXP_SLTCTL_HPIE && + ctrl->slot_ctrl & PCI_EXP_SLTCTL_CCIE) + rc = wait_event_timeout(ctrl->queue, !ctrl->cmd_busy, timeout); + else + rc = pcie_poll_cmd(ctrl, jiffies_to_msecs(timeout)); + + if (!rc) + ctrl_info(ctrl, "Timeout on hotplug command %#06x (issued %u msec ago)\n", + ctrl->slot_ctrl, + jiffies_to_msecs(jiffies - ctrl->cmd_started)); +} + +#define CC_ERRATUM_MASK (PCI_EXP_SLTCTL_PCC | \ + PCI_EXP_SLTCTL_PIC | \ + PCI_EXP_SLTCTL_AIC | \ + PCI_EXP_SLTCTL_EIC) + +static void pcie_do_write_cmd(struct controller *ctrl, u16 cmd, + u16 mask, bool wait) +{ + struct pci_dev *pdev = ctrl_dev(ctrl); + u16 slot_ctrl_orig, slot_ctrl; + + mutex_lock(&ctrl->ctrl_lock); + + /* + * Always wait for any previous command that might still be in progress + */ + pcie_wait_cmd(ctrl); + + pcie_capability_read_word(pdev, PCI_EXP_SLTCTL, &slot_ctrl); + if (slot_ctrl == (u16) ~0) { + ctrl_info(ctrl, "%s: no response from device\n", __func__); + goto out; + } + + slot_ctrl_orig = slot_ctrl; + slot_ctrl &= ~mask; + slot_ctrl |= (cmd & mask); + ctrl->cmd_busy = 1; + smp_mb(); + ctrl->slot_ctrl = slot_ctrl; + pcie_capability_write_word(pdev, PCI_EXP_SLTCTL, slot_ctrl); + ctrl->cmd_started = jiffies; + + /* + * Controllers with the Intel CF118 and similar errata advertise + * Command Completed support, but they only set Command Completed + * if we change the "Control" bits for power, power indicator, + * attention indicator, or interlock. If we only change the + * "Enable" bits, they never set the Command Completed bit. + */ + if ((slot_ctrl_orig & CC_ERRATUM_MASK) == (slot_ctrl & CC_ERRATUM_MASK)) + ctrl->cmd_busy = 0; + + /* + * Optionally wait for the hardware to be ready for a new command, + * indicating completion of the above issued command. + */ + if (wait) + pcie_wait_cmd(ctrl); + +out: + mutex_unlock(&ctrl->ctrl_lock); +} + +/** + * pcie_write_cmd - Issue controller command + * @ctrl: controller to which the command is issued + * @cmd: command value written to slot control register + * @mask: bitmask of slot control register to be modified + */ +static void pcie_write_cmd(struct controller *ctrl, u16 cmd, u16 mask) +{ + pcie_do_write_cmd(ctrl, cmd, mask, true); +} + +/* Same as above without waiting for the hardware to latch */ +static void pcie_write_cmd_nowait(struct controller *ctrl, u16 cmd, u16 mask) +{ + pcie_do_write_cmd(ctrl, cmd, mask, false); +} + +/** + * sunway_pciehp_check_link_active() - Is the link active + * @ctrl: PCIe hotplug controller + * + * Check whether the downstream link is currently active. Note it is + * possible that the card is removed immediately after this so the + * caller may need to take it into account. + * + * If the hotplug controller itself is not available anymore returns + * %-ENODEV. + */ +int sunway_pciehp_check_link_active(struct controller *ctrl) +{ + struct pci_dev *pdev = ctrl_dev(ctrl); + u16 lnk_status; + int ret; + + ret = pcie_capability_read_word(pdev, PCI_EXP_LNKSTA, &lnk_status); + if (ret == PCIBIOS_DEVICE_NOT_FOUND || lnk_status == (u16)~0) + return -ENODEV; + + ret = !!(lnk_status & PCI_EXP_LNKSTA_DLLLA); + ctrl_dbg(ctrl, "%s: lnk_status = %x\n", __func__, lnk_status); + + return ret; +} + +static bool pcie_wait_link_active(struct controller *ctrl) +{ + struct pci_dev *pdev = ctrl_dev(ctrl); + struct pci_bus *bus = pdev->bus; + struct pci_controller *hose = pci_bus_to_pci_controller(bus); + + if (pcie_wait_for_link(pdev, true)) { + pci_mark_rc_linkup(hose->node, hose->index); + sunway_pciehp_restore_rc_piu(ctrl); + return true; + } + + return false; +} + +static bool pci_bus_check_dev(struct pci_bus *bus, int devfn) +{ + u32 l; + int count = 0; + int delay = 1000, step = 20; + bool found = false; + + do { + found = pci_bus_read_dev_vendor_id(bus, devfn, &l, 0); + count++; + + if (found) + break; + + msleep(step); + delay -= step; + } while (delay > 0); + + if (count > 1) + pr_debug("pci %04x:%02x:%02x.%d id reading try %d times with interval %d ms to get %08x\n", + pci_domain_nr(bus), bus->number, PCI_SLOT(devfn), + PCI_FUNC(devfn), count, step, l); + + return found; +} + +static void pcie_wait_for_presence(struct pci_dev *pdev) +{ + int timeout = 1250; + u16 slot_status; + + do { + pcie_capability_read_word(pdev, PCI_EXP_SLTSTA, &slot_status); + if (slot_status & PCI_EXP_SLTSTA_PDS) + return; + msleep(10); + timeout -= 10; + } while (timeout > 0); +} + +static bool pci_bus_check_linkup(struct pci_controller *hose) +{ + int count = 0; + int delay = 1000, step = 20; + bool linkup = false; + + do { + if (sw64_chip_init->pci_init.check_pci_linkup(hose) == 0) { + udelay(10); + if (sw64_chip_init->pci_init.check_pci_linkup(hose) == 0) + linkup = true; + } + count++; + + if (linkup) + break; + + msleep(step); + delay -= step; + } while (delay > 0); + + if (count > 1) + pr_debug("Node %ld RC %ld linkup reading try %d times with interval %d ms\n", + hose->node, hose->index, count, step); + + return linkup; +} + +int sunway_pciehp_check_link_status(struct controller *ctrl) +{ + struct pci_dev *pdev = ctrl_dev(ctrl); + struct pci_bus *bus = pdev->bus; + struct pci_controller *hose = pci_bus_to_pci_controller(bus); + bool found, linkup; + u16 lnk_status; + + if (!pcie_wait_link_active(ctrl)) { + ctrl_info(ctrl, "Slot(%s): No link\n", slot_name(ctrl)); + return -1; + } + + if (ctrl->inband_presence_disabled) + pcie_wait_for_presence(pdev); + + /* wait 1000ms and check RC linkup before read pci conf */ + msleep(1000); + linkup = pci_bus_check_linkup(hose); + + if (!linkup) + return -1; + + /* try read device id in 1s */ + found = pci_bus_check_dev(ctrl->pci_dev->subordinate, + PCI_DEVFN(0, 0)); + + /* ignore link or presence changes up to this point */ + if (found) + atomic_and(~(PCI_EXP_SLTSTA_DLLSC | PCI_EXP_SLTSTA_PDC), + &ctrl->pending_events); + + pcie_capability_read_word(pdev, PCI_EXP_LNKSTA, &lnk_status); + ctrl_dbg(ctrl, "%s: lnk_status = %x\n", __func__, lnk_status); + if ((lnk_status & PCI_EXP_LNKSTA_LT) || + !(lnk_status & PCI_EXP_LNKSTA_NLW)) { + ctrl_info(ctrl, "Slot(%s): Cannot train link: status %#06x\n", + slot_name(ctrl), lnk_status); + return -1; + } + + pcie_update_link_speed(ctrl->pci_dev->subordinate, lnk_status); + + if (!found) { + ctrl_info(ctrl, "Slot(%s): No device found\n", + slot_name(ctrl)); + return -1; + } + + return 0; +} + +static int __sunway_pciehp_link_set(struct controller *ctrl, bool enable) +{ + struct pci_dev *pdev = ctrl_dev(ctrl); + u16 lnk_ctrl; + + pcie_capability_read_word(pdev, PCI_EXP_LNKCTL, &lnk_ctrl); + + if (enable) + lnk_ctrl &= ~PCI_EXP_LNKCTL_LD; + else + lnk_ctrl |= PCI_EXP_LNKCTL_LD; + + pcie_capability_write_word(pdev, PCI_EXP_LNKCTL, lnk_ctrl); + ctrl_dbg(ctrl, "%s: lnk_ctrl = %x\n", __func__, lnk_ctrl); + + return 0; +} + +int sunway_pciehp_link_enable(struct controller *ctrl) +{ + return __sunway_pciehp_link_set(ctrl, true); +} + +int sunway_pciehp_link_disable(struct controller *ctrl) +{ + struct pci_dev *pdev = ctrl_dev(ctrl); + struct pci_bus *bus = pdev->bus; + struct pci_controller *hose = pci_bus_to_pci_controller(bus); + int ret; + + ret = __sunway_pciehp_link_set(ctrl, false); + pci_clear_rc_linkup(hose->node, hose->index); + + return ret; +} + +int sunway_pciehp_get_raw_indicator_status(struct hotplug_slot *hotplug_slot, + u8 *status) +{ + struct controller *ctrl = to_ctrl(hotplug_slot); + struct pci_dev *pdev = ctrl_dev(ctrl); + u16 slot_ctrl; + + pci_config_pm_runtime_get(pdev); + pcie_capability_read_word(pdev, PCI_EXP_SLTCTL, &slot_ctrl); + pci_config_pm_runtime_put(pdev); + *status = (slot_ctrl & (PCI_EXP_SLTCTL_AIC | PCI_EXP_SLTCTL_PIC)) >> 6; + return 0; +} + +int sunway_pciehp_get_attention_status(struct hotplug_slot *hotplug_slot, u8 *status) +{ + struct controller *ctrl = to_ctrl(hotplug_slot); + struct pci_dev *pdev = ctrl_dev(ctrl); + u16 slot_ctrl; + + pci_config_pm_runtime_get(pdev); + pcie_capability_read_word(pdev, PCI_EXP_SLTCTL, &slot_ctrl); + pci_config_pm_runtime_put(pdev); + ctrl_dbg(ctrl, "%s: SLOTCTRL %x, value read %x\n", __func__, + pci_pcie_cap(ctrl->pci_dev) + PCI_EXP_SLTCTL, slot_ctrl); + + switch (slot_ctrl & PCI_EXP_SLTCTL_AIC) { + case PCI_EXP_SLTCTL_ATTN_IND_ON: + *status = 1; /* On */ + break; + case PCI_EXP_SLTCTL_ATTN_IND_BLINK: + *status = 2; /* Blink */ + break; + case PCI_EXP_SLTCTL_ATTN_IND_OFF: + *status = 0; /* Off */ + break; + default: + *status = 0xFF; + break; + } + + return 0; +} + +void sunway_pciehp_get_power_status(struct controller *ctrl, u8 *status) +{ + struct pci_dev *pdev = ctrl_dev(ctrl); + u16 slot_ctrl; + + pcie_capability_read_word(pdev, PCI_EXP_SLTCTL, &slot_ctrl); + + ctrl_dbg(ctrl, "%s: SLOTCTRL %x value read %x\n", __func__, + pci_pcie_cap(ctrl->pci_dev) + PCI_EXP_SLTCTL, slot_ctrl); + + switch (slot_ctrl & PCI_EXP_SLTCTL_PCC) { + case PCI_EXP_SLTCTL_PWR_ON: + *status = 1; /* On */ + break; + case PCI_EXP_SLTCTL_PWR_OFF: + *status = 0; /* Off */ + break; + default: + *status = 0xFF; + break; + } +} + +void sunway_pciehp_get_latch_status(struct controller *ctrl, u8 *status) +{ + struct pci_dev *pdev = ctrl_dev(ctrl); + u16 slot_status; + + pcie_capability_read_word(pdev, PCI_EXP_SLTSTA, &slot_status); + *status = !!(slot_status & PCI_EXP_SLTSTA_MRLSS); +} + +/** + * sunway_pciehp_card_present() - Is the card present + * @ctrl: PCIe hotplug controller + * + * Function checks whether the card is currently present in the slot and + * in that case returns true. Note it is possible that the card is + * removed immediately after the check so the caller may need to take + * this into account. + * + * It the hotplug controller itself is not available anymore returns + * %-ENODEV. + */ +int sunway_pciehp_card_present(struct controller *ctrl) +{ + struct pci_dev *pdev = ctrl_dev(ctrl); + u16 slot_status; + int ret; + + ret = pcie_capability_read_word(pdev, PCI_EXP_SLTSTA, &slot_status); + if (ret == PCIBIOS_DEVICE_NOT_FOUND || slot_status == (u16)~0) + return -ENODEV; + + return !!(slot_status & PCI_EXP_SLTSTA_PDS); +} + +/** + * sunway_pciehp_card_present_or_link_active() - whether given slot is occupied + * @ctrl: PCIe hotplug controller + * + * Unlike pciehp_card_present(), which determines presence solely from the + * Presence Detect State bit, this helper also returns true if the Link Active + * bit is set. This is a concession to broken hotplug ports which hardwire + * Presence Detect State to zero, such as Wilocity's [1ae9:0200]. + * + * Returns: %1 if the slot is occupied and %0 if it is not. If the hotplug + * port is not present anymore returns %-ENODEV. + */ +int sunway_pciehp_card_present_or_link_active(struct controller *ctrl) +{ + int ret; + + ret = sunway_pciehp_card_present(ctrl); + if (ret) + return ret; + + return sunway_pciehp_check_link_active(ctrl); +} + +int sunway_pciehp_query_power_fault(struct controller *ctrl) +{ + struct pci_dev *pdev = ctrl_dev(ctrl); + u16 slot_status; + + pcie_capability_read_word(pdev, PCI_EXP_SLTSTA, &slot_status); + return !!(slot_status & PCI_EXP_SLTSTA_PFD); +} + +int sunway_pciehp_set_raw_indicator_status(struct hotplug_slot *hotplug_slot, + u8 status) +{ + struct controller *ctrl = to_ctrl(hotplug_slot); + struct pci_dev *pdev = ctrl_dev(ctrl); + + pci_config_pm_runtime_get(pdev); + pcie_write_cmd_nowait(ctrl, status << 6, + PCI_EXP_SLTCTL_AIC | PCI_EXP_SLTCTL_PIC); + pci_config_pm_runtime_put(pdev); + return 0; +} + +/** + * sunway_pciehp_set_indicators() - set attention indicator, power indicator, or both + * @ctrl: PCIe hotplug controller + * @pwr: one of: + * PCI_EXP_SLTCTL_PWR_IND_ON + * PCI_EXP_SLTCTL_PWR_IND_BLINK + * PCI_EXP_SLTCTL_PWR_IND_OFF + * @attn: one of: + * PCI_EXP_SLTCTL_ATTN_IND_ON + * PCI_EXP_SLTCTL_ATTN_IND_BLINK + * PCI_EXP_SLTCTL_ATTN_IND_OFF + * + * Either @pwr or @attn can also be INDICATOR_NOOP to leave that indicator + * unchanged. + */ +void sunway_pciehp_set_indicators(struct controller *ctrl, int pwr, int attn) +{ + u16 cmd = 0, mask = 0; + + if (PWR_LED(ctrl) && pwr != INDICATOR_NOOP) { + cmd |= (pwr & PCI_EXP_SLTCTL_PIC); + mask |= PCI_EXP_SLTCTL_PIC; + } + + if (ATTN_LED(ctrl) && attn != INDICATOR_NOOP) { + cmd |= (attn & PCI_EXP_SLTCTL_AIC); + mask |= PCI_EXP_SLTCTL_AIC; + } + + if (cmd) { + pcie_write_cmd_nowait(ctrl, cmd, mask); + ctrl_dbg(ctrl, "%s: SLOTCTRL %x write cmd %x\n", __func__, + pci_pcie_cap(ctrl->pci_dev) + PCI_EXP_SLTCTL, cmd); + } +} + +int sunway_pciehp_power_on_slot(struct controller *ctrl) +{ + struct pci_dev *pdev = ctrl_dev(ctrl); + u16 slot_status; + int retval; + + /* Clear power-fault bit from previous power failures */ + pcie_capability_read_word(pdev, PCI_EXP_SLTSTA, &slot_status); + if (slot_status & PCI_EXP_SLTSTA_PFD) + pcie_capability_write_word(pdev, PCI_EXP_SLTSTA, + PCI_EXP_SLTSTA_PFD); + ctrl->power_fault_detected = 0; + + pcie_write_cmd(ctrl, PCI_EXP_SLTCTL_PWR_ON, PCI_EXP_SLTCTL_PCC); + ctrl_dbg(ctrl, "%s: SLOTCTRL %x write cmd %x\n", __func__, + pci_pcie_cap(ctrl->pci_dev) + PCI_EXP_SLTCTL, + PCI_EXP_SLTCTL_PWR_ON); + + retval = sunway_pciehp_link_enable(ctrl); + if (retval) + ctrl_err(ctrl, "%s: Can not enable the link!\n", __func__); + + return retval; +} + +void sunway_pciehp_power_off_slot(struct controller *ctrl) +{ + pcie_write_cmd(ctrl, PCI_EXP_SLTCTL_PWR_OFF, PCI_EXP_SLTCTL_PCC); + ctrl_dbg(ctrl, "%s: SLOTCTRL %x write cmd %x\n", __func__, + pci_pcie_cap(ctrl->pci_dev) + PCI_EXP_SLTCTL, + PCI_EXP_SLTCTL_PWR_OFF); +} + +void sunway_pciehp_poll(struct controller *ctrl, u32 events) +{ + struct pci_dev *pdev = ctrl_dev(ctrl); + struct pci_bus *bus = pdev->bus; + struct pci_controller *hose = pci_bus_to_pci_controller(bus); + unsigned long piu_value; + u16 slot_ctrl; + int i; + void __iomem *piu_ior0_base; + void __iomem *piu_ior1_base; + + piu_ior0_base = hose->piu_ior0_base; + piu_ior1_base = hose->piu_ior1_base; + + if (events & SW64_POLL_DISABLE_SLOT) { + while (1) { + piu_value = readq(piu_ior1_base + NEWLTSSMSTATE0); + piu_value &= 0xff; + + if (piu_value == 0x19) + break; + + udelay(10); + } + + writeq(HP_CTRL_REMOVE, (piu_ior0_base + HP_CTRL)); + + while (1) { + piu_value = readq(piu_ior0_base + HP_WATCHOUT); + piu_value >>= 24; + + if (piu_value == 0x19) + break; + + udelay(10); + } + + sunway_pciehp_link_enable(ctrl); + + mdelay(100); + while (1) { + piu_value = readq(piu_ior1_base + NEWLTSSMSTATE0); + piu_value &= 0xff; + + if (piu_value == 0x0) + break; + + udelay(10); + } + + pcie_capability_read_word(pdev, PCI_EXP_SLTCTL, &slot_ctrl); + slot_ctrl &= ~(PCI_EXP_SLTCTL_ABPE | + PCI_EXP_SLTCTL_PFDE | + PCI_EXP_SLTCTL_MRLSCE | + PCI_EXP_SLTCTL_PDCE | + PCI_EXP_SLTCTL_CCIE | + PCI_EXP_SLTCTL_HPIE); + slot_ctrl |= (PCI_EXP_SLTCTL_ATTN_IND_OFF | + PCI_EXP_SLTCTL_PWR_IND_OFF | + PCI_EXP_SLTCTL_DLLSCE); + pcie_capability_write_word(pdev, PCI_EXP_SLTCTL, slot_ctrl); + + writeq(HP_CTRL_FINISH, (piu_ior0_base + HP_CTRL)); + + ctrl->state = OFF_STATE; + } + + if (events & SW64_POLL_ENABLE_SLOT) { + writeq(HP_CTRL_INSERT, (piu_ior0_base + HP_CTRL)); + + for (i = 0; i < 30; i++) { + if (pcie_wait_for_link(pdev, true)) { + pci_mark_rc_linkup(hose->node, hose->index); + sunway_pciehp_restore_rc_piu(ctrl); + + writeq(HP_CTRL_FINISH, (piu_ior0_base + HP_CTRL)); + + pcie_capability_read_word(pdev, PCI_EXP_SLTCTL, &slot_ctrl); + slot_ctrl &= ~PCI_EXP_SLTCTL_PWR_OFF; + pcie_capability_write_word(pdev, PCI_EXP_SLTCTL, slot_ctrl); + + ctrl->state = ON_STATE; + return; + } + + if (sunway_pciehp_query_power_fault(ctrl)) { + pcie_capability_write_word(pdev, PCI_EXP_SLTSTA, + PCI_EXP_SLTSTA_PFD); + udelay(10); + if (sunway_pciehp_query_power_fault(ctrl)) { + ctrl_err(ctrl, "power fault\n"); + return; + } + } + } + + ctrl_err(ctrl, "insert failed\n"); + } +} + +static irqreturn_t sunway_pciehp_isr(int irq, void *dev_id) +{ + struct controller *ctrl = (struct controller *)dev_id; + struct pci_dev *pdev = ctrl_dev(ctrl); + struct device *parent = pdev->dev.parent; + u16 status, events = 0; + + /* + * Interrupts only occur in D3hot or shallower and only if enabled + * in the Slot Control register (PCIe r4.0, sec 6.7.3.4). + */ + if (pdev->current_state == PCI_D3cold || + (!(ctrl->slot_ctrl & PCI_EXP_SLTCTL_HPIE) && !sunway_pciehp_poll_mode)) + return IRQ_NONE; + + /* + * Keep the port accessible by holding a runtime PM ref on its parent. + * Defer resume of the parent to the IRQ thread if it's suspended. + * Mask the interrupt until then. + */ + if (parent) { + pm_runtime_get_noresume(parent); + if (!pm_runtime_active(parent)) { + pm_runtime_put(parent); + disable_irq_nosync(irq); + atomic_or(RERUN_ISR, &ctrl->pending_events); + return IRQ_WAKE_THREAD; + } + } + + if (sunway_pciehp_poll_mode) { + if (atomic_read(&ctrl->pending_events) & SW64_POLL_DISABLE_SLOT) { + sunway_pciehp_link_disable(ctrl); + goto out; + } else if (atomic_read(&ctrl->pending_events) & SW64_POLL_ENABLE_SLOT) + goto out; + + return IRQ_NONE; + } + +read_status: + pcie_capability_read_word(pdev, PCI_EXP_SLTSTA, &status); + if (status == (u16) ~0) { + ctrl_info(ctrl, "%s: no response from device\n", __func__); + if (parent) + pm_runtime_put(parent); + return IRQ_NONE; + } + + /* + * Slot Status contains plain status bits as well as event + * notification bits; right now we only want the event bits. + */ + status &= PCI_EXP_SLTSTA_ABP | PCI_EXP_SLTSTA_PFD | + PCI_EXP_SLTSTA_PDC | PCI_EXP_SLTSTA_CC | + PCI_EXP_SLTSTA_DLLSC; + + /* + * If we've already reported a power fault, don't report it again + * until we've done something to handle it. + */ + if (ctrl->power_fault_detected) + status &= ~PCI_EXP_SLTSTA_PFD; + + events |= status; + if (!events) { + if (parent) + pm_runtime_put(parent); + return IRQ_NONE; + } + + if (status) { + pcie_capability_write_word(pdev, PCI_EXP_SLTSTA, events); + + /* + * In MSI mode, all event bits must be zero before the port + * will send a new interrupt (PCIe Base Spec r5.0 sec 6.7.3.4). + * So re-read the Slot Status register in case a bit was set + * between read and write. + */ + if (pci_dev_msi_enabled(pdev) && !sunway_pciehp_poll_mode) + goto read_status; + } + + ctrl_dbg(ctrl, "pending interrupts %#06x from Slot Status\n", events); + if (parent) + pm_runtime_put(parent); + + /* + * Command Completed notifications are not deferred to the + * IRQ thread because it may be waiting for their arrival. + */ + if (events & PCI_EXP_SLTSTA_CC) { + ctrl->cmd_busy = 0; + smp_mb(); + wake_up(&ctrl->queue); + + if (events == PCI_EXP_SLTSTA_CC) + return IRQ_HANDLED; + + events &= ~PCI_EXP_SLTSTA_CC; + } + +out: + if (pdev->ignore_hotplug) { + ctrl_dbg(ctrl, "ignoring hotplug event %#06x\n", events); + return IRQ_HANDLED; + } + + /* Save pending events for consumption by IRQ thread. */ + atomic_or(events, &ctrl->pending_events); + return IRQ_WAKE_THREAD; +} + +static irqreturn_t sunway_pciehp_ist(int irq, void *dev_id) +{ + struct controller *ctrl = (struct controller *)dev_id; + struct pci_dev *pdev = ctrl_dev(ctrl); + irqreturn_t ret; + u32 events; + + ctrl->ist_running = true; + pci_config_pm_runtime_get(pdev); + + /* rerun sunway_pciehp_isr() if the port was inaccessible on interrupt */ + if (atomic_fetch_and(~RERUN_ISR, &ctrl->pending_events) & RERUN_ISR) { + ret = sunway_pciehp_isr(irq, dev_id); + enable_irq(irq); + if (ret != IRQ_WAKE_THREAD) + goto out; + } + + synchronize_hardirq(irq); + events = atomic_xchg(&ctrl->pending_events, 0); + if (!events) { + ret = IRQ_NONE; + goto out; + } + + if (sunway_pciehp_poll_mode) { + sunway_pciehp_poll(ctrl, events); + ret = IRQ_HANDLED; + goto out; + } + + /* Check Attention Button Pressed */ + if (events & PCI_EXP_SLTSTA_ABP) { + ctrl_info(ctrl, "Slot(%s): Attention button pressed\n", + slot_name(ctrl)); + sunway_pciehp_handle_button_press(ctrl); + } + + /* Check Power Fault Detected */ + if ((events & PCI_EXP_SLTSTA_PFD) && !ctrl->power_fault_detected) { + ctrl->power_fault_detected = 1; + ctrl_err(ctrl, "Slot(%s): Power fault\n", slot_name(ctrl)); + sunway_pciehp_set_indicators(ctrl, PCI_EXP_SLTCTL_PWR_IND_OFF, + PCI_EXP_SLTCTL_ATTN_IND_ON); + } + + /* + * Disable requests have higher priority than Presence Detect Changed + * or Data Link Layer State Changed events. + */ + down_read(&ctrl->reset_lock); + + if (events & DISABLE_SLOT) + sunway_pciehp_handle_disable_request(ctrl); + else if (events & (PCI_EXP_SLTSTA_PDC | PCI_EXP_SLTSTA_DLLSC)) + sunway_pciehp_handle_presence_or_link_change(ctrl, events); + + up_read(&ctrl->reset_lock); + + ret = IRQ_HANDLED; +out: + pci_config_pm_runtime_put(pdev); + ctrl->ist_running = false; + wake_up(&ctrl->requester); + return ret; +} + +static int sunway_pciehp_poll_start(void *data) +{ + struct controller *ctrl = data; + + schedule_timeout_idle(10 * HZ); /* start with 10 sec delay */ + + while (!kthread_should_stop()) { + /* poll for interrupt events or user requests */ + while (sunway_pciehp_isr(IRQ_NOTCONNECTED, ctrl) == IRQ_WAKE_THREAD || + atomic_read(&ctrl->pending_events)) + sunway_pciehp_ist(IRQ_NOTCONNECTED, ctrl); + + if (sunway_pciehp_poll_time <= 0 || sunway_pciehp_poll_time > 60) + sunway_pciehp_poll_time = 2; /* clamp to sane value */ + + schedule_timeout_idle(sunway_pciehp_poll_time * HZ); + } + + return 0; +} + +static void pcie_enable_notification(struct controller *ctrl) +{ + u16 cmd, mask; + + /* + * TBD: Power fault detected software notification support. + * + * Power fault detected software notification is not enabled + * now, because it caused power fault detected interrupt storm + * on some machines. On those machines, power fault detected + * bit in the slot status register was set again immediately + * when it is cleared in the interrupt service routine, and + * next power fault detected interrupt was notified again. + */ + + /* + * Always enable link events: thus link-up and link-down shall + * always be treated as hotplug and unplug respectively. Enable + * presence detect only if Attention Button is not present. + */ + cmd = PCI_EXP_SLTCTL_DLLSCE; + if (ATTN_BUTTN(ctrl)) + cmd |= PCI_EXP_SLTCTL_ABPE; + else + cmd |= PCI_EXP_SLTCTL_PDCE; + if (!sunway_pciehp_poll_mode) + cmd |= PCI_EXP_SLTCTL_HPIE | PCI_EXP_SLTCTL_CCIE; + + mask = (PCI_EXP_SLTCTL_PDCE | PCI_EXP_SLTCTL_ABPE | + PCI_EXP_SLTCTL_PFDE | + PCI_EXP_SLTCTL_HPIE | PCI_EXP_SLTCTL_CCIE | + PCI_EXP_SLTCTL_DLLSCE); + + pcie_write_cmd_nowait(ctrl, cmd, mask); + ctrl_dbg(ctrl, "%s: SLOTCTRL %x write cmd %x\n", __func__, + pci_pcie_cap(ctrl->pci_dev) + PCI_EXP_SLTCTL, cmd); +} + +static void pcie_disable_notification(struct controller *ctrl) +{ + u16 mask; + + mask = (PCI_EXP_SLTCTL_PDCE | PCI_EXP_SLTCTL_ABPE | + PCI_EXP_SLTCTL_MRLSCE | PCI_EXP_SLTCTL_PFDE | + PCI_EXP_SLTCTL_HPIE | PCI_EXP_SLTCTL_CCIE | + PCI_EXP_SLTCTL_DLLSCE); + pcie_write_cmd(ctrl, 0, mask); + ctrl_dbg(ctrl, "%s: SLOTCTRL %x write cmd %x\n", __func__, + pci_pcie_cap(ctrl->pci_dev) + PCI_EXP_SLTCTL, 0); +} + +void sunway_pcie_clear_hotplug_events(struct controller *ctrl) +{ + pcie_capability_write_word(ctrl_dev(ctrl), PCI_EXP_SLTSTA, + PCI_EXP_SLTSTA_PDC | PCI_EXP_SLTSTA_DLLSC); +} + +/* + * sunway_pciehp has a 1:1 bus:slot relationship so we ultimately want a secondary + * bus reset of the bridge, but at the same time we want to ensure that it is + * not seen as a hot-unplug, followed by the hot-plug of the device. Thus, + * disable link state notification and presence detection change notification + * momentarily, if we see that they could interfere. Also, clear any spurious + * events after. + */ +int sunway_pciehp_reset_slot(struct hotplug_slot *hotplug_slot, int probe) +{ + struct controller *ctrl = to_ctrl(hotplug_slot); + struct pci_dev *pdev = ctrl_dev(ctrl); + u16 stat_mask = 0, ctrl_mask = 0; + int rc; + + if (probe) + return 0; + + down_write(&ctrl->reset_lock); + + if (!ATTN_BUTTN(ctrl)) { + ctrl_mask |= PCI_EXP_SLTCTL_PDCE; + stat_mask |= PCI_EXP_SLTSTA_PDC; + } + ctrl_mask |= PCI_EXP_SLTCTL_DLLSCE; + stat_mask |= PCI_EXP_SLTSTA_DLLSC; + + pcie_write_cmd(ctrl, 0, ctrl_mask); + ctrl_dbg(ctrl, "%s: SLOTCTRL %x write cmd %x\n", __func__, + pci_pcie_cap(ctrl->pci_dev) + PCI_EXP_SLTCTL, 0); + + rc = pci_bridge_secondary_bus_reset(ctrl->pci_dev); + + pcie_capability_write_word(pdev, PCI_EXP_SLTSTA, stat_mask); + pcie_write_cmd_nowait(ctrl, ctrl_mask, ctrl_mask); + ctrl_dbg(ctrl, "%s: SLOTCTRL %x write cmd %x\n", __func__, + pci_pcie_cap(ctrl->pci_dev) + PCI_EXP_SLTCTL, ctrl_mask); + + up_write(&ctrl->reset_lock); + return rc; +} + +int sunway_pcie_init_notification(struct controller *ctrl) +{ + if (sunway_pciehp_request_irq(ctrl)) + return -1; + pcie_enable_notification(ctrl); + ctrl->notification_enabled = 1; + return 0; +} + +void sunway_pcie_shutdown_notification(struct controller *ctrl) +{ + if (ctrl->notification_enabled) { + pcie_disable_notification(ctrl); + sunway_pciehp_free_irq(ctrl); + ctrl->notification_enabled = 0; + } +} + +static inline void dbg_ctrl(struct controller *ctrl) +{ + struct pci_dev *pdev = ctrl->pci_dev; + u16 reg16; + + ctrl_dbg(ctrl, "Slot Capabilities : 0x%08x\n", ctrl->slot_cap); + pcie_capability_read_word(pdev, PCI_EXP_SLTSTA, ®16); + ctrl_dbg(ctrl, "Slot Status : 0x%04x\n", reg16); + pcie_capability_read_word(pdev, PCI_EXP_SLTCTL, ®16); + ctrl_dbg(ctrl, "Slot Control : 0x%04x\n", reg16); +} + +#define FLAG(x, y) (((x) & (y)) ? '+' : '-') + +struct controller *sunwayhpc_init(struct pci_dev *dev) +{ + struct controller *ctrl; + u32 slot_cap, slot_cap2, link_cap; + u8 poweron; + struct pci_bus *subordinate = dev->subordinate; + + ctrl = kzalloc(sizeof(*ctrl), GFP_KERNEL); + if (!ctrl) + return NULL; + + ctrl->pci_dev = dev; + pcie_capability_read_dword(dev, PCI_EXP_SLTCAP, &slot_cap); + + if (dev->hotplug_user_indicators) + slot_cap &= ~(PCI_EXP_SLTCAP_AIP | PCI_EXP_SLTCAP_PIP); + + /* + * We assume no Thunderbolt controllers support Command Complete events, + * but some controllers falsely claim they do. + */ + if (dev->is_thunderbolt) + slot_cap |= PCI_EXP_SLTCAP_NCCS; + + ctrl->slot_cap = slot_cap; + mutex_init(&ctrl->ctrl_lock); + mutex_init(&ctrl->state_lock); + init_rwsem(&ctrl->reset_lock); + init_waitqueue_head(&ctrl->requester); + init_waitqueue_head(&ctrl->queue); + INIT_DELAYED_WORK(&ctrl->button_work, sunway_pciehp_queue_pushbutton_work); + dbg_ctrl(ctrl); + + down_read(&pci_bus_sem); + ctrl->state = list_empty(&subordinate->devices) ? OFF_STATE : ON_STATE; + up_read(&pci_bus_sem); + + pcie_capability_read_dword(dev, PCI_EXP_SLTCAP2, &slot_cap2); + if (slot_cap2 & PCI_EXP_SLTCAP2_IBPD) { + pcie_write_cmd_nowait(ctrl, PCI_EXP_SLTCTL_IBPD_DISABLE, + PCI_EXP_SLTCTL_IBPD_DISABLE); + ctrl->inband_presence_disabled = 1; + } + + if (dmi_first_match(inband_presence_disabled_dmi_table)) + ctrl->inband_presence_disabled = 1; + + /* Check if Data Link Layer Link Active Reporting is implemented */ + pcie_capability_read_dword(dev, PCI_EXP_LNKCAP, &link_cap); + + /* Clear all remaining event bits in Slot Status register. */ + pcie_capability_write_word(dev, PCI_EXP_SLTSTA, + PCI_EXP_SLTSTA_ABP | PCI_EXP_SLTSTA_PFD | + PCI_EXP_SLTSTA_MRLSC | PCI_EXP_SLTSTA_CC | + PCI_EXP_SLTSTA_DLLSC | PCI_EXP_SLTSTA_PDC); + + ctrl_info(ctrl, "Slot #%d AttnBtn%c PwrCtrl%c MRL%c AttnInd%c PwrInd%c HotPlug%c Surprise%c Interlock%c NoCompl%c IbPresDis%c LLActRep%c\n", + (slot_cap & PCI_EXP_SLTCAP_PSN) >> 19, + FLAG(slot_cap, PCI_EXP_SLTCAP_ABP), + FLAG(slot_cap, PCI_EXP_SLTCAP_PCP), + FLAG(slot_cap, PCI_EXP_SLTCAP_MRLSP), + FLAG(slot_cap, PCI_EXP_SLTCAP_AIP), + FLAG(slot_cap, PCI_EXP_SLTCAP_PIP), + FLAG(slot_cap, PCI_EXP_SLTCAP_HPC), + FLAG(slot_cap, PCI_EXP_SLTCAP_HPS), + FLAG(slot_cap, PCI_EXP_SLTCAP_EIP), + FLAG(slot_cap, PCI_EXP_SLTCAP_NCCS), + FLAG(slot_cap2, PCI_EXP_SLTCAP2_IBPD), + FLAG(link_cap, PCI_EXP_LNKCAP_DLLLARC)); + + /* + * If empty slot's power status is on, turn power off. The IRQ isn't + * requested yet, so avoid triggering a notification with this command. + */ + if (POWER_CTRL(ctrl)) { + sunway_pciehp_get_power_status(ctrl, &poweron); + if (!sunway_pciehp_card_present_or_link_active(ctrl) && poweron) { + pcie_disable_notification(ctrl); + sunway_pciehp_power_off_slot(ctrl); + } + } + + return ctrl; +} + +void sunway_pciehp_release_ctrl(struct controller *ctrl) +{ + cancel_delayed_work_sync(&ctrl->button_work); + kfree(ctrl); +} diff --git a/drivers/pci/hotplug/sunway_pciehp_pci.c b/drivers/pci/hotplug/sunway_pciehp_pci.c new file mode 100644 index 0000000000000000000000000000000000000000..f627ea054342128f81acaaa1a0d3f2b2a94d2a6f --- /dev/null +++ b/drivers/pci/hotplug/sunway_pciehp_pci.c @@ -0,0 +1,113 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Sunway PCI Express Hot Plug Controller Driver + */ + +#define dev_fmt(fmt) "sunway_pciehp: " fmt + +#include +#include +#include +#include +#include "../pci.h" +#include "sunway_pciehp.h" + +/** + * pciehp_configure_device() - enumerate PCI devices below a hotplug bridge + * @ctrl: PCIe hotplug controller + * + * Enumerate PCI devices below a hotplug bridge and add them to the system. + * Return 0 on success, %-EEXIST if the devices are already enumerated or + * %-ENODEV if enumeration failed. + */ +int sunway_pciehp_configure_device(struct controller *ctrl) +{ + struct pci_dev *dev; + struct pci_dev *bridge = ctrl->pci_dev; + struct pci_bus *parent = bridge->subordinate; + int num, ret = 0; + + pci_lock_rescan_remove(); + + dev = pci_get_slot(parent, PCI_DEVFN(0, 0)); + if (dev) { + /* + * The device is already there. Either configured by the + * boot firmware or a previous hotplug event. + */ + ctrl_dbg(ctrl, "Device %s already exists at %04x:%02x:00, skipping hot-add\n", + pci_name(dev), pci_domain_nr(parent), parent->number); + pci_dev_put(dev); + ret = -EEXIST; + goto out; + } + + num = pci_scan_slot(parent, PCI_DEVFN(0, 0)); + if (num == 0) { + ctrl_err(ctrl, "No new device found\n"); + ret = -ENODEV; + goto out; + } + + for_each_pci_bridge(dev, parent) + pci_hp_add_bridge(dev); + + pci_assign_unassigned_bridge_resources(bridge); + pcie_bus_configure_settings(parent); + pci_bus_add_devices(parent); + + out: + pci_unlock_rescan_remove(); + return ret; +} + +/** + * pciehp_unconfigure_device() - remove PCI devices below a hotplug bridge + * @ctrl: PCIe hotplug controller + * @presence: whether the card is still present in the slot; + * true for safe removal via sysfs or an Attention Button press, + * false for surprise removal + * + * Unbind PCI devices below a hotplug bridge from their drivers and remove + * them from the system. Safely removed devices are quiesced. Surprise + * removed devices are marked as such to prevent further accesses. + */ +void sunway_pciehp_unconfigure_device(struct controller *ctrl, bool presence) +{ + struct pci_dev *dev, *temp; + struct pci_bus *parent = ctrl->pci_dev->subordinate; + u16 command; + + ctrl_dbg(ctrl, "%s: domain:bus:dev = %04x:%02x:00\n", + __func__, pci_domain_nr(parent), parent->number); + + if (!presence) + pci_walk_bus(parent, pci_dev_set_disconnected, NULL); + + pci_lock_rescan_remove(); + + /* + * Stopping an SR-IOV PF device removes all the associated VFs, + * which will update the bus->devices list and confuse the + * iterator. Therefore, iterate in reverse so we remove the VFs + * first, then the PF. We do the same in pci_stop_bus_device(). + */ + list_for_each_entry_safe_reverse(dev, temp, &parent->devices, + bus_list) { + pci_dev_get(dev); + pci_stop_and_remove_bus_device(dev); + /* + * Ensure that no new Requests will be generated from + * the device. + */ + if (presence) { + pci_read_config_word(dev, PCI_COMMAND, &command); + command &= ~(PCI_COMMAND_MASTER | PCI_COMMAND_SERR); + command |= PCI_COMMAND_INTX_DISABLE; + pci_write_config_word(dev, PCI_COMMAND, command); + } + pci_dev_put(dev); + } + + pci_unlock_rescan_remove(); +}