* [PATCH iwl-net 1/4] ice: fix timestamp interrupt configuration for E825C
2026-04-08 18:46 [PATCH iwl-net 0/4] ice: E825C missing PHY timestamp interrupt fixes Jacob Keller
@ 2026-04-08 18:46 ` Jacob Keller
2026-04-10 9:33 ` [Intel-wired-lan] " Petr Oros
2026-04-08 18:46 ` [PATCH iwl-net 2/4] ice: perform PHY soft reset for E825C ports at initialization Jacob Keller
` (2 subsequent siblings)
3 siblings, 1 reply; 9+ messages in thread
From: Jacob Keller @ 2026-04-08 18:46 UTC (permalink / raw)
To: Anthony Nguyen, Intel Wired LAN, netdev
Cc: Grzegorz Nitka, Timothy Miskell, Aleksandr Loktionov,
Jacob Keller
From: Grzegorz Nitka <grzegorz.nitka@intel.com>
The E825C ice_phy_cfg_intr_eth56g() function is responsible for programming
the PHY interrupt for a given port. This function writes to the
PHY_REG_TS_INT_CONFIG register of the port. The register is responsible for
configuring whether the port interrupt logic is enabled, as well as
programming the threshold of waiting timestamps that will trigger an
interrupt from this port.
This threshold value must not be programmed to zero while the interrupt is
enabled. Doing so puts the port in a misconfigured state where the PHY
timestamp interrupt for the quad of connected ports will become stuck.
This occurs, because a threshold of zero results in the timestamp interrupt
status for the port becoming stuck high. The four ports in the connected
quad have their timestamp status indicators muxed together. A new interrupt
cannot be generated until the timestamp status indicators return low for
all four ports.
Normally, the timestamp status for a port will clear once there are fewer
timestamps in that ports timestamp memory bank than the threshold. A
threshold of zero makes this impossible, so the timestamp status for the
port does not clear.
The ice driver never intentionally programs the threshold to zero, indeed
the driver always programs it to a value of 1, intending to get an
interrupt immediately as soon as even a single packet is waiting for a
timestamp.
However, there is a subtle flaw in the programming logic in the
ice_phy_cfg_intr_eth56g() function. Due to the way that the hardware
handles enabling the PHY interrupt. If the threshold value is modified at
the same time as the interrupt is enabled, the HW PHY state machine might
enable the interrupt before the new threshold value is actually updated.
This leaves a potential race condition caused by the hardware logic where
a PHY timestamp interrupt might be triggered before the non-zero threshold
is written, resulting in the PHY timestamp logic becoming stuck.
Once the PHY timestamp status is stuck high, it will remain stuck even
after attempting to reprogram the PHY block by changing its threshold or
disabling the interrupt. Even a typical PF or CORE reset will not reset the
particular block of the PHY that becomes stuck. Even a warm power cycle is
not guaranteed to cause the PHY block to reset, and a cold power cycle is
required.
Prevent this by always writing the PHY_REG_TS_INT_CONFIG in two stages.
First write the threshold value with the interrupt disabled, and only write
the enable bit after the threshold has been programmed. When disabling the
interrupt, leave the threshold unchanged. Additionally, re-read the
register after writing it to guarantee that the write to the PHY has been
flushed upon exit of the function.
While we're modifying this function implementation, explicitly reject
programming a threshold of 0 when enabling the interrupt. No caller does
this today, but the consequences of doing so are significant. An explicit
rejection in the code makes this clear.
Fixes: 7cab44f1c35f ("ice: Introduce ETH56G PHY model for E825C products")
Signed-off-by: Grzegorz Nitka <grzegorz.nitka@intel.com>
Signed-off-by: Jacob Keller <jacob.e.keller@intel.com>
Reviewed-by: Aleksandr Loktionov <aleksandr.loktionov@intel.com>
---
drivers/net/ethernet/intel/ice/ice_ptp_hw.c | 36 +++++++++++++++++++++++++----
1 file changed, 32 insertions(+), 4 deletions(-)
diff --git a/drivers/net/ethernet/intel/ice/ice_ptp_hw.c b/drivers/net/ethernet/intel/ice/ice_ptp_hw.c
index e3db252c3918..67775beb9449 100644
--- a/drivers/net/ethernet/intel/ice/ice_ptp_hw.c
+++ b/drivers/net/ethernet/intel/ice/ice_ptp_hw.c
@@ -1847,6 +1847,8 @@ static int ice_phy_cfg_mac_eth56g(struct ice_hw *hw, u8 port)
* @ena: enable or disable interrupt
* @threshold: interrupt threshold
*
+ * The threshold cannot be 0 while the interrupt is enabled.
+ *
* Configure TX timestamp interrupt for the specified port
*
* Return:
@@ -1858,19 +1860,45 @@ int ice_phy_cfg_intr_eth56g(struct ice_hw *hw, u8 port, bool ena, u8 threshold)
int err;
u32 val;
+ if (ena && !threshold)
+ return -EINVAL;
+
err = ice_read_ptp_reg_eth56g(hw, port, PHY_REG_TS_INT_CONFIG, &val);
if (err)
return err;
+ val &= ~PHY_TS_INT_CONFIG_ENA_M;
if (ena) {
- val |= PHY_TS_INT_CONFIG_ENA_M;
val &= ~PHY_TS_INT_CONFIG_THRESHOLD_M;
val |= FIELD_PREP(PHY_TS_INT_CONFIG_THRESHOLD_M, threshold);
- } else {
- val &= ~PHY_TS_INT_CONFIG_ENA_M;
+ err = ice_write_ptp_reg_eth56g(hw, port, PHY_REG_TS_INT_CONFIG,
+ val);
+ if (err) {
+ ice_debug(hw, ICE_DBG_PTP,
+ "Failed to update 'threshold' PHY_REG_TS_INT_CONFIG port=%u ena=%u threshold=%u\n",
+ port, !!ena, threshold);
+ return err;
+ }
+ val |= PHY_TS_INT_CONFIG_ENA_M;
}
- return ice_write_ptp_reg_eth56g(hw, port, PHY_REG_TS_INT_CONFIG, val);
+ err = ice_write_ptp_reg_eth56g(hw, port, PHY_REG_TS_INT_CONFIG, val);
+ if (err) {
+ ice_debug(hw, ICE_DBG_PTP,
+ "Failed to update 'ena' PHY_REG_TS_INT_CONFIG port=%u ena=%u threshold=%u\n",
+ port, !!ena, threshold);
+ return err;
+ }
+
+ err = ice_read_ptp_reg_eth56g(hw, port, PHY_REG_TS_INT_CONFIG, &val);
+ if (err) {
+ ice_debug(hw, ICE_DBG_PTP,
+ "Failed to read PHY_REG_TS_INT_CONFIG port=%u ena=%u threshold=%u\n",
+ port, !!ena, threshold);
+ return err;
+ }
+
+ return 0;
}
/**
--
2.53.0.1066.g1eceb487f285
^ permalink raw reply related [flat|nested] 9+ messages in thread* Re: [Intel-wired-lan] [PATCH iwl-net 1/4] ice: fix timestamp interrupt configuration for E825C
2026-04-08 18:46 ` [PATCH iwl-net 1/4] ice: fix timestamp interrupt configuration for E825C Jacob Keller
@ 2026-04-10 9:33 ` Petr Oros
0 siblings, 0 replies; 9+ messages in thread
From: Petr Oros @ 2026-04-10 9:33 UTC (permalink / raw)
To: Jacob Keller, Anthony Nguyen, Intel Wired LAN, netdev
Cc: Aleksandr Loktionov, Timothy Miskell
On 4/8/26 20:46, Jacob Keller wrote:
> From: Grzegorz Nitka <grzegorz.nitka@intel.com>
>
> The E825C ice_phy_cfg_intr_eth56g() function is responsible for programming
> the PHY interrupt for a given port. This function writes to the
> PHY_REG_TS_INT_CONFIG register of the port. The register is responsible for
> configuring whether the port interrupt logic is enabled, as well as
> programming the threshold of waiting timestamps that will trigger an
> interrupt from this port.
>
> This threshold value must not be programmed to zero while the interrupt is
> enabled. Doing so puts the port in a misconfigured state where the PHY
> timestamp interrupt for the quad of connected ports will become stuck.
>
> This occurs, because a threshold of zero results in the timestamp interrupt
> status for the port becoming stuck high. The four ports in the connected
> quad have their timestamp status indicators muxed together. A new interrupt
> cannot be generated until the timestamp status indicators return low for
> all four ports.
>
> Normally, the timestamp status for a port will clear once there are fewer
> timestamps in that ports timestamp memory bank than the threshold. A
> threshold of zero makes this impossible, so the timestamp status for the
> port does not clear.
>
> The ice driver never intentionally programs the threshold to zero, indeed
> the driver always programs it to a value of 1, intending to get an
> interrupt immediately as soon as even a single packet is waiting for a
> timestamp.
>
> However, there is a subtle flaw in the programming logic in the
> ice_phy_cfg_intr_eth56g() function. Due to the way that the hardware
> handles enabling the PHY interrupt. If the threshold value is modified at
> the same time as the interrupt is enabled, the HW PHY state machine might
> enable the interrupt before the new threshold value is actually updated.
> This leaves a potential race condition caused by the hardware logic where
> a PHY timestamp interrupt might be triggered before the non-zero threshold
> is written, resulting in the PHY timestamp logic becoming stuck.
>
> Once the PHY timestamp status is stuck high, it will remain stuck even
> after attempting to reprogram the PHY block by changing its threshold or
> disabling the interrupt. Even a typical PF or CORE reset will not reset the
> particular block of the PHY that becomes stuck. Even a warm power cycle is
> not guaranteed to cause the PHY block to reset, and a cold power cycle is
> required.
>
> Prevent this by always writing the PHY_REG_TS_INT_CONFIG in two stages.
> First write the threshold value with the interrupt disabled, and only write
> the enable bit after the threshold has been programmed. When disabling the
> interrupt, leave the threshold unchanged. Additionally, re-read the
> register after writing it to guarantee that the write to the PHY has been
> flushed upon exit of the function.
>
> While we're modifying this function implementation, explicitly reject
> programming a threshold of 0 when enabling the interrupt. No caller does
> this today, but the consequences of doing so are significant. An explicit
> rejection in the code makes this clear.
>
> Fixes: 7cab44f1c35f ("ice: Introduce ETH56G PHY model for E825C products")
> Signed-off-by: Grzegorz Nitka <grzegorz.nitka@intel.com>
> Signed-off-by: Jacob Keller <jacob.e.keller@intel.com>
> Reviewed-by: Aleksandr Loktionov <aleksandr.loktionov@intel.com>
> ---
> drivers/net/ethernet/intel/ice/ice_ptp_hw.c | 36 +++++++++++++++++++++++++----
> 1 file changed, 32 insertions(+), 4 deletions(-)
>
> diff --git a/drivers/net/ethernet/intel/ice/ice_ptp_hw.c b/drivers/net/ethernet/intel/ice/ice_ptp_hw.c
> index e3db252c3918..67775beb9449 100644
> --- a/drivers/net/ethernet/intel/ice/ice_ptp_hw.c
> +++ b/drivers/net/ethernet/intel/ice/ice_ptp_hw.c
> @@ -1847,6 +1847,8 @@ static int ice_phy_cfg_mac_eth56g(struct ice_hw *hw, u8 port)
> * @ena: enable or disable interrupt
> * @threshold: interrupt threshold
> *
> + * The threshold cannot be 0 while the interrupt is enabled.
> + *
> * Configure TX timestamp interrupt for the specified port
> *
> * Return:
> @@ -1858,19 +1860,45 @@ int ice_phy_cfg_intr_eth56g(struct ice_hw *hw, u8 port, bool ena, u8 threshold)
> int err;
> u32 val;
>
> + if (ena && !threshold)
> + return -EINVAL;
> +
> err = ice_read_ptp_reg_eth56g(hw, port, PHY_REG_TS_INT_CONFIG, &val);
> if (err)
> return err;
>
> + val &= ~PHY_TS_INT_CONFIG_ENA_M;
> if (ena) {
> - val |= PHY_TS_INT_CONFIG_ENA_M;
> val &= ~PHY_TS_INT_CONFIG_THRESHOLD_M;
> val |= FIELD_PREP(PHY_TS_INT_CONFIG_THRESHOLD_M, threshold);
> - } else {
> - val &= ~PHY_TS_INT_CONFIG_ENA_M;
> + err = ice_write_ptp_reg_eth56g(hw, port, PHY_REG_TS_INT_CONFIG,
> + val);
> + if (err) {
> + ice_debug(hw, ICE_DBG_PTP,
> + "Failed to update 'threshold' PHY_REG_TS_INT_CONFIG port=%u ena=%u threshold=%u\n",
> + port, !!ena, threshold);
> + return err;
> + }
> + val |= PHY_TS_INT_CONFIG_ENA_M;
> }
>
> - return ice_write_ptp_reg_eth56g(hw, port, PHY_REG_TS_INT_CONFIG, val);
> + err = ice_write_ptp_reg_eth56g(hw, port, PHY_REG_TS_INT_CONFIG, val);
> + if (err) {
> + ice_debug(hw, ICE_DBG_PTP,
> + "Failed to update 'ena' PHY_REG_TS_INT_CONFIG port=%u ena=%u threshold=%u\n",
> + port, !!ena, threshold);
> + return err;
> + }
> +
> + err = ice_read_ptp_reg_eth56g(hw, port, PHY_REG_TS_INT_CONFIG, &val);
> + if (err) {
> + ice_debug(hw, ICE_DBG_PTP,
> + "Failed to read PHY_REG_TS_INT_CONFIG port=%u ena=%u threshold=%u\n",
> + port, !!ena, threshold);
> + return err;
> + }
> +
> + return 0;
> }
>
> /**
>
Reviewed-by: Petr Oros <poros@redhat.com>
^ permalink raw reply [flat|nested] 9+ messages in thread
* [PATCH iwl-net 2/4] ice: perform PHY soft reset for E825C ports at initialization
2026-04-08 18:46 [PATCH iwl-net 0/4] ice: E825C missing PHY timestamp interrupt fixes Jacob Keller
2026-04-08 18:46 ` [PATCH iwl-net 1/4] ice: fix timestamp interrupt configuration for E825C Jacob Keller
@ 2026-04-08 18:46 ` Jacob Keller
2026-04-10 9:33 ` [Intel-wired-lan] " Petr Oros
2026-04-08 18:46 ` [PATCH iwl-net 3/4] ice: fix ready bitmap check for non-E822 devices Jacob Keller
2026-04-08 18:46 ` [PATCH iwl-net 4/4] ice: fix ice_ptp_read_tx_hwtstamp_status_eth56g Jacob Keller
3 siblings, 1 reply; 9+ messages in thread
From: Jacob Keller @ 2026-04-08 18:46 UTC (permalink / raw)
To: Anthony Nguyen, Intel Wired LAN, netdev
Cc: Grzegorz Nitka, Timothy Miskell, Aleksandr Loktionov,
Jacob Keller
From: Grzegorz Nitka <grzegorz.nitka@intel.com>
In some cases the PHY timestamp block of the E825C can become stuck. This
is known to occur if the software writes 0 to the Tx timestamp threshold,
and with older versions of the ice driver the threshold configuration is
buggy and can race in such that hardware briefly operates with a zero
threshold enabled. There are no other known ways to trigger this behavior,
but once it occurs, the hardware is not recovered by normal reset, a driver
reload, or even a warm power cycle of the system. A cold power cycle is
sufficient to recover hardware, but this is extremely invasive and can
result in significant downtime on customer deployments.
The PHY for each port has a timestamping block which has its own reset
functionality accessible by programming the PHY_REG_GLOBAL register.
Writing to the PHY_REG_GLOBAL_SOFT_RESET_BIT triggers the hardware to
perform a complete reset of the timestamping block of the PHY. This
includes clearing the timestamp status for the port, clearing all
outstanding timestamps in the memory bank, and resetting the PHY timer.
The new ice_ptp_phy_soft_reset_eth56g() function toggles the
PHY_REG_GLOBAL soft reset bit with the required delays, ensuring the
PHY is properly reinitialized without requiring a full device reset.
The sequence clears the reset bit, asserts it, then clears it again,
with short waits between transitions to allow hardware stabilization.
Call this function in the new ice_ptp_init_phc_e825c(), implementing the
E825C device specific variant of the ice_ptp_init_phc(). Note that if
ice_ptp_init_phc() fails, PTP functionality may be disabled, but the driver
will still load to allow basic functionality to continue.
This causes the clock owning PF driver to perform a PHY soft reset for
every port during initialization. This ensures the driver begins life in a
known functional state regardless of how it was previously programmed.
This ensures that we properly reconfigure the hardware after a device reset
or when loading the driver, even if it was previously misconfigured with an
out-of-date or modified driver.
Fixes: 7cab44f1c35f ("ice: Introduce ETH56G PHY model for E825C products")
Signed-off-by: Timothy Miskell <timothy.miskell@intel.com>
Signed-off-by: Grzegorz Nitka <grzegorz.nitka@intel.com>
Signed-off-by: Jacob Keller <jacob.e.keller@intel.com>
Reviewed-by: Aleksandr Loktionov <aleksandr.loktionov@intel.com>
---
drivers/net/ethernet/intel/ice/ice_ptp_hw.h | 4 ++
drivers/net/ethernet/intel/ice/ice_ptp_hw.c | 90 ++++++++++++++++++++++++++++-
2 files changed, 93 insertions(+), 1 deletion(-)
diff --git a/drivers/net/ethernet/intel/ice/ice_ptp_hw.h b/drivers/net/ethernet/intel/ice/ice_ptp_hw.h
index 5896b346e579..9d7acc7eb2ce 100644
--- a/drivers/net/ethernet/intel/ice/ice_ptp_hw.h
+++ b/drivers/net/ethernet/intel/ice/ice_ptp_hw.h
@@ -374,6 +374,7 @@ int ice_stop_phy_timer_eth56g(struct ice_hw *hw, u8 port, bool soft_reset);
int ice_start_phy_timer_eth56g(struct ice_hw *hw, u8 port);
int ice_phy_cfg_intr_eth56g(struct ice_hw *hw, u8 port, bool ena, u8 threshold);
int ice_phy_cfg_ptp_1step_eth56g(struct ice_hw *hw, u8 port);
+int ice_ptp_phy_soft_reset_eth56g(struct ice_hw *hw, u8 port);
#define ICE_ETH56G_NOMINAL_INCVAL 0x140000000ULL
#define ICE_ETH56G_NOMINAL_PCS_REF_TUS 0x100000000ULL
@@ -676,6 +677,9 @@ static inline u64 ice_get_base_incval(struct ice_hw *hw)
#define ICE_P0_GNSS_PRSNT_N BIT(4)
/* ETH56G PHY register addresses */
+#define PHY_REG_GLOBAL 0x0
+#define PHY_REG_GLOBAL_SOFT_RESET_M BIT(11)
+
/* Timestamp PHY incval registers */
#define PHY_REG_TIMETUS_L 0x8
#define PHY_REG_TIMETUS_U 0xC
diff --git a/drivers/net/ethernet/intel/ice/ice_ptp_hw.c b/drivers/net/ethernet/intel/ice/ice_ptp_hw.c
index 67775beb9449..441b5f10e4bb 100644
--- a/drivers/net/ethernet/intel/ice/ice_ptp_hw.c
+++ b/drivers/net/ethernet/intel/ice/ice_ptp_hw.c
@@ -377,6 +377,31 @@ static void ice_ptp_cfg_sync_delay(const struct ice_hw *hw, u32 delay)
* The following functions operate on devices with the ETH 56G PHY.
*/
+/**
+ * ice_ptp_init_phc_e825c - Perform E825C specific PHC initialization
+ * @hw: pointer to HW struct
+ *
+ * Perform E825C-specific PTP hardware clock initialization steps.
+ *
+ * Return: 0 on success, or a negative error value on failure.
+ */
+static int ice_ptp_init_phc_e825c(struct ice_hw *hw)
+{
+ int err;
+
+ /* Soft reset all ports, to ensure everything is at a clean state */
+ for (int port = 0; port < hw->ptp.num_lports; port++) {
+ err = ice_ptp_phy_soft_reset_eth56g(hw, port);
+ if (err) {
+ ice_debug(hw, ICE_DBG_PTP, "Failed to soft reset port %d, err %d\n",
+ port, err);
+ return err;
+ }
+ }
+
+ return 0;
+}
+
/**
* ice_ptp_get_dest_dev_e825 - get destination PHY for given port number
* @hw: pointer to the HW struct
@@ -2179,6 +2204,69 @@ int ice_ptp_read_tx_hwtstamp_status_eth56g(struct ice_hw *hw, u32 *ts_status)
return 0;
}
+/**
+ * ice_ptp_phy_soft_reset_eth56g - Perform a PHY soft reset on ETH56G
+ * @hw: pointer to the HW structure
+ * @port: PHY port number
+ *
+ * Trigger a soft reset of the ETH56G PHY by toggling the soft reset
+ * bit in the PHY global register. The reset sequence consists of:
+ * 1. Clearing the soft reset bit
+ * 2. Asserting the soft reset bit
+ * 3. Clearing the soft reset bit again
+ *
+ * Short delays are inserted between each step to allow the hardware
+ * to settle. This provides a controlled way to reinitialize the PHY
+ * without requiring a full device reset.
+ *
+ * Return: 0 on success, or a negative error code on failure when
+ * reading or writing the PHY register.
+ */
+int ice_ptp_phy_soft_reset_eth56g(struct ice_hw *hw, u8 port)
+{
+ u32 global_val;
+ int err;
+
+ err = ice_read_ptp_reg_eth56g(hw, port, PHY_REG_GLOBAL, &global_val);
+ if (err) {
+ ice_debug(hw, ICE_DBG_PTP, "Failed to read PHY_REG_GLOBAL for port %d, err %d\n",
+ port, err);
+ return err;
+ }
+
+ global_val &= ~PHY_REG_GLOBAL_SOFT_RESET_M;
+ ice_debug(hw, ICE_DBG_PTP, "Clearing soft reset bit for port %d, val: 0x%x\n",
+ port, global_val);
+ err = ice_write_ptp_reg_eth56g(hw, port, PHY_REG_GLOBAL, global_val);
+ if (err) {
+ ice_debug(hw, ICE_DBG_PTP, "Failed to write PHY_REG_GLOBAL for port %d, err %d\n",
+ port, err);
+ return err;
+ }
+
+ usleep_range(5000, 6000);
+
+ global_val |= PHY_REG_GLOBAL_SOFT_RESET_M;
+ ice_debug(hw, ICE_DBG_PTP, "Set soft reset bit for port %d, val: 0x%x\n",
+ port, global_val);
+ err = ice_write_ptp_reg_eth56g(hw, port, PHY_REG_GLOBAL, global_val);
+ if (err) {
+ ice_debug(hw, ICE_DBG_PTP, "Failed to write PHY_REG_GLOBAL for port %d, err %d\n",
+ port, err);
+ return err;
+ }
+ usleep_range(5000, 6000);
+
+ global_val &= ~PHY_REG_GLOBAL_SOFT_RESET_M;
+ ice_debug(hw, ICE_DBG_PTP, "Clear soft reset bit for port %d, val: 0x%x\n",
+ port, global_val);
+ err = ice_write_ptp_reg_eth56g(hw, port, PHY_REG_GLOBAL, global_val);
+ if (err)
+ ice_debug(hw, ICE_DBG_PTP, "Failed to write PHY_REG_GLOBAL for port %d, err %d\n",
+ port, err);
+ return err;
+}
+
/**
* ice_get_phy_tx_tstamp_ready_eth56g - Read the Tx memory status register
* @hw: pointer to the HW struct
@@ -5591,7 +5679,7 @@ int ice_ptp_init_phc(struct ice_hw *hw)
case ICE_MAC_GENERIC:
return ice_ptp_init_phc_e82x(hw);
case ICE_MAC_GENERIC_3K_E825:
- return 0;
+ return ice_ptp_init_phc_e825c(hw);
default:
return -EOPNOTSUPP;
}
--
2.53.0.1066.g1eceb487f285
^ permalink raw reply related [flat|nested] 9+ messages in thread* Re: [Intel-wired-lan] [PATCH iwl-net 2/4] ice: perform PHY soft reset for E825C ports at initialization
2026-04-08 18:46 ` [PATCH iwl-net 2/4] ice: perform PHY soft reset for E825C ports at initialization Jacob Keller
@ 2026-04-10 9:33 ` Petr Oros
0 siblings, 0 replies; 9+ messages in thread
From: Petr Oros @ 2026-04-10 9:33 UTC (permalink / raw)
To: Jacob Keller, Anthony Nguyen, Intel Wired LAN, netdev
Cc: Aleksandr Loktionov, Timothy Miskell
On 4/8/26 20:46, Jacob Keller wrote:
> From: Grzegorz Nitka <grzegorz.nitka@intel.com>
>
> In some cases the PHY timestamp block of the E825C can become stuck. This
> is known to occur if the software writes 0 to the Tx timestamp threshold,
> and with older versions of the ice driver the threshold configuration is
> buggy and can race in such that hardware briefly operates with a zero
> threshold enabled. There are no other known ways to trigger this behavior,
> but once it occurs, the hardware is not recovered by normal reset, a driver
> reload, or even a warm power cycle of the system. A cold power cycle is
> sufficient to recover hardware, but this is extremely invasive and can
> result in significant downtime on customer deployments.
>
> The PHY for each port has a timestamping block which has its own reset
> functionality accessible by programming the PHY_REG_GLOBAL register.
> Writing to the PHY_REG_GLOBAL_SOFT_RESET_BIT triggers the hardware to
> perform a complete reset of the timestamping block of the PHY. This
> includes clearing the timestamp status for the port, clearing all
> outstanding timestamps in the memory bank, and resetting the PHY timer.
>
> The new ice_ptp_phy_soft_reset_eth56g() function toggles the
> PHY_REG_GLOBAL soft reset bit with the required delays, ensuring the
> PHY is properly reinitialized without requiring a full device reset.
> The sequence clears the reset bit, asserts it, then clears it again,
> with short waits between transitions to allow hardware stabilization.
>
> Call this function in the new ice_ptp_init_phc_e825c(), implementing the
> E825C device specific variant of the ice_ptp_init_phc(). Note that if
> ice_ptp_init_phc() fails, PTP functionality may be disabled, but the driver
> will still load to allow basic functionality to continue.
>
> This causes the clock owning PF driver to perform a PHY soft reset for
> every port during initialization. This ensures the driver begins life in a
> known functional state regardless of how it was previously programmed.
>
> This ensures that we properly reconfigure the hardware after a device reset
> or when loading the driver, even if it was previously misconfigured with an
> out-of-date or modified driver.
>
> Fixes: 7cab44f1c35f ("ice: Introduce ETH56G PHY model for E825C products")
> Signed-off-by: Timothy Miskell <timothy.miskell@intel.com>
> Signed-off-by: Grzegorz Nitka <grzegorz.nitka@intel.com>
> Signed-off-by: Jacob Keller <jacob.e.keller@intel.com>
> Reviewed-by: Aleksandr Loktionov <aleksandr.loktionov@intel.com>
> ---
> drivers/net/ethernet/intel/ice/ice_ptp_hw.h | 4 ++
> drivers/net/ethernet/intel/ice/ice_ptp_hw.c | 90 ++++++++++++++++++++++++++++-
> 2 files changed, 93 insertions(+), 1 deletion(-)
>
> diff --git a/drivers/net/ethernet/intel/ice/ice_ptp_hw.h b/drivers/net/ethernet/intel/ice/ice_ptp_hw.h
> index 5896b346e579..9d7acc7eb2ce 100644
> --- a/drivers/net/ethernet/intel/ice/ice_ptp_hw.h
> +++ b/drivers/net/ethernet/intel/ice/ice_ptp_hw.h
> @@ -374,6 +374,7 @@ int ice_stop_phy_timer_eth56g(struct ice_hw *hw, u8 port, bool soft_reset);
> int ice_start_phy_timer_eth56g(struct ice_hw *hw, u8 port);
> int ice_phy_cfg_intr_eth56g(struct ice_hw *hw, u8 port, bool ena, u8 threshold);
> int ice_phy_cfg_ptp_1step_eth56g(struct ice_hw *hw, u8 port);
> +int ice_ptp_phy_soft_reset_eth56g(struct ice_hw *hw, u8 port);
>
> #define ICE_ETH56G_NOMINAL_INCVAL 0x140000000ULL
> #define ICE_ETH56G_NOMINAL_PCS_REF_TUS 0x100000000ULL
> @@ -676,6 +677,9 @@ static inline u64 ice_get_base_incval(struct ice_hw *hw)
> #define ICE_P0_GNSS_PRSNT_N BIT(4)
>
> /* ETH56G PHY register addresses */
> +#define PHY_REG_GLOBAL 0x0
> +#define PHY_REG_GLOBAL_SOFT_RESET_M BIT(11)
> +
> /* Timestamp PHY incval registers */
> #define PHY_REG_TIMETUS_L 0x8
> #define PHY_REG_TIMETUS_U 0xC
> diff --git a/drivers/net/ethernet/intel/ice/ice_ptp_hw.c b/drivers/net/ethernet/intel/ice/ice_ptp_hw.c
> index 67775beb9449..441b5f10e4bb 100644
> --- a/drivers/net/ethernet/intel/ice/ice_ptp_hw.c
> +++ b/drivers/net/ethernet/intel/ice/ice_ptp_hw.c
> @@ -377,6 +377,31 @@ static void ice_ptp_cfg_sync_delay(const struct ice_hw *hw, u32 delay)
> * The following functions operate on devices with the ETH 56G PHY.
> */
>
> +/**
> + * ice_ptp_init_phc_e825c - Perform E825C specific PHC initialization
> + * @hw: pointer to HW struct
> + *
> + * Perform E825C-specific PTP hardware clock initialization steps.
> + *
> + * Return: 0 on success, or a negative error value on failure.
> + */
> +static int ice_ptp_init_phc_e825c(struct ice_hw *hw)
> +{
> + int err;
> +
> + /* Soft reset all ports, to ensure everything is at a clean state */
> + for (int port = 0; port < hw->ptp.num_lports; port++) {
> + err = ice_ptp_phy_soft_reset_eth56g(hw, port);
> + if (err) {
> + ice_debug(hw, ICE_DBG_PTP, "Failed to soft reset port %d, err %d\n",
> + port, err);
> + return err;
> + }
> + }
> +
> + return 0;
> +}
> +
> /**
> * ice_ptp_get_dest_dev_e825 - get destination PHY for given port number
> * @hw: pointer to the HW struct
> @@ -2179,6 +2204,69 @@ int ice_ptp_read_tx_hwtstamp_status_eth56g(struct ice_hw *hw, u32 *ts_status)
> return 0;
> }
>
> +/**
> + * ice_ptp_phy_soft_reset_eth56g - Perform a PHY soft reset on ETH56G
> + * @hw: pointer to the HW structure
> + * @port: PHY port number
> + *
> + * Trigger a soft reset of the ETH56G PHY by toggling the soft reset
> + * bit in the PHY global register. The reset sequence consists of:
> + * 1. Clearing the soft reset bit
> + * 2. Asserting the soft reset bit
> + * 3. Clearing the soft reset bit again
> + *
> + * Short delays are inserted between each step to allow the hardware
> + * to settle. This provides a controlled way to reinitialize the PHY
> + * without requiring a full device reset.
> + *
> + * Return: 0 on success, or a negative error code on failure when
> + * reading or writing the PHY register.
> + */
> +int ice_ptp_phy_soft_reset_eth56g(struct ice_hw *hw, u8 port)
> +{
> + u32 global_val;
> + int err;
> +
> + err = ice_read_ptp_reg_eth56g(hw, port, PHY_REG_GLOBAL, &global_val);
> + if (err) {
> + ice_debug(hw, ICE_DBG_PTP, "Failed to read PHY_REG_GLOBAL for port %d, err %d\n",
> + port, err);
> + return err;
> + }
> +
> + global_val &= ~PHY_REG_GLOBAL_SOFT_RESET_M;
> + ice_debug(hw, ICE_DBG_PTP, "Clearing soft reset bit for port %d, val: 0x%x\n",
> + port, global_val);
> + err = ice_write_ptp_reg_eth56g(hw, port, PHY_REG_GLOBAL, global_val);
> + if (err) {
> + ice_debug(hw, ICE_DBG_PTP, "Failed to write PHY_REG_GLOBAL for port %d, err %d\n",
> + port, err);
> + return err;
> + }
> +
> + usleep_range(5000, 6000);
> +
> + global_val |= PHY_REG_GLOBAL_SOFT_RESET_M;
> + ice_debug(hw, ICE_DBG_PTP, "Set soft reset bit for port %d, val: 0x%x\n",
> + port, global_val);
> + err = ice_write_ptp_reg_eth56g(hw, port, PHY_REG_GLOBAL, global_val);
> + if (err) {
> + ice_debug(hw, ICE_DBG_PTP, "Failed to write PHY_REG_GLOBAL for port %d, err %d\n",
> + port, err);
> + return err;
> + }
> + usleep_range(5000, 6000);
> +
> + global_val &= ~PHY_REG_GLOBAL_SOFT_RESET_M;
> + ice_debug(hw, ICE_DBG_PTP, "Clear soft reset bit for port %d, val: 0x%x\n",
> + port, global_val);
> + err = ice_write_ptp_reg_eth56g(hw, port, PHY_REG_GLOBAL, global_val);
> + if (err)
> + ice_debug(hw, ICE_DBG_PTP, "Failed to write PHY_REG_GLOBAL for port %d, err %d\n",
> + port, err);
> + return err;
> +}
> +
> /**
> * ice_get_phy_tx_tstamp_ready_eth56g - Read the Tx memory status register
> * @hw: pointer to the HW struct
> @@ -5591,7 +5679,7 @@ int ice_ptp_init_phc(struct ice_hw *hw)
> case ICE_MAC_GENERIC:
> return ice_ptp_init_phc_e82x(hw);
> case ICE_MAC_GENERIC_3K_E825:
> - return 0;
> + return ice_ptp_init_phc_e825c(hw);
> default:
> return -EOPNOTSUPP;
> }
>
Reviewed-by: Petr Oros <poros@redhat.com>
^ permalink raw reply [flat|nested] 9+ messages in thread
* [PATCH iwl-net 3/4] ice: fix ready bitmap check for non-E822 devices
2026-04-08 18:46 [PATCH iwl-net 0/4] ice: E825C missing PHY timestamp interrupt fixes Jacob Keller
2026-04-08 18:46 ` [PATCH iwl-net 1/4] ice: fix timestamp interrupt configuration for E825C Jacob Keller
2026-04-08 18:46 ` [PATCH iwl-net 2/4] ice: perform PHY soft reset for E825C ports at initialization Jacob Keller
@ 2026-04-08 18:46 ` Jacob Keller
2026-04-10 9:34 ` [Intel-wired-lan] " Petr Oros
2026-04-08 18:46 ` [PATCH iwl-net 4/4] ice: fix ice_ptp_read_tx_hwtstamp_status_eth56g Jacob Keller
3 siblings, 1 reply; 9+ messages in thread
From: Jacob Keller @ 2026-04-08 18:46 UTC (permalink / raw)
To: Anthony Nguyen, Intel Wired LAN, netdev
Cc: Grzegorz Nitka, Timothy Miskell, Aleksandr Loktionov,
Jacob Keller
The E800 hardware (apart from E810) has a ready bitmap for the PHY
indicating which timestamp slots currently have an outstanding timestamp
waiting to be read by software.
This bitmap is checked in multiple places using the
ice_get_phy_tx_tstamp_ready():
* ice_ptp_process_tx_tstamp() calls it to determine which timestamps to
attempt reading from the PHY
* ice_ptp_tx_tstamps_pending() calls it in a loop at the end of the
miscellaneous IRQ to check if new timestamps came in while the interrupt
handler was executing.
* ice_ptp_maybe_trigger_tx_interrupt() calls it in the auxiliary work task
to trigger a software interrupt in the event that the hardware logic
gets stuck.
For E82X devices, multiple PHYs share the same block, and the parameter
passed to the ready bitmap is a block number associated with the given
port. For E825-C devices, the PHYs have their own independent blocks and do
not share, so the parameter passed needs to be the port number. For E810
devices, the ice_get_phy_tx_tstamp_ready() always returns all 1s regardless
of what port, since this hardware does not have a ready bitmap. Finally,
for E830 devices, each PF has its own ready bitmap accessible via register,
and the block parameter is unused.
The first call correctly uses the Tx timestamp tracker block parameter to
check the appropriate timestamp block. This works because the tracker is
setup correctly for each timestamp device type.
The second two callers behave incorrectly for all device types other than
the older E822 devices. They both iterate in a loop using
ICE_GET_QUAD_NUM() which is a macro only used by E822 devices. This logic
is incorrect for devices other than the E822 devices.
For E810 the calls would always return true, causing E810 devices to always
attempt to trigger a software interrupt even when they have no reason to.
For E830, this results in duplicate work as the ready bitmap is checked
once per number of quads. Finally, for E825-C, this results in the pending
checks failing to detect timestamps on ports other than the first two.
Fix this by introducing a new hardware API function to ice_ptp_hw.c,
ice_check_phy_tx_tstamp_ready(). This function will check if any timestamps
are available and returns a positive value if any timestamps are pending.
For E810, the function always returns false, so that the re-trigger checks
never happen. For E830, check the ready bitmap just once. For E82x
hardware, check each quad. Finally, for E825-C, check every port.
The interface function returns an integer to enable reporting of error code
if the driver is unable read the ready bitmap. This enables callers to
handle this case properly. The previous implementation assumed that
timestamps are available if they failed to read the bitmap. This is
problematic as it could lead to continuous software IRQ triggering if the
PHY timestamp registers somehow become inaccessible.
This change is especially important for E825-C devices, as the missing
checks could leave a window open where a new timestamp could arrive while
the existing timestamps aren't completed. As a result, the hardware
threshold logic would not trigger a new interrupt. Without the check, the
timestamp is left unhandled, and new timestamps will not cause an interrupt
again until the timestamp is handled. Since both the interrupt check and
the backup check in the auxiliary task do not function properly, the device
may have Tx timestamps permanently stuck failing on a given port.
The faulty checks originate from commit d938a8cca88a ("ice: Auxbus devices
& driver for E822 TS") and commit 712e876371f8 ("ice: periodically kick Tx
timestamp interrupt"), however at the time of the original coding, both
functions only operated on E822 hardware. This is no longer the case, and
hasn't been since the introduction of the ETH56G PHY model in commit
7cab44f1c35f ("ice: Introduce ETH56G PHY model for E825C products")
Fixes: 7cab44f1c35f ("ice: Introduce ETH56G PHY model for E825C products")
Signed-off-by: Jacob Keller <jacob.e.keller@intel.com>
Reviewed-by: Aleksandr Loktionov <aleksandr.loktionov@intel.com>
---
drivers/net/ethernet/intel/ice/ice_ptp_hw.h | 1 +
drivers/net/ethernet/intel/ice/ice_ptp.c | 40 ++++------
drivers/net/ethernet/intel/ice/ice_ptp_hw.c | 117 ++++++++++++++++++++++++++++
3 files changed, 132 insertions(+), 26 deletions(-)
diff --git a/drivers/net/ethernet/intel/ice/ice_ptp_hw.h b/drivers/net/ethernet/intel/ice/ice_ptp_hw.h
index 9d7acc7eb2ce..1b58b054f4a5 100644
--- a/drivers/net/ethernet/intel/ice/ice_ptp_hw.h
+++ b/drivers/net/ethernet/intel/ice/ice_ptp_hw.h
@@ -300,6 +300,7 @@ void ice_ptp_reset_ts_memory(struct ice_hw *hw);
int ice_ptp_init_phc(struct ice_hw *hw);
void ice_ptp_init_hw(struct ice_hw *hw);
int ice_get_phy_tx_tstamp_ready(struct ice_hw *hw, u8 block, u64 *tstamp_ready);
+int ice_check_phy_tx_tstamp_ready(struct ice_hw *hw);
int ice_ptp_one_port_cmd(struct ice_hw *hw, u8 configured_port,
enum ice_ptp_tmr_cmd configured_cmd);
diff --git a/drivers/net/ethernet/intel/ice/ice_ptp.c b/drivers/net/ethernet/intel/ice/ice_ptp.c
index ada42bcc4d0b..34906f972d17 100644
--- a/drivers/net/ethernet/intel/ice/ice_ptp.c
+++ b/drivers/net/ethernet/intel/ice/ice_ptp.c
@@ -2718,7 +2718,7 @@ static bool ice_any_port_has_timestamps(struct ice_pf *pf)
bool ice_ptp_tx_tstamps_pending(struct ice_pf *pf)
{
struct ice_hw *hw = &pf->hw;
- unsigned int i;
+ int ret;
/* Check software indicator */
switch (pf->ptp.tx_interrupt_mode) {
@@ -2739,16 +2739,15 @@ bool ice_ptp_tx_tstamps_pending(struct ice_pf *pf)
}
/* Check hardware indicator */
- for (i = 0; i < ICE_GET_QUAD_NUM(hw->ptp.num_lports); i++) {
- u64 tstamp_ready = 0;
- int err;
-
- err = ice_get_phy_tx_tstamp_ready(&pf->hw, i, &tstamp_ready);
- if (err || tstamp_ready)
- return true;
+ ret = ice_check_phy_tx_tstamp_ready(hw);
+ if (ret < 0) {
+ dev_dbg(ice_pf_to_dev(pf), "Unable to read PHY Tx timestamp ready bitmap, err %d\n",
+ ret);
+ /* Stop triggering IRQs if we're unable to read PHY */
+ return false;
}
- return false;
+ return ret;
}
/**
@@ -2832,8 +2831,7 @@ static void ice_ptp_maybe_trigger_tx_interrupt(struct ice_pf *pf)
{
struct device *dev = ice_pf_to_dev(pf);
struct ice_hw *hw = &pf->hw;
- bool trigger_oicr = false;
- unsigned int i;
+ int ret;
if (!pf->ptp.port.tx.has_ready_bitmap)
return;
@@ -2841,21 +2839,11 @@ static void ice_ptp_maybe_trigger_tx_interrupt(struct ice_pf *pf)
if (!ice_pf_src_tmr_owned(pf))
return;
- for (i = 0; i < ICE_GET_QUAD_NUM(hw->ptp.num_lports); i++) {
- u64 tstamp_ready;
- int err;
-
- err = ice_get_phy_tx_tstamp_ready(&pf->hw, i, &tstamp_ready);
- if (!err && tstamp_ready) {
- trigger_oicr = true;
- break;
- }
- }
-
- if (trigger_oicr) {
- /* Trigger a software interrupt, to ensure this data
- * gets processed.
- */
+ ret = ice_check_phy_tx_tstamp_ready(hw);
+ if (ret < 0) {
+ dev_dbg(dev, "PTP periodic task unable to read PHY timestamp ready bitmap, err %d\n",
+ ret);
+ } else if (ret) {
dev_dbg(dev, "PTP periodic task detected waiting timestamps. Triggering Tx timestamp interrupt now.\n");
wr32(hw, PFINT_OICR, PFINT_OICR_TSYN_TX_M);
diff --git a/drivers/net/ethernet/intel/ice/ice_ptp_hw.c b/drivers/net/ethernet/intel/ice/ice_ptp_hw.c
index 441b5f10e4bb..64ad5ed5c688 100644
--- a/drivers/net/ethernet/intel/ice/ice_ptp_hw.c
+++ b/drivers/net/ethernet/intel/ice/ice_ptp_hw.c
@@ -2168,6 +2168,35 @@ int ice_start_phy_timer_eth56g(struct ice_hw *hw, u8 port)
return 0;
}
+/**
+ * ice_check_phy_tx_tstamp_ready_eth56g - Check Tx memory status for all ports
+ * @hw: pointer to the HW struct
+ *
+ * Check the PHY_REG_TX_MEMORY_STATUS for all ports. A set bit indicates
+ * a waiting timestamp.
+ *
+ * Return: 1 if any port has at least one timestamp ready bit set,
+ * 0 otherwise, and a negative error code if unable to read the bitmap.
+ */
+static int ice_check_phy_tx_tstamp_ready_eth56g(struct ice_hw *hw)
+{
+ int port;
+
+ for (port = 0; port < hw->ptp.num_lports; port++) {
+ u64 tstamp_ready;
+ int err;
+
+ err = ice_get_phy_tx_tstamp_ready(hw, port, &tstamp_ready);
+ if (err)
+ return err;
+
+ if (tstamp_ready)
+ return 1;
+ }
+
+ return 0;
+}
+
/**
* ice_ptp_read_tx_hwtstamp_status_eth56g - Get TX timestamp status
* @hw: pointer to the HW struct
@@ -4318,6 +4347,35 @@ ice_get_phy_tx_tstamp_ready_e82x(struct ice_hw *hw, u8 quad, u64 *tstamp_ready)
return 0;
}
+/**
+ * ice_check_phy_tx_tstamp_ready_e82x - Check Tx memory status for all quads
+ * @hw: pointer to the HW struct
+ *
+ * Check the Q_REG_TX_MEMORY_STATUS for all quads. A set bit indicates
+ * a waiting timestamp.
+ *
+ * Return: 1 if any quad has at least one timestamp ready bit set,
+ * 0 otherwise, and a negative error value if unable to read the bitmap.
+ */
+static int ice_check_phy_tx_tstamp_ready_e82x(struct ice_hw *hw)
+{
+ int quad;
+
+ for (quad = 0; quad < ICE_GET_QUAD_NUM(hw->ptp.num_lports); quad++) {
+ u64 tstamp_ready;
+ int err;
+
+ err = ice_get_phy_tx_tstamp_ready(hw, quad, &tstamp_ready);
+ if (err)
+ return err;
+
+ if (tstamp_ready)
+ return 1;
+ }
+
+ return 0;
+}
+
/**
* ice_phy_cfg_intr_e82x - Configure TX timestamp interrupt
* @hw: pointer to the HW struct
@@ -4870,6 +4928,23 @@ ice_get_phy_tx_tstamp_ready_e810(struct ice_hw *hw, u8 port, u64 *tstamp_ready)
return 0;
}
+/**
+ * ice_check_phy_tx_tstamp_ready_e810 - Check Tx memory status register
+ * @hw: pointer to the HW struct
+ *
+ * The E810 devices do not have a Tx memory status register. Note this is
+ * intentionally different behavior from ice_get_phy_tx_tstamp_ready_e810
+ * which always says that all bits are ready. This function is called in cases
+ * where code will trigger interrupts if timestamps are waiting, and should
+ * not be called for E810 hardware.
+ *
+ * Return: 0.
+ */
+static int ice_check_phy_tx_tstamp_ready_e810(struct ice_hw *hw)
+{
+ return 0;
+}
+
/* E810 SMA functions
*
* The following functions operate specifically on E810 hardware and are used
@@ -5124,6 +5199,21 @@ static void ice_get_phy_tx_tstamp_ready_e830(const struct ice_hw *hw, u8 port,
*tstamp_ready |= rd32(hw, E830_PRTMAC_TS_TX_MEM_VALID_L);
}
+/**
+ * ice_check_phy_tx_tstamp_ready_e830 - Check Tx memory status register
+ * @hw: pointer to the HW struct
+ *
+ * Return: 1 if the device has waiting timestamps, 0 otherwise.
+ */
+static int ice_check_phy_tx_tstamp_ready_e830(struct ice_hw *hw)
+{
+ u64 tstamp_ready;
+
+ ice_get_phy_tx_tstamp_ready_e830(hw, 0, &tstamp_ready);
+
+ return !!tstamp_ready;
+}
+
/**
* ice_ptp_init_phy_e830 - initialize PHY parameters
* @ptp: pointer to the PTP HW struct
@@ -5716,6 +5806,33 @@ int ice_get_phy_tx_tstamp_ready(struct ice_hw *hw, u8 block, u64 *tstamp_ready)
}
}
+/**
+ * ice_check_phy_tx_tstamp_ready - Check PHY Tx timestamp memory status
+ * @hw: pointer to the HW struct
+ *
+ * Check the PHY for Tx timestamp memory status on all ports. If you need to
+ * see individual timestamp status for each index, use
+ * ice_get_phy_tx_tstamp_ready() instead.
+ *
+ * Return: 1 if any port has timestamps available, 0 if there are no timestamps
+ * available, and a negative error code on failure.
+ */
+int ice_check_phy_tx_tstamp_ready(struct ice_hw *hw)
+{
+ switch (hw->mac_type) {
+ case ICE_MAC_E810:
+ return ice_check_phy_tx_tstamp_ready_e810(hw);
+ case ICE_MAC_E830:
+ return ice_check_phy_tx_tstamp_ready_e830(hw);
+ case ICE_MAC_GENERIC:
+ return ice_check_phy_tx_tstamp_ready_e82x(hw);
+ case ICE_MAC_GENERIC_3K_E825:
+ return ice_check_phy_tx_tstamp_ready_eth56g(hw);
+ default:
+ return -EOPNOTSUPP;
+ }
+}
+
/**
* ice_cgu_get_pin_desc_e823 - get pin description array
* @hw: pointer to the hw struct
--
2.53.0.1066.g1eceb487f285
^ permalink raw reply related [flat|nested] 9+ messages in thread* Re: [Intel-wired-lan] [PATCH iwl-net 3/4] ice: fix ready bitmap check for non-E822 devices
2026-04-08 18:46 ` [PATCH iwl-net 3/4] ice: fix ready bitmap check for non-E822 devices Jacob Keller
@ 2026-04-10 9:34 ` Petr Oros
0 siblings, 0 replies; 9+ messages in thread
From: Petr Oros @ 2026-04-10 9:34 UTC (permalink / raw)
To: Jacob Keller, Anthony Nguyen, Intel Wired LAN, netdev
Cc: Aleksandr Loktionov, Timothy Miskell
On 4/8/26 20:46, Jacob Keller wrote:
> The E800 hardware (apart from E810) has a ready bitmap for the PHY
> indicating which timestamp slots currently have an outstanding timestamp
> waiting to be read by software.
>
> This bitmap is checked in multiple places using the
> ice_get_phy_tx_tstamp_ready():
>
> * ice_ptp_process_tx_tstamp() calls it to determine which timestamps to
> attempt reading from the PHY
> * ice_ptp_tx_tstamps_pending() calls it in a loop at the end of the
> miscellaneous IRQ to check if new timestamps came in while the interrupt
> handler was executing.
> * ice_ptp_maybe_trigger_tx_interrupt() calls it in the auxiliary work task
> to trigger a software interrupt in the event that the hardware logic
> gets stuck.
>
> For E82X devices, multiple PHYs share the same block, and the parameter
> passed to the ready bitmap is a block number associated with the given
> port. For E825-C devices, the PHYs have their own independent blocks and do
> not share, so the parameter passed needs to be the port number. For E810
> devices, the ice_get_phy_tx_tstamp_ready() always returns all 1s regardless
> of what port, since this hardware does not have a ready bitmap. Finally,
> for E830 devices, each PF has its own ready bitmap accessible via register,
> and the block parameter is unused.
>
> The first call correctly uses the Tx timestamp tracker block parameter to
> check the appropriate timestamp block. This works because the tracker is
> setup correctly for each timestamp device type.
>
> The second two callers behave incorrectly for all device types other than
> the older E822 devices. They both iterate in a loop using
> ICE_GET_QUAD_NUM() which is a macro only used by E822 devices. This logic
> is incorrect for devices other than the E822 devices.
>
> For E810 the calls would always return true, causing E810 devices to always
> attempt to trigger a software interrupt even when they have no reason to.
> For E830, this results in duplicate work as the ready bitmap is checked
> once per number of quads. Finally, for E825-C, this results in the pending
> checks failing to detect timestamps on ports other than the first two.
>
> Fix this by introducing a new hardware API function to ice_ptp_hw.c,
> ice_check_phy_tx_tstamp_ready(). This function will check if any timestamps
> are available and returns a positive value if any timestamps are pending.
> For E810, the function always returns false, so that the re-trigger checks
> never happen. For E830, check the ready bitmap just once. For E82x
> hardware, check each quad. Finally, for E825-C, check every port.
>
> The interface function returns an integer to enable reporting of error code
> if the driver is unable read the ready bitmap. This enables callers to
> handle this case properly. The previous implementation assumed that
> timestamps are available if they failed to read the bitmap. This is
> problematic as it could lead to continuous software IRQ triggering if the
> PHY timestamp registers somehow become inaccessible.
>
> This change is especially important for E825-C devices, as the missing
> checks could leave a window open where a new timestamp could arrive while
> the existing timestamps aren't completed. As a result, the hardware
> threshold logic would not trigger a new interrupt. Without the check, the
> timestamp is left unhandled, and new timestamps will not cause an interrupt
> again until the timestamp is handled. Since both the interrupt check and
> the backup check in the auxiliary task do not function properly, the device
> may have Tx timestamps permanently stuck failing on a given port.
>
> The faulty checks originate from commit d938a8cca88a ("ice: Auxbus devices
> & driver for E822 TS") and commit 712e876371f8 ("ice: periodically kick Tx
> timestamp interrupt"), however at the time of the original coding, both
> functions only operated on E822 hardware. This is no longer the case, and
> hasn't been since the introduction of the ETH56G PHY model in commit
> 7cab44f1c35f ("ice: Introduce ETH56G PHY model for E825C products")
>
> Fixes: 7cab44f1c35f ("ice: Introduce ETH56G PHY model for E825C products")
> Signed-off-by: Jacob Keller <jacob.e.keller@intel.com>
> Reviewed-by: Aleksandr Loktionov <aleksandr.loktionov@intel.com>
> ---
> drivers/net/ethernet/intel/ice/ice_ptp_hw.h | 1 +
> drivers/net/ethernet/intel/ice/ice_ptp.c | 40 ++++------
> drivers/net/ethernet/intel/ice/ice_ptp_hw.c | 117 ++++++++++++++++++++++++++++
> 3 files changed, 132 insertions(+), 26 deletions(-)
>
> diff --git a/drivers/net/ethernet/intel/ice/ice_ptp_hw.h b/drivers/net/ethernet/intel/ice/ice_ptp_hw.h
> index 9d7acc7eb2ce..1b58b054f4a5 100644
> --- a/drivers/net/ethernet/intel/ice/ice_ptp_hw.h
> +++ b/drivers/net/ethernet/intel/ice/ice_ptp_hw.h
> @@ -300,6 +300,7 @@ void ice_ptp_reset_ts_memory(struct ice_hw *hw);
> int ice_ptp_init_phc(struct ice_hw *hw);
> void ice_ptp_init_hw(struct ice_hw *hw);
> int ice_get_phy_tx_tstamp_ready(struct ice_hw *hw, u8 block, u64 *tstamp_ready);
> +int ice_check_phy_tx_tstamp_ready(struct ice_hw *hw);
> int ice_ptp_one_port_cmd(struct ice_hw *hw, u8 configured_port,
> enum ice_ptp_tmr_cmd configured_cmd);
>
> diff --git a/drivers/net/ethernet/intel/ice/ice_ptp.c b/drivers/net/ethernet/intel/ice/ice_ptp.c
> index ada42bcc4d0b..34906f972d17 100644
> --- a/drivers/net/ethernet/intel/ice/ice_ptp.c
> +++ b/drivers/net/ethernet/intel/ice/ice_ptp.c
> @@ -2718,7 +2718,7 @@ static bool ice_any_port_has_timestamps(struct ice_pf *pf)
> bool ice_ptp_tx_tstamps_pending(struct ice_pf *pf)
> {
> struct ice_hw *hw = &pf->hw;
> - unsigned int i;
> + int ret;
>
> /* Check software indicator */
> switch (pf->ptp.tx_interrupt_mode) {
> @@ -2739,16 +2739,15 @@ bool ice_ptp_tx_tstamps_pending(struct ice_pf *pf)
> }
>
> /* Check hardware indicator */
> - for (i = 0; i < ICE_GET_QUAD_NUM(hw->ptp.num_lports); i++) {
> - u64 tstamp_ready = 0;
> - int err;
> -
> - err = ice_get_phy_tx_tstamp_ready(&pf->hw, i, &tstamp_ready);
> - if (err || tstamp_ready)
> - return true;
> + ret = ice_check_phy_tx_tstamp_ready(hw);
> + if (ret < 0) {
> + dev_dbg(ice_pf_to_dev(pf), "Unable to read PHY Tx timestamp ready bitmap, err %d\n",
> + ret);
> + /* Stop triggering IRQs if we're unable to read PHY */
> + return false;
> }
>
> - return false;
> + return ret;
> }
>
> /**
> @@ -2832,8 +2831,7 @@ static void ice_ptp_maybe_trigger_tx_interrupt(struct ice_pf *pf)
> {
> struct device *dev = ice_pf_to_dev(pf);
> struct ice_hw *hw = &pf->hw;
> - bool trigger_oicr = false;
> - unsigned int i;
> + int ret;
>
> if (!pf->ptp.port.tx.has_ready_bitmap)
> return;
> @@ -2841,21 +2839,11 @@ static void ice_ptp_maybe_trigger_tx_interrupt(struct ice_pf *pf)
> if (!ice_pf_src_tmr_owned(pf))
> return;
>
> - for (i = 0; i < ICE_GET_QUAD_NUM(hw->ptp.num_lports); i++) {
> - u64 tstamp_ready;
> - int err;
> -
> - err = ice_get_phy_tx_tstamp_ready(&pf->hw, i, &tstamp_ready);
> - if (!err && tstamp_ready) {
> - trigger_oicr = true;
> - break;
> - }
> - }
> -
> - if (trigger_oicr) {
> - /* Trigger a software interrupt, to ensure this data
> - * gets processed.
> - */
> + ret = ice_check_phy_tx_tstamp_ready(hw);
> + if (ret < 0) {
> + dev_dbg(dev, "PTP periodic task unable to read PHY timestamp ready bitmap, err %d\n",
> + ret);
> + } else if (ret) {
> dev_dbg(dev, "PTP periodic task detected waiting timestamps. Triggering Tx timestamp interrupt now.\n");
>
> wr32(hw, PFINT_OICR, PFINT_OICR_TSYN_TX_M);
> diff --git a/drivers/net/ethernet/intel/ice/ice_ptp_hw.c b/drivers/net/ethernet/intel/ice/ice_ptp_hw.c
> index 441b5f10e4bb..64ad5ed5c688 100644
> --- a/drivers/net/ethernet/intel/ice/ice_ptp_hw.c
> +++ b/drivers/net/ethernet/intel/ice/ice_ptp_hw.c
> @@ -2168,6 +2168,35 @@ int ice_start_phy_timer_eth56g(struct ice_hw *hw, u8 port)
> return 0;
> }
>
> +/**
> + * ice_check_phy_tx_tstamp_ready_eth56g - Check Tx memory status for all ports
> + * @hw: pointer to the HW struct
> + *
> + * Check the PHY_REG_TX_MEMORY_STATUS for all ports. A set bit indicates
> + * a waiting timestamp.
> + *
> + * Return: 1 if any port has at least one timestamp ready bit set,
> + * 0 otherwise, and a negative error code if unable to read the bitmap.
> + */
> +static int ice_check_phy_tx_tstamp_ready_eth56g(struct ice_hw *hw)
> +{
> + int port;
> +
> + for (port = 0; port < hw->ptp.num_lports; port++) {
> + u64 tstamp_ready;
> + int err;
> +
> + err = ice_get_phy_tx_tstamp_ready(hw, port, &tstamp_ready);
> + if (err)
> + return err;
> +
> + if (tstamp_ready)
> + return 1;
> + }
> +
> + return 0;
> +}
> +
> /**
> * ice_ptp_read_tx_hwtstamp_status_eth56g - Get TX timestamp status
> * @hw: pointer to the HW struct
> @@ -4318,6 +4347,35 @@ ice_get_phy_tx_tstamp_ready_e82x(struct ice_hw *hw, u8 quad, u64 *tstamp_ready)
> return 0;
> }
>
> +/**
> + * ice_check_phy_tx_tstamp_ready_e82x - Check Tx memory status for all quads
> + * @hw: pointer to the HW struct
> + *
> + * Check the Q_REG_TX_MEMORY_STATUS for all quads. A set bit indicates
> + * a waiting timestamp.
> + *
> + * Return: 1 if any quad has at least one timestamp ready bit set,
> + * 0 otherwise, and a negative error value if unable to read the bitmap.
> + */
> +static int ice_check_phy_tx_tstamp_ready_e82x(struct ice_hw *hw)
> +{
> + int quad;
> +
> + for (quad = 0; quad < ICE_GET_QUAD_NUM(hw->ptp.num_lports); quad++) {
> + u64 tstamp_ready;
> + int err;
> +
> + err = ice_get_phy_tx_tstamp_ready(hw, quad, &tstamp_ready);
> + if (err)
> + return err;
> +
> + if (tstamp_ready)
> + return 1;
> + }
> +
> + return 0;
> +}
> +
> /**
> * ice_phy_cfg_intr_e82x - Configure TX timestamp interrupt
> * @hw: pointer to the HW struct
> @@ -4870,6 +4928,23 @@ ice_get_phy_tx_tstamp_ready_e810(struct ice_hw *hw, u8 port, u64 *tstamp_ready)
> return 0;
> }
>
> +/**
> + * ice_check_phy_tx_tstamp_ready_e810 - Check Tx memory status register
> + * @hw: pointer to the HW struct
> + *
> + * The E810 devices do not have a Tx memory status register. Note this is
> + * intentionally different behavior from ice_get_phy_tx_tstamp_ready_e810
> + * which always says that all bits are ready. This function is called in cases
> + * where code will trigger interrupts if timestamps are waiting, and should
> + * not be called for E810 hardware.
> + *
> + * Return: 0.
> + */
> +static int ice_check_phy_tx_tstamp_ready_e810(struct ice_hw *hw)
> +{
> + return 0;
> +}
> +
> /* E810 SMA functions
> *
> * The following functions operate specifically on E810 hardware and are used
> @@ -5124,6 +5199,21 @@ static void ice_get_phy_tx_tstamp_ready_e830(const struct ice_hw *hw, u8 port,
> *tstamp_ready |= rd32(hw, E830_PRTMAC_TS_TX_MEM_VALID_L);
> }
>
> +/**
> + * ice_check_phy_tx_tstamp_ready_e830 - Check Tx memory status register
> + * @hw: pointer to the HW struct
> + *
> + * Return: 1 if the device has waiting timestamps, 0 otherwise.
> + */
> +static int ice_check_phy_tx_tstamp_ready_e830(struct ice_hw *hw)
> +{
> + u64 tstamp_ready;
> +
> + ice_get_phy_tx_tstamp_ready_e830(hw, 0, &tstamp_ready);
> +
> + return !!tstamp_ready;
> +}
> +
> /**
> * ice_ptp_init_phy_e830 - initialize PHY parameters
> * @ptp: pointer to the PTP HW struct
> @@ -5716,6 +5806,33 @@ int ice_get_phy_tx_tstamp_ready(struct ice_hw *hw, u8 block, u64 *tstamp_ready)
> }
> }
>
> +/**
> + * ice_check_phy_tx_tstamp_ready - Check PHY Tx timestamp memory status
> + * @hw: pointer to the HW struct
> + *
> + * Check the PHY for Tx timestamp memory status on all ports. If you need to
> + * see individual timestamp status for each index, use
> + * ice_get_phy_tx_tstamp_ready() instead.
> + *
> + * Return: 1 if any port has timestamps available, 0 if there are no timestamps
> + * available, and a negative error code on failure.
> + */
> +int ice_check_phy_tx_tstamp_ready(struct ice_hw *hw)
> +{
> + switch (hw->mac_type) {
> + case ICE_MAC_E810:
> + return ice_check_phy_tx_tstamp_ready_e810(hw);
> + case ICE_MAC_E830:
> + return ice_check_phy_tx_tstamp_ready_e830(hw);
> + case ICE_MAC_GENERIC:
> + return ice_check_phy_tx_tstamp_ready_e82x(hw);
> + case ICE_MAC_GENERIC_3K_E825:
> + return ice_check_phy_tx_tstamp_ready_eth56g(hw);
> + default:
> + return -EOPNOTSUPP;
> + }
> +}
> +
> /**
> * ice_cgu_get_pin_desc_e823 - get pin description array
> * @hw: pointer to the hw struct
>
Reviewed-by: Petr Oros <poros@redhat.com>
^ permalink raw reply [flat|nested] 9+ messages in thread
* [PATCH iwl-net 4/4] ice: fix ice_ptp_read_tx_hwtstamp_status_eth56g
2026-04-08 18:46 [PATCH iwl-net 0/4] ice: E825C missing PHY timestamp interrupt fixes Jacob Keller
` (2 preceding siblings ...)
2026-04-08 18:46 ` [PATCH iwl-net 3/4] ice: fix ready bitmap check for non-E822 devices Jacob Keller
@ 2026-04-08 18:46 ` Jacob Keller
2026-04-10 9:34 ` [Intel-wired-lan] " Petr Oros
3 siblings, 1 reply; 9+ messages in thread
From: Jacob Keller @ 2026-04-08 18:46 UTC (permalink / raw)
To: Anthony Nguyen, Intel Wired LAN, netdev
Cc: Grzegorz Nitka, Timothy Miskell, Aleksandr Loktionov,
Jacob Keller
The ice_ptp_read_tx_hwtstamp_status_eth56g function calls
ice_read_phy_eth56g with a PHY index. However the function actually expects
a port index. This causes the function to read the wrong PHY_PTP_INT_STATUS
registers, and effectively makes the status wrong for the second set of
ports from 4 to 7.
The ice_read_phy_eth56g function uses the provided port index to determine
which PHY device to read. We could refactor the entire chain to take a PHY
index, but this would impact many code sites. Instead, multiply the PHY
index by the number of ports, so that we read from the first port of each
PHY.
Fixes: 7cab44f1c35f ("ice: Introduce ETH56G PHY model for E825C products")
Signed-off-by: Jacob Keller <jacob.e.keller@intel.com>
Reviewed-by: Aleksandr Loktionov <aleksandr.loktionov@intel.com>
---
drivers/net/ethernet/intel/ice/ice_ptp_hw.c | 10 ++++++++--
1 file changed, 8 insertions(+), 2 deletions(-)
diff --git a/drivers/net/ethernet/intel/ice/ice_ptp_hw.c b/drivers/net/ethernet/intel/ice/ice_ptp_hw.c
index 64ad5ed5c688..672218e5d1f9 100644
--- a/drivers/net/ethernet/intel/ice/ice_ptp_hw.c
+++ b/drivers/net/ethernet/intel/ice/ice_ptp_hw.c
@@ -2219,13 +2219,19 @@ int ice_ptp_read_tx_hwtstamp_status_eth56g(struct ice_hw *hw, u32 *ts_status)
*ts_status = 0;
for (phy = 0; phy < params->num_phys; phy++) {
+ u8 port;
int err;
- err = ice_read_phy_eth56g(hw, phy, PHY_PTP_INT_STATUS, &status);
+ /* ice_read_phy_eth56g expects a port index, so use the first
+ * port of the PHY
+ */
+ port = phy * hw->ptp.ports_per_phy;
+
+ err = ice_read_phy_eth56g(hw, port, PHY_PTP_INT_STATUS, &status);
if (err)
return err;
- *ts_status |= (status & mask) << (phy * hw->ptp.ports_per_phy);
+ *ts_status |= (status & mask) << port;
}
ice_debug(hw, ICE_DBG_PTP, "PHY interrupt err: %x\n", *ts_status);
--
2.53.0.1066.g1eceb487f285
^ permalink raw reply related [flat|nested] 9+ messages in thread* Re: [Intel-wired-lan] [PATCH iwl-net 4/4] ice: fix ice_ptp_read_tx_hwtstamp_status_eth56g
2026-04-08 18:46 ` [PATCH iwl-net 4/4] ice: fix ice_ptp_read_tx_hwtstamp_status_eth56g Jacob Keller
@ 2026-04-10 9:34 ` Petr Oros
0 siblings, 0 replies; 9+ messages in thread
From: Petr Oros @ 2026-04-10 9:34 UTC (permalink / raw)
To: Jacob Keller, Anthony Nguyen, Intel Wired LAN, netdev
Cc: Aleksandr Loktionov, Timothy Miskell
On 4/8/26 20:46, Jacob Keller wrote:
> The ice_ptp_read_tx_hwtstamp_status_eth56g function calls
> ice_read_phy_eth56g with a PHY index. However the function actually expects
> a port index. This causes the function to read the wrong PHY_PTP_INT_STATUS
> registers, and effectively makes the status wrong for the second set of
> ports from 4 to 7.
>
> The ice_read_phy_eth56g function uses the provided port index to determine
> which PHY device to read. We could refactor the entire chain to take a PHY
> index, but this would impact many code sites. Instead, multiply the PHY
> index by the number of ports, so that we read from the first port of each
> PHY.
>
> Fixes: 7cab44f1c35f ("ice: Introduce ETH56G PHY model for E825C products")
> Signed-off-by: Jacob Keller <jacob.e.keller@intel.com>
> Reviewed-by: Aleksandr Loktionov <aleksandr.loktionov@intel.com>
> ---
> drivers/net/ethernet/intel/ice/ice_ptp_hw.c | 10 ++++++++--
> 1 file changed, 8 insertions(+), 2 deletions(-)
>
> diff --git a/drivers/net/ethernet/intel/ice/ice_ptp_hw.c b/drivers/net/ethernet/intel/ice/ice_ptp_hw.c
> index 64ad5ed5c688..672218e5d1f9 100644
> --- a/drivers/net/ethernet/intel/ice/ice_ptp_hw.c
> +++ b/drivers/net/ethernet/intel/ice/ice_ptp_hw.c
> @@ -2219,13 +2219,19 @@ int ice_ptp_read_tx_hwtstamp_status_eth56g(struct ice_hw *hw, u32 *ts_status)
> *ts_status = 0;
>
> for (phy = 0; phy < params->num_phys; phy++) {
> + u8 port;
> int err;
>
> - err = ice_read_phy_eth56g(hw, phy, PHY_PTP_INT_STATUS, &status);
> + /* ice_read_phy_eth56g expects a port index, so use the first
> + * port of the PHY
> + */
> + port = phy * hw->ptp.ports_per_phy;
> +
> + err = ice_read_phy_eth56g(hw, port, PHY_PTP_INT_STATUS, &status);
> if (err)
> return err;
>
> - *ts_status |= (status & mask) << (phy * hw->ptp.ports_per_phy);
> + *ts_status |= (status & mask) << port;
> }
>
> ice_debug(hw, ICE_DBG_PTP, "PHY interrupt err: %x\n", *ts_status);
Reviewed-by: Petr Oros <poros@redhat.com>
^ permalink raw reply [flat|nested] 9+ messages in thread