git: c4e24e95c3f9 - main - pci: Expose PME support by power state

From: Kevin Bowling <kbowling_at_FreeBSD.org>
Date: Sun, 30 Aug 2026 12:44:55 UTC
The branch main has been updated by kbowling:

URL: https://cgit.FreeBSD.org/src/commit/?id=c4e24e95c3f905b817d4bfc6e93457eb297bfa4b

commit c4e24e95c3f905b817d4bfc6e93457eb297bfa4b
Author:     Kevin Bowling <kbowling@FreeBSD.org>
AuthorDate: 2026-08-30 08:18:10 +0000
Commit:     Kevin Bowling <kbowling@FreeBSD.org>
CommitDate: 2026-08-30 12:39:41 +0000

    pci: Expose PME support by power state
    
    The presence of the PCI power management capability does not imply that
    a function can signal PME# from every power state.  Drivers which
    advertise wake based only on pci_has_pm() can consequently expose wake
    modes that cannot work.
    
    Add pci_has_pme() to query the PME_Support bitmap for a specific state.
    Use it to implement LinuxKPI pci_pme_capable(), removing its duplicate
    PME_Support decoder.
    
    Validated the helper against PCI PMC capability values from 82571EB,
    82573L, 82579LM, I210, I225, and I226-V controllers.  The 82571 and
    82573 reported PMC 0xc822, while the I226-V reported 0xc823.  In both
    values, bits 15, 14, and 11 advertise PME from D3cold, D3hot, and D0;
    the low-bit difference is only the PM capability version.
    
    MFC after:      2 weeks
    Sponsored by:   BBOX.io
---
 share/man/man9/Makefile                        |  1 +
 share/man/man9/pci.9                           | 19 ++++++++++++++++++-
 sys/compat/linuxkpi/common/include/linux/pci.h | 20 ++------------------
 sys/dev/pci/pci.c                              | 19 +++++++++++++++++++
 sys/dev/pci/pcivar.h                           |  1 +
 5 files changed, 41 insertions(+), 19 deletions(-)

diff --git a/share/man/man9/Makefile b/share/man/man9/Makefile
index 54a121faca0f..392e35dd954a 100644
--- a/share/man/man9/Makefile
+++ b/share/man/man9/Makefile
@@ -1838,6 +1838,7 @@ MLINKS+=pci.9 is_pci_device.9 \
 	pci.9 pci_get_vpd_ident.9 \
 	pci.9 pci_get_vpd_readonly.9 \
 	pci.9 pci_has_pm.9 \
+	pci.9 pci_has_pme.9 \
 	pci.9 pci_iov_attach.9 \
 	pci.9 pci_iov_attach_name.9 \
 	pci.9 pci_iov_detach.9 \
diff --git a/share/man/man9/pci.9 b/share/man/man9/pci.9
index 65d1f0bc867b..f113120fa2dd 100644
--- a/share/man/man9/pci.9
+++ b/share/man/man9/pci.9
@@ -23,7 +23,7 @@
 .\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 .\" SUCH DAMAGE.
 .\"
-.Dd August 15, 2026
+.Dd August 30, 2026
 .Dt PCI 9
 .Os
 .Sh NAME
@@ -54,6 +54,7 @@
 .Nm pci_get_vpd_ident ,
 .Nm pci_get_vpd_readonly ,
 .Nm pci_has_pm ,
+.Nm pci_has_pme ,
 .Nm pci_iov_attach ,
 .Nm pci_iov_attach_name ,
 .Nm pci_iov_detach ,
@@ -134,6 +135,8 @@
 .Fn pci_get_vpd_readonly "device_t dev" "const char *kw" "const char **vptr"
 .Ft bool
 .Fn pci_has_pm "device_t dev"
+.Ft bool
+.Fn pci_has_pme "device_t dev" "int state"
 .Ft int
 .Fn pci_msi_count "device_t dev"
 .Ft int
@@ -391,6 +394,20 @@ function returns true if
 supports power management.
 .Pp
 The
+.Fn pci_has_pme
+function returns true if
+.Fa dev
+can generate a power management event from the PCI power state
+.Fa state .
+The state must be one of
+.Dv PCI_POWERSTATE_D0 ,
+.Dv PCI_POWERSTATE_D1 ,
+.Dv PCI_POWERSTATE_D2 ,
+.Dv PCI_POWERSTATE_D3_HOT ,
+or
+.Dv PCI_POWERSTATE_D3_COLD .
+.Pp
+The
 .Fn pci_find_extcap
 function is used to locate the first instance of a PCI-express
 extended capability register set for the device
diff --git a/sys/compat/linuxkpi/common/include/linux/pci.h b/sys/compat/linuxkpi/common/include/linux/pci.h
index 56b9c6e05f27..8cd52707a7bd 100644
--- a/sys/compat/linuxkpi/common/include/linux/pci.h
+++ b/sys/compat/linuxkpi/common/include/linux/pci.h
@@ -658,26 +658,10 @@ pci_find_ext_capability(struct pci_dev *pdev, int capid)
 	return (reg);
 }
 
-#define	PCIM_PCAP_PME_SHIFT	11
 static __inline bool
-pci_pme_capable(struct pci_dev *pdev, uint32_t flag)
+pci_pme_capable(struct pci_dev *pdev, pci_power_t state)
 {
-	struct pci_devinfo *dinfo;
-	pcicfgregs *cfg;
-
-	if (flag > (PCIM_PCAP_D3PME_COLD >> PCIM_PCAP_PME_SHIFT))
-		return (false);
-
-	dinfo = device_get_ivars(pdev->dev.bsddev);
-	cfg = &dinfo->cfg;
-
-	if (cfg->pp.pp_cap == 0)
-		return (false);
-
-	if ((cfg->pp.pp_cap & (1 << (PCIM_PCAP_PME_SHIFT + flag))) != 0)
-		return (true);
-
-	return (false);
+	return (pci_has_pme(pdev->dev.bsddev, state));
 }
 
 static inline int
diff --git a/sys/dev/pci/pci.c b/sys/dev/pci/pci.c
index 9472095bc058..3ed95ca2bf87 100644
--- a/sys/dev/pci/pci.c
+++ b/sys/dev/pci/pci.c
@@ -3030,6 +3030,25 @@ pci_has_pm(device_t dev)
 	return (cfg->pp.pp_location != 0);
 }
 
+bool
+pci_has_pme(device_t dev, int state)
+{
+	static const uint16_t pme_mask[PCI_POWERSTATE_COUNT] = {
+		[PCI_POWERSTATE_D0] = PCIM_PCAP_D0PME,
+		[PCI_POWERSTATE_D1] = PCIM_PCAP_D1PME,
+		[PCI_POWERSTATE_D2] = PCIM_PCAP_D2PME,
+		[PCI_POWERSTATE_D3_HOT] = PCIM_PCAP_D3PME_HOT,
+		[PCI_POWERSTATE_D3_COLD] = PCIM_PCAP_D3PME_COLD,
+	};
+	struct pci_devinfo *dinfo = device_get_ivars(dev);
+	pcicfgregs *cfg = &dinfo->cfg;
+
+	if (state < PCI_POWERSTATE_D0 || state > PCI_POWERSTATE_MAX)
+		return (false);
+	return (cfg->pp.pp_location != 0 &&
+	    (cfg->pp.pp_cap & pme_mask[state]) != 0);
+}
+
 /*
  * Some convenience functions for PCI device drivers.
  */
diff --git a/sys/dev/pci/pcivar.h b/sys/dev/pci/pcivar.h
index 19ba20a7d1f3..9eeff1be12b3 100644
--- a/sys/dev/pci/pcivar.h
+++ b/sys/dev/pci/pcivar.h
@@ -705,6 +705,7 @@ int	pci_power_reset(device_t dev);
 void	pci_clear_pme(device_t dev);
 void	pci_enable_pme(device_t dev);
 bool	pci_has_pm(device_t dev);
+bool	pci_has_pme(device_t dev, int state);
 uint32_t pcie_read_config(device_t dev, int reg, int width);
 void	pcie_write_config(device_t dev, int reg, uint32_t value, int width);
 uint32_t pcie_adjust_config(device_t dev, int reg, uint32_t mask,