diff --git a/build_debian.sh b/build_debian.sh index 1a0bb63ddb6..5a152eb8015 100755 --- a/build_debian.sh +++ b/build_debian.sh @@ -806,33 +806,6 @@ if [[ $TARGET_BOOTLOADER == uboot ]]; then 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/platform.json b/device/aspeed/arm64-aspeed_ast2700_evb-r0/platform.json index cd233ebdd9d..719ceaaecbb 100644 --- a/device/aspeed/arm64-aspeed_ast2700_evb-r0/platform.json +++ b/device/aspeed/arm64-aspeed_ast2700_evb-r0/platform.json @@ -7,9 +7,6 @@ "colors": ["green", "amber", "off"] }, "components": [ - { - "name": "BMC" - }, { "name": "BIOS" } diff --git a/device/aspeed/arm64-aspeed_ast2700_evb-r0/platform_components.json b/device/aspeed/arm64-aspeed_ast2700_evb-r0/platform_components.json index ee3ca09448d..40cb46e42c4 100644 --- a/device/aspeed/arm64-aspeed_ast2700_evb-r0/platform_components.json +++ b/device/aspeed/arm64-aspeed_ast2700_evb-r0/platform_components.json @@ -2,7 +2,6 @@ "chassis": { "AST2700-EVB-BMC": { "component": { - "BMC": {}, "BIOS": {} } } diff --git a/device/nexthop/arm64-nexthop_b27-r0/70-usb-network.rules b/device/nexthop/arm64-nexthop_b27-r0/70-usb-network.rules new file mode 100644 index 00000000000..d5bf6cd33be --- /dev/null +++ b/device/nexthop/arm64-nexthop_b27-r0/70-usb-network.rules @@ -0,0 +1,6 @@ +# Aspeed BMC USB Network Gadget Interface Renaming Rule +# Renames USB NCM gadget interface from usb0 to bmc0 for consistent naming + +# Match USB NCM gadget interface with BMC MAC address and rename to bmc0 +SUBSYSTEM=="net", ACTION=="add", DRIVERS=="?*", ATTR{address}=="02:00:00:00:00:01", NAME="bmc0" + diff --git a/device/nexthop/arm64-nexthop_b27-r0/bmc.json b/device/nexthop/arm64-nexthop_b27-r0/bmc.json new file mode 100644 index 00000000000..93b357d16a9 --- /dev/null +++ b/device/nexthop/arm64-nexthop_b27-r0/bmc.json @@ -0,0 +1,7 @@ +{ + "usb_network": { + "interface": "bmc0", + "bmc_ipv4": "192.168.100.1/24", + "switch_host_ipv4": "192.168.100.2" + } +} diff --git a/device/nexthop/arm64-nexthop_b27-r0/nexthop_bmc_chassis_db_init.supervisord.conf b/device/nexthop/arm64-nexthop_b27-r0/nexthop_bmc_chassis_db_init.supervisord.conf new file mode 100644 index 00000000000..36450646bd5 --- /dev/null +++ b/device/nexthop/arm64-nexthop_b27-r0/nexthop_bmc_chassis_db_init.supervisord.conf @@ -0,0 +1,10 @@ +[program:nexthop_bmc_chassis_db_init] +command=/usr/local/bin/nexthop_bmc_chassis_db_init +priority=1 +autostart=true +autorestart=false +startsecs=0 +stdout_logfile=syslog +stderr_logfile=syslog +dependent_startup=true + diff --git a/device/nexthop/arm64-nexthop_b27-r0/platform.json b/device/nexthop/arm64-nexthop_b27-r0/platform.json index 1ae61b1366f..c07b0491c95 100644 --- a/device/nexthop/arm64-nexthop_b27-r0/platform.json +++ b/device/nexthop/arm64-nexthop_b27-r0/platform.json @@ -1,15 +1,12 @@ { "chassis": { - "name": "NH-B27-BMC", + "name": "Nexthop BMC Card", "thermal_manager": false, "status_led": { "controllable": true, "colors": ["green", "amber", "off"] }, "components": [ - { - "name": "BMC" - }, { "name": "BIOS" } diff --git a/device/nexthop/arm64-nexthop_b27-r0/platform_components.json b/device/nexthop/arm64-nexthop_b27-r0/platform_components.json index 969ef4fb222..1596cd59dc5 100644 --- a/device/nexthop/arm64-nexthop_b27-r0/platform_components.json +++ b/device/nexthop/arm64-nexthop_b27-r0/platform_components.json @@ -2,7 +2,6 @@ "chassis": { "NH-B27-BMC": { "component": { - "BMC": {}, "BIOS": {} } } diff --git a/device/nexthop/arm64-nexthop_b27-r0/pmon_daemon_control.json b/device/nexthop/arm64-nexthop_b27-r0/pmon_daemon_control.json index de0341b4f0a..2af1d740712 100644 --- a/device/nexthop/arm64-nexthop_b27-r0/pmon_daemon_control.json +++ b/device/nexthop/arm64-nexthop_b27-r0/pmon_daemon_control.json @@ -3,9 +3,10 @@ "skip_ledd": true, "skip_xcvrd": true, "skip_psud": true, - "skip_syseepromd": true, + "skip_syseepromd": false, "skip_pcied": true, "skip_chassisd": true, + "skip_chassis_db_init": true, "skip_fancontrol": true, "include_sensormond": false, "thermalctld": { diff --git a/dockers/docker-platform-monitor/docker_init.j2 b/dockers/docker-platform-monitor/docker_init.j2 index 11c834f782e..ec874cac052 100755 --- a/dockers/docker-platform-monitor/docker_init.j2 +++ b/dockers/docker-platform-monitor/docker_init.j2 @@ -85,6 +85,14 @@ else SONIC_PLATFORM_API_PYTHON_VERSION=3 fi +# Copy platform-specific supervisor configs if they exist (Aspeed BMC platforms) +{% if CONFIGURED_PLATFORM == "aspeed" %} +if [ -n "$(ls /usr/share/sonic/platform/*.supervisord.conf 2>/dev/null)" ]; then + echo "Installing platform-specific supervisor configs..." + cp /usr/share/sonic/platform/*.supervisord.conf /etc/supervisor/conf.d/ +fi +{% endif %} + {% if CONFIGURED_PLATFORM == "mellanox" %} # Dynamic sensors.conf setup: create a symlink from SONiC platform directory sensors.conf -> hw-management sensors.conf diff --git a/platform/aspeed/aspeed-platform-services.mk b/platform/aspeed/aspeed-platform-services.mk new file mode 100644 index 00000000000..1c9fee35552 --- /dev/null +++ b/platform/aspeed/aspeed-platform-services.mk @@ -0,0 +1,7 @@ +# Aspeed Platform Services Package +# Vendor-independent services for all Aspeed platforms +# +ASPEED_PLATFORM_SERVICES = aspeed-platform-services_1.0.0_all.deb +$(ASPEED_PLATFORM_SERVICES)_SRC_PATH = $(PLATFORM_PATH)/aspeed-platform-services +SONIC_DPKG_DEBS += $(ASPEED_PLATFORM_SERVICES) + diff --git a/platform/aspeed/aspeed-platform-services/Makefile b/platform/aspeed/aspeed-platform-services/Makefile new file mode 100644 index 00000000000..6aafc3d2c85 --- /dev/null +++ b/platform/aspeed/aspeed-platform-services/Makefile @@ -0,0 +1,8 @@ +.PHONY: clean + +clean: + rm -rf debian/aspeed-platform-services + rm -rf debian/.debhelper + rm -f debian/files + rm -f debian/*.log + rm -f debian/*.substvars diff --git a/platform/aspeed/aspeed-platform-services/debian/changelog b/platform/aspeed/aspeed-platform-services/debian/changelog new file mode 100644 index 00000000000..c9e18360017 --- /dev/null +++ b/platform/aspeed/aspeed-platform-services/debian/changelog @@ -0,0 +1,8 @@ +aspeed-platform-services (1.0.0) unstable; urgency=low + + * Initial release + * Consolidate Aspeed platform initialization services + * Add watchdog keepalive daemon (180s timeout, 60s interval) + + -- SONiC Team Mon, 16 Mar 2026 00:00:00 +0000 + diff --git a/platform/aspeed/aspeed-platform-services/debian/compat b/platform/aspeed/aspeed-platform-services/debian/compat new file mode 100644 index 00000000000..021ea30c0eb --- /dev/null +++ b/platform/aspeed/aspeed-platform-services/debian/compat @@ -0,0 +1,2 @@ +10 + diff --git a/platform/aspeed/aspeed-platform-services/debian/control b/platform/aspeed/aspeed-platform-services/debian/control new file mode 100644 index 00000000000..554b50333cc --- /dev/null +++ b/platform/aspeed/aspeed-platform-services/debian/control @@ -0,0 +1,21 @@ +Source: aspeed-platform-services +Section: misc +Priority: optional +Maintainer: SONiC Team +Build-Depends: debhelper (>= 10) +Standards-Version: 3.9.6 + +Package: aspeed-platform-services +Architecture: all +Depends: ${misc:Depends} +Description: Aspeed platform services for SONiC + This package provides platform-specific services for Aspeed-based BMC platforms: + - Machine configuration initialization + - Platform initialization + - Switch CPU console initialization + - U-Boot environment initialization + - Watchdog keepalive daemon + . + The watchdog keepalive daemon keeps the hardware watchdog armed and sends + periodic keepalive signals to prevent system resets. + diff --git a/platform/aspeed/aspeed-platform-services/debian/rules b/platform/aspeed/aspeed-platform-services/debian/rules new file mode 100755 index 00000000000..36a0021b5ee --- /dev/null +++ b/platform/aspeed/aspeed-platform-services/debian/rules @@ -0,0 +1,24 @@ +#!/usr/bin/make -f + +%: + dh $@ + +override_dh_auto_build: + # Nothing to build + +override_dh_auto_install: + # Install scripts + install -d $(CURDIR)/debian/aspeed-platform-services/usr/bin + install -m 0755 $(CURDIR)/scripts/*.sh $(CURDIR)/debian/aspeed-platform-services/usr/bin/ + + # Install systemd service files + install -d $(CURDIR)/debian/aspeed-platform-services/lib/systemd/system + install -m 0644 $(CURDIR)/systemd/*.service $(CURDIR)/debian/aspeed-platform-services/lib/systemd/system/ + +override_dh_installsystemd: + dh_installsystemd --name=sonic-machine-conf-init sonic-machine-conf-init.service + dh_installsystemd --name=sonic-platform-init sonic-platform-init.service + dh_installsystemd --name=sonic-switchcpu-console-init sonic-switchcpu-console-init.service + dh_installsystemd --name=sonic-uboot-env-init sonic-uboot-env-init.service + dh_installsystemd --name=watchdog-keepalive watchdog-keepalive.service + diff --git a/platform/aspeed/scripts/sonic-machine-conf-init.sh b/platform/aspeed/aspeed-platform-services/scripts/sonic-machine-conf-init.sh similarity index 100% rename from platform/aspeed/scripts/sonic-machine-conf-init.sh rename to platform/aspeed/aspeed-platform-services/scripts/sonic-machine-conf-init.sh diff --git a/platform/aspeed/scripts/sonic-platform-init.sh b/platform/aspeed/aspeed-platform-services/scripts/sonic-platform-init.sh similarity index 100% rename from platform/aspeed/scripts/sonic-platform-init.sh rename to platform/aspeed/aspeed-platform-services/scripts/sonic-platform-init.sh diff --git a/platform/aspeed/scripts/sonic-switchcpu-console-init.sh b/platform/aspeed/aspeed-platform-services/scripts/sonic-switchcpu-console-init.sh similarity index 100% rename from platform/aspeed/scripts/sonic-switchcpu-console-init.sh rename to platform/aspeed/aspeed-platform-services/scripts/sonic-switchcpu-console-init.sh diff --git a/platform/aspeed/scripts/sonic-uboot-env-init.sh b/platform/aspeed/aspeed-platform-services/scripts/sonic-uboot-env-init.sh similarity index 100% rename from platform/aspeed/scripts/sonic-uboot-env-init.sh rename to platform/aspeed/aspeed-platform-services/scripts/sonic-uboot-env-init.sh diff --git a/platform/aspeed/aspeed-platform-services/scripts/watchdog-keepalive.sh b/platform/aspeed/aspeed-platform-services/scripts/watchdog-keepalive.sh new file mode 100755 index 00000000000..b11b942f6e7 --- /dev/null +++ b/platform/aspeed/aspeed-platform-services/scripts/watchdog-keepalive.sh @@ -0,0 +1,80 @@ +#!/bin/bash +# +# Watchdog keepalive daemon for Aspeed platforms. +# +# This daemon keeps the hardware watchdog alive by sending periodic keepalive +# signals. It opens /dev/watchdog and writes 'w' character every 60 seconds +# (with a 180 second watchdog timeout). +# + +WATCHDOG_DEVICE="/dev/watchdog0" +KEEPALIVE_INTERVAL=60 # seconds +LOG_FILE="/var/log/aspeed-watchdog.log" +MAX_RETAINED_LOGS=5 + +# Rotate old logs +rotate_logs() { + ls -t /var/log/aspeed-watchdog*.log 2>/dev/null | cat -n | while read n f; do + if [ $n -gt $MAX_RETAINED_LOGS ]; then + rm -f "$f" + fi + done +} + +# Signal handler for graceful shutdown +cleanup() { + echo "$(date -u): Watchdog keepalive daemon stopping" >> "$LOG_FILE" + + # Disarm the watchdog using watchdogutil + if command -v watchdogutil &> /dev/null; then + watchdogutil disarm >> "$LOG_FILE" 2>&1 || { + echo "$(date -u): WARNING: Failed to disarm watchdog - system may reboot" >> "$LOG_FILE" + } + else + echo "$(date -u): WARNING: watchdogutil not found - cannot disarm watchdog" >> "$LOG_FILE" + fi + + # Close the file descriptor + exec {wdt_fd}>&- 2>/dev/null || true + + echo "$(date -u): Watchdog keepalive daemon stopped" >> "$LOG_FILE" + exit 0 +} + +trap 'cleanup' SIGTERM SIGINT + +# Rotate logs +rotate_logs + +# Start logging +echo "$(date -u): Watchdog keepalive daemon starting" > "$LOG_FILE" + +# Set watchdog interval to 180 seconds +# Set watchdog timeout to 180 seconds +if command -v watchdogutil &> /dev/null; then + watchdogutil arm -s 180 >> "$LOG_FILE" 2>&1 + echo "$(date -u): Watchdog timeout set to 180 seconds" >> "$LOG_FILE" +else + echo "$(date -u): WARNING: watchdogutil not found, using default timeout" >> "$LOG_FILE" +fi + +# Open watchdog device and assign FD to variable wdt_fd +exec {wdt_fd}>"$WATCHDOG_DEVICE" +if [ $? -ne 0 ]; then + echo "$(date -u): ERROR: Failed to open $WATCHDOG_DEVICE" >> "$LOG_FILE" + exit 1 +fi + +echo "$(date -u): Opened $WATCHDOG_DEVICE with FD $wdt_fd" >> "$LOG_FILE" + +# Send first keepalive +echo "w" >&${wdt_fd} +echo "$(date -u): First watchdog keepalive sent" >> "$LOG_FILE" + +# Main keepalive loop +while true; do + sleep $KEEPALIVE_INTERVAL + echo "w" >&${wdt_fd} + echo "$(date -u): Watchdog keepalive sent" >> "$LOG_FILE" +done + diff --git a/platform/aspeed/systemd/sonic-machine-conf-init.service b/platform/aspeed/aspeed-platform-services/systemd/sonic-machine-conf-init.service similarity index 100% rename from platform/aspeed/systemd/sonic-machine-conf-init.service rename to platform/aspeed/aspeed-platform-services/systemd/sonic-machine-conf-init.service diff --git a/platform/aspeed/systemd/sonic-platform-init.service b/platform/aspeed/aspeed-platform-services/systemd/sonic-platform-init.service similarity index 100% rename from platform/aspeed/systemd/sonic-platform-init.service rename to platform/aspeed/aspeed-platform-services/systemd/sonic-platform-init.service diff --git a/platform/aspeed/systemd/sonic-switchcpu-console-init.service b/platform/aspeed/aspeed-platform-services/systemd/sonic-switchcpu-console-init.service similarity index 100% rename from platform/aspeed/systemd/sonic-switchcpu-console-init.service rename to platform/aspeed/aspeed-platform-services/systemd/sonic-switchcpu-console-init.service diff --git a/platform/aspeed/systemd/sonic-uboot-env-init.service b/platform/aspeed/aspeed-platform-services/systemd/sonic-uboot-env-init.service similarity index 100% rename from platform/aspeed/systemd/sonic-uboot-env-init.service rename to platform/aspeed/aspeed-platform-services/systemd/sonic-uboot-env-init.service diff --git a/platform/aspeed/aspeed-platform-services/systemd/watchdog-keepalive.service b/platform/aspeed/aspeed-platform-services/systemd/watchdog-keepalive.service new file mode 100644 index 00000000000..3d1ebb4de3b --- /dev/null +++ b/platform/aspeed/aspeed-platform-services/systemd/watchdog-keepalive.service @@ -0,0 +1,17 @@ +[Unit] +Description=Watchdog Keepalive Daemon +Documentation=man:watchdog(8) +After=multi-user.target +Before=shutdown.target + +[Service] +Type=simple +ExecStart=/usr/bin/watchdog-keepalive.sh +Restart=always +RestartSec=5 +StandardOutput=journal +StandardError=journal + +[Install] +WantedBy=multi-user.target + diff --git a/platform/aspeed/build-emmc-image-installer.sh b/platform/aspeed/build-emmc-image-installer.sh index 80afa854a08..8712b3923ab 100755 --- a/platform/aspeed/build-emmc-image-installer.sh +++ b/platform/aspeed/build-emmc-image-installer.sh @@ -24,6 +24,7 @@ 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 +#EMMC_SIZE_MB=7168 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) diff --git a/platform/aspeed/install-sonic-to-emmc.sh b/platform/aspeed/install-sonic-to-emmc.sh new file mode 100755 index 00000000000..0dbb11c4518 --- /dev/null +++ b/platform/aspeed/install-sonic-to-emmc.sh @@ -0,0 +1,265 @@ +#!/bin/bash +# +# Install SONiC to eMMC from OpenBMC +# +# This script is designed to be run from a running OpenBMC instance (in SPI flash) +# to install SONiC onto the eMMC storage. +# +# Usage: ./install-sonic-to-emmc.sh +# + +set -e + +EMMC_DEVICE="/dev/mmcblk0" +EMMC_PARTITION="${EMMC_DEVICE}p1" +MOUNT_POINT="/mnt/sonic_install" +#BOOTCONF="ast2700-evb" +BOOTCONF="nexthop-b27-r0" + +# Platform-specific console settings +# AST2700 EVB uses ttyS12, AST2720 uses ttyS12 +CONSOLE_DEV="12" +CONSOLE_SPEED="115200" +EARLYCON="earlycon=uart8250,mmio32,0x14c33b00" +VAR_LOG_SIZE="512" + +# Color output +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +NC='\033[0m' # No Color + +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 if running as root +if [ "$EUID" -ne 0 ]; then + log_error "This script must be run as root" + exit 1 +fi + +# Check arguments +if [ $# -ne 1 ]; then + log_error "Usage: $0 " + exit 1 +fi + +SONIC_IMAGE="$1" + +# Verify image file exists +if [ ! -f "$SONIC_IMAGE" ]; then + log_error "Image file not found: $SONIC_IMAGE" + exit 1 +fi + +log_info "SONiC eMMC Installation Script" +log_info "================================" +log_info "Image: $SONIC_IMAGE" +log_info "Target: $EMMC_DEVICE" +log_info "" + +# Verify eMMC device exists +if [ ! -b "$EMMC_DEVICE" ]; then + log_error "eMMC device not found: $EMMC_DEVICE" + exit 1 +fi + +log_info "eMMC device found: $EMMC_DEVICE" + +# Get eMMC size +EMMC_SIZE=$(blockdev --getsize64 $EMMC_DEVICE 2>/dev/null || echo "0") +EMMC_SIZE_GB=$((EMMC_SIZE / 1024 / 1024 / 1024)) +log_info "eMMC size: ${EMMC_SIZE_GB}GB" + +# Confirm installation +log_warn "WARNING: This will ERASE all data on $EMMC_DEVICE!" +read -p "Are you sure you want to continue? (yes/no): " CONFIRM +if [ "$CONFIRM" != "yes" ]; then + log_info "Installation cancelled" + exit 0 +fi + +# Unmount any existing partitions +log_info "Unmounting any existing eMMC partitions..." +umount ${EMMC_DEVICE}p* 2>/dev/null || true + +# Write image to eMMC +log_info "Writing SONiC image to eMMC (this may take several minutes)..." +log_info "Command: gunzip -c $SONIC_IMAGE | dd of=$EMMC_DEVICE bs=4M" + +if ! gunzip -c "$SONIC_IMAGE" | dd of="$EMMC_DEVICE" bs=4M; then + log_error "Failed to write image to eMMC" + exit 1 +fi + +log_info "Syncing writes to disk..." +sync +sleep 2 + +# Verify partition exists +if [ ! -b "$EMMC_PARTITION" ]; then + log_error "Partition not found after writing: $EMMC_PARTITION" + exit 1 +fi + +# Mount the SONiC partition +log_info "Mounting SONiC partition..." +mkdir -p "$MOUNT_POINT" +if ! mount "$EMMC_PARTITION" "$MOUNT_POINT"; then + log_error "Failed to mount $EMMC_PARTITION" + exit 1 +fi + +# Get SONiC version from image directory +log_info "Detecting SONiC version..." +SONIC_IMAGE_DIR=$(ls -1 "$MOUNT_POINT" | grep "^image-" | head -n 1) + +if [ -z "$SONIC_IMAGE_DIR" ]; then + log_error "No SONiC image directory found in $MOUNT_POINT" + umount "$MOUNT_POINT" + exit 1 +fi + +# Extract version (remove 'image-' prefix) +IMAGE_DIR="$SONIC_IMAGE_DIR" +SONIC_VERSION="${SONIC_IMAGE_DIR#image-}" + +log_info "Detected image directory: $IMAGE_DIR" +log_info "Detected SONiC version: $SONIC_VERSION" + +# Get partition UUID +log_info "Getting partition UUID..." +UUID=$(blkid "$EMMC_PARTITION" | sed -n 's/.*UUID="\([^"]*\)".*/\1/p') + +if [ -z "$UUID" ]; then + log_error "Failed to get partition UUID" + umount "$MOUNT_POINT" + exit 1 +fi + +log_info "Partition UUID: $UUID" + +# Unmount +log_info "Unmounting SONiC partition..." +umount "$MOUNT_POINT" +rmdir "$MOUNT_POINT" + +# Configure U-Boot environment +log_info "Configuring U-Boot environment for dual boot..." + +# Construct console port +CONSOLE_PORT="ttyS${CONSOLE_DEV}" + +# Construct kernel command line arguments (linuxargs) +LINUXARGS="console=${CONSOLE_PORT},${CONSOLE_SPEED}n8 ${EARLYCON} loopfstype=squashfs loop=${IMAGE_DIR}/fs.squashfs varlog_size=${VAR_LOG_SIZE}" + +# FIT image path +FIT_NAME="${IMAGE_DIR}/boot/sonic_arm64.fit" + +# Partition number (typically 1 for single partition setup) +DEMO_PART="1" + +# Disk interface (mmc for eMMC) +DISK_INTERFACE="mmc" + +log_info "Setting U-Boot variables..." + +# Image configuration (slot 1 - current image) +fw_setenv image_dir "$IMAGE_DIR" || { log_error "Failed to set image_dir"; exit 1; } +fw_setenv fit_name "$FIT_NAME" || { log_error "Failed to set fit_name"; exit 1; } +fw_setenv sonic_version_1 "$SONIC_VERSION" || { log_error "Failed to set sonic_version_1"; exit 1; } + +# Old/backup image (slot 2 - empty for first installation) +fw_setenv image_dir_old "" || { log_error "Failed to set image_dir_old"; exit 1; } +fw_setenv fit_name_old "" || { log_error "Failed to set fit_name_old"; exit 1; } +fw_setenv sonic_version_2 "None" || { log_error "Failed to set sonic_version_2"; exit 1; } +fw_setenv linuxargs_old "" || { log_error "Failed to set linuxargs_old"; exit 1; } + +# Kernel command line arguments +fw_setenv linuxargs "$LINUXARGS" || { log_error "Failed to set linuxargs"; exit 1; } + +# Boot commands +fw_setenv sonic_boot_load "ext4load ${DISK_INTERFACE} 0:${DEMO_PART} \${loadaddr} \${fit_name}" || { log_error "Failed to set sonic_boot_load"; exit 1; } +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"; exit 1; } +fw_setenv sonic_bootargs "setenv bootargs root=UUID=${UUID} rw rootwait panic=1 \${linuxargs}" || { log_error "Failed to set sonic_bootargs"; exit 1; } +fw_setenv sonic_bootargs_old "setenv bootargs root=UUID=${UUID} rw rootwait panic=1 \${linuxargs_old}" || { log_error "Failed to set sonic_bootargs_old"; exit 1; } +fw_setenv sonic_image_1 "run sonic_bootargs; run sonic_boot_load; bootm \${loadaddr}#conf-\${bootconf}" || { log_error "Failed to set sonic_image_1"; exit 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"; exit 1; } + +# 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"; exit 1; } + +# Boot configuration +fw_setenv boot_next "run sonic_image_1" || { log_error "Failed to set boot_next"; exit 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" || { log_error "Failed to set bootcmd"; exit 1; } + +# Set bootconf (platform identifier) +fw_setenv bootconf "$BOOTCONF" || { log_error "Failed to set bootconf"; exit 1; } + +# Memory addresses (from platform_arm64.conf) +fw_setenv loadaddr "0x432000000" || { log_error "Failed to set loadaddr"; exit 1; } +fw_setenv kernel_addr "0x403000000" || { log_error "Failed to set kernel_addr"; exit 1; } +fw_setenv fdt_addr "0x44C000000" || { log_error "Failed to set fdt_addr"; exit 1; } +fw_setenv initrd_addr "0x440000000" || { log_error "Failed to set initrd_addr"; exit 1; } + +log_info "✓ All U-Boot variables set successfully" + +# Verify U-Boot environment +log_info "" +log_info "Verifying U-Boot environment..." +log_info "================================" + +VERIFY_FAILED=0 + +# Verify critical variables +for var in image_dir fit_name sonic_version_1 linuxargs bootconf sonic_image_1 boot_next bootcmd; do + VALUE=$(fw_printenv -n $var 2>/dev/null || echo "") + if [ -z "$VALUE" ]; then + log_error "Variable '$var' is not set!" + VERIFY_FAILED=1 + else + log_info "✓ $var: ${VALUE:0:60}$([ ${#VALUE} -gt 60 ] && echo '...')" + fi +done + +log_info "" + +if [ $VERIFY_FAILED -eq 1 ]; then + log_error "U-Boot environment verification failed!" + exit 1 +fi + +# Installation complete +log_info "================================" +log_info "SONiC installation completed successfully!" +log_info "" +log_info "Summary:" +log_info " - SONiC version: $SONIC_VERSION" +log_info " - Image directory: $IMAGE_DIR" +log_info " - Partition: ${EMMC_PARTITION} (UUID: $UUID)" +log_info " - Boot configuration: $BOOTCONF" +log_info " - Console: $CONSOLE_PORT @ ${CONSOLE_SPEED}n8" +log_info " - Default boot: sonic_image_1" +log_info "" +log_info "Boot configuration:" +log_info " - Image 1 (sonic_version_1): $SONIC_VERSION" +log_info " - Image 2 (sonic_version_2): None" +log_info "" +log_info "Next steps:" +log_info " 1. Reboot the system: 'reboot'" +log_info " 2. U-Boot will boot SONiC from eMMC" +log_info " 3. After boot, verify with: 'show version'" +log_info "" +log_warn "Note: Ensure U-Boot is configured to boot from eMMC!" +log_info "" + +exit 0 diff --git a/platform/aspeed/one-image.mk b/platform/aspeed/one-image.mk index 45c3b18561e..984cf9d02bf 100644 --- a/platform/aspeed/one-image.mk +++ b/platform/aspeed/one-image.mk @@ -16,6 +16,7 @@ $(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)_INSTALLS += $(ASPEED_PLATFORM_SERVICES) $(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) diff --git a/platform/aspeed/rules.mk b/platform/aspeed/rules.mk index 0fb44e3b5da..f03b44e9963 100644 --- a/platform/aspeed/rules.mk +++ b/platform/aspeed/rules.mk @@ -1,5 +1,6 @@ include $(PLATFORM_PATH)/platform-modules-ast-evb.mk include $(PLATFORM_PATH)/platform-modules-nexthop.mk +include $(PLATFORM_PATH)/aspeed-platform-services.mk include $(PLATFORM_PATH)/one-image.mk SONIC_ALL += $(SONIC_ONE_IMAGE) 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 deleted file mode 100644 index b14524de5a7..00000000000 --- a/platform/aspeed/sonic-platform-modules-ast-evb/ast2700/obmc-console/server.tty.conf +++ /dev/null @@ -1,22 +0,0 @@ -# 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 deleted file mode 100644 index 1f2766aba8f..00000000000 --- a/platform/aspeed/sonic-platform-modules-ast-evb/ast2700/setup.py +++ /dev/null @@ -1,31 +0,0 @@ -#!/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/chassis.py b/platform/aspeed/sonic-platform-modules-ast-evb/ast2700/sonic_platform/chassis.py index 7a5fd3790f3..a558b7a9911 100644 --- 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 @@ -133,11 +133,21 @@ def get_reboot_cause(self): 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) + # CARDRESET can indicate either: + # 1. Watchdog timeout reset (no software reboot cause file) + # 2. Normal software reboot (software reboot cause file exists) + # Check if software reboot cause exists + try: + with open('/host/reboot-cause/reboot-cause.txt', 'r') as f: + software_cause = f.read().strip() + if software_cause and not software_cause.startswith('Unknown'): + # Software initiated reboot + return (self.REBOOT_CAUSE_NON_HARDWARE, None) + except (IOError, OSError): + pass + + # No software reboot cause found - assume watchdog timeout + return (self.REBOOT_CAUSE_WATCHDOG, "Watchdog timeout reset") if bootstatus & (WDIOF_EXTERN1 | WDIOF_EXTERN2): return (self.REBOOT_CAUSE_HARDWARE_OTHER, f"External Reset (bootstatus=0x{bootstatus:x})") 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 index 3f9a8248c76..d24e29733e6 100644 --- 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 @@ -53,44 +53,14 @@ def __init__(self): 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 """ @@ -99,64 +69,74 @@ def _read_sysfs_int(self, filename): return int(f.read().strip()) except (IOError, OSError, ValueError): return -1 - - def arm(self, seconds): + + def _read_sysfs_str(self, filename): """ - Arm the hardware watchdog with a timeout of seconds - + Read a string value from sysfs + Args: - seconds: Timeout value in seconds - + filename: Name of the file in WATCHDOG_SYSFS_PATH + Returns: - An integer specifying the actual number of seconds the watchdog - was armed with. On failure returns -1. + String value, or empty string on error """ - 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 + with open(os.path.join(WATCHDOG_SYSFS_PATH, filename), 'r') as f: + return f.read().strip() except (IOError, OSError): - return -1 - - def disarm(self): + return "" + + def _open_watchdog(self): """ - Disarm the hardware watchdog + Open the watchdog device file Returns: - A boolean, True if watchdog is disarmed successfully, False if not + True if successful, False otherwise """ - if not self._open_watchdog(): - return False + if self.watchdog_fd is None: + try: + self.watchdog_fd = os.open(self.watchdog_device, os.O_WRONLY) + except (IOError, OSError): + return False + return True - try: - # Disable watchdog - options = array.array('h', [WDIOS_DISABLECARD]) - fcntl.ioctl(self.watchdog_fd, WDIOC_SETOPTIONS, options, False) + def _disablewatchdog(self): + """ + Turn off the watchdog timer + """ + req = array.array('I', [WDIOS_DISABLECARD]) + fcntl.ioctl(self.watchdog_fd, WDIOC_SETOPTIONS, req, False) - self.armed = False - self.timeout = 0 + def _enablewatchdog(self): + """ + Turn on the watchdog timer + """ + req = array.array('I', [WDIOS_ENABLECARD]) + fcntl.ioctl(self.watchdog_fd, WDIOC_SETOPTIONS, req, False) - # Close the watchdog device properly - self._close_watchdog() + def _keepalive(self): + """ + Keep alive watchdog timer + """ + fcntl.ioctl(self.watchdog_fd, WDIOC_KEEPALIVE) - return True - except (IOError, OSError): - return False + def _settimeout(self, seconds): + """ + Set watchdog timer timeout + @param seconds - timeout in seconds + @return is the actual set timeout + """ + req = array.array('I', [seconds]) + fcntl.ioctl(self.watchdog_fd, WDIOC_SETTIMEOUT, req, True) + + return int(req[0]) + + def _gettimeout(self): + """ + Get watchdog timeout + @return watchdog timeout + """ + return self._read_sysfs_int("timeout") def is_armed(self): """ @@ -167,54 +147,65 @@ def is_armed(self): """ # Check the state from sysfs state = self._read_sysfs_str("state") - if state == "active": - self.armed = True + if (state != 'inactive'): return True else: - self.armed = False return False - def get_remaining_time(self): + def arm(self, seconds): """ - Get the number of seconds remaining on the watchdog timer + Arm the hardware watchdog with a timeout of seconds + + Args: + seconds: Timeout value in seconds Returns: - An integer specifying the number of seconds remaining on the - watchdog timer. If the watchdog is not armed, returns -1. + An integer specifying the actual number of seconds the watchdog + was armed with. On failure returns -1. """ - if not self.is_armed(): + if (seconds < 0 or seconds > 300 ): 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]) + if self.timeout != seconds: + self.timeout = self._settimeout(seconds) + if self.is_armed(): + self._keepalive() + else: + self._enablewatchdog() except (IOError, OSError): - # If ioctl fails, return the configured timeout as an estimate - return self.timeout if self.armed else -1 + return -1 - def _read_sysfs_str(self, filename): - """ - Read a string value from sysfs + return self.timeout - Args: - filename: Name of the file in WATCHDOG_SYSFS_PATH + def disarm(self): + """ + Disarm the hardware watchdog Returns: - String value, or empty string on error + A boolean, True if watchdog is disarmed successfully, False if not """ + if not self._open_watchdog(): + return False + try: - with open(os.path.join(WATCHDOG_SYSFS_PATH, filename), 'r') as f: - return f.read().strip() + self._disablewatchdog() + self.timeout = 0 + except (IOError, OSError): - return "" + return False + + return True + + 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. + """ + return -1 diff --git a/platform/aspeed/sonic-platform-modules-ast-evb/debian/rules b/platform/aspeed/sonic-platform-modules-ast-evb/debian/rules index 311ef608ff4..37d206906ef 100755 --- a/platform/aspeed/sonic-platform-modules-ast-evb/debian/rules +++ b/platform/aspeed/sonic-platform-modules-ast-evb/debian/rules @@ -16,9 +16,7 @@ override_dh_auto_install: 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: 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 deleted file mode 100644 index f43bdcf569d..00000000000 --- a/platform/aspeed/sonic-platform-modules-nexthop/b27/obmc-console/server.tty.conf +++ /dev/null @@ -1,18 +0,0 @@ -# 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 old mode 100644 new mode 100755 index 1467c77fe65..e64ae0bdb81 --- 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 @@ -1,16 +1,12 @@ #!/bin/bash # Switch CPU Utility Script for ASPEED AST2700 BMC # Provides utilities to manage the switch CPU (x86) from BMC +# Uses the SwitchHostModule Python platform API # # 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" @@ -23,115 +19,105 @@ usage() { Usage: ${SCRIPT_NAME} [options] Switch CPU management utilities for ASPEED AST2700 BMC. +This script uses the SwitchHostModule platform API. 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 + power-on Power on switch CPU (bring out of reset) + power-off Power off switch CPU (put into reset) + power-cycle Power cycle switch CPU + status Show switch CPU operational status help Show this help message Options: -h, --help Show this help message Examples: - ${SCRIPT_NAME} reset-out - ${SCRIPT_NAME} reset-cycle + ${SCRIPT_NAME} power-on + ${SCRIPT_NAME} power-cycle ${SCRIPT_NAME} status EOF } ####################################### -# Bring switch CPU out of reset +# Execute Python platform API command ####################################### -bring_switch_cpu_out_of_reset() { - logger -t ${LOG_TAG} "Bringing switch CPU out of reset..." +execute_python_command() { + local command="$1" - # 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 -} + python3 << EOF +import sys -####################################### -# Put switch CPU into reset -####################################### -put_switch_cpu_into_reset() { - logger -t ${LOG_TAG} "Putting switch CPU into reset..." +try: + from sonic_platform.switch_host_module import SwitchHostModule - # 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)..." + # Create module instance + module = SwitchHostModule(module_index=0) - # Put into reset - if ! put_switch_cpu_into_reset; then - return 1 - fi + # Execute command + if "${command}" == "power-on": + result = module.set_admin_state(True) + if result: + print("Switch CPU powered on successfully") + sys.exit(0) + else: + print("ERROR: Failed to power on switch CPU", file=sys.stderr) + sys.exit(1) - # Wait for reset to take effect - echo "Waiting 2 seconds..." - sleep 2 + elif "${command}" == "power-off": + result = module.set_admin_state(False) + if result: + print("Switch CPU powered off successfully") + sys.exit(0) + else: + print("ERROR: Failed to power off switch CPU", file=sys.stderr) + sys.exit(1) - # Bring out of reset - if ! bring_switch_cpu_out_of_reset; then - return 1 - fi + elif "${command}" == "power-cycle": + result = module.do_power_cycle() + if result: + print("Switch CPU power cycle completed successfully") + sys.exit(0) + else: + print("ERROR: Failed to power cycle switch CPU", file=sys.stderr) + sys.exit(1) - 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..." + elif "${command}" == "status": + status = module.get_oper_status() + name = module.get_name() + desc = module.get_description() + + print("Switch CPU Status:") + print(f" Name: {name}") + print(f" Description: {desc}") + print(f" Operational State: {status}") + + # Additional human-readable interpretation + if status == module.MODULE_STATUS_ONLINE: + print(" Interpretation: CPU is OUT OF RESET (running)") + elif status == module.MODULE_STATUS_OFFLINE: + print(" Interpretation: CPU is IN RESET (held in reset)") + elif status == module.MODULE_STATUS_FAULT: + print(" Interpretation: ERROR reading CPU status") + else: + print(f" Interpretation: Unknown status ({status})") + + sys.exit(0) - # 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 + else: + print(f"ERROR: Unknown command '{command}'", file=sys.stderr) + sys.exit(1) + +except ImportError as e: + print(f"ERROR: Failed to import SwitchHostModule: {e}", file=sys.stderr) + print("Make sure the platform module is installed correctly", file=sys.stderr) + sys.exit(1) +except Exception as e: + print(f"ERROR: {e}", file=sys.stderr) + sys.exit(1) +EOF - return 0 + return $? } ####################################### @@ -151,18 +137,39 @@ main() { shift case "${COMMAND}" in + power-on) + logger -t ${LOG_TAG} "Executing power-on command via SwitchHostModule" + execute_python_command "power-on" + ;; + power-off) + logger -t ${LOG_TAG} "Executing power-off command via SwitchHostModule" + execute_python_command "power-off" + ;; + power-cycle) + logger -t ${LOG_TAG} "Executing power-cycle command via SwitchHostModule" + execute_python_command "power-cycle" + ;; + status) + execute_python_command "status" + ;; + + # Legacy command compatibility (optional - map old names to new) reset-out) - bring_switch_cpu_out_of_reset + echo "NOTE: 'reset-out' is deprecated, use 'power-on' instead" >&2 + logger -t ${LOG_TAG} "Executing power-on command (via legacy reset-out)" + execute_python_command "power-on" ;; reset-in) - put_switch_cpu_into_reset + echo "NOTE: 'reset-in' is deprecated, use 'power-off' instead" >&2 + logger -t ${LOG_TAG} "Executing power-off command (via legacy reset-in)" + execute_python_command "power-off" ;; reset-cycle) - cycle_switch_cpu_reset - ;; - status) - show_switch_cpu_status + echo "NOTE: 'reset-cycle' is deprecated, use 'power-cycle' instead" >&2 + logger -t ${LOG_TAG} "Executing power-cycle command (via legacy reset-cycle)" + execute_python_command "power-cycle" ;; + help|--help|-h) usage exit 0 @@ -174,10 +181,8 @@ main() { exit 1 ;; esac - exit $? } # Run main function main "$@" - diff --git a/platform/aspeed/sonic-platform-modules-nexthop/common/scripts/nexthop_bmc_chassis_db_init b/platform/aspeed/sonic-platform-modules-nexthop/common/scripts/nexthop_bmc_chassis_db_init new file mode 100755 index 00000000000..36df311289c --- /dev/null +++ b/platform/aspeed/sonic-platform-modules-nexthop/common/scripts/nexthop_bmc_chassis_db_init @@ -0,0 +1,52 @@ +#!/usr/bin/env python3 +""" +Nexthop BMC-specific chassis_db_init +Populates both BMC Serial (from BMC EEPROM) and Switch-Host Serial (from switch card EEPROM) +""" + +import sys +from sonic_py_common import daemon_base +from swsscommon import swsscommon + +CHASSIS_INFO_TABLE = 'CHASSIS_INFO' +CHASSIS_INFO_KEY_TEMPLATE = 'chassis {}' +CHASSIS_INFO_SERIAL_FIELD = 'serial' +CHASSIS_INFO_MODEL_FIELD = 'model' +CHASSIS_INFO_REV_FIELD = 'revision' +CHASSIS_INFO_SWITCH_HOST_SERIAL_FIELD = 'switch_host_serial' # NEW + +def try_get(callback, default='N/A'): + try: + ret = callback() + if ret is None: + ret = default + except NotImplementedError: + ret = default + except Exception: + ret = default + return ret + +def main(): + state_db = daemon_base.db_connect("STATE_DB") + chassis_tbl = swsscommon.Table(state_db, CHASSIS_INFO_TABLE) + + try: + import sonic_platform + chassis = sonic_platform.platform.Platform().get_chassis() + except Exception as e: + print(f"Failed to load chassis: {e}", file=sys.stderr) + return 1 + + # Populate with BMC serial (standard) and switch-host serial (custom) + fvs = swsscommon.FieldValuePairs([ + (CHASSIS_INFO_SERIAL_FIELD, try_get(chassis.get_serial_number)), # BMC serial from i2c-4 + (CHASSIS_INFO_MODEL_FIELD, try_get(chassis.get_model)), + (CHASSIS_INFO_REV_FIELD, try_get(chassis.get_revision)), + (CHASSIS_INFO_SWITCH_HOST_SERIAL_FIELD, try_get(chassis.get_switch_host_serial_number)) # System serial from i2c-10 + ]) + + chassis_tbl.set(CHASSIS_INFO_KEY_TEMPLATE.format(1), fvs) + return 0 + +if __name__ == '__main__': + sys.exit(main()) diff --git a/platform/aspeed/sonic-platform-modules-nexthop/common/scripts/sonic-usb-network-udev-init.sh b/platform/aspeed/sonic-platform-modules-nexthop/common/scripts/sonic-usb-network-udev-init.sh new file mode 100755 index 00000000000..535fad211dc --- /dev/null +++ b/platform/aspeed/sonic-platform-modules-nexthop/common/scripts/sonic-usb-network-udev-init.sh @@ -0,0 +1,46 @@ +#!/bin/bash +# Aspeed USB Network Gadget udev Rules Initialization Script +# This script installs udev rules to rename USB network interface from usb0 to bmc0 + +set -e + +LOG_FILE="/var/log/nexthop-usb-network-udev-init.log" +PLATFORM_SYMLINK="/usr/share/sonic/platform" +RULES_SRC="${PLATFORM_SYMLINK}/70-usb-network.rules" +RULES_DEST="/etc/udev/rules.d/70-usb-network.rules" + +log() { + echo "[$(date '+%Y-%m-%d %H:%M:%S')] $*" | tee -a "$LOG_FILE" +} + +log "Starting USB network udev rules initialization..." + +# Check if rules source exists +if [ ! -f "$RULES_SRC" ]; then + log "ERROR: USB network udev rules not found at $RULES_SRC" + exit 1 +fi + +# Install udev rules +log "Installing USB network udev rules..." +cp "$RULES_SRC" "$RULES_DEST" +log "Installed: $RULES_DEST" + +# Reload udev to activate the rules +log "Reloading udev rules..." +udevadm control --reload-rules +udevadm trigger --subsystem-match=net +log "Udev rules reloaded and triggered" + +# Check if interface was renamed (if it already exists) +if ip link show bmc0 &>/dev/null; then + log "USB network interface 'bmc0' found" +elif ip link show usb0 &>/dev/null; then + log "WARNING: Interface 'usb0' still exists, rename may apply on next interface creation" +else + log "No USB network interface found yet (will be created by usb-network-init.sh)" +fi + +log "USB network udev rules initialization complete" +exit 0 + 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 index 986fcb9fa04..b426a2a6d25 100755 --- 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 @@ -7,7 +7,7 @@ set -e # Configuration GADGET_NAME="g1" FUNCTION_TYPE="ncm" -INTERFACE_NAME="usb0" +INTERFACE_NAME="bmc0" VENDOR_ID="0x1d6b" # Linux Foundation PRODUCT_ID="0x0104" # Multifunction Composite Gadget SERIAL_NUMBER="0123456789" @@ -71,8 +71,8 @@ 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) +# Use the specific UDC port for AST2700 USB virtual hub +UDC_NAME="12021000.usb-vhub:p1" if [ -z "${UDC_NAME}" ]; then logger -t usb-network "ERROR: No UDC (USB Device Controller) found!" @@ -109,6 +109,39 @@ 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 +# Step 6b: Add static IPv4 for gNOI communication with switch host +# Read configuration from bmc.json +BMC_CONFIG="/usr/share/sonic/platform/bmc.json" +BMC_IPV4="" + +if [ -f "${BMC_CONFIG}" ]; then + logger -t usb-network "Reading BMC configuration from ${BMC_CONFIG}" + BMC_IPV4=$(python3 -c " +import json +import sys +try: + with open('${BMC_CONFIG}') as f: + data = json.load(f) + bmc_ip = data.get('usb_network', {}).get('bmc_ipv4', '') + if bmc_ip: + print(bmc_ip) + else: + sys.exit(1) +except Exception as e: + print(f'Error reading BMC config: {e}', file=sys.stderr) + sys.exit(1) +" 2>/dev/null) +fi + +if [ -n "${BMC_IPV4}" ]; then + # Configure IPv4 address + ip addr add "${BMC_IPV4}" dev "${INTERFACE_NAME}" + + # Display configuration + IPV4_ADDR=$(ip -4 addr show dev "${INTERFACE_NAME}" | grep -oP 'inet \K[0-9.]+') + logger -t usb-network "IPv4 address configured: ${IPV4_ADDR}" +fi + # Wait a moment for interface to stabilize sleep 1 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 index e44cf7cc662..a790bc7b299 100644 --- a/platform/aspeed/sonic-platform-modules-nexthop/common/sonic_platform/chassis.py +++ b/platform/aspeed/sonic-platform-modules-nexthop/common/sonic_platform/chassis.py @@ -9,6 +9,8 @@ from sonic_platform_base.chassis_base import ChassisBase from sonic_platform.thermal import Thermal from sonic_platform.watchdog import Watchdog + from sonic_platform.eeprom import Eeprom + from sonic_platform.switch_host_module import SwitchHostModule except ImportError as e: raise ImportError(str(e) + " - required module not found") @@ -56,6 +58,14 @@ def __init__(self): # Initialize watchdog (same as base class) self._watchdog = Watchdog() + # Initialize eeprom + self._eeprom = Eeprom() + + # Initialize Switch Host Module (x86 CPU managed by BMC) + self._module_list = [] + switch_host = SwitchHostModule(module_index=0) + self._module_list.append(switch_host) + # NextHop has NO fans - create empty lists self._fan_list = [] self._fan_drawer_list = [] @@ -69,13 +79,10 @@ def __init__(self): 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 @@ -92,11 +99,11 @@ def _read_watchdog_bootstatus(self, path): 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 @@ -115,32 +122,42 @@ def get_reboot_cause(self): """ # 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) - + # CARDRESET can indicate either: + # 1. Watchdog timeout reset (no software reboot cause file) + # 2. Normal software reboot (software reboot cause file exists) + # Check if software reboot cause exists + try: + with open('/host/reboot-cause/reboot-cause.txt', 'r') as f: + software_cause = f.read().strip() + if software_cause and not software_cause.startswith('Unknown'): + # Software initiated reboot + return (self.REBOOT_CAUSE_NON_HARDWARE, None) + except (IOError, OSError): + pass + + # No software reboot cause found - assume watchdog timeout + return (self.REBOOT_CAUSE_WATCHDOG, "Watchdog timeout reset") + 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 @@ -148,24 +165,57 @@ def get_reboot_cause(self): else: # Unknown bootstatus bits return (self.REBOOT_CAUSE_HARDWARE_OTHER, f"Unknown (bootstatus=0x{bootstatus:x})") + + def get_all_modules(self): + """ + Retrieves all modules available on this chassis + + Returns: + A list of Module objects representing all modules on the chassis + """ + return self._module_list def get_name(self): """ Retrieves the name of the chassis - + Returns: String containing the name of the chassis """ - return "Nexthop B27" + return "Nexthop BMC Card" 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" + return self._eeprom.modelstr() + + def get_revision(self): + """ + Retrieves the hardware revision of the device + Returns: + string: Label Revision value of device + """ + return self._eeprom.label_revision_str() + + def get_serial_number(self): + """ + Returns the BMC card's serial number from BMC EEPROM + + Returns: + string: BMC serial number from BMC EEPROM (i2c-4) + """ + if self._eeprom: + try: + e = self._eeprom.read_eeprom() + bmc_sn = self._eeprom.serial_number_str(e) + return bmc_sn if bmc_sn else "N/A" + except Exception: + pass + return "N/A" def get_serial(self): """ @@ -174,7 +224,18 @@ def get_serial(self): Returns: String containing the serial number of the chassis """ - return "N/A" + return self.get_serial_number() + + def get_switch_host_serial_number(self): + """ + Returns the switch/host system serial number (from switch card EEPROM). + This is the primary system/chassis identifier. + + Returns: + string: System serial number from switch card EEPROM (i2c-10) + """ + system_sn = self._eeprom.get_system_serial_number() + return system_sn if system_sn else "N/A" def get_watchdog(self): """ @@ -254,14 +315,3 @@ def get_fan(self, index): 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/eeprom.py b/platform/aspeed/sonic-platform-modules-nexthop/common/sonic_platform/eeprom.py new file mode 100644 index 00000000000..2e47cb7b34f --- /dev/null +++ b/platform/aspeed/sonic-platform-modules-nexthop/common/sonic_platform/eeprom.py @@ -0,0 +1,135 @@ +from __future__ import annotations + +import os +import time +from sonic_platform import eeprom_utils + +BMC_EEPROM_PATH = "/sys/bus/i2c/devices/4-0050/eeprom" + +class Eeprom(eeprom_utils.Eeprom): + """ + BMC SONiC EEPROM handler for the BMC IDPROM on Chipmunk. + + - Uses Nexthop Eeprom subclass so Vendor Extension TLVs + (IANA 63074 + custom field codes) map to friendly names like + "Switch Host Serial Number". + """ + + def __init__(self): + # Nexthop Eeprom ctor matches TlvInfoDecoder(path, start, status, ro) + if not os.path.exists(BMC_EEPROM_PATH): + raise RuntimeError(f"EEPROM device not found at {BMC_EEPROM_PATH}") + super(Eeprom, self).__init__(BMC_EEPROM_PATH, start=0, status="", ro=True) + + def get_eeprom(self): + """ + Read EEPROM, update Redis, and return raw bytes. + syseepromd calls this. + """ + e = self.read_eeprom() + # Populate STATE_DB: EEPROM_INFO|* keys + self.update_eeprom_db(e) + return e + + def read_eeprom(self): + # Just delegate to base class + return super(Eeprom, self).read_eeprom() + + def get_system_serial_number(self, + chip: str = "24c64", + instantiate_timeout_sec: float = 1.0, + ) -> str | None: + """ + Read the system/chassis serial number from the switch card EEPROM. + + Steps: + - If SWITCH_CARD_EEPROM_PATH exists, read it + - Otherwise: + - echo " 0x50" > /sys/bus/i2c/devices/i2c-10/new_device + - wait for SWITCH_CARD_EEPROM_PATH to appear + - read it + - echo "10-0050" > /sys/bus/i2c/devices/delete_device + + On Nexthop switch cards, TLV 0x23 ("Serial Number") is the *system* serial. + + Returns: + Serial string if found, else None. + """ + SWITCH_CARD_EEPROM_I2C_PATH = "/sys/bus/i2c/devices/i2c-10" + SWITCH_CARD_EEPROM_PATH = "/sys/bus/i2c/devices/10-0050/eeprom" + created = False + + # Helper: instantiate device if missing + def ensure_device(): + nonlocal created + if os.path.exists(SWITCH_CARD_EEPROM_PATH): + return True + + new_dev_path = SWITCH_CARD_EEPROM_I2C_PATH + "/new_device" + if not os.path.exists(new_dev_path): + return False + + try: + with open(new_dev_path, "w") as f: + f.write(f"{chip} 0x50\n") + created = True + except OSError: + return False + + # Poll for eeprom node to appear + deadline = time.time() + instantiate_timeout_sec + while time.time() < deadline: + if os.path.exists(SWITCH_CARD_EEPROM_PATH): + return True + time.sleep(0.05) + + return os.path.exists(SWITCH_CARD_EEPROM_PATH) + + # Helper: best-effort cleanup if we created the device + def cleanup(): + if not created: + return + delete_path_bus = SWITCH_CARD_EEPROM_I2C_PATH + "/delete_device" + if not os.path.exists(delete_path_bus): + return + try: + with open(delete_path_bus, "w") as f: + f.write("10-0050\n") + except OSError: + # best effort only + pass + + if not ensure_device(): + return None + + try: + with open(SWITCH_CARD_EEPROM_PATH, "rb") as f: + e = f.read() + finally: + cleanup() + + # Parse TlvInfo TLV 0x23 + if len(e) < 11 or e[0:7] != b"TlvInfo": + return None + + total_len = (e[9] << 8) | e[10] + idx = 11 + end = 11 + total_len + + while idx + 2 <= len(e) and idx < end: + t = e[idx] + l = e[idx + 1] + vstart = idx + 2 + vend = vstart + l + if vend > len(e): + break + + if t == 0x23: # Serial Number = system/chassis SN + return e[vstart:vend].decode("ascii", errors="ignore").strip() + + if t == 0xFE: # CRC TLV + break + + idx = vend + + return None diff --git a/platform/aspeed/sonic-platform-modules-nexthop/common/sonic_platform/eeprom_utils.py b/platform/aspeed/sonic-platform-modules-nexthop/common/sonic_platform/eeprom_utils.py new file mode 100644 index 00000000000..8daae6f16a7 --- /dev/null +++ b/platform/aspeed/sonic-platform-modules-nexthop/common/sonic_platform/eeprom_utils.py @@ -0,0 +1,355 @@ +# Copyright 2025 Nexthop Systems Inc. All rights reserved. +# SPDX-License-Identifier: Apache-2.0 + +import os +import sys +import struct + +from dataclasses import dataclass +from enum import Enum + +try: + from sonic_platform_base.sonic_eeprom import eeprom_tlvinfo +except ImportError as e: + raise ImportError(str(e) + "- required module not found") + +NEXTHOP_IANA = "63074" +NEXTHOP_IANA_SIZE = 4 # IANA is 4 bytes +VENDOR_EXT_STR = "Vendor Extension" + + +class CustomField(Enum): + """ + Enum for Nexthop custom EEPROM fields used for "Vendor Extension" + TLV programming and decoding. + """ + + SECONDARY_SERIAL_NUMBER = (0x01, "Custom Serial Number") + REGULATORY_MODEL_NUMBER = (0x02, "Regulatory Model Number") + SWITCH_HOST_SERIAL_NUMBER = (0x03, "Switch Host Serial Number") + + def __init__(self, code, display_name): + self.code = code + self.display_name = display_name + + @classmethod + def get_by_code(cls, code): + for field in cls: + if field.code == code: + return field + return None + + +@dataclass +class CustomFieldStruct: + iana: bytearray + code: int + payload: bytearray + + +def big_endian_to_int(bytes: bytearray) -> int: + return struct.unpack(">I", bytes)[0] + + +def tlv_to_custom_field_struct(t: bytearray) -> tuple[CustomFieldStruct | None, str]: + """ + Parses the given TLV in the form of bytes into a CustomFieldStruct. + Returns (None, error_message) if the given TLV bytes is not a valid Vendor Extension TLV. + + Vendor Extension TLV schema: + Byte | Name + ------------- + 0 | Type + 1 | Payload length (including IANA, Custom field code, and Custom payload) + 2-5 | IANA + 6 | Custom field code + 7+ | Custom payload (variable length) + """ + # Check minimum TLV length (1 + 1 + 4 + 1 = 7 bytes minimum) + if len(t) < 7: + return None, "Invalid format - too short" + + if t[0] != eeprom_tlvinfo.TlvInfoDecoder._TLV_CODE_VENDOR_EXT: + return None, "Not a Vendor Extension TLV" + + # Check minimum payload length (4 + 1 = 5 bytes minimum) + if t[1] < 5: + return None, "Invalid payload length - too short" + + # Parse the structure + iana_bytes = t[2:6] + custom_field_code = t[6] + payload = t[7 : 2 + t[1]] + # ============= FOR BACKWARD COMPATIBILITY ============= + # Some early units may have a garbage value at byte 2 + # of the "Custom Serial Number" (code 0x01) TLV. So, + # IANA, custom field code, and custom payload are all + # shifted right by 1 byte. This special check is so we + # can decode the "Custom Serial Number" for those units. + # Although, the value of payload length at byte 1 is + # still correct. + if ( + big_endian_to_int(iana_bytes) != int(NEXTHOP_IANA) + and len(t) >= 8 + and t[1] >= 6 + and big_endian_to_int(t[3:7]) == int(NEXTHOP_IANA) + and t[7] == CustomField.SECONDARY_SERIAL_NUMBER.code + ): + iana_bytes = t[3:7] + custom_field_code = t[7] + payload = t[8 : 2 + t[1]] + # ====================================================== + return CustomFieldStruct(iana_bytes, custom_field_code, payload), "" + + +class NexthopEepromDecodeVisitor(eeprom_tlvinfo.EepromDecodeVisitor): + """ + Custom visitor class, which lengthen the "TLV Name" column to 25 characters. + """ + def visit_header(self, eeprom_id, version, header_length): + if eeprom_id is not None: + print("TlvInfo Header:") + print(" Id String: %s" % eeprom_id) + print(" Version: %d" % version) + print(" Total Length: %d" % header_length) + print("TLV Name Code Len Value") + print("------------------------- ---- --- -----") + + def visit_tlv(self, name, code, length, value): + print("%-25s 0x%02X %3d %s" % (name, code, length, value)) + + +class Eeprom(eeprom_tlvinfo.TlvInfoDecoder): + def decoder(self, s, t): + # Vendor Extension TLV schema: + # Byte | Name + # ------------- + # 0 | Type + # 1 | Payload length (including IANA, Custom field code, and Custom payload) + # 2-5 | IANA + # 6 | Custom field code + # 7+ | Custom payload (variable length) + if t[0] != self._TLV_CODE_VENDOR_EXT: + return eeprom_tlvinfo.TlvInfoDecoder.decoder(self, s, t) + + # Parse the structure + custom_field_struct, err = tlv_to_custom_field_struct(t) + if custom_field_struct is None: + name = VENDOR_EXT_STR + return name, err + + # Verify IANA + iana = big_endian_to_int(custom_field_struct.iana) + if iana != int(NEXTHOP_IANA): + name = VENDOR_EXT_STR + return ( + name, + f"Invalid IANA: {iana}, expected {NEXTHOP_IANA}", + ) + + # Verify custom field code + custom_field = CustomField.get_by_code(custom_field_struct.code) + if custom_field is None: + name = VENDOR_EXT_STR + return name, f"Invalid field code: 0x{custom_field_struct.code:02x}" + + # Extract and decode the actual payload + custom_value = custom_field_struct.payload.decode("utf-8", errors="replace") + name = custom_field.display_name + return name, custom_value + + def _encode_arg_to_tlv_bytes(self, arg: str) -> bytearray: + """ + Convert a string in the format of "{type} = {payload}", where payload is + space-separated hexidecimal numbers, to a TLV bytearray consisting of + type (1 byte) + payload length (1 byte) + payload (variable length). + """ + type, payload = arg.split("=") + type = int(type.strip(), base=0) + payload = payload.strip() + tlv = self.encoder((type,), payload) + return tlv + + def _find_tlv_vendor_ext_start_offset( + self, e: bytearray, iana: bytearray, custom_field_code: int + ) -> int | None: + """ + If exists in the given EEPROM data, returns the start index of the + Vendor Extension TLV which contains the given IANA (bytes [2,5]) and + the given custom field code (6th byte). + Returns None if not found. + """ + tlv_start = 0 + # Iterate through TLVs until we find the matching Vendor Extension TLV + while tlv_start < len(e) and self.is_valid_tlv(e[tlv_start:]): + # Check if this is the Vendor Extension TLV we're looking for. + tlv_end = tlv_start + 2 + e[tlv_start + 1] + custom_field_struct, _ = tlv_to_custom_field_struct(e[tlv_start:tlv_end]) + if ( + custom_field_struct is not None + and custom_field_struct.iana == iana + and custom_field_struct.code == custom_field_code + ): + return tlv_start + + # Move to the next TLV. + tlv_start = tlv_end + return None + + def _remove_header_and_checksum_tlv(self, e: bytearray) -> bytearray: + """ + Returns the given EEPROM data with the header and the checksum TLV removed. + """ + # Skip header. + tlv_start = 0 + if self._TLV_HDR_ENABLED: + tlv_start = self._TLV_INFO_HDR_LEN + total_length = (e[9] << 8) | e[10] + e_end = self._TLV_INFO_HDR_LEN + total_length + else: + tlv_start = self.eeprom_start + e_end = min(self._TLV_INFO_MAX_LEN, self.eeprom_max_len) + + # Include all TLVs but not the checksum TLV which is always the last TLV. + # TLV schema: + # Byte | Name + # ------------ + # 0 | Type + # 1 | Payload length + # 2+ | Payload (variable length) + new_e = bytearray() + while ( + tlv_start < len(e) + and tlv_start < e_end + and self.is_valid_tlv(e[tlv_start:]) + and e[tlv_start] != self._TLV_CODE_CRC_32 + ): + tlv_end = tlv_start + 2 + e[tlv_start + 1] + new_e += e[tlv_start:tlv_end] + tlv_start = tlv_end + return new_e + + def decode_eeprom(self, e): + visitor = NexthopEepromDecodeVisitor() + self.visit_eeprom(e, visitor) + + +def format_vendor_ext(custom_payload, custom_field_code): + """ + Format vendor extension field according to Nexthop specification: + - NEXTHOP IANA (4 bytes) + - Custom field code (1 byte) + - Custom payload (variable length) + + Args: + custom_payload (bytes): The custom payload data + custom_field_code (int): Custom field code (default: 0x01) + + Returns: + str: Formatted vendor extension string in hex format + """ + + # Ensure custom_payload is bytes + if not isinstance(custom_payload, bytes): + raise TypeError("custom_payload must be bytes") + + # Build the vendor extension data + vendor_ext_data = bytearray() + + # Add NEXTHOP IANA (4 bytes) - convert string to integer then to 4 bytes + iana_int = int(NEXTHOP_IANA) + vendor_ext_data.extend(struct.pack(">I", iana_int)) # Big-endian 4-byte integer + + # Add custom field code (1 byte) + vendor_ext_data.append(custom_field_code & 0xFF) + + # Add custom payload + vendor_ext_data.extend(custom_payload) + + # Convert to hex string format expected by eeprom_tlvinfo + return " ".join(f"0x{b:02x}" for b in vendor_ext_data) + + +def get_at24_eeprom_paths(root=""): + results = [] + i2c_devices_dir = f"{root}/sys/bus/i2c/devices" + if not os.path.isdir(i2c_devices_dir): + return results + for device in os.listdir(i2c_devices_dir): + name_file_path = os.path.join(i2c_devices_dir, device, "name") + os.path.isfile(name_file_path) + with open(name_file_path, "r") as f: + if f.read().strip() == "24c64": + eeprom_path = os.path.join(i2c_devices_dir, device, "eeprom") + if os.path.isfile(eeprom_path): + results.append(eeprom_path) + return results + +def decode_eeprom(eeprom_path: str): + eeprom_class = Eeprom(eeprom_path, start=0, status="", ro=True) + eeprom = eeprom_class.read_eeprom() + # will print out contents + eeprom_class.decode_eeprom(eeprom) + +def program_eeprom( + eeprom_path, + product_name, + part_num, + serial_num, + mac, + device_version, + label_revision, + platform_name, + manufacturer_name, + vendor_name, + service_tag, + custom_serial_number, + regulatory_model_number, +): + eeprom_class = Eeprom(eeprom_path, start=0, status="", ro=True) + tmp_contents = eeprom_class.read_eeprom() + cmds = [] + if product_name is not None: + cmds.append(f"{eeprom_class._TLV_CODE_PRODUCT_NAME} = {product_name}") + if part_num is not None: + cmds.append(f"{eeprom_class._TLV_CODE_PART_NUMBER} = {part_num}") + if serial_num is not None: + cmds.append(f"{eeprom_class._TLV_CODE_SERIAL_NUMBER} = {serial_num}") + if mac is not None: + cmds.append(f"{eeprom_class._TLV_CODE_MAC_BASE} = {mac}") + if device_version is not None: + cmds.append(f"{eeprom_class._TLV_CODE_DEVICE_VERSION} = {device_version}") + if label_revision is not None: + cmds.append(f"{eeprom_class._TLV_CODE_LABEL_REVISION} = {label_revision}") + if platform_name is not None: + cmds.append(f"{eeprom_class._TLV_CODE_PLATFORM_NAME} = {platform_name}") + if manufacturer_name is not None: + cmds.append(f"{eeprom_class._TLV_CODE_MANUF_NAME} = {manufacturer_name}") + if vendor_name is not None: + cmds.append(f"{eeprom_class._TLV_CODE_VENDOR_NAME} = {vendor_name}") + if service_tag is not None: + cmds.append(f"{eeprom_class._TLV_CODE_SERVICE_TAG} = {service_tag}") + + # Vendor extension fields. See Nexthop custom EEPROM fields in `CustomField` enum above. + if custom_serial_number is not None: + payload_hex_str = format_vendor_ext( + custom_serial_number.encode("utf-8"), + CustomField.SECONDARY_SERIAL_NUMBER.code, + ) + cmds.append(f"{eeprom_class._TLV_CODE_VENDOR_EXT} = {payload_hex_str}") + if regulatory_model_number is not None: + payload_hex_str = format_vendor_ext( + regulatory_model_number.encode("utf-8"), + CustomField.REGULATORY_MODEL_NUMBER.code, + ) + cmds.append(f"{eeprom_class._TLV_CODE_VENDOR_EXT} = {payload_hex_str}") + + tmp_contents = eeprom_class.set_eeprom(tmp_contents, cmds) + + eeprom_class.write_eeprom(tmp_contents) + + +def clear_eeprom(eeprom_path: str): + size = os.path.getsize(eeprom_path) + with open(eeprom_path, "r+b") as f: + f.write(bytearray([0xFF] * size)) diff --git a/platform/aspeed/sonic-platform-modules-nexthop/common/sonic_platform/switch_host_module.py b/platform/aspeed/sonic-platform-modules-nexthop/common/sonic_platform/switch_host_module.py new file mode 100644 index 00000000000..4b698afc2d0 --- /dev/null +++ b/platform/aspeed/sonic-platform-modules-nexthop/common/sonic_platform/switch_host_module.py @@ -0,0 +1,258 @@ +""" +SwitchHostModule implementation for Nexthop BMC Platform + +This module provides an abstraction for the BMC's interaction with the +switch host CPU, including power management operations. +""" + +import subprocess +import json +import os +import sys +import time + +try: + from sonic_platform_base.module_base import ModuleBase +except ImportError as e: + raise ImportError(str(e) + " - required module not found") + + +class SwitchHostModule(ModuleBase): + """ + Module representing the main x86 Switch Host CPU managed by the BMC. + + This module provides an abstraction for the BMC's interaction with the + switch host CPU, including power management and status reporting. + """ + + # Hardware register constants + CPE_CTRL_REG = "0x14C0B208" # CPU reset control register + RESET_VALUE_ASSERT = "2" # Write 2 => drive low (into reset) + RESET_VALUE_DEASSERT = "3" # Write 3 => drive high (out of reset) + + def __init__(self, module_index=0): + """ + Initialize SwitchHostModule + + Args: + module_index: Module index (default 0, as BMC manages single switch host) + """ + super(SwitchHostModule, self).__init__() + self.module_index = module_index + + def _write_reset_register(self, value): + """ + Write to the switch CPU reset register using devmem. + + Args: + value: Register value to write (str) + + Returns: + bool: True if operation succeeded, False otherwise + """ + try: + cmd = ["busybox", "devmem", self.CPE_CTRL_REG, "32", value] + result = subprocess.run(cmd, capture_output=True, timeout=5) + if result.returncode != 0: + sys.stderr.write(f"devmem write failed: {result.stderr}\n") + return False + return True + except Exception as e: + sys.stderr.write(f"Failed to write reset register: {e}\n") + return False + + def _read_reset_register(self): + """ + Read current value from switch CPU reset register. + + Returns: + int: 0 or 1 (CPU in reset or out of reset), -1 on error + """ + try: + cmd = ["busybox", "devmem", self.CPE_CTRL_REG, "32"] + result = subprocess.run(cmd, capture_output=True, timeout=5, text=True) + if result.returncode == 0: + # Parse hex value (e.g., "0x00000001") + value = int(result.stdout.strip(), 16) + return value & 0x1 # Extract bit 0: 0=in reset, 1=out of reset + except Exception as e: + sys.stderr.write(f"Failed to read reset register: {e}\n") + return -1 + + def _is_cpu_released_from_reset(self): + """ + Check if CPU is released from reset. + + Returns: + bool: True if CPU is out of reset, False otherwise + """ + value = self._read_reset_register() + return bool(value & 0x1) # 1 = out of reset + + ############################################## + # Core Power Management APIs + ############################################## + + def set_admin_state(self, up): + """ + Power ON (up=True) or Power OFF (up=False) the switch host CPU. + + Args: + up: True to power on (release from reset), False to power off (put into reset) + + Returns: + bool: True if operation succeeded, False otherwise + """ + if up: + # Power ON: Drive high (release from reset) + sys.stderr.write("SwitchHost: Powering ON (releasing from reset)...\n") + return self._write_reset_register(self.RESET_VALUE_DEASSERT) + else: + # Power OFF: Drive low (assert reset) + sys.stderr.write("SwitchHost: Powering OFF (asserting reset)...\n") + return self._write_reset_register(self.RESET_VALUE_ASSERT) + + def do_power_cycle(self): + """ + Power cycle the switch host CPU. + + Sequence: + 1. Assert reset (drive low) + 2. Wait 2 seconds + 3. Deassert reset (drive high) + + Returns: + bool: True if operation succeeded, False otherwise + """ + sys.stderr.write("SwitchHost: Starting power cycle...\n") + + # Step 1: Assert reset (power off) + if not self._write_reset_register(self.RESET_VALUE_ASSERT): + sys.stderr.write("SwitchHost: Failed to assert reset\n") + return False + + sys.stderr.write("SwitchHost: Reset asserted, waiting 2 seconds...\n") + + # Step 2: Wait for reset to take effect (2 seconds as per switch_cpu_utils.sh) + time.sleep(2) + + # Step 3: Deassert reset (power on) + if not self._write_reset_register(self.RESET_VALUE_DEASSERT): + sys.stderr.write("SwitchHost: Failed to deassert reset\n") + return False + + sys.stderr.write("SwitchHost: Power cycle complete\n") + return True + + def reboot(self, reboot_type=None): + """ + Alias for do_power_cycle() to maintain ModuleBase compatibility. + + Args: + reboot_type: Reboot type (unused, for compatibility) + + Returns: + bool: True if operation succeeded + """ + return self.do_power_cycle() + + def get_oper_status(self): + """ + Get operational status of the switch host CPU. + + Based on hardware register read: + - Register value bit 0 = 1 (out of reset) => MODULE_STATUS_ONLINE + - Register value bit 0 = 0 (in reset) => MODULE_STATUS_OFFLINE + - Read error => MODULE_STATUS_FAULT + + Returns: + str: One of MODULE_STATUS_ONLINE, MODULE_STATUS_OFFLINE, MODULE_STATUS_FAULT + """ + reg_value = self._read_reset_register() + + if reg_value == -1: + # Read error + return self.MODULE_STATUS_FAULT + + if self._is_cpu_released_from_reset(): + # Bit 0 = 1: CPU is out of reset + return self.MODULE_STATUS_ONLINE + else: + # Bit 0 = 0: CPU is in reset + return self.MODULE_STATUS_OFFLINE + + ############################################## + # Required ModuleBase Implementations + ############################################## + + def get_name(self): + """ + Returns module name: SWITCH_HOST0 + + Returns: + str: Module name + """ + return f"{self.MODULE_TYPE_SWITCH_HOST}{self.module_index}" + + def get_type(self): + """ + Returns module type + + Returns: + str: Module type (SWITCH_HOST) + """ + return self.MODULE_TYPE_SWITCH_HOST + + def get_slot(self): + """ + Returns slot number (0 for single switch host) + + Returns: + int: Slot number + """ + return 0 + + def get_presence(self): + """ + Switch host is always present (fixed hardware) + + Returns: + bool: True (always present) + """ + return True + + def get_description(self): + """ + Returns description + + Returns: + str: Module description + """ + return "Main x86 Switch Host CPU managed by BMC" + + def get_maximum_consumed_power(self): + """ + Returns maximum consumed power. + Returns: + None: Power measurement not available for switch host module + """ + return None + + def get_base_mac(self): + """ + Not applicable for switch host + + Raises: + NotImplementedError + """ + raise NotImplementedError + + def get_system_eeprom_info(self): + """ + Not applicable for switch host + + Raises: + NotImplementedError + """ + raise NotImplementedError + 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 index 3f9a8248c76..d24e29733e6 100644 --- a/platform/aspeed/sonic-platform-modules-nexthop/common/sonic_platform/watchdog.py +++ b/platform/aspeed/sonic-platform-modules-nexthop/common/sonic_platform/watchdog.py @@ -53,44 +53,14 @@ def __init__(self): 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 """ @@ -99,64 +69,74 @@ def _read_sysfs_int(self, filename): return int(f.read().strip()) except (IOError, OSError, ValueError): return -1 - - def arm(self, seconds): + + def _read_sysfs_str(self, filename): """ - Arm the hardware watchdog with a timeout of seconds - + Read a string value from sysfs + Args: - seconds: Timeout value in seconds - + filename: Name of the file in WATCHDOG_SYSFS_PATH + Returns: - An integer specifying the actual number of seconds the watchdog - was armed with. On failure returns -1. + String value, or empty string on error """ - 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 + with open(os.path.join(WATCHDOG_SYSFS_PATH, filename), 'r') as f: + return f.read().strip() except (IOError, OSError): - return -1 - - def disarm(self): + return "" + + def _open_watchdog(self): """ - Disarm the hardware watchdog + Open the watchdog device file Returns: - A boolean, True if watchdog is disarmed successfully, False if not + True if successful, False otherwise """ - if not self._open_watchdog(): - return False + if self.watchdog_fd is None: + try: + self.watchdog_fd = os.open(self.watchdog_device, os.O_WRONLY) + except (IOError, OSError): + return False + return True - try: - # Disable watchdog - options = array.array('h', [WDIOS_DISABLECARD]) - fcntl.ioctl(self.watchdog_fd, WDIOC_SETOPTIONS, options, False) + def _disablewatchdog(self): + """ + Turn off the watchdog timer + """ + req = array.array('I', [WDIOS_DISABLECARD]) + fcntl.ioctl(self.watchdog_fd, WDIOC_SETOPTIONS, req, False) - self.armed = False - self.timeout = 0 + def _enablewatchdog(self): + """ + Turn on the watchdog timer + """ + req = array.array('I', [WDIOS_ENABLECARD]) + fcntl.ioctl(self.watchdog_fd, WDIOC_SETOPTIONS, req, False) - # Close the watchdog device properly - self._close_watchdog() + def _keepalive(self): + """ + Keep alive watchdog timer + """ + fcntl.ioctl(self.watchdog_fd, WDIOC_KEEPALIVE) - return True - except (IOError, OSError): - return False + def _settimeout(self, seconds): + """ + Set watchdog timer timeout + @param seconds - timeout in seconds + @return is the actual set timeout + """ + req = array.array('I', [seconds]) + fcntl.ioctl(self.watchdog_fd, WDIOC_SETTIMEOUT, req, True) + + return int(req[0]) + + def _gettimeout(self): + """ + Get watchdog timeout + @return watchdog timeout + """ + return self._read_sysfs_int("timeout") def is_armed(self): """ @@ -167,54 +147,65 @@ def is_armed(self): """ # Check the state from sysfs state = self._read_sysfs_str("state") - if state == "active": - self.armed = True + if (state != 'inactive'): return True else: - self.armed = False return False - def get_remaining_time(self): + def arm(self, seconds): """ - Get the number of seconds remaining on the watchdog timer + Arm the hardware watchdog with a timeout of seconds + + Args: + seconds: Timeout value in seconds Returns: - An integer specifying the number of seconds remaining on the - watchdog timer. If the watchdog is not armed, returns -1. + An integer specifying the actual number of seconds the watchdog + was armed with. On failure returns -1. """ - if not self.is_armed(): + if (seconds < 0 or seconds > 300 ): 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]) + if self.timeout != seconds: + self.timeout = self._settimeout(seconds) + if self.is_armed(): + self._keepalive() + else: + self._enablewatchdog() except (IOError, OSError): - # If ioctl fails, return the configured timeout as an estimate - return self.timeout if self.armed else -1 + return -1 - def _read_sysfs_str(self, filename): - """ - Read a string value from sysfs + return self.timeout - Args: - filename: Name of the file in WATCHDOG_SYSFS_PATH + def disarm(self): + """ + Disarm the hardware watchdog Returns: - String value, or empty string on error + A boolean, True if watchdog is disarmed successfully, False if not """ + if not self._open_watchdog(): + return False + try: - with open(os.path.join(WATCHDOG_SYSFS_PATH, filename), 'r') as f: - return f.read().strip() + self._disablewatchdog() + self.timeout = 0 + except (IOError, OSError): - return "" + return False + + return True + + 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. + """ + return -1 diff --git a/platform/aspeed/sonic-platform-modules-nexthop/common/systemd/sonic-usb-network-udev-init.service b/platform/aspeed/sonic-platform-modules-nexthop/common/systemd/sonic-usb-network-udev-init.service new file mode 100644 index 00000000000..1371cf7feec --- /dev/null +++ b/platform/aspeed/sonic-platform-modules-nexthop/common/systemd/sonic-usb-network-udev-init.service @@ -0,0 +1,16 @@ +[Unit] +Description=Aspeed USB Network Gadget udev Rules Initialization +Documentation=https://github.com/sonic-net/sonic-buildimage +After=local-fs.target +Before=usb-network-init.service + +[Service] +Type=oneshot +ExecStart=/usr/local/bin/sonic-usb-network-udev-init.sh +RemainAfterExit=yes +StandardOutput=journal +StandardError=journal + +[Install] +WantedBy=multi-user.target + diff --git a/platform/aspeed/sonic-platform-modules-nexthop/debian/rules b/platform/aspeed/sonic-platform-modules-nexthop/debian/rules index 81be080ceb4..52fadd763a4 100755 --- a/platform/aspeed/sonic-platform-modules-nexthop/debian/rules +++ b/platform/aspeed/sonic-platform-modules-nexthop/debian/rules @@ -19,7 +19,6 @@ MODULE_DIRS += b27 SERVICE_DIR := service SYSTEMD_DIR := systemd SCRIPTS_DIR := scripts -OBMC_CONSOLE_DIR := obmc-console CFG_DIR := cfg # Uncomment this to turn on verbose mode. @@ -98,13 +97,6 @@ override_dh_auto_install: 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: 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 index 1559df584a8..2a926fc67d6 100755 --- 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 @@ -9,7 +9,7 @@ case "$1" in # 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" @@ -20,10 +20,14 @@ case "$1" in echo " (This is expected during image build)" >&2 fi done - + # Update module dependencies depmod -a 2>/dev/null || true - + + # Enable USB network udev init service (must run before usb-network-init) + echo "Enabling USB network udev init service..." + systemctl enable --now sonic-usb-network-udev-init.service 2>/dev/null || true + # Enable USB network service echo "Enabling USB network service..." systemctl enable --now usb-network-init.service 2>/dev/null || true diff --git a/platform/aspeed/sonic-platform-modules-nexthop/setup.py b/platform/aspeed/sonic-platform-modules-nexthop/setup.py index f9d2f21b603..09ea9d550a7 100644 --- a/platform/aspeed/sonic-platform-modules-nexthop/setup.py +++ b/platform/aspeed/sonic-platform-modules-nexthop/setup.py @@ -13,6 +13,9 @@ package_dir={ "sonic_platform": "common/sonic_platform", }, + scripts=[ + "common/scripts/nexthop_bmc_chassis_db_init", + ], classifiers=[ "Development Status :: 3 - Alpha", "Environment :: Plugins",