Both Atlantic generations report a PHY die temperature that the driver
never surfaced, so there was no way to tell a card that is running hot
from one that is merely dropping packets.
Add a get_temp method to the firmware ops, reporting millidegrees
Celsius, and publish it per instance as dev.aq.N.temperature in the
usual decikelvin "IK" encoding, so sysctl renders it as degrees.
Atlantic 1 v2 firmware answers through the mailbox: toggle the
temperature bit in the MPI control register, wait for the F/W to echo
it back in the state register, then read phy_temperature, which is a
signed 1/256 degree value. The capability bit is checked first and
ENOTSUP returned when the F/W lacks it. Atlantic 2 keeps a
phy_health_monitor block in the OUT window holding a whole-degree
value. Its address is not derived anywhere in the driver, so compute
it the way the shared-memory layout does and check the result against
the two offsets the driver already knows: link_status at 0x13014 and
statistics at 0x13700 fix the block at 0x13620, which the hardware
confirms by reporting a ready bit, a plausible temperature and a
ticking heartbeat there.
Atlantic 1 v1 firmware has no sensor, so its ops leave get_temp NULL
and no node appears.
The F/W zeroes the Atlantic 2 block until the PHY reports in, which is
still the case when attach runs, so return ENXIO while the ready bit
is clear rather than publishing a false zero. Registration probes
once and only skips the node on ENOTSUP, so a cold PHY still gets a
node that starts answering once the sensor is live.
This is the driver's first firmware accessor that iflib does not
serialise, and it forces a lock. Every other path into the firmware
runs from an ifdi method, and iflib holds its exclusive context sx
across all of them, so the v2 read-modify-write of the 64-bit MPI
control register at 0x368 in set_mode() and get_stats() could not
previously interleave. A sysctl handler is not an ifdi method: iflib
wraps exactly one sysctl of its own in the context lock and leaves
driver-registered oids entirely unserialised, so a temperature read
can land in the middle of the statistics toggle and either write can
discard the other's bit. e1000 hit this same edge and had to take the
iflib context lock explicitly in its NVM sysctl to stop a KASSERT.
Add a per-instance mutex to struct aq_hw and take it across the
read-modify-write in set_mode() and get_stats(), across the paired
register reads in get_mode(), and across the toggle, echo wait and
mailbox read in get_temp(). Locking in the firmware ops rather than
in the sysctl handler follows bnxt and ice, which serialise at the
firmware transport and leave their sysctls bare. A regular MTX_DEF
mutex is sufficient: every firmware path runs in a sleepable context,
and the only interrupt handler, aq_linkstat_isr(), just acknowledges
the interrupt and defers to the admin task. None of the firmware ops
call one another, so the mutex cannot recurse. Destroy it on the
attach_pre failure path as well, since iflib skips ifdi_detach when
ifdi_attach_pre fails.
The v1 and Atlantic 2 ops do not take it. v1 writes its control
register whole rather than modifying it, and while Atlantic 2 does
read-modify-write its IN window, it only does so from ifdi paths;
its sysctl-reachable read touches the OUT window, which the firmware
brackets with a transaction counter. Both carry a comment saying so,
because the asymmetry otherwise reads as an omission.
Tested on a host with an AQC107 and an AQC113 direct-attached at
10Gbase-T. The AQC107 reads 66.6C and the AQC113 52.1C at idle,
rising to 67.9C and 53.1C after thirty seconds of iperf3 moving
20.7 GB at 5.92 Gbit/s, and the AQC113 climbs from 32C to 52C as the
PHY warms up after link establishment. 2400 concurrent reads issued
against both cards while that traffic ran produced no torn values and
no WITNESS reports on a kernel with WITNESS and INVARIANTS.
Signed-off-by: Nick Price <nick@spun.io>