* [GIT pull] irq/core for v7.4-rc1
@ 2026-10-21 9:50 Thomas Gleixner
2026-10-21 9:51 ` [GIT pull] irq/drivers " Thomas Gleixner
` (5 more replies)
0 siblings, 6 replies; 12+ messages in thread
From: Thomas Gleixner @ 2026-10-21 9:50 UTC (permalink / raw)
To: Linus Torvalds; +Cc: linux-kernel, x86
Linus,
please pull the latest irq/core branch from:
git://git.kernel.org/pub/scm/linux/kernel/git/tip/tip.git irq-core-2026-10-20
up to: e74798f2f8e1: genirq/proc: Fix missing CPU header in /proc/interrupts
Updates for the generic interrupt subsystem:
- Skip programming of MSI/MSI-X related data while a PCI channel is
offline (Link is down) in the error recovery code path. Accessing an
offline channel results in a BUS error which affects the underlying
Root Port.
Check for pci_channel_offline() and if true avoid the writes and just
cache the modifications which are subsequently applied when the Link
has recovered.
- Prevent a stack UAF when a faulting interrupt thread exits. On X86 the
crash causes the stack to be discarded, which contains the callback
head for the exit task_work to run the destructor. If the stack is
clobbered before the destructor runs, then the indirect call of the
task work function ends up in a random place. Move the callback head
into the interrupt action to prevent that.
- Prevent an UAF in the interrupt thread exit code which can happen when a
faulting interrupt thread exits and races with a concurrent free_irq()
by synchronizing free_irq() against thread exit.
- Ensure that interrupt context accounting is accurate on interrupt
return when checking for soft interrupt invocation.
The code removed the hard interrupt context prematurely so that the
subsequent code appeared to be in task context according to the preempt
counter. That confuses ftrace, perf and K*SAN which get their recording
context wrong. On Preempt-RT this can result in misbehaviour when BPF
programs are attached to the affected region.
Cure it by preserving the hard interrupt count up to the point where
soft interrupts are actually invoked and switch directly to soft
interrupt processing right there. That avoids the intermediate "in task
context" state.
- The usual small fixes and improvements
Thanks,
tglx
------------------>
Enlin Mu (1):
softirq: Remove redundant h->action from preempt_count mismatch error
Haris Okanovic (1):
genirq/proc: Fix missing CPU header in /proc/interrupts
Hemanth Selam (1):
genirq: Fix typos in comments
Karl Mehltretter (1):
softirq: Preserve interrupt context on return from interrupt
Lukas Beckmann (1):
genirq: Discard cpumask of irqaffinity= boot parameter if parsing fails
Richard Patel (2):
genirq: Defer kfree(irqaction) until irq_thread_dtor() finishes
genirq: Move interrupt thread exit_work object to irqaction
Vidya Sagar (1):
PCI/MSI: Skip MSI/MSI-X programming while the channel is offline
Documentation/core-api/entry.rst | 16 ++++++----
drivers/pci/msi/msi.c | 15 ++++++++--
drivers/pci/msi/msi.h | 8 +++++
include/linux/interrupt.h | 2 ++
kernel/irq/internals.h | 2 ++
kernel/irq/irqdesc.c | 10 ++++++-
kernel/irq/manage.c | 31 +++++++++++++------
kernel/irq/msi.c | 4 +--
kernel/irq/proc.c | 24 ++++++++++-----
kernel/softirq.c | 64 ++++++++++++++++++++++++++++++----------
10 files changed, 134 insertions(+), 42 deletions(-)
diff --git a/Documentation/core-api/entry.rst b/Documentation/core-api/entry.rst
index 79fdaed954d9..09942135aad1 100644
--- a/Documentation/core-api/entry.rst
+++ b/Documentation/core-api/entry.rst
@@ -197,8 +197,9 @@ return true, handles NOHZ tick state and interrupt time accounting. This
means that up to the point where irq_enter_rcu() is invoked in_hardirq()
returns false.
-irq_exit_rcu() handles interrupt time accounting, undoes the preemption
-count update and eventually handles soft interrupts and NOHZ tick state.
+irq_exit_rcu() handles interrupt time accounting, handles soft interrupts if
+possible, handles the NOHZ tick state and finally undoes the preemption count
+update.
In theory, the preemption count could be updated in irqentry_enter(). In
practice, deferring this update to irq_enter_rcu() allows the preemption-count
@@ -207,10 +208,13 @@ irqentry_exit(), which are described in the next paragraph. The only downside
is that the early entry code up to irq_enter_rcu() must be aware that the
preemption count has not yet been updated with the HARDIRQ_OFFSET state.
-Note that irq_exit_rcu() must remove HARDIRQ_OFFSET from the preemption count
-before it handles soft interrupts, whose handlers must run in BH context rather
-than irq-disabled context. In addition, irqentry_exit() might schedule, which
-also requires that HARDIRQ_OFFSET has been removed from the preemption count.
+Note that soft interrupt handlers must run in BH context rather than in hard
+interrupt context. irq_exit_rcu() therefore replaces HARDIRQ_OFFSET with
+SOFTIRQ_OFFSET in the preemption count while it handles soft interrupts and
+puts HARDIRQ_OFFSET back afterwards, so that the remaining interrupt exit work
+is still attributed to the interrupt. HARDIRQ_OFFSET is removed before
+irq_exit_rcu() returns because irqentry_exit() might schedule, which requires
+that HARDIRQ_OFFSET has been removed from the preemption count.
Even though interrupt handlers are expected to run with local interrupts
disabled, interrupt nesting is common from an entry/exit perspective. For
diff --git a/drivers/pci/msi/msi.c b/drivers/pci/msi/msi.c
index 80a9db417dc8..8a0133a6a286 100644
--- a/drivers/pci/msi/msi.c
+++ b/drivers/pci/msi/msi.c
@@ -133,7 +133,9 @@ void pci_msi_update_mask(struct msi_desc *desc, u32 clear, u32 set)
raw_spin_lock_irqsave(lock, flags);
desc->pci.msi_mask &= ~clear;
desc->pci.msi_mask |= set;
- pci_write_config_dword(dev, desc->pci.mask_pos, desc->pci.msi_mask);
+ /* Cached either way, for __pci_restore_msi_state() to replay */
+ if (!pci_channel_offline(dev))
+ pci_write_config_dword(dev, desc->pci.mask_pos, desc->pci.msi_mask);
raw_spin_unlock_irqrestore(lock, flags);
}
@@ -249,7 +251,7 @@ void __pci_write_msi_msg(struct msi_desc *entry, struct msi_msg *msg)
{
struct pci_dev *dev = msi_desc_to_pci_dev(entry);
- if (dev->current_state != PCI_D0 || pci_dev_is_disconnected(dev)) {
+ if (dev->current_state != PCI_D0 || pci_channel_offline(dev)) {
/* Don't touch the hardware now */
} else if (entry->pci.msi_attrib.is_msix) {
pci_write_msg_msix(entry, msg);
@@ -976,6 +978,15 @@ int pci_msix_write_tph_tag(struct pci_dev *pdev, unsigned int index, u16 tag)
if (!msi_desc || msi_desc->pci.msi_attrib.is_virtual)
return -ENXIO;
+ /*
+ * The tag update below is a write to the MSI-X Table followed by a
+ * flush read, neither of which can be completed while the Link is
+ * down. Check as late as possible, as the Link can go down at any
+ * point. Let the caller disable TPH.
+ */
+ if (pci_channel_offline(pdev))
+ return -EIO;
+
FIELD_MODIFY(PCI_MSIX_ENTRY_CTRL_ST, &msi_desc->pci.msix_ctrl, tag);
pci_msix_write_vector_ctrl(msi_desc, msi_desc->pci.msix_ctrl);
/* Flush the write */
diff --git a/drivers/pci/msi/msi.h b/drivers/pci/msi/msi.h
index 0b420b319f50..f987cf897264 100644
--- a/drivers/pci/msi/msi.h
+++ b/drivers/pci/msi/msi.h
@@ -36,6 +36,10 @@ static inline void pci_msix_write_vector_ctrl(struct msi_desc *desc, u32 ctrl)
{
void __iomem *desc_addr = pci_msix_desc_addr(desc);
+ /* The Table is unreachable while the Link is down */
+ if (pci_channel_offline(msi_desc_to_pci_dev(desc)))
+ return;
+
if (desc->pci.msi_attrib.can_mask)
writel(ctrl, desc_addr + PCI_MSIX_ENTRY_VECTOR_CTRL);
}
@@ -43,6 +47,10 @@ static inline void pci_msix_write_vector_ctrl(struct msi_desc *desc, u32 ctrl)
static inline void pci_msix_mask(struct msi_desc *desc)
{
desc->pci.msix_ctrl |= PCI_MSIX_ENTRY_CTRL_MASKBIT;
+
+ if (pci_channel_offline(msi_desc_to_pci_dev(desc)))
+ return;
+
pci_msix_write_vector_ctrl(desc, desc->pci.msix_ctrl);
/* Flush write to device */
readl(desc->pci.mask_base);
diff --git a/include/linux/interrupt.h b/include/linux/interrupt.h
index 3bf969ad8fe0..50745608dc15 100644
--- a/include/linux/interrupt.h
+++ b/include/linux/interrupt.h
@@ -119,6 +119,7 @@ typedef irqreturn_t (*irq_handler_t)(int, void *);
* @thread_flags: flags related to @thread
* @thread_mask: bitmask for keeping track of @thread activity
* @dir: pointer to the proc/irq/NN/name entry
+ * @thread_dtor: task_work for @thread irregular exit cleanup
*/
struct irqaction {
irq_handler_t handler;
@@ -137,6 +138,7 @@ struct irqaction {
unsigned long thread_mask;
const char *name;
struct proc_dir_entry *dir;
+ struct callback_head thread_dtor;
} ____cacheline_internodealigned_in_smp;
extern irqreturn_t no_action(int cpl, void *dev_id);
diff --git a/kernel/irq/internals.h b/kernel/irq/internals.h
index 0ce21dd45404..2c30e7537987 100644
--- a/kernel/irq/internals.h
+++ b/kernel/irq/internals.h
@@ -36,6 +36,7 @@ extern struct irqaction chained_action;
* IRQTF_AFFINITY - irq thread is requested to adjust affinity
* IRQTF_FORCED_THREAD - irq action is force threaded
* IRQTF_READY - signals that irq thread is ready
+ * IRQTF_EXIT_WORK - irq thread exit task_work is pending
*/
enum {
IRQTF_RUNTHREAD,
@@ -43,6 +44,7 @@ enum {
IRQTF_AFFINITY,
IRQTF_FORCED_THREAD,
IRQTF_READY,
+ IRQTF_EXIT_WORK,
};
/*
diff --git a/kernel/irq/irqdesc.c b/kernel/irq/irqdesc.c
index 3a818f07a101..7cbcc3fb9e6d 100644
--- a/kernel/irq/irqdesc.c
+++ b/kernel/irq/irqdesc.c
@@ -28,7 +28,15 @@ static struct lock_class_key irq_desc_lock_class;
static int __init irq_affinity_setup(char *str)
{
alloc_bootmem_cpumask_var(&irq_default_affinity);
- cpulist_parse(str, irq_default_affinity);
+ if (cpulist_parse(str, irq_default_affinity) < 0) {
+ /*
+ * Clear the mask so that init_irq_default_affinity()
+ * defaults it later.
+ */
+ cpumask_clear(irq_default_affinity);
+ pr_warn("irqaffinity: incorrect CPU range, using default\n");
+ return 1;
+ }
/*
* Set at least the boot cpu. We don't want to end up with
* bugreports caused by random commandline masks
diff --git a/kernel/irq/manage.c b/kernel/irq/manage.c
index 57eff26fa646..3b647d9b458a 100644
--- a/kernel/irq/manage.c
+++ b/kernel/irq/manage.c
@@ -21,6 +21,7 @@
#include <linux/sched/isolation.h>
#include <uapi/linux/sched/types.h>
#include <linux/task_work.h>
+#include <linux/wait_bit.h>
#include "internals.h"
@@ -1176,17 +1177,14 @@ void wake_threads_waitq(struct irq_desc *desc)
wake_up(&desc->wait_for_threads);
}
-static void irq_thread_dtor(struct callback_head *unused)
+static void __irq_thread_dtor(struct irqaction *action)
{
struct task_struct *tsk = current;
struct irq_desc *desc;
- struct irqaction *action;
if (WARN_ON_ONCE(!(current->flags & PF_EXITING)))
return;
- action = kthread_data(tsk);
-
pr_err("exiting task \"%s\" (%d) is an active IRQ thread (irq %d)\n",
tsk->comm, tsk->pid, action->irq);
@@ -1203,6 +1201,14 @@ static void irq_thread_dtor(struct callback_head *unused)
irq_finalize_oneshot(desc, action);
}
+static void irq_thread_dtor(struct callback_head *work)
+{
+ struct irqaction *action = container_of(work, struct irqaction, thread_dtor);
+
+ __irq_thread_dtor(action);
+ clear_and_wake_up_bit(IRQTF_EXIT_WORK, &action->thread_flags);
+}
+
static void irq_wake_secondary(struct irq_desc *desc, struct irqaction *action)
{
struct irqaction *secondary = action->secondary;
@@ -1244,7 +1250,6 @@ static void wake_up_and_wait_for_irq_thread_ready(struct irq_desc *desc,
*/
static int irq_thread(void *data)
{
- struct callback_head on_exit_work;
struct irqaction *action = data;
struct irq_desc *desc = irq_to_desc(action->irq);
irqreturn_t (*handler_fn)(struct irq_desc *desc,
@@ -1263,8 +1268,9 @@ static int irq_thread(void *data)
else
handler_fn = irq_thread_fn;
- init_task_work(&on_exit_work, irq_thread_dtor);
- task_work_add(current, &on_exit_work, TWA_NONE);
+ set_bit(IRQTF_EXIT_WORK, &action->thread_flags);
+ init_task_work(&action->thread_dtor, irq_thread_dtor);
+ task_work_add(current, &action->thread_dtor, TWA_NONE);
while (!irq_wait_for_interrupt(desc, action)) {
irqreturn_t action_ret;
@@ -1283,6 +1289,7 @@ static int irq_thread(void *data)
* oneshot mask bit can be set.
*/
task_work_cancel_func(current, irq_thread_dtor);
+ clear_and_wake_up_bit(IRQTF_EXIT_WORK, &action->thread_flags);
return 0;
}
@@ -1853,6 +1860,12 @@ __setup_irq(unsigned int irq, struct irq_desc *desc, struct irqaction *new)
return ret;
}
+static void irq_stop_thread(struct irqaction *action)
+{
+ kthread_stop_put(action->thread);
+ wait_on_bit(&action->thread_flags, IRQTF_EXIT_WORK, TASK_UNINTERRUPTIBLE);
+}
+
/*
* Internal function to unregister an irqaction - used to free
* regular and special interrupts that are part of the architecture.
@@ -1958,9 +1971,9 @@ static struct irqaction *__free_irq(struct irq_desc *desc, void *dev_id)
* the same bit to a newly requested action.
*/
if (action->thread) {
- kthread_stop_put(action->thread);
+ irq_stop_thread(action);
if (action->secondary && action->secondary->thread)
- kthread_stop_put(action->secondary->thread);
+ irq_stop_thread(action->secondary);
}
/* Last action releases resources */
diff --git a/kernel/irq/msi.c b/kernel/irq/msi.c
index fb5f372215bf..b22d74496af1 100644
--- a/kernel/irq/msi.c
+++ b/kernel/irq/msi.c
@@ -142,7 +142,7 @@ static int msi_insert_desc(struct device *dev, struct msi_desc *desc,
* insert it at @init_desc->msi_index
*
* @dev: Pointer to the device for which the descriptor is allocated
- * @domid: The id of the interrupt domain to which the desriptor is added
+ * @domid: The id of the interrupt domain to which the descriptor is added
* @init_desc: Pointer to an MSI descriptor to initialize the new descriptor
*
* Return: 0 on success or an appropriate failure code.
@@ -416,7 +416,7 @@ EXPORT_SYMBOL_GPL(msi_domain_first_desc);
* @domid: The id of the interrupt domain which should be walked.
* @filter: Descriptor state filter
*
- * The first invocation of msi_next_desc() has to be preceeded by a
+ * The first invocation of msi_next_desc() has to be preceded by a
* successful invocation of __msi_first_desc(). Consecutive invocations are
* only valid if the previous one was successful. All these operations have
* to be done within the same MSI mutex held region.
diff --git a/kernel/irq/proc.c b/kernel/irq/proc.c
index 1b835725f7b1..0c0df16de581 100644
--- a/kernel/irq/proc.c
+++ b/kernel/irq/proc.c
@@ -462,7 +462,7 @@ int __weak arch_show_interrupts(struct seq_file *p, int prec)
static DEFINE_RAW_SPINLOCK(irq_proc_constraints_lock);
static struct irq_proc_constraints {
- bool print_header;
+ loff_t first_pos;
unsigned int num_prec;
unsigned int chip_width;
} irq_proc_constraints __read_mostly = {
@@ -543,14 +543,13 @@ static int irq_seq_show(struct seq_file *p, void *v)
struct irqaction *action;
/* Print header for the first interrupt? */
- if (constr->print_header) {
+ if (p->index == constr->first_pos) {
unsigned int cpu;
seq_printf(p, "%*s", constr->num_prec + 8, "");
for_each_online_cpu(cpu)
seq_printf(p, "CPU%-8d", cpu);
seq_putc(p, '\n');
- constr->print_header = false;
}
if (desc == ARCH_PROC_IRQDESC)
@@ -635,14 +634,25 @@ static void *irq_seq_next_desc(loff_t *pos)
static void *irq_seq_start(struct seq_file *f, loff_t *pos)
{
- if (!*pos) {
- struct irq_proc_constraints *constr = f->private;
+ struct irq_proc_constraints *constr = f->private;
+ bool first = !*pos;
+ void *ret;
+ if (first) {
constr->num_prec = READ_ONCE(irq_proc_constraints.num_prec);
constr->chip_width = READ_ONCE(irq_proc_constraints.chip_width);
- constr->print_header = true;
}
- return irq_seq_next_desc(pos);
+
+ ret = irq_seq_next_desc(pos);
+
+ /*
+ * Record position of the first output record so that irq_seq_show()
+ * knows where to write the header.
+ */
+ if (first)
+ constr->first_pos = *pos;
+
+ return ret;
}
static void *irq_seq_next(struct seq_file *f, void *v, loff_t *pos)
diff --git a/kernel/softirq.c b/kernel/softirq.c
index 5d02c36c40e3..7fc2200392cd 100644
--- a/kernel/softirq.c
+++ b/kernel/softirq.c
@@ -350,8 +350,8 @@ static inline void ksoftirqd_run_end(void)
local_irq_enable();
}
-static inline void softirq_handle_begin(void) { }
-static inline void softirq_handle_end(void) { }
+static inline bool softirq_handle_begin(void) { return false; }
+static inline void softirq_handle_end(bool from_irq_exit) { }
static inline bool should_wake_ksoftirqd(void)
{
@@ -481,15 +481,38 @@ void __local_bh_enable_ip(unsigned long ip, unsigned int cnt)
}
EXPORT_SYMBOL(__local_bh_enable_ip);
-static inline void softirq_handle_begin(void)
+static inline bool softirq_handle_begin(void)
{
- __local_bh_disable_ip(_RET_IP_, SOFTIRQ_OFFSET);
+ if (!in_hardirq()) {
+ __local_bh_disable_ip(_RET_IP_, SOFTIRQ_OFFSET);
+ return false;
+ }
+
+ /*
+ * Only reached from irq_exit(), with HARDIRQ_OFFSET still set.
+ * Replace it with SOFTIRQ_OFFSET before handle_softirqs() enables
+ * interrupts. Use the raw operation to preserve the preemption
+ * disable location recorded by irq_enter_rcu(), and update lockdep
+ * directly.
+ */
+ __preempt_count_sub(HARDIRQ_OFFSET - SOFTIRQ_OFFSET);
+ lockdep_softirqs_off(_RET_IP_);
+ WARN_ON_ONCE(irq_count() != SOFTIRQ_OFFSET);
+
+ return true;
}
-static inline void softirq_handle_end(void)
+static inline void softirq_handle_end(bool from_irq_exit)
{
- __local_bh_enable(SOFTIRQ_OFFSET);
- WARN_ON_ONCE(in_interrupt());
+ if (!from_irq_exit) {
+ __local_bh_enable(SOFTIRQ_OFFSET);
+ WARN_ON_ONCE(in_interrupt());
+ return;
+ }
+
+ lockdep_softirqs_on(_RET_IP_);
+ __preempt_count_add(HARDIRQ_OFFSET - SOFTIRQ_OFFSET);
+ WARN_ON_ONCE(irq_count() != HARDIRQ_OFFSET);
}
static inline void ksoftirqd_run_begin(void)
@@ -605,6 +628,7 @@ static void handle_softirqs(bool ksirqd)
unsigned long old_flags = current->flags;
int max_restart = MAX_SOFTIRQ_RESTART;
struct softirq_action *h;
+ bool from_irq_exit;
bool in_hardirq;
__u32 pending;
int softirq_bit;
@@ -618,7 +642,7 @@ static void handle_softirqs(bool ksirqd)
pending = local_softirq_pending();
- softirq_handle_begin();
+ from_irq_exit = softirq_handle_begin();
in_hardirq = lockdep_softirq_start();
account_softirq_enter(current);
@@ -645,8 +669,8 @@ static void handle_softirqs(bool ksirqd)
h->action();
trace_softirq_exit(vec_nr);
if (unlikely(prev_count != preempt_count())) {
- pr_err("huh, entered softirq %u %s %p with preempt_count %08x, exited with %08x?\n",
- vec_nr, softirq_to_name[vec_nr], h->action,
+ pr_err("huh, entered softirq %u %s with preempt_count %08x, exited with %08x?\n",
+ vec_nr, softirq_to_name[vec_nr],
prev_count, preempt_count());
preempt_count_set(prev_count);
}
@@ -670,7 +694,7 @@ static void handle_softirqs(bool ksirqd)
account_softirq_exit(current);
lockdep_softirq_end(in_hardirq);
- softirq_handle_end();
+ softirq_handle_end(from_irq_exit);
current_restore_flags(old_flags, PF_MEMALLOC);
}
@@ -716,7 +740,7 @@ static inline void tick_irq_exit(void)
/* Make sure that timer wheel updates are propagated */
if ((sched_core_idle_cpu(cpu) && !need_resched()) || tick_nohz_full_cpu(cpu)) {
- if (!in_hardirq())
+ if (hardirq_count() == HARDIRQ_OFFSET)
tick_nohz_irq_exit();
}
#endif
@@ -748,8 +772,12 @@ static inline void __irq_exit_rcu(void)
lockdep_assert_irqs_disabled();
#endif
account_hardirq_exit(current);
- preempt_count_sub(HARDIRQ_OFFSET);
- if (!in_interrupt() && local_softirq_pending()) {
+
+ /*
+ * HARDIRQ_OFFSET is still set. Only the outermost interrupt handles
+ * softirqs, and only if it did not hit a softirq or BH disabled section.
+ */
+ if (irq_count() == HARDIRQ_OFFSET && local_softirq_pending()) {
/*
* If we left hrtimers unarmed, make sure to arm them now,
* before enabling interrupts to run softirq.
@@ -758,11 +786,17 @@ static inline void __irq_exit_rcu(void)
invoke_softirq();
}
+ /*
+ * Wake the timer thread even if the interrupt hit a softirq or a
+ * section with BHs disabled. Only the outermost interrupt can wake it.
+ */
if (IS_ENABLED(CONFIG_IRQ_FORCED_THREADING) && force_irqthreads() &&
- local_timers_pending_force_th() && !(in_nmi() | in_hardirq()))
+ local_timers_pending_force_th() &&
+ hardirq_count() == HARDIRQ_OFFSET)
wake_timersd();
tick_irq_exit();
+ preempt_count_sub(HARDIRQ_OFFSET);
}
/**
^ permalink raw reply [flat|nested] 12+ messages in thread
* [GIT pull] irq/drivers for v7.4-rc1
2026-10-21 9:50 [GIT pull] irq/core for v7.4-rc1 Thomas Gleixner
@ 2026-10-21 9:51 ` Thomas Gleixner
2026-10-21 21:43 ` pr-tracker-bot
2026-10-21 9:51 ` [GIT pull] timers/clocksource " Thomas Gleixner
` (4 subsequent siblings)
5 siblings, 1 reply; 12+ messages in thread
From: Thomas Gleixner @ 2026-10-21 9:51 UTC (permalink / raw)
To: Linus Torvalds; +Cc: linux-kernel, x86
Linus,
please pull the latest irq/drivers branch from:
git://git.kernel.org/pub/scm/linux/kernel/git/tip/tip.git irq-drivers-2026-10-20
up to: 9376df349675: irqchip/al-fic: Support per-child affinity via interrupt redirection
Updates for interrupt chip drivers:
- Remove the futile attempts to cleanup resources when the probing of the
main interrupt chip fails for SiFIVE/PLIC devices. If that probe fails
the system will stall immediately afterwards in the boot process
without reaching a state where the error could be diagnosed. The lack
of resource cleanup led to a frenzy of AI detected/assisted "fixes"
which just make the code pretend to be correct for no value.
Remove the existing half baked cleanup and immediately panic the
machine when the probe fails. That's far more useful than letting it
proceed and stall out a millisecond later with no information about the
root cause.
- A series of fixes for the ARM GIC-V3 driver addressing a potential
double free, a bitmask overrun on 32-bit ARM and a OF node leak.
- Automatically set the IRQ_DOMAIN_FLAG_DESTROY_GC when a interrupt
domain is created that also allocates the generic interrupt chip during
domain creation. That removes the requirement for manually setting the
flag in the domain creation info structure when it contains a valid
entry for allocating the generic interrupt chip. Remove the therefore
redundant setters of IRQ_DOMAIN_FLAG_DESTROY_GC in the existing
drivers.
- Add support for Annapurna Labs FIC version 2 and 3 and implement
affinity control for the demultiplexed interrupts by utilizing the
recently merged interrupt redirection infrastructure.
- Convert the ACPI device ID structures to named initializers
- Consolidate the IPI related constants for RISC-V interrupt controllers
- Add support for Qualcomm Purwa SoC interrupt chip and consolidate the
workaround for the previous version in Hamoa SoCs.
- The usual small fixes and improvements all over the place
Thanks,
tglx
------------------>
Adrian Ng Ho Yin (1):
irqchip/gic-v3: Add Altera SoCFPGA Agilex5 GIC600 DMA32 erratum workaround
Andrew Davis (2):
irqchip/pruss-intc: Use match data directly
irqchip/pruss-intc: Use scoped lock guard and devm_mutex_init
Anirudha Sarangi (2):
dt-bindings: interrupt-controller: Add Xilinx Versal NET SMMU CSR
irqchip: Add Xilinx Versal NET SMMU CSR interrupt controller driver
Arnd Bergmann (1):
irqchip/qcom-pdc: Fix linking against QCOM_SCM firmware
Au Dang (1):
irqchip/renesas-rzv2h: Preserve TIEN state in rzv2h_tint_set_type()
Benoît Monin (3):
irqchip/mips-gic: Fix unbalanced cm_core_lock in for_each_online_cpu_gic()
irqchip/mips-gic: Fix recursive acquisition of gic_lock in gic_set_affinity()
irqchip/mips-gic: Transfer interrupt mask state across clusters
Eliav Farber (9):
irqchip/al-fic: Fix coding style issues
irqchip/al-fic: Use %pe to print init error
irqchip/al-fic: Use %pOF and raise init log level
irqchip/al-fic: Keep the device_node instead of a cached name string
irqchip/al-fic: Switch to shared parent interrupt
dt-bindings: interrupt-controller: amazon,al-fic: Add mask selection
irqchip/al-fic: Support error and fatal outputs and FIC v2
irqchip/al-fic: Add support for FIC v3
irqchip/al-fic: Support per-child affinity via interrupt redirection
Fuad Tabba (6):
irqchip/gic-v3: Release the partition node in gic_irq_get_fwspec_info()
irqchip/gic-v3-its: Don't clamp nvecs to zero when id_bits is 32
irqchip/gic-v3-its: Clear vpt_page after freeing the pending table
irqchip/gic-v3-its: Don't free a vPE table shared with another ITS
irqchip/gic-v3-its: Don't free the tables of an enabled ITS
irqchip/gic-v3: Don't register a redistributor that was never counted
GUO Ren (XuanTie) (5):
riscv: smp: Move enum ipi_message_type to asm/smp.h
riscv: sbi: Use IPI_MAX for SBI IPI muxing
clocksource: clint: Use IPI_MAX for IPI muxing
irqchip/aclint-sswi: Use IPI_MAX for IPI muxing
irqchip/imsic: Use IPI_MAX instead of IMSIC_NR_IPI
Guangshuo Li (1):
irqchip/imx-intmux: Fix runtime PM teardown on remove
Hemanth Selam (1):
irqchip: Fix typos and repeated words in comments
Huacai Chen (1):
irqchip/loongson-eiointc: Use pa_to_nid() rather than hard-code
Igor Velkov (1):
irqchip/meson-gpio: Allow the GPIO interrupts to wake the system
Jiangshan Yi (1):
irqchip/gic-v5: Install root IRQ handler last in gicv5_init_common()
Karl Mehltretter (3):
irqchip/gic-v3-its: Don't WARN on LPI free allocation failure
irqchip/atmel-aic[5]: Disable interrupts in the irq_pm_shutdown() callbacks
irqchip/jcore-aic: Fix lost IPIs on SMP
Krzysztof Kozlowski (3):
irqchip/qcom: Enable compile testing for QCOM_PDC and QCOM_MPM
irqchip/qcom: Add defaults for desired SoC drivers
arm64: defconfig: Drop redundant Qualcomm irqchip entries
Lorenzo Pieralisi (2):
dt-bindings: interrupt-controller: gicv5: Relax IRS properties constraints
irqchip/gic-v5: Fix standalone IRSes OF probe
Marc Zyngier (1):
irqchip/gic: Simplify Maintenance Interrupt ACPI parsing
Maulik Shah (3):
dt-bindings: interrupt-controller: qcom,pdc: Document Purwa PDC
irqchip/qcom-pdc: Add purwa compatible for PDC secondary mode
arm64: dts: qcom: purwa: Drop the Hamoa workaround for PDC
Nam Cao (1):
irqchip/sifive-plic: Panic if initialization fails
Pawel Zalewski (1):
irqchip: Use named initializers for acpi_device_id
Pengpeng Hou (4):
irqchip/riscv-imsic: Use GENMASK for base address masks
irqchip/mst-intc: Validate the DT interrupt range
irqchip/sifive-plic: Avoid signed shift in interrupt enable mask
irqchip/riscv-imsic: Reject overlapping group index fields
Prajwal N (1):
irqchip/riscv-imsic: Expose the fwnode via the global config
Qingshuang Fu (3):
irqdomain: Set IRQ_DOMAIN_FLAG_DESTROY_GC in __irq_domain_instantiate()
irqchip/lan966x-oic: Drop redundant IRQ_DOMAIN_FLAG_DESTROY_GC
soc/fsl/qe: qe_ports_ic: Drop redundant IRQ_DOMAIN_FLAG_DESTROY_GC
Triet Hoang (1):
irqchip: st: Convert to DEFINE_SIMPLE_DEV_PM_OPS()
Vladimir Murzin (1):
irqchip/gic-v3: Ensure change to DS is visible
Wentao Liang (1):
irqchip/mchp-eic: Fix clock reference leak in mchp_eic_probe()
Zhu Ling (1):
irqchip/gic: Fix UBSAN shift-out-of-bounds in GIC helpers
Documentation/arch/arm64/silicon-errata.rst | 3 +
.../interrupt-controller/amazon,al-fic.yaml | 16 +-
.../bindings/interrupt-controller/arm,gic-v5.yaml | 9 +-
.../bindings/interrupt-controller/qcom,pdc.yaml | 1 +
.../xlnx,versal-net-smmu-csr.yaml | 75 ++++++
MAINTAINERS | 10 +
arch/arm64/Kconfig | 9 +
arch/arm64/Kconfig.platforms | 1 +
arch/arm64/boot/dts/qcom/purwa.dtsi | 5 +
arch/arm64/configs/defconfig | 2 -
arch/riscv/include/asm/smp.h | 12 +
arch/riscv/kernel/sbi-ipi.c | 4 +-
arch/riscv/kernel/smp.c | 12 -
drivers/clocksource/timer-clint.c | 4 +-
drivers/irqchip/Kconfig | 16 +-
drivers/irqchip/Makefile | 1 +
drivers/irqchip/irq-aclint-sswi.c | 4 +-
drivers/irqchip/irq-al-fic.c | 255 +++++++++++++++------
drivers/irqchip/irq-atmel-aic.c | 2 +-
drivers/irqchip/irq-atmel-aic5.c | 2 +-
drivers/irqchip/irq-gic-common.c | 2 +-
drivers/irqchip/irq-gic-v3-its.c | 25 +-
drivers/irqchip/irq-gic-v3.c | 36 +--
drivers/irqchip/irq-gic-v5-irs.c | 4 +
drivers/irqchip/irq-gic-v5-iwb.c | 4 +-
drivers/irqchip/irq-gic-v5.c | 9 +-
drivers/irqchip/irq-gic.c | 12 +-
drivers/irqchip/irq-imx-intmux.c | 15 +-
drivers/irqchip/irq-jcore-aic.c | 4 +-
drivers/irqchip/irq-lan966x-oic.c | 1 -
drivers/irqchip/irq-loongson-eiointc.c | 2 +-
drivers/irqchip/irq-mbigen.c | 4 +-
drivers/irqchip/irq-mchp-eic.c | 4 +-
drivers/irqchip/irq-meson-gpio.c | 2 +-
drivers/irqchip/irq-mips-gic.c | 59 +++--
drivers/irqchip/irq-mst-intc.c | 3 +
drivers/irqchip/irq-mtk-cirq.c | 2 +-
drivers/irqchip/irq-pruss-intc.c | 38 ++-
drivers/irqchip/irq-renesas-rzv2h.c | 6 +-
drivers/irqchip/irq-riscv-aplic-main.c | 4 +-
drivers/irqchip/irq-riscv-imsic-early.c | 6 +-
drivers/irqchip/irq-riscv-imsic-platform.c | 18 +-
drivers/irqchip/irq-riscv-imsic-state.c | 23 +-
drivers/irqchip/irq-riscv-imsic-state.h | 4 -
drivers/irqchip/irq-riscv-rpmi-sysmsi.c | 4 +-
drivers/irqchip/irq-sifive-plic.c | 64 ++----
drivers/irqchip/irq-sni-exiu.c | 4 +-
drivers/irqchip/irq-st.c | 6 +-
drivers/irqchip/irq-vic.c | 2 +-
drivers/irqchip/irq-xilinx-smmu-csr.c | 244 ++++++++++++++++++++
drivers/irqchip/qcom-irq-combiner.c | 2 +-
drivers/irqchip/qcom-pdc.c | 3 +
drivers/soc/fsl/qe/qe_ports_ic.c | 1 -
include/linux/irqchip/riscv-imsic.h | 3 +
kernel/irq/irqdomain.c | 1 +
55 files changed, 797 insertions(+), 267 deletions(-)
create mode 100644 Documentation/devicetree/bindings/interrupt-controller/xlnx,versal-net-smmu-csr.yaml
create mode 100644 drivers/irqchip/irq-xilinx-smmu-csr.c
diff --git a/Documentation/arch/arm64/silicon-errata.rst b/Documentation/arch/arm64/silicon-errata.rst
index ac3248b9f2f3..25afa79266a4 100644
--- a/Documentation/arch/arm64/silicon-errata.rst
+++ b/Documentation/arch/arm64/silicon-errata.rst
@@ -53,6 +53,9 @@ stable kernels.
| Allwinner | A64/R18 | UNKNOWN1 | SUN50I_ERRATUM_UNKNOWN1 |
+----------------+-----------------+-----------------+-----------------------------+
+----------------+-----------------+-----------------+-----------------------------+
+| Altera | SoCFPGA Agilex5 | 2.1.23 |ALTERA_ERRATUM_AGILEX5_2_1_23|
++----------------+-----------------+-----------------+-----------------------------+
++----------------+-----------------+-----------------+-----------------------------+
| Ampere | AmpereOne | AC03_CPU_38 | AMPERE_ERRATUM_AC03_CPU_38 |
+----------------+-----------------+-----------------+-----------------------------+
| Ampere | AmpereOne | AC03_CPU_57 | N/A |
diff --git a/Documentation/devicetree/bindings/interrupt-controller/amazon,al-fic.yaml b/Documentation/devicetree/bindings/interrupt-controller/amazon,al-fic.yaml
index 26bc05dee0bc..1ebf9a427f2f 100644
--- a/Documentation/devicetree/bindings/interrupt-controller/amazon,al-fic.yaml
+++ b/Documentation/devicetree/bindings/interrupt-controller/amazon,al-fic.yaml
@@ -24,6 +24,19 @@ properties:
interrupts:
maxItems: 1
+ amazon,al-fic-mask:
+ $ref: /schemas/types.yaml#/definitions/string
+ description:
+ Selects which of the controller's severity outputs this group drives,
+ and thus which mask register the driver programs. A group has one cause
+ register and three severity outputs - info, error and fatal - each
+ gated by its own mask register. Each output is aggregated into a
+ separate interrupt tree, and a group belongs to exactly one of them.
+ The error and fatal outputs exist only on controller revisions that
+ implement them; earlier revisions provide the info output only.
+ enum: [info, error, fatal]
+ default: info
+
required:
- compatible
- reg
@@ -39,8 +52,9 @@ examples:
interrupt-controller@fd8a8500 {
compatible = "amazon,al-fic";
- reg = <0xfd8a8500 0x1000>;
+ reg = <0xfd8a8500 0x40>;
interrupt-controller;
#interrupt-cells = <2>;
interrupts = <GIC_SPI 0x0 IRQ_TYPE_LEVEL_HIGH>;
+ amazon,al-fic-mask = "error";
};
diff --git a/Documentation/devicetree/bindings/interrupt-controller/arm,gic-v5.yaml b/Documentation/devicetree/bindings/interrupt-controller/arm,gic-v5.yaml
index 86ca7f3ac281..b901e4288816 100644
--- a/Documentation/devicetree/bindings/interrupt-controller/arm,gic-v5.yaml
+++ b/Documentation/devicetree/bindings/interrupt-controller/arm,gic-v5.yaml
@@ -120,13 +120,14 @@ patternProperties:
cpus:
description:
- CPUs managed by the IRS.
+ CPUs managed by the IRS. Omit it if the IRS has no associated CPUs.
arm,iaffids:
$ref: /schemas/types.yaml#/definitions/uint16-array
description:
Interrupt AFFinity ID (IAFFID) associated with the CPU whose
CPU node phandle is at the same index in the cpus array.
+ Omit it if the IRS has no associated CPUs.
patternProperties:
"^its@[0-9a-f]+$":
@@ -210,12 +211,14 @@ patternProperties:
- reg
- reg-names
+ dependencies:
+ cpus: [ "arm,iaffids" ]
+ arm,iaffids: [ cpus ]
+
required:
- compatible
- reg
- reg-names
- - cpus
- - arm,iaffids
additionalProperties: false
diff --git a/Documentation/devicetree/bindings/interrupt-controller/qcom,pdc.yaml b/Documentation/devicetree/bindings/interrupt-controller/qcom,pdc.yaml
index 8162a49d49a6..b3d19c86a240 100644
--- a/Documentation/devicetree/bindings/interrupt-controller/qcom,pdc.yaml
+++ b/Documentation/devicetree/bindings/interrupt-controller/qcom,pdc.yaml
@@ -58,6 +58,7 @@ properties:
- qcom,sm8650-pdc
- qcom,sm8750-pdc
- qcom,x1e80100-pdc
+ - qcom,x1p42100-pdc
- const: qcom,pdc
reg:
diff --git a/Documentation/devicetree/bindings/interrupt-controller/xlnx,versal-net-smmu-csr.yaml b/Documentation/devicetree/bindings/interrupt-controller/xlnx,versal-net-smmu-csr.yaml
new file mode 100644
index 000000000000..38b63e51b732
--- /dev/null
+++ b/Documentation/devicetree/bindings/interrupt-controller/xlnx,versal-net-smmu-csr.yaml
@@ -0,0 +1,75 @@
+# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/interrupt-controller/xlnx,versal-net-smmu-csr.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: Xilinx Versal NET SMMU CSR Interrupt Controller
+
+maintainers:
+ - Anirudha Sarangi <anirudha.sarangi@amd.com>
+ - Tushar Nimkar <tushar.nimkar@amd.com>
+
+description: |
+ The Versal NET platform includes a Xilinx-specific SMMU CSR (Control and
+ Status Register) block that acts as an intermediate interrupt controller
+ for ARM SMMUv3.
+
+ The block provides registers to enable, disable and clear the
+ standard SMMUv3 interrupt sources (EVENTQ, CMDQ_SYNC, GERROR and PRIQ)
+ before forwarding them to the parent interrupt controller,
+ typically a GIC.
+
+ The controller receives one parent interrupt and demultiplexes CSR
+ status bits into the following child interrupt identifiers
+ 0 - EVENTQ
+ 1 - CMDQ_SYNC
+ 2 - GERROR (GLOBAL)
+ 3 - PRIQ
+
+allOf:
+ - $ref: /schemas/interrupt-controller.yaml#
+
+properties:
+ compatible:
+ const: xlnx,versal-net-smmu-csr
+
+ reg:
+ maxItems: 1
+
+ interrupt-controller: true
+
+ "#interrupt-cells":
+ const: 1
+ description: |
+ Single cell containing the child interrupt identifier
+ 0 - EVENTQ
+ 1 - CMDQ_SYNC
+ 2 - GERROR (GLOBAL)
+ 3 - PRIQ
+
+ interrupts:
+ maxItems: 1
+
+required:
+ - compatible
+ - reg
+ - interrupt-controller
+ - "#interrupt-cells"
+ - interrupts
+
+additionalProperties: false
+
+examples:
+ - |
+ #include <dt-bindings/interrupt-controller/arm-gic.h>
+ #include <dt-bindings/interrupt-controller/irq.h>
+
+ interrupt-controller@eca10000 {
+ compatible = "xlnx,versal-net-smmu-csr";
+ reg = <0xeca10000 0x1000>;
+ interrupt-controller;
+ #interrupt-cells = <1>;
+ interrupt-parent = <&gic>;
+ interrupts = <GIC_SPI 169 IRQ_TYPE_LEVEL_HIGH>;
+ };
diff --git a/MAINTAINERS b/MAINTAINERS
index 3a19da74d00c..f6007e77445f 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -29913,6 +29913,16 @@ F: Documentation/misc-devices/xilinx_sdfec.rst
F: drivers/misc/xilinx_sdfec.c
F: include/uapi/misc/xilinx_sdfec.h
+XILINX SMMU CSR IRQ DRIVER
+M: Anirudha Sarangi <anirudha.sarangi@amd.com>
+M: Tushar Nimkar <tushar.nimkar@amd.com>
+L: git@amd.com
+L: linux-arm-kernel@lists.infradead.org
+L: linux-kernel@vger.kernel.org
+S: Maintained
+F: Documentation/devicetree/bindings/interrupt-controller/xlnx,versal-net-smmu-csr.yaml
+F: drivers/irqchip/irq-xilinx-smmu-csr.c
+
XILINX TRNG DRIVER
M: Mounika Botcha <mounika.botcha@amd.com>
M: Harsh Jain <h.jain@amd.com>
diff --git a/arch/arm64/Kconfig b/arch/arm64/Kconfig
index b5a51b0ef944..d25c77a15276 100644
--- a/arch/arm64/Kconfig
+++ b/arch/arm64/Kconfig
@@ -1427,6 +1427,15 @@ config NVIDIA_OLYMPUS_1027_ERRATUM
If unsure, say Y.
+config ALTERA_ERRATUM_AGILEX5_2_1_23
+ bool
+ help
+ The Altera SoCFPGA Agilex5 GIC600 SoC integration has ACE-lite
+ addressing limited to the first 32bit of physical address space,
+ so the ITS cannot access memory above 4GB.
+
+ Selected by ARCH_INTEL_SOCFPGA, as all Agilex5 devices are affected.
+
config RENESAS_ERRATUM_GEN4GICITS1
bool "Renesas R-Car Gen4: GIC600 can not access physical addresses above 4 GiB"
default y
diff --git a/arch/arm64/Kconfig.platforms b/arch/arm64/Kconfig.platforms
index d2acfac73003..07127f7291db 100644
--- a/arch/arm64/Kconfig.platforms
+++ b/arch/arm64/Kconfig.platforms
@@ -361,6 +361,7 @@ config ARCH_SEATTLE
config ARCH_INTEL_SOCFPGA
bool "Intel's SoCFPGA ARMv8 Families"
+ select ALTERA_ERRATUM_AGILEX5_2_1_23
help
This enables support for Intel's SoCFPGA ARMv8 families:
Stratix 10 (ex. Altera), Stratix10 Software Virtual Platform,
diff --git a/arch/arm64/boot/dts/qcom/purwa.dtsi b/arch/arm64/boot/dts/qcom/purwa.dtsi
index c698e6cb2543..4348dd3d1dc5 100644
--- a/arch/arm64/boot/dts/qcom/purwa.dtsi
+++ b/arch/arm64/boot/dts/qcom/purwa.dtsi
@@ -224,6 +224,11 @@ &pcie6a_phy {
compatible = "qcom,x1p42100-qmp-gen4x4-pcie-phy";
};
+/* X1P42100 PDC is same as X1E80100, but without hardware register bug */
+&pdc {
+ compatible = "qcom,x1p42100-pdc", "qcom,pdc";
+};
+
&qfprom {
gpu_speed_bin: gpu-speed-bin@119 {
reg = <0x119 0x2>;
diff --git a/arch/arm64/configs/defconfig b/arch/arm64/configs/defconfig
index 6ec8d3a69c98..978e00891dca 100644
--- a/arch/arm64/configs/defconfig
+++ b/arch/arm64/configs/defconfig
@@ -1743,8 +1743,6 @@ CONFIG_PWM_VISCONTI=m
CONFIG_PWM_XILINX=m
CONFIG_SL28CPLD_INTC=y
CONFIG_XILINX_INTC=y
-CONFIG_QCOM_PDC=y
-CONFIG_QCOM_MPM=y
CONFIG_TI_SCI_INTR_IRQCHIP=y
CONFIG_TI_SCI_INTA_IRQCHIP=y
CONFIG_RESET_GPIO=m
diff --git a/arch/riscv/include/asm/smp.h b/arch/riscv/include/asm/smp.h
index 0ecc67641b09..bed39fff1f8a 100644
--- a/arch/riscv/include/asm/smp.h
+++ b/arch/riscv/include/asm/smp.h
@@ -15,6 +15,18 @@
struct seq_file;
extern unsigned long boot_cpu_hartid;
+enum ipi_message_type {
+ IPI_RESCHEDULE,
+ IPI_CALL_FUNC,
+ IPI_CPU_STOP,
+ IPI_CPU_CRASH_STOP,
+ IPI_IRQ_WORK,
+ IPI_TIMER,
+ IPI_CPU_BACKTRACE,
+ IPI_KGDB_ROUNDUP,
+ IPI_MAX
+};
+
#ifdef CONFIG_SMP
#include <linux/jump_label.h>
diff --git a/arch/riscv/kernel/sbi-ipi.c b/arch/riscv/kernel/sbi-ipi.c
index 0cc5559c08d8..eeec178a9b95 100644
--- a/arch/riscv/kernel/sbi-ipi.c
+++ b/arch/riscv/kernel/sbi-ipi.c
@@ -57,7 +57,7 @@ void __init sbi_ipi_init(void)
return;
}
- virq = ipi_mux_create(BITS_PER_BYTE, sbi_send_ipi);
+ virq = ipi_mux_create(IPI_MAX, sbi_send_ipi);
if (virq <= 0) {
pr_err("unable to create muxed IPIs\n");
irq_dispose_mapping(sbi_ipi_virq);
@@ -75,7 +75,7 @@ void __init sbi_ipi_init(void)
"irqchip/sbi-ipi:starting",
sbi_ipi_starting_cpu, NULL);
- riscv_ipi_set_virq_range(virq, BITS_PER_BYTE);
+ riscv_ipi_set_virq_range(virq, IPI_MAX);
pr_info("providing IPIs using SBI IPI extension\n");
/*
diff --git a/arch/riscv/kernel/smp.c b/arch/riscv/kernel/smp.c
index fa66f9c97d74..8930b62b15e7 100644
--- a/arch/riscv/kernel/smp.c
+++ b/arch/riscv/kernel/smp.c
@@ -28,18 +28,6 @@
#include <asm/cacheflush.h>
#include <asm/cpu_ops.h>
-enum ipi_message_type {
- IPI_RESCHEDULE,
- IPI_CALL_FUNC,
- IPI_CPU_STOP,
- IPI_CPU_CRASH_STOP,
- IPI_IRQ_WORK,
- IPI_TIMER,
- IPI_CPU_BACKTRACE,
- IPI_KGDB_ROUNDUP,
- IPI_MAX
-};
-
static const char * const ipi_names[] = {
[IPI_RESCHEDULE] = "Rescheduling interrupts",
[IPI_CALL_FUNC] = "Function call interrupts",
diff --git a/drivers/clocksource/timer-clint.c b/drivers/clocksource/timer-clint.c
index 0bdd9d7ec545..e56eee7e3781 100644
--- a/drivers/clocksource/timer-clint.c
+++ b/drivers/clocksource/timer-clint.c
@@ -243,7 +243,7 @@ static int __init clint_timer_init_dt(struct device_node *np)
}
#ifdef CONFIG_SMP
- rc = ipi_mux_create(BITS_PER_BYTE, clint_send_ipi);
+ rc = ipi_mux_create(IPI_MAX, clint_send_ipi);
if (rc <= 0) {
pr_err("unable to create muxed IPIs\n");
rc = (rc < 0) ? rc : -ENODEV;
@@ -251,7 +251,7 @@ static int __init clint_timer_init_dt(struct device_node *np)
}
irq_set_chained_handler(clint_ipi_irq, clint_ipi_interrupt);
- riscv_ipi_set_virq_range(rc, BITS_PER_BYTE);
+ riscv_ipi_set_virq_range(rc, IPI_MAX);
clint_clear_ipi();
#endif
diff --git a/drivers/irqchip/Kconfig b/drivers/irqchip/Kconfig
index 20b77fbc51ee..136bab41f801 100644
--- a/drivers/irqchip/Kconfig
+++ b/drivers/irqchip/Kconfig
@@ -522,17 +522,19 @@ config GOLDFISH_PIC
config QCOM_PDC
tristate "Qualcomm PDC"
- depends on ARCH_QCOM
+ depends on QCOM_SCM
select IRQ_DOMAIN_HIERARCHY
+ default ARCH_QCOM
help
Power Domain Controller driver to manage and configure wakeup
IRQs for Qualcomm Technologies Inc (QTI) mobile chips.
config QCOM_MPM
tristate "Qualcomm MPM"
- depends on ARCH_QCOM
+ depends on ARCH_QCOM || COMPILE_TEST
depends on MAILBOX
select IRQ_DOMAIN_HIERARCHY
+ default ARCH_QCOM if ARM64
help
MSM Power Manager driver to manage and configure wakeup
IRQs for Qualcomm Technologies Inc (QTI) mobile chips.
@@ -840,4 +842,14 @@ config SUNPLUS_SP7021_INTC
chained controller, routing all interrupt source in P-Chip to
the primary controller on C-Chip.
+config IRQCHIP_XILINX_SMMU_CSR
+ bool "Xilinx SMMU CSR Interrupt Driver"
+ depends on ARCH_ZYNQMP
+ select IRQ_DOMAIN
+ help
+ This driver does the initial handling of SMMU interrupts before
+ forwarding them to the ARM SMMU v3 driver. It is intended for
+ ZynqMP-class platforms where a vendor CSR block mediates SMMU
+ interrupts.
+
endmenu
diff --git a/drivers/irqchip/Makefile b/drivers/irqchip/Makefile
index ab33cccd8471..16a5fd126c9a 100644
--- a/drivers/irqchip/Makefile
+++ b/drivers/irqchip/Makefile
@@ -135,3 +135,4 @@ obj-$(CONFIG_APPLE_AIC) += irq-apple-aic.o
obj-$(CONFIG_MCHP_EIC) += irq-mchp-eic.o
obj-$(CONFIG_SOPHGO_SG2042_MSI) += irq-sg2042-msi.o
obj-$(CONFIG_SUNPLUS_SP7021_INTC) += irq-sp7021-intc.o
+obj-$(CONFIG_IRQCHIP_XILINX_SMMU_CSR) += irq-xilinx-smmu-csr.o
diff --git a/drivers/irqchip/irq-aclint-sswi.c b/drivers/irqchip/irq-aclint-sswi.c
index ca06efd86fa1..5e010fc401f7 100644
--- a/drivers/irqchip/irq-aclint-sswi.c
+++ b/drivers/irqchip/irq-aclint-sswi.c
@@ -138,7 +138,7 @@ static int __init aclint_sswi_probe(struct fwnode_handle *fwnode)
}
/* Register SSWI irq and handler */
- virq = ipi_mux_create(BITS_PER_BYTE, aclint_sswi_ipi_send);
+ virq = ipi_mux_create(IPI_MAX, aclint_sswi_ipi_send);
if (virq <= 0) {
pr_err("unable to create muxed IPIs\n");
irq_dispose_mapping(sswi_ipi_virq);
@@ -152,7 +152,7 @@ static int __init aclint_sswi_probe(struct fwnode_handle *fwnode)
aclint_sswi_starting_cpu,
aclint_sswi_dying_cpu);
- riscv_ipi_set_virq_range(virq, BITS_PER_BYTE);
+ riscv_ipi_set_virq_range(virq, IPI_MAX);
return 0;
}
diff --git a/drivers/irqchip/irq-al-fic.c b/drivers/irqchip/irq-al-fic.c
index d10ac9b63c99..e6068e0d0660 100644
--- a/drivers/irqchip/irq-al-fic.c
+++ b/drivers/irqchip/irq-al-fic.c
@@ -4,9 +4,9 @@
*/
#include <linux/bitfield.h>
+#include <linux/interrupt.h>
#include <linux/irq.h>
#include <linux/irqchip.h>
-#include <linux/irqchip/chained_irq.h>
#include <linux/irqdomain.h>
#include <linux/module.h>
#include <linux/of.h>
@@ -16,11 +16,14 @@
/* FIC Registers */
#define AL_FIC_CAUSE 0x00
#define AL_FIC_SET_CAUSE 0x08
-#define AL_FIC_MASK 0x10
+#define AL_FIC_INFO_MASK 0x10
#define AL_FIC_CONTROL 0x28
+#define AL_FIC_ERROR_MASK 0x2c
+#define AL_FIC_FATAL_MASK 0x34
#define CONTROL_TRIGGER_RISING BIT(3)
#define CONTROL_MASK_MSI_X BIT(5)
+#define CONTROL_VERSION_ID GENMASK(29, 28)
#define NR_FIC_IRQS 32
@@ -33,20 +36,51 @@ enum al_fic_state {
AL_FIC_CONFIGURED_RISING_EDGE,
};
+/*
+ * FIC hardware revision, as reported by the CONTROL register version field
+ * (CONTROL_VERSION_ID, bits 29-28). These are this driver's names for that
+ * field's encoding.
+ */
+enum al_fic_version {
+ AL_FIC_VERSION_V1,
+ AL_FIC_VERSION_V2,
+ AL_FIC_VERSION_V3,
+};
+
+enum al_fic_id {
+ AL_FIC_ID_INFO,
+ AL_FIC_ID_ERROR,
+ AL_FIC_ID_FATAL,
+ AL_FIC_ID_MAX,
+};
+
+/* Mask register offset for each interrupt group */
+static const unsigned int al_fic_mask_offset[AL_FIC_ID_MAX] = {
+ [AL_FIC_ID_INFO] = AL_FIC_INFO_MASK,
+ [AL_FIC_ID_ERROR] = AL_FIC_ERROR_MASK,
+ [AL_FIC_ID_FATAL] = AL_FIC_FATAL_MASK,
+};
+
+/* amazon,al-fic-mask property value for each interrupt group */
+static const char * const al_fic_mask_name[AL_FIC_ID_MAX] = {
+ [AL_FIC_ID_INFO] = "info",
+ [AL_FIC_ID_ERROR] = "error",
+ [AL_FIC_ID_FATAL] = "fatal",
+};
+
struct al_fic {
- void __iomem *base;
- struct irq_domain *domain;
- const char *name;
- unsigned int parent_irq;
- enum al_fic_state state;
+ void __iomem *base;
+ struct irq_domain *domain;
+ struct device_node *node;
+ unsigned int parent_irq;
+ enum al_fic_state state;
};
-static void al_fic_set_trigger(struct al_fic *fic,
- struct irq_chip_generic *gc,
+static void al_fic_set_trigger(struct al_fic *fic, struct irq_chip_generic *gc,
enum al_fic_state new_state)
{
- irq_flow_handler_t handler;
u32 control = readl_relaxed(fic->base + AL_FIC_CONTROL);
+ irq_flow_handler_t handler;
if (new_state == AL_FIC_CONFIGURED_LEVEL) {
handler = handle_level_irq;
@@ -89,30 +123,34 @@ static int al_fic_irq_set_type(struct irq_data *data, unsigned int flow_type)
if (fic->state == AL_FIC_UNCONFIGURED) {
al_fic_set_trigger(fic, gc, new_state);
} else if (fic->state != new_state) {
- pr_debug("fic %s state already configured to %d\n", fic->name, fic->state);
+ pr_debug("fic %pOF state already configured to %d\n", fic->node, fic->state);
return -EINVAL;
}
return 0;
}
-static void al_fic_irq_handler(struct irq_desc *desc)
+static irqreturn_t al_fic_irq_handler(int irq, void *data)
{
- struct al_fic *fic = irq_desc_get_handler_data(desc);
+ struct al_fic *fic = data;
struct irq_domain *domain = fic->domain;
- struct irq_chip *irqchip = irq_desc_get_chip(desc);
struct irq_chip_generic *gc = irq_get_domain_generic_chip(domain, 0);
unsigned long pending;
u32 hwirq;
- chained_irq_enter(irqchip, desc);
-
pending = readl_relaxed(fic->base + AL_FIC_CAUSE);
pending &= ~gc->mask_cache;
- for_each_set_bit(hwirq, &pending, NR_FIC_IRQS)
- generic_handle_domain_irq(domain, hwirq);
+ for_each_set_bit(hwirq, &pending, NR_FIC_IRQS) {
+ /*
+ * Redirect a child whose affinity excludes this CPU to its
+ * target CPU via irq_work. The ack moves to
+ * al_fic_irq_pre_redirect(), because the whole flow handler is
+ * deferred when that happens.
+ */
+ generic_handle_demux_domain_irq(domain, hwirq);
+ }
- chained_irq_exit(irqchip, desc);
+ return pending ? IRQ_HANDLED : IRQ_NONE;
}
static int al_fic_irq_retrigger(struct irq_data *data)
@@ -125,26 +163,53 @@ static int al_fic_irq_retrigger(struct irq_data *data)
return 1;
}
-static int al_fic_register(struct device_node *node,
- struct al_fic *fic)
+#ifdef CONFIG_SMP
+/* The ack happens in al_fic_irq_pre_redirect() on both paths instead. */
+static void al_fic_ack_noop(struct irq_data *data) { }
+
+/*
+ * Runs in the parent interrupt context on every dispatch, redirected or not.
+ * Clear the cause bit here: once the flow handler is deferred, the FIC keeps
+ * driving its output until the irq_work runs, and a level parent SPI re-fires.
+ *
+ * A level configured group is masked too, because its cause bit re-latches
+ * while the input stays asserted; the deferred handle_level_irq() unmasks
+ * when the handler is done. An edge configured group is only acked, since
+ * handle_edge_irq() never unmasks unconditionally and the mask would stay.
+ */
+static void al_fic_irq_pre_redirect(struct irq_data *data)
+{
+ struct irq_chip_generic *gc = irq_data_get_irq_chip_data(data);
+ struct al_fic *fic = gc->private;
+
+ if (fic->state == AL_FIC_CONFIGURED_LEVEL)
+ irq_gc_mask_set_bit(data);
+
+ irq_gc_ack_clr_bit(data);
+}
+#endif /* CONFIG_SMP */
+
+static int al_fic_register(struct device_node *node, struct al_fic *fic, enum al_fic_id fic_id)
{
struct irq_chip_generic *gc;
int ret;
- fic->domain = irq_domain_create_linear(of_fwnode_handle(node),
- NR_FIC_IRQS,
- &irq_generic_chip_ops,
- fic);
+ fic->domain = irq_domain_create_linear(of_fwnode_handle(node), NR_FIC_IRQS,
+ &irq_generic_chip_ops, fic);
if (!fic->domain) {
pr_err("fail to add irq domain\n");
return -ENOMEM;
}
- ret = irq_alloc_domain_generic_chips(fic->domain,
- NR_FIC_IRQS,
- 1, fic->name,
- handle_level_irq,
- 0, 0, IRQ_GC_INIT_MASK_CACHE);
+ /*
+ * Let irq_domain_remove() free the generic chips on either error path
+ * below, instead of calling irq_domain_remove_generic_chips() by hand.
+ */
+ fic->domain->flags |= IRQ_DOMAIN_FLAG_DESTROY_GC;
+
+ ret = irq_alloc_domain_generic_chips(fic->domain, NR_FIC_IRQS,
+ 1, of_node_full_name(fic->node), handle_level_irq,
+ 0, 0, 0);
if (ret) {
pr_err("fail to allocate generic chip (%d)\n", ret);
goto err_domain_remove;
@@ -152,47 +217,67 @@ static int al_fic_register(struct device_node *node,
gc = irq_get_domain_generic_chip(fic->domain, 0);
gc->reg_base = fic->base;
- gc->chip_types->regs.mask = AL_FIC_MASK;
+ gc->chip_types->regs.mask = al_fic_mask_offset[fic_id];
gc->chip_types->regs.ack = AL_FIC_CAUSE;
gc->chip_types->chip.irq_mask = irq_gc_mask_set_bit;
gc->chip_types->chip.irq_unmask = irq_gc_mask_clr_bit;
- gc->chip_types->chip.irq_ack = irq_gc_ack_clr_bit;
gc->chip_types->chip.irq_set_type = al_fic_irq_set_type;
gc->chip_types->chip.irq_retrigger = al_fic_irq_retrigger;
+#ifdef CONFIG_SMP
+ /*
+ * The FIC has no hardware affinity control, so give each child real
+ * affinity by redirecting its handler to the target CPU.
+ */
+ gc->chip_types->chip.irq_ack = al_fic_ack_noop;
+ gc->chip_types->chip.irq_pre_redirect = al_fic_irq_pre_redirect;
+ gc->chip_types->chip.irq_set_affinity = irq_chip_redirect_set_affinity;
+#else
+ gc->chip_types->chip.irq_ack = irq_gc_ack_clr_bit;
+#endif
gc->chip_types->chip.flags = IRQCHIP_SKIP_SET_WAKE;
gc->private = fic;
- irq_set_chained_handler_and_data(fic->parent_irq,
- al_fic_irq_handler,
- fic);
+ /*
+ * Seed the mask cache with the value al_fic_wire_init() programmed,
+ * rather than having the generic chip read the register back on the
+ * first child mapping: that is later than the parent is requested, and
+ * never happens at all for a group with no consumer in the devicetree.
+ */
+ gc->mask_cache = ~0U;
+
+ ret = request_irq(fic->parent_irq, al_fic_irq_handler, IRQF_NO_THREAD | IRQF_SHARED,
+ of_node_full_name(fic->node), fic);
+ if (ret) {
+ pr_err("fail to request irq (%d)\n", ret);
+ goto err_domain_remove;
+ }
+
return 0;
err_domain_remove:
irq_domain_remove(fic->domain);
-
return ret;
}
/*
* al_fic_wire_init() - initialize and configure fic in wire mode
- * @of_node: optional pointer to interrupt controller's device tree node.
+ * @node: pointer to the interrupt controller's device tree node
* @base: mmio to fic register
- * @name: name of the fic
* @parent_irq: interrupt of parent
+ * @fic_id: which of the controller's outputs (info, error or fatal) this
+ * group drives
*
- * This API will configure the fic hardware to to work in wire mode.
+ * This API will configure the fic hardware to work in wire mode.
* In wire mode, fic hardware is generating a wire ("wired") interrupt.
* Interrupt can be generated based on positive edge or level - configuration is
* to be determined based on connected hardware to this fic.
*/
-static struct al_fic *al_fic_wire_init(struct device_node *node,
- void __iomem *base,
- const char *name,
- unsigned int parent_irq)
+static struct al_fic *al_fic_wire_init(struct device_node *node, void __iomem *base,
+ unsigned int parent_irq, enum al_fic_id fic_id)
{
+ u32 control, version_id;
struct al_fic *fic;
int ret;
- u32 control = CONTROL_MASK_MSI_X;
fic = kzalloc_obj(*fic);
if (!fic)
@@ -200,67 +285,110 @@ static struct al_fic *al_fic_wire_init(struct device_node *node,
fic->base = base;
fic->parent_irq = parent_irq;
- fic->name = name;
+ fic->node = of_node_get(node);
+
+ control = readl_relaxed(fic->base + AL_FIC_CONTROL);
+ version_id = FIELD_GET(CONTROL_VERSION_ID, control);
+ if (version_id == AL_FIC_VERSION_V1 && fic_id != AL_FIC_ID_INFO) {
+ pr_err("%pOF: amazon,al-fic-mask = \"%s\" not available on FIC v1\n",
+ node, al_fic_mask_name[fic_id]);
+ ret = -EINVAL;
+ goto err_free;
+ }
/* mask out all interrupts */
- writel_relaxed(0xFFFFFFFF, fic->base + AL_FIC_MASK);
+ writel_relaxed(~0U, fic->base + AL_FIC_INFO_MASK);
+ if (version_id > AL_FIC_VERSION_V1) {
+ writel_relaxed(~0U, fic->base + AL_FIC_ERROR_MASK);
+ writel_relaxed(~0U, fic->base + AL_FIC_FATAL_MASK);
+ }
/* clear any pending interrupt */
writel_relaxed(0, fic->base + AL_FIC_CAUSE);
+ /* make sure the controller works in non msi_x mode */
+ control |= CONTROL_MASK_MSI_X;
writel_relaxed(control, fic->base + AL_FIC_CONTROL);
- ret = al_fic_register(node, fic);
+ ret = al_fic_register(node, fic, fic_id);
if (ret) {
pr_err("fail to register irqchip\n");
goto err_free;
}
- pr_debug("%s initialized successfully in Legacy mode (parent-irq=%u)\n",
- fic->name, parent_irq);
+ pr_info("%pOF initialized successfully (mask=%s parent-irq=%u)\n",
+ node, al_fic_mask_name[fic_id], parent_irq);
return fic;
err_free:
+ of_node_put(fic->node);
kfree(fic);
return ERR_PTR(ret);
}
-static int __init al_fic_init_dt(struct device_node *node,
- struct device_node *parent)
+/*
+ * Parse the amazon,al-fic-mask property into an enum al_fic_id, selecting
+ * which of the controller's outputs this group drives. The property is
+ * optional; an absent property means the info output.
+ */
+static int al_fic_parse_mask(struct device_node *node, enum al_fic_id *fic_id)
{
+ const char *mask;
int ret;
- void __iomem *base;
+
+ ret = of_property_read_string(node, "amazon,al-fic-mask", &mask);
+ if (ret == -EINVAL) {
+ *fic_id = AL_FIC_ID_INFO;
+ return 0;
+ }
+ if (ret)
+ return ret;
+
+ ret = match_string(al_fic_mask_name, AL_FIC_ID_MAX, mask);
+ if (ret < 0)
+ return ret;
+
+ *fic_id = ret;
+ return 0;
+}
+
+static int __init al_fic_init_dt(struct device_node *node, struct device_node *parent)
+{
unsigned int parent_irq;
+ enum al_fic_id fic_id;
struct al_fic *fic;
+ void __iomem *base;
+ int ret;
if (!parent) {
- pr_err("%s: unsupported - device require a parent\n",
- node->name);
+ pr_err("%pOF: unsupported - device requires a parent\n", node);
return -EINVAL;
}
+ ret = al_fic_parse_mask(node, &fic_id);
+ if (ret) {
+ pr_err("%pOF: invalid amazon,al-fic-mask\n", node);
+ return ret;
+ }
+
base = of_iomap(node, 0);
if (!base) {
- pr_err("%s: fail to map memory\n", node->name);
+ pr_err("%pOF: fail to map memory\n", node);
return -ENOMEM;
}
parent_irq = irq_of_parse_and_map(node, 0);
if (!parent_irq) {
- pr_err("%s: fail to map irq\n", node->name);
+ pr_err("%pOF: fail to map irq\n", node);
ret = -EINVAL;
goto err_unmap;
}
- fic = al_fic_wire_init(node,
- base,
- node->name,
- parent_irq);
+ fic = al_fic_wire_init(node, base, parent_irq, fic_id);
+
if (IS_ERR(fic)) {
- pr_err("%s: fail to initialize irqchip (%lu)\n",
- node->name,
- PTR_ERR(fic));
+ pr_err("%pOF: fail to initialize irqchip (%pe)\n", node, fic);
ret = PTR_ERR(fic);
goto err_irq_dispose;
}
@@ -271,7 +399,6 @@ static int __init al_fic_init_dt(struct device_node *node,
irq_dispose_mapping(parent_irq);
err_unmap:
iounmap(base);
-
return ret;
}
diff --git a/drivers/irqchip/irq-atmel-aic.c b/drivers/irqchip/irq-atmel-aic.c
index 1dcc52760eca..4a7935e75e4d 100644
--- a/drivers/irqchip/irq-atmel-aic.c
+++ b/drivers/irqchip/irq-atmel-aic.c
@@ -123,7 +123,7 @@ static void aic_pm_shutdown(struct irq_data *d)
{
struct irq_chip_generic *gc = irq_data_get_irq_chip_data(d);
- guard(raw_spinlock)(&gc->lock);
+ guard(raw_spinlock_irqsave)(&gc->lock);
irq_reg_writel(gc, 0xffffffff, AT91_AIC_IDCR);
irq_reg_writel(gc, 0xffffffff, AT91_AIC_ICCR);
}
diff --git a/drivers/irqchip/irq-atmel-aic5.c b/drivers/irqchip/irq-atmel-aic5.c
index 1f14b401f71d..07a317ed06ef 100644
--- a/drivers/irqchip/irq-atmel-aic5.c
+++ b/drivers/irqchip/irq-atmel-aic5.c
@@ -217,7 +217,7 @@ static void aic5_pm_shutdown(struct irq_data *d)
struct irq_chip_generic *gc = irq_data_get_irq_chip_data(d);
int i;
- guard(raw_spinlock)(&bgc->lock);
+ guard(raw_spinlock_irqsave)(&bgc->lock);
for (i = 0; i < dgc->irqs_per_chip; i++) {
irq_reg_writel(bgc, i + gc->irq_base, AT91_AIC5_SSR);
irq_reg_writel(bgc, 1, AT91_AIC5_IDCR);
diff --git a/drivers/irqchip/irq-gic-common.c b/drivers/irqchip/irq-gic-common.c
index c776f9142610..8bd1eaa54295 100644
--- a/drivers/irqchip/irq-gic-common.c
+++ b/drivers/irqchip/irq-gic-common.c
@@ -48,7 +48,7 @@ void gic_enable_quirks(u32 iidr, const struct gic_quirk *quirks,
int gic_configure_irq(unsigned int irq, unsigned int type,
void __iomem *base)
{
- u32 confmask = 0x2 << ((irq % 16) * 2);
+ u32 confmask = BIT(((irq % 16) * 2) + 1);
u32 confoff = (irq / 16) * 4;
u32 val, oldval;
int ret = 0;
diff --git a/drivers/irqchip/irq-gic-v3-its.c b/drivers/irqchip/irq-gic-v3-its.c
index e9807af23537..282e4bafb084 100644
--- a/drivers/irqchip/irq-gic-v3-its.c
+++ b/drivers/irqchip/irq-gic-v3-its.c
@@ -87,6 +87,8 @@ struct its_baser {
u64 val;
u32 order;
u32 psz;
+ /* Inherited from a sibling ITS, not freed here */
+ bool shared;
};
struct its_device;
@@ -2248,7 +2250,8 @@ static unsigned long *its_lpi_alloc(int nr_irqs, u32 *base, int *nr_ids)
static void its_lpi_free(unsigned long *bitmap, u32 base, u32 nr_ids)
{
- WARN_ON(free_lpi_range(base, nr_ids));
+ if (free_lpi_range(base, nr_ids))
+ pr_err_ratelimited("ITS: failed to free LPI range %u:%u\n", base, nr_ids);
bitmap_free(bitmap);
}
@@ -2604,7 +2607,8 @@ static void its_free_tables(struct its_node *its)
for (i = 0; i < GITS_BASER_NR_REGS; i++) {
if (its->tables[i].base) {
- its_free_pages(its->tables[i].base, its->tables[i].order);
+ if (!its->tables[i].shared)
+ its_free_pages(its->tables[i].base, its->tables[i].order);
its->tables[i].base = NULL;
}
}
@@ -2703,6 +2707,7 @@ static int its_alloc_tables(struct its_node *its)
WARN_ON(i != 2);
if ((sibling = find_sibling_its(its))) {
*baser = sibling->tables[2];
+ baser->shared = true;
its_write_baser(its, baser, baser->val);
continue;
}
@@ -3484,7 +3489,7 @@ static struct its_device *its_create_device(struct its_node *its, u32 dev_id,
* Also honor the ITS's own EID limit.
*/
id_bits = FIELD_GET(GITS_TYPER_IDBITS, its->typer) + 1;
- nvecs = min_t(unsigned int, nvecs, BIT(id_bits));
+ nvecs = min_t(u64, nvecs, BIT_ULL(id_bits));
nr_ites = max(2, nvecs);
sz = nr_ites * (FIELD_GET(GITS_TYPER_ITT_ENTRY_SIZE, its->typer) + 1);
sz = max(sz, ITS_ITT_ALIGN);
@@ -4599,6 +4604,7 @@ static void its_vpe_teardown(struct its_vpe *vpe)
its_vpe_db_proxy_unmap(vpe);
its_vpe_id_free(vpe->vpe_id);
its_free_pending_table(vpe->vpt_page);
+ vpe->vpt_page = NULL;
}
static void its_vpe_irq_domain_free(struct irq_domain *domain,
@@ -4895,6 +4901,9 @@ static bool __maybe_unused its_enable_quirk_hip09_162100801(void *data)
}
static const char * const dma_32bit_impaired_platforms[] = {
+#ifdef CONFIG_ALTERA_ERRATUM_AGILEX5_2_1_23
+ "intel,socfpga-agilex5",
+#endif
#ifdef CONFIG_RENESAS_ERRATUM_GEN4GICITS1
"renesas,r8a779f0",
"renesas,r8a779g0",
@@ -5328,16 +5337,18 @@ static int __init its_probe_one(struct its_node *its)
}
gits_write_cwriter(0, its->base + GITS_CWRITER);
+
+ err = its_init_domain(its);
+ if (err)
+ goto out_free_collection;
+
+ /* Enable last: the unwind frees memory an enabled ITS may access */
ctlr = readl_relaxed(its->base + GITS_CTLR);
ctlr |= GITS_CTLR_ENABLE;
if (is_v4(its))
ctlr |= GITS_CTLR_ImDe;
writel_relaxed(ctlr, its->base + GITS_CTLR);
- err = its_init_domain(its);
- if (err)
- goto out_free_collection;
-
raw_spin_lock(&its_lock);
list_add(&its->entry, &its_nodes);
raw_spin_unlock(&its_lock);
diff --git a/drivers/irqchip/irq-gic-v3.c b/drivers/irqchip/irq-gic-v3.c
index 6e1fa5b247fc..94873646d46f 100644
--- a/drivers/irqchip/irq-gic-v3.c
+++ b/drivers/irqchip/irq-gic-v3.c
@@ -164,6 +164,7 @@ static inline bool gic_dist_security_disabled(void)
static bool cpus_have_security_disabled __ro_after_init;
static bool cpus_have_group0 __ro_after_init;
+static void gic_dist_wait_for_rwp(void);
static void __init gic_prio_init(void)
{
@@ -179,6 +180,7 @@ static void __init gic_prio_init(void)
val = readl_relaxed(gic_data.dist_base + GICD_CTLR);
val |= GICD_CTLR_DS;
writel_relaxed(val, gic_data.dist_base + GICD_CTLR);
+ gic_dist_wait_for_rwp();
ds = gic_dist_security_disabled();
if (ds)
@@ -457,7 +459,7 @@ static int gic_peek_irq(struct irq_data *d, u32 offset)
u32 index, mask;
offset = convert_offset_index(d, offset, &index);
- mask = 1 << (index % 32);
+ mask = BIT(index % 32);
if (gic_irq_in_rdist(d))
base = gic_data_rdist_sgi_base();
@@ -473,7 +475,7 @@ static void gic_poke_irq(struct irq_data *d, u32 offset)
u32 index, mask;
offset = convert_offset_index(d, offset, &index);
- mask = 1 << (index % 32);
+ mask = BIT(index % 32);
if (gic_irq_in_rdist(d))
base = gic_data_rdist_sgi_base();
@@ -687,7 +689,7 @@ static void gic_eoimode1_eoi_irq(struct irq_data *d)
{
/*
* No need to deactivate an LPI, or an interrupt that
- * is is getting forwarded to a vcpu.
+ * is getting forwarded to a vcpu.
*/
if (irqd_to_hwirq(d) >= 8192 || irqd_is_forwarded_to_vcpu(d))
return;
@@ -1728,8 +1730,6 @@ static int gic_irq_get_fwspec_info(struct irq_fwspec *fwspec, struct irq_fwspec_
/* If the specifier provides an affinity, use it */
if (fwspec->param_count == 4 && fwspec->param[3]) {
- struct fwnode_handle *fw;
-
switch (fwspec->param[0]) {
case 1: /* PPI */
case 3: /* EPPI */
@@ -1738,7 +1738,10 @@ static int gic_irq_get_fwspec_info(struct irq_fwspec *fwspec, struct irq_fwspec_
return 0;
}
- fw = of_fwnode_handle(of_find_node_by_phandle(fwspec->param[3]));
+ struct device_node *np __free(device_node) =
+ of_find_node_by_phandle(fwspec->param[3]);
+ struct fwnode_handle *fw = of_fwnode_handle(np);
+
if (!fw)
return -ENOENT;
@@ -2276,7 +2279,6 @@ static struct
bool single_redist;
int enabled_rdists;
u32 maint_irq;
- int maint_irq_mode;
phys_addr_t vcpu_base;
} acpi_data __initdata;
@@ -2344,6 +2346,17 @@ gic_acpi_parse_madt_gicc(union acpi_subtable_headers *header,
return 0;
}
+ /* Not counted by gic_acpi_match_gicc(), so there is no slot for it */
+ if (!gicc->gicr_base_address) {
+ int cpu = get_cpu_for_acpi_id(gicc->uid);
+
+ pr_warn(FW_BUG "GICC entry with ACPI UID %u has no GICR base address, CPU kept offline\n",
+ gicc->uid);
+ if (cpu >= 0)
+ cpumask_set_cpu(cpu, &broken_rdists);
+ return 0;
+ }
+
redist_base = ioremap(gicc->gicr_base_address, size);
if (!redist_base)
return -ENOMEM;
@@ -2454,21 +2467,19 @@ static int __init gic_acpi_parse_virt_madt_gicc(union acpi_subtable_headers *hea
{
struct acpi_madt_generic_interrupt *gicc =
(struct acpi_madt_generic_interrupt *)header;
- int maint_irq_mode;
static int first_madt = true;
if (!(gicc->flags &
(ACPI_MADT_ENABLED | ACPI_MADT_GICC_ONLINE_CAPABLE)))
return 0;
- maint_irq_mode = (gicc->flags & ACPI_MADT_VGIC_IRQ_MODE) ?
- ACPI_EDGE_SENSITIVE : ACPI_LEVEL_SENSITIVE;
+ if (gicc->flags & ACPI_MADT_VGIC_IRQ_MODE)
+ pr_warn_once(FW_BUG "MI wrongly advertised as Edge-triggered\n");
if (first_madt) {
first_madt = false;
acpi_data.maint_irq = gicc->vgic_interrupt;
- acpi_data.maint_irq_mode = maint_irq_mode;
acpi_data.vcpu_base = gicc->gicv_base_address;
return 0;
@@ -2478,7 +2489,6 @@ static int __init gic_acpi_parse_virt_madt_gicc(union acpi_subtable_headers *hea
* The maintenance interrupt and GICV should be the same for every CPU
*/
if ((acpi_data.maint_irq != gicc->vgic_interrupt) ||
- (acpi_data.maint_irq_mode != maint_irq_mode) ||
(acpi_data.vcpu_base != gicc->gicv_base_address))
return -EINVAL;
@@ -2511,7 +2521,7 @@ static void __init gic_acpi_setup_kvm_info(void)
gic_v3_kvm_info.type = GIC_V3;
irq = acpi_register_gsi(NULL, acpi_data.maint_irq,
- acpi_data.maint_irq_mode,
+ ACPI_LEVEL_SENSITIVE,
ACPI_ACTIVE_HIGH);
if (irq <= 0)
return;
diff --git a/drivers/irqchip/irq-gic-v5-irs.c b/drivers/irqchip/irq-gic-v5-irs.c
index b3feb6340b59..53639e6f85bd 100644
--- a/drivers/irqchip/irq-gic-v5-irs.c
+++ b/drivers/irqchip/irq-gic-v5-irs.c
@@ -609,6 +609,10 @@ static int __init gicv5_irs_of_init_affinity(struct device_node *node,
u16 iaffid_mask = GENMASK(iaffid_bits - 1, 0);
int ret, i, ncpus, niaffids;
+ /* An IRS is architecturally allowed to have no cpus associated with it */
+ if (!of_property_present(node, "cpus") && !of_property_present(node, "arm,iaffids"))
+ return 0;
+
ncpus = of_count_phandle_with_args(node, "cpus", NULL);
if (ncpus < 0)
return -EINVAL;
diff --git a/drivers/irqchip/irq-gic-v5-iwb.c b/drivers/irqchip/irq-gic-v5-iwb.c
index 442c20614dbe..34afa036c34d 100644
--- a/drivers/irqchip/irq-gic-v5-iwb.c
+++ b/drivers/irqchip/irq-gic-v5-iwb.c
@@ -282,8 +282,8 @@ MODULE_DEVICE_TABLE(of, gicv5_iwb_of_match);
#ifdef CONFIG_ACPI
static const struct acpi_device_id iwb_acpi_match[] = {
- { "ARMH0003", 0 },
- {}
+ { .id = "ARMH0003" },
+ { }
};
#endif
diff --git a/drivers/irqchip/irq-gic-v5.c b/drivers/irqchip/irq-gic-v5.c
index ac2d423b1723..c365f8150300 100644
--- a/drivers/irqchip/irq-gic-v5.c
+++ b/drivers/irqchip/irq-gic-v5.c
@@ -1166,21 +1166,18 @@ static int __init gicv5_init_common(struct fwnode_handle *parent_domain)
if (ret)
goto out_int;
- ret = set_handle_irq(gicv5_handle_irq);
+ ret = gicv5_irs_enable();
if (ret)
goto out_int;
- ret = gicv5_irs_enable();
- if (ret)
- goto out_handle;
+ if (set_handle_irq(gicv5_handle_irq))
+ panic("GICv5: unable to install root IRQ handler\n");
gicv5_smp_init();
gicv5_irs_its_probe();
return 0;
-out_handle:
- set_handle_irq(NULL);
out_int:
gicv5_cpu_disable_interrupts();
gicv5_free_domains();
diff --git a/drivers/irqchip/irq-gic.c b/drivers/irqchip/irq-gic.c
index f6bc29f515fb..a5ca04e9be64 100644
--- a/drivers/irqchip/irq-gic.c
+++ b/drivers/irqchip/irq-gic.c
@@ -178,14 +178,14 @@ static inline bool cascading_gic_irq(struct irq_data *d)
*/
static void gic_poke_irq(struct irq_data *d, u32 offset)
{
- u32 mask = 1 << (irqd_to_hwirq(d) % 32);
+ u32 mask = BIT(irqd_to_hwirq(d) % 32);
writel_relaxed(mask, gic_dist_base(d) + offset + (irqd_to_hwirq(d) / 32) * 4);
}
static int gic_peek_irq(struct irq_data *d, u32 offset)
{
- u32 mask = 1 << (irqd_to_hwirq(d) % 32);
+ u32 mask = BIT(irqd_to_hwirq(d) % 32);
return !!(readl_relaxed(gic_dist_base(d) + offset + (irqd_to_hwirq(d) / 32) * 4) & mask);
}
@@ -1527,7 +1527,6 @@ static struct
{
phys_addr_t cpu_phys_base;
u32 maint_irq;
- int maint_irq_mode;
phys_addr_t vctrl_base;
phys_addr_t vcpu_base;
} acpi_data __initdata;
@@ -1553,10 +1552,11 @@ gic_acpi_parse_madt_cpu(union acpi_subtable_headers *header,
if (cpu_base_assigned && gic_cpu_base != acpi_data.cpu_phys_base)
return -EINVAL;
+ if (processor->flags & ACPI_MADT_VGIC_IRQ_MODE)
+ pr_warn_once(FW_BUG "MI wrongly advertised as Edge-triggered\n");
+
acpi_data.cpu_phys_base = gic_cpu_base;
acpi_data.maint_irq = processor->vgic_interrupt;
- acpi_data.maint_irq_mode = (processor->flags & ACPI_MADT_VGIC_IRQ_MODE) ?
- ACPI_EDGE_SENSITIVE : ACPI_LEVEL_SENSITIVE;
acpi_data.vctrl_base = processor->gich_base_address;
acpi_data.vcpu_base = processor->gicv_base_address;
@@ -1616,7 +1616,7 @@ static void __init gic_acpi_setup_kvm_info(void)
vcpu_res->end = vcpu_res->start + ACPI_GICV2_VCPU_MEM_SIZE - 1;
irq = acpi_register_gsi(NULL, acpi_data.maint_irq,
- acpi_data.maint_irq_mode,
+ ACPI_LEVEL_SENSITIVE,
ACPI_ACTIVE_HIGH);
if (irq <= 0)
return;
diff --git a/drivers/irqchip/irq-imx-intmux.c b/drivers/irqchip/irq-imx-intmux.c
index 47c2681d138a..de2a8f98f625 100644
--- a/drivers/irqchip/irq-imx-intmux.c
+++ b/drivers/irqchip/irq-imx-intmux.c
@@ -288,12 +288,16 @@ static int imx_intmux_probe(struct platform_device *pdev)
static void imx_intmux_remove(struct platform_device *pdev)
{
struct intmux_data *data = platform_get_drvdata(pdev);
- int i;
+ int i, ret;
+
+ ret = pm_runtime_resume_and_get(&pdev->dev);
+ if (ret < 0)
+ dev_warn(&pdev->dev, "failed to resume device: %d\n", ret);
for (i = 0; i < data->channum; i++) {
/* disable all interrupt sources of this channel */
- writel_relaxed(0, data->regs + CHANIER(i));
-
+ if (ret >= 0)
+ writel_relaxed(0, data->regs + CHANIER(i));
irq_set_chained_handler_and_data(data->irqchip_data[i].irq,
NULL, NULL);
@@ -301,6 +305,11 @@ static void imx_intmux_remove(struct platform_device *pdev)
}
pm_runtime_disable(&pdev->dev);
+ if (ret >= 0) {
+ pm_runtime_put_noidle(&pdev->dev);
+ clk_disable_unprepare(data->ipg_clk);
+ pm_runtime_set_suspended(&pdev->dev);
+ }
}
#ifdef CONFIG_PM
diff --git a/drivers/irqchip/irq-jcore-aic.c b/drivers/irqchip/irq-jcore-aic.c
index 94c05cf974be..b4fee02997ee 100644
--- a/drivers/irqchip/irq-jcore-aic.c
+++ b/drivers/irqchip/irq-jcore-aic.c
@@ -37,8 +37,10 @@ static struct irq_chip jcore_aic;
static void handle_jcore_irq(struct irq_desc *desc)
{
- if (irqd_is_per_cpu(irq_desc_get_irq_data(desc)))
+ if (irq_is_percpu_devid(irq_desc_get_irq(desc)))
handle_percpu_devid_irq(desc);
+ else if (irqd_is_per_cpu(irq_desc_get_irq_data(desc)))
+ handle_percpu_irq(desc);
else
handle_simple_irq(desc);
}
diff --git a/drivers/irqchip/irq-lan966x-oic.c b/drivers/irqchip/irq-lan966x-oic.c
index 8af08d0e4182..5122f3f1b353 100644
--- a/drivers/irqchip/irq-lan966x-oic.c
+++ b/drivers/irqchip/irq-lan966x-oic.c
@@ -220,7 +220,6 @@ static int lan966x_oic_probe(struct platform_device *pdev)
};
struct irq_domain_info d_info = {
.fwnode = of_fwnode_handle(pdev->dev.of_node),
- .domain_flags = IRQ_DOMAIN_FLAG_DESTROY_GC,
.size = LAN966X_OIC_NR_IRQ,
.hwirq_max = LAN966X_OIC_NR_IRQ,
.ops = &irq_generic_chip_ops,
diff --git a/drivers/irqchip/irq-loongson-eiointc.c b/drivers/irqchip/irq-loongson-eiointc.c
index a9e8b481d31d..ba6e8480e383 100644
--- a/drivers/irqchip/irq-loongson-eiointc.c
+++ b/drivers/irqchip/irq-loongson-eiointc.c
@@ -454,7 +454,7 @@ static int __init pch_pic_parse_madt(union acpi_subtable_headers *header,
const unsigned long end)
{
struct acpi_madt_bio_pic *pchpic_entry = (struct acpi_madt_bio_pic *)header;
- unsigned int node = (pchpic_entry->address >> 44) & 0xf;
+ unsigned int node = pa_to_nid(pchpic_entry->address) & 0xf;
struct irq_domain *parent = acpi_get_vec_parent(node, pch_group);
if (parent)
diff --git a/drivers/irqchip/irq-mbigen.c b/drivers/irqchip/irq-mbigen.c
index 6f69f4e5dbac..3c54f2402aeb 100644
--- a/drivers/irqchip/irq-mbigen.c
+++ b/drivers/irqchip/irq-mbigen.c
@@ -259,8 +259,8 @@ static int mbigen_of_create_domain(struct platform_device *pdev,
#ifdef CONFIG_ACPI
static const struct acpi_device_id mbigen_acpi_match[] = {
- { "HISI0152", 0 },
- {}
+ { .id = "HISI0152" },
+ { }
};
MODULE_DEVICE_TABLE(acpi, mbigen_acpi_match);
diff --git a/drivers/irqchip/irq-mchp-eic.c b/drivers/irqchip/irq-mchp-eic.c
index be007d325e67..5250e6baff63 100644
--- a/drivers/irqchip/irq-mchp-eic.c
+++ b/drivers/irqchip/irq-mchp-eic.c
@@ -233,7 +233,7 @@ static int mchp_eic_probe(struct platform_device *pdev, struct device_node *pare
ret = clk_prepare_enable(eic->clk);
if (ret)
- goto unmap;
+ goto clk_put;
for (i = 0; i < MCHP_EIC_NIRQ; i++) {
struct of_phandle_args irq;
@@ -270,6 +270,8 @@ static int mchp_eic_probe(struct platform_device *pdev, struct device_node *pare
clk_unprepare:
clk_disable_unprepare(eic->clk);
+clk_put:
+ clk_put(eic->clk);
unmap:
iounmap(eic->base);
free:
diff --git a/drivers/irqchip/irq-meson-gpio.c b/drivers/irqchip/irq-meson-gpio.c
index 91a9c337fe6d..e0329ef5e4a2 100644
--- a/drivers/irqchip/irq-meson-gpio.c
+++ b/drivers/irqchip/irq-meson-gpio.c
@@ -538,7 +538,7 @@ static struct irq_chip meson_gpio_irq_chip = {
#ifdef CONFIG_SMP
.irq_set_affinity = irq_chip_set_affinity_parent,
#endif
- .flags = IRQCHIP_SET_TYPE_MASKED,
+ .flags = IRQCHIP_SET_TYPE_MASKED | IRQCHIP_SKIP_SET_WAKE,
};
static int meson_gpio_irq_domain_translate(struct irq_domain *domain,
diff --git a/drivers/irqchip/irq-mips-gic.c b/drivers/irqchip/irq-mips-gic.c
index 19a57c5e2b2e..be38989d8e73 100644
--- a/drivers/irqchip/irq-mips-gic.c
+++ b/drivers/irqchip/irq-mips-gic.c
@@ -70,6 +70,10 @@ static int __gic_with_next_online_cpu(int prev)
{
unsigned int cpu;
+ /* Release the redirect/other region lock to the previous CPU, if any. */
+ if (prev >= 0)
+ mips_cm_unlock_other();
+
/* Discover the next online CPU */
cpu = cpumask_next(prev, cpu_online_mask);
@@ -77,23 +81,12 @@ static int __gic_with_next_online_cpu(int prev)
if (cpu >= nr_cpu_ids)
return cpu;
- /*
- * Move the access lock to the next CPU's GIC local register block.
- *
- * Set GIC_VL_OTHER. Since the caller holds gic_lock nothing can
- * clobber the written value.
- */
- write_gic_vl_other(mips_cm_vp_id(cpu));
+ /* Lock access to redirect/other region to the next CPU */
+ mips_cm_lock_other_cpu(cpu, CM_GCR_Cx_OTHER_BLOCK_LOCAL);
return cpu;
}
-static inline void gic_unlock_cluster(void)
-{
- if (mips_cps_multicluster_cpus())
- mips_cm_unlock_other();
-}
-
/**
* for_each_online_cpu_gic() - Iterate over online CPUs, access local registers
* @cpu: An integer variable to hold the current CPU number
@@ -108,7 +101,6 @@ static inline void gic_unlock_cluster(void)
guard(raw_spinlock_irqsave)(gic_lock); \
for ((cpu) = __gic_with_next_online_cpu(-1); \
(cpu) < nr_cpu_ids; \
- gic_unlock_cluster(), \
(cpu) = __gic_with_next_online_cpu(cpu))
/**
@@ -305,14 +297,14 @@ static void gic_ack_irq(struct irq_data *d)
}
}
-static int gic_set_type(struct irq_data *d, unsigned int type)
+static int gic_set_type_locked(struct irq_data *d, unsigned int type)
{
unsigned int irq, pol, trig, dual;
- unsigned long flags;
+
+ lockdep_assert_held(&gic_lock);
irq = GIC_HWIRQ_TO_SHARED(d->hwirq);
- raw_spin_lock_irqsave(&gic_lock, flags);
switch (type & IRQ_TYPE_SENSE_MASK) {
case IRQ_TYPE_EDGE_FALLING:
pol = GIC_POL_FALLING_EDGE;
@@ -359,11 +351,16 @@ static int gic_set_type(struct irq_data *d, unsigned int type)
else
irq_set_chip_handler_name_locked(d, &gic_level_irq_controller,
handle_level_irq, NULL);
- raw_spin_unlock_irqrestore(&gic_lock, flags);
return 0;
}
+static int gic_set_type(struct irq_data *d, unsigned int type)
+{
+ guard(raw_spinlock_irqsave)(&gic_lock);
+ return gic_set_type_locked(d, type);
+}
+
#ifdef CONFIG_SMP
static int gic_set_affinity(struct irq_data *d, const struct cpumask *cpumask,
bool force)
@@ -371,6 +368,7 @@ static int gic_set_affinity(struct irq_data *d, const struct cpumask *cpumask,
unsigned int irq = GIC_HWIRQ_TO_SHARED(d->hwirq);
unsigned int cpu, cl, old_cpu, old_cl;
unsigned long flags;
+ bool enabled;
/*
* The GIC specifies that we can only route an interrupt to one VP(E),
@@ -392,15 +390,20 @@ static int gic_set_affinity(struct irq_data *d, const struct cpumask *cpumask,
raw_spin_lock_irqsave(&gic_lock, flags);
/*
- * If we're moving affinity between clusters, stop routing the
- * interrupt to any VP(E) in the old cluster.
+ * If we're moving affinity between clusters, save the interrupt's
+ * mask state, stop routing it to any VP(E) in the old cluster and
+ * disable it there so it is no longer delivered.
*/
if (cl != old_cl) {
if (gic_irq_lock_cluster(d)) {
+ enabled = read_gic_redir_mask(irq);
write_gic_redir_map_vp(irq, 0);
+ write_gic_redir_rmask(irq);
mips_cm_unlock_other();
} else {
+ enabled = read_gic_mask(irq);
write_gic_map_vp(irq, 0);
+ write_gic_rmask(irq);
}
}
@@ -412,10 +415,20 @@ static int gic_set_affinity(struct irq_data *d, const struct cpumask *cpumask,
/*
* If we're moving affinity between clusters, configure the interrupt
- * trigger type in the new cluster.
+ * trigger type and, if it was enabled in the old cluster, enable it
+ * in the new one.
*/
- if (cl != old_cl)
- gic_set_type(d, irqd_get_trigger_type(d));
+ if (cl != old_cl) {
+ gic_set_type_locked(d, irqd_get_trigger_type(d));
+ if (enabled) {
+ if (gic_irq_lock_cluster(d)) {
+ write_gic_redir_smask(irq);
+ mips_cm_unlock_other();
+ } else {
+ write_gic_smask(irq);
+ }
+ }
+ }
/* Route the interrupt to its new VP(E) */
if (gic_irq_lock_cluster(d)) {
diff --git a/drivers/irqchip/irq-mst-intc.c b/drivers/irqchip/irq-mst-intc.c
index b5335f6fd6d6..1070de5de8cc 100644
--- a/drivers/irqchip/irq-mst-intc.c
+++ b/drivers/irqchip/irq-mst-intc.c
@@ -263,6 +263,9 @@ static int __init mst_intc_of_init(struct device_node *dn,
of_property_read_u32_index(dn, "mstar,irqs-map-range", 1, &irq_end))
return -EINVAL;
+ if (irq_end < irq_start || irq_end - irq_start >= MST_INTC_MAX_IRQS)
+ return -EINVAL;
+
cd = kzalloc_obj(*cd);
if (!cd)
return -ENOMEM;
diff --git a/drivers/irqchip/irq-mtk-cirq.c b/drivers/irqchip/irq-mtk-cirq.c
index 914d1d639fe3..d30c34ce0f56 100644
--- a/drivers/irqchip/irq-mtk-cirq.c
+++ b/drivers/irqchip/irq-mtk-cirq.c
@@ -247,7 +247,7 @@ static int mtk_cirq_suspend(void *data)
writel_relaxed(mask, reg);
}
- /* set edge_only mode, record edge-triggerd interrupts */
+ /* set edge_only mode, record edge-triggered interrupts */
/* enable cirq */
reg = mtk_cirq_reg(cirq_data, CIRQ_CONTROL);
value = readl_relaxed(reg);
diff --git a/drivers/irqchip/irq-pruss-intc.c b/drivers/irqchip/irq-pruss-intc.c
index 81078d56f38d..5a3e9e5bccbe 100644
--- a/drivers/irqchip/irq-pruss-intc.c
+++ b/drivers/irqchip/irq-pruss-intc.c
@@ -181,7 +181,7 @@ static void pruss_intc_map(struct pruss_intc *intc, unsigned long hwirq)
u8 ch, host, reg_idx;
u32 val;
- mutex_lock(&intc->lock);
+ guard(mutex)(&intc->lock);
intc->event_channel[hwirq].ref_count++;
@@ -206,8 +206,6 @@ static void pruss_intc_map(struct pruss_intc *intc, unsigned long hwirq)
dev_dbg(dev, "mapped system_event = %lu channel = %d host = %d",
hwirq, ch, host);
-
- mutex_unlock(&intc->lock);
}
/**
@@ -224,7 +222,7 @@ static void pruss_intc_unmap(struct pruss_intc *intc, unsigned long hwirq)
u8 ch, host, reg_idx;
u32 val;
- mutex_lock(&intc->lock);
+ guard(mutex)(&intc->lock);
ch = intc->event_channel[hwirq].value;
host = intc->channel_host[ch].value;
@@ -251,8 +249,6 @@ static void pruss_intc_unmap(struct pruss_intc *intc, unsigned long hwirq)
dev_dbg(intc->dev, "unmapped system_event = %lu channel = %d host = %d\n",
hwirq, ch, host);
-
- mutex_unlock(&intc->lock);
}
static void pruss_intc_init(struct pruss_intc *intc)
@@ -376,17 +372,15 @@ static int pruss_intc_validate_mapping(struct pruss_intc *intc, int event,
int channel, int host)
{
struct device *dev = intc->dev;
- int ret = 0;
- mutex_lock(&intc->lock);
+ guard(mutex)(&intc->lock);
/* check if sysevent already assigned */
if (intc->event_channel[event].ref_count > 0 &&
intc->event_channel[event].value != channel) {
dev_err(dev, "event %d (req. ch %d) already assigned to channel %d\n",
event, channel, intc->event_channel[event].value);
- ret = -EBUSY;
- goto unlock;
+ return -EBUSY;
}
/* check if channel already assigned */
@@ -394,16 +388,13 @@ static int pruss_intc_validate_mapping(struct pruss_intc *intc, int event,
intc->channel_host[channel].value != host) {
dev_err(dev, "channel %d (req. host %d) already assigned to host %d\n",
channel, host, intc->channel_host[channel].value);
- ret = -EBUSY;
- goto unlock;
+ return -EBUSY;
}
intc->event_channel[event].value = channel;
intc->channel_host[channel].value = host;
-unlock:
- mutex_unlock(&intc->lock);
- return ret;
+ return 0;
}
static int
@@ -516,24 +507,21 @@ static const char * const irq_names[MAX_NUM_HOST_IRQS] = {
static int pruss_intc_probe(struct platform_device *pdev)
{
- const struct pruss_intc_match_data *data;
struct device *dev = &pdev->dev;
struct pruss_intc *intc;
struct pruss_host_irq_data *host_data;
int i, irq, ret;
u8 max_system_events, irqs_reserved = 0;
- data = of_device_get_match_data(dev);
- if (!data)
- return -ENODEV;
-
- max_system_events = data->num_system_events;
-
intc = devm_kzalloc(dev, sizeof(*intc), GFP_KERNEL);
if (!intc)
return -ENOMEM;
- intc->soc_config = data;
+ intc->soc_config = of_device_get_match_data(dev);
+ if (!intc->soc_config)
+ return -ENODEV;
+ max_system_events = intc->soc_config->num_system_events;
+
intc->dev = dev;
platform_set_drvdata(pdev, intc);
@@ -553,7 +541,9 @@ static int pruss_intc_probe(struct platform_device *pdev)
pruss_intc_init(intc);
- mutex_init(&intc->lock);
+ ret = devm_mutex_init(dev, &intc->lock);
+ if (ret)
+ return ret;
intc->domain = irq_domain_create_linear(dev_fwnode(dev), max_system_events,
&pruss_intc_irq_domain_ops, intc);
diff --git a/drivers/irqchip/irq-renesas-rzv2h.c b/drivers/irqchip/irq-renesas-rzv2h.c
index 1a4703a353e9..485c3eea7632 100644
--- a/drivers/irqchip/irq-renesas-rzv2h.c
+++ b/drivers/irqchip/irq-renesas-rzv2h.c
@@ -420,9 +420,9 @@ static void rzv2h_clear_tint_int(struct rzv2h_icu_priv *priv, unsigned int hwirq
static int rzv2h_tint_set_type(struct irq_data *d, unsigned int type)
{
u32 titsr, titsr_k, titsel_n, tien;
+ u32 titsr_cur, tssr_cur, tien_cur;
struct rzv2h_icu_priv *priv;
u32 tssr, tssr_k, tssel_n;
- u32 titsr_cur, tssr_cur;
unsigned int hwirq;
u32 tint, sense;
int tint_nr;
@@ -477,6 +477,8 @@ static int rzv2h_tint_set_type(struct irq_data *d, unsigned int type)
if (tssr_cur == tint && titsr_cur == sense)
goto set_parent_type;
+ tien_cur = tssr & tien;
+
tssr &= ~(ICU_TSSR_TSSEL_MASK(tssel_n, priv->info->field_width) | tien);
tssr |= ICU_TSSR_TSSEL_PREP(tint, tssel_n, priv->info->field_width);
@@ -489,7 +491,7 @@ static int rzv2h_tint_set_type(struct irq_data *d, unsigned int type)
rzv2h_clear_tint_int(priv, hwirq);
- writel_relaxed(tssr | tien, priv->base + priv->info->t_offs + ICU_TSSR(tssr_k));
+ writel_relaxed(tssr | tien_cur, priv->base + priv->info->t_offs + ICU_TSSR(tssr_k));
}
set_parent_type:
return irq_chip_set_type_parent(d, IRQ_TYPE_LEVEL_HIGH);
diff --git a/drivers/irqchip/irq-riscv-aplic-main.c b/drivers/irqchip/irq-riscv-aplic-main.c
index d9afb6ae98cf..1676d64e6e4a 100644
--- a/drivers/irqchip/irq-riscv-aplic-main.c
+++ b/drivers/irqchip/irq-riscv-aplic-main.c
@@ -305,8 +305,8 @@ static void aplic_init_hw_irqs(struct aplic_priv *priv)
#ifdef CONFIG_ACPI
static const struct acpi_device_id aplic_acpi_match[] = {
- { "RSCV0002", 0 },
- {}
+ { .id = "RSCV0002" },
+ { }
};
MODULE_DEVICE_TABLE(acpi, aplic_acpi_match);
diff --git a/drivers/irqchip/irq-riscv-imsic-early.c b/drivers/irqchip/irq-riscv-imsic-early.c
index 12efd241ce88..06726a062b75 100644
--- a/drivers/irqchip/irq-riscv-imsic-early.c
+++ b/drivers/irqchip/irq-riscv-imsic-early.c
@@ -67,15 +67,15 @@ static int __init imsic_ipi_domain_init(void)
return 0;
/* Create IMSIC IPI multiplexing */
- virq = ipi_mux_create(IMSIC_NR_IPI, imsic_ipi_send);
+ virq = ipi_mux_create(IPI_MAX, imsic_ipi_send);
if (virq <= 0)
return virq < 0 ? virq : -ENOMEM;
/* Set vIRQ range */
- riscv_ipi_set_virq_range(virq, IMSIC_NR_IPI);
+ riscv_ipi_set_virq_range(virq, IPI_MAX);
/* Announce that IMSIC is providing IPIs */
- pr_info("%pfwP: providing IPIs using interrupt %d\n", imsic->fwnode, IMSIC_IPI_ID);
+ pr_info("%pfwP: providing IPIs using interrupt %d\n", imsic->global.fwnode, IMSIC_IPI_ID);
return 0;
}
diff --git a/drivers/irqchip/irq-riscv-imsic-platform.c b/drivers/irqchip/irq-riscv-imsic-platform.c
index 643c8e459611..95ff76e63a69 100644
--- a/drivers/irqchip/irq-riscv-imsic-platform.c
+++ b/drivers/irqchip/irq-riscv-imsic-platform.c
@@ -313,40 +313,40 @@ int imsic_irqdomain_init(void)
};
struct imsic_global_config *global;
- if (!imsic || !imsic->fwnode) {
+ if (!imsic || !imsic->global.fwnode) {
pr_err("early driver not probed\n");
return -ENODEV;
}
if (imsic->base_domain) {
- pr_err("%pfwP: irq domain already created\n", imsic->fwnode);
+ pr_err("%pfwP: irq domain already created\n", imsic->global.fwnode);
return -ENODEV;
}
/* Create Base IRQ domain */
- info.fwnode = imsic->fwnode,
+ info.fwnode = imsic->global.fwnode,
imsic->base_domain = msi_create_parent_irq_domain(&info, &imsic_msi_parent_ops);
if (!imsic->base_domain) {
- pr_err("%pfwP: failed to create IMSIC base domain\n", imsic->fwnode);
+ pr_err("%pfwP: failed to create IMSIC base domain\n", imsic->global.fwnode);
return -ENOMEM;
}
global = &imsic->global;
pr_info("%pfwP: hart-index-bits: %d, guest-index-bits: %d\n",
- imsic->fwnode, global->hart_index_bits, global->guest_index_bits);
+ imsic->global.fwnode, global->hart_index_bits, global->guest_index_bits);
pr_info("%pfwP: group-index-bits: %d, group-index-shift: %d\n",
- imsic->fwnode, global->group_index_bits, global->group_index_shift);
+ imsic->global.fwnode, global->group_index_bits, global->group_index_shift);
pr_info("%pfwP: per-CPU IDs %d at base address %pa\n",
- imsic->fwnode, global->nr_ids, &global->base_addr);
+ imsic->global.fwnode, global->nr_ids, &global->base_addr);
pr_info("%pfwP: total %d interrupts available\n",
- imsic->fwnode, num_possible_cpus() * (global->nr_ids - 1));
+ imsic->global.fwnode, num_possible_cpus() * (global->nr_ids - 1));
return 0;
}
static int imsic_platform_probe_common(struct fwnode_handle *fwnode)
{
- if (imsic && imsic->fwnode != fwnode) {
+ if (imsic && imsic->global.fwnode != fwnode) {
pr_err("%pfwP: fwnode mismatch\n", fwnode);
return -ENODEV;
}
diff --git a/drivers/irqchip/irq-riscv-imsic-state.c b/drivers/irqchip/irq-riscv-imsic-state.c
index b8d1bbbf42f7..01620373b43a 100644
--- a/drivers/irqchip/irq-riscv-imsic-state.c
+++ b/drivers/irqchip/irq-riscv-imsic-state.c
@@ -7,6 +7,7 @@
#define pr_fmt(fmt) "riscv-imsic: " fmt
#include <linux/acpi.h>
#include <linux/cpu.h>
+#include <linux/bits.h>
#include <linux/bitmap.h>
#include <linux/interrupt.h>
#include <linux/irq.h>
@@ -739,6 +740,14 @@ static int __init imsic_parse_fwnode(struct fwnode_handle *fwnode,
return -EINVAL;
}
+ /* Group index bits must not overlap guest and HART index bits. */
+ i = IMSIC_MMIO_PAGE_SHIFT + global->guest_index_bits +
+ global->hart_index_bits;
+ if (global->group_index_bits && global->group_index_shift < i) {
+ pr_err("%pfwP: group index shift too small\n", fwnode);
+ return -EINVAL;
+ }
+
/* Sanity check group index shift */
i = global->group_index_bits + global->group_index_shift - 1;
if (i >= BITS_PER_LONG) {
@@ -769,9 +778,9 @@ static int __init imsic_parse_fwnode(struct fwnode_handle *fwnode,
return -EINVAL;
}
global->base_addr = res.start;
- global->base_addr &= ~(BIT(global->guest_index_bits +
- global->hart_index_bits +
- IMSIC_MMIO_PAGE_SHIFT) - 1);
+ global->base_addr &= ~GENMASK(global->guest_index_bits +
+ global->hart_index_bits +
+ IMSIC_MMIO_PAGE_SHIFT - 1, 0);
global->base_addr &= ~((BIT(global->group_index_bits) - 1) <<
global->group_index_shift);
@@ -813,9 +822,9 @@ int __init imsic_setup_state(struct fwnode_handle *fwnode, void *opaque)
imsic = kzalloc_obj(*imsic);
if (!imsic)
return -ENOMEM;
- imsic->fwnode = fwnode;
global = &imsic->global;
+ global->fwnode = fwnode;
global->local = alloc_percpu(typeof(*global->local));
if (!global->local) {
rc = -ENOMEM;
@@ -850,9 +859,9 @@ int __init imsic_setup_state(struct fwnode_handle *fwnode, void *opaque)
}
base_addr = mmios[i].start;
- base_addr &= ~(BIT(global->guest_index_bits +
- global->hart_index_bits +
- IMSIC_MMIO_PAGE_SHIFT) - 1);
+ base_addr &= ~GENMASK(global->guest_index_bits +
+ global->hart_index_bits +
+ IMSIC_MMIO_PAGE_SHIFT - 1, 0);
base_addr &= ~((BIT(global->group_index_bits) - 1) <<
global->group_index_shift);
if (base_addr != global->base_addr) {
diff --git a/drivers/irqchip/irq-riscv-imsic-state.h b/drivers/irqchip/irq-riscv-imsic-state.h
index c42ee180b305..189829b8673a 100644
--- a/drivers/irqchip/irq-riscv-imsic-state.h
+++ b/drivers/irqchip/irq-riscv-imsic-state.h
@@ -13,7 +13,6 @@
#include <linux/timer.h>
#define IMSIC_IPI_ID 1
-#define IMSIC_NR_IPI 8
struct imsic_vector {
/* Fixed details of the vector */
@@ -44,9 +43,6 @@ struct imsic_local_priv {
};
struct imsic_priv {
- /* Device details */
- struct fwnode_handle *fwnode;
-
/* Global configuration common for all HARTs */
struct imsic_global_config global;
diff --git a/drivers/irqchip/irq-riscv-rpmi-sysmsi.c b/drivers/irqchip/irq-riscv-rpmi-sysmsi.c
index 612f3972f7af..bc80c38bb864 100644
--- a/drivers/irqchip/irq-riscv-rpmi-sysmsi.c
+++ b/drivers/irqchip/irq-riscv-rpmi-sysmsi.c
@@ -313,8 +313,8 @@ static const struct of_device_id rpmi_sysmsi_match[] = {
};
static const struct acpi_device_id acpi_rpmi_sysmsi_match[] = {
- { "RSCV0006" },
- {}
+ { .id = "RSCV0006" },
+ { }
};
MODULE_DEVICE_TABLE(acpi, acpi_rpmi_sysmsi_match);
diff --git a/drivers/irqchip/irq-sifive-plic.c b/drivers/irqchip/irq-sifive-plic.c
index 5b0dac104814..55d94c49f083 100644
--- a/drivers/irqchip/irq-sifive-plic.c
+++ b/drivers/irqchip/irq-sifive-plic.c
@@ -108,7 +108,7 @@ static int plic_irq_set_type(struct irq_data *d, unsigned int type);
static void __plic_toggle(struct plic_handler *handler, int hwirq, int enable)
{
u32 __iomem *base = handler->enable_base;
- u32 hwirq_mask = 1 << (hwirq % 32);
+ u32 hwirq_mask = BIT(hwirq % 32);
int group = hwirq / 32;
u32 value;
@@ -452,7 +452,7 @@ static irq_hw_number_t cp100_get_hwirq(struct plic_handler *handler, void __iome
return 0;
/*
- * Interrupts delievered to hardware still become pending, but only
+ * Interrupts delivered to hardware still become pending, but only
* interrupts that are both pending and enabled can be claimed.
* Clearing the enable bit for all interrupts but the first pending
* one avoids a hardware bug that occurs during read from the claim
@@ -538,8 +538,8 @@ static const struct of_device_id plic_match[] = {
#ifdef CONFIG_ACPI
static const struct acpi_device_id plic_acpi_match[] = {
- { "RSCV0001", 0 },
- {}
+ { .id = "RSCV0001" },
+ { }
};
MODULE_DEVICE_TABLE(acpi, plic_acpi_match);
@@ -643,13 +643,11 @@ static int plic_probe(struct fwnode_handle *fwnode)
error = plic_parse_nr_irqs_and_contexts(fwnode, &nr_irqs, &nr_contexts, &gsi_base, &id);
if (error)
- goto fail_free_regs;
+ return error;
priv = kzalloc_obj(*priv);
- if (!priv) {
- error = -ENOMEM;
- goto fail_free_regs;
- }
+ if (!priv)
+ return -ENOMEM;
priv->fwnode = fwnode;
priv->plic_quirks = plic_quirks;
@@ -668,10 +666,8 @@ static int plic_probe(struct fwnode_handle *fwnode)
priv->acpi_plic_id = id;
priv->prio_save = bitmap_zalloc(nr_irqs, GFP_KERNEL);
- if (!priv->prio_save) {
- error = -ENOMEM;
- goto fail_free_priv;
- }
+ if (!priv->prio_save)
+ return -ENOMEM;
for (i = 0; i < nr_contexts; i++) {
error = plic_parse_context_parent(fwnode, i, &parent_hwirq, &cpu,
@@ -735,10 +731,8 @@ static int plic_probe(struct fwnode_handle *fwnode)
handler->enable_save = kcalloc(priv->irq_groups, sizeof(*handler->enable_save),
GFP_KERNEL);
- if (!handler->enable_save) {
- error = -ENOMEM;
- goto fail_cleanup_contexts;
- }
+ if (!handler->enable_save)
+ return -ENOMEM;
done:
for_each_device_irq(hwirq, priv) {
plic_toggle(handler, hwirq, 0);
@@ -748,10 +742,8 @@ static int plic_probe(struct fwnode_handle *fwnode)
}
priv->irqdomain = irq_domain_create_linear(fwnode, nr_irqs, &plic_irqdomain_ops, priv);
- if (WARN_ON(!priv->irqdomain)) {
- error = -ENOMEM;
- goto fail_cleanup_contexts;
- }
+ if (!priv->irqdomain)
+ return -ENOMEM;
/*
* We can have multiple PLIC instances so setup global state
@@ -799,33 +791,13 @@ static int plic_probe(struct fwnode_handle *fwnode)
pr_info("%pfwP: mapped %d interrupts with %d handlers for %d contexts.\n",
fwnode, nr_irqs, nr_handlers, nr_contexts);
return 0;
-
-fail_cleanup_contexts:
- for (i = 0; i < nr_contexts; i++) {
- if (plic_parse_context_parent(fwnode, i, &parent_hwirq, &cpu, priv->acpi_plic_id))
- continue;
- if (parent_hwirq != RV_IRQ_EXT || cpu < 0)
- continue;
-
- handler = per_cpu_ptr(&plic_handlers, cpu);
- handler->present = false;
- handler->hart_base = NULL;
- handler->enable_base = NULL;
- kfree(handler->enable_save);
- handler->enable_save = NULL;
- handler->priv = NULL;
- }
- bitmap_free(priv->prio_save);
-fail_free_priv:
- kfree(priv);
-fail_free_regs:
- iounmap(regs);
- return error;
}
static int plic_platform_probe(struct platform_device *pdev)
{
- return plic_probe(pdev->dev.fwnode);
+ if (plic_probe(pdev->dev.fwnode))
+ panic("%pfwP: Failed to initialize\n", pdev->dev.fwnode);
+ return 0;
}
static struct platform_driver plic_driver = {
@@ -842,7 +814,9 @@ builtin_platform_driver(plic_driver);
static int __init plic_early_probe(struct device_node *node,
struct device_node *parent)
{
- return plic_probe(&node->fwnode);
+ if (plic_probe(&node->fwnode))
+ panic("%pfwP: Failed to initialize\n", &node->fwnode);
+ return 0;
}
IRQCHIP_DECLARE(riscv, "allwinner,sun20i-d1-plic", plic_early_probe);
diff --git a/drivers/irqchip/irq-sni-exiu.c b/drivers/irqchip/irq-sni-exiu.c
index bfd2adedf8f3..108d7f2e59c4 100644
--- a/drivers/irqchip/irq-sni-exiu.c
+++ b/drivers/irqchip/irq-sni-exiu.c
@@ -307,8 +307,8 @@ static int exiu_acpi_probe(struct platform_device *pdev)
}
static const struct acpi_device_id exiu_acpi_ids[] = {
- { "SCX0008" },
- { /* sentinel */ }
+ { .id = "SCX0008" },
+ { }
};
MODULE_DEVICE_TABLE(acpi, exiu_acpi_ids);
diff --git a/drivers/irqchip/irq-st.c b/drivers/irqchip/irq-st.c
index de71bb350d57..fef0521831aa 100644
--- a/drivers/irqchip/irq-st.c
+++ b/drivers/irqchip/irq-st.c
@@ -157,7 +157,7 @@ static int st_irq_syscfg_probe(struct platform_device *pdev)
return st_irq_syscfg_enable(pdev);
}
-static int __maybe_unused st_irq_syscfg_resume(struct device *dev)
+static int st_irq_syscfg_resume(struct device *dev)
{
struct st_irq_syscfg *ddata = dev_get_drvdata(dev);
@@ -165,12 +165,12 @@ static int __maybe_unused st_irq_syscfg_resume(struct device *dev)
ST_A9_IRQ_MASK, ddata->config);
}
-static SIMPLE_DEV_PM_OPS(st_irq_syscfg_pm_ops, NULL, st_irq_syscfg_resume);
+static DEFINE_SIMPLE_DEV_PM_OPS(st_irq_syscfg_pm_ops, NULL, st_irq_syscfg_resume);
static struct platform_driver st_irq_syscfg_driver = {
.driver = {
.name = "st_irq_syscfg",
- .pm = &st_irq_syscfg_pm_ops,
+ .pm = pm_sleep_ptr(&st_irq_syscfg_pm_ops),
.of_match_table = st_irq_syscfg_match,
},
.probe = st_irq_syscfg_probe,
diff --git a/drivers/irqchip/irq-vic.c b/drivers/irqchip/irq-vic.c
index e38104c5064e..607e3284f700 100644
--- a/drivers/irqchip/irq-vic.c
+++ b/drivers/irqchip/irq-vic.c
@@ -478,7 +478,7 @@ static void __init __vic_init(void __iomem *base, int parent_irq, int irq_start,
/**
* vic_init() - initialise a vectored interrupt controller
* @base: iomem base address
- * @irq_start: starting interrupt number, must be muliple of 32
+ * @irq_start: starting interrupt number, must be multiple of 32
* @vic_sources: bitmask of interrupt sources to allow
* @resume_sources: bitmask of interrupt sources to allow for resume
*/
diff --git a/drivers/irqchip/irq-xilinx-smmu-csr.c b/drivers/irqchip/irq-xilinx-smmu-csr.c
new file mode 100644
index 000000000000..9bb625598107
--- /dev/null
+++ b/drivers/irqchip/irq-xilinx-smmu-csr.c
@@ -0,0 +1,244 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Xilinx Versal Net SMMU CSR interrupt controller driver
+ *
+ * Copyright (C) 2026 Advanced Micro Devices, Inc.
+ *
+ * Vendor CSR block that gates the ARM SMMUv3 wired interrupts. Chains
+ * off the single GIC parent line and demultiplexes the CSR status into
+ * three children (eventq, gerror, priq), each enabled/acked
+ * via SMMU_CSR_IER/IDR/ISR that the ARM SMMUv3 driver requests individually.
+ */
+
+#include <linux/bitops.h>
+#include <linux/io.h>
+#include <linux/irqchip.h>
+#include <linux/irqchip/chained_irq.h>
+#include <linux/of_address.h>
+#include <linux/of_irq.h>
+#include <linux/slab.h>
+#include <linux/spinlock.h>
+
+/* Interrupt types */
+#define SMMU_INTR_EVENT BIT(0)
+#define SMMU_INTR_GLOBAL BIT(2)
+#define SMMU_INTR_PRI BIT(3)
+
+/* Mask for the SMMU interrupts this driver demultiplexes */
+#define SMMU_INTR_ALL (SMMU_INTR_EVENT | SMMU_INTR_GLOBAL | SMMU_INTR_PRI)
+
+/* Every interrupt source the CSR block implements (incl. unsupported CMD_SYNC) */
+#define SMMU_INTR_HW_ALL GENMASK(3, 0)
+
+#define SMMU_CSR_ISR 0x24 /* Interrupt Status (raw, W1C) */
+#define SMMU_CSR_MASK 0x28 /* Interrupt Mask (RO): 1 = masked */
+#define SMMU_CSR_IER 0x2c /* Interrupt Enable (W1 to unmask) */
+#define SMMU_CSR_IDR 0x30 /* Interrupt Disable (W1 to mask) */
+
+/**
+ * struct xilinx_smmu_csr - SMMU CSR interrupt controller context
+ * @base: MMIO base address of the CSR registers
+ * @domain: Interrupt domain for the child interrupts
+ * @parent_irq: parent (GIC) interrupt this block is chained to
+ * @lock: protects the SMMU_CSR_IER/IDR/ISR read and writes
+ */
+struct xilinx_smmu_csr {
+ void __iomem *base;
+ struct irq_domain *domain;
+ int parent_irq;
+ raw_spinlock_t lock;
+};
+
+enum xilinx_smmu_csr_irq {
+ SMMU_CSR_IRQ_EVENTQ = 0,
+ SMMU_CSR_IRQ_GERROR = 2,
+ SMMU_CSR_IRQ_PRIQ = 3,
+ SMMU_CSR_IRQ_NR,
+};
+
+static u32 xilinx_smmu_csr_hwirq_mask(irq_hw_number_t hwirq)
+{
+ if (hwirq >= SMMU_CSR_IRQ_NR)
+ return 0;
+
+ return BIT(hwirq) & SMMU_INTR_ALL;
+}
+
+static void xilinx_smmu_csr_irq_mask(struct irq_data *d)
+{
+ struct xilinx_smmu_csr *csr = irq_data_get_irq_chip_data(d);
+ u32 mask = xilinx_smmu_csr_hwirq_mask(d->hwirq);
+
+ if (!mask)
+ return;
+
+ guard(raw_spinlock)(&csr->lock);
+ writel(mask, csr->base + SMMU_CSR_IDR);
+}
+
+static void xilinx_smmu_csr_irq_unmask(struct irq_data *d)
+{
+ struct xilinx_smmu_csr *csr = irq_data_get_irq_chip_data(d);
+ u32 mask = xilinx_smmu_csr_hwirq_mask(d->hwirq);
+
+ if (!mask)
+ return;
+
+ guard(raw_spinlock)(&csr->lock);
+ writel(mask, csr->base + SMMU_CSR_IER);
+}
+
+static void xilinx_smmu_csr_irq_eoi(struct irq_data *d)
+{
+ struct xilinx_smmu_csr *csr = irq_data_get_irq_chip_data(d);
+ u32 mask = xilinx_smmu_csr_hwirq_mask(d->hwirq);
+
+ if (!mask)
+ return;
+
+ guard(raw_spinlock)(&csr->lock);
+ writel(mask, csr->base + SMMU_CSR_ISR);
+}
+
+static struct irq_chip xilinx_smmu_csr_chip = {
+ .name = "xlnx-smmu-csr",
+ .irq_mask = xilinx_smmu_csr_irq_mask,
+ .irq_unmask = xilinx_smmu_csr_irq_unmask,
+ .irq_eoi = xilinx_smmu_csr_irq_eoi,
+ /* Defer sticky ISR clear until the threaded SMMU handler drains it. */
+ .flags = IRQCHIP_EOI_THREADED,
+};
+
+static void xilinx_smmu_csr_irq_handler(struct irq_desc *desc)
+{
+ struct xilinx_smmu_csr *csr = irq_desc_get_handler_data(desc);
+ struct irq_chip *chip = irq_desc_get_chip(desc);
+ u32 status, mask, active, pending, spurious;
+
+ chained_irq_enter(chip, desc);
+
+ /*
+ * SMMU_CSR_ISR latches raw, pre-mask status, so gate it with the mask
+ * register to only act on sources that are actually enabled.
+ */
+ status = readl(csr->base + SMMU_CSR_ISR);
+ mask = readl(csr->base + SMMU_CSR_MASK);
+ active = status & ~mask;
+
+ /*
+ * Defend against any enabled source this driver does not demultiplex
+ * (e.g. CMD_SYNC). It would never be acked and would livelock the
+ * parent line, so mask and clear it here.
+ */
+ spurious = active & ~SMMU_INTR_ALL;
+ if (spurious) {
+ writel(spurious, csr->base + SMMU_CSR_IDR);
+ writel(spurious, csr->base + SMMU_CSR_ISR);
+ }
+
+ pending = active & SMMU_INTR_ALL;
+
+ while (pending) {
+ irq_hw_number_t hwirq = __ffs(pending);
+
+ if (generic_handle_domain_irq(csr->domain, hwirq)) {
+ /*
+ * No mapped consumer: mask and clear so an unexpected
+ * source cannot spin the parent line.
+ */
+ writel(BIT(hwirq), csr->base + SMMU_CSR_IDR);
+ writel(BIT(hwirq), csr->base + SMMU_CSR_ISR);
+ }
+
+ pending &= ~BIT(hwirq);
+ }
+
+ chained_irq_exit(chip, desc);
+}
+
+static int xilinx_smmu_csr_domain_map(struct irq_domain *d, unsigned int virq,
+ irq_hw_number_t hwirq)
+{
+ struct xilinx_smmu_csr *csr = d->host_data;
+
+ if (!xilinx_smmu_csr_hwirq_mask(hwirq))
+ return -EINVAL;
+
+ irq_set_chip_and_handler(virq, &xilinx_smmu_csr_chip, handle_fasteoi_irq);
+ irq_set_chip_data(virq, csr);
+ irq_set_status_flags(virq, IRQ_LEVEL);
+
+ return 0;
+}
+
+static const struct irq_domain_ops xilinx_smmu_csr_domain_ops = {
+ .map = xilinx_smmu_csr_domain_map,
+ .xlate = irq_domain_xlate_onecell,
+};
+
+static int __init xilinx_smmu_csr_init(struct device_node *node, struct device_node *parent)
+{
+ struct xilinx_smmu_csr *csr;
+ int ret;
+
+ if (WARN_ON_ONCE(!parent))
+ return -EINVAL;
+
+ if (irq_find_matching_fwnode(of_fwnode_handle(node), DOMAIN_BUS_ANY))
+ return -ENODEV;
+
+ csr = kzalloc(sizeof(*csr), GFP_KERNEL);
+ if (!csr)
+ return -ENOMEM;
+
+ raw_spin_lock_init(&csr->lock);
+
+ csr->base = of_iomap(node, 0);
+ if (!csr->base) {
+ ret = -ENOMEM;
+ goto free;
+ }
+
+ /*
+ * Start from a known state: mask and clear *every* source the block
+ * implements, including CMD_SYNC (bit 1) which this driver does not
+ * service. The CSR ORs (status & ~mask) onto a single parent line, so
+ * a source left unmasked and latched by a prior boot stage (firmware,
+ * kexec) would assert the line forever with no one to ack it. Masking
+ * the whole set here makes that impossible; the ARM SMMUv3 driver
+ * unmasks the sources it needs via the child irq_unmask() callbacks.
+ */
+ writel(SMMU_INTR_HW_ALL, csr->base + SMMU_CSR_IDR);
+ writel(SMMU_INTR_HW_ALL, csr->base + SMMU_CSR_ISR);
+
+ csr->domain = irq_domain_create_linear(of_fwnode_handle(node), SMMU_CSR_IRQ_NR,
+ &xilinx_smmu_csr_domain_ops, csr);
+ if (!csr->domain) {
+ pr_err("%pOF: failed to create irq domain\n", node);
+ ret = -ENOMEM;
+ goto unmap;
+ }
+
+ csr->parent_irq = irq_of_parse_and_map(node, 0);
+ if (!csr->parent_irq) {
+ pr_err("%pOF: failed to map parent irq\n", node);
+ ret = -EINVAL;
+ goto remove_domain;
+ }
+
+ irq_set_chained_handler_and_data(csr->parent_irq, xilinx_smmu_csr_irq_handler, csr);
+
+ pr_debug("%pOF: Xilinx SMMU CSR interrupt controller registered\n", node);
+
+ return 0;
+
+remove_domain:
+ irq_domain_remove(csr->domain);
+unmap:
+ iounmap(csr->base);
+free:
+ kfree(csr);
+ return ret;
+}
+
+IRQCHIP_DECLARE(xilinx_smmu_csr, "xlnx,versal-net-smmu-csr", xilinx_smmu_csr_init);
diff --git a/drivers/irqchip/qcom-irq-combiner.c b/drivers/irqchip/qcom-irq-combiner.c
index 09819007d08e..6d83e1e4a7ea 100644
--- a/drivers/irqchip/qcom-irq-combiner.c
+++ b/drivers/irqchip/qcom-irq-combiner.c
@@ -262,7 +262,7 @@ static int combiner_probe(struct platform_device *pdev)
}
static const struct acpi_device_id qcom_irq_combiner_ids[] = {
- { "QCOM80B1", },
+ { .id = "QCOM80B1" },
{ }
};
diff --git a/drivers/irqchip/qcom-pdc.c b/drivers/irqchip/qcom-pdc.c
index ce6d80c7f17a..29025a212ece 100644
--- a/drivers/irqchip/qcom-pdc.c
+++ b/drivers/irqchip/qcom-pdc.c
@@ -715,7 +715,10 @@ static int qcom_pdc_probe(struct platform_device *pdev, struct device_node *pare
}
pdc->x1e_quirk = true;
+ }
+ if (of_device_is_compatible(node, "qcom,x1e80100-pdc") ||
+ of_device_is_compatible(node, "qcom,x1p42100-pdc")) {
if (!qcom_scm_is_available())
return -EPROBE_DEFER;
diff --git a/drivers/soc/fsl/qe/qe_ports_ic.c b/drivers/soc/fsl/qe/qe_ports_ic.c
index 7375f92f528b..fb3b92039547 100644
--- a/drivers/soc/fsl/qe/qe_ports_ic.c
+++ b/drivers/soc/fsl/qe/qe_ports_ic.c
@@ -140,7 +140,6 @@ static int qepic_probe(struct platform_device *pdev)
};
struct irq_domain_info d_info = {
.fwnode = of_fwnode_handle(pdev->dev.of_node),
- .domain_flags = IRQ_DOMAIN_FLAG_DESTROY_GC,
.size = 32,
.hwirq_max = 32,
.ops = &irq_generic_chip_ops,
diff --git a/include/linux/irqchip/riscv-imsic.h b/include/linux/irqchip/riscv-imsic.h
index 61af3a5bea09..b7be1aad7774 100644
--- a/include/linux/irqchip/riscv-imsic.h
+++ b/include/linux/irqchip/riscv-imsic.h
@@ -46,6 +46,9 @@ struct imsic_local_config {
};
struct imsic_global_config {
+ /* Device details */
+ struct fwnode_handle *fwnode;
+
/*
* MSI Target Address Scheme
*
diff --git a/kernel/irq/irqdomain.c b/kernel/irq/irqdomain.c
index 57c819da30c2..4fdcb6df5306 100644
--- a/kernel/irq/irqdomain.c
+++ b/kernel/irq/irqdomain.c
@@ -344,6 +344,7 @@ static struct irq_domain *__irq_domain_instantiate(const struct irq_domain_info
err = irq_domain_alloc_generic_chips(domain, info->dgc_info);
if (err)
goto err_domain_free;
+ domain->flags |= IRQ_DOMAIN_FLAG_DESTROY_GC;
}
if (info->init) {
^ permalink raw reply [flat|nested] 12+ messages in thread
* [GIT pull] timers/clocksource for v7.4-rc1
2026-10-21 9:50 [GIT pull] irq/core for v7.4-rc1 Thomas Gleixner
2026-10-21 9:51 ` [GIT pull] irq/drivers " Thomas Gleixner
@ 2026-10-21 9:51 ` Thomas Gleixner
2026-10-21 21:43 ` pr-tracker-bot
2026-10-21 9:51 ` [GIT pull] timers/core " Thomas Gleixner
` (3 subsequent siblings)
5 siblings, 1 reply; 12+ messages in thread
From: Thomas Gleixner @ 2026-10-21 9:51 UTC (permalink / raw)
To: Linus Torvalds; +Cc: linux-kernel, x86
Linus,
please pull the latest timers/clocksource branch from:
git://git.kernel.org/pub/scm/linux/kernel/git/tip/tip.git timers-clocksource-2026-10-20
up to: 5bbd49a96e04: Merge tag 'timers-v7.4-rc1' of git://git.kernel.org/pub/scm/linux/kernel/git/daniel.lezcano/linux into timers/clocksource
Updates for clocksource and clockevent drivers:
- Support for the Renesas RZ/N1 timers
- Rework of the per CPU and per cluster MIPS GIC timers to improve
performance, sched clock precision and to add VDSO support.
- The usual assorted pile of small fixes and improvements all over the
place.
Thanks,
tglx
------------------>
Benoît Monin (2):
clocksource/drivers/mips-gic-timer: Simplify gic_next_event() for per-cpu timer
clocksource/drivers/mips-gic-timer: Use local counter on synced multi-cluster systems
Herve Codina (Schneider Electric) (3):
dt-bindings: timer: Add the Renesas RZ/N1 timer
clocksource/drivers: Add support for the Renesas RZ/N1 timers
MAINTAINERS: Add the Renesas RZ/N1 timers driver entry
Jiale Yao (1):
clocksource/drivers/timer-ti-dm: Handle runtime resume failure
Luca Leonardo Scorcia (3):
clocksource/drivers/timer-mediatek: Fix random crash at boot
clocksource/drivers/timer-mediatek: Convert to OF probing
clocksource/drivers/timer-mediatek: Request and enable the bus clock
Markus Schneider-Pargmann (TI) (1):
clocksource/drivers/timer-ti-dm: Register clocksource/event only for am654
Slavin Liu (1):
clocksource/drivers/dbx500-prcmu: Check timer register mapping
Wentao Liang (3):
clocksource/drivers/fttmr010: Fix clk reference leak in error paths
clocksource/drivers/digicolor: Fix clk reference leak on request_irq failure
clocksource/drivers/timer-econet-en751221: Fix clock reference leak
.../bindings/timer/renesas,rzn1-timer.yaml | 74 ++++
MAINTAINERS | 7 +
drivers/clocksource/Kconfig | 11 +
drivers/clocksource/Makefile | 1 +
drivers/clocksource/clksrc-dbx500-prcmu.c | 2 +
drivers/clocksource/mips-gic-timer.c | 138 +++++-
drivers/clocksource/timer-digicolor.c | 2 +
drivers/clocksource/timer-econet-en751221.c | 1 +
drivers/clocksource/timer-fttmr010.c | 2 +
drivers/clocksource/timer-mediatek.c | 47 +-
drivers/clocksource/timer-rzn1.c | 483 +++++++++++++++++++++
drivers/clocksource/timer-ti-dm.c | 10 +-
include/linux/platform_data/dmtimer-omap.h | 2 +
13 files changed, 753 insertions(+), 27 deletions(-)
create mode 100644 Documentation/devicetree/bindings/timer/renesas,rzn1-timer.yaml
create mode 100644 drivers/clocksource/timer-rzn1.c
diff --git a/Documentation/devicetree/bindings/timer/renesas,rzn1-timer.yaml b/Documentation/devicetree/bindings/timer/renesas,rzn1-timer.yaml
new file mode 100644
index 000000000000..7218c0c2671d
--- /dev/null
+++ b/Documentation/devicetree/bindings/timer/renesas,rzn1-timer.yaml
@@ -0,0 +1,74 @@
+# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/timer/renesas,rzn1-timer.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: Renesas RZ/N1 timers
+
+maintainers:
+ - Herve Codina <herve.codina@bootlin.com>
+
+description: |
+ The Renesas RZ/N1 SoCs timers block controller is composed of 8 independent
+ timers.
+ - 6 are 16-bit timers
+ - 2 are 32-bit timers
+
+ Each timer has its own interrupt line and can work in either one-shot or
+ periodic mode.
+
+properties:
+ compatible:
+ items:
+ - const: renesas,r9a06g032-timer # RZ/N1D
+ - const: renesas,rzn1-timer
+
+ reg:
+ maxItems: 1
+
+ clocks:
+ items:
+ - description: APB internal bus clock
+
+ clock-names:
+ items:
+ - const: pclk
+
+ power-domains:
+ maxItems: 1
+
+ interrupts:
+ maxItems: 8
+
+required:
+ - compatible
+ - reg
+ - clocks
+ - clock-names
+ - power-domains
+ - interrupts
+
+additionalProperties: false
+
+examples:
+ - |
+ #include <dt-bindings/interrupt-controller/arm-gic.h>
+ #include <dt-bindings/clock/r9a06g032-sysctrl.h>
+
+ timer@51001000 {
+ compatible = "renesas,r9a06g032-timer", "renesas,rzn1-timer";
+ reg = <0x51001000 0x200>;
+ clocks = <&sysctrl R9A06G032_HCLK_TIMER0>;
+ clock-names = "pclk";
+ power-domains = <&sysctrl>;
+ interrupts = <GIC_SPI 112 IRQ_TYPE_LEVEL_HIGH>,
+ <GIC_SPI 113 IRQ_TYPE_LEVEL_HIGH>,
+ <GIC_SPI 114 IRQ_TYPE_LEVEL_HIGH>,
+ <GIC_SPI 115 IRQ_TYPE_LEVEL_HIGH>,
+ <GIC_SPI 116 IRQ_TYPE_LEVEL_HIGH>,
+ <GIC_SPI 117 IRQ_TYPE_LEVEL_HIGH>,
+ <GIC_SPI 118 IRQ_TYPE_LEVEL_HIGH>,
+ <GIC_SPI 119 IRQ_TYPE_LEVEL_HIGH>;
+ };
+...
diff --git a/MAINTAINERS b/MAINTAINERS
index 72294ddfa5b7..7bdacc8feeed 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -23295,6 +23295,13 @@ S: Maintained
F: Documentation/devicetree/bindings/rtc/renesas,rzn1-rtc.yaml
F: drivers/rtc/rtc-rzn1.c
+RENESAS RZ/N1 TIMERS DRIVER
+M: Herve Codina <herve.codina@bootlin.com>
+L: linux-renesas-soc@vger.kernel.org
+S: Maintained
+F: Documentation/devicetree/bindings/timer/renesas,rzn1-timer.yaml
+F: drivers/clocksource/timer-rzn1.c
+
RENESAS RZ/N1 USBF CONTROLLER DRIVER
M: Herve Codina <herve.codina@bootlin.com>
L: linux-renesas-soc@vger.kernel.org
diff --git a/drivers/clocksource/Kconfig b/drivers/clocksource/Kconfig
index d9c76dd443f8..da83483296d5 100644
--- a/drivers/clocksource/Kconfig
+++ b/drivers/clocksource/Kconfig
@@ -152,6 +152,17 @@ config REALTEK_OTTO_TIMER
RT8391, RTL8392, RTL8393 and RTL8396 and chips of the RTL930x series
such as RTL9301, RTL9302 or RTL9303.
+config RZN1_TIMER
+ bool "Renesas RZ/N1 Timer"
+ depends on HAS_IOMEM && COMMON_CLK
+ depends on ARCH_RZN1 || COMPILE_TEST
+ help
+ Enables support for RZ/N1 SoC timers.
+ A timers block in RZ/N1 SoCs is composed of 8 timers
+ - 6 16-bit timers
+ - 2 32-bit timers
+ Two timers blocks are available in RZ/N1 SoCs.
+
config SUN4I_TIMER
bool "Sun4i timer driver" if COMPILE_TEST
depends on HAS_IOMEM
diff --git a/drivers/clocksource/Makefile b/drivers/clocksource/Makefile
index b46376af6b49..464ec6690ab6 100644
--- a/drivers/clocksource/Makefile
+++ b/drivers/clocksource/Makefile
@@ -96,3 +96,4 @@ obj-$(CONFIG_EP93XX_TIMER) += timer-ep93xx.o
obj-$(CONFIG_RALINK_TIMER) += timer-ralink.o
obj-$(CONFIG_NXP_STM_TIMER) += timer-nxp-stm.o
obj-$(CONFIG_RTK_SYSTIMER) += timer-realtek.o
+obj-$(CONFIG_RZN1_TIMER) += timer-rzn1.o
diff --git a/drivers/clocksource/clksrc-dbx500-prcmu.c b/drivers/clocksource/clksrc-dbx500-prcmu.c
index 2fc93e46cea3..1af223b0e896 100644
--- a/drivers/clocksource/clksrc-dbx500-prcmu.c
+++ b/drivers/clocksource/clksrc-dbx500-prcmu.c
@@ -52,6 +52,8 @@ static struct clocksource clocksource_dbx500_prcmu = {
static int __init clksrc_dbx500_prcmu_init(struct device_node *node)
{
clksrc_dbx500_timer_base = of_iomap(node, 0);
+ if (!clksrc_dbx500_timer_base)
+ return -ENOMEM;
/*
* The A9 sub system expects the timer to be configured as
diff --git a/drivers/clocksource/mips-gic-timer.c b/drivers/clocksource/mips-gic-timer.c
index 1501c7db9a8e..fc97b31f0adb 100644
--- a/drivers/clocksource/mips-gic-timer.c
+++ b/drivers/clocksource/mips-gic-timer.c
@@ -6,6 +6,7 @@
#include <linux/clk.h>
#include <linux/clockchips.h>
#include <linux/cpu.h>
+#include <linux/delay.h>
#include <linux/init.h>
#include <linux/interrupt.h>
#include <linux/notifier.h>
@@ -21,6 +22,9 @@ static int gic_timer_irq;
static unsigned int gic_frequency;
static unsigned int gic_count_width;
static bool __read_mostly gic_clock_unstable;
+static bool __read_mostly gic_use_local_counter = true;
+static unsigned long *gic_synced_cl_map;
+static struct work_struct gic_promote_work;
static void gic_clocksource_unstable(char *reason);
@@ -52,18 +56,13 @@ static u64 notrace gic_read_count(void)
static int gic_next_event(unsigned long delta, struct clock_event_device *evt)
{
- int cpu = cpumask_first(evt->cpumask);
u64 cnt;
int res;
cnt = gic_read_count();
cnt += (u64)delta;
- if (cpu == raw_smp_processor_id()) {
- write_gic_vl_compare(cnt);
- } else {
- write_gic_vl_other(mips_cm_vp_id(cpu));
- write_gic_vo_compare(cnt);
- }
+ write_gic_vl_compare(cnt);
+
res = ((int)(gic_read_count() - cnt) >= 0) ? -ETIME : 0;
return res;
}
@@ -82,6 +81,7 @@ static void gic_clockevent_cpu_init(unsigned int cpu,
{
cd->name = "MIPS GIC";
cd->features = CLOCK_EVT_FEAT_ONESHOT |
+ CLOCK_EVT_FEAT_PERCPU |
CLOCK_EVT_FEAT_C3STOP;
cd->rating = 350;
@@ -106,10 +106,71 @@ static void gic_update_frequency(void *data)
clockevents_update_freq(this_cpu_ptr(&gic_clockevent_device), rate);
}
+/* Number of iterations to synchronize the local GIC counter */
+#define GIC_SYNC_ITERATIONS 4
+
+/* Delay in us to check if the local GIC counter is still in sync with cluster 0 */
+#define GIC_SYNC_CHECK_DELAY 100
+
+static void gic_sync_counter_64(unsigned int cluster)
+{
+ unsigned int config = read_gic_config();
+ u64 t0, t1, t2;
+ s64 offset = 0;
+
+ mips_cm_lock_other(0, 0, 0, CM_GCR_Cx_OTHER_BLOCK_GLOBAL);
+
+ for (int i = 0; i < GIC_SYNC_ITERATIONS; i++) {
+ write_gic_config(config | GIC_CONFIG_COUNTSTOP);
+ write_gic_counter(read_gic_redir_counter() + offset);
+ write_gic_config(config & ~GIC_CONFIG_COUNTSTOP);
+
+ t0 = read_gic_counter();
+ t1 = read_gic_redir_counter();
+ t2 = read_gic_counter();
+
+ if (time_in_range64(t1, t0, t2))
+ break;
+
+ /*
+ * Compute the offset to apply to the local counter
+ * so that (t1 - t0) equals (t2 - t1).
+ */
+ offset += (s64)(2 * t1 - t0 - t2) / 2;
+ }
+
+ mips_cm_unlock_other();
+
+ if (!time_in_range64(t1, t0, t2))
+ return;
+
+ udelay(GIC_SYNC_CHECK_DELAY);
+
+ mips_cm_lock_other(0, 0, 0, CM_GCR_Cx_OTHER_BLOCK_GLOBAL);
+ t0 = read_gic_counter();
+ t1 = read_gic_redir_counter();
+ t2 = read_gic_counter();
+ mips_cm_unlock_other();
+
+ /* If so, mark the cluster as synchronized */
+ if (time_in_range64(t1, t0, t2) && gic_synced_cl_map)
+ bitmap_set(gic_synced_cl_map, cluster, 1);
+}
+
static int gic_starting_cpu(unsigned int cpu)
{
- /* Ensure the GIC counter is running */
- clear_gic_config(GIC_CONFIG_COUNTSTOP);
+ unsigned int cluster = cpu_cluster(&cpu_data[cpu]);
+
+ if (read_gic_config() & GIC_CONFIG_COUNTSTOP) {
+ clear_gic_config(GIC_CONFIG_COUNTSTOP);
+
+ if (cluster && mips_cm_is64 && !gic_clock_unstable)
+ gic_sync_counter_64(cluster);
+
+ if (gic_synced_cl_map &&
+ bitmap_full(gic_synced_cl_map, mips_cps_numclusters()))
+ schedule_work(&gic_promote_work);
+ }
gic_clockevent_cpu_init(cpu, this_cpu_ptr(&gic_clockevent_device));
return 0;
@@ -158,11 +219,6 @@ static int gic_clockevent_init(void)
return 0;
}
-static u64 gic_hpt_read(struct clocksource *cs)
-{
- return gic_read_count();
-}
-
static u64 gic_hpt_read_multicluster(struct clocksource *cs)
{
unsigned int hi, hi2, lo;
@@ -194,6 +250,14 @@ static u64 gic_hpt_read_multicluster(struct clocksource *cs)
return count;
}
+static u64 gic_hpt_read(struct clocksource *cs)
+{
+ if (unlikely(!READ_ONCE(gic_use_local_counter)))
+ return gic_hpt_read_multicluster(cs);
+
+ return gic_read_count();
+}
+
static struct clocksource gic_clocksource = {
.name = "GIC",
.read = gic_hpt_read,
@@ -213,8 +277,28 @@ static void gic_clocksource_unstable(char *reason)
clocksource_mark_unstable(&gic_clocksource);
}
+static void gic_clocksource_promote(struct work_struct *work)
+{
+ if (gic_clock_unstable || READ_ONCE(gic_use_local_counter))
+ return;
+
+ WRITE_ONCE(gic_use_local_counter, true);
+
+#ifdef CONFIG_GENERIC_GETTIMEOFDAY
+ gic_clocksource.vdso_clock_mode = VDSO_CLOCKMODE_GIC;
+#endif
+
+ if (mips_cm_revision() >= CM_REV_CM3 || !IS_ENABLED(CONFIG_CPU_FREQ)) {
+ sched_clock_register(mips_cm_is64 ?
+ gic_read_count_64 : gic_read_count_2x32,
+ gic_count_width, gic_frequency);
+ }
+}
+
static int __init __gic_clocksource_init(void)
{
+ unsigned int numclusters;
+ bool synced = false;
int ret;
/* Set clocksource mask. */
@@ -226,14 +310,32 @@ static int __init __gic_clocksource_init(void)
/* Calculate a somewhat reasonable rating value. */
if (mips_cm_revision() >= CM_REV_CM3 || !IS_ENABLED(CONFIG_CPU_FREQ))
- gic_clocksource.rating = 300; /* Good when frequecy is stable */
+ gic_clocksource.rating = 300; /* Good when frequency is stable */
else
gic_clocksource.rating = 200;
gic_clocksource.rating += clamp(gic_frequency / 10000000, 0, 99);
- if (mips_cps_multicluster_cpus()) {
- gic_clocksource.read = &gic_hpt_read_multicluster;
+ numclusters = mips_cps_numclusters();
+ if (numclusters > 1)
+ gic_synced_cl_map = bitmap_zalloc(numclusters, GFP_KERNEL);
+
+ /*
+ * Mark cluster 0 as synchronized (with itself), and all clusters
+ * without cores since there is no local GIC counter access on those.
+ */
+ if (gic_synced_cl_map) {
+ bitmap_set(gic_synced_cl_map, 0, 1);
+ for (unsigned int cl = 0; cl < numclusters; cl++) {
+ if (!mips_cps_numcores(cl))
+ bitmap_set(gic_synced_cl_map, cl, 1);
+ }
+ synced = bitmap_full(gic_synced_cl_map, numclusters);
+ }
+
+ if (numclusters > 1 && !synced) {
+ WRITE_ONCE(gic_use_local_counter, false);
gic_clocksource.vdso_clock_mode = VDSO_CLOCKMODE_NONE;
+ INIT_WORK(&gic_promote_work, gic_clocksource_promote);
}
ret = clocksource_register_hz(&gic_clocksource, gic_frequency);
@@ -292,7 +394,7 @@ static int __init gic_clocksource_of_init(struct device_node *node)
* change performed by the CPC core clocks divider.
*/
if ((mips_cm_revision() >= CM_REV_CM3 || !IS_ENABLED(CONFIG_CPU_FREQ)) &&
- !mips_cps_multicluster_cpus()) {
+ READ_ONCE(gic_use_local_counter)) {
sched_clock_register(mips_cm_is64 ?
gic_read_count_64 : gic_read_count_2x32,
gic_count_width, gic_frequency);
diff --git a/drivers/clocksource/timer-digicolor.c b/drivers/clocksource/timer-digicolor.c
index 559aa96089c3..46e0c329957a 100644
--- a/drivers/clocksource/timer-digicolor.c
+++ b/drivers/clocksource/timer-digicolor.c
@@ -190,6 +190,8 @@ static int __init digicolor_timer_init(struct device_node *node)
&dc_timer_dev.ce);
if (ret) {
pr_warn("request of timer irq %d failed (%d)\n", irq, ret);
+ clk_disable_unprepare(clk);
+ clk_put(clk);
return ret;
}
diff --git a/drivers/clocksource/timer-econet-en751221.c b/drivers/clocksource/timer-econet-en751221.c
index 4008076b1a21..1859335345b5 100644
--- a/drivers/clocksource/timer-econet-en751221.c
+++ b/drivers/clocksource/timer-econet-en751221.c
@@ -181,6 +181,7 @@ static int __init timer_init(struct device_node *np)
}
econet_timer.freq_hz = clk_get_rate(clk);
+ clk_put(clk);
for (int i = 0; i < num_blocks; i++) {
econet_timer.membase[i] = of_iomap(np, i);
diff --git a/drivers/clocksource/timer-fttmr010.c b/drivers/clocksource/timer-fttmr010.c
index 308f94bfbaed..c2553851e8d6 100644
--- a/drivers/clocksource/timer-fttmr010.c
+++ b/drivers/clocksource/timer-fttmr010.c
@@ -292,6 +292,7 @@ static int __init fttmr010_common_init(struct device_node *np,
ret = clk_prepare_enable(clk);
if (ret) {
pr_err("failed to enable PCLK\n");
+ clk_put(clk);
return ret;
}
@@ -432,6 +433,7 @@ static int __init fttmr010_common_init(struct device_node *np,
kfree(fttmr010);
out_disable_clock:
clk_disable_unprepare(clk);
+ clk_put(clk);
return ret;
}
diff --git a/drivers/clocksource/timer-mediatek.c b/drivers/clocksource/timer-mediatek.c
index 7bcb4a3f26fb..842d6ee4259e 100644
--- a/drivers/clocksource/timer-mediatek.c
+++ b/drivers/clocksource/timer-mediatek.c
@@ -9,10 +9,14 @@
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+#include <linux/clk.h>
#include <linux/clockchips.h>
#include <linux/clocksource.h>
#include <linux/interrupt.h>
#include <linux/irqreturn.h>
+#include <linux/module.h>
+#include <linux/of_device.h>
+#include <linux/platform_device.h>
#include <linux/sched_clock.h>
#include <linux/slab.h>
#include "timer-of.h"
@@ -215,8 +219,7 @@ static irqreturn_t mtk_gpt_interrupt(int irq, void *dev_id)
return IRQ_HANDLED;
}
-static void
-__init mtk_gpt_setup(struct timer_of *to, u8 timer, u8 option)
+static void mtk_gpt_setup(struct timer_of *to, u8 timer, u8 option)
{
writel(GPT_CTRL_CLEAR | GPT_CTRL_DISABLE,
timer_of_base(to) + GPT_CTRL_REG(timer));
@@ -277,12 +280,13 @@ static struct timer_of to = {
},
.of_irq = {
- .flags = IRQF_TIMER | IRQF_IRQPOLL,
+ .flags = IRQF_TIMER | IRQF_IRQPOLL | IRQF_NO_AUTOEN,
},
};
-static int __init mtk_syst_init(struct device_node *node)
+static int mtk_syst_init(struct platform_device *pdev)
{
+ struct device_node *node = pdev->dev.of_node;
int ret;
to.clkevt.features = CLOCK_EVT_FEAT_DYNIRQ | CLOCK_EVT_FEAT_ONESHOT;
@@ -298,14 +302,19 @@ static int __init mtk_syst_init(struct device_node *node)
clockevents_config_and_register(&to.clkevt, timer_of_rate(&to),
TIMER_SYNC_TICKS, 0xffffffff);
+ enable_irq(timer_of_irq(&to));
return 0;
}
-static int __init mtk_gpt_init(struct device_node *node)
+static int mtk_gpt_init(struct platform_device *pdev)
{
+ struct device_node *node = pdev->dev.of_node;
+ struct clk *clk_bus;
int ret;
+ clk_bus = devm_clk_get_optional_enabled(&pdev->dev, "bus");
+
to.clkevt.features = CLOCK_EVT_FEAT_PERIODIC | CLOCK_EVT_FEAT_ONESHOT;
to.clkevt.set_state_shutdown = mtk_gpt_clkevt_shutdown;
to.clkevt.set_state_periodic = mtk_gpt_clkevt_set_periodic;
@@ -334,8 +343,32 @@ static int __init mtk_gpt_init(struct device_node *node)
TIMER_SYNC_TICKS, 0xffffffff);
mtk_gpt_enable_irq(&to, TIMER_CLK_EVT);
+ enable_irq(timer_of_irq(&to));
return 0;
}
-TIMER_OF_DECLARE(mtk_mt6577, "mediatek,mt6577-timer", mtk_gpt_init);
-TIMER_OF_DECLARE(mtk_mt6765, "mediatek,mt6765-timer", mtk_syst_init);
+
+static int mtk_timer_probe(struct platform_device *pdev)
+{
+ int (*timer_init)(struct platform_device *pdev);
+
+ timer_init = of_device_get_match_data(&pdev->dev);
+ return timer_init(pdev);
+}
+
+static const struct of_device_id mtk_timer_match_table[] = {
+ { .compatible = "mediatek,mt6577-timer", .data = mtk_gpt_init },
+ { .compatible = "mediatek,mt6765-timer", .data = mtk_syst_init },
+ { /* sentinel */ }
+};
+
+static struct platform_driver mtk_timer_driver = {
+ .probe = mtk_timer_probe,
+ .driver = {
+ .name = "mediatek-timer",
+ .of_match_table = mtk_timer_match_table,
+ },
+};
+module_platform_driver(mtk_timer_driver);
+
+MODULE_DESCRIPTION("MediaTek Timer driver");
diff --git a/drivers/clocksource/timer-rzn1.c b/drivers/clocksource/timer-rzn1.c
new file mode 100644
index 000000000000..25ebebff3c63
--- /dev/null
+++ b/drivers/clocksource/timer-rzn1.c
@@ -0,0 +1,483 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * RZ/N1 timers driver
+ *
+ * Copyright (C) 2026 Schneider-Electric
+ *
+ * Author: Herve Codina <herve.codina@bootlin.com>
+ */
+
+#include <linux/bits.h>
+#include <linux/clk.h>
+#include <linux/clockchips.h>
+#include <linux/clocksource.h>
+#include <linux/cpuhotplug.h>
+#include <linux/interrupt.h>
+#include <linux/of.h>
+#include <linux/platform_device.h>
+#include <linux/sched_clock.h>
+
+/* Per-timer register offsets */
+#define RZN1_TIMER_LOAD_COUNT_REG 0x00
+#define RZN1_TIMER_CURRENT_COUNT_REG 0x04
+#define RZN1_TIMER_CONTROL_REG 0x08
+#define RZN1_TIMER_CONTROL_INT_UNMASK BIT(3)
+#define RZN1_TIMER_CONTROL_ENABLE BIT(2)
+#define RZN1_TIMER_CONTROL_AUTORELOAD BIT(1)
+#define RZN1_TIMER_CONTROL_PRESCALER_DIV25 BIT(0)
+#define RZN1_TIMER_CLEAR_INT_REG 0x0c
+#define RZN1_TIMER_STATUS_INT0_REG 0x10
+#define RZN1_TIMER_STATUS_INT1_REG 0x14
+
+/*
+ * 8 timers are available. Among those 8 timers, the first 6 timers are 16-bit
+ * timers and the last two ones are 32-bit timers.
+ */
+#define RZN1_TIMER_BASE_INDEX_16BIT_TIMERS 0
+#define RZN1_TIMER_NB_16BIT_TIMERS 6
+#define RZN1_TIMER_LAST_INDEX_16BIT_TIMERS (RZN1_TIMER_BASE_INDEX_16BIT_TIMERS + \
+ RZN1_TIMER_NB_16BIT_TIMERS - 1)
+
+#define RZN1_TIMER_BASE_INDEX_32BIT_TIMERS 6
+#define RZN1_TIMER_NB_32BIT_TIMERS 2
+#define RZN1_TIMER_LAST_INDEX_32BIT_TIMERS (RZN1_TIMER_BASE_INDEX_32BIT_TIMERS + \
+ RZN1_TIMER_NB_32BIT_TIMERS - 1)
+
+#define RZN1_TIMER_IS_16BIT_TIMER(_i) ((_i) < RZN1_TIMER_BASE_INDEX_32BIT_TIMERS)
+
+/* Total numbers of timers */
+#define RZN1_TIMER_NB_TIMERS (RZN1_TIMER_NB_16BIT_TIMERS + \
+ RZN1_TIMER_NB_32BIT_TIMERS)
+
+/* Offset of registers related to the timer number _i in the timers regs area */
+#define RZN1_TIMER_REGS_OFFSET(_i) (0x20 * (_i))
+
+struct rzn1_timer {
+ void __iomem *base;
+ unsigned int width;
+ unsigned long rate;
+ unsigned long hz_period;
+ struct clock_event_device ced;
+};
+
+static int rzn1_timer_config(struct rzn1_timer *timer, u32 period, bool is_periodic)
+{
+ u32 ctrl;
+
+ ctrl = readl(timer->base + RZN1_TIMER_CONTROL_REG);
+
+ /* Disable the timer */
+ writel(ctrl & ~RZN1_TIMER_CONTROL_ENABLE,
+ timer->base + RZN1_TIMER_CONTROL_REG);
+
+ /* Set the new period */
+ writel(period, timer->base + RZN1_TIMER_LOAD_COUNT_REG);
+
+ /* Prepare the timer mode */
+ if (is_periodic)
+ ctrl |= RZN1_TIMER_CONTROL_AUTORELOAD;
+ else
+ ctrl &= ~RZN1_TIMER_CONTROL_AUTORELOAD;
+
+ /*
+ * Enable the timer.
+ *
+ * This automatically resets the CURRENT_COUNT register.
+ */
+ ctrl |= RZN1_TIMER_CONTROL_ENABLE;
+ writel(ctrl, timer->base + RZN1_TIMER_CONTROL_REG);
+
+ return 0;
+}
+
+static void rzn1_timer_disable(struct rzn1_timer *timer)
+{
+ u32 ctrl;
+
+ ctrl = readl(timer->base + RZN1_TIMER_CONTROL_REG);
+ ctrl &= ~RZN1_TIMER_CONTROL_ENABLE;
+ writel(ctrl, timer->base + RZN1_TIMER_CONTROL_REG);
+}
+
+static void rzn1_timer_int_enable(struct rzn1_timer *timer)
+{
+ u32 ctrl;
+
+ ctrl = readl(timer->base + RZN1_TIMER_CONTROL_REG);
+ ctrl |= RZN1_TIMER_CONTROL_INT_UNMASK;
+ writel(ctrl, timer->base + RZN1_TIMER_CONTROL_REG);
+}
+
+static irqreturn_t rzn1_timer_interrupt(int irq, void *dev_id)
+{
+ struct rzn1_timer *timer = dev_id;
+
+ /* Ack the interrupt */
+ readl(timer->base + RZN1_TIMER_CLEAR_INT_REG);
+
+ if (timer->ced.event_handler)
+ timer->ced.event_handler(&timer->ced);
+
+ return IRQ_HANDLED;
+}
+
+static int rzn1_clkevt_set_next_event(unsigned long cycles,
+ struct clock_event_device *evt)
+{
+ struct rzn1_timer *timer = container_of(evt, struct rzn1_timer, ced);
+
+ return rzn1_timer_config(timer, cycles, false);
+}
+
+static int rzn1_clkevt_shutdown(struct clock_event_device *evt)
+{
+ struct rzn1_timer *timer = container_of(evt, struct rzn1_timer, ced);
+
+ rzn1_timer_disable(timer);
+ return 0;
+}
+
+static int rzn1_clkevt_set_periodic(struct clock_event_device *evt)
+{
+ struct rzn1_timer *timer = container_of(evt, struct rzn1_timer, ced);
+
+ rzn1_timer_config(timer, timer->hz_period, true);
+ return 0;
+}
+
+/*
+ * This global lock is used to prevent race conditions during global timers
+ * test and set operation in case the driver is using the async probe option.
+ */
+static DEFINE_MUTEX(rzn1_global_timers_lock);
+
+static int rzn1_timer_instance;
+
+/* scheduler and clocksource */
+static struct rzn1_timer *rzn1_sched_clock;
+
+static u64 notrace rzn1_sched_read(void)
+{
+ return readl_relaxed(rzn1_sched_clock->base + RZN1_TIMER_CURRENT_COUNT_REG);
+}
+
+static u64 rzn1_clksrc_read(struct clocksource *cs)
+{
+ return readl_relaxed(rzn1_sched_clock->base + RZN1_TIMER_CURRENT_COUNT_REG);
+}
+
+static struct clocksource rzn1_clocksource = {
+ .name = "rzn1_timer_clocksource",
+ .rating = 200,
+ .flags = CLOCK_SOURCE_IS_CONTINUOUS,
+ .read = rzn1_clksrc_read,
+};
+
+/* Timers table where 16-bit timers will be assigned per-cpu */
+static struct rzn1_timer *rzn1_tab_timers;
+
+static int rzn1_local_timer_starting_cpu(unsigned int cpu)
+{
+ struct rzn1_timer *timer;
+
+ /* We assign the first and second 16-bit timer as per-cpu timers. */
+ if (cpu >= 2)
+ return 0;
+
+ timer = &rzn1_tab_timers[cpu + RZN1_TIMER_BASE_INDEX_16BIT_TIMERS];
+
+ timer->ced.cpumask = cpumask_of(cpu);
+ irq_force_affinity(timer->ced.irq, cpumask_of(cpu));
+
+ clockevents_config_and_register(&timer->ced, timer->rate, 1,
+ GENMASK(timer->width - 1, 0));
+ /*
+ * Time to enable the interrupt even if no interrupt can be fired yet.
+ * Indeed, the timer is still globally disabled.
+ * It will be enabled when rzn1_timer_config() is called
+ */
+ enable_irq(timer->ced.irq);
+ return 0;
+}
+
+static int rzn1_local_timer_dying_cpu(unsigned int cpu)
+{
+ struct rzn1_timer *timer;
+
+ if (cpu >= 2)
+ return 0;
+
+ timer = &rzn1_tab_timers[cpu + RZN1_TIMER_BASE_INDEX_16BIT_TIMERS];
+
+ rzn1_timer_disable(timer);
+ disable_irq_nosync(timer->ced.irq);
+ return 0;
+}
+
+static void rzn1_timer_init(struct rzn1_timer *timer, unsigned int index,
+ void __iomem *timers_base, unsigned long clock_rate)
+{
+ timer->base = timers_base + RZN1_TIMER_REGS_OFFSET(index);
+
+ /* Disable the timer */
+ writel(0, timer->base + RZN1_TIMER_CONTROL_REG);
+
+ /* Clear potential interrupts */
+ readl(timer->base + RZN1_TIMER_CLEAR_INT_REG);
+
+ if (RZN1_TIMER_IS_16BIT_TIMER(index)) {
+ timer->width = 16;
+ /*
+ * Enable pre-scaler for 16-bit timers.
+ *
+ * Timers are fed by a 25MHz clock. With this pre-scaler (1/25)
+ * enabled, the 16-bit timers resolution is 1 microsecond.
+ */
+ writel(RZN1_TIMER_CONTROL_PRESCALER_DIV25,
+ timer->base + RZN1_TIMER_CONTROL_REG);
+ timer->rate = clock_rate / 25;
+ } else {
+ timer->width = 32;
+ timer->rate = clock_rate;
+ }
+ timer->hz_period = DIV_ROUND_UP(timer->rate, HZ);
+}
+
+static void rzn1_timer_clkevt_init_ced(struct rzn1_timer *timer, const char *name,
+ int irq)
+{
+ timer->ced.features = CLOCK_EVT_FEAT_PERIODIC | CLOCK_EVT_FEAT_ONESHOT;
+ timer->ced.set_next_event = rzn1_clkevt_set_next_event;
+ timer->ced.set_state_shutdown = rzn1_clkevt_shutdown;
+ timer->ced.set_state_periodic = rzn1_clkevt_set_periodic;
+ timer->ced.cpumask = cpu_possible_mask;
+
+ /*
+ * 32-bit timers are fed with a 25 MHz clock directly derived from the
+ * main PLL (1 GHz). The arch timer, ARMv7 CP15 in RZ/N1 SoCs, is fed
+ * with to a 6.25 MHz clock.
+ *
+ * The clock of the RZ/N1 timers block is a stable one and the RZ/N1
+ * timers have a higher resolution than the ARM CP15 one.
+ *
+ * Use a high rating for the 32-bit RZ/N1 timers.
+ */
+ timer->ced.rating = timer->width == 16 ? 200 : 460;
+ timer->ced.owner = THIS_MODULE;
+ timer->ced.name = name;
+ timer->ced.irq = irq;
+}
+
+static int rzn1_timer_setup_timers(struct platform_device *pdev,
+ struct rzn1_timer *tab_timers,
+ unsigned int first_timer_index,
+ unsigned int last_timer_index,
+ void __iomem *base, unsigned long clock_rate)
+{
+ struct device *dev = &pdev->dev;
+ struct rzn1_timer *timer;
+ unsigned int i;
+ char *name;
+ int irq;
+ int ret;
+
+ for (i = first_timer_index; i <= last_timer_index; i++) {
+ timer = &tab_timers[i];
+
+ rzn1_timer_init(timer, i, base, clock_rate);
+
+ irq = platform_get_irq(pdev, i);
+ if (irq < 0)
+ return irq;
+
+ name = devm_kasprintf(dev, GFP_KERNEL, "%s-%u", dev_name(dev), i);
+ if (!name)
+ return -ENOMEM;
+
+ rzn1_timer_clkevt_init_ced(timer, name, irq);
+
+ ret = devm_request_irq(dev, timer->ced.irq, rzn1_timer_interrupt,
+ IRQF_TIMER | IRQF_NO_AUTOEN,
+ timer->ced.name, timer);
+ if (ret < 0)
+ return dev_err_probe(dev, ret, "timer%u: Failed to request IRQ\n", i);
+
+ rzn1_timer_int_enable(timer);
+ }
+ return 0;
+}
+
+static void rzn1_timer_register_clkevt(struct rzn1_timer *tab_timers,
+ unsigned int first_timer_index,
+ unsigned int last_timer_index)
+{
+ struct rzn1_timer *timer;
+ unsigned int i;
+
+ for (i = first_timer_index; i <= last_timer_index; i++) {
+ timer = &tab_timers[i];
+ clockevents_config_and_register(&timer->ced, timer->rate,
+ 1, GENMASK(timer->width - 1, 0));
+ /*
+ * Time to enable the interrupt even if no interrupt can be
+ * fired yet. Indeed, the timer is still globally disabled.
+ * It will be enabled when rzn1_timer_config() is called
+ */
+ enable_irq(timer->ced.irq);
+ }
+}
+
+static int rzn1_timer_probe_first(struct platform_device *pdev,
+ struct rzn1_timer *tab_timers,
+ void __iomem *base, unsigned long clock_rate)
+{
+ struct device *dev = &pdev->dev;
+ struct rzn1_timer *timer;
+ int ret;
+
+ /*
+ * Each timer instance has 6 x 16-bit timers and 2 x 32-bit timers.
+ *
+ * Probe the first instance. In that case, timers are assigned as
+ * follow:
+ * - First and second 16-bit timers: clock events per CPU
+ * - Last (second one) 32-bit timer: clocksource and sched_clock
+ * - Other timers: clock events for all possible CPUs
+ *
+ * First step, perform all operation that could fail without calling
+ * clockevents_config_and_register(), sched_clock_register() nor
+ * cpuhp_setup_state(). Those operation don't have unregister nor
+ * teardown counterparts and so, once called, we cannot remove the
+ * related resource.
+ */
+
+ /* First step for 16-bit timers and the first 32-bit timers. */
+ ret = rzn1_timer_setup_timers(pdev, tab_timers,
+ RZN1_TIMER_BASE_INDEX_16BIT_TIMERS,
+ RZN1_TIMER_BASE_INDEX_32BIT_TIMERS,
+ base, clock_rate);
+ if (ret)
+ return ret;
+
+ /*
+ * Second step, almost all operations that can fail have been called.
+ * Timers are ready to work. Start with the last operation that can fail,
+ * installing and invoking hotplug callbacks
+ */
+ rzn1_tab_timers = tab_timers;
+ ret = cpuhp_setup_state(CPUHP_AP_ONLINE_DYN,
+ "clockevents/rzn1/global_timer:starting",
+ rzn1_local_timer_starting_cpu,
+ rzn1_local_timer_dying_cpu);
+ if (ret < 0)
+ return dev_err_probe(dev, ret, "Setup CPU hotplug state failed\n");
+
+ /*
+ * At this point, no more operations can fail. Perform remaining
+ * operations. Starting by handling the last (second one) 32-bit timer
+ */
+ timer = &tab_timers[RZN1_TIMER_LAST_INDEX_32BIT_TIMERS];
+
+ rzn1_timer_init(timer, RZN1_TIMER_LAST_INDEX_32BIT_TIMERS, base, clock_rate);
+ rzn1_timer_config(timer, GENMASK(timer->width - 1, 0), true);
+
+ rzn1_sched_clock = timer;
+
+ sched_clock_register(rzn1_sched_read, rzn1_sched_clock->width, rzn1_sched_clock->rate);
+
+ rzn1_clocksource.mask = CLOCKSOURCE_MASK(rzn1_sched_clock->width);
+ clocksource_register_hz(&rzn1_clocksource, rzn1_sched_clock->rate);
+
+ /*
+ * Register clockevents for 16-bit timers (except the first and second
+ * ones) and for the first 32-bit timer. The first and second 16-bit
+ * timers clockevents are registered by CPU hotplug startup function set
+ * previously by the cpuhp_setup_state() call.
+ */
+ rzn1_timer_register_clkevt(tab_timers,
+ RZN1_TIMER_BASE_INDEX_16BIT_TIMERS + 2,
+ RZN1_TIMER_BASE_INDEX_32BIT_TIMERS);
+
+ return 0;
+}
+
+static int rzn1_timer_probe_other(struct platform_device *pdev,
+ struct rzn1_timer *tab_timers,
+ void __iomem *base, unsigned long clock_rate)
+{
+ int ret;
+
+ /*
+ * Probe other instance(s), i.e. not the first one. In that case,
+ * all timers are used as clock events and available for all possible
+ * CPUs
+ *
+ * First step, perform all operation that could fail without calling
+ * clockevents_config_and_register(). Unregister counterpart does not
+ * exist and so, once called, we cannot remove the related resource.
+ */
+ ret = rzn1_timer_setup_timers(pdev, tab_timers,
+ RZN1_TIMER_BASE_INDEX_16BIT_TIMERS,
+ RZN1_TIMER_LAST_INDEX_32BIT_TIMERS,
+ base, clock_rate);
+ if (ret)
+ return ret;
+
+ /*
+ * Second step, all operation that can fail have been called. We can
+ * register our timers.
+ */
+ rzn1_timer_register_clkevt(tab_timers,
+ RZN1_TIMER_BASE_INDEX_16BIT_TIMERS,
+ RZN1_TIMER_LAST_INDEX_32BIT_TIMERS);
+ return 0;
+}
+
+static int rzn1_timer_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct rzn1_timer *tab_timers;
+ unsigned long clock_rate;
+ void __iomem *base;
+ struct clk *clk;
+ int ret;
+
+ tab_timers = devm_kcalloc(dev, RZN1_TIMER_NB_TIMERS, sizeof(*tab_timers),
+ GFP_KERNEL);
+ if (!tab_timers)
+ return -ENOMEM;
+
+ base = devm_platform_ioremap_resource(pdev, 0);
+ if (IS_ERR(base))
+ return PTR_ERR(base);
+
+ clk = devm_clk_get_enabled(dev, "pclk");
+ if (IS_ERR(clk))
+ return dev_err_probe(dev, PTR_ERR(clk), "Failed to get pclk\n");
+
+ clock_rate = clk_get_rate(clk);
+
+ scoped_guard(mutex, &rzn1_global_timers_lock) {
+ ret = !rzn1_timer_instance ?
+ rzn1_timer_probe_first(pdev, tab_timers, base, clock_rate) :
+ rzn1_timer_probe_other(pdev, tab_timers, base, clock_rate);
+ if (!ret)
+ rzn1_timer_instance++;
+ }
+
+ return ret;
+}
+
+static const struct of_device_id rzn1_timer_of_match[] = {
+ { .compatible = "renesas,rzn1-timer", },
+ { /* sentinel */ }
+};
+
+static struct platform_driver rzn1_timer_driver = {
+ .driver = {
+ .name = "rzn1_timer",
+ .of_match_table = rzn1_timer_of_match,
+ .suppress_bind_attrs = true,
+ },
+};
+builtin_platform_driver_probe(rzn1_timer_driver, rzn1_timer_probe);
diff --git a/drivers/clocksource/timer-ti-dm.c b/drivers/clocksource/timer-ti-dm.c
index 6787acac9a43..8012d3f3e1a3 100644
--- a/drivers/clocksource/timer-ti-dm.c
+++ b/drivers/clocksource/timer-ti-dm.c
@@ -1095,6 +1095,7 @@ static int omap_dm_timer_write_counter(struct omap_dm_timer *cookie, unsigned in
{
struct dmtimer *timer;
struct device *dev;
+ int ret;
timer = to_dmtimer(cookie);
if (unlikely(!timer)) {
@@ -1104,7 +1105,10 @@ static int omap_dm_timer_write_counter(struct omap_dm_timer *cookie, unsigned in
dev = &timer->pdev->dev;
- pm_runtime_resume_and_get(dev);
+ ret = pm_runtime_resume_and_get(dev);
+ if (ret < 0)
+ return ret;
+
dmtimer_write(timer, OMAP_TIMER_COUNTER_REG, value);
pm_runtime_put_sync(dev);
@@ -1475,7 +1479,8 @@ static int omap_dm_timer_probe(struct platform_device *pdev)
timer->pdev = pdev;
- if (timer->capability & OMAP_TIMER_ALWON && !IS_ERR_OR_NULL(timer->fclk)) {
+ if (pdata->system_timer_capable &&
+ timer->capability & OMAP_TIMER_ALWON && !IS_ERR_OR_NULL(timer->fclk)) {
if (!omap_dm_timer_sched_clock_counter) {
ret = omap_dm_timer_setup_clocksource(timer);
if (ret)
@@ -1585,6 +1590,7 @@ static const struct dmtimer_platform_data omap3plus_pdata = {
static const struct dmtimer_platform_data am6_pdata = {
.timer_ops = &dmtimer_ops,
+ .system_timer_capable = true,
};
static const struct of_device_id omap_timer_match[] = {
diff --git a/include/linux/platform_data/dmtimer-omap.h b/include/linux/platform_data/dmtimer-omap.h
index 726d89143842..b78ee4676b98 100644
--- a/include/linux/platform_data/dmtimer-omap.h
+++ b/include/linux/platform_data/dmtimer-omap.h
@@ -57,6 +57,8 @@ struct dmtimer_platform_data {
u32 timer_errata;
int (*get_context_loss_count)(struct device *);
const struct omap_dm_timer_ops *timer_ops;
+ /* always-on timers can be used for clocksource/clockevent */
+ bool system_timer_capable;
};
#endif /* __PLATFORM_DATA_DMTIMER_OMAP_H__ */
^ permalink raw reply [flat|nested] 12+ messages in thread
* [GIT pull] timers/core for v7.4-rc1
2026-10-21 9:50 [GIT pull] irq/core for v7.4-rc1 Thomas Gleixner
2026-10-21 9:51 ` [GIT pull] irq/drivers " Thomas Gleixner
2026-10-21 9:51 ` [GIT pull] timers/clocksource " Thomas Gleixner
@ 2026-10-21 9:51 ` Thomas Gleixner
2026-10-21 21:43 ` pr-tracker-bot
2026-10-21 9:51 ` [GIT pull] timers/nohz " Thomas Gleixner
` (2 subsequent siblings)
5 siblings, 1 reply; 12+ messages in thread
From: Thomas Gleixner @ 2026-10-21 9:51 UTC (permalink / raw)
To: Linus Torvalds; +Cc: linux-kernel, x86
Linus,
please pull the latest timers/core branch from:
git://git.kernel.org/pub/scm/linux/kernel/git/tip/tip.git timers-core-2026-10-20
up to: 348f54c435bf: selftests/timers: clocksource-switch: Fix unchecked open()/read()
Updates for the core timekeeping and timers subsystem:
- Futher cleanups for the POSIX timer exit/exec related code to simplify
the code flow and to make it more robust against future changes.
- Prevent tearing of timekeeper::ktime_sec which can be accessed without the
sequence count protection.
The timekeeper is updated by memcpy() with an uneven sequence count to
force readers into the retry path. On 64-bit machine the ktime_sec
member can be accessed atomically without the sequence count
protection. That is only correct when the update side memcpy()
guarantees atomicity at the 64-bit granularity. That's not guaranteed
due to debug options, compilers and instruction availability. If the
memcpy() does not provide the atomicity then the unprotected readers
can observe store tearing.
To prevent this write the ktime_sec member before the memcpy() so that
a non-atomic memcpy() will write the same data. A similar fix has been
done for the xtime_sec member, but the ktime_sec equivalent was missed.
- Prevent time64_to_tm() day truncation on 32-bit kernels
The function receives a 64-bit seconds value but stores the quotient of
the computation in a long, which truncates the result for large input
values. Cure this by using div_s64_rem() which stores the quotient in
s64 and only leaves the seconds-within-day reminder in a long, which is
guaranteed to fit. This fix comes with a corresponding kunit test to
prevent further mishaps.
- Mark data-racy accesses to the hrtimer_sleeper::task member.
The recent updates to KCSAN flagged these benign races. Annotate them
with new helper functions, convert all existing usage sites and mark
the task member private after that so that future users which fail to
use the helpers can be detected automatically.
- Address similar benign KCSAN flagged issues in the hrtimer, timer and
timer migration code.
- Fixes for a few selftest issues
Thanks,
tglx
------------------>
Eva Kurchatova (2):
selftests: timers: Count what tick is worth in the drift estimate
selftests: timers: Measure the CPU timers on the clock they count
Karl Mehltretter (2):
time: Prevent time64_to_tm() day truncation on 32-bit
time/kunit: Add time64_to_tm() case beyond 32-bit day count
Paul E. McKenney (14):
hrtimer: Mark data-racy accesses to hrtimer_sleeper ->task field
aio: Use accessor for hrtimer_sleeper ->task field
wait: Use accessor for hrtimer_sleeper ->task field
io-uring/rw: Use accessor for hrtimer_sleeper ->task field
futex: Use accessor for hrtimer_sleeper ->task field in waitwake.c
timers: Use accessor for hrtimer_sleeper ->task field in sleep_timeout.c
net: pktgen: Use accessor for hrtimer_sleeper ->task field
rtmutex: Use accessor for hrtimer_sleeper ->task field
futex: Use accessor for hrtimer_sleeper ->task field in requeue
hrtimer: Update hrtimer_resolution only if value changes
hrtimer: Mark the hrtimer_sleeper structure's ->task field __private
hrtimer: Apply READ_ONCE() to lockless base->running loads
timers: Mark racy updates to hlist_node::pprev field
timers/migration: Mark racy updates to tmigr_event::ignore field
Qingshuang Fu (1):
selftests/timers: clocksource-switch: Fix unchecked open()/read()
Thomas Gleixner (6):
posix-timers: Move posixtimer_exec_cleanup() out of exec.c
posix-timers: Move POSIX timer group exit related code out of do_exit()
posix-cpu-timers: Move inlines out of public header
posix-cpu-timers: Use PF_EXITING to indicate exit
posix-cpu-timers: Prevent enqueueing when PF_EXITING is set
posix-timers: Handle exit in do_exit() completely
Thomas Weißschuh (1):
timekeeping: Use READ_ONCE/WRITE_ONCE() for ktime_sec to prevent tearing
fs/aio.c | 2 +-
fs/exec.c | 13 +--
include/linux/hrtimer.h | 10 +-
include/linux/list.h | 6 +-
include/linux/posix-timers.h | 39 +-------
include/linux/sched/task.h | 1 -
include/linux/wait.h | 2 +-
io_uring/rw.c | 2 +-
kernel/exit.c | 13 +--
kernel/futex/requeue.c | 2 +-
kernel/futex/waitwake.c | 8 +-
kernel/locking/rtmutex.c | 2 +-
kernel/time/hrtimer.c | 23 ++---
kernel/time/posix-cpu-timers.c | 103 +++++++++++++++++++--
kernel/time/posix-timers.c | 26 ++++--
kernel/time/posix-timers.h | 3 +
kernel/time/sleep_timeout.c | 4 +-
kernel/time/time_test.c | 16 ++++
kernel/time/timeconv.c | 6 +-
kernel/time/timekeeping.c | 4 +-
kernel/time/timer.c | 2 +-
kernel/time/timer_migration.c | 6 +-
net/core/pktgen.c | 4 +-
.../testing/selftests/timers/clocksource-switch.c | 23 ++++-
tools/testing/selftests/timers/posix_timers.c | 7 +-
tools/testing/selftests/timers/raw_skew.c | 12 +++
26 files changed, 227 insertions(+), 112 deletions(-)
diff --git a/fs/aio.c b/fs/aio.c
index d78acc69f487..8130d9e637ea 100644
--- a/fs/aio.c
+++ b/fs/aio.c
@@ -1402,7 +1402,7 @@ static long read_events(struct kioctx *ctx, long min_nr, long nr,
w.min_nr = min_nr - ret;
ret2 = prepare_to_wait_event(&ctx->wait, &w.w, TASK_INTERRUPTIBLE);
- if (!ret2 && !t.task)
+ if (!ret2 && !hrtimer_sleeper_task_get(&t))
ret2 = -ETIME;
if (aio_read_events(ctx, min_nr, nr, event, &ret) || ret2)
diff --git a/fs/exec.c b/fs/exec.c
index 819643408e6d..1fc567155f34 100644
--- a/fs/exec.c
+++ b/fs/exec.c
@@ -1115,17 +1115,6 @@ static struct file *bprm_identity_file(const struct linux_binprm *bprm)
return bprm->file;
}
-static void posixtimer_exec(struct task_struct *me)
-{
-#ifdef CONFIG_POSIX_TIMERS
- spin_lock_irq(&me->sighand->siglock);
- posix_cpu_timers_exit(me);
- spin_unlock_irq(&me->sighand->siglock);
- exit_itimers(me);
- flush_itimer_signals();
-#endif
-}
-
/*
* Calling this is the point of no return. None of the failures will be
* seen by userspace since either the process is already taking a fatal
@@ -1171,7 +1160,7 @@ int begin_new_exec(struct linux_binprm * bprm)
* timer would not remove an enqueued timer because the TID lookup
* of the old TID fails.
*/
- posixtimer_exec(me);
+ posixtimer_exec();
/* see the comment in check_unsafe_exec() */
current->fs->in_exec = 0;
diff --git a/include/linux/hrtimer.h b/include/linux/hrtimer.h
index 29072d89e5cb..cad8482337cb 100644
--- a/include/linux/hrtimer.h
+++ b/include/linux/hrtimer.h
@@ -72,7 +72,7 @@ enum hrtimer_mode {
*/
struct hrtimer_sleeper {
struct hrtimer timer;
- struct task_struct *task;
+ struct task_struct *__private task;
};
static inline void hrtimer_set_expires(struct hrtimer *timer, ktime_t time)
@@ -320,6 +320,14 @@ extern int schedule_hrtimeout_range_clock(ktime_t *expires,
const enum hrtimer_mode mode,
clockid_t clock_id);
extern int schedule_hrtimeout(ktime_t *expires, const enum hrtimer_mode mode);
+static inline struct task_struct *hrtimer_sleeper_task_get(struct hrtimer_sleeper *sl)
+{
+ return READ_ONCE(ACCESS_PRIVATE(sl, task));
+}
+static inline void hrtimer_sleeper_task_set(struct hrtimer_sleeper *sl, struct task_struct *t)
+{
+ WRITE_ONCE(ACCESS_PRIVATE(sl, task), t);
+}
/* Soft interrupt function to run the hrtimer queues: */
extern void hrtimer_run_queues(void);
diff --git a/include/linux/list.h b/include/linux/list.h
index 77fb62f79928..e3753695e76c 100644
--- a/include/linux/list.h
+++ b/include/linux/list.h
@@ -1172,7 +1172,7 @@ static inline void hlist_move_list(struct hlist_head *old,
{
new->first = old->first;
if (new->first)
- new->first->pprev = &new->first;
+ WRITE_ONCE(new->first->pprev, &new->first);
old->first = NULL;
}
@@ -1189,10 +1189,10 @@ static inline void hlist_splice_init(struct hlist_head *from,
struct hlist_head *to)
{
if (to->first)
- to->first->pprev = &last->next;
+ WRITE_ONCE(to->first->pprev, &last->next);
last->next = to->first;
to->first = from->first;
- from->first->pprev = &to->first;
+ WRITE_ONCE(from->first->pprev, &to->first);
from->first = NULL;
}
diff --git a/include/linux/posix-timers.h b/include/linux/posix-timers.h
index 9a1a0c61361c..00767acbc111 100644
--- a/include/linux/posix-timers.h
+++ b/include/linux/posix-timers.h
@@ -66,38 +66,6 @@ struct cpu_timer {
struct task_struct __rcu *handling;
};
-static inline bool cpu_timer_enqueue(struct timerqueue_head *head,
- struct cpu_timer *ctmr)
-{
- ctmr->head = head;
- return timerqueue_add(head, &ctmr->node);
-}
-
-static inline bool cpu_timer_queued(struct cpu_timer *ctmr)
-{
- return !!ctmr->head;
-}
-
-static inline bool cpu_timer_dequeue(struct cpu_timer *ctmr)
-{
- if (cpu_timer_queued(ctmr)) {
- timerqueue_del(ctmr->head, &ctmr->node);
- ctmr->head = NULL;
- return true;
- }
- return false;
-}
-
-static inline u64 cpu_timer_getexpires(struct cpu_timer *ctmr)
-{
- return ctmr->node.expires;
-}
-
-static inline void cpu_timer_setexpires(struct cpu_timer *ctmr, u64 exp)
-{
- ctmr->node.expires = exp;
-}
-
static inline void posix_cputimers_init(struct posix_cputimers *pct)
{
memset(pct, 0, sizeof(*pct));
@@ -224,14 +192,15 @@ struct k_itimer {
} ____cacheline_aligned_in_smp;
void run_posix_cpu_timers(void);
-void posix_cpu_timers_exit(struct task_struct *task);
-void posix_cpu_timers_exit_group(struct task_struct *task);
void set_process_cpu_timer(struct task_struct *task, unsigned int clock_idx,
u64 *newval, u64 *oldval);
int update_rlimit_cpu(struct task_struct *task, unsigned long rlim_new);
#ifdef CONFIG_POSIX_TIMERS
+void posixtimer_exec(void);
+void posixtimer_exit(bool group_dead);
+
static inline void posixtimer_putref(struct k_itimer *tmr)
{
if (rcuref_put(&tmr->rcuref))
@@ -259,6 +228,8 @@ static inline bool posixtimer_valid(const struct k_itimer *timer)
return !(val & 0x1UL);
}
#else /* CONFIG_POSIX_TIMERS */
+static inline void posixtimer_exec(void) { }
+static inline void posixtimer_exit(bool group_dead) { }
static inline void posixtimer_sigqueue_getref(struct sigqueue *q) { }
static inline void posixtimer_sigqueue_putref(struct sigqueue *q) { }
#endif /* !CONFIG_POSIX_TIMERS */
diff --git a/include/linux/sched/task.h b/include/linux/sched/task.h
index e0c1ca8c6a18..90ed5bc3c7af 100644
--- a/include/linux/sched/task.h
+++ b/include/linux/sched/task.h
@@ -94,7 +94,6 @@ static inline void exit_thread(struct task_struct *tsk)
extern __noreturn void do_group_exit(int);
extern void exit_files(struct task_struct *);
-extern void exit_itimers(struct task_struct *);
extern pid_t kernel_clone(struct kernel_clone_args *kargs);
struct task_struct *copy_process(struct pid *pid, int trace, int node,
diff --git a/include/linux/wait.h b/include/linux/wait.h
index 7e215330199c..c2af98b0074d 100644
--- a/include/linux/wait.h
+++ b/include/linux/wait.h
@@ -556,7 +556,7 @@ do { \
} \
\
__ret = ___wait_event(wq_head, condition, state, 0, 0, \
- if (!__t.task) { \
+ if (!hrtimer_sleeper_task_get(&__t)) { \
__ret = -ETIME; \
break; \
} \
diff --git a/io_uring/rw.c b/io_uring/rw.c
index 432820f86251..d9b1e096f07b 100644
--- a/io_uring/rw.c
+++ b/io_uring/rw.c
@@ -1295,7 +1295,7 @@ static u64 io_hybrid_iopoll_delay(struct io_ring_ctx *ctx, struct io_kiocb *req)
set_current_state(TASK_INTERRUPTIBLE);
hrtimer_sleeper_start_expires(&timer, mode);
- if (timer.task)
+ if (hrtimer_sleeper_task_get(&timer))
io_schedule();
hrtimer_cancel(&timer.timer);
diff --git a/kernel/exit.c b/kernel/exit.c
index 424c44a42a4d..06a1eb1fdfcd 100644
--- a/kernel/exit.c
+++ b/kernel/exit.c
@@ -167,12 +167,6 @@ static void __exit_signal(struct release_task_post *post, struct task_struct *ts
lockdep_tasklist_lock_is_held());
spin_lock(&sighand->siglock);
-#ifdef CONFIG_POSIX_TIMERS
- posix_cpu_timers_exit(tsk);
- if (group_dead)
- posix_cpu_timers_exit_group(tsk);
-#endif
-
if (group_dead) {
tty = sig->tty;
sig->tty = NULL;
@@ -966,13 +960,12 @@ void __noreturn do_exit(long code)
panic("Attempted to kill init! exitcode=0x%08x\n",
tsk->signal->group_exit_code ?: (int)code);
-#ifdef CONFIG_POSIX_TIMERS
- hrtimer_cancel(&tsk->signal->real_timer);
- exit_itimers(tsk);
-#endif
if (tsk->mm)
setmax_mm_hiwater_rss(&tsk->signal->maxrss, tsk->mm);
}
+
+ posixtimer_exit(group_dead);
+
acct_collect(code, group_dead);
if (group_dead)
tty_audit_exit();
diff --git a/kernel/futex/requeue.c b/kernel/futex/requeue.c
index b3f4a4bccb12..842d852302dd 100644
--- a/kernel/futex/requeue.c
+++ b/kernel/futex/requeue.c
@@ -744,7 +744,7 @@ int handle_early_requeue_pi_wakeup(struct futex_hash_bucket *hb,
/* Handle spurious wakeups gracefully */
ret = -EWOULDBLOCK;
- if (timeout && !timeout->task)
+ if (timeout && !hrtimer_sleeper_task_get(timeout))
ret = -ETIMEDOUT;
else if (signal_pending(current))
ret = -ERESTARTNOINTR;
diff --git a/kernel/futex/waitwake.c b/kernel/futex/waitwake.c
index d4483d15d30a..cf18309e5770 100644
--- a/kernel/futex/waitwake.c
+++ b/kernel/futex/waitwake.c
@@ -383,7 +383,7 @@ void futex_do_wait(struct futex_q *q, struct hrtimer_sleeper *timeout)
* flagged for rescheduling. Only call schedule if there
* is no timeout, or if it has yet to expire.
*/
- if (!timeout || timeout->task)
+ if (!timeout || hrtimer_sleeper_task_get(timeout))
schedule();
}
__set_current_state(TASK_RUNNING);
@@ -539,7 +539,7 @@ int futex_wait_multiple_setup(struct futex_vector *vs, int count, int *woken)
static void futex_sleep_multiple(struct futex_vector *vs, unsigned int count,
struct hrtimer_sleeper *to)
{
- if (to && !to->task)
+ if (to && !hrtimer_sleeper_task_get(to))
return;
for (; count; count--, vs++) {
@@ -590,7 +590,7 @@ int futex_wait_multiple(struct futex_vector *vs, unsigned int count,
if (ret >= 0)
return ret;
- if (to && !to->task)
+ if (to && !hrtimer_sleeper_task_get(to))
return -ETIMEDOUT;
else if (signal_pending(current))
return -ERESTARTSYS;
@@ -725,7 +725,7 @@ int __futex_wait(u32 __user *uaddr, unsigned int flags, u32 val,
if (!futex_unqueue(&q))
return 0;
- if (to && !to->task)
+ if (to && !hrtimer_sleeper_task_get(to))
return -ETIMEDOUT;
/*
diff --git a/kernel/locking/rtmutex.c b/kernel/locking/rtmutex.c
index 4728631ae719..5a9534c715b8 100644
--- a/kernel/locking/rtmutex.c
+++ b/kernel/locking/rtmutex.c
@@ -1644,7 +1644,7 @@ static int __sched rt_mutex_slowlock_block(struct rt_mutex_base *lock,
break;
}
- if (timeout && !timeout->task) {
+ if (timeout && !hrtimer_sleeper_task_get(timeout)) {
ret = -ETIMEDOUT;
break;
}
diff --git a/kernel/time/hrtimer.c b/kernel/time/hrtimer.c
index cbf1693c86b3..17dd38a6cee7 100644
--- a/kernel/time/hrtimer.c
+++ b/kernel/time/hrtimer.c
@@ -780,7 +780,8 @@ static void hrtimer_switch_to_hres(void)
return;
}
base->hres_active = true;
- hrtimer_resolution = HIGH_RES_NSEC;
+ if (hrtimer_resolution != HIGH_RES_NSEC)
+ hrtimer_resolution = HIGH_RES_NSEC;
tick_setup_sched_timer(true);
/* "Retrigger" the interrupt to get things going */
@@ -2003,7 +2004,7 @@ bool hrtimer_active(const struct hrtimer *timer)
base = READ_ONCE(timer->base);
seq = raw_read_seqcount_begin(&base->seq);
- if (timer->is_queued || base->running == timer)
+ if (timer->is_queued || READ_ONCE(base->running) == timer)
return true;
} while (read_seqcount_retry(&base->seq, seq) || base != READ_ONCE(timer->base));
@@ -2040,7 +2041,7 @@ static void __run_hrtimer(struct hrtimer_cpu_base *cpu_base, struct hrtimer_cloc
lockdep_assert_held(&cpu_base->lock);
debug_hrtimer_deactivate(timer);
- base->running = timer;
+ WRITE_ONCE(base->running, timer);
/*
* Separate the ->running assignment from the ->is_queued assignment.
@@ -2099,7 +2100,7 @@ static void __run_hrtimer(struct hrtimer_cpu_base *cpu_base, struct hrtimer_cloc
raw_write_seqcount_barrier(&base->seq);
WARN_ON_ONCE(base->running != timer);
- base->running = NULL;
+ WRITE_ONCE(base->running, NULL);
}
static void __hrtimer_run_queues(struct hrtimer_cpu_base *cpu_base, ktime_t now,
@@ -2323,9 +2324,9 @@ void hrtimer_run_queues(void)
static enum hrtimer_restart hrtimer_wakeup(struct hrtimer *timer)
{
struct hrtimer_sleeper *t = container_of(timer, struct hrtimer_sleeper, timer);
- struct task_struct *task = t->task;
+ struct task_struct *task = hrtimer_sleeper_task_get(t);
- t->task = NULL;
+ hrtimer_sleeper_task_set(t, NULL);
if (task)
wake_up_process(task);
@@ -2354,7 +2355,7 @@ void hrtimer_sleeper_start_expires(struct hrtimer_sleeper *sl, enum hrtimer_mode
/* If already expired, clear the task pointer and set current state to running */
if (!hrtimer_start_expires_user(&sl->timer, mode)) {
- sl->task = NULL;
+ hrtimer_sleeper_task_set(sl, NULL);
__set_current_state(TASK_RUNNING);
}
}
@@ -2388,7 +2389,7 @@ static void __hrtimer_setup_sleeper(struct hrtimer_sleeper *sl, clockid_t clock_
}
__hrtimer_setup(&sl->timer, hrtimer_wakeup, clock_id, mode);
- sl->task = current;
+ hrtimer_sleeper_task_set(sl, current);
}
/**
@@ -2432,17 +2433,17 @@ static int __sched do_nanosleep(struct hrtimer_sleeper *t, enum hrtimer_mode mod
set_current_state(TASK_INTERRUPTIBLE|TASK_FREEZABLE);
hrtimer_sleeper_start_expires(t, mode);
- if (likely(t->task))
+ if (likely(hrtimer_sleeper_task_get(t)))
schedule();
hrtimer_cancel(&t->timer);
mode = HRTIMER_MODE_ABS;
- } while (t->task && !signal_pending(current));
+ } while (hrtimer_sleeper_task_get(t) && !signal_pending(current));
__set_current_state(TASK_RUNNING);
- if (!t->task)
+ if (!hrtimer_sleeper_task_get(t))
return 0;
restart = ¤t->restart_block;
diff --git a/kernel/time/posix-cpu-timers.c b/kernel/time/posix-cpu-timers.c
index d73d31c7994f..9f90500894dc 100644
--- a/kernel/time/posix-cpu-timers.c
+++ b/kernel/time/posix-cpu-timers.c
@@ -438,6 +438,38 @@ static void trigger_base_recalc_expires(struct k_itimer *timer,
base->nextevt = 0;
}
+static inline bool cpu_timer_enqueue(struct timerqueue_head *head,
+ struct cpu_timer *ctmr)
+{
+ ctmr->head = head;
+ return timerqueue_add(head, &ctmr->node);
+}
+
+static inline bool cpu_timer_queued(struct cpu_timer *ctmr)
+{
+ return !!ctmr->head;
+}
+
+static inline bool cpu_timer_dequeue(struct cpu_timer *ctmr)
+{
+ if (cpu_timer_queued(ctmr)) {
+ timerqueue_del(ctmr->head, &ctmr->node);
+ ctmr->head = NULL;
+ return true;
+ }
+ return false;
+}
+
+static inline u64 cpu_timer_getexpires(struct cpu_timer *ctmr)
+{
+ return ctmr->node.expires;
+}
+
+static inline void cpu_timer_setexpires(struct cpu_timer *ctmr, u64 exp)
+{
+ ctmr->node.expires = exp;
+}
+
/*
* Dequeue the timer and reset the base if it was its earliest expiration.
* It makes sure the next tick recalculates the base next expiration so we
@@ -596,6 +628,7 @@ static int posix_cpu_timer_del(struct k_itimer *timer)
}
if (!ret) {
+ WARN_ON_ONCE(cpu_timer_queued(&timer->it.cpu));
put_pid(timer->it.cpu.pid);
timer->it_status = POSIX_TIMER_DISARMED;
}
@@ -628,18 +661,50 @@ static void cleanup_timers(struct posix_cputimers *pct)
cleanup_timerqueue(&pct->bases[CPUCLOCK_SCHED].tqhead);
}
+static inline void posix_cpu_timers_exit_work(void);
+
/*
- * These are both called with the siglock held, when the current thread
- * is being reaped. When the final (leader) thread in the group is reaped,
- * posix_cpu_timers_exit_group will be called after posix_cpu_timers_exit.
+ * Invoked from posixtimer_exit_task() after PF_EXITING was set in tsk::flags or
+ * from posixtimer_exec_cleanup().
*/
-void posix_cpu_timers_exit(struct task_struct *tsk)
+void posix_cpu_timers_exit_task(void)
{
- cleanup_timers(&tsk->posix_cputimers);
+ posix_cpu_timers_exit_work();
+
+ guard(spinlock_irq)(¤t->sighand->siglock);
+ cleanup_timers(¤t->posix_cputimers);
}
-void posix_cpu_timers_exit_group(struct task_struct *tsk)
+
+/*
+ * Invoked from posixtimer_exit_group() after PF_EXITING was set in tsk::flags.
+ */
+void posix_cpu_timers_exit_group(void)
{
- cleanup_timers(&tsk->signal->posix_cputimers);
+ posix_cpu_timers_exit_task();
+
+ guard(spinlock_irq)(¤t->sighand->siglock);
+ cleanup_timers(¤t->signal->posix_cputimers);
+}
+
+/*
+ * This function validates that POSIX CPU timers can be safely enqueued on the
+ * target task.
+ *
+ * Enqueue is allowed when PF_EXITING is not set. If set then it is only allowed
+ * for process shared timers (type = PIDTYPE_TGID) as long as tsk::signal::flags
+ * does not have SIGNAL_GROUP_EXIT set. PIDTYPE_PID targets are not allowed at
+ * all when the task has PF_EXITING set.
+ *
+ * This guarantees that after the POSIX timer cleanup in posixtimer_exit() no
+ * POSIX CPU timers are queued on the task or in case of a group exit on the
+ * process.
+ */
+static inline bool task_can_enqueue_timer(struct task_struct *tsk, enum pid_type type)
+{
+ if (likely(!(tsk->flags & PF_EXITING)))
+ return true;
+
+ return type == PIDTYPE_TGID && !(tsk->signal->flags & SIGNAL_GROUP_EXIT);
}
/*
@@ -652,7 +717,13 @@ static void arm_timer(struct k_itimer *timer, struct task_struct *p)
struct cpu_timer *ctmr = &timer->it.cpu;
u64 newexp = cpu_timer_getexpires(ctmr);
+ lockdep_assert_held(&p->sighand->siglock);
+
timer->it_status = POSIX_TIMER_ARMED;
+
+ if (unlikely(!task_can_enqueue_timer(p, clock_pid_type(timer->it_clock))))
+ return;
+
if (!cpu_timer_enqueue(&base->tqhead, ctmr))
return;
@@ -1197,6 +1268,20 @@ static void posix_cpu_timers_work(struct callback_head *work)
mutex_unlock(&cw->mutex);
}
+static inline void posix_cpu_timers_exit_work(void)
+{
+ /* Canceling the work is only valid for exit() but not for exec() */
+ if (!(current->flags & PF_EXITING))
+ return;
+ /*
+ * current->flags has PF_EXITING set so this can be done lockless and
+ * with interrupts enabled as PF_EXITING prevents the interrupt from
+ * scheduling the work.
+ */
+ if (current->posix_cputimers_work.scheduled)
+ task_work_cancel(current, ¤t->posix_cputimers_work.work);
+}
+
/*
* Invoked from the posix-timer core when a cancel operation failed because
* the timer is marked firing. The caller holds rcu_read_lock(), which
@@ -1327,6 +1412,8 @@ static inline void __run_posix_cpu_timers(struct task_struct *tsk)
lockdep_posixtimer_exit();
}
+static inline void posix_cpu_timers_exit_work(void) { }
+
static void posix_cpu_timer_wait_running(struct k_itimer *timr)
{
cpu_relax();
@@ -1473,7 +1560,7 @@ void run_posix_cpu_timers(void)
* posix_cpu_timer_del() may fail to lock_task_sighand(tsk) and
* miss timer->it.cpu.firing != 0.
*/
- if (tsk->exit_state)
+ if (tsk->flags & PF_EXITING)
return;
/*
diff --git a/kernel/time/posix-timers.c b/kernel/time/posix-timers.c
index 436ba794cc0b..188dbedbffca 100644
--- a/kernel/time/posix-timers.c
+++ b/kernel/time/posix-timers.c
@@ -1077,13 +1077,9 @@ SYSCALL_DEFINE1(timer_delete, timer_t, timer_id)
return 0;
}
-/*
- * Invoked from do_exit() when the last thread of a thread group exits.
- * At that point no other task can access the timers of the dying
- * task anymore.
- */
-void exit_itimers(struct task_struct *tsk)
+static void posixtimer_delete_timers(void)
{
+ struct task_struct *tsk = current;
struct hlist_head timers;
struct hlist_node *next;
struct k_itimer *timer;
@@ -1120,6 +1116,24 @@ void exit_itimers(struct task_struct *tsk)
}
}
+void posixtimer_exit(bool group_dead)
+{
+ if (group_dead) {
+ hrtimer_cancel(¤t->signal->real_timer);
+ posix_cpu_timers_exit_group();
+ posixtimer_delete_timers();
+ } else {
+ posix_cpu_timers_exit_task();
+ }
+}
+
+void posixtimer_exec(void)
+{
+ posix_cpu_timers_exit_task();
+ posixtimer_delete_timers();
+ flush_itimer_signals();
+}
+
SYSCALL_DEFINE2(clock_settime, const clockid_t, which_clock,
const struct __kernel_timespec __user *, tp)
{
diff --git a/kernel/time/posix-timers.h b/kernel/time/posix-timers.h
index 4ea9611dd716..79fd7ea71046 100644
--- a/kernel/time/posix-timers.h
+++ b/kernel/time/posix-timers.h
@@ -51,3 +51,6 @@ int common_timer_set(struct k_itimer *timr, int flags,
struct itimerspec64 *old_setting);
void posix_timer_set_common(struct k_itimer *timer, struct itimerspec64 *new_setting);
int common_timer_del(struct k_itimer *timer);
+
+void posix_cpu_timers_exit_task(void);
+void posix_cpu_timers_exit_group(void);
diff --git a/kernel/time/sleep_timeout.c b/kernel/time/sleep_timeout.c
index 3c90574bd904..ad8c415851ae 100644
--- a/kernel/time/sleep_timeout.c
+++ b/kernel/time/sleep_timeout.c
@@ -212,7 +212,7 @@ int __sched schedule_hrtimeout_range_clock(ktime_t *expires, u64 delta,
hrtimer_set_expires_range_ns(&t.timer, *expires, delta);
hrtimer_sleeper_start_expires(&t, mode);
- if (likely(t.task))
+ if (likely(hrtimer_sleeper_task_get(&t)))
schedule();
hrtimer_cancel(&t.timer);
@@ -220,7 +220,7 @@ int __sched schedule_hrtimeout_range_clock(ktime_t *expires, u64 delta,
__set_current_state(TASK_RUNNING);
- return !t.task ? 0 : -EINTR;
+ return !hrtimer_sleeper_task_get(&t) ? 0 : -EINTR;
}
EXPORT_SYMBOL_GPL(schedule_hrtimeout_range_clock);
diff --git a/kernel/time/time_test.c b/kernel/time/time_test.c
index 1b99180da288..8b718767b3ba 100644
--- a/kernel/time/time_test.c
+++ b/kernel/time/time_test.c
@@ -87,8 +87,24 @@ static void time64_to_tm_test_date_range(struct kunit *test)
}
}
+static void time64_to_tm_test_wide_day_count(struct kunit *test)
+{
+ /* 2^31 days: the first count that does not fit in a 32-bit long. */
+ time64_t timestamp = (1LL << 31) * 86400;
+ struct tm result;
+
+ time64_to_tm(timestamp, 0, &result);
+
+ KUNIT_EXPECT_EQ(test, result.tm_year, 5879680);
+ KUNIT_EXPECT_EQ(test, result.tm_mon, 6);
+ KUNIT_EXPECT_EQ(test, result.tm_mday, 12);
+ KUNIT_EXPECT_EQ(test, result.tm_yday, 193);
+ KUNIT_EXPECT_EQ(test, result.tm_wday, 6);
+}
+
static struct kunit_case time_test_cases[] = {
KUNIT_CASE_SLOW(time64_to_tm_test_date_range),
+ KUNIT_CASE(time64_to_tm_test_wide_day_count),
{}
};
diff --git a/kernel/time/timeconv.c b/kernel/time/timeconv.c
index 59b922c826e7..aed3af950fa0 100644
--- a/kernel/time/timeconv.c
+++ b/kernel/time/timeconv.c
@@ -49,8 +49,9 @@ void time64_to_tm(time64_t totalsecs, int offset, struct tm *result)
u32 u32tmp, day_of_century, year_of_century, day_of_year, month, day;
u64 u64tmp, udays, century, year;
bool is_Jan_or_Feb, is_leap_year;
- long days, rem;
int remainder;
+ long rem;
+ s64 days;
days = div_s64_rem(totalsecs, SECS_PER_DAY, &remainder);
rem = remainder;
@@ -70,7 +71,8 @@ void time64_to_tm(time64_t totalsecs, int offset, struct tm *result)
result->tm_sec = rem % 60;
/* January 1, 1970 was a Thursday. */
- result->tm_wday = (4 + days) % 7;
+ div_s64_rem(days + 4, 7, &remainder);
+ result->tm_wday = remainder;
if (result->tm_wday < 0)
result->tm_wday += 7;
diff --git a/kernel/time/timekeeping.c b/kernel/time/timekeeping.c
index ea2e6e55f37b..d54c4d303db6 100644
--- a/kernel/time/timekeeping.c
+++ b/kernel/time/timekeeping.c
@@ -861,8 +861,10 @@ static void timekeeping_update_from_shadow(struct tk_data *tkd, unsigned int act
*
* Write xtime_sec first so that even if the memcpy() tears the store
* data integrity is provided for ktime_get_real_seconds().
+ * The same goes for ktime_sec and ktime_get_seconds().
*/
WRITE_ONCE(tkd->timekeeper.xtime_sec, tk->xtime_sec);
+ WRITE_ONCE(tkd->timekeeper.ktime_sec, tk->ktime_sec);
memcpy(&tkd->timekeeper, tk, sizeof(*tk));
write_seqcount_end(&tkd->seq);
}
@@ -1169,7 +1171,7 @@ time64_t ktime_get_seconds(void)
struct timekeeper *tk = &tk_core.timekeeper;
WARN_ON(timekeeping_suspended);
- return tk->ktime_sec;
+ return READ_ONCE(tk->ktime_sec);
}
EXPORT_SYMBOL_GPL(ktime_get_seconds);
diff --git a/kernel/time/timer.c b/kernel/time/timer.c
index ae9abf14688e..42afdcb229d8 100644
--- a/kernel/time/timer.c
+++ b/kernel/time/timer.c
@@ -890,7 +890,7 @@ static inline void detach_timer(struct timer_list *timer, bool clear_pending)
__hlist_del(entry);
if (clear_pending)
- entry->pprev = NULL;
+ WRITE_ONCE(entry->pprev, NULL);
entry->next = LIST_POISON2;
}
diff --git a/kernel/time/timer_migration.c b/kernel/time/timer_migration.c
index 059d43355e65..f920e73fff51 100644
--- a/kernel/time/timer_migration.c
+++ b/kernel/time/timer_migration.c
@@ -715,7 +715,7 @@ static void __tmigr_cpu_activate(struct tmigr_cpu *tmc)
trace_tmigr_cpu_active(tmc);
- tmc->cpuevt.ignore = true;
+ WRITE_ONCE(tmc->cpuevt.ignore, true);
WRITE_ONCE(tmc->wakeup, KTIME_MAX);
walk_groups(&tmigr_active_up, &data, tmc);
@@ -1258,7 +1258,7 @@ u64 tmigr_cpu_new_timer(u64 nextexp)
ret = READ_ONCE(tmc->wakeup);
if (nextexp != KTIME_MAX) {
if (nextexp != tmc->cpuevt.nextevt.expires ||
- tmc->cpuevt.ignore) {
+ READ_ONCE(tmc->cpuevt.ignore)) {
ret = tmigr_new_timer(tmc, nextexp);
/*
* Make sure the reevaluation of timers in idle path
@@ -1362,7 +1362,7 @@ static u64 __tmigr_cpu_deactivate(struct tmigr_cpu *tmc, u64 nextexp)
* or CPU goes offline.
*/
if (nextexp != KTIME_MAX)
- tmc->cpuevt.ignore = false;
+ WRITE_ONCE(tmc->cpuevt.ignore, false);
walk_groups(&tmigr_inactive_up, &data, tmc);
return data.firstexp;
diff --git a/net/core/pktgen.c b/net/core/pktgen.c
index 7f81aed46672..784d8398fb7a 100644
--- a/net/core/pktgen.c
+++ b/net/core/pktgen.c
@@ -2346,11 +2346,11 @@ static void spin(struct pktgen_dev *pkt_dev, ktime_t spin_until)
set_current_state(TASK_INTERRUPTIBLE);
hrtimer_sleeper_start_expires(&t, HRTIMER_MODE_ABS);
- if (likely(t.task))
+ if (likely(hrtimer_sleeper_task_get(&t)))
schedule();
hrtimer_cancel(&t.timer);
- } while (t.task && pkt_dev->running && !signal_pending(current));
+ } while (hrtimer_sleeper_task_get(&t) && pkt_dev->running && !signal_pending(current));
__set_current_state(TASK_RUNNING);
end_time = ktime_get();
}
diff --git a/tools/testing/selftests/timers/clocksource-switch.c b/tools/testing/selftests/timers/clocksource-switch.c
index db62a764c29e..2e86f56d953e 100644
--- a/tools/testing/selftests/timers/clocksource-switch.c
+++ b/tools/testing/selftests/timers/clocksource-switch.c
@@ -40,16 +40,23 @@
int get_clocksources(char list[][30])
{
int fd, i;
- size_t size;
+ ssize_t size;
char buf[512];
char *head, *tmp;
fd = open("/sys/devices/system/clocksource/clocksource0/available_clocksource", O_RDONLY);
+ if (fd < 0)
+ return 0;
- size = read(fd, buf, 512);
+ size = read(fd, buf, sizeof(buf) - 1);
close(fd);
+ if (size <= 0)
+ return 0;
+
+ buf[size] = '\0';
+
for (i = 0; i < 10; i++)
list[i][0] = '\0';
@@ -74,11 +81,21 @@ int get_clocksources(char list[][30])
int get_cur_clocksource(char *buf, size_t size)
{
+ ssize_t len;
int fd;
fd = open("/sys/devices/system/clocksource/clocksource0/current_clocksource", O_RDONLY);
+ if (fd < 0)
+ return -1;
+
+ len = read(fd, buf, size - 1);
+
+ close(fd);
+
+ if (len <= 0)
+ return -1;
- size = read(fd, buf, size);
+ buf[len] = '\0';
return 0;
}
diff --git a/tools/testing/selftests/timers/posix_timers.c b/tools/testing/selftests/timers/posix_timers.c
index a92d4b957747..52b7289e1450 100644
--- a/tools/testing/selftests/timers/posix_timers.c
+++ b/tools/testing/selftests/timers/posix_timers.c
@@ -113,9 +113,10 @@ static void check_itimer(int which, const char *name)
done = 0;
- if (which == ITIMER_VIRTUAL)
+ if (which == ITIMER_VIRTUAL) {
+ clock_id = CLOCK_THREAD_CPUTIME_ID;
signal(SIGVTALRM, sig_handler);
- else if (which == ITIMER_PROF) {
+ } else if (which == ITIMER_PROF) {
clock_id = CLOCK_THREAD_CPUTIME_ID;
signal(SIGPROF, sig_handler);
}
@@ -148,7 +149,7 @@ static void check_timer_create(int which)
struct itimerspec val = {
.it_value.tv_sec = DELAY,
};
- int clock_id = CLOCK_REALTIME;
+ int clock_id = which;
timer_t id;
done = 0;
diff --git a/tools/testing/selftests/timers/raw_skew.c b/tools/testing/selftests/timers/raw_skew.c
index 0c87a8fb0d7f..dfca3e2d50d2 100644
--- a/tools/testing/selftests/timers/raw_skew.c
+++ b/tools/testing/selftests/timers/raw_skew.c
@@ -91,6 +91,7 @@ int main(int argc, char **argv)
{
struct timespec mon, raw, start, end;
long long delta1, delta2, interval, eppm, ppm;
+ long tick_nom;
struct timex tx1, tx2;
setbuf(stdout, NULL);
@@ -129,6 +130,17 @@ int main(int argc, char **argv)
/* Avg the two actual freq samples adjtimex gave us */
ppm = (long long)(tx1.freq + tx2.freq) * 1000 / 2;
ppm = shift_right(ppm, 16);
+
+ /*
+ * The tick value holds the coarse part of the adjustment, a
+ * microsecond of the tick length per unit, and time sync daemons
+ * do put part of their correction there. What it is worth has to
+ * be counted in as well, or a clock disciplined through it looks
+ * off by a hundred ppm a unit against what the two clocks show.
+ */
+ tick_nom = USEC_PER_SEC / sysconf(_SC_CLK_TCK);
+ ppm += (long long)(tx1.tick + tx2.tick - 2 * tick_nom) *
+ (USEC_PER_SEC / tick_nom) * 1000 / 2;
printf(" %lld.%i(act)", ppm/1000, abs((int)(ppm%1000)));
if (llabs(eppm - ppm) > 1000) {
^ permalink raw reply [flat|nested] 12+ messages in thread
* [GIT pull] timers/nohz for v7.4-rc1
2026-10-21 9:50 [GIT pull] irq/core for v7.4-rc1 Thomas Gleixner
` (2 preceding siblings ...)
2026-10-21 9:51 ` [GIT pull] timers/core " Thomas Gleixner
@ 2026-10-21 9:51 ` Thomas Gleixner
2026-10-21 21:43 ` pr-tracker-bot
2026-10-21 9:51 ` [GIT pull] timers/vdso " Thomas Gleixner
2026-10-21 21:43 ` [GIT pull] irq/core " pr-tracker-bot
5 siblings, 1 reply; 12+ messages in thread
From: Thomas Gleixner @ 2026-10-21 9:51 UTC (permalink / raw)
To: Linus Torvalds; +Cc: linux-kernel, x86
Linus,
please pull the latest timers/nohz branch from:
git://git.kernel.org/pub/scm/linux/kernel/git/tip/tip.git timers-nohz-2026-10-20
up to: d305927765cf: tick/nohz: Avoid unused timekeeping_max_deferment() calls
Updates for the tick/NOHZ machinery:
- Add the BLOCK_SOFTIRQ to the hotplug safe local softirq pending mask to
prevent false positive error messages. A BLOCK_SOFTIRQ can be pending
when a CPU goes offline as the subsequent cleanup will take care of the
outstanding completions.
- Remove a few redundant invocations of local_irq_save/restore() and
timekeeping_max_deferment() to speed up the NOHZ idle transitions.
Thanks,
tglx
------------------>
Usama Arif (2):
tick/nohz: Remove redundant local_irq_save()/restore()
tick/nohz: Avoid unused timekeeping_max_deferment() calls
Wang Shuaiwei (1):
tick/nohz: Add BLOCK_SOFTIRQ to the hotplug safe mask
include/linux/interrupt.h | 6 ++++--
kernel/time/tick-sched.c | 30 ++++++++++++++----------------
2 files changed, 18 insertions(+), 18 deletions(-)
diff --git a/include/linux/interrupt.h b/include/linux/interrupt.h
index 3bf969ad8fe0..52bb684090c0 100644
--- a/include/linux/interrupt.h
+++ b/include/linux/interrupt.h
@@ -573,9 +573,11 @@ enum
* _ IRQ_POLL: irq_poll_cpu_dead() migrates the queue
*
* _ (HR)TIMER_SOFTIRQ: (hr)timers_dead_cpu() migrates the queue
+ *
+ * _ BLOCK_SOFTIRQ: blk_softirq_cpu_dead() completes the remaining requests
*/
-#define SOFTIRQ_HOTPLUG_SAFE_MASK (BIT(TIMER_SOFTIRQ) | BIT(IRQ_POLL_SOFTIRQ) |\
- BIT(HRTIMER_SOFTIRQ) | BIT(RCU_SOFTIRQ))
+#define SOFTIRQ_HOTPLUG_SAFE_MASK (BIT(TIMER_SOFTIRQ) | BIT(BLOCK_SOFTIRQ) |\
+ BIT(IRQ_POLL_SOFTIRQ) | BIT(HRTIMER_SOFTIRQ) | BIT(RCU_SOFTIRQ))
/* map softirq index to softirq name. update 'softirq_to_name' in
diff --git a/kernel/time/tick-sched.c b/kernel/time/tick-sched.c
index 6c3fea386713..a7893a079a83 100644
--- a/kernel/time/tick-sched.c
+++ b/kernel/time/tick-sched.c
@@ -738,14 +738,11 @@ bool tick_nohz_tick_stopped_cpu(int cpu)
*/
static void tick_nohz_update_jiffies(ktime_t now)
{
- unsigned long flags;
+ /* Reached only from irq_enter_rcu(), i.e. hard interrupt entry. */
+ lockdep_assert_irqs_disabled();
__this_cpu_write(tick_cpu_sched.idle_waketime, now);
-
- local_irq_save(flags);
tick_do_update_jiffies64(now);
- local_irq_restore(flags);
-
touch_softlockup_watchdog_sched();
}
@@ -819,7 +816,7 @@ u64 get_jiffies_update(unsigned long *basej)
*/
static ktime_t tick_nohz_next_event(struct tick_sched *ts, int cpu)
{
- u64 basemono, next_tick, delta, expires;
+ u64 basemono, next_tick, expires;
unsigned long basejiff;
int tick_cpu;
@@ -859,8 +856,7 @@ static ktime_t tick_nohz_next_event(struct tick_sched *ts, int cpu)
* If the tick is due in the next period, keep it ticking or
* force prod the timer.
*/
- delta = next_tick - basemono;
- if (delta <= (u64)TICK_NSEC) {
+ if (next_tick - basemono <= (u64)TICK_NSEC) {
/*
* We've not stopped the tick yet, and there's a timer in the
* next period, so no point in stopping it either, bail.
@@ -876,17 +872,19 @@ static ktime_t tick_nohz_next_event(struct tick_sched *ts, int cpu)
* the sleep time to the timekeeping 'max_deferment' value.
* Otherwise we can sleep as long as we want.
*/
- delta = timekeeping_max_deferment();
tick_cpu = READ_ONCE(tick_do_timer_cpu);
if (tick_cpu != cpu &&
- (tick_cpu != TICK_DO_TIMER_NONE || !tick_sched_flag_test(ts, TS_FLAG_DO_TIMER_LAST)))
- delta = KTIME_MAX;
-
- /* Calculate the next expiry time */
- if (delta < (KTIME_MAX - basemono))
- expires = basemono + delta;
- else
+ (tick_cpu != TICK_DO_TIMER_NONE || !tick_sched_flag_test(ts, TS_FLAG_DO_TIMER_LAST))) {
expires = KTIME_MAX;
+ } else {
+ expires = timekeeping_max_deferment();
+
+ /* Calculate the next expiry time */
+ if (expires < (KTIME_MAX - basemono))
+ expires += basemono;
+ else
+ expires = KTIME_MAX;
+ }
ts->timer_expires = min_t(u64, expires, next_tick);
^ permalink raw reply [flat|nested] 12+ messages in thread
* [GIT pull] timers/vdso for v7.4-rc1
2026-10-21 9:50 [GIT pull] irq/core for v7.4-rc1 Thomas Gleixner
` (3 preceding siblings ...)
2026-10-21 9:51 ` [GIT pull] timers/nohz " Thomas Gleixner
@ 2026-10-21 9:51 ` Thomas Gleixner
2026-10-21 21:43 ` pr-tracker-bot
2026-10-21 21:43 ` [GIT pull] irq/core " pr-tracker-bot
5 siblings, 1 reply; 12+ messages in thread
From: Thomas Gleixner @ 2026-10-21 9:51 UTC (permalink / raw)
To: Linus Torvalds; +Cc: linux-kernel, x86
Linus,
please pull the latest timers/vdso branch from:
git://git.kernel.org/pub/scm/linux/kernel/git/tip/tip.git timers-vdso-2026-10-20
up to: 8d17190fae2c: vdso/vsyscall: Keep the CLOCK_AUX base scaled
Updates for the VDSO subsystem:
- Ensure that the CLOCK_AUX base calculations are properly scaled against
the base timekeeper. The update function failed to take the fractional
nanosecond part into account, which results in a rounding error.
- Cleanup and consolidate the vsyscall headers to get rid of pointless
copies all over the architectures.
- Replace a runtime conditional in the time getter function for namespaced
clocks by simple pointer arithmetic which speeds up the read function.
Thanks,
tglx
------------------>
Thomas Weißschuh (16):
vdso: Move the kernel-side time update helpers to a new header
vdso/gettimeofday: Define fallback __arch_get_vdso_u_time_data() in gettimeofday.c
vdso/getrandom: Define fallback __arch_get_vdso_u_rng_data() in getrandom.c
vdso/vsyscall: Define fallback time synchronization functions in vsyscall.c
vdso: Delete asm-generic/vdso/vsyscall.h
x86/vdso: Move vDSO page definitions to asm/vdso.h
sparc/vdso: Move __VDSO_PAGES to asm/vdso.h
s390/vdso: Delete asm/vdso/vsyscall.h inclusions
sparc/vdso: Delete asm/vdso/vsyscall.h inclusion
x86/vdso: Delete asm/vdso/vsyscall.h inclusions
arm64/vdso: Delete asm/vdso/vsyscall.h inclusions
riscv/hwprobe: Include vdso/datapage.h
vdso: Remove unnecessary vdso/vsyscall.h includes
vdso: Add asm-generic/vdso/vsyscall.h
vdso: Remove __ASSEMBLER__ guards from vsyscall.h
vdso/gettimeofday: Avoid runtime conditional in do_hres_timens()
Zhan Xusheng (3):
vdso/math64: Use OPTIMIZER_HIDE_VAR() in __iter_div_u64_rem()
vdso/math64: Add and use __iter_div64_u64_rem()
vdso/vsyscall: Keep the CLOCK_AUX base scaled
MAINTAINERS | 2 +-
arch/arm/include/asm/vdso/vsyscall.h | 7 ----
arch/arm/kernel/vdso.c | 1 -
arch/arm64/include/asm/vdso/getrandom.h | 1 -
arch/arm64/include/asm/vdso/vsyscall.h | 7 ----
arch/arm64/kernel/vdso.c | 1 -
arch/arm64/kernel/vdso/vdso.lds.S | 1 -
arch/loongarch/include/asm/vdso/vsyscall.h | 14 --------
arch/loongarch/kernel/vdso.c | 1 -
arch/mips/include/asm/vdso/vsyscall.h | 16 ---------
arch/mips/kernel/vdso.c | 1 -
arch/powerpc/include/asm/vdso/vsyscall.h | 14 --------
arch/riscv/include/asm/vdso/vsyscall.h | 14 --------
arch/riscv/kernel/sys_hwprobe.c | 2 +-
arch/riscv/kernel/vdso.c | 1 -
arch/s390/include/asm/vdso/getrandom.h | 1 -
arch/s390/include/asm/vdso/vsyscall.h | 16 ---------
arch/s390/kernel/time.c | 6 ++--
arch/s390/kernel/vdso.c | 1 -
arch/s390/kernel/vdso/vdso.lds.S | 1 -
arch/sparc/include/asm/vdso.h | 6 ++++
arch/sparc/include/asm/vdso/vsyscall.h | 10 ------
arch/sparc/vdso/vdso-layout.lds.S | 2 +-
arch/sparc/vdso/vma.c | 1 -
arch/x86/entry/vdso/common/vdso-layout.lds.S | 1 -
arch/x86/entry/vdso/vma.c | 1 -
arch/x86/include/asm/vdso.h | 7 ++++
arch/x86/include/asm/vdso/vsyscall.h | 22 ------------
include/asm-generic/Kbuild | 1 +
include/asm-generic/vdso/vsyscall.h | 35 ------------------
include/linux/vdso_time.h | 8 +++++
include/vdso/math64.h | 33 ++++++++++++++---
include/vdso/vsyscall.h | 7 ----
kernel/time/vsyscall.c | 53 ++++++++++++++++------------
lib/vdso/getrandom.c | 8 +++--
lib/vdso/gettimeofday.c | 23 +++++++-----
36 files changed, 106 insertions(+), 220 deletions(-)
delete mode 100644 arch/loongarch/include/asm/vdso/vsyscall.h
delete mode 100644 arch/mips/include/asm/vdso/vsyscall.h
delete mode 100644 arch/powerpc/include/asm/vdso/vsyscall.h
delete mode 100644 arch/riscv/include/asm/vdso/vsyscall.h
delete mode 100644 arch/s390/include/asm/vdso/vsyscall.h
delete mode 100644 arch/sparc/include/asm/vdso/vsyscall.h
delete mode 100644 arch/x86/include/asm/vdso/vsyscall.h
create mode 100644 include/linux/vdso_time.h
diff --git a/MAINTAINERS b/MAINTAINERS
index 72294ddfa5b7..6c9810ff274a 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -11062,8 +11062,8 @@ M: Vincenzo Frascino <vincenzo.frascino@arm.com>
L: linux-kernel@vger.kernel.org
S: Maintained
T: git git://git.kernel.org/pub/scm/linux/kernel/git/tip/tip.git timers/vdso
-F: include/asm-generic/vdso/vsyscall.h
F: include/linux/vdso_datastore.h
+F: include/linux/vdso_time.h
F: include/vdso/
F: kernel/time/namespace_vdso.c
F: kernel/time/vsyscall.c
diff --git a/arch/arm/include/asm/vdso/vsyscall.h b/arch/arm/include/asm/vdso/vsyscall.h
index ff1c729af05f..b97dda100e52 100644
--- a/arch/arm/include/asm/vdso/vsyscall.h
+++ b/arch/arm/include/asm/vdso/vsyscall.h
@@ -2,8 +2,6 @@
#ifndef __ASM_VDSO_VSYSCALL_H
#define __ASM_VDSO_VSYSCALL_H
-#ifndef __ASSEMBLY__
-
#include <vdso/datapage.h>
#include <asm/cacheflush.h>
@@ -14,9 +12,4 @@ void __arch_sync_vdso_time_data(struct vdso_time_data *vdata)
}
#define __arch_sync_vdso_time_data __arch_sync_vdso_time_data
-/* The asm-generic header needs to be included after the definitions above */
-#include <asm-generic/vdso/vsyscall.h>
-
-#endif /* !__ASSEMBLY__ */
-
#endif /* __ASM_VDSO_VSYSCALL_H */
diff --git a/arch/arm/kernel/vdso.c b/arch/arm/kernel/vdso.c
index e306d0c5a21c..7be871f5eb9e 100644
--- a/arch/arm/kernel/vdso.c
+++ b/arch/arm/kernel/vdso.c
@@ -23,7 +23,6 @@
#include <asm/vdso.h>
#include <clocksource/arm_arch_timer.h>
#include <vdso/helpers.h>
-#include <vdso/vsyscall.h>
#define MAX_SYMNAME 64
diff --git a/arch/arm64/include/asm/vdso/getrandom.h b/arch/arm64/include/asm/vdso/getrandom.h
index da1d58bbfabe..d9b9104d4ea1 100644
--- a/arch/arm64/include/asm/vdso/getrandom.h
+++ b/arch/arm64/include/asm/vdso/getrandom.h
@@ -6,7 +6,6 @@
#ifndef __ASSEMBLER__
#include <asm/unistd.h>
-#include <asm/vdso/vsyscall.h>
#include <vdso/datapage.h>
/**
diff --git a/arch/arm64/include/asm/vdso/vsyscall.h b/arch/arm64/include/asm/vdso/vsyscall.h
index 3f3c8eb74e2e..7c46c37fd36f 100644
--- a/arch/arm64/include/asm/vdso/vsyscall.h
+++ b/arch/arm64/include/asm/vdso/vsyscall.h
@@ -2,8 +2,6 @@
#ifndef __ASM_VDSO_VSYSCALL_H
#define __ASM_VDSO_VSYSCALL_H
-#ifndef __ASSEMBLER__
-
#include <vdso/datapage.h>
#define VDSO_PRECISION_MASK ~(0xFF00ULL<<48)
@@ -19,9 +17,4 @@ void __arch_update_vdso_clock(struct vdso_clock *vc)
}
#define __arch_update_vdso_clock __arch_update_vdso_clock
-/* The asm-generic header needs to be included after the definitions above */
-#include <asm-generic/vdso/vsyscall.h>
-
-#endif /* !__ASSEMBLER__ */
-
#endif /* __ASM_VDSO_VSYSCALL_H */
diff --git a/arch/arm64/kernel/vdso.c b/arch/arm64/kernel/vdso.c
index 592dd8668de4..50596dab23f9 100644
--- a/arch/arm64/kernel/vdso.c
+++ b/arch/arm64/kernel/vdso.c
@@ -22,7 +22,6 @@
#include <linux/vmalloc.h>
#include <vdso/datapage.h>
#include <vdso/helpers.h>
-#include <vdso/vsyscall.h>
#include <asm/cacheflush.h>
#include <asm/signal32.h>
diff --git a/arch/arm64/kernel/vdso/vdso.lds.S b/arch/arm64/kernel/vdso/vdso.lds.S
index 52314be29191..6334ca0a7673 100644
--- a/arch/arm64/kernel/vdso/vdso.lds.S
+++ b/arch/arm64/kernel/vdso/vdso.lds.S
@@ -11,7 +11,6 @@
#include <linux/const.h>
#include <asm/page.h>
#include <asm/vdso.h>
-#include <asm/vdso/vsyscall.h>
#include <asm-generic/vmlinux.lds.h>
#include <vdso/datapage.h>
diff --git a/arch/loongarch/include/asm/vdso/vsyscall.h b/arch/loongarch/include/asm/vdso/vsyscall.h
deleted file mode 100644
index 558eb9dfda52..000000000000
--- a/arch/loongarch/include/asm/vdso/vsyscall.h
+++ /dev/null
@@ -1,14 +0,0 @@
-/* SPDX-License-Identifier: GPL-2.0 */
-#ifndef __ASM_VDSO_VSYSCALL_H
-#define __ASM_VDSO_VSYSCALL_H
-
-#ifndef __ASSEMBLER__
-
-#include <vdso/datapage.h>
-
-/* The asm-generic header needs to be included after the definitions above */
-#include <asm-generic/vdso/vsyscall.h>
-
-#endif /* !__ASSEMBLER__ */
-
-#endif /* __ASM_VDSO_VSYSCALL_H */
diff --git a/arch/loongarch/kernel/vdso.c b/arch/loongarch/kernel/vdso.c
index 8ce8159c10b9..1edc8fb720de 100644
--- a/arch/loongarch/kernel/vdso.c
+++ b/arch/loongarch/kernel/vdso.c
@@ -20,7 +20,6 @@
#include <asm/vdso.h>
#include <asm/vdso/vdso.h>
#include <vdso/helpers.h>
-#include <vdso/vsyscall.h>
#include <vdso/datapage.h>
#include <generated/vdso-offsets.h>
diff --git a/arch/mips/include/asm/vdso/vsyscall.h b/arch/mips/include/asm/vdso/vsyscall.h
deleted file mode 100644
index 0f061a9babd1..000000000000
--- a/arch/mips/include/asm/vdso/vsyscall.h
+++ /dev/null
@@ -1,16 +0,0 @@
-/* SPDX-License-Identifier: GPL-2.0 */
-#ifndef __ASM_VDSO_VSYSCALL_H
-#define __ASM_VDSO_VSYSCALL_H
-
-#include <asm/page.h>
-
-#ifndef __ASSEMBLER__
-
-#include <vdso/datapage.h>
-
-/* The asm-generic header needs to be included after the definitions above */
-#include <asm-generic/vdso/vsyscall.h>
-
-#endif /* !__ASSEMBLER__ */
-
-#endif /* __ASM_VDSO_VSYSCALL_H */
diff --git a/arch/mips/kernel/vdso.c b/arch/mips/kernel/vdso.c
index 8165dd3fc1f8..8eb1760a8a9b 100644
--- a/arch/mips/kernel/vdso.c
+++ b/arch/mips/kernel/vdso.c
@@ -23,7 +23,6 @@
#include <asm/vdso.h>
#include <asm/vdso/vdso.h>
#include <vdso/helpers.h>
-#include <vdso/vsyscall.h>
static_assert(VDSO_NR_PAGES == __VDSO_PAGES);
diff --git a/arch/powerpc/include/asm/vdso/vsyscall.h b/arch/powerpc/include/asm/vdso/vsyscall.h
deleted file mode 100644
index bee18e8660a0..000000000000
--- a/arch/powerpc/include/asm/vdso/vsyscall.h
+++ /dev/null
@@ -1,14 +0,0 @@
-/* SPDX-License-Identifier: GPL-2.0 */
-#ifndef _ASM_POWERPC_VDSO_VSYSCALL_H
-#define _ASM_POWERPC_VDSO_VSYSCALL_H
-
-#ifndef __ASSEMBLER__
-
-#include <asm/vdso_datapage.h>
-
-/* The asm-generic header needs to be included after the definitions above */
-#include <asm-generic/vdso/vsyscall.h>
-
-#endif /* !__ASSEMBLER__ */
-
-#endif /* _ASM_POWERPC_VDSO_VSYSCALL_H */
diff --git a/arch/riscv/include/asm/vdso/vsyscall.h b/arch/riscv/include/asm/vdso/vsyscall.h
deleted file mode 100644
index 558eb9dfda52..000000000000
--- a/arch/riscv/include/asm/vdso/vsyscall.h
+++ /dev/null
@@ -1,14 +0,0 @@
-/* SPDX-License-Identifier: GPL-2.0 */
-#ifndef __ASM_VDSO_VSYSCALL_H
-#define __ASM_VDSO_VSYSCALL_H
-
-#ifndef __ASSEMBLER__
-
-#include <vdso/datapage.h>
-
-/* The asm-generic header needs to be included after the definitions above */
-#include <asm-generic/vdso/vsyscall.h>
-
-#endif /* !__ASSEMBLER__ */
-
-#endif /* __ASM_VDSO_VSYSCALL_H */
diff --git a/arch/riscv/kernel/sys_hwprobe.c b/arch/riscv/kernel/sys_hwprobe.c
index 7818e1d32622..d427d2f02c60 100644
--- a/arch/riscv/kernel/sys_hwprobe.c
+++ b/arch/riscv/kernel/sys_hwprobe.c
@@ -18,7 +18,7 @@
#include <asm/vendor_extensions/mips_hwprobe.h>
#include <asm/vendor_extensions/sifive_hwprobe.h>
#include <asm/vendor_extensions/thead_hwprobe.h>
-#include <vdso/vsyscall.h>
+#include <vdso/datapage.h>
#define EXT_KEY(isa_arg, ext, pv, missing) \
diff --git a/arch/riscv/kernel/vdso.c b/arch/riscv/kernel/vdso.c
index 9c2f5e442338..5666cec180ac 100644
--- a/arch/riscv/kernel/vdso.c
+++ b/arch/riscv/kernel/vdso.c
@@ -15,7 +15,6 @@
#include <asm/vdso.h>
#include <linux/vdso_datastore.h>
#include <vdso/datapage.h>
-#include <vdso/vsyscall.h>
#define VVAR_SIZE (VDSO_NR_PAGES << PAGE_SHIFT)
diff --git a/arch/s390/include/asm/vdso/getrandom.h b/arch/s390/include/asm/vdso/getrandom.h
index 6741a27199f8..ff5882f2fc70 100644
--- a/arch/s390/include/asm/vdso/getrandom.h
+++ b/arch/s390/include/asm/vdso/getrandom.h
@@ -6,7 +6,6 @@
#ifndef __ASSEMBLER__
#include <vdso/datapage.h>
-#include <asm/vdso/vsyscall.h>
#include <asm/syscall.h>
#include <asm/unistd.h>
#include <asm/page.h>
diff --git a/arch/s390/include/asm/vdso/vsyscall.h b/arch/s390/include/asm/vdso/vsyscall.h
deleted file mode 100644
index b00acec8ddbc..000000000000
--- a/arch/s390/include/asm/vdso/vsyscall.h
+++ /dev/null
@@ -1,16 +0,0 @@
-/* SPDX-License-Identifier: GPL-2.0 */
-#ifndef __ASM_VDSO_VSYSCALL_H
-#define __ASM_VDSO_VSYSCALL_H
-
-#ifndef __ASSEMBLER__
-
-#include <linux/hrtimer.h>
-#include <vdso/datapage.h>
-#include <asm/vdso.h>
-
-/* The asm-generic header needs to be included after the definitions above */
-#include <asm-generic/vdso/vsyscall.h>
-
-#endif /* !__ASSEMBLER__ */
-
-#endif /* __ASM_VDSO_VSYSCALL_H */
diff --git a/arch/s390/kernel/time.c b/arch/s390/kernel/time.c
index 2b989bebd220..bca65bf1c8cb 100644
--- a/arch/s390/kernel/time.c
+++ b/arch/s390/kernel/time.c
@@ -39,7 +39,7 @@
#include <linux/gfp.h>
#include <linux/kprobes.h>
#include <linux/uaccess.h>
-#include <vdso/vsyscall.h>
+#include <linux/vdso_time.h>
#include <vdso/clocksource.h>
#include <vdso/helpers.h>
#include <asm/facility.h>
@@ -527,7 +527,7 @@ static int stp_sync_clock(void *data)
cpu_relax();
rc = 0;
if (stp_info.todoff || stp_info.tmd != 2) {
- flags = vdso_update_begin();
+ flags = vdso_time_update_begin();
rc = chsc_sstpc(stp_page, STP_OP_SYNC, 0,
&clock_delta);
if (rc == 0) {
@@ -537,7 +537,7 @@ static int stp_sync_clock(void *data)
if (rc == 0 && stp_info.tmd != 2)
rc = -EAGAIN;
}
- vdso_update_end(flags);
+ vdso_time_update_end(flags);
}
sync->in_sync = rc ? -EAGAIN : 1;
xchg(&first, 0);
diff --git a/arch/s390/kernel/vdso.c b/arch/s390/kernel/vdso.c
index 0fe616c49fcc..a905f989f910 100644
--- a/arch/s390/kernel/vdso.c
+++ b/arch/s390/kernel/vdso.c
@@ -18,7 +18,6 @@
#include <linux/random.h>
#include <linux/vdso_datastore.h>
#include <vdso/datapage.h>
-#include <asm/vdso/vsyscall.h>
#include <asm/alternative.h>
#include <asm/vdso.h>
diff --git a/arch/s390/kernel/vdso/vdso.lds.S b/arch/s390/kernel/vdso/vdso.lds.S
index 841daeec4be2..6bcdb64315f2 100644
--- a/arch/s390/kernel/vdso/vdso.lds.S
+++ b/arch/s390/kernel/vdso/vdso.lds.S
@@ -4,7 +4,6 @@
* library
*/
-#include <asm/vdso/vsyscall.h>
#include <asm/page.h>
#include <asm/vdso.h>
#include <asm-generic/vmlinux.lds.h>
diff --git a/arch/sparc/include/asm/vdso.h b/arch/sparc/include/asm/vdso.h
index f08562d10215..b17c37c8075b 100644
--- a/arch/sparc/include/asm/vdso.h
+++ b/arch/sparc/include/asm/vdso.h
@@ -5,6 +5,10 @@
#ifndef _ASM_SPARC_VDSO_H
#define _ASM_SPARC_VDSO_H
+#define __VDSO_PAGES 4
+
+#ifndef __ASSEMBLER__
+
struct vdso_image {
void *data;
unsigned long size; /* Always a multiple of PAGE_SIZE */
@@ -17,4 +21,6 @@ extern const struct vdso_image vdso_image_64_builtin;
extern const struct vdso_image vdso_image_32_builtin;
#endif
+#endif /* __ASSEMBLER__ */
+
#endif /* _ASM_SPARC_VDSO_H */
diff --git a/arch/sparc/include/asm/vdso/vsyscall.h b/arch/sparc/include/asm/vdso/vsyscall.h
deleted file mode 100644
index 8bfe703fedc5..000000000000
--- a/arch/sparc/include/asm/vdso/vsyscall.h
+++ /dev/null
@@ -1,10 +0,0 @@
-/* SPDX-License-Identifier: GPL-2.0 */
-
-#ifndef _ASM_SPARC_VDSO_VSYSCALL_H
-#define _ASM_SPARC_VDSO_VSYSCALL_H
-
-#define __VDSO_PAGES 4
-
-#include <asm-generic/vdso/vsyscall.h>
-
-#endif /* _ASM_SPARC_VDSO_VSYSCALL_H */
diff --git a/arch/sparc/vdso/vdso-layout.lds.S b/arch/sparc/vdso/vdso-layout.lds.S
index 180e5d0ee071..920396203a2e 100644
--- a/arch/sparc/vdso/vdso-layout.lds.S
+++ b/arch/sparc/vdso/vdso-layout.lds.S
@@ -6,7 +6,7 @@
#include <vdso/datapage.h>
#include <vdso/page.h>
-#include <asm/vdso/vsyscall.h>
+#include <asm/vdso.h>
SECTIONS
{
diff --git a/arch/sparc/vdso/vma.c b/arch/sparc/vdso/vma.c
index 60029d60f4d3..ab6626c1ee61 100644
--- a/arch/sparc/vdso/vma.c
+++ b/arch/sparc/vdso/vma.c
@@ -23,7 +23,6 @@
#include <asm/page.h>
#include <vdso/datapage.h>
-#include <asm/vdso/vsyscall.h>
unsigned int __read_mostly vdso_enabled = 1;
diff --git a/arch/x86/entry/vdso/common/vdso-layout.lds.S b/arch/x86/entry/vdso/common/vdso-layout.lds.S
index a1e30be3e83d..f190efbc640e 100644
--- a/arch/x86/entry/vdso/common/vdso-layout.lds.S
+++ b/arch/x86/entry/vdso/common/vdso-layout.lds.S
@@ -1,6 +1,5 @@
/* SPDX-License-Identifier: GPL-2.0 */
#include <asm/vdso.h>
-#include <asm/vdso/vsyscall.h>
#include <vdso/datapage.h>
/*
diff --git a/arch/x86/entry/vdso/vma.c b/arch/x86/entry/vdso/vma.c
index 18dfd80a81ef..4bd3b22beea9 100644
--- a/arch/x86/entry/vdso/vma.c
+++ b/arch/x86/entry/vdso/vma.c
@@ -25,7 +25,6 @@
#include <asm/page.h>
#include <asm/desc.h>
#include <asm/cpufeature.h>
-#include <asm/vdso/vsyscall.h>
#include <clocksource/hyperv_timer.h>
static_assert(VDSO_NR_PAGES + VDSO_NR_VCLOCK_PAGES == __VDSO_PAGES);
diff --git a/arch/x86/include/asm/vdso.h b/arch/x86/include/asm/vdso.h
index 4e735158c1b2..071e4a262b50 100644
--- a/arch/x86/include/asm/vdso.h
+++ b/arch/x86/include/asm/vdso.h
@@ -6,6 +6,13 @@
#include <linux/linkage.h>
#include <linux/init.h>
+#define __VDSO_PAGES 6
+
+#define VDSO_NR_VCLOCK_PAGES 2
+#define VDSO_VCLOCK_PAGES_START(_b) ((_b) + (__VDSO_PAGES - VDSO_NR_VCLOCK_PAGES) * PAGE_SIZE)
+#define VDSO_PAGE_PVCLOCK_OFFSET 0
+#define VDSO_PAGE_HVCLOCK_OFFSET 1
+
#ifndef __ASSEMBLER__
#include <linux/mm_types.h>
diff --git a/arch/x86/include/asm/vdso/vsyscall.h b/arch/x86/include/asm/vdso/vsyscall.h
deleted file mode 100644
index 4aa311a923f2..000000000000
--- a/arch/x86/include/asm/vdso/vsyscall.h
+++ /dev/null
@@ -1,22 +0,0 @@
-/* SPDX-License-Identifier: GPL-2.0 */
-#ifndef __ASM_VDSO_VSYSCALL_H
-#define __ASM_VDSO_VSYSCALL_H
-
-#define __VDSO_PAGES 6
-
-#define VDSO_NR_VCLOCK_PAGES 2
-#define VDSO_VCLOCK_PAGES_START(_b) ((_b) + (__VDSO_PAGES - VDSO_NR_VCLOCK_PAGES) * PAGE_SIZE)
-#define VDSO_PAGE_PVCLOCK_OFFSET 0
-#define VDSO_PAGE_HVCLOCK_OFFSET 1
-
-#ifndef __ASSEMBLER__
-
-#include <vdso/datapage.h>
-#include <asm/vgtod.h>
-
-/* The asm-generic header needs to be included after the definitions above */
-#include <asm-generic/vdso/vsyscall.h>
-
-#endif /* !__ASSEMBLER__ */
-
-#endif /* __ASM_VDSO_VSYSCALL_H */
diff --git a/include/asm-generic/Kbuild b/include/asm-generic/Kbuild
index 2bc00c67dc54..b7a388bef6ea 100644
--- a/include/asm-generic/Kbuild
+++ b/include/asm-generic/Kbuild
@@ -63,6 +63,7 @@ mandatory-y += trace_clock.h
mandatory-y += uaccess.h
mandatory-y += unwind_user.h
mandatory-y += vdso/clocksource.h
+mandatory-y += vdso/vsyscall.h
mandatory-y += vermagic.h
mandatory-y += vga.h
mandatory-y += video.h
diff --git a/include/asm-generic/vdso/vsyscall.h b/include/asm-generic/vdso/vsyscall.h
index a6b03cfba0e2..e69de29bb2d1 100644
--- a/include/asm-generic/vdso/vsyscall.h
+++ b/include/asm-generic/vdso/vsyscall.h
@@ -1,35 +0,0 @@
-/* SPDX-License-Identifier: GPL-2.0 */
-#ifndef __ASM_GENERIC_VSYSCALL_H
-#define __ASM_GENERIC_VSYSCALL_H
-
-#ifndef __ASSEMBLER__
-
-#ifndef __arch_get_vdso_u_time_data
-static __always_inline const struct vdso_time_data *__arch_get_vdso_u_time_data(void)
-{
- return &vdso_u_time_data;
-}
-#endif
-
-#ifndef __arch_get_vdso_u_rng_data
-static __always_inline const struct vdso_rng_data *__arch_get_vdso_u_rng_data(void)
-{
- return &vdso_u_rng_data;
-}
-#endif
-
-#ifndef __arch_update_vdso_clock
-static __always_inline void __arch_update_vdso_clock(struct vdso_clock *vc)
-{
-}
-#endif /* __arch_update_vdso_clock */
-
-#ifndef __arch_sync_vdso_time_data
-static __always_inline void __arch_sync_vdso_time_data(struct vdso_time_data *vdata)
-{
-}
-#endif /* __arch_sync_vdso_time_data */
-
-#endif /* !__ASSEMBLER__ */
-
-#endif /* __ASM_GENERIC_VSYSCALL_H */
diff --git a/include/linux/vdso_time.h b/include/linux/vdso_time.h
new file mode 100644
index 000000000000..6d81ee3a3c9c
--- /dev/null
+++ b/include/linux/vdso_time.h
@@ -0,0 +1,8 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#ifndef _LINUX_VDSO_TIME_H
+#define _LINUX_VDSO_TIME_H
+
+unsigned long vdso_time_update_begin(void);
+void vdso_time_update_end(unsigned long flags);
+
+#endif /* _LINUX_VDSO_TIME_H */
diff --git a/include/vdso/math64.h b/include/vdso/math64.h
index 22ae212f8b28..6f1e311cfdbd 100644
--- a/include/vdso/math64.h
+++ b/include/vdso/math64.h
@@ -2,15 +2,38 @@
#ifndef __VDSO_MATH64_H
#define __VDSO_MATH64_H
-static __always_inline u32
-__iter_div_u64_rem(u64 dividend, u32 divisor, u64 *remainder)
+#include <linux/compiler.h>
+
+static __always_inline u32 __iter_div_u64_rem(u64 dividend, u32 divisor, u64 *remainder)
+{
+ u32 ret = 0;
+
+ while (dividend >= divisor) {
+ /*
+ * Prevent the compiler from optimising this loop into a
+ * modulo operation.
+ */
+ OPTIMIZER_HIDE_VAR(dividend);
+
+ dividend -= divisor;
+ ret++;
+ }
+
+ *remainder = dividend;
+
+ return ret;
+}
+
+static __always_inline u32 __iter_div64_u64_rem(u64 dividend, u64 divisor, u64 *remainder)
{
u32 ret = 0;
while (dividend >= divisor) {
- /* The following asm() prevents the compiler from
- optimising this loop into a modulo operation. */
- asm("" : "+rm"(dividend));
+ /*
+ * Prevent the compiler from optimising this loop into a
+ * modulo operation.
+ */
+ OPTIMIZER_HIDE_VAR(dividend);
dividend -= divisor;
ret++;
diff --git a/include/vdso/vsyscall.h b/include/vdso/vsyscall.h
index c5c2a2c07857..af62bbf04eac 100644
--- a/include/vdso/vsyscall.h
+++ b/include/vdso/vsyscall.h
@@ -2,13 +2,6 @@
#ifndef __VDSO_VSYSCALL_H
#define __VDSO_VSYSCALL_H
-#ifndef __ASSEMBLER__
-
#include <asm/vdso/vsyscall.h>
-unsigned long vdso_update_begin(void);
-void vdso_update_end(unsigned long flags);
-
-#endif /* !__ASSEMBLER__ */
-
#endif /* __VDSO_VSYSCALL_H */
diff --git a/kernel/time/vsyscall.c b/kernel/time/vsyscall.c
index aa59919b8f2c..658553fd910b 100644
--- a/kernel/time/vsyscall.c
+++ b/kernel/time/vsyscall.c
@@ -9,12 +9,25 @@
#include <linux/hrtimer.h>
#include <linux/timekeeper_internal.h>
+#include <linux/vdso_time.h>
#include <vdso/datapage.h>
#include <vdso/helpers.h>
#include <vdso/vsyscall.h>
#include "timekeeping_internal.h"
+#ifndef __arch_update_vdso_clock
+static __always_inline void __arch_update_vdso_clock(struct vdso_clock *vc)
+{
+}
+#endif /* __arch_update_vdso_clock */
+
+#ifndef __arch_sync_vdso_time_data
+static __always_inline void __arch_sync_vdso_time_data(struct vdso_time_data *vdata)
+{
+}
+#endif /* __arch_sync_vdso_time_data */
+
static inline void fill_clock_configuration(struct vdso_clock *vc, const struct tk_read_base *base)
{
vc->cycle_last = base->cycle_last;
@@ -41,14 +54,12 @@ static inline void update_vdso_time_data(struct vdso_time_data *vdata, struct ti
nsec = tk->tkr_mono.xtime_nsec;
nsec += ((u64)tk->wall_to_monotonic.tv_nsec << tk->tkr_mono.shift);
- while (nsec >= (((u64)NSEC_PER_SEC) << tk->tkr_mono.shift)) {
- nsec -= (((u64)NSEC_PER_SEC) << tk->tkr_mono.shift);
- vdso_ts->sec++;
- }
- vdso_ts->nsec = nsec;
+ vdso_ts->sec += __iter_div64_u64_rem(nsec, (u64)NSEC_PER_SEC << tk->tkr_mono.shift,
+ &vdso_ts->nsec);
/* Copy MONOTONIC time for BOOTTIME */
sec = vdso_ts->sec;
+ nsec = vdso_ts->nsec;
/* Add the boot offset */
sec += tk->monotonic_to_boot.tv_sec;
nsec += (u64)tk->monotonic_to_boot.tv_nsec << tk->tkr_mono.shift;
@@ -56,12 +67,8 @@ static inline void update_vdso_time_data(struct vdso_time_data *vdata, struct ti
/* CLOCK_BOOTTIME */
vdso_ts = &vc[CS_HRES_COARSE].basetime[CLOCK_BOOTTIME];
vdso_ts->sec = sec;
-
- while (nsec >= (((u64)NSEC_PER_SEC) << tk->tkr_mono.shift)) {
- nsec -= (((u64)NSEC_PER_SEC) << tk->tkr_mono.shift);
- vdso_ts->sec++;
- }
- vdso_ts->nsec = nsec;
+ vdso_ts->sec += __iter_div64_u64_rem(nsec, (u64)NSEC_PER_SEC << tk->tkr_mono.shift,
+ &vdso_ts->nsec);
/* CLOCK_MONOTONIC_RAW */
vdso_ts = &vc[CS_RAW].basetime[CLOCK_MONOTONIC_RAW];
@@ -161,11 +168,11 @@ void vdso_time_update_aux(struct timekeeper *tk)
vdso_ts->sec = tk->xtime_sec + tk->monotonic_to_aux.tv_sec;
- nsec = tk->tkr_mono.xtime_nsec >> tk->tkr_mono.shift;
- nsec += tk->monotonic_to_aux.tv_nsec;
- vdso_ts->sec += __iter_div_u64_rem(nsec, NSEC_PER_SEC, &nsec);
- nsec = nsec << tk->tkr_mono.shift;
- vdso_ts->nsec = nsec;
+ nsec = tk->tkr_mono.xtime_nsec;
+ nsec += (u64)tk->monotonic_to_aux.tv_nsec << tk->tkr_mono.shift;
+ vdso_ts->sec += __iter_div64_u64_rem(nsec,
+ (u64)NSEC_PER_SEC << tk->tkr_mono.shift,
+ &vdso_ts->nsec);
}
__arch_update_vdso_clock(vc);
@@ -177,7 +184,7 @@ void vdso_time_update_aux(struct timekeeper *tk)
#endif
/**
- * vdso_update_begin - Start of a VDSO update section
+ * vdso_time_update_begin - Start of a VDSO update section
*
* Allows architecture code to safely update the architecture specific VDSO
* data. Disables interrupts, acquires timekeeper lock to serialize against
@@ -186,9 +193,9 @@ void vdso_time_update_aux(struct timekeeper *tk)
* inconsistent data.
*
* Returns: Saved interrupt flags which need to be handed in to
- * vdso_update_end().
+ * vdso_time_update_end().
*/
-unsigned long vdso_update_begin(void)
+unsigned long vdso_time_update_begin(void)
{
struct vdso_time_data *vdata = vdso_k_time_data;
unsigned long flags = timekeeper_lock_irqsave();
@@ -198,14 +205,14 @@ unsigned long vdso_update_begin(void)
}
/**
- * vdso_update_end - End of a VDSO update section
- * @flags: Interrupt flags as returned from vdso_update_begin()
+ * vdso_time_update_end - End of a VDSO update section
+ * @flags: Interrupt flags as returned from vdso_time_update_begin()
*
- * Pairs with vdso_update_begin(). Marks vdso data consistent, invokes data
+ * Pairs with vdso_time_update_begin(). Marks vdso data consistent, invokes data
* synchronization if the architecture requires it, drops timekeeper lock
* and restores interrupt flags.
*/
-void vdso_update_end(unsigned long flags)
+void vdso_time_update_end(unsigned long flags)
{
struct vdso_time_data *vdata = vdso_k_time_data;
diff --git a/lib/vdso/getrandom.c b/lib/vdso/getrandom.c
index 2851afa9154f..e0fa10bbefc9 100644
--- a/lib/vdso/getrandom.c
+++ b/lib/vdso/getrandom.c
@@ -16,8 +16,12 @@
#include <uapi/linux/mman.h>
#include <uapi/linux/random.h>
-/* Bring in default accessors */
-#include <vdso/vsyscall.h>
+#ifndef __arch_get_vdso_u_rng_data
+static __always_inline const struct vdso_rng_data *__arch_get_vdso_u_rng_data(void)
+{
+ return &vdso_u_rng_data;
+}
+#endif
#define MEMCPY_AND_ZERO_SRC(type, dst, src, len) do { \
while (len >= sizeof(type)) { \
diff --git a/lib/vdso/gettimeofday.c b/lib/vdso/gettimeofday.c
index f7a591aba59f..c0e81996532d 100644
--- a/lib/vdso/gettimeofday.c
+++ b/lib/vdso/gettimeofday.c
@@ -27,9 +27,6 @@
#include <linux/build_bug.h>
-/* Bring in default accessors */
-#include <vdso/vsyscall.h>
-
#ifndef vdso_calc_ns
#ifdef VDSO_DELTA_NOMASK
@@ -93,6 +90,13 @@ static inline bool vdso_cycles_ok(u64 cycles)
}
#endif
+#ifndef __arch_get_vdso_u_time_data
+static __always_inline const struct vdso_time_data *__arch_get_vdso_u_time_data(void)
+{
+ return &vdso_u_time_data;
+}
+#endif
+
static __always_inline bool vdso_clockid_valid(clockid_t clock)
{
/* Check for negative values or invalid clocks */
@@ -135,22 +139,23 @@ const struct vdso_time_data *vdso_timens_data(const struct vdso_time_data *vd)
return (void *)vd + PAGE_SIZE;
}
+static __always_inline
+const struct vdso_clock *vdso_timens_clock(const struct vdso_clock *vc)
+{
+ return (void *)vc + PAGE_SIZE;
+}
+
static __always_inline
bool do_hres_timens(const struct vdso_time_data *vdns, const struct vdso_clock *vcns,
clockid_t clk, struct __kernel_timespec *ts)
{
const struct vdso_time_data *vd = vdso_timens_data(vdns);
+ const struct vdso_clock *vc = vdso_timens_clock(vcns);
const struct timens_offset *offs = &vcns->offset[clk];
- const struct vdso_clock *vc = vd->clock_data;
u32 seq;
s64 sec;
u64 ns;
- if (clk != CLOCK_MONOTONIC_RAW)
- vc = &vc[CS_HRES_COARSE];
- else
- vc = &vc[CS_RAW];
-
do {
seq = vdso_read_begin(vc);
^ permalink raw reply [flat|nested] 12+ messages in thread
* Re: [GIT pull] irq/core for v7.4-rc1
2026-10-21 9:50 [GIT pull] irq/core for v7.4-rc1 Thomas Gleixner
` (4 preceding siblings ...)
2026-10-21 9:51 ` [GIT pull] timers/vdso " Thomas Gleixner
@ 2026-10-21 21:43 ` pr-tracker-bot
5 siblings, 0 replies; 12+ messages in thread
From: pr-tracker-bot @ 2026-10-21 21:43 UTC (permalink / raw)
To: Thomas Gleixner; +Cc: Linus Torvalds, linux-kernel, x86
The pull request you sent on Wed, 21 Oct 2026 11:50:56 +0200:
> git://git.kernel.org/pub/scm/linux/kernel/git/tip/tip.git irq-core-2026-10-20
has been merged into torvalds/linux.git:
https://git.kernel.org/torvalds/c/1fbe92c39e72f6baf1ebda5a21c54442865d9622
Thank you!
--
Deet-doot-dot, I am a bot.
https://korg.docs.kernel.org/prtracker.html
^ permalink raw reply [flat|nested] 12+ messages in thread
* Re: [GIT pull] irq/drivers for v7.4-rc1
2026-10-21 9:51 ` [GIT pull] irq/drivers " Thomas Gleixner
@ 2026-10-21 21:43 ` pr-tracker-bot
0 siblings, 0 replies; 12+ messages in thread
From: pr-tracker-bot @ 2026-10-21 21:43 UTC (permalink / raw)
To: Thomas Gleixner; +Cc: Linus Torvalds, linux-kernel, x86
The pull request you sent on Wed, 21 Oct 2026 11:51:01 +0200:
> git://git.kernel.org/pub/scm/linux/kernel/git/tip/tip.git irq-drivers-2026-10-20
has been merged into torvalds/linux.git:
https://git.kernel.org/torvalds/c/2e973d4882230079bfbdc9f33077006bb50de700
Thank you!
--
Deet-doot-dot, I am a bot.
https://korg.docs.kernel.org/prtracker.html
^ permalink raw reply [flat|nested] 12+ messages in thread
* Re: [GIT pull] timers/clocksource for v7.4-rc1
2026-10-21 9:51 ` [GIT pull] timers/clocksource " Thomas Gleixner
@ 2026-10-21 21:43 ` pr-tracker-bot
0 siblings, 0 replies; 12+ messages in thread
From: pr-tracker-bot @ 2026-10-21 21:43 UTC (permalink / raw)
To: Thomas Gleixner; +Cc: Linus Torvalds, linux-kernel, x86
The pull request you sent on Wed, 21 Oct 2026 11:51:06 +0200:
> git://git.kernel.org/pub/scm/linux/kernel/git/tip/tip.git timers-clocksource-2026-10-20
has been merged into torvalds/linux.git:
https://git.kernel.org/torvalds/c/b4460f89a274240204038d559e8571f370d14327
Thank you!
--
Deet-doot-dot, I am a bot.
https://korg.docs.kernel.org/prtracker.html
^ permalink raw reply [flat|nested] 12+ messages in thread
* Re: [GIT pull] timers/core for v7.4-rc1
2026-10-21 9:51 ` [GIT pull] timers/core " Thomas Gleixner
@ 2026-10-21 21:43 ` pr-tracker-bot
0 siblings, 0 replies; 12+ messages in thread
From: pr-tracker-bot @ 2026-10-21 21:43 UTC (permalink / raw)
To: Thomas Gleixner; +Cc: Linus Torvalds, linux-kernel, x86
The pull request you sent on Wed, 21 Oct 2026 11:51:11 +0200:
> git://git.kernel.org/pub/scm/linux/kernel/git/tip/tip.git timers-core-2026-10-20
has been merged into torvalds/linux.git:
https://git.kernel.org/torvalds/c/aaf3d47e530616ed0cf9110ba8c537ace455dd90
Thank you!
--
Deet-doot-dot, I am a bot.
https://korg.docs.kernel.org/prtracker.html
^ permalink raw reply [flat|nested] 12+ messages in thread
* Re: [GIT pull] timers/nohz for v7.4-rc1
2026-10-21 9:51 ` [GIT pull] timers/nohz " Thomas Gleixner
@ 2026-10-21 21:43 ` pr-tracker-bot
0 siblings, 0 replies; 12+ messages in thread
From: pr-tracker-bot @ 2026-10-21 21:43 UTC (permalink / raw)
To: Thomas Gleixner; +Cc: Linus Torvalds, linux-kernel, x86
The pull request you sent on Wed, 21 Oct 2026 11:51:15 +0200:
> git://git.kernel.org/pub/scm/linux/kernel/git/tip/tip.git timers-nohz-2026-10-20
has been merged into torvalds/linux.git:
https://git.kernel.org/torvalds/c/1ae0ee1ba729a0c55c2eb8593fa708c23b66e1b3
Thank you!
--
Deet-doot-dot, I am a bot.
https://korg.docs.kernel.org/prtracker.html
^ permalink raw reply [flat|nested] 12+ messages in thread
* Re: [GIT pull] timers/vdso for v7.4-rc1
2026-10-21 9:51 ` [GIT pull] timers/vdso " Thomas Gleixner
@ 2026-10-21 21:43 ` pr-tracker-bot
0 siblings, 0 replies; 12+ messages in thread
From: pr-tracker-bot @ 2026-10-21 21:43 UTC (permalink / raw)
To: Thomas Gleixner; +Cc: Linus Torvalds, linux-kernel, x86
The pull request you sent on Wed, 21 Oct 2026 11:51:20 +0200:
> git://git.kernel.org/pub/scm/linux/kernel/git/tip/tip.git timers-vdso-2026-10-20
has been merged into torvalds/linux.git:
https://git.kernel.org/torvalds/c/ccc4d9f8912a5d9f519a34c3633fe53c8f9b429e
Thank you!
--
Deet-doot-dot, I am a bot.
https://korg.docs.kernel.org/prtracker.html
^ permalink raw reply [flat|nested] 12+ messages in thread
end of thread, other threads:[~2026-10-21 21:43 UTC | newest]
Thread overview: 12+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-10-21 9:50 [GIT pull] irq/core for v7.4-rc1 Thomas Gleixner
2026-10-21 9:51 ` [GIT pull] irq/drivers " Thomas Gleixner
2026-10-21 21:43 ` pr-tracker-bot
2026-10-21 9:51 ` [GIT pull] timers/clocksource " Thomas Gleixner
2026-10-21 21:43 ` pr-tracker-bot
2026-10-21 9:51 ` [GIT pull] timers/core " Thomas Gleixner
2026-10-21 21:43 ` pr-tracker-bot
2026-10-21 9:51 ` [GIT pull] timers/nohz " Thomas Gleixner
2026-10-21 21:43 ` pr-tracker-bot
2026-10-21 9:51 ` [GIT pull] timers/vdso " Thomas Gleixner
2026-10-21 21:43 ` pr-tracker-bot
2026-10-21 21:43 ` [GIT pull] irq/core " pr-tracker-bot
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox
all inboxes | Powered by JetHome®