Merge tag 'irq-core-2024-09-16' of git://git.kernel.org/pub/scm/linux/kernel/git/tip/tip

Pull irq updates from Thomas Gleixner:
 "Core:

   - Remove a global lock in the affinity setting code

     The lock protects a cpumask for intermediate results and the lock
     causes a bottleneck on simultaneous start of multiple virtual
     machines. Replace the lock and the static cpumask with a per CPU
     cpumask which is nicely serialized by raw spinlock held when
     executing this code.

   - Provide support for giving a suffix to interrupt domain names.

     That's required to support devices with subfunctions so that the
     domain names are distinct even if they originate from the same
     device node.

   - The usual set of cleanups and enhancements all over the place

  Drivers:

   - Support for longarch AVEC interrupt chip

   - Refurbishment of the Armada driver so it can be extended for new
     variants.

   - The usual set of cleanups and enhancements all over the place"

* tag 'irq-core-2024-09-16' of git://git.kernel.org/pub/scm/linux/kernel/git/tip/tip: (73 commits)
  genirq: Use cpumask_intersects()
  genirq/cpuhotplug: Use cpumask_intersects()
  irqchip/apple-aic: Only access system registers on SoCs which provide them
  irqchip/apple-aic: Add a new "Global fast IPIs only" feature level
  irqchip/apple-aic: Skip unnecessary enabling of use_fast_ipi
  dt-bindings: apple,aic: Document A7-A11 compatibles
  irqdomain: Use IS_ERR_OR_NULL() in irq_domain_trim_hierarchy()
  genirq/msi: Use kmemdup_array() instead of kmemdup()
  genirq/proc: Change the return value for set affinity permission error
  genirq/proc: Use irq_move_pending() in show_irq_affinity()
  genirq/proc: Correctly set file permissions for affinity control files
  genirq: Get rid of global lock in irq_do_set_affinity()
  genirq: Fix typo in struct comment
  irqchip/loongarch-avec: Add AVEC irqchip support
  irqchip/loongson-pch-msi: Prepare get_pch_msi_handle() for AVECINTC
  irqchip/loongson-eiointc: Rename CPUHP_AP_IRQ_LOONGARCH_STARTING
  LoongArch: Architectural preparation for AVEC irqchip
  LoongArch: Move irqchip function prototypes to irq-loongson.h
  irqchip/loongson-pch-msi: Switch to MSI parent domains
  softirq: Remove unused 'action' parameter from action callback
  ...
This commit is contained in:
Linus Torvalds
2024-09-17 07:09:17 +02:00
58 changed files with 1268 additions and 683 deletions
+1 -1
View File
@@ -198,7 +198,7 @@ __irq_startup_managed(struct irq_desc *desc, const struct cpumask *aff,
irqd_clr_managed_shutdown(d);
if (cpumask_any_and(aff, cpu_online_mask) >= nr_cpu_ids) {
if (!cpumask_intersects(aff, cpu_online_mask)) {
/*
* Catch code which fiddles with enable_irq() on a managed
* and potentially shutdown IRQ. Chained interrupt
+2 -2
View File
@@ -37,7 +37,7 @@ static inline bool irq_needs_fixup(struct irq_data *d)
* has been removed from the online mask already.
*/
if (cpumask_any_but(m, cpu) < nr_cpu_ids &&
cpumask_any_and(m, cpu_online_mask) >= nr_cpu_ids) {
!cpumask_intersects(m, cpu_online_mask)) {
/*
* If this happens then there was a missed IRQ fixup at some
* point. Warn about it and enforce fixup.
@@ -110,7 +110,7 @@ static bool migrate_one_irq(struct irq_desc *desc)
if (maskchip && chip->irq_mask)
chip->irq_mask(d);
if (cpumask_any_and(affinity, cpu_online_mask) >= nr_cpu_ids) {
if (!cpumask_intersects(affinity, cpu_online_mask)) {
/*
* If the interrupt is managed, then shut it down and leave
* the affinity untouched.
-1
View File
@@ -13,7 +13,6 @@
struct irq_sim_work_ctx {
struct irq_work work;
int irq_base;
unsigned int irq_count;
unsigned long *pending;
struct irq_domain *domain;
+124 -86
View File
@@ -128,72 +128,98 @@ void irq_domain_free_fwnode(struct fwnode_handle *fwnode)
}
EXPORT_SYMBOL_GPL(irq_domain_free_fwnode);
static int irq_domain_set_name(struct irq_domain *domain,
const struct fwnode_handle *fwnode,
enum irq_domain_bus_token bus_token)
static int alloc_name(struct irq_domain *domain, char *base, enum irq_domain_bus_token bus_token)
{
if (bus_token == DOMAIN_BUS_ANY)
domain->name = kasprintf(GFP_KERNEL, "%s", base);
else
domain->name = kasprintf(GFP_KERNEL, "%s-%d", base, bus_token);
if (!domain->name)
return -ENOMEM;
domain->flags |= IRQ_DOMAIN_NAME_ALLOCATED;
return 0;
}
static int alloc_fwnode_name(struct irq_domain *domain, const struct fwnode_handle *fwnode,
enum irq_domain_bus_token bus_token, const char *suffix)
{
const char *sep = suffix ? "-" : "";
const char *suf = suffix ? : "";
char *name;
if (bus_token == DOMAIN_BUS_ANY)
name = kasprintf(GFP_KERNEL, "%pfw%s%s", fwnode, sep, suf);
else
name = kasprintf(GFP_KERNEL, "%pfw%s%s-%d", fwnode, sep, suf, bus_token);
if (!name)
return -ENOMEM;
/*
* fwnode paths contain '/', which debugfs is legitimately unhappy
* about. Replace them with ':', which does the trick and is not as
* offensive as '\'...
*/
domain->name = strreplace(name, '/', ':');
domain->flags |= IRQ_DOMAIN_NAME_ALLOCATED;
return 0;
}
static int alloc_unknown_name(struct irq_domain *domain, enum irq_domain_bus_token bus_token)
{
static atomic_t unknown_domains;
struct irqchip_fwid *fwid;
int id = atomic_inc_return(&unknown_domains);
if (bus_token == DOMAIN_BUS_ANY)
domain->name = kasprintf(GFP_KERNEL, "unknown-%d", id);
else
domain->name = kasprintf(GFP_KERNEL, "unknown-%d-%d", id, bus_token);
if (!domain->name)
return -ENOMEM;
domain->flags |= IRQ_DOMAIN_NAME_ALLOCATED;
return 0;
}
static int irq_domain_set_name(struct irq_domain *domain, const struct irq_domain_info *info)
{
enum irq_domain_bus_token bus_token = info->bus_token;
const struct fwnode_handle *fwnode = info->fwnode;
if (is_fwnode_irqchip(fwnode)) {
fwid = container_of(fwnode, struct irqchip_fwid, fwnode);
struct irqchip_fwid *fwid = container_of(fwnode, struct irqchip_fwid, fwnode);
/*
* The name_suffix is only intended to be used to avoid a name
* collision when multiple domains are created for a single
* device and the name is picked using a real device node.
* (Typical use-case is regmap-IRQ controllers for devices
* providing more than one physical IRQ.) There should be no
* need to use name_suffix with irqchip-fwnode.
*/
if (info->name_suffix)
return -EINVAL;
switch (fwid->type) {
case IRQCHIP_FWNODE_NAMED:
case IRQCHIP_FWNODE_NAMED_ID:
domain->name = bus_token ?
kasprintf(GFP_KERNEL, "%s-%d",
fwid->name, bus_token) :
kstrdup(fwid->name, GFP_KERNEL);
if (!domain->name)
return -ENOMEM;
domain->flags |= IRQ_DOMAIN_NAME_ALLOCATED;
break;
return alloc_name(domain, fwid->name, bus_token);
default:
domain->name = fwid->name;
if (bus_token) {
domain->name = kasprintf(GFP_KERNEL, "%s-%d",
fwid->name, bus_token);
if (!domain->name)
return -ENOMEM;
domain->flags |= IRQ_DOMAIN_NAME_ALLOCATED;
}
break;
if (bus_token != DOMAIN_BUS_ANY)
return alloc_name(domain, fwid->name, bus_token);
}
} else if (is_of_node(fwnode) || is_acpi_device_node(fwnode) ||
is_software_node(fwnode)) {
char *name;
/*
* fwnode paths contain '/', which debugfs is legitimately
* unhappy about. Replace them with ':', which does
* the trick and is not as offensive as '\'...
*/
name = bus_token ?
kasprintf(GFP_KERNEL, "%pfw-%d", fwnode, bus_token) :
kasprintf(GFP_KERNEL, "%pfw", fwnode);
if (!name)
return -ENOMEM;
domain->name = strreplace(name, '/', ':');
domain->flags |= IRQ_DOMAIN_NAME_ALLOCATED;
} else if (is_of_node(fwnode) || is_acpi_device_node(fwnode) || is_software_node(fwnode)) {
return alloc_fwnode_name(domain, fwnode, bus_token, info->name_suffix);
}
if (!domain->name) {
if (fwnode)
pr_err("Invalid fwnode type for irqdomain\n");
domain->name = bus_token ?
kasprintf(GFP_KERNEL, "unknown-%d-%d",
atomic_inc_return(&unknown_domains),
bus_token) :
kasprintf(GFP_KERNEL, "unknown-%d",
atomic_inc_return(&unknown_domains));
if (!domain->name)
return -ENOMEM;
domain->flags |= IRQ_DOMAIN_NAME_ALLOCATED;
}
if (domain->name)
return 0;
return 0;
if (fwnode)
pr_err("Invalid fwnode type for irqdomain\n");
return alloc_unknown_name(domain, bus_token);
}
static struct irq_domain *__irq_domain_create(const struct irq_domain_info *info)
@@ -211,7 +237,7 @@ static struct irq_domain *__irq_domain_create(const struct irq_domain_info *info
if (!domain)
return ERR_PTR(-ENOMEM);
err = irq_domain_set_name(domain, info->fwnode, info->bus_token);
err = irq_domain_set_name(domain, info);
if (err) {
kfree(domain);
return ERR_PTR(err);
@@ -267,13 +293,20 @@ static void irq_domain_free(struct irq_domain *domain)
kfree(domain);
}
/**
* irq_domain_instantiate() - Instantiate a new irq domain data structure
* @info: Domain information pointer pointing to the information for this domain
*
* Return: A pointer to the instantiated irq domain or an ERR_PTR value.
*/
struct irq_domain *irq_domain_instantiate(const struct irq_domain_info *info)
static void irq_domain_instantiate_descs(const struct irq_domain_info *info)
{
if (!IS_ENABLED(CONFIG_SPARSE_IRQ))
return;
if (irq_alloc_descs(info->virq_base, info->virq_base, info->size,
of_node_to_nid(to_of_node(info->fwnode))) < 0) {
pr_info("Cannot allocate irq_descs @ IRQ%d, assuming pre-allocated\n",
info->virq_base);
}
}
static struct irq_domain *__irq_domain_instantiate(const struct irq_domain_info *info,
bool cond_alloc_descs, bool force_associate)
{
struct irq_domain *domain;
int err;
@@ -306,6 +339,19 @@ struct irq_domain *irq_domain_instantiate(const struct irq_domain_info *info)
__irq_domain_publish(domain);
if (cond_alloc_descs && info->virq_base > 0)
irq_domain_instantiate_descs(info);
/*
* Legacy interrupt domains have a fixed Linux interrupt number
* associated. Other interrupt domains can request association by
* providing a Linux interrupt number > 0.
*/
if (force_associate || info->virq_base > 0) {
irq_domain_associate_many(domain, info->virq_base, info->hwirq_base,
info->size - info->hwirq_base);
}
return domain;
err_domain_gc_remove:
@@ -315,6 +361,17 @@ err_domain_free:
irq_domain_free(domain);
return ERR_PTR(err);
}
/**
* irq_domain_instantiate() - Instantiate a new irq domain data structure
* @info: Domain information pointer pointing to the information for this domain
*
* Return: A pointer to the instantiated irq domain or an ERR_PTR value.
*/
struct irq_domain *irq_domain_instantiate(const struct irq_domain_info *info)
{
return __irq_domain_instantiate(info, false, false);
}
EXPORT_SYMBOL_GPL(irq_domain_instantiate);
/**
@@ -413,28 +470,13 @@ struct irq_domain *irq_domain_create_simple(struct fwnode_handle *fwnode,
.fwnode = fwnode,
.size = size,
.hwirq_max = size,
.virq_base = first_irq,
.ops = ops,
.host_data = host_data,
};
struct irq_domain *domain;
struct irq_domain *domain = __irq_domain_instantiate(&info, true, false);
domain = irq_domain_instantiate(&info);
if (IS_ERR(domain))
return NULL;
if (first_irq > 0) {
if (IS_ENABLED(CONFIG_SPARSE_IRQ)) {
/* attempt to allocated irq_descs */
int rc = irq_alloc_descs(first_irq, first_irq, size,
of_node_to_nid(to_of_node(fwnode)));
if (rc < 0)
pr_info("Cannot allocate irq_descs @ IRQ%d, assuming pre-allocated\n",
first_irq);
}
irq_domain_associate_many(domain, first_irq, 0, size);
}
return domain;
return IS_ERR(domain) ? NULL : domain;
}
EXPORT_SYMBOL_GPL(irq_domain_create_simple);
@@ -476,18 +518,14 @@ struct irq_domain *irq_domain_create_legacy(struct fwnode_handle *fwnode,
.fwnode = fwnode,
.size = first_hwirq + size,
.hwirq_max = first_hwirq + size,
.hwirq_base = first_hwirq,
.virq_base = first_irq,
.ops = ops,
.host_data = host_data,
};
struct irq_domain *domain;
struct irq_domain *domain = __irq_domain_instantiate(&info, false, true);
domain = irq_domain_instantiate(&info);
if (IS_ERR(domain))
return NULL;
irq_domain_associate_many(domain, first_irq, first_hwirq, size);
return domain;
return IS_ERR(domain) ? NULL : domain;
}
EXPORT_SYMBOL_GPL(irq_domain_create_legacy);
@@ -1365,7 +1403,7 @@ static int irq_domain_trim_hierarchy(unsigned int virq)
tail = NULL;
/* The first entry must have a valid irqchip */
if (!irq_data->chip || IS_ERR(irq_data->chip))
if (IS_ERR_OR_NULL(irq_data->chip))
return -EINVAL;
/*
+9 -12
View File
@@ -218,21 +218,20 @@ static void irq_validate_effective_affinity(struct irq_data *data)
static inline void irq_validate_effective_affinity(struct irq_data *data) { }
#endif
static DEFINE_PER_CPU(struct cpumask, __tmp_mask);
int irq_do_set_affinity(struct irq_data *data, const struct cpumask *mask,
bool force)
{
struct cpumask *tmp_mask = this_cpu_ptr(&__tmp_mask);
struct irq_desc *desc = irq_data_to_desc(data);
struct irq_chip *chip = irq_data_get_irq_chip(data);
const struct cpumask *prog_mask;
int ret;
static DEFINE_RAW_SPINLOCK(tmp_mask_lock);
static struct cpumask tmp_mask;
if (!chip || !chip->irq_set_affinity)
return -EINVAL;
raw_spin_lock(&tmp_mask_lock);
/*
* If this is a managed interrupt and housekeeping is enabled on
* it check whether the requested affinity mask intersects with
@@ -258,11 +257,11 @@ int irq_do_set_affinity(struct irq_data *data, const struct cpumask *mask,
hk_mask = housekeeping_cpumask(HK_TYPE_MANAGED_IRQ);
cpumask_and(&tmp_mask, mask, hk_mask);
if (!cpumask_intersects(&tmp_mask, cpu_online_mask))
cpumask_and(tmp_mask, mask, hk_mask);
if (!cpumask_intersects(tmp_mask, cpu_online_mask))
prog_mask = mask;
else
prog_mask = &tmp_mask;
prog_mask = tmp_mask;
} else {
prog_mask = mask;
}
@@ -272,16 +271,14 @@ int irq_do_set_affinity(struct irq_data *data, const struct cpumask *mask,
* unless we are being asked to force the affinity (in which
* case we do as we are told).
*/
cpumask_and(&tmp_mask, prog_mask, cpu_online_mask);
if (!force && !cpumask_empty(&tmp_mask))
ret = chip->irq_set_affinity(data, &tmp_mask, force);
cpumask_and(tmp_mask, prog_mask, cpu_online_mask);
if (!force && !cpumask_empty(tmp_mask))
ret = chip->irq_set_affinity(data, tmp_mask, force);
else if (force)
ret = chip->irq_set_affinity(data, mask, force);
else
ret = -EINVAL;
raw_spin_unlock(&tmp_mask_lock);
switch (ret) {
case IRQ_SET_MASK_OK:
case IRQ_SET_MASK_OK_DONE:
+2 -2
View File
@@ -26,7 +26,7 @@ bool irq_fixup_move_pending(struct irq_desc *desc, bool force_clear)
* The outgoing CPU might be the last online target in a pending
* interrupt move. If that's the case clear the pending move bit.
*/
if (cpumask_any_and(desc->pending_mask, cpu_online_mask) >= nr_cpu_ids) {
if (!cpumask_intersects(desc->pending_mask, cpu_online_mask)) {
irqd_clr_move_pending(data);
return false;
}
@@ -74,7 +74,7 @@ void irq_move_masked_irq(struct irq_data *idata)
* For correct operation this depends on the caller
* masking the irqs.
*/
if (cpumask_any_and(desc->pending_mask, cpu_online_mask) < nr_cpu_ids) {
if (cpumask_intersects(desc->pending_mask, cpu_online_mask)) {
int ret;
ret = irq_do_set_affinity(data, desc->pending_mask, false);
+1 -1
View File
@@ -82,7 +82,7 @@ static struct msi_desc *msi_alloc_desc(struct device *dev, int nvec,
desc->dev = dev;
desc->nvec_used = nvec;
if (affinity) {
desc->affinity = kmemdup(affinity, nvec * sizeof(*desc->affinity), GFP_KERNEL);
desc->affinity = kmemdup_array(affinity, nvec, sizeof(*desc->affinity), GFP_KERNEL);
if (!desc->affinity) {
kfree(desc);
return NULL;
+10 -7
View File
@@ -52,10 +52,8 @@ static int show_irq_affinity(int type, struct seq_file *m)
case AFFINITY:
case AFFINITY_LIST:
mask = desc->irq_common_data.affinity;
#ifdef CONFIG_GENERIC_PENDING_IRQ
if (irqd_is_setaffinity_pending(&desc->irq_data))
mask = desc->pending_mask;
#endif
if (irq_move_pending(&desc->irq_data))
mask = irq_desc_get_pending_mask(desc);
break;
case EFFECTIVE:
case EFFECTIVE_LIST:
@@ -142,7 +140,7 @@ static ssize_t write_irq_affinity(int type, struct file *file,
int err;
if (!irq_can_set_affinity_usr(irq) || no_irq_affinity)
return -EIO;
return -EPERM;
if (!zalloc_cpumask_var(&new_value, GFP_KERNEL))
return -ENOMEM;
@@ -362,8 +360,13 @@ void register_irq_proc(unsigned int irq, struct irq_desc *desc)
goto out_unlock;
#ifdef CONFIG_SMP
umode_t umode = S_IRUGO;
if (irq_can_set_affinity_usr(desc->irq_data.irq))
umode |= S_IWUSR;
/* create /proc/irq/<irq>/smp_affinity */
proc_create_data("smp_affinity", 0644, desc->dir,
proc_create_data("smp_affinity", umode, desc->dir,
&irq_affinity_proc_ops, irqp);
/* create /proc/irq/<irq>/affinity_hint */
@@ -371,7 +374,7 @@ void register_irq_proc(unsigned int irq, struct irq_desc *desc)
irq_affinity_hint_proc_show, irqp);
/* create /proc/irq/<irq>/smp_affinity_list */
proc_create_data("smp_affinity_list", 0644, desc->dir,
proc_create_data("smp_affinity_list", umode, desc->dir,
&irq_affinity_list_proc_ops, irqp);
proc_create_single_data("node", 0444, desc->dir, irq_node_proc_show,
+1 -1
View File
@@ -105,7 +105,7 @@ static inline bool rcu_reclaim_tiny(struct rcu_head *head)
}
/* Invoke the RCU callbacks whose grace period has elapsed. */
static __latent_entropy void rcu_process_callbacks(struct softirq_action *unused)
static __latent_entropy void rcu_process_callbacks(void)
{
struct rcu_head *next, *list;
unsigned long flags;
+1 -1
View File
@@ -2855,7 +2855,7 @@ static __latent_entropy void rcu_core(void)
queue_work_on(rdp->cpu, rcu_gp_wq, &rdp->strict_work);
}
static void rcu_core_si(struct softirq_action *h)
static void rcu_core_si(void)
{
rcu_core();
}
+1 -1
View File
@@ -12483,7 +12483,7 @@ out:
* - indirectly from a remote scheduler_tick() for NOHZ idle balancing
* through the SMP cross-call nohz_csd_func()
*/
static __latent_entropy void sched_balance_softirq(struct softirq_action *h)
static __latent_entropy void sched_balance_softirq(void)
{
struct rq *this_rq = this_rq();
enum cpu_idle_type idle = this_rq->idle_balance;
+7 -8
View File
@@ -551,7 +551,7 @@ restart:
kstat_incr_softirqs_this_cpu(vec_nr);
trace_softirq_entry(vec_nr);
h->action(h);
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",
@@ -700,7 +700,7 @@ void __raise_softirq_irqoff(unsigned int nr)
or_softirq_pending(1UL << nr);
}
void open_softirq(int nr, void (*action)(struct softirq_action *))
void open_softirq(int nr, void (*action)(void))
{
softirq_vec[nr].action = action;
}
@@ -760,8 +760,7 @@ static bool tasklet_clear_sched(struct tasklet_struct *t)
return false;
}
static void tasklet_action_common(struct softirq_action *a,
struct tasklet_head *tl_head,
static void tasklet_action_common(struct tasklet_head *tl_head,
unsigned int softirq_nr)
{
struct tasklet_struct *list;
@@ -805,16 +804,16 @@ static void tasklet_action_common(struct softirq_action *a,
}
}
static __latent_entropy void tasklet_action(struct softirq_action *a)
static __latent_entropy void tasklet_action(void)
{
workqueue_softirq_action(false);
tasklet_action_common(a, this_cpu_ptr(&tasklet_vec), TASKLET_SOFTIRQ);
tasklet_action_common(this_cpu_ptr(&tasklet_vec), TASKLET_SOFTIRQ);
}
static __latent_entropy void tasklet_hi_action(struct softirq_action *a)
static __latent_entropy void tasklet_hi_action(void)
{
workqueue_softirq_action(true);
tasklet_action_common(a, this_cpu_ptr(&tasklet_hi_vec), HI_SOFTIRQ);
tasklet_action_common(this_cpu_ptr(&tasklet_hi_vec), HI_SOFTIRQ);
}
void tasklet_setup(struct tasklet_struct *t,
+1 -1
View File
@@ -1757,7 +1757,7 @@ static void __hrtimer_run_queues(struct hrtimer_cpu_base *cpu_base, ktime_t now,
}
}
static __latent_entropy void hrtimer_run_softirq(struct softirq_action *h)
static __latent_entropy void hrtimer_run_softirq(void)
{
struct hrtimer_cpu_base *cpu_base = this_cpu_ptr(&hrtimer_bases);
unsigned long flags;
+1 -1
View File
@@ -2440,7 +2440,7 @@ static void run_timer_base(int index)
/*
* This function runs timers and the timer-tq in bottom half context.
*/
static __latent_entropy void run_timer_softirq(struct softirq_action *h)
static __latent_entropy void run_timer_softirq(void)
{
run_timer_base(BASE_LOCAL);
if (IS_ENABLED(CONFIG_NO_HZ_COMMON)) {