* [PATCH v2 04/10] arm64: dts: actions: *do not merge* disable sps node from S700
From: Amit Singh Tomar @ 2020-05-19 18:19 UTC (permalink / raw)
To: andre.przywara, afaerber, manivannan.sadhasivam, robh+dt
Cc: cristian.ciocaltea, linux-kernel, linux-arm-kernel, linux-actions,
devicetree
In-Reply-To: <1589912368-480-1-git-send-email-amittomer25@gmail.com>
After commit 7cdf8446ed1d ("arm64: dts: actions: Add pinctrl node for
Actions Semi S700") following error has been observed while booting
Linux on Cubieboard7-lite(based on S700 SoC).
[ 0.257415] pinctrl-s700 e01b0000.pinctrl: can't request region for
resource [mem 0xe01b0000-0xe01b0fff]
[ 0.266902] pinctrl-s700: probe of e01b0000.pinctrl failed with error -16
This is due to the fact that memory range for "sps" power domain controller
clashes with pinctrl.
This commit disable "sps" to avoid this conflict and let us test DMA and MMC
related changes.
Signed-off-by: Amit Singh Tomar <amittomer25@gmail.com>
---
Changes since v1:
* No change.
Changes since RFC:
* kept as do not merge.
---
arch/arm64/boot/dts/actions/s700.dtsi | 1 +
1 file changed, 1 insertion(+)
diff --git a/arch/arm64/boot/dts/actions/s700.dtsi b/arch/arm64/boot/dts/actions/s700.dtsi
index 2006ad5424fa..0397c5dd3dec 100644
--- a/arch/arm64/boot/dts/actions/s700.dtsi
+++ b/arch/arm64/boot/dts/actions/s700.dtsi
@@ -220,6 +220,7 @@
compatible = "actions,s700-sps";
reg = <0x0 0xe01b0100 0x0 0x100>;
#power-domain-cells = <1>;
+ status = "disabled";
};
timer: timer@e024c000 {
--
2.7.4
^ permalink raw reply related
* [PATCH v2 5/6] sc16is7xx: Allow sharing the IRQ line
From: Daniel Mack @ 2020-05-19 18:21 UTC (permalink / raw)
To: devicetree, linux-serial
Cc: gregkh, robh+dt, jslaby, jringle, m.brock, pascal.huerst,
Daniel Mack
In-Reply-To: <20200519182147.218713-1-daniel@zonque.org>
When the interrupt line is shared with other devices, the IRQ must be
level-triggered, as only one device can trigger a falling edge. To support
this, try to acquire the IRQ with IRQF_TRIGGER_LOW|IRQF_SHARED first.
Interrupt controllers that lack support for level-triggers will return an
error, in which case the driver will now retry the acqusition with
IRQF_TRIGGER_FALLING, which was also the default before.
Signed-off-by: Daniel Mack <daniel@zonque.org>
---
drivers/tty/serial/sc16is7xx.c | 14 +++++++++++++-
1 file changed, 13 insertions(+), 1 deletion(-)
diff --git a/drivers/tty/serial/sc16is7xx.c b/drivers/tty/serial/sc16is7xx.c
index cebc0cf9c30e..7e2360f8e393 100644
--- a/drivers/tty/serial/sc16is7xx.c
+++ b/drivers/tty/serial/sc16is7xx.c
@@ -1313,7 +1313,19 @@ static int sc16is7xx_probe(struct device *dev,
s->p[u].irda_mode = true;
}
- /* Setup interrupt */
+ /*
+ * Setup interrupt. We first try to acquire the IRQ line as level IRQ.
+ * If that succeeds, we can allow sharing the interrupt as well.
+ * In case the interrupt controller doesn't support that, we fall
+ * back to a non-shared falling-edge trigger.
+ */
+ ret = devm_request_threaded_irq(dev, irq, NULL, sc16is7xx_irq,
+ IRQF_TRIGGER_LOW | IRQF_SHARED |
+ IRQF_ONESHOT,
+ dev_name(dev), s);
+ if (!ret)
+ return 0;
+
ret = devm_request_threaded_irq(dev, irq, NULL, sc16is7xx_irq,
IRQF_TRIGGER_FALLING | IRQF_ONESHOT,
dev_name(dev), s);
--
2.26.2
^ permalink raw reply related
* [PATCH v2 4/6] sc16is7xx: Use threaded IRQ
From: Daniel Mack @ 2020-05-19 18:21 UTC (permalink / raw)
To: devicetree, linux-serial
Cc: gregkh, robh+dt, jslaby, jringle, m.brock, pascal.huerst,
Daniel Mack
In-Reply-To: <20200519182147.218713-1-daniel@zonque.org>
Use a threaded IRQ handler to get rid of the irq_work kthread.
This also allows for the driver to use interrupts generated by
a threaded controller.
Signed-off-by: Daniel Mack <daniel@zonque.org>
---
drivers/tty/serial/sc16is7xx.c | 18 +++++-------------
1 file changed, 5 insertions(+), 13 deletions(-)
diff --git a/drivers/tty/serial/sc16is7xx.c b/drivers/tty/serial/sc16is7xx.c
index fcb2551a5df2..cebc0cf9c30e 100644
--- a/drivers/tty/serial/sc16is7xx.c
+++ b/drivers/tty/serial/sc16is7xx.c
@@ -328,7 +328,6 @@ struct sc16is7xx_port {
unsigned char buf[SC16IS7XX_FIFO_SIZE];
struct kthread_worker kworker;
struct task_struct *kworker_task;
- struct kthread_work irq_work;
struct mutex efr_lock;
struct sc16is7xx_one p[0];
};
@@ -711,9 +710,9 @@ static bool sc16is7xx_port_irq(struct sc16is7xx_port *s, int portno)
return true;
}
-static void sc16is7xx_ist(struct kthread_work *ws)
+static irqreturn_t sc16is7xx_irq(int irq, void *dev_id)
{
- struct sc16is7xx_port *s = to_sc16is7xx_port(ws, irq_work);
+ struct sc16is7xx_port *s = (struct sc16is7xx_port *)dev_id;
mutex_lock(&s->efr_lock);
@@ -728,13 +727,6 @@ static void sc16is7xx_ist(struct kthread_work *ws)
}
mutex_unlock(&s->efr_lock);
-}
-
-static irqreturn_t sc16is7xx_irq(int irq, void *dev_id)
-{
- struct sc16is7xx_port *s = (struct sc16is7xx_port *)dev_id;
-
- kthread_queue_work(&s->kworker, &s->irq_work);
return IRQ_HANDLED;
}
@@ -1230,7 +1222,6 @@ static int sc16is7xx_probe(struct device *dev,
mutex_init(&s->efr_lock);
kthread_init_worker(&s->kworker);
- kthread_init_work(&s->irq_work, sc16is7xx_ist);
s->kworker_task = kthread_run(kthread_worker_fn, &s->kworker,
"sc16is7xx");
if (IS_ERR(s->kworker_task)) {
@@ -1323,8 +1314,9 @@ static int sc16is7xx_probe(struct device *dev,
}
/* Setup interrupt */
- ret = devm_request_irq(dev, irq, sc16is7xx_irq,
- IRQF_TRIGGER_FALLING, dev_name(dev), s);
+ ret = devm_request_threaded_irq(dev, irq, NULL, sc16is7xx_irq,
+ IRQF_TRIGGER_FALLING | IRQF_ONESHOT,
+ dev_name(dev), s);
if (!ret)
return 0;
--
2.26.2
^ permalink raw reply related
* [PATCH v2 2/6] sc16is7xx: Add flag to activate IrDA mode
From: Daniel Mack @ 2020-05-19 18:21 UTC (permalink / raw)
To: devicetree, linux-serial
Cc: gregkh, robh+dt, jslaby, jringle, m.brock, pascal.huerst
In-Reply-To: <20200519182147.218713-1-daniel@zonque.org>
From: Pascal Huerst <pascal.huerst@gmail.com>
This series of uart controllers is able to work in IrDA mode.
Add per-port flag to the device-tree to enable that feature if needed.
Signed-off-by: Pascal Huerst <pascal.huerst@gmail.com>
---
drivers/tty/serial/sc16is7xx.c | 20 ++++++++++++++++++++
1 file changed, 20 insertions(+)
diff --git a/drivers/tty/serial/sc16is7xx.c b/drivers/tty/serial/sc16is7xx.c
index 7d3ae31cc720..cf9cf59bb04e 100644
--- a/drivers/tty/serial/sc16is7xx.c
+++ b/drivers/tty/serial/sc16is7xx.c
@@ -315,6 +315,7 @@ struct sc16is7xx_one {
struct kthread_work tx_work;
struct kthread_work reg_work;
struct sc16is7xx_one_config config;
+ bool irda_mode;
};
struct sc16is7xx_port {
@@ -994,6 +995,7 @@ static int sc16is7xx_config_rs485(struct uart_port *port,
static int sc16is7xx_startup(struct uart_port *port)
{
+ struct sc16is7xx_one *one = to_sc16is7xx_one(port, port);
struct sc16is7xx_port *s = dev_get_drvdata(port->dev);
unsigned int val;
@@ -1032,6 +1034,13 @@ static int sc16is7xx_startup(struct uart_port *port)
/* Now, initialize the UART */
sc16is7xx_port_write(port, SC16IS7XX_LCR_REG, SC16IS7XX_LCR_WORD_LEN_8);
+ /* Enable IrDA mode if requested in DT */
+ /* This bit must be written with LCR[7] = 0 */
+ sc16is7xx_port_update(port, SC16IS7XX_MCR_REG,
+ SC16IS7XX_MCR_IRDA_BIT,
+ one->irda_mode ?
+ SC16IS7XX_MCR_IRDA_BIT : 0);
+
/* Enable the Rx and Tx FIFO */
sc16is7xx_port_update(port, SC16IS7XX_EFCR_REG,
SC16IS7XX_EFCR_RXDISABLE_BIT |
@@ -1302,6 +1311,17 @@ static int sc16is7xx_probe(struct device *dev,
sc16is7xx_power(&s->p[i].port, 0);
}
+ if (dev->of_node) {
+ struct property *prop;
+ const __be32 *p;
+ u32 u;
+
+ of_property_for_each_u32(dev->of_node, "linux,irda-mode-ports",
+ prop, p, u)
+ if (u < devtype->nr_uart)
+ s->p[u].irda_mode = true;
+ }
+
/* Setup interrupt */
ret = devm_request_irq(dev, irq, sc16is7xx_irq,
flags, dev_name(dev), s);
--
2.26.2
^ permalink raw reply related
* [PATCH v2 3/6] sc16is7xx: Always use falling edge IRQ
From: Daniel Mack @ 2020-05-19 18:21 UTC (permalink / raw)
To: devicetree, linux-serial
Cc: gregkh, robh+dt, jslaby, jringle, m.brock, pascal.huerst,
Daniel Mack
In-Reply-To: <20200519182147.218713-1-daniel@zonque.org>
The driver currently only uses IRQF_TRIGGER_FALLING if the probing
happened without a device-tree setup. The device however will always
generate falling edges on its IRQ line, so let's use that flag in
all cases.
Signed-off-by: Daniel Mack <daniel@zonque.org>
---
drivers/tty/serial/sc16is7xx.c | 12 ++++--------
1 file changed, 4 insertions(+), 8 deletions(-)
diff --git a/drivers/tty/serial/sc16is7xx.c b/drivers/tty/serial/sc16is7xx.c
index cf9cf59bb04e..fcb2551a5df2 100644
--- a/drivers/tty/serial/sc16is7xx.c
+++ b/drivers/tty/serial/sc16is7xx.c
@@ -1185,7 +1185,7 @@ static int sc16is7xx_gpio_direction_output(struct gpio_chip *chip,
static int sc16is7xx_probe(struct device *dev,
const struct sc16is7xx_devtype *devtype,
- struct regmap *regmap, int irq, unsigned long flags)
+ struct regmap *regmap, int irq)
{
struct sched_param sched_param = { .sched_priority = MAX_RT_PRIO / 2 };
unsigned long freq = 0, *pfreq = dev_get_platdata(dev);
@@ -1324,7 +1324,7 @@ static int sc16is7xx_probe(struct device *dev,
/* Setup interrupt */
ret = devm_request_irq(dev, irq, sc16is7xx_irq,
- flags, dev_name(dev), s);
+ IRQF_TRIGGER_FALLING, dev_name(dev), s);
if (!ret)
return 0;
@@ -1398,7 +1398,6 @@ static struct regmap_config regcfg = {
static int sc16is7xx_spi_probe(struct spi_device *spi)
{
const struct sc16is7xx_devtype *devtype;
- unsigned long flags = 0;
struct regmap *regmap;
int ret;
@@ -1419,14 +1418,13 @@ static int sc16is7xx_spi_probe(struct spi_device *spi)
const struct spi_device_id *id_entry = spi_get_device_id(spi);
devtype = (struct sc16is7xx_devtype *)id_entry->driver_data;
- flags = IRQF_TRIGGER_FALLING;
}
regcfg.max_register = (0xf << SC16IS7XX_REG_SHIFT) |
(devtype->nr_uart - 1);
regmap = devm_regmap_init_spi(spi, ®cfg);
- return sc16is7xx_probe(&spi->dev, devtype, regmap, spi->irq, flags);
+ return sc16is7xx_probe(&spi->dev, devtype, regmap, spi->irq);
}
static int sc16is7xx_spi_remove(struct spi_device *spi)
@@ -1465,7 +1463,6 @@ static int sc16is7xx_i2c_probe(struct i2c_client *i2c,
const struct i2c_device_id *id)
{
const struct sc16is7xx_devtype *devtype;
- unsigned long flags = 0;
struct regmap *regmap;
if (i2c->dev.of_node) {
@@ -1474,14 +1471,13 @@ static int sc16is7xx_i2c_probe(struct i2c_client *i2c,
return -ENODEV;
} else {
devtype = (struct sc16is7xx_devtype *)id->driver_data;
- flags = IRQF_TRIGGER_FALLING;
}
regcfg.max_register = (0xf << SC16IS7XX_REG_SHIFT) |
(devtype->nr_uart - 1);
regmap = devm_regmap_init_i2c(i2c, ®cfg);
- return sc16is7xx_probe(&i2c->dev, devtype, regmap, i2c->irq, flags);
+ return sc16is7xx_probe(&i2c->dev, devtype, regmap, i2c->irq);
}
static int sc16is7xx_i2c_remove(struct i2c_client *client)
--
2.26.2
^ permalink raw reply related
* [PATCH v2 0/6] sc16is7xx: IrDA mode and threaded IRQs
From: Daniel Mack @ 2020-05-19 18:21 UTC (permalink / raw)
To: devicetree, linux-serial
Cc: gregkh, robh+dt, jslaby, jringle, m.brock, pascal.huerst,
Daniel Mack
This is v2 of the series.
v2:
* Change single bool properties into an array
(suggested by Rob Herring)
* Add a patch first try TRIGGER_LOW and SHARED interrupts, and then
fall back to FALLING edge if the IRQ controller fails to provide the
former (suggested by Maarten Brock)
* Add a patch to check for the device presence
Daniel Mack (4):
sc16is7xx: Always use falling edge IRQ
sc16is7xx: Use threaded IRQ
sc16is7xx: Allow sharing the IRQ line
sc16is7xx: Read the LSR register for basic device presence check
Pascal Huerst (2):
dt-bindings: sc16is7xx: Add flag to activate IrDA mode
sc16is7xx: Add flag to activate IrDA mode
.../bindings/serial/nxp,sc16is7xx.txt | 4 +
drivers/tty/serial/sc16is7xx.c | 73 +++++++++++++------
2 files changed, 56 insertions(+), 21 deletions(-)
--
2.26.2
^ permalink raw reply
* [PATCH v2 1/6] dt-bindings: sc16is7xx: Add flag to activate IrDA mode
From: Daniel Mack @ 2020-05-19 18:21 UTC (permalink / raw)
To: devicetree, linux-serial
Cc: gregkh, robh+dt, jslaby, jringle, m.brock, pascal.huerst
In-Reply-To: <20200519182147.218713-1-daniel@zonque.org>
From: Pascal Huerst <pascal.huerst@gmail.com>
This series of uart controllers is able to work in IrDA mode.
This adds a property to the device tree to enable that feature on
selected ports if needed.
Signed-off-by: Pascal Huerst <pascal.huerst@gmail.com>
---
Documentation/devicetree/bindings/serial/nxp,sc16is7xx.txt | 4 ++++
1 file changed, 4 insertions(+)
diff --git a/Documentation/devicetree/bindings/serial/nxp,sc16is7xx.txt b/Documentation/devicetree/bindings/serial/nxp,sc16is7xx.txt
index c1091a923a89..4d1f55abe876 100644
--- a/Documentation/devicetree/bindings/serial/nxp,sc16is7xx.txt
+++ b/Documentation/devicetree/bindings/serial/nxp,sc16is7xx.txt
@@ -21,6 +21,8 @@ Optional properties:
the second cell is used to specify the GPIO polarity:
0 = active high,
1 = active low.
+- linux,irda-mode-ports: An array that lists the indices of the port that
+ should operate in IrDA mode.
Example:
sc16is750: sc16is750@51 {
@@ -55,6 +57,8 @@ Optional properties:
the second cell is used to specify the GPIO polarity:
0 = active high,
1 = active low.
+- linux,irda-mode-ports: An array that lists the indices of the port that
+ should operate in IrDA mode.
Example:
sc16is750: sc16is750@0 {
--
2.26.2
^ permalink raw reply related
* Re: [PATCH v3 2/5] dt-bindings: net: mdio-gpio: add compatible for microchip,mdio-smi0
From: Rob Herring @ 2020-05-19 18:21 UTC (permalink / raw)
To: Michael Grzeschik; +Cc: kernel, andrew, f.fainelli, devicetree, netdev, davem
In-Reply-To: <20200508154343.6074-3-m.grzeschik@pengutronix.de>
On Fri, 8 May 2020 17:43:40 +0200, Michael Grzeschik wrote:
> Microchip SMI0 Mode is a special mode, where the MDIO Read/Write
> commands are part of the PHY Address and the OP Code is always 0. We add
> the compatible for this special mode of the bitbanged mdio driver.
>
> Cc: devicetree@vger.kernel.org
> Signed-off-by: Michael Grzeschik <m.grzeschik@pengutronix.de>
> ---
> Documentation/devicetree/bindings/net/mdio-gpio.txt | 1 +
> 1 file changed, 1 insertion(+)
>
Acked-by: Rob Herring <robh@kernel.org>
^ permalink raw reply
* Re: [PATCH v2 1/4] dt-bindings: iio: imu: bmi160: convert txt format to yaml
From: Rob Herring @ 2020-05-19 18:20 UTC (permalink / raw)
To: Jonathan Albrieux
Cc: linux-kernel,
open list:OPEN FIRMWARE AND FLATTENED DEVICE TREE BINDINGS,
Hartmut Knaack, Lars-Peter Clausen,
open list:IIO SUBSYSTEM AND DRIVERS, Peter Meerwald-Stadler,
Jonathan Cameron
In-Reply-To: <20200519075111.6356-2-jonathan.albrieux@gmail.com>
On Tue, May 19, 2020 at 09:50:57AM +0200, Jonathan Albrieux wrote:
> Converts documentation from txt format to yaml
>
> Signed-off-by: Jonathan Albrieux <jonathan.albrieux@gmail.com>
> ---
> .../devicetree/bindings/iio/imu/bmi160.txt | 37 --------
> .../devicetree/bindings/iio/imu/bmi160.yaml | 84 +++++++++++++++++++
> 2 files changed, 84 insertions(+), 37 deletions(-)
> delete mode 100644 Documentation/devicetree/bindings/iio/imu/bmi160.txt
> create mode 100644 Documentation/devicetree/bindings/iio/imu/bmi160.yaml
Use compatible string for filename: bosch,bmi160.yaml
>
> diff --git a/Documentation/devicetree/bindings/iio/imu/bmi160.txt b/Documentation/devicetree/bindings/iio/imu/bmi160.txt
> deleted file mode 100644
> index 900c169de00f..000000000000
> --- a/Documentation/devicetree/bindings/iio/imu/bmi160.txt
> +++ /dev/null
> @@ -1,37 +0,0 @@
> -Bosch BMI160 - Inertial Measurement Unit with Accelerometer, Gyroscope
> -and externally connectable Magnetometer
> -
> -https://www.bosch-sensortec.com/bst/products/all_products/bmi160
> -
> -Required properties:
> - - compatible : should be "bosch,bmi160"
> - - reg : the I2C address or SPI chip select number of the sensor
> - - spi-max-frequency : set maximum clock frequency (only for SPI)
> -
> -Optional properties:
> - - interrupts : interrupt mapping for IRQ
> - - interrupt-names : set to "INT1" if INT1 pin should be used as interrupt
> - input, set to "INT2" if INT2 pin should be used instead
> - - drive-open-drain : set if the specified interrupt pin should be configured as
> - open drain. If not set, defaults to push-pull.
> -
> -Examples:
> -
> -bmi160@68 {
> - compatible = "bosch,bmi160";
> - reg = <0x68>;
> -
> - interrupt-parent = <&gpio4>;
> - interrupts = <12 IRQ_TYPE_EDGE_RISING>;
> - interrupt-names = "INT1";
> -};
> -
> -bmi160@0 {
> - compatible = "bosch,bmi160";
> - reg = <0>;
> - spi-max-frequency = <10000000>;
> -
> - interrupt-parent = <&gpio2>;
> - interrupts = <12 IRQ_TYPE_LEVEL_LOW>;
> - interrupt-names = "INT2";
> -};
> diff --git a/Documentation/devicetree/bindings/iio/imu/bmi160.yaml b/Documentation/devicetree/bindings/iio/imu/bmi160.yaml
> new file mode 100644
> index 000000000000..6b464ce5ed0b
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/iio/imu/bmi160.yaml
> @@ -0,0 +1,84 @@
> +# SPDX-License-Identifier: (GPL-2.0 OR BSD-2-Clause)
> +%YAML 1.2
> +---
> +$id: http://devicetree.org/schemas/iio/imu/bmi160.yaml#
> +$schema: http://devicetree.org/meta-schemas/core.yaml#
> +
> +title: Bosch BMI160
> +
> +maintainers:
> + - can't find a mantainer, author is Daniel Baluta <daniel.baluta@intel.com>
Would help to Cc him perhaps.
> +
> +description: |
> + Inertial Measurement Unit with Accelerometer, Gyroscope and externally
> + connectable Magnetometer
> + https://www.bosch-sensortec.com/bst/products/all_products/bmi160
> +
> +properties:
> + compatible:
> + const: bosch,bmi160
> +
> + reg:
> + maxItems: 1
> + description: the I2C address or SPI chip select number of the sensor
> +
> + spi-max-frequency:
> + maxItems: 1
> + description: set maximum clock frequency (required only for SPI)
> +
> + interrupts:
> + maxItems: 1
> + description: interrupt mapping for IRQ
No need for description if not adding anything unique for this device.
> +
> + interrupt-names:
> + minItems: 1
> + maxItems: 1
> + items:
> + enum:
> + - INT1
> + - INT2
Just the enum is enough.
> + description: |
> + set to "INT1" if INT1 pin should be used as interrupt input, set
> + to "INT2" if INT2 pin should be used instead
> +
> + drive-open-drain:
> + description: |
> + set if the specified interrupt pin should be configured as
> + open drain. If not set, defaults to push-pull.
> +
> +required:
> + - compatible
> + - reg
> +
> +examples:
> + - |
> + // Example for I2C
> + i2c@78b7000 {
> + reg = <0x78b6000 0x600>;
> + #address-cells = <1>;
> + #size-cells = <0>;
> +
> + bmi160@68 {
> + compatible = "bosch,bmi160";
> + reg = <0x68>;
> + interrupt-parent = <&gpio4>;
> + interrupts = <12 1>;
> + interrupt-names = "INT1";
> + };
> + - |
> + // Example for SPI
> + spi@78b7000 {
> + reg = <0x78b7000 0x600>,
> + <0x7884000 0x23000>;
> + #address-cells = <1>;
> + #size-cells = <0>;
> +
> + bmi160@0 {
> + compatible = "bosch,bmi160";
> + reg = <0>;
> + spi-max-frequency = <10000000>;
> + interrupt-parent = <&gpio2>;
> + interrupts = <12 1>;
> + interrupt-names = "INT2";
> + };
> + };
> --
> 2.17.1
>
^ permalink raw reply
* Re: [PATCH v7 2/7] IIO: Ingenic JZ47xx: Error check clk_enable calls.
From: Jonathan Cameron @ 2020-05-19 18:15 UTC (permalink / raw)
To: Artur Rojek
Cc: Dmitry Torokhov, Rob Herring, Mark Rutland, Jonathan Cameron,
Paul Cercueil, Andy Shevchenko, Heiko Stuebner, Ezequiel Garcia,
linux-input, devicetree, linux-iio, linux-kernel
In-Reply-To: <20200517194904.34758-2-contact@artur-rojek.eu>
On Sun, 17 May 2020 21:48:59 +0200
Artur Rojek <contact@artur-rojek.eu> wrote:
> Introduce error checks for the clk_enable calls used in this driver.
> As part of the changes, move clk_enable/clk_disable calls out of
> ingenic_adc_set_config and into respective logic of its callers.
>
> Signed-off-by: Artur Rojek <contact@artur-rojek.eu>
> Tested-by: Paul Cercueil <paul@crapouillou.net>
One trivial thing inline.
> ---
>
> Changes:
>
> v6: new patch
>
> v7: no change
>
> drivers/iio/adc/ingenic-adc.c | 23 ++++++++++++++++++++---
> 1 file changed, 20 insertions(+), 3 deletions(-)
>
> diff --git a/drivers/iio/adc/ingenic-adc.c b/drivers/iio/adc/ingenic-adc.c
> index 39c0a609fc94..6c3bbba7c44b 100644
> --- a/drivers/iio/adc/ingenic-adc.c
> +++ b/drivers/iio/adc/ingenic-adc.c
> @@ -73,7 +73,6 @@ static void ingenic_adc_set_config(struct ingenic_adc *adc,
> {
> uint32_t cfg;
>
> - clk_enable(adc->clk);
> mutex_lock(&adc->lock);
>
> cfg = readl(adc->base + JZ_ADC_REG_CFG) & ~mask;
> @@ -81,7 +80,6 @@ static void ingenic_adc_set_config(struct ingenic_adc *adc,
> writel(cfg, adc->base + JZ_ADC_REG_CFG);
>
> mutex_unlock(&adc->lock);
> - clk_disable(adc->clk);
> }
>
> static void ingenic_adc_enable(struct ingenic_adc *adc,
> @@ -124,6 +122,8 @@ static int ingenic_adc_write_raw(struct iio_dev *iio_dev,
> long m)
> {
> struct ingenic_adc *adc = iio_priv(iio_dev);
> + struct device *dev = iio_dev->dev.parent;
> + int ret;
>
> switch (m) {
> case IIO_CHAN_INFO_SCALE:
> @@ -131,6 +131,14 @@ static int ingenic_adc_write_raw(struct iio_dev *iio_dev,
> case INGENIC_ADC_BATTERY:
> if (!adc->soc_data->battery_vref_mode)
> return -EINVAL;
> +
> + ret = clk_enable(adc->clk);
> + if (ret) {
> + dev_err(dev, "Failed to enable clock: %d\n",
> + ret);
> + return ret;
> + }
> +
> if (val > JZ_ADC_BATTERY_LOW_VREF) {
> ingenic_adc_set_config(adc,
> JZ_ADC_REG_CFG_BAT_MD,
> @@ -142,6 +150,9 @@ static int ingenic_adc_write_raw(struct iio_dev *iio_dev,
> JZ_ADC_REG_CFG_BAT_MD);
> adc->low_vref_mode = true;
> }
> +
> + clk_disable(adc->clk);
> +
> return 0;
> default:
> return -EINVAL;
> @@ -317,6 +328,13 @@ static int ingenic_adc_read_chan_info_raw(struct ingenic_adc *adc,
> int *val)
> {
> int bit, ret, engine = (chan->channel == INGENIC_ADC_BATTERY);
> + struct device *dev = iio_priv_to_dev(adc)->dev.parent;
> +
> + ret = clk_enable(adc->clk);
> + if (ret) {
> + dev_err(dev, "Failed to enable clock: %d\n", ret);
> + return ret;
> + }
It almost certainly doesn't matter, but if we are going to move the clk enable
outside the lock, we should do the same with the disable.
>
> /* We cannot sample AUX/AUX2 in parallel. */
> mutex_lock(&adc->aux_lock);
> @@ -325,7 +343,6 @@ static int ingenic_adc_read_chan_info_raw(struct ingenic_adc *adc,
> ingenic_adc_set_config(adc, JZ_ADC_REG_CFG_AUX_MD, bit);
> }
>
> - clk_enable(adc->clk);
> ret = ingenic_adc_capture(adc, engine);
> if (ret)
> goto out;
^ permalink raw reply
* Re: [PATCH v5 3/5] remoteproc: qcom: Update PIL relocation info on load
From: rishabhb @ 2020-05-19 18:14 UTC (permalink / raw)
To: Bjorn Andersson
Cc: Andy Gross, Ohad Ben-Cohen, Rob Herring, linux-arm-msm,
linux-remoteproc, devicetree, linux-kernel,
linux-remoteproc-owner
In-Reply-To: <20200513055641.1413100-4-bjorn.andersson@linaro.org>
On 2020-05-12 22:56, Bjorn Andersson wrote:
> Update the PIL relocation information in IMEM with information about
> where the firmware for various remoteprocs are loaded.
>
> Signed-off-by: Bjorn Andersson <bjorn.andersson@linaro.org>
> ---
>
> Changes since v4:
> - Dropped unnecessary comment about ignoring return value.
>
> drivers/remoteproc/Kconfig | 3 +++
> drivers/remoteproc/qcom_q6v5_adsp.c | 16 +++++++++++++---
> drivers/remoteproc/qcom_q6v5_mss.c | 3 +++
> drivers/remoteproc/qcom_q6v5_pas.c | 15 ++++++++++++---
> drivers/remoteproc/qcom_q6v5_wcss.c | 14 +++++++++++---
> drivers/remoteproc/qcom_wcnss.c | 14 +++++++++++---
> 6 files changed, 53 insertions(+), 12 deletions(-)
>
> diff --git a/drivers/remoteproc/Kconfig b/drivers/remoteproc/Kconfig
> index 8088ca4dd6dc..6bd42a411ca8 100644
> --- a/drivers/remoteproc/Kconfig
> +++ b/drivers/remoteproc/Kconfig
> @@ -126,6 +126,7 @@ config QCOM_Q6V5_ADSP
> depends on RPMSG_QCOM_GLINK_SMEM || RPMSG_QCOM_GLINK_SMEM=n
> depends on QCOM_SYSMON || QCOM_SYSMON=n
> select MFD_SYSCON
> + select QCOM_PIL_INFO
> select QCOM_MDT_LOADER
> select QCOM_Q6V5_COMMON
> select QCOM_RPROC_COMMON
> @@ -158,6 +159,7 @@ config QCOM_Q6V5_PAS
> depends on RPMSG_QCOM_GLINK_SMEM || RPMSG_QCOM_GLINK_SMEM=n
> depends on QCOM_SYSMON || QCOM_SYSMON=n
> select MFD_SYSCON
> + select QCOM_PIL_INFO
> select QCOM_MDT_LOADER
> select QCOM_Q6V5_COMMON
> select QCOM_RPROC_COMMON
> @@ -209,6 +211,7 @@ config QCOM_WCNSS_PIL
> depends on QCOM_SMEM
> depends on QCOM_SYSMON || QCOM_SYSMON=n
> select QCOM_MDT_LOADER
> + select QCOM_PIL_INFO
> select QCOM_RPROC_COMMON
> select QCOM_SCM
> help
> diff --git a/drivers/remoteproc/qcom_q6v5_adsp.c
> b/drivers/remoteproc/qcom_q6v5_adsp.c
> index d2a2574dcf35..c539e89664cb 100644
> --- a/drivers/remoteproc/qcom_q6v5_adsp.c
> +++ b/drivers/remoteproc/qcom_q6v5_adsp.c
> @@ -26,6 +26,7 @@
> #include <linux/soc/qcom/smem_state.h>
>
> #include "qcom_common.h"
> +#include "qcom_pil_info.h"
> #include "qcom_q6v5.h"
> #include "remoteproc_internal.h"
>
> @@ -82,6 +83,7 @@ struct qcom_adsp {
> unsigned int halt_lpass;
>
> int crash_reason_smem;
> + const char *info_name;
>
> struct completion start_done;
> struct completion stop_done;
> @@ -164,10 +166,17 @@ static int qcom_adsp_shutdown(struct qcom_adsp
> *adsp)
> static int adsp_load(struct rproc *rproc, const struct firmware *fw)
> {
> struct qcom_adsp *adsp = (struct qcom_adsp *)rproc->priv;
> + int ret;
> +
> + ret = qcom_mdt_load_no_init(adsp->dev, fw, rproc->firmware, 0,
> + adsp->mem_region, adsp->mem_phys,
> + adsp->mem_size, &adsp->mem_reloc);
> + if (ret)
> + return ret;
> +
> + qcom_pil_info_store(adsp->info_name, adsp->mem_reloc,
> adsp->mem_size);
>
> - return qcom_mdt_load_no_init(adsp->dev, fw, rproc->firmware, 0,
> - adsp->mem_region, adsp->mem_phys, adsp->mem_size,
> - &adsp->mem_reloc);
> + return 0;
> }
>
> static int adsp_start(struct rproc *rproc)
> @@ -436,6 +445,7 @@ static int adsp_probe(struct platform_device *pdev)
> adsp = (struct qcom_adsp *)rproc->priv;
> adsp->dev = &pdev->dev;
> adsp->rproc = rproc;
> + adsp->info_name = desc->sysmon_name;
> platform_set_drvdata(pdev, adsp);
>
> ret = adsp_alloc_memory_region(adsp);
> diff --git a/drivers/remoteproc/qcom_q6v5_mss.c
> b/drivers/remoteproc/qcom_q6v5_mss.c
> index c4936f4d1e80..fdbcae11ae64 100644
> --- a/drivers/remoteproc/qcom_q6v5_mss.c
> +++ b/drivers/remoteproc/qcom_q6v5_mss.c
> @@ -29,6 +29,7 @@
>
> #include "remoteproc_internal.h"
> #include "qcom_common.h"
> +#include "qcom_pil_info.h"
> #include "qcom_q6v5.h"
>
> #include <linux/qcom_scm.h>
> @@ -1221,6 +1222,8 @@ static int q6v5_mpss_load(struct q6v5 *qproc)
> else if (ret < 0)
> dev_err(qproc->dev, "MPSS authentication failed: %d\n", ret);
>
> + qcom_pil_info_store("modem", mpss_reloc, qproc->mpss_size);
> +
> release_firmware:
> release_firmware(fw);
> out:
> diff --git a/drivers/remoteproc/qcom_q6v5_pas.c
> b/drivers/remoteproc/qcom_q6v5_pas.c
> index 3bb69f58e086..84cb19231c35 100644
> --- a/drivers/remoteproc/qcom_q6v5_pas.c
> +++ b/drivers/remoteproc/qcom_q6v5_pas.c
> @@ -25,6 +25,7 @@
> #include <linux/soc/qcom/smem_state.h>
>
> #include "qcom_common.h"
> +#include "qcom_pil_info.h"
> #include "qcom_q6v5.h"
> #include "remoteproc_internal.h"
>
> @@ -64,6 +65,7 @@ struct qcom_adsp {
> int pas_id;
> int crash_reason_smem;
> bool has_aggre2_clk;
> + const char *info_name;
>
> struct completion start_done;
> struct completion stop_done;
> @@ -117,11 +119,17 @@ static void adsp_pds_disable(struct qcom_adsp
> *adsp, struct device **pds,
> static int adsp_load(struct rproc *rproc, const struct firmware *fw)
> {
> struct qcom_adsp *adsp = (struct qcom_adsp *)rproc->priv;
> + int ret;
>
> - return qcom_mdt_load(adsp->dev, fw, rproc->firmware, adsp->pas_id,
> - adsp->mem_region, adsp->mem_phys, adsp->mem_size,
> - &adsp->mem_reloc);
> + ret = qcom_mdt_load(adsp->dev, fw, rproc->firmware, adsp->pas_id,
> + adsp->mem_region, adsp->mem_phys, adsp->mem_size,
> + &adsp->mem_reloc);
> + if (ret)
> + return ret;
>
> + qcom_pil_info_store(adsp->info_name, adsp->mem_reloc,
> adsp->mem_size);
mem_reloc is used to calculate offset and then we again add that offset
to the
ioremapped region base. So we should pass adsp->mem_phys as start here?
> +
> + return 0;
> }
>
> static int adsp_start(struct rproc *rproc)
> @@ -405,6 +413,7 @@ static int adsp_probe(struct platform_device *pdev)
> adsp->rproc = rproc;
> adsp->pas_id = desc->pas_id;
> adsp->has_aggre2_clk = desc->has_aggre2_clk;
> + adsp->info_name = desc->sysmon_name;
> platform_set_drvdata(pdev, adsp);
>
> ret = adsp_alloc_memory_region(adsp);
> diff --git a/drivers/remoteproc/qcom_q6v5_wcss.c
> b/drivers/remoteproc/qcom_q6v5_wcss.c
> index f1924b740a10..962e37a86b8b 100644
> --- a/drivers/remoteproc/qcom_q6v5_wcss.c
> +++ b/drivers/remoteproc/qcom_q6v5_wcss.c
> @@ -421,10 +421,18 @@ static void *q6v5_wcss_da_to_va(struct rproc
> *rproc, u64 da, size_t len)
> static int q6v5_wcss_load(struct rproc *rproc, const struct firmware
> *fw)
> {
> struct q6v5_wcss *wcss = rproc->priv;
> + int ret;
> +
> + ret = qcom_mdt_load_no_init(wcss->dev, fw, rproc->firmware,
> + 0, wcss->mem_region, wcss->mem_phys,
> + wcss->mem_size, &wcss->mem_reloc);
> + if (ret)
> + return ret;
> +
> + /* Failures only affect post mortem debugging, so ignore return value
> */
> + qcom_pil_info_store("wcnss", wcss->mem_reloc, wcss->mem_size);
>
> - return qcom_mdt_load_no_init(wcss->dev, fw, rproc->firmware,
> - 0, wcss->mem_region, wcss->mem_phys,
> - wcss->mem_size, &wcss->mem_reloc);
> + return ret;
> }
>
> static const struct rproc_ops q6v5_wcss_ops = {
> diff --git a/drivers/remoteproc/qcom_wcnss.c
> b/drivers/remoteproc/qcom_wcnss.c
> index 5d65e1a9329a..229482b3231f 100644
> --- a/drivers/remoteproc/qcom_wcnss.c
> +++ b/drivers/remoteproc/qcom_wcnss.c
> @@ -27,6 +27,7 @@
>
> #include "qcom_common.h"
> #include "remoteproc_internal.h"
> +#include "qcom_pil_info.h"
> #include "qcom_wcnss.h"
>
> #define WCNSS_CRASH_REASON_SMEM 422
> @@ -145,10 +146,17 @@ void qcom_wcnss_assign_iris(struct qcom_wcnss
> *wcnss,
> static int wcnss_load(struct rproc *rproc, const struct firmware *fw)
> {
> struct qcom_wcnss *wcnss = (struct qcom_wcnss *)rproc->priv;
> + int ret;
> +
> + ret = qcom_mdt_load(wcnss->dev, fw, rproc->firmware, WCNSS_PAS_ID,
> + wcnss->mem_region, wcnss->mem_phys,
> + wcnss->mem_size, &wcnss->mem_reloc);
> + if (ret)
> + return ret;
> +
> + qcom_pil_info_store("wcnss", wcnss->mem_reloc, wcnss->mem_size);
>
> - return qcom_mdt_load(wcnss->dev, fw, rproc->firmware, WCNSS_PAS_ID,
> - wcnss->mem_region, wcnss->mem_phys,
> - wcnss->mem_size, &wcnss->mem_reloc);
> + return 0;
> }
>
> static void wcnss_indicate_nv_download(struct qcom_wcnss *wcnss)
^ permalink raw reply
* Re: [PATCH v1 4/4] of: platform: Batch fwnode parsing when adding all top level devices
From: Saravana Kannan @ 2020-05-19 18:02 UTC (permalink / raw)
To: Marek Szyprowski
Cc: Greg Kroah-Hartman, Rafael J. Wysocki, Rob Herring, Frank Rowand,
Len Brown, Android Kernel Team, LKML,
open list:OPEN FIRMWARE AND FLATTENED DEVICE TREE BINDINGS,
ACPI Devel Maling List, Ji Luo, Linux Samsung SOC
In-Reply-To: <8dd9ecc2-0c61-49b7-d485-b169eb721712@samsung.com>
On Tue, May 19, 2020 at 3:32 AM Marek Szyprowski
<m.szyprowski@samsung.com> wrote:
>
> Hi
>
> On 19.05.2020 09:11, Marek Szyprowski wrote:
> > On 19.05.2020 08:48, Saravana Kannan wrote:
> >> On Mon, May 18, 2020 at 11:25 PM Marek Szyprowski
> >> <m.szyprowski@samsung.com> wrote:
> >>> On 15.05.2020 07:35, Saravana Kannan wrote:
> >>>> The fw_devlink_pause() and fw_devlink_resume() APIs allow batching the
> >>>> parsing of the device tree nodes when a lot of devices are added. This
> >>>> will significantly cut down parsing time (as much a 1 second on some
> >>>> systems). So, use them when adding devices for all the top level
> >>>> device
> >>>> tree nodes in a system.
> >>>>
> >>>> Signed-off-by: Saravana Kannan <saravanak@google.com>
> >>> This patch recently landed in linux-next 20200518. Sadly, it causes
> >>> regression on Samsung Exynos5433-based TM2e board:
> >>>
> >>> ...
> >>>
> >>> Both issues, the lack of DMA for SPI device and Synchronous abort in
> >>> I2S
> >>> probe are new after applying this patch. I'm trying to investigate
> >>> which
> >>> resources are missing and why. The latter issue means typically that
> >>> the
> >>> registers for the given device has been accessed without enabling the
> >>> needed clocks or power domains.
> >> Did you try this copy-pasta fix that I sent later?
> >> https://lore.kernel.org/lkml/20200517173453.157703-1-saravanak@google.com/
> >>
> >>
> >> Not every system would need it (my test setup didn't), but it helps
> >> some cases.
> >>
> >> If that fix doesn't help, then some tips for debugging the failing
> >> drivers.
> >> What this pause/resume patch effectively (not explicitly) does is:
> >> 1. Doesn't immediately probe the devices as they are added in
> >> of_platform_default_populate_init()
> >> 2. Adds them in order to the deferred probe list.
> >> 3. Then kicks off deferred probe on them in the order they were added.
> >>
> >> These drivers are just not handling -EPROBE_DEFER correctly or
> >> assuming probe order and that's causing these issues.
> >>
> >> So, we can either fix that or you can try adding some code to flush
> >> the deferred probe workqueue at the end of fw_devlink_resume().
> >>
> >> Let me know how it goes.
> >
> > So far it looks that your patch revealed a hidden issue in exynos5433
> > clocks configuration, because adding clk_ignore_unused parameter to
> > kernel command line fixes the boot. I'm still investigating it, so
> > probable you can ignore my regression report. I will let you know asap
> > I finish checking it.
> >
> Okay, I confirm that the issue is in the Exynos I2S driver and
> Exynos5433 clock provider. I've posted a quick workaround. I'm sorry for
> the noise, your patch is fine.
Thanks for debugging and finding the real issue. I tried finding your
patches, but couldn't. Can you point me to a lore.kernel.org link? I'm
just curious to see what the issue was.
I'm guessing you didn't need to pick up this one?
https://lore.kernel.org/lkml/20200517173453.157703-1-saravanak@google.com/
-Saravana
^ permalink raw reply
* [PATCH v7 24/24] iommu/arm-smmu-v3: Add support for PRI
From: Jean-Philippe Brucker @ 2020-05-19 17:55 UTC (permalink / raw)
To: iommu, devicetree, linux-arm-kernel, linux-pci, linux-mm
Cc: joro, catalin.marinas, will, robin.murphy, kevin.tian, baolu.lu,
Jonathan.Cameron, jacob.jun.pan, christian.koenig, felix.kuehling,
zhangfei.gao, jgg, xuzaibo, fenghua.yu, hch,
Jean-Philippe Brucker
In-Reply-To: <20200519175502.2504091-1-jean-philippe@linaro.org>
For PCI devices that support it, enable the PRI capability and handle PRI
Page Requests with the generic fault handler. It is enabled on demand by
iommu_sva_device_init().
Signed-off-by: Jean-Philippe Brucker <jean-philippe@linaro.org>
---
drivers/iommu/arm-smmu-v3.c | 286 +++++++++++++++++++++++++++++-------
1 file changed, 236 insertions(+), 50 deletions(-)
diff --git a/drivers/iommu/arm-smmu-v3.c b/drivers/iommu/arm-smmu-v3.c
index 9ec2f362802b..b4c49c6fe221 100644
--- a/drivers/iommu/arm-smmu-v3.c
+++ b/drivers/iommu/arm-smmu-v3.c
@@ -254,6 +254,7 @@
#define STRTAB_STE_1_S1COR GENMASK_ULL(5, 4)
#define STRTAB_STE_1_S1CSH GENMASK_ULL(7, 6)
+#define STRTAB_STE_1_PPAR (1UL << 18)
#define STRTAB_STE_1_S1STALLD (1UL << 27)
#define STRTAB_STE_1_EATS GENMASK_ULL(29, 28)
@@ -384,6 +385,9 @@
#define CMDQ_PRI_0_SID GENMASK_ULL(63, 32)
#define CMDQ_PRI_1_GRPID GENMASK_ULL(8, 0)
#define CMDQ_PRI_1_RESP GENMASK_ULL(13, 12)
+#define CMDQ_PRI_1_RESP_FAILURE 0UL
+#define CMDQ_PRI_1_RESP_INVALID 1UL
+#define CMDQ_PRI_1_RESP_SUCCESS 2UL
#define CMDQ_RESUME_0_SID GENMASK_ULL(63, 32)
#define CMDQ_RESUME_0_RESP_TERM 0UL
@@ -456,12 +460,6 @@ module_param_named(disable_bypass, disable_bypass, bool, S_IRUGO);
MODULE_PARM_DESC(disable_bypass,
"Disable bypass streams such that incoming transactions from devices that are not attached to an iommu domain will report an abort back to the device and will not be allowed to pass through the SMMU.");
-enum pri_resp {
- PRI_RESP_DENY = 0,
- PRI_RESP_FAIL = 1,
- PRI_RESP_SUCC = 2,
-};
-
enum arm_smmu_msi_index {
EVTQ_MSI_INDEX,
GERROR_MSI_INDEX,
@@ -548,7 +546,7 @@ struct arm_smmu_cmdq_ent {
u32 sid;
u32 ssid;
u16 grpid;
- enum pri_resp resp;
+ u8 resp;
} pri;
#define CMDQ_OP_RESUME 0x44
@@ -626,6 +624,7 @@ struct arm_smmu_evtq {
struct arm_smmu_priq {
struct arm_smmu_queue q;
+ struct iopf_queue *iopf;
};
/* High-level stream table and context descriptor structures */
@@ -760,6 +759,8 @@ struct arm_smmu_master {
unsigned int num_streams;
bool ats_enabled;
bool stall_enabled;
+ bool pri_supported;
+ bool prg_resp_needs_ssid;
bool sva_enabled;
struct list_head bonds;
unsigned int ssid_bits;
@@ -1064,14 +1065,6 @@ static int arm_smmu_cmdq_build_cmd(u64 *cmd, struct arm_smmu_cmdq_ent *ent)
cmd[0] |= FIELD_PREP(CMDQ_PRI_0_SSID, ent->pri.ssid);
cmd[0] |= FIELD_PREP(CMDQ_PRI_0_SID, ent->pri.sid);
cmd[1] |= FIELD_PREP(CMDQ_PRI_1_GRPID, ent->pri.grpid);
- switch (ent->pri.resp) {
- case PRI_RESP_DENY:
- case PRI_RESP_FAIL:
- case PRI_RESP_SUCC:
- break;
- default:
- return -EINVAL;
- }
cmd[1] |= FIELD_PREP(CMDQ_PRI_1_RESP, ent->pri.resp);
break;
case CMDQ_OP_RESUME:
@@ -1651,6 +1644,7 @@ static int arm_smmu_page_response(struct device *dev,
{
struct arm_smmu_cmdq_ent cmd = {0};
struct arm_smmu_master *master = dev_iommu_priv_get(dev);
+ bool pasid_valid = resp->flags & IOMMU_PAGE_RESP_PASID_VALID;
int sid = master->streams[0].id;
if (master->stall_enabled) {
@@ -1668,8 +1662,27 @@ static int arm_smmu_page_response(struct device *dev,
default:
return -EINVAL;
}
+ } else if (master->pri_supported) {
+ cmd.opcode = CMDQ_OP_PRI_RESP;
+ cmd.substream_valid = pasid_valid &&
+ master->prg_resp_needs_ssid;
+ cmd.pri.sid = sid;
+ cmd.pri.ssid = resp->pasid;
+ cmd.pri.grpid = resp->grpid;
+ switch (resp->code) {
+ case IOMMU_PAGE_RESP_FAILURE:
+ cmd.pri.resp = CMDQ_PRI_1_RESP_FAILURE;
+ break;
+ case IOMMU_PAGE_RESP_INVALID:
+ cmd.pri.resp = CMDQ_PRI_1_RESP_INVALID;
+ break;
+ case IOMMU_PAGE_RESP_SUCCESS:
+ cmd.pri.resp = CMDQ_PRI_1_RESP_SUCCESS;
+ break;
+ default:
+ return -EINVAL;
+ }
} else {
- /* TODO: insert PRI response here */
return -ENODEV;
}
@@ -2253,6 +2266,9 @@ static void arm_smmu_write_strtab_ent(struct arm_smmu_master *master, u32 sid,
FIELD_PREP(STRTAB_STE_1_S1CSH, ARM_SMMU_SH_ISH) |
FIELD_PREP(STRTAB_STE_1_STRW, strw));
+ if (master->prg_resp_needs_ssid)
+ dst[1] |= STRTAB_STE_1_PPAR;
+
if (smmu->features & ARM_SMMU_FEAT_STALLS &&
!master->stall_enabled)
dst[1] |= cpu_to_le64(STRTAB_STE_1_S1STALLD);
@@ -2497,61 +2513,110 @@ static irqreturn_t arm_smmu_evtq_thread(int irq, void *dev)
static void arm_smmu_handle_ppr(struct arm_smmu_device *smmu, u64 *evt)
{
- u32 sid, ssid;
- u16 grpid;
- bool ssv, last;
-
- sid = FIELD_GET(PRIQ_0_SID, evt[0]);
- ssv = FIELD_GET(PRIQ_0_SSID_V, evt[0]);
- ssid = ssv ? FIELD_GET(PRIQ_0_SSID, evt[0]) : 0;
- last = FIELD_GET(PRIQ_0_PRG_LAST, evt[0]);
- grpid = FIELD_GET(PRIQ_1_PRG_IDX, evt[1]);
-
- dev_info(smmu->dev, "unexpected PRI request received:\n");
- dev_info(smmu->dev,
- "\tsid 0x%08x.0x%05x: [%u%s] %sprivileged %s%s%s access at iova 0x%016llx\n",
- sid, ssid, grpid, last ? "L" : "",
- evt[0] & PRIQ_0_PERM_PRIV ? "" : "un",
- evt[0] & PRIQ_0_PERM_READ ? "R" : "",
- evt[0] & PRIQ_0_PERM_WRITE ? "W" : "",
- evt[0] & PRIQ_0_PERM_EXEC ? "X" : "",
- evt[1] & PRIQ_1_ADDR_MASK);
-
- if (last) {
- struct arm_smmu_cmdq_ent cmd = {
- .opcode = CMDQ_OP_PRI_RESP,
- .substream_valid = ssv,
- .pri = {
- .sid = sid,
- .ssid = ssid,
- .grpid = grpid,
- .resp = PRI_RESP_DENY,
- },
+ u32 sid = FIELD_PREP(PRIQ_0_SID, evt[0]);
+
+ bool pasid_valid, last;
+ struct arm_smmu_master *master;
+ struct iommu_fault_event fault_evt = {
+ .fault.type = IOMMU_FAULT_PAGE_REQ,
+ .fault.prm = {
+ .pasid = FIELD_GET(PRIQ_0_SSID, evt[0]),
+ .grpid = FIELD_GET(PRIQ_1_PRG_IDX, evt[1]),
+ .addr = evt[1] & PRIQ_1_ADDR_MASK,
+ },
+ };
+ struct iommu_fault_page_request *pr = &fault_evt.fault.prm;
+
+ pasid_valid = evt[0] & PRIQ_0_SSID_V;
+ last = evt[0] & PRIQ_0_PRG_LAST;
+
+ /* Discard Stop PASID marker, it isn't used */
+ if (!(evt[0] & (PRIQ_0_PERM_READ | PRIQ_0_PERM_WRITE)) && last)
+ return;
+
+ if (last)
+ pr->flags |= IOMMU_FAULT_PAGE_REQUEST_LAST_PAGE;
+ if (pasid_valid)
+ pr->flags |= IOMMU_FAULT_PAGE_REQUEST_PASID_VALID;
+ if (evt[0] & PRIQ_0_PERM_READ)
+ pr->perm |= IOMMU_FAULT_PERM_READ;
+ if (evt[0] & PRIQ_0_PERM_WRITE)
+ pr->perm |= IOMMU_FAULT_PERM_WRITE;
+ if (evt[0] & PRIQ_0_PERM_EXEC)
+ pr->perm |= IOMMU_FAULT_PERM_EXEC;
+ if (evt[0] & PRIQ_0_PERM_PRIV)
+ pr->perm |= IOMMU_FAULT_PERM_PRIV;
+
+ master = arm_smmu_find_master(smmu, sid);
+ if (WARN_ON(!master))
+ return;
+
+ if (iommu_report_device_fault(master->dev, &fault_evt)) {
+ /*
+ * No handler registered, so subsequent faults won't produce
+ * better results. Try to disable PRI.
+ */
+ struct iommu_page_response resp = {
+ .flags = pasid_valid ?
+ IOMMU_PAGE_RESP_PASID_VALID : 0,
+ .pasid = pr->pasid,
+ .grpid = pr->grpid,
+ .code = IOMMU_PAGE_RESP_FAILURE,
};
- arm_smmu_cmdq_issue_cmd(smmu, &cmd);
+ dev_warn(master->dev,
+ "PPR 0x%x:0x%llx 0x%x: nobody cared, disabling PRI\n",
+ pasid_valid ? pr->pasid : 0, pr->addr, pr->perm);
+ if (last)
+ arm_smmu_page_response(master->dev, NULL, &resp);
}
}
static irqreturn_t arm_smmu_priq_thread(int irq, void *dev)
{
+ int num_handled = 0;
+ bool overflow = false;
struct arm_smmu_device *smmu = dev;
struct arm_smmu_queue *q = &smmu->priq.q;
struct arm_smmu_ll_queue *llq = &q->llq;
+ size_t queue_size = 1 << llq->max_n_shift;
u64 evt[PRIQ_ENT_DWORDS];
+ spin_lock(&q->wq.lock);
do {
- while (!queue_remove_raw(q, evt))
+ while (!queue_remove_raw(q, evt)) {
+ spin_unlock(&q->wq.lock);
arm_smmu_handle_ppr(smmu, evt);
+ spin_lock(&q->wq.lock);
+ if (++num_handled == queue_size) {
+ q->batch++;
+ wake_up_all_locked(&q->wq);
+ num_handled = 0;
+ }
+ }
- if (queue_sync_prod_in(q) == -EOVERFLOW)
+ if (queue_sync_prod_in(q) == -EOVERFLOW) {
dev_err(smmu->dev, "PRIQ overflow detected -- requests lost\n");
+ overflow = true;
+ }
} while (!queue_empty(llq));
/* Sync our overflow flag, as we believe we're up to speed */
llq->cons = Q_OVF(llq->prod) | Q_WRP(llq, llq->cons) |
Q_IDX(llq, llq->cons);
queue_sync_cons_out(q);
+
+ wake_up_all_locked(&q->wq);
+ spin_unlock(&q->wq.lock);
+
+ /*
+ * On overflow, the SMMU might have discarded the last PPR in a group.
+ * There is no way to know more about it, so we have to discard all
+ * partial faults already queued.
+ */
+ if (overflow)
+ iopf_queue_discard_partial(smmu->priq.iopf);
+
return IRQ_HANDLED;
}
@@ -2585,6 +2650,35 @@ static int arm_smmu_flush_evtq(struct arm_smmu_device *smmu)
return ret;
}
+/*
+ * arm_smmu_flush_priq - wait until all requests currently in the queue have
+ * been consumed.
+ *
+ * See arm_smmu_flush_evtq().
+ */
+static int arm_smmu_flush_priq(struct arm_smmu_device *smmu)
+{
+ int ret;
+ u64 batch;
+ bool overflow = false;
+ struct arm_smmu_queue *q = &smmu->priq.q;
+
+ spin_lock(&q->wq.lock);
+ if (queue_sync_prod_in(q) == -EOVERFLOW) {
+ dev_err(smmu->dev, "priq overflow detected -- requests lost\n");
+ overflow = true;
+ }
+
+ batch = q->batch;
+ ret = wait_event_interruptible_locked(q->wq, queue_empty(&q->llq) ||
+ q->batch >= batch + 2);
+ spin_unlock(&q->wq.lock);
+
+ if (overflow)
+ iopf_queue_discard_partial(smmu->priq.iopf);
+ return ret;
+}
+
static int arm_smmu_device_disable(struct arm_smmu_device *smmu);
static irqreturn_t arm_smmu_gerror_handler(int irq, void *dev)
@@ -3303,6 +3397,75 @@ static void arm_smmu_disable_pasid(struct arm_smmu_master *master)
pci_disable_pasid(pdev);
}
+static int arm_smmu_init_pri(struct arm_smmu_master *master)
+{
+ int pos;
+ struct pci_dev *pdev;
+
+ if (!dev_is_pci(master->dev))
+ return -EINVAL;
+
+ if (!(master->smmu->features & ARM_SMMU_FEAT_PRI))
+ return 0;
+
+ pdev = to_pci_dev(master->dev);
+ pos = pci_find_ext_capability(pdev, PCI_EXT_CAP_ID_PRI);
+ if (!pos)
+ return 0;
+
+ /* If the device supports PASID and PRI, set STE.PPAR */
+ if (master->ssid_bits)
+ master->prg_resp_needs_ssid = pci_prg_resp_pasid_required(pdev);
+
+ master->pri_supported = true;
+ return 0;
+}
+
+static int arm_smmu_enable_pri(struct arm_smmu_master *master)
+{
+ int ret;
+ struct pci_dev *pdev;
+ /*
+ * TODO: find a good inflight PPR number. According to the SMMU spec we
+ * should divide the PRI queue by the number of PRI-capable devices, but
+ * it's impossible to know about future (probed late or hotplugged)
+ * devices. So we might miss some PPRs due to queue overflow.
+ */
+ size_t max_inflight_pprs = 16;
+
+ if (!master->pri_supported || !master->ats_enabled)
+ return -ENODEV;
+
+ pdev = to_pci_dev(master->dev);
+
+ ret = pci_reset_pri(pdev);
+ if (ret)
+ return ret;
+
+ ret = pci_enable_pri(pdev, max_inflight_pprs);
+ if (ret) {
+ dev_err(master->dev, "cannot enable PRI: %d\n", ret);
+ return ret;
+ }
+
+ return 0;
+}
+
+static void arm_smmu_disable_pri(struct arm_smmu_master *master)
+{
+ struct pci_dev *pdev;
+
+ if (!dev_is_pci(master->dev))
+ return;
+
+ pdev = to_pci_dev(master->dev);
+
+ if (!pdev->pri_enabled)
+ return;
+
+ pci_disable_pri(pdev);
+}
+
static void arm_smmu_detach_dev(struct arm_smmu_master *master)
{
unsigned long flags;
@@ -3653,6 +3816,8 @@ static void arm_smmu_sva_unbind(struct iommu_sva *handle)
if (master->stall_enabled)
arm_smmu_flush_evtq(master->smmu);
+ else if (master->pri_supported)
+ arm_smmu_flush_priq(master->smmu);
iopf_queue_flush_dev(handle->dev);
mutex_lock(&sva_lock);
@@ -3836,6 +4001,8 @@ static int arm_smmu_add_device(struct device *dev)
smmu->features & ARM_SMMU_FEAT_STALL_FORCE)
master->stall_enabled = true;
+ arm_smmu_init_pri(master);
+
ret = iommu_device_link(&smmu->iommu, dev);
if (ret)
goto err_disable_pasid;
@@ -3871,6 +4038,7 @@ static void arm_smmu_remove_device(struct device *dev)
master = dev_iommu_priv_get(dev);
smmu = master->smmu;
+ iopf_queue_remove_device(smmu->priq.iopf, dev);
iopf_queue_remove_device(smmu->evtq.iopf, dev);
WARN_ON(master->sva_enabled);
arm_smmu_detach_dev(master);
@@ -3995,7 +4163,7 @@ static void arm_smmu_get_resv_regions(struct device *dev,
static bool arm_smmu_iopf_supported(struct arm_smmu_master *master)
{
- return master->stall_enabled;
+ return master->stall_enabled || master->pri_supported;
}
static bool arm_smmu_dev_has_feature(struct device *dev,
@@ -4047,6 +4215,15 @@ static int arm_smmu_dev_enable_sva(struct device *dev)
ret = iopf_queue_add_device(master->smmu->evtq.iopf, dev);
if (ret)
return ret;
+ } else if (master->pri_supported) {
+ ret = iopf_queue_add_device(master->smmu->priq.iopf, dev);
+ if (ret)
+ return ret;
+
+ if (arm_smmu_enable_pri(master)) {
+ iopf_queue_remove_device(master->smmu->priq.iopf, dev);
+ return ret;
+ }
}
ret = iommu_register_device_fault_handler(dev, iommu_queue_iopf, dev);
@@ -4060,6 +4237,8 @@ static int arm_smmu_dev_enable_sva(struct device *dev)
return 0;
err_disable_iopf:
+ arm_smmu_disable_pri(master);
+ iopf_queue_remove_device(master->smmu->priq.iopf, dev);
iopf_queue_remove_device(master->smmu->evtq.iopf, dev);
return ret;
}
@@ -4078,6 +4257,8 @@ static int arm_smmu_dev_disable_sva(struct device *dev)
mutex_unlock(&sva_lock);
iommu_unregister_device_fault_handler(dev);
+ arm_smmu_disable_pri(master);
+ iopf_queue_remove_device(master->smmu->priq.iopf, dev);
iopf_queue_remove_device(master->smmu->evtq.iopf, dev);
return 0;
@@ -4250,6 +4431,10 @@ static int arm_smmu_init_queues(struct arm_smmu_device *smmu)
if (!(smmu->features & ARM_SMMU_FEAT_PRI))
return 0;
+ smmu->priq.iopf = iopf_queue_alloc(dev_name(smmu->dev));
+ if (!smmu->priq.iopf)
+ return -ENOMEM;
+
return arm_smmu_init_one_queue(smmu, &smmu->priq.q, ARM_SMMU_PRIQ_PROD,
ARM_SMMU_PRIQ_CONS, PRIQ_ENT_DWORDS,
"priq");
@@ -5252,6 +5437,7 @@ static int arm_smmu_device_remove(struct platform_device *pdev)
iommu_device_sysfs_remove(&smmu->iommu);
arm_smmu_device_disable(smmu);
iopf_queue_free(smmu->evtq.iopf);
+ iopf_queue_free(smmu->priq.iopf);
return 0;
}
--
2.26.2
^ permalink raw reply related
* [PATCH v7 23/24] PCI/ATS: Export PRI functions
From: Jean-Philippe Brucker @ 2020-05-19 17:55 UTC (permalink / raw)
To: iommu, devicetree, linux-arm-kernel, linux-pci, linux-mm
Cc: joro, catalin.marinas, will, robin.murphy, kevin.tian, baolu.lu,
Jonathan.Cameron, jacob.jun.pan, christian.koenig, felix.kuehling,
zhangfei.gao, jgg, xuzaibo, fenghua.yu, hch,
Jean-Philippe Brucker, Bjorn Helgaas, Kuppuswamy Sathyanarayanan
In-Reply-To: <20200519175502.2504091-1-jean-philippe@linaro.org>
The SMMUv3 driver uses pci_{enable,disable}_pri() and related
functions. Export those functions to allow the driver to be built as a
module.
Acked-by: Bjorn Helgaas <bhelgaas@google.com>
Reviewed-by: Kuppuswamy Sathyanarayanan <sathyanarayanan.kuppuswamy@linux.intel.com>
Signed-off-by: Jean-Philippe Brucker <jean-philippe@linaro.org>
---
drivers/pci/ats.c | 4 ++++
1 file changed, 4 insertions(+)
diff --git a/drivers/pci/ats.c b/drivers/pci/ats.c
index a4722e8b6a51..418737a3c2c2 100644
--- a/drivers/pci/ats.c
+++ b/drivers/pci/ats.c
@@ -191,6 +191,7 @@ void pci_pri_init(struct pci_dev *pdev)
if (status & PCI_PRI_STATUS_PASID)
pdev->pasid_required = 1;
}
+EXPORT_SYMBOL_GPL(pci_pri_init);
/**
* pci_enable_pri - Enable PRI capability
@@ -237,6 +238,7 @@ int pci_enable_pri(struct pci_dev *pdev, u32 reqs)
return 0;
}
+EXPORT_SYMBOL_GPL(pci_enable_pri);
/**
* pci_disable_pri - Disable PRI capability
@@ -316,6 +318,7 @@ int pci_reset_pri(struct pci_dev *pdev)
return 0;
}
+EXPORT_SYMBOL_GPL(pci_reset_pri);
/**
* pci_prg_resp_pasid_required - Return PRG Response PASID Required bit
@@ -331,6 +334,7 @@ int pci_prg_resp_pasid_required(struct pci_dev *pdev)
return pdev->pasid_required;
}
+EXPORT_SYMBOL_GPL(pci_prg_resp_pasid_required);
#endif /* CONFIG_PCI_PRI */
#ifdef CONFIG_PCI_PASID
--
2.26.2
^ permalink raw reply related
* [PATCH v7 22/24] PCI/ATS: Add PRI stubs
From: Jean-Philippe Brucker @ 2020-05-19 17:55 UTC (permalink / raw)
To: iommu, devicetree, linux-arm-kernel, linux-pci, linux-mm
Cc: joro, catalin.marinas, will, robin.murphy, kevin.tian, baolu.lu,
Jonathan.Cameron, jacob.jun.pan, christian.koenig, felix.kuehling,
zhangfei.gao, jgg, xuzaibo, fenghua.yu, hch,
Jean-Philippe Brucker, Bjorn Helgaas, Kuppuswamy Sathyanarayanan
In-Reply-To: <20200519175502.2504091-1-jean-philippe@linaro.org>
The SMMUv3 driver, which can be built without CONFIG_PCI, will soon gain
support for PRI. Partially revert commit c6e9aefbf9db ("PCI/ATS: Remove
unused PRI and PASID stubs") to re-introduce the PRI stubs, and avoid
adding more #ifdefs to the SMMU driver.
Acked-by: Bjorn Helgaas <bhelgaas@google.com>
Reviewed-by: Kuppuswamy Sathyanarayanan <sathyanarayanan.kuppuswamy@linux.intel.com>
Signed-off-by: Jean-Philippe Brucker <jean-philippe@linaro.org>
---
include/linux/pci-ats.h | 8 ++++++++
1 file changed, 8 insertions(+)
diff --git a/include/linux/pci-ats.h b/include/linux/pci-ats.h
index f75c307f346d..e9e266df9b37 100644
--- a/include/linux/pci-ats.h
+++ b/include/linux/pci-ats.h
@@ -28,6 +28,14 @@ int pci_enable_pri(struct pci_dev *pdev, u32 reqs);
void pci_disable_pri(struct pci_dev *pdev);
int pci_reset_pri(struct pci_dev *pdev);
int pci_prg_resp_pasid_required(struct pci_dev *pdev);
+#else /* CONFIG_PCI_PRI */
+static inline int pci_enable_pri(struct pci_dev *pdev, u32 reqs)
+{ return -ENODEV; }
+static inline void pci_disable_pri(struct pci_dev *pdev) { }
+static inline int pci_reset_pri(struct pci_dev *pdev)
+{ return -ENODEV; }
+static inline int pci_prg_resp_pasid_required(struct pci_dev *pdev)
+{ return 0; }
#endif /* CONFIG_PCI_PRI */
#ifdef CONFIG_PCI_PASID
--
2.26.2
^ permalink raw reply related
* [PATCH v7 21/24] iommu/arm-smmu-v3: Add stall support for platform devices
From: Jean-Philippe Brucker @ 2020-05-19 17:54 UTC (permalink / raw)
To: iommu, devicetree, linux-arm-kernel, linux-pci, linux-mm
Cc: joro, catalin.marinas, will, robin.murphy, kevin.tian, baolu.lu,
Jonathan.Cameron, jacob.jun.pan, christian.koenig, felix.kuehling,
zhangfei.gao, jgg, xuzaibo, fenghua.yu, hch,
Jean-Philippe Brucker
In-Reply-To: <20200519175502.2504091-1-jean-philippe@linaro.org>
The SMMU provides a Stall model for handling page faults in platform
devices. It is similar to PCI PRI, but doesn't require devices to have
their own translation cache. Instead, faulting transactions are parked
and the OS is given a chance to fix the page tables and retry the
transaction.
Enable stall for devices that support it (opt-in by firmware). When an
event corresponds to a translation error, call the IOMMU fault handler.
If the fault is recoverable, it will call us back to terminate or
continue the stall.
Signed-off-by: Jean-Philippe Brucker <jean-philippe@linaro.org>
---
drivers/iommu/Kconfig | 1 +
include/linux/iommu.h | 2 +
drivers/iommu/arm-smmu-v3.c | 284 ++++++++++++++++++++++++++++++++++--
drivers/iommu/of_iommu.c | 5 +-
4 files changed, 281 insertions(+), 11 deletions(-)
diff --git a/drivers/iommu/Kconfig b/drivers/iommu/Kconfig
index 00b517f449ab..16fb38d5dcc7 100644
--- a/drivers/iommu/Kconfig
+++ b/drivers/iommu/Kconfig
@@ -433,6 +433,7 @@ config ARM_SMMU_V3
depends on ARM64
select IOMMU_API
select IOMMU_SVA
+ select IOMMU_PAGE_FAULT
select IOMMU_IO_PGTABLE_LPAE
select GENERIC_MSI_IRQ_DOMAIN
select MMU_NOTIFIER
diff --git a/include/linux/iommu.h b/include/linux/iommu.h
index a462157c855b..2768f9927237 100644
--- a/include/linux/iommu.h
+++ b/include/linux/iommu.h
@@ -567,6 +567,7 @@ struct iommu_group *fsl_mc_device_group(struct device *dev);
* @iommu_fwnode: firmware handle for this device's IOMMU
* @iommu_priv: IOMMU driver private data for this device
* @num_pasid_bits: number of PASID bits supported by this device
+ * @can_stall: the device is allowed to stall
* @num_ids: number of associated device IDs
* @ids: IDs which this device may present to the IOMMU
*/
@@ -574,6 +575,7 @@ struct iommu_fwspec {
const struct iommu_ops *ops;
struct fwnode_handle *iommu_fwnode;
u32 num_pasid_bits;
+ bool can_stall;
unsigned int num_ids;
u32 ids[];
};
diff --git a/drivers/iommu/arm-smmu-v3.c b/drivers/iommu/arm-smmu-v3.c
index 70dfbd2817aa..9ec2f362802b 100644
--- a/drivers/iommu/arm-smmu-v3.c
+++ b/drivers/iommu/arm-smmu-v3.c
@@ -385,6 +385,13 @@
#define CMDQ_PRI_1_GRPID GENMASK_ULL(8, 0)
#define CMDQ_PRI_1_RESP GENMASK_ULL(13, 12)
+#define CMDQ_RESUME_0_SID GENMASK_ULL(63, 32)
+#define CMDQ_RESUME_0_RESP_TERM 0UL
+#define CMDQ_RESUME_0_RESP_RETRY 1UL
+#define CMDQ_RESUME_0_RESP_ABORT 2UL
+#define CMDQ_RESUME_0_RESP GENMASK_ULL(13, 12)
+#define CMDQ_RESUME_1_STAG GENMASK_ULL(15, 0)
+
#define CMDQ_SYNC_0_CS GENMASK_ULL(13, 12)
#define CMDQ_SYNC_0_CS_NONE 0
#define CMDQ_SYNC_0_CS_IRQ 1
@@ -401,6 +408,25 @@
#define EVTQ_0_ID GENMASK_ULL(7, 0)
+#define EVT_ID_TRANSLATION_FAULT 0x10
+#define EVT_ID_ADDR_SIZE_FAULT 0x11
+#define EVT_ID_ACCESS_FAULT 0x12
+#define EVT_ID_PERMISSION_FAULT 0x13
+
+#define EVTQ_0_SSV (1UL << 11)
+#define EVTQ_0_SSID GENMASK_ULL(31, 12)
+#define EVTQ_0_SID GENMASK_ULL(63, 32)
+#define EVTQ_1_STAG GENMASK_ULL(15, 0)
+#define EVTQ_1_STALL (1UL << 31)
+#define EVTQ_1_PRIV (1UL << 33)
+#define EVTQ_1_EXEC (1UL << 34)
+#define EVTQ_1_READ (1UL << 35)
+#define EVTQ_1_S2 (1UL << 39)
+#define EVTQ_1_CLASS GENMASK_ULL(41, 40)
+#define EVTQ_1_TT_READ (1UL << 44)
+#define EVTQ_2_ADDR GENMASK_ULL(63, 0)
+#define EVTQ_3_IPA GENMASK_ULL(51, 12)
+
/* PRI queue */
#define PRIQ_ENT_SZ_SHIFT 4
#define PRIQ_ENT_DWORDS ((1 << PRIQ_ENT_SZ_SHIFT) >> 3)
@@ -525,6 +551,13 @@ struct arm_smmu_cmdq_ent {
enum pri_resp resp;
} pri;
+ #define CMDQ_OP_RESUME 0x44
+ struct {
+ u32 sid;
+ u16 stag;
+ u8 resp;
+ } resume;
+
#define CMDQ_OP_CMD_SYNC 0x46
struct {
u64 msiaddr;
@@ -560,6 +593,10 @@ struct arm_smmu_queue {
u32 __iomem *prod_reg;
u32 __iomem *cons_reg;
+
+ /* Event and PRI */
+ u64 batch;
+ wait_queue_head_t wq;
};
struct arm_smmu_queue_poll {
@@ -583,6 +620,7 @@ struct arm_smmu_cmdq_batch {
struct arm_smmu_evtq {
struct arm_smmu_queue q;
+ struct iopf_queue *iopf;
u32 max_stalls;
};
@@ -721,6 +759,7 @@ struct arm_smmu_master {
struct arm_smmu_stream *streams;
unsigned int num_streams;
bool ats_enabled;
+ bool stall_enabled;
bool sva_enabled;
struct list_head bonds;
unsigned int ssid_bits;
@@ -740,6 +779,7 @@ struct arm_smmu_domain {
struct io_pgtable_ops *pgtbl_ops;
bool non_strict;
+ bool stall_enabled;
atomic_t nr_ats_masters;
enum arm_smmu_domain_stage stage;
@@ -1034,6 +1074,11 @@ static int arm_smmu_cmdq_build_cmd(u64 *cmd, struct arm_smmu_cmdq_ent *ent)
}
cmd[1] |= FIELD_PREP(CMDQ_PRI_1_RESP, ent->pri.resp);
break;
+ case CMDQ_OP_RESUME:
+ cmd[0] |= FIELD_PREP(CMDQ_RESUME_0_SID, ent->resume.sid);
+ cmd[0] |= FIELD_PREP(CMDQ_RESUME_0_RESP, ent->resume.resp);
+ cmd[1] |= FIELD_PREP(CMDQ_RESUME_1_STAG, ent->resume.stag);
+ break;
case CMDQ_OP_CMD_SYNC:
if (ent->sync.msiaddr) {
cmd[0] |= FIELD_PREP(CMDQ_SYNC_0_CS, CMDQ_SYNC_0_CS_IRQ);
@@ -1600,6 +1645,45 @@ static int arm_smmu_cmdq_batch_submit(struct arm_smmu_device *smmu,
return arm_smmu_cmdq_issue_cmdlist(smmu, cmds->cmds, cmds->num, true);
}
+static int arm_smmu_page_response(struct device *dev,
+ struct iommu_fault_event *unused,
+ struct iommu_page_response *resp)
+{
+ struct arm_smmu_cmdq_ent cmd = {0};
+ struct arm_smmu_master *master = dev_iommu_priv_get(dev);
+ int sid = master->streams[0].id;
+
+ if (master->stall_enabled) {
+ cmd.opcode = CMDQ_OP_RESUME;
+ cmd.resume.sid = sid;
+ cmd.resume.stag = resp->grpid;
+ switch (resp->code) {
+ case IOMMU_PAGE_RESP_INVALID:
+ case IOMMU_PAGE_RESP_FAILURE:
+ cmd.resume.resp = CMDQ_RESUME_0_RESP_ABORT;
+ break;
+ case IOMMU_PAGE_RESP_SUCCESS:
+ cmd.resume.resp = CMDQ_RESUME_0_RESP_RETRY;
+ break;
+ default:
+ return -EINVAL;
+ }
+ } else {
+ /* TODO: insert PRI response here */
+ return -ENODEV;
+ }
+
+ arm_smmu_cmdq_issue_cmd(master->smmu, &cmd);
+ /*
+ * Don't send a SYNC, it doesn't do anything for RESUME or PRI_RESP.
+ * RESUME consumption guarantees that the stalled transaction will be
+ * terminated... at some point in the future. PRI_RESP is fire and
+ * forget.
+ */
+
+ return 0;
+}
+
/* Context descriptor manipulation functions */
static void arm_smmu_tlb_inv_asid(struct arm_smmu_device *smmu, u16 asid)
{
@@ -1762,8 +1846,7 @@ static int arm_smmu_write_ctx_desc(struct arm_smmu_domain *smmu_domain,
FIELD_PREP(CTXDESC_CD_0_ASID, cd->asid) |
CTXDESC_CD_0_V;
- /* STALL_MODEL==0b10 && CD.S==0 is ILLEGAL */
- if (smmu->features & ARM_SMMU_FEAT_STALL_FORCE)
+ if (smmu_domain->stall_enabled)
val |= CTXDESC_CD_0_S;
}
@@ -2171,7 +2254,7 @@ static void arm_smmu_write_strtab_ent(struct arm_smmu_master *master, u32 sid,
FIELD_PREP(STRTAB_STE_1_STRW, strw));
if (smmu->features & ARM_SMMU_FEAT_STALLS &&
- !(smmu->features & ARM_SMMU_FEAT_STALL_FORCE))
+ !master->stall_enabled)
dst[1] |= cpu_to_le64(STRTAB_STE_1_S1STALLD);
val |= (s1_cfg->cdcfg.cdtab_dma & STRTAB_STE_0_S1CTXPTR_MASK) |
@@ -2248,7 +2331,6 @@ static int arm_smmu_init_l2_strtab(struct arm_smmu_device *smmu, u32 sid)
return 0;
}
-__maybe_unused
static struct arm_smmu_master *
arm_smmu_find_master(struct arm_smmu_device *smmu, u32 sid)
{
@@ -2275,23 +2357,123 @@ arm_smmu_find_master(struct arm_smmu_device *smmu, u32 sid)
}
/* IRQ and event handlers */
+static int arm_smmu_handle_evt(struct arm_smmu_device *smmu, u64 *evt)
+{
+ int ret;
+ u32 perm = 0;
+ struct arm_smmu_master *master;
+ bool ssid_valid = evt[0] & EVTQ_0_SSV;
+ u8 type = FIELD_GET(EVTQ_0_ID, evt[0]);
+ u32 sid = FIELD_GET(EVTQ_0_SID, evt[0]);
+ struct iommu_fault_event fault_evt = { };
+ struct iommu_fault *flt = &fault_evt.fault;
+
+ /* Stage-2 is always pinned at the moment */
+ if (evt[1] & EVTQ_1_S2)
+ return -EFAULT;
+
+ master = arm_smmu_find_master(smmu, sid);
+ if (!master)
+ return -EINVAL;
+
+ if (evt[1] & EVTQ_1_READ)
+ perm |= IOMMU_FAULT_PERM_READ;
+ else
+ perm |= IOMMU_FAULT_PERM_WRITE;
+
+ if (evt[1] & EVTQ_1_EXEC)
+ perm |= IOMMU_FAULT_PERM_EXEC;
+
+ if (evt[1] & EVTQ_1_PRIV)
+ perm |= IOMMU_FAULT_PERM_PRIV;
+
+ if (evt[1] & EVTQ_1_STALL) {
+ flt->type = IOMMU_FAULT_PAGE_REQ;
+ flt->prm = (struct iommu_fault_page_request) {
+ .flags = IOMMU_FAULT_PAGE_REQUEST_LAST_PAGE,
+ .pasid = FIELD_GET(EVTQ_0_SSID, evt[0]),
+ .grpid = FIELD_GET(EVTQ_1_STAG, evt[1]),
+ .perm = perm,
+ .addr = FIELD_GET(EVTQ_2_ADDR, evt[2]),
+ };
+
+ if (ssid_valid)
+ flt->prm.flags |= IOMMU_FAULT_PAGE_REQUEST_PASID_VALID;
+ } else {
+ flt->type = IOMMU_FAULT_DMA_UNRECOV;
+ flt->event = (struct iommu_fault_unrecoverable) {
+ .flags = IOMMU_FAULT_UNRECOV_ADDR_VALID |
+ IOMMU_FAULT_UNRECOV_FETCH_ADDR_VALID,
+ .pasid = FIELD_GET(EVTQ_0_SSID, evt[0]),
+ .perm = perm,
+ .addr = FIELD_GET(EVTQ_2_ADDR, evt[2]),
+ .fetch_addr = FIELD_GET(EVTQ_3_IPA, evt[3]),
+ };
+
+ if (ssid_valid)
+ flt->event.flags |= IOMMU_FAULT_UNRECOV_PASID_VALID;
+
+ switch (type) {
+ case EVT_ID_TRANSLATION_FAULT:
+ case EVT_ID_ADDR_SIZE_FAULT:
+ case EVT_ID_ACCESS_FAULT:
+ flt->event.reason = IOMMU_FAULT_REASON_PTE_FETCH;
+ break;
+ case EVT_ID_PERMISSION_FAULT:
+ flt->event.reason = IOMMU_FAULT_REASON_PERMISSION;
+ break;
+ default:
+ /* TODO: report other unrecoverable faults. */
+ return -EFAULT;
+ }
+ }
+
+ ret = iommu_report_device_fault(master->dev, &fault_evt);
+ if (ret && flt->type == IOMMU_FAULT_PAGE_REQ) {
+ /* Nobody cared, abort the access */
+ struct iommu_page_response resp = {
+ .pasid = flt->prm.pasid,
+ .grpid = flt->prm.grpid,
+ .code = IOMMU_PAGE_RESP_FAILURE,
+ };
+ arm_smmu_page_response(master->dev, NULL, &resp);
+ }
+
+ return ret;
+}
+
static irqreturn_t arm_smmu_evtq_thread(int irq, void *dev)
{
- int i;
+ int i, ret;
+ int num_handled = 0;
struct arm_smmu_device *smmu = dev;
struct arm_smmu_queue *q = &smmu->evtq.q;
struct arm_smmu_ll_queue *llq = &q->llq;
+ size_t queue_size = 1 << llq->max_n_shift;
u64 evt[EVTQ_ENT_DWORDS];
+ spin_lock(&q->wq.lock);
do {
while (!queue_remove_raw(q, evt)) {
u8 id = FIELD_GET(EVTQ_0_ID, evt[0]);
- dev_info(smmu->dev, "event 0x%02x received:\n", id);
- for (i = 0; i < ARRAY_SIZE(evt); ++i)
- dev_info(smmu->dev, "\t0x%016llx\n",
- (unsigned long long)evt[i]);
+ spin_unlock(&q->wq.lock);
+ ret = arm_smmu_handle_evt(smmu, evt);
+ spin_lock(&q->wq.lock);
+
+ if (++num_handled == queue_size) {
+ q->batch++;
+ wake_up_all_locked(&q->wq);
+ num_handled = 0;
+ }
+ if (ret) {
+ dev_info(smmu->dev, "event 0x%02x received:\n",
+ id);
+ for (i = 0; i < ARRAY_SIZE(evt); ++i)
+ dev_info(smmu->dev, "\t0x%016llx\n",
+ (unsigned long long)evt[i]);
+ }
}
/*
@@ -2305,6 +2487,11 @@ static irqreturn_t arm_smmu_evtq_thread(int irq, void *dev)
/* Sync our overflow flag, as we believe we're up to speed */
llq->cons = Q_OVF(llq->prod) | Q_WRP(llq, llq->cons) |
Q_IDX(llq, llq->cons);
+ queue_sync_cons_out(q);
+
+ wake_up_all_locked(&q->wq);
+ spin_unlock(&q->wq.lock);
+
return IRQ_HANDLED;
}
@@ -2368,6 +2555,36 @@ static irqreturn_t arm_smmu_priq_thread(int irq, void *dev)
return IRQ_HANDLED;
}
+/*
+ * arm_smmu_flush_evtq - wait until all events currently in the queue have been
+ * consumed.
+ *
+ * Wait until there are no more event for this @pasid in the queue. Either until
+ * the queue becomes empty or, if new events are continually added the queue,
+ * until the event queue thread has handled a full batch (where one batch
+ * corresponds to the queue size). For that we take the batch number when
+ * entering flush() and wait for the event queue thread to increment it twice.
+ * Note that we don't handle overflows on q->batch. If it occurs, just wait for
+ * the queue to become empty.
+ */
+static int arm_smmu_flush_evtq(struct arm_smmu_device *smmu)
+{
+ int ret;
+ u64 batch;
+ struct arm_smmu_queue *q = &smmu->evtq.q;
+
+ spin_lock(&q->wq.lock);
+ if (queue_sync_prod_in(q) == -EOVERFLOW)
+ dev_err(smmu->dev, "evtq overflow detected -- requests lost\n");
+
+ batch = q->batch;
+ ret = wait_event_interruptible_locked(q->wq, queue_empty(&q->llq) ||
+ q->batch >= batch + 2);
+ spin_unlock(&q->wq.lock);
+
+ return ret;
+}
+
static int arm_smmu_device_disable(struct arm_smmu_device *smmu);
static irqreturn_t arm_smmu_gerror_handler(int irq, void *dev)
@@ -2812,6 +3029,8 @@ static int arm_smmu_domain_finalise_s1(struct arm_smmu_domain *smmu_domain,
cfg->s1cdmax = master->ssid_bits;
+ smmu_domain->stall_enabled = master->stall_enabled;
+
ret = arm_smmu_alloc_cd_tables(smmu_domain);
if (ret)
goto out_free_asid;
@@ -3156,6 +3375,11 @@ static int arm_smmu_attach_dev(struct iommu_domain *domain, struct device *dev)
smmu_domain->s1_cfg.s1cdmax, master->ssid_bits);
ret = -EINVAL;
goto out_unlock;
+ } else if (smmu_domain->stall_enabled != master->stall_enabled) {
+ dev_err(dev, "cannot attach to stall-%s domain\n",
+ smmu_domain->stall_enabled ? "enabled" : "disabled");
+ ret = -EINVAL;
+ goto out_unlock;
}
master->domain = smmu_domain;
@@ -3425,6 +3649,11 @@ arm_smmu_sva_bind(struct device *dev, struct mm_struct *mm, void *drvdata)
static void arm_smmu_sva_unbind(struct iommu_sva *handle)
{
struct arm_smmu_bond *bond = sva_to_bond(handle);
+ struct arm_smmu_master *master = dev_iommu_priv_get(handle->dev);
+
+ if (master->stall_enabled)
+ arm_smmu_flush_evtq(master->smmu);
+ iopf_queue_flush_dev(handle->dev);
mutex_lock(&sva_lock);
if (refcount_dec_and_test(&bond->refs)) {
@@ -3603,6 +3832,10 @@ static int arm_smmu_add_device(struct device *dev)
master->ssid_bits = min_t(u8, master->ssid_bits,
CTXDESC_LINEAR_CDMAX);
+ if ((smmu->features & ARM_SMMU_FEAT_STALLS && fwspec->can_stall) ||
+ smmu->features & ARM_SMMU_FEAT_STALL_FORCE)
+ master->stall_enabled = true;
+
ret = iommu_device_link(&smmu->iommu, dev);
if (ret)
goto err_disable_pasid;
@@ -3638,6 +3871,7 @@ static void arm_smmu_remove_device(struct device *dev)
master = dev_iommu_priv_get(dev);
smmu = master->smmu;
+ iopf_queue_remove_device(smmu->evtq.iopf, dev);
WARN_ON(master->sva_enabled);
arm_smmu_detach_dev(master);
iommu_group_remove_device(dev);
@@ -3761,7 +3995,7 @@ static void arm_smmu_get_resv_regions(struct device *dev,
static bool arm_smmu_iopf_supported(struct arm_smmu_master *master)
{
- return false;
+ return master->stall_enabled;
}
static bool arm_smmu_dev_has_feature(struct device *dev,
@@ -3806,13 +4040,28 @@ static bool arm_smmu_dev_feature_enabled(struct device *dev,
static int arm_smmu_dev_enable_sva(struct device *dev)
{
+ int ret;
struct arm_smmu_master *master = dev_iommu_priv_get(dev);
+ if (master->stall_enabled) {
+ ret = iopf_queue_add_device(master->smmu->evtq.iopf, dev);
+ if (ret)
+ return ret;
+ }
+
+ ret = iommu_register_device_fault_handler(dev, iommu_queue_iopf, dev);
+ if (ret)
+ goto err_disable_iopf;
+
mutex_lock(&sva_lock);
master->sva_enabled = true;
mutex_unlock(&sva_lock);
return 0;
+
+err_disable_iopf:
+ iopf_queue_remove_device(master->smmu->evtq.iopf, dev);
+ return ret;
}
static int arm_smmu_dev_disable_sva(struct device *dev)
@@ -3828,6 +4077,9 @@ static int arm_smmu_dev_disable_sva(struct device *dev)
master->sva_enabled = false;
mutex_unlock(&sva_lock);
+ iommu_unregister_device_fault_handler(dev);
+ iopf_queue_remove_device(master->smmu->evtq.iopf, dev);
+
return 0;
}
@@ -3887,6 +4139,7 @@ static struct iommu_ops arm_smmu_ops = {
.sva_bind = arm_smmu_sva_bind,
.sva_unbind = arm_smmu_sva_unbind,
.sva_get_pasid = arm_smmu_sva_get_pasid,
+ .page_response = arm_smmu_page_response,
.pgsize_bitmap = -1UL, /* Restricted during device attach */
};
@@ -3930,6 +4183,10 @@ static int arm_smmu_init_one_queue(struct arm_smmu_device *smmu,
q->q_base |= FIELD_PREP(Q_BASE_LOG2SIZE, q->llq.max_n_shift);
q->llq.prod = q->llq.cons = 0;
+
+ init_waitqueue_head(&q->wq);
+ q->batch = 0;
+
return 0;
}
@@ -3983,6 +4240,12 @@ static int arm_smmu_init_queues(struct arm_smmu_device *smmu)
if (ret)
return ret;
+ if (smmu->features & ARM_SMMU_FEAT_STALLS) {
+ smmu->evtq.iopf = iopf_queue_alloc(dev_name(smmu->dev));
+ if (!smmu->evtq.iopf)
+ return -ENOMEM;
+ }
+
/* priq */
if (!(smmu->features & ARM_SMMU_FEAT_PRI))
return 0;
@@ -4988,6 +5251,7 @@ static int arm_smmu_device_remove(struct platform_device *pdev)
iommu_device_unregister(&smmu->iommu);
iommu_device_sysfs_remove(&smmu->iommu);
arm_smmu_device_disable(smmu);
+ iopf_queue_free(smmu->evtq.iopf);
return 0;
}
diff --git a/drivers/iommu/of_iommu.c b/drivers/iommu/of_iommu.c
index 20738aacac89..dd7017750954 100644
--- a/drivers/iommu/of_iommu.c
+++ b/drivers/iommu/of_iommu.c
@@ -205,9 +205,12 @@ const struct iommu_ops *of_iommu_configure(struct device *dev,
}
fwspec = dev_iommu_fwspec_get(dev);
- if (!err && fwspec)
+ if (!err && fwspec) {
of_property_read_u32(master_np, "pasid-num-bits",
&fwspec->num_pasid_bits);
+ fwspec->can_stall = of_property_read_bool(master_np,
+ "dma-can-stall");
+ }
}
/*
--
2.26.2
^ permalink raw reply related
* [PATCH v7 20/24] dt-bindings: document stall property for IOMMU masters
From: Jean-Philippe Brucker @ 2020-05-19 17:54 UTC (permalink / raw)
To: iommu, devicetree, linux-arm-kernel, linux-pci, linux-mm
Cc: joro, catalin.marinas, will, robin.murphy, kevin.tian, baolu.lu,
Jonathan.Cameron, jacob.jun.pan, christian.koenig, felix.kuehling,
zhangfei.gao, jgg, xuzaibo, fenghua.yu, hch,
Jean-Philippe Brucker, Rob Herring
In-Reply-To: <20200519175502.2504091-1-jean-philippe@linaro.org>
On ARM systems, some platform devices behind an IOMMU may support stall,
which is the ability to recover from page faults. Let the firmware tell us
when a device supports stall.
Reviewed-by: Rob Herring <robh@kernel.org>
Signed-off-by: Jean-Philippe Brucker <jean-philippe@linaro.org>
---
.../devicetree/bindings/iommu/iommu.txt | 18 ++++++++++++++++++
1 file changed, 18 insertions(+)
diff --git a/Documentation/devicetree/bindings/iommu/iommu.txt b/Documentation/devicetree/bindings/iommu/iommu.txt
index 3c36334e4f94..26ba9e530f13 100644
--- a/Documentation/devicetree/bindings/iommu/iommu.txt
+++ b/Documentation/devicetree/bindings/iommu/iommu.txt
@@ -92,6 +92,24 @@ Optional properties:
tagging DMA transactions with an address space identifier. By default,
this is 0, which means that the device only has one address space.
+- dma-can-stall: When present, the master can wait for a transaction to
+ complete for an indefinite amount of time. Upon translation fault some
+ IOMMUs, instead of aborting the translation immediately, may first
+ notify the driver and keep the transaction in flight. This allows the OS
+ to inspect the fault and, for example, make physical pages resident
+ before updating the mappings and completing the transaction. Such IOMMU
+ accepts a limited number of simultaneous stalled transactions before
+ having to either put back-pressure on the master, or abort new faulting
+ transactions.
+
+ Firmware has to opt-in stalling, because most buses and masters don't
+ support it. In particular it isn't compatible with PCI, where
+ transactions have to complete before a time limit. More generally it
+ won't work in systems and masters that haven't been designed for
+ stalling. For example the OS, in order to handle a stalled transaction,
+ may attempt to retrieve pages from secondary storage in a stalled
+ domain, leading to a deadlock.
+
Notes:
======
--
2.26.2
^ permalink raw reply related
* [PATCH v7 19/24] iommu/arm-smmu-v3: Maintain a SID->device structure
From: Jean-Philippe Brucker @ 2020-05-19 17:54 UTC (permalink / raw)
To: iommu, devicetree, linux-arm-kernel, linux-pci, linux-mm
Cc: joro, catalin.marinas, will, robin.murphy, kevin.tian, baolu.lu,
Jonathan.Cameron, jacob.jun.pan, christian.koenig, felix.kuehling,
zhangfei.gao, jgg, xuzaibo, fenghua.yu, hch,
Jean-Philippe Brucker
In-Reply-To: <20200519175502.2504091-1-jean-philippe@linaro.org>
When handling faults from the event or PRI queue, we need to find the
struct device associated to a SID. Add a rb_tree to keep track of SIDs.
Signed-off-by: Jean-Philippe Brucker <jean-philippe@linaro.org>
---
drivers/iommu/arm-smmu-v3.c | 175 +++++++++++++++++++++++++++++-------
1 file changed, 145 insertions(+), 30 deletions(-)
diff --git a/drivers/iommu/arm-smmu-v3.c b/drivers/iommu/arm-smmu-v3.c
index 6a368218f54c..70dfbd2817aa 100644
--- a/drivers/iommu/arm-smmu-v3.c
+++ b/drivers/iommu/arm-smmu-v3.c
@@ -701,6 +701,15 @@ struct arm_smmu_device {
/* IOMMU core code handle */
struct iommu_device iommu;
+
+ struct rb_root streams;
+ struct mutex streams_mutex;
+};
+
+struct arm_smmu_stream {
+ u32 id;
+ struct arm_smmu_master *master;
+ struct rb_node node;
};
/* SMMU private data for each master */
@@ -709,8 +718,8 @@ struct arm_smmu_master {
struct device *dev;
struct arm_smmu_domain *domain;
struct list_head domain_head;
- u32 *sids;
- unsigned int num_sids;
+ struct arm_smmu_stream *streams;
+ unsigned int num_streams;
bool ats_enabled;
bool sva_enabled;
struct list_head bonds;
@@ -1622,8 +1631,8 @@ static void arm_smmu_sync_cd(struct arm_smmu_domain *smmu_domain,
spin_lock_irqsave(&smmu_domain->devices_lock, flags);
list_for_each_entry(master, &smmu_domain->devices, domain_head) {
- for (i = 0; i < master->num_sids; i++) {
- cmd.cfgi.sid = master->sids[i];
+ for (i = 0; i < master->num_streams; i++) {
+ cmd.cfgi.sid = master->streams[i].id;
arm_smmu_cmdq_batch_add(smmu, &cmds, &cmd);
}
}
@@ -2239,6 +2248,32 @@ static int arm_smmu_init_l2_strtab(struct arm_smmu_device *smmu, u32 sid)
return 0;
}
+__maybe_unused
+static struct arm_smmu_master *
+arm_smmu_find_master(struct arm_smmu_device *smmu, u32 sid)
+{
+ struct rb_node *node;
+ struct arm_smmu_stream *stream;
+ struct arm_smmu_master *master = NULL;
+
+ mutex_lock(&smmu->streams_mutex);
+ node = smmu->streams.rb_node;
+ while (node) {
+ stream = rb_entry(node, struct arm_smmu_stream, node);
+ if (stream->id < sid) {
+ node = node->rb_right;
+ } else if (stream->id > sid) {
+ node = node->rb_left;
+ } else {
+ master = stream->master;
+ break;
+ }
+ }
+ mutex_unlock(&smmu->streams_mutex);
+
+ return master;
+}
+
/* IRQ and event handlers */
static irqreturn_t arm_smmu_evtq_thread(int irq, void *dev)
{
@@ -2472,8 +2507,8 @@ static int arm_smmu_atc_inv_master(struct arm_smmu_master *master, int ssid)
arm_smmu_atc_inv_to_cmd(ssid, 0, 0, &cmd);
- for (i = 0; i < master->num_sids; i++) {
- cmd.atc.sid = master->sids[i];
+ for (i = 0; i < master->num_streams; i++) {
+ cmd.atc.sid = master->streams[i].id;
arm_smmu_cmdq_issue_cmd(master->smmu, &cmd);
}
@@ -2516,8 +2551,8 @@ static int arm_smmu_atc_inv_domain(struct arm_smmu_domain *smmu_domain,
if (!master->ats_enabled)
continue;
- for (i = 0; i < master->num_sids; i++) {
- cmd.atc.sid = master->sids[i];
+ for (i = 0; i < master->num_streams; i++) {
+ cmd.atc.sid = master->streams[i].id;
arm_smmu_cmdq_batch_add(smmu_domain->smmu, &cmds, &cmd);
}
}
@@ -2940,13 +2975,13 @@ static void arm_smmu_install_ste_for_dev(struct arm_smmu_master *master)
int i, j;
struct arm_smmu_device *smmu = master->smmu;
- for (i = 0; i < master->num_sids; ++i) {
- u32 sid = master->sids[i];
+ for (i = 0; i < master->num_streams; ++i) {
+ u32 sid = master->streams[i].id;
__le64 *step = arm_smmu_get_step_for_sid(smmu, sid);
/* Bridged PCI devices may end up with duplicated IDs */
for (j = 0; j < i; j++)
- if (master->sids[j] == sid)
+ if (master->streams[j].id == sid)
break;
if (j < i)
continue;
@@ -3429,11 +3464,101 @@ static bool arm_smmu_sid_in_range(struct arm_smmu_device *smmu, u32 sid)
return sid < limit;
}
+static int arm_smmu_insert_master(struct arm_smmu_device *smmu,
+ struct arm_smmu_master *master)
+{
+ int i;
+ int ret = 0;
+ struct arm_smmu_stream *new_stream, *cur_stream;
+ struct rb_node **new_node, *parent_node = NULL;
+ struct iommu_fwspec *fwspec = dev_iommu_fwspec_get(master->dev);
+
+ master->streams = kcalloc(fwspec->num_ids,
+ sizeof(struct arm_smmu_stream), GFP_KERNEL);
+ if (!master->streams)
+ return -ENOMEM;
+ master->num_streams = fwspec->num_ids;
+
+ mutex_lock(&smmu->streams_mutex);
+ for (i = 0; i < fwspec->num_ids && !ret; i++) {
+ u32 sid = fwspec->ids[i];
+
+ new_stream = &master->streams[i];
+ new_stream->id = sid;
+ new_stream->master = master;
+
+ /*
+ * Check the SIDs are in range of the SMMU and our stream table
+ */
+ if (!arm_smmu_sid_in_range(smmu, sid)) {
+ ret = -ERANGE;
+ break;
+ }
+
+ /* Ensure l2 strtab is initialised */
+ if (smmu->features & ARM_SMMU_FEAT_2_LVL_STRTAB) {
+ ret = arm_smmu_init_l2_strtab(smmu, sid);
+ if (ret)
+ break;
+ }
+
+ /* Insert into SID tree */
+ new_node = &(smmu->streams.rb_node);
+ while (*new_node) {
+ cur_stream = rb_entry(*new_node, struct arm_smmu_stream,
+ node);
+ parent_node = *new_node;
+ if (cur_stream->id > new_stream->id) {
+ new_node = &((*new_node)->rb_left);
+ } else if (cur_stream->id < new_stream->id) {
+ new_node = &((*new_node)->rb_right);
+ } else {
+ dev_warn(master->dev,
+ "stream %u already in tree\n",
+ cur_stream->id);
+ ret = -EINVAL;
+ break;
+ }
+ }
+
+ if (!ret) {
+ rb_link_node(&new_stream->node, parent_node, new_node);
+ rb_insert_color(&new_stream->node, &smmu->streams);
+ }
+ }
+
+ if (ret) {
+ for (; i > 0; i--)
+ rb_erase(&master->streams[i].node, &smmu->streams);
+ kfree(master->streams);
+ }
+ mutex_unlock(&smmu->streams_mutex);
+
+ return ret;
+}
+
+static void arm_smmu_remove_master(struct arm_smmu_device *smmu,
+ struct arm_smmu_master *master)
+{
+ int i;
+ struct iommu_fwspec *fwspec = dev_iommu_fwspec_get(master->dev);
+
+ if (!master->streams)
+ return;
+
+ mutex_lock(&smmu->streams_mutex);
+ for (i = 0; i < fwspec->num_ids; i++)
+ rb_erase(&master->streams[i].node, &smmu->streams);
+ mutex_unlock(&smmu->streams_mutex);
+
+ kfree(master->streams);
+}
+
static struct iommu_ops arm_smmu_ops;
static int arm_smmu_add_device(struct device *dev)
{
- int i, ret;
+ int ret;
struct arm_smmu_device *smmu;
struct arm_smmu_master *master;
struct iommu_fwspec *fwspec = dev_iommu_fwspec_get(dev);
@@ -3455,27 +3580,12 @@ static int arm_smmu_add_device(struct device *dev)
master->dev = dev;
master->smmu = smmu;
- master->sids = fwspec->ids;
- master->num_sids = fwspec->num_ids;
INIT_LIST_HEAD(&master->bonds);
dev_iommu_priv_set(dev, master);
- /* Check the SIDs are in range of the SMMU and our stream table */
- for (i = 0; i < master->num_sids; i++) {
- u32 sid = master->sids[i];
-
- if (!arm_smmu_sid_in_range(smmu, sid)) {
- ret = -ERANGE;
- goto err_free_master;
- }
-
- /* Ensure l2 strtab is initialised */
- if (smmu->features & ARM_SMMU_FEAT_2_LVL_STRTAB) {
- ret = arm_smmu_init_l2_strtab(smmu, sid);
- if (ret)
- goto err_free_master;
- }
- }
+ ret = arm_smmu_insert_master(smmu, master);
+ if (ret)
+ goto err_free_master;
master->ssid_bits = min(smmu->ssid_bits, fwspec->num_pasid_bits);
@@ -3510,6 +3620,7 @@ static int arm_smmu_add_device(struct device *dev)
iommu_device_unlink(&smmu->iommu, dev);
err_disable_pasid:
arm_smmu_disable_pasid(master);
+ arm_smmu_remove_master(smmu, master);
err_free_master:
kfree(master);
dev_iommu_priv_set(dev, NULL);
@@ -3532,6 +3643,7 @@ static void arm_smmu_remove_device(struct device *dev)
iommu_group_remove_device(dev);
iommu_device_unlink(&smmu->iommu, dev);
arm_smmu_disable_pasid(master);
+ arm_smmu_remove_master(smmu, master);
kfree(master);
iommu_fwspec_free(dev);
}
@@ -3994,6 +4106,9 @@ static int arm_smmu_init_structures(struct arm_smmu_device *smmu)
{
int ret;
+ mutex_init(&smmu->streams_mutex);
+ smmu->streams = RB_ROOT;
+
ret = arm_smmu_init_queues(smmu);
if (ret)
return ret;
--
2.26.2
^ permalink raw reply related
* [PATCH v7 18/24] iommu/arm-smmu-v3: Add support for Hardware Translation Table Update
From: Jean-Philippe Brucker @ 2020-05-19 17:54 UTC (permalink / raw)
To: iommu, devicetree, linux-arm-kernel, linux-pci, linux-mm
Cc: joro, catalin.marinas, will, robin.murphy, kevin.tian, baolu.lu,
Jonathan.Cameron, jacob.jun.pan, christian.koenig, felix.kuehling,
zhangfei.gao, jgg, xuzaibo, fenghua.yu, hch,
Jean-Philippe Brucker
In-Reply-To: <20200519175502.2504091-1-jean-philippe@linaro.org>
If the SMMU supports it and the kernel was built with HTTU support,
enable hardware update of access and dirty flags. This is essential for
shared page tables, to reduce the number of access faults on the fault
queue. Normal DMA with io-pgtables doesn't currently use the access or
dirty flags.
We can enable HTTU even if CPUs don't support it, because the kernel
always checks for HW dirty bit and updates the PTE flags atomically.
Signed-off-by: Jean-Philippe Brucker <jean-philippe@linaro.org>
---
drivers/iommu/arm-smmu-v3.c | 24 +++++++++++++++++++++++-
1 file changed, 23 insertions(+), 1 deletion(-)
diff --git a/drivers/iommu/arm-smmu-v3.c b/drivers/iommu/arm-smmu-v3.c
index 1386d4d2bc60..6a368218f54c 100644
--- a/drivers/iommu/arm-smmu-v3.c
+++ b/drivers/iommu/arm-smmu-v3.c
@@ -58,6 +58,8 @@
#define IDR0_ASID16 (1 << 12)
#define IDR0_ATS (1 << 10)
#define IDR0_HYP (1 << 9)
+#define IDR0_HD (1 << 7)
+#define IDR0_HA (1 << 6)
#define IDR0_BTM (1 << 5)
#define IDR0_COHACC (1 << 4)
#define IDR0_TTF GENMASK(3, 2)
@@ -311,6 +313,9 @@
#define CTXDESC_CD_0_TCR_IPS GENMASK_ULL(34, 32)
#define CTXDESC_CD_0_TCR_TBI0 (1ULL << 38)
+#define CTXDESC_CD_0_TCR_HA (1UL << 43)
+#define CTXDESC_CD_0_TCR_HD (1UL << 42)
+
#define CTXDESC_CD_0_AA64 (1UL << 41)
#define CTXDESC_CD_0_S (1UL << 44)
#define CTXDESC_CD_0_R (1UL << 45)
@@ -663,6 +668,8 @@ struct arm_smmu_device {
#define ARM_SMMU_FEAT_E2H (1 << 16)
#define ARM_SMMU_FEAT_BTM (1 << 17)
#define ARM_SMMU_FEAT_SVA (1 << 18)
+#define ARM_SMMU_FEAT_HA (1 << 19)
+#define ARM_SMMU_FEAT_HD (1 << 20)
u32 features;
#define ARM_SMMU_OPT_SKIP_PREFETCH (1 << 0)
@@ -1718,10 +1725,17 @@ static int arm_smmu_write_ctx_desc(struct arm_smmu_domain *smmu_domain,
* this substream's traffic
*/
} else { /* (1) and (2) */
+ u64 tcr = cd->tcr;
+
cdptr[1] = cpu_to_le64(cd->ttbr & CTXDESC_CD_1_TTB0_MASK);
cdptr[2] = 0;
cdptr[3] = cpu_to_le64(cd->mair);
+ if (!(smmu->features & ARM_SMMU_FEAT_HD))
+ tcr &= ~CTXDESC_CD_0_TCR_HD;
+ if (!(smmu->features & ARM_SMMU_FEAT_HA))
+ tcr &= ~CTXDESC_CD_0_TCR_HA;
+
/*
* STE is live, and the SMMU might read dwords of this CD in any
* order. Ensure that it observes valid values before reading
@@ -1729,7 +1743,7 @@ static int arm_smmu_write_ctx_desc(struct arm_smmu_domain *smmu_domain,
*/
arm_smmu_sync_cd(smmu_domain, ssid, true);
- val = cd->tcr |
+ val = tcr |
#ifdef __BIG_ENDIAN
CTXDESC_CD_0_ENDI |
#endif
@@ -1958,10 +1972,12 @@ static struct arm_smmu_ctx_desc *arm_smmu_alloc_shared_cd(struct mm_struct *mm)
return old_cd;
}
+ /* HA and HD will be filtered out later if not supported by the SMMU */
tcr = FIELD_PREP(CTXDESC_CD_0_TCR_T0SZ, 64ULL - VA_BITS) |
FIELD_PREP(CTXDESC_CD_0_TCR_IRGN0, ARM_LPAE_TCR_RGN_WBWA) |
FIELD_PREP(CTXDESC_CD_0_TCR_ORGN0, ARM_LPAE_TCR_RGN_WBWA) |
FIELD_PREP(CTXDESC_CD_0_TCR_SH0, ARM_LPAE_TCR_SH_IS) |
+ CTXDESC_CD_0_TCR_HA | CTXDESC_CD_0_TCR_HD |
CTXDESC_CD_0_TCR_EPD1 | CTXDESC_CD_0_AA64;
switch (PAGE_SIZE) {
@@ -4454,6 +4470,12 @@ static int arm_smmu_device_hw_probe(struct arm_smmu_device *smmu)
smmu->features |= ARM_SMMU_FEAT_E2H;
}
+ if (reg & (IDR0_HA | IDR0_HD)) {
+ smmu->features |= ARM_SMMU_FEAT_HA;
+ if (reg & IDR0_HD)
+ smmu->features |= ARM_SMMU_FEAT_HD;
+ }
+
/*
* If the CPU is using VHE, but the SMMU doesn't support it, the SMMU
* will create TLB entries for NH-EL1 world and will miss the
--
2.26.2
^ permalink raw reply related
* [PATCH v7 17/24] iommu/arm-smmu-v3: Hook up ATC invalidation to mm ops
From: Jean-Philippe Brucker @ 2020-05-19 17:54 UTC (permalink / raw)
To: iommu, devicetree, linux-arm-kernel, linux-pci, linux-mm
Cc: joro, catalin.marinas, will, robin.murphy, kevin.tian, baolu.lu,
Jonathan.Cameron, jacob.jun.pan, christian.koenig, felix.kuehling,
zhangfei.gao, jgg, xuzaibo, fenghua.yu, hch,
Jean-Philippe Brucker
In-Reply-To: <20200519175502.2504091-1-jean-philippe@linaro.org>
The invalidate_range() notifier is called for any change to the address
space. Perform the required ATC invalidations.
Signed-off-by: Jean-Philippe Brucker <jean-philippe@linaro.org>
---
v6->v7: invalidate() doesn't need RCU protection anymore.
---
drivers/iommu/arm-smmu-v3.c | 29 +++++++++++++++++++++++------
1 file changed, 23 insertions(+), 6 deletions(-)
diff --git a/drivers/iommu/arm-smmu-v3.c b/drivers/iommu/arm-smmu-v3.c
index 00a9342eed99..1386d4d2bc60 100644
--- a/drivers/iommu/arm-smmu-v3.c
+++ b/drivers/iommu/arm-smmu-v3.c
@@ -2392,6 +2392,20 @@ arm_smmu_atc_inv_to_cmd(int ssid, unsigned long iova, size_t size,
size_t inval_grain_shift = 12;
unsigned long page_start, page_end;
+ /*
+ * ATS and PASID:
+ *
+ * If substream_valid is clear, the PCIe TLP is sent without a PASID
+ * prefix. In that case all ATC entries within the address range are
+ * invalidated, including those that were requested with a PASID! There
+ * is no way to invalidate only entries without PASID.
+ *
+ * When using STRTAB_STE_1_S1DSS_SSID0 (reserving CD 0 for non-PASID
+ * traffic), translation requests without PASID create ATC entries
+ * without PASID, which must be invalidated with substream_valid clear.
+ * This has the unpleasant side-effect of invalidating all PASID-tagged
+ * ATC entries within the address range.
+ */
*cmd = (struct arm_smmu_cmdq_ent) {
.opcode = CMDQ_OP_ATC_INV,
.substream_valid = !!ssid,
@@ -2435,12 +2449,12 @@ arm_smmu_atc_inv_to_cmd(int ssid, unsigned long iova, size_t size,
cmd->atc.size = log2_span;
}
-static int arm_smmu_atc_inv_master(struct arm_smmu_master *master)
+static int arm_smmu_atc_inv_master(struct arm_smmu_master *master, int ssid)
{
int i;
struct arm_smmu_cmdq_ent cmd;
- arm_smmu_atc_inv_to_cmd(0, 0, 0, &cmd);
+ arm_smmu_atc_inv_to_cmd(ssid, 0, 0, &cmd);
for (i = 0; i < master->num_sids; i++) {
cmd.atc.sid = master->sids[i];
@@ -2968,7 +2982,7 @@ static void arm_smmu_disable_ats(struct arm_smmu_master *master)
* ATC invalidation via the SMMU.
*/
wmb();
- arm_smmu_atc_inv_master(master);
+ arm_smmu_atc_inv_master(master, 0);
atomic_dec(&smmu_domain->nr_ats_masters);
}
@@ -3169,7 +3183,10 @@ static void arm_smmu_mm_invalidate_range(struct mmu_notifier *mn,
struct mm_struct *mm,
unsigned long start, unsigned long end)
{
- /* TODO: invalidate ATS */
+ struct arm_smmu_mmu_notifier *smmu_mn = mn_to_smmu(mn);
+
+ arm_smmu_atc_inv_domain(smmu_mn->domain, mm->pasid, start,
+ end - start + 1);
}
static void arm_smmu_mm_release(struct mmu_notifier *mn, struct mm_struct *mm)
@@ -3190,7 +3207,7 @@ static void arm_smmu_mm_release(struct mmu_notifier *mn, struct mm_struct *mm)
arm_smmu_write_ctx_desc(smmu_domain, mm->pasid, &invalid_cd);
arm_smmu_tlb_inv_asid(smmu_domain->smmu, smmu_mn->cd->asid);
- /* TODO: invalidate ATS */
+ arm_smmu_atc_inv_domain(smmu_domain, mm->pasid, 0, 0);
smmu_mn->cleared = true;
mutex_unlock(&sva_lock);
@@ -3281,7 +3298,7 @@ void arm_smmu_mmu_notifier_put(struct arm_smmu_mmu_notifier *smmu_mn)
*/
if (!smmu_mn->cleared) {
arm_smmu_tlb_inv_asid(smmu_domain->smmu, cd->asid);
- /* TODO: invalidate ATS */
+ arm_smmu_atc_inv_domain(smmu_domain, mm->pasid, 0, 0);
}
/* Frees smmu_mn */
--
2.26.2
^ permalink raw reply related
* [PATCH v7 16/24] iommu/arm-smmu-v3: Implement iommu_sva_bind/unbind()
From: Jean-Philippe Brucker @ 2020-05-19 17:54 UTC (permalink / raw)
To: iommu, devicetree, linux-arm-kernel, linux-pci, linux-mm
Cc: joro, catalin.marinas, will, robin.murphy, kevin.tian, baolu.lu,
Jonathan.Cameron, jacob.jun.pan, christian.koenig, felix.kuehling,
zhangfei.gao, jgg, xuzaibo, fenghua.yu, hch,
Jean-Philippe Brucker
In-Reply-To: <20200519175502.2504091-1-jean-philippe@linaro.org>
The sva_bind() function allows devices to access process address spaces
using a PASID (aka SSID).
(1) bind() allocates or gets an existing MMU notifier tied to the
(domain, mm) pair. Each mm gets one PASID.
(2) Any change to the address space calls invalidate_range() which sends
ATC invalidations (in a subsequent patch).
(3) When the process address space dies, the release() notifier disables
the CD to allow reclaiming the page tables. Since release() has to
be light we do not instruct device drivers to stop DMA here, we just
ignore incoming page faults from this point onwards.
To avoid any event 0x0a print (C_BAD_CD) we disable translation
without clearing CD.V. PCIe Translation Requests and Page Requests
are silently denied. Don't clear the R bit because the S bit can't
be cleared when STALL_MODEL==0b10 (forced), and clearing R without
clearing S is useless. Faulting transactions will stall and will be
aborted by the IOPF handler.
(4) After stopping DMA, the device driver releases the bond by calling
unbind(). We release the MMU notifier, free the PASID and the bond.
Three structures keep track of bonds:
* arm_smmu_bond: one per {device, mm} pair, the handle returned to the
device driver for a bind() request.
* arm_smmu_mmu_notifier: one per {domain, mm} pair, deals with ATS/TLB
invalidations and clearing the context descriptor on mm exit.
* arm_smmu_ctx_desc: one per mm, holds the pinned ASID and pgd.
Signed-off-by: Jean-Philippe Brucker <jean-philippe@linaro.org>
---
v6->v7: Keep track of {domains, mm} pairs. Move
mmu_notifier_synchronize() to module_exit().
---
drivers/iommu/Kconfig | 2 +
drivers/iommu/arm-smmu-v3.c | 272 +++++++++++++++++++++++++++++++++++-
2 files changed, 269 insertions(+), 5 deletions(-)
diff --git a/drivers/iommu/Kconfig b/drivers/iommu/Kconfig
index 15e9dc4e503c..00b517f449ab 100644
--- a/drivers/iommu/Kconfig
+++ b/drivers/iommu/Kconfig
@@ -432,8 +432,10 @@ config ARM_SMMU_V3
tristate "ARM Ltd. System MMU Version 3 (SMMUv3) Support"
depends on ARM64
select IOMMU_API
+ select IOMMU_SVA
select IOMMU_IO_PGTABLE_LPAE
select GENERIC_MSI_IRQ_DOMAIN
+ select MMU_NOTIFIER
help
Support for implementations of the ARM System MMU architecture
version 3 providing translation support to a PCIe root complex.
diff --git a/drivers/iommu/arm-smmu-v3.c b/drivers/iommu/arm-smmu-v3.c
index b016b61cee23..00a9342eed99 100644
--- a/drivers/iommu/arm-smmu-v3.c
+++ b/drivers/iommu/arm-smmu-v3.c
@@ -24,6 +24,7 @@
#include <linux/iopoll.h>
#include <linux/module.h>
#include <linux/mmu_context.h>
+#include <linux/mmu_notifier.h>
#include <linux/msi.h>
#include <linux/of.h>
#include <linux/of_address.h>
@@ -36,6 +37,7 @@
#include <linux/amba/bus.h>
#include "io-pgtable-arm.h"
+#include "iommu-sva.h"
/* MMIO registers */
#define ARM_SMMU_IDR0 0x0
@@ -734,8 +736,32 @@ struct arm_smmu_domain {
struct list_head devices;
spinlock_t devices_lock;
+
+ struct list_head mmu_notifiers;
+};
+
+struct arm_smmu_mmu_notifier {
+ struct mmu_notifier mn;
+ struct arm_smmu_ctx_desc *cd;
+ bool cleared;
+ refcount_t refs;
+ struct list_head list;
+ struct arm_smmu_domain *domain;
};
+#define mn_to_smmu(mn) container_of(mn, struct arm_smmu_mmu_notifier, mn)
+
+struct arm_smmu_bond {
+ struct iommu_sva sva;
+ struct mm_struct *mm;
+ struct arm_smmu_mmu_notifier *smmu_mn;
+ struct list_head list;
+ refcount_t refs;
+};
+
+#define sva_to_bond(handle) \
+ container_of(handle, struct arm_smmu_bond, sva)
+
struct arm_smmu_option_prop {
u32 opt;
const char *prop;
@@ -745,6 +771,13 @@ static DEFINE_XARRAY_ALLOC1(asid_xa);
static DEFINE_MUTEX(asid_lock);
static DEFINE_MUTEX(sva_lock);
+/*
+ * When a process dies, DMA is still running but we need to clear the pgd. If we
+ * simply cleared the valid bit from the context descriptor, we'd get event 0x0a
+ * which are not recoverable.
+ */
+static struct arm_smmu_ctx_desc invalid_cd = { 0 };
+
static struct arm_smmu_option_prop arm_smmu_options[] = {
{ ARM_SMMU_OPT_SKIP_PREFETCH, "hisilicon,broken-prefetch-cmd" },
{ ARM_SMMU_OPT_PAGE0_REGS_ONLY, "cavium,cn9900-broken-page1-regspace"},
@@ -1654,7 +1687,9 @@ static int arm_smmu_write_ctx_desc(struct arm_smmu_domain *smmu_domain,
* (2) Install a secondary CD, for SID+SSID traffic.
* (3) Update ASID of a CD. Atomically write the first 64 bits of the
* CD, then invalidate the old entry and mappings.
- * (4) Remove a secondary CD.
+ * (4) Quiesce the context without clearing the valid bit. Disable
+ * translation, and ignore any translation fault.
+ * (5) Remove a secondary CD.
*/
u64 val;
bool cd_live;
@@ -1671,8 +1706,10 @@ static int arm_smmu_write_ctx_desc(struct arm_smmu_domain *smmu_domain,
val = le64_to_cpu(cdptr[0]);
cd_live = !!(val & CTXDESC_CD_0_V);
- if (!cd) { /* (4) */
+ if (!cd) { /* (5) */
val = 0;
+ } else if (cd == &invalid_cd) { /* (4) */
+ val |= CTXDESC_CD_0_TCR_EPD0;
} else if (cd_live) { /* (3) */
val &= ~CTXDESC_CD_0_ASID;
val |= FIELD_PREP(CTXDESC_CD_0_ASID, cd->asid);
@@ -1872,7 +1909,6 @@ static struct arm_smmu_ctx_desc *arm_smmu_share_asid(u16 asid)
return NULL;
}
-__maybe_unused
static struct arm_smmu_ctx_desc *arm_smmu_alloc_shared_cd(struct mm_struct *mm)
{
u16 asid;
@@ -1969,7 +2005,6 @@ static struct arm_smmu_ctx_desc *arm_smmu_alloc_shared_cd(struct mm_struct *mm)
return ERR_PTR(ret);
}
-__maybe_unused
static void arm_smmu_free_shared_cd(struct arm_smmu_ctx_desc *cd)
{
lockdep_assert_held(&sva_lock);
@@ -2606,6 +2641,8 @@ static bool arm_smmu_capable(enum iommu_cap cap)
}
}
+static struct mmu_notifier_ops arm_smmu_mmu_notifier_ops;
+
static struct iommu_domain *arm_smmu_domain_alloc(unsigned type)
{
struct arm_smmu_domain *smmu_domain;
@@ -2633,6 +2670,7 @@ static struct iommu_domain *arm_smmu_domain_alloc(unsigned type)
mutex_init(&smmu_domain->init_mutex);
INIT_LIST_HEAD(&smmu_domain->devices);
spin_lock_init(&smmu_domain->devices_lock);
+ INIT_LIST_HEAD(&smmu_domain->mmu_notifiers);
return &smmu_domain->domain;
}
@@ -3127,6 +3165,216 @@ arm_smmu_iova_to_phys(struct iommu_domain *domain, dma_addr_t iova)
return ops->iova_to_phys(ops, iova);
}
+static void arm_smmu_mm_invalidate_range(struct mmu_notifier *mn,
+ struct mm_struct *mm,
+ unsigned long start, unsigned long end)
+{
+ /* TODO: invalidate ATS */
+}
+
+static void arm_smmu_mm_release(struct mmu_notifier *mn, struct mm_struct *mm)
+{
+ struct arm_smmu_mmu_notifier *smmu_mn = mn_to_smmu(mn);
+ struct arm_smmu_domain *smmu_domain = smmu_mn->domain;
+
+ mutex_lock(&sva_lock);
+ if (smmu_mn->cleared) {
+ mutex_unlock(&sva_lock);
+ return;
+ }
+
+ /*
+ * DMA may still be running. Keep the cd valid to avoid C_BAD_CD events,
+ * but disable translation.
+ */
+ arm_smmu_write_ctx_desc(smmu_domain, mm->pasid, &invalid_cd);
+
+ arm_smmu_tlb_inv_asid(smmu_domain->smmu, smmu_mn->cd->asid);
+ /* TODO: invalidate ATS */
+
+ smmu_mn->cleared = true;
+ mutex_unlock(&sva_lock);
+}
+
+static void arm_smmu_mmu_notifier_free(struct mmu_notifier *mn)
+{
+ kfree(mn_to_smmu(mn));
+}
+
+static struct mmu_notifier_ops arm_smmu_mmu_notifier_ops = {
+ .invalidate_range = arm_smmu_mm_invalidate_range,
+ .release = arm_smmu_mm_release,
+ .free_notifier = arm_smmu_mmu_notifier_free,
+};
+
+/* Allocate or get existing MMU notifier for this {domain, mm} pair */
+static struct arm_smmu_mmu_notifier *
+arm_smmu_mmu_notifier_get(struct arm_smmu_domain *smmu_domain,
+ struct mm_struct *mm)
+{
+ int ret;
+ struct arm_smmu_ctx_desc *cd;
+ struct arm_smmu_mmu_notifier *smmu_mn;
+
+ lockdep_assert_held(&sva_lock);
+
+ list_for_each_entry(smmu_mn, &smmu_domain->mmu_notifiers, list) {
+ if (smmu_mn->mn.mm == mm) {
+ refcount_inc(&smmu_mn->refs);
+ return smmu_mn;
+ }
+ }
+
+ cd = arm_smmu_alloc_shared_cd(mm);
+ if (IS_ERR(cd))
+ return ERR_CAST(cd);
+
+ smmu_mn = kzalloc(sizeof(*smmu_mn), GFP_KERNEL);
+ if (!smmu_mn) {
+ ret = -ENOMEM;
+ goto err_free_cd;
+ }
+
+ refcount_set(&smmu_mn->refs, 1);
+ smmu_mn->cd = cd;
+ smmu_mn->domain = smmu_domain;
+ smmu_mn->mn.ops = &arm_smmu_mmu_notifier_ops;
+
+ ret = mmu_notifier_register(&smmu_mn->mn, mm);
+ if (ret) {
+ kfree(smmu_mn);
+ goto err_free_cd;
+ }
+
+ ret = arm_smmu_write_ctx_desc(smmu_domain, mm->pasid, cd);
+ if (ret)
+ goto err_put_notifier;
+
+ list_add(&smmu_mn->list, &smmu_domain->mmu_notifiers);
+ return smmu_mn;
+
+err_put_notifier:
+ /* Frees smmu_mn */
+ mmu_notifier_put(&smmu_mn->mn);
+err_free_cd:
+ arm_smmu_free_shared_cd(cd);
+ return ERR_PTR(ret);
+}
+
+static void arm_smmu_mmu_notifier_put(struct arm_smmu_mmu_notifier *smmu_mn)
+{
+ struct mm_struct *mm = smmu_mn->mn.mm;
+ struct arm_smmu_ctx_desc *cd = smmu_mn->cd;
+ struct arm_smmu_domain *smmu_domain = smmu_mn->domain;
+
+ lockdep_assert_held(&sva_lock);
+
+ if (!refcount_dec_and_test(&smmu_mn->refs))
+ return;
+
+ list_del(&smmu_mn->list);
+ arm_smmu_write_ctx_desc(smmu_domain, mm->pasid, NULL);
+
+ /*
+ * If we went through clear(), we've already invalidated, and no
+ * new TLB entry can have been formed.
+ */
+ if (!smmu_mn->cleared) {
+ arm_smmu_tlb_inv_asid(smmu_domain->smmu, cd->asid);
+ /* TODO: invalidate ATS */
+ }
+
+ /* Frees smmu_mn */
+ mmu_notifier_put(&smmu_mn->mn);
+ arm_smmu_free_shared_cd(cd);
+}
+
+static struct iommu_sva *
+__arm_smmu_sva_bind(struct device *dev, struct mm_struct *mm)
+{
+ int ret;
+ struct arm_smmu_bond *bond;
+ struct arm_smmu_master *master = dev_iommu_priv_get(dev);
+ struct iommu_domain *domain = iommu_get_domain_for_dev(dev);
+ struct arm_smmu_domain *smmu_domain = to_smmu_domain(domain);
+
+ lockdep_assert_held(&sva_lock);
+
+ if (!master || !master->sva_enabled)
+ return ERR_PTR(-ENODEV);
+
+ /* If bind() was already called for this {dev, mm} pair, reuse it. */
+ list_for_each_entry(bond, &master->bonds, list) {
+ if (bond->mm == mm) {
+ refcount_inc(&bond->refs);
+ return &bond->sva;
+ }
+ }
+
+ bond = kzalloc(sizeof(*bond), GFP_KERNEL);
+ if (!bond)
+ return ERR_PTR(-ENOMEM);
+
+ /* Allocate a PASID for this mm if necessary */
+ ret = iommu_sva_alloc_pasid(mm, 1, (1U << master->ssid_bits) - 1);
+ if (ret)
+ goto err_free_bond;
+
+ bond->mm = mm;
+ bond->sva.dev = dev;
+ refcount_set(&bond->refs, 1);
+
+ bond->smmu_mn = arm_smmu_mmu_notifier_get(smmu_domain, mm);
+ if (IS_ERR(bond->smmu_mn))
+ goto err_free_pasid;
+
+ list_add(&bond->list, &master->bonds);
+ return &bond->sva;
+
+err_free_pasid:
+ iommu_sva_free_pasid(mm);
+err_free_bond:
+ kfree(bond);
+ return ERR_PTR(ret);
+}
+
+static struct iommu_sva *
+arm_smmu_sva_bind(struct device *dev, struct mm_struct *mm, void *drvdata)
+{
+ struct iommu_sva *handle;
+ struct iommu_domain *domain = iommu_get_domain_for_dev(dev);
+ struct arm_smmu_domain *smmu_domain = to_smmu_domain(domain);
+
+ if (smmu_domain->stage != ARM_SMMU_DOMAIN_S1)
+ return ERR_PTR(-EINVAL);
+
+ mutex_lock(&sva_lock);
+ handle = __arm_smmu_sva_bind(dev, mm);
+ mutex_unlock(&sva_lock);
+ return handle;
+}
+
+static void arm_smmu_sva_unbind(struct iommu_sva *handle)
+{
+ struct arm_smmu_bond *bond = sva_to_bond(handle);
+
+ mutex_lock(&sva_lock);
+ if (refcount_dec_and_test(&bond->refs)) {
+ list_del(&bond->list);
+ arm_smmu_mmu_notifier_put(bond->smmu_mn);
+ iommu_sva_free_pasid(bond->mm);
+ kfree(bond);
+ }
+ mutex_unlock(&sva_lock);
+}
+
+static int arm_smmu_sva_get_pasid(struct iommu_sva *handle)
+{
+ struct arm_smmu_bond *bond = sva_to_bond(handle);
+
+ return bond->mm->pasid;
+}
+
static struct platform_driver arm_smmu_driver;
static
@@ -3491,6 +3739,9 @@ static struct iommu_ops arm_smmu_ops = {
.dev_feat_enabled = arm_smmu_dev_feature_enabled,
.dev_enable_feat = arm_smmu_dev_enable_feature,
.dev_disable_feat = arm_smmu_dev_disable_feature,
+ .sva_bind = arm_smmu_sva_bind,
+ .sva_unbind = arm_smmu_sva_unbind,
+ .sva_get_pasid = arm_smmu_sva_get_pasid,
.pgsize_bitmap = -1UL, /* Restricted during device attach */
};
@@ -4598,6 +4849,16 @@ static const struct of_device_id arm_smmu_of_match[] = {
};
MODULE_DEVICE_TABLE(of, arm_smmu_of_match);
+static void arm_smmu_driver_unregister(struct platform_driver *drv)
+{
+ /*
+ * Wait for all notifiers free() RCU callbacks, since they are still
+ * using the arm_smmu_mmu_notifier_ops.
+ */
+ mmu_notifier_synchronize();
+ platform_driver_unregister(drv);
+}
+
static struct platform_driver arm_smmu_driver = {
.driver = {
.name = "arm-smmu-v3",
@@ -4608,7 +4869,8 @@ static struct platform_driver arm_smmu_driver = {
.remove = arm_smmu_device_remove,
.shutdown = arm_smmu_device_shutdown,
};
-module_platform_driver(arm_smmu_driver);
+module_driver(arm_smmu_driver, platform_driver_register,
+ arm_smmu_driver_unregister);
MODULE_DESCRIPTION("IOMMU API for ARM architected SMMUv3 implementations");
MODULE_AUTHOR("Will Deacon <will@kernel.org>");
--
2.26.2
^ permalink raw reply related
* [PATCH v7 15/24] iommu/arm-smmu-v3: Add SVA device feature
From: Jean-Philippe Brucker @ 2020-05-19 17:54 UTC (permalink / raw)
To: iommu, devicetree, linux-arm-kernel, linux-pci, linux-mm
Cc: joro, catalin.marinas, will, robin.murphy, kevin.tian, baolu.lu,
Jonathan.Cameron, jacob.jun.pan, christian.koenig, felix.kuehling,
zhangfei.gao, jgg, xuzaibo, fenghua.yu, hch,
Jean-Philippe Brucker
In-Reply-To: <20200519175502.2504091-1-jean-philippe@linaro.org>
Implement the IOMMU device feature callbacks to support the SVA feature.
At the moment dev_has_feat() returns false since I/O Page Faults isn't
yet implemented.
Signed-off-by: Jean-Philippe Brucker <jean-philippe@linaro.org>
---
drivers/iommu/arm-smmu-v3.c | 124 ++++++++++++++++++++++++++++++++++++
1 file changed, 124 insertions(+)
diff --git a/drivers/iommu/arm-smmu-v3.c b/drivers/iommu/arm-smmu-v3.c
index a9f6f1d7014e..b016b61cee23 100644
--- a/drivers/iommu/arm-smmu-v3.c
+++ b/drivers/iommu/arm-smmu-v3.c
@@ -703,6 +703,8 @@ struct arm_smmu_master {
u32 *sids;
unsigned int num_sids;
bool ats_enabled;
+ bool sva_enabled;
+ struct list_head bonds;
unsigned int ssid_bits;
};
@@ -3013,6 +3015,19 @@ static int arm_smmu_attach_dev(struct iommu_domain *domain, struct device *dev)
master = dev_iommu_priv_get(dev);
smmu = master->smmu;
+ /*
+ * Checking that SVA is disabled ensures that this device isn't bound to
+ * any mm, and can be safely detached from its old domain. Bonds cannot
+ * be removed concurrently since we're holding the group mutex.
+ */
+ mutex_lock(&sva_lock);
+ if (master->sva_enabled) {
+ mutex_unlock(&sva_lock);
+ dev_err(dev, "cannot attach - SVA enabled\n");
+ return -EBUSY;
+ }
+ mutex_unlock(&sva_lock);
+
arm_smmu_detach_dev(master);
mutex_lock(&smmu_domain->init_mutex);
@@ -3161,6 +3176,7 @@ static int arm_smmu_add_device(struct device *dev)
master->smmu = smmu;
master->sids = fwspec->ids;
master->num_sids = fwspec->num_ids;
+ INIT_LIST_HEAD(&master->bonds);
dev_iommu_priv_set(dev, master);
/* Check the SIDs are in range of the SMMU and our stream table */
@@ -3230,6 +3246,7 @@ static void arm_smmu_remove_device(struct device *dev)
master = dev_iommu_priv_get(dev);
smmu = master->smmu;
+ WARN_ON(master->sva_enabled);
arm_smmu_detach_dev(master);
iommu_group_remove_device(dev);
iommu_device_unlink(&smmu->iommu, dev);
@@ -3349,6 +3366,109 @@ static void arm_smmu_get_resv_regions(struct device *dev,
iommu_dma_get_resv_regions(dev, head);
}
+static bool arm_smmu_iopf_supported(struct arm_smmu_master *master)
+{
+ return false;
+}
+
+static bool arm_smmu_dev_has_feature(struct device *dev,
+ enum iommu_dev_features feat)
+{
+ struct arm_smmu_master *master = dev_iommu_priv_get(dev);
+
+ if (!master)
+ return false;
+
+ switch (feat) {
+ case IOMMU_DEV_FEAT_SVA:
+ if (!(master->smmu->features & ARM_SMMU_FEAT_SVA))
+ return false;
+
+ /* SSID and IOPF support are mandatory for the moment */
+ return master->ssid_bits && arm_smmu_iopf_supported(master);
+ default:
+ return false;
+ }
+}
+
+static bool arm_smmu_dev_feature_enabled(struct device *dev,
+ enum iommu_dev_features feat)
+{
+ bool enabled = false;
+ struct arm_smmu_master *master = dev_iommu_priv_get(dev);
+
+ if (!master)
+ return false;
+
+ switch (feat) {
+ case IOMMU_DEV_FEAT_SVA:
+ mutex_lock(&sva_lock);
+ enabled = master->sva_enabled;
+ mutex_unlock(&sva_lock);
+ return enabled;
+ default:
+ return false;
+ }
+}
+
+static int arm_smmu_dev_enable_sva(struct device *dev)
+{
+ struct arm_smmu_master *master = dev_iommu_priv_get(dev);
+
+ mutex_lock(&sva_lock);
+ master->sva_enabled = true;
+ mutex_unlock(&sva_lock);
+
+ return 0;
+}
+
+static int arm_smmu_dev_disable_sva(struct device *dev)
+{
+ struct arm_smmu_master *master = dev_iommu_priv_get(dev);
+
+ mutex_lock(&sva_lock);
+ if (!list_empty(&master->bonds)) {
+ dev_err(dev, "cannot disable SVA, device is bound\n");
+ mutex_unlock(&sva_lock);
+ return -EBUSY;
+ }
+ master->sva_enabled = false;
+ mutex_unlock(&sva_lock);
+
+ return 0;
+}
+
+static int arm_smmu_dev_enable_feature(struct device *dev,
+ enum iommu_dev_features feat)
+{
+ if (!arm_smmu_dev_has_feature(dev, feat))
+ return -ENODEV;
+
+ if (arm_smmu_dev_feature_enabled(dev, feat))
+ return -EBUSY;
+
+ switch (feat) {
+ case IOMMU_DEV_FEAT_SVA:
+ return arm_smmu_dev_enable_sva(dev);
+ default:
+ return -EINVAL;
+ }
+}
+
+static int arm_smmu_dev_disable_feature(struct device *dev,
+ enum iommu_dev_features feat)
+{
+ if (!arm_smmu_dev_feature_enabled(dev, feat))
+ return -EINVAL;
+
+ switch (feat) {
+ case IOMMU_DEV_FEAT_SVA:
+ return arm_smmu_dev_disable_sva(dev);
+ default:
+ return -EINVAL;
+ }
+}
+
static struct iommu_ops arm_smmu_ops = {
.capable = arm_smmu_capable,
.domain_alloc = arm_smmu_domain_alloc,
@@ -3367,6 +3487,10 @@ static struct iommu_ops arm_smmu_ops = {
.of_xlate = arm_smmu_of_xlate,
.get_resv_regions = arm_smmu_get_resv_regions,
.put_resv_regions = generic_iommu_put_resv_regions,
+ .dev_has_feat = arm_smmu_dev_has_feature,
+ .dev_feat_enabled = arm_smmu_dev_feature_enabled,
+ .dev_enable_feat = arm_smmu_dev_enable_feature,
+ .dev_disable_feat = arm_smmu_dev_disable_feature,
.pgsize_bitmap = -1UL, /* Restricted during device attach */
};
--
2.26.2
^ permalink raw reply related
* [PATCH v7 13/24] iommu/arm-smmu-v3: Enable broadcast TLB maintenance
From: Jean-Philippe Brucker @ 2020-05-19 17:54 UTC (permalink / raw)
To: iommu, devicetree, linux-arm-kernel, linux-pci, linux-mm
Cc: joro, catalin.marinas, will, robin.murphy, kevin.tian, baolu.lu,
Jonathan.Cameron, jacob.jun.pan, christian.koenig, felix.kuehling,
zhangfei.gao, jgg, xuzaibo, fenghua.yu, hch,
Jean-Philippe Brucker
In-Reply-To: <20200519175502.2504091-1-jean-philippe@linaro.org>
The SMMUv3 can handle invalidation targeted at TLB entries with shared
ASIDs. If the implementation supports broadcast TLB maintenance, enable it
and keep track of it in a feature bit. The SMMU will then be affected by
inner-shareable TLB invalidations from other agents.
A major side-effect of this change is that stage-2 translation contexts
are now affected by all invalidations by VMID. VMIDs are all shared and
the only ways to prevent over-invalidation, since the stage-2 page tables
are not shared between CPU and SMMU, are to either disable BTM or allocate
different VMIDs. This patch does not address the problem.
Signed-off-by: Jean-Philippe Brucker <jean-philippe@linaro.org>
---
drivers/iommu/arm-smmu-v3.c | 19 +++++++++++++++++--
1 file changed, 17 insertions(+), 2 deletions(-)
diff --git a/drivers/iommu/arm-smmu-v3.c b/drivers/iommu/arm-smmu-v3.c
index 7e1933e7e35f..9332253e3608 100644
--- a/drivers/iommu/arm-smmu-v3.c
+++ b/drivers/iommu/arm-smmu-v3.c
@@ -56,6 +56,7 @@
#define IDR0_ASID16 (1 << 12)
#define IDR0_ATS (1 << 10)
#define IDR0_HYP (1 << 9)
+#define IDR0_BTM (1 << 5)
#define IDR0_COHACC (1 << 4)
#define IDR0_TTF GENMASK(3, 2)
#define IDR0_TTF_AARCH64 2
@@ -658,6 +659,7 @@ struct arm_smmu_device {
#define ARM_SMMU_FEAT_VAX (1 << 14)
#define ARM_SMMU_FEAT_RANGE_INV (1 << 15)
#define ARM_SMMU_FEAT_E2H (1 << 16)
+#define ARM_SMMU_FEAT_BTM (1 << 17)
u32 features;
#define ARM_SMMU_OPT_SKIP_PREFETCH (1 << 0)
@@ -3819,11 +3821,14 @@ static int arm_smmu_device_reset(struct arm_smmu_device *smmu, bool bypass)
writel_relaxed(reg, smmu->base + ARM_SMMU_CR1);
/* CR2 (random crap) */
- reg = CR2_PTM | CR2_RECINVSID;
+ reg = CR2_RECINVSID;
if (smmu->features & ARM_SMMU_FEAT_E2H)
reg |= CR2_E2H;
+ if (!(smmu->features & ARM_SMMU_FEAT_BTM))
+ reg |= CR2_PTM;
+
writel_relaxed(reg, smmu->base + ARM_SMMU_CR2);
/* Stream table */
@@ -3934,6 +3939,7 @@ static int arm_smmu_device_hw_probe(struct arm_smmu_device *smmu)
{
u32 reg;
bool coherent = smmu->features & ARM_SMMU_FEAT_COHERENCY;
+ bool vhe = cpus_have_cap(ARM64_HAS_VIRT_HOST_EXTN);
/* IDR0 */
reg = readl_relaxed(smmu->base + ARM_SMMU_IDR0);
@@ -3983,10 +3989,19 @@ static int arm_smmu_device_hw_probe(struct arm_smmu_device *smmu)
if (reg & IDR0_HYP) {
smmu->features |= ARM_SMMU_FEAT_HYP;
- if (cpus_have_cap(ARM64_HAS_VIRT_HOST_EXTN))
+ if (vhe)
smmu->features |= ARM_SMMU_FEAT_E2H;
}
+ /*
+ * If the CPU is using VHE, but the SMMU doesn't support it, the SMMU
+ * will create TLB entries for NH-EL1 world and will miss the
+ * broadcasted TLB invalidations that target EL2-E2H world. Don't enable
+ * BTM in that case.
+ */
+ if (reg & IDR0_BTM && (!vhe || reg & IDR0_HYP))
+ smmu->features |= ARM_SMMU_FEAT_BTM;
+
/*
* The coherency feature as set by FW is used in preference to the ID
* register, but warn on mismatch.
--
2.26.2
^ permalink raw reply related
* [PATCH v7 14/24] iommu/arm-smmu-v3: Add SVA feature checking
From: Jean-Philippe Brucker @ 2020-05-19 17:54 UTC (permalink / raw)
To: iommu, devicetree, linux-arm-kernel, linux-pci, linux-mm
Cc: joro, catalin.marinas, will, robin.murphy, kevin.tian, baolu.lu,
Jonathan.Cameron, jacob.jun.pan, christian.koenig, felix.kuehling,
zhangfei.gao, jgg, xuzaibo, fenghua.yu, hch,
Jean-Philippe Brucker, Suzuki K Poulose
In-Reply-To: <20200519175502.2504091-1-jean-philippe@linaro.org>
Aggregate all sanity-checks for sharing CPU page tables with the SMMU
under a single ARM_SMMU_FEAT_SVA bit. For PCIe SVA, users also need to
check FEAT_ATS and FEAT_PRI. For platform SVA, they will most likely have
to check FEAT_STALLS.
Cc: Suzuki K Poulose <suzuki.poulose@arm.com>
Signed-off-by: Jean-Philippe Brucker <jean-philippe@linaro.org>
---
drivers/iommu/arm-smmu-v3.c | 72 +++++++++++++++++++++++++++++++++++++
1 file changed, 72 insertions(+)
diff --git a/drivers/iommu/arm-smmu-v3.c b/drivers/iommu/arm-smmu-v3.c
index 9332253e3608..a9f6f1d7014e 100644
--- a/drivers/iommu/arm-smmu-v3.c
+++ b/drivers/iommu/arm-smmu-v3.c
@@ -660,6 +660,7 @@ struct arm_smmu_device {
#define ARM_SMMU_FEAT_RANGE_INV (1 << 15)
#define ARM_SMMU_FEAT_E2H (1 << 16)
#define ARM_SMMU_FEAT_BTM (1 << 17)
+#define ARM_SMMU_FEAT_SVA (1 << 18)
u32 features;
#define ARM_SMMU_OPT_SKIP_PREFETCH (1 << 0)
@@ -3935,6 +3936,74 @@ static int arm_smmu_device_reset(struct arm_smmu_device *smmu, bool bypass)
return 0;
}
+static bool arm_smmu_supports_sva(struct arm_smmu_device *smmu)
+{
+ unsigned long reg, fld;
+ unsigned long oas;
+ unsigned long asid_bits;
+
+ u32 feat_mask = ARM_SMMU_FEAT_BTM | ARM_SMMU_FEAT_COHERENCY;
+
+ if ((smmu->features & feat_mask) != feat_mask)
+ return false;
+
+ if (!(smmu->pgsize_bitmap & PAGE_SIZE))
+ return false;
+
+ /*
+ * Get the smallest PA size of all CPUs (sanitized by cpufeature). We're
+ * not even pretending to support AArch32 here.
+ */
+ reg = read_sanitised_ftr_reg(SYS_ID_AA64MMFR0_EL1);
+ fld = cpuid_feature_extract_unsigned_field(reg, ID_AA64MMFR0_PARANGE_SHIFT);
+ switch (fld) {
+ case 0x0:
+ oas = 32;
+ break;
+ case 0x1:
+ oas = 36;
+ break;
+ case 0x2:
+ oas = 40;
+ break;
+ case 0x3:
+ oas = 42;
+ break;
+ case 0x4:
+ oas = 44;
+ break;
+ case 0x5:
+ oas = 48;
+ break;
+ case 0x6:
+ oas = 52;
+ break;
+ default:
+ return false;
+ }
+
+ /* abort if MMU outputs addresses larger than what we support. */
+ if (smmu->oas < oas)
+ return false;
+
+ /* We can support bigger ASIDs than the CPU, but not smaller */
+ fld = cpuid_feature_extract_unsigned_field(reg, ID_AA64MMFR0_ASID_SHIFT);
+ asid_bits = fld ? 16 : 8;
+ if (smmu->asid_bits < asid_bits)
+ return false;
+
+ /*
+ * See max_pinned_asids in arch/arm64/mm/context.c. The following is
+ * generally the maximum number of bindable processes.
+ */
+ if (IS_ENABLED(CONFIG_UNMAP_KERNEL_AT_EL0))
+ asid_bits--;
+ dev_dbg(smmu->dev, "%d shared contexts\n", (1 << asid_bits) -
+ num_possible_cpus() - 2);
+
+ return true;
+}
+
static int arm_smmu_device_hw_probe(struct arm_smmu_device *smmu)
{
u32 reg;
@@ -4147,6 +4216,9 @@ static int arm_smmu_device_hw_probe(struct arm_smmu_device *smmu)
smmu->ias = max(smmu->ias, smmu->oas);
+ if (arm_smmu_supports_sva(smmu))
+ smmu->features |= ARM_SMMU_FEAT_SVA;
+
dev_info(smmu->dev, "ias %lu-bit, oas %lu-bit (features 0x%08x)\n",
smmu->ias, smmu->oas, smmu->features);
return 0;
--
2.26.2
^ permalink raw reply related
* [PATCH v7 12/24] iommu/arm-smmu-v3: Add support for VHE
From: Jean-Philippe Brucker @ 2020-05-19 17:54 UTC (permalink / raw)
To: iommu, devicetree, linux-arm-kernel, linux-pci, linux-mm
Cc: joro, catalin.marinas, will, robin.murphy, kevin.tian, baolu.lu,
Jonathan.Cameron, jacob.jun.pan, christian.koenig, felix.kuehling,
zhangfei.gao, jgg, xuzaibo, fenghua.yu, hch,
Jean-Philippe Brucker
In-Reply-To: <20200519175502.2504091-1-jean-philippe@linaro.org>
ARMv8.1 extensions added Virtualization Host Extensions (VHE), which allow
to run a host kernel at EL2. When using normal DMA, Device and CPU address
spaces are dissociated, and do not need to implement the same
capabilities, so VHE hasn't been used in the SMMU until now.
With shared address spaces however, ASIDs are shared between MMU and SMMU,
and broadcast TLB invalidations issued by a CPU are taken into account by
the SMMU. TLB entries on both sides need to have identical exception level
in order to be cleared with a single invalidation.
When the CPU is using VHE, enable VHE in the SMMU for all STEs. Normal DMA
mappings will need to use TLBI_EL2 commands instead of TLBI_NH, but
shouldn't be otherwise affected by this change.
Signed-off-by: Jean-Philippe Brucker <jean-philippe@linaro.org>
---
drivers/iommu/arm-smmu-v3.c | 31 ++++++++++++++++++++++++++-----
1 file changed, 26 insertions(+), 5 deletions(-)
diff --git a/drivers/iommu/arm-smmu-v3.c b/drivers/iommu/arm-smmu-v3.c
index 403871d36438..7e1933e7e35f 100644
--- a/drivers/iommu/arm-smmu-v3.c
+++ b/drivers/iommu/arm-smmu-v3.c
@@ -13,6 +13,7 @@
#include <linux/acpi_iort.h>
#include <linux/bitfield.h>
#include <linux/bitops.h>
+#include <linux/cpufeature.h>
#include <linux/crash_dump.h>
#include <linux/delay.h>
#include <linux/dma-iommu.h>
@@ -482,6 +483,8 @@ struct arm_smmu_cmdq_ent {
#define CMDQ_OP_TLBI_NH_ASID 0x11
#define CMDQ_OP_TLBI_NH_VA 0x12
#define CMDQ_OP_TLBI_EL2_ALL 0x20
+ #define CMDQ_OP_TLBI_EL2_ASID 0x21
+ #define CMDQ_OP_TLBI_EL2_VA 0x22
#define CMDQ_OP_TLBI_S12_VMALL 0x28
#define CMDQ_OP_TLBI_S2_IPA 0x2a
#define CMDQ_OP_TLBI_NSNH_ALL 0x30
@@ -654,6 +657,7 @@ struct arm_smmu_device {
#define ARM_SMMU_FEAT_STALL_FORCE (1 << 13)
#define ARM_SMMU_FEAT_VAX (1 << 14)
#define ARM_SMMU_FEAT_RANGE_INV (1 << 15)
+#define ARM_SMMU_FEAT_E2H (1 << 16)
u32 features;
#define ARM_SMMU_OPT_SKIP_PREFETCH (1 << 0)
@@ -927,6 +931,8 @@ static int arm_smmu_cmdq_build_cmd(u64 *cmd, struct arm_smmu_cmdq_ent *ent)
cmd[0] |= FIELD_PREP(CMDQ_TLBI_0_NUM, ent->tlbi.num);
cmd[0] |= FIELD_PREP(CMDQ_TLBI_0_SCALE, ent->tlbi.scale);
cmd[0] |= FIELD_PREP(CMDQ_TLBI_0_VMID, ent->tlbi.vmid);
+ /* Fallthrough */
+ case CMDQ_OP_TLBI_EL2_VA:
cmd[0] |= FIELD_PREP(CMDQ_TLBI_0_ASID, ent->tlbi.asid);
cmd[1] |= FIELD_PREP(CMDQ_TLBI_1_LEAF, ent->tlbi.leaf);
cmd[1] |= FIELD_PREP(CMDQ_TLBI_1_TTL, ent->tlbi.ttl);
@@ -948,6 +954,9 @@ static int arm_smmu_cmdq_build_cmd(u64 *cmd, struct arm_smmu_cmdq_ent *ent)
case CMDQ_OP_TLBI_S12_VMALL:
cmd[0] |= FIELD_PREP(CMDQ_TLBI_0_VMID, ent->tlbi.vmid);
break;
+ case CMDQ_OP_TLBI_EL2_ASID:
+ cmd[0] |= FIELD_PREP(CMDQ_TLBI_0_ASID, ent->tlbi.asid);
+ break;
case CMDQ_OP_ATC_INV:
cmd[0] |= FIELD_PREP(CMDQ_0_SSV, ent->substream_valid);
cmd[0] |= FIELD_PREP(CMDQ_ATC_0_GLOBAL, ent->atc.global);
@@ -1541,7 +1550,8 @@ static int arm_smmu_cmdq_batch_submit(struct arm_smmu_device *smmu,
static void arm_smmu_tlb_inv_asid(struct arm_smmu_device *smmu, u16 asid)
{
struct arm_smmu_cmdq_ent cmd = {
- .opcode = CMDQ_OP_TLBI_NH_ASID,
+ .opcode = smmu->features & ARM_SMMU_FEAT_E2H ?
+ CMDQ_OP_TLBI_EL2_ASID : CMDQ_OP_TLBI_NH_ASID,
.tlbi.asid = asid,
};
@@ -2084,13 +2094,16 @@ static void arm_smmu_write_strtab_ent(struct arm_smmu_master *master, u32 sid,
}
if (s1_cfg) {
+ int strw = smmu->features & ARM_SMMU_FEAT_E2H ?
+ STRTAB_STE_1_STRW_EL2 : STRTAB_STE_1_STRW_NSEL1;
+
BUG_ON(ste_live);
dst[1] = cpu_to_le64(
FIELD_PREP(STRTAB_STE_1_S1DSS, STRTAB_STE_1_S1DSS_SSID0) |
FIELD_PREP(STRTAB_STE_1_S1CIR, STRTAB_STE_1_S1C_CACHE_WBRA) |
FIELD_PREP(STRTAB_STE_1_S1COR, STRTAB_STE_1_S1C_CACHE_WBRA) |
FIELD_PREP(STRTAB_STE_1_S1CSH, ARM_SMMU_SH_ISH) |
- FIELD_PREP(STRTAB_STE_1_STRW, STRTAB_STE_1_STRW_NSEL1));
+ FIELD_PREP(STRTAB_STE_1_STRW, strw));
if (smmu->features & ARM_SMMU_FEAT_STALLS &&
!(smmu->features & ARM_SMMU_FEAT_STALL_FORCE))
@@ -2486,7 +2499,8 @@ static void arm_smmu_tlb_inv_range(unsigned long iova, size_t size,
return;
if (smmu_domain->stage == ARM_SMMU_DOMAIN_S1) {
- cmd.opcode = CMDQ_OP_TLBI_NH_VA;
+ cmd.opcode = smmu->features & ARM_SMMU_FEAT_E2H ?
+ CMDQ_OP_TLBI_EL2_VA : CMDQ_OP_TLBI_NH_VA;
cmd.tlbi.asid = smmu_domain->s1_cfg.cd.asid;
} else {
cmd.opcode = CMDQ_OP_TLBI_S2_IPA;
@@ -3805,7 +3819,11 @@ static int arm_smmu_device_reset(struct arm_smmu_device *smmu, bool bypass)
writel_relaxed(reg, smmu->base + ARM_SMMU_CR1);
/* CR2 (random crap) */
- reg = CR2_PTM | CR2_RECINVSID | CR2_E2H;
+ reg = CR2_PTM | CR2_RECINVSID;
+
+ if (smmu->features & ARM_SMMU_FEAT_E2H)
+ reg |= CR2_E2H;
+
writel_relaxed(reg, smmu->base + ARM_SMMU_CR2);
/* Stream table */
@@ -3963,8 +3981,11 @@ static int arm_smmu_device_hw_probe(struct arm_smmu_device *smmu)
if (reg & IDR0_MSI)
smmu->features |= ARM_SMMU_FEAT_MSI;
- if (reg & IDR0_HYP)
+ if (reg & IDR0_HYP) {
smmu->features |= ARM_SMMU_FEAT_HYP;
+ if (cpus_have_cap(ARM64_HAS_VIRT_HOST_EXTN))
+ smmu->features |= ARM_SMMU_FEAT_E2H;
+ }
/*
* The coherency feature as set by FW is used in preference to the ID
--
2.26.2
^ permalink raw reply related
page: next (older) | prev (newer) | latest
- recent:[subjects (threaded)|topics (new)|topics (active)]
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox