git: d831cb2ad3d8 - stable/14 - pci: Expose PME support by power state

From: Kevin Bowling <kbowling_at_FreeBSD.org>
Date: Sun, 13 Sep 2026 00:48:46 UTC
The branch stable/14 has been updated by kbowling:

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

commit d831cb2ad3d8caa31b9338cf6a4c11dc88290255
Author:     Kevin Bowling <kbowling@FreeBSD.org>
AuthorDate: 2026-08-30 08:18:10 +0000
Commit:     Kevin Bowling <kbowling@FreeBSD.org>
CommitDate: 2026-09-13 00:48:24 +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.
    
    Sponsored by:   BBOX.io
    
    (cherry picked from commit c4e24e95c3f905b817d4bfc6e93457eb297bfa4b)
---
 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 a6f3f016c1b4..5d2efec76ed1 100644
--- a/share/man/man9/Makefile
+++ b/share/man/man9/Makefile
@@ -1810,6 +1810,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 9e45b37b7580..97a8c6a02cfb 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 247057f0fedf..d6b4ee846950 100644
--- a/sys/compat/linuxkpi/common/include/linux/pci.h
+++ b/sys/compat/linuxkpi/common/include/linux/pci.h
@@ -632,26 +632,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 becda495f92a..a6df0ff2ebc7 100644
--- a/sys/dev/pci/pci.c
+++ b/sys/dev/pci/pci.c
@@ -3002,6 +3002,25 @@ pci_has_pm(device_t dev)
 	return (cfg->pp.pp_cap != 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 7701b82cc688..b71814f79aed 100644
--- a/sys/dev/pci/pcivar.h
+++ b/sys/dev/pci/pcivar.h
@@ -691,6 +691,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,