git: 90bee82638c3 - main - igc: Correct Wake-on-LAN filter programming
- Go to: [ bottom of page ] [ top of archives ] [ this month ]
Date: Sun, 30 Aug 2026 13:05:02 UTC
The branch main has been updated by kbowling:
URL: https://cgit.FreeBSD.org/src/commit/?id=90bee82638c3f277f729aa5321aacd5d7333d512
commit 90bee82638c3f277f729aa5321aacd5d7333d512
Author: Kevin Bowling <kbowling@FreeBSD.org>
AuthorDate: 2026-08-30 06:06:02 +0000
Commit: Kevin Bowling <kbowling@FreeBSD.org>
CommitDate: 2026-08-30 12:56:31 +0000
igc: Correct Wake-on-LAN filter programming
The attach path translated WUC.APME into a saved link-change filter,
then advertised magic-packet wake. Suspend removed unselected magic,
unicast, and multicast bits from that saved value, commonly leaving no
hardware wake filter at all. The destructive masking also made later
capability changes ineffective.
Advertise the I225/I226 wake filters whenever PCI power management is
available and enable magic-packet wake by default. Build a fresh WUFC
mask for every suspend, and explicitly clear WUC, WUFC, and PCI PME when
wake is disabled.
Require the PCI power-management capability to report D3hot PME support
before advertising or arming wake. A PM capability alone does not mean
the function can signal PME from the state used during system sleep.
Reconstruct RAR0, the multicast table, and the receive filter after the
stop-time reset so unicast and multicast wake use the current interface
state. Keep the PHY powered while wake is armed, drain pending PCIe
transactions, and disable bus mastering before D3.
Clear the sticky base and extended wake status before arming filters.
On resume, report the saved hardware wake cause, clear the device wake
source, and then clear PCI PME. Run the DMA-fencing sequence even when
wake programming fails, while allowing shutdown to continue after
logging the failure. Explicitly restore PCI bus mastering during
initialization so an iflib-local resume after another child rejects
suspend can restart the device.
Restore the Intel shared code PHY power down test from DPDK. The
FreeBSD split inverted the reset-block condition, so its dormant hook
would power down only when firmware explicitly vetoed the operation.
Do not copy the generic legacy management test verbatim. The I225 and
I226 define MANC bits 0 and 1 as flow-control and NC-SI discard
controls, not SMBus and ASF enable bits. Preserve the link when the
documented TCO receive path is enabled; the shared reset-block test
separately honors the firmware keep-link-up veto. This follows section
8.21.1 of the Foxville Software User Manual.
Evaluate management pass-through at each suspend. When neither a host
wake filter nor management requires the link, invoke the shared-code
power-down hook before D3. Leave a management-owned link untouched and
restore a link previously powered down by the driver without another
PHY reset. This follows DPDK's stop/start pairing without changing
ordinary ifconfig down behavior.
The implementation was checked against the Intel I225/I226 programming
model, the Intel Linux igc lifecycle, and DPDK.
On an I225-LM, a device-only D3 test observed PME for a valid magic
packet and no PME with every host wake filter disabled. Both cases
resumed to D0 with the link and configured addresses operational.
The I225 system also completed a full ACPI S3 cycle and resumed with
link, configured addresses, and traffic operational.
On an I226-V, a device-only D3 test changed PMCSR accordingly after a
magic packet. With dev.igc.0.wake enabled, a full ACPI S3 cycle
remained asleep until a delayed magic packet and resumed with link,
addresses, and traffic operational.
PR: 282140
Obtained from: DPDK (shared-code power-down structure)
MFC after: 2 weeks
---
share/man/man4/igc.4 | 24 +++++-
sys/dev/igc/if_igc.c | 209 ++++++++++++++++++++++++++++++++++------------
sys/dev/igc/if_igc.h | 4 +-
sys/dev/igc/igc_base.c | 7 +-
sys/dev/igc/igc_defines.h | 7 +-
sys/dev/igc/igc_mac.c | 20 +++++
sys/dev/igc/igc_mac.h | 1 +
sys/dev/igc/igc_regs.h | 2 +-
8 files changed, 204 insertions(+), 70 deletions(-)
diff --git a/share/man/man4/igc.4 b/share/man/man4/igc.4
index 1fe87421738d..b7c3637ae7c0 100644
--- a/share/man/man4/igc.4
+++ b/share/man/man4/igc.4
@@ -3,7 +3,7 @@
.\" Copyright 2021 Rubicon Communications, LLC (Netgate)
.\" SPDX-License-Identifier: BSD-3-Clause
.\"
-.Dd August 11, 2026
+.Dd August 30, 2026
.Dt IGC 4
.Os
.Sh NAME
@@ -32,6 +32,24 @@ On Motherboard) based on the Intel I225 Multi Gigabit Controller.
The driver supports Transmit/Receive checksum offload, Jumbo Frames,
MSI/MSI-X, TSO, and RSS.
.Pp
+On systems that expose PCI power-management wake, the driver supports the
+.Cm wol_magic ,
+.Cm wol_ucast ,
+and
+.Cm wol_mcast
+capabilities shown by
+.Xr ifconfig 8 .
+Magic-packet wake is enabled by default and any advertised wake mode can be
+enabled or disabled with
+.Xr ifconfig 8 .
+Waking the system from an ACPI sleep state also requires enabling the
+corresponding device wake source when it is exposed, for example:
+.Bd -literal -offset indent
+sysctl dev.igc.0.wake=1
+.Ed
+The interface wake capabilities select the controller packet filters; they do
+not enable the ACPI wake source.
+.Pp
Support for Jumbo Frames is provided via the interface MTU setting.
Selecting an MTU larger than 1500 bytes with the
.Xr ifconfig 8
@@ -154,8 +172,8 @@ A fatal initialization error has occurred.
.It "igc%d: Unable to allocate bus resource: memory"
A fatal initialization error has occurred.
.It "igc%d: Invalid MAC address"
-The MAC address programmed into the EEPROM is either empty or a multicast/broadcast
-address.
+The MAC address programmed into the EEPROM is either empty or a multicast or
+broadcast address.
.El
.Sh SEE ALSO
.Xr altq 4 ,
diff --git a/sys/dev/igc/if_igc.c b/sys/dev/igc/if_igc.c
index 0ac2d2a7cc20..b07548821172 100644
--- a/sys/dev/igc/if_igc.c
+++ b/sys/dev/igc/if_igc.c
@@ -162,8 +162,10 @@ static int igc_sysctl_tso_tcp_flags_mask(SYSCTL_HANDLER_ARGS);
/* Management and WOL Support */
static void igc_get_hw_control(struct igc_softc *);
static void igc_release_hw_control(struct igc_softc *);
-static void igc_get_wakeup(if_ctx_t);
-static void igc_enable_wakeup(if_ctx_t);
+static int igc_enable_pci_busmaster(struct igc_softc *);
+static void igc_configure_wakeup(if_ctx_t);
+static int igc_enable_wakeup(if_ctx_t);
+static void igc_power_up_wakeup_link(struct igc_softc *);
int igc_intr(void *);
@@ -719,12 +721,7 @@ igc_if_attach_pre(if_ctx_t ctx)
/*
* Get Wake-on-Lan and Management info for later use
*/
- igc_get_wakeup(ctx);
-
- /* Enable only WOL MAGIC by default */
- scctx->isc_capenable &= ~IFCAP_WOL;
- if (sc->wol != 0)
- scctx->isc_capenable |= IFCAP_WOL_MAGIC;
+ igc_configure_wakeup(ctx);
iflib_set_mac(ctx, hw->mac.addr);
@@ -801,7 +798,13 @@ igc_if_detach(if_ctx_t ctx)
static int
igc_if_shutdown(if_ctx_t ctx)
{
- return igc_if_suspend(ctx);
+ int error;
+
+ error = igc_if_suspend(ctx);
+ if (error != 0)
+ device_printf(iflib_get_dev(ctx),
+ "Wake configuration failed during shutdown: %d\n", error);
+ return (0);
}
/*
@@ -811,22 +814,38 @@ static int
igc_if_suspend(if_ctx_t ctx)
{
struct igc_softc *sc = iflib_get_softc(ctx);
+ int error;
+ error = igc_enable_wakeup(ctx);
igc_release_hw_control(sc);
- igc_enable_wakeup(ctx);
- return (0);
+ return (error);
}
static int
igc_if_resume(if_ctx_t ctx)
{
+ struct igc_softc *sc = iflib_get_softc(ctx);
+ u32 wus, wus_ext;
+
/*
* PCIe config space, and with it L1.2, may have been reset
* across the suspend/resume cycle.
*/
igc_disable_broken_l1_2(ctx);
+ wus = IGC_READ_REG(&sc->hw, IGC_WUS);
+ wus_ext = IGC_READ_REG(&sc->hw, IGC_WUS_EXT);
+ if (wus != 0 || wus_ext != 0)
+ device_printf(sc->dev,
+ "Wakeup status: %#010x, extended %#010x\n", wus, wus_ext);
+ IGC_WRITE_REG(&sc->hw, IGC_WUFC, 0);
+ IGC_WRITE_REG(&sc->hw, IGC_WUFC_EXT, 0);
+ IGC_WRITE_REG(&sc->hw, IGC_WUC, 0);
+ IGC_WRITE_REG(&sc->hw, IGC_WUS, ~0U);
+ IGC_WRITE_REG(&sc->hw, IGC_WUS_EXT, ~0U);
+ /* Clear PME after its MAC wake source has been removed. */
+ pci_clear_pme(sc->dev);
- return(0);
+ return (0);
}
static int
@@ -870,6 +889,14 @@ igc_if_init(if_ctx_t ctx)
INIT_DEBUGOUT("igc_if_init: begin");
+ if (igc_enable_pci_busmaster(sc) != 0) {
+ device_printf(sc->dev, "Could not enable PCI bus mastering\n");
+ iflib_init_failed(ctx);
+ return;
+ }
+ if (sc->suspend_link_powered_down)
+ igc_power_up_wakeup_link(sc);
+
/* Get the latest mac address, User can use a LAA */
bcopy(if_getlladdr(ifp), sc->hw.mac.addr,
ETHER_ADDR_LEN);
@@ -2836,6 +2863,29 @@ igc_release_hw_control(struct igc_softc *sc)
return;
}
+static int
+igc_enable_pci_busmaster(struct igc_softc *sc)
+{
+ device_t dev;
+ u16 command;
+ int error;
+
+ dev = sc->dev;
+ command = pci_read_config(dev, PCIR_COMMAND, 2);
+ if (command == 0xffff)
+ return (ENXIO);
+ if ((command & PCIM_CMD_BUSMASTEREN) != 0)
+ return (0);
+
+ error = pci_enable_busmaster(dev);
+ command = pci_read_config(dev, PCIR_COMMAND, 2);
+ if (command == 0xffff)
+ return (ENXIO);
+ if ((command & PCIM_CMD_BUSMASTEREN) == 0)
+ return (error != 0 ? error : EIO);
+ return (0);
+}
+
static int
igc_is_valid_ether_addr(u8 *addr)
{
@@ -2848,75 +2898,124 @@ igc_is_valid_ether_addr(u8 *addr)
return (true);
}
-/*
-** Parse the interface capabilities with regard
-** to both system management and wake-on-lan for
-** later use.
-*/
+/* Advertise the wake modes supported by I225/I226 physical functions. */
static void
-igc_get_wakeup(if_ctx_t ctx)
+igc_configure_wakeup(if_ctx_t ctx)
{
- struct igc_softc *sc = iflib_get_softc(ctx);
- u16 eeprom_data = 0, apme_mask;
-
- apme_mask = IGC_WUC_APME;
- eeprom_data = IGC_READ_REG(&sc->hw, IGC_WUC);
+ if_softc_ctx_t scctx = iflib_get_softc_ctx(ctx);
+ device_t dev = iflib_get_dev(ctx);
+ int capabilities;
- if (eeprom_data & apme_mask)
- sc->wol = IGC_WUFC_LNKC;
+ capabilities = pci_has_pme(dev, PCI_POWERSTATE_D3_HOT) ?
+ IFCAP_WOL : 0;
+ scctx->isc_capabilities &= ~IFCAP_WOL;
+ scctx->isc_capabilities |= capabilities;
+ scctx->isc_capenable &= ~IFCAP_WOL;
+ if (capabilities != 0)
+ scctx->isc_capenable |= IFCAP_WOL_MAGIC;
}
-
-/*
- * Enable PCI Wake On Lan capability
- */
-static void
+/* Configure the requested PCI Wake-on-LAN filters for suspend. */
+static int
igc_enable_wakeup(if_ctx_t ctx)
{
struct igc_softc *sc = iflib_get_softc(ctx);
device_t dev = iflib_get_dev(ctx);
if_t ifp = iflib_get_ifp(ctx);
- int error = 0;
- u32 ctrl, rctl;
-
- if (!pci_has_pm(dev))
- return;
+ int enabled, error = 0, master_error, mcnt;
+ u32 ctrl, rctl, wufc;
+ bool manage;
- /*
- * Determine type of Wakeup: note that wol
- * is set with all bits on by default.
- */
- if ((if_getcapenable(ifp) & IFCAP_WOL_MAGIC) == 0)
- sc->wol &= ~IGC_WUFC_MAG;
-
- if ((if_getcapenable(ifp) & IFCAP_WOL_UCAST) == 0)
- sc->wol &= ~IGC_WUFC_EX;
+ if (!pci_has_pme(dev, PCI_POWERSTATE_D3_HOT))
+ return (0);
- if ((if_getcapenable(ifp) & IFCAP_WOL_MCAST) == 0)
- sc->wol &= ~IGC_WUFC_MC;
- else {
- rctl = IGC_READ_REG(&sc->hw, IGC_RCTL);
- rctl |= IGC_RCTL_MPE;
- IGC_WRITE_REG(&sc->hw, IGC_RCTL, rctl);
+ enabled = if_getcapenable(ifp) & if_getcapabilities(ifp) & IFCAP_WOL;
+ manage = igc_enable_mng_pass_thru(&sc->hw);
+ wufc = 0;
+ if ((enabled & IFCAP_WOL_MAGIC) != 0)
+ wufc |= IGC_WUFC_MAG;
+ if ((enabled & IFCAP_WOL_UCAST) != 0)
+ wufc |= IGC_WUFC_EX;
+ if ((enabled & IFCAP_WOL_MCAST) != 0) {
+ wufc |= IGC_WUFC_MC;
+ bzero(sc->mta, ETHER_ADDR_LEN *
+ MAX_NUM_MULTICAST_ADDRESSES);
+ mcnt = if_foreach_llmaddr(ifp, igc_copy_maddr, sc->mta);
+ if (mcnt < MAX_NUM_MULTICAST_ADDRESSES)
+ igc_update_mc_addr_list(&sc->hw, sc->mta, mcnt);
+ }
+
+ IGC_WRITE_REG(&sc->hw, IGC_WUFC, 0);
+ IGC_WRITE_REG(&sc->hw, IGC_WUFC_EXT, 0);
+ IGC_WRITE_REG(&sc->hw, IGC_WUC, 0);
+ IGC_WRITE_REG(&sc->hw, IGC_WUS, ~0U);
+ IGC_WRITE_REG(&sc->hw, IGC_WUS_EXT, ~0U);
+
+ if (wufc == 0) {
+ if (manage) {
+ if (sc->suspend_link_powered_down)
+ igc_power_up_wakeup_link(sc);
+ pci_enable_pme(dev);
+ } else {
+ igc_power_down_phy(&sc->hw);
+ sc->suspend_link_powered_down = true;
+ pci_clear_pme(dev);
+ }
+ goto master_disable;
}
-
- if (!(sc->wol & (IGC_WUFC_EX | IGC_WUFC_MAG | IGC_WUFC_MC)))
+ bcopy(if_getlladdr(ifp), sc->hw.mac.addr, ETHER_ADDR_LEN);
+ error = igc_rar_set(&sc->hw, sc->hw.mac.addr, 0);
+ if (error != IGC_SUCCESS) {
+ device_printf(dev,
+ "Could not restore unicast wake address: %d\n", error);
goto pme;
+ }
+ rctl = IGC_READ_REG(&sc->hw, IGC_RCTL);
+ rctl &= ~(IGC_RCTL_UPE | IGC_RCTL_MPE | IGC_RCTL_MO_3);
+ rctl |= IGC_RCTL_EN | IGC_RCTL_BAM |
+ (sc->hw.mac.mc_filter_type << IGC_RCTL_MO_SHIFT);
+ if ((wufc & IGC_WUFC_MC) != 0)
+ rctl |= IGC_RCTL_MPE;
+ IGC_WRITE_REG(&sc->hw, IGC_RCTL, rctl);
/* Advertise the wakeup capability */
ctrl = IGC_READ_REG(&sc->hw, IGC_CTRL);
ctrl |= IGC_CTRL_ADVD3WUC;
IGC_WRITE_REG(&sc->hw, IGC_CTRL, ctrl);
+ igc_power_up_wakeup_link(sc);
/* Enable wakeup by the MAC */
IGC_WRITE_REG(&sc->hw, IGC_WUC, IGC_WUC_PME_EN);
- IGC_WRITE_REG(&sc->hw, IGC_WUFC, sc->wol);
+ IGC_WRITE_REG(&sc->hw, IGC_WUFC, wufc);
pme:
- if (!error && (if_getcapenable(ifp) & IFCAP_WOL))
+ if (error == IGC_SUCCESS)
pci_enable_pme(dev);
+ else {
+ IGC_WRITE_REG(&sc->hw, IGC_WUFC, 0);
+ IGC_WRITE_REG(&sc->hw, IGC_WUFC_EXT, 0);
+ IGC_WRITE_REG(&sc->hw, IGC_WUC, 0);
+ pci_clear_pme(dev);
+ }
- return;
+master_disable:
+ master_error = igc_disable_pcie_master(&sc->hw);
+ if (master_error != IGC_SUCCESS)
+ device_printf(dev, "PCIe master disable timed out: %d\n",
+ master_error);
+ master_error = pci_disable_busmaster(dev);
+ if (master_error != 0)
+ device_printf(dev, "PCI bus-master disable failed: %d\n",
+ master_error);
+
+ return (error == IGC_SUCCESS ? 0 : EIO);
+}
+
+static void
+igc_power_up_wakeup_link(struct igc_softc *sc)
+{
+ igc_power_up_phy(&sc->hw);
+ sc->suspend_link_powered_down = false;
}
/**********************************************************************
diff --git a/sys/dev/igc/if_igc.h b/sys/dev/igc/if_igc.h
index 3457f5e477ff..5b728b6b1193 100644
--- a/sys/dev/igc/if_igc.h
+++ b/sys/dev/igc/if_igc.h
@@ -380,9 +380,7 @@ struct igc_softc {
u32 rx_mbuf_sz;
int enable_aim;
-
- /* Management and WOL features */
- u32 wol;
+ bool suspend_link_powered_down;
/* Multicast array memory */
u8 *mta;
diff --git a/sys/dev/igc/igc_base.c b/sys/dev/igc/igc_base.c
index 510f4c132b9a..cc8b4afbacd1 100644
--- a/sys/dev/igc/igc_base.c
+++ b/sys/dev/igc/igc_base.c
@@ -100,11 +100,10 @@ void igc_power_down_phy_copper_base(struct igc_hw *hw)
if (!(phy->ops.check_reset_block))
return;
- /* If the management interface is not enabled, then power down */
- if (phy->ops.check_reset_block(hw))
+ /* If the management interface is not enabled, then power down. */
+ if (!(igc_enable_mng_pass_thru(hw) ||
+ phy->ops.check_reset_block(hw)))
igc_power_down_phy_copper(hw);
-
- return;
}
/**
diff --git a/sys/dev/igc/igc_defines.h b/sys/dev/igc/igc_defines.h
index 1856c86b91a8..55d0cf8918c5 100644
--- a/sys/dev/igc/igc_defines.h
+++ b/sys/dev/igc/igc_defines.h
@@ -124,16 +124,15 @@
#define IGC_RXDPS_HDRSTAT_HDRSP 0x00008000
/* Management Control */
-#define IGC_MANC_SMBUS_EN 0x00000001 /* SMBus Enabled - RO */
-#define IGC_MANC_ASF_EN 0x00000002 /* ASF Enabled - RO */
+#define IGC_MANC_FC_DISCARD 0x00000001 /* Discard flow control */
+#define IGC_MANC_NCSI_DISCARD 0x00000002 /* Discard NC-SI */
#define IGC_MANC_ARP_EN 0x00002000 /* Enable ARP Request Filtering */
#define IGC_MANC_RCV_TCO_EN 0x00020000 /* Receive TCO Packets Enabled */
-#define IGC_MANC_BLK_PHY_RST_ON_IDE 0x00040000 /* Block phy resets */
+#define IGC_MANC_BLK_PHY_RST_ON_IDE 0x00040000 /* Keep PHY link up */
/* Enable MAC address filtering */
#define IGC_MANC_EN_MAC_ADDR_FILTER 0x00100000
/* Enable MNG packets to host memory */
#define IGC_MANC_EN_MNG2HOST 0x00200000
-
#define IGC_MANC2H_PORT_623 0x00000020 /* Port 0x26f */
#define IGC_MANC2H_PORT_664 0x00000040 /* Port 0x298 */
#define IGC_MDEF_PORT_623 0x00000800 /* Port 0x26f */
diff --git a/sys/dev/igc/igc_mac.c b/sys/dev/igc/igc_mac.c
index add6a7327ab6..722fb4e6864e 100644
--- a/sys/dev/igc/igc_mac.c
+++ b/sys/dev/igc/igc_mac.c
@@ -69,6 +69,26 @@ bool igc_null_mng_mode(struct igc_hw IGC_UNUSEDARG *hw)
return false;
}
+/**
+ * igc_enable_mng_pass_thru - Check if management passthrough is needed
+ * @hw: pointer to the HW structure
+ *
+ * Verify that the I225/I226 management engine needs the external link.
+ **/
+bool
+igc_enable_mng_pass_thru(struct igc_hw *hw)
+{
+ u32 manc;
+
+ DEBUGFUNC("igc_enable_mng_pass_thru");
+
+ if (!hw->mac.asf_firmware_present)
+ return (false);
+
+ manc = IGC_READ_REG(hw, IGC_MANC);
+ return ((manc & IGC_MANC_RCV_TCO_EN) != 0);
+}
+
/**
* igc_null_update_mc - No-op function, return void
* @hw: pointer to the HW structure
diff --git a/sys/dev/igc/igc_mac.h b/sys/dev/igc/igc_mac.h
index 8349a45188b4..fac3712eac3a 100644
--- a/sys/dev/igc/igc_mac.h
+++ b/sys/dev/igc/igc_mac.h
@@ -12,6 +12,7 @@ void igc_null_mac_generic(struct igc_hw *hw);
s32 igc_null_ops_generic(struct igc_hw *hw);
s32 igc_null_link_info(struct igc_hw *hw, u16 *s, u16 *d);
bool igc_null_mng_mode(struct igc_hw *hw);
+bool igc_enable_mng_pass_thru(struct igc_hw *hw);
void igc_null_update_mc(struct igc_hw *hw, u8 *h, u32 a);
void igc_null_write_vfta(struct igc_hw *hw, u32 a, u32 b);
int igc_null_rar_set(struct igc_hw *hw, u8 *h, u32 a);
diff --git a/sys/dev/igc/igc_regs.h b/sys/dev/igc/igc_regs.h
index 849b13b2adf2..0cc31eb158e5 100644
--- a/sys/dev/igc/igc_regs.h
+++ b/sys/dev/igc/igc_regs.h
@@ -231,7 +231,7 @@
#define IGC_VFTA 0x05600 /* VLAN Filter Table Array - RW Array */
#define IGC_WUC 0x05800 /* Wakeup Control - RW */
#define IGC_WUFC 0x05808 /* Wakeup Filter Control - RW */
-#define IGC_WUS 0x05810 /* Wakeup Status - RO */
+#define IGC_WUS 0x05810 /* Wakeup Status - RW1C */
/* Management registers */
#define IGC_MANC 0x05820 /* Management Control - RW */
#define IGC_IPAV 0x05838 /* IP Address Valid - RW */