mirror of https://lore.kernel.org/lkml/
 help / color / mirror / Atom feed
From: "Ilpo Järvinen" <ilpo.jarvinen@linux.intel.com>
To: hangej <hangej@amazon.com>
Cc: bhelgaas@google.com, linux-pci@vger.kernel.org,
	 LKML <linux-kernel@vger.kernel.org>,
	dwmw2@infradead.org,  kexec@lists.infradead.org,
	nh-open-source@amazon.com
Subject: Re: [PATCH v4] pci_crash: capture PCI config space at panic time
Date: Wed, 26 Aug 2026 20:44:54 +0300 (EEST)	[thread overview]
Message-ID: <5188f7e8-4d19-685b-49e3-f2249685d9a3@linux.intel.com> (raw)
In-Reply-To: <20260723185216.1089927-1-hangej@amazon.com>

[-- Attachment #1: Type: text/plain, Size: 66958 bytes --]

On Thu, 23 Jul 2026, hangej wrote:

> From: Johannes Hange <hangej@amazon.com>
> 
> Add CONFIG_PCI_CRASH, a crash-time PCI config-space capture facility.  A
> pre-allocated, RCU-published snapshot of all PCI devices is maintained via
> a bus notifier; at panic time pci_crash_save() reads config space into the
> buffer using a trylock-based accessor that skips devices on lock
> contention.  The buffer and a physical-page directory (pagemap) are
> exported through VMCOREINFO so crash analysis tools can extract PCI
> register state without relying on /proc or sysfs in the crashed kernel.
> 
> Key design points:
> 
>   - Snapshot rebuild is debounced (200 ms) to coalesce VF enumeration
>     storms; rebuild runs in process context on system_wq.
> 
>   - Retired snapshots are freed via queue_rcu_work() (process context)
>     because pci_dev_put() may trigger device_release() ->
>     devres_release_all() which can sleep -- incompatible with the softirq
>     context of plain call_rcu() callbacks.
> 
>   - pci_crash_endpoint_reachable() gates every config read with
>     software-state checks (pci_dev_is_disconnected, pci_channel_offline,
>     pci_dev_is_removed, D3cold) plus a live upstream-bridge LNKSTA read
>     to confirm link presence.  The pci_dev_is_removed() check catches
>     cleanly-removed devices whose host bridge module may have been
>     unloaded (bus->ops freed) but whose pci_dev struct persists due to
>     snapshot references.
> 
>   - capture= module parameter selects 'always' (every panic) or 'aer'
>     (only when an uncorrectable AER error is detected on a root port).
> 
>   - devices= parameter filters the capture set by class code or the
>     keyword 'bridges'.
> 
>   - Buffer is kvmalloc'd with __GFP_ZERO; capped at 24 MiB, 4096 bytes
>     per device.  A pagemap records physical addresses of each buffer page
>     for the crash parser.
> 
> Introduce pci_dev_is_removed() as a public read-only helper in
> include/linux/pci.h, mirroring the existing pci_dev_is_disconnected()
> pattern.  The PCI_DEV_REMOVED bit (set by pci_destroy_dev during clean
> removal) was previously only accessible via drivers/pci/pci.h.
> 
> Signed-off-by: Johannes Hange <hangej@amazon.com>
> ---
>  Documentation/PCI/index.rst                   |    1 +
>  Documentation/PCI/pci-crash-capture.rst       |  236 ++++
>  .../admin-guide/kernel-parameters.txt         |   16 +
>  MAINTAINERS                                   |    8 +
>  drivers/pci/access.c                          |   60 +
>  include/linux/pci.h                           |    8 +
>  include/linux/pci_crash.h                     |  122 ++
>  kernel/Kconfig.kexec                          |   16 +
>  kernel/Makefile                               |    1 +
>  kernel/pci_crash.c                            | 1013 +++++++++++++++++
>  kernel/vmcore_info.c                          |   13 +
>  11 files changed, 1494 insertions(+)
>  create mode 100644 Documentation/PCI/pci-crash-capture.rst
>  create mode 100644 include/linux/pci_crash.h
>  create mode 100644 kernel/pci_crash.c
> 
> diff --git a/Documentation/PCI/index.rst b/Documentation/PCI/index.rst
> index 5d720d2a415e..7f499a43ddb4 100644
> --- a/Documentation/PCI/index.rst
> +++ b/Documentation/PCI/index.rst
> @@ -19,4 +19,5 @@ PCI Bus Subsystem
>     endpoint/index
>     controller/index
>     boot-interrupts
> +   pci-crash-capture
>     tph
> diff --git a/Documentation/PCI/pci-crash-capture.rst b/Documentation/PCI/pci-crash-capture.rst
> new file mode 100644
> index 000000000000..3a57696afa6f
> --- /dev/null
> +++ b/Documentation/PCI/pci-crash-capture.rst
> @@ -0,0 +1,236 @@
> +.. SPDX-License-Identifier: GPL-2.0
> +
> +========================
> +PCI Crash Capture Buffer
> +========================
> +
> +Overview
> +========
> +
> +The PCI crash capture module (``CONFIG_PCI_CRASH``) saves PCI configuration
> +space for all (or selected) devices at panic time.  The data is written into
> +a pre-allocated buffer whose physical pages are exported via VMCOREINFO,
> +allowing crash analysis tools to extract device state from the vmcore.
> +
> +This is useful because AER (Advanced Error Reporting) registers are volatile
> +and cleared by device reset during kexec into the crash kernel.  Capturing
> +them before kexec preserves the error state that caused or contributed to the
> +crash.
> +
> +Boot Parameters
> +===============
> +
> +``pci_crash.capture=`` (default: ``always``)
> +  When to capture PCI config space.  Comma-separated tokens:
> +
> +  ``aer``
> +    Capture only if a root port reports an uncorrectable error in its
> +    AER ROOT_STATUS register.  Non-PCI panics skip capture entirely
> +    (a handful of MMIO reads to root ports, sub-microsecond).
> +
> +  ``always``
> +    Capture on every panic regardless of AER state.  Useful for
> +    cascading failures where a PCI link-down causes an MCE or NMI
> +    watchdog timeout before DPC/AER fires, so the crash reason is
> +    unrelated but the AER registers still hold the originating error.
> +
> +``pci_crash.devices=`` (default: ``all``)
> +  Which devices to include in the capture buffer.  Comma-separated tokens:
> +
> +  ``all``
> +    Every PCI device in the system.
> +
> +  ``bridges``
> +    PCI-to-PCI bridges (class 0604) and CardBus bridges (class 0607).
> +
> +  ``root_ports``
> +    PCIe root ports only.
> +
> +  ``XXYY``
> +    Hex PCI class code (class byte XX, subclass byte YY).
> +    Up to 8 class codes may be specified.
> +
> +  Bridges are always implicitly included regardless of the filter value
> +  because they hold AER registers needed for root cause analysis.  The
> +  filter is applied at device enumeration and hotplug rebuild time, not at
> +  crash time (zero overhead on the panic path).
> +
> +Both parameters are writable at runtime via sysfs
> +(``/sys/module/pci_crash/parameters/``).
> +
> +Architecture
> +============
> +
> +::
> +
> +  late_initcall
> +      │
> +      ├── register PCI bus notifier (before the first rebuild)
> +      ├── enumerate PCI devices (filtered by devices= param)
> +      ├── allocate buffer via kvmalloc (may be vmalloc for >4 MiB)
> +      ├── build pagemap: kmalloc'd array of per-page physical addresses
> +      └── publish snapshot via rcu_assign_pointer()
> +
> +  hotplug (BUS_NOTIFY_ADD_DEVICE / BUS_NOTIFY_DEL_DEVICE)
> +      │
> +      └── schedule delayed rebuild (200 ms debounce)
> +              └── re-enumerate, re-allocate buffer + pagemap,
> +                  publish new snapshot, retire old via queue_rcu_work()
> +
> +  panic (__crash_kexec → crash_save_vmcoreinfo → pci_crash_save)
> +      │
> +      ├── rcu_read_lock(); sample the published snapshot
> +      ├── quick-scan root port AER ROOT_STATUS (capture=aer)
> +      │     └── bail if no uncorrectable errors
> +      ├── for each device: skip if unreachable, else read config space
> +      │     via pci_bus_read_config_dword_trylock()
> +      ├── flush dcache (buffer + pagemap) to RAM
> +      └── VMCOREINFO exports: PCI_CRASH_PAGEMAP, PCI_CRASH_BUF_SZ,
> +          PCI_CRASH_VERSION
> +
> +Buffer Format
> +=============
> +
> +The buffer consists of a 32-byte header followed by variable-length
> +device records:
> +
> +.. code-block:: c
> +
> +    struct pci_crash_buffer_header {   /* 32 bytes */
> +        __le32 magic;           /* 0x50434943 "PCIC" */
> +        __le32 version;         /* 1 */
> +        __le32 device_count;
> +        __le32 config_size;     /* 0 = variable-length records */
> +        __le64 timestamp;       /* ktime_get_real_fast_ns() */
> +        __le32 flags;           /* reserved */
> +        __le32 reserved;
> +    };
> +
> +    struct pci_crash_device_record {   /* 8 + cfg_size bytes */
> +        __le16 domain;
> +        __u8   bus;
> +        __u8   devfn;
> +        __le32 config_size;     /* 256 or 4096 */
> +        __u8   config_data[];   /* 0xffffffff for unreachable dwords */
> +    };
> +
> +The pagemap (exported via ``PCI_CRASH_PAGEMAP``) allows the parser to
> +locate buffer pages without walking page tables:
> +
> +.. code-block:: c
> +
> +    struct pci_crash_pagemap {
> +        __le32 magic;           /* 0x5043504d "PCPM" */
> +        __le32 num_pages;
> +        __le64 buf_size;
> +        __le32 buf_offset;      /* offset of buffer start within first page */
> +        __le64 addrs[];         /* physical address per page */
> +    };
> +
> +All multi-byte fields are little-endian.  The struct sizes and the
> +``addrs[]`` offset are asserted with ``BUILD_BUG_ON()`` so the on-wire
> +layout cannot drift away from the userspace parser silently.
> +
> +VMCOREINFO keys
> +===============
> +
> +``pci_crash_save()`` exports the following keys into VMCOREINFO (consumed by
> +makedumpfile / the crash-utility and any bespoke vmcore parser).  They are
> +emitted whenever a valid snapshot exists at panic time; the buffer may be
> +unfilled when the AER quick-scan found no errors and skipped the capture
> +(``capture=aer``).  Parsers must check the buffer header magic (``PCIC``)
> +to confirm config space was actually captured:
> +
> +``PCI_CRASH_PAGEMAP=<hex>``
> +  Physical address of the ``struct pci_crash_pagemap``.  The pagemap is
> +  always kmalloc'd (direct-mapped), so this physical address is stable and
> +  the parser can read it directly from the vmcore.  From the pagemap the
> +  parser reconstructs the (possibly vmalloc'd, physically discontiguous)
> +  buffer page by page.
> +
> +``PCI_CRASH_VERSION=<dec>``
> +  On-wire format version (``PCI_CRASH_VERSION``).  Parsers must reject a
> +  version they do not understand rather than misinterpret the layout.
> +
> +``PCI_CRASH_BUF_SZ=<dec>``
> +  Total buffer size in bytes, matching ``pci_crash_pagemap::buf_size``.
> +
> +Safety Considerations
> +=====================
> +
> +``pci_crash_save()`` runs from ``crash_save_vmcoreinfo()`` inside
> +``__crash_kexec()``, before ``machine_kexec()``.  It executes in crash
> +context, so every access on that path is constrained accordingly:
> +
> +- **Config reads use** ``pci_bus_read_config_dword_trylock()``, which takes
> +  ``pci_lock`` with a *trylock* and skips the device on contention.  Depending
> +  on ``crash_kexec_post_notifiers`` the other CPUs may still be running or may
> +  already be halted (possibly while holding ``pci_lock``), and the panicking
> +  CPU may itself have been interrupted mid config access while holding it.
> +  ``pci_lock`` is a raw, non-reentrant spinlock, so a blocking acquire could
> +  deadlock the dump in either case; the trylock skips the device instead.  This
> +  avoids the lock deadlock only; it does not make the read itself fault-safe,
> +  which is why unreachable devices are skipped first (below).
> +
> +- **Unreachable devices are skipped before any access to them.**  A config
> +  read to a device whose PCIe link is physically down can, on some
> +  architectures (notably arm64), raise a synchronous external abort.  Before
> +  reading an endpoint, the module establishes reachability *without touching
> +  the endpoint*:
> +
> +  - software state -- ``pci_dev_is_disconnected()``, ``pci_channel_offline()``,
> +    ``pci_dev_is_removed()`` and ``PCI_D3cold`` are flag reads (no MMIO); they
> +    catch devices a subsystem has already marked gone or powered off.
> +    ``pci_dev_is_removed()`` additionally covers a cleanly-removed device whose
> +    ``pci_dev`` is kept alive by a snapshot reference while its host bridge
> +    module (and thus ``bus->ops``) may already be freed; and
> +
> +  - the immediate upstream PCIe port's Link Status (Data Link Layer Link
> +    Active).  The upstream port is on-die and always responds, so reading its
> +    Link Status cannot fault on the endpoint's dead link; if the link is down,
> +    the endpoint is skipped and its record is filled with ``0xffffffff``.
> +
> +- ``ktime_get_real_fast_ns()`` is NMI-safe (lockless timekeeper snapshot).
> +
> +- **Live capture state is a single RCU-published snapshot.**  The rebuild
> +  worker (process context) swaps it via ``rcu_assign_pointer()`` and frees the
> +  old snapshot via ``queue_rcu_work()`` (the free runs in process context after
> +  a grace period, because dropping device references via ``pci_dev_put()`` can
> +  sleep); ``pci_crash_save()`` reads it under ``rcu_read_lock()``.  RCU keeps the snapshot alive for the *fill*, but the
> +  exported buffer/pagemap addresses are consumed after ``pci_crash_save()``
> +  returns (the VMCOREINFO export, and ``machine_kexec()`` snapshotting RAM),
> +  i.e. after ``rcu_read_unlock()`` -- and on the default panic path peer CPUs
> +  are still live and may retire snapshots.  ``pci_crash_save()`` therefore
> +  *pins* the snapshot it captured (``pci_crash_captured_snap``); the RCU free
> +  callback leaks a pinned snapshot instead of freeing it, which is harmless
> +  because the system is rebooting into the crash kernel.
> +
> +- Buffer capped at 24 MiB to bound allocation on systems with thousands of
> +  VFs; per-device reads are clamped to 4096 bytes and the fill loop
> +  bounds-checks every record against the buffer end.
> +
> +- ``pci_crash_ready`` defers param parsing and rebuild until ``late_initcall``
> +  completes; kernel command-line values are stored and take effect once the
> +  PCI subsystem is up.
> +
> +Architecture support and residual risk
> +---------------------------------------
> +
> +The upstream-port Link-Status pre-check eliminates the common and
> +deterministic hang: an endpoint whose link is already down at panic is never
> +read.  Two narrow residual cases remain on architectures where a config read
> +to a dead device raises a fatal abort (e.g. arm64 ``do_sea()``, which has no
> +kernel-mode recovery for an external abort):
> +
> +- a link that drops in the small window *between* the upstream-port check and
> +  the endpoint read (a true hardware race); and
> +
> +- a multi-level fabric collapse in which an upstream port is itself behind a
> +  dead link (only the immediate parent is checked).
> +
> +On x86 a read to an absent device returns all-ones harmlessly, so these cases
> +are arm64-specific.  Capturing such a device may therefore, in those narrow
> +races, abort the dump on arm64.  Making the read itself recoverable would
> +require new architecture support in the abort handler and is intentionally not
> +part of this module; it can be added later as a separate, properly-typed
> +arch facility without changing the on-wire format.
> diff --git a/Documentation/admin-guide/kernel-parameters.txt b/Documentation/admin-guide/kernel-parameters.txt
> index b5493a7f8f22..7ef515c8b849 100644
> --- a/Documentation/admin-guide/kernel-parameters.txt
> +++ b/Documentation/admin-guide/kernel-parameters.txt
> @@ -5298,6 +5298,22 @@ Kernel parameters
>  		nomsi	Do not use MSI for native PCIe PME signaling (this makes
>  			all PCIe root ports use INTx for all services).
>  
> +	pci_crash.capture=
> +			[PCI] When to capture PCI config space at panic time.
> +			always (default): capture on every panic.
> +			aer: capture only if root port AER reports
> +				uncorrectable errors.
> +			Requires CONFIG_PCI_CRASH=y.
> +
> +	pci_crash.devices=
> +			[PCI] Which devices to include in crash capture.
> +			all (default): every PCI device.
> +			bridges: PCI bridges only.
> +			root_ports: PCIe root ports only.
> +			XXYY: hex class code (up to 8).
> +			Bridges always implicitly included.
> +			Requires CONFIG_PCI_CRASH=y.
> +
>  	pcmv=		[HW,PCMCIA] BadgePAD 4
>  
>  	pd_ignore_unused
> diff --git a/MAINTAINERS b/MAINTAINERS
> index f37a81950e25..47a562820582 100644
> --- a/MAINTAINERS
> +++ b/MAINTAINERS
> @@ -20560,6 +20560,14 @@ S:	Maintained
>  F:	drivers/leds/leds-pca9532.c
>  F:	include/linux/leds-pca9532.h
>  
> +PCI CRASH BUFFER
> +M:	Johannes Hange <hangej@amazon.com>
> +L:	linux-pci@vger.kernel.org
> +S:	Maintained
> +F:	Documentation/PCI/pci-crash-capture.rst
> +F:	include/linux/pci_crash.h
> +F:	kernel/pci_crash.c
> +
>  PCI DRIVER FOR AARDVARK (Marvell Armada 3700)
>  M:	Thomas Petazzoni <thomas.petazzoni@bootlin.com>
>  M:	Pali Rohár <pali@kernel.org>
> diff --git a/drivers/pci/access.c b/drivers/pci/access.c
> index b123da16b63b..c05d02efd602 100644
> --- a/drivers/pci/access.c
> +++ b/drivers/pci/access.c
> @@ -1,5 +1,6 @@
>  // SPDX-License-Identifier: GPL-2.0
>  #include <linux/pci.h>
> +#include <linux/pci_crash.h>
>  #include <linux/module.h>
>  #include <linux/slab.h>
>  #include <linux/ioport.h>
> @@ -27,9 +28,11 @@ DEFINE_RAW_SPINLOCK(pci_lock);
>  #ifdef CONFIG_PCI_LOCKLESS_CONFIG
>  # define pci_lock_config(f)	do { (void)(f); } while (0)
>  # define pci_unlock_config(f)	do { (void)(f); } while (0)
> +# define pci_trylock_config(f)	({ (void)(f); true; })
>  #else
>  # define pci_lock_config(f)	raw_spin_lock_irqsave(&pci_lock, f)
>  # define pci_unlock_config(f)	raw_spin_unlock_irqrestore(&pci_lock, f)
> +# define pci_trylock_config(f)	raw_spin_trylock_irqsave(&pci_lock, f)
>  #endif
>  
>  #define PCI_OP_READ(size, type, len) \
> @@ -85,6 +88,63 @@ EXPORT_SYMBOL(pci_bus_write_config_byte);
>  EXPORT_SYMBOL(pci_bus_write_config_word);
>  EXPORT_SYMBOL(pci_bus_write_config_dword);
>  
> +#ifdef CONFIG_PCI_CRASH
> +/**
> + * pci_bus_read_config_dword_trylock - non-blocking config read for the crash path
> + * @bus: target PCI bus
> + * @devfn: target device/function
> + * @pos: dword-aligned config space offset
> + * @value: result; set to ~0 (PCI "no response") if the read is skipped
> + *
> + * Like pci_bus_read_config_dword() but acquires pci_lock with a trylock instead
> + * of blocking.  The PCI crash capture (CONFIG_PCI_CRASH) reads config space from
> + * crash_save_vmcoreinfo() inside __crash_kexec(), which can run while a halted
> + * peer CPU still holds pci_lock, or after this CPU was interrupted mid config
> + * access while holding it.  pci_lock is a raw (non-reentrant) spinlock, so a
> + * blocking acquire in either case would spin forever and hang the dump.  On
> + * contention this skips the device (value ~0, PCIBIOS_SET_FAILED) instead.
> + *
> + * This only avoids the pci_lock deadlock.  On x86 with legacy conf1 or
> + * mmconfig_32 port-I/O, a second blocking lock (pci_config_lock in
> + * arch/x86/pci/common.c) is taken below bus->ops->read() and is NOT covered
> + * by this trylock -- those paths can still hang the dump.  arm64 ECAM and
> + * x86-64 MMCONFIG are fully covered (no second lock).  The trylock also does
> + * not make the underlying MMIO access fault-tolerant: a read to a device whose
> + * link is down can still raise an external abort, so callers must confirm the
> + * device is reachable first.
> + *
> + * Context: crash/panic path only.  Returns 0 on success or a PCIBIOS_* error
> + * (PCIBIOS_SET_FAILED on lock contention).
> + */
> +int pci_bus_read_config_dword_trylock(struct pci_bus *bus, unsigned int devfn,
> +				      int pos, u32 *value)
> +{
> +	unsigned long flags;
> +	u32 data = 0;
> +	int res;
> +
> +	if (pos & 3) {
> +		PCI_SET_ERROR_RESPONSE(value);
> +		return PCIBIOS_BAD_REGISTER_NUMBER;
> +	}
> +	if (!bus || !bus->ops || !bus->ops->read) {
> +		PCI_SET_ERROR_RESPONSE(value);
> +		return PCIBIOS_DEVICE_NOT_FOUND;
> +	}
> +	if (!pci_trylock_config(flags)) {
> +		PCI_SET_ERROR_RESPONSE(value);
> +		return PCIBIOS_SET_FAILED;
> +	}
> +	res = bus->ops->read(bus, devfn, pos, 4, &data);
> +	if (res)
> +		PCI_SET_ERROR_RESPONSE(value);
> +	else
> +		*value = data;
> +	pci_unlock_config(flags);
> +	return res;
> +}
> +#endif /* CONFIG_PCI_CRASH */
> +
>  int pci_generic_config_read(struct pci_bus *bus, unsigned int devfn,
>  			    int where, int size, u32 *val)
>  {
> diff --git a/include/linux/pci.h b/include/linux/pci.h
> index 64b308b6e61c..1f6e1cebaecd 100644
> --- a/include/linux/pci.h
> +++ b/include/linux/pci.h
> @@ -2709,6 +2709,14 @@ static inline bool pci_dev_is_disconnected(const struct pci_dev *dev)
>  	return READ_ONCE(dev->error_state) == pci_channel_io_perm_failure;
>  }
>  
> +/* Bit index in pci_dev->priv_flags for device removal state. */
> +#define PCI_DEV_REMOVED		3
> +
> +static inline bool pci_dev_is_removed(const struct pci_dev *dev)
> +{
> +	return test_bit(PCI_DEV_REMOVED, &dev->priv_flags);
> +}
> +
>  void pci_request_acs(void);
>  bool pci_acs_enabled(struct pci_dev *pdev, u16 acs_flags);
>  bool pci_acs_path_enabled(struct pci_dev *start,
> diff --git a/include/linux/pci_crash.h b/include/linux/pci_crash.h
> new file mode 100644
> index 000000000000..dc29fe120ba9
> --- /dev/null
> +++ b/include/linux/pci_crash.h
> @@ -0,0 +1,122 @@
> +/* SPDX-License-Identifier: GPL-2.0-only */
> +/*
> + * PCI Crash Buffer - Capture PCI config space at panic time
> + *
> + * This module captures PCI configuration space data (including AER
> + * extended capability registers) for all PCI devices at panic time.
> + * The data is stored in a buffer whose pages are captured in the
> + * vmcore for off-site analysis.
> + *
> + * Copyright (c) 2026 Amazon.com, Inc. or its affiliates.
> + */
> +#ifndef _LINUX_PCI_CRASH_H
> +#define _LINUX_PCI_CRASH_H
> +
> +#include <linux/types.h>
> +
> +#define PCI_CRASH_MAGIC         0x50434943  /* "PCIC" in ASCII */
> +#define PCI_CRASH_VERSION       1
> +
> +/**
> + * struct pci_crash_buffer_header - Header for PCI crash buffer
> + * @magic:        Magic number (PCI_CRASH_MAGIC)
> + * @version:      Format version (PCI_CRASH_VERSION)
> + * @device_count: Number of device records following this header
> + * @config_size:  0 -- indicates variable-length records. Each device
> + *                record stores its own config_size (pdev->cfg_size:
> + *                256 for legacy PCI, 4096 for PCIe). Parsers walk
> + *                records sequentially using per-record config_size.
> + * @timestamp:    Capture timestamp from ktime_get_real_fast_ns()
> + * @flags:        Reserved for future use (0 for now)
> + * @reserved:     Padding to align to 32 bytes
> + *
> + * Total size: 32 bytes
> + */
> +struct pci_crash_buffer_header {
> +	__le32 magic;
> +	__le32 version;
> +	__le32 device_count;
> +	__le32 config_size;
> +	__le64 timestamp;
> +	__le32 flags;
> +	__le32 reserved;
> +} __packed;

I think you're missing header for __packed.

> +
> +/**
> + * struct pci_crash_device_record - Per-device record in crash buffer
> + * @domain:      PCI domain number
> + * @bus:         PCI bus number
> + * @devfn:       Device and function number (PCI_DEVFN format)
> + * @config_size: Config space size for this device (pdev->cfg_size:
> + *               256 for legacy PCI, 4096 for PCIe)
> + * @config_data: Raw PCI config space (config_size bytes)
> + *
> + * Records are variable-length: total size per record is
> + * PCI_CRASH_RECORD_META + config_size bytes.
> + */
> +struct pci_crash_device_record {
> +	__le16 domain;
> +	__u8   bus;
> +	__u8   devfn;
> +	__le32 config_size;
> +	__u8   config_data[];
> +} __packed;
> +
> +#define PCI_CRASH_HEADER_SIZE  sizeof(struct pci_crash_buffer_header)
> +#define PCI_CRASH_RECORD_META  sizeof(struct pci_crash_device_record)
> +
> +/**
> + * struct pci_crash_pagemap - Physical page directory for crash buffer
> + *
> + * The PCI crash buffer may be allocated via vmalloc (for buffers
> + * exceeding ~4 MB where the buddy allocator cannot provide contiguous
> + * pages).  virt_to_phys() returns garbage for vmalloc addresses, so
> + * we maintain this small kmalloc'd directory that maps the buffer's
> + * virtual pages to their actual physical addresses.
> + *
> + * At panic time, crash_core.c exports the pagemap's physical address
> + * via VMCOREINFO.  The parser reads the pagemap, then reads each
> + * physical page from the vmcore to reconstruct the full buffer.
> + *
> + * The pagemap itself is always kmalloc'd (direct-mapped), so
> + * virt_to_phys() works correctly on it.
> + *
> + * @magic:     0x5043504d ("PCPM") -- validates this is a pagemap
> + * @num_pages: Number of entries in the addrs[] array
> + * @buf_size:  Exact buffer size in bytes (last page may be partial)
> + * @buf_offset: Offset of buffer start within the first page
> + * @addrs:     Physical address of each PAGE_SIZE page backing the buffer
> + */
> +struct pci_crash_pagemap {
> +	__le32 magic;
> +	__le32 num_pages;
> +	__le64 buf_size;
> +	__le32 buf_offset;
> +	__le64 addrs[];
> +} __packed;
> +
> +#define PCI_CRASH_PAGEMAP_MAGIC  0x5043504d  /* "PCPM" in ASCII */
> +
> +struct pci_bus;
> +
> +#ifdef CONFIG_PCI_CRASH
> +void pci_crash_save(void);
> +extern void *pci_crash_buffer;
> +extern size_t pci_crash_buffer_size;
> +extern phys_addr_t pci_crash_pagemap_phys;
> +
> +/*
> + * Non-blocking config read used only by the crash capture path; defined in
> + * drivers/pci/access.c (where pci_lock lives).  Not exported and not part of
> + * the public PCI API -- it is specific to this feature.
> + */
> +int pci_bus_read_config_dword_trylock(struct pci_bus *bus, unsigned int devfn,
> +				      int pos, u32 *value);
> +#else
> +static inline void pci_crash_save(void) {}
> +#define pci_crash_buffer        ((void *)NULL)
> +#define pci_crash_buffer_size   ((size_t)0)
> +#define pci_crash_pagemap_phys  ((phys_addr_t)0)
> +#endif
> +
> +#endif /* _LINUX_PCI_CRASH_H */
> diff --git a/kernel/Kconfig.kexec b/kernel/Kconfig.kexec
> index 15632358bcf7..056767c8ea8d 100644
> --- a/kernel/Kconfig.kexec
> +++ b/kernel/Kconfig.kexec
> @@ -179,4 +179,20 @@ config CRASH_MAX_MEMORY_RANGES
>  	  the computation behind the value provided through the
>  	  /sys/kernel/crash_elfcorehdr_size attribute.
>  
> +
> +config PCI_CRASH
> +	bool "Capture PCI config space at panic time"
> +	depends on VMCORE_INFO && PCI && (ARM64 || X86)
> +	help
> +	  Capture PCI configuration space (including AER extended capability
> +	  registers) for all PCI devices at panic time.  The data is stored
> +	  in a buffer whose pages are recorded in VMCOREINFO for off-site
> +	  crash analysis.
> +
> +	  This is useful for diagnosing PCI errors that caused or contributed
> +	  to the crash, especially when AER registers are volatile and cleared
> +	  by device reset during kexec.
> +
> +	  If unsure, say Y.
> +
>  endmenu
> diff --git a/kernel/Makefile b/kernel/Makefile
> index 1e1a31673577..584ab235496e 100644
> --- a/kernel/Makefile
> +++ b/kernel/Makefile
> @@ -82,6 +82,7 @@ obj-$(CONFIG_KEXEC_CORE) += kexec_core.o
>  obj-$(CONFIG_CRASH_DUMP) += crash_core.o
>  obj-$(CONFIG_CRASH_DM_CRYPT) += crash_dump_dm_crypt.o
>  obj-$(CONFIG_CRASH_DUMP_KUNIT_TEST) += crash_core_test.o
> +obj-$(CONFIG_PCI_CRASH) += pci_crash.o
>  obj-$(CONFIG_KEXEC) += kexec.o
>  obj-$(CONFIG_KEXEC_FILE) += kexec_file.o
>  obj-$(CONFIG_KEXEC_ELF) += kexec_elf.o
> diff --git a/kernel/pci_crash.c b/kernel/pci_crash.c
> new file mode 100644
> index 000000000000..15d7fa0fc74f
> --- /dev/null
> +++ b/kernel/pci_crash.c
> @@ -0,0 +1,1013 @@
> +// SPDX-License-Identifier: GPL-2.0-only
> +/*
> + * PCI Crash Buffer - Capture PCI config space for crash analysis
> + *
> + * Copyright (c) 2026 Amazon.com, Inc. or its affiliates.
> + *
> + * Captures PCI configuration space at crash time so AER error
> + * registers reflect the crash-time state for off-site analysis.
> + *
> + * Design:
> + * - Init (late_initcall): enumerate devices, allocate buffer.
> + * - Hotplug: bus notifier queues deferred rebuild of device list
> + *   and buffer via workqueue -- no PCI reads.
> + * - Crash: crash_save_vmcoreinfo() calls pci_crash_save() which
> + *   reads config space into buffer, flushes dcache to RAM so
> + *   data survives kexec into crash kernel.
> + *
> + * Records are variable-length: each device's record is exactly
> + * 8 + pdev->cfg_size bytes (264 for legacy PCI, 4104 for PCIe).
> + * The parser walks records sequentially using per-record config_size.
> + *
> + * Buffer pages may be physically scattered (kvmalloc falls back to
> + * vmalloc for buffers exceeding ~4 MB).  A small kmalloc'd pagemap
> + * records each page's physical address so the crash parser can
> + * reconstruct the buffer without page-table walking.
> + *
> + * Config reads at crash time use pci_bus_read_config_dword_trylock(), which
> + * trylocks pci_lock and skips the device on contention.  pci_crash_save() runs
> + * from crash_save_vmcoreinfo() inside __crash_kexec(); depending on
> + * crash_kexec_post_notifiers, peer CPUs may already be halted (possibly while
> + * holding pci_lock) and this CPU may itself hold pci_lock (a panic inside a
> + * config access).  pci_lock is a raw, non-reentrant spinlock, so a blocking
> + * acquire would deadlock the dump in either case; the trylock skips instead.
> + * This guards against the lock deadlock only -- a read to an unreachable device
> + * is handled separately by pci_crash_endpoint_reachable().
> + * for_each_pci_dev() needs pci_bus_sem -- only used at init/hotplug.
> + */
> +
> +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
> +
> +#include <linux/crash_dump.h>
> +#include <linux/init.h>
> +#include <linux/kernel.h>
> +#include <linux/mm.h>
> +#include <linux/moduleparam.h>
> +#include <linux/mutex.h>
> +#include <linux/pci.h>
> +#include <linux/pci_crash.h>
> +#include <linux/slab.h>
> +#include <linux/timekeeping.h>
> +#include <linux/workqueue.h>
> +#include <linux/rcupdate.h>

Move this to its place in alphabetical order.

> +
> +#include <linux/cacheflush.h>
> +#include <linux/unaligned.h>

I don't know why these are not among the other headers but in a separate 
group.

> +
> +/**
> + * pci_crash_flush_dcache() - Flush a memory region from CPU cache to RAM
> + * @addr: virtual address of region to flush
> + * @size: size in bytes
> + *
> + * Used at crash time to ensure the crash kernel sees our buffer/pagemap
> + * writes after kexec.
> + */
> +static inline void pci_crash_flush_dcache(void *addr, size_t size)
> +{
> +	/* Only ARM64 and x86 implemented; Kconfig enforces depends on (ARM64 || X86). */
> +#ifdef CONFIG_ARM64
> +	unsigned long start = (unsigned long)addr;
> +	unsigned long end = start + size;
> +
> +	dcache_clean_inval_poc(start, end);
> +#elif defined(CONFIG_X86)
> +	clflush_cache_range(addr, size);
> +#else
> +#error "CONFIG_PCI_CRASH requires ARM64 or X86 dcache flush support (Kconfig depends)"
> +#endif
> +}
> +
> +/*
> + * Live capture state is published as a single RCU-managed snapshot so the
> + * lockless crash-time reader (pci_crash_save) always observes a consistent
> + * {devs, num_devs, buffer, pagemap} set and can never race the rebuild
> + * worker freeing the old arrays.  The retired snapshot is reclaimed via
> + * queue_rcu_work() once no reader can hold it -- see pci_crash_rebuild_snapshot().
> + */
> +struct pci_crash_snapshot {
> +	struct pci_dev		**devs;
> +	unsigned int		num_devs;
> +	void			*buffer;
> +	size_t			buffer_size;
> +	struct pci_crash_pagemap *pagemap;
> +	size_t			pagemap_size;
> +	phys_addr_t		pagemap_phys;
> +	struct rcu_work		rcu_work;
> +};
> +
> +static struct pci_crash_snapshot __rcu *pci_crash_snap;
> +
> +/*
> + * Scalars consumed by crash_core.c's crash_save_vmcoreinfo() right AFTER it
> + * calls pci_crash_save().  pci_crash_save() publishes them from the snapshot
> + * it captured; on a skipped or failed capture they are set to 0 so no stale
> + * pagemap is exported into the vmcore.
> + */
> +void *pci_crash_buffer;
> +EXPORT_SYMBOL_GPL(pci_crash_buffer);

Add include.

> +
> +size_t pci_crash_buffer_size;
> +EXPORT_SYMBOL_GPL(pci_crash_buffer_size);
> +
> +phys_addr_t pci_crash_pagemap_phys;
> +EXPORT_SYMBOL_GPL(pci_crash_pagemap_phys);
> +
> +/*
> + * Set by pci_crash_save() to the snapshot it captured, so its buffer/pagemap
> + * (whose addresses are exported into the vmcore via vmcore_info.c) cannot be
> + * reclaimed out from under the crash kernel.  pci_crash_save() publishes the
> + * buffer scalars and returns; vmcore_info.c then reads them and machine_kexec()
> + * snapshots RAM -- all AFTER rcu_read_unlock(), so RCU read-side protection has
> + * already ended by the time the buffer matters.  A rebuild racing on a live
> + * peer CPU (the default panic path runs __crash_kexec() before halting peers)
> + * could otherwise queue_rcu_work()-free this snapshot before kexec.  The free
> + * callback below honours this pin and leaks the snapshot instead -- harmless,
> + * the system is going down.  Written under rcu_read_lock() before the scalars
> + * are published, so the grace period ordering guarantees the callback sees it.
> + */
> +static struct pci_crash_snapshot *pci_crash_captured_snap;
> +
> +/*
> + * Reclaim a retired snapshot after a grace period: drop dev refs + free.
> + * Runs via queue_rcu_work() in process context -- pci_dev_put() may trigger
> + * device_release() -> devres_release_all() which can sleep.
> + */
> +static void pci_crash_snapshot_free_work(struct work_struct *work)
> +{
> +	struct pci_crash_snapshot *s =
> +		container_of(to_rcu_work(work), struct pci_crash_snapshot, rcu_work);

Include for container_of().

> +	unsigned int i;
> +
> +	/*
> +	 * Pinned by an in-progress crash capture: its buffer address is live in
> +	 * the vmcore export.  Leak it rather than free memory kexec will read.
> +	 */
> +	if (READ_ONCE(pci_crash_captured_snap) == s)

I don't remember what's the header for READ_ONCE() but you're likely 
missing it as well.

> +		return;
> +
> +	for (i = 0; i < s->num_devs; i++)
> +		if (s->devs && s->devs[i])
> +			pci_dev_put(s->devs[i]);
> +	kvfree(s->devs);
> +	kvfree(s->buffer);
> +	kfree(s->pagemap);
> +	kfree(s);
> +}
> +
> +static DEFINE_MUTEX(pci_crash_lock);

Please document what a lock protects.

> +
> +/*
> + * Set in pci_crash_init() after delayed_work, PCI bus and notifier are
> + * ready.  Guards parse + rebuild in param setters: at boot (level -1)
> + * the setter just stores the string; pci_crash_init() parses and does
> + * the initial rebuild once PCI is up.
> + */
> +static bool pci_crash_ready;
> +
> +/*
> + * capture -- when to capture PCI config space.
> + * Comma-separated tokens:
> + *   aer    -- root port ROOT_STATUS has uncorrectable errors
> + *   always -- every panic regardless of PCI error state (default)
> + *
> + * Writable at runtime (0644) so operators and tests can toggle without
> + * reboot.  Writes re-parse capture_flags immediately.
> + */
> +#define PCI_CRASH_PARAM_CAPTURE_LEN	32
> +static char capture[PCI_CRASH_PARAM_CAPTURE_LEN] = "always";
> +
> +#define PCI_CRASH_CAPTURE_AER		BIT(0)
> +#define PCI_CRASH_CAPTURE_ALWAYS	BIT(1)

Add include for BIT()

> +static unsigned long capture_flags = PCI_CRASH_CAPTURE_ALWAYS;
> +
> +static void pci_crash_parse_capture(void);
> +
> +static int capture_param_set(const char *val, const struct kernel_param *kp)
> +{
> +	char *trimmed;
> +
> +	if (strlen(val) >= sizeof(capture))

Include for strlen()

> +		return -EINVAL;
> +
> +	/* Serialize against concurrent sysfs writers mutating the string. */
> +	mutex_lock(&pci_crash_lock);
> +	strscpy(capture, val, sizeof(capture));

Doesn't this work with two parameters version of strscpy(), IIRC we had 
the macro magic for that?

> +	trimmed = strim(capture);
> +	if (trimmed != capture)
> +		memmove(capture, trimmed, strlen(trimmed) + 1);
> +	if (READ_ONCE(pci_crash_ready))
> +		pci_crash_parse_capture();
> +	mutex_unlock(&pci_crash_lock);
> +	return 0;
> +}
> +
> +static int capture_param_get(char *buf, const struct kernel_param *kp)
> +{
> +	return scnprintf(buf, PAGE_SIZE, "%s\n", capture);
> +}
> +
> +static const struct kernel_param_ops capture_param_ops = {
> +	.set = capture_param_set,
> +	.get = capture_param_get,
> +};
> +module_param_cb(capture, &capture_param_ops, NULL, 0644);
> +MODULE_PARM_DESC(capture, "When to capture: aer, always (default: always)");
> +
> +/*
> + * devices -- which devices to capture.
> + * Comma-separated tokens:
> + *   all        -- every PCI device (default)
> + *   bridges    -- PCI bridges (class 0604, 0607)
> + *   root_ports -- PCIe root ports only
> + *   XXYY       -- hex PCI class code (class + subclass)
> + *
> + * Bridges are always implicitly included regardless of filter value
> + * because they hold the AER registers needed for root cause analysis.
> + * Applies at rebuild time only -- zero cost at crash time.  Writable
> + * at runtime (0644); writes re-parse and trigger async rebuild.
> + */
> +#define PCI_CRASH_PARAM_DEVICES_LEN	256
> +static char devices[PCI_CRASH_PARAM_DEVICES_LEN] = "all";
> +
> +static void pci_crash_parse_devices(void);
> +static struct delayed_work pci_crash_rebuild_dwork;
> +
> +/* Debounce period for bus notifications (ms).
> + * SR-IOV liveupdate can enumerate ~3000 VFs in ~1.5s -- this coalesces
> + * the storm into a single rebuild after the last event.
> + */
> +#define PCI_CRASH_REBUILD_DELAY_MS	200
> +
> +static int devices_param_set(const char *val, const struct kernel_param *kp)
> +{
> +	if (strlen(val) >= sizeof(devices))
> +		return -EINVAL;
> +
> +	mutex_lock(&pci_crash_lock);
> +	strscpy(devices, val, sizeof(devices));

2 params version?

> +	{
> +		char *trimmed = strim(devices);
> +
> +		if (trimmed != devices)
> +			memmove(devices, trimmed, strlen(trimmed) + 1);
> +	}
> +	if (READ_ONCE(pci_crash_ready)) {
> +		pci_crash_parse_devices();
> +		mod_delayed_work(system_wq, &pci_crash_rebuild_dwork,
> +				 msecs_to_jiffies(PCI_CRASH_REBUILD_DELAY_MS));
> +	}
> +	mutex_unlock(&pci_crash_lock);
> +	return 0;
> +}
> +
> +static int devices_param_get(char *buf, const struct kernel_param *kp)
> +{
> +	return scnprintf(buf, PAGE_SIZE, "%s\n", devices);
> +}
> +
> +static const struct kernel_param_ops devices_param_ops = {
> +	.set = devices_param_set,
> +	.get = devices_param_get,
> +};
> +module_param_cb(devices, &devices_param_ops, NULL, 0644);
> +MODULE_PARM_DESC(devices,
> +	"Which devices: all, bridges, root_ports, XXYY hex class (default: all)");
> +
> +#define PCI_CRASH_DEVICES_ALL		BIT(0)
> +#define PCI_CRASH_DEVICES_BRIDGES	BIT(1)
> +#define PCI_CRASH_DEVICES_ROOT_PORTS	BIT(2)
> +/* Max distinct class-code filters in a devices= list; 8 covers realistic use. */
> +#define PCI_CRASH_MAX_DEVICE_CLASSES	8
> +static unsigned long devices_flags = PCI_CRASH_DEVICES_ALL;
> +static u16 device_classes[PCI_CRASH_MAX_DEVICE_CLASSES];
> +static unsigned int device_class_count;
> +
> +static void pci_crash_parse_capture(void)
> +{
> +	char *buf, *token, *rest;
> +	unsigned long flags = 0;
> +
> +	if (!*capture) {
> +		WRITE_ONCE(capture_flags, PCI_CRASH_CAPTURE_ALWAYS);
> +		return;
> +	}
> +
> +	buf = kstrdup(capture, GFP_KERNEL);
> +	if (!buf) {
> +		WRITE_ONCE(capture_flags, PCI_CRASH_CAPTURE_ALWAYS);
> +		return;
> +	}
> +
> +	rest = buf;
> +	while ((token = strsep(&rest, ",")) != NULL) {
> +		if (strcmp(token, "aer") == 0)
> +			flags |= PCI_CRASH_CAPTURE_AER;
> +		else if (strcmp(token, "always") == 0)
> +			flags |= PCI_CRASH_CAPTURE_ALWAYS;
> +		else
> +			pr_warn("unknown capture token: %s\n",
> +				token);

Fits to one line.

Add include.

> +	}
> +	kfree(buf);
> +
> +	if (!flags) {
> +		pr_warn("no valid capture tokens, defaulting to always\n");
> +		flags = PCI_CRASH_CAPTURE_ALWAYS;
> +	}
> +	WRITE_ONCE(capture_flags, flags);
> +}
> +
> +static void pci_crash_parse_devices(void)
> +{
> +	char *buf, *token, *rest;
> +	unsigned long val;
> +
> +	devices_flags = 0;
> +	device_class_count = 0;
> +
> +	if (!*devices) {
> +		devices_flags = PCI_CRASH_DEVICES_ALL;
> +		return;
> +	}
> +
> +	buf = kstrdup(devices, GFP_KERNEL);
> +	if (!buf) {
> +		devices_flags = PCI_CRASH_DEVICES_ALL;
> +		return;
> +	}
> +
> +	rest = buf;
> +	while ((token = strsep(&rest, ",")) != NULL) {
> +		if (strcmp(token, "all") == 0) {
> +			devices_flags |= PCI_CRASH_DEVICES_ALL;
> +		} else if (strcmp(token, "bridges") == 0) {
> +			devices_flags |= PCI_CRASH_DEVICES_BRIDGES;
> +		} else if (strcmp(token, "root_ports") == 0) {
> +			devices_flags |= PCI_CRASH_DEVICES_ROOT_PORTS;
> +		} else if (kstrtoul(token, 16, &val) == 0 && val <= 0xFFFF) {
> +			if (device_class_count < PCI_CRASH_MAX_DEVICE_CLASSES)
> +				device_classes[device_class_count++] = (u16)val;
> +			else
> +				pr_warn("too many device classes (max %d)\n",
> +					PCI_CRASH_MAX_DEVICE_CLASSES);
> +		} else {
> +			pr_warn("unknown devices token: %s\n",
> +				token);

One line.

> +		}
> +	}
> +	kfree(buf);
> +
> +	if (!devices_flags && device_class_count == 0) {
> +		pr_warn("no valid devices tokens, defaulting to all\n");
> +		devices_flags = PCI_CRASH_DEVICES_ALL;
> +	}
> +}
> +
> +static bool pci_crash_device_matches(struct pci_dev *pdev)
> +{
> +	unsigned int i;
> +	u16 dev_class = pdev->class >> 8;
> +
> +	if (devices_flags & PCI_CRASH_DEVICES_ALL)
> +		return true;
> +
> +	/* Bridges always included -- they hold AER registers */
> +	if (dev_class == PCI_CLASS_BRIDGE_PCI ||
> +	    dev_class == PCI_CLASS_BRIDGE_CARDBUS)

Is cardbus that relevant still? :-/

> +		return true;
> +
> +	if ((devices_flags & PCI_CRASH_DEVICES_ROOT_PORTS) &&
> +	    pci_pcie_type(pdev) == PCI_EXP_TYPE_ROOT_PORT)
> +		return true;
> +
> +	for (i = 0; i < device_class_count; i++) {
> +		if (dev_class == device_classes[i])
> +			return true;
> +	}
> +
> +	return false;
> +}
> +
> +/* Sanity limit -- prevents multi-GB allocations on systems with many VFs */
> +#define PCI_CRASH_MAX_BUFFER_SIZE	(24 * 1024 * 1024)

Please use SZ_xx + remember to add the header.

> +
> +/*
> + * PCIe extended config space size. Per-device reads are clamped to this in
> + * case a device's cfg_size is corrupt at crash time.
> + */
> +#define PCI_CRASH_MAX_CFG_SIZE		4096

PCI_CFG_SPACE_EXP_SIZE

> +
> +/**
> + * pci_crash_build_pagemap() - Build physical page directory for buffer
> + * @buf: buffer allocated via kvmalloc (may be vmalloc'd)
> + * @buf_size: buffer size in bytes
> + *
> + * Allocates a kmalloc'd directory containing the physical address of
> + * each page backing @buf.  The pagemap is always direct-mapped, so
> + * virt_to_phys() works on it at crash time.
> + *
> + * Return: the new pagemap, or NULL on allocation failure.
> + */
> +static struct pci_crash_pagemap *pci_crash_build_pagemap(void *buf,
> +							 size_t buf_size)
> +{
> +	unsigned int num_pages = DIV_ROUND_UP(offset_in_page(buf) + buf_size, PAGE_SIZE);

Add include.

> +	struct pci_crash_pagemap *pm;
> +	unsigned int i;
> +
> +	pm = kmalloc(struct_size(pm, addrs, num_pages), GFP_KERNEL);
> +	if (!pm)
> +		return NULL;
> +
> +	pm->magic = cpu_to_le32(PCI_CRASH_PAGEMAP_MAGIC);
> +	pm->num_pages = cpu_to_le32(num_pages);
> +	pm->buf_size = cpu_to_le64(buf_size);
> +	pm->buf_offset = cpu_to_le32(offset_in_page(buf));
> +
> +	for (i = 0; i < num_pages; i++) {
> +		struct page *page;
> +		phys_addr_t pa;
> +
> +		if (is_vmalloc_addr(buf + i * PAGE_SIZE))
> +			page = vmalloc_to_page(buf + i * PAGE_SIZE);
> +		else
> +			page = virt_to_page(buf + i * PAGE_SIZE);
> +
> +		if (!page) {
> +			kfree(pm);
> +			return NULL;
> +		}
> +		pa = page_to_phys(page);
> +		pm->addrs[i] = cpu_to_le64(pa);
> +	}
> +
> +	return pm;
> +}
> +
> +/**
> + * pci_crash_endpoint_reachable() - Decide if @pdev is safe to read at crash time
> + * @pdev: PCI device about to be read
> + *
> + * A config read to a device whose PCIe link is physically down can, on some
> + * architectures (notably arm64), raise a synchronous external abort.  In the
> + * crash path that abort is unrecoverable -- the arm64 SEA handler (do_sea())
> + * has no kernel-mode fixup and calls arm64_notify_die(), double-faulting the
> + * panic and hanging the very dump we are trying to produce.  On x86 such a
> + * read returns all-ones harmlessly.  So before touching an endpoint we must
> + * establish reachability WITHOUT reading the endpoint itself.
> + *
> + * Two cheap, panic-safe signals are used, in order:
> + *
> + *  1. Software state -- pci_dev_is_disconnected(), pci_channel_offline(),
> + *     pci_dev_is_removed() and PCI_D3cold are pure flag reads (no MMIO).  They
> + *     catch devices a subsystem has already marked gone (hotplug remove, failed
> + *     AER/DPC recovery, powered-off).  pci_dev_is_removed() also covers a
> + *     cleanly-removed device whose pci_dev is pinned by a snapshot reference
> + *     while its host bridge module (hence bus->ops) may already be freed.
> + *     They are necessary but not sufficient: in the
> + *     cascading-failure window this feature targets, a link can be down before
> + *     any subsystem has updated error_state, so these flags can still read
> + *     "live".
> + *
> + *  2. Parent-bridge link state -- read PCI_EXP_LNKSTA on the immediate upstream
> + *     PCIe port and test Data Link Layer Link Active (DLLLA).  The upstream
> + *     port is on-die and always responds, so reading *its* config space cannot
> + *     raise an abort caused by the endpoint's dead link.  If DLLLA is clear,
> + *     the endpoint is unreachable and is skipped without ever being touched.
> + *     The bridge read uses the crash-safe trylock accessor so it cannot hang
> + *     on pci_lock either.
> + *
> + * Return: false if @pdev should be skipped (record filled with 0xFFFFFFFF).
> + *
> + * Residual: a single immediate-parent check; a link that drops between this
> + * check and the read (TOCTOU), or a multi-level fabric collapse where the
> + * upstream port itself sits behind a dead link, is not covered here.  See
> + * Documentation/PCI/pci-crash-capture.rst for the documented arm64 caveat.
> + */
> +static bool pci_crash_endpoint_reachable(struct pci_dev *pdev)
> +{
> +	struct pci_dev *bridge;
> +	u32 lnksta = 0;
> +	u16 sta;
> +	int pos;
> +
> +	/* Software-state flags: pure reads, no MMIO -- always panic-safe. */
> +	if (pci_dev_is_disconnected(pdev) || pci_channel_offline(pdev) ||
> +	    pci_dev_is_removed(pdev) ||
> +	    pdev->current_state == PCI_D3cold)
> +		return false;
> +
> +	/*
> +	 * Live link check via the immediate upstream PCIe port.  Only meaningful
> +	 * for a device sitting below a PCIe downstream/root port; for non-PCIe
> +	 * or root-complex-integrated devices there is no such link to test, so
> +	 * treat them as reachable and let the crash-safe accessor handle the read.
> +	 */
> +	bridge = pci_upstream_bridge(pdev);
> +	if (bridge && pci_is_pcie(bridge) &&
> +	    (pci_pcie_type(bridge) == PCI_EXP_TYPE_ROOT_PORT ||
> +	     pci_pcie_type(bridge) == PCI_EXP_TYPE_DOWNSTREAM) &&
> +	    bridge->pcie_cap && bridge->bus) {
> +		pos = bridge->pcie_cap + PCI_EXP_LNKSTA;
> +		/*
> +		 * Read the aligned dword containing LNKSTA from the on-die
> +		 * upstream port (always present), then extract the 16-bit field.
> +		 *
> +		 * Fail closed: if the bridge read cannot complete -- lock
> +		 * contention (PCIBIOS_SET_FAILED) or any PCIBIOS error -- we
> +		 * cannot prove the link is up, so treat the endpoint as
> +		 * unreachable rather than issuing an MMIO read that may raise a
> +		 * fatal external abort.  Contention is exactly a crash-path
> +		 * condition we assume is likely, so "unknown" must mean "skip".
> +		 */
> +		if (pci_bus_read_config_dword_trylock(bridge->bus, bridge->devfn,
> +						      pos & ~0x3, &lnksta) != 0)

!= PCIBIOS_SUCCESSFUL, please add ret variable to keep line length sane.

> +			return false;
> +
> +		sta = (pos & 0x2) ? (lnksta >> 16) : (lnksta & 0xffff);

Why is this necessary???

> +		if (!(sta & PCI_EXP_LNKSTA_DLLLA))
> +			return false;	/* link down -> endpoint gone */
> +	}
> +
> +	return true;
> +}
> +
> +/**
> + * pci_crash_read_config_space() - Read config space for one device
> + * @pdev: PCI device to read
> + * @ptr: destination pointer within the crash buffer
> + * @cfg_size: number of config bytes to read (already clamped by the caller)
> + *
> + * Reads @cfg_size bytes one dword at a time.  Devices deemed unreachable by
> + * pci_crash_endpoint_reachable() (disconnected, in error recovery, powered
> + * off, or behind a down PCIe link) are skipped without being touched: on x86
> + * such a read returns all-ones harmlessly, but on other architectures (e.g.
> + * arm64) it can raise a fatal external abort that double-faults the panic
> + * path.  Skipped or failed reads store 0xFFFFFFFF -- the standard PCI
> + * convention for absent/unreachable registers.
> + */
> +static void pci_crash_read_config_space(struct pci_dev *pdev, u8 *ptr,
> +					unsigned int cfg_size)
> +{
> +	struct pci_crash_device_record *record =
> +		(struct pci_crash_device_record *)ptr;
> +	u8 *cfg_data = ptr + PCI_CRASH_RECORD_META;
> +	unsigned int offset;
> +	u32 val;
> +
> +	/* Defensive: never trust cfg_size, never deref a torn-down bus. */
> +	if (cfg_size > PCI_CRASH_MAX_CFG_SIZE)
> +		cfg_size = PCI_CRASH_MAX_CFG_SIZE;
> +
> +	if (!pdev->bus) {
> +		record->domain = 0;
> +		record->bus = 0;
> +		record->devfn = pdev->devfn;
> +		record->config_size = cpu_to_le32(cfg_size);
> +		memset(cfg_data, 0xff, cfg_size);
> +		return;
> +	}
> +
> +	record->domain = cpu_to_le16(pci_domain_nr(pdev->bus));
> +	record->bus = pdev->bus->number;
> +	record->devfn = pdev->devfn;
> +	record->config_size = cpu_to_le32(cfg_size);
> +
> +	if (!pci_crash_endpoint_reachable(pdev)) {
> +		memset(cfg_data, 0xff, cfg_size);
> +		return;
> +	}
> +
> +	for (offset = 0; offset < cfg_size; offset += 4) {
> +		if (pci_bus_read_config_dword_trylock(pdev->bus, pdev->devfn,
> +						      offset, &val)) {
> +			put_unaligned_le32(0xFFFFFFFF, &cfg_data[offset]);
> +			continue;
> +		}
> +		put_unaligned_le32(val, &cfg_data[offset]);

Why would the destination be unaligned?

> +	}
> +}
> +
> +/**
> + * pci_crash_fill_buffer() - Populate buffer with config space
> + * @s: snapshot whose buffer is filled from its captured device list
> + *
> + * Records are variable-length: each is PCI_CRASH_RECORD_META +
> + * pdev->cfg_size bytes. The header's config_size is 0 to indicate
> + * variable-length; parsers walk records using per-record config_size.
> + *
> + * Uses ktime_get_real_fast_ns() for the timestamp -- safe in NMI/panic
> + * context (lockless, reads the NMI-safe timekeeper snapshot).
> + *
> + * Caller holds rcu_read_lock() so @s stays valid for the whole fill.
> + */
> +static void pci_crash_fill_buffer(struct pci_crash_snapshot *s)
> +{
> +	struct pci_crash_buffer_header *header = s->buffer;
> +	struct pci_dev **devs = s->devs;
> +	u8 *ptr, *end;
> +	unsigned int i;
> +
> +	header->magic = cpu_to_le32(PCI_CRASH_MAGIC);
> +	header->version = cpu_to_le32(PCI_CRASH_VERSION);
> +	header->device_count = cpu_to_le32(s->num_devs);
> +	header->config_size = 0;
> +	header->timestamp = cpu_to_le64(ktime_get_real_fast_ns());
> +	header->flags = 0;
> +	header->reserved = 0;
> +
> +	ptr = (u8 *)s->buffer + PCI_CRASH_HEADER_SIZE;
> +	end = (u8 *)s->buffer + s->buffer_size;
> +	for (i = 0; i < s->num_devs; i++) {
> +		struct pci_dev *pdev = devs[i];
> +		unsigned int cfg_size;
> +		size_t rec_size;
> +
> +		if (unlikely(!pdev))
> +			break;
> +
> +		cfg_size = pdev->cfg_size;
> +		if (cfg_size > PCI_CRASH_MAX_CFG_SIZE)
> +			cfg_size = PCI_CRASH_MAX_CFG_SIZE;
> +		rec_size = PCI_CRASH_RECORD_META + cfg_size;
> +
> +		/*
> +		 * Never write past the buffer if the device set or a device's
> +		 * cfg_size grew since the buffer was sized at rebuild time.
> +		 */
> +		if (unlikely(ptr + rec_size > end))
> +			break;
> +
> +		pci_crash_read_config_space(pdev, ptr, cfg_size);
> +		ptr += rec_size;
> +	}
> +
> +	header->device_count = cpu_to_le32(i);
> +}
> +
> +/**
> + * pci_crash_rebuild_snapshot() - Rebuild device list and allocate buffer
> + *
> + * Two-pass approach:
> + *   Pass 1: count PCI devices
> + *   Pass 2: populate device array (filtered) and compute exact buffer
> + *            size from actual pdev->cfg_size per device (no padding)
> + *
> + * The devices param controls which devices are included.  Bridges are
> + * always included regardless of devices setting (they hold AER registers).
> + * devices=all (default) includes everything.
> + *
> + * Does NOT read PCI config space -- reads happen only at crash time.
> + * This keeps rebuild fast during VF enumeration storms (~6000 ADD
> + * events on large accelerator hosts during liveupdate).
> + *
> + * After allocation, builds the pagemap so the crash parser can
> + * locate the buffer's physical pages in the vmcore.
> + *
> + * Publishes the new snapshot via rcu_assign_pointer() and retires the
> + * previous one via queue_rcu_work(), so the lockless crash-time reader never
> + * sees a half-updated state or a freed array.
> + *
> + * Caller must hold pci_crash_lock.
> + */
> +static void pci_crash_rebuild_snapshot(void)
> +{
> +	struct pci_crash_snapshot *old, *new;
> +	struct pci_dev *pdev = NULL;
> +	unsigned int count = 0, i;
> +	size_t total_size;
> +
> +	old = rcu_dereference_protected(pci_crash_snap,
> +					lockdep_is_held(&pci_crash_lock));
> +
> +	/* Pass 1: count devices (upper bound). */
> +	for_each_pci_dev(pdev)
> +		count++;
> +
> +	new = kzalloc(sizeof(*new), GFP_KERNEL);
> +	if (!new) {
> +		pr_warn_ratelimited("snapshot alloc failed; keeping previous (capture may be stale)\n");
> +		return;
> +	}
> +	INIT_RCU_WORK(&new->rcu_work, pci_crash_snapshot_free_work);
> +
> +	if (count == 0) {
> +		pr_info("no PCI devices found\n");
> +		goto publish;	/* publish an empty snapshot */
> +	}
> +
> +	new->devs = kvmalloc_array(count, sizeof(*new->devs),
> +				   GFP_KERNEL | __GFP_ZERO);
> +	if (!new->devs) {
> +		kfree(new);
> +		pr_warn_ratelimited("devs alloc failed; keeping previous (capture may be stale)\n");
> +		return;
> +	}
> +
> +	/*
> +	 * Pass 2: populate filtered device array and compute the exact
> +	 * buffer size.  count (pass 1) is an upper bound; actual may be less.
> +	 */
> +	total_size = PCI_CRASH_HEADER_SIZE;
> +	pdev = NULL;
> +	i = 0;
> +	for_each_pci_dev(pdev) {
> +		if (i >= count) {
> +			pci_dev_put(pdev);
> +			break;
> +		}
> +		if (!pci_crash_device_matches(pdev))
> +			continue;
> +		new->devs[i] = pci_dev_get(pdev);
> +		total_size += PCI_CRASH_RECORD_META + pdev->cfg_size;
> +		i++;
> +	}
> +	new->num_devs = i;
> +
> +	if (new->num_devs == 0) {
> +		/* Publish empty: releases the previous (now stale) device set. */
> +		kvfree(new->devs);
> +		new->devs = NULL;
> +		pr_info("no devices match devices=%s\n", devices);
> +		goto publish;
> +	}
> +
> +	if (total_size > PCI_CRASH_MAX_BUFFER_SIZE) {
> +		pr_warn_ratelimited("buffer too large (%zu > %d bytes); keeping previous snapshot (capture may be stale)\n",
> +				    total_size, PCI_CRASH_MAX_BUFFER_SIZE);
> +		goto err_free_devs;
> +	}
> +
> +	new->buffer = kvmalloc(total_size, GFP_KERNEL | __GFP_ZERO);
> +	if (!new->buffer)
> +		goto err_free_devs;
> +	new->buffer_size = total_size;
> +
> +	new->pagemap = pci_crash_build_pagemap(new->buffer, total_size);
> +	if (!new->pagemap)
> +		goto err_free_buf;
> +	new->pagemap_size = struct_size(new->pagemap, addrs,
> +					le32_to_cpu(new->pagemap->num_pages));
> +	new->pagemap_phys = virt_to_phys(new->pagemap);
> +
> +	pr_info("rebuild: %u devices (%zu bytes, %u pages)\n",
> +		new->num_devs, total_size,
> +		le32_to_cpu(new->pagemap->num_pages));
> +
> +publish:
> +	/*
> +	 * Publish the new snapshot and retire the old one.  Readers in
> +	 * pci_crash_save() hold rcu_read_lock(), so queue_rcu_work() defers
> +	 * the old snapshot's frees until a grace period elapses, then runs
> +	 * the free in process context (pci_dev_put may sleep via
> +	 * device_release -> devres_release_all).
> +	 */
> +	rcu_assign_pointer(pci_crash_snap, new);
> +	if (old)
> +		WARN_ON(!queue_rcu_work(system_wq, &old->rcu_work));

Add include for WARN_ON().

> +	return;
> +
> +err_free_buf:
> +	kvfree(new->buffer);
> +err_free_devs:
> +	for (i = 0; i < new->num_devs; i++)
> +		pci_dev_put(new->devs[i]);
> +	kvfree(new->devs);
> +	kfree(new);
> +	/*
> +	 * Allocation failed building the new snapshot.  Keep the existing
> +	 * snapshot live (do not publish) so capture still works with the
> +	 * prior device set; warn (ratelimited) so persistent failures show.
> +	 */
> +	pr_warn_ratelimited("rebuild failed; keeping previous snapshot (capture may be stale)\n");
> +}
> +
> +#ifdef CONFIG_PCIEAER
> +/*
> + * Quick-scan root ports for a received uncorrectable AER error -- the signal
> + * that this panic is PCI-related and worth capturing.
> + *
> + * Return: true on the first root port whose ROOT_STATUS reports an
> + * uncorrectable error.
> + */
> +static bool pci_crash_aer_error_present(struct pci_crash_snapshot *s)
> +{
> +	unsigned int i;
> +
> +	for (i = 0; i < s->num_devs; i++) {
> +		struct pci_dev *pdev = s->devs[i];
> +		u32 status = 0;
> +
> +		if (!pdev || !pdev->aer_cap)
> +			continue;
> +		if (pci_pcie_type(pdev) != PCI_EXP_TYPE_ROOT_PORT)
> +			continue;
> +		/*
> +		 * Same reachability gate as the capture path.  A root port is
> +		 * on-die (no upstream bridge), so this reduces to the software-
> +		 * state flags -- we read the port's own AER registers, never an
> +		 * endpoint behind a potentially-dead link.
> +		 */
> +		if (!pci_crash_endpoint_reachable(pdev))
> +			continue;
> +
> +		/*
> +		 * Fail closed, like the reachability check: a failed read
> +		 * (lock contention or PCIBIOS error) sets status to ~0, which
> +		 * would falsely test as "uncorrectable error present" and force
> +		 * a pointless full capture.  Unknown means "no error seen".
> +		 */
> +		if (pci_bus_read_config_dword_trylock(pdev->bus, pdev->devfn,
> +						      pdev->aer_cap + PCI_ERR_ROOT_STATUS,
> +						      &status) != 0)

!= PCIBIOS_SUCCESSFUL, use ret variable and do the compare separate 
from the call.

> +			continue;
> +		if (status & PCI_ERR_ROOT_UNCOR_RCV)
> +			return true;
> +	}
> +	return false;
> +}
> +#else
> +static inline bool pci_crash_aer_error_present(struct pci_crash_snapshot *s)
> +{
> +	return false;
> +}
> +#endif
> +
> +/**
> + * pci_crash_save() - Capture PCI config space at crash time
> + *
> + * Called from crash_save_vmcoreinfo() inside __crash_kexec(), which
> + * runs before machine_kexec() boots the crash kernel.  This is the
> + * only reliable capture point -- panic notifiers run AFTER kexec by
> + * default (crash_kexec_post_notifiers=0).
> + *
> + * Capture check (capture param):
> + *   always  -- capture unconditionally
> + *   aer     -- quick-scan root port AER ROOT_STATUS for uncorrectable
> + *             errors; skip if none found
> + *
> + * When capture=always, captures on every panic.
> + * This is useful for cascading failures: a PCI link-down can cause
> + * an MCE or NMI watchdog timeout before DPC/AER fires, so the crash
> + * reason is UNKNOWN but AER registers may still hold error state.
> + *
> + * Reads config space fresh -- successful reads get current register
> + * state, failed reads (offline devices) write 0xFFFFFFFF.
> + *
> + * Flushes both buffer and pagemap from CPU cache to RAM so data
> + * survives kexec into crash kernel.
> + *
> + * Runs under rcu_read_lock(): a rebuild worker may still be mid-flight on a
> + * peer CPU, so RCU keeps the sampled snapshot alive for the whole capture.
> + */
> +void pci_crash_save(void)
> +{
> +	struct pci_crash_snapshot *s;
> +	unsigned long cflags;
> +
> +	/* Cleared first; set only on a successful capture below. */
> +	pci_crash_buffer = NULL;
> +	pci_crash_buffer_size = 0;
> +	pci_crash_pagemap_phys = 0;
> +
> +	rcu_read_lock();
> +	s = rcu_dereference(pci_crash_snap);
> +	if (!s || s->num_devs == 0)
> +		goto out;
> +	if (!s->buffer || s->buffer_size == 0)
> +		goto out;
> +
> +	/*
> +	 * Pin this snapshot so a rebuild racing on a live peer CPU cannot
> +	 * queue_rcu_work()-free its buffer/pagemap before machine_kexec() snapshots
> +	 * RAM.  The scalars below are read by vmcore_info.c AFTER we return and
> +	 * rcu_read_unlock() -- i.e. after RCU read-side protection has ended --
> +	 * so RCU alone does not keep the buffer alive that long.  Written here,
> +	 * under rcu_read_lock() and before the scalars are published; the free
> +	 * callback honours it (see pci_crash_snapshot_free_work).
> +	 */
> +	WRITE_ONCE(pci_crash_captured_snap, s);
> +
> +	/*
> +	 * Publish the buffer location now -- before the AER quick-scan that may
> +	 * skip the capture -- so vmcore_info.c always exports a valid (possibly
> +	 * empty) buffer.  vmcore_info.c reads only these scalars, immediately
> +	 * after we return and still inside __crash_kexec() before
> +	 * machine_kexec().
> +	 */
> +	pci_crash_buffer = s->buffer;
> +	pci_crash_buffer_size = s->buffer_size;
> +	pci_crash_pagemap_phys = s->pagemap_phys;
> +
> +	cflags = READ_ONCE(capture_flags);
> +	if (!(cflags & PCI_CRASH_CAPTURE_ALWAYS)) {
> +		if (!(cflags & PCI_CRASH_CAPTURE_AER)) {
> +			/* Neither 'always' nor a usable 'aer' mode -- skip. */
> +			goto out;
> +		}
> +		if (!pci_crash_aer_error_present(s)) {
> +			pr_info("no PCI errors detected, skipping capture\n");
> +			goto out;
> +		}
> +	}
> +
> +	pci_crash_fill_buffer(s);
> +
> +	/*
> +	 * Flush buffer and pagemap from CPU cache to RAM so the
> +	 * crash kernel sees our writes after kexec.
> +	 */
> +	pci_crash_flush_dcache(s->buffer, s->buffer_size);
> +	if (s->pagemap && s->pagemap_size > 0)
> +		pci_crash_flush_dcache(s->pagemap, s->pagemap_size);
> +
> +	pr_info("CAPTURE: %u devices, %zu bytes\n",
> +		s->num_devs, s->buffer_size);
> +out:
> +	rcu_read_unlock();
> +}
> +EXPORT_SYMBOL_GPL(pci_crash_save);
> +
> +static void pci_crash_rebuild_worker(struct work_struct *work)
> +{
> +	mutex_lock(&pci_crash_lock);
> +	pci_crash_rebuild_snapshot();
> +	mutex_unlock(&pci_crash_lock);
> +}
> +
> +static int pci_crash_bus_notifier(struct notifier_block *nb,
> +				  unsigned long action, void *data)
> +{
> +	if (action == BUS_NOTIFY_ADD_DEVICE ||
> +	    action == BUS_NOTIFY_DEL_DEVICE)
> +		mod_delayed_work(system_wq, &pci_crash_rebuild_dwork,
> +				 msecs_to_jiffies(PCI_CRASH_REBUILD_DELAY_MS));
> +
> +	return NOTIFY_OK;
> +}
> +
> +static struct notifier_block pci_crash_bus_nb = {
> +	.notifier_call = pci_crash_bus_notifier,
> +};
> +
> +static int __init pci_crash_init(void)
> +{
> +	/*
> +	 * The on-wire buffer/pagemap layout is shared with userspace vmcore
> +	 * parsers, which hardcode these sizes.  Catch any struct drift at
> +	 * build time.
> +	 */
> +	BUILD_BUG_ON(sizeof(struct pci_crash_buffer_header) != 32);
> +	BUILD_BUG_ON(sizeof(struct pci_crash_device_record) != 8);
> +	BUILD_BUG_ON(offsetof(struct pci_crash_pagemap, addrs) != 20);
> +
> +	/* Nothing to do in crash kernel -- the buffer from the first kernel
> +	 * is already in RAM (flushed before kexec) and the parser finds it
> +	 * via the pagemap in VMCOREINFO.
> +	 */
> +	if (is_kdump_kernel())
> +		return 0;
> +
> +	INIT_DELAYED_WORK(&pci_crash_rebuild_dwork, pci_crash_rebuild_worker);
> +
> +	pci_crash_parse_capture();
> +	pci_crash_parse_devices();
> +
> +	/*
> +	 * Register the hotplug notifier BEFORE the initial snapshot so no
> +	 * ADD/DEL event in the startup window is missed.  The notifier only
> +	 * schedules the debounced rebuild worker, which serializes on
> +	 * pci_crash_lock behind this initial rebuild.
> +	 */
> +	bus_register_notifier(&pci_bus_type, &pci_crash_bus_nb);
> +
> +	mutex_lock(&pci_crash_lock);
> +	pci_crash_rebuild_snapshot();
> +	mutex_unlock(&pci_crash_lock);
> +
> +	WRITE_ONCE(pci_crash_ready, true);
> +
> +#ifndef CONFIG_PCIEAER
> +	if ((capture_flags & PCI_CRASH_CAPTURE_AER) &&
> +	    !(capture_flags & PCI_CRASH_CAPTURE_ALWAYS))
> +		pr_warn("capture=aer but CONFIG_PCIEAER=n; capture will not trigger unless set to 'always'\n");
> +#endif
> +
> +	rcu_read_lock();
> +	{
> +		struct pci_crash_snapshot *s = rcu_dereference(pci_crash_snap);
> +
> +		pr_info("ready: %u devices (%zu bytes), capture=%s devices=%s\n",
> +			s ? s->num_devs : 0, s ? s->buffer_size : 0,
> +			capture, devices);
> +	}
> +	rcu_read_unlock();
> +
> +	return 0;
> +}
> +late_initcall(pci_crash_init);
> +
> +/* Built-in only: crash infrastructure must outlive all drivers. */
> +MODULE_LICENSE("GPL");
> +MODULE_DESCRIPTION("Capture PCI config space at panic time for crash analysis");
> +MODULE_AUTHOR("Amazon.com, Inc.");
> diff --git a/kernel/vmcore_info.c b/kernel/vmcore_info.c
> index 8614430ca212..8813f7e2e516 100644
> --- a/kernel/vmcore_info.c
> +++ b/kernel/vmcore_info.c
> @@ -14,6 +14,7 @@
>  #include <linux/cpuhotplug.h>
>  #include <linux/memblock.h>
>  #include <linux/kmemleak.h>
> +#include <linux/pci_crash.h>
>  
>  #include <asm/page.h>
>  #include <asm/sections.h>
> @@ -91,6 +92,18 @@ void crash_save_vmcoreinfo(void)
>  		vmcoreinfo_data = vmcoreinfo_data_safecopy;
>  
>  	vmcoreinfo_append_str("CRASHTIME=%lld\n", ktime_get_real_seconds());
> +
> +	/* Capture PCI config space before kexec into crash kernel */
> +	pci_crash_save();
> +	if (pci_crash_pagemap_phys && pci_crash_buffer_size > 0) {
> +		vmcoreinfo_append_str("PCI_CRASH_PAGEMAP=0x%llx\n",
> +				      (unsigned long long)pci_crash_pagemap_phys);
> +		vmcoreinfo_append_str("PCI_CRASH_VERSION=%d\n",
> +				      PCI_CRASH_VERSION);
> +		vmcoreinfo_append_str("PCI_CRASH_BUF_SZ=%zu\n",
> +				      pci_crash_buffer_size);
> +	}
> +
>  	update_vmcoreinfo_note();
>  }
>  
> 

-- 
 i.

      reply	other threads:[~2026-08-26 17:45 UTC|newest]

Thread overview: 2+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-07-23 18:49 hangej
2026-08-26 17:44 ` Ilpo Järvinen [this message]

Reply instructions:

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

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

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

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

  git send-email \
    --in-reply-to=5188f7e8-4d19-685b-49e3-f2249685d9a3@linux.intel.com \
    --to=ilpo.jarvinen@linux.intel.com \
    --cc=bhelgaas@google.com \
    --cc=dwmw2@infradead.org \
    --cc=hangej@amazon.com \
    --cc=kexec@lists.infradead.org \
    --cc=linux-kernel@vger.kernel.org \
    --cc=linux-pci@vger.kernel.org \
    --cc=nh-open-source@amazon.com \
    /path/to/YOUR_REPLY

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

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

all inboxes | Powered by JetHome®