2018-11-19 09:42:48

by Stefan Agner

[permalink] [raw]
Subject: [PATCH 1/2] PCI: dwc: allow to limit registers set length

Add length to the struct dw_pcie and check that the accessors
dw_pcie_(rd|wr)_own_conf() do not read/write beyond that point.

Signed-off-by: Stefan Agner <[email protected]>
---
drivers/pci/controller/dwc/pcie-designware-host.c | 4 ++++
drivers/pci/controller/dwc/pcie-designware.h | 1 +
2 files changed, 5 insertions(+)

diff --git a/drivers/pci/controller/dwc/pcie-designware-host.c b/drivers/pci/controller/dwc/pcie-designware-host.c
index 29a05759a294..b422538ee0bb 100644
--- a/drivers/pci/controller/dwc/pcie-designware-host.c
+++ b/drivers/pci/controller/dwc/pcie-designware-host.c
@@ -29,6 +29,8 @@ static int dw_pcie_rd_own_conf(struct pcie_port *pp, int where, int size,
return pp->ops->rd_own_conf(pp, where, size, val);

pci = to_dw_pcie_from_pp(pp);
+ if (pci->dbi_length && where + size > pci->dbi_length)
+ return PCIBIOS_BAD_REGISTER_NUMBER;
return dw_pcie_read(pci->dbi_base + where, size, val);
}

@@ -41,6 +43,8 @@ static int dw_pcie_wr_own_conf(struct pcie_port *pp, int where, int size,
return pp->ops->wr_own_conf(pp, where, size, val);

pci = to_dw_pcie_from_pp(pp);
+ if (pci->dbi_length && where + size > pci->dbi_length)
+ return PCIBIOS_BAD_REGISTER_NUMBER;
return dw_pcie_write(pci->dbi_base + where, size, val);
}

diff --git a/drivers/pci/controller/dwc/pcie-designware.h b/drivers/pci/controller/dwc/pcie-designware.h
index 9f1a5e399b70..5be5f369abf2 100644
--- a/drivers/pci/controller/dwc/pcie-designware.h
+++ b/drivers/pci/controller/dwc/pcie-designware.h
@@ -215,6 +215,7 @@ struct dw_pcie {
struct device *dev;
void __iomem *dbi_base;
void __iomem *dbi_base2;
+ int dbi_length;
u32 num_viewport;
u8 iatu_unroll_enabled;
struct pcie_port pp;
--
2.19.1



2018-11-19 09:44:07

by Stefan Agner

[permalink] [raw]
Subject: [PATCH 2/2] PCI: imx6: limit DBI register length

Define the length of the DBI registers. This makes sure that
the kernel does not access registers beyond that point, avoiding
the following abort on a i.MX 6Quad:
# cat /sys/devices/soc0/soc/1ffc000.pcie/pci0000\:00/0000\:00\:00.0/config
[ 100.021433] Unhandled fault: imprecise external abort (0x1406) at 0xb6ea7000
...
[ 100.056423] PC is at dw_pcie_read+0x50/0x84
[ 100.060790] LR is at dw_pcie_rd_own_conf+0x44/0x48
...

Signed-off-by: Stefan Agner <[email protected]>
---
drivers/pci/controller/dwc/pci-imx6.c | 51 +++++++++++++++++++--------
1 file changed, 37 insertions(+), 14 deletions(-)

diff --git a/drivers/pci/controller/dwc/pci-imx6.c b/drivers/pci/controller/dwc/pci-imx6.c
index 4a9a673b4777..8c96af414dac 100644
--- a/drivers/pci/controller/dwc/pci-imx6.c
+++ b/drivers/pci/controller/dwc/pci-imx6.c
@@ -39,6 +39,11 @@ enum imx6_pcie_variants {
IMX7D,
};

+struct imx6_pcie_drvdata {
+ enum imx6_pcie_variants variant;
+ int dbi_length;
+};
+
struct imx6_pcie {
struct dw_pcie *pci;
int reset_gpio;
@@ -50,7 +55,6 @@ struct imx6_pcie {
struct regmap *iomuxc_gpr;
struct reset_control *pciephy_reset;
struct reset_control *apps_reset;
- enum imx6_pcie_variants variant;
u32 tx_deemph_gen1;
u32 tx_deemph_gen2_3p5db;
u32 tx_deemph_gen2_6db;
@@ -58,6 +62,7 @@ struct imx6_pcie {
u32 tx_swing_low;
int link_gen;
struct regulator *vpcie;
+ const struct imx6_pcie_drvdata *drvdata;
};

/* Parameters for the waiting for PCIe PHY PLL to lock on i.MX7 */
@@ -285,7 +290,7 @@ static void imx6_pcie_assert_core_reset(struct imx6_pcie *imx6_pcie)
{
struct device *dev = imx6_pcie->pci->dev;

- switch (imx6_pcie->variant) {
+ switch (imx6_pcie->drvdata->variant) {
case IMX7D:
reset_control_assert(imx6_pcie->pciephy_reset);
reset_control_assert(imx6_pcie->apps_reset);
@@ -327,7 +332,7 @@ static int imx6_pcie_enable_ref_clk(struct imx6_pcie *imx6_pcie)
struct device *dev = pci->dev;
int ret = 0;

- switch (imx6_pcie->variant) {
+ switch (imx6_pcie->drvdata->variant) {
case IMX6SX:
ret = clk_prepare_enable(imx6_pcie->pcie_inbound_axi);
if (ret) {
@@ -430,7 +435,7 @@ static void imx6_pcie_deassert_core_reset(struct imx6_pcie *imx6_pcie)
!imx6_pcie->gpio_active_high);
}

- switch (imx6_pcie->variant) {
+ switch (imx6_pcie->drvdata->variant) {
case IMX7D:
reset_control_deassert(imx6_pcie->pciephy_reset);
imx7d_pcie_wait_for_phy_pll_lock(imx6_pcie);
@@ -468,7 +473,7 @@ static void imx6_pcie_deassert_core_reset(struct imx6_pcie *imx6_pcie)

static void imx6_pcie_init_phy(struct imx6_pcie *imx6_pcie)
{
- switch (imx6_pcie->variant) {
+ switch (imx6_pcie->drvdata->variant) {
case IMX7D:
regmap_update_bits(imx6_pcie->iomuxc_gpr, IOMUXC_GPR12,
IMX7D_GPR12_PCIE_PHY_REFCLK_SEL, 0);
@@ -560,7 +565,7 @@ static int imx6_pcie_establish_link(struct imx6_pcie *imx6_pcie)
dw_pcie_writel_dbi(pci, PCIE_RC_LCR, tmp);

/* Start LTSSM. */
- if (imx6_pcie->variant == IMX7D)
+ if (imx6_pcie->drvdata->variant == IMX7D)
reset_control_deassert(imx6_pcie->apps_reset);
else
regmap_update_bits(imx6_pcie->iomuxc_gpr, IOMUXC_GPR12,
@@ -585,7 +590,7 @@ static int imx6_pcie_establish_link(struct imx6_pcie *imx6_pcie)
tmp |= PORT_LOGIC_SPEED_CHANGE;
dw_pcie_writel_dbi(pci, PCIE_LINK_WIDTH_SPEED_CONTROL, tmp);

- if (imx6_pcie->variant != IMX7D) {
+ if (imx6_pcie->drvdata->variant != IMX7D) {
/*
* On i.MX7, DIRECT_SPEED_CHANGE behaves differently
* from i.MX6 family when no link speed transition
@@ -703,8 +708,7 @@ static int imx6_pcie_probe(struct platform_device *pdev)
pci->ops = &dw_pcie_ops;

imx6_pcie->pci = pci;
- imx6_pcie->variant =
- (enum imx6_pcie_variants)of_device_get_match_data(dev);
+ imx6_pcie->drvdata = of_device_get_match_data(dev);

dbi_base = platform_get_resource(pdev, IORESOURCE_MEM, 0);
pci->dbi_base = devm_ioremap_resource(dev, dbi_base);
@@ -748,7 +752,7 @@ static int imx6_pcie_probe(struct platform_device *pdev)
return PTR_ERR(imx6_pcie->pcie);
}

- switch (imx6_pcie->variant) {
+ switch (imx6_pcie->drvdata->variant) {
case IMX6SX:
imx6_pcie->pcie_inbound_axi = devm_clk_get(dev,
"pcie_inbound_axi");
@@ -776,6 +780,8 @@ static int imx6_pcie_probe(struct platform_device *pdev)
break;
}

+ pci->dbi_length = imx6_pcie->drvdata->dbi_length;
+
/* Grab GPR config register range */
imx6_pcie->iomuxc_gpr =
syscon_regmap_lookup_by_compatible("fsl,imx6q-iomuxc-gpr");
@@ -835,11 +841,28 @@ static void imx6_pcie_shutdown(struct platform_device *pdev)
imx6_pcie_assert_core_reset(imx6_pcie);
}

+static const struct imx6_pcie_drvdata imx6q_pcie_drvdata = {
+ .variant = IMX6Q,
+ .dbi_length = 0x15c,
+};
+
+static const struct imx6_pcie_drvdata imx6sx_pcie_drvdata = {
+ .variant = IMX6SX,
+};
+
+static const struct imx6_pcie_drvdata imx6qp_pcie_drvdata = {
+ .variant = IMX6QP,
+};
+
+static const struct imx6_pcie_drvdata imx7d_pcie_drvdata = {
+ .variant = IMX7D,
+};
+
static const struct of_device_id imx6_pcie_of_match[] = {
- { .compatible = "fsl,imx6q-pcie", .data = (void *)IMX6Q, },
- { .compatible = "fsl,imx6sx-pcie", .data = (void *)IMX6SX, },
- { .compatible = "fsl,imx6qp-pcie", .data = (void *)IMX6QP, },
- { .compatible = "fsl,imx7d-pcie", .data = (void *)IMX7D, },
+ { .compatible = "fsl,imx6q-pcie", .data = &imx6q_pcie_drvdata, },
+ { .compatible = "fsl,imx6sx-pcie", .data = &imx6sx_pcie_drvdata, },
+ { .compatible = "fsl,imx6qp-pcie", .data = &imx6qp_pcie_drvdata, },
+ { .compatible = "fsl,imx7d-pcie", .data = &imx7d_pcie_drvdata, },
{},
};

--
2.19.1


2018-11-20 02:21:23

by Trent Piepho

[permalink] [raw]
Subject: Re: [PATCH 2/2] PCI: imx6: limit DBI register length

On Mon, 2018-11-19 at 10:41 +0100, Stefan Agner wrote:
> Define the length of the DBI registers. This makes sure that
> the kernel does not access registers beyond that point, avoiding
> the following abort on a i.MX 6Quad:
> # cat
> /sys/devices/soc0/soc/1ffc000.pcie/pci0000\:00/0000\:00\:00.0/config
> [ 100.021433] Unhandled fault: imprecise external abort (0x1406)
> at 0xb6ea7000
> ...
> [ 100.056423] PC is at dw_pcie_read+0x50/0x84
> [ 100.060790] LR is at dw_pcie_rd_own_conf+0x44/0x48
> ...
>
> Signed-off-by: Stefan Agner <[email protected]>

After updating this for v4.20-rc2, tested on IMX 7d, config space
access works as before.

Tested-by: Trent Piepho <[email protected]>

2018-11-20 02:30:23

by Trent Piepho

[permalink] [raw]
Subject: Re: [PATCH 2/2] PCI: imx6: limit DBI register length

On Mon, 2018-11-19 at 10:41 +0100, Stefan Agner wrote:
>
>
> +static const struct imx6_pcie_drvdata imx6q_pcie_drvdata = {
> + .variant = IMX6Q,
> + .dbi_length = 0x15c,
> +};
> +
> +static const struct imx6_pcie_drvdata imx6sx_pcie_drvdata = {
> + .variant = IMX6SX,
> +};
> +
> +static const struct imx6_pcie_drvdata imx6qp_pcie_drvdata = {
> + .variant = IMX6QP,
> +};
> +
> +static const struct imx6_pcie_drvdata imx7d_pcie_drvdata = {
> + .variant = IMX7D,
> +};
> +
> static const struct of_device_id imx6_pcie_of_match[] = {
> - { .compatible = "fsl,imx6q-pcie", .data = (void *)IMX6Q, },
> - { .compatible = "fsl,imx6sx-pcie", .data = (void *)IMX6SX, },
> - { .compatible = "fsl,imx6qp-pcie", .data = (void *)IMX6QP, },
> - { .compatible = "fsl,imx7d-pcie", .data = (void *)IMX7D, },
> + { .compatible = "fsl,imx6q-pcie", .data = &imx6q_pcie_drvdata, },
> + { .compatible = "fsl,imx6sx-pcie", .data = &imx6sx_pcie_drvdata, },
> + { .compatible = "fsl,imx6qp-pcie", .data = &imx6qp_pcie_drvdata, },
> + { .compatible = "fsl,imx7d-pcie", .data = &imx7d_pcie_drvdata, },
> {},
> };

Instead of making a single drvdata struct for each type, this could use
an array:

static const struct imx6_pcie_drvdata drvdata[] = {
[IMX6Q] = { .variant = IMX6Q, .dbi_length = 0x15c },
[IMX6SX] = { .variant = IMX6SX },
[...]
};

static const struct of_device_id imx6_pcie_of_match[] = {
{ .compatible = "fsl,imx6q-pcie", .data = &drvdata[IMX6Q], },
{ .compatible = "fsl,imx6sx-pcie", .data = &drvdata[IMX6SX], },
...
};

2018-11-20 13:28:42

by Stefan Agner

[permalink] [raw]
Subject: Re: [PATCH 2/2] PCI: imx6: limit DBI register length

On 20.11.2018 14:03, Leonard Crestez wrote:
> From: Stefan Agner <[email protected]>
>> On 20.11.2018 11:22, Leonard Crestez wrote:
>> > On Mon, 2018-11-19 at 10:41 +0100, Stefan Agner wrote:
>> >> Define the length of the DBI registers. This makes sure that
>> >> the kernel does not access registers beyond that point, avoiding
>> >> the following abort on a i.MX 6Quad:
>> >> # cat
>> /sys/devices/soc0/soc/1ffc000.pcie/pci0000\:00/0000\:00\:00.0/config
>> >> [ 100.021433] Unhandled fault: imprecise external abort (0x1406) at
>> 0xb6ea7000
>> >> ...
>> >> [ 100.056423] PC is at dw_pcie_read+0x50/0x84
>> >> [ 100.060790] LR is at dw_pcie_rd_own_conf+0x44/0x48
>> >> ...
>> >>
>> >> Signed-off-by: Stefan Agner <[email protected]>
>> >>
>> >> diff --git a/drivers/pci/controller/dwc/pci-imx6.c
>> >
>> >> +struct imx6_pcie_drvdata {
>> >> + enum imx6_pcie_variants variant;
>> >> + int dbi_length;
>> >> +};
>> >
>> > Turning imx6_pcie drvdata into a struct is very nice, maybe in the
>> > future some of the long case statements in this driver could be split
>> > into per-soc functions called via drvdata.
>>
>> Yeah I thought that too. At a quick glance I did not saw an obvious
>> contender. Should certainly help for similar cases in the future.
>
> Yes, there are other cases in which it would be useful.
>
> But I think it makes more sense to split introducing drvdata to a
> separate patch.
> It's nice to separate functional and code cleanup changes.
>
> For example maybe dbi_length causes issues and has to be reverted?

Ok, makes sense, will split drvdata introduction in its own patch.

--
Stefan

2018-11-20 14:37:04

by Leonard Crestez

[permalink] [raw]
Subject: Re: [PATCH 2/2] PCI: imx6: limit DBI register length

On Mon, 2018-11-19 at 10:41 +0100, Stefan Agner wrote:
> Define the length of the DBI registers. This makes sure that
> the kernel does not access registers beyond that point, avoiding
> the following abort on a i.MX 6Quad:
> # cat /sys/devices/soc0/soc/1ffc000.pcie/pci0000\:00/0000\:00\:00.0/config
> [ 100.021433] Unhandled fault: imprecise external abort (0x1406) at 0xb6ea7000
> ...
> [ 100.056423] PC is at dw_pcie_read+0x50/0x84
> [ 100.060790] LR is at dw_pcie_rd_own_conf+0x44/0x48
> ...
>
> Signed-off-by: Stefan Agner <[email protected]>
>
> diff --git a/drivers/pci/controller/dwc/pci-imx6.c

> +struct imx6_pcie_drvdata {
> + enum imx6_pcie_variants variant;
> + int dbi_length;
> +};

Turning imx6_pcie drvdata into a struct is very nice, maybe in the
future some of the long case statements in this driver could be split
into per-soc functions called via drvdata.

2018-11-20 14:37:49

by Stefan Agner

[permalink] [raw]
Subject: Re: [PATCH 2/2] PCI: imx6: limit DBI register length

On 20.11.2018 11:22, Leonard Crestez wrote:
> On Mon, 2018-11-19 at 10:41 +0100, Stefan Agner wrote:
>> Define the length of the DBI registers. This makes sure that
>> the kernel does not access registers beyond that point, avoiding
>> the following abort on a i.MX 6Quad:
>> # cat /sys/devices/soc0/soc/1ffc000.pcie/pci0000\:00/0000\:00\:00.0/config
>> [ 100.021433] Unhandled fault: imprecise external abort (0x1406) at 0xb6ea7000
>> ...
>> [ 100.056423] PC is at dw_pcie_read+0x50/0x84
>> [ 100.060790] LR is at dw_pcie_rd_own_conf+0x44/0x48
>> ...
>>
>> Signed-off-by: Stefan Agner <[email protected]>
>>
>> diff --git a/drivers/pci/controller/dwc/pci-imx6.c
>
>> +struct imx6_pcie_drvdata {
>> + enum imx6_pcie_variants variant;
>> + int dbi_length;
>> +};
>
> Turning imx6_pcie drvdata into a struct is very nice, maybe in the
> future some of the long case statements in this driver could be split
> into per-soc functions called via drvdata.

Yeah I thought that too. At a quick glance I did not saw an obvious
contender. Should certainly help for similar cases in the future.

--
Stefan

2018-11-20 16:00:29

by Leonard Crestez

[permalink] [raw]
Subject: RE: [PATCH 2/2] PCI: imx6: limit DBI register length

From: Stefan Agner <[email protected]>
> On 20.11.2018 11:22, Leonard Crestez wrote:
> > On Mon, 2018-11-19 at 10:41 +0100, Stefan Agner wrote:
> >> Define the length of the DBI registers. This makes sure that
> >> the kernel does not access registers beyond that point, avoiding
> >> the following abort on a i.MX 6Quad:
> >> # cat
> /sys/devices/soc0/soc/1ffc000.pcie/pci0000\:00/0000\:00\:00.0/config
> >> [ 100.021433] Unhandled fault: imprecise external abort (0x1406) at
> 0xb6ea7000
> >> ...
> >> [ 100.056423] PC is at dw_pcie_read+0x50/0x84
> >> [ 100.060790] LR is at dw_pcie_rd_own_conf+0x44/0x48
> >> ...
> >>
> >> Signed-off-by: Stefan Agner <[email protected]>
> >>
> >> diff --git a/drivers/pci/controller/dwc/pci-imx6.c
> >
> >> +struct imx6_pcie_drvdata {
> >> + enum imx6_pcie_variants variant;
> >> + int dbi_length;
> >> +};
> >
> > Turning imx6_pcie drvdata into a struct is very nice, maybe in the
> > future some of the long case statements in this driver could be split
> > into per-soc functions called via drvdata.
>
> Yeah I thought that too. At a quick glance I did not saw an obvious
> contender. Should certainly help for similar cases in the future.

Yes, there are other cases in which it would be useful.

But I think it makes more sense to split introducing drvdata to a separate patch.
It's nice to separate functional and code cleanup changes.

For example maybe dbi_length causes issues and has to be reverted?

--
Regards,
Leonard