Merge tag 'irqchip-5.8' of git://git.kernel.org/pub/scm/linux/kernel/git/maz/arm-platforms into irq/core

Pull irqchip updates from Marc Zyngier:

 - A few new drivers for the Loongson MIPS platform (HTVEC, PIC, MSI)
 - A cleanup of the __irq_domain_add() API
 - A cleanup of the IRQ simulator to actually use some of
   the irq infrastructure
 - Some fixes for the Sifive PLIC when used in a multi-controller
   context
 - Fixes for the GICv3 ITS to spread interrupts according to the
   load of each CPU, and to honor managed interrupts
 - Numerous cleanups and documentation fixes
This commit is contained in:
Thomas Gleixner
2020-05-30 09:40:12 +02:00
20 changed files with 1343 additions and 227 deletions
+1
View File
@@ -72,6 +72,7 @@ config IRQ_DOMAIN
config IRQ_SIM
bool
select IRQ_WORK
select IRQ_DOMAIN
# Support for hierarchical irq domains
config IRQ_DOMAIN_HIERARCHY
+172 -109
View File
@@ -1,14 +1,31 @@
// SPDX-License-Identifier: GPL-2.0+
/*
* Copyright (C) 2017-2018 Bartosz Golaszewski <brgl@bgdev.pl>
* Copyright (C) 2020 Bartosz Golaszewski <bgolaszewski@baylibre.com>
*/
#include <linux/slab.h>
#include <linux/irq_sim.h>
#include <linux/irq.h>
#include <linux/irq_sim.h>
#include <linux/irq_work.h>
#include <linux/interrupt.h>
#include <linux/slab.h>
struct irq_sim_work_ctx {
struct irq_work work;
int irq_base;
unsigned int irq_count;
unsigned long *pending;
struct irq_domain *domain;
};
struct irq_sim_irq_ctx {
int irqnum;
bool enabled;
struct irq_sim_work_ctx *work_ctx;
};
struct irq_sim_devres {
struct irq_sim *sim;
struct irq_domain *domain;
};
static void irq_sim_irqmask(struct irq_data *data)
@@ -36,159 +53,205 @@ static int irq_sim_set_type(struct irq_data *data, unsigned int type)
return 0;
}
static int irq_sim_get_irqchip_state(struct irq_data *data,
enum irqchip_irq_state which, bool *state)
{
struct irq_sim_irq_ctx *irq_ctx = irq_data_get_irq_chip_data(data);
irq_hw_number_t hwirq = irqd_to_hwirq(data);
switch (which) {
case IRQCHIP_STATE_PENDING:
if (irq_ctx->enabled)
*state = test_bit(hwirq, irq_ctx->work_ctx->pending);
break;
default:
return -EINVAL;
}
return 0;
}
static int irq_sim_set_irqchip_state(struct irq_data *data,
enum irqchip_irq_state which, bool state)
{
struct irq_sim_irq_ctx *irq_ctx = irq_data_get_irq_chip_data(data);
irq_hw_number_t hwirq = irqd_to_hwirq(data);
switch (which) {
case IRQCHIP_STATE_PENDING:
if (irq_ctx->enabled) {
assign_bit(hwirq, irq_ctx->work_ctx->pending, state);
if (state)
irq_work_queue(&irq_ctx->work_ctx->work);
}
break;
default:
return -EINVAL;
}
return 0;
}
static struct irq_chip irq_sim_irqchip = {
.name = "irq_sim",
.irq_mask = irq_sim_irqmask,
.irq_unmask = irq_sim_irqunmask,
.irq_set_type = irq_sim_set_type,
.name = "irq_sim",
.irq_mask = irq_sim_irqmask,
.irq_unmask = irq_sim_irqunmask,
.irq_set_type = irq_sim_set_type,
.irq_get_irqchip_state = irq_sim_get_irqchip_state,
.irq_set_irqchip_state = irq_sim_set_irqchip_state,
};
static void irq_sim_handle_irq(struct irq_work *work)
{
struct irq_sim_work_ctx *work_ctx;
unsigned int offset = 0;
struct irq_sim *sim;
int irqnum;
work_ctx = container_of(work, struct irq_sim_work_ctx, work);
sim = container_of(work_ctx, struct irq_sim, work_ctx);
while (!bitmap_empty(work_ctx->pending, sim->irq_count)) {
while (!bitmap_empty(work_ctx->pending, work_ctx->irq_count)) {
offset = find_next_bit(work_ctx->pending,
sim->irq_count, offset);
work_ctx->irq_count, offset);
clear_bit(offset, work_ctx->pending);
irqnum = irq_sim_irqnum(sim, offset);
irqnum = irq_find_mapping(work_ctx->domain, offset);
handle_simple_irq(irq_to_desc(irqnum));
}
}
/**
* irq_sim_init - Initialize the interrupt simulator: allocate a range of
* dummy interrupts.
*
* @sim: The interrupt simulator object to initialize.
* @num_irqs: Number of interrupts to allocate
*
* On success: return the base of the allocated interrupt range.
* On failure: a negative errno.
*/
int irq_sim_init(struct irq_sim *sim, unsigned int num_irqs)
static int irq_sim_domain_map(struct irq_domain *domain,
unsigned int virq, irq_hw_number_t hw)
{
int i;
struct irq_sim_work_ctx *work_ctx = domain->host_data;
struct irq_sim_irq_ctx *irq_ctx;
sim->irqs = kmalloc_array(num_irqs, sizeof(*sim->irqs), GFP_KERNEL);
if (!sim->irqs)
irq_ctx = kzalloc(sizeof(*irq_ctx), GFP_KERNEL);
if (!irq_ctx)
return -ENOMEM;
sim->irq_base = irq_alloc_descs(-1, 0, num_irqs, 0);
if (sim->irq_base < 0) {
kfree(sim->irqs);
return sim->irq_base;
}
irq_set_chip(virq, &irq_sim_irqchip);
irq_set_chip_data(virq, irq_ctx);
irq_set_handler(virq, handle_simple_irq);
irq_modify_status(virq, IRQ_NOREQUEST | IRQ_NOAUTOEN, IRQ_NOPROBE);
irq_ctx->work_ctx = work_ctx;
sim->work_ctx.pending = bitmap_zalloc(num_irqs, GFP_KERNEL);
if (!sim->work_ctx.pending) {
kfree(sim->irqs);
irq_free_descs(sim->irq_base, num_irqs);
return -ENOMEM;
}
for (i = 0; i < num_irqs; i++) {
sim->irqs[i].irqnum = sim->irq_base + i;
sim->irqs[i].enabled = false;
irq_set_chip(sim->irq_base + i, &irq_sim_irqchip);
irq_set_chip_data(sim->irq_base + i, &sim->irqs[i]);
irq_set_handler(sim->irq_base + i, &handle_simple_irq);
irq_modify_status(sim->irq_base + i,
IRQ_NOREQUEST | IRQ_NOAUTOEN, IRQ_NOPROBE);
}
init_irq_work(&sim->work_ctx.work, irq_sim_handle_irq);
sim->irq_count = num_irqs;
return sim->irq_base;
return 0;
}
EXPORT_SYMBOL_GPL(irq_sim_init);
static void irq_sim_domain_unmap(struct irq_domain *domain, unsigned int virq)
{
struct irq_sim_irq_ctx *irq_ctx;
struct irq_data *irqd;
irqd = irq_domain_get_irq_data(domain, virq);
irq_ctx = irq_data_get_irq_chip_data(irqd);
irq_set_handler(virq, NULL);
irq_domain_reset_irq_data(irqd);
kfree(irq_ctx);
}
static const struct irq_domain_ops irq_sim_domain_ops = {
.map = irq_sim_domain_map,
.unmap = irq_sim_domain_unmap,
};
/**
* irq_sim_fini - Deinitialize the interrupt simulator: free the interrupt
* descriptors and allocated memory.
* irq_domain_create_sim - Create a new interrupt simulator irq_domain and
* allocate a range of dummy interrupts.
*
* @sim: The interrupt simulator to tear down.
* @fnode: struct fwnode_handle to be associated with this domain.
* @num_irqs: Number of interrupts to allocate.
*
* On success: return a new irq_domain object.
* On failure: a negative errno wrapped with ERR_PTR().
*/
void irq_sim_fini(struct irq_sim *sim)
struct irq_domain *irq_domain_create_sim(struct fwnode_handle *fwnode,
unsigned int num_irqs)
{
irq_work_sync(&sim->work_ctx.work);
bitmap_free(sim->work_ctx.pending);
irq_free_descs(sim->irq_base, sim->irq_count);
kfree(sim->irqs);
}
EXPORT_SYMBOL_GPL(irq_sim_fini);
struct irq_sim_work_ctx *work_ctx;
static void devm_irq_sim_release(struct device *dev, void *res)
work_ctx = kmalloc(sizeof(*work_ctx), GFP_KERNEL);
if (!work_ctx)
goto err_out;
work_ctx->pending = bitmap_zalloc(num_irqs, GFP_KERNEL);
if (!work_ctx->pending)
goto err_free_work_ctx;
work_ctx->domain = irq_domain_create_linear(fwnode, num_irqs,
&irq_sim_domain_ops,
work_ctx);
if (!work_ctx->domain)
goto err_free_bitmap;
work_ctx->irq_count = num_irqs;
init_irq_work(&work_ctx->work, irq_sim_handle_irq);
return work_ctx->domain;
err_free_bitmap:
bitmap_free(work_ctx->pending);
err_free_work_ctx:
kfree(work_ctx);
err_out:
return ERR_PTR(-ENOMEM);
}
EXPORT_SYMBOL_GPL(irq_domain_create_sim);
/**
* irq_domain_remove_sim - Deinitialize the interrupt simulator domain: free
* the interrupt descriptors and allocated memory.
*
* @domain: The interrupt simulator domain to tear down.
*/
void irq_domain_remove_sim(struct irq_domain *domain)
{
struct irq_sim_work_ctx *work_ctx = domain->host_data;
irq_work_sync(&work_ctx->work);
bitmap_free(work_ctx->pending);
kfree(work_ctx);
irq_domain_remove(domain);
}
EXPORT_SYMBOL_GPL(irq_domain_remove_sim);
static void devm_irq_domain_release_sim(struct device *dev, void *res)
{
struct irq_sim_devres *this = res;
irq_sim_fini(this->sim);
irq_domain_remove_sim(this->domain);
}
/**
* irq_sim_init - Initialize the interrupt simulator for a managed device.
* devm_irq_domain_create_sim - Create a new interrupt simulator for
* a managed device.
*
* @dev: Device to initialize the simulator object for.
* @sim: The interrupt simulator object to initialize.
* @fnode: struct fwnode_handle to be associated with this domain.
* @num_irqs: Number of interrupts to allocate
*
* On success: return the base of the allocated interrupt range.
* On failure: a negative errno.
* On success: return a new irq_domain object.
* On failure: a negative errno wrapped with ERR_PTR().
*/
int devm_irq_sim_init(struct device *dev, struct irq_sim *sim,
unsigned int num_irqs)
struct irq_domain *devm_irq_domain_create_sim(struct device *dev,
struct fwnode_handle *fwnode,
unsigned int num_irqs)
{
struct irq_sim_devres *dr;
int rv;
dr = devres_alloc(devm_irq_sim_release, sizeof(*dr), GFP_KERNEL);
dr = devres_alloc(devm_irq_domain_release_sim,
sizeof(*dr), GFP_KERNEL);
if (!dr)
return -ENOMEM;
return ERR_PTR(-ENOMEM);
rv = irq_sim_init(sim, num_irqs);
if (rv < 0) {
dr->domain = irq_domain_create_sim(fwnode, num_irqs);
if (IS_ERR(dr->domain)) {
devres_free(dr);
return rv;
return dr->domain;
}
dr->sim = sim;
devres_add(dev, dr);
return rv;
return dr->domain;
}
EXPORT_SYMBOL_GPL(devm_irq_sim_init);
/**
* irq_sim_fire - Enqueue an interrupt.
*
* @sim: The interrupt simulator object.
* @offset: Offset of the simulated interrupt which should be fired.
*/
void irq_sim_fire(struct irq_sim *sim, unsigned int offset)
{
if (sim->irqs[offset].enabled) {
set_bit(offset, sim->work_ctx.pending);
irq_work_queue(&sim->work_ctx.work);
}
}
EXPORT_SYMBOL_GPL(irq_sim_fire);
/**
* irq_sim_irqnum - Get the allocated number of a dummy interrupt.
*
* @sim: The interrupt simulator object.
* @offset: Offset of the simulated interrupt for which to retrieve
* the number.
*/
int irq_sim_irqnum(struct irq_sim *sim, unsigned int offset)
{
return sim->irqs[offset].irqnum;
}
EXPORT_SYMBOL_GPL(irq_sim_irqnum);
EXPORT_SYMBOL_GPL(devm_irq_domain_create_sim);
+19 -34
View File
@@ -132,14 +132,13 @@ struct irq_domain *__irq_domain_add(struct fwnode_handle *fwnode, int size,
const struct irq_domain_ops *ops,
void *host_data)
{
struct device_node *of_node = to_of_node(fwnode);
struct irqchip_fwid *fwid;
struct irq_domain *domain;
static atomic_t unknown_domains;
domain = kzalloc_node(sizeof(*domain) + (sizeof(unsigned int) * size),
GFP_KERNEL, of_node_to_nid(of_node));
GFP_KERNEL, of_node_to_nid(to_of_node(fwnode)));
if (!domain)
return NULL;
@@ -162,30 +161,16 @@ struct irq_domain *__irq_domain_add(struct fwnode_handle *fwnode, int size,
domain->name = fwid->name;
break;
}
#ifdef CONFIG_ACPI
} else if (is_acpi_device_node(fwnode)) {
struct acpi_buffer buf = {
.length = ACPI_ALLOCATE_BUFFER,
};
acpi_handle handle;
handle = acpi_device_handle(to_acpi_device_node(fwnode));
if (acpi_get_name(handle, ACPI_FULL_PATHNAME, &buf) == AE_OK) {
domain->name = buf.pointer;
domain->flags |= IRQ_DOMAIN_NAME_ALLOCATED;
}
domain->fwnode = fwnode;
#endif
} else if (of_node) {
} else if (is_of_node(fwnode) || is_acpi_device_node(fwnode) ||
is_software_node(fwnode)) {
char *name;
/*
* DT paths contain '/', which debugfs is legitimately
* fwnode paths contain '/', which debugfs is legitimately
* unhappy about. Replace them with ':', which does
* the trick and is not as offensive as '\'...
*/
name = kasprintf(GFP_KERNEL, "%pOF", of_node);
name = kasprintf(GFP_KERNEL, "%pfw", fwnode);
if (!name) {
kfree(domain);
return NULL;
@@ -210,7 +195,7 @@ struct irq_domain *__irq_domain_add(struct fwnode_handle *fwnode, int size,
domain->flags |= IRQ_DOMAIN_NAME_ALLOCATED;
}
of_node_get(of_node);
fwnode_handle_get(fwnode);
/* Fill structure */
INIT_RADIX_TREE(&domain->revmap_tree, GFP_KERNEL);
@@ -259,7 +244,7 @@ void irq_domain_remove(struct irq_domain *domain)
pr_debug("Removed domain %s\n", domain->name);
of_node_put(irq_domain_get_of_node(domain));
fwnode_handle_put(domain->fwnode);
if (domain->flags & IRQ_DOMAIN_NAME_ALLOCATED)
kfree(domain->name);
kfree(domain);
@@ -1047,6 +1032,18 @@ int irq_domain_alloc_descs(int virq, unsigned int cnt, irq_hw_number_t hwirq,
return virq;
}
/**
* irq_domain_reset_irq_data - Clear hwirq, chip and chip_data in @irq_data
* @irq_data: The pointer to irq_data
*/
void irq_domain_reset_irq_data(struct irq_data *irq_data)
{
irq_data->hwirq = 0;
irq_data->chip = &no_irq_chip;
irq_data->chip_data = NULL;
}
EXPORT_SYMBOL_GPL(irq_domain_reset_irq_data);
#ifdef CONFIG_IRQ_DOMAIN_HIERARCHY
/**
* irq_domain_create_hierarchy - Add a irqdomain into the hierarchy
@@ -1247,18 +1244,6 @@ void irq_domain_set_info(struct irq_domain *domain, unsigned int virq,
}
EXPORT_SYMBOL(irq_domain_set_info);
/**
* irq_domain_reset_irq_data - Clear hwirq, chip and chip_data in @irq_data
* @irq_data: The pointer to irq_data
*/
void irq_domain_reset_irq_data(struct irq_data *irq_data)
{
irq_data->hwirq = 0;
irq_data->chip = &no_irq_chip;
irq_data->chip_data = NULL;
}
EXPORT_SYMBOL_GPL(irq_domain_reset_irq_data);
/**
* irq_domain_free_irqs_common - Clear irq_data and free the parent
* @domain: Interrupt domain to match