lists.openwall.net   lists  /  announce  owl-users  owl-dev  john-users  john-dev  passwdqc-users  yescrypt  popa3d-users  /  oss-security  kernel-hardening  musl  sabotage  tlsify  passwords  /  crypt-dev  xvendor  /  Bugtraq  Full-Disclosure  linux-kernel  linux-netdev  linux-ext4  linux-hardening  linux-cve-announce  PHC 
Open Source and information security mailing list archives
 
Hash Suite: Windows password security audit tool. GUI, reports in PDF.
[<prev] [next>] [thread-next>] [day] [month] [year] [list]
Message-ID: <61dd3e0c.1c69fb81.cea15.8d98@mx.google.com>
Date:   Tue, 11 Jan 2022 21:07:47 +1300
From:   Daniel Beer <daniel.beer@...rinstitute.com>
To:     linux-iio@...r.kernel.org
Cc:     linux-kernel@...r.kernel.org, Lars-Peter Clausen <lars@...afoo.de>,
        Michael Hennerich <Michael.Hennerich@...log.com>,
        Jonathan Cameron <jic23@...nel.org>,
        Daniel Beer <daniel.beer@...rinstitute.com>,
        Derek Simkowiak <derek.simkowiak@...rinstitute.com>
Subject: [PATCH] iio: adc: ad_sigma_delta: IRQ sharing mechanism.

This patch allows for multiple Analog Devices ADCs to be placed on the
same SPI bus. While it's not possible for them to share interrupts
arbitrarily, a special restricted form of sharing for this special case
is implemented here.

The first instance of an ADC using a given interrupt will acquire the
IRQ and register an object in a global list. Any subsequent instances
will increment a reference count on this object.

During a conversion, the active instance indicates that it is the
recipient of the interrupt by setting a pointer on the object shared
among it and the other instances.

The existing CS locking mechanism guarantees that no more than one
instance per bus will be expecting the interrupt at any time.

Signed-off-by: Daniel Beer <daniel.beer@...rinstitute.com>
---
 drivers/iio/adc/ad_sigma_delta.c       | 191 +++++++++++++++++++++++--
 include/linux/iio/adc/ad_sigma_delta.h |   4 +
 2 files changed, 181 insertions(+), 14 deletions(-)

diff --git a/drivers/iio/adc/ad_sigma_delta.c b/drivers/iio/adc/ad_sigma_delta.c
index cd418bd8bd87..cd593af6ef3a 100644
--- a/drivers/iio/adc/ad_sigma_delta.c
+++ b/drivers/iio/adc/ad_sigma_delta.c
@@ -13,6 +13,7 @@
 #include <linux/spi/spi.h>
 #include <linux/err.h>
 #include <linux/module.h>
+#include <linux/list.h>
 
 #include <linux/iio/iio.h>
 #include <linux/iio/sysfs.h>
@@ -24,6 +25,161 @@
 
 #include <asm/unaligned.h>
 
+static irqreturn_t ad_sd_data_rdy_trig_poll(int irq, void *private);
+
+struct ad_sigma_delta_interrupt {
+	/* Constant from time of creation */
+	int			irq;
+	struct spi_master	*master;
+
+	/* Protected by global lock */
+	struct list_head	list;
+	int			refcnt;
+
+	/* Protected by lock on corresponding SPI bus */
+	struct ad_sigma_delta	*active;
+};
+
+static DEFINE_MUTEX(interrupt_lock);
+static LIST_HEAD(interrupt_list);
+
+static void adsdi_enable(struct ad_sigma_delta *a)
+{
+	struct ad_sigma_delta_interrupt *intr = a->interrupt;
+
+	WARN_ON(intr->active);
+	intr->active = a;
+	pr_debug("ad_sigma_delta: enable %d for %p\n", intr->irq, a);
+	enable_irq(intr->irq);
+}
+
+static void adsdi_disable(struct ad_sigma_delta *a, int nosync)
+{
+	struct ad_sigma_delta_interrupt *intr = a->interrupt;
+
+	if (nosync)
+		disable_irq_nosync(intr->irq);
+	else
+		disable_irq(intr->irq);
+
+	pr_debug("ad_sigma_delta: disable %d for %p\n", intr->irq, intr->active);
+
+	/* In the case of a timeout, it's possible for adsdi_disable to
+	 * be called twice by the same instance (if the interrupt runs
+	 * between the call to check and the call to disable).
+	 *
+	 * We still need to disable first before checking intr->active.
+	 * Then we can roll back if we've done it twice.
+	 */
+	if (intr->active != a) {
+		WARN_ON(intr->active);
+		pr_debug("ad_sigma_delta: double-disable\n");
+		enable_irq(intr->irq);
+	}
+
+	intr->active = NULL;
+}
+
+static int adsdi_get(struct ad_sigma_delta_interrupt **intr_ret,
+		     int irq, struct spi_master *master,
+		     int flags)
+{
+	struct ad_sigma_delta_interrupt *intr = NULL;
+	struct list_head *ptr;
+	int ret = 0;
+
+	mutex_lock(&interrupt_lock);
+
+	/* Try to find an existing instance */
+	list_for_each(ptr, &interrupt_list) {
+		struct ad_sigma_delta_interrupt *i = list_entry(ptr,
+			struct ad_sigma_delta_interrupt, list);
+
+		if (i->irq == irq) {
+			/* No instance will attempt to wait for the
+			 * interrupt without the SPI bus locked, which
+			 * we can rely on to ensure correct operation.
+			 * However, we would like to detect
+			 * misconfiguration that would lead to unsafe
+			 * access.
+			 */
+			if (i->master != master) {
+				pr_err(
+				    "ad_sigma_delta: SPI master mismatch on IRQ %d\n",
+				    irq);
+				ret = -EINVAL;
+				goto fail_search;
+			}
+
+			intr = i;
+			break;
+		}
+	}
+
+	/* Allocate a new one if necessary */
+	if (!intr) {
+		intr = kmalloc(sizeof(*intr), GFP_KERNEL);
+		if (!intr) {
+			ret = -ENOMEM;
+			pr_err("ad_sigma_delta: can't allocate memory\n");
+			goto fail_search;
+		}
+
+		intr->irq = irq;
+		intr->refcnt = 0;
+		intr->active = NULL;
+		intr->master = master;
+
+		ret = request_irq(irq,
+				  ad_sd_data_rdy_trig_poll,
+				  flags | IRQF_NO_AUTOEN,
+				  "ad_sigma_delta",
+				  intr);
+		if (ret)
+			goto fail_search;
+
+		pr_debug("ad_sigma_delta: sharing interrupt %d\n", irq);
+		list_add(&intr->list, &interrupt_list);
+	}
+
+	intr->refcnt++;
+	*intr_ret = intr;
+
+fail_search:
+	mutex_unlock(&interrupt_lock);
+	return ret;
+}
+
+static void adsdi_put(struct ad_sigma_delta_interrupt *intr)
+{
+	mutex_lock(&interrupt_lock);
+	if (!--intr->refcnt) {
+		pr_debug("ad_sigma_delta: interrupt %d deallocated\n",
+			intr->irq);
+		free_irq(intr->irq, intr);
+		list_del(&intr->list);
+		kfree(intr);
+	}
+	mutex_unlock(&interrupt_lock);
+}
+
+static void devm_adsdi_release(void *arg)
+{
+	adsdi_put(arg);
+}
+
+static int devm_adsdi_get(struct device *dev,
+			  struct ad_sigma_delta_interrupt **intr_ret,
+			  int irq, struct spi_master *master,
+			  int flags)
+{
+	const int ret = adsdi_get(intr_ret, irq, master, flags);
+
+	if (ret < 0)
+		return ret;
+
+	return devm_add_action_or_reset(dev, devm_adsdi_release, *intr_ret);
+}
 
 #define AD_SD_COMM_CHAN_MASK	0x3
 
@@ -221,11 +377,11 @@ int ad_sd_calibrate(struct ad_sigma_delta *sigma_delta,
 		goto out;
 
 	sigma_delta->irq_dis = false;
-	enable_irq(sigma_delta->spi->irq);
+	adsdi_enable(sigma_delta);
 	timeout = wait_for_completion_timeout(&sigma_delta->completion, 2 * HZ);
 	if (timeout == 0) {
 		sigma_delta->irq_dis = true;
-		disable_irq_nosync(sigma_delta->spi->irq);
+		adsdi_disable(sigma_delta, 0);
 		ret = -EIO;
 	} else {
 		ret = 0;
@@ -294,7 +450,7 @@ int ad_sigma_delta_single_conversion(struct iio_dev *indio_dev,
 	ad_sigma_delta_set_mode(sigma_delta, AD_SD_MODE_SINGLE);
 
 	sigma_delta->irq_dis = false;
-	enable_irq(sigma_delta->spi->irq);
+	adsdi_enable(sigma_delta);
 	ret = wait_for_completion_interruptible_timeout(
 			&sigma_delta->completion, HZ);
 
@@ -314,7 +470,7 @@ int ad_sigma_delta_single_conversion(struct iio_dev *indio_dev,
 
 out:
 	if (!sigma_delta->irq_dis) {
-		disable_irq_nosync(sigma_delta->spi->irq);
+		adsdi_disable(sigma_delta, 0);
 		sigma_delta->irq_dis = true;
 	}
 
@@ -361,7 +517,7 @@ static int ad_sd_buffer_postenable(struct iio_dev *indio_dev)
 		goto err_unlock;
 
 	sigma_delta->irq_dis = false;
-	enable_irq(sigma_delta->spi->irq);
+	adsdi_enable(sigma_delta);
 
 	return 0;
 
@@ -379,7 +535,7 @@ static int ad_sd_buffer_postdisable(struct iio_dev *indio_dev)
 	wait_for_completion_timeout(&sigma_delta->completion, HZ);
 
 	if (!sigma_delta->irq_dis) {
-		disable_irq_nosync(sigma_delta->spi->irq);
+		adsdi_disable(sigma_delta, 0);
 		sigma_delta->irq_dis = true;
 	}
 
@@ -425,7 +581,7 @@ static irqreturn_t ad_sd_trigger_handler(int irq, void *p)
 
 	iio_trigger_notify_done(indio_dev->trig);
 	sigma_delta->irq_dis = false;
-	enable_irq(sigma_delta->spi->irq);
+	adsdi_enable(sigma_delta);
 
 	return IRQ_HANDLED;
 }
@@ -438,10 +594,17 @@ static const struct iio_buffer_setup_ops ad_sd_buffer_setup_ops = {
 
 static irqreturn_t ad_sd_data_rdy_trig_poll(int irq, void *private)
 {
-	struct ad_sigma_delta *sigma_delta = private;
+	struct ad_sigma_delta_interrupt *intr = private;
+	struct ad_sigma_delta *sigma_delta = intr->active;
+
+	WARN_ON(!sigma_delta);
+	if (!sigma_delta)
+		return IRQ_NONE;
 
+	pr_debug("ad_sigma_delta: interrupt %d fired for %p\n",
+		intr->irq, sigma_delta);
 	complete(&sigma_delta->completion);
-	disable_irq_nosync(irq);
+	adsdi_disable(sigma_delta, 1);
 	sigma_delta->irq_dis = true;
 	iio_trigger_poll(sigma_delta->trig);
 
@@ -486,11 +649,11 @@ static int devm_ad_sd_probe_trigger(struct device *dev, struct iio_dev *indio_de
 	init_completion(&sigma_delta->completion);
 
 	sigma_delta->irq_dis = true;
-	ret = devm_request_irq(dev, sigma_delta->spi->irq,
-			       ad_sd_data_rdy_trig_poll,
-			       sigma_delta->info->irq_flags | IRQF_NO_AUTOEN,
-			       indio_dev->name,
-			       sigma_delta);
+	ret = devm_adsdi_get(dev,
+			&sigma_delta->interrupt,
+			sigma_delta->spi->irq,
+			sigma_delta->spi->master,
+			sigma_delta->info->irq_flags);
 	if (ret)
 		return ret;
 
diff --git a/include/linux/iio/adc/ad_sigma_delta.h b/include/linux/iio/adc/ad_sigma_delta.h
index c525fd51652f..62f38cfe807b 100644
--- a/include/linux/iio/adc/ad_sigma_delta.h
+++ b/include/linux/iio/adc/ad_sigma_delta.h
@@ -54,6 +54,9 @@ struct ad_sigma_delta_info {
 	unsigned long irq_flags;
 };
 
+/* Data relating to interrupt sharing */
+struct ad_sigma_delta_interrupt;
+
 /**
  * struct ad_sigma_delta - Sigma Delta device struct
  * @spi: The spi device associated with the Sigma Delta device.
@@ -76,6 +79,7 @@ struct ad_sigma_delta {
 	uint8_t			comm;
 
 	const struct ad_sigma_delta_info *info;
+	struct ad_sigma_delta_interrupt *interrupt;
 
 	/*
 	 * DMA (thus cache coherency maintenance) requires the
-- 
2.30.2

Powered by blists - more mailing lists

Powered by Openwall GNU/*/Linux Powered by OpenVZ