From 5e8be07ca8f7f49d49c6d5d44f69caaa2fa4b660 Mon Sep 17 00:00:00 2001 From: Qing Zhang Date: Sat, 6 Aug 2022 15:19:32 +0800 Subject: LoongArch: Requires __force attributes for any casts This fix a warning when "make C=2": arch/loongarch/kernel/ptrace.c: note: in included file (through include/linux/uaccess.h, include/linux/sched/task.h, include/linux/sched/signal.h, include/linux/ptrace.h, include/linux/audit.h): ./arch/loongarch/include/asm/uaccess.h:232:32: warning: incorrect type in argument 2 (different address spaces) ./arch/loongarch/include/asm/uaccess.h:232:32: expected void const *from ./arch/loongarch/include/asm/uaccess.h:232:32: got void const [noderef] __user *from Reported-by: kernel test robot Signed-off-by: Qing Zhang Signed-off-by: Huacai Chen --- arch/loongarch/include/asm/uaccess.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'arch/loongarch/include') diff --git a/arch/loongarch/include/asm/uaccess.h b/arch/loongarch/include/asm/uaccess.h index 2b44edc604a2..a8ae2af4025a 100644 --- a/arch/loongarch/include/asm/uaccess.h +++ b/arch/loongarch/include/asm/uaccess.h @@ -229,13 +229,13 @@ extern unsigned long __copy_user(void *to, const void *from, __kernel_size_t n); static inline unsigned long __must_check raw_copy_from_user(void *to, const void __user *from, unsigned long n) { - return __copy_user(to, from, n); + return __copy_user(to, (__force const void *)from, n); } static inline unsigned long __must_check raw_copy_to_user(void __user *to, const void *from, unsigned long n) { - return __copy_user(to, from, n); + return __copy_user((__force void *)to, from, n); } #define INLINE_COPY_FROM_USER -- cgit v1.2.3 From e9e7ff16d7f098f6fa9394e9d2b191c01ba0d5f6 Mon Sep 17 00:00:00 2001 From: Huacai Chen Date: Tue, 19 Jul 2022 10:53:13 +0800 Subject: LoongArch: Parse MADT to get multi-processor information Parse MADT to get multi-processor information, in order to fix the boot problem and cpu-hotplug problem for SMP platform. Signed-off-by: Huacai Chen --- arch/loongarch/include/asm/bootinfo.h | 2 +- arch/loongarch/include/asm/irq.h | 2 -- arch/loongarch/kernel/acpi.c | 38 +++++++++++++++++++++++++++++++++++ arch/loongarch/kernel/smp.c | 5 +---- 4 files changed, 40 insertions(+), 7 deletions(-) (limited to 'arch/loongarch/include') diff --git a/arch/loongarch/include/asm/bootinfo.h b/arch/loongarch/include/asm/bootinfo.h index 9b8d49d9e61b..e02ac4af7f6e 100644 --- a/arch/loongarch/include/asm/bootinfo.h +++ b/arch/loongarch/include/asm/bootinfo.h @@ -28,10 +28,10 @@ struct loongson_board_info { struct loongson_system_configuration { int nr_cpus; int nr_nodes; - int nr_io_pics; int boot_cpu_id; int cores_per_node; int cores_per_package; + unsigned long cores_io_master; const char *cpuname; }; diff --git a/arch/loongarch/include/asm/irq.h b/arch/loongarch/include/asm/irq.h index 149b2123e7f4..f6c2455b4584 100644 --- a/arch/loongarch/include/asm/irq.h +++ b/arch/loongarch/include/asm/irq.h @@ -82,8 +82,6 @@ extern struct acpi_vector_group msi_group[MAX_IO_PICS]; #define GSI_MAX_PCH_IRQ (LOONGSON_PCH_IRQ_BASE + 256 - 1) extern int find_pch_pic(u32 gsi); -extern int eiointc_get_node(int id); - struct acpi_madt_lio_pic; struct acpi_madt_eio_pic; struct acpi_madt_ht_pic; diff --git a/arch/loongarch/kernel/acpi.c b/arch/loongarch/kernel/acpi.c index 03aa14581d0a..f1c928648a4a 100644 --- a/arch/loongarch/kernel/acpi.c +++ b/arch/loongarch/kernel/acpi.c @@ -104,6 +104,39 @@ static int set_processor_mask(u32 id, u32 flags) } #endif +static int __init +acpi_parse_processor(union acpi_subtable_headers *header, const unsigned long end) +{ + struct acpi_madt_core_pic *processor = NULL; + + processor = (struct acpi_madt_core_pic *)header; + if (BAD_MADT_ENTRY(processor, end)) + return -EINVAL; + + acpi_table_print_madt_entry(&header->common); +#ifdef CONFIG_SMP + set_processor_mask(processor->core_id, processor->flags); +#endif + + return 0; +} + +static int __init +acpi_parse_eio_master(union acpi_subtable_headers *header, const unsigned long end) +{ + static int core = 0; + struct acpi_madt_eio_pic *eiointc = NULL; + + eiointc = (struct acpi_madt_eio_pic *)header; + if (BAD_MADT_ENTRY(eiointc, end)) + return -EINVAL; + + core = eiointc->node * CORES_PER_EIO_NODE; + set_bit(core, &(loongson_sysconf.cores_io_master)); + + return 0; +} + static void __init acpi_process_madt(void) { #ifdef CONFIG_SMP @@ -114,6 +147,11 @@ static void __init acpi_process_madt(void) __cpu_logical_map[i] = -1; } #endif + acpi_table_parse_madt(ACPI_MADT_TYPE_CORE_PIC, + acpi_parse_processor, MAX_CORE_PIC); + + acpi_table_parse_madt(ACPI_MADT_TYPE_EIO_PIC, + acpi_parse_eio_master, MAX_IO_PICS); loongson_sysconf.nr_cpus = num_processors; } diff --git a/arch/loongarch/kernel/smp.c b/arch/loongarch/kernel/smp.c index 09743103d9b3..b5fab308dcf2 100644 --- a/arch/loongarch/kernel/smp.c +++ b/arch/loongarch/kernel/smp.c @@ -242,10 +242,7 @@ void loongson3_smp_finish(void) static bool io_master(int cpu) { - if (cpu == 0) - return true; - - return false; + return test_bit(cpu, &loongson_sysconf.cores_io_master); } int loongson3_cpu_disable(void) -- cgit v1.2.3 From 57fc7323a8e7c2e7c1d5795ab63cb3ffea3cfdfb Mon Sep 17 00:00:00 2001 From: Huacai Chen Date: Sat, 6 Aug 2022 15:19:33 +0800 Subject: LoongArch: Add PCI controller support Loongson64 based systems are PC-like systems which use PCI/PCIe as its I/O bus, This patch adds the PCI host controller support for LoongArch. Reviewed-by: WANG Xuerui Signed-off-by: Jianmin Lv Signed-off-by: Huacai Chen --- arch/loongarch/Kconfig | 7 ++ arch/loongarch/Makefile | 2 + arch/loongarch/include/asm/dma.h | 11 +++ arch/loongarch/include/asm/irq.h | 8 -- arch/loongarch/include/asm/page.h | 2 - arch/loongarch/include/asm/pci.h | 25 ++++++ arch/loongarch/pci/acpi.c | 175 ++++++++++++++++++++++++++++++++++++++ arch/loongarch/pci/pci.c | 101 ++++++++++++++++++++++ 8 files changed, 321 insertions(+), 10 deletions(-) create mode 100644 arch/loongarch/include/asm/dma.h create mode 100644 arch/loongarch/include/asm/pci.h create mode 100644 arch/loongarch/pci/acpi.c create mode 100644 arch/loongarch/pci/pci.c (limited to 'arch/loongarch/include') diff --git a/arch/loongarch/Kconfig b/arch/loongarch/Kconfig index 4ea781e44425..5b4f7bdf69fa 100644 --- a/arch/loongarch/Kconfig +++ b/arch/loongarch/Kconfig @@ -4,6 +4,7 @@ config LOONGARCH default y select ACPI select ACPI_GENERIC_GSI if ACPI + select ACPI_MCFG if ACPI select ACPI_SYSTEM_POWER_STATES_SUPPORT if ACPI select ARCH_BINFMT_ELF_STATE select ARCH_ENABLE_MEMORY_HOTPLUG @@ -88,6 +89,7 @@ config LOONGARCH select HAVE_IRQ_TIME_ACCOUNTING select HAVE_MOD_ARCH_SPECIFIC select HAVE_NMI + select HAVE_PCI select HAVE_PERF_EVENTS select HAVE_REGS_AND_STACK_ACCESS_API select HAVE_RSEQ @@ -103,6 +105,11 @@ config LOONGARCH select NEED_PER_CPU_PAGE_FIRST_CHUNK select OF select OF_EARLY_FLATTREE + select PCI + select PCI_DOMAINS_GENERIC + select PCI_ECAM if ACPI + select PCI_LOONGSON + select PCI_MSI_ARCH_FALLBACKS select PERF_USE_VMALLOC select RTC_LIB select SMP diff --git a/arch/loongarch/Makefile b/arch/loongarch/Makefile index fbe4277e6404..ec3de6191276 100644 --- a/arch/loongarch/Makefile +++ b/arch/loongarch/Makefile @@ -47,6 +47,8 @@ cflags-y += $(call cc-option, -mno-check-zero-division) load-y = 0x9000000000200000 bootvars-y = VMLINUX_LOAD_ADDRESS=$(load-y) +drivers-$(CONFIG_PCI) += arch/loongarch/pci/ + KBUILD_AFLAGS += $(cflags-y) KBUILD_CFLAGS += $(cflags-y) KBUILD_CPPFLAGS += -DVMLINUX_LOAD_ADDRESS=$(load-y) diff --git a/arch/loongarch/include/asm/dma.h b/arch/loongarch/include/asm/dma.h new file mode 100644 index 000000000000..1a8866319fe2 --- /dev/null +++ b/arch/loongarch/include/asm/dma.h @@ -0,0 +1,11 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Copyright (C) 2020-2022 Loongson Technology Corporation Limited + */ +#ifndef __ASM_DMA_H +#define __ASM_DMA_H + +#define MAX_DMA_ADDRESS PAGE_OFFSET +#define MAX_DMA32_PFN (1UL << (32 - PAGE_SHIFT)) + +#endif diff --git a/arch/loongarch/include/asm/irq.h b/arch/loongarch/include/asm/irq.h index f6c2455b4584..4b130199ceae 100644 --- a/arch/loongarch/include/asm/irq.h +++ b/arch/loongarch/include/asm/irq.h @@ -98,16 +98,8 @@ struct irq_domain *htvec_acpi_init(struct irq_domain *parent, struct acpi_madt_ht_pic *acpi_htvec); int pch_lpc_acpi_init(struct irq_domain *parent, struct acpi_madt_lpc_pic *acpi_pchlpc); -#if IS_ENABLED(CONFIG_LOONGSON_PCH_MSI) int pch_msi_acpi_init(struct irq_domain *parent, struct acpi_madt_msi_pic *acpi_pchmsi); -#else -static inline int pch_msi_acpi_init(struct irq_domain *parent, - struct acpi_madt_msi_pic *acpi_pchmsi) -{ - return 0; -} -#endif int pch_pic_acpi_init(struct irq_domain *parent, struct acpi_madt_bio_pic *acpi_pchpic); int find_pch_pic(u32 gsi); diff --git a/arch/loongarch/include/asm/page.h b/arch/loongarch/include/asm/page.h index dc47fc724fa1..a37324ac460b 100644 --- a/arch/loongarch/include/asm/page.h +++ b/arch/loongarch/include/asm/page.h @@ -33,8 +33,6 @@ #include #include -#define MAX_DMA32_PFN (1UL << (32 - PAGE_SHIFT)) - /* * It's normally defined only for FLATMEM config but it's * used in our early mem init code for all memory models. diff --git a/arch/loongarch/include/asm/pci.h b/arch/loongarch/include/asm/pci.h new file mode 100644 index 000000000000..846909d7e831 --- /dev/null +++ b/arch/loongarch/include/asm/pci.h @@ -0,0 +1,25 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Copyright (C) 2020-2022 Loongson Technology Corporation Limited + */ +#ifndef _ASM_PCI_H +#define _ASM_PCI_H + +#include +#include +#include +#include + +#define PCIBIOS_MIN_IO 0x4000 +#define PCIBIOS_MIN_MEM 0x20000000 +#define PCIBIOS_MIN_CARDBUS_IO 0x4000 + +#define HAVE_PCI_MMAP +#define pcibios_assign_all_busses() 0 + +extern phys_addr_t mcfg_addr_init(int node); + +/* generic pci stuff */ +#include + +#endif /* _ASM_PCI_H */ diff --git a/arch/loongarch/pci/acpi.c b/arch/loongarch/pci/acpi.c new file mode 100644 index 000000000000..bf921487333c --- /dev/null +++ b/arch/loongarch/pci/acpi.c @@ -0,0 +1,175 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) 2020-2022 Loongson Technology Corporation Limited + */ +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +struct pci_root_info { + struct acpi_pci_root_info common; + struct pci_config_window *cfg; +}; + +void pcibios_add_bus(struct pci_bus *bus) +{ + acpi_pci_add_bus(bus); +} + +int pcibios_root_bridge_prepare(struct pci_host_bridge *bridge) +{ + struct pci_config_window *cfg = bridge->bus->sysdata; + struct acpi_device *adev = to_acpi_device(cfg->parent); + struct device *bus_dev = &bridge->bus->dev; + + ACPI_COMPANION_SET(&bridge->dev, adev); + set_dev_node(bus_dev, pa_to_nid(cfg->res.start)); + + return 0; +} + +int acpi_pci_bus_find_domain_nr(struct pci_bus *bus) +{ + struct pci_config_window *cfg = bus->sysdata; + struct acpi_device *adev = to_acpi_device(cfg->parent); + struct acpi_pci_root *root = acpi_driver_data(adev); + + return root->segment; +} + +static void acpi_release_root_info(struct acpi_pci_root_info *ci) +{ + struct pci_root_info *info; + + info = container_of(ci, struct pci_root_info, common); + pci_ecam_free(info->cfg); + kfree(ci->ops); + kfree(info); +} + +static int acpi_prepare_root_resources(struct acpi_pci_root_info *ci) +{ + int status; + struct resource_entry *entry, *tmp; + struct acpi_device *device = ci->bridge; + + status = acpi_pci_probe_root_resources(ci); + if (status > 0) { + resource_list_for_each_entry_safe(entry, tmp, &ci->resources) { + if (entry->res->flags & IORESOURCE_MEM) { + entry->offset = ci->root->mcfg_addr & GENMASK_ULL(63, 40); + entry->res->start |= entry->offset; + entry->res->end |= entry->offset; + } + } + return status; + } + + resource_list_for_each_entry_safe(entry, tmp, &ci->resources) { + dev_dbg(&device->dev, + "host bridge window %pR (ignored)\n", entry->res); + resource_list_destroy_entry(entry); + } + + return 0; +} + +/* + * Lookup the bus range for the domain in MCFG, and set up config space + * mapping. + */ +static struct pci_config_window * +pci_acpi_setup_ecam_mapping(struct acpi_pci_root *root) +{ + int ret, bus_shift; + u16 seg = root->segment; + struct device *dev = &root->device->dev; + struct resource cfgres; + struct resource *bus_res = &root->secondary; + struct pci_config_window *cfg; + const struct pci_ecam_ops *ecam_ops; + + ret = pci_mcfg_lookup(root, &cfgres, &ecam_ops); + if (ret < 0) { + dev_err(dev, "%04x:%pR ECAM region not found, use default value\n", seg, bus_res); + ecam_ops = &loongson_pci_ecam_ops; + root->mcfg_addr = mcfg_addr_init(0); + } + + bus_shift = ecam_ops->bus_shift ? : 20; + + cfgres.start = root->mcfg_addr + (bus_res->start << bus_shift); + cfgres.end = cfgres.start + (resource_size(bus_res) << bus_shift) - 1; + cfgres.flags = IORESOURCE_MEM; + + cfg = pci_ecam_create(dev, &cfgres, bus_res, ecam_ops); + if (IS_ERR(cfg)) { + dev_err(dev, "%04x:%pR error %ld mapping ECAM\n", seg, bus_res, PTR_ERR(cfg)); + return NULL; + } + + return cfg; +} + +struct pci_bus *pci_acpi_scan_root(struct acpi_pci_root *root) +{ + struct pci_bus *bus; + struct pci_root_info *info; + struct acpi_pci_root_ops *root_ops; + int domain = root->segment; + int busnum = root->secondary.start; + + info = kzalloc(sizeof(*info), GFP_KERNEL); + if (!info) { + pr_warn("pci_bus %04x:%02x: ignored (out of memory)\n", domain, busnum); + return NULL; + } + + root_ops = kzalloc(sizeof(*root_ops), GFP_KERNEL); + if (!root_ops) { + kfree(info); + return NULL; + } + + info->cfg = pci_acpi_setup_ecam_mapping(root); + if (!info->cfg) { + kfree(info); + kfree(root_ops); + return NULL; + } + + root_ops->release_info = acpi_release_root_info; + root_ops->prepare_resources = acpi_prepare_root_resources; + root_ops->pci_ops = (struct pci_ops *)&info->cfg->ops->pci_ops; + + bus = pci_find_bus(domain, busnum); + if (bus) { + memcpy(bus->sysdata, info->cfg, sizeof(struct pci_config_window)); + kfree(info); + } else { + struct pci_bus *child; + + bus = acpi_pci_root_create(root, root_ops, + &info->common, info->cfg); + if (!bus) { + kfree(info); + kfree(root_ops); + return NULL; + } + + pci_bus_size_bridges(bus); + pci_bus_assign_resources(bus); + list_for_each_entry(child, &bus->children, node) + pcie_bus_configure_settings(child); + } + + return bus; +} diff --git a/arch/loongarch/pci/pci.c b/arch/loongarch/pci/pci.c new file mode 100644 index 000000000000..e9b7c34d9b6d --- /dev/null +++ b/arch/loongarch/pci/pci.c @@ -0,0 +1,101 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2020-2022 Loongson Technology Corporation Limited + */ +#include +#include +#include +#include +#include +#include +#include +#include + +#define PCI_DEVICE_ID_LOONGSON_HOST 0x7a00 +#define PCI_DEVICE_ID_LOONGSON_DC1 0x7a06 +#define PCI_DEVICE_ID_LOONGSON_DC2 0x7a36 + +int raw_pci_read(unsigned int domain, unsigned int bus, unsigned int devfn, + int reg, int len, u32 *val) +{ + struct pci_bus *bus_tmp = pci_find_bus(domain, bus); + + if (bus_tmp) + return bus_tmp->ops->read(bus_tmp, devfn, reg, len, val); + return -EINVAL; +} + +int raw_pci_write(unsigned int domain, unsigned int bus, unsigned int devfn, + int reg, int len, u32 val) +{ + struct pci_bus *bus_tmp = pci_find_bus(domain, bus); + + if (bus_tmp) + return bus_tmp->ops->write(bus_tmp, devfn, reg, len, val); + return -EINVAL; +} + +phys_addr_t mcfg_addr_init(int node) +{ + return (((u64)node << 44) | MCFG_EXT_PCICFG_BASE); +} + +static int __init pcibios_init(void) +{ + unsigned int lsize; + + /* + * Set PCI cacheline size to that of the highest level in the + * cache hierarchy. + */ + lsize = cpu_dcache_line_size(); + lsize = cpu_vcache_line_size() ? : lsize; + lsize = cpu_scache_line_size() ? : lsize; + + BUG_ON(!lsize); + + pci_dfl_cache_line_size = lsize >> 2; + + pr_debug("PCI: pci_cache_line_size set to %d bytes\n", lsize); + + return 0; +} + +subsys_initcall(pcibios_init); + +int pcibios_device_add(struct pci_dev *dev) +{ + int id; + struct irq_domain *dom; + + id = pci_domain_nr(dev->bus); + dom = irq_find_matching_fwnode(get_pch_msi_handle(id), DOMAIN_BUS_PCI_MSI); + dev_set_msi_domain(&dev->dev, dom); + + return 0; +} + +int pcibios_alloc_irq(struct pci_dev *dev) +{ + if (acpi_disabled) + return 0; + if (pci_dev_msi_enabled(dev)) + return 0; + return acpi_pci_irq_enable(dev); +} + +static void pci_fixup_vgadev(struct pci_dev *pdev) +{ + struct pci_dev *devp = NULL; + + while ((devp = pci_get_class(PCI_CLASS_DISPLAY_VGA << 8, devp))) { + if (devp->vendor != PCI_VENDOR_ID_LOONGSON) { + vga_set_default_device(devp); + dev_info(&pdev->dev, + "Overriding boot device as %X:%X\n", + devp->vendor, devp->device); + } + } +} +DECLARE_PCI_FIXUP_FINAL(PCI_VENDOR_ID_LOONGSON, PCI_DEVICE_ID_LOONGSON_DC1, pci_fixup_vgadev); +DECLARE_PCI_FIXUP_FINAL(PCI_VENDOR_ID_LOONGSON, PCI_DEVICE_ID_LOONGSON_DC2, pci_fixup_vgadev); -- cgit v1.2.3 From dce6098b22d58e5b646b1c67174c53f5a6a05605 Mon Sep 17 00:00:00 2001 From: Huacai Chen Date: Sat, 6 Aug 2022 15:19:33 +0800 Subject: LoongArch: Add vDSO syscall __vdso_getcpu() We test 20 million times of getcpu(), the real syscall version take 25 seconds, while the vsyscall version take only 2.4 seconds. Signed-off-by: Rui Wang Signed-off-by: Huacai Chen --- arch/loongarch/include/asm/vdso.h | 1 + arch/loongarch/include/asm/vdso/vdso.h | 15 +++++++++++- arch/loongarch/kernel/vdso.c | 25 ++++++++++++-------- arch/loongarch/vdso/Makefile | 2 +- arch/loongarch/vdso/vdso.lds.S | 1 + arch/loongarch/vdso/vgetcpu.c | 43 ++++++++++++++++++++++++++++++++++ 6 files changed, 75 insertions(+), 12 deletions(-) create mode 100644 arch/loongarch/vdso/vgetcpu.c (limited to 'arch/loongarch/include') diff --git a/arch/loongarch/include/asm/vdso.h b/arch/loongarch/include/asm/vdso.h index 8f8a0f9a4953..d3ba35eb23e7 100644 --- a/arch/loongarch/include/asm/vdso.h +++ b/arch/loongarch/include/asm/vdso.h @@ -7,6 +7,7 @@ #ifndef __ASM_VDSO_H #define __ASM_VDSO_H +#include #include #include diff --git a/arch/loongarch/include/asm/vdso/vdso.h b/arch/loongarch/include/asm/vdso/vdso.h index 5a01643a65b3..3b55d32a0619 100644 --- a/arch/loongarch/include/asm/vdso/vdso.h +++ b/arch/loongarch/include/asm/vdso/vdso.h @@ -8,6 +8,18 @@ #include #include +#include + +struct vdso_pcpu_data { + u32 node; +} ____cacheline_aligned_in_smp; + +struct loongarch_vdso_data { + struct vdso_pcpu_data pdata[NR_CPUS]; + struct vdso_data data[CS_BASES]; /* Arch-independent data */ +}; + +#define VDSO_DATA_SIZE PAGE_ALIGN(sizeof(struct loongarch_vdso_data)) static inline unsigned long get_vdso_base(void) { @@ -24,7 +36,8 @@ static inline unsigned long get_vdso_base(void) static inline const struct vdso_data *get_vdso_data(void) { - return (const struct vdso_data *)(get_vdso_base() - PAGE_SIZE); + return (const struct vdso_data *)(get_vdso_base() + - VDSO_DATA_SIZE + SMP_CACHE_BYTES * NR_CPUS); } #endif /* __ASSEMBLY__ */ diff --git a/arch/loongarch/kernel/vdso.c b/arch/loongarch/kernel/vdso.c index e20c8ca87473..f32c38abd791 100644 --- a/arch/loongarch/kernel/vdso.c +++ b/arch/loongarch/kernel/vdso.c @@ -25,12 +25,14 @@ extern char vdso_start[], vdso_end[]; /* Kernel-provided data used by the VDSO. */ -static union loongarch_vdso_data { - u8 page[PAGE_SIZE]; - struct vdso_data data[CS_BASES]; +static union { + u8 page[VDSO_DATA_SIZE]; + struct loongarch_vdso_data vdata; } loongarch_vdso_data __page_aligned_data; -struct vdso_data *vdso_data = loongarch_vdso_data.data; + static struct page *vdso_pages[] = { NULL }; +struct vdso_data *vdso_data = loongarch_vdso_data.vdata.data; +struct vdso_pcpu_data *vdso_pdata = loongarch_vdso_data.vdata.pdata; static int vdso_mremap(const struct vm_special_mapping *sm, struct vm_area_struct *new_vma) { @@ -55,11 +57,14 @@ struct loongarch_vdso_info vdso_info = { static int __init init_vdso(void) { - unsigned long i, pfn; + unsigned long i, cpu, pfn; BUG_ON(!PAGE_ALIGNED(vdso_info.vdso)); BUG_ON(!PAGE_ALIGNED(vdso_info.size)); + for_each_possible_cpu(cpu) + vdso_pdata[cpu].node = cpu_to_node(cpu); + pfn = __phys_to_pfn(__pa_symbol(vdso_info.vdso)); for (i = 0; i < vdso_info.size / PAGE_SIZE; i++) vdso_info.code_mapping.pages[i] = pfn_to_page(pfn + i); @@ -93,9 +98,9 @@ int arch_setup_additional_pages(struct linux_binprm *bprm, int uses_interp) /* * Determine total area size. This includes the VDSO data itself - * and the data page. + * and the data pages. */ - vvar_size = PAGE_SIZE; + vvar_size = VDSO_DATA_SIZE; size = vvar_size + info->size; data_addr = get_unmapped_area(NULL, vdso_base(), size, 0, 0); @@ -103,7 +108,7 @@ int arch_setup_additional_pages(struct linux_binprm *bprm, int uses_interp) ret = data_addr; goto out; } - vdso_addr = data_addr + PAGE_SIZE; + vdso_addr = data_addr + VDSO_DATA_SIZE; vma = _install_special_mapping(mm, data_addr, vvar_size, VM_READ | VM_MAYREAD, @@ -115,8 +120,8 @@ int arch_setup_additional_pages(struct linux_binprm *bprm, int uses_interp) /* Map VDSO data page. */ ret = remap_pfn_range(vma, data_addr, - virt_to_phys(vdso_data) >> PAGE_SHIFT, - PAGE_SIZE, PAGE_READONLY); + virt_to_phys(&loongarch_vdso_data) >> PAGE_SHIFT, + vvar_size, PAGE_READONLY); if (ret) goto out; diff --git a/arch/loongarch/vdso/Makefile b/arch/loongarch/vdso/Makefile index 92e404032257..d89e2ac75f7b 100644 --- a/arch/loongarch/vdso/Makefile +++ b/arch/loongarch/vdso/Makefile @@ -6,7 +6,7 @@ ARCH_REL_TYPE_ABS := R_LARCH_32|R_LARCH_64|R_LARCH_MARK_LA|R_LARCH_JUMP_SLOT include $(srctree)/lib/vdso/Makefile -obj-vdso-y := elf.o vgettimeofday.o sigreturn.o +obj-vdso-y := elf.o vgetcpu.o vgettimeofday.o sigreturn.o # Common compiler flags between ABIs. ccflags-vdso := \ diff --git a/arch/loongarch/vdso/vdso.lds.S b/arch/loongarch/vdso/vdso.lds.S index 955f02de4a2d..56ad855896de 100644 --- a/arch/loongarch/vdso/vdso.lds.S +++ b/arch/loongarch/vdso/vdso.lds.S @@ -58,6 +58,7 @@ VERSION { LINUX_5.10 { global: + __vdso_getcpu; __vdso_clock_getres; __vdso_clock_gettime; __vdso_gettimeofday; diff --git a/arch/loongarch/vdso/vgetcpu.c b/arch/loongarch/vdso/vgetcpu.c new file mode 100644 index 000000000000..43a0078e4418 --- /dev/null +++ b/arch/loongarch/vdso/vgetcpu.c @@ -0,0 +1,43 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Fast user context implementation of getcpu() + */ + +#include +#include + +static __always_inline int read_cpu_id(void) +{ + int cpu_id; + + __asm__ __volatile__( + " rdtime.d $zero, %0\n" + : "=r" (cpu_id) + : + : "memory"); + + return cpu_id; +} + +static __always_inline const struct vdso_pcpu_data *get_pcpu_data(void) +{ + return (struct vdso_pcpu_data *)(get_vdso_base() - VDSO_DATA_SIZE); +} + +int __vdso_getcpu(unsigned int *cpu, unsigned int *node, struct getcpu_cache *unused) +{ + int cpu_id; + const struct vdso_pcpu_data *data; + + cpu_id = read_cpu_id(); + + if (cpu) + *cpu = cpu_id; + + if (node) { + data = get_pcpu_data(); + *node = data[cpu_id].node; + } + + return 0; +} -- cgit v1.2.3 From 49232773d8233ed70c4998851bc84e465fc1c788 Mon Sep 17 00:00:00 2001 From: Qing Zhang Date: Sat, 6 Aug 2022 16:10:02 +0800 Subject: LoongArch: Add guess unwinder support Name "guess unwinder" comes from x86, it scans the stack and reports every kernel text address it finds. Unwinders can be used by dump_stack() and other stacktrace functions. Three stages when we do unwind, 1) unwind_start(), the prapare of unwinding, fill unwind_state. 2) unwind_done(), judge whether the unwind process is finished or not. 3) unwind_next_frame(), unwind the next frame. Add get_stack_info() to get stack info. At present we have irq stack and task stack. The next_sp is the key info between two types of stacks. Dividing unwinder helps to add new unwinders in the future. Signed-off-by: Qing Zhang Signed-off-by: Huacai Chen --- arch/loongarch/Kconfig.debug | 9 +++++ arch/loongarch/include/asm/stacktrace.h | 15 ++++++++ arch/loongarch/include/asm/unwind.h | 36 ++++++++++++++++++ arch/loongarch/kernel/Makefile | 2 + arch/loongarch/kernel/process.c | 61 ++++++++++++++++++++++++++++++ arch/loongarch/kernel/traps.c | 21 +++++------ arch/loongarch/kernel/unwind_guess.c | 67 +++++++++++++++++++++++++++++++++ 7 files changed, 200 insertions(+), 11 deletions(-) create mode 100644 arch/loongarch/include/asm/unwind.h create mode 100644 arch/loongarch/kernel/unwind_guess.c (limited to 'arch/loongarch/include') diff --git a/arch/loongarch/Kconfig.debug b/arch/loongarch/Kconfig.debug index e69de29bb2d1..68634d4fa27b 100644 --- a/arch/loongarch/Kconfig.debug +++ b/arch/loongarch/Kconfig.debug @@ -0,0 +1,9 @@ +config UNWINDER_GUESS + bool "Guess unwinder" + help + This option enables the "guess" unwinder for unwinding kernel stack + traces. It scans the stack and reports every kernel text address it + finds. Some of the addresses it reports may be incorrect. + + While this option often produces false positives, it can still be + useful in many cases. diff --git a/arch/loongarch/include/asm/stacktrace.h b/arch/loongarch/include/asm/stacktrace.h index 6b5c2a7aa706..5820a0cabe3a 100644 --- a/arch/loongarch/include/asm/stacktrace.h +++ b/arch/loongarch/include/asm/stacktrace.h @@ -10,6 +10,21 @@ #include #include +enum stack_type { + STACK_TYPE_UNKNOWN, + STACK_TYPE_IRQ, + STACK_TYPE_TASK, +}; + +struct stack_info { + enum stack_type type; + unsigned long begin, end, next_sp; +}; + +bool in_irq_stack(unsigned long stack, struct stack_info *info); +bool in_task_stack(unsigned long stack, struct task_struct *task, struct stack_info *info); +int get_stack_info(unsigned long stack, struct task_struct *task, struct stack_info *info); + #define STR_LONG_L __stringify(LONG_L) #define STR_LONG_S __stringify(LONG_S) #define STR_LONGSIZE __stringify(LONGSIZE) diff --git a/arch/loongarch/include/asm/unwind.h b/arch/loongarch/include/asm/unwind.h new file mode 100644 index 000000000000..206fcbe24c0a --- /dev/null +++ b/arch/loongarch/include/asm/unwind.h @@ -0,0 +1,36 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Most of this ideas comes from x86. + * + * Copyright (C) 2022 Loongson Technology Corporation Limited + */ +#ifndef _ASM_UNWIND_H +#define _ASM_UNWIND_H + +#include + +#include + +struct unwind_state { + struct stack_info stack_info; + struct task_struct *task; + bool first, error; + unsigned long sp, pc; +}; + +void unwind_start(struct unwind_state *state, + struct task_struct *task, struct pt_regs *regs); +bool unwind_next_frame(struct unwind_state *state); +unsigned long unwind_get_return_address(struct unwind_state *state); + +static inline bool unwind_done(struct unwind_state *state) +{ + return state->stack_info.type == STACK_TYPE_UNKNOWN; +} + +static inline bool unwind_error(struct unwind_state *state) +{ + return state->error; +} + +#endif /* _ASM_UNWIND_H */ diff --git a/arch/loongarch/kernel/Makefile b/arch/loongarch/kernel/Makefile index 940de9173542..c5fa4adb23b6 100644 --- a/arch/loongarch/kernel/Makefile +++ b/arch/loongarch/kernel/Makefile @@ -22,4 +22,6 @@ obj-$(CONFIG_SMP) += smp.o obj-$(CONFIG_NUMA) += numa.o +obj-$(CONFIG_UNWINDER_GUESS) += unwind_guess.o + CPPFLAGS_vmlinux.lds := $(KBUILD_CFLAGS) diff --git a/arch/loongarch/kernel/process.c b/arch/loongarch/kernel/process.c index bfa0dfe8b7d7..839f0e963152 100644 --- a/arch/loongarch/kernel/process.c +++ b/arch/loongarch/kernel/process.c @@ -44,6 +44,7 @@ #include #include #include +#include #include /* @@ -183,6 +184,66 @@ unsigned long __get_wchan(struct task_struct *task) return 0; } +bool in_irq_stack(unsigned long stack, struct stack_info *info) +{ + unsigned long nextsp; + unsigned long begin = (unsigned long)this_cpu_read(irq_stack); + unsigned long end = begin + IRQ_STACK_START; + + if (stack < begin || stack >= end) + return false; + + nextsp = *(unsigned long *)end; + if (nextsp & (SZREG - 1)) + return false; + + info->begin = begin; + info->end = end; + info->next_sp = nextsp; + info->type = STACK_TYPE_IRQ; + + return true; +} + +bool in_task_stack(unsigned long stack, struct task_struct *task, + struct stack_info *info) +{ + unsigned long begin = (unsigned long)task_stack_page(task); + unsigned long end = begin + THREAD_SIZE - 32; + + if (stack < begin || stack >= end) + return false; + + info->begin = begin; + info->end = end; + info->next_sp = 0; + info->type = STACK_TYPE_TASK; + + return true; +} + +int get_stack_info(unsigned long stack, struct task_struct *task, + struct stack_info *info) +{ + task = task ? : current; + + if (!stack || stack & (SZREG - 1)) + goto unknown; + + if (in_task_stack(stack, task, info)) + return 0; + + if (task != current) + goto unknown; + + if (in_irq_stack(stack, info)) + return 0; + +unknown: + info->type = STACK_TYPE_UNKNOWN; + return -EINVAL; +} + unsigned long stack_top(void) { unsigned long top = TASK_SIZE & PAGE_MASK; diff --git a/arch/loongarch/kernel/traps.c b/arch/loongarch/kernel/traps.c index 1bf58c65e2bf..f65fdf90d29e 100644 --- a/arch/loongarch/kernel/traps.c +++ b/arch/loongarch/kernel/traps.c @@ -43,6 +43,7 @@ #include #include #include +#include #include "access-helper.h" @@ -64,19 +65,17 @@ static void show_backtrace(struct task_struct *task, const struct pt_regs *regs, const char *loglvl, bool user) { unsigned long addr; - unsigned long *sp = (unsigned long *)(regs->regs[3] & ~3); + struct unwind_state state; + struct pt_regs *pregs = (struct pt_regs *)regs; + + if (!task) + task = current; printk("%sCall Trace:", loglvl); -#ifdef CONFIG_KALLSYMS - printk("%s\n", loglvl); -#endif - while (!kstack_end(sp)) { - if (__get_addr(&addr, sp++, user)) { - printk("%s (Bad stack address)", loglvl); - break; - } - if (__kernel_text_address(addr)) - print_ip_sym(loglvl, addr); + for (unwind_start(&state, task, pregs); + !unwind_done(&state); unwind_next_frame(&state)) { + addr = unwind_get_return_address(&state); + print_ip_sym(loglvl, addr); } printk("%s\n", loglvl); } diff --git a/arch/loongarch/kernel/unwind_guess.c b/arch/loongarch/kernel/unwind_guess.c new file mode 100644 index 000000000000..5afa6064d73e --- /dev/null +++ b/arch/loongarch/kernel/unwind_guess.c @@ -0,0 +1,67 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) 2022 Loongson Technology Corporation Limited + */ +#include + +#include + +unsigned long unwind_get_return_address(struct unwind_state *state) +{ + if (unwind_done(state)) + return 0; + else if (state->first) + return state->pc; + + return *(unsigned long *)(state->sp); +} +EXPORT_SYMBOL_GPL(unwind_get_return_address); + +void unwind_start(struct unwind_state *state, struct task_struct *task, + struct pt_regs *regs) +{ + memset(state, 0, sizeof(*state)); + + if (regs) { + state->sp = regs->regs[3]; + state->pc = regs->csr_era; + } + + state->task = task; + state->first = true; + + get_stack_info(state->sp, state->task, &state->stack_info); + + if (!unwind_done(state) && !__kernel_text_address(state->pc)) + unwind_next_frame(state); +} +EXPORT_SYMBOL_GPL(unwind_start); + +bool unwind_next_frame(struct unwind_state *state) +{ + struct stack_info *info = &state->stack_info; + unsigned long addr; + + if (unwind_done(state)) + return false; + + if (state->first) + state->first = false; + + do { + for (state->sp += sizeof(unsigned long); + state->sp < info->end; + state->sp += sizeof(unsigned long)) { + addr = *(unsigned long *)(state->sp); + + if (__kernel_text_address(addr)) + return true; + } + + state->sp = info->next_sp; + + } while (!get_stack_info(state->sp, state->task, info)); + + return false; +} +EXPORT_SYMBOL_GPL(unwind_next_frame); -- cgit v1.2.3 From 49aef111e2dae176a7708b532118f33f24289248 Mon Sep 17 00:00:00 2001 From: Qing Zhang Date: Sat, 6 Aug 2022 16:10:03 +0800 Subject: LoongArch: Add prologue unwinder support It unwind the stack frame based on prologue code analyze. CONFIG_KALLSYMS is needed, at least the address and length of each function. Three stages when we do unwind, 1) unwind_start(), the prapare of unwinding, fill unwind_state. 2) unwind_done(), judge whether the unwind process is finished or not. 3) unwind_next_frame(), unwind the next frame. Dividing unwinder helps to add new unwinders in the future, e.g.: unwinder_frame, unwinder_orc, .etc. Signed-off-by: Qing Zhang Signed-off-by: Huacai Chen --- arch/loongarch/Kconfig.debug | 20 ++++ arch/loongarch/include/asm/inst.h | 52 ++++++++++ arch/loongarch/include/asm/unwind.h | 8 +- arch/loongarch/kernel/Makefile | 1 + arch/loongarch/kernel/traps.c | 3 + arch/loongarch/kernel/unwind_prologue.c | 176 ++++++++++++++++++++++++++++++++ 6 files changed, 259 insertions(+), 1 deletion(-) create mode 100644 arch/loongarch/kernel/unwind_prologue.c (limited to 'arch/loongarch/include') diff --git a/arch/loongarch/Kconfig.debug b/arch/loongarch/Kconfig.debug index 68634d4fa27b..8d36aab53008 100644 --- a/arch/loongarch/Kconfig.debug +++ b/arch/loongarch/Kconfig.debug @@ -1,3 +1,11 @@ +choice + prompt "Choose kernel unwinder" + default UNWINDER_PROLOGUE if KALLSYMS + help + This determines which method will be used for unwinding kernel stack + traces for panics, oopses, bugs, warnings, perf, /proc//stack, + lockdep, and more. + config UNWINDER_GUESS bool "Guess unwinder" help @@ -7,3 +15,15 @@ config UNWINDER_GUESS While this option often produces false positives, it can still be useful in many cases. + +config UNWINDER_PROLOGUE + bool "Prologue unwinder" + depends on KALLSYMS + help + This option enables the "prologue" unwinder for unwinding kernel stack + traces. It unwind the stack frame based on prologue code analyze. Symbol + information is needed, at least the address and length of each function. + Some of the addresses it reports may be incorrect (but better than the + Guess unwinder). + +endchoice diff --git a/arch/loongarch/include/asm/inst.h b/arch/loongarch/include/asm/inst.h index 575d1bb66ffb..7b07cbb3188c 100644 --- a/arch/loongarch/include/asm/inst.h +++ b/arch/loongarch/include/asm/inst.h @@ -23,12 +23,33 @@ enum reg1i20_op { lu32id_op = 0x0b, }; +enum reg1i21_op { + beqz_op = 0x10, + bnez_op = 0x11, +}; + enum reg2i12_op { + addiw_op = 0x0a, + addid_op = 0x0b, lu52id_op = 0x0c, + ldb_op = 0xa0, + ldh_op = 0xa1, + ldw_op = 0xa2, + ldd_op = 0xa3, + stb_op = 0xa4, + sth_op = 0xa5, + stw_op = 0xa6, + std_op = 0xa7, }; enum reg2i16_op { jirl_op = 0x13, + beq_op = 0x16, + bne_op = 0x17, + blt_op = 0x18, + bge_op = 0x19, + bltu_op = 0x1a, + bgeu_op = 0x1b, }; struct reg0i26_format { @@ -110,6 +131,37 @@ enum loongarch_gpr { LOONGARCH_GPR_MAX }; +#define is_imm12_negative(val) is_imm_negative(val, 12) + +static inline bool is_imm_negative(unsigned long val, unsigned int bit) +{ + return val & (1UL << (bit - 1)); +} + +static inline bool is_branch_ins(union loongarch_instruction *ip) +{ + return ip->reg1i21_format.opcode >= beqz_op && + ip->reg1i21_format.opcode <= bgeu_op; +} + +static inline bool is_ra_save_ins(union loongarch_instruction *ip) +{ + /* st.d $ra, $sp, offset */ + return ip->reg2i12_format.opcode == std_op && + ip->reg2i12_format.rj == LOONGARCH_GPR_SP && + ip->reg2i12_format.rd == LOONGARCH_GPR_RA && + !is_imm12_negative(ip->reg2i12_format.immediate); +} + +static inline bool is_stack_alloc_ins(union loongarch_instruction *ip) +{ + /* addi.d $sp, $sp, -imm */ + return ip->reg2i12_format.opcode == addid_op && + ip->reg2i12_format.rj == LOONGARCH_GPR_SP && + ip->reg2i12_format.rd == LOONGARCH_GPR_SP && + is_imm12_negative(ip->reg2i12_format.immediate); +} + u32 larch_insn_gen_lu32id(enum loongarch_gpr rd, int imm); u32 larch_insn_gen_lu52id(enum loongarch_gpr rd, enum loongarch_gpr rj, int imm); u32 larch_insn_gen_jirl(enum loongarch_gpr rd, enum loongarch_gpr rj, unsigned long pc, unsigned long dest); diff --git a/arch/loongarch/include/asm/unwind.h b/arch/loongarch/include/asm/unwind.h index 206fcbe24c0a..6af4718bdf01 100644 --- a/arch/loongarch/include/asm/unwind.h +++ b/arch/loongarch/include/asm/unwind.h @@ -11,11 +11,17 @@ #include +enum unwinder_type { + UNWINDER_GUESS, + UNWINDER_PROLOGUE, +}; + struct unwind_state { + char type; /* UNWINDER_XXX */ struct stack_info stack_info; struct task_struct *task; bool first, error; - unsigned long sp, pc; + unsigned long sp, pc, ra; }; void unwind_start(struct unwind_state *state, diff --git a/arch/loongarch/kernel/Makefile b/arch/loongarch/kernel/Makefile index c5fa4adb23b6..918600e7b30f 100644 --- a/arch/loongarch/kernel/Makefile +++ b/arch/loongarch/kernel/Makefile @@ -23,5 +23,6 @@ obj-$(CONFIG_SMP) += smp.o obj-$(CONFIG_NUMA) += numa.o obj-$(CONFIG_UNWINDER_GUESS) += unwind_guess.o +obj-$(CONFIG_UNWINDER_PROLOGUE) += unwind_prologue.o CPPFLAGS_vmlinux.lds := $(KBUILD_CFLAGS) diff --git a/arch/loongarch/kernel/traps.c b/arch/loongarch/kernel/traps.c index f65fdf90d29e..aa1c95aaf595 100644 --- a/arch/loongarch/kernel/traps.c +++ b/arch/loongarch/kernel/traps.c @@ -71,6 +71,9 @@ static void show_backtrace(struct task_struct *task, const struct pt_regs *regs, if (!task) task = current; + if (user_mode(regs)) + state.type = UNWINDER_GUESS; + printk("%sCall Trace:", loglvl); for (unwind_start(&state, task, pregs); !unwind_done(&state); unwind_next_frame(&state)) { diff --git a/arch/loongarch/kernel/unwind_prologue.c b/arch/loongarch/kernel/unwind_prologue.c new file mode 100644 index 000000000000..b206d9159205 --- /dev/null +++ b/arch/loongarch/kernel/unwind_prologue.c @@ -0,0 +1,176 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) 2022 Loongson Technology Corporation Limited + */ +#include + +#include +#include +#include + +unsigned long unwind_get_return_address(struct unwind_state *state) +{ + + if (unwind_done(state)) + return 0; + else if (state->type) + return state->pc; + else if (state->first) + return state->pc; + + return *(unsigned long *)(state->sp); + +} +EXPORT_SYMBOL_GPL(unwind_get_return_address); + +static bool unwind_by_guess(struct unwind_state *state) +{ + struct stack_info *info = &state->stack_info; + unsigned long addr; + + for (state->sp += sizeof(unsigned long); + state->sp < info->end; + state->sp += sizeof(unsigned long)) { + addr = *(unsigned long *)(state->sp); + if (__kernel_text_address(addr)) + return true; + } + + return false; +} + +static bool unwind_by_prologue(struct unwind_state *state) +{ + struct stack_info *info = &state->stack_info; + union loongarch_instruction *ip, *ip_end; + unsigned long frame_size = 0, frame_ra = -1; + unsigned long size, offset, pc = state->pc; + + if (state->sp >= info->end || state->sp < info->begin) + return false; + + if (!kallsyms_lookup_size_offset(pc, &size, &offset)) + return false; + + ip = (union loongarch_instruction *)(pc - offset); + ip_end = (union loongarch_instruction *)pc; + + while (ip < ip_end) { + if (is_stack_alloc_ins(ip)) { + frame_size = (1 << 12) - ip->reg2i12_format.immediate; + ip++; + break; + } + ip++; + } + + if (!frame_size) { + if (state->first) + goto first; + + return false; + } + + while (ip < ip_end) { + if (is_ra_save_ins(ip)) { + frame_ra = ip->reg2i12_format.immediate; + break; + } + if (is_branch_ins(ip)) + break; + ip++; + } + + if (frame_ra < 0) { + if (state->first) { + state->sp = state->sp + frame_size; + goto first; + } + return false; + } + + if (state->first) + state->first = false; + + state->pc = *(unsigned long *)(state->sp + frame_ra); + state->sp = state->sp + frame_size; + return !!__kernel_text_address(state->pc); + +first: + state->first = false; + if (state->pc == state->ra) + return false; + + state->pc = state->ra; + + return !!__kernel_text_address(state->ra); +} + +void unwind_start(struct unwind_state *state, struct task_struct *task, + struct pt_regs *regs) +{ + memset(state, 0, sizeof(*state)); + + if (regs && __kernel_text_address(regs->csr_era)) { + state->pc = regs->csr_era; + state->sp = regs->regs[3]; + state->ra = regs->regs[1]; + state->type = UNWINDER_PROLOGUE; + } + + state->task = task; + state->first = true; + + get_stack_info(state->sp, state->task, &state->stack_info); + + if (!unwind_done(state) && !__kernel_text_address(state->pc)) + unwind_next_frame(state); +} +EXPORT_SYMBOL_GPL(unwind_start); + +bool unwind_next_frame(struct unwind_state *state) +{ + struct stack_info *info = &state->stack_info; + struct pt_regs *regs; + unsigned long pc; + + if (unwind_done(state)) + return false; + + do { + switch (state->type) { + case UNWINDER_GUESS: + state->first = false; + if (unwind_by_guess(state)) + return true; + break; + + case UNWINDER_PROLOGUE: + if (unwind_by_prologue(state)) + return true; + + if (info->type == STACK_TYPE_IRQ && + info->end == state->sp) { + regs = (struct pt_regs *)info->next_sp; + pc = regs->csr_era; + + if (user_mode(regs) || !__kernel_text_address(pc)) + return false; + + state->pc = pc; + state->sp = regs->regs[3]; + state->ra = regs->regs[1]; + state->first = true; + get_stack_info(state->sp, state->task, info); + + return true; + } + } + + state->sp = info->next_sp; + + } while (!get_stack_info(state->sp, state->task, info)); + + return false; +} +EXPORT_SYMBOL_GPL(unwind_next_frame); -- cgit v1.2.3 From 93a4fa622eb061f75f87f0cf9609ab4e69c67d01 Mon Sep 17 00:00:00 2001 From: Qing Zhang Date: Sat, 6 Aug 2022 16:10:04 +0800 Subject: LoongArch: Add STACKTRACE support 1. Use common arch_stack_walk() infrastructure to avoid duplicated code and avoid taking care of the stack storage and filtering. 2. Add sched_ra (means sched return address) and sched_cfa (means sched call frame address) to thread_info, and store them in switch_to(). 3. Add __get_wchan() implementation. Now we can print the process stack and wait channel by cat /proc/*/stack and /proc/*/wchan. Signed-off-by: Qing Zhang Signed-off-by: Huacai Chen --- arch/loongarch/Kconfig | 5 +++++ arch/loongarch/include/asm/processor.h | 9 +++++++++ arch/loongarch/include/asm/switch_to.h | 14 ++++++++----- arch/loongarch/kernel/Makefile | 1 + arch/loongarch/kernel/asm-offsets.c | 2 ++ arch/loongarch/kernel/process.c | 29 +++++++++++++++++++++++++- arch/loongarch/kernel/stacktrace.c | 37 ++++++++++++++++++++++++++++++++++ arch/loongarch/kernel/switch.S | 2 ++ 8 files changed, 93 insertions(+), 6 deletions(-) create mode 100644 arch/loongarch/kernel/stacktrace.c (limited to 'arch/loongarch/include') diff --git a/arch/loongarch/Kconfig b/arch/loongarch/Kconfig index 5b4f7bdf69fa..947cb633744b 100644 --- a/arch/loongarch/Kconfig +++ b/arch/loongarch/Kconfig @@ -42,6 +42,7 @@ config LOONGARCH select ARCH_MIGHT_HAVE_PC_PARPORT select ARCH_MIGHT_HAVE_PC_SERIO select ARCH_SPARSEMEM_ENABLE + select ARCH_STACKWALK select ARCH_SUPPORTS_ACPI select ARCH_SUPPORTS_ATOMIC_RMW select ARCH_SUPPORTS_HUGETLBFS @@ -151,6 +152,10 @@ config LOCKDEP_SUPPORT bool default y +config STACKTRACE_SUPPORT + bool + default y + # MACH_LOONGSON32 and MACH_LOONGSON64 are delibrately carried over from the # MIPS Loongson code, to preserve Loongson-specific code paths in drivers that # are shared between architectures, and specifically expecting the symbols. diff --git a/arch/loongarch/include/asm/processor.h b/arch/loongarch/include/asm/processor.h index 57ec45aa078e..1c4b4308378d 100644 --- a/arch/loongarch/include/asm/processor.h +++ b/arch/loongarch/include/asm/processor.h @@ -101,6 +101,10 @@ struct thread_struct { unsigned long reg23, reg24, reg25, reg26; /* s0-s3 */ unsigned long reg27, reg28, reg29, reg30, reg31; /* s4-s8 */ + /* __schedule() return address / call frame address */ + unsigned long sched_ra; + unsigned long sched_cfa; + /* CSR registers */ unsigned long csr_prmd; unsigned long csr_crmd; @@ -129,6 +133,9 @@ struct thread_struct { struct loongarch_fpu fpu FPU_ALIGN; }; +#define thread_saved_ra(tsk) (tsk->thread.sched_ra) +#define thread_saved_fp(tsk) (tsk->thread.sched_cfa) + #define INIT_THREAD { \ /* \ * Main processor registers \ @@ -145,6 +152,8 @@ struct thread_struct { .reg29 = 0, \ .reg30 = 0, \ .reg31 = 0, \ + .sched_ra = 0, \ + .sched_cfa = 0, \ .csr_crmd = 0, \ .csr_prmd = 0, \ .csr_euen = 0, \ diff --git a/arch/loongarch/include/asm/switch_to.h b/arch/loongarch/include/asm/switch_to.h index 2a8d04375574..43a5ab162d38 100644 --- a/arch/loongarch/include/asm/switch_to.h +++ b/arch/loongarch/include/asm/switch_to.h @@ -15,12 +15,15 @@ struct task_struct; * @prev: The task previously executed. * @next: The task to begin executing. * @next_ti: task_thread_info(next). + * @sched_ra: __schedule return address. + * @sched_cfa: __schedule call frame address. * * This function is used whilst scheduling to save the context of prev & load * the context of next. Returns prev. */ extern asmlinkage struct task_struct *__switch_to(struct task_struct *prev, - struct task_struct *next, struct thread_info *next_ti); + struct task_struct *next, struct thread_info *next_ti, + void *sched_ra, void *sched_cfa); /* * For newly created kernel threads switch_to() will return to @@ -28,10 +31,11 @@ extern asmlinkage struct task_struct *__switch_to(struct task_struct *prev, * That is, everything following __switch_to() will be skipped for new threads. * So everything that matters to new threads should be placed before __switch_to(). */ -#define switch_to(prev, next, last) \ -do { \ - lose_fpu_inatomic(1, prev); \ - (last) = __switch_to(prev, next, task_thread_info(next)); \ +#define switch_to(prev, next, last) \ +do { \ + lose_fpu_inatomic(1, prev); \ + (last) = __switch_to(prev, next, task_thread_info(next), \ + __builtin_return_address(0), __builtin_frame_address(0)); \ } while (0) #endif /* _ASM_SWITCH_TO_H */ diff --git a/arch/loongarch/kernel/Makefile b/arch/loongarch/kernel/Makefile index 918600e7b30f..e5be17009fe8 100644 --- a/arch/loongarch/kernel/Makefile +++ b/arch/loongarch/kernel/Makefile @@ -15,6 +15,7 @@ obj-$(CONFIG_EFI) += efi.o obj-$(CONFIG_CPU_HAS_FPU) += fpu.o obj-$(CONFIG_MODULES) += module.o module-sections.o +obj-$(CONFIG_STACKTRACE) += stacktrace.o obj-$(CONFIG_PROC_FS) += proc.o diff --git a/arch/loongarch/kernel/asm-offsets.c b/arch/loongarch/kernel/asm-offsets.c index 20cd9e16a95a..eb350f3ffae5 100644 --- a/arch/loongarch/kernel/asm-offsets.c +++ b/arch/loongarch/kernel/asm-offsets.c @@ -103,6 +103,8 @@ void output_thread_defines(void) OFFSET(THREAD_REG29, task_struct, thread.reg29); OFFSET(THREAD_REG30, task_struct, thread.reg30); OFFSET(THREAD_REG31, task_struct, thread.reg31); + OFFSET(THREAD_SCHED_RA, task_struct, thread.sched_ra); + OFFSET(THREAD_SCHED_CFA, task_struct, thread.sched_cfa); OFFSET(THREAD_CSRCRMD, task_struct, thread.csr_crmd); OFFSET(THREAD_CSRPRMD, task_struct, diff --git a/arch/loongarch/kernel/process.c b/arch/loongarch/kernel/process.c index 839f0e963152..660492f064e7 100644 --- a/arch/loongarch/kernel/process.c +++ b/arch/loongarch/kernel/process.c @@ -135,6 +135,7 @@ int copy_thread(struct task_struct *p, const struct kernel_clone_args *args) childregs = (struct pt_regs *) childksp - 1; /* Put the stack after the struct pt_regs. */ childksp = (unsigned long) childregs; + p->thread.sched_cfa = 0; p->thread.csr_euen = 0; p->thread.csr_crmd = csr_read32(LOONGARCH_CSR_CRMD); p->thread.csr_prmd = csr_read32(LOONGARCH_CSR_PRMD); @@ -145,6 +146,7 @@ int copy_thread(struct task_struct *p, const struct kernel_clone_args *args) p->thread.reg23 = (unsigned long)args->fn; p->thread.reg24 = (unsigned long)args->fn_arg; p->thread.reg01 = (unsigned long)ret_from_kernel_thread; + p->thread.sched_ra = (unsigned long)ret_from_kernel_thread; memset(childregs, 0, sizeof(struct pt_regs)); childregs->csr_euen = p->thread.csr_euen; childregs->csr_crmd = p->thread.csr_crmd; @@ -161,6 +163,7 @@ int copy_thread(struct task_struct *p, const struct kernel_clone_args *args) p->thread.reg03 = (unsigned long) childregs; p->thread.reg01 = (unsigned long) ret_from_fork; + p->thread.sched_ra = (unsigned long) ret_from_fork; /* * New tasks lose permission to use the fpu. This accelerates context @@ -181,7 +184,31 @@ int copy_thread(struct task_struct *p, const struct kernel_clone_args *args) unsigned long __get_wchan(struct task_struct *task) { - return 0; + unsigned long pc; + struct unwind_state state; + + if (!try_get_task_stack(task)) + return 0; + + unwind_start(&state, task, NULL); + state.sp = thread_saved_fp(task); + get_stack_info(state.sp, state.task, &state.stack_info); + state.pc = thread_saved_ra(task); +#ifdef CONFIG_UNWINDER_PROLOGUE + state.type = UNWINDER_PROLOGUE; +#endif + for (; !unwind_done(&state); unwind_next_frame(&state)) { + pc = unwind_get_return_address(&state); + if (!pc) + break; + if (in_sched_functions(pc)) + continue; + break; + } + + put_task_stack(task); + + return pc; } bool in_irq_stack(unsigned long stack, struct stack_info *info) diff --git a/arch/loongarch/kernel/stacktrace.c b/arch/loongarch/kernel/stacktrace.c new file mode 100644 index 000000000000..e690c1c769f2 --- /dev/null +++ b/arch/loongarch/kernel/stacktrace.c @@ -0,0 +1,37 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Stack trace management functions + * + * Copyright (C) 2022 Loongson Technology Corporation Limited + */ +#include +#include + +#include +#include + +void arch_stack_walk(stack_trace_consume_fn consume_entry, void *cookie, + struct task_struct *task, struct pt_regs *regs) +{ + unsigned long addr; + struct pt_regs dummyregs; + struct unwind_state state; + + regs = &dummyregs; + + if (task == current) { + regs->regs[3] = (unsigned long)__builtin_frame_address(0); + regs->csr_era = (unsigned long)__builtin_return_address(0); + } else { + regs->regs[3] = thread_saved_fp(task); + regs->csr_era = thread_saved_ra(task); + } + + regs->regs[1] = 0; + for (unwind_start(&state, task, regs); + !unwind_done(&state); unwind_next_frame(&state)) { + addr = unwind_get_return_address(&state); + if (!addr || !consume_entry(cookie, addr)) + break; + } +} diff --git a/arch/loongarch/kernel/switch.S b/arch/loongarch/kernel/switch.S index 37e84ac8ffc2..43ebbc3990f7 100644 --- a/arch/loongarch/kernel/switch.S +++ b/arch/loongarch/kernel/switch.S @@ -21,6 +21,8 @@ SYM_FUNC_START(__switch_to) cpu_save_nonscratch a0 stptr.d ra, a0, THREAD_REG01 + stptr.d a3, a0, THREAD_SCHED_RA + stptr.d a4, a0, THREAD_SCHED_CFA move tp, a2 cpu_restore_nonscratch a1 -- cgit v1.2.3 From 4d7bf939df08218e682f7a42952eee3bad4dceb7 Mon Sep 17 00:00:00 2001 From: Qing Zhang Date: Sat, 6 Aug 2022 16:10:05 +0800 Subject: LoongArch: Add USER_STACKTRACE support To get the best stacktrace output, you can compile your userspace programs with frame pointers (at least glibc + the app you are tracing). 1, export "CC = gcc -fno-omit-frame-pointer"; 2, compile your programs with "CC"; 3, use uprobe to get stacktrace output. ... echo 'p:malloc /usr/lib64/libc.so.6:0x0a4704 size=%r4:u64' > uprobe_events echo 'p:free /usr/lib64/libc.so.6:0x0a4d50 ptr=%r4:x64' >> uprobe_events echo 'comm == "demo"' > ./events/uprobes/malloc/filter echo 'comm == "demo"' > ./events/uprobes/free/filter echo 1 > ./options/userstacktrace echo 1 > ./options/sym-userobj ... Signed-off-by: Qing Zhang Signed-off-by: Huacai Chen --- arch/loongarch/Kconfig | 1 + arch/loongarch/include/asm/stacktrace.h | 5 ++++ arch/loongarch/kernel/stacktrace.c | 41 +++++++++++++++++++++++++++++++++ 3 files changed, 47 insertions(+) (limited to 'arch/loongarch/include') diff --git a/arch/loongarch/Kconfig b/arch/loongarch/Kconfig index 947cb633744b..2f110a00a930 100644 --- a/arch/loongarch/Kconfig +++ b/arch/loongarch/Kconfig @@ -119,6 +119,7 @@ config LOONGARCH select SWIOTLB select TRACE_IRQFLAGS_SUPPORT select USE_PERCPU_NUMA_NODE_ID + select USER_STACKTRACE_SUPPORT select ZONE_DMA32 config 32BIT diff --git a/arch/loongarch/include/asm/stacktrace.h b/arch/loongarch/include/asm/stacktrace.h index 5820a0cabe3a..f23adb15f418 100644 --- a/arch/loongarch/include/asm/stacktrace.h +++ b/arch/loongarch/include/asm/stacktrace.h @@ -21,6 +21,11 @@ struct stack_info { unsigned long begin, end, next_sp; }; +struct stack_frame { + unsigned long fp; + unsigned long ra; +}; + bool in_irq_stack(unsigned long stack, struct stack_info *info); bool in_task_stack(unsigned long stack, struct task_struct *task, struct stack_info *info); int get_stack_info(unsigned long stack, struct task_struct *task, struct stack_info *info); diff --git a/arch/loongarch/kernel/stacktrace.c b/arch/loongarch/kernel/stacktrace.c index e690c1c769f2..3a690f96f00c 100644 --- a/arch/loongarch/kernel/stacktrace.c +++ b/arch/loongarch/kernel/stacktrace.c @@ -6,6 +6,7 @@ */ #include #include +#include #include #include @@ -35,3 +36,43 @@ void arch_stack_walk(stack_trace_consume_fn consume_entry, void *cookie, break; } } + +static int +copy_stack_frame(unsigned long fp, struct stack_frame *frame) +{ + int ret = 1; + unsigned long err; + unsigned long __user *user_frame_tail; + + user_frame_tail = (unsigned long *)(fp - sizeof(struct stack_frame)); + if (!access_ok(user_frame_tail, sizeof(*frame))) + return 0; + + pagefault_disable(); + err = (__copy_from_user_inatomic(frame, user_frame_tail, sizeof(*frame))); + if (err || (unsigned long)user_frame_tail >= frame->fp) + ret = 0; + pagefault_enable(); + + return ret; +} + +void arch_stack_walk_user(stack_trace_consume_fn consume_entry, void *cookie, + const struct pt_regs *regs) +{ + unsigned long fp = regs->regs[22]; + + while (fp && !((unsigned long)fp & 0xf)) { + struct stack_frame frame; + + frame.fp = 0; + frame.ra = 0; + if (!copy_stack_frame(fp, &frame)) + break; + if (!frame.ra) + break; + if (!consume_entry(cookie, frame.ra)) + break; + fp = frame.fp; + } +} -- cgit v1.2.3