Skip to content

Commit 0229057

Browse files
committed
statd: fix temperature sensor display on Marvell switches
The per-port PHY temperature sensors on Marvell DSA switches show up in 'show hardware' with the name the kernel derives from the full device tree path, e.g. cp0busbusf2000000mdio12a200switch2mdio01, which honestly is completely unreadable and also overruns the value column. Name each sensor after the front-panel port it serves (e1, e2, ...) by matching the PHY's device-tree phandle against each interface's phy-handle. Also: - show system: report a representative SoC temperature on CN913x by matching the ap-* and cp<N>-* thermal zones, hottest wins. - cli-pretty: truncate over-long sensor names so they can never spill into the value column again. Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
1 parent b3450d0 commit 0229057

3 files changed

Lines changed: 76 additions & 9 deletions

File tree

src/bin/show/__init__.py

Lines changed: 18 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -544,6 +544,13 @@ def mdns(args: List[str]) -> None:
544544
cli_pretty(data, "show-mdns")
545545

546546

547+
# Sensor names that represent the SoC/CPU temperature (not per-port PHYs).
548+
# Matches "cpu"/"soc"/"core", and Marvell CN913x "ap-*" / "cp<N>-*" zones.
549+
# Note the hyphen after "cp<N>" so mangled PHY names like "cp0busbus…" never
550+
# match.
551+
SOC_TEMP_RE = re.compile(r'^(cpu|soc|core|ap-|cp\d+-)')
552+
553+
547554
def system(args: List[str]) -> None:
548555
# Get system state from sysrepo
549556
data = get_json("/ietf-system:system-state")
@@ -562,6 +569,7 @@ def system(args: List[str]) -> None:
562569
fan_rpm = None
563570
if hardware_data and "ietf-hardware:hardware" in hardware_data:
564571
components = hardware_data.get("ietf-hardware:hardware", {}).get("component", [])
572+
soc_temps = []
565573
for component in components:
566574
sensor_data = component.get("sensor-data", {})
567575
if not sensor_data:
@@ -570,16 +578,21 @@ def system(args: List[str]) -> None:
570578
name = component.get("name", "")
571579
value_type = sensor_data.get("value-type")
572580

573-
# Only capture CPU/SoC temperature (ignore phy, sfp, etc.)
574-
# Different platforms use different names: cpu, soc, core, etc.
575-
if value_type == "celsius" and name in ("cpu", "soc", "core") and cpu_temp is None:
576-
temp_millidegrees = sensor_data.get("value", 0)
577-
cpu_temp = temp_millidegrees / 1000.0
581+
# Capture SoC/CPU temperature, ignoring per-port phy, sfp, etc.
582+
# Platforms name the zone differently: a plain "cpu"/"soc"/"core",
583+
# or, on Marvell CN913x, an "ap-*" (application processor) or
584+
# "cp<N>-*" (communication processor) cluster. Collect them all
585+
# and report the hottest as the representative SoC temperature.
586+
if value_type == "celsius" and SOC_TEMP_RE.match(name):
587+
soc_temps.append(sensor_data.get("value", 0) / 1000.0)
578588

579589
# Capture fan speed if available
580590
elif value_type == "rpm" and fan_rpm is None:
581591
fan_rpm = sensor_data.get("value", 0)
582592

593+
if soc_temps:
594+
cpu_temp = max(soc_temps)
595+
583596
if cpu_temp is not None:
584597
runtime["cpu_temp"] = cpu_temp
585598
if fan_rpm is not None:

src/statd/python/cli_pretty/cli_pretty.py

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -916,7 +916,12 @@ def print(self, indent=0):
916916
# Standalone sensor without description: use name as-is
917917
display_name = self.name
918918

919-
row = f"{indent_str}{display_name:<{PadSensor.name - len(indent_str)}}"
919+
# Truncate over-long names so they never spill into the VALUE column
920+
# (e.g. unmapped switch-PHY hwmon names derived from the DT path).
921+
field = PadSensor.name - len(indent_str)
922+
if len(display_name) >= field:
923+
display_name = display_name[:field - 2] + "…"
924+
row = f"{indent_str}{display_name:<{field}}"
920925
# For colored value, pad manually to account for ANSI codes
921926
value_str = self.get_formatted_value()
922927
# Count visible characters (strip ANSI codes for length calculation)
@@ -2245,6 +2250,13 @@ def show_services(json):
22452250
service_table.print()
22462251

22472252

2253+
def sensor_sort_key(component):
2254+
"""Natural sort key for sensor names: digit runs compare numerically so
2255+
e2 sorts before e10, while keeping ap-cpu/cp0-ic/sfp groups together."""
2256+
name = component.get("name", "")
2257+
return [int(t) if t.isdigit() else t for t in re.split(r'(\d+)', name)]
2258+
2259+
22482260
def show_hardware(json):
22492261
if not json.get("ietf-hardware:hardware"):
22502262
print("Error, top level \"ietf-hardware:component\" missing")
@@ -2418,15 +2430,16 @@ def show_hardware(json):
24182430
print(f"\n{module_name}:")
24192431

24202432
if module_name in children:
2421-
for child in sorted(children[module_name], key=lambda c: c.get("name", "")):
2433+
for child in sorted(children[module_name], key=sensor_sort_key):
24222434
sensor = Sensor(child)
24232435
sensor.print(indent=1)
24242436

2425-
# Display standalone sensors (no parent)
2437+
# Display standalone sensors (no parent), naturally sorted so port
2438+
# temperatures read e1, e2, ... e28 rather than e1, e10, e11, ...
24262439
if standalone:
24272440
if modules:
24282441
print() # Add blank line between modules and standalone
2429-
for component in sorted(standalone, key=lambda c: c.get("name", "")):
2442+
for component in sorted(standalone, key=sensor_sort_key):
24302443
sensor = Sensor(component)
24312444
sensor.print()
24322445

src/statd/python/yanger/ietf_hardware.py

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -149,6 +149,36 @@ def normalize_sensor_name(name):
149149
return name
150150

151151

152+
def _dt_phandle(path):
153+
"""Read a device-tree phandle cell as a normalized hex string.
154+
155+
phandle/phy-handle properties are 4-byte big-endian cells. Read them via
156+
od(1) so the binary content survives the text-based HOST transport (works
157+
both locally and over the ssh-style remote transport).
158+
"""
159+
out = HOST.run(("od", "-An", "-tx1", path), default="")
160+
return "".join(out.split()) if out else None
161+
162+
163+
def phy_handle_to_ifname():
164+
"""Map a PHY's device-tree phandle to the interface it drives.
165+
166+
DSA user ports carry a "phy-handle" pointing at the PHY that serves them.
167+
The reverse map lets us name a switch PHY's hwmon temperature sensor after
168+
the front-panel port (e.g. e1) instead of the unreadable name the kernel
169+
derives from the full device-tree path (cp0busbusf2000000mdio...).
170+
"""
171+
mapping = {}
172+
for ifname in HOST.run(("ls", "/sys/class/net"), default="").split():
173+
handle_path = os.path.join("/sys/class/net", ifname, "of_node", "phy-handle")
174+
if not HOST.exists(handle_path):
175+
continue
176+
handle = _dt_phandle(handle_path)
177+
if handle:
178+
mapping[handle] = ifname
179+
return mapping
180+
181+
152182
def get_wifi_phy_info():
153183
"""
154184
Discover WiFi PHYs using iw list command.
@@ -218,6 +248,7 @@ def hwmon_sensor_components():
218248
"""
219249
components = []
220250
device_sensors = {} # Track {device_base_name: [list of sensor components]}
251+
phy_ifname = phy_handle_to_ifname()
221252

222253
def add_sensor(base_name, sensor_component):
223254
"""Helper to track sensors per device"""
@@ -244,6 +275,16 @@ def add_sensor(base_name, sensor_component):
244275

245276
base_name = normalize_sensor_name(device_name)
246277

278+
# Switch PHYs get an hwmon name derived from their full
279+
# device-tree path (e.g. cp0busbusf2000000mdio12a200switch2mdio01).
280+
# If this PHY drives a known port, name the sensor after that
281+
# port (e1, e2, ...) instead.
282+
phandle_path = os.path.join(hwmon_path, "device", "of_node", "phandle")
283+
if HOST.exists(phandle_path):
284+
ifname = phy_ifname.get(_dt_phandle(phandle_path))
285+
if ifname:
286+
base_name = ifname
287+
247288
# Helper to create sensor component with human-readable description
248289
def create_sensor(sensor_name, value, value_type, value_scale, label=None):
249290
component = {

0 commit comments

Comments
 (0)