Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions device/aspeed/arm64-aspeed_nvidia_ast2700_bmc-r0/pcie.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
[]
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,9 @@
"components": [],
"fans": [],
"psus": [],
"thermals": [],
"thermals": [{
"name": "BMC Ambient"
}],
"sfps": []
},
"interfaces": {}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"services_to_ignore": [],
"devices_to_ignore": ["psu", "fan", "asic"],
"devices_to_ignore": ["psu", "pdb", "fan", "asic"],
"user_defined_checkers": [],
"polling_interval": 300,
"led_color": {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -157,3 +157,54 @@ def get_liquid_cooling(self):
from sonic_platform.liquid_cooling import LiquidCooling
self._liquid_cooling = LiquidCooling()
return self._liquid_cooling

def get_module_index(self, module_name):
"""
Retrieves module index from the module name

Args:
module_name: A string, only SWITCH-HOST is supported
Returns:
An integer, the index of the ModuleBase object in the module_list
"""
if module_name != self._switch_host_module.get_name():
return None
return 0

def get_position_in_parent(self):
"""
Retrieves 1-based relative physical position in parent device.

Returns:
integer: The 1-based relative physical position in parent device,
or -1 if the position cannot be determined.
"""
return -1

def is_replaceable(self):
"""
Indicate whether this device is replaceable.

Returns:
bool: True if it is replaceable.
"""
return False

def initizalize_system_led(self):
return True

def set_status_led(self, color):
"""
Sets the state of the system LED

Not available on this platform
"""
return False

def get_status_led(self):
"""
Gets the state of the system LED

Not available on this platform
"""
return self.STATUS_LED_COLOR_OFF
Original file line number Diff line number Diff line change
Expand Up @@ -132,7 +132,7 @@ def _eeprom_init(self):
checksum = self._eeprom_init_raw(tlv_dict)

self._eeprom_info_dict = {hex(k): v for k, v in tlv_dict.items()}
self._eeprom_info_dict[hex(self._TLV_CODE_CRC_32)] = checksum
self._eeprom_info_dict[hex(self._TLV_CODE_CRC_32)] = f"0x{checksum:08X}"

def _get_eeprom_value(self, code):
info = self.get_system_eeprom_info()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -97,11 +97,11 @@ def _verify_hwmgmt_data(self) -> Tuple[List[str], Optional[str]]:
self._max_threshold,
)
for lo, hi, lo_n, hi_n in zip(values, values[1:], order, order[1:]):
if not lo < hi:
if not lo <= hi:
msg = (
"thresholds must be strictly ordered as "
"lcrit < crit < lwarn < warn < min < max; "
f"failed {lo_n} ({lo!r}) < {hi_n} ({hi!r})"
"thresholds must be ordered non-decreasingly as "
"lcrit <= crit <= lwarn <= warn <= min <= max; "
f"failed {lo_n} ({lo!r}) <= {hi_n} ({hi!r})"
)
return [], msg
return [], None
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -115,14 +115,17 @@ def set_admin_state(self, up):
action = _POWERCTRL_POWER_ON if up else _POWERCTRL_POWER_OFF
return self._run_powerctrl(action)

def do_power_cycle(self):
return self._run_powerctrl(_POWERCTRL_RESET)

def reboot(self, reboot_type=ModuleBase.MODULE_REBOOT_DEFAULT):
if reboot_type != ModuleBase.MODULE_REBOOT_DEFAULT:
logger.log_error(
f"Switch-Host reboot type {reboot_type!r} is not supported; "
"only MODULE_REBOOT_DEFAULT is supported"
)
return False
return self._run_powerctrl(_POWERCTRL_RESET)
return self.do_power_cycle()

def get_oper_status(self):
value = read_sysfs_int(PWR_DOWN_SYSFS)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,20 +36,18 @@ class ThermalBMC(ThermalBase):

HW_MGMT_ROOT = hw_mgmt_path("thermal")
TEMP_SCALE = 1000.0
NA = "N/A"

def __init__(self):
ThermalBase.__init__(self)

self.name = "BMC Ambient"
self.input_path = os.path.join(self.HW_MGMT_ROOT, "bmc_temp_input")
self.max_path = os.path.join(self.HW_MGMT_ROOT, "bmc_temp")
self.min_path = os.path.join(self.HW_MGMT_ROOT, "bmc_min")

def _read_scaled(self, path):
value = read_sysfs_float(path)
if value is None:
return self.NA
return None
return value / self.TEMP_SCALE

def get_name(self):
Expand All @@ -61,13 +59,49 @@ def get_name(self):
"""
return self.name

def get_presence(self):
"""
Retrieve the presence of the thermal sensor.

Returns:
bool: Always True.
"""
return True

def get_model(self):
"""
Retrieve the model of the thermal sensor.

Returns:
str: Model identifier for the sensor, or None if not available.
"""
return None

def get_serial(self):
"""
Retrieve the serial number of the thermal sensor.

Returns:
str: Serial number of the sensor, or None if not available.
"""
return None

def get_status(self):
"""
Retrieve the operational status of the thermal sensor.

Returns:
bool: True if the sensor is present and reporting a valid temperature.
"""
return self.get_temperature() is not None

def get_temperature(self):
"""
Retrieve the current temperature reading from the thermal sensor.

Returns:
The current temperature in Celsius to the nearest thousandth of a degree (e.g. 30.125),
or `"N/A"` if unavailable.
or None if unavailable.
"""
return self._read_scaled(self.input_path)

Expand All @@ -76,7 +110,7 @@ def get_high_threshold(self):
Retrieve the high threshold temperature of the thermal sensor.

Returns:
The high threshold temperature in Celsius, or `"N/A"` if unavailable.
The high threshold temperature in Celsius, or None if unavailable.
"""
return self._read_scaled(self.max_path)

Expand All @@ -85,24 +119,40 @@ def get_low_threshold(self):
Retrieve the low threshold temperature of the thermal sensor.

Returns:
The low threshold temperature in Celsius, or `"N/A"` if unavailable.
None -- the BMC ambient sensor has no low threshold.
"""
return self._read_scaled(self.min_path)
return None

def get_high_critical_threshold(self):
"""
Retrieve the high critical threshold temperature of the thermal sensor.

Returns:
`"N/A"` -- the BMC ambient sensor has no critical threshold.
None -- the BMC ambient sensor has no critical threshold.
"""
return self.NA
return None

def get_low_critical_threshold(self):
"""
Retrieve the low critical threshold temperature of the thermal sensor.

Returns:
`"N/A"` -- the BMC ambient sensor has no critical threshold.
None -- the BMC ambient sensor has no critical threshold.
"""
return None

def get_position_in_parent(self):
"""
Retrieves 1-based relative physical position in parent device
Returns:
integer: The 1-based relative physical position in parent device
"""
return 1

def is_replaceable(self):
"""
Indicate whether this device is replaceable.
Returns:
bool: True if it is replaceable.
"""
return self.NA
return False
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
import pytest

from sonic_platform_base.chassis_base import ChassisBase
from sonic_platform_base.module_base import ModuleBase


# `EepromBMC` / `EepromSystem` run an `ipmi-fru` subprocess from `__init__` via
Expand All @@ -42,7 +43,9 @@ def chassis():
patch(PATCH_REBOOT_CAUSE) as reboot_cls:
eeprom_cls.return_value = MagicMock(name="EepromBMC")
thermal_cls.return_value = MagicMock(name="ThermalBMC")
module_cls.return_value = MagicMock(name="SwitchHostModule")
switch_host = MagicMock(name="SwitchHostModule")
switch_host.get_name.return_value = ModuleBase.MODULE_TYPE_SWITCH_HOST
module_cls.return_value = switch_host
reboot_cls.return_value = MagicMock(name="RebootCause")

from sonic_platform.chassis import Chassis
Expand Down Expand Up @@ -148,3 +151,21 @@ def test_get_liquid_cooling_is_cached(self, chassis):
def test_thermal_and_module_lists_accessible_via_base_api(self, chassis):
assert chassis.get_all_thermals() == chassis._thermal_list
assert chassis.get_all_modules() == chassis._module_list

def test_get_module_index_switch_host_returns_zero(self, chassis):
index = chassis.get_module_index(chassis._switch_host_module.get_name())
assert index == 0
assert chassis.get_module(index) is chassis._switch_host_module

@pytest.mark.parametrize("module_name", ["DPU0", "invalid", ""])
def test_get_module_index_unknown_name_returns_none(self, chassis, module_name):
assert chassis.get_module_index(module_name) is None

def test_system_led_stubs_do_not_require_hardware(self, chassis):
assert chassis.initizalize_system_led() is True
assert chassis.set_status_led("green") is False
assert chassis.get_status_led() == ChassisBase.STATUS_LED_COLOR_OFF

def test_get_position_in_parent_and_replaceable(self, chassis):
assert chassis.get_position_in_parent() == -1
assert chassis.is_replaceable() is False
Original file line number Diff line number Diff line change
Expand Up @@ -154,8 +154,12 @@ def test_eeprom_init_populates_info_and_raw(self):
assert info[hex(Eeprom._TLV_CODE_SERIAL_NUMBER)] == "SN1234567"
assert info[hex(Eeprom._TLV_CODE_MAC_BASE)] == "aa:bb:cc:dd:ee:ff"
assert info[hex(Eeprom._TLV_CODE_LABEL_REVISION)] == "A0"
# The checksum is appended as a CRC32 entry.
assert hex(Eeprom._TLV_CODE_CRC_32) in info
# The checksum is appended as a CRC32 entry in ONIE hex format.
crc_key = hex(Eeprom._TLV_CODE_CRC_32)
assert crc_key in info
crc_value = info[crc_key]
assert isinstance(crc_value, str)
assert crc_value == f"0x{int(crc_value, 16):08X}"

def test_getter_helpers_use_system_eeprom_info(self):
eeprom = self._make_eeprom()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -101,21 +101,33 @@ def test_set_admin_state_down_invokes_power_off(self, module):
assert module.set_admin_state(False) is True
assert run.call_args[0][0][1] == "power_off"

def test_reboot_invokes_reset(self, module):
def test_do_power_cycle_invokes_reset(self, module):
with patch("sonic_platform.switch_host_module.subprocess.run", return_value=self._mock_run()) as run:
assert module.do_power_cycle() is True
args = run.call_args[0][0]
assert args[0] == shm_mod.HW_MGMT_POWERCTRL
assert args[1] == "reset"

def test_do_power_cycle_returns_false_on_non_zero_exit(self, module):
fake = self._mock_run(returncode=7, stderr=b"boom")
with patch("sonic_platform.switch_host_module.subprocess.run", return_value=fake):
assert module.do_power_cycle() is False

def test_do_power_cycle_returns_false_on_oserror(self, module):
with patch("sonic_platform.switch_host_module.subprocess.run", side_effect=OSError("no such binary")):
assert module.do_power_cycle() is False

def test_reboot_delegates_to_do_power_cycle(self, module):
with patch.object(module, "do_power_cycle", return_value=True) as do_cycle:
assert module.reboot() is True
assert run.call_args[0][0][1] == "reset"
do_cycle.assert_called_once_with()

def test_reboot_unsupported_type_returns_false_without_running(self, module):
with patch("sonic_platform.switch_host_module.subprocess.run") as run:
with patch.object(module, "do_power_cycle") as do_cycle:
assert module.reboot(ModuleBase.MODULE_REBOOT_CPU_COMPLEX) is False
run.assert_not_called()
do_cycle.assert_not_called()

def test_powerctrl_returns_false_on_non_zero_exit(self, module):
fake = self._mock_run(returncode=7, stderr=b"boom")
with patch("sonic_platform.switch_host_module.subprocess.run", return_value=fake):
assert module.set_admin_state(True) is False

def test_powerctrl_returns_false_on_oserror(self, module):
with patch("sonic_platform.switch_host_module.subprocess.run", side_effect=OSError("no such binary")):
assert module.reboot() is False
Loading
Loading