git: c4e24e95c3f9 - main - pci: Expose PME support by power state
- Go to: [ bottom of page ] [ top of archives ] [ this month ]
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,