* [PATCH] PCI: host-generic: Fix NULL pointer dereference on 32-bit CAM systems
@ 2026-07-09 12:24 Steffen Persvold
2026-07-09 20:09 ` Bjorn Helgaas
0 siblings, 1 reply; 3+ messages in thread
From: Steffen Persvold @ 2026-07-09 12:24 UTC (permalink / raw)
To: Will Deacon, Lorenzo Pieralisi, Krzysztof Wilczyński,
Manivannan Sadhasivam, Bjorn Helgaas
Cc: Rob Herring, linux-pci, linux-arm-kernel, linux-kernel,
Steffen Persvold, stable
On 32-bit systems the config space is too large to ioremap in one go, so
pci_ecam_create() maps each bus segment separately and relies on the
->add_bus callback (pci_ecam_add_bus) to populate the per-bus mapping in
cfg->winp[]. pci_ecam_map_bus() then uses that mapping as the base for
every config access.
The generic ECAM ops (pci_generic_ecam_ops) already provide the ->add_bus
and ->remove_bus callbacks, but the CAM (legacy) ops in pci-host-generic.c
do not. As a result, on a 32-bit host using "pci-host-cam-generic" the
per-bus mapping is never set up and the first config read dereferences a
NULL base, crashing during bus enumeration:
[ 1.430647] Unable to handle kernel NULL pointer dereference at virtual address 00000800
[ 1.439441] Oops [#1]
[ 1.442152] CPU: 0 PID: 1 Comm: swapper Not tainted 6.9.7+ #43
[ 1.448753] Hardware name: Digilent Nexys-Video-A7 RV32 (DT)
[ 1.454968] epc : pci_generic_config_read+0x40/0xb0
[ 1.460652] ra : pci_generic_config_read+0x2c/0xb0
[ 1.534729] [<c038db9c>] pci_generic_config_read+0x40/0xb0
[ 1.541096] [<c038da04>] pci_bus_read_config_dword+0x50/0xb0
[ 1.547623] [<c0391e94>] pci_bus_generic_read_dev_vendor_id+0x3c/0x1ec
[ 1.555010] [<c039245c>] pci_scan_single_device+0xa4/0x11c
[ 1.561273] [<c0392570>] pci_scan_slot+0x9c/0x23c
[ 1.566716] [<c039388c>] pci_scan_child_bus_extend+0x58/0x2f4
[ 1.573275] [<c0393db0>] pci_scan_root_bus_bridge+0x64/0xe8
[ 1.579650] [<c0393e54>] pci_host_probe+0x20/0xc8
[ 1.591396] [<c03bc6f4>] pci_host_common_probe+0x144/0x1e4
Fix this by giving the CAM ops the same ->add_bus/->remove_bus callbacks.
Since pci_ecam_add_bus() and pci_ecam_remove_bus() are static to ecam.c,
move the CAM ops definition there as pci_generic_cam_ops (mirroring
pci_generic_ecam_ops) and export it for pci-host-generic.c to reference.
Fixes: 8fe55ef23387 ("PCI: Dynamically map ECAM regions")
Cc: stable@vger.kernel.org
Signed-off-by: Steffen Persvold <spersvold@gmail.com>
---
drivers/pci/controller/pci-host-generic.c | 11 +----------
drivers/pci/ecam.c | 13 +++++++++++++
include/linux/pci-ecam.h | 3 +++
3 files changed, 17 insertions(+), 10 deletions(-)
diff --git a/drivers/pci/controller/pci-host-generic.c b/drivers/pci/controller/pci-host-generic.c
index c1bc0d34..9e85c6e9 100644
--- a/drivers/pci/controller/pci-host-generic.c
+++ b/drivers/pci/controller/pci-host-generic.c
@@ -16,15 +16,6 @@
#include "pci-host-common.h"
-static const struct pci_ecam_ops gen_pci_cfg_cam_bus_ops = {
- .bus_shift = 16,
- .pci_ops = {
- .map_bus = pci_ecam_map_bus,
- .read = pci_generic_config_read,
- .write = pci_generic_config_write,
- }
-};
-
static bool pci_dw_valid_device(struct pci_bus *bus, unsigned int devfn)
{
struct pci_config_window *cfg = bus->sysdata;
@@ -60,7 +51,7 @@ static const struct pci_ecam_ops pci_dw_ecam_bus_ops = {
static const struct of_device_id gen_pci_of_match[] = {
{ .compatible = "pci-host-cam-generic",
- .data = &gen_pci_cfg_cam_bus_ops },
+ .data = &pci_generic_cam_ops },
{ .compatible = "pci-host-ecam-generic",
.data = &pci_generic_ecam_ops },
diff --git a/drivers/pci/ecam.c b/drivers/pci/ecam.c
index 119de32f..a9b3bce2 100644
--- a/drivers/pci/ecam.c
+++ b/drivers/pci/ecam.c
@@ -208,6 +208,19 @@ const struct pci_ecam_ops pci_generic_ecam_ops = {
};
EXPORT_SYMBOL_GPL(pci_generic_ecam_ops);
+/* CAM ops */
+const struct pci_ecam_ops pci_generic_cam_ops = {
+ .bus_shift = 16,
+ .pci_ops = {
+ .add_bus = pci_ecam_add_bus,
+ .remove_bus = pci_ecam_remove_bus,
+ .map_bus = pci_ecam_map_bus,
+ .read = pci_generic_config_read,
+ .write = pci_generic_config_write,
+ }
+};
+EXPORT_SYMBOL_GPL(pci_generic_cam_ops);
+
#if defined(CONFIG_ACPI) && defined(CONFIG_PCI_QUIRKS)
/* ECAM ops for 32-bit access only (non-compliant) */
const struct pci_ecam_ops pci_32b_ops = {
diff --git a/include/linux/pci-ecam.h b/include/linux/pci-ecam.h
index d9306514..044f67ce 100644
--- a/include/linux/pci-ecam.h
+++ b/include/linux/pci-ecam.h
@@ -81,6 +81,9 @@ void __iomem *pci_ecam_map_bus(struct pci_bus *bus, unsigned int devfn,
/* default ECAM ops */
extern const struct pci_ecam_ops pci_generic_ecam_ops;
+/* default CAM ops */
+extern const struct pci_ecam_ops pci_generic_cam_ops;
+
#if defined(CONFIG_ACPI) && defined(CONFIG_PCI_QUIRKS)
extern const struct pci_ecam_ops pci_32b_ops; /* 32-bit accesses only */
extern const struct pci_ecam_ops pci_32b_read_ops; /* 32-bit read only */
base-commit: 53bf92818a8362815708fc7b18e6d2c6a5fc665b
--
2.40.1
^ permalink raw reply related [flat|nested] 3+ messages in thread* Re: [PATCH] PCI: host-generic: Fix NULL pointer dereference on 32-bit CAM systems
2026-07-09 12:24 [PATCH] PCI: host-generic: Fix NULL pointer dereference on 32-bit CAM systems Steffen Persvold
@ 2026-07-09 20:09 ` Bjorn Helgaas
0 siblings, 0 replies; 3+ messages in thread
From: Bjorn Helgaas @ 2026-07-09 20:09 UTC (permalink / raw)
To: Steffen Persvold
Cc: Will Deacon, Lorenzo Pieralisi, Krzysztof Wilczyński,
Manivannan Sadhasivam, Bjorn Helgaas, Rob Herring, linux-pci,
linux-arm-kernel, linux-kernel, stable
On Thu, Jul 09, 2026 at 02:24:46PM +0200, Steffen Persvold wrote:
> On 32-bit systems the config space is too large to ioremap in one go, so
> pci_ecam_create() maps each bus segment separately and relies on the
> ->add_bus callback (pci_ecam_add_bus) to populate the per-bus mapping in
> cfg->winp[]. pci_ecam_map_bus() then uses that mapping as the base for
> every config access.
>
> The generic ECAM ops (pci_generic_ecam_ops) already provide the ->add_bus
> and ->remove_bus callbacks, but the CAM (legacy) ops in pci-host-generic.c
> do not. As a result, on a 32-bit host using "pci-host-cam-generic" the
> per-bus mapping is never set up and the first config read dereferences a
> NULL base, crashing during bus enumeration:
>
> [ 1.430647] Unable to handle kernel NULL pointer dereference at virtual address 00000800
> [ 1.439441] Oops [#1]
> [ 1.442152] CPU: 0 PID: 1 Comm: swapper Not tainted 6.9.7+ #43
> [ 1.448753] Hardware name: Digilent Nexys-Video-A7 RV32 (DT)
> [ 1.454968] epc : pci_generic_config_read+0x40/0xb0
> [ 1.460652] ra : pci_generic_config_read+0x2c/0xb0
> [ 1.534729] [<c038db9c>] pci_generic_config_read+0x40/0xb0
> [ 1.541096] [<c038da04>] pci_bus_read_config_dword+0x50/0xb0
> [ 1.547623] [<c0391e94>] pci_bus_generic_read_dev_vendor_id+0x3c/0x1ec
> [ 1.555010] [<c039245c>] pci_scan_single_device+0xa4/0x11c
> [ 1.561273] [<c0392570>] pci_scan_slot+0x9c/0x23c
> [ 1.566716] [<c039388c>] pci_scan_child_bus_extend+0x58/0x2f4
> [ 1.573275] [<c0393db0>] pci_scan_root_bus_bridge+0x64/0xe8
> [ 1.579650] [<c0393e54>] pci_host_probe+0x20/0xc8
> [ 1.591396] [<c03bc6f4>] pci_host_common_probe+0x144/0x1e4
Remove timestamps, since they don't contribute to understanding the
problem, and indent the quoted material two spaces.
If there's no other reason for a v2, we can do this while merging.
> Fix this by giving the CAM ops the same ->add_bus/->remove_bus callbacks.
> Since pci_ecam_add_bus() and pci_ecam_remove_bus() are static to ecam.c,
> move the CAM ops definition there as pci_generic_cam_ops (mirroring
> pci_generic_ecam_ops) and export it for pci-host-generic.c to reference.
>
> Fixes: 8fe55ef23387 ("PCI: Dynamically map ECAM regions")
> Cc: stable@vger.kernel.org
> Signed-off-by: Steffen Persvold <spersvold@gmail.com>
> ---
> drivers/pci/controller/pci-host-generic.c | 11 +----------
> drivers/pci/ecam.c | 13 +++++++++++++
> include/linux/pci-ecam.h | 3 +++
> 3 files changed, 17 insertions(+), 10 deletions(-)
>
> diff --git a/drivers/pci/controller/pci-host-generic.c b/drivers/pci/controller/pci-host-generic.c
> index c1bc0d34..9e85c6e9 100644
> --- a/drivers/pci/controller/pci-host-generic.c
> +++ b/drivers/pci/controller/pci-host-generic.c
> @@ -16,15 +16,6 @@
>
> #include "pci-host-common.h"
>
> -static const struct pci_ecam_ops gen_pci_cfg_cam_bus_ops = {
> - .bus_shift = 16,
> - .pci_ops = {
> - .map_bus = pci_ecam_map_bus,
> - .read = pci_generic_config_read,
> - .write = pci_generic_config_write,
> - }
> -};
> -
> static bool pci_dw_valid_device(struct pci_bus *bus, unsigned int devfn)
> {
> struct pci_config_window *cfg = bus->sysdata;
> @@ -60,7 +51,7 @@ static const struct pci_ecam_ops pci_dw_ecam_bus_ops = {
>
> static const struct of_device_id gen_pci_of_match[] = {
> { .compatible = "pci-host-cam-generic",
> - .data = &gen_pci_cfg_cam_bus_ops },
> + .data = &pci_generic_cam_ops },
>
> { .compatible = "pci-host-ecam-generic",
> .data = &pci_generic_ecam_ops },
> diff --git a/drivers/pci/ecam.c b/drivers/pci/ecam.c
> index 119de32f..a9b3bce2 100644
> --- a/drivers/pci/ecam.c
> +++ b/drivers/pci/ecam.c
> @@ -208,6 +208,19 @@ const struct pci_ecam_ops pci_generic_ecam_ops = {
> };
> EXPORT_SYMBOL_GPL(pci_generic_ecam_ops);
>
> +/* CAM ops */
> +const struct pci_ecam_ops pci_generic_cam_ops = {
> + .bus_shift = 16,
> + .pci_ops = {
> + .add_bus = pci_ecam_add_bus,
> + .remove_bus = pci_ecam_remove_bus,
> + .map_bus = pci_ecam_map_bus,
> + .read = pci_generic_config_read,
> + .write = pci_generic_config_write,
> + }
> +};
> +EXPORT_SYMBOL_GPL(pci_generic_cam_ops);
> +
> #if defined(CONFIG_ACPI) && defined(CONFIG_PCI_QUIRKS)
> /* ECAM ops for 32-bit access only (non-compliant) */
> const struct pci_ecam_ops pci_32b_ops = {
> diff --git a/include/linux/pci-ecam.h b/include/linux/pci-ecam.h
> index d9306514..044f67ce 100644
> --- a/include/linux/pci-ecam.h
> +++ b/include/linux/pci-ecam.h
> @@ -81,6 +81,9 @@ void __iomem *pci_ecam_map_bus(struct pci_bus *bus, unsigned int devfn,
> /* default ECAM ops */
> extern const struct pci_ecam_ops pci_generic_ecam_ops;
>
> +/* default CAM ops */
> +extern const struct pci_ecam_ops pci_generic_cam_ops;
> +
> #if defined(CONFIG_ACPI) && defined(CONFIG_PCI_QUIRKS)
> extern const struct pci_ecam_ops pci_32b_ops; /* 32-bit accesses only */
> extern const struct pci_ecam_ops pci_32b_read_ops; /* 32-bit read only */
>
> base-commit: 53bf92818a8362815708fc7b18e6d2c6a5fc665b
> --
> 2.40.1
>
^ permalink raw reply [flat|nested] 3+ messages in thread
* [PATCH] PCI: host-generic: Fix NULL pointer dereference on 32-bit CAM systems
@ 2024-08-20 14:04 Steffen Persvold
0 siblings, 0 replies; 3+ messages in thread
From: Steffen Persvold @ 2024-08-20 14:04 UTC (permalink / raw)
To: Will Deacon, Lorenzo Pieralisi, Krzysztof Wilczyński,
Rob Herring, Bjorn Helgaas
Cc: spersvold, linux-pci, linux-arm-kernel, linux-kernel
CAM (legacy) host drivers also need to refer to pci_ecam_add_bus and
pci_ecam_remove_bus functions to get mapped resources on 32-bit systems.
This is because 32-bit systems have separate iomap resources per bus
segment and pci_ecam_add_bus is the one that sets that up. Move the
pci_ecam_ops for CAM mode to ecam.c so we can refer the static functions.
This fixes :
[ 1.356001] pci-host-generic 30000000.pci: host bridge /soc/pci@30000000 ranges:
[ 1.365324] pci-host-generic 30000000.pci: MEM 0x0040000000..0x004fffffff -> 0x0040000000
[ 1.375556] pci-host-generic 30000000.pci: MEM 0x0050000000..0x006fffffff -> 0x0050000000
[ 1.386132] pci-host-generic 30000000.pci: ECAM at [mem 0x30000000-0x30ffffff] for [bus 00-ff]
[ 1.399490] pci-host-generic 30000000.pci: PCI host bridge to bus 0000:00
[ 1.407073] pci_bus 0000:00: root bus resource [bus 00-ff]
[ 1.413648] pci_bus 0000:00: root bus resource [mem 0x40000000-0x4fffffff]
[ 1.421718] pci_bus 0000:00: root bus resource [mem 0x50000000-0x6fffffff pref]
[ 1.430647] Unable to handle kernel NULL pointer dereference at virtual address 00000800
[ 1.439441] Oops [#1]
[ 1.442152] CPU: 0 PID: 1 Comm: swapper Not tainted 6.9.7+ #43
[ 1.448753] Hardware name: Digilent Nexys-Video-A7 RV32 (DT)
[ 1.454968] epc : pci_generic_config_read+0x40/0xb0
[ 1.460652] ra : pci_generic_config_read+0x2c/0xb0
[ 1.466322] epc : c038db9c ra : c038db88 sp : c1c3bc20
[ 1.472095] gp : c18726d0 tp : c1c5c000 t0 : 0000006e
[ 1.477859] t1 : 00000063 t2 : 00000000 s0 : c1c3bc30
[ 1.483604] s1 : 00000004 a0 : 00000800 a1 : 00000800
[ 1.489348] a2 : 00000000 a3 : 00000008 a4 : c1d15800
[ 1.495090] a5 : 00000002 a6 : 0000008a a7 : c1809ec0
[ 1.500844] s2 : c1c3bc38 s3 : 0000ea60 s4 : 00000008
[ 1.506600] s5 : c1ce4a00 s6 : 00000006 s7 : c11c7460
[ 1.512353] s8 : 00000008 s9 : c0800108 s10: 00000000
[ 1.518095] s11: 00000000 t3 : 3ffff7ff t4 : 00000000
[ 1.523833] t5 : 00000001 t6 : 00000000
[ 1.528252] status: 00000100 badaddr: 00000800 cause: 0000000d
[ 1.534729] [<c038db9c>] pci_generic_config_read+0x40/0xb0
[ 1.541096] [<c038da04>] pci_bus_read_config_dword+0x50/0xb0
[ 1.547623] [<c0391e94>] pci_bus_generic_read_dev_vendor_id+0x3c/0x1ec
[ 1.555010] [<c039245c>] pci_scan_single_device+0xa4/0x11c
[ 1.561273] [<c0392570>] pci_scan_slot+0x9c/0x23c
[ 1.566716] [<c039388c>] pci_scan_child_bus_extend+0x58/0x2f4
[ 1.573275] [<c0393db0>] pci_scan_root_bus_bridge+0x64/0xe8
[ 1.579650] [<c0393e54>] pci_host_probe+0x20/0xc8
[ 1.585104] [<c03bc6f4>] pci_host_common_probe+0x144/0x1e4
[ 1.591396] [<c042ec20>] platform_probe+0x54/0x9c
[ 1.596957] [<c042b5c8>] really_probe+0xbc/0x418
[ 1.602367] [<c042bb0c>] __driver_probe_device+0x70/0xfc
[ 1.608507] [<c042bbe0>] driver_probe_device+0x48/0xf0
[ 1.614487] [<c042be98>] __driver_attach+0xbc/0x264
[ 1.620190] [<c0428d04>] bus_for_each_dev+0x84/0xf8
[ 1.625868] [<c042aeec>] driver_attach+0x28/0x38
[ 1.631269] [<c042a510>] bus_add_driver+0x140/0x278
[ 1.636956] [<c042cf48>] driver_register+0x70/0x15c
[ 1.642666] [<c042e8f8>] __platform_driver_register+0x28/0x38
[ 1.649332] [<c081b694>] gen_pci_driver_init+0x24/0x34
[ 1.655241] [<c0801424>] do_one_initcall+0x88/0x164
[ 1.660943] [<c0801768>] kernel_init_freeable+0x1dc/0x264
[ 1.667199] [<c0699f34>] kernel_init+0x28/0x138
[ 1.672578] [<c06a0b5c>] ret_from_fork+0x14/0x24
[ 1.678399] Code: 0463 0605 0793 0010 8663 04f4 0793 0020 8663 02f4 (2503) 0005
[ 1.686462] ---[ end trace 0000000000000000 ]---
[ 1.691591] Kernel panic - not syncing: Fatal exception
Signed-off-by: Steffen Persvold <spersvold@gmail.com>
---
drivers/pci/controller/pci-host-generic.c | 11 +----------
drivers/pci/ecam.c | 13 +++++++++++++
include/linux/pci-ecam.h | 3 +++
3 files changed, 17 insertions(+), 10 deletions(-)
diff --git a/drivers/pci/controller/pci-host-generic.c b/drivers/pci/controller/pci-host-generic.c
index 41cb6a05..3b7da1c3 100644
--- a/drivers/pci/controller/pci-host-generic.c
+++ b/drivers/pci/controller/pci-host-generic.c
@@ -14,15 +14,6 @@
#include <linux/pci-ecam.h>
#include <linux/platform_device.h>
-static const struct pci_ecam_ops gen_pci_cfg_cam_bus_ops = {
- .bus_shift = 16,
- .pci_ops = {
- .map_bus = pci_ecam_map_bus,
- .read = pci_generic_config_read,
- .write = pci_generic_config_write,
- }
-};
-
static bool pci_dw_valid_device(struct pci_bus *bus, unsigned int devfn)
{
struct pci_config_window *cfg = bus->sysdata;
@@ -58,7 +49,7 @@ static const struct pci_ecam_ops pci_dw_ecam_bus_ops = {
static const struct of_device_id gen_pci_of_match[] = {
{ .compatible = "pci-host-cam-generic",
- .data = &gen_pci_cfg_cam_bus_ops },
+ .data = &pci_generic_cam_ops },
{ .compatible = "pci-host-ecam-generic",
.data = &pci_generic_ecam_ops },
diff --git a/drivers/pci/ecam.c b/drivers/pci/ecam.c
index 1c40d250..97430664 100644
--- a/drivers/pci/ecam.c
+++ b/drivers/pci/ecam.c
@@ -208,6 +208,19 @@ const struct pci_ecam_ops pci_generic_ecam_ops = {
};
EXPORT_SYMBOL_GPL(pci_generic_ecam_ops);
+/* CAM ops */
+const struct pci_ecam_ops pci_generic_cam_ops = {
+ .bus_shift = 16,
+ .pci_ops = {
+ .add_bus = pci_ecam_add_bus,
+ .remove_bus = pci_ecam_remove_bus,
+ .map_bus = pci_ecam_map_bus,
+ .read = pci_generic_config_read,
+ .write = pci_generic_config_write,
+ }
+};
+EXPORT_SYMBOL_GPL(pci_generic_cam_ops);
+
#if defined(CONFIG_ACPI) && defined(CONFIG_PCI_QUIRKS)
/* ECAM ops for 32-bit access only (non-compliant) */
const struct pci_ecam_ops pci_32b_ops = {
diff --git a/include/linux/pci-ecam.h b/include/linux/pci-ecam.h
index 3a4860bd..7ebec8ce 100644
--- a/include/linux/pci-ecam.h
+++ b/include/linux/pci-ecam.h
@@ -77,6 +77,9 @@ void __iomem *pci_ecam_map_bus(struct pci_bus *bus, unsigned int devfn,
/* default ECAM ops */
extern const struct pci_ecam_ops pci_generic_ecam_ops;
+/* default CAM ops */
+extern const struct pci_ecam_ops pci_generic_cam_ops;
+
#if defined(CONFIG_ACPI) && defined(CONFIG_PCI_QUIRKS)
extern const struct pci_ecam_ops pci_32b_ops; /* 32-bit accesses only */
extern const struct pci_ecam_ops pci_32b_read_ops; /* 32-bit read only */
--
2.40.1
^ permalink raw reply related [flat|nested] 3+ messages in thread
end of thread, other threads:[~2026-07-09 20:09 UTC | newest]
Thread overview: 3+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-07-09 12:24 [PATCH] PCI: host-generic: Fix NULL pointer dereference on 32-bit CAM systems Steffen Persvold
2026-07-09 20:09 ` Bjorn Helgaas
-- strict thread matches above, loose matches on Subject: below --
2024-08-20 14:04 Steffen Persvold
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox