From a9b0c34507eba2b386be61fe49ab2fe0db121674 Mon Sep 17 00:00:00 2001 From: Bibo Mao Date: Tue, 27 Oct 2026 22:51:12 +0800 Subject: [PATCH 01/24] ACPI: LoongArch: Add acpi_arch_[late]_init() LoongArch IOMMU scanning flow is similar with virtio-iommu, there is an early init phase and a late init phase. In the early init function, PCI ACS is enabled if there is IOMMU table, and the late init function is called after PCI device scanning, where IOVT table scanning function is executed. Here add acpi_arch_init() and acpi_arch_late_init() API for LoongArch: the acpi_iovt_init() function is to enable PCI ACS function if the IOMMU device exists in IOVT table; while the acpi_iovt_late_init() function is to scan IOVT table and add IOMMU devices. Signed-off-by: Bibo Mao Signed-off-by: Huacai Chen --- arch/loongarch/Kconfig | 1 + drivers/acpi/Kconfig | 4 + drivers/acpi/Makefile | 2 +- drivers/acpi/bus.c | 2 + drivers/acpi/loongarch/Kconfig | 7 ++ drivers/acpi/loongarch/Makefile | 3 + drivers/acpi/loongarch/init.c | 16 +++ drivers/acpi/loongarch/init.h | 5 + drivers/acpi/loongarch/iovt.c | 211 ++++++++++++++++++++++++++++++++ include/linux/acpi.h | 1 + 10 files changed, 251 insertions(+), 1 deletion(-) create mode 100644 drivers/acpi/loongarch/Kconfig create mode 100644 drivers/acpi/loongarch/Makefile create mode 100644 drivers/acpi/loongarch/init.c create mode 100644 drivers/acpi/loongarch/init.h create mode 100644 drivers/acpi/loongarch/iovt.c diff --git a/arch/loongarch/Kconfig b/arch/loongarch/Kconfig index 2067d1f2ad7a..d7f8e4fec952 100644 --- a/arch/loongarch/Kconfig +++ b/arch/loongarch/Kconfig @@ -6,6 +6,7 @@ config LOONGARCH select ACPI_GENERIC_GSI if ACPI select ACPI_MCFG if ACPI select ACPI_HOTPLUG_CPU if ACPI_PROCESSOR && HOTPLUG_CPU + select ACPI_IOVT if ACPI select ACPI_PPTT if ACPI select ACPI_SYSTEM_POWER_STATES_SUPPORT if ACPI select ARCH_BINFMT_ELF_STATE diff --git a/drivers/acpi/Kconfig b/drivers/acpi/Kconfig index f165d14cf61a..192e6f18eda1 100644 --- a/drivers/acpi/Kconfig +++ b/drivers/acpi/Kconfig @@ -550,6 +550,10 @@ if ARM64 source "drivers/acpi/arm64/Kconfig" endif +if LOONGARCH +source "drivers/acpi/loongarch/Kconfig" +endif + if RISCV source "drivers/acpi/riscv/Kconfig" endif diff --git a/drivers/acpi/Makefile b/drivers/acpi/Makefile index d1b0affb844f..91584a3c1e01 100644 --- a/drivers/acpi/Makefile +++ b/drivers/acpi/Makefile @@ -131,6 +131,6 @@ obj-y += dptf/ obj-$(CONFIG_ARM64) += arm64/ obj-$(CONFIG_ACPI_VIOT) += viot.o - +obj-$(CONFIG_LOONGARCH) += loongarch/ obj-$(CONFIG_RISCV) += riscv/ obj-$(CONFIG_X86) += x86/ diff --git a/drivers/acpi/bus.c b/drivers/acpi/bus.c index d5d234dfa9a2..1d6867dec925 100644 --- a/drivers/acpi/bus.c +++ b/drivers/acpi/bus.c @@ -1449,6 +1449,7 @@ struct kobject *acpi_kobj; EXPORT_SYMBOL_GPL(acpi_kobj); void __weak __init acpi_arch_init(void) { } +void __weak __init acpi_arch_late_init(void) { } static int __init acpi_init(void) { @@ -1487,6 +1488,7 @@ static int __init acpi_init(void) acpi_wakeup_device_init(); acpi_debugger_init(); acpi_setup_sb_notify_handler(); + acpi_arch_late_init(); acpi_viot_init(); return 0; } diff --git a/drivers/acpi/loongarch/Kconfig b/drivers/acpi/loongarch/Kconfig new file mode 100644 index 000000000000..91ba3c35b9bf --- /dev/null +++ b/drivers/acpi/loongarch/Kconfig @@ -0,0 +1,7 @@ +# SPDX-License-Identifier: GPL-2.0-only +# +# ACPI Configuration for LOONGARCH +# + +config ACPI_IOVT + bool diff --git a/drivers/acpi/loongarch/Makefile b/drivers/acpi/loongarch/Makefile new file mode 100644 index 000000000000..d0cb177294a6 --- /dev/null +++ b/drivers/acpi/loongarch/Makefile @@ -0,0 +1,3 @@ +# SPDX-License-Identifier: GPL-2.0-only +obj-y += init.o +obj-$(CONFIG_ACPI_IOVT) += iovt.o diff --git a/drivers/acpi/loongarch/init.c b/drivers/acpi/loongarch/init.c new file mode 100644 index 000000000000..e7272ee197c7 --- /dev/null +++ b/drivers/acpi/loongarch/init.c @@ -0,0 +1,16 @@ +// SPDX-License-Identifier: GPL-2.0-only + +#include +#include "init.h" + +void __init acpi_arch_init(void) +{ + if (IS_ENABLED(CONFIG_ACPI_IOVT)) + acpi_iovt_init(); +} + +void __init acpi_arch_late_init(void) +{ + if (IS_ENABLED(CONFIG_ACPI_IOVT)) + acpi_iovt_late_init(); +} diff --git a/drivers/acpi/loongarch/init.h b/drivers/acpi/loongarch/init.h new file mode 100644 index 000000000000..85e5197553ad --- /dev/null +++ b/drivers/acpi/loongarch/init.h @@ -0,0 +1,5 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +#include + +void __init acpi_iovt_init(void); +void __init acpi_iovt_late_init(void); diff --git a/drivers/acpi/loongarch/iovt.c b/drivers/acpi/loongarch/iovt.c new file mode 100644 index 000000000000..023959cf34e5 --- /dev/null +++ b/drivers/acpi/loongarch/iovt.c @@ -0,0 +1,211 @@ +// SPDX-License-Identifier: GPL-2.0-only + +#include +#include +#include "init.h" + +struct iovt_device_entry { + struct list_head list; + int start_devid, end_devid; +}; + +struct iovt_fwnode { + struct list_head list; + struct fwnode_handle *fwnode; + int flag; + int nid, segment, devid; + struct list_head ep_list; +}; + +static LIST_HEAD(iovt_fwnode_list); +static DEFINE_SPINLOCK(iovt_fwnode_lock); + +#ifdef CONFIG_PCI +static void __init iovt_enable_acs(struct acpi_iovt_iommu *iommu) +{ + static bool acs_enabled __initdata; + + if (acs_enabled) + return; + + /* IOMMU V1 only supports PCI device management */ + if ((iommu->header.type == ACPI_IOVT_IOMMU_V1) || + (iommu->flags & (ACPI_IOVT_IOMMU_PCI_DEVICE | ACPI_IOVT_IOMMU_MAGAGE_BY_SEGMENT))) { + pci_request_acs(); + acs_enabled = true; + } +} +#else +static inline void iovt_enable_acs(struct acpi_iovt_iommu *iommu) { } +#endif + +static int __init iovt_get_pci_iommu_fwnode(struct iovt_fwnode *np, u16 segment, u16 bdf) +{ + struct pci_dev *pdev; + struct fwnode_handle *fwnode; + + pdev = pci_get_domain_bus_and_slot(segment, PCI_BUS_NUM(bdf), bdf & 0xff); + if (!pdev) { + pr_err("No PCI IOMMU found for segment 0x%x bdf 0x%x\n", segment, bdf); + return -ENODEV; + } + + fwnode = dev_fwnode(&pdev->dev); + if (!fwnode) { + /* + * PCI devices aren't necessarily described by ACPI. Create + * a fwnode so the IOMMU subsystem can identify this device. + */ + fwnode = acpi_alloc_fwnode_static(); + if (!fwnode) { + pci_dev_put(pdev); + return -ENOMEM; + } + set_primary_fwnode(&pdev->dev, fwnode); + } + + np->fwnode = dev_fwnode(&pdev->dev); + if (np->flag & ACPI_IOVT_IOMMU_PXM_VALID) + set_dev_node(&pdev->dev, np->nid); + pci_dev_put(pdev); + + return 0; +} + +static int __init iovt_add_iommu(struct acpi_iovt_iommu *iommu) +{ + int i, ret; + bool is_start = false; + struct iovt_fwnode *np; + struct iovt_device_entry *entry; + struct acpi_iovt_device_entry *ep; + + np = kzalloc_obj(struct iovt_fwnode, GFP_ATOMIC); + if (WARN_ON(!np)) + return -ENOMEM; + + INIT_LIST_HEAD(&np->list); + np->flag = iommu->flags; + np->segment = iommu->segment; + if (np->flag & ACPI_IOVT_IOMMU_PXM_VALID) + np->nid = pxm_to_node(iommu->proximity_domain); + + if (np->flag & ACPI_IOVT_IOMMU_PCI_DEVICE) { + np->devid = iommu->device_id; + ret = iovt_get_pci_iommu_fwnode(np, np->segment, np->devid); + if (ret < 0) { + kfree(np); + return ret; + } + } else { + np->fwnode = acpi_alloc_fwnode_static(); + if (!np->fwnode) { + kfree(np); + return -ENOMEM; + } + } + + /* All devices in the segment are managed by this IOMMU */ + if (np->flag & ACPI_IOVT_IOMMU_MAGAGE_BY_SEGMENT) + goto skip; + + INIT_LIST_HEAD(&np->ep_list); + ep = ACPI_ADD_PTR(struct acpi_iovt_device_entry, iommu, iommu->device_entry_offset); + for (i = 0; i < iommu->device_entry_num; i++) { + switch (ep->type) { + case ACPI_IOVT_DEVICE_ENTRY_START: + is_start = true; + entry->start_devid = ep->device_id; + break; + case ACPI_IOVT_DEVICE_ENTRY_END: + if (!is_start) + break; + + entry = kzalloc_obj(struct iovt_device_entry, GFP_ATOMIC); + if (!entry) + return -ENOMEM; + + is_start = false; + entry->end_devid = ep->device_id; + list_add_tail(&entry->list, &np->ep_list); + break; + case ACPI_IOVT_DEVICE_ENTRY_SINGLE: + entry = kzalloc_obj(struct iovt_device_entry, GFP_ATOMIC); + if (!entry) + return -ENOMEM; + + is_start = false; + entry->start_devid = ep->device_id; + entry->end_devid = ep->device_id; + list_add_tail(&entry->list, &np->ep_list); + break; + default: + break; + } + ep = ACPI_ADD_PTR(struct acpi_iovt_device_entry, ep, ep->length); + } + +skip: + spin_lock(&iovt_fwnode_lock); + list_add_tail(&np->list, &iovt_fwnode_list); + spin_unlock(&iovt_fwnode_lock); + return 0; +} + +static void __init iovt_init_devices(struct acpi_table_header *header) +{ + int i; + struct acpi_table_iovt *iovt; + struct acpi_iovt_iommu *iommu; + + /* Get the first IOVT node */ + iovt = (struct acpi_table_iovt *)header; + iommu = ACPI_ADD_PTR(struct acpi_iovt_iommu, iovt, iovt->iommu_offset); + for (i = 0; i < iovt->iommu_count; i++) { + iovt_add_iommu(iommu); + iommu = ACPI_ADD_PTR(struct acpi_iovt_iommu, iommu, iommu->header.length); + } +} + +void __init acpi_iovt_init(void) +{ + int i; + acpi_status status; + struct acpi_table_header *hdr; + struct acpi_table_iovt *iovt; + struct acpi_iovt_iommu *iommu; + + status = acpi_get_table(ACPI_SIG_IOVT, 0, &hdr); + if (ACPI_FAILURE(status)) { + if (status != AE_NOT_FOUND) + pr_err("Failed to get table, %s\n", acpi_format_exception(status)); + + return; + } + + iovt = (struct acpi_table_iovt *)&hdr; + iommu = ACPI_ADD_PTR(struct acpi_iovt_iommu, iovt, iovt->iommu_offset); + for (i = 0; i < iovt->iommu_count; i++) { + iovt_enable_acs(iommu); + iommu = ACPI_ADD_PTR(struct acpi_iovt_iommu, iommu, iommu->header.length); + } + + acpi_put_table(hdr); +} + +void __init acpi_iovt_late_init(void) +{ + acpi_status status; + struct acpi_table_header *hdr; + + status = acpi_get_table(ACPI_SIG_IOVT, 0, &hdr); + if (ACPI_FAILURE(status)) { + if (status != AE_NOT_FOUND) + pr_err("Failed to get table, %s\n", acpi_format_exception(status)); + + return; + } + + iovt_init_devices(hdr); + acpi_put_table(hdr); +} diff --git a/include/linux/acpi.h b/include/linux/acpi.h index d7f1f3d05390..9d3f039ebd94 100644 --- a/include/linux/acpi.h +++ b/include/linux/acpi.h @@ -1637,6 +1637,7 @@ static inline int acpi_pptt_get_cpumask_from_cache_id(u32 cache_id, #endif void acpi_arch_init(void); +void acpi_arch_late_init(void); #ifdef CONFIG_ACPI_PCC void acpi_init_pcc(void); From 33e340160884ca61e414c237bbaef10553639457 Mon Sep 17 00:00:00 2001 From: Bibo Mao Date: Tue, 27 Oct 2026 22:51:12 +0800 Subject: [PATCH 02/24] ACPI: LoongArch: Add support for acpi_iommu_configure_id() acpi_iommu_configure_id() currently only supports IORT and VIOT. Add support for LoongArch's IOVT as well. Acked-by: Rafael J. Wysocki (Intel) Signed-off-by: Bibo Mao Signed-off-by: Huacai Chen --- drivers/acpi/loongarch/iovt.c | 47 +++++++++++++++++++++++++++++++++++ drivers/acpi/scan.c | 3 +++ include/linux/acpi_iovt.h | 13 ++++++++++ 3 files changed, 63 insertions(+) create mode 100644 include/linux/acpi_iovt.h diff --git a/drivers/acpi/loongarch/iovt.c b/drivers/acpi/loongarch/iovt.c index 023959cf34e5..87cee4f0fc80 100644 --- a/drivers/acpi/loongarch/iovt.c +++ b/drivers/acpi/loongarch/iovt.c @@ -1,6 +1,7 @@ // SPDX-License-Identifier: GPL-2.0-only #include +#include #include #include "init.h" @@ -209,3 +210,49 @@ void __init acpi_iovt_late_init(void) iovt_init_devices(hdr); acpi_put_table(hdr); } + +static int iovt_pci_iommu_init(struct pci_dev *pdev, u16 dev_id, void *data) +{ + struct iovt_fwnode *np; + struct iovt_device_entry *entry; + struct device *aliased_dev = data; + u32 domain = pci_domain_nr(pdev->bus); + + list_for_each_entry(np, &iovt_fwnode_list, list) { + if (domain != np->segment) + continue; + + /* We're not translating ourself */ + if (dev_id == np->devid) + return -EINVAL; + + if (np->flag & ACPI_IOVT_IOMMU_MAGAGE_BY_SEGMENT) + goto found; + + list_for_each_entry(entry, &np->ep_list, list) { + if (dev_id >= entry->start_devid && dev_id <= entry->end_devid) + goto found; + } + } + + return -ENODEV; + +found: + return acpi_iommu_fwspec_init(aliased_dev, (domain << 16) + dev_id, np->fwnode); +} + +/* + * iovt_iommu_configure_id - Set-up IOMMU configuration for a device. + * + * @dev: device to configure + * @id_in: optional input id const value pointer + * + * Returns: 0 on success, <0 on failure + */ +int iovt_iommu_configure_id(struct device *dev, const u32 *id_in) +{ + if (!dev_is_pci(dev)) + return -ENODEV; + + return pci_for_each_dma_alias(to_pci_dev(dev), iovt_pci_iommu_init, dev); +} diff --git a/drivers/acpi/scan.c b/drivers/acpi/scan.c index 30bfef67cbaa..3fbba17ccf19 100644 --- a/drivers/acpi/scan.c +++ b/drivers/acpi/scan.c @@ -13,6 +13,7 @@ #include #include #include +#include #include #include #include @@ -1625,6 +1626,8 @@ static int acpi_iommu_configure_id(struct device *dev, const u32 *id_in) err = iort_iommu_configure_id(dev, id_in); if (err && err != -EPROBE_DEFER) err = rimt_iommu_configure_id(dev, id_in); + if (err && err != -EPROBE_DEFER) + err = iovt_iommu_configure_id(dev, id_in); if (err && err != -EPROBE_DEFER) err = viot_iommu_configure(dev); diff --git a/include/linux/acpi_iovt.h b/include/linux/acpi_iovt.h new file mode 100644 index 000000000000..663b4c679bc5 --- /dev/null +++ b/include/linux/acpi_iovt.h @@ -0,0 +1,13 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ + +#ifndef __ACPI_IOVT_H__ +#define __ACPI_IOVT_H__ + +#ifdef CONFIG_ACPI_IOVT +int iovt_iommu_configure_id(struct device *dev, const u32 *id_in); +#else +static inline int iovt_iommu_configure_id(struct device *dev, const u32 *id_in) +{ return -ENODEV; } +#endif + +#endif /* __ACPI_IOVT_H__ */ From e9c878f510c2e409f51602cfc8f128610f6932cd Mon Sep 17 00:00:00 2001 From: Bibo Mao Date: Tue, 27 Oct 2026 22:51:22 +0800 Subject: [PATCH 03/24] MAINTAINERS: Add entry for drivers/acpi/loongarch ACPI defines few LoongArch specific tables which need parsing code added in drivers/acpi/loongarch. Add maintainer entries for this newly created folder. Signed-off-by: Bibo Mao Signed-off-by: Huacai Chen --- MAINTAINERS | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/MAINTAINERS b/MAINTAINERS index 782389bd5cd3..f38c636cfea5 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -339,6 +339,15 @@ S: Maintained F: drivers/acpi/arm64 F: include/linux/acpi_iort.h +ACPI FOR LOONGARCH (ACPI/LoongArch) +M: Bibo Mao +M: Huacai Chen +L: linux-acpi@vger.kernel.org +L: loongarch@lists.linux.dev +S: Maintained +F: drivers/acpi/loongarch/ +F: include/linux/acpi_iovt.h + ACPI FOR RISC-V (ACPI/riscv) M: Sunil V L L: linux-acpi@vger.kernel.org From 21e3af1bc639c4bd2376d155def53fe0e9ea1264 Mon Sep 17 00:00:00 2001 From: Heinrich Schuchardt Date: Tue, 27 Oct 2026 22:51:22 +0800 Subject: [PATCH 04/24] LoongArch: Set NX compat flag in PE/COFF efi header The flag IMAGE_DLLCHARACTERISTICS_NX_COMPAT informs the firmware that the EFI stub binary does not rely on pages that are both executable and writable. As the Linux kernel neither has RWX sections nor needs RWX pages for relocation we should set the flag. Signed-off-by: Heinrich Schuchardt Signed-off-by: Huacai Chen --- arch/loongarch/kernel/efi-header.S | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/arch/loongarch/kernel/efi-header.S b/arch/loongarch/kernel/efi-header.S index 6df56241cb95..c041dad5bbb6 100644 --- a/arch/loongarch/kernel/efi-header.S +++ b/arch/loongarch/kernel/efi-header.S @@ -51,7 +51,7 @@ .long .Lefi_header_end - _head /* SizeOfHeaders */ .long 0 /* CheckSum */ .short IMAGE_SUBSYSTEM_EFI_APPLICATION /* Subsystem */ - .short 0 /* DllCharacteristics */ + .short IMAGE_DLLCHARACTERISTICS_NX_COMPAT /* DllCharacteristics */ .quad 0 /* SizeOfStackReserve */ .quad 0 /* SizeOfStackCommit */ .quad 0 /* SizeOfHeapReserve */ From 12782fb6dfa9746c652443a412ed55bf0e2e357c Mon Sep 17 00:00:00 2001 From: Peng Fan Date: Tue, 27 Oct 2026 22:51:22 +0800 Subject: [PATCH 05/24] LoongArch: Use assign_bit() API where applicable Convert the open-coded if/else with set_bit()/clear_bit() API to the assign_bit() API in init_environ(). Signed-off-by: Peng Fan Signed-off-by: Huacai Chen --- arch/loongarch/kernel/env.c | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/arch/loongarch/kernel/env.c b/arch/loongarch/kernel/env.c index 652456768b55..c529a14deb18 100644 --- a/arch/loongarch/kernel/env.c +++ b/arch/loongarch/kernel/env.c @@ -25,10 +25,7 @@ void __init init_environ(void) int efi_boot = fw_arg0; char *cmdline = early_memremap_ro(fw_arg1, COMMAND_LINE_SIZE); - if (efi_boot) - set_bit(EFI_BOOT, &efi.flags); - else - clear_bit(EFI_BOOT, &efi.flags); + assign_bit(EFI_BOOT, &efi.flags, efi_boot); strscpy(boot_command_line, cmdline, COMMAND_LINE_SIZE); strscpy(init_command_line, cmdline, COMMAND_LINE_SIZE); From 9b56c3659a7e5d930ab3c07ea74cc46af65b6d20 Mon Sep 17 00:00:00 2001 From: Huacai Chen Date: Tue, 27 Oct 2026 22:51:23 +0800 Subject: [PATCH 06/24] LoongArch: Call cpu_final_simd() with pure_initcall() After commit 35ebc4de105989034f12 ("xor: remove macro abuse for XOR implementation registrations"), using "simd=128" to limit SIMD on a platform with 256bit SIMD instructions causes a boot failure. Because cpu_final_simd() is called with arch_initcall() now, which is after xor_init(), LASX flavors are selected but not workable. Call cpu_final_simd() with pure_initcall() which is before xor_init(). Cc: stable@vger.kernel.org Signed-off-by: Huacai Chen --- arch/loongarch/kernel/cpu-probe.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/arch/loongarch/kernel/cpu-probe.c b/arch/loongarch/kernel/cpu-probe.c index 74d31f260dfd..629c976c33c3 100644 --- a/arch/loongarch/kernel/cpu-probe.c +++ b/arch/loongarch/kernel/cpu-probe.c @@ -93,7 +93,7 @@ static int __init cpu_final_simd(void) return 0; } -arch_initcall(cpu_final_simd); +pure_initcall(cpu_final_simd); static inline void set_elf_platform(int cpu, const char *plat) { From 87c6d144bc7bbed0c4573848b9c92006edca7848 Mon Sep 17 00:00:00 2001 From: Huacai Chen Date: Tue, 27 Oct 2026 22:51:23 +0800 Subject: [PATCH 07/24] LoongArch: Don't init & online empty nodes Don't init & online empty nodes without any CPU cores (and memory), in this way "lscpu" will not display confusing empty nodes. Modify fake_numa_init() to make sure at least Node-0 isn't empty for the FDT booting case. Cc: stable@vger.kernel.org Signed-off-by: Huacai Chen --- arch/loongarch/kernel/numa.c | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/arch/loongarch/kernel/numa.c b/arch/loongarch/kernel/numa.c index c96c53623715..7dedc134ddfe 100644 --- a/arch/loongarch/kernel/numa.c +++ b/arch/loongarch/kernel/numa.c @@ -216,6 +216,8 @@ static int __init fake_numa_init(void) phys_addr_t start = memblock_start_of_DRAM(); phys_addr_t end = memblock_end_of_DRAM() - 1; + set_cpuid_to_node(0, 0); + early_numa_add_cpu(0, 0); pr_info("Faking a node at [mem %pap-%pap]\n", &start, &end); return numa_add_memblk(0, start, end + 1); @@ -244,8 +246,10 @@ int __init init_numa_memory(void) return -EINVAL; for_each_node_mask(node, node_possible_map) { - node_mem_init(node); - node_set_online(node); + if (!cpumask_empty(&cpus_on_node[node])) { + node_mem_init(node); + node_set_online(node); + } } max_pfn = PFN_DOWN(memblock_end_of_DRAM()); max_low_pfn = min(PFN_DOWN(HIGHMEM_START), max_pfn); From 244eb26bbd152896f976db6415550993cd60b46d Mon Sep 17 00:00:00 2001 From: Huacai Chen Date: Tue, 27 Oct 2026 22:51:23 +0800 Subject: [PATCH 08/24] LoongArch: Use pa_to_nid()/nid_to_addrbase() rather than hard-code NODE_ADDRSPACE_SHIFT is 44 now but may be changed in future, so use pa_to_nid()/nid_to_addrbase() rather than hard-code. Cc: stable@vger.kernel.org Signed-off-by: Huacai Chen --- arch/loongarch/include/asm/numa.h | 2 +- arch/loongarch/kernel/irq.c | 3 ++- arch/loongarch/pci/pci.c | 3 ++- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/arch/loongarch/include/asm/numa.h b/arch/loongarch/include/asm/numa.h index bbf9f70bd25f..c9654ff900a3 100644 --- a/arch/loongarch/include/asm/numa.h +++ b/arch/loongarch/include/asm/numa.h @@ -14,7 +14,7 @@ #define NODE_ADDRSPACE_SHIFT 44 #define pa_to_nid(addr) (((addr) & 0xf00000000000) >> NODE_ADDRSPACE_SHIFT) -#define nid_to_addrbase(nid) (_ULCAST_(nid) << NODE_ADDRSPACE_SHIFT) +#define nid_to_addrbase(nid) (_U64CAST_(nid) << NODE_ADDRSPACE_SHIFT) #ifdef CONFIG_NUMA diff --git a/arch/loongarch/kernel/irq.c b/arch/loongarch/kernel/irq.c index 7bf68a7a5f4b..29e0f2baaa8c 100644 --- a/arch/loongarch/kernel/irq.c +++ b/arch/loongarch/kernel/irq.c @@ -20,6 +20,7 @@ #include #include +#include #include DEFINE_PER_CPU(unsigned long, irq_stack); @@ -69,7 +70,7 @@ static int __init early_pci_mcfg_parse(struct acpi_table_header *header) for (i = 0; i < n; i++, mptr++) { msi_group[i].pci_segment = mptr->pci_segment; - pch_group[i].node = msi_group[i].node = (mptr->address >> 44) & 0xf; + pch_group[i].node = msi_group[i].node = pa_to_nid(mptr->address); } return 0; diff --git a/arch/loongarch/pci/pci.c b/arch/loongarch/pci/pci.c index f33c7ea1443d..0b99b32110a5 100644 --- a/arch/loongarch/pci/pci.c +++ b/arch/loongarch/pci/pci.c @@ -12,6 +12,7 @@ #include #include #include +#include #define PCI_DEVICE_ID_LOONGSON_HOST 0x7a00 #define PCI_DEVICE_ID_LOONGSON_DC1 0x7a06 @@ -43,7 +44,7 @@ int raw_pci_write(unsigned int domain, unsigned int bus, unsigned int devfn, phys_addr_t mcfg_addr_init(int node) { - return (((u64)node << 44) | MCFG_EXT_PCICFG_BASE); + return (nid_to_addrbase(node) | MCFG_EXT_PCICFG_BASE); } static int __init pcibios_init(void) From f4eeaa783e040443cc7fd8a926c4b148e95a3356 Mon Sep 17 00:00:00 2001 From: Rong Bao Date: Tue, 27 Oct 2026 22:51:23 +0800 Subject: [PATCH 09/24] LoongArch: Detect and expose LAMCAS feature existence Currently, neither /proc/cpuinfo nor hwcap exposes the existence of the LAMCAS extension, which is part of LA64v1.1. For example, on a Loongson 3B6000, /proc/cpuinfo is as follows: processor : 23 package : 0 core : 11 global_id : 31 CPU Family : Loongson-64bit Model Name : Loongson-3B6000 PRID : LA664 (0014d011) CPU Revision : 0x11 FPU Revision : 0x01 CPU MHz : 2200.00 BogoMIPS : 4400.00 TLB Entries : 2112 Address Sizes : 48 bits physical, 48 bits virtual ISA : loongarch32r loongarch32s loongarch64 Features : cpucfg lam lam_bh scq ual fpu lsx lasx crc32 complex crypto lspw lvz lbt_x86 lbt_arm lbt_mips Hardware Watchpoint : yes, iwatch count: 8, dwatch count: 4 ... while the CPUCFG 2 instruction reads "0x7e7ccfcf", whose bit28 is 1, indicating the actual presence of LAMCAS. This hinders user-space inspection and detection of LAMCAS. For example, in SpiderMonkey, we need to switch to larchintrin.h and then use the raw __cpucfg() to manually perform the bit extraction. Also, a curious user reading /proc/cpuinfo might be puzzled by the lack of display of LAMCAS. Expose the existence of LAMCAS via cpuinfo and hwcap. The definition point of the new LOONGARCH_CPU_LAMCAS flag is chosen so as to match the practices in commit 1dd3e8a8eeb4059fb34b ("LoongArch: Define instruction formats for AM{SWAP/ADD}.{B/H} and DBAR"). After this patch, /proc/cpuinfo looks like the following: processor : 23 package : 0 core : 11 global_id : 31 CPU Family : Loongson-64bit Model Name : Loongson-3B6000 PRID : LA664 (0014d011) CPU Revision : 0x11 FPU Revision : 0x01 CPU MHz : 2200.00 BogoMIPS : 4400.00 TLB Entries : 2112 Address Sizes : 48 bits physical, 48 bits virtual ISA : loongarch32r loongarch32s loongarch64 Features : cpucfg lam lam_bh lamcas scq ual fpu lsx lasx crc32 complex crypto lspw lvz lbt_x86 lbt_arm lbt_mips Hardware Watchpoint : yes, iwatch count: 8, dwatch count: 4 Note the addition of "lamcas" to the "Features" row. Signed-off-by: Rong Bao Signed-off-by: Huacai Chen --- arch/loongarch/include/asm/cpu-features.h | 1 + arch/loongarch/include/asm/cpu.h | 64 ++++++++++++----------- arch/loongarch/include/uapi/asm/hwcap.h | 1 + arch/loongarch/kernel/cpu-probe.c | 4 ++ arch/loongarch/kernel/proc.c | 2 + 5 files changed, 41 insertions(+), 31 deletions(-) diff --git a/arch/loongarch/include/asm/cpu-features.h b/arch/loongarch/include/asm/cpu-features.h index 62059c5551b9..19cb0ccd0fb6 100644 --- a/arch/loongarch/include/asm/cpu-features.h +++ b/arch/loongarch/include/asm/cpu-features.h @@ -36,6 +36,7 @@ #define cpu_has_cpucfg cpu_opt(LOONGARCH_CPU_CPUCFG) #define cpu_has_lam cpu_opt(LOONGARCH_CPU_LAM) #define cpu_has_lam_bh cpu_opt(LOONGARCH_CPU_LAM_BH) +#define cpu_has_lamcas cpu_opt(LOONGARCH_CPU_LAMCAS) #define cpu_has_scq cpu_opt(LOONGARCH_CPU_SCQ) #define cpu_has_ual cpu_opt(LOONGARCH_CPU_UAL) #define cpu_has_fpu cpu_opt(LOONGARCH_CPU_FPU) diff --git a/arch/loongarch/include/asm/cpu.h b/arch/loongarch/include/asm/cpu.h index 91b96938861e..334ca9d69ec4 100644 --- a/arch/loongarch/include/asm/cpu.h +++ b/arch/loongarch/include/asm/cpu.h @@ -96,41 +96,43 @@ static inline char *id_to_core_name(unsigned int id) #define CPU_FEATURE_CPUCFG 0 /* CPU has CPUCFG */ #define CPU_FEATURE_LAM 1 /* CPU has Atomic instructions */ #define CPU_FEATURE_LAM_BH 2 /* CPU has AM{SWAP/ADD}[_DB].{B/H} instructions */ -#define CPU_FEATURE_SCQ 3 /* CPU has SC.Q instruction */ -#define CPU_FEATURE_UAL 4 /* CPU supports unaligned access */ -#define CPU_FEATURE_FPU 5 /* CPU has FPU */ -#define CPU_FEATURE_LSX 6 /* CPU has LSX (128-bit SIMD) */ -#define CPU_FEATURE_LASX 7 /* CPU has LASX (256-bit SIMD) */ -#define CPU_FEATURE_CRC32 8 /* CPU has CRC32 instructions */ -#define CPU_FEATURE_COMPLEX 9 /* CPU has Complex instructions */ -#define CPU_FEATURE_CRYPTO 10 /* CPU has Crypto instructions */ -#define CPU_FEATURE_LVZ 11 /* CPU has Virtualization extension */ -#define CPU_FEATURE_LBT_X86 12 /* CPU has X86 Binary Translation */ -#define CPU_FEATURE_LBT_ARM 13 /* CPU has ARM Binary Translation */ -#define CPU_FEATURE_LBT_MIPS 14 /* CPU has MIPS Binary Translation */ -#define CPU_FEATURE_TLB 15 /* CPU has TLB */ -#define CPU_FEATURE_CSR 16 /* CPU has CSR */ -#define CPU_FEATURE_IOCSR 17 /* CPU has IOCSR */ -#define CPU_FEATURE_WATCH 18 /* CPU has watchpoint registers */ -#define CPU_FEATURE_VINT 19 /* CPU has vectored interrupts */ -#define CPU_FEATURE_CSRIPI 20 /* CPU has CSR-IPI */ -#define CPU_FEATURE_EXTIOI 21 /* CPU has EXT-IOI */ -#define CPU_FEATURE_PREFETCH 22 /* CPU has prefetch instructions */ -#define CPU_FEATURE_PMP 23 /* CPU has perfermance counter */ -#define CPU_FEATURE_SCALEFREQ 24 /* CPU supports cpufreq scaling */ -#define CPU_FEATURE_FLATMODE 25 /* CPU has flat mode */ -#define CPU_FEATURE_EIODECODE 26 /* CPU has EXTIOI interrupt pin decode mode */ -#define CPU_FEATURE_GUESTID 27 /* CPU has GuestID feature */ -#define CPU_FEATURE_HYPERVISOR 28 /* CPU has hypervisor (running in VM) */ -#define CPU_FEATURE_PTW 29 /* CPU has hardware page table walker */ -#define CPU_FEATURE_LSPW 30 /* CPU has LSPW (lddir/ldpte instructions) */ -#define CPU_FEATURE_MSGINT 31 /* CPU has MSG interrupt */ -#define CPU_FEATURE_AVECINT 32 /* CPU has AVEC interrupt */ -#define CPU_FEATURE_REDIRECTINT 33 /* CPU has interrupt remapping */ +#define CPU_FEATURE_LAMCAS 3 /* CPU has AMCAS[_DB].{B/H/W/D} instructions */ +#define CPU_FEATURE_SCQ 4 /* CPU has SC.Q instruction */ +#define CPU_FEATURE_UAL 5 /* CPU supports unaligned access */ +#define CPU_FEATURE_FPU 6 /* CPU has FPU */ +#define CPU_FEATURE_LSX 7 /* CPU has LSX (128-bit SIMD) */ +#define CPU_FEATURE_LASX 8 /* CPU has LASX (256-bit SIMD) */ +#define CPU_FEATURE_CRC32 9 /* CPU has CRC32 instructions */ +#define CPU_FEATURE_COMPLEX 10 /* CPU has Complex instructions */ +#define CPU_FEATURE_CRYPTO 11 /* CPU has Crypto instructions */ +#define CPU_FEATURE_LVZ 12 /* CPU has Virtualization extension */ +#define CPU_FEATURE_LBT_X86 13 /* CPU has X86 Binary Translation */ +#define CPU_FEATURE_LBT_ARM 14 /* CPU has ARM Binary Translation */ +#define CPU_FEATURE_LBT_MIPS 15 /* CPU has MIPS Binary Translation */ +#define CPU_FEATURE_TLB 16 /* CPU has TLB */ +#define CPU_FEATURE_CSR 17 /* CPU has CSR */ +#define CPU_FEATURE_IOCSR 18 /* CPU has IOCSR */ +#define CPU_FEATURE_WATCH 19 /* CPU has watchpoint registers */ +#define CPU_FEATURE_VINT 20 /* CPU has vectored interrupts */ +#define CPU_FEATURE_CSRIPI 21 /* CPU has CSR-IPI */ +#define CPU_FEATURE_EXTIOI 22 /* CPU has EXT-IOI */ +#define CPU_FEATURE_PREFETCH 23 /* CPU has prefetch instructions */ +#define CPU_FEATURE_PMP 24 /* CPU has perfermance counter */ +#define CPU_FEATURE_SCALEFREQ 25 /* CPU supports cpufreq scaling */ +#define CPU_FEATURE_FLATMODE 26 /* CPU has flat mode */ +#define CPU_FEATURE_EIODECODE 27 /* CPU has EXTIOI interrupt pin decode mode */ +#define CPU_FEATURE_GUESTID 28 /* CPU has GuestID feature */ +#define CPU_FEATURE_HYPERVISOR 29 /* CPU has hypervisor (running in VM) */ +#define CPU_FEATURE_PTW 30 /* CPU has hardware page table walker */ +#define CPU_FEATURE_LSPW 31 /* CPU has LSPW (lddir/ldpte instructions) */ +#define CPU_FEATURE_MSGINT 32 /* CPU has MSG interrupt */ +#define CPU_FEATURE_AVECINT 33 /* CPU has AVEC interrupt */ +#define CPU_FEATURE_REDIRECTINT 34 /* CPU has interrupt remapping */ #define LOONGARCH_CPU_CPUCFG BIT_ULL(CPU_FEATURE_CPUCFG) #define LOONGARCH_CPU_LAM BIT_ULL(CPU_FEATURE_LAM) #define LOONGARCH_CPU_LAM_BH BIT_ULL(CPU_FEATURE_LAM_BH) +#define LOONGARCH_CPU_LAMCAS BIT_ULL(CPU_FEATURE_LAMCAS) #define LOONGARCH_CPU_SCQ BIT_ULL(CPU_FEATURE_SCQ) #define LOONGARCH_CPU_UAL BIT_ULL(CPU_FEATURE_UAL) #define LOONGARCH_CPU_FPU BIT_ULL(CPU_FEATURE_FPU) diff --git a/arch/loongarch/include/uapi/asm/hwcap.h b/arch/loongarch/include/uapi/asm/hwcap.h index 90e96113ba51..d354787b937c 100644 --- a/arch/loongarch/include/uapi/asm/hwcap.h +++ b/arch/loongarch/include/uapi/asm/hwcap.h @@ -20,5 +20,6 @@ #define HWCAP_LOONGARCH_LSPW (1 << 14) #define HWCAP_LOONGARCH_SCQ (1 << 15) #define HWCAP_LOONGARCH_LAM_BH (1 << 16) +#define HWCAP_LOONGARCH_LAMCAS (1 << 17) #endif /* _UAPI_ASM_HWCAP_H */ diff --git a/arch/loongarch/kernel/cpu-probe.c b/arch/loongarch/kernel/cpu-probe.c index 629c976c33c3..7db83b8b1e52 100644 --- a/arch/loongarch/kernel/cpu-probe.c +++ b/arch/loongarch/kernel/cpu-probe.c @@ -182,6 +182,10 @@ static void cpu_probe_common(struct cpuinfo_loongarch *c) c->options |= LOONGARCH_CPU_LAM_BH; elf_hwcap |= HWCAP_LOONGARCH_LAM_BH; } + if (config & CPUCFG2_LAMCAS) { + c->options |= LOONGARCH_CPU_LAMCAS; + elf_hwcap |= HWCAP_LOONGARCH_LAMCAS; + } if (config & CPUCFG2_SCQ) { c->options |= LOONGARCH_CPU_SCQ; elf_hwcap |= HWCAP_LOONGARCH_SCQ; diff --git a/arch/loongarch/kernel/proc.c b/arch/loongarch/kernel/proc.c index d4ce5b585453..f86be83ad6f1 100644 --- a/arch/loongarch/kernel/proc.c +++ b/arch/loongarch/kernel/proc.c @@ -66,6 +66,8 @@ static int show_cpuinfo(struct seq_file *m, void *v) seq_puts(m, " lam"); if (cpu_has_lam_bh) seq_puts(m, " lam_bh"); + if (cpu_has_lamcas) + seq_puts(m, " lamcas"); if (cpu_has_scq) seq_puts(m, " scq"); if (cpu_has_ual) From 9af0ce286fac828f37747d78990b44f34b8b29b6 Mon Sep 17 00:00:00 2001 From: Tiezhu Yang Date: Tue, 27 Oct 2026 22:51:23 +0800 Subject: [PATCH 10/24] LoongArch: Detect and expose DIV32 feature existence The LoongArch Architecture Manual defines a hardware capability bit CPUCFG2_DIV32 (bit 26 of CPUCFG 0x2). When this bit is set to 1, it indicates that on 64-bit machines, the division and mod instructions (DIV.W[U] and MOD.W[U]) will ignore the upper 32 bits of the input registers and only compute based on the lower 32 bits. On certain CPU cores without this feature, these 32-bit instructions require both input operands to be properly sign-extended. Otherwise, the hardware execution results can be randomly meaningless values. Add the CPU_FEATURE_DIV32 definition, detect and export this hardware capability via elf_hwcap and cpu_has_div32 (in cpuinfo). This provides the infrastructure for subsystems like BPF JIT to safely choose either native 32-bit instructions or fallback instruction sequences based on the actual CPU hardware capabilities. Signed-off-by: Tiezhu Yang Signed-off-by: Huacai Chen --- arch/loongarch/include/asm/cpu-features.h | 1 + arch/loongarch/include/asm/cpu.h | 48 ++++++++++++----------- arch/loongarch/include/uapi/asm/hwcap.h | 1 + arch/loongarch/kernel/cpu-probe.c | 4 ++ arch/loongarch/kernel/proc.c | 2 + 5 files changed, 33 insertions(+), 23 deletions(-) diff --git a/arch/loongarch/include/asm/cpu-features.h b/arch/loongarch/include/asm/cpu-features.h index 19cb0ccd0fb6..f587cdd1acab 100644 --- a/arch/loongarch/include/asm/cpu-features.h +++ b/arch/loongarch/include/asm/cpu-features.h @@ -45,6 +45,7 @@ #define cpu_has_crc32 cpu_opt(LOONGARCH_CPU_CRC32) #define cpu_has_complex cpu_opt(LOONGARCH_CPU_COMPLEX) #define cpu_has_crypto cpu_opt(LOONGARCH_CPU_CRYPTO) +#define cpu_has_div32 cpu_opt(LOONGARCH_CPU_DIV32) #define cpu_has_lvz cpu_opt(LOONGARCH_CPU_LVZ) #define cpu_has_lbt_x86 cpu_opt(LOONGARCH_CPU_LBT_X86) #define cpu_has_lbt_arm cpu_opt(LOONGARCH_CPU_LBT_ARM) diff --git a/arch/loongarch/include/asm/cpu.h b/arch/loongarch/include/asm/cpu.h index 334ca9d69ec4..8d389bb49421 100644 --- a/arch/loongarch/include/asm/cpu.h +++ b/arch/loongarch/include/asm/cpu.h @@ -105,29 +105,30 @@ static inline char *id_to_core_name(unsigned int id) #define CPU_FEATURE_CRC32 9 /* CPU has CRC32 instructions */ #define CPU_FEATURE_COMPLEX 10 /* CPU has Complex instructions */ #define CPU_FEATURE_CRYPTO 11 /* CPU has Crypto instructions */ -#define CPU_FEATURE_LVZ 12 /* CPU has Virtualization extension */ -#define CPU_FEATURE_LBT_X86 13 /* CPU has X86 Binary Translation */ -#define CPU_FEATURE_LBT_ARM 14 /* CPU has ARM Binary Translation */ -#define CPU_FEATURE_LBT_MIPS 15 /* CPU has MIPS Binary Translation */ -#define CPU_FEATURE_TLB 16 /* CPU has TLB */ -#define CPU_FEATURE_CSR 17 /* CPU has CSR */ -#define CPU_FEATURE_IOCSR 18 /* CPU has IOCSR */ -#define CPU_FEATURE_WATCH 19 /* CPU has watchpoint registers */ -#define CPU_FEATURE_VINT 20 /* CPU has vectored interrupts */ -#define CPU_FEATURE_CSRIPI 21 /* CPU has CSR-IPI */ -#define CPU_FEATURE_EXTIOI 22 /* CPU has EXT-IOI */ -#define CPU_FEATURE_PREFETCH 23 /* CPU has prefetch instructions */ -#define CPU_FEATURE_PMP 24 /* CPU has perfermance counter */ -#define CPU_FEATURE_SCALEFREQ 25 /* CPU supports cpufreq scaling */ -#define CPU_FEATURE_FLATMODE 26 /* CPU has flat mode */ -#define CPU_FEATURE_EIODECODE 27 /* CPU has EXTIOI interrupt pin decode mode */ -#define CPU_FEATURE_GUESTID 28 /* CPU has GuestID feature */ -#define CPU_FEATURE_HYPERVISOR 29 /* CPU has hypervisor (running in VM) */ -#define CPU_FEATURE_PTW 30 /* CPU has hardware page table walker */ -#define CPU_FEATURE_LSPW 31 /* CPU has LSPW (lddir/ldpte instructions) */ -#define CPU_FEATURE_MSGINT 32 /* CPU has MSG interrupt */ -#define CPU_FEATURE_AVECINT 33 /* CPU has AVEC interrupt */ -#define CPU_FEATURE_REDIRECTINT 34 /* CPU has interrupt remapping */ +#define CPU_FEATURE_DIV32 12 /* CPU has DIV32 feature */ +#define CPU_FEATURE_LVZ 13 /* CPU has Virtualization extension */ +#define CPU_FEATURE_LBT_X86 14 /* CPU has X86 Binary Translation */ +#define CPU_FEATURE_LBT_ARM 15 /* CPU has ARM Binary Translation */ +#define CPU_FEATURE_LBT_MIPS 16 /* CPU has MIPS Binary Translation */ +#define CPU_FEATURE_TLB 17 /* CPU has TLB */ +#define CPU_FEATURE_CSR 18 /* CPU has CSR */ +#define CPU_FEATURE_IOCSR 19 /* CPU has IOCSR */ +#define CPU_FEATURE_WATCH 20 /* CPU has watchpoint registers */ +#define CPU_FEATURE_VINT 21 /* CPU has vectored interrupts */ +#define CPU_FEATURE_CSRIPI 22 /* CPU has CSR-IPI */ +#define CPU_FEATURE_EXTIOI 23 /* CPU has EXT-IOI */ +#define CPU_FEATURE_PREFETCH 24 /* CPU has prefetch instructions */ +#define CPU_FEATURE_PMP 25 /* CPU has perfermance counter */ +#define CPU_FEATURE_SCALEFREQ 26 /* CPU supports cpufreq scaling */ +#define CPU_FEATURE_FLATMODE 27 /* CPU has flat mode */ +#define CPU_FEATURE_EIODECODE 28 /* CPU has EXTIOI interrupt pin decode mode */ +#define CPU_FEATURE_GUESTID 29 /* CPU has GuestID feature */ +#define CPU_FEATURE_HYPERVISOR 30 /* CPU has hypervisor (running in VM) */ +#define CPU_FEATURE_PTW 31 /* CPU has hardware page table walker */ +#define CPU_FEATURE_LSPW 32 /* CPU has LSPW (lddir/ldpte instructions) */ +#define CPU_FEATURE_MSGINT 33 /* CPU has MSG interrupt */ +#define CPU_FEATURE_AVECINT 34 /* CPU has AVEC interrupt */ +#define CPU_FEATURE_REDIRECTINT 35 /* CPU has interrupt remapping */ #define LOONGARCH_CPU_CPUCFG BIT_ULL(CPU_FEATURE_CPUCFG) #define LOONGARCH_CPU_LAM BIT_ULL(CPU_FEATURE_LAM) @@ -141,6 +142,7 @@ static inline char *id_to_core_name(unsigned int id) #define LOONGARCH_CPU_CRC32 BIT_ULL(CPU_FEATURE_CRC32) #define LOONGARCH_CPU_COMPLEX BIT_ULL(CPU_FEATURE_COMPLEX) #define LOONGARCH_CPU_CRYPTO BIT_ULL(CPU_FEATURE_CRYPTO) +#define LOONGARCH_CPU_DIV32 BIT_ULL(CPU_FEATURE_DIV32) #define LOONGARCH_CPU_LVZ BIT_ULL(CPU_FEATURE_LVZ) #define LOONGARCH_CPU_LBT_X86 BIT_ULL(CPU_FEATURE_LBT_X86) #define LOONGARCH_CPU_LBT_ARM BIT_ULL(CPU_FEATURE_LBT_ARM) diff --git a/arch/loongarch/include/uapi/asm/hwcap.h b/arch/loongarch/include/uapi/asm/hwcap.h index d354787b937c..cc609e3486bc 100644 --- a/arch/loongarch/include/uapi/asm/hwcap.h +++ b/arch/loongarch/include/uapi/asm/hwcap.h @@ -21,5 +21,6 @@ #define HWCAP_LOONGARCH_SCQ (1 << 15) #define HWCAP_LOONGARCH_LAM_BH (1 << 16) #define HWCAP_LOONGARCH_LAMCAS (1 << 17) +#define HWCAP_LOONGARCH_DIV32 (1 << 18) #endif /* _UAPI_ASM_HWCAP_H */ diff --git a/arch/loongarch/kernel/cpu-probe.c b/arch/loongarch/kernel/cpu-probe.c index 7db83b8b1e52..ce006fa2f607 100644 --- a/arch/loongarch/kernel/cpu-probe.c +++ b/arch/loongarch/kernel/cpu-probe.c @@ -214,6 +214,10 @@ static void cpu_probe_common(struct cpuinfo_loongarch *c) c->options |= LOONGARCH_CPU_CRYPTO; elf_hwcap |= HWCAP_LOONGARCH_CRYPTO; } + if (config & CPUCFG2_DIV32) { + c->options |= LOONGARCH_CPU_DIV32; + elf_hwcap |= HWCAP_LOONGARCH_DIV32; + } if (config & CPUCFG2_PTW) { c->options |= LOONGARCH_CPU_PTW; elf_hwcap |= HWCAP_LOONGARCH_PTW; diff --git a/arch/loongarch/kernel/proc.c b/arch/loongarch/kernel/proc.c index f86be83ad6f1..37e9b144ec09 100644 --- a/arch/loongarch/kernel/proc.c +++ b/arch/loongarch/kernel/proc.c @@ -84,6 +84,8 @@ static int show_cpuinfo(struct seq_file *m, void *v) seq_puts(m, " complex"); if (cpu_has_crypto) seq_puts(m, " crypto"); + if (cpu_has_div32) + seq_puts(m, " div32"); if (cpu_has_ptw) seq_puts(m, " ptw"); if (cpu_has_lspw) From 85cebdfee765e92e748e71b9b91889eef5988282 Mon Sep 17 00:00:00 2001 From: Tiezhu Yang Date: Tue, 27 Oct 2026 22:51:23 +0800 Subject: [PATCH 11/24] LoongArch: Remove arch-specific __smp_mb__{before,after}_atomic() When running BPF selftest "test_progs -t timer_lockup" on a LoongArch machine, the kernel intermittently experiences soft lockups and panics in hrtimer_cancel(). The root cause is that the bpf_timer_cancel() implementation relies on smp_mb__after_atomic() to provide a full data barrier after a relaxed atomic_inc() to ensure the proper store-load ordering before performing lockless dependency checks on weakly-ordered architectures, such as on LoongArch, but the current definition of smp_mb__after_atomic() is just a plain compiler barrier. This allows a subsequent read to bypass the prior write due to store- load reordering, enabling concurrent CPUs to simultaneously bypass the software deadlock detection, enter hrtimer_cancel(), and then trigger a severe ABBA deadlock in the hrtimer core. Moreover, there are hundreds of smp_mb__after_atomic() call sites across the core kernel (kernel/, drivers/, mm/, net/, fs/) that currently lack necessary full data barriers on LoongArch, which have potential risks. To comply with the LKMM specification, fix this by removing the arch- specific definition of __smp_mb__after_atomic(), so that this macro can automatically fall back to the generic definition in asm-generic that maps to __smp_mb() and emits the required hardware DBAR instruction. While at it, do the same thing for __smp_mb__before_atomic(). With this patch the BPF timer_lockup selftest was stressed for millions consecutive loops on a physical LoongArch machine, without encountering any further lockups. Cc: stable@vger.kernel.org # 6.7+ Fixes: affef66b65889a0e ("LoongArch: Relax memory ordering for atomic operations") Closes: https://lore.kernel.org/loongarch/CAK3+h2xOSEZUHhou7N2cRL-aGrZCNSm45g+P7thObMe+fpgYCA@mail.gmail.com/ Reported-by: Vincent Li Reviewed-by: Xi Ruoyao Signed-off-by: Tiezhu Yang Signed-off-by: Huacai Chen --- arch/loongarch/include/asm/barrier.h | 3 --- 1 file changed, 3 deletions(-) diff --git a/arch/loongarch/include/asm/barrier.h b/arch/loongarch/include/asm/barrier.h index 4b663f197706..84c6d739119d 100644 --- a/arch/loongarch/include/asm/barrier.h +++ b/arch/loongarch/include/asm/barrier.h @@ -57,9 +57,6 @@ #define __WEAK_LLSC_MB " \n" #endif -#define __smp_mb__before_atomic() barrier() -#define __smp_mb__after_atomic() barrier() - /** * array_index_mask_nospec() - generate a ~0 mask when index < size, 0 otherwise * @index: array element index From 23534c6efeb718e8a4d9107fa7d6707b5b1f5859 Mon Sep 17 00:00:00 2001 From: Tiezhu Yang Date: Tue, 27 Oct 2026 22:51:24 +0800 Subject: [PATCH 12/24] LoongArch: Remove redundant call in update_bp_registers() In update_bp_registers(), there is a duplicate call to write_wb_reg() when enabling an execute breakpoint: write_wb_reg(CSR_CFG_CTRL, i, 0, CTRL_PLV_ENABLE); write_wb_reg(CSR_CFG_CTRL, i, 0, CTRL_PLV_ENABLE); The two lines are completely identical. Remove the redundant call to clean up the code and eliminate unnecessary register writes. Cc: stable@vger.kernel.org Fixes: edffa33c7bb5 ("LoongArch: Add hardware breakpoints/watchpoints support") Signed-off-by: Tiezhu Yang Signed-off-by: Huacai Chen --- arch/loongarch/kernel/hw_breakpoint.c | 1 - 1 file changed, 1 deletion(-) diff --git a/arch/loongarch/kernel/hw_breakpoint.c b/arch/loongarch/kernel/hw_breakpoint.c index c35f9bf38033..deeb2f979570 100644 --- a/arch/loongarch/kernel/hw_breakpoint.c +++ b/arch/loongarch/kernel/hw_breakpoint.c @@ -453,7 +453,6 @@ static void update_bp_registers(struct pt_regs *regs, int enable, int type) if (enable) { if ((info->ctrl.type == LOONGARCH_BREAKPOINT_EXECUTE) && (type == 0)) { write_wb_reg(CSR_CFG_CTRL, i, 0, CTRL_PLV_ENABLE); - write_wb_reg(CSR_CFG_CTRL, i, 0, CTRL_PLV_ENABLE); } else { ctrl = read_wb_reg(CSR_CFG_CTRL, i, 1); if (info->ctrl.type == LOONGARCH_BREAKPOINT_LOAD) From 30c52672bf456decf675c11f3c65acd73a6bcca8 Mon Sep 17 00:00:00 2001 From: Tiezhu Yang Date: Tue, 27 Oct 2026 22:51:34 +0800 Subject: [PATCH 13/24] LoongArch: Fix bitmask corruption in update_bp_registers() In update_bp_registers(), when disabling a LOAD or STORE watchpoint, the code attempts to clear the bit of LoadEn or StoreEn by using the standard bit-clearing pattern. However, due to the omission of parentheses, the bitwise NOT operator '~' takes higher precedence than the left shift operator '<<'. Then: (1) ~0x1 << MWPnCFG3_LoadEn evaluates to "(~0x1) << 8 = 0xFFFFFE00", (2) ~0x1 << MWPnCFG3_StoreEn evaluates to "(~0x1) << 9 = 0xFFFFFC00", that incorrectly clears all configuration fields in the lower 8 bits. These fields in the lower 8 bits contain critical configurations such as DSOnly (bit 0), PLV0-PLV3 privilege levels (bits 1-4), and LCL (bit 7). Writing back these corrupted values severely breaks configuration isolation and ruins the hardware watchpoint states. Add parentheses to ensure the correct evaluation order, so that only the targeted LoadEn/StoreEn bit is cleared while preserving the other crucial configuration bits in the lower 8 bits. Cc: stable@vger.kernel.org Fixes: edffa33c7bb5 ("LoongArch: Add hardware breakpoints/watchpoints support") Signed-off-by: Tiezhu Yang Signed-off-by: Huacai Chen --- arch/loongarch/kernel/hw_breakpoint.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/arch/loongarch/kernel/hw_breakpoint.c b/arch/loongarch/kernel/hw_breakpoint.c index deeb2f979570..bd891b3808a6 100644 --- a/arch/loongarch/kernel/hw_breakpoint.c +++ b/arch/loongarch/kernel/hw_breakpoint.c @@ -468,9 +468,9 @@ static void update_bp_registers(struct pt_regs *regs, int enable, int type) } else { ctrl = read_wb_reg(CSR_CFG_CTRL, i, 1); if (info->ctrl.type == LOONGARCH_BREAKPOINT_LOAD) - ctrl &= ~0x1 << MWPnCFG3_LoadEn; + ctrl &= ~(0x1 << MWPnCFG3_LoadEn); if (info->ctrl.type == LOONGARCH_BREAKPOINT_STORE) - ctrl &= ~0x1 << MWPnCFG3_StoreEn; + ctrl &= ~(0x1 << MWPnCFG3_StoreEn); write_wb_reg(CSR_CFG_CTRL, i, 1, ctrl); } regs->csr_prmd &= ~CSR_PRMD_PWE; From f322ebfdfda2bce91fb1d3fb0072c735051fed85 Mon Sep 17 00:00:00 2001 From: Tiezhu Yang Date: Tue, 27 Oct 2026 22:51:35 +0800 Subject: [PATCH 14/24] LoongArch: Fix architectural naming typo for CSR_FWPS_SKIP According to the LoongArch Reference Manual, the single-step 'Skip' bit resides in the instruction breakpoint status register (CSR.FWPS), rather than the configuration register (CSR.FWPC). Furthermore, the kernel code comments also explicitly document it as "CSR.FWPS.Skip", yet the macro was erroneously defined as CSR_FWPC_SKIP and used as such in traps.c and hw_breakpoint.c. This mismatch creates architectural naming confusion. Fix this by renaming the macro from CSR_FWPC_SKIP to CSR_FWPS_SKIP to precisely match the manual and comments. Also, update all call sites to align with the rectified definition. Cc: stable@vger.kernel.org Fixes: 424421a7f34c ("LoongArch: ptrace: Add hardware single step support") Signed-off-by: Tiezhu Yang Signed-off-by: Huacai Chen --- arch/loongarch/include/asm/loongarch.h | 4 ++-- arch/loongarch/kernel/hw_breakpoint.c | 2 +- arch/loongarch/kernel/traps.c | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/arch/loongarch/include/asm/loongarch.h b/arch/loongarch/include/asm/loongarch.h index 2a6bc99177d8..32bbff337c5c 100644 --- a/arch/loongarch/include/asm/loongarch.h +++ b/arch/loongarch/include/asm/loongarch.h @@ -1130,8 +1130,8 @@ #define LOONGARCH_CSR_DERA 0x501 /* debug era */ #define LOONGARCH_CSR_DESAVE 0x502 /* debug save */ -#define CSR_FWPC_SKIP_SHIFT 16 -#define CSR_FWPC_SKIP (_ULCAST_(1) << CSR_FWPC_SKIP_SHIFT) +#define CSR_FWPS_SKIP_SHIFT 16 +#define CSR_FWPS_SKIP (_ULCAST_(1) << CSR_FWPS_SKIP_SHIFT) /* * CSR_ECFG IM diff --git a/arch/loongarch/kernel/hw_breakpoint.c b/arch/loongarch/kernel/hw_breakpoint.c index bd891b3808a6..7f69cf361a5e 100644 --- a/arch/loongarch/kernel/hw_breakpoint.c +++ b/arch/loongarch/kernel/hw_breakpoint.c @@ -550,7 +550,7 @@ void hw_breakpoint_thread_switch(struct task_struct *next) addr = read_wb_reg(CSR_CFG_ADDR, 0, 0); mask = read_wb_reg(CSR_CFG_MASK, 0, 0); if (!((regs->csr_era ^ addr) & ~mask)) - csr_write32(CSR_FWPC_SKIP, LOONGARCH_CSR_FWPS); + csr_write32(CSR_FWPS_SKIP, LOONGARCH_CSR_FWPS); regs->csr_prmd |= CSR_PRMD_PWE; } else { /* Update breakpoints */ diff --git a/arch/loongarch/kernel/traps.c b/arch/loongarch/kernel/traps.c index 776523747ea3..c4d7e55fb3ea 100644 --- a/arch/loongarch/kernel/traps.c +++ b/arch/loongarch/kernel/traps.c @@ -829,7 +829,7 @@ asmlinkage void noinstr do_watch(struct pt_regs *regs) * instruction. So don't clear llbit and reset CSR.FWPS.Skip until * the llsc execution is completed. */ - csr_write32(CSR_FWPC_SKIP, LOONGARCH_CSR_FWPS); + csr_write32(CSR_FWPS_SKIP, LOONGARCH_CSR_FWPS); csr_write32(CSR_LLBCTL_KLO, LOONGARCH_CSR_LLBCTL); goto out; } @@ -846,7 +846,7 @@ asmlinkage void noinstr do_watch(struct pt_regs *regs) * current pc, If yes, then we should not set the CSR.FWPS.SKIP * bit to break the original instruction stream. */ - csr_write32(CSR_FWPC_SKIP, LOONGARCH_CSR_FWPS); + csr_write32(CSR_FWPS_SKIP, LOONGARCH_CSR_FWPS); goto out; } } From f087903018e35d38b8932e18b010ee2e8699d1a1 Mon Sep 17 00:00:00 2001 From: Tiezhu Yang Date: Tue, 27 Oct 2026 22:51:35 +0800 Subject: [PATCH 15/24] LoongArch: Only send SIGTRAP signal if necessary in do_watch() In do_watch(), the kernel unconditionally forces a SIGTRAP signal at the end of the handler via force_sig(SIGTRAP). This is essential for ptrace operations, but it severely disrupts standard perf_event usage. Under normal perf usage, it is not necessary to force a SIGTRAP signal on every hit. Forcing it unnecessarily aborts the target process if no user-space signal handler is registered, which violates performance monitoring behaviors and injects context switch overheads. However, a conditional check using only 'current->ptrace' would block legitimate user-requested signals when a perf_event is configured with 'attr.sigtrap = 1' for asynchronous user-space tracking. Fix this by refactoring breakpoint_handler() and watchpoint_handler() to return a boolean status indicating whether any triggered breakpoint explicitly requires a signal notification. Update do_watch() to enforce the SIGTRAP signal only when the process is actively being debugged via ptrace, or when the underlying perf_event infrastructure specifically demands it via 'attr.sigtrap'. Cc: stable@vger.kernel.org Fixes: 424421a7f34c ("LoongArch: ptrace: Add hardware single step support") Signed-off-by: Tiezhu Yang Signed-off-by: Huacai Chen --- arch/loongarch/include/asm/hw_breakpoint.h | 4 ++-- arch/loongarch/kernel/hw_breakpoint.c | 18 ++++++++++++++++-- arch/loongarch/kernel/traps.c | 9 ++++++--- 3 files changed, 24 insertions(+), 7 deletions(-) diff --git a/arch/loongarch/include/asm/hw_breakpoint.h b/arch/loongarch/include/asm/hw_breakpoint.h index 5faa97a87a9e..d202052df8a1 100644 --- a/arch/loongarch/include/asm/hw_breakpoint.h +++ b/arch/loongarch/include/asm/hw_breakpoint.h @@ -116,8 +116,8 @@ extern void arch_uninstall_hw_breakpoint(struct perf_event *bp); extern int hw_breakpoint_slots(int type); extern void hw_breakpoint_pmu_read(struct perf_event *bp); -void breakpoint_handler(struct pt_regs *regs); -void watchpoint_handler(struct pt_regs *regs); +bool breakpoint_handler(struct pt_regs *regs); +bool watchpoint_handler(struct pt_regs *regs); #ifdef CONFIG_HAVE_HW_BREAKPOINT extern void ptrace_hw_copy_thread(struct task_struct *task); diff --git a/arch/loongarch/kernel/hw_breakpoint.c b/arch/loongarch/kernel/hw_breakpoint.c index 7f69cf361a5e..d009ed780cf0 100644 --- a/arch/loongarch/kernel/hw_breakpoint.c +++ b/arch/loongarch/kernel/hw_breakpoint.c @@ -482,9 +482,10 @@ NOKPROBE_SYMBOL(update_bp_registers); /* * Debug exception handlers. */ -void breakpoint_handler(struct pt_regs *regs) +bool breakpoint_handler(struct pt_regs *regs) { int i; + bool need_sigtrap = false; struct perf_event *bp, **slots; slots = this_cpu_ptr(bp_on_reg); @@ -494,17 +495,24 @@ void breakpoint_handler(struct pt_regs *regs) bp = slots[i]; if (bp == NULL) continue; + perf_bp_event(bp, regs); + if (bp->attr.sigtrap) + need_sigtrap = true; + csr_write32(0x1 << i, LOONGARCH_CSR_FWPS); update_bp_registers(regs, 0, 0); } } + + return need_sigtrap; } NOKPROBE_SYMBOL(breakpoint_handler); -void watchpoint_handler(struct pt_regs *regs) +bool watchpoint_handler(struct pt_regs *regs) { int i; + bool need_sigtrap = false; struct perf_event *wp, **slots; slots = this_cpu_ptr(wp_on_reg); @@ -514,11 +522,17 @@ void watchpoint_handler(struct pt_regs *regs) wp = slots[i]; if (wp == NULL) continue; + perf_bp_event(wp, regs); + if (wp->attr.sigtrap) + need_sigtrap = true; + csr_write32(0x1 << i, LOONGARCH_CSR_MWPS); update_bp_registers(regs, 0, 1); } } + + return need_sigtrap; } NOKPROBE_SYMBOL(watchpoint_handler); diff --git a/arch/loongarch/kernel/traps.c b/arch/loongarch/kernel/traps.c index c4d7e55fb3ea..20ce2c8d77a1 100644 --- a/arch/loongarch/kernel/traps.c +++ b/arch/loongarch/kernel/traps.c @@ -815,6 +815,8 @@ asmlinkage void noinstr do_watch(struct pt_regs *regs) #ifndef CONFIG_HAVE_HW_BREAKPOINT pr_warn("Hardware watch point handler not implemented!\n"); #else + bool need_sigtrap = !!current->ptrace; + if (kgdb_breakpoint_handler(regs)) goto out; @@ -851,11 +853,12 @@ asmlinkage void noinstr do_watch(struct pt_regs *regs) } } } else { - breakpoint_handler(regs); - watchpoint_handler(regs); + need_sigtrap |= breakpoint_handler(regs); + need_sigtrap |= watchpoint_handler(regs); } - force_sig(SIGTRAP); + if (need_sigtrap) + force_sig(SIGTRAP); out: #endif irqentry_exit(regs, state); From f5c0f8427469e703465c719c4304ce91282a32eb Mon Sep 17 00:00:00 2001 From: Tiezhu Yang Date: Tue, 27 Oct 2026 22:51:35 +0800 Subject: [PATCH 16/24] LoongArch: Fix perf hardware breakpoint failure via installation In hw_breakpoint_control(), the logic to enable CSR_PRMD_PWE (global watchpoint enable) incorrectly relies on TIF_LOAD_WATCH. This thread flag is only set during ptrace operations, meaning that the standard hardware breakpoints created via perf_event_open() will never have this flag set. As a result, the CSR_PRMD_PWE switch is skipped, leaving the hardware breakpoint completely inactive in perf usage. Fix this by decoupling the CSR_PRMD_PWE from the ptrace specific flag TIF_LOAD_WATCH, which ensures that CSR_PRMD_PWE is enabled whenever a hardware breakpoint is installed. Here is a user-space reproducer to demonstrate the issue: (1) Test program (test_perf_install.c): #include #include #include #include #include #include static int var = 0; int main(void) { size_t count = 0; struct perf_event_attr attr = { .type = PERF_TYPE_BREAKPOINT, .size = sizeof(attr), .bp_type = HW_BREAKPOINT_W, .bp_addr = (unsigned long)&var, .bp_len = HW_BREAKPOINT_LEN_1, .exclude_kernel = 1, }; int fd = syscall(__NR_perf_event_open, &attr, 0, -1, -1, 0); ioctl(fd, PERF_EVENT_IOC_ENABLE, 0); asm volatile("st.b %1, %0" : "=m"(var) : "r"(11) : "memory"); ioctl(fd, PERF_EVENT_IOC_DISABLE, 0); read(fd, &count, sizeof(size_t)); printf("Watchpoint counts: expected = 1, actual = %zu\n", count); close(fd); return 0; } (2) Test steps: $ gcc test_perf_install.c -o test_perf_install $ ./test_perf_install (3) Test results: Without this patch: Watchpoint counts: expected = 1, actual = 0 With this patch: Watchpoint counts: expected = 1, actual = 1 Cc: stable@vger.kernel.org Fixes: 3892b11eac5a ("LoongArch: Check TIF_LOAD_WATCH to enable user space watchpoint") Signed-off-by: Tiezhu Yang Signed-off-by: Huacai Chen --- arch/loongarch/kernel/hw_breakpoint.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/arch/loongarch/kernel/hw_breakpoint.c b/arch/loongarch/kernel/hw_breakpoint.c index d009ed780cf0..1a05350159f7 100644 --- a/arch/loongarch/kernel/hw_breakpoint.c +++ b/arch/loongarch/kernel/hw_breakpoint.c @@ -233,7 +233,7 @@ static int hw_breakpoint_control(struct perf_event *bp, } enable = csr_read64(LOONGARCH_CSR_CRMD); csr_write64(CSR_CRMD_WE | enable, LOONGARCH_CSR_CRMD); - if (bp->hw.target && test_tsk_thread_flag(bp->hw.target, TIF_LOAD_WATCH)) + if (bp->hw.target) regs->csr_prmd |= CSR_PRMD_PWE; break; case HW_BREAKPOINT_UNINSTALL: From 5e0b00fd000a53a5428fed6c96be74019bd0ad17 Mon Sep 17 00:00:00 2001 From: Tiezhu Yang Date: Tue, 27 Oct 2026 22:51:35 +0800 Subject: [PATCH 17/24] LoongArch: Fix one-shot limitation for perf hardware breakpoints In breakpoint_handler() and watchpoint_handler(), the code disables the hardware slot by executing update_bp_registers(regs, 0, ...). This design forces the active hardware breakpoint configuration to be wiped out upon its first hit, turning standard perf hardware breakpoints into "one-shot" events. Furthermore, while the ptrace single-step path in do_watch() executes a hardware single-step skip mechanism to advance the PC, the standard perf path lacks any mechanism to bypass the original triggering instruction. To maintain the long-term persistence of hardware breakpoints for perf usage, eliminate the disruptive calls to update_bp_registers() within the handler loops to keep the breakpoint configuration enabled. Concurrently, execute a single, unified register write outside the loop to atomize the state transition and explicitly enforce the hardware skip mechanism once per exception return, ensuring the processor safely steps forward without lockups. Meanwhile, in order to prevent status contamination during single-core context switches or cross-core thread migrations, the hardware register skip state of the previous thread is saved first. Then, the skip state is restored and its software skip flag is also cleared only if the next thread has the skip flag; otherwise, 0 is written to clear the FWPS and MWPS registers, which explicitly clears the skip bit according to the architectural specification. Additionally, explicitly clear hbp_break_skip and hbp_watch_skip inside ptrace_hw_copy_thread() which is called by copy_thread() during the task creation. This guarantees that a newly cloned child task starts with a clean slate. Here is a user-space reproducer to demonstrate the issue. (1) Test program (test_perf_continuous.c): #include #include #include #include #include #include static int var = 0; int main(void) { size_t count = 0; struct perf_event_attr attr = { .type = PERF_TYPE_BREAKPOINT, .size = sizeof(attr), .bp_type = HW_BREAKPOINT_W, .bp_addr = (unsigned long)&var, .bp_len = HW_BREAKPOINT_LEN_1, .exclude_kernel = 1, }; int fd = syscall(__NR_perf_event_open, &attr, 0, -1, -1, 0); ioctl(fd, PERF_EVENT_IOC_ENABLE, 0); asm volatile("st.b %1, %0" : "=m"(var) : "r"(11) : "memory"); asm volatile("st.b %1, %0" : "=m"(var) : "r"(22) : "memory"); asm volatile("st.b %1, %0" : "=m"(var) : "r"(33) : "memory"); ioctl(fd, PERF_EVENT_IOC_DISABLE, 0); read(fd, &count, sizeof(size_t)); printf("Watchpoint counts: expected = 3, actual = %zu\n", count); close(fd); return 0; } (2) Test steps: $ gcc test_perf_continuous.c -o test_perf_continuous $ ./test_perf_continuous (3) Test results: Without this patch: Watchpoint counts: expected = 3, actual = 1 With this patch: Watchpoint counts: expected = 3, actual = 3 Cc: stable@vger.kernel.org Fixes: 3eb2a8b23598 ("LoongArch: Fix multiple hardware watchpoint issues") Signed-off-by: Tiezhu Yang Signed-off-by: Huacai Chen --- arch/loongarch/include/asm/hw_breakpoint.h | 4 +-- arch/loongarch/include/asm/loongarch.h | 3 ++ arch/loongarch/include/asm/processor.h | 1 + arch/loongarch/include/asm/switch_to.h | 2 +- arch/loongarch/kernel/hw_breakpoint.c | 39 +++++++++++++++++++--- 5 files changed, 41 insertions(+), 8 deletions(-) diff --git a/arch/loongarch/include/asm/hw_breakpoint.h b/arch/loongarch/include/asm/hw_breakpoint.h index d202052df8a1..f4478936787d 100644 --- a/arch/loongarch/include/asm/hw_breakpoint.h +++ b/arch/loongarch/include/asm/hw_breakpoint.h @@ -121,12 +121,12 @@ bool watchpoint_handler(struct pt_regs *regs); #ifdef CONFIG_HAVE_HW_BREAKPOINT extern void ptrace_hw_copy_thread(struct task_struct *task); -extern void hw_breakpoint_thread_switch(struct task_struct *next); +extern void hw_breakpoint_thread_switch(struct task_struct *prev, struct task_struct *next); #else static inline void ptrace_hw_copy_thread(struct task_struct *task) { } -static inline void hw_breakpoint_thread_switch(struct task_struct *next) +static inline void hw_breakpoint_thread_switch(struct task_struct *prev, struct task_struct *next) { } #endif diff --git a/arch/loongarch/include/asm/loongarch.h b/arch/loongarch/include/asm/loongarch.h index 32bbff337c5c..28eff8b49d1b 100644 --- a/arch/loongarch/include/asm/loongarch.h +++ b/arch/loongarch/include/asm/loongarch.h @@ -1133,6 +1133,9 @@ #define CSR_FWPS_SKIP_SHIFT 16 #define CSR_FWPS_SKIP (_ULCAST_(1) << CSR_FWPS_SKIP_SHIFT) +#define CSR_MWPS_SKIP_SHIFT 16 +#define CSR_MWPS_SKIP (_ULCAST_(1) << CSR_MWPS_SKIP_SHIFT) + /* * CSR_ECFG IM */ diff --git a/arch/loongarch/include/asm/processor.h b/arch/loongarch/include/asm/processor.h index ce8b953f8c79..a884f964ec98 100644 --- a/arch/loongarch/include/asm/processor.h +++ b/arch/loongarch/include/asm/processor.h @@ -138,6 +138,7 @@ struct thread_struct { struct loongarch_lbt lbt; /* Also conditionally copied */ /* Hardware breakpoints pinned to this task. */ + bool hbp_break_skip, hbp_watch_skip; struct perf_event *hbp_break[LOONGARCH_MAX_BRP]; struct perf_event *hbp_watch[LOONGARCH_MAX_WRP]; }; diff --git a/arch/loongarch/include/asm/switch_to.h b/arch/loongarch/include/asm/switch_to.h index 27acbf913774..ab9e0292c7f3 100644 --- a/arch/loongarch/include/asm/switch_to.h +++ b/arch/loongarch/include/asm/switch_to.h @@ -34,7 +34,7 @@ extern asmlinkage struct task_struct *__switch_to(struct task_struct *prev, do { \ lose_fpu_inatomic(1, prev); \ lose_lbt_inatomic(1, prev); \ - hw_breakpoint_thread_switch(next); \ + hw_breakpoint_thread_switch(prev, next); \ set_current(next); \ (last) = __switch_to(prev, next, \ __builtin_return_address(0), __builtin_frame_address(0)); \ diff --git a/arch/loongarch/kernel/hw_breakpoint.c b/arch/loongarch/kernel/hw_breakpoint.c index 1a05350159f7..df149ff06dd5 100644 --- a/arch/loongarch/kernel/hw_breakpoint.c +++ b/arch/loongarch/kernel/hw_breakpoint.c @@ -156,6 +156,9 @@ static int hw_breakpoint_slot_setup(struct perf_event **slots, int max_slots, void ptrace_hw_copy_thread(struct task_struct *tsk) { + tsk->thread.hbp_break_skip = 0; + tsk->thread.hbp_watch_skip = 0; + memset(tsk->thread.hbp_break, 0, sizeof(tsk->thread.hbp_break)); memset(tsk->thread.hbp_watch, 0, sizeof(tsk->thread.hbp_watch)); } @@ -485,6 +488,7 @@ NOKPROBE_SYMBOL(update_bp_registers); bool breakpoint_handler(struct pt_regs *regs) { int i; + unsigned int clear_mask = 0; bool need_sigtrap = false; struct perf_event *bp, **slots; @@ -500,11 +504,13 @@ bool breakpoint_handler(struct pt_regs *regs) if (bp->attr.sigtrap) need_sigtrap = true; - csr_write32(0x1 << i, LOONGARCH_CSR_FWPS); - update_bp_registers(regs, 0, 0); + clear_mask |= (0x1 << i); } } + if (clear_mask) + csr_write32(clear_mask | CSR_FWPS_SKIP, LOONGARCH_CSR_FWPS); + return need_sigtrap; } NOKPROBE_SYMBOL(breakpoint_handler); @@ -512,6 +518,7 @@ NOKPROBE_SYMBOL(breakpoint_handler); bool watchpoint_handler(struct pt_regs *regs) { int i; + unsigned int clear_mask = 0; bool need_sigtrap = false; struct perf_event *wp, **slots; @@ -527,11 +534,13 @@ bool watchpoint_handler(struct pt_regs *regs) if (wp->attr.sigtrap) need_sigtrap = true; - csr_write32(0x1 << i, LOONGARCH_CSR_MWPS); - update_bp_registers(regs, 0, 1); + clear_mask |= (0x1 << i); } } + if (clear_mask) + csr_write32(clear_mask | CSR_MWPS_SKIP, LOONGARCH_CSR_MWPS); + return need_sigtrap; } NOKPROBE_SYMBOL(watchpoint_handler); @@ -555,7 +564,7 @@ static int __init arch_hw_breakpoint_init(void) } arch_initcall(arch_hw_breakpoint_init); -void hw_breakpoint_thread_switch(struct task_struct *next) +void hw_breakpoint_thread_switch(struct task_struct *prev, struct task_struct *next) { u64 addr, mask; struct pt_regs *regs = task_pt_regs(next); @@ -567,6 +576,26 @@ void hw_breakpoint_thread_switch(struct task_struct *next) csr_write32(CSR_FWPS_SKIP, LOONGARCH_CSR_FWPS); regs->csr_prmd |= CSR_PRMD_PWE; } else { + unsigned int fwps = csr_read32(LOONGARCH_CSR_FWPS); + unsigned int mwps = csr_read32(LOONGARCH_CSR_MWPS); + + prev->thread.hbp_break_skip = !!(fwps & CSR_FWPS_SKIP); + prev->thread.hbp_watch_skip = !!(mwps & CSR_MWPS_SKIP); + + if (!next->thread.hbp_break_skip) + csr_write32(0, LOONGARCH_CSR_FWPS); + else { + csr_write32(CSR_FWPS_SKIP, LOONGARCH_CSR_FWPS); + next->thread.hbp_break_skip = 0; + } + + if (!next->thread.hbp_watch_skip) + csr_write32(0, LOONGARCH_CSR_MWPS); + else { + csr_write32(CSR_MWPS_SKIP, LOONGARCH_CSR_MWPS); + next->thread.hbp_watch_skip = 0; + } + /* Update breakpoints */ update_bp_registers(regs, 1, 0); /* Update watchpoints */ From 2a5ec3a553f7b23359344d52332263d5fa88aac6 Mon Sep 17 00:00:00 2001 From: Yangyang Lian Date: Tue, 27 Oct 2026 22:51:35 +0800 Subject: [PATCH 18/24] LoongArch: Add arch_crash_save_vmcoreinfo() support Since commit 23c85094fe18 ("proc/kcore: add vmcoreinfo note to /proc/ kcore") the kernel has exported the vmcoreinfo PT_NOTE on /proc/kcore as well as /proc/vmcore. LoongArch does not provide arch_crash_save_vmcoreinfo(), so the arch- specific values are missing from the vmcoreinfo PT_NOTE. The generic exporter in kernel/vmcore_info.c already provides NUMBER(VMALLOC_START), but tools like crash cannot derive VMALLOC_END, the module and vmemmap ranges, VA_BITS, or the KASLR offset of a LoongArch dump. Since commit 18210a104bb9 ("LoongArch: Expand module virtual address space to 2GB") the vmalloc and module boundaries are no longer stable across kernel versions either, so vmcoreinfo is the only reliable source for them. Provide this function in a separate file that is built at the same time as its caller in kernel/vmcore_info.c, and add a LOONGARCH64 section to Documentation/admin-guide/kdump/vmcoreinfo.rst. Reported-by: Haoran Jiang Suggested-by: Haoran Jiang Tested-by: Tao Cui Co-developed-by: George Guo Signed-off-by: George Guo Signed-off-by: Yangyang Lian Signed-off-by: Huacai Chen --- .../admin-guide/kdump/vmcoreinfo.rst | 36 +++++++++++++++++++ arch/loongarch/kernel/Makefile | 1 + arch/loongarch/kernel/vmcore_info.c | 22 ++++++++++++ 3 files changed, 59 insertions(+) create mode 100644 arch/loongarch/kernel/vmcore_info.c diff --git a/Documentation/admin-guide/kdump/vmcoreinfo.rst b/Documentation/admin-guide/kdump/vmcoreinfo.rst index 7663c610fe90..e948095888fe 100644 --- a/Documentation/admin-guide/kdump/vmcoreinfo.rst +++ b/Documentation/admin-guide/kdump/vmcoreinfo.rst @@ -594,3 +594,39 @@ va_kernel_pa_offset Indicates the offset between the kernel virtual and physical mappings. Used to translate virtual to physical addresses. + +LOONGARCH64 +=========== + +VA_BITS +------- + +The maximum number of bits for virtual addresses. Used to compute the +virtual memory ranges. + +PHYS_OFFSET +----------- + +Indicates the physical address of the start of memory. Used to translate +virtual to physical addresses. + +PAGE_OFFSET +----------- + +Indicates the virtual kernel start address of the direct-mapped RAM region. +Used together with PHYS_OFFSET to translate between virtual and physical +addresses. + +KERNELOFFSET +------------ + +The kernel randomization offset. Used to locate the kernel text in virtual +memory. If KASLR is disabled, this value is zero. + +MODULES_VADDR|MODULES_END|VMALLOC_START|VMALLOC_END|VMEMMAP_START|VMEMMAP_END +----------------------------------------------------------------------------- + +Used to get the correct ranges: + MODULES_VADDR ~ MODULES_END-1 : Kernel module space. + VMALLOC_START ~ VMALLOC_END-1 : vmalloc() / ioremap() space. + VMEMMAP_START ~ VMEMMAP_END-1 : vmemmap region, used for struct page array. diff --git a/arch/loongarch/kernel/Makefile b/arch/loongarch/kernel/Makefile index 001924877772..6260c15adaba 100644 --- a/arch/loongarch/kernel/Makefile +++ b/arch/loongarch/kernel/Makefile @@ -64,6 +64,7 @@ obj-$(CONFIG_RELOCATABLE) += relocate.o obj-$(CONFIG_KEXEC_CORE) += machine_kexec.o relocate_kernel.o obj-$(CONFIG_KEXEC_FILE) += machine_kexec_file.o kexec_efi.o kexec_elf.o obj-$(CONFIG_CRASH_DUMP) += crash_dump.o +obj-$(CONFIG_VMCORE_INFO) += vmcore_info.o obj-$(CONFIG_UNWINDER_GUESS) += unwind_guess.o obj-$(CONFIG_UNWINDER_PROLOGUE) += unwind_prologue.o diff --git a/arch/loongarch/kernel/vmcore_info.c b/arch/loongarch/kernel/vmcore_info.c new file mode 100644 index 000000000000..e4efc35165b1 --- /dev/null +++ b/arch/loongarch/kernel/vmcore_info.c @@ -0,0 +1,22 @@ +// SPDX-License-Identifier: GPL-2.0 + +#include +#include +#include +#include + +void arch_crash_save_vmcoreinfo(void) +{ + VMCOREINFO_NUMBER(VA_BITS); + /* NUMBER(VMALLOC_START) is exported by the generic code */ + vmcoreinfo_append_str("NUMBER(VMALLOC_END)=0x%lx\n", VMALLOC_END); +#ifdef CONFIG_64BIT + vmcoreinfo_append_str("NUMBER(MODULES_VADDR)=0x%lx\n", MODULES_VADDR); + vmcoreinfo_append_str("NUMBER(MODULES_END)=0x%lx\n", MODULES_END); + vmcoreinfo_append_str("NUMBER(VMEMMAP_START)=0x%lx\n", (unsigned long)vmemmap); + vmcoreinfo_append_str("NUMBER(VMEMMAP_END)=0x%lx\n", VMEMMAP_END + 1); +#endif + vmcoreinfo_append_str("NUMBER(PHYS_OFFSET)=0x%lx\n", PHYS_OFFSET); + vmcoreinfo_append_str("NUMBER(PAGE_OFFSET)=0x%lx\n", PAGE_OFFSET); + vmcoreinfo_append_str("KERNELOFFSET=%lx\n", kaslr_offset()); +} From 67ab1a331481ef282c53003e6c041c8c4348d4cd Mon Sep 17 00:00:00 2001 From: Kumar Kartikeya Dwivedi Date: Tue, 27 Oct 2026 22:51:36 +0800 Subject: [PATCH 19/24] LoongArch: BPF: Fix stack arguments for indirect trampolines LoongArch passes arguments beyond a0-a7 at the caller stack pointer. The trampoline store_args() helper always reads those arguments at FP + 16, which is correct for a regular fentry trampoline: its prologue leaves FP 16 bytes below the stack pointer at trampoline entry to hold the saved RA/FP frame record of the parent function. A struct_ops indirect trampoline is entered through a function pointer and only saves its own RA and FP before setting FP to the entry stack pointer. Its stack arguments therefore start at FP rather than FP + 16. As a result, every stack-passed struct_ops argument is currently read two slots late. Select the source offset based on whether the trampoline is indirect. This also prepares the stack-passed arena argument path to consume the actual pointer slot. With this patch the BPF selftest "./test_progs -t struct_ops_multi_args" passed on LoongArch. Fixes: c9ebe2016de9 ("LoongArch: BPF: Support up to 12 function arguments for trampoline") Acked-by: Hengqi Chen Signed-off-by: Kumar Kartikeya Dwivedi Signed-off-by: Tiezhu Yang Signed-off-by: Huacai Chen --- arch/loongarch/net/bpf_jit.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/arch/loongarch/net/bpf_jit.c b/arch/loongarch/net/bpf_jit.c index e78eb582c300..119a20a7edf1 100644 --- a/arch/loongarch/net/bpf_jit.c +++ b/arch/loongarch/net/bpf_jit.c @@ -1674,17 +1674,17 @@ int bpf_arch_text_invalidate(void *dst, size_t len) return ret; } -static void store_args(struct jit_ctx *ctx, int nr_arg_slots, int args_off) +static void store_args(struct jit_ctx *ctx, int nr_arg_slots, int args_off, bool is_struct_ops) { int i; + int stack_args_off = is_struct_ops ? 0 : 16; for (i = 0; i < nr_arg_slots; i++) { if (i < LOONGARCH_MAX_REG_ARGS) emit_insn(ctx, std, LOONGARCH_GPR_A0 + i, LOONGARCH_GPR_FP, -args_off); else { - /* Skip slots for T0 and FP of traced function */ emit_insn(ctx, ldd, LOONGARCH_GPR_T1, LOONGARCH_GPR_FP, - 16 + (i - LOONGARCH_MAX_REG_ARGS) * 8); + stack_args_off + (i - LOONGARCH_MAX_REG_ARGS) * 8); emit_insn(ctx, std, LOONGARCH_GPR_T1, LOONGARCH_GPR_FP, -args_off); } args_off -= 8; @@ -2017,7 +2017,7 @@ static int __arch_prepare_bpf_trampoline(struct jit_ctx *ctx, struct bpf_tramp_i func_meta = nr_arg_slots; emit_store_stack_imm64(ctx, LOONGARCH_GPR_T1, -func_meta_off, func_meta); - store_args(ctx, nr_arg_slots, args_off); + store_args(ctx, nr_arg_slots, args_off, is_struct_ops); if (bpf_fsession_cnt(tnodes)) { /* clear all session cookies' value */ From b3e1c11698ea80c9c94e0aae042bbca6688d627f Mon Sep 17 00:00:00 2001 From: Tiezhu Yang Date: Tue, 27 Oct 2026 22:51:36 +0800 Subject: [PATCH 20/24] LoongArch: BPF: Clean up and decouple is32 parameter from move_imm() The current move_imm() relies on the 'is32' parameter for both ALU32 and JMP32, forcing the helper to internally clear the upper 32 bits. However this parameter is redundant for all 64-bit immediate loads. Furthermore, it is even undesirable for load/store signed address offset computations. Clean up move_imm() by removing the 'is32' parameter which allows it to focus on loading values, while moving the zero-extension logic outward to the explicit callers. For 64-bit and load-store operations, just drop the parameter. This is preparation for later patch, no functional changes. Acked-by: Hengqi Chen Signed-off-by: Tiezhu Yang Signed-off-by: Huacai Chen --- arch/loongarch/net/bpf_jit.c | 102 +++++++++++++++++++---------------- arch/loongarch/net/bpf_jit.h | 7 +-- 2 files changed, 58 insertions(+), 51 deletions(-) diff --git a/arch/loongarch/net/bpf_jit.c b/arch/loongarch/net/bpf_jit.c index 119a20a7edf1..edc66a563ca8 100644 --- a/arch/loongarch/net/bpf_jit.c +++ b/arch/loongarch/net/bpf_jit.c @@ -194,7 +194,7 @@ static void build_prologue(struct jit_ctx *ctx) ctx->stack_size = stack_adjust; if (ctx->arena_vm_start) - move_imm(ctx, REG_ARENA, ctx->arena_vm_start, false); + move_imm(ctx, REG_ARENA, ctx->arena_vm_start); } static void __build_epilogue(struct jit_ctx *ctx, bool is_tail_call) @@ -339,7 +339,7 @@ static int emit_bpf_tail_call(struct jit_ctx *ctx, int insn) static void emit_store_stack_imm64(struct jit_ctx *ctx, int reg, int stack_off, u64 imm64) { - move_imm(ctx, reg, imm64, false); + move_imm(ctx, reg, imm64); emit_insn(ctx, std, reg, LOONGARCH_GPR_FP, stack_off); } @@ -355,7 +355,7 @@ static int emit_atomic_rmw(const struct bpf_insn *insn, struct jit_ctx *ctx) const s32 imm = insn->imm; const bool isdw = BPF_SIZE(insn->code) == BPF_DW; - move_imm(ctx, t1, off, false); + move_imm(ctx, t1, off); emit_insn(ctx, addd, t1, dst, t1); move_reg(ctx, t3, src); @@ -528,7 +528,7 @@ static int emit_atomic_ld_st(const struct bpf_insn *insn, struct jit_ctx *ctx) if (is_signed_imm12(off)) { emit_insn(ctx, ldbu, dst, src, off); } else { - move_imm(ctx, t1, off, false); + move_imm(ctx, t1, off); emit_insn(ctx, ldxbu, dst, src, t1); } break; @@ -536,7 +536,7 @@ static int emit_atomic_ld_st(const struct bpf_insn *insn, struct jit_ctx *ctx) if (is_signed_imm12(off)) { emit_insn(ctx, ldhu, dst, src, off); } else { - move_imm(ctx, t1, off, false); + move_imm(ctx, t1, off); emit_insn(ctx, ldxhu, dst, src, t1); } break; @@ -544,7 +544,7 @@ static int emit_atomic_ld_st(const struct bpf_insn *insn, struct jit_ctx *ctx) if (is_signed_imm12(off)) { emit_insn(ctx, ldwu, dst, src, off); } else { - move_imm(ctx, t1, off, false); + move_imm(ctx, t1, off); emit_insn(ctx, ldxwu, dst, src, t1); } break; @@ -552,7 +552,7 @@ static int emit_atomic_ld_st(const struct bpf_insn *insn, struct jit_ctx *ctx) if (is_signed_imm12(off)) { emit_insn(ctx, ldd, dst, src, off); } else { - move_imm(ctx, t1, off, false); + move_imm(ctx, t1, off); emit_insn(ctx, ldxd, dst, src, t1); } break; @@ -567,7 +567,7 @@ static int emit_atomic_ld_st(const struct bpf_insn *insn, struct jit_ctx *ctx) if (is_signed_imm12(off)) { emit_insn(ctx, stb, src, dst, off); } else { - move_imm(ctx, t1, off, false); + move_imm(ctx, t1, off); emit_insn(ctx, stxb, src, dst, t1); } break; @@ -575,7 +575,7 @@ static int emit_atomic_ld_st(const struct bpf_insn *insn, struct jit_ctx *ctx) if (is_signed_imm12(off)) { emit_insn(ctx, sth, src, dst, off); } else { - move_imm(ctx, t1, off, false); + move_imm(ctx, t1, off); emit_insn(ctx, stxh, src, dst, t1); } break; @@ -583,7 +583,7 @@ static int emit_atomic_ld_st(const struct bpf_insn *insn, struct jit_ctx *ctx) if (is_signed_imm12(off)) { emit_insn(ctx, stw, src, dst, off); } else { - move_imm(ctx, t1, off, false); + move_imm(ctx, t1, off); emit_insn(ctx, stxw, src, dst, t1); } break; @@ -591,7 +591,7 @@ static int emit_atomic_ld_st(const struct bpf_insn *insn, struct jit_ctx *ctx) if (is_signed_imm12(off)) { emit_insn(ctx, std, src, dst, off); } else { - move_imm(ctx, t1, off, false); + move_imm(ctx, t1, off); emit_insn(ctx, stxd, src, dst, t1); } break; @@ -716,7 +716,7 @@ static int build_insn(const struct bpf_insn *insn, struct jit_ctx *ctx, bool ext if (insn_is_cast_user(insn)) { move_reg(ctx, t1, src); emit_zext_32(ctx, t1, true); - move_imm(ctx, dst, (ctx->user_vm_start >> 32) << 32, false); + move_imm(ctx, dst, (ctx->user_vm_start >> 32) << 32); emit_insn(ctx, beq, t1, LOONGARCH_GPR_ZERO, 2); emit_insn(ctx, or, t1, dst, t1); move_reg(ctx, dst, t1); @@ -753,7 +753,8 @@ static int build_insn(const struct bpf_insn *insn, struct jit_ctx *ctx, bool ext /* dst = imm */ case BPF_ALU | BPF_MOV | BPF_K: case BPF_ALU64 | BPF_MOV | BPF_K: - move_imm(ctx, dst, imm, is32); + move_imm(ctx, dst, imm); + emit_zext_32(ctx, dst, is32); break; /* dst = dst + src */ @@ -769,7 +770,8 @@ static int build_insn(const struct bpf_insn *insn, struct jit_ctx *ctx, bool ext if (is_signed_imm12(imm)) { emit_insn(ctx, addid, dst, dst, imm); } else { - move_imm(ctx, t1, imm, is32); + move_imm(ctx, t1, imm); + emit_zext_32(ctx, t1, is32); emit_insn(ctx, addd, dst, dst, t1); } emit_zext_32(ctx, dst, is32); @@ -788,7 +790,8 @@ static int build_insn(const struct bpf_insn *insn, struct jit_ctx *ctx, bool ext if (is_signed_imm12(-imm)) { emit_insn(ctx, addid, dst, dst, -imm); } else { - move_imm(ctx, t1, imm, is32); + move_imm(ctx, t1, imm); + emit_zext_32(ctx, t1, is32); emit_insn(ctx, subd, dst, dst, t1); } emit_zext_32(ctx, dst, is32); @@ -804,7 +807,8 @@ static int build_insn(const struct bpf_insn *insn, struct jit_ctx *ctx, bool ext /* dst = dst * imm */ case BPF_ALU | BPF_MUL | BPF_K: case BPF_ALU64 | BPF_MUL | BPF_K: - move_imm(ctx, t1, imm, is32); + move_imm(ctx, t1, imm); + emit_zext_32(ctx, t1, is32); emit_insn(ctx, muld, dst, dst, t1); emit_zext_32(ctx, dst, is32); break; @@ -831,12 +835,13 @@ static int build_insn(const struct bpf_insn *insn, struct jit_ctx *ctx, bool ext case BPF_ALU | BPF_DIV | BPF_K: case BPF_ALU64 | BPF_DIV | BPF_K: if (!off) { - move_imm(ctx, t1, imm, is32); + move_imm(ctx, t1, imm); + emit_zext_32(ctx, t1, is32); emit_zext_32(ctx, dst, is32); emit_insn(ctx, divdu, dst, dst, t1); emit_zext_32(ctx, dst, is32); } else { - move_imm(ctx, t1, imm, false); + move_imm(ctx, t1, imm); emit_sext_32(ctx, t1, is32); emit_sext_32(ctx, dst, is32); emit_insn(ctx, divd, dst, dst, t1); @@ -866,12 +871,13 @@ static int build_insn(const struct bpf_insn *insn, struct jit_ctx *ctx, bool ext case BPF_ALU | BPF_MOD | BPF_K: case BPF_ALU64 | BPF_MOD | BPF_K: if (!off) { - move_imm(ctx, t1, imm, is32); + move_imm(ctx, t1, imm); + emit_zext_32(ctx, t1, is32); emit_zext_32(ctx, dst, is32); emit_insn(ctx, moddu, dst, dst, t1); emit_zext_32(ctx, dst, is32); } else { - move_imm(ctx, t1, imm, false); + move_imm(ctx, t1, imm); emit_sext_32(ctx, t1, is32); emit_sext_32(ctx, dst, is32); emit_insn(ctx, modd, dst, dst, t1); @@ -899,7 +905,8 @@ static int build_insn(const struct bpf_insn *insn, struct jit_ctx *ctx, bool ext if (is_unsigned_imm12(imm)) { emit_insn(ctx, andi, dst, dst, imm); } else { - move_imm(ctx, t1, imm, is32); + move_imm(ctx, t1, imm); + emit_zext_32(ctx, t1, is32); emit_insn(ctx, and, dst, dst, t1); } emit_zext_32(ctx, dst, is32); @@ -918,7 +925,8 @@ static int build_insn(const struct bpf_insn *insn, struct jit_ctx *ctx, bool ext if (is_unsigned_imm12(imm)) { emit_insn(ctx, ori, dst, dst, imm); } else { - move_imm(ctx, t1, imm, is32); + move_imm(ctx, t1, imm); + emit_zext_32(ctx, t1, is32); emit_insn(ctx, or, dst, dst, t1); } emit_zext_32(ctx, dst, is32); @@ -937,7 +945,8 @@ static int build_insn(const struct bpf_insn *insn, struct jit_ctx *ctx, bool ext if (is_unsigned_imm12(imm)) { emit_insn(ctx, xori, dst, dst, imm); } else { - move_imm(ctx, t1, imm, is32); + move_imm(ctx, t1, imm); + emit_zext_32(ctx, t1, is32); emit_insn(ctx, xor, dst, dst, t1); } emit_zext_32(ctx, dst, is32); @@ -1097,7 +1106,7 @@ static int build_insn(const struct bpf_insn *insn, struct jit_ctx *ctx, bool ext case BPF_JMP32 | BPF_JSLE | BPF_K: jmp_offset = bpf2la_offset(i, off, ctx); if (imm) { - move_imm(ctx, t1, imm, false); + move_imm(ctx, t1, imm); tm = t1; } else { /* If imm is 0, simply use zero register. */ @@ -1129,7 +1138,8 @@ static int build_insn(const struct bpf_insn *insn, struct jit_ctx *ctx, bool ext case BPF_JMP | BPF_JSET | BPF_K: case BPF_JMP32 | BPF_JSET | BPF_K: jmp_offset = bpf2la_offset(i, off, ctx); - move_imm(ctx, t1, imm, is32); + move_imm(ctx, t1, imm); + emit_zext_32(ctx, t1, is32); emit_insn(ctx, and, t1, dst, t1); emit_zext_32(ctx, t1, is32); if (emit_cond_jmp(ctx, cond, t1, LOONGARCH_GPR_ZERO, jmp_offset) < 0) @@ -1227,7 +1237,7 @@ static int build_insn(const struct bpf_insn *insn, struct jit_ctx *ctx, bool ext if (bpf_pseudo_func(insn)) move_addr(ctx, dst, imm64); else - move_imm(ctx, dst, imm64, is32); + move_imm(ctx, dst, imm64); return 1; } @@ -1268,7 +1278,7 @@ static int build_insn(const struct bpf_insn *insn, struct jit_ctx *ctx, bool ext else emit_insn(ctx, ldbu, dst, src, off); } else { - move_imm(ctx, t1, off, is32); + move_imm(ctx, t1, off); if (sign_extend) emit_insn(ctx, ldxb, dst, src, t1); else @@ -1282,7 +1292,7 @@ static int build_insn(const struct bpf_insn *insn, struct jit_ctx *ctx, bool ext else emit_insn(ctx, ldhu, dst, src, off); } else { - move_imm(ctx, t1, off, is32); + move_imm(ctx, t1, off); if (sign_extend) emit_insn(ctx, ldxh, dst, src, t1); else @@ -1296,7 +1306,7 @@ static int build_insn(const struct bpf_insn *insn, struct jit_ctx *ctx, bool ext else emit_insn(ctx, ldwu, dst, src, off); } else { - move_imm(ctx, t1, off, is32); + move_imm(ctx, t1, off); if (sign_extend) emit_insn(ctx, ldxw, dst, src, t1); else @@ -1304,7 +1314,7 @@ static int build_insn(const struct bpf_insn *insn, struct jit_ctx *ctx, bool ext } break; case BPF_DW: - move_imm(ctx, t1, off, is32); + move_imm(ctx, t1, off); emit_insn(ctx, ldxd, dst, src, t1); break; } @@ -1331,42 +1341,42 @@ static int build_insn(const struct bpf_insn *insn, struct jit_ctx *ctx, bool ext switch (BPF_SIZE(code)) { case BPF_B: - move_imm(ctx, t1, imm, is32); + move_imm(ctx, t1, imm); if (is_signed_imm12(off)) { emit_insn(ctx, stb, t1, dst, off); } else { - move_imm(ctx, t2, off, is32); + move_imm(ctx, t2, off); emit_insn(ctx, stxb, t1, dst, t2); } break; case BPF_H: - move_imm(ctx, t1, imm, is32); + move_imm(ctx, t1, imm); if (is_signed_imm12(off)) { emit_insn(ctx, sth, t1, dst, off); } else { - move_imm(ctx, t2, off, is32); + move_imm(ctx, t2, off); emit_insn(ctx, stxh, t1, dst, t2); } break; case BPF_W: - move_imm(ctx, t1, imm, is32); + move_imm(ctx, t1, imm); if (is_signed_imm12(off)) { emit_insn(ctx, stw, t1, dst, off); } else if (is_signed_imm14(off)) { emit_insn(ctx, stptrw, t1, dst, off); } else { - move_imm(ctx, t2, off, is32); + move_imm(ctx, t2, off); emit_insn(ctx, stxw, t1, dst, t2); } break; case BPF_DW: - move_imm(ctx, t1, imm, is32); + move_imm(ctx, t1, imm); if (is_signed_imm12(off)) { emit_insn(ctx, std, t1, dst, off); } else if (is_signed_imm14(off)) { emit_insn(ctx, stptrd, t1, dst, off); } else { - move_imm(ctx, t2, off, is32); + move_imm(ctx, t2, off); emit_insn(ctx, stxd, t1, dst, t2); } break; @@ -1397,7 +1407,7 @@ static int build_insn(const struct bpf_insn *insn, struct jit_ctx *ctx, bool ext if (is_signed_imm12(off)) { emit_insn(ctx, stb, src, dst, off); } else { - move_imm(ctx, t1, off, is32); + move_imm(ctx, t1, off); emit_insn(ctx, stxb, src, dst, t1); } break; @@ -1405,7 +1415,7 @@ static int build_insn(const struct bpf_insn *insn, struct jit_ctx *ctx, bool ext if (is_signed_imm12(off)) { emit_insn(ctx, sth, src, dst, off); } else { - move_imm(ctx, t1, off, is32); + move_imm(ctx, t1, off); emit_insn(ctx, stxh, src, dst, t1); } break; @@ -1415,7 +1425,7 @@ static int build_insn(const struct bpf_insn *insn, struct jit_ctx *ctx, bool ext } else if (is_signed_imm14(off)) { emit_insn(ctx, stptrw, src, dst, off); } else { - move_imm(ctx, t1, off, is32); + move_imm(ctx, t1, off); emit_insn(ctx, stxw, src, dst, t1); } break; @@ -1425,7 +1435,7 @@ static int build_insn(const struct bpf_insn *insn, struct jit_ctx *ctx, bool ext } else if (is_signed_imm14(off)) { emit_insn(ctx, stptrd, src, dst, off); } else { - move_imm(ctx, t1, off, is32); + move_imm(ctx, t1, off); emit_insn(ctx, stxd, src, dst, t1); } break; @@ -1537,7 +1547,7 @@ static int emit_jump_and_link(struct jit_ctx *ctx, u8 rd, u64 target) return -EFAULT; } - move_imm(ctx, LOONGARCH_GPR_T1, target, false); + move_imm(ctx, LOONGARCH_GPR_T1, target); emit_insn(ctx, jirl, rd, LOONGARCH_GPR_T1, 0); return 0; @@ -1734,7 +1744,7 @@ static int invoke_bpf_prog(struct jit_ctx *ctx, struct bpf_tramp_image *im, emit_insn(ctx, std, LOONGARCH_GPR_ZERO, LOONGARCH_GPR_FP, -run_ctx_off + cookie_off); /* arg1: prog */ - move_imm(ctx, LOONGARCH_GPR_A0, (const s64)p, false); + move_imm(ctx, LOONGARCH_GPR_A0, (const s64)p); /* arg2: &run_ctx */ emit_insn(ctx, addid, LOONGARCH_GPR_A1, LOONGARCH_GPR_FP, -run_ctx_off); ret = emit_call(ctx, (const u64)bpf_trampoline_enter(p)); @@ -1755,7 +1765,7 @@ static int invoke_bpf_prog(struct jit_ctx *ctx, struct bpf_tramp_image *im, /* arg1: &args_off */ emit_insn(ctx, addid, LOONGARCH_GPR_A0, LOONGARCH_GPR_FP, -args_off); if (!p->jited) - move_imm(ctx, LOONGARCH_GPR_A1, (const s64)p->insnsi, false); + move_imm(ctx, LOONGARCH_GPR_A1, (const s64)p->insnsi); ret = emit_call(ctx, (const u64)p->bpf_func); if (ret) return ret; @@ -1772,7 +1782,7 @@ static int invoke_bpf_prog(struct jit_ctx *ctx, struct bpf_tramp_image *im, } /* arg1: prog */ - move_imm(ctx, LOONGARCH_GPR_A0, (const s64)p, false); + move_imm(ctx, LOONGARCH_GPR_A0, (const s64)p); /* arg2: prog start time */ move_reg(ctx, LOONGARCH_GPR_A1, LOONGARCH_GPR_S1); /* arg3: &run_ctx */ diff --git a/arch/loongarch/net/bpf_jit.h b/arch/loongarch/net/bpf_jit.h index bb58c42c2f2a..57a98f8a055b 100644 --- a/arch/loongarch/net/bpf_jit.h +++ b/arch/loongarch/net/bpf_jit.h @@ -137,7 +137,7 @@ static inline void move_addr(struct jit_ctx *ctx, enum loongarch_gpr rd, u64 add emit_insn(ctx, lu52id, rd, rd, imm_63_52); } -static inline void move_imm(struct jit_ctx *ctx, enum loongarch_gpr rd, long imm, bool is32) +static inline void move_imm(struct jit_ctx *ctx, enum loongarch_gpr rd, long imm) { long imm_11_0, imm_31_12, imm_51_32, imm_63_52, imm_51_0, imm_51_31; @@ -150,7 +150,7 @@ static inline void move_imm(struct jit_ctx *ctx, enum loongarch_gpr rd, long imm /* addiw rd, $zero, imm_11_0 */ if (is_signed_imm12(imm)) { emit_insn(ctx, addiw, rd, LOONGARCH_GPR_ZERO, imm); - goto zext; + return; } /* ori rd, $zero, imm_11_0 */ @@ -195,9 +195,6 @@ static inline void move_imm(struct jit_ctx *ctx, enum loongarch_gpr rd, long imm if (!is_signed_imm52(imm)) emit_insn(ctx, lu52id, rd, rd, imm_63_52); } - -zext: - emit_zext_32(ctx, rd, is32); } static inline void move_reg(struct jit_ctx *ctx, enum loongarch_gpr rd, From 0311361db915406ae682a074895a5ea867122ded Mon Sep 17 00:00:00 2001 From: Tiezhu Yang Date: Wed, 28 Oct 2026 15:09:15 +0800 Subject: [PATCH 21/24] LoongArch: BPF: Optimize 32-bit operations using native instructions Currently, the LoongArch BPF JIT emits numerous redundant zero and sign extensions across 32-bit operations, bloating the final JIT image size and also leading to runtime overhead, particularly in the division and modulo operations. Thus, it is necessary to optimize 32-bit operations by emitting LoongArch native 32-bit word instructions. For general ALU32 operations, utilize native 32-bit word instructions for core calculations. Given that the BPF spec requires the upper 32 bits of the destination register to be zero-extended upon completion, use a unified 'bstrpickd' instruction to clear the upper 32 bits at the common exit of build_insn(), while immediate loads are optimized via (u32) casting to bypass the trailing extension. According to the LoongArch Reference Manual, native 32-bit division and modulo instructions (DIV.W[U] and MOD.W[U]) require the upper 33 bits [63:31] of both input operands to be all 0s or all 1s (0x0 or 0x1ffffffff), that is to say, they need to be properly sign-extended by the software on CPU cores without the DIV32 feature. Otherwise, the 32-bit division and modulo instructions will trigger undefined hardware behaviors and return unexpected values. To ensure both performance and hardware compatibility, leverage the newly introduced 'cpu_has_div32' helper: (1) on newer CPUs with DIV32, emit native 32-bit div/mod instructions directly for optimal performance; (2) on older CPUs without DIV32, sign-extend the destination register in place and the source register into a temporary register via 'addw' before executing the native 32-bit div/mod instructions. Note that the JIT compiler must never alter the state of the src register because it remains read-only for the duration of the instruction. For JMP32 conditional branches, the source operands may originate from 64-bit contexts where the upper 32 bits contain arbitrary data, so use 6 independent case structures to prevent 64-bit comparison inversions. Furthermore, use 'bstrpickd' to mask out upper bits for unsigned JMP32, and 'addw' with GPR_ZERO to re-align sign bits for signed JMP32. With this patch, the emit_{zext, sext}_32() and is_signed_bpf_cond() helpers are completely wiped out from the header and source files, delivering a native execution stream with a reduced JIT image size, especially for division and mod operations. All of the testcases of test_bpf passed on Loongson-3A6000 (with DIV32) and Loongson-3A5000 (without DIV32), here are the test results: $ sudo modprobe test_bpf $ dmesg -t | grep Summary test_bpf: Summary: 1061 PASSED, 0 FAILED, [1049/1049 JIT'ed] test_bpf: test_tail_calls: Summary: 10 PASSED, 0 FAILED, [10/10 JIT'ed] test_bpf: test_skb_segment: Summary: 2 PASSED, 0 FAILED For the BPF selftests test_verifier and test_progs, there are no any regressions. Acked-by: Hengqi Chen Signed-off-by: Tiezhu Yang Signed-off-by: Huacai Chen --- arch/loongarch/include/asm/inst.h | 6 + arch/loongarch/net/bpf_jit.c | 323 +++++++++++++++++------------- arch/loongarch/net/bpf_jit.h | 18 -- 3 files changed, 188 insertions(+), 159 deletions(-) diff --git a/arch/loongarch/include/asm/inst.h b/arch/loongarch/include/asm/inst.h index 585667e361c2..b55aae325300 100644 --- a/arch/loongarch/include/asm/inst.h +++ b/arch/loongarch/include/asm/inst.h @@ -752,11 +752,17 @@ static inline void emit_##NAME(union loongarch_instruction *insn, \ DEF_EMIT_REG3_FORMAT(addw, addw_op) DEF_EMIT_REG3_FORMAT(addd, addd_op) +DEF_EMIT_REG3_FORMAT(subw, subw_op) DEF_EMIT_REG3_FORMAT(subd, subd_op) +DEF_EMIT_REG3_FORMAT(mulw, mulw_op) DEF_EMIT_REG3_FORMAT(muld, muld_op) +DEF_EMIT_REG3_FORMAT(divw, divw_op) DEF_EMIT_REG3_FORMAT(divd, divd_op) +DEF_EMIT_REG3_FORMAT(modw, modw_op) DEF_EMIT_REG3_FORMAT(modd, modd_op) +DEF_EMIT_REG3_FORMAT(divwu, divwu_op) DEF_EMIT_REG3_FORMAT(divdu, divdu_op) +DEF_EMIT_REG3_FORMAT(modwu, modwu_op) DEF_EMIT_REG3_FORMAT(moddu, moddu_op) DEF_EMIT_REG3_FORMAT(maskeqz, maskeqz_op) DEF_EMIT_REG3_FORMAT(masknez, masknez_op) diff --git a/arch/loongarch/net/bpf_jit.c b/arch/loongarch/net/bpf_jit.c index edc66a563ca8..0ebb83797e08 100644 --- a/arch/loongarch/net/bpf_jit.c +++ b/arch/loongarch/net/bpf_jit.c @@ -288,7 +288,7 @@ static int emit_bpf_tail_call(struct jit_ctx *ctx, int insn) * if (index >= array->map.max_entries) * goto out; */ - emit_zext_32(ctx, a2, true); + emit_insn(ctx, bstrpickd, a2, a2, 31, 0); off = offsetof(struct bpf_array, map.max_entries); emit_insn(ctx, ldwu, t1, a1, off); @@ -412,7 +412,6 @@ static int emit_atomic_rmw(const struct bpf_insn *insn, struct jit_ctx *ctx) return -EINVAL; } emit_insn(ctx, amadddbb, src, t1, t3); - emit_zext_32(ctx, src, true); break; case BPF_H: if (!cpu_has_lam_bh) { @@ -420,23 +419,23 @@ static int emit_atomic_rmw(const struct bpf_insn *insn, struct jit_ctx *ctx) return -EINVAL; } emit_insn(ctx, amadddbh, src, t1, t3); - emit_zext_32(ctx, src, true); break; case BPF_W: emit_insn(ctx, amadddbw, src, t1, t3); - emit_zext_32(ctx, src, true); break; case BPF_DW: emit_insn(ctx, amadddbd, src, t1, t3); break; } + if (!isdw) + emit_insn(ctx, bstrpickd, src, src, 31, 0); break; case BPF_AND | BPF_FETCH: if (isdw) { emit_insn(ctx, amanddbd, src, t1, t3); } else { emit_insn(ctx, amanddbw, src, t1, t3); - emit_zext_32(ctx, src, true); + emit_insn(ctx, bstrpickd, src, src, 31, 0); } break; case BPF_OR | BPF_FETCH: @@ -444,7 +443,7 @@ static int emit_atomic_rmw(const struct bpf_insn *insn, struct jit_ctx *ctx) emit_insn(ctx, amordbd, src, t1, t3); } else { emit_insn(ctx, amordbw, src, t1, t3); - emit_zext_32(ctx, src, true); + emit_insn(ctx, bstrpickd, src, src, 31, 0); } break; case BPF_XOR | BPF_FETCH: @@ -452,7 +451,7 @@ static int emit_atomic_rmw(const struct bpf_insn *insn, struct jit_ctx *ctx) emit_insn(ctx, amxordbd, src, t1, t3); } else { emit_insn(ctx, amxordbw, src, t1, t3); - emit_zext_32(ctx, src, true); + emit_insn(ctx, bstrpickd, src, src, 31, 0); } break; /* src = atomic_xchg(dst + off, src); */ @@ -464,7 +463,6 @@ static int emit_atomic_rmw(const struct bpf_insn *insn, struct jit_ctx *ctx) return -EINVAL; } emit_insn(ctx, amswapdbb, src, t1, t3); - emit_zext_32(ctx, src, true); break; case BPF_H: if (!cpu_has_lam_bh) { @@ -472,16 +470,16 @@ static int emit_atomic_rmw(const struct bpf_insn *insn, struct jit_ctx *ctx) return -EINVAL; } emit_insn(ctx, amswapdbh, src, t1, t3); - emit_zext_32(ctx, src, true); break; case BPF_W: emit_insn(ctx, amswapdbw, src, t1, t3); - emit_zext_32(ctx, src, true); break; case BPF_DW: emit_insn(ctx, amswapdbd, src, t1, t3); break; } + if (!isdw) + emit_insn(ctx, bstrpickd, src, src, 31, 0); break; /* r0 = atomic_cmpxchg(dst + off, r0, src); */ case BPF_CMPXCHG: @@ -494,13 +492,13 @@ static int emit_atomic_rmw(const struct bpf_insn *insn, struct jit_ctx *ctx) emit_insn(ctx, beq, t3, LOONGARCH_GPR_ZERO, -4); } else { emit_insn(ctx, llw, r0, t1, 0); - emit_zext_32(ctx, t2, true); - emit_zext_32(ctx, r0, true); + emit_insn(ctx, bstrpickd, t2, t2, 31, 0); + emit_insn(ctx, bstrpickd, r0, r0, 31, 0); emit_insn(ctx, bne, t2, r0, 4); move_reg(ctx, t3, src); emit_insn(ctx, scw, t3, t1, 0); emit_insn(ctx, beq, t3, LOONGARCH_GPR_ZERO, -6); - emit_zext_32(ctx, r0, true); + emit_insn(ctx, bstrpickd, r0, r0, 31, 0); } emit_insn(ctx, dbar, DBAR_LLSC_MB); break; @@ -605,12 +603,6 @@ static int emit_atomic_ld_st(const struct bpf_insn *insn, struct jit_ctx *ctx) return 0; } -static bool is_signed_bpf_cond(u8 cond) -{ - return cond == BPF_JSGT || cond == BPF_JSLT || - cond == BPF_JSGE || cond == BPF_JSLE; -} - #define BPF_FIXUP_REG_MASK GENMASK(31, 27) #define BPF_FIXUP_OFFSET_MASK GENMASK(26, 0) #define REG_DONT_CLEAR_MARKER 0 @@ -707,7 +699,7 @@ static int build_insn(const struct bpf_insn *insn, struct jit_ctx *ctx, bool ext u8 dst = regmap[insn->dst_reg]; const s16 off = insn->off; const s32 imm = insn->imm; - const bool is32 = BPF_CLASS(insn->code) == BPF_ALU || BPF_CLASS(insn->code) == BPF_JMP32; + const bool is32 = BPF_CLASS(insn->code) == BPF_ALU && BPF_OP(insn->code) != BPF_END; switch (code) { /* dst = src */ @@ -715,7 +707,7 @@ static int build_insn(const struct bpf_insn *insn, struct jit_ctx *ctx, bool ext case BPF_ALU64 | BPF_MOV | BPF_X: if (insn_is_cast_user(insn)) { move_reg(ctx, t1, src); - emit_zext_32(ctx, t1, true); + emit_insn(ctx, bstrpickd, t1, t1, 31, 0); move_imm(ctx, dst, (ctx->user_vm_start >> 32) << 32); emit_insn(ctx, beq, t1, LOONGARCH_GPR_ZERO, 2); emit_insn(ctx, or, t1, dst, t1); @@ -734,15 +726,12 @@ static int build_insn(const struct bpf_insn *insn, struct jit_ctx *ctx, bool ext switch (off) { case 0: move_reg(ctx, dst, src); - emit_zext_32(ctx, dst, is32); break; case 8: emit_insn(ctx, extwb, dst, src); - emit_zext_32(ctx, dst, is32); break; case 16: emit_insn(ctx, extwh, dst, src); - emit_zext_32(ctx, dst, is32); break; case 32: emit_insn(ctx, addw, dst, src, LOONGARCH_GPR_ZERO); @@ -752,151 +741,188 @@ static int build_insn(const struct bpf_insn *insn, struct jit_ctx *ctx, bool ext /* dst = imm */ case BPF_ALU | BPF_MOV | BPF_K: + move_imm(ctx, dst, (u32)imm); + return 0; case BPF_ALU64 | BPF_MOV | BPF_K: move_imm(ctx, dst, imm); - emit_zext_32(ctx, dst, is32); - break; + return 0; /* dst = dst + src */ case BPF_ALU | BPF_ADD | BPF_X: + emit_insn(ctx, addw, dst, dst, src); + break; case BPF_ALU64 | BPF_ADD | BPF_X: emit_insn(ctx, addd, dst, dst, src); - emit_zext_32(ctx, dst, is32); break; /* dst = dst + imm */ case BPF_ALU | BPF_ADD | BPF_K: + if (is_signed_imm12(imm)) { + emit_insn(ctx, addiw, dst, dst, imm); + } else { + move_imm(ctx, t1, (u32)imm); + emit_insn(ctx, addw, dst, dst, t1); + } + break; case BPF_ALU64 | BPF_ADD | BPF_K: if (is_signed_imm12(imm)) { emit_insn(ctx, addid, dst, dst, imm); } else { move_imm(ctx, t1, imm); - emit_zext_32(ctx, t1, is32); emit_insn(ctx, addd, dst, dst, t1); } - emit_zext_32(ctx, dst, is32); break; /* dst = dst - src */ case BPF_ALU | BPF_SUB | BPF_X: + emit_insn(ctx, subw, dst, dst, src); + break; case BPF_ALU64 | BPF_SUB | BPF_X: emit_insn(ctx, subd, dst, dst, src); - emit_zext_32(ctx, dst, is32); break; /* dst = dst - imm */ case BPF_ALU | BPF_SUB | BPF_K: + if (is_signed_imm12(-imm)) { + emit_insn(ctx, addiw, dst, dst, -imm); + } else { + move_imm(ctx, t1, (u32)imm); + emit_insn(ctx, subw, dst, dst, t1); + } + break; case BPF_ALU64 | BPF_SUB | BPF_K: if (is_signed_imm12(-imm)) { emit_insn(ctx, addid, dst, dst, -imm); } else { move_imm(ctx, t1, imm); - emit_zext_32(ctx, t1, is32); emit_insn(ctx, subd, dst, dst, t1); } - emit_zext_32(ctx, dst, is32); break; /* dst = dst * src */ case BPF_ALU | BPF_MUL | BPF_X: + emit_insn(ctx, mulw, dst, dst, src); + break; case BPF_ALU64 | BPF_MUL | BPF_X: emit_insn(ctx, muld, dst, dst, src); - emit_zext_32(ctx, dst, is32); break; /* dst = dst * imm */ case BPF_ALU | BPF_MUL | BPF_K: + move_imm(ctx, t1, (u32)imm); + emit_insn(ctx, mulw, dst, dst, t1); + break; case BPF_ALU64 | BPF_MUL | BPF_K: move_imm(ctx, t1, imm); - emit_zext_32(ctx, t1, is32); emit_insn(ctx, muld, dst, dst, t1); - emit_zext_32(ctx, dst, is32); break; /* dst = dst / src */ case BPF_ALU | BPF_DIV | BPF_X: - case BPF_ALU64 | BPF_DIV | BPF_X: - if (!off) { - emit_zext_32(ctx, dst, is32); - move_reg(ctx, t1, src); - emit_zext_32(ctx, t1, is32); - emit_insn(ctx, divdu, dst, dst, t1); - emit_zext_32(ctx, dst, is32); - } else { - emit_sext_32(ctx, dst, is32); - move_reg(ctx, t1, src); - emit_sext_32(ctx, t1, is32); - emit_insn(ctx, divd, dst, dst, t1); - emit_zext_32(ctx, dst, is32); + if (cpu_has_div32) { + if (!off) + emit_insn(ctx, divwu, dst, dst, src); + else + emit_insn(ctx, divw, dst, dst, src); + break; } + /* + * Legacy fallback for !div32: src must be extended into a + * temporary register to keep the original src register intact. + */ + emit_insn(ctx, addw, dst, dst, LOONGARCH_GPR_ZERO); + emit_insn(ctx, addw, t1, src, LOONGARCH_GPR_ZERO); + if (!off) + emit_insn(ctx, divwu, dst, dst, t1); + else + emit_insn(ctx, divw, dst, dst, t1); + break; + case BPF_ALU64 | BPF_DIV | BPF_X: + if (!off) + emit_insn(ctx, divdu, dst, dst, src); + else + emit_insn(ctx, divd, dst, dst, src); break; /* dst = dst / imm */ case BPF_ALU | BPF_DIV | BPF_K: - case BPF_ALU64 | BPF_DIV | BPF_K: - if (!off) { - move_imm(ctx, t1, imm); - emit_zext_32(ctx, t1, is32); - emit_zext_32(ctx, dst, is32); - emit_insn(ctx, divdu, dst, dst, t1); - emit_zext_32(ctx, dst, is32); - } else { - move_imm(ctx, t1, imm); - emit_sext_32(ctx, t1, is32); - emit_sext_32(ctx, dst, is32); - emit_insn(ctx, divd, dst, dst, t1); - emit_zext_32(ctx, dst, is32); + move_imm(ctx, t1, (u32)imm); + if (!cpu_has_div32) { + emit_insn(ctx, addw, dst, dst, LOONGARCH_GPR_ZERO); + emit_insn(ctx, addw, t1, t1, LOONGARCH_GPR_ZERO); } + if (!off) + emit_insn(ctx, divwu, dst, dst, t1); + else + emit_insn(ctx, divw, dst, dst, t1); + break; + case BPF_ALU64 | BPF_DIV | BPF_K: + move_imm(ctx, t1, imm); + if (!off) + emit_insn(ctx, divdu, dst, dst, t1); + else + emit_insn(ctx, divd, dst, dst, t1); break; /* dst = dst % src */ case BPF_ALU | BPF_MOD | BPF_X: - case BPF_ALU64 | BPF_MOD | BPF_X: - if (!off) { - emit_zext_32(ctx, dst, is32); - move_reg(ctx, t1, src); - emit_zext_32(ctx, t1, is32); - emit_insn(ctx, moddu, dst, dst, t1); - emit_zext_32(ctx, dst, is32); - } else { - emit_sext_32(ctx, dst, is32); - move_reg(ctx, t1, src); - emit_sext_32(ctx, t1, is32); - emit_insn(ctx, modd, dst, dst, t1); - emit_zext_32(ctx, dst, is32); + if (cpu_has_div32) { + if (!off) + emit_insn(ctx, modwu, dst, dst, src); + else + emit_insn(ctx, modw, dst, dst, src); + break; } + /* + * Legacy fallback for !div32: src must be extended into a + * temporary register to keep the original src register intact. + */ + emit_insn(ctx, addw, dst, dst, LOONGARCH_GPR_ZERO); + emit_insn(ctx, addw, t1, src, LOONGARCH_GPR_ZERO); + if (!off) + emit_insn(ctx, modwu, dst, dst, t1); + else + emit_insn(ctx, modw, dst, dst, t1); + break; + case BPF_ALU64 | BPF_MOD | BPF_X: + if (!off) + emit_insn(ctx, moddu, dst, dst, src); + else + emit_insn(ctx, modd, dst, dst, src); break; /* dst = dst % imm */ case BPF_ALU | BPF_MOD | BPF_K: - case BPF_ALU64 | BPF_MOD | BPF_K: - if (!off) { - move_imm(ctx, t1, imm); - emit_zext_32(ctx, t1, is32); - emit_zext_32(ctx, dst, is32); - emit_insn(ctx, moddu, dst, dst, t1); - emit_zext_32(ctx, dst, is32); - } else { - move_imm(ctx, t1, imm); - emit_sext_32(ctx, t1, is32); - emit_sext_32(ctx, dst, is32); - emit_insn(ctx, modd, dst, dst, t1); - emit_zext_32(ctx, dst, is32); + move_imm(ctx, t1, (u32)imm); + if (!cpu_has_div32) { + emit_insn(ctx, addw, dst, dst, LOONGARCH_GPR_ZERO); + emit_insn(ctx, addw, t1, t1, LOONGARCH_GPR_ZERO); } + if (!off) + emit_insn(ctx, modwu, dst, dst, t1); + else + emit_insn(ctx, modw, dst, dst, t1); + break; + case BPF_ALU64 | BPF_MOD | BPF_K: + move_imm(ctx, t1, imm); + if (!off) + emit_insn(ctx, moddu, dst, dst, t1); + else + emit_insn(ctx, modd, dst, dst, t1); break; /* dst = -dst */ case BPF_ALU | BPF_NEG: + emit_insn(ctx, subw, dst, LOONGARCH_GPR_ZERO, dst); + break; case BPF_ALU64 | BPF_NEG: emit_insn(ctx, subd, dst, LOONGARCH_GPR_ZERO, dst); - emit_zext_32(ctx, dst, is32); break; /* dst = dst & src */ case BPF_ALU | BPF_AND | BPF_X: case BPF_ALU64 | BPF_AND | BPF_X: emit_insn(ctx, and, dst, dst, src); - emit_zext_32(ctx, dst, is32); break; /* dst = dst & imm */ @@ -905,18 +931,18 @@ static int build_insn(const struct bpf_insn *insn, struct jit_ctx *ctx, bool ext if (is_unsigned_imm12(imm)) { emit_insn(ctx, andi, dst, dst, imm); } else { - move_imm(ctx, t1, imm); - emit_zext_32(ctx, t1, is32); + if (is32) + move_imm(ctx, t1, (u32)imm); + else + move_imm(ctx, t1, imm); emit_insn(ctx, and, dst, dst, t1); } - emit_zext_32(ctx, dst, is32); break; /* dst = dst | src */ case BPF_ALU | BPF_OR | BPF_X: case BPF_ALU64 | BPF_OR | BPF_X: emit_insn(ctx, or, dst, dst, src); - emit_zext_32(ctx, dst, is32); break; /* dst = dst | imm */ @@ -925,18 +951,18 @@ static int build_insn(const struct bpf_insn *insn, struct jit_ctx *ctx, bool ext if (is_unsigned_imm12(imm)) { emit_insn(ctx, ori, dst, dst, imm); } else { - move_imm(ctx, t1, imm); - emit_zext_32(ctx, t1, is32); + if (is32) + move_imm(ctx, t1, (u32)imm); + else + move_imm(ctx, t1, imm); emit_insn(ctx, or, dst, dst, t1); } - emit_zext_32(ctx, dst, is32); break; /* dst = dst ^ src */ case BPF_ALU | BPF_XOR | BPF_X: case BPF_ALU64 | BPF_XOR | BPF_X: emit_insn(ctx, xor, dst, dst, src); - emit_zext_32(ctx, dst, is32); break; /* dst = dst ^ imm */ @@ -945,19 +971,18 @@ static int build_insn(const struct bpf_insn *insn, struct jit_ctx *ctx, bool ext if (is_unsigned_imm12(imm)) { emit_insn(ctx, xori, dst, dst, imm); } else { - move_imm(ctx, t1, imm); - emit_zext_32(ctx, t1, is32); + if (is32) + move_imm(ctx, t1, (u32)imm); + else + move_imm(ctx, t1, imm); emit_insn(ctx, xor, dst, dst, t1); } - emit_zext_32(ctx, dst, is32); break; /* dst = dst << src (logical) */ case BPF_ALU | BPF_LSH | BPF_X: emit_insn(ctx, sllw, dst, dst, src); - emit_zext_32(ctx, dst, is32); break; - case BPF_ALU64 | BPF_LSH | BPF_X: emit_insn(ctx, slld, dst, dst, src); break; @@ -965,9 +990,7 @@ static int build_insn(const struct bpf_insn *insn, struct jit_ctx *ctx, bool ext /* dst = dst << imm (logical) */ case BPF_ALU | BPF_LSH | BPF_K: emit_insn(ctx, slliw, dst, dst, imm); - emit_zext_32(ctx, dst, is32); break; - case BPF_ALU64 | BPF_LSH | BPF_K: emit_insn(ctx, sllid, dst, dst, imm); break; @@ -975,9 +998,7 @@ static int build_insn(const struct bpf_insn *insn, struct jit_ctx *ctx, bool ext /* dst = dst >> src (logical) */ case BPF_ALU | BPF_RSH | BPF_X: emit_insn(ctx, srlw, dst, dst, src); - emit_zext_32(ctx, dst, is32); break; - case BPF_ALU64 | BPF_RSH | BPF_X: emit_insn(ctx, srld, dst, dst, src); break; @@ -985,9 +1006,7 @@ static int build_insn(const struct bpf_insn *insn, struct jit_ctx *ctx, bool ext /* dst = dst >> imm (logical) */ case BPF_ALU | BPF_RSH | BPF_K: emit_insn(ctx, srliw, dst, dst, imm); - emit_zext_32(ctx, dst, is32); break; - case BPF_ALU64 | BPF_RSH | BPF_K: emit_insn(ctx, srlid, dst, dst, imm); break; @@ -995,9 +1014,7 @@ static int build_insn(const struct bpf_insn *insn, struct jit_ctx *ctx, bool ext /* dst = dst >> src (arithmetic) */ case BPF_ALU | BPF_ARSH | BPF_X: emit_insn(ctx, sraw, dst, dst, src); - emit_zext_32(ctx, dst, is32); break; - case BPF_ALU64 | BPF_ARSH | BPF_X: emit_insn(ctx, srad, dst, dst, src); break; @@ -1005,9 +1022,7 @@ static int build_insn(const struct bpf_insn *insn, struct jit_ctx *ctx, bool ext /* dst = dst >> imm (arithmetic) */ case BPF_ALU | BPF_ARSH | BPF_K: emit_insn(ctx, sraiw, dst, dst, imm); - emit_zext_32(ctx, dst, is32); break; - case BPF_ALU64 | BPF_ARSH | BPF_K: emit_insn(ctx, sraid, dst, dst, imm); break; @@ -1016,12 +1031,10 @@ static int build_insn(const struct bpf_insn *insn, struct jit_ctx *ctx, bool ext case BPF_ALU | BPF_END | BPF_FROM_LE: switch (imm) { case 16: - /* zero-extend 16 bits into 64 bits */ emit_insn(ctx, bstrpickd, dst, dst, 15, 0); break; case 32: - /* zero-extend 32 bits into 64 bits */ - emit_zext_32(ctx, dst, is32); + emit_insn(ctx, bstrpickd, dst, dst, 31, 0); break; case 64: /* do nothing */ @@ -1034,13 +1047,11 @@ static int build_insn(const struct bpf_insn *insn, struct jit_ctx *ctx, bool ext switch (imm) { case 16: emit_insn(ctx, revb2h, dst, dst); - /* zero-extend 16 bits into 64 bits */ emit_insn(ctx, bstrpickd, dst, dst, 15, 0); break; case 32: emit_insn(ctx, revb2w, dst, dst); - /* clear the upper 32 bits */ - emit_zext_32(ctx, dst, true); + emit_insn(ctx, bstrpickd, dst, dst, 31, 0); break; case 64: emit_insn(ctx, revbd, dst, dst); @@ -1048,7 +1059,7 @@ static int build_insn(const struct bpf_insn *insn, struct jit_ctx *ctx, bool ext } break; - /* PC += off if dst cond src */ + /* PC += off if dst cond src (64-bit) */ case BPF_JMP | BPF_JEQ | BPF_X: case BPF_JMP | BPF_JNE | BPF_X: case BPF_JMP | BPF_JGT | BPF_X: @@ -1059,31 +1070,38 @@ static int build_insn(const struct bpf_insn *insn, struct jit_ctx *ctx, bool ext case BPF_JMP | BPF_JSGE | BPF_X: case BPF_JMP | BPF_JSLT | BPF_X: case BPF_JMP | BPF_JSLE | BPF_X: + jmp_offset = bpf2la_offset(i, off, ctx); + if (emit_cond_jmp(ctx, cond, dst, src, jmp_offset) < 0) + goto toofar; + break; + + /* PC += off if dst cond src (32-bit unsigned) */ case BPF_JMP32 | BPF_JEQ | BPF_X: case BPF_JMP32 | BPF_JNE | BPF_X: case BPF_JMP32 | BPF_JGT | BPF_X: case BPF_JMP32 | BPF_JGE | BPF_X: case BPF_JMP32 | BPF_JLT | BPF_X: case BPF_JMP32 | BPF_JLE | BPF_X: + jmp_offset = bpf2la_offset(i, off, ctx); + emit_insn(ctx, bstrpickd, t1, dst, 31, 0); + emit_insn(ctx, bstrpickd, t2, src, 31, 0); + if (emit_cond_jmp(ctx, cond, t1, t2, jmp_offset) < 0) + goto toofar; + break; + + /* PC += off if dst cond src (32-bit signed) */ case BPF_JMP32 | BPF_JSGT | BPF_X: case BPF_JMP32 | BPF_JSGE | BPF_X: case BPF_JMP32 | BPF_JSLT | BPF_X: case BPF_JMP32 | BPF_JSLE | BPF_X: jmp_offset = bpf2la_offset(i, off, ctx); - move_reg(ctx, t1, dst); - move_reg(ctx, t2, src); - if (is_signed_bpf_cond(BPF_OP(code))) { - emit_sext_32(ctx, t1, is32); - emit_sext_32(ctx, t2, is32); - } else { - emit_zext_32(ctx, t1, is32); - emit_zext_32(ctx, t2, is32); - } + emit_insn(ctx, addw, t1, dst, LOONGARCH_GPR_ZERO); + emit_insn(ctx, addw, t2, src, LOONGARCH_GPR_ZERO); if (emit_cond_jmp(ctx, cond, t1, t2, jmp_offset) < 0) goto toofar; break; - /* PC += off if dst cond imm */ + /* PC += off if dst cond imm (64-bit) */ case BPF_JMP | BPF_JEQ | BPF_K: case BPF_JMP | BPF_JNE | BPF_K: case BPF_JMP | BPF_JGT | BPF_K: @@ -1094,12 +1112,38 @@ static int build_insn(const struct bpf_insn *insn, struct jit_ctx *ctx, bool ext case BPF_JMP | BPF_JSGE | BPF_K: case BPF_JMP | BPF_JSLT | BPF_K: case BPF_JMP | BPF_JSLE | BPF_K: + jmp_offset = bpf2la_offset(i, off, ctx); + if (imm) { + move_imm(ctx, t1, imm); + tm = t1; + } else { + tm = LOONGARCH_GPR_ZERO; + } + if (emit_cond_jmp(ctx, cond, dst, tm, jmp_offset) < 0) + goto toofar; + break; + + /* PC += off if dst cond imm (32-bit unsigned) */ case BPF_JMP32 | BPF_JEQ | BPF_K: case BPF_JMP32 | BPF_JNE | BPF_K: case BPF_JMP32 | BPF_JGT | BPF_K: case BPF_JMP32 | BPF_JGE | BPF_K: case BPF_JMP32 | BPF_JLT | BPF_K: case BPF_JMP32 | BPF_JLE | BPF_K: + jmp_offset = bpf2la_offset(i, off, ctx); + if (imm) { + move_imm(ctx, t1, imm); + emit_insn(ctx, bstrpickd, t1, t1, 31, 0); + tm = t1; + } else { + tm = LOONGARCH_GPR_ZERO; + } + emit_insn(ctx, bstrpickd, t2, dst, 31, 0); + if (emit_cond_jmp(ctx, cond, t2, tm, jmp_offset) < 0) + goto toofar; + break; + + /* PC += off if dst cond imm (32-bit signed) */ case BPF_JMP32 | BPF_JSGT | BPF_K: case BPF_JMP32 | BPF_JSGE | BPF_K: case BPF_JMP32 | BPF_JSLT | BPF_K: @@ -1107,19 +1151,12 @@ static int build_insn(const struct bpf_insn *insn, struct jit_ctx *ctx, bool ext jmp_offset = bpf2la_offset(i, off, ctx); if (imm) { move_imm(ctx, t1, imm); + emit_insn(ctx, addw, t1, t1, LOONGARCH_GPR_ZERO); tm = t1; } else { - /* If imm is 0, simply use zero register. */ tm = LOONGARCH_GPR_ZERO; } - move_reg(ctx, t2, dst); - if (is_signed_bpf_cond(BPF_OP(code))) { - emit_sext_32(ctx, tm, is32); - emit_sext_32(ctx, t2, is32); - } else { - emit_zext_32(ctx, tm, is32); - emit_zext_32(ctx, t2, is32); - } + emit_insn(ctx, addw, t2, dst, LOONGARCH_GPR_ZERO); if (emit_cond_jmp(ctx, cond, t2, tm, jmp_offset) < 0) goto toofar; break; @@ -1129,7 +1166,8 @@ static int build_insn(const struct bpf_insn *insn, struct jit_ctx *ctx, bool ext case BPF_JMP32 | BPF_JSET | BPF_X: jmp_offset = bpf2la_offset(i, off, ctx); emit_insn(ctx, and, t1, dst, src); - emit_zext_32(ctx, t1, is32); + if (BPF_CLASS(code) == BPF_JMP32) + emit_insn(ctx, bstrpickd, t1, t1, 31, 0); if (emit_cond_jmp(ctx, cond, t1, LOONGARCH_GPR_ZERO, jmp_offset) < 0) goto toofar; break; @@ -1139,9 +1177,9 @@ static int build_insn(const struct bpf_insn *insn, struct jit_ctx *ctx, bool ext case BPF_JMP32 | BPF_JSET | BPF_K: jmp_offset = bpf2la_offset(i, off, ctx); move_imm(ctx, t1, imm); - emit_zext_32(ctx, t1, is32); emit_insn(ctx, and, t1, dst, t1); - emit_zext_32(ctx, t1, is32); + if (BPF_CLASS(code) == BPF_JMP32) + emit_insn(ctx, bstrpickd, t1, t1, 31, 0); if (emit_cond_jmp(ctx, cond, t1, LOONGARCH_GPR_ZERO, jmp_offset) < 0) goto toofar; break; @@ -1468,6 +1506,9 @@ static int build_insn(const struct bpf_insn *insn, struct jit_ctx *ctx, bool ext return -EINVAL; } + if (is32) + emit_insn(ctx, bstrpickd, dst, dst, 31, 0); + return 0; toofar: diff --git a/arch/loongarch/net/bpf_jit.h b/arch/loongarch/net/bpf_jit.h index 57a98f8a055b..2bd0dc5b911c 100644 --- a/arch/loongarch/net/bpf_jit.h +++ b/arch/loongarch/net/bpf_jit.h @@ -72,24 +72,6 @@ static inline int epilogue_offset(const struct jit_ctx *ctx) return (to - from); } -/* Zero-extend 32 bits into 64 bits */ -static inline void emit_zext_32(struct jit_ctx *ctx, enum loongarch_gpr reg, bool is32) -{ - if (!is32) - return; - - emit_insn(ctx, lu32id, reg, 0); -} - -/* Signed-extend 32 bits into 64 bits */ -static inline void emit_sext_32(struct jit_ctx *ctx, enum loongarch_gpr reg, bool is32) -{ - if (!is32) - return; - - emit_insn(ctx, addiw, reg, reg, 0); -} - /* Emit proper extension according to ABI requirements. * Note that it requires a value of size `size` already resides in register `reg`. */ From ed1a4f15b6b50f8cadec78e800c8cc9d35e1e25e Mon Sep 17 00:00:00 2001 From: Krzysztof Kozlowski Date: Wed, 28 Oct 2026 15:09:21 +0800 Subject: [PATCH 22/24] LoongArch: dts: loongson-2k0500: Drop unsupported bus_id in DWMAC There is no "bus_id" property at all defined in the bindings and new DTS is not allowed to bring undocumented properties. Fix dtbs_check warning: loongson-2k0500-ref.dtb: ethernet@1f020000 (snps,dwmac-3.70a): bus_id: b'\x00\x00\x00\x00' is not of type 'object', 'integer', 'array', 'boolean', 'null' Signed-off-by: Krzysztof Kozlowski Signed-off-by: Huacai Chen --- arch/loongarch/boot/dts/loongson-2k0500-ref.dts | 2 -- 1 file changed, 2 deletions(-) diff --git a/arch/loongarch/boot/dts/loongson-2k0500-ref.dts b/arch/loongarch/boot/dts/loongson-2k0500-ref.dts index 7ace54c84244..d2a3ed9fa578 100644 --- a/arch/loongarch/boot/dts/loongson-2k0500-ref.dts +++ b/arch/loongarch/boot/dts/loongson-2k0500-ref.dts @@ -73,14 +73,12 @@ &gmac0 { status = "okay"; phy-mode = "rgmii-id"; - bus_id = <0x0>; }; &gmac1 { status = "okay"; phy-mode = "rgmii-id"; - bus_id = <0x1>; }; &i2c0 { From 48b960ad7057f84a375ae7ec803ea3ba6b038b5c Mon Sep 17 00:00:00 2001 From: Huacai Chen Date: Wed, 28 Oct 2026 15:09:27 +0800 Subject: [PATCH 23/24] soc: loongson: loongson2_pm: Fix build warnings for 32BIT Since loongson_sysconf.suspend_addr is unsigned long, use "unsigned long" instead of "u64" for casting, so as to avoid build warnings for 32BIT: drivers/soc/loongson/loongson2_pm.c: In function 'loongson2_pm_probe': >> drivers/soc/loongson/loongson2_pm.c:178:49: warning: cast from pointer to integer of different size [-Wpointer-to-int-cast] 178 | loongson_sysconf.suspend_addr = (u64)phys_to_virt(suspend_addr); | Cc: stable@vger.kernel.org Fixes: 3d9aba6618d1 ("LoongArch: Adjust build infrastructure for 32BIT/64BIT") Closes: https://lore.kernel.org/oe-kbuild-all/202610190549.WM2PO3vO-lkp@intel.com/ Reported-by: kernel test robot Reviewed-by: Arnd Bergmann Signed-off-by: Huacai Chen --- drivers/soc/loongson/loongson2_pm.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/soc/loongson/loongson2_pm.c b/drivers/soc/loongson/loongson2_pm.c index b8e5e1e3528a..1e2c897d055b 100644 --- a/drivers/soc/loongson/loongson2_pm.c +++ b/drivers/soc/loongson/loongson2_pm.c @@ -175,7 +175,7 @@ static int loongson2_pm_probe(struct platform_device *pdev) return irq; if (!device_property_read_u64(dev, "loongson,suspend-address", &suspend_addr)) - loongson_sysconf.suspend_addr = (u64)phys_to_virt(suspend_addr); + loongson_sysconf.suspend_addr = (unsigned long)phys_to_virt(suspend_addr); else dev_err(dev, "No loongson,suspend-address, could not support S3!\n"); From 6f8d12bb1a1231dbba579932772701880745a21d Mon Sep 17 00:00:00 2001 From: Binbin Zhou Date: Wed, 28 Oct 2026 15:09:27 +0800 Subject: [PATCH 24/24] soc: loongson: loongson2_guts: Use of_root instead of own reference There is already a global of_root reference. Use that instead of getting one on our own. We don't need to care about the reference count either this way. Signed-off-by: Binbin Zhou Signed-off-by: Huacai Chen --- drivers/soc/loongson/loongson2_guts.c | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/drivers/soc/loongson/loongson2_guts.c b/drivers/soc/loongson/loongson2_guts.c index 16913c3ef65c..a2f2c1e119fe 100644 --- a/drivers/soc/loongson/loongson2_guts.c +++ b/drivers/soc/loongson/loongson2_guts.c @@ -92,8 +92,8 @@ static u32 loongson2_guts_get_svr(void) static int loongson2_guts_probe(struct platform_device *pdev) { - struct device_node *root, *np = pdev->dev.of_node; struct device *dev = &pdev->dev; + struct device_node *np = pdev->dev.of_node; const struct loongson2_soc_die_attr *soc_die; const char *machine; u32 svr; @@ -109,11 +109,8 @@ static int loongson2_guts_probe(struct platform_device *pdev) if (IS_ERR(guts->regs)) return PTR_ERR(guts->regs); - /* Register soc device */ - root = of_find_node_by_path("/"); - if (of_property_read_string(root, "model", &machine)) - of_property_read_string_index(root, "compatible", 0, &machine); - of_node_put(root); + if (of_property_read_string(of_root, "model", &machine)) + of_property_read_string_index(of_root, "compatible", 0, &machine); if (machine) { soc_dev_attr.machine = devm_kstrdup(dev, machine, GFP_KERNEL); if (!soc_dev_attr.machine)