The branch main has been updated by adrian:

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

commit b68f4667612ff8cde2fefc94f8b31377b128ea7e
Author:     Nick Price <[email protected]>
AuthorDate: 2026-08-02 22:44:33 +0000
Commit:     Adrian Chadd <[email protected]>
CommitDate: 2026-08-02 22:44:33 +0000

    aq(4): PHY thermal-shutdown handling and correctness fixes
    
    Fold the thermal-protection work and the correctness fixes that landed
    alongside it.
    
    Report and auto-recover from PHY thermal shutdown.  The Atlantic PHYs can
    autonomously shut down on over-temperature, latching global fault 0x8007
    and dropping the link; Atlantic 2 ships this armed, Atlantic 1 disabled.
    Arm it on Atlantic 1 at interface init (1E.C478.A via the MAC's MDIO
    controller), and recover from a trip automatically: the admin-status poll
    detects the fault, logs the shutdown limit and measured temperature, and
    holds the link down until the PHY cools, then restores it -- Atlantic 1
    needs a PHY reset (1E.2681.0) with the MAC firmware running plus a full
    re-init, Atlantic 2 recovers on the re-init alone.  New firmware ops
    get_phy_fault, phy_reset, thermal_arm, and get_thermal_limit back the
    state machine in aq_if_update_admin_status().
    
    Make that Atlantic 1 thermal MDIO path address-correct and fail-safe.
    The direct-MDIO helpers hardcoded the Clause-45 port address to 0, but it
    is strap-selectable: on a board whose PHY answers elsewhere every thermal
    op targeted nothing, so arming silently no-oped and the post-trip reset
    never cleared the latch.  Discover the address by scanning ports 0..31
    for a PMA/PMD identifier and form it as (phy_id << 5) | mmd, marking it
    valid only when a PHY actually answers.  aq_fw2x_phy_read also returned 0
    on a semaphore timeout, indistinguishable from a real 1E.C478 == 0, so
    thermal_arm could zero live provisioning bits; give the read an error
    return and gate thermal_arm and get_thermal_limit on it.
    
    Bound the multicast filter slot index.  aq_mc_filter_apply() programmed
    slot count + 1 and bailed only at count == AQ_HW_MAC_MAX (33), one
    address too late, so a 33rd entry raced in between the if_llmaddr_count()
    snapshot and the if_foreach_llmaddr() walk drove an out-of-bounds MMIO
    write to slot 33.  Fire the guard at AQ_HW_MAC_MAX - 1, and also reject
    index >= AQ_HW_MAC_MAX in aq_hw_mac_addr_set() where the slot becomes an
    RPF register offset.
    
    Correctness and safety fixes: initialize the sysctl context in
    attach_pre so the iflib fail-path detach cannot sysctl_ctx_free() an
    uninitialized list (a page fault when MSI/MSI-X is denied); range-check
    the Atlantic 2 action-resolver table index, taken verbatim from a
    firmware-supplied base, before writing the ART registers; and accumulate
    statistics deltas as unsigned, since AQ_SDELTA discarded a forward delta
    of 2^31 or more at 10G across a stretched admin poll.
    
    Signed-off-by: Nick Price <[email protected]>
    
    Reviewed by:    adrian
    Differential Revision:  https://reviews.freebsd.org/D58435
---
 sys/dev/aq/aq2_fw.c    |  51 ++++++++++++++-
 sys/dev/aq/aq2_hw.h    |  12 ++++
 sys/dev/aq/aq_device.h |  10 +++
 sys/dev/aq/aq_fw.h     |  15 +++++
 sys/dev/aq/aq_fw2x.c   | 167 ++++++++++++++++++++++++++++++++++++++++++++++++-
 sys/dev/aq/aq_hw.c     |  13 +++-
 sys/dev/aq/aq_hw.h     |   4 ++
 sys/dev/aq/aq_irq.c    | 103 +++++++++++++++++++++++++++++-
 sys/dev/aq/aq_main.c   |  14 ++++-
 9 files changed, 380 insertions(+), 9 deletions(-)

diff --git a/sys/dev/aq/aq2_fw.c b/sys/dev/aq/aq2_fw.c
index a9353a9cfa96..9dc477dcf8e9 100644
--- a/sys/dev/aq/aq2_fw.c
+++ b/sys/dev/aq/aq2_fw.c
@@ -42,6 +42,7 @@ static int aq2_fw_get_mode(struct aq_hw *hw, enum 
aq_hw_fw_mpi_state *mode,
 static int aq2_fw_get_mac_addr(struct aq_hw *hw, uint8_t *mac);
 static int aq2_fw_get_stats(struct aq_hw *hw, struct aq_hw_stats *stats);
 static int aq2_fw_get_temp(struct aq_hw *hw, int *temp_mc);
+static int aq2_fw_get_thermal_limit(struct aq_hw *hw, int *limit_mc);
 
 /* Coherent OUT-window read, bracketed by the transaction id. */
 static int
@@ -490,7 +491,7 @@ aq2_fw_get_temp(struct aq_hw *hw, int *temp_mc)
                return (ENXIO);
        }
 
-       /* F/W reports whole degrees Celsius. */
+       /* F/W reports whole degrees Celsius, signed. */
        *temp_mc = (int)(int8_t)((raw &
            AQ2_FW_INTERFACE_OUT_PHY_TEMPERATURE) >>
            AQ2_FW_INTERFACE_OUT_PHY_TEMPERATURE_S) * 1000;
@@ -498,6 +499,50 @@ aq2_fw_get_temp(struct aq_hw *hw, int *temp_mc)
        return (0);
 }
 
+/* interface-in thermal_shutdown.shutdown_temperature, whole degC. */
+static int
+aq2_fw_get_thermal_limit(struct aq_hw *hw, int *limit_mc)
+{
+       uint32_t v;
+       int temp_c;
+
+       v = AQ_READ_REG(hw, AQ2_FW_INTERFACE_IN_THERMAL_SHUTDOWN_REG);
+       temp_c = (v & AQ2_FW_INTERFACE_IN_THERMAL_SHUTDOWN_TEMP) >>
+           AQ2_FW_INTERFACE_IN_THERMAL_SHUTDOWN_TEMP_S;
+       if (temp_c == 0 || temp_c == 0xff)
+               return (ENXIO);
+       *limit_mc = temp_c * 1000;
+
+       return (0);
+}
+
+static int
+aq2_fw_get_phy_fault(struct aq_hw *hw, uint16_t *fault)
+{
+       uint32_t health, code;
+       int err;
+
+       err = aq2_fw_interface_buffer_read(hw,
+           AQ2_FW_INTERFACE_OUT_PHY_HEALTH_MONITOR_REG, &health,
+           sizeof(health));
+       if (err != 0)
+               return (err);
+
+       if ((health & AQ2_FW_INTERFACE_OUT_PHY_HEALTH_MONITOR_FAULT) == 0) {
+               *fault = 0;
+               return (0);
+       }
+
+       err = aq2_fw_interface_buffer_read(hw,
+           AQ2_FW_INTERFACE_OUT_PHY_FAULT_CODE_REG, &code, sizeof(code));
+       if (err != 0)
+               return (err);
+
+       *fault = (uint16_t)(code & AQ2_FW_INTERFACE_OUT_PHY_FAULT_CODE);
+
+       return (0);
+}
+
 const struct aq_firmware_ops aq2_fw_ops = {
        .reset = aq2_fw_reset,
        .set_mode = aq2_fw_set_mode,
@@ -505,5 +550,9 @@ const struct aq_firmware_ops aq2_fw_ops = {
        .get_mac_addr = aq2_fw_get_mac_addr,
        .get_stats = aq2_fw_get_stats,
        .get_temp = aq2_fw_get_temp,
+       .get_phy_fault = aq2_fw_get_phy_fault,
+       .phy_reset = NULL,      /* A2 clears thermal shutdown on its own reset 
*/
+       .thermal_arm = NULL,    /* A2 firmware ships thermal shutdown armed */
+       .get_thermal_limit = aq2_fw_get_thermal_limit,
        .led_control = NULL,
 };
diff --git a/sys/dev/aq/aq2_hw.h b/sys/dev/aq/aq2_hw.h
index d707e2dad51e..1f68a9c4a1d0 100644
--- a/sys/dev/aq/aq2_hw.h
+++ b/sys/dev/aq/aq2_hw.h
@@ -194,6 +194,11 @@ aq_is_atlantic2(uint16_t device_id)
 #define  AQ2_FW_INTERFACE_IN_LINK_OPTIONS_LINK_RENEGOTIATE (1 << 1)
 #define  AQ2_FW_INTERFACE_IN_LINK_OPTIONS_LINK_UP      (1 << 0)
 
+#define AQ2_FW_INTERFACE_IN_THERMAL_SHUTDOWN_REG       0x12020
+#define  AQ2_FW_INTERFACE_IN_THERMAL_SHUTDOWN_ENABLE   (1 << 0)
+#define  AQ2_FW_INTERFACE_IN_THERMAL_SHUTDOWN_TEMP     0x0000ff00      /* 
whole degC */
+#define  AQ2_FW_INTERFACE_IN_THERMAL_SHUTDOWN_TEMP_S   8
+
 #define AQ2_FW_INTERFACE_IN_REQUEST_POLICY_REG 0x12a58
 #define  AQ2_FW_INTERFACE_IN_REQUEST_POLICY_MCAST_QUEUE_OR_TC  0x00800000
 #define  AQ2_FW_INTERFACE_IN_REQUEST_POLICY_MCAST_RX_QUEUE_TC_INDEX 0x007c0000
@@ -231,9 +236,13 @@ aq_is_atlantic2(uint16_t device_id)
 
 #define AQ2_FW_INTERFACE_OUT_PHY_HEALTH_MONITOR_REG    0x13620
 #define  AQ2_FW_INTERFACE_OUT_PHY_HEALTH_MONITOR_READY 0x00000001
+#define  AQ2_FW_INTERFACE_OUT_PHY_HEALTH_MONITOR_FAULT 0x00000002
 #define  AQ2_FW_INTERFACE_OUT_PHY_TEMPERATURE          0x0000ff00
 #define  AQ2_FW_INTERFACE_OUT_PHY_TEMPERATURE_S                8
 
+#define AQ2_FW_INTERFACE_OUT_PHY_FAULT_CODE_REG                0x13624
+#define  AQ2_FW_INTERFACE_OUT_PHY_FAULT_CODE           0x0000ffff
+
 #define AQ2_FW_INTERFACE_OUT_STATS_REG         0x13700
 
 #define AQ2_FW_INTERFACE_OUT_LINK_STATUS_REG   0x13014
@@ -260,6 +269,9 @@ aq_is_atlantic2(uint16_t device_id)
 #define AQ2_RPF_ACT_ART_REQ_MASK_REG(i)                (0x14004 + (i) * 0x10)
 #define AQ2_RPF_ACT_ART_REQ_ACTION_REG(i)      (0x14008 + (i) * 0x10)
 
+/* Action-resolver table depth: 16 enable sections of 8 rows. */
+#define AQ2_ART_TABLE_SIZE                     128
+
 /*
  * L2 unicast filter MSW register (shared with Atlantic 1).  Atlantic 2 sets
  * the TAG field so unicast frames are classified into the resolver table.
diff --git a/sys/dev/aq/aq_device.h b/sys/dev/aq/aq_device.h
index 6c2453d8277c..1ace227974a5 100644
--- a/sys/dev/aq/aq_device.h
+++ b/sys/dev/aq/aq_device.h
@@ -139,6 +139,16 @@ struct aq_dev {
        uint32_t          tx_rings_count;
        uint32_t          rx_rings_count;
        bool              linkup;
+       uint16_t          phy_fault_last;       /* last fault code reported */
+       enum aq_thermal_state {
+               AQ_THERMAL_NORMAL = 0,  /* no thermal shutdown pending */
+               AQ_THERMAL_COOLING,     /* shut down; waiting to cool */
+               AQ_THERMAL_SETTLING,    /* PHY reset; waiting to re-init */
+       }                 thermal_state;
+       int               thermal_settle;
+       int               thermal_retry_ticks;  /* earliest tick to retry at */
+       int               thermal_temp_mc;      /* temp at the last 
shutdown/cool */
+       int               thermal_recover_mc;   /* recover once cooled to here 
*/
        int               media_active;
 
        struct aq_hw_stats  last_stats;
diff --git a/sys/dev/aq/aq_fw.h b/sys/dev/aq/aq_fw.h
index ee45aedfc4e1..a7e880df51a7 100644
--- a/sys/dev/aq/aq_fw.h
+++ b/sys/dev/aq/aq_fw.h
@@ -71,9 +71,24 @@ struct aq_firmware_ops
        /* Reports millidegrees Celsius. */
        int (*get_temp)(struct aq_hw* hw, int* temp_mc);
 
+       /* Reports the PHY global fault code; zero means no fault. */
+       int (*get_phy_fault)(struct aq_hw* hw, uint16_t* fault);
+
+       /* Resets the PHY (clears a latched thermal shutdown). */
+       int (*phy_reset)(struct aq_hw* hw);
+
+       /* Arms the firmware autonomous thermal shutdown (A2 ships armed). */
+       int (*thermal_arm)(struct aq_hw* hw);
+
+       /* Reports the PHY high-temperature shutdown threshold (millidegrees 
C). */
+       int (*get_thermal_limit)(struct aq_hw* hw, int* limit_mc);
+
        int (*led_control)(struct aq_hw* hw, uint32_t mode);
 };
 
+/* PHY global fault codes, register 1E.C850. */
+#define        AQ_PHY_FAULT_THERMAL_SHUTDOWN   0x8007
+
 /* aq_fw1x/aq_fw2x: Atlantic 1 firmware ABIs; aq2_fw: Atlantic 2 (AQC11x). */
 extern const struct aq_firmware_ops aq_fw1x_ops;
 extern const struct aq_firmware_ops aq_fw2x_ops;
diff --git a/sys/dev/aq/aq_fw2x.c b/sys/dev/aq/aq_fw2x.c
index 0f4bb7a89906..be1baa93c2ab 100644
--- a/sys/dev/aq/aq_fw2x.c
+++ b/sys/dev/aq/aq_fw2x.c
@@ -88,7 +88,7 @@ enum aq_fw2x_caps_hi {
        CAPS_HI_TEMPERATURE,
        CAPS_HI_DOWNSHIFT,
        CAPS_HI_PTP_AVB_EN,
-       CAPS_HI_MEDIA_DETECT,
+       CAPS_HI_THERMAL_SHUTDOWN,
        CAPS_HI_LINK_DROP,
        CAPS_HI_SLEEP_PROXY,
        CAPS_HI_WOL,
@@ -499,6 +499,167 @@ aq_fw2x_get_temp(struct aq_hw* hw, int* temp_mc)
        return (0);
 }
 
+static int
+aq_fw2x_get_phy_fault(struct aq_hw* hw, uint16_t* fault)
+{
+       uint32_t raw;
+       int err;
+
+       mtx_lock(&hw->fw_mtx);
+       err = aq_hw_fw_downld_dwords(hw, hw->mbox_addr +
+           offsetof(struct aq_fw2x_mailbox, phy_h_bit), &raw, 1);
+       mtx_unlock(&hw->fw_mtx);
+
+       if (err != 0)
+               return (err);
+
+       *fault = (uint16_t)(raw >> 16);
+
+       return (0);
+}
+
+/* PHY MDIO access: MMD register read/write via the MAC's MDIO controller. */
+#define AQ_MDIO_IFACE(n)       (0x280 + (((n) - 1) * 4))
+#define AQ_MDIO_BUSY           0x80000000u     /* iface2 bit 31 */
+#define AQ_MDIO_EXECUTE                0x00008000u     /* iface2 bit 15 */
+#define AQ_MDIO_OP_S           12              /* iface2 bits 13:12 */
+#define AQ_MDIO_OP_ADDR                3
+#define AQ_MDIO_OP_READ                1
+#define AQ_MDIO_OP_WRITE       2
+#define AQ_MDIO_PHYADDR_MSK    0x3ffu
+#define AQ_FW_SM_MDIO          0               /* cpu semaphore index */
+
+#define AQ_PHY_ID_MAX          32              /* MDIO port addresses to scan 
*/
+#define AQ_MDIO_MMD_PMAPMD     0x01            /* PMA/PMD MMD */
+#define AQ_PHY_ID2_REG         0x0003          /* PMA/PMD Device Identifier 2 
*/
+
+#define AQ_PHY_MMD_GLOBAL      0x1e
+#define AQ_PHY_RESET_REG       0x2681          /* 1E.2681.0 = PHY hard reset */
+#define  AQ_PHY_RESET          0x0001
+#define AQ_PHY_THERMAL_CTRL_REG        0xc478          /* 1E.C478 thermal 
control */
+#define  AQ_PHY_THERMAL_SD_EN  0x0400          /* .A thermalShutdownEnable */
+
+static uint16_t
+aq_fw2x_mdio_op(struct aq_hw* hw, uint16_t mmd, uint16_t addr, int write,
+    uint16_t data)
+{
+       uint32_t pa = (((uint32_t)hw->phy_id & 0x1f) << 5) | (mmd & 0x1f);
+
+       AQ_WRITE_REG(hw, AQ_MDIO_IFACE(4), addr);
+       AQ_WRITE_REG(hw, AQ_MDIO_IFACE(2),
+           AQ_MDIO_EXECUTE | (AQ_MDIO_OP_ADDR << AQ_MDIO_OP_S) | pa);
+       AQ_HW_WAIT_FOR((AQ_READ_REG(hw, AQ_MDIO_IFACE(2)) & AQ_MDIO_BUSY) == 0,
+           10, 10000);
+       if (write) {
+               AQ_WRITE_REG(hw, AQ_MDIO_IFACE(3), data);
+               AQ_WRITE_REG(hw, AQ_MDIO_IFACE(2),
+                   AQ_MDIO_EXECUTE | (AQ_MDIO_OP_WRITE << AQ_MDIO_OP_S) | pa);
+       } else {
+               AQ_WRITE_REG(hw, AQ_MDIO_IFACE(2),
+                   AQ_MDIO_EXECUTE | (AQ_MDIO_OP_READ << AQ_MDIO_OP_S) | pa);
+       }
+       AQ_HW_WAIT_FOR((AQ_READ_REG(hw, AQ_MDIO_IFACE(2)) & AQ_MDIO_BUSY) == 0,
+           10, 10000);
+       return ((uint16_t)AQ_READ_REG(hw, AQ_MDIO_IFACE(5)));
+}
+
+/* MDIO is serialized against the F/W by cpu semaphore 0. */
+static void
+aq_fw2x_phy_write(struct aq_hw* hw, uint16_t mmd, uint16_t addr, uint16_t data)
+{
+       if (AQ_HW_WAIT_FOR(reg_glb_cpu_sem_get(hw, AQ_FW_SM_MDIO) == 1U,
+           10, 10000) != 0)
+               return;
+       aq_fw2x_mdio_op(hw, mmd, addr, 1, data);
+       reg_glb_cpu_sem_set(hw, 1U, AQ_FW_SM_MDIO);
+}
+
+static int
+aq_fw2x_phy_read(struct aq_hw* hw, uint16_t mmd, uint16_t addr, uint16_t* val)
+{
+       if (AQ_HW_WAIT_FOR(reg_glb_cpu_sem_get(hw, AQ_FW_SM_MDIO) == 1U,
+           10, 10000) != 0)
+               return (ETIMEDOUT);
+       *val = aq_fw2x_mdio_op(hw, mmd, addr, 0, 0);
+       reg_glb_cpu_sem_set(hw, 1U, AQ_FW_SM_MDIO);
+
+       return (0);
+}
+
+/* Discover the PHY's MDIO port address; it is strap-selectable, not fixed at 
0. */
+static bool
+aq_fw2x_init_phy_id(struct aq_hw* hw)
+{
+       uint16_t val;
+       uint8_t id;
+
+       for (id = 0; id < AQ_PHY_ID_MAX; id++) {
+               hw->phy_id = id;
+               if (aq_fw2x_phy_read(hw, AQ_MDIO_MMD_PMAPMD, AQ_PHY_ID2_REG,
+                   &val) == 0 && val != 0xffff)
+                       return (true);
+       }
+       hw->phy_id = 0;
+       return (false);
+}
+
+/* Called with fw_mtx held; the port address is needed by every MDIO caller. */
+static void
+aq_fw2x_phy_id_probe(struct aq_hw* hw)
+{
+       if (!hw->phy_id_valid && aq_fw2x_init_phy_id(hw))
+               hw->phy_id_valid = true;
+}
+
+/* PHY hard reset (1E.2681.0): clears a latched thermal shutdown a MAC reset 
cannot. */
+static int
+aq_fw2x_phy_reset(struct aq_hw* hw)
+{
+       mtx_lock(&hw->fw_mtx);
+       aq_fw2x_phy_id_probe(hw);
+       aq_fw2x_phy_write(hw, AQ_PHY_MMD_GLOBAL, AQ_PHY_RESET_REG, 
AQ_PHY_RESET);
+       mtx_unlock(&hw->fw_mtx);
+
+       return (0);
+}
+
+/* Arm autonomous thermal shutdown (1E.C478.A), cleared by any PHY reset. */
+static int
+aq_fw2x_thermal_arm(struct aq_hw* hw)
+{
+       uint16_t ctrl;
+
+       mtx_lock(&hw->fw_mtx);
+       aq_fw2x_phy_id_probe(hw);
+       if (aq_fw2x_phy_read(hw, AQ_PHY_MMD_GLOBAL, AQ_PHY_THERMAL_CTRL_REG,
+           &ctrl) == 0 && ctrl != 0xffff && (ctrl & AQ_PHY_THERMAL_SD_EN) == 0)
+               aq_fw2x_phy_write(hw, AQ_PHY_MMD_GLOBAL,
+                   AQ_PHY_THERMAL_CTRL_REG, ctrl | AQ_PHY_THERMAL_SD_EN);
+       mtx_unlock(&hw->fw_mtx);
+
+       return (0);
+}
+
+/* 1E.C421 high-temp shutdown threshold, degrees C in Q8.8 fixed point. */
+#define AQ_PHY_THERMAL_HIGH_REG        0xc421
+static int
+aq_fw2x_get_thermal_limit(struct aq_hw* hw, int* limit_mc)
+{
+       uint16_t raw;
+       int err;
+
+       mtx_lock(&hw->fw_mtx);
+       aq_fw2x_phy_id_probe(hw);
+       err = aq_fw2x_phy_read(hw, AQ_PHY_MMD_GLOBAL, AQ_PHY_THERMAL_HIGH_REG,
+           &raw);
+       mtx_unlock(&hw->fw_mtx);
+       if (err != 0 || raw == 0 || raw == 0xffff)
+               return (ENXIO);
+       *limit_mc = (int)(int16_t)raw * 1000 / 256;
+
+       return (0);
+}
+
 static int
 aq_fw2x_led_control(struct aq_hw* hw, uint32_t onoff)
 {
@@ -526,6 +687,10 @@ const struct aq_firmware_ops aq_fw2x_ops =
        .get_mac_addr = aq_fw2x_get_mac_addr,
        .get_stats = aq_fw2x_get_stats,
        .get_temp = aq_fw2x_get_temp,
+       .get_phy_fault = aq_fw2x_get_phy_fault,
+       .phy_reset = aq_fw2x_phy_reset,
+       .thermal_arm = aq_fw2x_thermal_arm,
+       .get_thermal_limit = aq_fw2x_get_thermal_limit,
 
        .led_control = aq_fw2x_led_control,
 };
diff --git a/sys/dev/aq/aq_hw.c b/sys/dev/aq/aq_hw.c
index ca733ffa7e3d..a7dae250c66e 100644
--- a/sys/dev/aq/aq_hw.c
+++ b/sys/dev/aq/aq_hw.c
@@ -598,13 +598,20 @@ static int
 aq2_art_filter_set(struct aq_hw *hw, uint32_t idx, uint32_t tag,
     uint32_t mask, uint32_t action)
 {
+       idx += hw->art_filter_base_index;
+       if (idx >= AQ2_ART_TABLE_SIZE) {
+               device_printf(hw->dev,
+                   "ART index %u out of range (firmware base %u)\n", idx,
+                   hw->art_filter_base_index);
+               return (EINVAL);
+       }
+
        if (AQ_HW_WAIT_FOR(reg_glb_cpu_sem_get(hw, AQ2_ART_SEM_INDEX) == 1U,
            10U, 1000U) != 0) {
                device_printf(hw->dev, "ART semaphore timeout, idx %u\n", idx);
                return (EBUSY);
        }
 
-       idx += hw->art_filter_base_index;
        AQ_WRITE_REG(hw, AQ2_RPF_ACT_ART_REQ_TAG_REG(idx), tag);
        AQ_WRITE_REG(hw, AQ2_RPF_ACT_ART_REQ_MASK_REG(idx), mask);
        AQ_WRITE_REG(hw, AQ2_RPF_ACT_ART_REQ_ACTION_REG(idx), action);
@@ -739,6 +746,10 @@ aq_hw_mac_addr_set(struct aq_hw *hw, uint8_t *mac_addr, 
uint8_t index)
                err = EINVAL;
                goto err_exit;
        }
+       if (index >= AQ_HW_MAC_MAX) {
+               err = EINVAL;
+               goto err_exit;
+       }
        h = (mac_addr[0] << 8) | (mac_addr[1]);
        l = (mac_addr[2] << 24) | (mac_addr[3] << 16) | (mac_addr[4] << 8) |
            mac_addr[5];
diff --git a/sys/dev/aq/aq_hw.h b/sys/dev/aq/aq_hw.h
index c0888f03ab23..1156b33c1904 100644
--- a/sys/dev/aq/aq_hw.h
+++ b/sys/dev/aq/aq_hw.h
@@ -195,6 +195,10 @@ struct aq_hw {
        uint32_t chip_features;
        uint64_t fw_caps;
 
+       /* Atlantic 1: MDIO port address of the PHY, discovered once. */
+       uint8_t phy_id;
+       bool phy_id_valid;
+
        bool lro_enabled;
 
        uint32_t mbox_addr;
diff --git a/sys/dev/aq/aq_irq.c b/sys/dev/aq/aq_irq.c
index ac3b8a66a900..325a957b1e8f 100644
--- a/sys/dev/aq/aq_irq.c
+++ b/sys/dev/aq/aq_irq.c
@@ -51,6 +51,7 @@ __FBSDID("$FreeBSD$");
 #include "aq_dbg.h"
 #include "aq_hw.h"
 #include "aq_hw_llh.h"
+#include "aq_fw.h"
 
 int
 aq_update_hw_stats(struct aq_dev *aq_dev)
@@ -63,9 +64,7 @@ aq_update_hw_stats(struct aq_dev *aq_dev)
                return (0);
 
 #define AQ_SDELTA(_N_) do { \
-       int32_t _d = (int32_t)(stats._N_ - aq_dev->last_stats._N_); \
-       if (_d > 0) \
-               aq_dev->curr_stats._N_ += _d; \
+       aq_dev->curr_stats._N_ += stats._N_ - aq_dev->last_stats._N_; \
 } while (0)
        if (aq_dev->linkup) {
                AQ_SDELTA(uprc);
@@ -115,6 +114,100 @@ aq_update_hw_stats(struct aq_dev *aq_dev)
 }
 
 
+#define        AQ_THERMAL_HYSTERESIS_MC        18000   /* recover this far 
below the limit */
+#define        AQ_THERMAL_RECOVER_MC   90000   /* fallback when the limit is 
unreadable */
+#define        AQ_THERMAL_SETTLE_POLLS 5       /* ~5 s for the PHY reset to 
settle */
+#define        AQ_THERMAL_RETRY_SECS   60      /* minimum spacing between 
recoveries */
+
+/* Temperature here is post-trip; the PHY is already dropping to low power. */
+static void
+aq_thermal_report_shutdown(struct aq_dev *aq_dev)
+{
+       struct aq_hw *hw = &aq_dev->hw;
+       int temp_mc, limit_mc;
+       bool have_temp, have_limit;
+
+       have_temp = hw->fw_ops->get_temp(hw, &temp_mc) == 0;
+       have_limit = hw->fw_ops->get_thermal_limit(hw, &limit_mc) == 0;
+       if (have_temp)
+               aq_dev->thermal_temp_mc = temp_mc;
+       /* The F/W also exposes a cold_temperature hysteresis point. */
+       aq_dev->thermal_recover_mc = have_limit ?
+           limit_mc - AQ_THERMAL_HYSTERESIS_MC : AQ_THERMAL_RECOVER_MC;
+
+       if (have_temp && have_limit)
+               device_printf(aq_dev->dev, "PHY thermal shutdown; "
+                   "limit %d C, temp %d C; holding link down until it cools\n",
+                   limit_mc / 1000, temp_mc / 1000);
+       else if (have_temp)
+               device_printf(aq_dev->dev, "PHY thermal shutdown; "
+                   "temp %d C; holding link down until it cools\n",
+                   temp_mc / 1000);
+       else
+               device_printf(aq_dev->dev, "PHY thermal shutdown; "
+                   "holding link down until it cools\n");
+}
+
+/* Recover after cooldown: A1 needs a PHY reset then re-init, A2 re-inits 
alone. */
+static void
+aq_thermal_poll(struct aq_dev *aq_dev)
+{
+       struct aq_hw *hw = &aq_dev->hw;
+       uint16_t fault;
+       int temp_mc;
+
+       switch (aq_dev->thermal_state) {
+       case AQ_THERMAL_NORMAL:
+               if (aq_dev->linkup)
+                       return;
+               /* The F/W raises the fault a poll after it drops the link. */
+               if (hw->fw_ops->get_phy_fault(hw, &fault) != 0 || fault == 0)
+                       return;
+               /* Report each code once; do not mask a later shutdown. */
+               if (fault == aq_dev->phy_fault_last)
+                       return;
+               aq_dev->phy_fault_last = fault;
+               if (fault != AQ_PHY_FAULT_THERMAL_SHUTDOWN) {
+                       device_printf(aq_dev->dev,
+                           "PHY fault 0x%04x\n", fault);
+                       return;
+               }
+               aq_thermal_report_shutdown(aq_dev);
+               aq_dev->thermal_state = AQ_THERMAL_COOLING;
+               return;
+
+       case AQ_THERMAL_COOLING:
+               if (hw->fw_ops->get_temp(hw, &temp_mc) != 0 ||
+                   temp_mc > aq_dev->thermal_recover_mc)
+                       return;
+               aq_dev->thermal_temp_mc = temp_mc;
+               if (hw->fw_ops->phy_reset != NULL) {
+                       hw->fw_ops->phy_reset(hw);
+                       aq_dev->thermal_settle = 0;
+                       aq_dev->thermal_state = AQ_THERMAL_SETTLING;
+                       return;
+               }
+               /* No PHY reset needed (A2): re-init below restores the link. */
+               break;
+
+       case AQ_THERMAL_SETTLING:
+               if (++aq_dev->thermal_settle < AQ_THERMAL_SETTLE_POLLS)
+                       return;
+               break;
+       }
+
+       /* Space attempts out: recovery costs a re-init and a renegotiation. */
+       if ((int)(ticks - aq_dev->thermal_retry_ticks) < 0)
+               return;
+       aq_dev->thermal_retry_ticks = ticks + AQ_THERMAL_RETRY_SECS * hz;
+
+       device_printf(aq_dev->dev, "PHY cooled to %d C; restoring "
+           "link\n", aq_dev->thermal_temp_mc / 1000);
+       aq_dev->thermal_state = AQ_THERMAL_NORMAL;
+       iflib_request_reset(aq_dev->ctx);
+       iflib_admin_intr_deferred(aq_dev->ctx);
+}
+
 void
 aq_if_update_admin_status(if_ctx_t ctx)
 {
@@ -129,6 +222,7 @@ aq_if_update_admin_status(if_ctx_t ctx)
                device_printf(aq_dev->dev, "atlantic: link UP: speed=%d\n", 
link_speed);
 
                aq_dev->linkup = 1;
+               aq_dev->phy_fault_last = 0;
 
                /* turn on/off RX Pause in RPB */
                rpb_rx_xoff_en_per_tc_set(hw, fc_neg.fc_rx, 0);
@@ -151,6 +245,9 @@ aq_if_update_admin_status(if_ctx_t ctx)
                aq_mediastatus_update(aq_dev, link_speed, &fc_neg);
        }
 
+       if (hw->fw_ops->get_phy_fault != NULL)
+               aq_thermal_poll(aq_dev);
+
        aq_update_hw_stats(aq_dev);
 }
 
diff --git a/sys/dev/aq/aq_main.c b/sys/dev/aq/aq_main.c
index 7c3a88740f3b..4fe9aad42a71 100644
--- a/sys/dev/aq/aq_main.c
+++ b/sys/dev/aq/aq_main.c
@@ -338,6 +338,8 @@ aq_if_attach_pre(if_ctx_t ctx)
        softc = iflib_get_softc(ctx);
        rc = 0;
 
+       sysctl_ctx_init(&softc->aq_sysctl_ctx);
+
        softc->ctx = ctx;
        softc->dev = iflib_get_dev(ctx);
        softc->media = iflib_get_media(ctx);
@@ -374,6 +376,8 @@ aq_if_attach_pre(if_ctx_t ctx)
        hw->fc.fc_rx = 1;
        hw->fc.fc_tx = 1;
        softc->linkup = 0U;
+       /* Set here, not in aq_if_init(): a recovery re-init must not reset it. 
*/
+       softc->thermal_retry_ticks = ticks;
 
        softc->dbg_level = AQ_DBG_LEVEL_DEFAULT;
        softc->dbg_categories = AQ_DBG_CATEGORIES_DEFAULT;
@@ -745,6 +749,8 @@ aq_if_init(if_ctx_t ctx)
 
        atomic_store_rel_long(&hw->flags, 0);
 
+       softc->phy_fault_last = 0;
+       softc->thermal_state = AQ_THERMAL_NORMAL;
        hw->tx_rings_count = softc->tx_rings_count;
 
        err = aq_hw_init(&softc->hw, softc->hw.mac_addr, softc->msix,
@@ -755,6 +761,10 @@ aq_if_init(if_ctx_t ctx)
                return;
        }
 
+       /* aq_hw_init reloads the PHY, resetting the thermal-shutdown arming. */
+       if (hw->fw_ops->thermal_arm != NULL)
+               hw->fw_ops->thermal_arm(hw);
+
        aq_if_media_status(ctx, &ifmr);
 
        aq_update_vlan_filters(softc);
@@ -864,7 +874,7 @@ aq_mc_filter_apply(void *arg, struct sockaddr_dl *dl, u_int 
count)
        struct aq_hw *hw = &softc->hw;
        uint8_t *mac_addr = NULL;
 
-       if (count == AQ_HW_MAC_MAX)
+       if (count >= AQ_HW_MAC_MAX - 1)
                return (0);
 
        mac_addr = LLADDR(dl);
@@ -1425,8 +1435,6 @@ aq_add_stats_sysctls(struct aq_dev *softc)
 #define QUEUE_NAME_LEN 32
        char                    namebuf[QUEUE_NAME_LEN];
 
-       /* Own these oids so aq_if_detach can drain and free them in order. */
-       sysctl_ctx_init(ctx);
        /* RSS configuration */
        SYSCTL_ADD_PROC(ctx, child, OID_AUTO, "print_rss_config",
            CTLTYPE_STRING | CTLFLAG_RD, softc, 0,

Reply via email to