genirq/msi: Allow level-triggered MSIs to be exposed by MSI providers

So far, MSIs have been used to signal edge-triggered interrupts, as
a write is a good model for an edge (you can't "unwrite" something).
On the other hand, routing zillions of wires in an SoC because you
need level interrupts is a bit extreme.

People have come up with a variety of schemes to support this, which
involves sending two messages: one to signal the interrupt, and one
to clear it. Since the kernel cannot represent this, we've ended up
with side-band mechanisms that are pretty awful.

Instead, let's acknoledge the requirement, and ensure that, under the
right circumstances, the irq_compose_msg and irq_write_msg can take
as a parameter an array of two messages instead of a pointer to a
single one. We also add some checking that the compose method only
clobbers the second message if the MSI domain has been created with
the MSI_FLAG_LEVEL_CAPABLE flags.

Signed-off-by: Marc Zyngier <marc.zyngier@arm.com>
Signed-off-by: Thomas Gleixner <tglx@linutronix.de>
Cc: Rob Herring <robh@kernel.org>
Cc: Jason Cooper <jason@lakedaemon.net>
Cc: Ard Biesheuvel <ard.biesheuvel@linaro.org>
Cc: Srinivas Kandagatla <srinivas.kandagatla@linaro.org>
Cc: Thomas Petazzoni <thomas.petazzoni@bootlin.com>
Cc: Miquel Raynal <miquel.raynal@bootlin.com>
Link: https://lkml.kernel.org/r/20180508121438.11301-2-marc.zyngier@arm.com
This commit is contained in:
Marc Zyngier 2018-05-08 13:14:30 +01:00 committed by Thomas Gleixner
parent b5c5f3959b
commit 0be8153cbc
2 changed files with 26 additions and 9 deletions

View File

@ -289,6 +289,8 @@ enum {
* MSI_FLAG_ACTIVATE_EARLY has been set. * MSI_FLAG_ACTIVATE_EARLY has been set.
*/ */
MSI_FLAG_MUST_REACTIVATE = (1 << 5), MSI_FLAG_MUST_REACTIVATE = (1 << 5),
/* Is level-triggered capable, using two messages */
MSI_FLAG_LEVEL_CAPABLE = (1 << 6),
}; };
int msi_domain_set_affinity(struct irq_data *data, const struct cpumask *mask, int msi_domain_set_affinity(struct irq_data *data, const struct cpumask *mask,

View File

@ -76,6 +76,19 @@ static inline void irq_chip_write_msi_msg(struct irq_data *data,
data->chip->irq_write_msi_msg(data, msg); data->chip->irq_write_msi_msg(data, msg);
} }
static void msi_check_level(struct irq_domain *domain, struct msi_msg *msg)
{
struct msi_domain_info *info = domain->host_data;
/*
* If the MSI provider has messed with the second message and
* not advertized that it is level-capable, signal the breakage.
*/
WARN_ON(!((info->flags & MSI_FLAG_LEVEL_CAPABLE) &&
(info->chip->flags & IRQCHIP_SUPPORTS_LEVEL_MSI)) &&
(msg[1].address_lo || msg[1].address_hi || msg[1].data));
}
/** /**
* msi_domain_set_affinity - Generic affinity setter function for MSI domains * msi_domain_set_affinity - Generic affinity setter function for MSI domains
* @irq_data: The irq data associated to the interrupt * @irq_data: The irq data associated to the interrupt
@ -89,13 +102,14 @@ int msi_domain_set_affinity(struct irq_data *irq_data,
const struct cpumask *mask, bool force) const struct cpumask *mask, bool force)
{ {
struct irq_data *parent = irq_data->parent_data; struct irq_data *parent = irq_data->parent_data;
struct msi_msg msg; struct msi_msg msg[2] = { [1] = { }, };
int ret; int ret;
ret = parent->chip->irq_set_affinity(parent, mask, force); ret = parent->chip->irq_set_affinity(parent, mask, force);
if (ret >= 0 && ret != IRQ_SET_MASK_OK_DONE) { if (ret >= 0 && ret != IRQ_SET_MASK_OK_DONE) {
BUG_ON(irq_chip_compose_msi_msg(irq_data, &msg)); BUG_ON(irq_chip_compose_msi_msg(irq_data, msg));
irq_chip_write_msi_msg(irq_data, &msg); msi_check_level(irq_data->domain, msg);
irq_chip_write_msi_msg(irq_data, msg);
} }
return ret; return ret;
@ -104,20 +118,21 @@ int msi_domain_set_affinity(struct irq_data *irq_data,
static int msi_domain_activate(struct irq_domain *domain, static int msi_domain_activate(struct irq_domain *domain,
struct irq_data *irq_data, bool early) struct irq_data *irq_data, bool early)
{ {
struct msi_msg msg; struct msi_msg msg[2] = { [1] = { }, };
BUG_ON(irq_chip_compose_msi_msg(irq_data, &msg)); BUG_ON(irq_chip_compose_msi_msg(irq_data, msg));
irq_chip_write_msi_msg(irq_data, &msg); msi_check_level(irq_data->domain, msg);
irq_chip_write_msi_msg(irq_data, msg);
return 0; return 0;
} }
static void msi_domain_deactivate(struct irq_domain *domain, static void msi_domain_deactivate(struct irq_domain *domain,
struct irq_data *irq_data) struct irq_data *irq_data)
{ {
struct msi_msg msg; struct msi_msg msg[2];
memset(&msg, 0, sizeof(msg)); memset(msg, 0, sizeof(msg));
irq_chip_write_msi_msg(irq_data, &msg); irq_chip_write_msi_msg(irq_data, msg);
} }
static int msi_domain_alloc(struct irq_domain *domain, unsigned int virq, static int msi_domain_alloc(struct irq_domain *domain, unsigned int virq,