Pull IRQ fixes from Ingo Molnar: - Fix use-after-free in irq_work_single() on PREEMPT_RT (Jiayuan Chen) - Don't call add_interrupt_randomness() for NMIs in handle_percpu_devid_irq() (Mark Rutland) - Remove unused function in the ath79-cpu irqchip driver causing LKP CI build warnings (Rosen Penev) - Fix IRQ allocation/teardown leakage regressions in the GICv5 irqchip driver (Sascha Bischoff) - Fix an IRQ trigger type regression in the Meson S4 SoC irqchip driver (Xianwei Zhao) - Fix CPU offlining regression in the RiscV IMSIC irqchip driver (Yong-Xuan Wang) * tag 'irq-urgent-2026-05-17' of git://git.kernel.org/pub/scm/linux/kernel/git/tip/tip: irq_work: Fix use-after-free in irq_work_single() on PREEMPT_RT irqchip/riscv-imsic: Clear interrupt move state during CPU offlining irqchip/meson-gpio: Use the correct register in meson_s4_gpio_irq_set_type() irqchip/ath79-cpu: Remove unused function genirq/chip: Don't call add_interrupt_randomness() for NMIs irqchip/gic-v5: Allocate ITS parent LPIs as a range irqchip/gic-v5: Support range allocation for LPIs irqchip/gic-v5: Move LPI allocation into the LPI domain
Unified · Split
| @@ -85,10 +85,3 @@ static int __init ar79_cpu_intc_of_init( · 84 unmodified lines | ||
| 85 | 85 | } |
| 86 | 86 | IRQCHIP_DECLARE(ar79_cpu_intc, "qca,ar7100-cpu-intc", |
| 87 | 87 | ar79_cpu_intc_of_init); |
| 88 | − | |
| 89 | −void __init ath79_cpu_irq_init(unsigned irq_wb_chan2, unsigned irq_wb_chan3) | |
| 90 | −{ | |
| 91 | − irq_wb_chan[2] = irq_wb_chan2; | |
| 92 | − irq_wb_chan[3] = irq_wb_chan3; | |
| 93 | − mips_cpu_irq_init(); | |
| 94 | −} | |
| @@ -929,14 +929,15 @@ static void gicv5_its_free_eventid(struct gicv5_its_dev *its_dev, u32 event_id_b · 928 unmodified lines | ||
| 929 | 929 | static int gicv5_its_irq_domain_alloc(struct irq_domain *domain, unsigned int virq, |
| 930 | 930 | unsigned int nr_irqs, void *arg) |
| 931 | 931 | { |
| 932 | − u32 device_id, event_id_base, lpi; | |
| 933 | 932 | struct gicv5_its_dev *its_dev; |
| 933 | + u32 device_id, event_id_base; | |
| 934 | 934 | msi_alloc_info_t *info = arg; |
| 935 | 935 | irq_hw_number_t hwirq; |
| 936 | 936 | struct irq_data *irqd; |
| 937 | 937 | int ret, i; |
| 938 | 938 | |
| 939 | 939 | its_dev = info->scratchpad[0].ptr; |
| 940 | + device_id = its_dev->device_id; | |
| 940 | 941 | |
| 941 | 942 | ret = gicv5_its_alloc_eventid(its_dev, info, nr_irqs, &event_id_base); |
| 942 | 943 | if (ret) |
| @@ -946,22 +947,11 @@ static int gicv5_its_irq_domain_alloc(struct irq_domain *domain, unsigned int vi · 3 unmodified lines | ||
| 946 | 947 | if (ret) |
| 947 | 948 | goto out_eventid; |
| 948 | 949 | |
| 949 | − device_id = its_dev->device_id; | |
| 950 | + ret = irq_domain_alloc_irqs_parent(domain, virq, nr_irqs, NULL); | |
| 951 | + if (ret) | |
| 952 | + goto out_eventid; | |
| 950 | 953 | |
| 951 | 954 | for (i = 0; i < nr_irqs; i++) { |
| 952 | − ret = gicv5_alloc_lpi(); | |
| 953 | − if (ret < 0) { | |
| 954 | − pr_debug("Failed to find free LPI!\n"); | |
| 955 | − goto out_free_irqs; | |
| 956 | − } | |
| 957 | − lpi = ret; | |
| 958 | − | |
| 959 | − ret = irq_domain_alloc_irqs_parent(domain, virq + i, 1, &lpi); | |
| 960 | − if (ret) { | |
| 961 | − gicv5_free_lpi(lpi); | |
| 962 | − goto out_free_irqs; | |
| 963 | − } | |
| 964 | − | |
| 965 | 955 | /* |
| 966 | 956 | * Store eventid and deviceid into the hwirq for later use. |
| 967 | 957 | * |
| @@ -980,13 +970,6 @@ static int gicv5_its_irq_domain_alloc(struct irq_domain *domain, unsigned int vi · 12 unmodified lines | ||
| 980 | 970 | |
| 981 | 971 | return 0; |
| 982 | 972 | |
| 983 | −out_free_irqs: | |
| 984 | − while (--i >= 0) { | |
| 985 | − irqd = irq_domain_get_irq_data(domain, virq + i); | |
| 986 | − gicv5_free_lpi(irqd->parent_data->hwirq); | |
| 987 | − irq_domain_reset_irq_data(irqd); | |
| 988 | − irq_domain_free_irqs_parent(domain, virq + i, 1); | |
| 989 | − } | |
| 990 | 973 | out_eventid: |
| 991 | 974 | gicv5_its_free_eventid(its_dev, event_id_base, nr_irqs); |
| 992 | 975 | return ret; |
| @@ -1009,15 +992,14 @@ static void gicv5_its_irq_domain_free(struct irq_domain *domain, unsigned int vi · 16 unmodified lines | ||
| 1009 | 992 | bitmap_release_region(its_dev->event_map, event_id_base, |
| 1010 | 993 | get_count_order(nr_irqs)); |
| 1011 | 994 | |
| 1012 | − /* Hierarchically free irq data */ | |
| 1013 | 995 | for (i = 0; i < nr_irqs; i++) { |
| 1014 | 996 | d = irq_domain_get_irq_data(domain, virq + i); |
| 1015 | − | |
| 1016 | − gicv5_free_lpi(d->parent_data->hwirq); | |
| 1017 | 997 | irq_domain_reset_irq_data(d); |
| 1018 | − irq_domain_free_irqs_parent(domain, virq + i, 1); | |
| 1019 | 998 | } |
| 1020 | 999 | |
| 1000 | + /* Hierarchically free irq data */ | |
| 1001 | + irq_domain_free_irqs_parent(domain, virq, nr_irqs); | |
| 1002 | + | |
| 1021 | 1003 | gicv5_its_syncr(its, its_dev); |
| 1022 | 1004 | gicv5_irs_syncr(); |
| 1023 | 1005 | } |
| @@ -59,16 +59,6 @@ static void release_lpi(u32 lpi) · 58 unmodified lines | ||
| 59 | 59 | ida_free(&lpi_ida, lpi); |
| 60 | 60 | } |
| 61 | 61 | |
| 62 | −int gicv5_alloc_lpi(void) | |
| 63 | −{ | |
| 64 | − return alloc_lpi(); | |
| 65 | −} | |
| 66 | − | |
| 67 | −void gicv5_free_lpi(u32 lpi) | |
| 68 | −{ | |
| 69 | − release_lpi(lpi); | |
| 70 | −} | |
| 71 | − | |
| 72 | 62 | static void gicv5_ppi_priority_init(void) |
| 73 | 63 | { |
| 74 | 64 | write_sysreg_s(REPEAT_BYTE(GICV5_IRQ_PRI_MI), SYS_ICC_PPI_PRIORITYR0_EL1); |
| @@ -806,38 +796,64 @@ static void gicv5_lpi_config_reset(struct irq_data *d) · 731 unmodified lines | ||
| 806 | 796 | gicv5_lpi_irq_write_pending_state(d, false); |
| 807 | 797 | } |
| 808 | 798 | |
| 799 | +static void gicv5_irq_lpi_domain_free(struct irq_domain *domain, unsigned int virq, | |
| 800 | + unsigned int nr_irqs) | |
| 801 | +{ | |
| 802 | + struct irq_data *d; | |
| 803 | + | |
| 804 | + for (unsigned int i = 0; i < nr_irqs; i++, virq++) { | |
| 805 | + d = irq_domain_get_irq_data(domain, virq); | |
| 806 | + | |
| 807 | + release_lpi(d->hwirq); | |
| 808 | + | |
| 809 | + irq_set_handler(virq, NULL); | |
| 810 | + irq_domain_reset_irq_data(d); | |
| 811 | + } | |
| 812 | +} | |
| 813 | + | |
| 809 | 814 | static int gicv5_irq_lpi_domain_alloc(struct irq_domain *domain, unsigned int virq, |
| 810 | 815 | unsigned int nr_irqs, void *arg) |
| 811 | 816 | { |
| 812 | 817 | irq_hw_number_t hwirq; |
| 813 | 818 | struct irq_data *irqd; |
| 814 | − u32 *lpi = arg; | |
| 819 | + unsigned int i; | |
| 815 | 820 | int ret; |
| 816 | 821 | |
| 817 | − if (WARN_ON_ONCE(nr_irqs != 1)) | |
| 818 | − return -EINVAL; | |
| 822 | + for (i = 0; i < nr_irqs; i++) { | |
| 823 | + ret = alloc_lpi(); | |
| 824 | + if (ret < 0) | |
| 825 | + goto out_free_lpis; | |
| 826 | + hwirq = ret; | |
| 827 | + | |
| 828 | + ret = gicv5_irs_iste_alloc(hwirq); | |
| 829 | + if (ret < 0) { | |
| 830 | + /* Undo partial state first, then clean up the rest */ | |
| 831 | + release_lpi(hwirq); | |
| 832 | + goto out_free_lpis; | |
| 833 | + } | |
| 819 | 834 | |
| 820 | − hwirq = *lpi; | |
| 835 | + irqd = irq_domain_get_irq_data(domain, virq + i); | |
| 821 | 836 | |
| 822 | − irqd = irq_domain_get_irq_data(domain, virq); | |
| 837 | + irq_domain_set_info(domain, virq + i, hwirq, &gicv5_lpi_irq_chip, | |
| 838 | + NULL, handle_fasteoi_irq, NULL, NULL); | |
| 839 | + irqd_set_single_target(irqd); | |
| 823 | 840 | |
| 824 | − irq_domain_set_info(domain, virq, hwirq, &gicv5_lpi_irq_chip, NULL, | |
| 825 | − handle_fasteoi_irq, NULL, NULL); | |
| 826 | − irqd_set_single_target(irqd); | |
| 841 | + gicv5_hwirq_init(hwirq, GICV5_IRQ_PRI_MI, GICV5_HWIRQ_TYPE_LPI); | |
| 842 | + gicv5_lpi_config_reset(irqd); | |
| 843 | + } | |
| 827 | 844 | |
| 828 | − ret = gicv5_irs_iste_alloc(hwirq); | |
| 829 | − if (ret < 0) | |
| 830 | − return ret; | |
| 845 | + return 0; | |
| 831 | 846 | |
| 832 | − gicv5_hwirq_init(hwirq, GICV5_IRQ_PRI_MI, GICV5_HWIRQ_TYPE_LPI); | |
| 833 | − gicv5_lpi_config_reset(irqd); | |
| 847 | +out_free_lpis: | |
| 848 | + if (i) | |
| 849 | + gicv5_irq_lpi_domain_free(domain, virq, i); | |
| 834 | 850 | |
| 835 | − return 0; | |
| 851 | + return ret; | |
| 836 | 852 | } |
| 837 | 853 | |
| 838 | 854 | static const struct irq_domain_ops gicv5_irq_lpi_domain_ops = { |
| 839 | 855 | .alloc = gicv5_irq_lpi_domain_alloc, |
| 840 | − .free = gicv5_irq_domain_free, | |
| 856 | + .free = gicv5_irq_lpi_domain_free, | |
| 841 | 857 | }; |
| 842 | 858 | |
| 843 | 859 | void __init gicv5_init_lpi_domain(void) |
| @@ -858,30 +874,21 @@ static int gicv5_irq_ipi_domain_alloc(struct irq_domain *domain, unsigned int vi · 14 unmodified lines | ||
| 858 | 874 | unsigned int nr_irqs, void *arg) |
| 859 | 875 | { |
| 860 | 876 | struct irq_data *irqd; |
| 861 | − int ret, i; | |
| 862 | − u32 lpi; | |
| 863 | − | |
| 864 | − for (i = 0; i < nr_irqs; i++) { | |
| 865 | − ret = gicv5_alloc_lpi(); | |
| 866 | − if (ret < 0) | |
| 867 | − return ret; | |
| 868 | − | |
| 869 | − lpi = ret; | |
| 877 | + int ret; | |
| 870 | 878 | |
| 871 | − ret = irq_domain_alloc_irqs_parent(domain, virq + i, 1, &lpi); | |
| 872 | − if (ret) { | |
| 873 | − gicv5_free_lpi(lpi); | |
| 874 | − return ret; | |
| 875 | − } | |
| 879 | + ret = irq_domain_alloc_irqs_parent(domain, virq, nr_irqs, arg); | |
| 880 | + if (ret) | |
| 881 | + return ret; | |
| 876 | 882 | |
| 877 | − irqd = irq_domain_get_irq_data(domain, virq + i); | |
| 883 | + for (unsigned int i = 0; i < nr_irqs; i++, virq++) { | |
| 884 | + irqd = irq_domain_get_irq_data(domain, virq); | |
| 878 | 885 | |
| 879 | − irq_domain_set_hwirq_and_chip(domain, virq + i, i, | |
| 880 | − &gicv5_ipi_irq_chip, NULL); | |
| 886 | + irq_domain_set_hwirq_and_chip(domain, virq, i, | |
| 887 | + &gicv5_ipi_irq_chip, NULL); | |
| 881 | 888 | |
| 882 | 889 | irqd_set_single_target(irqd); |
| 883 | 890 | |
| 884 | − irq_set_handler(virq + i, handle_percpu_irq); | |
| 891 | + irq_set_handler(virq, handle_percpu_irq); | |
| 885 | 892 | } |
| 886 | 893 | |
| 887 | 894 | return 0; |
| @@ -899,12 +906,11 @@ static void gicv5_irq_ipi_domain_free(struct irq_domain *domain, unsigned int vi · 11 unmodified lines | ||
| 899 | 906 | if (!d) |
| 900 | 907 | return; |
| 901 | 908 | |
| 902 | − gicv5_free_lpi(d->parent_data->hwirq); | |
| 903 | − | |
| 904 | 909 | irq_set_handler(virq + i, NULL); |
| 905 | 910 | irq_domain_reset_irq_data(d); |
| 906 | − irq_domain_free_irqs_parent(domain, virq + i, 1); | |
| 907 | 911 | } |
| 912 | + | |
| 913 | + irq_domain_free_irqs_parent(domain, virq, nr_irqs); | |
| 908 | 914 | } |
| 909 | 915 | |
| 910 | 916 | static const struct irq_domain_ops gicv5_irq_ipi_domain_ops = { |
| @@ -415,8 +415,7 @@ static int meson_s4_gpio_irq_set_type(struct meson_gpio_irq_controller *ctl, · 414 unmodified lines | ||
| 415 | 415 | if (type & (IRQ_TYPE_EDGE_RISING | IRQ_TYPE_EDGE_FALLING)) |
| 416 | 416 | val |= BIT(ctl->params->edge_single_offset + idx); |
| 417 | 417 | |
| 418 | − meson_gpio_irq_update_bits(ctl, params->edge_pol_reg, | |
| 419 | − BIT(idx) | BIT(12 + idx), val); | |
| 418 | + meson_gpio_irq_update_bits(ctl, REG_EDGE_POL, BIT(idx) | BIT(12 + idx), val); | |
| 420 | 419 | return 0; |
| 421 | 420 | }; |
| 422 | 421 | |
| @@ -158,6 +158,8 @@ static int imsic_dying_cpu(unsigned int cpu) · 157 unmodified lines | ||
| 158 | 158 | /* Cleanup IPIs */ |
| 159 | 159 | imsic_ipi_dying_cpu(); |
| 160 | 160 | |
| 161 | + imsic_local_sync_all(false); | |
| 162 | + | |
| 161 | 163 | /* Mark per-CPU IMSIC state as offline */ |
| 162 | 164 | imsic_state_offline(); |
| 163 | 165 | |
| @@ -425,9 +425,6 @@ struct gicv5_its_itt_cfg { · 424 unmodified lines | ||
| 425 | 425 | void gicv5_init_lpis(u32 max); |
| 426 | 426 | void gicv5_deinit_lpis(void); |
| 427 | 427 | |
| 428 | −int gicv5_alloc_lpi(void); | |
| 429 | −void gicv5_free_lpi(u32 lpi); | |
| 430 | − | |
| 431 | 428 | void __init gicv5_its_of_probe(struct device_node *parent); |
| 432 | 429 | void __init gicv5_its_acpi_probe(void); |
| 433 | 430 | #endif |
| @@ -14,6 +14,7 @@ · 13 unmodified lines | ||
| 14 | 14 | #include <linux/interrupt.h> |
| 15 | 15 | #include <linux/kernel_stat.h> |
| 16 | 16 | #include <linux/irqdomain.h> |
| 17 | +#include <linux/preempt.h> | |
| 17 | 18 | #include <linux/random.h> |
| 18 | 19 | |
| 19 | 20 | #include <trace/events/irq.h> |
| @@ -893,7 +894,10 @@ void handle_percpu_irq(struct irq_desc *desc) · 873 unmodified lines | ||
| 893 | 894 | * |
| 894 | 895 | * action->percpu_dev_id is a pointer to percpu variables which |
| 895 | 896 | * contain the real device id for the cpu on which this handler is |
| 896 | − * called | |
| 897 | + * called. | |
| 898 | + * | |
| 899 | + * May be used for NMI interrupt lines, and so may be called in IRQ or NMI | |
| 900 | + * context. | |
| 897 | 901 | */ |
| 898 | 902 | void handle_percpu_devid_irq(struct irq_desc *desc) |
| 899 | 903 | { |
| @@ -930,7 +934,8 @@ void handle_percpu_devid_irq(struct irq_desc *desc) · 30 unmodified lines | ||
| 930 | 934 | enabled ? " and unmasked" : "", irq, cpu); |
| 931 | 935 | } |
| 932 | 936 | |
| 933 | − add_interrupt_randomness(irq); | |
| 937 | + if (!in_nmi()) | |
| 938 | + add_interrupt_randomness(irq); | |
| 934 | 939 | |
| 935 | 940 | if (chip->irq_eoi) |
| 936 | 941 | chip->irq_eoi(&desc->irq_data); |
| @@ -292,6 +292,12 @@ void irq_work_sync(struct irq_work *work) · 291 unmodified lines | ||
| 292 | 292 | !arch_irq_work_has_interrupt()) { |
| 293 | 293 | rcuwait_wait_event(&work->irqwait, !irq_work_is_busy(work), |
| 294 | 294 | TASK_UNINTERRUPTIBLE); |
| 295 | + /* | |
| 296 | + * Ensure irq_work_single() does not access @work | |
| 297 | + * after removing IRQ_WORK_BUSY. It is always | |
| 298 | + * accessed within a RCU-read section. | |
| 299 | + */ | |
| 300 | + synchronize_rcu(); | |
| 295 | 301 | return; |
| 296 | 302 | } |
| 297 | 303 | |
| @@ -302,6 +308,7 @@ EXPORT_SYMBOL_GPL(irq_work_sync); · 4 unmodified lines | ||
| 302 | 308 | |
| 303 | 309 | static void run_irq_workd(unsigned int cpu) |
| 304 | 310 | { |
| 311 | + guard(rcu)(); | |
| 305 | 312 | irq_work_run_list(this_cpu_ptr(&lazy_list)); |
| 306 | 313 | } |
| 307 | 314 | |