All of lore.kernel.org
 help / color / mirror / Atom feed
From: Marc Zyngier <maz@kernel.org>
To: Thomas Gleixner <tglx@linutronix.de>
Cc: linux-tegra@vger.kernel.org, linux-kernel@vger.kernel.org,
	linux-arm-kernel@lists.infradead.org,
	Thierry Reding <thierry.reding@gmail.com>,
	Jonathan Hunter <jonathanh@nvidia.com>,
	Dmitry Osipenko <digetx@gmail.com>,
	Sowjanya Komatineni <skomatineni@nvidia.com>,
	Venkat Reddy Talla <vreddytalla@nvidia.com>,
	kernel-team@android.com
Subject: Re: [PATCH v3 1/4] genirq/irqdomain: Allow partial trimming of irq_data hierarchy
Date: Thu, 08 Oct 2020 14:06:19 +0100	[thread overview]
Message-ID: <9341eb039193d630d8a3f7bac920a76c@kernel.org> (raw)
In-Reply-To: <87d01t2c90.fsf@nanos.tec.linutronix.de>

On 2020-10-08 12:22, Thomas Gleixner wrote:
> On Wed, Oct 07 2020 at 13:45, Marc Zyngier wrote:
>> +/**
>> + * irq_domain_trim_hierarchy - Trim the uninitialized part of a irq 
>> hierarchy
>> + * @virq:	IRQ number to trim where the hierarchy is to be trimmed
>> + *
>> + * Drop the partial irq_data hierarchy from the level where the
>> + * irq_data->chip is NULL.
>> + *
>> + * Its only use is to be able to trim levels of hierarchy that do not
>> + * have any real meaning for this interrupt, and that the driver 
>> leaves
>> + * uninitialized in its .alloc() callback.
>> + */
>> +static void irq_domain_trim_hierarchy(unsigned int virq)
>> +{
>> +	struct irq_data *tail, *irq_data = irq_get_irq_data(virq);
>> +
>> +	/* It really needs to be a hierarchy, and not a single entry */
>> +	if (!irq_data->parent_data)
>> +		return;
>> +
>> +	/* Skip until we find a parent irq_data without a populated chip */
>> +	while (irq_data->parent_data && irq_data->parent_data->chip)
>> +		irq_data = irq_data->parent_data;
>> +
>> +	/* All levels populated */
>> +	if (!irq_data->parent_data)
>> +		return;
>> +
>> +	pr_info("IRQ%d: trimming hierarchy from %s\n",
>> +		virq, irq_data->parent_data->domain->name);
>> +
>> +	/* Sever the inner part of the hierarchy...  */
>> +	tail = irq_data->parent_data;
>> +	irq_data->parent_data = NULL;
>> +	__irq_domain_free_hierarchy(tail);
>> +}
> 
> I like that way more than the previous version, but there are still
> quite some dangeroos waiting to bite.
> 
> Just for robustness sake we should do the following:

[...]

Here's what I have now, with the pmc driver calling
irq_domain_disconnect_hierarchy() at the right spots.

         M.

diff --git a/include/linux/irqdomain.h b/include/linux/irqdomain.h
index b37350c4fe37..a52b095bd404 100644
--- a/include/linux/irqdomain.h
+++ b/include/linux/irqdomain.h
@@ -509,6 +509,9 @@ extern void irq_domain_free_irqs_parent(struct 
irq_domain *domain,
  					unsigned int irq_base,
  					unsigned int nr_irqs);

+extern int irq_domain_disconnect_hierarchy(struct irq_domain *domain,
+					   unsigned int virq);
+
  static inline bool irq_domain_is_hierarchy(struct irq_domain *domain)
  {
  	return domain->flags & IRQ_DOMAIN_FLAG_HIERARCHY;
diff --git a/kernel/irq/irqdomain.c b/kernel/irq/irqdomain.c
index 76cd7ebd1178..316f5baa9cd9 100644
--- a/kernel/irq/irqdomain.c
+++ b/kernel/irq/irqdomain.c
@@ -1136,6 +1136,17 @@ static struct irq_data 
*irq_domain_insert_irq_data(struct irq_domain *domain,
  	return irq_data;
  }

+static void __irq_domain_free_hierarchy(struct irq_data *irq_data)
+{
+	struct irq_data *tmp;
+
+	while (irq_data) {
+		tmp = irq_data;
+		irq_data = irq_data->parent_data;
+		kfree(tmp);
+	}
+}
+
  static void irq_domain_free_irq_data(unsigned int virq, unsigned int 
nr_irqs)
  {
  	struct irq_data *irq_data, *tmp;
@@ -1147,12 +1158,81 @@ static void irq_domain_free_irq_data(unsigned 
int virq, unsigned int nr_irqs)
  		irq_data->parent_data = NULL;
  		irq_data->domain = NULL;

-		while (tmp) {
-			irq_data = tmp;
-			tmp = tmp->parent_data;
-			kfree(irq_data);
+		__irq_domain_free_hierarchy(tmp);
+	}
+}
+
+int irq_domain_disconnect_hierarchy(struct irq_domain *domain,
+				    unsigned int virq)
+{
+	struct irq_data *irqd;
+
+	irqd = irq_domain_get_irq_data(domain, virq);
+	if (!irqd)
+		return -EINVAL;
+
+	irqd->chip = ERR_PTR(-ENOTCONN);
+	return 0;
+}
+
+/**
+ * irq_domain_trim_hierarchy - Trim the uninitialized part of a irq 
hierarchy
+ * @virq:	IRQ number to trim where the hierarchy is to be trimmed
+ *
+ * Drop the partial irq_data hierarchy from the level where the
+ * irq_data->chip is a trim marker (PTR_ERR(-ENOTCONN)).
+ *
+ * Its only use is to be able to trim levels of hierarchy that do not
+ * have any real meaning for this interrupt, and that the driver marks
+ * as such from its .alloc() callback.
+ */
+static int irq_domain_trim_hierarchy(unsigned int virq)
+{
+	struct irq_data *tail, *irqd, *irq_data;
+
+	irq_data = irq_get_irq_data(virq);
+	tail = NULL;
+
+	/* The first entry must have a valid irqchip */
+	if (!irq_data->chip || IS_ERR(irq_data->chip))
+		return -EINVAL;
+
+	/*
+	 * Validate that the irq_data chain is sane in the presence of
+	 * a hierarchy trimming marker.
+	 */
+	for (irqd = irq_data->parent_data; irqd; irq_data = irqd, irqd = 
irqd->parent_data) {
+		/* Can't have a valid irqchip after a trim marker */
+		if (irqd->chip && tail)
+			return -EINVAL;
+
+		/* Can't have an empty irqchip before a trim marker */
+		if (!irqd->chip && !tail)
+			return -EINVAL;
+
+		if (IS_ERR(irqd->chip)) {
+			/* Only -ENOTCONN is a valid trim marker */
+			if (PTR_ERR(irqd->chip) != -ENOTCONN)
+				return -EINVAL;
+
+			tail = irq_data;
  		}
  	}
+
+	/* No trim marker, nothing to do */
+	if (!tail)
+		return 0;
+
+	pr_info("IRQ%d: trimming hierarchy from %s\n",
+		virq, tail->parent_data->domain->name);
+
+	/* Sever the inner part of the hierarchy...  */
+	irqd = tail;
+	tail = tail->parent_data;
+	irqd->parent_data = NULL;
+	__irq_domain_free_hierarchy(tail);
+
+	return 0;
  }

  static int irq_domain_alloc_irq_data(struct irq_domain *domain,
@@ -1362,11 +1442,16 @@ int __irq_domain_alloc_irqs(struct irq_domain 
*domain, int irq_base,
  		mutex_unlock(&irq_domain_mutex);
  		goto out_free_irq_data;
  	}
-	for (i = 0; i < nr_irqs; i++)
+	for (i = 0; i < nr_irqs; i++) {
+		ret = irq_domain_trim_hierarchy(virq + i);
+		if (ret)
+			break;
  		irq_domain_insert_irq(virq + i);
+	}
  	mutex_unlock(&irq_domain_mutex);

-	return virq;
+	if (!ret)
+		return virq;

  out_free_irq_data:
  	irq_domain_free_irq_data(virq, nr_irqs);

-- 
Jazz is not dead. It just smells funny...

WARNING: multiple messages have this Message-ID (diff)
From: Marc Zyngier <maz@kernel.org>
To: Thomas Gleixner <tglx@linutronix.de>
Cc: Venkat Reddy Talla <vreddytalla@nvidia.com>,
	linux-kernel@vger.kernel.org,
	Jonathan Hunter <jonathanh@nvidia.com>,
	Thierry Reding <thierry.reding@gmail.com>,
	Sowjanya Komatineni <skomatineni@nvidia.com>,
	linux-tegra@vger.kernel.org, Dmitry Osipenko <digetx@gmail.com>,
	kernel-team@android.com, linux-arm-kernel@lists.infradead.org
Subject: Re: [PATCH v3 1/4] genirq/irqdomain: Allow partial trimming of irq_data hierarchy
Date: Thu, 08 Oct 2020 14:06:19 +0100	[thread overview]
Message-ID: <9341eb039193d630d8a3f7bac920a76c@kernel.org> (raw)
In-Reply-To: <87d01t2c90.fsf@nanos.tec.linutronix.de>

On 2020-10-08 12:22, Thomas Gleixner wrote:
> On Wed, Oct 07 2020 at 13:45, Marc Zyngier wrote:
>> +/**
>> + * irq_domain_trim_hierarchy - Trim the uninitialized part of a irq 
>> hierarchy
>> + * @virq:	IRQ number to trim where the hierarchy is to be trimmed
>> + *
>> + * Drop the partial irq_data hierarchy from the level where the
>> + * irq_data->chip is NULL.
>> + *
>> + * Its only use is to be able to trim levels of hierarchy that do not
>> + * have any real meaning for this interrupt, and that the driver 
>> leaves
>> + * uninitialized in its .alloc() callback.
>> + */
>> +static void irq_domain_trim_hierarchy(unsigned int virq)
>> +{
>> +	struct irq_data *tail, *irq_data = irq_get_irq_data(virq);
>> +
>> +	/* It really needs to be a hierarchy, and not a single entry */
>> +	if (!irq_data->parent_data)
>> +		return;
>> +
>> +	/* Skip until we find a parent irq_data without a populated chip */
>> +	while (irq_data->parent_data && irq_data->parent_data->chip)
>> +		irq_data = irq_data->parent_data;
>> +
>> +	/* All levels populated */
>> +	if (!irq_data->parent_data)
>> +		return;
>> +
>> +	pr_info("IRQ%d: trimming hierarchy from %s\n",
>> +		virq, irq_data->parent_data->domain->name);
>> +
>> +	/* Sever the inner part of the hierarchy...  */
>> +	tail = irq_data->parent_data;
>> +	irq_data->parent_data = NULL;
>> +	__irq_domain_free_hierarchy(tail);
>> +}
> 
> I like that way more than the previous version, but there are still
> quite some dangeroos waiting to bite.
> 
> Just for robustness sake we should do the following:

[...]

Here's what I have now, with the pmc driver calling
irq_domain_disconnect_hierarchy() at the right spots.

         M.

diff --git a/include/linux/irqdomain.h b/include/linux/irqdomain.h
index b37350c4fe37..a52b095bd404 100644
--- a/include/linux/irqdomain.h
+++ b/include/linux/irqdomain.h
@@ -509,6 +509,9 @@ extern void irq_domain_free_irqs_parent(struct 
irq_domain *domain,
  					unsigned int irq_base,
  					unsigned int nr_irqs);

+extern int irq_domain_disconnect_hierarchy(struct irq_domain *domain,
+					   unsigned int virq);
+
  static inline bool irq_domain_is_hierarchy(struct irq_domain *domain)
  {
  	return domain->flags & IRQ_DOMAIN_FLAG_HIERARCHY;
diff --git a/kernel/irq/irqdomain.c b/kernel/irq/irqdomain.c
index 76cd7ebd1178..316f5baa9cd9 100644
--- a/kernel/irq/irqdomain.c
+++ b/kernel/irq/irqdomain.c
@@ -1136,6 +1136,17 @@ static struct irq_data 
*irq_domain_insert_irq_data(struct irq_domain *domain,
  	return irq_data;
  }

+static void __irq_domain_free_hierarchy(struct irq_data *irq_data)
+{
+	struct irq_data *tmp;
+
+	while (irq_data) {
+		tmp = irq_data;
+		irq_data = irq_data->parent_data;
+		kfree(tmp);
+	}
+}
+
  static void irq_domain_free_irq_data(unsigned int virq, unsigned int 
nr_irqs)
  {
  	struct irq_data *irq_data, *tmp;
@@ -1147,12 +1158,81 @@ static void irq_domain_free_irq_data(unsigned 
int virq, unsigned int nr_irqs)
  		irq_data->parent_data = NULL;
  		irq_data->domain = NULL;

-		while (tmp) {
-			irq_data = tmp;
-			tmp = tmp->parent_data;
-			kfree(irq_data);
+		__irq_domain_free_hierarchy(tmp);
+	}
+}
+
+int irq_domain_disconnect_hierarchy(struct irq_domain *domain,
+				    unsigned int virq)
+{
+	struct irq_data *irqd;
+
+	irqd = irq_domain_get_irq_data(domain, virq);
+	if (!irqd)
+		return -EINVAL;
+
+	irqd->chip = ERR_PTR(-ENOTCONN);
+	return 0;
+}
+
+/**
+ * irq_domain_trim_hierarchy - Trim the uninitialized part of a irq 
hierarchy
+ * @virq:	IRQ number to trim where the hierarchy is to be trimmed
+ *
+ * Drop the partial irq_data hierarchy from the level where the
+ * irq_data->chip is a trim marker (PTR_ERR(-ENOTCONN)).
+ *
+ * Its only use is to be able to trim levels of hierarchy that do not
+ * have any real meaning for this interrupt, and that the driver marks
+ * as such from its .alloc() callback.
+ */
+static int irq_domain_trim_hierarchy(unsigned int virq)
+{
+	struct irq_data *tail, *irqd, *irq_data;
+
+	irq_data = irq_get_irq_data(virq);
+	tail = NULL;
+
+	/* The first entry must have a valid irqchip */
+	if (!irq_data->chip || IS_ERR(irq_data->chip))
+		return -EINVAL;
+
+	/*
+	 * Validate that the irq_data chain is sane in the presence of
+	 * a hierarchy trimming marker.
+	 */
+	for (irqd = irq_data->parent_data; irqd; irq_data = irqd, irqd = 
irqd->parent_data) {
+		/* Can't have a valid irqchip after a trim marker */
+		if (irqd->chip && tail)
+			return -EINVAL;
+
+		/* Can't have an empty irqchip before a trim marker */
+		if (!irqd->chip && !tail)
+			return -EINVAL;
+
+		if (IS_ERR(irqd->chip)) {
+			/* Only -ENOTCONN is a valid trim marker */
+			if (PTR_ERR(irqd->chip) != -ENOTCONN)
+				return -EINVAL;
+
+			tail = irq_data;
  		}
  	}
+
+	/* No trim marker, nothing to do */
+	if (!tail)
+		return 0;
+
+	pr_info("IRQ%d: trimming hierarchy from %s\n",
+		virq, tail->parent_data->domain->name);
+
+	/* Sever the inner part of the hierarchy...  */
+	irqd = tail;
+	tail = tail->parent_data;
+	irqd->parent_data = NULL;
+	__irq_domain_free_hierarchy(tail);
+
+	return 0;
  }

  static int irq_domain_alloc_irq_data(struct irq_domain *domain,
@@ -1362,11 +1442,16 @@ int __irq_domain_alloc_irqs(struct irq_domain 
*domain, int irq_base,
  		mutex_unlock(&irq_domain_mutex);
  		goto out_free_irq_data;
  	}
-	for (i = 0; i < nr_irqs; i++)
+	for (i = 0; i < nr_irqs; i++) {
+		ret = irq_domain_trim_hierarchy(virq + i);
+		if (ret)
+			break;
  		irq_domain_insert_irq(virq + i);
+	}
  	mutex_unlock(&irq_domain_mutex);

-	return virq;
+	if (!ret)
+		return virq;

  out_free_irq_data:
  	irq_domain_free_irq_data(virq, nr_irqs);

-- 
Jazz is not dead. It just smells funny...

_______________________________________________
linux-arm-kernel mailing list
linux-arm-kernel@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/linux-arm-kernel

  reply	other threads:[~2020-10-08 13:06 UTC|newest]

Thread overview: 18+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2020-10-07 12:45 [PATCH v3 0/4] soc/tegra: Prevent the PMC driver from corrupting interrupt routing Marc Zyngier
2020-10-07 12:45 ` Marc Zyngier
2020-10-07 12:45 ` [PATCH v3 1/4] genirq/irqdomain: Allow partial trimming of irq_data hierarchy Marc Zyngier
2020-10-07 12:45   ` Marc Zyngier
2020-10-08 11:22   ` Thomas Gleixner
2020-10-08 11:22     ` Thomas Gleixner
2020-10-08 13:06     ` Marc Zyngier [this message]
2020-10-08 13:06       ` Marc Zyngier
2020-10-08 20:47       ` Thomas Gleixner
2020-10-08 20:47         ` Thomas Gleixner
2020-10-10  9:42         ` Marc Zyngier
2020-10-10  9:42           ` Marc Zyngier
2020-10-07 12:45 ` [PATCH v3 2/4] gpio: tegra186: Allow optional irq parent callbacks Marc Zyngier
2020-10-07 12:45   ` Marc Zyngier
2020-10-07 12:45 ` [PATCH v3 3/4] soc/tegra: pmc: " Marc Zyngier
2020-10-07 12:45   ` Marc Zyngier
2020-10-07 12:45 ` [PATCH v3 4/4] soc/tegra: pmc: Don't create fake interrupt hierarchy levels Marc Zyngier
2020-10-07 12:45   ` Marc Zyngier

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=9341eb039193d630d8a3f7bac920a76c@kernel.org \
    --to=maz@kernel.org \
    --cc=digetx@gmail.com \
    --cc=jonathanh@nvidia.com \
    --cc=kernel-team@android.com \
    --cc=linux-arm-kernel@lists.infradead.org \
    --cc=linux-kernel@vger.kernel.org \
    --cc=linux-tegra@vger.kernel.org \
    --cc=skomatineni@nvidia.com \
    --cc=tglx@linutronix.de \
    --cc=thierry.reding@gmail.com \
    --cc=vreddytalla@nvidia.com \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.