git: b68f4667612f - main - aq(4): PHY thermal-shutdown handling and correctness fixes
Adrian Chadd <[email protected]> Sun, 02 Aug 2026 23:10:33 +0000
| Newsgroups | gmane.os.freebsd.devel.cvs.src |
|---|---|
| Message-ID | <6a6fce69.42365.17d7cea__41538.4618494258$1785712350$gmane$org@gitrepo.freebsd.org> |
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,