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 c4e24e95c3)
This commit is contained in:
Kevin Bowling committed 2026-09-12 17:48:24 -07:00
1 parent 8e1ad850df
commit d831cb2ad3
5 files changed
+41 -19

No files matched your search

+1
View File
@@ -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 \
+18 -1
View File
@@ -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
+2 -18
View File
@@ -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
+19
View File
@@ -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.
*/
+1
View File
@@ -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,