diff --git a/build_debian.sh b/build_debian.sh index b0395ca0637..006958389ad 100755 --- a/build_debian.sh +++ b/build_debian.sh @@ -764,10 +764,73 @@ if [[ $TARGET_BOOTLOADER == uboot ]]; then ## Overwriting the initrd image with uInitrd sudo LANG=C chroot $FILESYSTEM_ROOT mv /boot/u${INITRD_FILE} /boot/$INITRD_FILE else - sudo cp -v $PLATFORM_DIR/$CONFIGURED_PLATFORM/sonic_fit.its $FILESYSTEM_ROOT/boot/ + # Check if sonic_fit.its uses placeholders (template-based) + if grep -q "__KERNEL_VERSION__\|__KERNEL_PATH__" $PLATFORM_DIR/$CONFIGURED_PLATFORM/sonic_fit.its 2>/dev/null; then + # Generate sonic_fit.its with actual kernel version + # Detect the actual installed kernel version from the filesystem + # The kernel package may have a different version suffix than LINUX_KERNEL_VERSION + KERNEL_FILE=$(ls $FILESYSTEM_ROOT/boot/vmlinuz-* 2>/dev/null | head -1) + if [ -z "$KERNEL_FILE" ]; then + echo "Error: No kernel found in $FILESYSTEM_ROOT/boot/" + exit 1 + fi + KERNEL_VERSION_FULL=$(basename "$KERNEL_FILE" | sed 's/^vmlinuz-//') + + # Replace placeholders with actual paths + KERNEL_PATH="/boot/vmlinuz-${KERNEL_VERSION_FULL}" + INITRD_PATH="/boot/initrd.img-${KERNEL_VERSION_FULL}" + + # For aspeed platform, construct DTB directory path + # The FIT image template contains multiple DTBs, we only substitute the directory path + if [[ $CONFIGURED_PLATFORM == aspeed ]]; then + DTB_DIR_PATH="/usr/lib/linux-image-${KERNEL_VERSION_FULL}/aspeed" + else + DTB_DIR_PATH="/usr/lib/linux-image-${KERNEL_VERSION_FULL}/${CONFIGURED_PLATFORM}" + fi + + # Substitute placeholders in sonic_fit.its template + sed -e "s|__KERNEL_PATH__|${KERNEL_PATH}|g" \ + -e "s|__INITRD_PATH__|${INITRD_PATH}|g" \ + -e "s|__DTB_PATH_ASPEED__|${DTB_DIR_PATH}|g" \ + $PLATFORM_DIR/$CONFIGURED_PLATFORM/sonic_fit.its > /tmp/sonic_fit.its.tmp + + sudo cp -v /tmp/sonic_fit.its.tmp $FILESYSTEM_ROOT/boot/sonic_fit.its + rm -f /tmp/sonic_fit.its.tmp + else + # Platform uses hardcoded paths - copy as-is + sudo cp -v $PLATFORM_DIR/$CONFIGURED_PLATFORM/sonic_fit.its $FILESYSTEM_ROOT/boot/ + fi + sudo LANG=C chroot $FILESYSTEM_ROOT mkimage -f /boot/sonic_fit.its /boot/sonic_${CONFIGURED_ARCH}.fit fi fi + + # Install platform-level scripts and services for aspeed platform + if [[ $CONFIGURED_PLATFORM == aspeed ]]; then + echo "Installing platform scripts and services for aspeed..." + + # Copy all scripts from platform/aspeed/scripts/ + if [ -d "$PLATFORM_DIR/$CONFIGURED_PLATFORM/scripts" ]; then + for script in $PLATFORM_DIR/$CONFIGURED_PLATFORM/scripts/*.sh; do + if [ -f "$script" ]; then + echo "Installing $(basename $script)..." + sudo cp -v "$script" $FILESYSTEM_ROOT/usr/bin/ + sudo chmod +x $FILESYSTEM_ROOT/usr/bin/$(basename $script) + fi + done + fi + + # Copy all systemd services from platform/aspeed/systemd/ + if [ -d "$PLATFORM_DIR/$CONFIGURED_PLATFORM/systemd" ]; then + for service in $PLATFORM_DIR/$CONFIGURED_PLATFORM/systemd/*.service; do + if [ -f "$service" ]; then + echo "Installing and enabling $(basename $service)..." + sudo cp -v "$service" $FILESYSTEM_ROOT/etc/systemd/system/ + sudo LANG=C chroot $FILESYSTEM_ROOT systemctl enable $(basename $service) + fi + done + fi + fi fi # Collect host image version files before cleanup diff --git a/device/aspeed/arm64-aspeed_ast2700_evb-r0/99-switchCpu.rules b/device/aspeed/arm64-aspeed_ast2700_evb-r0/99-switchCpu.rules new file mode 100644 index 00000000000..a733ca71b86 --- /dev/null +++ b/device/aspeed/arm64-aspeed_ast2700_evb-r0/99-switchCpu.rules @@ -0,0 +1,4 @@ +# Aspeed AST2700 EVB: Dummy placeholder for switch CPU console +# The EVB does not have a physical switch CPU console connection +# This file exists to prevent errors in sonic-platform-init.sh + diff --git a/device/aspeed/arm64-aspeed_ast2700_evb-r0/asic.conf b/device/aspeed/arm64-aspeed_ast2700_evb-r0/asic.conf new file mode 100644 index 00000000000..35c5884f133 --- /dev/null +++ b/device/aspeed/arm64-aspeed_ast2700_evb-r0/asic.conf @@ -0,0 +1,2 @@ +# aspeed-ast2700 doesn't have a switch asic but SONIC assumes NUM_ASIC to be atleast 1 +NUM_ASIC=1 diff --git a/device/aspeed/arm64-aspeed_ast2700_evb-r0/default_sku b/device/aspeed/arm64-aspeed_ast2700_evb-r0/default_sku new file mode 100644 index 00000000000..44c055e79b1 --- /dev/null +++ b/device/aspeed/arm64-aspeed_ast2700_evb-r0/default_sku @@ -0,0 +1 @@ +AST2700-EVB-BMC empty diff --git a/device/aspeed/arm64-aspeed_ast2700_evb-r0/installer.conf b/device/aspeed/arm64-aspeed_ast2700_evb-r0/installer.conf new file mode 100644 index 00000000000..bd4fd1caa65 --- /dev/null +++ b/device/aspeed/arm64-aspeed_ast2700_evb-r0/installer.conf @@ -0,0 +1,13 @@ +# Console configuration for AST2700 EVB platform +# CONSOLE_DEV=12 means ttyS12 +CONSOLE_DEV=12 +CONSOLE_SPEED=115200 + +# Early console for debugging (UART12 on AST2700) +EARLYCON="earlycon=uart8250,mmio32,0x14c33b00" + +# Variable log size(MB) +VAR_LOG_SIZE=512 + +# Additional kernel command line arguments +ONIE_PLATFORM_EXTRA_CMDLINE_LINUX="" diff --git a/device/aspeed/arm64-aspeed_ast2700_evb-r0/platform.json b/device/aspeed/arm64-aspeed_ast2700_evb-r0/platform.json new file mode 100644 index 00000000000..cd233ebdd9d --- /dev/null +++ b/device/aspeed/arm64-aspeed_ast2700_evb-r0/platform.json @@ -0,0 +1,23 @@ +{ + "chassis": { + "name": "AST2700-EVB-BMC", + "thermal_manager": false, + "status_led": { + "controllable": true, + "colors": ["green", "amber", "off"] + }, + "components": [ + { + "name": "BMC" + }, + { + "name": "BIOS" + } + ], + "fans": [], + "psus": [], + "thermals": [], + "sfps": [] + }, + "interfaces": {} +} diff --git a/device/aspeed/arm64-aspeed_ast2700_evb-r0/platform_asic b/device/aspeed/arm64-aspeed_ast2700_evb-r0/platform_asic new file mode 100644 index 00000000000..708a833cb62 --- /dev/null +++ b/device/aspeed/arm64-aspeed_ast2700_evb-r0/platform_asic @@ -0,0 +1 @@ +aspeed \ No newline at end of file diff --git a/device/aspeed/arm64-aspeed_ast2700_evb-r0/platform_components.json b/device/aspeed/arm64-aspeed_ast2700_evb-r0/platform_components.json new file mode 100644 index 00000000000..ee3ca09448d --- /dev/null +++ b/device/aspeed/arm64-aspeed_ast2700_evb-r0/platform_components.json @@ -0,0 +1,10 @@ +{ + "chassis": { + "AST2700-EVB-BMC": { + "component": { + "BMC": {}, + "BIOS": {} + } + } + } +} diff --git a/device/aspeed/arm64-aspeed_ast2700_evb-r0/platform_env.conf b/device/aspeed/arm64-aspeed_ast2700_evb-r0/platform_env.conf new file mode 100644 index 00000000000..bcd3149cc04 --- /dev/null +++ b/device/aspeed/arm64-aspeed_ast2700_evb-r0/platform_env.conf @@ -0,0 +1 @@ +# BMC platform environment diff --git a/device/aspeed/arm64-aspeed_ast2700_evb-r0/pmon_daemon_control.json b/device/aspeed/arm64-aspeed_ast2700_evb-r0/pmon_daemon_control.json new file mode 100644 index 00000000000..de0341b4f0a --- /dev/null +++ b/device/aspeed/arm64-aspeed_ast2700_evb-r0/pmon_daemon_control.json @@ -0,0 +1,17 @@ +{ + "skip_thermalctld": false, + "skip_ledd": true, + "skip_xcvrd": true, + "skip_psud": true, + "skip_syseepromd": true, + "skip_pcied": true, + "skip_chassisd": true, + "skip_fancontrol": true, + "include_sensormond": false, + "thermalctld": { + "thermal_monitor_initial_interval": 5, + "thermal_monitor_update_interval": 60, + "thermal_monitor_update_elapsed_threshold": 30 + } +} + diff --git a/device/aspeed/arm64-aspeed_ast2700_evb-r0/switch_cpu_console.conf b/device/aspeed/arm64-aspeed_ast2700_evb-r0/switch_cpu_console.conf new file mode 100644 index 00000000000..38e2b35d08f --- /dev/null +++ b/device/aspeed/arm64-aspeed_ast2700_evb-r0/switch_cpu_console.conf @@ -0,0 +1,12 @@ +# Console configuration for Aspeed AST2700 EVB +# This file defines console port settings for the switch CPU console + +# Baud rate for the switch CPU console (default: 115200) +CONSOLE_BAUD_RATE=115200 + +# Flow control: 0=disabled, 1=enabled (default: 0) +CONSOLE_FLOW_CONTROL=0 + +# Remote device name shown in consutil +CONSOLE_REMOTE_DEVICE=EVB + diff --git a/device/aspeed/arm64-aspeed_ast2700_evb-r0/system_health_monitoring_config.json b/device/aspeed/arm64-aspeed_ast2700_evb-r0/system_health_monitoring_config.json new file mode 100644 index 00000000000..59fb8ad3be7 --- /dev/null +++ b/device/aspeed/arm64-aspeed_ast2700_evb-r0/system_health_monitoring_config.json @@ -0,0 +1,12 @@ +{ + "services_to_ignore": [], + "devices_to_ignore": ["psu", "fan", "asic"], + "user_defined_checkers": [], + "polling_interval": 300, + "led_color": { + "fault": "amber", + "normal": "green", + "booting": "amber_blink" + } +} + diff --git a/device/aspeed/arm64-aspeed_ast2700_evb-r0/udevprefix.conf b/device/aspeed/arm64-aspeed_ast2700_evb-r0/udevprefix.conf new file mode 100644 index 00000000000..af15b82b629 --- /dev/null +++ b/device/aspeed/arm64-aspeed_ast2700_evb-r0/udevprefix.conf @@ -0,0 +1,2 @@ +ttySwitchCpu + diff --git a/device/nexthop/arm64-nexthop_b27-r0/99-switchCpu.rules b/device/nexthop/arm64-nexthop_b27-r0/99-switchCpu.rules new file mode 100644 index 00000000000..fc27893bf4b --- /dev/null +++ b/device/nexthop/arm64-nexthop_b27-r0/99-switchCpu.rules @@ -0,0 +1,3 @@ +# Nexthop B27 BMC: Map host CPU console /dev/ttyS1 to common alias /dev/ttySwitchCpu0 +SUBSYSTEM=="tty", KERNEL=="ttyS1", SYMLINK+="ttySwitchCpu0", MODE="0666" + diff --git a/device/nexthop/arm64-nexthop_b27-r0/asic.conf b/device/nexthop/arm64-nexthop_b27-r0/asic.conf new file mode 100644 index 00000000000..67ca94b0a77 --- /dev/null +++ b/device/nexthop/arm64-nexthop_b27-r0/asic.conf @@ -0,0 +1,2 @@ +# BMC's don't have a switch asic but SONIC assumes NUM_ASIC to be atleast 1 +NUM_ASIC=1 diff --git a/device/nexthop/arm64-nexthop_b27-r0/default_sku b/device/nexthop/arm64-nexthop_b27-r0/default_sku new file mode 100644 index 00000000000..4e1f841b612 --- /dev/null +++ b/device/nexthop/arm64-nexthop_b27-r0/default_sku @@ -0,0 +1 @@ +NH-B27 empty diff --git a/device/nexthop/arm64-nexthop_b27-r0/installer.conf b/device/nexthop/arm64-nexthop_b27-r0/installer.conf new file mode 100644 index 00000000000..5e7af2709b5 --- /dev/null +++ b/device/nexthop/arm64-nexthop_b27-r0/installer.conf @@ -0,0 +1,13 @@ +# Console configuration for AST2720 platform +# CONSOLE_DEV=12 means ttyS12 +CONSOLE_DEV=12 +CONSOLE_SPEED=115200 + +# Early console for debugging (UART12 on AST2720) +EARLYCON="earlycon=uart8250,mmio32,0x14c33b00" + +# Variable log size (MB) +VAR_LOG_SIZE=512 + +# Additional kernel command line arguments +ONIE_PLATFORM_EXTRA_CMDLINE_LINUX="" diff --git a/device/nexthop/arm64-nexthop_b27-r0/platform.json b/device/nexthop/arm64-nexthop_b27-r0/platform.json new file mode 100644 index 00000000000..1ae61b1366f --- /dev/null +++ b/device/nexthop/arm64-nexthop_b27-r0/platform.json @@ -0,0 +1,23 @@ +{ + "chassis": { + "name": "NH-B27-BMC", + "thermal_manager": false, + "status_led": { + "controllable": true, + "colors": ["green", "amber", "off"] + }, + "components": [ + { + "name": "BMC" + }, + { + "name": "BIOS" + } + ], + "fans": [], + "psus": [], + "thermals": [], + "sfps": [] + }, + "interfaces": {} +} diff --git a/device/nexthop/arm64-nexthop_b27-r0/platform_asic b/device/nexthop/arm64-nexthop_b27-r0/platform_asic new file mode 100644 index 00000000000..708a833cb62 --- /dev/null +++ b/device/nexthop/arm64-nexthop_b27-r0/platform_asic @@ -0,0 +1 @@ +aspeed \ No newline at end of file diff --git a/device/nexthop/arm64-nexthop_b27-r0/platform_components.json b/device/nexthop/arm64-nexthop_b27-r0/platform_components.json new file mode 100644 index 00000000000..969ef4fb222 --- /dev/null +++ b/device/nexthop/arm64-nexthop_b27-r0/platform_components.json @@ -0,0 +1,10 @@ +{ + "chassis": { + "NH-B27-BMC": { + "component": { + "BMC": {}, + "BIOS": {} + } + } + } +} diff --git a/device/nexthop/arm64-nexthop_b27-r0/platform_env.conf b/device/nexthop/arm64-nexthop_b27-r0/platform_env.conf new file mode 100644 index 00000000000..bcd3149cc04 --- /dev/null +++ b/device/nexthop/arm64-nexthop_b27-r0/platform_env.conf @@ -0,0 +1 @@ +# BMC platform environment diff --git a/device/nexthop/arm64-nexthop_b27-r0/pmon_daemon_control.json b/device/nexthop/arm64-nexthop_b27-r0/pmon_daemon_control.json new file mode 100644 index 00000000000..de0341b4f0a --- /dev/null +++ b/device/nexthop/arm64-nexthop_b27-r0/pmon_daemon_control.json @@ -0,0 +1,17 @@ +{ + "skip_thermalctld": false, + "skip_ledd": true, + "skip_xcvrd": true, + "skip_psud": true, + "skip_syseepromd": true, + "skip_pcied": true, + "skip_chassisd": true, + "skip_fancontrol": true, + "include_sensormond": false, + "thermalctld": { + "thermal_monitor_initial_interval": 5, + "thermal_monitor_update_interval": 60, + "thermal_monitor_update_elapsed_threshold": 30 + } +} + diff --git a/device/nexthop/arm64-nexthop_b27-r0/switch_cpu_console.conf b/device/nexthop/arm64-nexthop_b27-r0/switch_cpu_console.conf new file mode 100644 index 00000000000..51d843d9575 --- /dev/null +++ b/device/nexthop/arm64-nexthop_b27-r0/switch_cpu_console.conf @@ -0,0 +1,12 @@ +# Console configuration for Nexthop B27 BMC +# This file defines console port settings for the switch CPU console + +# Baud rate for the switch CPU console (default: 115200) +CONSOLE_BAUD_RATE=9600 + +# Flow control: 0=disabled, 1=enabled (default: 0) +CONSOLE_FLOW_CONTROL=0 + +# Remote device name shown in consutil +CONSOLE_REMOTE_DEVICE=SwitchCpu + diff --git a/device/nexthop/arm64-nexthop_b27-r0/system_health_monitoring_config.json b/device/nexthop/arm64-nexthop_b27-r0/system_health_monitoring_config.json new file mode 100644 index 00000000000..59fb8ad3be7 --- /dev/null +++ b/device/nexthop/arm64-nexthop_b27-r0/system_health_monitoring_config.json @@ -0,0 +1,12 @@ +{ + "services_to_ignore": [], + "devices_to_ignore": ["psu", "fan", "asic"], + "user_defined_checkers": [], + "polling_interval": 300, + "led_color": { + "fault": "amber", + "normal": "green", + "booting": "amber_blink" + } +} + diff --git a/device/nexthop/arm64-nexthop_b27-r0/udevprefix.conf b/device/nexthop/arm64-nexthop_b27-r0/udevprefix.conf new file mode 100644 index 00000000000..af15b82b629 --- /dev/null +++ b/device/nexthop/arm64-nexthop_b27-r0/udevprefix.conf @@ -0,0 +1,2 @@ +ttySwitchCpu + diff --git a/dockers/docker-database/database_config.json.j2 b/dockers/docker-database/database_config.json.j2 index b97589d3733..0e8c6bc93b4 100644 --- a/dockers/docker-database/database_config.json.j2 +++ b/dockers/docker-database/database_config.json.j2 @@ -24,7 +24,7 @@ "persistence_for_warm_boot" : "yes" } {% endif %} -{% if DATABASE_TYPE is defined and DATABASE_TYPE == "dpudb" %} +{% if DATABASE_TYPE is defined and (DATABASE_TYPE == "dpudb" or DATABASE_TYPE == "bmcdb") %} {% else %} , "redis_bmp":{ @@ -139,6 +139,8 @@ "separator": ":", "instance" : {% if include_remote_db %} "remote_redis" {% else %} "redis" {% endif %} } +{% elif DATABASE_TYPE is defined and DATABASE_TYPE == "bmcdb" %} + {# No BMP_STATE_DB for BMC devices #} {% else %} , "BMP_STATE_DB" : { diff --git a/dockers/docker-database/docker-database-init.sh b/dockers/docker-database/docker-database-init.sh index eb0348c2ae9..3200a3bc2f7 100755 --- a/dockers/docker-database/docker-database-init.sh +++ b/dockers/docker-database/docker-database-init.sh @@ -41,6 +41,11 @@ then fi fi +if [[ $IS_BMC_DEVICE == "true" ]] +then + export DATABASE_TYPE="bmcdb" +fi + export BMP_DB_PORT=6400 REDIS_DIR=/var/run/redis$NAMESPACE_ID @@ -143,6 +148,8 @@ done chown -R redis:redis $REDIS_DIR REDIS_BMP_DIR="/var/lib/redis_bmp" -chown -R redis:redis $REDIS_BMP_DIR +if [[ -d $REDIS_BMP_DIR ]]; then + chown -R redis:redis $REDIS_BMP_DIR +fi exec /usr/local/bin/supervisord diff --git a/dockers/docker-database/multi_database_config.json.j2 b/dockers/docker-database/multi_database_config.json.j2 index 26ddb006b83..8d633e685b7 100644 --- a/dockers/docker-database/multi_database_config.json.j2 +++ b/dockers/docker-database/multi_database_config.json.j2 @@ -59,13 +59,17 @@ "unix_socket_path": "", "persistence_for_warm_boot" : "yes" } -{% endif %}, +{% endif %} +{% if DATABASE_TYPE is defined and (DATABASE_TYPE == "dpudb" or DATABASE_TYPE == "bmcdb") %} +{% else %} + , "redis_bmp":{ "hostname" : "{{HOST_IP}}", "port" : {{BMP_DB_PORT}}, "unix_socket_path" : "/var/run/redis{{DEV}}/redis_bmp.sock", "persistence_for_warm_boot" : "yes" } +{% endif %} }, "DATABASES" : { "APPL_DB": { @@ -181,12 +185,16 @@ "separator": ":", "instance" : {% if include_remote_db %} "remote_redis" {% else %} "redis" {% endif %} } -{% endif %}, - "BMP_STATE_DB" : { +{% elif DATABASE_TYPE is defined and DATABASE_TYPE == "bmcdb" %} + {# No BMP_STATE_DB for BMC devices #} +{% else %} + , + "BMP_STATE_DB" : { "id" : 20, "separator": "|", "instance" : "redis_bmp" } +{% endif %} }, "VERSION" : "1.0" } diff --git a/files/build_scripts/aspeed_bmc_filter_services.py b/files/build_scripts/aspeed_bmc_filter_services.py new file mode 100644 index 00000000000..12bf5bd86f4 --- /dev/null +++ b/files/build_scripts/aspeed_bmc_filter_services.py @@ -0,0 +1,280 @@ +#!/usr/bin/env python3 +""" +Filter systemd services for Aspeed BMC platform. + +This script removes services that are not needed on BMC platform +and ensures BMC-specific services are included. +""" + +import sys +import os +import glob + +# Services to REMOVE for BMC platform (not needed) +BMC_EXCLUDED_SERVICES = { + # Backend ACL - not needed on BMC + 'backend-acl.service', + + # PCIe - not needed on BMC + 'pcie-check.service', + + # Smart switch / midplane - not needed on BMC + 'midplane-network-npu.service', + 'midplane-network-dpu.service', + + # Virtual switch specific + 'topology.service', + + # NOS-specific services not needed for BMC + 'copp-config.service', # Control Plane Policing - no ASIC on BMC + 'dhcp_dos_logger.service', # DHCP DoS detection - no switch ports on BMC + 'warmboot-finalizer.service', # Warm boot reconciliation - no SWSS/BGP on BMC + + # SONiC NOS services - not needed for BMC + 'sonic-hostservice.service', # SONiC host services (VLAN, LAG, etc.) - NOS-specific + + # UUID daemon - not needed + 'uuidd.service', # UUID daemon - kernel provides UUID generation + 'uuidd.socket', # UUID daemon socket activation + + # Console - not needed if no local display + 'getty@tty1.service', # Local console - only if VGA/HDMI available + +} + +# Services to ENSURE are included for BMC +BMC_REQUIRED_SERVICES = { + # AAA/TACACS - needed on BMC + 'tacacs-config.service', + 'tacacs-config.timer', + + # Feature management (required to start services based on CONFIG_DB) + 'featured.service', # SONiC feature management daemon + 'featured.timer', # Timer for featured service + + # Security and monitoring (important for BMC) + 'caclmgrd.service', # Firewall for management interfaces + 'haveged.service', # Entropy for SSH/SSL/TLS + 'monit.service', # Auto-restart failed services + 'procdockerstatsd.service', # Container resource monitoring + + # Time synchronization + 'chrony.service', # Chrony time synchronization + + # Zero Touch Provisioning + 'ztp.service', # ZTP for automated device provisioning + + # System health monitoring + 'system-health.service', # SONiC system health monitor + + # Platform monitoring + 'pmon.service', # Platform monitor container (sensors, fans, PSU, etc.) + + # Telemetry and monitoring + 'gnmi.service', # gNMI telemetry service + + 'hostcfgd.service', # SONiC host config daemon + 'hostcfgd.timer', # Timer for hostcfgd service + 'auditd.service', # Security audit - only needed for compliance + 'sysmgr.service', # SONiC system manager +} + +def create_service_override(filesystem_root, service_name, override_content, enable=True): + """Create systemd drop-in override for a service on BMC.""" + + systemd_system = os.path.join(filesystem_root, 'etc/systemd/system') + override_dir = os.path.join(systemd_system, f'{service_name}.d') + override_file = os.path.join(override_dir, 'bmc-override.conf') + + # Create override directory + os.makedirs(override_dir, exist_ok=True) + + try: + with open(override_file, 'w') as f: + f.write(override_content) + print(f" Created: {override_file}") + + # Create symlink in multi-user.target.wants to enable auto-start if requested + if enable: + multi_user_wants = os.path.join(systemd_system, 'multi-user.target.wants') + os.makedirs(multi_user_wants, exist_ok=True) + + service_symlink = os.path.join(multi_user_wants, service_name) + service_path = f'/usr/lib/systemd/system/{service_name}' + + # Remove existing symlink if present + if os.path.islink(service_symlink) or os.path.exists(service_symlink): + os.remove(service_symlink) + + # Create symlink to enable service + os.symlink(service_path, service_symlink) + print(f" Enabled: {service_name} -> multi-user.target.wants") + + return True + except Exception as e: + print(f" Warning: Could not create {service_name} override: {e}") + return False + +def create_gnmi_bmc_override(filesystem_root): + """Create systemd drop-in override for GNMI to remove swss/syncd dependencies on BMC.""" + + print("") + print("Creating GNMI service override for BMC (removing swss/syncd dependencies)...") + + override_content = """[Unit] +# BMC override: Remove ASIC/switch dependencies (swss, syncd) +# GNMI on BMC only needs database service +After=database.service +""" + + return create_service_override(filesystem_root, 'gnmi.service', override_content, enable=False) + +def create_watchdog_control_bmc_override(filesystem_root): + """Create systemd drop-in override for watchdog-control to remove swss dependency on BMC.""" + + print("") + print("Creating watchdog-control service override for BMC (removing swss dependency)...") + + override_content = """[Unit] +# BMC override: Remove ASIC/switch dependency (swss) +# Watchdog control on BMC doesn't need swss +After= +""" + + return create_service_override(filesystem_root, 'watchdog-control.service', override_content, enable=False) + +def create_determine_reboot_cause_bmc_override(filesystem_root): + """Create systemd drop-in override for determine-reboot-cause to remove rc-local dependency on BMC.""" + + print("") + print("Creating determine-reboot-cause service override for BMC (removing rc-local dependency)...") + + override_content = """[Unit] +# BMC override: Remove rc-local dependency +# Reboot cause determination on BMC doesn't need rc-local +Requires= +After= +""" + + return create_service_override(filesystem_root, 'determine-reboot-cause.service', override_content, enable=False) + +def create_sysmgr_bmc_override(filesystem_root): + """Create systemd drop-in override for sysmgr to remove swss dependency on BMC.""" + override_content = """[Unit] +# BMC override: Remove ASIC/switch dependency (swss) +# Sysmgr on BMC doesn't need swss +After=database.service +""" + return create_service_override(filesystem_root, 'sysmgr.service', override_content, enable=False) + +def create_telemetry_bmc_override(filesystem_root): + """Create systemd drop-in override for telemetry to remove swss/syncd dependencies on BMC.""" + override_content = """[Unit] +# BMC override: Remove ASIC/switch dependencies (swss, syncd) +# Telemetry on BMC doesn't need swss or syncd +After=database.service +""" + return create_service_override(filesystem_root, 'telemetry.service', override_content, enable=False) + +def mask_services_in_filesystem(filesystem_root): + """Mask excluded services by removing symlinks and creating mask files.""" + + print("") + print("Masking excluded services in filesystem...") + + systemd_system = os.path.join(filesystem_root, 'etc/systemd/system') + + masked_count = 0 + for service in BMC_EXCLUDED_SERVICES: + # Remove symlinks from target.wants directories + for wants_dir in glob.glob(os.path.join(systemd_system, '*.wants')): + symlink = os.path.join(wants_dir, service) + if os.path.islink(symlink) or os.path.exists(symlink): + try: + os.remove(symlink) + print(f" Removed: {symlink}") + masked_count += 1 + except Exception as e: + print(f" Warning: Could not remove {symlink}: {e}") + + # Create mask file (symlink to /dev/null) + mask_file = os.path.join(systemd_system, service) + if not os.path.exists(mask_file): + try: + os.symlink('/dev/null', mask_file) + print(f" Masked: {service}") + masked_count += 1 + except Exception as e: + print(f" Warning: Could not mask {service}: {e}") + + print(f"Total masked/disabled: {masked_count} service links") + return masked_count + +def filter_services(input_file, output_file, filesystem_root=None): + """Filter services for BMC platform.""" + + # Read existing services + try: + with open(input_file, 'r') as f: + services = set(line.strip() for line in f if line.strip() and not line.startswith('#')) + except FileNotFoundError: + services = set() + + # Remove excluded services + filtered_services = services - BMC_EXCLUDED_SERVICES + + # Add required BMC services + filtered_services.update(BMC_REQUIRED_SERVICES) + + # Sort for consistent output + sorted_services = sorted(filtered_services) + + # Write filtered services (NO COMMENTS - systemd-sonic-generator can't handle them) + with open(output_file, 'w') as f: + for service in sorted_services: + f.write(service + '\n') + + # Mask services in filesystem if root provided + if filesystem_root: + mask_services_in_filesystem(filesystem_root) + # Create service overrides to remove dependencies that don't exist on BMC + create_gnmi_bmc_override(filesystem_root) + create_watchdog_control_bmc_override(filesystem_root) + create_determine_reboot_cause_bmc_override(filesystem_root) + create_sysmgr_bmc_override(filesystem_root) + create_telemetry_bmc_override(filesystem_root) + + # Print summary to build log (not to file) + print("=" * 80) + print("Aspeed BMC Service Filtering Summary") + print("=" * 80) + print(f"Original services: {len(services)}") + print(f"Filtered services: {len(filtered_services)}") + print(f"") + print(f"Services REMOVED for BMC:") + removed = BMC_EXCLUDED_SERVICES & services + if removed: + for svc in sorted(removed): + print(f" - {svc}") + else: + print(f" (none)") + print(f"") + print(f"Services ADDED for BMC:") + added = BMC_REQUIRED_SERVICES - services + if added: + for svc in sorted(added): + print(f" + {svc}") + else: + print(f" (none)") + print(f"") + print(f"Excluded services list: {', '.join(sorted(BMC_EXCLUDED_SERVICES))}") + print("=" * 80) + +if __name__ == '__main__': + if len(sys.argv) < 3: + print("Usage: aspeed_bmc_filter_services.py [filesystem_root]") + sys.exit(1) + + filesystem_root = sys.argv[3] if len(sys.argv) > 3 else None + filter_services(sys.argv[1], sys.argv[2], filesystem_root) + diff --git a/files/build_templates/docker_image_ctl.j2 b/files/build_templates/docker_image_ctl.j2 index 57a76842c15..ab6711b3f84 100644 --- a/files/build_templates/docker_image_ctl.j2 +++ b/files/build_templates/docker_image_ctl.j2 @@ -111,7 +111,9 @@ function preStartAction() # Create an emtpy file and overwrite any RDB if already there echo -n > /tmp/dump.rdb docker cp /tmp/dump.rdb database$DEV:/var/lib/redis/ - docker cp /tmp/dump.rdb database$DEV:/var/lib/redis_bmp/ + if [[ "$DATABASE_TYPE" != "bmcdb" ]]; then + docker cp /tmp/dump.rdb database$DEV:/var/lib/redis_bmp/ + fi fi fi {%- elif docker_container_name == "pde" %} @@ -634,6 +636,7 @@ start() { DB_OPT=$DB_OPT" --env DATABASE_TYPE=$DATABASE_TYPE " DB_OPT=$DB_OPT" --env NUM_DPU=$NUM_DPU " DB_OPT=$DB_OPT" --env IS_DPU_DEVICE=$IS_DPU_DEVICE " + DB_OPT=$DB_OPT" --env IS_BMC_DEVICE=$IS_BMC_DEVICE " if [[ "$DEV" ]]; then DB_OPT=$DB_OPT" -v /var/run/redis$DEV:/var/run/redis$DEV:rw " fi @@ -864,6 +867,10 @@ if [[ "$DEV" == *"dpu"* ]]; then DATABASE_TYPE="dpudb" fi +if [[ "$IS_BMC_DEVICE" == "true" ]]; then + DATABASE_TYPE="bmcdb" +fi + {%- endif %} NAMESPACE_PREFIX="asic" DOCKERNAME=$DOCKERNAME$DEV diff --git a/files/build_templates/sonic_debian_extension.j2 b/files/build_templates/sonic_debian_extension.j2 index d11000b4ad7..d00d3251af1 100644 --- a/files/build_templates/sonic_debian_extension.j2 +++ b/files/build_templates/sonic_debian_extension.j2 @@ -1248,4 +1248,40 @@ sudo touch $FILESYSTEM_ROOT_ETC_SONIC/enable_multidb # Install syslog counter plugin install_deb_package $debs_path/syslog-counter_*.deb +############################################################################# +# PLATFORM-SPECIFIC SERVICE FILTERING +# This section MUST remain at the end of the file to ensure all services +# have been added to GENERATED_SERVICE_FILE before filtering. +# DO NOT add service registrations (tee -a $GENERATED_SERVICE_FILE) after this point! +############################################################################# + +# Aspeed BMC platform: Filter services for BMC use case +if [[ $CONFIGURED_PLATFORM == aspeed ]]; then + # Show original service count + ORIGINAL_COUNT=$(sudo bash -c "wc -l < $GENERATED_SERVICE_FILE") + + # Use filter script to remove unwanted services and add BMC services + sudo cp $BUILD_SCRIPTS_DIR/aspeed_bmc_filter_services.py $FILESYSTEM_ROOT/tmp/ + sudo chmod a+x $FILESYSTEM_ROOT/tmp/aspeed_bmc_filter_services.py + + # Filter the generated services and mask excluded services + sudo python3 $FILESYSTEM_ROOT/tmp/aspeed_bmc_filter_services.py \ + $GENERATED_SERVICE_FILE \ + $GENERATED_SERVICE_FILE.filtered \ + $FILESYSTEM_ROOT + + # Show filtered service count + FILTERED_COUNT=$(sudo bash -c "wc -l < $GENERATED_SERVICE_FILE.filtered") + + # Replace with filtered version + sudo mv $GENERATED_SERVICE_FILE.filtered $GENERATED_SERVICE_FILE + + # Cleanup + sudo rm -f $FILESYSTEM_ROOT/tmp/aspeed_bmc_filter_services.py +fi + +############################################################################# +# END OF FILE - Platform-specific service filtering complete +############################################################################# + diff --git a/files/build_templates/telemetry.service.j2 b/files/build_templates/telemetry.service.j2 index 97b622fc67c..d59ad1fc76e 100644 --- a/files/build_templates/telemetry.service.j2 +++ b/files/build_templates/telemetry.service.j2 @@ -13,3 +13,7 @@ ExecStartPre=/usr/local/bin/{{docker_container_name}}.sh start ExecStart=/usr/local/bin/{{docker_container_name}}.sh wait ExecStop=/usr/local/bin/{{docker_container_name}}.sh stop RestartSec=30 + +[Install] +WantedBy=sonic.target + diff --git a/platform/aspeed/build-emmc-image-installer.sh b/platform/aspeed/build-emmc-image-installer.sh new file mode 100755 index 00000000000..80afa854a08 --- /dev/null +++ b/platform/aspeed/build-emmc-image-installer.sh @@ -0,0 +1,161 @@ +#!/bin/bash +# Build Complete SONiC eMMC Image for AST2700 Using SONiC Installer +# This creates a ready-to-flash disk image by leveraging the built-in SONiC installer +# +# Usage: ./build-emmc-image-installer.sh [output-image-name] +# +# This script uses the SONiC installer's "build" mode to create a raw ext4 image, +# then embeds it into a full GPT-partitioned eMMC image with a single SONiC-OS partition. +# +# Partition Layout (Standard SONiC Approach): +# /dev/mmcblk0p1 - SONiC-OS (entire disk 10 GB - assumes pSLC on a 32G eMMC) +# ├── image-SONiC-OS-/ +# ├── image-SONiC-OS-/ +# └── machine.conf +# +# Requirements: +# - Root privileges (for loop device mounting) +# - sgdisk (gdisk package) +# - mkfs.ext4 (e2fsprogs package) +# - bash, unzip, tar, gzip + +set -e # Exit on error + +# Configuration +# We use 10GB as the 32G eMMC card in chipmunk will be 10G post pSLC partitioning +EMMC_SIZE_MB=10240 +PARTITION_LABEL="SONiC-OS" # Single partition for all SONiC images +ROOT_PART_SIZE_MB=$((EMMC_SIZE_MB - 100)) # SONiC-OS partition size (leave 100MB for GPT overhead) + +# Colors +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +NC='\033[0m' + +log_info() { echo -e "${GREEN}[INFO]${NC} $1"; } +log_warn() { echo -e "${YELLOW}[WARN]${NC} $1"; } +log_error() { echo -e "${RED}[ERROR]${NC} $1"; } + +check_requirements() { + log_info "Checking requirements..." + local missing=0 + for cmd in sgdisk mkfs.ext4 losetup bash unzip tar gzip; do + if ! command -v $cmd &> /dev/null; then + log_error "Required command not found: $cmd" + missing=1 + fi + done + [ $missing -eq 1 ] && exit 1 + [ "$(id -u)" -ne 0 ] && { log_error "Must run as root"; exit 1; } + log_info "All requirements satisfied" +} + +cleanup() { + log_info "Cleaning up..." + [ -n "$LOOP_DEV" ] && losetup -d "$LOOP_DEV" 2>/dev/null || true + [ -n "$RAW_IMAGE" ] && [ -f "$RAW_IMAGE" ] && rm -f "$RAW_IMAGE" || true +} + +# Parse arguments +[ $# -lt 1 ] && { echo "Usage: $0 [output-image-name]"; exit 1; } + +SONIC_BIN="$(realpath "$1")" +OUTPUT_IMAGE="${2:-sonic-aspeed-arm64-emmc.img}" + +[ ! -f "$SONIC_BIN" ] && { log_error "SONiC binary not found: $SONIC_BIN"; exit 1; } + +check_requirements +trap cleanup EXIT + +ORIG_DIR=$(pwd) +LOOP_DEV="" +RAW_IMAGE="target/sonic-aspeed.raw" + +# Create target directory if it doesn't exist +mkdir -p target + +log_info "Building eMMC image from: $SONIC_BIN" +log_info "Output image: $OUTPUT_IMAGE" + +# Step 1: Use SONiC installer in "build" mode to create raw ext4 image +log_info "Running SONiC installer in 'build' mode..." +log_info "This will create a raw ext4 image with SONiC installed..." + +# The installer expects to be run as a self-extracting archive +# In "build" mode, it creates OUTPUT_RAW_IMAGE +# We need to: +# 1. Pre-create the raw image file with the right size (installer expects it to exist) +# 2. Patch the installer to use our filename + +# Pre-create the raw image file (same as build_image.sh does) +log_info "Creating raw image file: $RAW_IMAGE (${ROOT_PART_SIZE_MB} MB)..." +fallocate -l "${ROOT_PART_SIZE_MB}M" "$RAW_IMAGE" || { + log_error "Failed to create raw image file" + exit 1 +} + +# Run the installer directly +# It will detect install_env="build" and use the pre-created raw image +# The installer already has the correct path: target/sonic-aspeed.raw +log_info "Running installer (this may take a few minutes)..." +bash "$SONIC_BIN" || { + log_error "Installer failed" + exit 1 +} + +# Verify raw image was created +if [ ! -f "$RAW_IMAGE" ]; then + log_error "Raw image not created by installer" + exit 1 +fi + +log_info "Raw SONiC image created: $RAW_IMAGE ($(du -h $RAW_IMAGE | cut -f1))" + +# Step 2: Mark first boot in the raw image +# Note: machine.conf will be created by first-boot service based on DTB detection +log_info "Marking first boot in raw image..." +TEMP_MOUNT=$(mktemp -d) +mount -o loop "$RAW_IMAGE" "$TEMP_MOUNT" + +touch "$TEMP_MOUNT/.first_boot_from_prebuilt_image" + +umount "$TEMP_MOUNT" +rmdir "$TEMP_MOUNT" + +# Step 3: Create full eMMC disk image with GPT partitions +DISK_IMAGE="${OUTPUT_IMAGE%.gz}" +DISK_IMAGE="${DISK_IMAGE%.img}.img" + +log_info "Creating eMMC disk image (${EMMC_SIZE_MB} MB)..." +dd if=/dev/zero of="$DISK_IMAGE" bs=1M count=$EMMC_SIZE_MB + +log_info "Creating GPT partition table..." +sgdisk -o "$DISK_IMAGE" + +log_info "Creating single SONiC-OS partition using entire disk..." +sgdisk -n 1:0:0 -t 1:8300 -c 1:"$PARTITION_LABEL" "$DISK_IMAGE" + +log_info "Setting up loop device..." +LOOP_DEV=$(losetup -f --show -P "$DISK_IMAGE") +log_info "Loop device: $LOOP_DEV" +sleep 1 +partprobe "$LOOP_DEV" || true +sleep 1 + +log_info "Copying SONiC installation to SONiC-OS partition..." +dd if="$RAW_IMAGE" of="${LOOP_DEV}p1" bs=128M conv=sparse oflag=direct status=progress + +log_info "Cleaning up..." +losetup -d "$LOOP_DEV" +LOOP_DEV="" +rm -f "$RAW_IMAGE" +RAW_IMAGE="" + +log_info "Compressing image..." +gzip -f "$DISK_IMAGE" + +log_info "eMMC image created successfully: ${DISK_IMAGE}.gz" +log_info "" +log_info "Image size: $(du -h ${DISK_IMAGE}.gz | cut -f1)" + diff --git a/platform/aspeed/one-image.mk b/platform/aspeed/one-image.mk new file mode 100644 index 00000000000..45c3b18561e --- /dev/null +++ b/platform/aspeed/one-image.mk @@ -0,0 +1,25 @@ +# ONIE installer for Aspeed BMC platform with U-Boot + +SONIC_ONE_IMAGE = sonic-aspeed-arm64.bin +$(SONIC_ONE_IMAGE)_ARCH = arm64 +$(SONIC_ONE_IMAGE)_MACHINE = aspeed +$(SONIC_ONE_IMAGE)_PLATFORM = aspeed +$(SONIC_ONE_IMAGE)_IMAGE_TYPE = onie + +# Aspeed BMC is a minimal embedded platform - only include essential components +DISABLED_PACKAGES_LOCAL = $(DOCKER_DHCP_RELAY) $(DOCKER_SFLOW) $(DOCKER_MGMT_FRAMEWORK) \ + $(DOCKER_NAT) $(DOCKER_TEAMD) $(DOCKER_ROUTER_ADVERTISER) \ + $(DOCKER_MUX) $(DOCKER_MACSEC) \ + $(DOCKER_EVENTD) $(DOCKER_DASH_HA) $(DOCKER_STP) + +$(info [aspeed] Filtering out packages: $(DISABLED_PACKAGES_LOCAL)) +SONIC_PACKAGES_LOCAL := $(filter-out $(DISABLED_PACKAGES_LOCAL), $(SONIC_PACKAGES_LOCAL)) + +$(SONIC_ONE_IMAGE)_INSTALLS += $(SYSTEMD_SONIC_GENERATOR) +$(SONIC_ONE_IMAGE)_LAZY_INSTALLS += $(ASPEED_EVB_AST2700_PLATFORM_MODULE) +$(SONIC_ONE_IMAGE)_LAZY_INSTALLS += $(NEXTHOP_COMMON_PLATFORM_MODULE) +$(SONIC_ONE_IMAGE)_LAZY_INSTALLS += $(ASPEED_NEXTHOP_B27_PLATFORM_MODULE) + +# $(SONIC_ONE_IMAGE)_DOCKERS = $(DOCKER_DATABASE) $(DOCKER_GNMI) $(DOCKER_RESTAPI) $(DOCKER_PLATFORM_MONITOR) $(DOCKER_LLDP) $(DOCKER_TELEMETRY) $(DOCKER_SYSMGR) $(DOCKER_OBMC_CONSOLE) $(DOCKER_BMCWEB) +$(SONIC_ONE_IMAGE)_DOCKERS = $(DOCKER_DATABASE) $(DOCKER_GNMI) $(DOCKER_RESTAPI) $(DOCKER_PLATFORM_MONITOR) $(DOCKER_LLDP) $(DOCKER_TELEMETRY) $(DOCKER_SYSMGR) +SONIC_INSTALLERS += $(SONIC_ONE_IMAGE) diff --git a/platform/aspeed/onie-image-arm64.conf b/platform/aspeed/onie-image-arm64.conf new file mode 100644 index 00000000000..90650d8717c --- /dev/null +++ b/platform/aspeed/onie-image-arm64.conf @@ -0,0 +1,37 @@ +## DESCRIPTION: +## config for ONIE image for Aspeed BMC platform +## + +## Partition size in MB +: ${ONIE_IMAGE_PART_SIZE:=32768} + +## Target hardware information +: ${TARGET_PLATFORM:=arm64} +: ${TARGET_MACHINE:=aspeed} + +ONIEIMAGE_VERSION=r0 + +## Filesystem root +FILESYSTEM_ROOT=./fsroot-${TARGET_MACHINE} + +## Filename for squashfs file system +FILESYSTEM_SQUASHFS=fs.squashfs + +## Filename for onie installer payload, will be the main part of onie installer +INSTALLER_PAYLOAD=fs.zip + +## Filename for docker file system +FILESYSTEM_DOCKERFS=dockerfs.tar.gz + +## docker directory on the root filesystem +DOCKERFS_DIR=docker + +## docker ramfs disk space +DOCKER_RAMFS_SIZE=3500M + +## Output file name for onie installer +OUTPUT_ONIE_IMAGE=target/sonic-$TARGET_MACHINE-$CONFIGURED_ARCH.bin + +## Output file name for raw image (referenced in installer script even if not building raw image) +OUTPUT_RAW_IMAGE=target/sonic-$TARGET_MACHINE-$CONFIGURED_ARCH.raw + diff --git a/platform/aspeed/platform-modules-ast-evb.dep b/platform/aspeed/platform-modules-ast-evb.dep new file mode 100644 index 00000000000..bd31268b451 --- /dev/null +++ b/platform/aspeed/platform-modules-ast-evb.dep @@ -0,0 +1,9 @@ +MPATH := $($(ASPEED_EVB_AST2700_PLATFORM_MODULE)_SRC_PATH) +DEP_FILES := $(SONIC_COMMON_FILES_LIST) platform/aspeed/platform-modules-ast-evb.mk platform/aspeed/platform-modules-ast-evb.dep +DEP_FILES += $(SONIC_COMMON_BASE_FILES_LIST) +DEP_FILES += $(addprefix $(MPATH)/,$(shell cd $(MPATH) && git ls-files)) + +$(ASPEED_EVB_AST2700_PLATFORM_MODULE)_CACHE_MODE := GIT_CONTENT_SHA +$(ASPEED_EVB_AST2700_PLATFORM_MODULE)_DEP_FLAGS := $(SONIC_COMMON_FLAGS_LIST) +$(ASPEED_EVB_AST2700_PLATFORM_MODULE)_DEP_FILES := $(DEP_FILES) + diff --git a/platform/aspeed/platform-modules-ast-evb.mk b/platform/aspeed/platform-modules-ast-evb.mk new file mode 100644 index 00000000000..127039039ae --- /dev/null +++ b/platform/aspeed/platform-modules-ast-evb.mk @@ -0,0 +1,6 @@ +# Aspeed EVB Platform modules +# +ASPEED_EVB_AST2700_PLATFORM_MODULE = sonic-platform-ast-evb-ast2700_1.0_arm64.deb +$(ASPEED_EVB_AST2700_PLATFORM_MODULE)_SRC_PATH = $(PLATFORM_PATH)/sonic-platform-modules-ast-evb +$(ASPEED_EVB_AST2700_PLATFORM_MODULE)_PLATFORM = arm64-aspeed_ast2700_evb-r0 +SONIC_DPKG_DEBS += $(ASPEED_EVB_AST2700_PLATFORM_MODULE) diff --git a/platform/aspeed/platform-modules-nexthop.dep b/platform/aspeed/platform-modules-nexthop.dep new file mode 100644 index 00000000000..467a3c51327 --- /dev/null +++ b/platform/aspeed/platform-modules-nexthop.dep @@ -0,0 +1,9 @@ +MPATH := $($(NEXTHOP_COMMON_PLATFORM_MODULE)_SRC_PATH) +DEP_FILES := $(SONIC_COMMON_FILES_LIST) platform/aspeed/platform-modules-nexthop.mk platform/aspeed/platform-modules-nexthop.dep +DEP_FILES += $(SONIC_COMMON_BASE_FILES_LIST) +DEP_FILES += $(addprefix $(MPATH)/,$(shell cd $(MPATH) && git ls-files)) + +$(NEXTHOP_COMMON_PLATFORM_MODULE)_CACHE_MODE := GIT_CONTENT_SHA +$(NEXTHOP_COMMON_PLATFORM_MODULE)_DEP_FLAGS := $(SONIC_COMMON_FLAGS_LIST) +$(NEXTHOP_COMMON_PLATFORM_MODULE)_DEP_FILES := $(DEP_FILES) + diff --git a/platform/aspeed/platform-modules-nexthop.mk b/platform/aspeed/platform-modules-nexthop.mk new file mode 100644 index 00000000000..e0de9eeb356 --- /dev/null +++ b/platform/aspeed/platform-modules-nexthop.mk @@ -0,0 +1,20 @@ +# NextHop Platform modules +# + +# Common package - contains USB network gadget and shared utilities +NEXTHOP_COMMON_PLATFORM_MODULE = sonic-platform-aspeed-nexthop-common_1.0_arm64.deb +$(NEXTHOP_COMMON_PLATFORM_MODULE)_SRC_PATH = $(PLATFORM_PATH)/sonic-platform-modules-nexthop +$(NEXTHOP_COMMON_PLATFORM_MODULE)_DEPENDS += $(LINUX_HEADERS) $(LINUX_HEADERS_COMMON) +$(NEXTHOP_COMMON_PLATFORM_MODULE)_PLATFORM = arm64-nexthop-common +SONIC_DPKG_DEBS += $(NEXTHOP_COMMON_PLATFORM_MODULE) + +# B27 platform package +ASPEED_NEXTHOP_B27_PLATFORM_MODULE = sonic-platform-aspeed-nexthop-b27_1.0_arm64.deb +$(ASPEED_NEXTHOP_B27_PLATFORM_MODULE)_PLATFORM = arm64-nexthop_b27-r0 +$(eval $(call add_extra_package,$(NEXTHOP_COMMON_PLATFORM_MODULE),$(ASPEED_NEXTHOP_B27_PLATFORM_MODULE))) + +# Future B28 platform (example): +# ASPEED_NEXTHOP_B28_PLATFORM_MODULE = sonic-platform-aspeed-nexthop-b28_1.0_arm64.deb +# $(ASPEED_NEXTHOP_B28_PLATFORM_MODULE)_PLATFORM = arm64-nexthop_b28-r0 +# $(eval $(call add_extra_package,$(NEXTHOP_COMMON_PLATFORM_MODULE),$(ASPEED_NEXTHOP_B28_PLATFORM_MODULE))) + diff --git a/platform/aspeed/platform_arm64.conf b/platform/aspeed/platform_arm64.conf new file mode 100644 index 00000000000..546bbfddb0c --- /dev/null +++ b/platform/aspeed/platform_arm64.conf @@ -0,0 +1,377 @@ +#!/bin/sh +# Implementation of installation behavior for Aspeed AST2700 BMC platform + +echo "Preparing for Aspeed AST2700 installation..." + +# SONiC filesystem definitions (must match installer/install.sh) +DOCKERFS_DIR=docker +FILESYSTEM_DOCKERFS=dockerfs.tar.gz +FILESYSTEM_SQUASHFS=fs.squashfs + +# FIT image configuration +fit_fname="/boot/sonic_arm64.fit" + +# Device tree blob configuration +# The DTB is selected at boot time by U-Boot based on hardware detection +# U-Boot sets the 'bootconf' environment variable to the appropriate configuration name +# This supports multiple vendors with a single image containing multiple DTBs in a FIT image +# Example: bootconf="ast2700-evb" or bootconf="nexthop-b27-r0" +# The bootconf variable is used to select the FIT configuration: #conf-${bootconf} +# Note: bootconf contains the base name without .dtb extension + +# Platform detection +# During ONIE or SONiC installation, $onie_platform is already set by install.sh from machine.conf +# During build (install_env=build), machine.conf doesn't exist yet, use a default +if [ "$install_env" = "build" ]; then + onie_platform="arm64-aspeed_ast2700_evb-r0" + echo "Build mode: Using default platform $onie_platform" +else + # Try to load device-specific configuration + if [ -n "$onie_platform" ] && [ -r "platforms/$onie_platform" ]; then + . "platforms/$onie_platform" + else + echo "Warning: No device-specific configuration found for $onie_platform" + echo " Will use default configuration for Aspeed AST2700" + fi +fi + +# Set defaults when invoked from 'build' install env OR +# if not specified in installer.conf +# Override these values in device-specific installer.conf +# AND here if hand crafting a disk image to burn for the +# first install +CONSOLE_DEV=${CONSOLE_DEV:-12} +CONSOLE_SPEED=${CONSOLE_SPEED:-115200} +EARLYCON=${EARLYCON:-"earlycon=uart8250,mmio32,0x14c33b00"} +VAR_LOG=${VAR_LOG:-${VAR_LOG_SIZE:-4096}} + +# Construct console device name +CONSOLE_PORT="ttyS${CONSOLE_DEV}" + +echo "Installing SONiC from ${install_env:-unknown} on Platform $onie_platform" + +# AST2700 platform configuration +disk_interface="mmc" +mmc_bus="mmc0:0001" + +# Device tree and FIT image configuration +# DTB is embedded in FIT image, selected by bootconf U-Boot variable +# FIT configuration is selected by U-Boot bootconf variable +# bootconf is set by U-Boot based on hardware detection +# Example: bootconf="ast2700-evb" selects #conf-ast2700-evb from FIT image + +# U-Boot memory addresses for AST2700 +# Based on actual hardware memory map from bdinfo: +# - RAM: 0x400000000 - 0x47e000000 (2016 MB) +# - Reserved: 0x41b800000-0x423ffffff (136 MB), 0x42c000000-0x431bfffff (92 MB) +# - Safe addresses avoid reserved regions +# See platform/aspeed/AST2700-MEMORY-MAP.md for details +# +# Memory Layout (works for both production and installation initramfs): +# 0x403000000: Kernel (~10 MB) +# 0x440000000: Initramfs (192 MB allocated for decompression) +# - Production initramfs: 37.4 MB compressed, 160 MB uncompressed +# - Installation initramfs: 3-5 MB compressed, ~10 MB uncompressed +# 0x44C000000: DTB (~50 KB) - after initramfs (0x440000000 + 192 MB) +# +# IMPORTANT: Initramfs is compressed (zstd) but kernel decompresses IN PLACE +# Need space for UNCOMPRESSED size (160 MB), not compressed size (37 MB)! +# +# Memory layout (avoiding reserved regions 0x41b800000-0x423ffffff and 0x42c000000-0x431bfffff): +# fit_addr: where FIT image is loaded (temporary, ~100 MB, after reserved region 2) +# kernel_addr: where kernel is extracted from FIT (U-Boot default loadaddr) +# initrd_addr: where initrd is extracted from FIT (after reserved regions, 192 MB allocated) +# fdt_addr: where device tree blob is extracted from FIT (after initramfs space) +fit_addr=0x432000000 +kernel_addr=0x403000000 +initrd_addr=0x440000000 +fdt_addr=0x44C000000 + +# U-Boot environment location +# AST2700 typically stores U-Boot env in SPI flash (MTD) +FW_ENV_DEFAULT='/dev/mtd1 0x0 0x20000 0x10000' + +# Extra kernel command line for GRUB bootloader configuration +# This is appended to the kernel command line in the generated grub.cfg +ONIE_PLATFORM_EXTRA_CMDLINE_LINUX="$EARLYCON console=$CONSOLE_PORT,${CONSOLE_SPEED}n8" + +# Get eMMC block device +# AST2700 EVB has eMMC at /dev/mmcblk0 (mmc0:0001) +get_install_device() +{ + # In build mode, there's no hardware - skip device detection + if [ "$install_env" = "build" ]; then + return 0 + fi + + if [ ! -z "$mmc_bus" ]; then + for i in 0 1 2 ; do + if $(ls -l /sys/block/mmcblk$i/device 2>/dev/null | grep -q "$mmc_bus") ; then + echo "/dev/mmcblk$i" + blk_dev=/dev/mmcblk$i + disk_interface="mmc" + echo "Selected eMMC device: $blk_dev" + return + fi + done + fi + + echo "Warning: eMMC not found. Will try installing on the same disk as ONIE." +} + +get_install_device + +# Configure U-Boot environment access +configure_uboot_env() { + echo "Configuring U-Boot environment access..." + + # Check for MTD device first by parsing /proc/mtd + # Example line: mtd1: 00020000 00010000 "u-boot-env" + MTD_ENV_LINE=$(grep -E '"u-boot-env"|"uboot-env"' /proc/mtd 2>/dev/null || true) + + if [ -n "$MTD_ENV_LINE" ]; then + # Parse MTD device name, size, and erasesize from /proc/mtd + MTD_DEV=$(echo "$MTD_ENV_LINE" | awk -F: '{print $1}') + MTD_SIZE=$(echo "$MTD_ENV_LINE" | awk '{print "0x" $2}') + MTD_ERASESIZE=$(echo "$MTD_ENV_LINE" | awk '{print "0x" $3}') + + if [ -c "/dev/$MTD_DEV" ]; then + FW_ENV_CONFIG="/dev/$MTD_DEV 0x0 $MTD_SIZE $MTD_ERASESIZE" + echo "Detected U-Boot env from /proc/mtd: $FW_ENV_CONFIG" + fi + fi + + # If not found in MTD, try device tree + if [ -z "$FW_ENV_CONFIG" ]; then + DTB_HAS_ENV_BLK=$(grep -E "uboot-env|u-boot-env" /proc/mtd 2>/dev/null | sed -e 's/:.*$//' || true) + if [ -n "$DTB_HAS_ENV_BLK" ] && [ -c "/dev/$DTB_HAS_ENV_BLK" ]; then + PROC_ENV_FILE=$(find /proc/device-tree/ -name env_size 2>/dev/null || true) + if [ -n "$PROC_ENV_FILE" ]; then + UBOOT_ENV_SIZ="0x$(hd $PROC_ENV_FILE | awk 'FNR==1 {print $2 $3 $4 $5}')" + UBOOT_ENV_ERASE_SIZ="0x$(grep -E "uboot-env|u-boot-env" /proc/mtd | awk '{print $3}')" + if [[ -n "$UBOOT_ENV_SIZ" && -n "$UBOOT_ENV_ERASE_SIZ" ]]; then + FW_ENV_CONFIG="/dev/$DTB_HAS_ENV_BLK 0x00000000 $UBOOT_ENV_SIZ $UBOOT_ENV_ERASE_SIZ" + echo "Detected U-Boot env from device tree: $FW_ENV_CONFIG" + fi + fi + fi + fi + + # If still not found, use eMMC default location + if [ -z "$FW_ENV_CONFIG" ]; then + # AST2700 default: U-Boot environment on eMMC at 31.25 MB offset + # This matches typical U-Boot configuration for eMMC devices + FW_ENV_CONFIG="/dev/mmcblk0 0x1F40000 0x20000 0x1000" + echo "Using default eMMC U-Boot env location: $FW_ENV_CONFIG" + fi + + # Write configuration file + echo "$FW_ENV_CONFIG" > /etc/fw_env.config + echo "Created /etc/fw_env.config: $FW_ENV_CONFIG" + + # Verify fw_printenv can access the environment + if fw_printenv bootcmd >/dev/null 2>&1; then + echo "U-Boot environment is accessible" + else + echo "WARNING: Cannot access U-Boot environment. fw_printenv/fw_setenv may not work." + echo "You may need to adjust the offset in /etc/fw_env.config" + fi +} + +# Prepare U-Boot boot menu +prepare_boot_menu() { + echo "Sync up cache ..." + sync + echo "Setting up U-Boot environment for Aspeed AST2700..." + + # Configure U-Boot environment access first + # This function detects MTD, device tree, or uses eMMC default + configure_uboot_env + + # U-Boot uses relative paths (without leading /) + # image_dir from installer.sh is already in correct format (e.g., "image-version") + fit_name=${image_dir}${fit_fname} + + # Get partition UUID for the installation target (single SONiC-OS partition) + uuid=$(blkid | grep "$demo_volume_label" | sed -ne 's/.* UUID=\"\([^"]*\)\".*/\1/p') + demo_part=$(sgdisk -p $blk_dev | grep -e "$demo_volume_label" | awk '{print $1}') + + # Standard SONiC approach: Single partition with multiple image directories + # sonic_image_1 = current/new image + # sonic_image_2 = previous image (for rollback) + + if [ "$install_env" = "onie" ] || [ "$install_env" = "build" ]; then + # First installation - initialize boot configuration + echo "First installation: Setting up boot configuration for image directory: $image_dir" + + fw_setenv image_dir_old "" + fw_setenv fit_name_old "" + fw_setenv sonic_version_2 "None" + fw_setenv linuxargs_old "" + else + # Upgrade scenario - save current image as "old" (image_2) + echo "Upgrade installation: Saving current image as backup" + + CURR_SONIC_IMAGE="$(sonic-installer list | grep "Current: " | cut -f2 -d' ')" + FIRST_SONIC_IMAGE="$(fw_printenv sonic_version_1 2>/dev/null | cut -f2 -d'=')" + SECOND_SONIC_IMAGE="$(fw_printenv sonic_version_2 2>/dev/null | cut -f2 -d'=')" + + echo "Current running image: $CURR_SONIC_IMAGE" + echo "Image slot 1 (sonic_version_1): $FIRST_SONIC_IMAGE" + echo "Image slot 2 (sonic_version_2): $SECOND_SONIC_IMAGE" + + # Determine which slot the current image is in + if [ "$CURR_SONIC_IMAGE" = "$FIRST_SONIC_IMAGE" ]; then + # Currently running image_1, save it to image_2 slot + echo "Current image matches slot 1, saving to slot 2 as backup" + image_dir_old=$(fw_printenv -n image_dir 2>/dev/null || true) + fit_name_old=$(fw_printenv -n fit_name 2>/dev/null || true) + sonic_version_2="$CURR_SONIC_IMAGE" + linuxargs_old=$(fw_printenv -n linuxargs 2>/dev/null || true) + + echo "Saving current image to slot 2: $sonic_version_2" + fw_setenv image_dir_old "$image_dir_old" + fw_setenv fit_name_old "$fit_name_old" + fw_setenv sonic_version_2 "$sonic_version_2" + fw_setenv linuxargs_old "$linuxargs_old" + elif [ "$CURR_SONIC_IMAGE" = "$SECOND_SONIC_IMAGE" ]; then + # Currently running image_2, keep it in slot 2 + echo "Current image matches slot 2, keeping it as backup" + # No need to update image_2 variables, they're already correct + else + # Current image doesn't match either slot - this shouldn't happen in normal operation + # But we'll save it anyway to be safe + echo "WARNING: Current image doesn't match any slot, saving to slot 2 anyway" + + # Try to find the current image directory from /proc/cmdline (most reliable) + CURR_IMAGE_DIR=$(cat /proc/cmdline | sed -n 's/^.*loop=\/*\(image-[^/]*\)\/.*$/\1/p') + + if [ -z "$CURR_IMAGE_DIR" ]; then + # Fallback: derive from sonic-installer list + CURR_IMAGE_DIR=$(echo "$CURR_SONIC_IMAGE" | sed 's/SONiC-OS-/image-/') + fi + + if [ -n "$CURR_IMAGE_DIR" ] && [ -d "/host/$CURR_IMAGE_DIR" ]; then + image_dir_old="$CURR_IMAGE_DIR" + fit_name_old="$CURR_IMAGE_DIR/boot/sonic_arm64.fit" + sonic_version_2="$CURR_SONIC_IMAGE" + + # Reconstruct linuxargs from current boot + linuxargs_old="console=$CONSOLE_PORT,${CONSOLE_SPEED}n8 $EARLYCON loopfstype=squashfs loop=$CURR_IMAGE_DIR/$FILESYSTEM_SQUASHFS varlog_size=$VAR_LOG" + + echo "Saving current image to slot 2: $sonic_version_2" + fw_setenv image_dir_old "$image_dir_old" + fw_setenv fit_name_old "$fit_name_old" + fw_setenv sonic_version_2 "$sonic_version_2" + fw_setenv linuxargs_old "$linuxargs_old" + else + echo "ERROR: Cannot find current image directory, skipping backup" + fi + fi + fi + + # Set current image variables (image_1) + echo "Setting sonic_version_1 to: $demo_volume_revision_label" + + fw_setenv image_dir "$image_dir" + fw_setenv fit_name "$fit_name" + fw_setenv sonic_version_1 "$demo_volume_revision_label" + + # Boot menu with instructions + BORDER='echo "===================================================";' + TITLE='echo "SONiC Boot Menu";' + BOOT1='echo "To boot $sonic_version_1";echo " type: run sonic_image_1";echo " at the U-Boot prompt after interrupting U-Boot when it says";echo " \"Hit any key to stop autoboot:\" during boot";echo;' + BOOT2='echo "To boot $sonic_version_2";echo " type: run sonic_image_2";echo " at the U-Boot prompt after interrupting U-Boot when it says";echo " \"Hit any key to stop autoboot:\" during boot";echo;' + FOOTER='echo "===================================================";' + fw_setenv print_menu "$BORDER $TITLE $BORDER $BOOT1 $BOOT2 $FOOTER" + + # Boot arguments for current image (linuxargs contains loop path) + fw_setenv linuxargs "console=$CONSOLE_PORT,${CONSOLE_SPEED}n8 $EARLYCON loopfstype=squashfs loop=$image_dir/$FILESYSTEM_SQUASHFS varlog_size=$VAR_LOG" + + # Boot commands - always load from partition 1 (SONiC-OS) + DISK_LOAD="ext4load $disk_interface 0:$demo_part \$loadaddr \$fit_name" + BOOTARGS='setenv bootargs root=UUID='$uuid' rw rootwait panic=1 ${linuxargs}' + SONIC_BOOT_CMD='run sonic_bootargs; run sonic_boot_load; bootm $loadaddr#conf-$bootconf' + + # Old image boot commands + sonic_bootargs_old='setenv bootargs root=UUID='$uuid' rw rootwait panic=1 ${linuxargs_old}' + DISK_LOAD_OLD="ext4load $disk_interface 0:$demo_part \$loadaddr \$fit_name_old" + SONIC_BOOT_CMD_OLD='run sonic_bootargs_old; run sonic_boot_load_old; bootm $loadaddr#conf-$bootconf' + + # Set U-Boot variables + fw_setenv sonic_boot_load "$DISK_LOAD" + fw_setenv sonic_boot_load_old "$DISK_LOAD_OLD" + fw_setenv sonic_bootargs "$BOOTARGS" + fw_setenv sonic_bootargs_old "$sonic_bootargs_old" + fw_setenv sonic_image_1 "$SONIC_BOOT_CMD" + fw_setenv sonic_image_2 "$SONIC_BOOT_CMD_OLD" + fw_setenv boot_next 'run sonic_image_1' + fw_setenv bootcmd 'run print_menu; test -n "$boot_once" && setenv do_boot_once "$boot_once" && setenv boot_once "" && saveenv && run do_boot_once; run boot_next' + + # Set memory addresses (used by manual boot if needed) + fw_setenv loadaddr $fit_addr + fw_setenv kernel_addr $kernel_addr + fw_setenv fdt_addr $fdt_addr + fw_setenv initrd_addr $initrd_addr + + # Display installation summary + echo "" + echo "=========================================" + echo "SONiC Installation Complete" + echo "=========================================" + echo "Installed to: Partition $demo_part ($demo_volume_label)" + echo "Image: $demo_volume_revision_label" + echo "" + + if [ "$install_env" != "onie" ] && [ "$install_env" != "build" ]; then + # Get current U-Boot variables for display + UBOOT_VERSION_1=$(fw_printenv -n sonic_version_1 2>/dev/null || echo "None") + UBOOT_VERSION_2=$(fw_printenv -n sonic_version_2 2>/dev/null || echo "None") + + echo "Dual-boot configuration:" + echo " - Image 1 (sonic_version_1): $UBOOT_VERSION_1" + echo " - Image 2 (sonic_version_2): $UBOOT_VERSION_2" + echo " - Next boot: Image 1" + echo "" + echo "To switch to backup image, run:" + echo " sonic-installer set-default $UBOOT_VERSION_2" + echo "" + fi + + echo "U-Boot configuration updated successfully" + echo "=========================================" +} + +# Configure bootloader menu +bootloader_menu_config() { + # In "build" mode (creating raw image), we can't access U-Boot environment + # Skip U-Boot configuration in this case + if [ "$install_env" = "build" ]; then + echo "Build mode: Skipping U-Boot environment configuration" + echo "U-Boot environment will be configured on first boot" + + # Sync to flush all writes + sync + + # Convert demo_mnt to absolute path before changing directory + # (demo_mnt is a relative path like "build_raw_image_mnt") + demo_mnt_abs=$(realpath "$demo_mnt") + + # Change back to root directory BEFORE unmounting (critical for cleanup) + cd / + + # Unmount the raw image using absolute path + umount "$demo_mnt_abs" || { + echo "Warning: Failed to unmount $demo_mnt_abs" + # Try lazy unmount as fallback + umount -l "$demo_mnt_abs" || true + } + + echo "Installed SONiC base image $demo_volume_label successfully" + return 0 + fi + + # Update U-Boot Environment for ONIE or SONiC installation + prepare_boot_menu +} + diff --git a/platform/aspeed/rules.mk b/platform/aspeed/rules.mk new file mode 100644 index 00000000000..0fb44e3b5da --- /dev/null +++ b/platform/aspeed/rules.mk @@ -0,0 +1,5 @@ +include $(PLATFORM_PATH)/platform-modules-ast-evb.mk +include $(PLATFORM_PATH)/platform-modules-nexthop.mk +include $(PLATFORM_PATH)/one-image.mk + +SONIC_ALL += $(SONIC_ONE_IMAGE) diff --git a/platform/aspeed/scripts/sonic-machine-conf-init.sh b/platform/aspeed/scripts/sonic-machine-conf-init.sh new file mode 100755 index 00000000000..14396b7d0d0 --- /dev/null +++ b/platform/aspeed/scripts/sonic-machine-conf-init.sh @@ -0,0 +1,69 @@ +#!/bin/bash +# SONiC Machine Configuration Initialization Script +# This script creates /host/machine.conf on first boot by detecting hardware from DTB + +set -e + +LOG_FILE="/var/log/sonic-machine-conf-init.log" +MACHINE_CONF="/host/machine.conf" + +log() { + echo "[$(date '+%Y-%m-%d %H:%M:%S')] $*" | tee -a "$LOG_FILE" +} + +log "Starting machine.conf initialization..." + +# Check if machine.conf already exists +if [ -f "$MACHINE_CONF" ]; then + log "machine.conf already exists at $MACHINE_CONF. Skipping initialization." + exit 0 +fi + +# Detect hardware from device tree compatible string +if [ ! -f /proc/device-tree/compatible ]; then + log "ERROR: /proc/device-tree/compatible not found. Cannot detect hardware." + exit 1 +fi + +# Read compatible string (null-separated values) +COMPATIBLE=$(cat /proc/device-tree/compatible | tr '\0' ' ') +log "Detected compatible string: $COMPATIBLE" + +# Map compatible string to platform name +# The compatible string contains multiple values, we check for specific vendor strings +PLATFORM="" +MACHINE="" + +if echo "$COMPATIBLE" | grep -q "nexthop,nexthop-b27-r0"; then + PLATFORM="arm64-nexthop_b27-r0" + MACHINE="aspeed_ast2700" + log "Detected NextHop B27 BMC platform" +elif echo "$COMPATIBLE" | grep -q "aspeed,ast2700-evb"; then + PLATFORM="arm64-aspeed_ast2700_evb-r0" + MACHINE="aspeed_ast2700" + log "Detected Aspeed AST2700 EVB platform" +else + log "ERROR: Unknown hardware. Compatible string: $COMPATIBLE" + log "Supported platforms:" + log " - nexthop,nexthop-b27-r0 -> arm64-nexthop_b27-r0" + log " - aspeed,ast2700-evb -> arm64-aspeed_ast2700_evb-r0" + exit 1 +fi + +# Create machine.conf +log "Creating $MACHINE_CONF for platform: $PLATFORM" + +cat > "$MACHINE_CONF" << EOF +onie_platform=$PLATFORM +onie_machine=$MACHINE +onie_arch=arm64 +onie_build_platform=$PLATFORM +EOF + +log "Created $MACHINE_CONF:" +cat "$MACHINE_CONF" | tee -a "$LOG_FILE" + +log "Machine configuration initialization complete" + +exit 0 + diff --git a/platform/aspeed/scripts/sonic-platform-init.sh b/platform/aspeed/scripts/sonic-platform-init.sh new file mode 100755 index 00000000000..1b2b2547869 --- /dev/null +++ b/platform/aspeed/scripts/sonic-platform-init.sh @@ -0,0 +1,88 @@ +#!/bin/bash +# Aspeed Platform Initialization Script +# This script sets up the platform symlink and installs platform wheels on the host + +set -e + +LOG_FILE="/var/log/aspeed-platform-init.log" +PLATFORM_SYMLINK="/usr/share/sonic/platform" + +log() { + echo "[$(date '+%Y-%m-%d %H:%M:%S')] $*" | tee -a "$LOG_FILE" +} + +log "Starting Aspeed platform initialization..." + +# Get platform name from machine.conf +if [ ! -f /host/machine.conf ]; then + log "ERROR: /host/machine.conf not found. Cannot determine platform." + exit 1 +fi + +# Source machine.conf to get platform +. /host/machine.conf + +PLATFORM="" +if [ -n "$aboot_platform" ]; then + PLATFORM="$aboot_platform" +elif [ -n "$onie_platform" ]; then + PLATFORM="$onie_platform" +else + log "ERROR: Could not determine platform from /host/machine.conf" + exit 1 +fi + +log "Detected platform: $PLATFORM" + +PLATFORM_DEVICE_DIR="/usr/share/sonic/device/$PLATFORM" + +# Check if platform device directory exists +if [ ! -d "$PLATFORM_DEVICE_DIR" ]; then + log "ERROR: Platform device directory not found: $PLATFORM_DEVICE_DIR" + exit 1 +fi + +# Create platform symlink for host services (watchdog-control, etc.) +log "Creating platform symlink..." + +# Remove old symlink if it exists +if [ -L "$PLATFORM_SYMLINK" ]; then + log "Removing existing symlink: $PLATFORM_SYMLINK" + rm -f "$PLATFORM_SYMLINK" +elif [ -e "$PLATFORM_SYMLINK" ]; then + log "WARNING: $PLATFORM_SYMLINK exists but is not a symlink. Removing..." + rm -rf "$PLATFORM_SYMLINK" +fi + +# Create the symlink +ln -sf "$PLATFORM_DEVICE_DIR" "$PLATFORM_SYMLINK" +log "Created symlink: $PLATFORM_SYMLINK -> $PLATFORM_DEVICE_DIR" + +# Install platform wheels on host for services like watchdog-control +# These services run on the host (not in containers) and need to import sonic_platform +log "Installing platform wheels on host..." + +WHEEL_COUNT=$(find "$PLATFORM_DEVICE_DIR" -maxdepth 1 -name "*.whl" 2>/dev/null | wc -l) + +if [ "$WHEEL_COUNT" -eq 0 ]; then + log "WARNING: No .whl files found in $PLATFORM_DEVICE_DIR" +else + log "Found $WHEEL_COUNT wheel file(s) to install" + + # Install wheels without dependencies (dependencies should already be installed) + for wheel in "$PLATFORM_DEVICE_DIR"/*.whl; do + if [ -f "$wheel" ]; then + log "Installing: $(basename $wheel)" + if pip3 install --no-deps "$wheel"; then + log "Successfully installed: $(basename $wheel)" + else + log "WARNING: Failed to install: $(basename $wheel)" + fi + fi + done +fi + +log "Aspeed platform initialization complete" + +exit 0 + diff --git a/platform/aspeed/scripts/sonic-switchcpu-console-init.sh b/platform/aspeed/scripts/sonic-switchcpu-console-init.sh new file mode 100755 index 00000000000..23e83b27533 --- /dev/null +++ b/platform/aspeed/scripts/sonic-switchcpu-console-init.sh @@ -0,0 +1,98 @@ +#!/bin/bash +# Aspeed Switch CPU Console Initialization Script +# This script configures the switch CPU console for consutil access + +set -e + +LOG_FILE="/var/log/aspeed-switchcpu-console-init.log" +PLATFORM_SYMLINK="/usr/share/sonic/platform" + +log() { + echo "[$(date '+%Y-%m-%d %H:%M:%S')] $*" | tee -a "$LOG_FILE" +} + +log "Starting switch CPU console initialization..." + +# Verify platform symlink exists +if [ ! -L "$PLATFORM_SYMLINK" ]; then + log "ERROR: Platform symlink $PLATFORM_SYMLINK does not exist" + exit 1 +fi + +RULES_SRC="${PLATFORM_SYMLINK}/99-switchCpu.rules" +RULES_DEST="/etc/udev/rules.d/99-switchCpu.rules" +CONSOLE_CONF="${PLATFORM_SYMLINK}/switch_cpu_console.conf" + +# Set default console parameters +CONSOLE_BAUD_RATE=115200 +CONSOLE_FLOW_CONTROL=0 +CONSOLE_REMOTE_DEVICE=SwitchCpu + +# Load platform-specific console configuration if available +if [ -f "$CONSOLE_CONF" ]; then + log "Loading console configuration from $CONSOLE_CONF" + # shellcheck disable=SC1090 + . "$CONSOLE_CONF" + log "Console settings: baud=$CONSOLE_BAUD_RATE, flow=$CONSOLE_FLOW_CONTROL, device=$CONSOLE_REMOTE_DEVICE" +else + log "No console.conf found, using defaults" +fi + +# Check if platform provides switch CPU console rules +if [ ! -f "$RULES_SRC" ]; then + log "No switch CPU console rules found at $RULES_SRC, skipping console setup" + exit 0 +fi + +log "Found switch CPU console rules, configuring console..." + +# Symlink udev rules +ln -sf "$RULES_SRC" "$RULES_DEST" +log "Created symlink: $RULES_DEST -> $RULES_SRC" + +# Enable console feature +log "Enabling console feature..." +if config console enable; then + log "Console feature enabled via config command" +else + log "WARNING: config console enable failed, trying direct DB write..." +fi + +# Verify and ensure it's actually enabled in CONFIG_DB +if sonic-db-cli CONFIG_DB HSET "CONSOLE_SWITCH|console_mgmt" enabled yes; then + log "Console feature enabled in CONFIG_DB" +else + log "ERROR: Failed to enable console feature in CONFIG_DB" + exit 1 +fi + +# Reload udev to activate the rules +log "Reloading udev rules..." +udevadm control --reload-rules +udevadm trigger +log "Udev rules reloaded and triggered" + +# Configure CONSOLE_PORT|0 with platform settings +log "Configuring CONSOLE_PORT|0 in CONFIG_DB..." + +# Check if entry exists +if sonic-db-cli CONFIG_DB HGETALL "CONSOLE_PORT|0" >/dev/null 2>&1; then + log "CONSOLE_PORT|0 already exists, deleting to ensure correct settings..." + sonic-db-cli CONFIG_DB DEL "CONSOLE_PORT|0" +fi + +# Create with platform-specific settings +log "Creating CONSOLE_PORT|0 with baud=$CONSOLE_BAUD_RATE, flow=$CONSOLE_FLOW_CONTROL, device=$CONSOLE_REMOTE_DEVICE" +if sonic-db-cli CONFIG_DB HMSET "CONSOLE_PORT|0" \ + baud_rate "$CONSOLE_BAUD_RATE" \ + flow_control "$CONSOLE_FLOW_CONTROL" \ + remote_device "$CONSOLE_REMOTE_DEVICE"; then + log "Created CONSOLE_PORT|0 successfully" +else + log "ERROR: Failed to create CONSOLE_PORT|0" + exit 1 +fi + +log "Switch CPU console initialization complete" +exit 0 + diff --git a/platform/aspeed/scripts/sonic-uboot-env-init.sh b/platform/aspeed/scripts/sonic-uboot-env-init.sh new file mode 100755 index 00000000000..3aafa30967e --- /dev/null +++ b/platform/aspeed/scripts/sonic-uboot-env-init.sh @@ -0,0 +1,237 @@ +#!/bin/bash +# SONiC U-Boot Environment Initialization Script +# This script has two parts: +# 1. Always create /etc/fw_env.config (runs every boot for every image) +# 2. Set U-Boot environment variables (runs once globally, controlled by marker file) + +set -e + +LOG_FILE="/var/log/sonic-uboot-env-init.log" +MARKER_FILE="/host/.uboot-env-initialized" + +log() { + echo "[$(date '+%Y-%m-%d %H:%M:%S')] $*" | tee -a "$LOG_FILE" +} + +log "Starting U-Boot environment configuration..." + +# Check if fw_setenv is available +if ! command -v fw_setenv &> /dev/null; then + log "ERROR: fw_setenv not found. Cannot configure U-Boot environment." + exit 1 +fi + +# Wait for /proc/mtd to be available (up to 10 seconds) +# MTD devices may not be ready immediately during early boot +WAIT_COUNT=0 +MAX_WAIT=10 +while [ ! -f /proc/mtd ] && [ $WAIT_COUNT -lt $MAX_WAIT ]; do + log "Waiting for /proc/mtd to be available... ($WAIT_COUNT/$MAX_WAIT)" + sleep 1 + WAIT_COUNT=$((WAIT_COUNT + 1)) +done + +if [ ! -f /proc/mtd ]; then + log "WARNING: /proc/mtd not available after ${MAX_WAIT}s, will use default configuration" +fi + +############################################################################# +# PART 1: Create /etc/fw_env.config (ALWAYS runs, every boot, every image) +############################################################################# +log "Configuring U-Boot environment access..." + +# Check for MTD device first by parsing /proc/mtd +# Example line: mtd1: 00020000 00010000 "u-boot-env" +MTD_ENV_LINE=$(grep -E '"u-boot-env"|"uboot-env"' /proc/mtd 2>/dev/null || true) + +if [ -n "$MTD_ENV_LINE" ]; then + # Parse MTD device name, size, and erasesize from /proc/mtd + MTD_DEV=$(echo "$MTD_ENV_LINE" | awk -F: '{print $1}') + MTD_SIZE=$(echo "$MTD_ENV_LINE" | awk '{print "0x" $2}') + MTD_ERASESIZE=$(echo "$MTD_ENV_LINE" | awk '{print "0x" $3}') + + if [ -c "/dev/$MTD_DEV" ]; then + FW_ENV_CONFIG="/dev/$MTD_DEV 0x0 $MTD_SIZE $MTD_ERASESIZE" + log "Detected U-Boot env from /proc/mtd: $FW_ENV_CONFIG" + fi +fi + +# If not found in MTD, try device tree +if [ -z "$FW_ENV_CONFIG" ]; then + DTB_HAS_ENV_BLK=$(grep -E "uboot-env|u-boot-env" /proc/mtd 2>/dev/null | sed -e 's/:.*$//' || true) + if [ -n "$DTB_HAS_ENV_BLK" ] && [ -c "/dev/$DTB_HAS_ENV_BLK" ]; then + PROC_ENV_FILE=$(find /proc/device-tree/ -name env_size 2>/dev/null || true) + if [ -n "$PROC_ENV_FILE" ]; then + UBOOT_ENV_SIZ="0x$(hd $PROC_ENV_FILE | awk 'FNR==1 {print $2 $3 $4 $5}')" + UBOOT_ENV_ERASE_SIZ="0x$(grep -E "uboot-env|u-boot-env" /proc/mtd | awk '{print $3}')" + if [[ -n "$UBOOT_ENV_SIZ" && -n "$UBOOT_ENV_ERASE_SIZ" ]]; then + FW_ENV_CONFIG="/dev/$DTB_HAS_ENV_BLK 0x00000000 $UBOOT_ENV_SIZ $UBOOT_ENV_ERASE_SIZ" + log "Detected U-Boot env from device tree: $FW_ENV_CONFIG" + fi + fi + fi +fi + +# If still not found, use eMMC default location +if [ -z "$FW_ENV_CONFIG" ]; then + # AST2700 default: U-Boot environment on eMMC at 31.25 MB offset + FW_ENV_CONFIG="/dev/mmcblk0 0x1F40000 0x20000 0x1000" + log "Using default eMMC U-Boot env location: $FW_ENV_CONFIG" +fi + +# Update /etc/fw_env.config if it's different from current content +if [ -f /etc/fw_env.config ]; then + CURRENT_CONFIG=$(cat /etc/fw_env.config) + if [ "$CURRENT_CONFIG" != "$FW_ENV_CONFIG" ]; then + log "Updating /etc/fw_env.config (was: $CURRENT_CONFIG)" + echo "$FW_ENV_CONFIG" > /etc/fw_env.config + log "Updated /etc/fw_env.config: $FW_ENV_CONFIG" + else + log "Using existing /etc/fw_env.config: $FW_ENV_CONFIG" + fi +else + echo "$FW_ENV_CONFIG" > /etc/fw_env.config + log "Created /etc/fw_env.config: $FW_ENV_CONFIG" +fi + +############################################################################# +# PART 2: Set U-Boot environment variables (runs ONCE globally) +############################################################################# + +# Check if ONIE partition is mounted and skip U-Boot env setup if found +if blkid -L "ONIE" >/dev/null 2>&1 || blkid -L "ONIE-BOOT" >/dev/null 2>&1; then + echo "ONIE parition found. skipping uboot environment configurations." + exit 0 +fi + +# Check if already initialized +if [ -f "$MARKER_FILE" ]; then + log "U-Boot environment already initialized (marker file exists). Skipping U-Boot env setup." + log "fw_env.config configuration complete." + exit 0 +fi + +log "U-Boot environment not yet initialized. Proceeding with first-time setup..." + +# Detect boot device from /host mount (assumes eMMC) +boot_device=$(findmnt -n -o SOURCE /host) +demo_dev=$(echo "$boot_device" | sed 's/p\?[0-9]*$//') +demo_part=$(echo "$boot_device" | grep -o '[0-9]*$') + +# Fallback if detection fails +if [ -z "$boot_device" ]; then + boot_device="/dev/mmcblk0p1" + demo_dev="/dev/mmcblk0" + demo_part=1 + log "WARNING: Could not detect boot device, defaulting to $boot_device" +fi + +# Determine storage interface type (mmc for eMMC, scsi for UFS) +if echo "$demo_dev" | grep -q "mmc"; then + disk_interface="mmc" +else + disk_interface="scsi" +fi + +log "Detected boot device: ${boot_device}, partition: ${demo_part}, interface: ${disk_interface}" + +# Detect current image directory from /proc/cmdline +# The kernel command line contains "loop=image-xxx/fs.squashfs" +IMAGE_DIR=$(grep -o 'loop=[^ ]*' /proc/cmdline | sed 's|loop=\([^/]*\)/.*|\1|') +if [ -z "$IMAGE_DIR" ]; then + log "WARNING: Cannot detect image from /proc/cmdline, trying fallback method" + # Fallback: use first image directory (may not be correct for multi-image systems) + IMAGE_DIR=$(ls -d /host/image-* 2>/dev/null | head -1 | xargs basename) + if [ -z "$IMAGE_DIR" ]; then + log "ERROR: Cannot find image directory in /host/" + exit 1 + fi + log "Using fallback image directory: $IMAGE_DIR" +else + log "Detected image directory from /proc/cmdline: $IMAGE_DIR" +fi + +# Get filesystem UUID (not partition UUID) +FS_UUID=$(blkid -s UUID -o value ${boot_device} 2>/dev/null || echo "") +if [ -z "$FS_UUID" ]; then + log "WARNING: Cannot detect filesystem UUID, using device path instead" + ROOT_DEV="${boot_device}" +else + ROOT_DEV="UUID=$FS_UUID" +fi + +# Extract version from image directory +SONIC_VERSION=$(echo "$IMAGE_DIR" | sed 's/^image-/SONiC-OS-/') + +log "Setting U-Boot environment variables..." + +# Get DTB configuration name from U-Boot bootconf variable +# bootconf is set by U-Boot based on hardware detection +BOOTCONF=$(fw_printenv -n bootconf 2>/dev/null || echo "") +if [ -z "$BOOTCONF" ]; then + log "WARNING: U-Boot bootconf variable not set, using default: ast2700-evb" + BOOTCONF="ast2700-evb" +fi +log "Using U-Boot bootconf: $BOOTCONF" + +# Set U-Boot environment variables + +# Image configuration +fw_setenv image_dir "$IMAGE_DIR" || log "ERROR: Failed to set image_dir" +fw_setenv fit_name "$IMAGE_DIR/boot/sonic_arm64.fit" || log "ERROR: Failed to set fit_name" +fw_setenv sonic_version_1 "$SONIC_VERSION" || log "ERROR: Failed to set sonic_version_1" + +# Old/backup image (none for first installation) +fw_setenv image_dir_old "" || log "ERROR: Failed to set image_dir_old" +fw_setenv fit_name_old "" || log "ERROR: Failed to set fit_name_old" +fw_setenv sonic_version_2 "None" || log "ERROR: Failed to set sonic_version_2" +fw_setenv linuxargs_old "" || log "ERROR: Failed to set linuxargs_old" + +# Kernel command line arguments +# Read device-specific configuration from installer.conf +if [ -f ${IMAGE_DIR}/installer.conf ]; then + source ${IMAGE_DIR}/installer.conf +fi + +# Set defaults if not specified in installer.conf +CONSOLE_DEV=${CONSOLE_DEV:-12} +CONSOLE_SPEED=${CONSOLE_SPEED:-115200} +EARLYCON=${EARLYCON:-"earlycon=uart8250,mmio32,0x14c33b00"} +VAR_LOG_SIZE=${VAR_LOG_SIZE:-512} + +# Construct console device name +CONSOLE_PORT="ttyS${CONSOLE_DEV}" + +fw_setenv linuxargs "console=${CONSOLE_PORT},${CONSOLE_SPEED}n8 ${EARLYCON} loopfstype=squashfs loop=$IMAGE_DIR/fs.squashfs varlog_size=${VAR_LOG_SIZE}" || log "ERROR: Failed to set linuxargs" + +# Boot commands +fw_setenv sonic_boot_load "ext4load ${disk_interface} 0:${demo_part} \${loadaddr} \${fit_name}" || log "ERROR: Failed to set sonic_boot_load" +fw_setenv sonic_boot_load_old "ext4load ${disk_interface} 0:${demo_part} \${loadaddr} \${fit_name_old}" || log "ERROR: Failed to set sonic_boot_load_old" +fw_setenv sonic_bootargs "setenv bootargs root=$ROOT_DEV rw rootwait panic=1 \${linuxargs}" || log "ERROR: Failed to set sonic_bootargs" +fw_setenv sonic_bootargs_old "setenv bootargs root=$ROOT_DEV rw rootwait panic=1 \${linuxargs_old}" || log "ERROR: Failed to set sonic_bootargs_old" +fw_setenv sonic_image_1 "run sonic_bootargs; run sonic_boot_load; bootm \${loadaddr}#conf-\${bootconf}" || log "ERROR: Failed to set sonic_image_1" +fw_setenv sonic_image_2 "run sonic_bootargs_old; run sonic_boot_load_old; bootm \${loadaddr}#conf-\${bootconf}" || log "ERROR: Failed to set sonic_image_2" + +# Boot menu with instructions +fw_setenv print_menu "echo ===================================================; echo SONiC Boot Menu; echo ===================================================; echo To boot \$sonic_version_1; echo type: run sonic_image_1; echo at the U-Boot prompt after interrupting U-Boot when it says; echo \\\"Hit any key to stop autoboot:\\\" during boot; echo; echo To boot \$sonic_version_2; echo type: run sonic_image_2; echo at the U-Boot prompt after interrupting U-Boot when it says; echo \\\"Hit any key to stop autoboot:\\\" during boot; echo; echo ===================================================" || log "ERROR: Failed to set print_menu" + +# Boot configuration +fw_setenv boot_next "run sonic_image_1" || log "ERROR: Failed to set boot_next" +fw_setenv bootcmd "run print_menu; test -n \"\$boot_once\" && setenv do_boot_once \"\$boot_once\" && setenv boot_once \"\" && saveenv && run do_boot_once; run boot_next" || log "ERROR: Failed to set bootcmd" + +# Memory addresses +fw_setenv loadaddr "0x432000000" || log "ERROR: Failed to set loadaddr" +fw_setenv kernel_addr "0x403000000" || log "ERROR: Failed to set kernel_addr" +fw_setenv fdt_addr "0x44C000000" || log "ERROR: Failed to set fdt_addr" +fw_setenv initrd_addr "0x440000000" || log "ERROR: Failed to set initrd_addr" + +log "U-Boot environment variables set successfully" + +# Create marker file to prevent re-initialization +mkdir -p "$(dirname "$MARKER_FILE")" +touch "$MARKER_FILE" + +log "U-Boot environment initialization complete" + +exit 0 + diff --git a/platform/aspeed/sonic-platform-modules-ast-evb/ast2700/obmc-console/server.tty.conf b/platform/aspeed/sonic-platform-modules-ast-evb/ast2700/obmc-console/server.tty.conf new file mode 100644 index 00000000000..b14524de5a7 --- /dev/null +++ b/platform/aspeed/sonic-platform-modules-ast-evb/ast2700/obmc-console/server.tty.conf @@ -0,0 +1,22 @@ +# obmc-console server configuration for virtual UART (AST2700) +# Based on OpenBMC reference: server.ttyVUART0.conf.in +# Aspeed EVB-specific configuration +# Virtual UART provides console access to x86 host via LPC + +# LPC address for virtual UART +lpc-address = 0x3f8 + +# Serial IRQ +sirq = 4 + +# Virtual UART device on BMC side +upstream-tty = ttyVUART0 + +# Local TTY device +local-tty = ttyS0 + +# Baud rate +local-tty-baud = 115200 + +# Console ID +console-id = host diff --git a/platform/aspeed/sonic-platform-modules-ast-evb/ast2700/setup.py b/platform/aspeed/sonic-platform-modules-ast-evb/ast2700/setup.py new file mode 100644 index 00000000000..1f2766aba8f --- /dev/null +++ b/platform/aspeed/sonic-platform-modules-ast-evb/ast2700/setup.py @@ -0,0 +1,31 @@ +#!/usr/bin/env python3 + +from setuptools import setup + +setup( + name='sonic-platform', + version='1.0', + description='SONiC platform API implementation for Aspeed AST2700 EVB', + license='Apache 2.0', + author='SONiC Team', + author_email='', + url='https://github.com/Azure/sonic-buildimage', + maintainer='Aspeed', + maintainer_email='', + packages=['sonic_platform'], + package_dir={'sonic_platform': 'sonic_platform'}, + classifiers=[ + 'Development Status :: 3 - Alpha', + 'Environment :: Plugins', + 'Intended Audience :: Developers', + 'Intended Audience :: Information Technology', + 'Intended Audience :: System Administrators', + 'License :: OSI Approved :: Apache Software License', + 'Natural Language :: English', + 'Operating System :: POSIX :: Linux', + 'Programming Language :: Python :: 3.9', + 'Topic :: Utilities', + ], + keywords='sonic SONiC platform PLATFORM bmc aspeed ast2700 evb', +) + diff --git a/platform/aspeed/sonic-platform-modules-ast-evb/ast2700/sonic_platform/__init__.py b/platform/aspeed/sonic-platform-modules-ast-evb/ast2700/sonic_platform/__init__.py new file mode 100644 index 00000000000..3f3f3118fc4 --- /dev/null +++ b/platform/aspeed/sonic-platform-modules-ast-evb/ast2700/sonic_platform/__init__.py @@ -0,0 +1,13 @@ +#!/usr/bin/env python3 +# +# __init__.py +# +# Platform-specific SONiC platform API for Aspeed AST2700 EVB +# + +try: + from sonic_platform.platform import Platform + from sonic_platform.chassis import Chassis +except ImportError as e: + raise ImportError(str(e) + " - required module not found") + diff --git a/platform/aspeed/sonic-platform-modules-ast-evb/ast2700/sonic_platform/chassis.py b/platform/aspeed/sonic-platform-modules-ast-evb/ast2700/sonic_platform/chassis.py new file mode 100644 index 00000000000..7a5fd3790f3 --- /dev/null +++ b/platform/aspeed/sonic-platform-modules-ast-evb/ast2700/sonic_platform/chassis.py @@ -0,0 +1,257 @@ +""" +SONiC Platform API - Chassis class for Aspeed BMC + +This module provides the Chassis class for Aspeed AST2700 BMC platform. +""" + +import os + +try: + from sonic_platform_base.chassis_base import ChassisBase + from sonic_platform.watchdog import Watchdog + from sonic_platform.thermal import Thermal + from sonic_platform.fan import Fan + from sonic_platform.fan_drawer import FanDrawer +except ImportError as e: + raise ImportError(str(e) + " - required module not found") + + +# Watchdog bootstatus file paths for AST2700 +WATCHDOG0_BOOTSTATUS_PATH = "/sys/devices/platform/soc@14000000/14c37000.watchdog/watchdog/watchdog0/bootstatus" +WATCHDOG1_BOOTSTATUS_PATH = "/sys/devices/platform/soc@14000000/14c37080.watchdog/watchdog/watchdog1/bootstatus" + +# Watchdog bootstatus bit flags (from Linux kernel watchdog.h) +WDIOF_OVERHEAT = 0x0001 # Reset due to CPU overheat +WDIOF_FANFAULT = 0x0002 # Fan failed +WDIOF_EXTERN1 = 0x0004 # External relay 1 +WDIOF_EXTERN2 = 0x0008 # External relay 2 +WDIOF_POWERUNDER = 0x0010 # Power bad/power fault +WDIOF_CARDRESET = 0x0020 # Card previously reset the CPU (normal reboot) +WDIOF_POWEROVER = 0x0040 # Power over voltage + + +class Chassis(ChassisBase): + """ + Chassis class for Aspeed AST2700 EVB BMC platform + + Provides chassis-level functionality including reboot cause detection, + thermal sensors, and fan management. + """ + + # EVB has 9 PWM-controlled fans (fan0-fan8) + # Even though there are 16 TACH inputs, only 9 have PWM control + NUM_FANS = 9 + + # EVB has 16 ADC channels enabled (ADC0 + ADC1) + NUM_THERMAL_SENSORS = 16 + + def __init__(self): + """ + Initialize the Chassis object + """ + super().__init__() + + # Initialize watchdog + self._watchdog = Watchdog() + + # Initialize thermal sensors (16 ADC channels) + self._thermal_list = [] + for i in range(self.NUM_THERMAL_SENSORS): + thermal = Thermal(i) + self._thermal_list.append(thermal) + + # Initialize fans (16 TACH inputs) + self._fan_list = [] + for i in range(self.NUM_FANS): + fan = Fan(i) + self._fan_list.append(fan) + + # Initialize fan drawers (wrap each fan in a virtual drawer for thermalctld) + # thermalctld reads fans from fan drawers, not directly from chassis + self._fan_drawer_list = [] + for i in range(self.NUM_FANS): + fan_drawer = FanDrawer(i, self._fan_list[i]) + self._fan_drawer_list.append(fan_drawer) + + def _read_watchdog_bootstatus(self, path): + """ + Read watchdog bootstatus value from sysfs + + Args: + path: Path to the bootstatus file + + Returns: + Integer value of bootstatus, or 0 if file doesn't exist or can't be read + """ + try: + with open(path, 'r') as f: + value = f.read().strip() + return int(value) + except (IOError, OSError, ValueError): + return 0 + + def get_reboot_cause(self): + """ + Retrieves the cause of the previous reboot + + This method reads the watchdog bootstatus register to determine + the hardware reboot cause. The AST2700 BMC has two watchdog timers, + and we check watchdog0 for the reboot cause. + + Returns: + A tuple (string, string) where the first element is a string + containing the cause of the previous reboot. This string must be + one of the predefined strings in ChassisBase. If the first string + is "REBOOT_CAUSE_HARDWARE_OTHER", the second string can be used + to pass a description of the reboot cause. + + Reboot cause mapping: + - WDIOF_OVERHEAT (0x01): Thermal overload + - WDIOF_FANFAULT (0x02): Insufficient fan speed + - WDIOF_POWERUNDER (0x10): Power loss + - WDIOF_POWEROVER (0x40): Power over voltage + - WDIOF_CARDRESET (0x20): Normal reboot/reset + - Other bits: Hardware other + - No bits set: Non-hardware (software reboot) + """ + # Read watchdog0 bootstatus + bootstatus = self._read_watchdog_bootstatus(WATCHDOG0_BOOTSTATUS_PATH) + + # Map bootstatus bits to reboot causes + # Check in order of priority (most specific first) + + if bootstatus & WDIOF_OVERHEAT: + return (self.REBOOT_CAUSE_THERMAL_OVERLOAD_CPU, "CPU Overheat") + + if bootstatus & WDIOF_FANFAULT: + return (self.REBOOT_CAUSE_INSUFFICIENT_FAN_SPEED, "Fan Fault") + + if bootstatus & WDIOF_POWERUNDER: + return (self.REBOOT_CAUSE_POWER_LOSS, "Power Under Voltage") + + if bootstatus & WDIOF_POWEROVER: + return (self.REBOOT_CAUSE_POWER_LOSS, "Power Over Voltage") + + if bootstatus & WDIOF_CARDRESET: + # CARDRESET typically indicates a normal reboot/reset + # This is not a hardware fault, so return NON_HARDWARE + # The determine-reboot-cause service will use the software + # reboot cause from /host/reboot-cause/reboot-cause.txt + return (self.REBOOT_CAUSE_NON_HARDWARE, None) + + if bootstatus & (WDIOF_EXTERN1 | WDIOF_EXTERN2): + return (self.REBOOT_CAUSE_HARDWARE_OTHER, f"External Reset (bootstatus=0x{bootstatus:x})") + + # If no specific bits are set, or only unknown bits are set + if bootstatus == 0: + # No hardware reboot cause detected + return (self.REBOOT_CAUSE_NON_HARDWARE, None) + else: + # Unknown bootstatus bits + return (self.REBOOT_CAUSE_HARDWARE_OTHER, f"Unknown (bootstatus=0x{bootstatus:x})") + + def get_name(self): + """ + Retrieves the name of the chassis + + Returns: + String containing the name of the chassis + """ + return "AST2700-BMC" + + def get_model(self): + """ + Retrieves the model number (or part number) of the chassis + + Returns: + String containing the model number of the chassis + """ + return "AST2700" + + def get_serial(self): + """ + Retrieves the serial number of the chassis + + Returns: + String containing the serial number of the chassis + """ + return "N/A" + + def get_watchdog(self): + """ + Retrieves the hardware watchdog device on this chassis + + Returns: + An object derived from WatchdogBase representing the hardware + watchdog device + """ + return self._watchdog + + def get_num_thermals(self): + """ + Retrieves the number of thermal sensors available on this chassis + + Returns: + An integer, the number of thermal sensors available on this chassis + """ + return len(self._thermal_list) + + def get_all_thermals(self): + """ + Retrieves all thermal sensors available on this chassis + + Returns: + A list of objects derived from ThermalBase representing all thermal + sensors available on this chassis + """ + return self._thermal_list + + def get_thermal(self, index): + """ + Retrieves thermal sensor represented by (0-based) index + + Args: + index: An integer, the index (0-based) of the thermal sensor to retrieve + + Returns: + An object derived from ThermalBase representing the specified thermal + sensor, or None if index is out of range + """ + if index < 0 or index >= len(self._thermal_list): + return None + return self._thermal_list[index] + + def get_num_fans(self): + """ + Retrieves the number of fans available on this chassis + + Returns: + An integer, the number of fans available on this chassis + """ + return len(self._fan_list) + + def get_all_fans(self): + """ + Retrieves all fan modules available on this chassis + + Returns: + A list of objects derived from FanBase representing all fan + modules available on this chassis + """ + return self._fan_list + + def get_fan(self, index): + """ + Retrieves fan module represented by (0-based) index + + Args: + index: An integer, the index (0-based) of the fan module to retrieve + + Returns: + An object derived from FanBase representing the specified fan + module, or None if index is out of range + """ + if index < 0 or index >= len(self._fan_list): + return None + return self._fan_list[index] + diff --git a/platform/aspeed/sonic-platform-modules-ast-evb/ast2700/sonic_platform/fan.py b/platform/aspeed/sonic-platform-modules-ast-evb/ast2700/sonic_platform/fan.py new file mode 100644 index 00000000000..d08beb7ff3e --- /dev/null +++ b/platform/aspeed/sonic-platform-modules-ast-evb/ast2700/sonic_platform/fan.py @@ -0,0 +1,293 @@ +""" +SONiC Platform API - Fan class for Aspeed BMC + +This module provides the Fan class for Aspeed AST2700 BMC platform. +Fans are controlled via PWM and monitored via TACH inputs. +""" + +import os + +try: + from sonic_platform_base.fan_base import FanBase +except ImportError as e: + raise ImportError(str(e) + " - required module not found") + + +class Fan(FanBase): + """ + Fan class for Aspeed AST2700 BMC platform + + The AST2700 supports: + - 16 TACH inputs for fan speed monitoring (hwmon0) + - 9 PWM outputs for fan speed control (hwmon1-9) + """ + + # Base paths for fan devices + TACH_HWMON_PATH = "/sys/class/hwmon/hwmon0" # aspeed_tach + PWM_HWMON_BASE = "/sys/class/hwmon/hwmon" # pwm-fan devices (hwmon1-9) + + # PWM range (0-255) + PWM_MAX = 255 + + def __init__(self, fan_index, is_psu_fan=False): + """ + Initialize a Fan object + + Args: + fan_index: 0-based index of the fan (0-15 for TACH, 0-8 for PWM) + is_psu_fan: True if this is a PSU fan, False otherwise + """ + FanBase.__init__(self) + + self.index = fan_index + self.is_psu_fan = is_psu_fan + self.name = f"Fan{fan_index + 1}" + + # TACH input path (1-based in sysfs) + self.tach_input_path = os.path.join( + self.TACH_HWMON_PATH, + f"fan{fan_index + 1}_input" + ) + + # PWM control path (for fans 0-8, maps to hwmon1-9) + # pwm-fan0 -> hwmon8, pwm-fan1 -> hwmon9, pwm-fan2 -> hwmon1, etc. + # We'll need to find the correct hwmon device + self.pwm_path = None + if fan_index < 9: + self._find_pwm_path(fan_index) + + def _find_pwm_path(self, fan_index): + """ + Find the PWM control path for this fan + + Args: + fan_index: 0-based fan index + """ + # Search for the pwm-fan device + for hwmon_num in range(1, 10): + hwmon_path = f"{self.PWM_HWMON_BASE}{hwmon_num}" + device_path = os.path.join(hwmon_path, "device") + + try: + # Read the device name + device_name = os.path.basename(os.readlink(device_path)) + if device_name == f"pwm-fan{fan_index}": + self.pwm_path = os.path.join(hwmon_path, "pwm1") + return + except (OSError, IOError): + continue + + def _read_sysfs_file(self, path): + """ + Read a value from a sysfs file + + Args: + path: Path to the sysfs file + + Returns: + String value from the file, or None if read fails + """ + try: + with open(path, 'r') as f: + return f.read().strip() + except (IOError, OSError): + return None + + def _write_sysfs_file(self, path, value): + """ + Write a value to a sysfs file + + Args: + path: Path to the sysfs file + value: Value to write + + Returns: + True if write succeeds, False otherwise + """ + try: + with open(path, 'w') as f: + f.write(str(value)) + return True + except (IOError, OSError): + return False + + def get_name(self): + """ + Retrieves the name of the fan + + Returns: + String containing the name of the fan + """ + return self.name + + def get_presence(self): + """ + Retrieves the presence of the fan + + Returns: + True if fan is present, False if not + """ + # On BMC, fans are always "present" if the TACH input exists + return os.path.exists(self.tach_input_path) + + def get_model(self): + """ + Retrieves the model of the fan + + Returns: + String containing the model of the fan + """ + return "AST2700-FAN" + + def get_serial(self): + """ + Retrieves the serial number of the fan + + Returns: + String containing the serial number of the fan + """ + return "N/A" + + def get_status(self): + """ + Retrieves the operational status of the fan + + Returns: + True if fan is operating properly, False if not + """ + # Fan is operational if it's present and speed can be read + if not self.get_presence(): + return False + + speed_rpm = self.get_speed_rpm() + return speed_rpm is not None + + def get_direction(self): + """ + Retrieves the direction of fan + + Returns: + A string, either FAN_DIRECTION_INTAKE or FAN_DIRECTION_EXHAUST + depending on fan direction + """ + # BMC fans don't have direction detection on EVB + return self.FAN_DIRECTION_NOT_APPLICABLE + + def get_speed_rpm(self): + """ + Retrieves the speed of fan in RPM + + Returns: + An integer, the fan speed in RPM, or None if not available + """ + value_str = self._read_sysfs_file(self.tach_input_path) + if value_str is None: + return None + + try: + return int(value_str) + except ValueError: + return None + + def get_speed(self): + """ + Retrieves the speed of fan as a percentage of full speed + + Returns: + An integer, the percentage of full fan speed, in the range 0 (off) + to 100 (full speed) + """ + # Get current PWM value + if self.pwm_path is None or not os.path.exists(self.pwm_path): + # If no PWM control, estimate from RPM + # Assume max speed is 10000 RPM (platform-specific) + rpm = self.get_speed_rpm() + if rpm is None: + return 0 + max_rpm = 10000 + speed_percent = min(100, int((rpm * 100) / max_rpm)) + return speed_percent + + pwm_str = self._read_sysfs_file(self.pwm_path) + if pwm_str is None: + return 0 + + try: + pwm_value = int(pwm_str) + # Convert PWM (0-255) to percentage (0-100) + return int((pwm_value * 100) / self.PWM_MAX) + except ValueError: + return 0 + + def get_target_speed(self): + """ + Retrieves the target (expected) speed of the fan + + Returns: + An integer, the percentage of full fan speed, in the range 0 (off) + to 100 (full speed) + """ + # Target speed is the same as current speed (no separate target register) + return self.get_speed() + + def get_speed_tolerance(self): + """ + Retrieves the speed tolerance of the fan + + Returns: + An integer, the percentage of variance from target speed which is + considered tolerable + """ + # Default tolerance of 20% + return 20 + + def set_speed(self, speed): + """ + Sets the fan speed + + Args: + speed: An integer, the percentage of full fan speed to set fan to, + in the range 0 (off) to 100 (full speed) + + Returns: + A boolean, True if speed is set successfully, False if not + """ + if self.is_psu_fan: + # PSU fans are controlled by the PSU itself + return False + + if speed < 0 or speed > 100: + return False + + if self.pwm_path is None or not os.path.exists(self.pwm_path): + return False + + # Convert percentage (0-100) to PWM value (0-255) + pwm_value = int((speed * self.PWM_MAX) / 100) + + return self._write_sysfs_file(self.pwm_path, pwm_value) + + def set_status_led(self, color): + """ + Sets the state of the fan status LED + + Args: + color: A string representing the color with which to set the + fan status LED + + Returns: + bool: True if status LED state is set successfully, False if not + """ + # BMC fans don't have individual status LEDs + return False + + def get_status_led(self): + """ + Gets the state of the fan status LED + + Returns: + A string, one of the predefined STATUS_LED_COLOR_* strings + """ + # BMC fans don't have individual status LEDs + return self.STATUS_LED_COLOR_OFF + diff --git a/platform/aspeed/sonic-platform-modules-ast-evb/ast2700/sonic_platform/fan_drawer.py b/platform/aspeed/sonic-platform-modules-ast-evb/ast2700/sonic_platform/fan_drawer.py new file mode 100644 index 00000000000..7e89313aeec --- /dev/null +++ b/platform/aspeed/sonic-platform-modules-ast-evb/ast2700/sonic_platform/fan_drawer.py @@ -0,0 +1,138 @@ +#!/usr/bin/env python3 + +""" +fan_drawer.py + +Implementation of FanDrawer class for Aspeed AST2700 BMC platform. +Since BMC platforms typically don't have physical fan drawers/trays, +we create virtual fan drawers with one fan each to satisfy SONiC's +thermalctld requirements. +""" + +try: + from sonic_platform_base.fan_drawer_base import FanDrawerBase +except ImportError as e: + raise ImportError(str(e) + " - required module not found") + + +class FanDrawer(FanDrawerBase): + """ + Platform-specific FanDrawer class for Aspeed AST2700 BMC. + + Each FanDrawer represents a virtual drawer containing a single fan. + This is required because thermalctld reads fans from fan drawers, + not directly from the chassis. + """ + + def __init__(self, drawer_index, fan): + """ + Initialize a FanDrawer with a single fan. + + Args: + drawer_index: 0-based index of this fan drawer + fan: Fan object to be contained in this drawer + """ + FanDrawerBase.__init__(self) + self._drawer_index = drawer_index + self._fan_list.append(fan) + + def get_name(self): + """ + Retrieves the fan drawer name. + + Returns: + string: The name of the fan drawer (e.g., "FanDrawer1") + """ + return "FanDrawer{}".format(self._drawer_index + 1) + + def get_presence(self): + """ + Retrieves the presence of the fan drawer. + + Returns: + bool: True if fan drawer is present, False otherwise + """ + # Virtual fan drawers are always present + return True + + def get_status(self): + """ + Retrieves the operational status of the fan drawer. + + Returns: + bool: True if fan drawer is operating properly, False otherwise + """ + # Fan drawer status is based on the contained fan's status + if len(self._fan_list) > 0: + return self._fan_list[0].get_status() + return False + + def get_model(self): + """ + Retrieves the model number of the fan drawer. + + Returns: + string: Model number of the fan drawer + """ + return "N/A" + + def get_serial(self): + """ + Retrieves the serial number of the fan drawer. + + Returns: + string: Serial number of the fan drawer + """ + return "N/A" + + def get_status_led(self): + """ + Gets the state of the fan drawer LED. + + Returns: + string: One of the STATUS_LED_COLOR_* strings (GREEN, RED, etc.) + """ + return "N/A" + + def set_status_led(self, color): + """ + Sets the state of the fan drawer LED. + + Args: + color: A string representing the color with which to set the LED + + Returns: + bool: True if LED state is set successfully, False otherwise + """ + # LED control not supported on BMC platform + return False + + def get_maximum_consumed_power(self): + """ + Retrieves the maximum power drawn by the fan drawer. + + Returns: + float: Maximum power consumption in watts + """ + # Typical BMC fan power consumption + return 5.0 + + def is_replaceable(self): + """ + Indicate whether this fan drawer is replaceable. + + Returns: + bool: True if replaceable, False otherwise + """ + # Virtual fan drawers are not physically replaceable + return False + + def get_position_in_parent(self): + """ + Retrieves the 1-based relative physical position in parent device. + + Returns: + integer: The 1-based relative physical position in parent device + """ + return self._drawer_index + 1 + diff --git a/platform/aspeed/sonic-platform-modules-ast-evb/ast2700/sonic_platform/platform.py b/platform/aspeed/sonic-platform-modules-ast-evb/ast2700/sonic_platform/platform.py new file mode 100644 index 00000000000..8ce51d8b1fc --- /dev/null +++ b/platform/aspeed/sonic-platform-modules-ast-evb/ast2700/sonic_platform/platform.py @@ -0,0 +1,34 @@ +#!/usr/bin/env python3 +# +# platform.py +# +# Platform implementation for Aspeed AST2700 EVB +# + +try: + from sonic_platform.chassis import Chassis +except ImportError as e: + raise ImportError(str(e) + " - required module not found") + +class Platform: + """ + Platform class for Aspeed AST2700 EVB + + Provides access to chassis-level functionality. + """ + + def __init__(self): + """ + Initialize the Platform object + """ + self._chassis = Chassis() + + def get_chassis(self): + """ + Retrieves the chassis object + + Returns: + An object derived from ChassisBase representing the chassis + """ + return self._chassis + diff --git a/platform/aspeed/sonic-platform-modules-ast-evb/ast2700/sonic_platform/thermal.py b/platform/aspeed/sonic-platform-modules-ast-evb/ast2700/sonic_platform/thermal.py new file mode 100644 index 00000000000..655da029d1d --- /dev/null +++ b/platform/aspeed/sonic-platform-modules-ast-evb/ast2700/sonic_platform/thermal.py @@ -0,0 +1,207 @@ +""" +SONiC Platform API - Thermal class for Aspeed BMC + +This module provides the Thermal class for Aspeed AST2700 BMC platform. +Thermal sensors are exposed via the IIO-HWMON bridge from ADC channels. +""" + +import os + +try: + from sonic_platform_base.thermal_base import ThermalBase +except ImportError as e: + raise ImportError(str(e) + " - required module not found") + + +class Thermal(ThermalBase): + """ + Thermal sensor class for Aspeed AST2700 BMC platform + + Reads temperature/voltage values from ADC channels exposed via iio-hwmon. + The AST2700 has 16 ADC channels available for monitoring. + """ + + # Base path for IIO-HWMON device + HWMON_IIO_PATH = "/sys/class/hwmon/hwmon10" + + def __init__(self, thermal_index): + """ + Initialize a Thermal object + + Args: + thermal_index: 0-based index of the thermal sensor (0-15 for 16 ADC channels) + """ + ThermalBase.__init__(self) + + self.index = thermal_index + self.name = f"ADC{thermal_index + 1}" + + # ADC channel input file (1-based in sysfs) + self.thermal_input_path = os.path.join( + self.HWMON_IIO_PATH, + f"in{thermal_index + 1}_input" + ) + + def _read_sysfs_file(self, path): + """ + Read a value from a sysfs file + + Args: + path: Path to the sysfs file + + Returns: + String value from the file, or None if read fails + """ + try: + with open(path, 'r') as f: + return f.read().strip() + except (IOError, OSError): + return None + + def get_name(self): + """ + Retrieves the name of the thermal sensor + + Returns: + String containing the name of the thermal sensor + """ + return self.name + + def get_presence(self): + """ + Retrieves the presence of the thermal sensor + + Returns: + True if thermal sensor is present, False if not + """ + # ADC channels are always present on the BMC + return os.path.exists(self.thermal_input_path) + + def get_model(self): + """ + Retrieves the model of the thermal sensor + + Returns: + String containing the model of the thermal sensor + """ + return "AST2700-ADC" + + def get_serial(self): + """ + Retrieves the serial number of the thermal sensor + + Returns: + String containing the serial number of the thermal sensor + """ + return "N/A" + + def get_status(self): + """ + Retrieves the operational status of the thermal sensor + + Returns: + True if thermal sensor is operating properly, False if not + """ + return self.get_presence() and self.get_temperature() is not None + + def get_temperature(self): + """ + Retrieves current temperature reading from thermal sensor + + Returns: + A float number of current temperature in Celsius up to nearest + thousandth of one degree Celsius, e.g. 30.125 + + Note: + ADC channels return millivolts. For actual temperature sensors, + the voltage needs to be converted based on the sensor type. + For now, we return the raw millivolt value divided by 1000 as + a placeholder. Platform-specific implementations should override + this with proper temperature conversion formulas. + """ + value_str = self._read_sysfs_file(self.thermal_input_path) + if value_str is None: + return None + + try: + # ADC returns millivolts, convert to a temperature-like value + # This is a placeholder - actual conversion depends on sensor type + millivolts = float(value_str) + # For demonstration, just return mV/10 as "temperature" + # Real implementation would use proper sensor calibration + return millivolts / 10.0 + except ValueError: + return None + + def get_high_threshold(self): + """ + Retrieves the high threshold temperature of thermal sensor + + Returns: + A float number, the high threshold temperature of thermal in + Celsius up to nearest thousandth of one degree Celsius + """ + # Default threshold - should be configured per platform + return 85.0 + + def get_low_threshold(self): + """ + Retrieves the low threshold temperature of thermal sensor + + Returns: + A float number, the low threshold temperature of thermal in + Celsius up to nearest thousandth of one degree Celsius + """ + # Default threshold - should be configured per platform + return 0.0 + + def get_high_critical_threshold(self): + """ + Retrieves the high critical threshold temperature of thermal sensor + + Returns: + A float number, the high critical threshold temperature of thermal + in Celsius up to nearest thousandth of one degree Celsius + """ + # Default threshold - should be configured per platform + return 100.0 + + def get_low_critical_threshold(self): + """ + Retrieves the low critical threshold temperature of thermal sensor + + Returns: + A float number, the low critical threshold temperature of thermal + in Celsius up to nearest thousandth of one degree Celsius + """ + # Default threshold - should be configured per platform + return -10.0 + + def set_high_threshold(self, temperature): + """ + Sets the high threshold temperature of thermal sensor + + Args: + temperature: A float number up to nearest thousandth of one degree + Celsius, e.g. 30.125 + + Returns: + A boolean, True if threshold is set successfully, False if not + """ + # ADC channels don't support setting thresholds via sysfs + return False + + def set_low_threshold(self, temperature): + """ + Sets the low threshold temperature of thermal sensor + + Args: + temperature: A float number up to nearest thousandth of one degree + Celsius, e.g. 30.125 + + Returns: + A boolean, True if threshold is set successfully, False if not + """ + # ADC channels don't support setting thresholds via sysfs + return False + diff --git a/platform/aspeed/sonic-platform-modules-ast-evb/ast2700/sonic_platform/watchdog.py b/platform/aspeed/sonic-platform-modules-ast-evb/ast2700/sonic_platform/watchdog.py new file mode 100644 index 00000000000..3f9a8248c76 --- /dev/null +++ b/platform/aspeed/sonic-platform-modules-ast-evb/ast2700/sonic_platform/watchdog.py @@ -0,0 +1,220 @@ +""" +SONiC Platform API - Watchdog class for Aspeed BMC + +This module provides the Watchdog class for Aspeed AST2700 BMC platform. +""" + +import os +import fcntl +import array +import ctypes + +try: + from sonic_platform_base.watchdog_base import WatchdogBase +except ImportError as e: + raise ImportError(str(e) + " - required module not found") + + +# Watchdog ioctl commands (from Linux kernel watchdog.h) +WDIOC_GETSUPPORT = 0x80285700 +WDIOC_GETSTATUS = 0x80045701 +WDIOC_GETBOOTSTATUS = 0x80045702 +WDIOC_GETTEMP = 0x80045703 +WDIOC_SETOPTIONS = 0x80045704 +WDIOC_KEEPALIVE = 0x80045705 +WDIOC_SETTIMEOUT = 0xc0045706 +WDIOC_GETTIMEOUT = 0x80045707 +WDIOC_SETPRETIMEOUT = 0xc0045708 +WDIOC_GETPRETIMEOUT = 0x80045709 +WDIOC_GETTIMELEFT = 0x8004570a + +# Watchdog options +WDIOS_DISABLECARD = 0x0001 +WDIOS_ENABLECARD = 0x0002 + +# Default watchdog device +WATCHDOG_DEVICE = "/dev/watchdog0" + +# Sysfs paths for AST2700 watchdog +WATCHDOG_SYSFS_PATH = "/sys/class/watchdog/watchdog0/" + + +class Watchdog(WatchdogBase): + """ + Watchdog class for Aspeed AST2700 BMC platform + + Provides hardware watchdog functionality using the aspeed_wdt driver. + """ + + def __init__(self): + """ + Initialize the Watchdog object + """ + self.watchdog_device = WATCHDOG_DEVICE + self.watchdog_fd = None + self.timeout = 0 + self.armed = False + + def _open_watchdog(self): + """ + Open the watchdog device file + + Returns: + True if successful, False otherwise + """ + if self.watchdog_fd is None: + try: + self.watchdog_fd = os.open(self.watchdog_device, os.O_WRONLY) + return True + except (IOError, OSError): + return False + return True + + def _close_watchdog(self): + """ + Close the watchdog device file + """ + if self.watchdog_fd is not None: + try: + # Write magic close character 'V' to properly close watchdog + os.write(self.watchdog_fd, b'V') + os.close(self.watchdog_fd) + except (IOError, OSError): + pass + finally: + self.watchdog_fd = None + + def _read_sysfs_int(self, filename): + """ + Read an integer value from sysfs + + Args: + filename: Name of the file in WATCHDOG_SYSFS_PATH + + Returns: + Integer value, or -1 on error + """ + try: + with open(os.path.join(WATCHDOG_SYSFS_PATH, filename), 'r') as f: + return int(f.read().strip()) + except (IOError, OSError, ValueError): + return -1 + + def arm(self, seconds): + """ + Arm the hardware watchdog with a timeout of seconds + + Args: + seconds: Timeout value in seconds + + Returns: + An integer specifying the actual number of seconds the watchdog + was armed with. On failure returns -1. + """ + if seconds < 0: + return -1 + + if not self._open_watchdog(): + return -1 + + try: + # Set timeout using ioctl + timeout_value = array.array('I', [seconds]) + fcntl.ioctl(self.watchdog_fd, WDIOC_SETTIMEOUT, timeout_value, True) + + # Enable watchdog + options = array.array('h', [WDIOS_ENABLECARD]) + fcntl.ioctl(self.watchdog_fd, WDIOC_SETOPTIONS, options, False) + + self.timeout = int(timeout_value[0]) + self.armed = True + + return self.timeout + except (IOError, OSError): + return -1 + + def disarm(self): + """ + Disarm the hardware watchdog + + Returns: + A boolean, True if watchdog is disarmed successfully, False if not + """ + if not self._open_watchdog(): + return False + + try: + # Disable watchdog + options = array.array('h', [WDIOS_DISABLECARD]) + fcntl.ioctl(self.watchdog_fd, WDIOC_SETOPTIONS, options, False) + + self.armed = False + self.timeout = 0 + + # Close the watchdog device properly + self._close_watchdog() + + return True + except (IOError, OSError): + return False + + def is_armed(self): + """ + Retrieves the armed state of the hardware watchdog + + Returns: + A boolean, True if watchdog is armed, False if not + """ + # Check the state from sysfs + state = self._read_sysfs_str("state") + if state == "active": + self.armed = True + return True + else: + self.armed = False + return False + + def get_remaining_time(self): + """ + Get the number of seconds remaining on the watchdog timer + + Returns: + An integer specifying the number of seconds remaining on the + watchdog timer. If the watchdog is not armed, returns -1. + """ + if not self.is_armed(): + return -1 + + # Try to read timeleft from sysfs first + timeleft = self._read_sysfs_int("timeleft") + if timeleft >= 0: + return timeleft + + # If timeleft is not available in sysfs, try ioctl + if not self._open_watchdog(): + return -1 + + try: + timeleft_value = array.array('I', [0]) + fcntl.ioctl(self.watchdog_fd, WDIOC_GETTIMELEFT, timeleft_value, True) + return int(timeleft_value[0]) + except (IOError, OSError): + # If ioctl fails, return the configured timeout as an estimate + return self.timeout if self.armed else -1 + + def _read_sysfs_str(self, filename): + """ + Read a string value from sysfs + + Args: + filename: Name of the file in WATCHDOG_SYSFS_PATH + + Returns: + String value, or empty string on error + """ + try: + with open(os.path.join(WATCHDOG_SYSFS_PATH, filename), 'r') as f: + return f.read().strip() + except (IOError, OSError): + return "" + diff --git a/platform/aspeed/sonic-platform-modules-ast-evb/debian/changelog b/platform/aspeed/sonic-platform-modules-ast-evb/debian/changelog new file mode 100644 index 00000000000..3d89c1cffc1 --- /dev/null +++ b/platform/aspeed/sonic-platform-modules-ast-evb/debian/changelog @@ -0,0 +1,5 @@ +sonic-platform-ast-evb (1.0) unstable; urgency=low + + * Initial release + + -- Aspeed Technology Inc. Mon, 17 Feb 2025 00:00:00 +0000 diff --git a/platform/aspeed/sonic-platform-modules-ast-evb/debian/control b/platform/aspeed/sonic-platform-modules-ast-evb/debian/control new file mode 100644 index 00000000000..c9889248be6 --- /dev/null +++ b/platform/aspeed/sonic-platform-modules-ast-evb/debian/control @@ -0,0 +1,13 @@ +Source: sonic-platform-ast-evb +Section: misc +Priority: optional +Maintainer: Aspeed Technology Inc. +Build-Depends: debhelper-compat (= 13), dh-python, python3-all, python3-setuptools, python3-wheel +Standards-Version: 3.9.6 + +Package: sonic-platform-ast-evb-ast2700 +Architecture: arm64 +Depends: ${misc:Depends}, ${python3:Depends} +Description: SONiC platform support for Aspeed AST2700 EVB + This package provides platform-specific implementation for Aspeed's + AST2700 EVB platform. diff --git a/platform/aspeed/sonic-platform-modules-ast-evb/debian/rules b/platform/aspeed/sonic-platform-modules-ast-evb/debian/rules new file mode 100755 index 00000000000..311ef608ff4 --- /dev/null +++ b/platform/aspeed/sonic-platform-modules-ast-evb/debian/rules @@ -0,0 +1,29 @@ +#!/usr/bin/make -f + +PACKAGE_PRE_NAME := sonic-platform-ast-evb +MODULE_DIRS := ast2700 + +%: + dh $@ --with python3 --buildsystem=pybuild + +override_dh_auto_build: + # Build the vendor-specific platform wheel + python3 setup.py bdist_wheel -d $(CURDIR) + +override_dh_auto_install: + set -e + (for mod in $(MODULE_DIRS); do \ + pkg_name=$(PACKAGE_PRE_NAME)-$${mod}; \ + device_name=arm64-aspeed_$${mod}_evb-r0; \ + dh_installdirs -p$${pkg_name} usr/share/sonic/device/$${device_name}/; \ + dh_installdirs -p$${pkg_name} etc/obmc-console/; \ + dh_install -p$${pkg_name} sonic_platform-1.0-py3-none-any.whl usr/share/sonic/device/$${device_name}/; \ + dh_install -p$${pkg_name} $${mod}/obmc-console/server.tty.conf etc/obmc-console/; \ + done) + +override_dh_auto_test: + +override_dh_clean: + dh_clean + rm -f *.whl + rm -rf build dist *.egg-info diff --git a/platform/aspeed/sonic-platform-modules-ast-evb/debian/sonic-platform-ast-evb-ast2700.postinst b/platform/aspeed/sonic-platform-modules-ast-evb/debian/sonic-platform-ast-evb-ast2700.postinst new file mode 100755 index 00000000000..c7decf252a7 --- /dev/null +++ b/platform/aspeed/sonic-platform-modules-ast-evb/debian/sonic-platform-ast-evb-ast2700.postinst @@ -0,0 +1,22 @@ +#!/bin/bash +set -e + +case "$1" in + configure) + if [ -f /host/machine.conf ]; then + . /host/machine.conf + DEVICE_NAME="$onie_platform" + else + echo "Warning: /host/machine.conf not found, cannot determine platform. Hardcoding to arm64-aspeed_ast2700_evb-r0" + DEVICE_NAME="arm64-aspeed_ast2700_evb-r0" + fi + DEVICE_DIR="/usr/share/sonic/device/${DEVICE_NAME}" + if [ -f "${DEVICE_DIR}"/*.whl ]; then + # Force reinstall to ensure this platform's wheel is installed + pip3 install --force-reinstall --no-deps "${DEVICE_DIR}"/*.whl + fi + ;; +esac + +#DEBHELPER# +exit 0 diff --git a/platform/aspeed/sonic-platform-modules-ast-evb/setup.py b/platform/aspeed/sonic-platform-modules-ast-evb/setup.py new file mode 100644 index 00000000000..a06fa2f861a --- /dev/null +++ b/platform/aspeed/sonic-platform-modules-ast-evb/setup.py @@ -0,0 +1,30 @@ +import os +from setuptools import setup + +setup( + name="sonic-platform", + version="1.0", + description="SONiC platform API implementation on Aspeed EVB Platforms", + license="Apache 2.0", + author="Aspeed Technology Inc.", + author_email="opensource@aspeedtech.com", + url="https://github.com/AspeedTech-BMC/sonic-buildimage", + packages=["sonic_platform"], + package_dir={ + "sonic_platform": "ast2700/sonic_platform", + }, + classifiers=[ + "Development Status :: 3 - Alpha", + "Environment :: Plugins", + "Intended Audience :: Developers", + "Intended Audience :: Information Technology", + "Intended Audience :: System Administrators", + "License :: OSI Approved :: Apache Software License", + "Natural Language :: English", + "Operating System :: POSIX :: Linux", + "Programming Language :: Python :: 3.11", + "Topic :: Utilities", + ], + keywords="sonic SONiC platform PLATFORM aspeed bmc", +) + diff --git a/platform/aspeed/sonic-platform-modules-nexthop/b27/obmc-console/server.tty.conf b/platform/aspeed/sonic-platform-modules-nexthop/b27/obmc-console/server.tty.conf new file mode 100644 index 00000000000..f43bdcf569d --- /dev/null +++ b/platform/aspeed/sonic-platform-modules-nexthop/b27/obmc-console/server.tty.conf @@ -0,0 +1,18 @@ +# obmc-console server configuration for ttyS1 (x86 console on AST2700) +# This UART is physically connected to the x86 host's ttyS0 +# NextHop-specific configuration + +# Physical UART device on BMC side +upstream-tty = ttyS1 + +# Baud rate - must match x86 side (9600 for compatibility) +local-tty-baud = 9600 + +# console-id is default to obmc-console + +# Log file for console output +logfile = /var/log/obmc-console-x86.log + +# Maximum log size (20MB) +logsize = 20971520 + diff --git a/platform/aspeed/sonic-platform-modules-nexthop/b27/scripts/switch_cpu_utils.sh b/platform/aspeed/sonic-platform-modules-nexthop/b27/scripts/switch_cpu_utils.sh new file mode 100644 index 00000000000..1467c77fe65 --- /dev/null +++ b/platform/aspeed/sonic-platform-modules-nexthop/b27/scripts/switch_cpu_utils.sh @@ -0,0 +1,183 @@ +#!/bin/bash +# Switch CPU Utility Script for ASPEED AST2700 BMC +# Provides utilities to manage the switch CPU (x86) from BMC +# +# Usage: switch_cpu_utils.sh [options] + +set -e + +# Hardware register addresses and values +RESET_REG_ADDR="0x14c0b208" +RESET_VALUE_OUT="3" # Value to bring CPU out of reset +RESET_VALUE_IN="2" # Value to put CPU into reset + +# Script metadata +SCRIPT_NAME="$(basename "$0")" +LOG_TAG="switch-cpu-utils" + +####################################### +# Print usage information +####################################### +usage() { + cat << EOF +Usage: ${SCRIPT_NAME} [options] + +Switch CPU management utilities for ASPEED AST2700 BMC. + +Commands: + reset-out Bring switch CPU out of reset + reset-in Put switch CPU into reset + reset-cycle Cycle switch CPU reset (in -> out) + status Show switch CPU reset status + help Show this help message + +Options: + -h, --help Show this help message + +Examples: + ${SCRIPT_NAME} reset-out + ${SCRIPT_NAME} reset-cycle + ${SCRIPT_NAME} status + +EOF +} + +####################################### +# Bring switch CPU out of reset +####################################### +bring_switch_cpu_out_of_reset() { + logger -t ${LOG_TAG} "Bringing switch CPU out of reset..." + + # Write to reset register to bring CPU out of reset + if busybox devmem ${RESET_REG_ADDR} 32 ${RESET_VALUE_OUT} 2>/dev/null; then + logger -t ${LOG_TAG} "Switch CPU successfully brought out of reset (wrote ${RESET_VALUE_OUT} to ${RESET_REG_ADDR})" + echo "Switch CPU brought out of reset successfully" + return 0 + else + logger -t ${LOG_TAG} "ERROR: Failed to bring switch CPU out of reset" + echo "ERROR: Failed to bring switch CPU out of reset" >&2 + return 1 + fi +} + +####################################### +# Put switch CPU into reset +####################################### +put_switch_cpu_into_reset() { + logger -t ${LOG_TAG} "Putting switch CPU into reset..." + + # Write to reset register to put CPU into reset + if busybox devmem ${RESET_REG_ADDR} 32 ${RESET_VALUE_IN} 2>/dev/null; then + logger -t ${LOG_TAG} "Switch CPU successfully put into reset (wrote ${RESET_VALUE_IN} to ${RESET_REG_ADDR})" + echo "Switch CPU put into reset successfully" + return 0 + else + logger -t ${LOG_TAG} "ERROR: Failed to put switch CPU into reset" + echo "ERROR: Failed to put switch CPU into reset" >&2 + return 1 + fi +} + +####################################### +# Cycle switch CPU reset (in -> out) +####################################### +cycle_switch_cpu_reset() { + logger -t ${LOG_TAG} "Cycling switch CPU reset..." + echo "Cycling switch CPU reset (in -> out)..." + + # Put into reset + if ! put_switch_cpu_into_reset; then + return 1 + fi + + # Wait for reset to take effect + echo "Waiting 2 seconds..." + sleep 2 + + # Bring out of reset + if ! bring_switch_cpu_out_of_reset; then + return 1 + fi + + logger -t ${LOG_TAG} "Switch CPU reset cycle completed successfully" + echo "Switch CPU reset cycle completed successfully" + return 0 +} + +####################################### +# Show switch CPU reset status +####################################### +show_switch_cpu_status() { + logger -t ${LOG_TAG} "Reading switch CPU reset status..." + + # Read current value from reset register + CURRENT_VALUE=$(busybox devmem ${RESET_REG_ADDR} 32 2>/dev/null) + + if [ $? -ne 0 ]; then + echo "ERROR: Failed to read reset register at ${RESET_REG_ADDR}" >&2 + return 1 + fi + + echo "Switch CPU Reset Status:" + echo " Register Address: ${RESET_REG_ADDR}" + echo " Current Value: ${CURRENT_VALUE}" + + # Interpret the value + if [ "${CURRENT_VALUE}" = "${RESET_VALUE_OUT}" ] || [ "${CURRENT_VALUE}" = "0x00000003" ]; then + echo " Status: OUT OF RESET (running)" + elif [ "${CURRENT_VALUE}" = "${RESET_VALUE_IN}" ] || [ "${CURRENT_VALUE}" = "0x00000000" ]; then + echo " Status: IN RESET (held in reset)" + else + echo " Status: UNKNOWN (value: ${CURRENT_VALUE})" + fi + + return 0 +} + +####################################### +# Main function +####################################### +main() { + # Handle no arguments + if [ $# -eq 0 ]; then + echo "ERROR: No command specified" >&2 + echo "" + usage + exit 1 + fi + + # Parse command + COMMAND="$1" + shift + + case "${COMMAND}" in + reset-out) + bring_switch_cpu_out_of_reset + ;; + reset-in) + put_switch_cpu_into_reset + ;; + reset-cycle) + cycle_switch_cpu_reset + ;; + status) + show_switch_cpu_status + ;; + help|--help|-h) + usage + exit 0 + ;; + *) + echo "ERROR: Unknown command '${COMMAND}'" >&2 + echo "" + usage + exit 1 + ;; + esac + + exit $? +} + +# Run main function +main "$@" + diff --git a/platform/aspeed/sonic-platform-modules-nexthop/common/cfg/usb-gadget.conf b/platform/aspeed/sonic-platform-modules-nexthop/common/cfg/usb-gadget.conf new file mode 100644 index 00000000000..931d01d6b6c --- /dev/null +++ b/platform/aspeed/sonic-platform-modules-nexthop/common/cfg/usb-gadget.conf @@ -0,0 +1,6 @@ +# USB Gadget kernel modules for NextHop BMC platforms +# Loaded at boot by systemd-modules-load.service +libcomposite +aspeed_vhub +u_ether +usb_f_ncm diff --git a/platform/aspeed/sonic-platform-modules-nexthop/common/scripts/usb-network-init.sh b/platform/aspeed/sonic-platform-modules-nexthop/common/scripts/usb-network-init.sh new file mode 100755 index 00000000000..986fcb9fa04 --- /dev/null +++ b/platform/aspeed/sonic-platform-modules-nexthop/common/scripts/usb-network-init.sh @@ -0,0 +1,137 @@ +#!/bin/bash +# USB Network Gadget Initialization Script for ASPEED AST2700 +# Sets up USB network device using ConfigFS + +set -e + +# Configuration +GADGET_NAME="g1" +FUNCTION_TYPE="ncm" +INTERFACE_NAME="usb0" +VENDOR_ID="0x1d6b" # Linux Foundation +PRODUCT_ID="0x0104" # Multifunction Composite Gadget +SERIAL_NUMBER="0123456789" +MANUFACTURER="Nexthop Inc." +PRODUCT_NAME="USB Network Device" +DEV_MAC="02:00:00:00:00:01" +HOST_MAC="02:00:00:00:00:02" + +logger -t usb-network "Starting USB network gadget initialization..." + +# Step 1: Mount ConfigFS +if ! mountpoint -q /sys/kernel/config 2>/dev/null; then + mount -t configfs none /sys/kernel/config + logger -t usb-network "ConfigFS mounted at /sys/kernel/config" +fi + +# Step 2: Create gadget configuration +GADGET_DIR="/sys/kernel/config/usb_gadget/${GADGET_NAME}" + +# Remove existing gadget if present +if [ -d "${GADGET_DIR}" ]; then + logger -t usb-network "Removing existing gadget configuration..." + # Disable gadget first + if [ -f "${GADGET_DIR}/UDC" ]; then + echo "" > "${GADGET_DIR}/UDC" 2>/dev/null || true + fi + # Remove gadget directory and all contents + rm -rf "${GADGET_DIR}" 2>/dev/null || true +fi + +# Create new gadget +logger -t usb-network "Creating USB gadget configuration..." +mkdir -p "${GADGET_DIR}" +cd "${GADGET_DIR}" + +# Set USB device descriptor +echo "${VENDOR_ID}" > idVendor +echo "${PRODUCT_ID}" > idProduct +echo "0x0100" > bcdDevice # v1.0.0 +echo "0x0200" > bcdUSB # USB 2.0 + +# Create English strings +mkdir -p strings/0x409 +echo "${SERIAL_NUMBER}" > strings/0x409/serialnumber +echo "${MANUFACTURER}" > strings/0x409/manufacturer +echo "${PRODUCT_NAME}" > strings/0x409/product + +# Create configuration +mkdir -p configs/c.1/strings/0x409 +echo "${FUNCTION_TYPE^^} Network" > configs/c.1/strings/0x409/configuration +echo "250" > configs/c.1/MaxPower # 250mA + +# Create network function +mkdir -p "functions/${FUNCTION_TYPE}.${INTERFACE_NAME}" +echo "${DEV_MAC}" > "functions/${FUNCTION_TYPE}.${INTERFACE_NAME}/dev_addr" +echo "${HOST_MAC}" > "functions/${FUNCTION_TYPE}.${INTERFACE_NAME}/host_addr" + +# Link function to configuration +ln -s "functions/${FUNCTION_TYPE}.${INTERFACE_NAME}" configs/c.1/ + +logger -t usb-network "Gadget '${GADGET_NAME}' created with ${FUNCTION_TYPE^^} function" + +# Step 4: Enable the gadget +# Find the first available UDC (USB Device Controller) +UDC_NAME=$(ls /sys/class/udc 2>/dev/null | head -n1) + +if [ -z "${UDC_NAME}" ]; then + logger -t usb-network "ERROR: No UDC (USB Device Controller) found!" + echo "ERROR: No UDC found. Make sure aspeed_vhub module is loaded." >&2 + exit 1 +fi + +logger -t usb-network "Using UDC: ${UDC_NAME}" +echo "${UDC_NAME}" > UDC + +# Step 5: Wait for interface to appear +logger -t usb-network "Waiting for network interface ${INTERFACE_NAME}..." +for i in {1..10}; do + if ip link show "${INTERFACE_NAME}" &>/dev/null; then + logger -t usb-network "Interface '${INTERFACE_NAME}' detected" + break + fi + sleep 1 + logger -t usb-network "Interface '${INTERFACE_NAME}' still not up" +done + +if ! ip link show "${INTERFACE_NAME}" &>/dev/null; then + logger -t usb-network "WARNING: Interface '${INTERFACE_NAME}' not found after 10 seconds" + exit 1 +fi + +# Step 6: Configure network interface +logger -t usb-network "Configuring network interface..." +ip addr flush dev "${INTERFACE_NAME}" 2>/dev/null || true + +# Bring interface up (required for IPv6) +ip link set "${INTERFACE_NAME}" up + +# Enable IPv6 on the interface +sysctl -w net.ipv6.conf.${INTERFACE_NAME}.disable_ipv6=0 2>/dev/null || true + +# Wait a moment for interface to stabilize +sleep 1 + +# Check interface state +LINK_STATE=$(ip link show "${INTERFACE_NAME}" | grep -oP 'state \K\w+') + +logger -t usb-network "USB network gadget initialized successfully" +logger -t usb-network "Interface: ${INTERFACE_NAME} (MAC: ${DEV_MAC}), State: ${LINK_STATE}, UDC: ${UDC_NAME}" + +# IPv6 link-local address will be auto-configured when link comes up +# Provide guidance on next steps +if [ "${LINK_STATE}" = "DOWN" ]; then + logger -t usb-network "Interface is DOWN - waiting for USB host connection" + logger -t usb-network "To bring switch CPU out of reset, run: switch_cpu_utils.sh reset-out" + echo "USB network interface '${INTERFACE_NAME}' is ready but link is DOWN (no host connected)" + echo "To bring switch CPU out of reset and establish USB connection, run:" + echo " switch_cpu_utils.sh reset-out" +else + # Link is UP - show IPv6 address + IPV6_ADDR=$(ip -6 addr show dev "${INTERFACE_NAME}" scope link | grep -oP 'fe80::[0-9a-f:]+' | head -n1) + logger -t usb-network "Link is UP - IPv6 link-local: ${IPV6_ADDR}" + echo "USB network interface '${INTERFACE_NAME}' is UP with IPv6 link-local: ${IPV6_ADDR}" +fi + +exit 0 + diff --git a/platform/aspeed/sonic-platform-modules-nexthop/common/sonic_platform/__init__.py b/platform/aspeed/sonic-platform-modules-nexthop/common/sonic_platform/__init__.py new file mode 100644 index 00000000000..464315b4746 --- /dev/null +++ b/platform/aspeed/sonic-platform-modules-nexthop/common/sonic_platform/__init__.py @@ -0,0 +1,13 @@ +#!/usr/bin/env python3 +# +# __init__.py +# +# Platform-specific SONiC platform API for NextHop Aspeed AST2700 BMC +# + +try: + from sonic_platform.platform import Platform + from sonic_platform.chassis import Chassis +except ImportError as e: + raise ImportError(str(e) + " - required module not found") + diff --git a/platform/aspeed/sonic-platform-modules-nexthop/common/sonic_platform/chassis.py b/platform/aspeed/sonic-platform-modules-nexthop/common/sonic_platform/chassis.py new file mode 100644 index 00000000000..e44cf7cc662 --- /dev/null +++ b/platform/aspeed/sonic-platform-modules-nexthop/common/sonic_platform/chassis.py @@ -0,0 +1,267 @@ +#!/usr/bin/env python3 +# +# chassis.py +# +# Chassis implementation for NextHop B27 BMC +# + +try: + from sonic_platform_base.chassis_base import ChassisBase + from sonic_platform.thermal import Thermal + from sonic_platform.watchdog import Watchdog +except ImportError as e: + raise ImportError(str(e) + " - required module not found") + + +# Watchdog bootstatus file paths for AST2700 +WATCHDOG0_BOOTSTATUS_PATH = "/sys/devices/platform/soc@14000000/14c37000.watchdog/watchdog/watchdog0/bootstatus" +WATCHDOG1_BOOTSTATUS_PATH = "/sys/devices/platform/soc@14000000/14c37080.watchdog/watchdog/watchdog1/bootstatus" + +# Watchdog bootstatus bit flags (from Linux kernel watchdog.h) +WDIOF_OVERHEAT = 0x0001 # Reset due to CPU overheat +WDIOF_FANFAULT = 0x0002 # Fan failed +WDIOF_EXTERN1 = 0x0004 # External relay 1 +WDIOF_EXTERN2 = 0x0008 # External relay 2 +WDIOF_POWERUNDER = 0x0010 # Power bad/power fault +WDIOF_CARDRESET = 0x0020 # Card previously reset the CPU (normal reboot) +WDIOF_POWEROVER = 0x0040 # Power over voltage + +class Chassis(ChassisBase): + """ + Platform-specific Chassis class for NextHop B27 BMC + + Hardware Configuration (from nexthop-b27-r0.dts): + - 0 PWM fans (all fan controllers disabled in DTS) + - 0 TACH inputs (all disabled in DTS) + - ADC controllers disabled (ADC0 and ADC1 both disabled) + - 2 Watchdog timers (wdt0, wdt1) + - BCM53134 managed switch with DSA configuration + + Supports multiple card revisions with runtime detection. + """ + + # NextHop has NO fans - all fan/PWM/TACH controllers are disabled in DTS + NUM_FANS = 0 + + # NextHop has ADC controllers disabled - thermal sensors may not be available + # We'll check at runtime which sensors actually exist + NUM_THERMAL_SENSORS = 16 # Keep same as base, but filter in __init__ + + def __init__(self): + """ + Initialize NextHop chassis with hardware-specific configuration + """ + super().__init__() + + # Initialize watchdog (same as base class) + self._watchdog = Watchdog() + + # NextHop has NO fans - create empty lists + self._fan_list = [] + self._fan_drawer_list = [] + + # For thermal sensors, only add those that actually exist + # ADC controllers are disabled in DTS, so most/all may not be present + self._thermal_list = [] + for i in range(self.NUM_THERMAL_SENSORS): + thermal = Thermal(i) + # Only add thermal sensor if it's actually present in hardware + if thermal.get_presence(): + self._thermal_list.append(thermal) + + # NextHop-specific initialization + self.card_revision = self._detect_card_revision() + + def _read_watchdog_bootstatus(self, path): + """ + Read watchdog bootstatus value from sysfs + + Args: + path: Path to the bootstatus file + + Returns: + Integer value of bootstatus, or 0 if file doesn't exist or can't be read + """ + try: + with open(path, 'r') as f: + value = f.read().strip() + return int(value) + except (IOError, OSError, ValueError): + return 0 + + def get_reboot_cause(self): + """ + Retrieves the cause of the previous reboot + + This method reads the watchdog bootstatus register to determine + the hardware reboot cause. The AST2700 based B27 has two watchdog timers, + and we check watchdog0 for the reboot cause. + + Returns: + A tuple (string, string) where the first element is a string + containing the cause of the previous reboot. This string must be + one of the predefined strings in ChassisBase. If the first string + is "REBOOT_CAUSE_HARDWARE_OTHER", the second string can be used + to pass a description of the reboot cause. + + Reboot cause mapping: + - WDIOF_OVERHEAT (0x01): Thermal overload + - WDIOF_FANFAULT (0x02): Insufficient fan speed + - WDIOF_POWERUNDER (0x10): Power loss + - WDIOF_POWEROVER (0x40): Power over voltage + - WDIOF_CARDRESET (0x20): Normal reboot/reset + - Other bits: Hardware other + - No bits set: Non-hardware (software reboot) + """ + # Read watchdog0 bootstatus + bootstatus = self._read_watchdog_bootstatus(WATCHDOG0_BOOTSTATUS_PATH) + + # Map bootstatus bits to reboot causes + # Check in order of priority (most specific first) + + if bootstatus & WDIOF_OVERHEAT: + return (self.REBOOT_CAUSE_THERMAL_OVERLOAD_CPU, "CPU Overheat") + + if bootstatus & WDIOF_FANFAULT: + return (self.REBOOT_CAUSE_INSUFFICIENT_FAN_SPEED, "Fan Fault") + + if bootstatus & WDIOF_POWERUNDER: + return (self.REBOOT_CAUSE_POWER_LOSS, "Power Under Voltage") + + if bootstatus & WDIOF_POWEROVER: + return (self.REBOOT_CAUSE_POWER_LOSS, "Power Over Voltage") + + if bootstatus & WDIOF_CARDRESET: + # CARDRESET typically indicates a normal reboot/reset + # This is not a hardware fault, so return NON_HARDWARE + # The determine-reboot-cause service will use the software + # reboot cause from /host/reboot-cause/reboot-cause.txt + return (self.REBOOT_CAUSE_NON_HARDWARE, None) + + if bootstatus & (WDIOF_EXTERN1 | WDIOF_EXTERN2): + return (self.REBOOT_CAUSE_HARDWARE_OTHER, f"External Reset (bootstatus=0x{bootstatus:x})") + + # If no specific bits are set, or only unknown bits are set + if bootstatus == 0: + # No hardware reboot cause detected + return (self.REBOOT_CAUSE_NON_HARDWARE, None) + else: + # Unknown bootstatus bits + return (self.REBOOT_CAUSE_HARDWARE_OTHER, f"Unknown (bootstatus=0x{bootstatus:x})") + + def get_name(self): + """ + Retrieves the name of the chassis + + Returns: + String containing the name of the chassis + """ + return "Nexthop B27" + + def get_model(self): + """ + Retrieves the model number (or part number) of the chassis + + Returns: + String containing the model number of the chassis + """ + return "Nexthop B27" + + def get_serial(self): + """ + Retrieves the serial number of the chassis + + Returns: + String containing the serial number of the chassis + """ + return "N/A" + + def get_watchdog(self): + """ + Retrieves the hardware watchdog device on this chassis + + Returns: + An object derived from WatchdogBase representing the hardware + watchdog device + """ + return self._watchdog + + def get_num_thermals(self): + """ + Retrieves the number of thermal sensors available on this chassis + + Returns: + An integer, the number of thermal sensors available on this chassis + """ + return len(self._thermal_list) + + def get_all_thermals(self): + """ + Retrieves all thermal sensors available on this chassis + + Returns: + A list of objects derived from ThermalBase representing all thermal + sensors available on this chassis + """ + return self._thermal_list + + def get_thermal(self, index): + """ + Retrieves thermal sensor represented by (0-based) index + + Args: + index: An integer, the index (0-based) of the thermal sensor to retrieve + + Returns: + An object derived from ThermalBase representing the specified thermal + sensor, or None if index is out of range + """ + if index < 0 or index >= len(self._thermal_list): + return None + return self._thermal_list[index] + + def get_num_fans(self): + """ + Retrieves the number of fans available on this chassis + + Returns: + An integer, the number of fans available on this chassis + """ + return len(self._fan_list) + + def get_all_fans(self): + """ + Retrieves all fan modules available on this chassis + + Returns: + A list of objects derived from FanBase representing all fan + modules available on this chassis + """ + return self._fan_list + + def get_fan(self, index): + """ + Retrieves fan module represented by (0-based) index + + Args: + index: An integer, the index (0-based) of the fan module to retrieve + + Returns: + An object derived from FanBase representing the specified fan + module, or None if index is out of range + """ + if index < 0 or index >= len(self._fan_list): + return None + return self._fan_list[index] + + def _detect_card_revision(self): + """ + Detect the NextHop BMC card revision from hardware + + Returns: + str: Card revision identifier (e.g., 'r0', 'r1') + """ + # TODO: Implement revision detection from EEPROM or device tree + # For now, default to 'r0' + return 'r0' + diff --git a/platform/aspeed/sonic-platform-modules-nexthop/common/sonic_platform/fan.py b/platform/aspeed/sonic-platform-modules-nexthop/common/sonic_platform/fan.py new file mode 100644 index 00000000000..d08beb7ff3e --- /dev/null +++ b/platform/aspeed/sonic-platform-modules-nexthop/common/sonic_platform/fan.py @@ -0,0 +1,293 @@ +""" +SONiC Platform API - Fan class for Aspeed BMC + +This module provides the Fan class for Aspeed AST2700 BMC platform. +Fans are controlled via PWM and monitored via TACH inputs. +""" + +import os + +try: + from sonic_platform_base.fan_base import FanBase +except ImportError as e: + raise ImportError(str(e) + " - required module not found") + + +class Fan(FanBase): + """ + Fan class for Aspeed AST2700 BMC platform + + The AST2700 supports: + - 16 TACH inputs for fan speed monitoring (hwmon0) + - 9 PWM outputs for fan speed control (hwmon1-9) + """ + + # Base paths for fan devices + TACH_HWMON_PATH = "/sys/class/hwmon/hwmon0" # aspeed_tach + PWM_HWMON_BASE = "/sys/class/hwmon/hwmon" # pwm-fan devices (hwmon1-9) + + # PWM range (0-255) + PWM_MAX = 255 + + def __init__(self, fan_index, is_psu_fan=False): + """ + Initialize a Fan object + + Args: + fan_index: 0-based index of the fan (0-15 for TACH, 0-8 for PWM) + is_psu_fan: True if this is a PSU fan, False otherwise + """ + FanBase.__init__(self) + + self.index = fan_index + self.is_psu_fan = is_psu_fan + self.name = f"Fan{fan_index + 1}" + + # TACH input path (1-based in sysfs) + self.tach_input_path = os.path.join( + self.TACH_HWMON_PATH, + f"fan{fan_index + 1}_input" + ) + + # PWM control path (for fans 0-8, maps to hwmon1-9) + # pwm-fan0 -> hwmon8, pwm-fan1 -> hwmon9, pwm-fan2 -> hwmon1, etc. + # We'll need to find the correct hwmon device + self.pwm_path = None + if fan_index < 9: + self._find_pwm_path(fan_index) + + def _find_pwm_path(self, fan_index): + """ + Find the PWM control path for this fan + + Args: + fan_index: 0-based fan index + """ + # Search for the pwm-fan device + for hwmon_num in range(1, 10): + hwmon_path = f"{self.PWM_HWMON_BASE}{hwmon_num}" + device_path = os.path.join(hwmon_path, "device") + + try: + # Read the device name + device_name = os.path.basename(os.readlink(device_path)) + if device_name == f"pwm-fan{fan_index}": + self.pwm_path = os.path.join(hwmon_path, "pwm1") + return + except (OSError, IOError): + continue + + def _read_sysfs_file(self, path): + """ + Read a value from a sysfs file + + Args: + path: Path to the sysfs file + + Returns: + String value from the file, or None if read fails + """ + try: + with open(path, 'r') as f: + return f.read().strip() + except (IOError, OSError): + return None + + def _write_sysfs_file(self, path, value): + """ + Write a value to a sysfs file + + Args: + path: Path to the sysfs file + value: Value to write + + Returns: + True if write succeeds, False otherwise + """ + try: + with open(path, 'w') as f: + f.write(str(value)) + return True + except (IOError, OSError): + return False + + def get_name(self): + """ + Retrieves the name of the fan + + Returns: + String containing the name of the fan + """ + return self.name + + def get_presence(self): + """ + Retrieves the presence of the fan + + Returns: + True if fan is present, False if not + """ + # On BMC, fans are always "present" if the TACH input exists + return os.path.exists(self.tach_input_path) + + def get_model(self): + """ + Retrieves the model of the fan + + Returns: + String containing the model of the fan + """ + return "AST2700-FAN" + + def get_serial(self): + """ + Retrieves the serial number of the fan + + Returns: + String containing the serial number of the fan + """ + return "N/A" + + def get_status(self): + """ + Retrieves the operational status of the fan + + Returns: + True if fan is operating properly, False if not + """ + # Fan is operational if it's present and speed can be read + if not self.get_presence(): + return False + + speed_rpm = self.get_speed_rpm() + return speed_rpm is not None + + def get_direction(self): + """ + Retrieves the direction of fan + + Returns: + A string, either FAN_DIRECTION_INTAKE or FAN_DIRECTION_EXHAUST + depending on fan direction + """ + # BMC fans don't have direction detection on EVB + return self.FAN_DIRECTION_NOT_APPLICABLE + + def get_speed_rpm(self): + """ + Retrieves the speed of fan in RPM + + Returns: + An integer, the fan speed in RPM, or None if not available + """ + value_str = self._read_sysfs_file(self.tach_input_path) + if value_str is None: + return None + + try: + return int(value_str) + except ValueError: + return None + + def get_speed(self): + """ + Retrieves the speed of fan as a percentage of full speed + + Returns: + An integer, the percentage of full fan speed, in the range 0 (off) + to 100 (full speed) + """ + # Get current PWM value + if self.pwm_path is None or not os.path.exists(self.pwm_path): + # If no PWM control, estimate from RPM + # Assume max speed is 10000 RPM (platform-specific) + rpm = self.get_speed_rpm() + if rpm is None: + return 0 + max_rpm = 10000 + speed_percent = min(100, int((rpm * 100) / max_rpm)) + return speed_percent + + pwm_str = self._read_sysfs_file(self.pwm_path) + if pwm_str is None: + return 0 + + try: + pwm_value = int(pwm_str) + # Convert PWM (0-255) to percentage (0-100) + return int((pwm_value * 100) / self.PWM_MAX) + except ValueError: + return 0 + + def get_target_speed(self): + """ + Retrieves the target (expected) speed of the fan + + Returns: + An integer, the percentage of full fan speed, in the range 0 (off) + to 100 (full speed) + """ + # Target speed is the same as current speed (no separate target register) + return self.get_speed() + + def get_speed_tolerance(self): + """ + Retrieves the speed tolerance of the fan + + Returns: + An integer, the percentage of variance from target speed which is + considered tolerable + """ + # Default tolerance of 20% + return 20 + + def set_speed(self, speed): + """ + Sets the fan speed + + Args: + speed: An integer, the percentage of full fan speed to set fan to, + in the range 0 (off) to 100 (full speed) + + Returns: + A boolean, True if speed is set successfully, False if not + """ + if self.is_psu_fan: + # PSU fans are controlled by the PSU itself + return False + + if speed < 0 or speed > 100: + return False + + if self.pwm_path is None or not os.path.exists(self.pwm_path): + return False + + # Convert percentage (0-100) to PWM value (0-255) + pwm_value = int((speed * self.PWM_MAX) / 100) + + return self._write_sysfs_file(self.pwm_path, pwm_value) + + def set_status_led(self, color): + """ + Sets the state of the fan status LED + + Args: + color: A string representing the color with which to set the + fan status LED + + Returns: + bool: True if status LED state is set successfully, False if not + """ + # BMC fans don't have individual status LEDs + return False + + def get_status_led(self): + """ + Gets the state of the fan status LED + + Returns: + A string, one of the predefined STATUS_LED_COLOR_* strings + """ + # BMC fans don't have individual status LEDs + return self.STATUS_LED_COLOR_OFF + diff --git a/platform/aspeed/sonic-platform-modules-nexthop/common/sonic_platform/fan_drawer.py b/platform/aspeed/sonic-platform-modules-nexthop/common/sonic_platform/fan_drawer.py new file mode 100644 index 00000000000..7e89313aeec --- /dev/null +++ b/platform/aspeed/sonic-platform-modules-nexthop/common/sonic_platform/fan_drawer.py @@ -0,0 +1,138 @@ +#!/usr/bin/env python3 + +""" +fan_drawer.py + +Implementation of FanDrawer class for Aspeed AST2700 BMC platform. +Since BMC platforms typically don't have physical fan drawers/trays, +we create virtual fan drawers with one fan each to satisfy SONiC's +thermalctld requirements. +""" + +try: + from sonic_platform_base.fan_drawer_base import FanDrawerBase +except ImportError as e: + raise ImportError(str(e) + " - required module not found") + + +class FanDrawer(FanDrawerBase): + """ + Platform-specific FanDrawer class for Aspeed AST2700 BMC. + + Each FanDrawer represents a virtual drawer containing a single fan. + This is required because thermalctld reads fans from fan drawers, + not directly from the chassis. + """ + + def __init__(self, drawer_index, fan): + """ + Initialize a FanDrawer with a single fan. + + Args: + drawer_index: 0-based index of this fan drawer + fan: Fan object to be contained in this drawer + """ + FanDrawerBase.__init__(self) + self._drawer_index = drawer_index + self._fan_list.append(fan) + + def get_name(self): + """ + Retrieves the fan drawer name. + + Returns: + string: The name of the fan drawer (e.g., "FanDrawer1") + """ + return "FanDrawer{}".format(self._drawer_index + 1) + + def get_presence(self): + """ + Retrieves the presence of the fan drawer. + + Returns: + bool: True if fan drawer is present, False otherwise + """ + # Virtual fan drawers are always present + return True + + def get_status(self): + """ + Retrieves the operational status of the fan drawer. + + Returns: + bool: True if fan drawer is operating properly, False otherwise + """ + # Fan drawer status is based on the contained fan's status + if len(self._fan_list) > 0: + return self._fan_list[0].get_status() + return False + + def get_model(self): + """ + Retrieves the model number of the fan drawer. + + Returns: + string: Model number of the fan drawer + """ + return "N/A" + + def get_serial(self): + """ + Retrieves the serial number of the fan drawer. + + Returns: + string: Serial number of the fan drawer + """ + return "N/A" + + def get_status_led(self): + """ + Gets the state of the fan drawer LED. + + Returns: + string: One of the STATUS_LED_COLOR_* strings (GREEN, RED, etc.) + """ + return "N/A" + + def set_status_led(self, color): + """ + Sets the state of the fan drawer LED. + + Args: + color: A string representing the color with which to set the LED + + Returns: + bool: True if LED state is set successfully, False otherwise + """ + # LED control not supported on BMC platform + return False + + def get_maximum_consumed_power(self): + """ + Retrieves the maximum power drawn by the fan drawer. + + Returns: + float: Maximum power consumption in watts + """ + # Typical BMC fan power consumption + return 5.0 + + def is_replaceable(self): + """ + Indicate whether this fan drawer is replaceable. + + Returns: + bool: True if replaceable, False otherwise + """ + # Virtual fan drawers are not physically replaceable + return False + + def get_position_in_parent(self): + """ + Retrieves the 1-based relative physical position in parent device. + + Returns: + integer: The 1-based relative physical position in parent device + """ + return self._drawer_index + 1 + diff --git a/platform/aspeed/sonic-platform-modules-nexthop/common/sonic_platform/platform.py b/platform/aspeed/sonic-platform-modules-nexthop/common/sonic_platform/platform.py new file mode 100644 index 00000000000..ae6e37873d2 --- /dev/null +++ b/platform/aspeed/sonic-platform-modules-nexthop/common/sonic_platform/platform.py @@ -0,0 +1,32 @@ +""" +SONiC Platform API - Platform class for Nexthop B27 BMC +""" + +try: + from sonic_platform.chassis import Chassis +except ImportError as e: + raise ImportError(str(e) + " - required module not found") + + +class Platform: + """ + Platform class for Nexthop B27 BMC + + Provides access to chassis-level functionality. + """ + + def __init__(self): + """ + Initialize the Platform object + """ + self._chassis = Chassis() + + def get_chassis(self): + """ + Retrieves the chassis object + + Returns: + An object derived from ChassisBase representing the chassis + """ + return self._chassis + diff --git a/platform/aspeed/sonic-platform-modules-nexthop/common/sonic_platform/thermal.py b/platform/aspeed/sonic-platform-modules-nexthop/common/sonic_platform/thermal.py new file mode 100644 index 00000000000..655da029d1d --- /dev/null +++ b/platform/aspeed/sonic-platform-modules-nexthop/common/sonic_platform/thermal.py @@ -0,0 +1,207 @@ +""" +SONiC Platform API - Thermal class for Aspeed BMC + +This module provides the Thermal class for Aspeed AST2700 BMC platform. +Thermal sensors are exposed via the IIO-HWMON bridge from ADC channels. +""" + +import os + +try: + from sonic_platform_base.thermal_base import ThermalBase +except ImportError as e: + raise ImportError(str(e) + " - required module not found") + + +class Thermal(ThermalBase): + """ + Thermal sensor class for Aspeed AST2700 BMC platform + + Reads temperature/voltage values from ADC channels exposed via iio-hwmon. + The AST2700 has 16 ADC channels available for monitoring. + """ + + # Base path for IIO-HWMON device + HWMON_IIO_PATH = "/sys/class/hwmon/hwmon10" + + def __init__(self, thermal_index): + """ + Initialize a Thermal object + + Args: + thermal_index: 0-based index of the thermal sensor (0-15 for 16 ADC channels) + """ + ThermalBase.__init__(self) + + self.index = thermal_index + self.name = f"ADC{thermal_index + 1}" + + # ADC channel input file (1-based in sysfs) + self.thermal_input_path = os.path.join( + self.HWMON_IIO_PATH, + f"in{thermal_index + 1}_input" + ) + + def _read_sysfs_file(self, path): + """ + Read a value from a sysfs file + + Args: + path: Path to the sysfs file + + Returns: + String value from the file, or None if read fails + """ + try: + with open(path, 'r') as f: + return f.read().strip() + except (IOError, OSError): + return None + + def get_name(self): + """ + Retrieves the name of the thermal sensor + + Returns: + String containing the name of the thermal sensor + """ + return self.name + + def get_presence(self): + """ + Retrieves the presence of the thermal sensor + + Returns: + True if thermal sensor is present, False if not + """ + # ADC channels are always present on the BMC + return os.path.exists(self.thermal_input_path) + + def get_model(self): + """ + Retrieves the model of the thermal sensor + + Returns: + String containing the model of the thermal sensor + """ + return "AST2700-ADC" + + def get_serial(self): + """ + Retrieves the serial number of the thermal sensor + + Returns: + String containing the serial number of the thermal sensor + """ + return "N/A" + + def get_status(self): + """ + Retrieves the operational status of the thermal sensor + + Returns: + True if thermal sensor is operating properly, False if not + """ + return self.get_presence() and self.get_temperature() is not None + + def get_temperature(self): + """ + Retrieves current temperature reading from thermal sensor + + Returns: + A float number of current temperature in Celsius up to nearest + thousandth of one degree Celsius, e.g. 30.125 + + Note: + ADC channels return millivolts. For actual temperature sensors, + the voltage needs to be converted based on the sensor type. + For now, we return the raw millivolt value divided by 1000 as + a placeholder. Platform-specific implementations should override + this with proper temperature conversion formulas. + """ + value_str = self._read_sysfs_file(self.thermal_input_path) + if value_str is None: + return None + + try: + # ADC returns millivolts, convert to a temperature-like value + # This is a placeholder - actual conversion depends on sensor type + millivolts = float(value_str) + # For demonstration, just return mV/10 as "temperature" + # Real implementation would use proper sensor calibration + return millivolts / 10.0 + except ValueError: + return None + + def get_high_threshold(self): + """ + Retrieves the high threshold temperature of thermal sensor + + Returns: + A float number, the high threshold temperature of thermal in + Celsius up to nearest thousandth of one degree Celsius + """ + # Default threshold - should be configured per platform + return 85.0 + + def get_low_threshold(self): + """ + Retrieves the low threshold temperature of thermal sensor + + Returns: + A float number, the low threshold temperature of thermal in + Celsius up to nearest thousandth of one degree Celsius + """ + # Default threshold - should be configured per platform + return 0.0 + + def get_high_critical_threshold(self): + """ + Retrieves the high critical threshold temperature of thermal sensor + + Returns: + A float number, the high critical threshold temperature of thermal + in Celsius up to nearest thousandth of one degree Celsius + """ + # Default threshold - should be configured per platform + return 100.0 + + def get_low_critical_threshold(self): + """ + Retrieves the low critical threshold temperature of thermal sensor + + Returns: + A float number, the low critical threshold temperature of thermal + in Celsius up to nearest thousandth of one degree Celsius + """ + # Default threshold - should be configured per platform + return -10.0 + + def set_high_threshold(self, temperature): + """ + Sets the high threshold temperature of thermal sensor + + Args: + temperature: A float number up to nearest thousandth of one degree + Celsius, e.g. 30.125 + + Returns: + A boolean, True if threshold is set successfully, False if not + """ + # ADC channels don't support setting thresholds via sysfs + return False + + def set_low_threshold(self, temperature): + """ + Sets the low threshold temperature of thermal sensor + + Args: + temperature: A float number up to nearest thousandth of one degree + Celsius, e.g. 30.125 + + Returns: + A boolean, True if threshold is set successfully, False if not + """ + # ADC channels don't support setting thresholds via sysfs + return False + diff --git a/platform/aspeed/sonic-platform-modules-nexthop/common/sonic_platform/watchdog.py b/platform/aspeed/sonic-platform-modules-nexthop/common/sonic_platform/watchdog.py new file mode 100644 index 00000000000..3f9a8248c76 --- /dev/null +++ b/platform/aspeed/sonic-platform-modules-nexthop/common/sonic_platform/watchdog.py @@ -0,0 +1,220 @@ +""" +SONiC Platform API - Watchdog class for Aspeed BMC + +This module provides the Watchdog class for Aspeed AST2700 BMC platform. +""" + +import os +import fcntl +import array +import ctypes + +try: + from sonic_platform_base.watchdog_base import WatchdogBase +except ImportError as e: + raise ImportError(str(e) + " - required module not found") + + +# Watchdog ioctl commands (from Linux kernel watchdog.h) +WDIOC_GETSUPPORT = 0x80285700 +WDIOC_GETSTATUS = 0x80045701 +WDIOC_GETBOOTSTATUS = 0x80045702 +WDIOC_GETTEMP = 0x80045703 +WDIOC_SETOPTIONS = 0x80045704 +WDIOC_KEEPALIVE = 0x80045705 +WDIOC_SETTIMEOUT = 0xc0045706 +WDIOC_GETTIMEOUT = 0x80045707 +WDIOC_SETPRETIMEOUT = 0xc0045708 +WDIOC_GETPRETIMEOUT = 0x80045709 +WDIOC_GETTIMELEFT = 0x8004570a + +# Watchdog options +WDIOS_DISABLECARD = 0x0001 +WDIOS_ENABLECARD = 0x0002 + +# Default watchdog device +WATCHDOG_DEVICE = "/dev/watchdog0" + +# Sysfs paths for AST2700 watchdog +WATCHDOG_SYSFS_PATH = "/sys/class/watchdog/watchdog0/" + + +class Watchdog(WatchdogBase): + """ + Watchdog class for Aspeed AST2700 BMC platform + + Provides hardware watchdog functionality using the aspeed_wdt driver. + """ + + def __init__(self): + """ + Initialize the Watchdog object + """ + self.watchdog_device = WATCHDOG_DEVICE + self.watchdog_fd = None + self.timeout = 0 + self.armed = False + + def _open_watchdog(self): + """ + Open the watchdog device file + + Returns: + True if successful, False otherwise + """ + if self.watchdog_fd is None: + try: + self.watchdog_fd = os.open(self.watchdog_device, os.O_WRONLY) + return True + except (IOError, OSError): + return False + return True + + def _close_watchdog(self): + """ + Close the watchdog device file + """ + if self.watchdog_fd is not None: + try: + # Write magic close character 'V' to properly close watchdog + os.write(self.watchdog_fd, b'V') + os.close(self.watchdog_fd) + except (IOError, OSError): + pass + finally: + self.watchdog_fd = None + + def _read_sysfs_int(self, filename): + """ + Read an integer value from sysfs + + Args: + filename: Name of the file in WATCHDOG_SYSFS_PATH + + Returns: + Integer value, or -1 on error + """ + try: + with open(os.path.join(WATCHDOG_SYSFS_PATH, filename), 'r') as f: + return int(f.read().strip()) + except (IOError, OSError, ValueError): + return -1 + + def arm(self, seconds): + """ + Arm the hardware watchdog with a timeout of seconds + + Args: + seconds: Timeout value in seconds + + Returns: + An integer specifying the actual number of seconds the watchdog + was armed with. On failure returns -1. + """ + if seconds < 0: + return -1 + + if not self._open_watchdog(): + return -1 + + try: + # Set timeout using ioctl + timeout_value = array.array('I', [seconds]) + fcntl.ioctl(self.watchdog_fd, WDIOC_SETTIMEOUT, timeout_value, True) + + # Enable watchdog + options = array.array('h', [WDIOS_ENABLECARD]) + fcntl.ioctl(self.watchdog_fd, WDIOC_SETOPTIONS, options, False) + + self.timeout = int(timeout_value[0]) + self.armed = True + + return self.timeout + except (IOError, OSError): + return -1 + + def disarm(self): + """ + Disarm the hardware watchdog + + Returns: + A boolean, True if watchdog is disarmed successfully, False if not + """ + if not self._open_watchdog(): + return False + + try: + # Disable watchdog + options = array.array('h', [WDIOS_DISABLECARD]) + fcntl.ioctl(self.watchdog_fd, WDIOC_SETOPTIONS, options, False) + + self.armed = False + self.timeout = 0 + + # Close the watchdog device properly + self._close_watchdog() + + return True + except (IOError, OSError): + return False + + def is_armed(self): + """ + Retrieves the armed state of the hardware watchdog + + Returns: + A boolean, True if watchdog is armed, False if not + """ + # Check the state from sysfs + state = self._read_sysfs_str("state") + if state == "active": + self.armed = True + return True + else: + self.armed = False + return False + + def get_remaining_time(self): + """ + Get the number of seconds remaining on the watchdog timer + + Returns: + An integer specifying the number of seconds remaining on the + watchdog timer. If the watchdog is not armed, returns -1. + """ + if not self.is_armed(): + return -1 + + # Try to read timeleft from sysfs first + timeleft = self._read_sysfs_int("timeleft") + if timeleft >= 0: + return timeleft + + # If timeleft is not available in sysfs, try ioctl + if not self._open_watchdog(): + return -1 + + try: + timeleft_value = array.array('I', [0]) + fcntl.ioctl(self.watchdog_fd, WDIOC_GETTIMELEFT, timeleft_value, True) + return int(timeleft_value[0]) + except (IOError, OSError): + # If ioctl fails, return the configured timeout as an estimate + return self.timeout if self.armed else -1 + + def _read_sysfs_str(self, filename): + """ + Read a string value from sysfs + + Args: + filename: Name of the file in WATCHDOG_SYSFS_PATH + + Returns: + String value, or empty string on error + """ + try: + with open(os.path.join(WATCHDOG_SYSFS_PATH, filename), 'r') as f: + return f.read().strip() + except (IOError, OSError): + return "" + diff --git a/platform/aspeed/sonic-platform-modules-nexthop/common/systemd/usb-network-init.service b/platform/aspeed/sonic-platform-modules-nexthop/common/systemd/usb-network-init.service new file mode 100644 index 00000000000..cb96ca78e7e --- /dev/null +++ b/platform/aspeed/sonic-platform-modules-nexthop/common/systemd/usb-network-init.service @@ -0,0 +1,12 @@ +[Unit] +Description=NextHop USB Network Gadget Service +Documentation=https://github.com/sonic-net/SONiC/wiki/ +After=systemd-modules-load.service local-fs.target + +[Service] +Type=oneshot +ExecStart=/usr/local/bin/usb-network-init.sh +RemainAfterExit=yes + +[Install] +WantedBy=multi-user.target diff --git a/platform/aspeed/sonic-platform-modules-nexthop/debian/changelog b/platform/aspeed/sonic-platform-modules-nexthop/debian/changelog new file mode 100644 index 00000000000..ab401fe88c0 --- /dev/null +++ b/platform/aspeed/sonic-platform-modules-nexthop/debian/changelog @@ -0,0 +1,6 @@ +sonic-platform-aspeed-nexthop (1.0) unstable; urgency=low + + * Initial release + + -- Nexthop Mon, 17 Feb 2025 00:00:00 +0000 + diff --git a/platform/aspeed/sonic-platform-modules-nexthop/debian/control b/platform/aspeed/sonic-platform-modules-nexthop/debian/control new file mode 100644 index 00000000000..7bb6ab3a23c --- /dev/null +++ b/platform/aspeed/sonic-platform-modules-nexthop/debian/control @@ -0,0 +1,19 @@ +Source: sonic-platform-aspeed-nexthop +Section: misc +Priority: optional +Maintainer: Nexthop +Build-Depends: debhelper-compat (= 13), dh-python, python3-all, python3-setuptools, python3-wheel +Standards-Version: 3.9.6 + +Package: sonic-platform-aspeed-nexthop-common +Architecture: arm64 +Depends: ${misc:Depends}, ${python3:Depends} +Description: Common modules, USB network gadget configuration and initialization scripts + shared across all NextHop Aspeed-based BMC cards. + +Package: sonic-platform-aspeed-nexthop-b27 +Architecture: arm64 +Depends: ${misc:Depends}, ${python3:Depends}, sonic-platform-aspeed-nexthop-common +Description: SONiC platform support for NextHop B27 + This package provides platform-specific implementation for NextHop's + B27 card based on Aspeed AST2700 BMC. diff --git a/platform/aspeed/sonic-platform-modules-nexthop/debian/rules b/platform/aspeed/sonic-platform-modules-nexthop/debian/rules new file mode 100755 index 00000000000..81be080ceb4 --- /dev/null +++ b/platform/aspeed/sonic-platform-modules-nexthop/debian/rules @@ -0,0 +1,138 @@ +#!/usr/bin/make -f + +include /usr/share/dpkg/pkg-info.mk + +export INSTALL_MOD_DIR := extra + +export KVERSION ?= $(shell uname -r) +export KERNEL_SRC ?= /lib/modules/$(KVERSION) +KERNEL_BUILD := $(KERNEL_SRC)/build +MOD_SRC_DIR := $(shell pwd) +MODULE_DIRS := + +# Common - shared across all NextHop BMC cards +MODULE_DIRS += common +# Card-specific +MODULE_DIRS += b27 + +# Directory names used in card-specific directories +SERVICE_DIR := service +SYSTEMD_DIR := systemd +SCRIPTS_DIR := scripts +OBMC_CONSOLE_DIR := obmc-console +CFG_DIR := cfg + +# Uncomment this to turn on verbose mode. +#export DH_VERBOSE=1 + +PACKAGE_PRE_NAME := sonic-platform-aspeed-nexthop + +%: + dh $@ --with python3 --buildsystem=pybuild + +override_dh_auto_build: + set -e + # Build kernel modules if they exist + (for mod in $(MODULE_DIRS); do \ + set -e; \ + if [ -f $(MOD_SRC_DIR)/$${mod}/modules/Makefile ]; then \ + if [ ! -d "$(KERNEL_BUILD)" ]; then \ + echo "WARNING: Kernel build directory $(KERNEL_BUILD) not found. Skipping kernel module build for $${mod}."; \ + else \ + $(MAKE) -C $(KERNEL_BUILD) M=$(MOD_SRC_DIR)/$${mod}/modules modules; \ + fi; \ + fi; \ + done) + # Build Python wheel from root setup.py + python3 setup.py bdist_wheel -d $(MOD_SRC_DIR) + echo "Finished making sonic_platform whl package" + +override_dh_auto_test: +# Skip tests for now + +override_dh_usrlocal: +# Needed since we are installing files to /usr/local + +override_dh_installsystemd: +# Disables dh_installsystemd from autogenerating postinst scripts which +# prematurely start platform services. + +override_dh_auto_install: + set -e + (for mod in $(MODULE_DIRS); do \ + set -e; \ + pkg_name=$(PACKAGE_PRE_NAME)-$${mod}; \ + dh_installdirs -p$${pkg_name} /lib/systemd/system; \ + dh_installdirs -p$${pkg_name} /usr/local/bin; \ + dh_installdirs -p$${pkg_name} etc/modules-load.d; \ + if [ -f $(MOD_SRC_DIR)/$${mod}/modules/Makefile ]; then \ + if [ -d "$(KERNEL_BUILD)" ]; then \ + dh_installdirs -p$${pkg_name} $(KERNEL_SRC)/$(INSTALL_MOD_DIR); \ + if [ -n "$$(ls -A $(MOD_SRC_DIR)/$${mod}/modules/*.ko 2>/dev/null)" ]; then \ + cp $(MOD_SRC_DIR)/$${mod}/modules/*.ko \ + debian/$${pkg_name}$(KERNEL_SRC)/$(INSTALL_MOD_DIR)/; \ + fi; \ + fi; \ + fi; \ + if [ -d $(MOD_SRC_DIR)/$${mod}/$(SERVICE_DIR) ]; then \ + if [ -n "$$(ls -A $(MOD_SRC_DIR)/$${mod}/$(SERVICE_DIR)/*.service 2>/dev/null)" ]; then \ + cp $(MOD_SRC_DIR)/$${mod}/$(SERVICE_DIR)/*.service \ + debian/$${pkg_name}/lib/systemd/system/; \ + fi; \ + fi; \ + if [ -d $(MOD_SRC_DIR)/$${mod}/$(SYSTEMD_DIR) ]; then \ + if [ -n "$$(ls -A $(MOD_SRC_DIR)/$${mod}/$(SYSTEMD_DIR)/*.service 2>/dev/null)" ]; then \ + cp $(MOD_SRC_DIR)/$${mod}/$(SYSTEMD_DIR)/*.service \ + debian/$${pkg_name}/lib/systemd/system/; \ + fi; \ + fi; \ + if [ -d $(MOD_SRC_DIR)/$${mod}/$(SCRIPTS_DIR) ]; then \ + if [ -n "$$(ls -A $(MOD_SRC_DIR)/$${mod}/$(SCRIPTS_DIR)/* 2>/dev/null)" ]; then \ + cp $(MOD_SRC_DIR)/$${mod}/$(SCRIPTS_DIR)/* \ + debian/$${pkg_name}/usr/local/bin/; \ + fi; \ + fi; \ + if [ -d $(MOD_SRC_DIR)/$${mod}/$(CFG_DIR) ]; then \ + if [ -n "$$(ls -A $(MOD_SRC_DIR)/$${mod}/$(CFG_DIR)/* 2>/dev/null)" ]; then \ + cp $(MOD_SRC_DIR)/$${mod}/$(CFG_DIR)/* \ + debian/$${pkg_name}/etc/modules-load.d/; \ + fi; \ + fi; \ + if [ -d $(MOD_SRC_DIR)/$${mod}/$(OBMC_CONSOLE_DIR) ]; then \ + dh_installdirs -p$${pkg_name} etc/obmc-console; \ + if [ -n "$$(ls -A $(MOD_SRC_DIR)/$${mod}/$(OBMC_CONSOLE_DIR)/* 2>/dev/null)" ]; then \ + cp $(MOD_SRC_DIR)/$${mod}/$(OBMC_CONSOLE_DIR)/* \ + debian/$${pkg_name}/etc/obmc-console/; \ + fi; \ + fi; \ + done) + +override_dh_install: + set -e + # Get all package names from debian/control + (for pkg_name in $$(dh_listpackages); do \ + set -e; \ + if [ "$${pkg_name}" = "$(PACKAGE_PRE_NAME)-common" ]; then \ + continue; \ + fi; \ + if [ -f sonic_platform-1.0-py3-none-any.whl ]; then \ + card_name="$${pkg_name#$(PACKAGE_PRE_NAME)-}"; \ + device_name="arm64-nexthop_$${card_name}-r0"; \ + dh_installdirs -p$${pkg_name} usr/share/sonic/device/$${device_name}/; \ + dh_install -p$${pkg_name} sonic_platform-1.0-py3-none-any.whl \ + usr/share/sonic/device/$${device_name}/; \ + fi; \ + done) + +override_dh_clean: + set -e + dh_clean + (for mod in $(MODULE_DIRS); do \ + set -e; \ + if [ -d "$(KERNEL_BUILD)" ] && [ -f "$(MOD_SRC_DIR)/$${mod}/modules/Makefile" ]; then \ + make -C $(KERNEL_BUILD) M=$(MOD_SRC_DIR)/$${mod}/modules clean; \ + fi; \ + done) + rm -f $(MOD_SRC_DIR)/*.whl + rm -rf $(MOD_SRC_DIR)/build $(MOD_SRC_DIR)/dist $(MOD_SRC_DIR)/*.egg-info + diff --git a/platform/aspeed/sonic-platform-modules-nexthop/debian/sonic-platform-aspeed-nexthop-b27.postinst b/platform/aspeed/sonic-platform-modules-nexthop/debian/sonic-platform-aspeed-nexthop-b27.postinst new file mode 100755 index 00000000000..6208596db41 --- /dev/null +++ b/platform/aspeed/sonic-platform-modules-nexthop/debian/sonic-platform-aspeed-nexthop-b27.postinst @@ -0,0 +1,22 @@ +#!/bin/bash +set -e + +case "$1" in + configure) + if [ -f /host/machine.conf ]; then + . /host/machine.conf + DEVICE_NAME="$onie_platform" + else + echo "Warning: /host/machine.conf not found, cannot determine platform" + exit 1 + fi + DEVICE_DIR="/usr/share/sonic/device/${DEVICE_NAME}" + if [ -f "${DEVICE_DIR}"/*.whl ]; then + # Force reinstall to ensure this platform's wheel is installed + pip3 install --force-reinstall --no-deps "${DEVICE_DIR}"/*.whl + fi + ;; +esac + +#DEBHELPER# +exit 0 diff --git a/platform/aspeed/sonic-platform-modules-nexthop/debian/sonic-platform-aspeed-nexthop-common.postinst b/platform/aspeed/sonic-platform-modules-nexthop/debian/sonic-platform-aspeed-nexthop-common.postinst new file mode 100755 index 00000000000..1559df584a8 --- /dev/null +++ b/platform/aspeed/sonic-platform-modules-nexthop/debian/sonic-platform-aspeed-nexthop-common.postinst @@ -0,0 +1,35 @@ +#!/bin/bash +# postinst script for sonic-platform-aspeed-nexthop-common + +set -e + +case "$1" in + configure) + # Load USB gadget modules + # These may fail during image build (wrong arch/kernel) - that's OK + # On real hardware, if they fail, the service will fail to start (visible error) + echo "Loading USB gadget kernel modules..." + + for module in libcomposite usb_f_ncm u_ether aspeed_vhub; do + if modprobe "$module" 2>/dev/null; then + echo " Loaded module: $module" + elif lsmod | grep -q "^${module} "; then + echo " Module already loaded: $module" + else + echo " WARNING: Could not load module: $module" >&2 + echo " (This is expected during image build)" >&2 + fi + done + + # Update module dependencies + depmod -a 2>/dev/null || true + + # Enable USB network service + echo "Enabling USB network service..." + systemctl enable --now usb-network-init.service 2>/dev/null || true + ;; +esac + +#DEBHELPER# + +exit 0 diff --git a/platform/aspeed/sonic-platform-modules-nexthop/setup.py b/platform/aspeed/sonic-platform-modules-nexthop/setup.py new file mode 100644 index 00000000000..f9d2f21b603 --- /dev/null +++ b/platform/aspeed/sonic-platform-modules-nexthop/setup.py @@ -0,0 +1,30 @@ +import os +from setuptools import setup + +setup( + name="sonic-platform", + version="1.0", + description="SONiC platform API implementation on NextHop Aspeed Platforms", + license="Apache 2.0", + author="Nexthop Team", + author_email="opensource@nexthop.ai", + url="https://github.com/nexthop-ai/sonic-buildimage", + packages=["sonic_platform"], + package_dir={ + "sonic_platform": "common/sonic_platform", + }, + classifiers=[ + "Development Status :: 3 - Alpha", + "Environment :: Plugins", + "Intended Audience :: Developers", + "Intended Audience :: Information Technology", + "Intended Audience :: System Administrators", + "License :: OSI Approved :: Apache Software License", + "Natural Language :: English", + "Operating System :: POSIX :: Linux", + "Programming Language :: Python :: 3.11", + "Topic :: Utilities", + ], + keywords="sonic SONiC platform PLATFORM aspeed bmc nexthop", +) + diff --git a/platform/aspeed/sonic_fit.its b/platform/aspeed/sonic_fit.its new file mode 100644 index 00000000000..c909c089ed4 --- /dev/null +++ b/platform/aspeed/sonic_fit.its @@ -0,0 +1,112 @@ +/dts-v1/; + +/* + * FIT Image Configuration for AST2700 SONiC - Multi-DTB Support + * + * This FIT image contains multiple DTBs for different hardware variants: + * - ast2700-evb: Aspeed AST2700 EVB reference platform + * - nexthop-b27-r0: NextHop AST2700 platform + * + * U-Boot selects the appropriate DTB configuration using the 'bootconf' variable + * which is set based on hardware detection at boot time. + * + * Load addresses are chosen based on AST2700 memory map: + * - RAM: 0x400000000 - 0x47e000000 (2016 MB) + * - Reserved regions: 0x41b800000-0x423ffffff, 0x42c000000-0x431bfffff + * + * Safe load addresses (avoiding reserved regions): + * - Kernel: 0x403000000 (U-Boot default loadaddr, ~10 MB) + * - Ramdisk: 0x440000000 (after reserved regions, 192 MB allocated) + * Production initramfs: 37.4 MB compressed, 160 MB uncompressed + * IMPORTANT: Kernel decompresses initramfs IN PLACE! + * - FDT: 0x44C000000 (after ramdisk: 0x440000000 + 192 MB) + * + * Memory Layout: + * 0x403000000 ──> Kernel (10 MB) + * 0x440000000 ──> Ramdisk (192 MB allocated for decompression) + * Compressed: 37.4 MB (zstd) + * Uncompressed: 160 MB (in-place decompression) + * 0x44C000000 ──> DTB (50 KB) + * + * Two-cell address format: <0xHIGH 0xLOW> + * Example: 0x44C000000 = <0x4 0x4C000000> + * + * See platform/aspeed/AST2700-MEMORY-MAP.md for details. + */ + +/ { + description = "Kernel fitImage for SONiC AST2700 with Multi-DTB Support"; + #address-cells = <2>; + + images { + kernel-1 { + description = "Linux kernel"; + data = /incbin/("__KERNEL_PATH__"); + type = "kernel"; + arch = "arm64"; + os = "linux"; + compression = "none"; + load = <0x4 0x03000000>; + entry = <0x4 0x03000000>; + hash-1 { + algo = "sha256"; + }; + }; + fdt-ast2700-evb { + description = "Flattened Device Tree blob for Aspeed AST2700 EVB"; + data = /incbin/("__DTB_PATH_ASPEED__/ast2700-evb.dtb"); + type = "flat_dt"; + arch = "arm64"; + compression = "none"; + load = <0x4 0x4C000000>; + hash-1 { + algo = "sha256"; + }; + }; + fdt-nexthop-b27-r0 { + description = "Flattened Device Tree blob for NextHop AST2700"; + data = /incbin/("__DTB_PATH_ASPEED__/nexthop-b27-r0.dtb"); + type = "flat_dt"; + arch = "arm64"; + compression = "none"; + load = <0x4 0x4C000000>; + hash-1 { + algo = "sha256"; + }; + }; + ramdisk-1 { + description = "Initramfs"; + data = /incbin/("__INITRD_PATH__"); + type = "ramdisk"; + arch = "arm64"; + os = "linux"; + compression = "none"; + load = <0x4 0x40000000>; + hash-1 { + algo = "sha256"; + }; + }; + }; + + configurations { + default = "conf-ast2700-evb"; + conf-ast2700-evb { + description = "SONiC for Aspeed AST2700 EVB"; + kernel = "kernel-1"; + fdt = "fdt-ast2700-evb"; + ramdisk = "ramdisk-1"; + hash-1 { + algo = "sha256"; + }; + }; + conf-nexthop-b27-r0 { + description = "SONiC for NextHop AST2700"; + kernel = "kernel-1"; + fdt = "fdt-nexthop-b27-r0"; + ramdisk = "ramdisk-1"; + hash-1 { + algo = "sha256"; + }; + }; + }; +}; diff --git a/platform/aspeed/systemd/sonic-machine-conf-init.service b/platform/aspeed/systemd/sonic-machine-conf-init.service new file mode 100644 index 00000000000..850fa0b58c7 --- /dev/null +++ b/platform/aspeed/systemd/sonic-machine-conf-init.service @@ -0,0 +1,16 @@ +[Unit] +Description=SONiC Machine Configuration Initialization +Documentation=man:machine.conf(5) +DefaultDependencies=no +After=local-fs.target +Before=sonic.target sonic-uboot-env-init.service rc-local.service +ConditionPathExists=!/host/machine.conf + +[Service] +Type=oneshot +ExecStart=/usr/bin/sonic-machine-conf-init.sh +RemainAfterExit=yes + +[Install] +WantedBy=multi-user.target + diff --git a/platform/aspeed/systemd/sonic-platform-init.service b/platform/aspeed/systemd/sonic-platform-init.service new file mode 100644 index 00000000000..4d77a079042 --- /dev/null +++ b/platform/aspeed/systemd/sonic-platform-init.service @@ -0,0 +1,16 @@ +[Unit] +Description=Aspeed Platform Initialization +Documentation=https://github.com/sonic-net/sonic-buildimage +DefaultDependencies=no +After=local-fs.target sonic-machine-conf-init.service +Before=watchdog-control.service determine-reboot-cause.service pmon.service +Requires=sonic-machine-conf-init.service + +[Service] +Type=oneshot +ExecStart=/usr/bin/sonic-platform-init.sh +RemainAfterExit=yes + +[Install] +WantedBy=multi-user.target + diff --git a/platform/aspeed/systemd/sonic-switchcpu-console-init.service b/platform/aspeed/systemd/sonic-switchcpu-console-init.service new file mode 100644 index 00000000000..d69a1da5a85 --- /dev/null +++ b/platform/aspeed/systemd/sonic-switchcpu-console-init.service @@ -0,0 +1,17 @@ +[Unit] +Description=Aspeed Switch CPU Console Initialization +Documentation=https://github.com/sonic-net/sonic-buildimage +After=database.service sonic-platform-init.service config-setup.service +Requires=database.service +PartOf=sonic.target + +[Service] +Type=oneshot +ExecStart=/usr/bin/sonic-switchcpu-console-init.sh +RemainAfterExit=yes +StandardOutput=journal +StandardError=journal + +[Install] +WantedBy=multi-user.target sonic.target + diff --git a/platform/aspeed/systemd/sonic-uboot-env-init.service b/platform/aspeed/systemd/sonic-uboot-env-init.service new file mode 100644 index 00000000000..a18622fefbc --- /dev/null +++ b/platform/aspeed/systemd/sonic-uboot-env-init.service @@ -0,0 +1,16 @@ +[Unit] +Description=SONiC U-Boot Environment Initialization +Documentation=man:fw_setenv(8) +DefaultDependencies=no +After=local-fs.target systemd-udev-settle.service +Wants=systemd-udev-settle.service +Before=sonic.target + +[Service] +Type=oneshot +ExecStart=/usr/bin/sonic-uboot-env-init.sh +RemainAfterExit=yes + +[Install] +WantedBy=multi-user.target + diff --git a/rules/syncd.mk b/rules/syncd.mk index cbed33fbf50..56f95d7790d 100644 --- a/rules/syncd.mk +++ b/rules/syncd.mk @@ -1,6 +1,7 @@ # only used for non-vs platforms ifneq ($(CONFIGURED_PLATFORM),vs) +ifneq ($(CONFIGURED_PLATFORM),aspeed) SYNCD = syncd_1.0.0_$(CONFIGURED_ARCH).deb $(SYNCD)_RDEPENDS += $(LIBSAIREDIS) $(LIBSAIMETADATA) @@ -38,3 +39,4 @@ ifeq ($(ENABLE_PY2_MODULES), n) endif endif +endif diff --git a/src/systemd-sonic-generator/systemd-sonic-generator.cpp b/src/systemd-sonic-generator/systemd-sonic-generator.cpp index 74662c98247..4d87ac3d211 100644 --- a/src/systemd-sonic-generator/systemd-sonic-generator.cpp +++ b/src/systemd-sonic-generator/systemd-sonic-generator.cpp @@ -119,6 +119,7 @@ static bool smart_switch_npu; static bool smart_switch_dpu; static bool smart_switch; static int num_dpus; +static bool is_bmc_device; static char* platform = NULL; static struct json_object *platform_info = NULL; @@ -458,6 +459,7 @@ static void update_environment(const std::filesystem::path& install_dir, const s std::unordered_map env_vars; env_vars["IS_DPU_DEVICE"] = (smart_switch_dpu ? "true" : "false"); env_vars["NUM_DPU"] = std::to_string(num_dpus); + env_vars["IS_BMC_DEVICE"] = (is_bmc_device ? "true" : "false"); unit_environment_file << "[Service]\n"; @@ -922,7 +924,7 @@ static bool is_smart_switch_npu() { /** * Checks if the current platform is a smart switch with a DPU (Data Processing Unit). - * + * * @return true if the platform is a smart switch with a DPU, false otherwise. */ static bool is_smart_switch_dpu() { @@ -935,6 +937,20 @@ static bool is_smart_switch_dpu() { } +/** + * Checks if the current platform is a BMC (Baseboard Management Controller) device. + * + * @return true if the platform contains "aspeed", false otherwise. + */ +static bool is_bmc_platform() { + const char* platform = get_platform(); + if (platform == NULL) { + return false; + } + return (strstr(platform, "aspeed") != NULL); +} + + /** * @brief Retrieves the number of DPUs (Data Processing Units). * @@ -1028,6 +1044,7 @@ int ssg_main(int argc, char **argv) { smart_switch_dpu = is_smart_switch_dpu(); smart_switch = smart_switch_npu || smart_switch_dpu; num_dpus = get_num_of_dpu(); + is_bmc_device = is_bmc_platform(); install_dir = std::string(argv[1]) + "/"; const char* config_file = get_config_file();