+#include "infix.dtsi"
+
+/* Only used by arch/arm topology to compare cores, silences boot errors */
+&cpu0 {
+ clock-frequency = <960000000>;
+};
+
+&cpu1 {
+ clock-frequency = <960000000>;
+};
+
+/* Keep the LED lit by U-Boot until iitod takes over */
+&{/leds/led} {
+ default-state = "keep";
+};
diff --git a/board/arm/bananapi-bpi-m1/genimage.cfg.in b/board/arm/bananapi-bpi-m1/genimage.cfg.in
new file mode 100644
index 000000000..b3a21092f
--- /dev/null
+++ b/board/arm/bananapi-bpi-m1/genimage.cfg.in
@@ -0,0 +1,78 @@
+# Disk image for Banana Pi BPI-M1 (Allwinner A20)
+
+image cfg.ext4 {
+ empty = true
+ temporary = true
+ size = 16M
+
+ ext4 {
+ label = "cfg"
+ use-mke2fs = true
+ features = "uninit_bg"
+ extraargs = "-m 0 -i 4096"
+ }
+}
+
+# The /var partition will be expanded automatically at first boot
+# to use the full size of the SD-card or eMMC media.
+image var.ext4 {
+ empty = true
+ temporary = true
+ size = 128M
+
+ ext4 {
+ label = "var"
+ use-mke2fs = true
+ features = "uninit_bg"
+ extraargs = "-m 0 -i 4096"
+ }
+}
+
+image #IX_ID##VERSION#-bpi-m1-#TARGET#.img {
+ hdimage {
+ partition-table-type = "gpt"
+ # The A20 boot ROM loads the SPL from 8 KiB, move the
+ # GPT partition entries out of its way
+ gpt-location = 1M
+ }
+
+ partition u-boot {
+ in-partition-table = "no"
+ image = "u-boot-sunxi-with-spl.bin"
+ offset = 8K
+ size = 1016K
+ }
+
+ partition aux {
+ partition-uuid = D4EF35A0-0652-45A1-B3DE-D63339C82035
+ image = "aux.ext4"
+ offset = 2M
+ }
+
+ partition primary {
+ partition-type-uuid = 0FC63DAF-8483-4772-8E79-3D69D8477DE4
+ bootable = true
+ size = 250M
+ image = "rootfs.squashfs"
+ }
+
+ partition secondary {
+ partition-type-uuid = 0FC63DAF-8483-4772-8E79-3D69D8477DE4
+ bootable = true
+ size = 250M
+ image = "rootfs.squashfs"
+ }
+
+ partition cfg {
+ partition-uuid = 7aa497f0-73b5-47e5-b2ab-8752d8a48105
+ image = "cfg.ext4"
+ }
+
+ partition var {
+ partition-uuid = 8046A06A-E45A-4A14-A6AD-6684704A393F
+ image = "var.ext4"
+ }
+}
+
+# Silence genimage warnings
+config {}
diff --git a/board/arm/bananapi-bpi-m1/rootfs/usr/share/product/lemaker,bananapi/etc/factory-config.cfg b/board/arm/bananapi-bpi-m1/rootfs/usr/share/product/lemaker,bananapi/etc/factory-config.cfg
new file mode 100644
index 000000000..e39e37c1d
--- /dev/null
+++ b/board/arm/bananapi-bpi-m1/rootfs/usr/share/product/lemaker,bananapi/etc/factory-config.cfg
@@ -0,0 +1,311 @@
+{
+ "ieee802-dot1ab-lldp:lldp": {
+ "infix-lldp:enabled": true
+ },
+ "ietf-hardware:hardware": {
+ "component": [
+ {
+ "name": "USB",
+ "class": "infix-hardware:usb",
+ "state": {
+ "admin-state": "unlocked"
+ }
+ }
+ ]
+ },
+ "ietf-interfaces:interfaces": {
+ "interface": [
+ {
+ "name": "lo",
+ "type": "infix-if-type:loopback",
+ "ietf-ip:ipv4": {
+ "address": [
+ {
+ "ip": "127.0.0.1",
+ "prefix-length": 8
+ }
+ ]
+ },
+ "ietf-ip:ipv6": {
+ "address": [
+ {
+ "ip": "::1",
+ "prefix-length": 128
+ }
+ ]
+ }
+ },
+ {
+ "name": "eth0",
+ "type": "infix-if-type:ethernet",
+ "ietf-ip:ipv4": {
+ "infix-dhcp-client:dhcp": {
+ "option": [
+ {
+ "id": "netmask"
+ },
+ {
+ "id": "broadcast"
+ },
+ {
+ "id": "router"
+ },
+ {
+ "id": "domain"
+ },
+ {
+ "id": "hostname",
+ "value": "auto"
+ },
+ {
+ "id": "dns-server"
+ },
+ {
+ "id": "ntp-server"
+ },
+ {
+ "id": "vendor-class",
+ "value": "Banana Pi BPI-M1"
+ }
+ ]
+ }
+ },
+ "ietf-ip:ipv6": {
+ "infix-dhcpv6-client:dhcp": {
+ "option": [
+ {
+ "id": "ntp-server"
+ },
+ {
+ "id": "client-fqdn"
+ },
+ {
+ "id": "domain-search"
+ },
+ {
+ "id": "dns-server"
+ }
+ ]
+ }
+ }
+ }
+ ]
+ },
+ "ietf-keystore:keystore": {
+ "asymmetric-keys": {
+ "asymmetric-key": [
+ {
+ "name": "gencert",
+ "public-key-format": "infix-crypto-types:x509-public-key-format",
+ "public-key": "",
+ "private-key-format": "infix-crypto-types:rsa-private-key-format",
+ "cleartext-private-key": "",
+ "certificates": {}
+ },
+ {
+ "name": "genkey",
+ "public-key-format": "infix-crypto-types:ssh-public-key-format",
+ "public-key": "",
+ "private-key-format": "infix-crypto-types:rsa-private-key-format",
+ "cleartext-private-key": "",
+ "certificates": {}
+ }
+ ]
+ }
+ },
+ "ietf-netconf-acm:nacm": {
+ "enable-nacm": true,
+ "read-default": "permit",
+ "write-default": "permit",
+ "exec-default": "permit",
+ "groups": {
+ "group": [
+ {
+ "name": "admin",
+ "user-name": [
+ "admin"
+ ]
+ },
+ {
+ "name": "operator",
+ "user-name": []
+ },
+ {
+ "name": "guest",
+ "user-name": []
+ }
+ ]
+ },
+ "rule-list": [
+ {
+ "name": "admin-acl",
+ "group": [
+ "admin"
+ ],
+ "rule": [
+ {
+ "name": "permit-all",
+ "module-name": "*",
+ "access-operations": "*",
+ "action": "permit",
+ "comment": "Allow 'admin' group complete access to all operations and data."
+ }
+ ]
+ },
+ {
+ "name": "operator-acl",
+ "group": [
+ "operator"
+ ],
+ "rule": [
+ {
+ "name": "permit-system-rpcs",
+ "module-name": "ietf-system",
+ "rpc-name": "*",
+ "access-operations": "exec",
+ "action": "permit",
+ "comment": "Operators can reboot, shutdown, and set system time."
+ }
+ ]
+ },
+ {
+ "name": "guest-acl",
+ "group": [
+ "guest"
+ ],
+ "rule": [
+ {
+ "name": "deny-all-write+exec",
+ "module-name": "*",
+ "access-operations": "create update delete exec",
+ "action": "deny",
+ "comment": "Guests cannot change anything or exec rpcs."
+ }
+ ]
+ },
+ {
+ "name": "default-deny-all",
+ "group": [
+ "*"
+ ],
+ "rule": [
+ {
+ "name": "deny-password-access",
+ "path": "/ietf-system:system/authentication/user/password",
+ "access-operations": "*",
+ "action": "deny",
+ "comment": "No user except admins can access password hashes."
+ },
+ {
+ "name": "deny-keystore-access",
+ "module-name": "ietf-keystore",
+ "access-operations": "*",
+ "action": "deny",
+ "comment": "No user except admins can access cryptographic keys."
+ },
+ {
+ "name": "deny-truststore-access",
+ "module-name": "ietf-truststore",
+ "access-operations": "*",
+ "action": "deny",
+ "comment": "No user except admins can access trust store."
+ }
+ ]
+ }
+ ]
+ },
+ "ietf-system:system": {
+ "hostname": "bpi-%m",
+ "infix-system:software": {
+ "update-url": "https://github.com/kernelkit/infix/releases.atom"
+ },
+ "infix-schedule:schedules": {
+ "schedule": [
+ {
+ "name": "nightly",
+ "description": "Every night at 03:00",
+ "recurrence": {
+ "frequency": "ietf-schedule:daily",
+ "byhour": [
+ 3
+ ]
+ }
+ },
+ {
+ "name": "weekly",
+ "description": "Sunday nights at 03:00",
+ "recurrence": {
+ "frequency": "ietf-schedule:weekly",
+ "byday": [
+ {
+ "weekday": "sunday"
+ }
+ ],
+ "byhour": [
+ 3
+ ]
+ }
+ }
+ ]
+ },
+ "ntp": {
+ "enabled": true,
+ "server": [
+ {
+ "name": "ntp.org",
+ "udp": {
+ "address": "pool.ntp.org"
+ },
+ "iburst": true
+ }
+ ]
+ },
+ "authentication": {
+ "user": [
+ {
+ "name": "admin",
+ "password": "$factory$",
+ "infix-system:shell": "bash"
+ }
+ ]
+ },
+ "infix-system:motd-banner": "Li0tLS0tLS0uCnwgIC4gLiAgfCBJbmZpeCBPUyDigJQgSW1tdXRhYmxlLkZyaWVuZGx5LlNlY3VyZQp8LS4gdiAuLXwgaHR0cHM6Ly9rZXJuZWxraXQub3JnCictJy0tLSctJwo="
+ },
+ "infix-meta:meta": {
+ "version": "1.10"
+ },
+ "infix-services:mdns": {
+ "enabled": true
+ },
+ "infix-services:web": {
+ "enabled": true,
+ "certificate": "gencert",
+ "console": {
+ "enabled": true
+ },
+ "netbrowse": {
+ "enabled": true
+ },
+ "restconf": {
+ "enabled": true
+ }
+ },
+ "infix-services:ssh": {
+ "enabled": true,
+ "hostkey": [
+ "genkey"
+ ],
+ "listen": [
+ {
+ "name": "ipv4",
+ "address": "0.0.0.0",
+ "port": 22
+ },
+ {
+ "name": "ipv6",
+ "address": "::",
+ "port": 22
+ }
+ ]
+ }
+}
diff --git a/board/arm/bananapi-bpi-m1/rootfs/usr/share/product/lemaker,bananapi/etc/finit.d/enabled/input-event-daemon.conf b/board/arm/bananapi-bpi-m1/rootfs/usr/share/product/lemaker,bananapi/etc/finit.d/enabled/input-event-daemon.conf
new file mode 120000
index 000000000..8238fd3b4
--- /dev/null
+++ b/board/arm/bananapi-bpi-m1/rootfs/usr/share/product/lemaker,bananapi/etc/finit.d/enabled/input-event-daemon.conf
@@ -0,0 +1 @@
+../available/input-event-daemon.conf
\ No newline at end of file
diff --git a/board/arm/bananapi-bpi-m1/rootfs/usr/share/product/lemaker,bananapi/etc/iitod.json b/board/arm/bananapi-bpi-m1/rootfs/usr/share/product/lemaker,bananapi/etc/iitod.json
new file mode 100644
index 000000000..287630ac2
--- /dev/null
+++ b/board/arm/bananapi-bpi-m1/rootfs/usr/share/product/lemaker,bananapi/etc/iitod.json
@@ -0,0 +1,129 @@
+{
+ "input": {
+ "path": {
+ "locate": {
+ "path": "/run/led/locate"
+ },
+ "status-prime": {
+ "path": "/run/led/status-prime"
+ },
+ "status-ok": {
+ "path": "/run/led/status-ok"
+ },
+ "status-err": {
+ "path": "/run/led/status-err"
+ },
+ "status-crit": {
+ "path": "/run/led/status-crit"
+ },
+ "fault-prime": {
+ "path": "/run/led/fault-prime"
+ },
+ "fault-ok": {
+ "path": "/run/led/fault-ok"
+ },
+ "fault-err": {
+ "path": "/run/led/fault-err"
+ },
+ "fault-crit": {
+ "path": "/run/led/fault-crit"
+ },
+ "startup": {
+ "path": "/run/finit/cond/usr/startup-config-ok"
+ },
+ "fail-safe": {
+ "path": "/run/finit/cond/usr/failure-config-ok"
+ },
+ "panic": {
+ "path": "/run/finit/cond/usr/failure-config-error"
+ }
+ },
+ "udev": {
+ "power-a": {
+ "subsystem": "power_supply"
+ },
+ "power-b": {
+ "subsystem": "power_supply"
+ }
+ }
+ },
+ "output": {
+ "led-group": {
+ "port-link-act": {
+ "match": [
+ "*:green:tp",
+ "*:green:sfp",
+ "*:green:port"
+ ],
+ "rules": [
+ {
+ "if": "true",
+ "then": {
+ "trigger": "netdev",
+ "link": 1,
+ "rx": 1,
+ "tx": 1
+ }
+ }
+ ]
+ },
+ "port-alarm": {
+ "match": [
+ "*:yellow:tp",
+ "*:yellow:sfp",
+ "*:yellow:port"
+ ],
+ "rules": []
+ }
+ },
+ "led": {
+ "bananapi:green:usr": {
+ "rules": [
+ {
+ "if": "locate",
+ "then": "@blink-10hz"
+ },
+ {
+ "if": "panic",
+ "then": "@blink-5hz"
+ },
+ {
+ "if": "fail-safe",
+ "then": "@blink-5hz"
+ },
+ {
+ "if": "startup",
+ "then": "@on"
+ },
+ {
+ "if": "true",
+ "then": "@blink-1hz"
+ }
+ ]
+ }
+ }
+ },
+ "aliases": {
+ "on": {
+ "brightness": true
+ },
+ "off": {
+ "brightness": false
+ },
+ "blink-1hz": {
+ "trigger": "timer",
+ "delay_on": 500,
+ "delay_off": 500
+ },
+ "blink-5hz": {
+ "trigger": "timer",
+ "delay_on": 100,
+ "delay_off": 100
+ },
+ "blink-10hz": {
+ "trigger": "timer",
+ "delay_on": 50,
+ "delay_off": 50
+ }
+ }
+}
diff --git a/board/arm/bananapi-bpi-m1/rootfs/usr/share/product/lemaker,bananapi/etc/input-event-daemon.conf b/board/arm/bananapi-bpi-m1/rootfs/usr/share/product/lemaker,bananapi/etc/input-event-daemon.conf
new file mode 100644
index 000000000..eb969fb68
--- /dev/null
+++ b/board/arm/bananapi-bpi-m1/rootfs/usr/share/product/lemaker,bananapi/etc/input-event-daemon.conf
@@ -0,0 +1,5 @@
+[Global]
+listen = /dev/input/power-key
+
+[Keys]
+POWER = poweroff
diff --git a/board/arm/bananapi-bpi-m1/rootfs/usr/share/product/lemaker,bananapi/etc/udev/rules.d/70-power-key.rules b/board/arm/bananapi-bpi-m1/rootfs/usr/share/product/lemaker,bananapi/etc/udev/rules.d/70-power-key.rules
new file mode 100644
index 000000000..9d1f1b10d
--- /dev/null
+++ b/board/arm/bananapi-bpi-m1/rootfs/usr/share/product/lemaker,bananapi/etc/udev/rules.d/70-power-key.rules
@@ -0,0 +1 @@
+ACTION=="add", SUBSYSTEM=="input", KERNEL=="event*", ATTRS{name}=="axp20x-pek", SYMLINK+="input/power-key"
diff --git a/board/arm/bananapi-bpi-m1/uart-pins.png b/board/arm/bananapi-bpi-m1/uart-pins.png
new file mode 100644
index 000000000..e2e5b5304
Binary files /dev/null and b/board/arm/bananapi-bpi-m1/uart-pins.png differ
diff --git a/board/arm/bananapi-bpi-m1/uboot/bpi-m1-env.dtsi b/board/arm/bananapi-bpi-m1/uboot/bpi-m1-env.dtsi
new file mode 100644
index 000000000..02328336b
--- /dev/null
+++ b/board/arm/bananapi-bpi-m1/uboot/bpi-m1-env.dtsi
@@ -0,0 +1,25 @@
+/ {
+ config {
+ environment {
+ vendor = "Banana Pi";
+ preboot = "led bananapi:green:usr on; run ixpreboot";
+ boot_targets = "mmc0";
+ ethprime = "eth0";
+
+ /* The sunxi board code resets fdtfile after the environment
+ * is imported, without the vendor directory on 32-bit Arm */
+ ixvariant = "setenv fdtfile allwinner/sun7i-a20-bananapi.dtb";
+
+ /* 1 GiB DRAM at 0x40000000, the squashfs is loaded whole
+ * at ramdisk_addr_r so keep that last with room above it */
+ fdt_addr_r = "0x42000000";
+ kernel_addr_r = "0x43000000";
+ scriptaddr = "0x48000000";
+ ramdisk_addr_r = "0x49000000";
+
+ /* No user button, this is a development platform */
+ ixbtn-devmode = "setenv dev_mode yes; echo Enabled";
+ ixbtn-factory = "echo \"No button, use bootmenu\"";
+ };
+ };
+};
diff --git a/board/arm/bananapi-bpi-m1/uboot/extras.config b/board/arm/bananapi-bpi-m1/uboot/extras.config
new file mode 100644
index 000000000..e5d221816
--- /dev/null
+++ b/board/arm/bananapi-bpi-m1/uboot/extras.config
@@ -0,0 +1,6 @@
+CONFIG_DEVICE_TREE_INCLUDES="infix-env.dtsi infix-key.dtsi bpi-m1-env.dtsi"
+CONFIG_SYS_PROMPT="(bpi-m1) "
+
+# CONFIG_ENV_IS_IN_FAT is not set
+CONFIG_LED=y
+CONFIG_LED_GPIO=y
diff --git a/board/arm/linux_defconfig b/board/arm/linux_defconfig
index 3c0639ad7..20ff6ac33 100644
--- a/board/arm/linux_defconfig
+++ b/board/arm/linux_defconfig
@@ -306,6 +306,7 @@ CONFIG_SERIAL_DEV_BUS=y
CONFIG_VIRTIO_CONSOLE=y
CONFIG_I2C_CHARDEV=y
CONFIG_SPI=y
+CONFIG_GPIOLIB=y
CONFIG_THERMAL=y
CONFIG_WATCHDOG=y
CONFIG_I6300ESB_WDT=y
diff --git a/board/common/uboot/extras.config b/board/common/uboot/extras.config
index 0037106a7..d88e46ed6 100644
--- a/board/common/uboot/extras.config
+++ b/board/common/uboot/extras.config
@@ -19,7 +19,6 @@ CONFIG_CMD_SETEXPR_FMT=y
CONFIG_MMC=y
-CONFIG_MMC_SDHCI=y
CONFIG_CMD_MMC=y
CONFIG_FS_SQUASHFS=y
diff --git a/configs/arm_defconfig b/configs/arm_defconfig
index b0deb8918..108521113 100644
--- a/configs/arm_defconfig
+++ b/configs/arm_defconfig
@@ -10,6 +10,7 @@ BR2_CCACHE_DIR="${BR2_EXTERNAL_INFIX_PATH}/.ccache"
BR2_ENABLE_DEBUG=y
BR2_GLOBAL_PATCH_DIR="${BR2_EXTERNAL_INFIX_PATH}/patches"
BR2_DOWNLOAD_FORCE_CHECK_HASHES=y
+BR2_TIME_BITS_64=y
BR2_TARGET_GENERIC_HOSTNAME="infix"
BR2_TARGET_GENERIC_ISSUE="Infix by KernelKit"
BR2_INIT_FINIT=y
@@ -33,6 +34,7 @@ BR2_LINUX_KERNEL_CUSTOM_CONFIG_FILE="${BR2_EXTERNAL_INFIX_PATH}/board/arm/linux_
BR2_LINUX_KERNEL_INSTALL_TARGET=y
BR2_LINUX_KERNEL_NEEDS_HOST_OPENSSL=y
BR2_PACKAGE_BUSYBOX_CONFIG="${BR2_EXTERNAL_INFIX_PATH}/board/common/busybox_defconfig"
+BR2_PACKAGE_BUSYBOX_SHOW_OTHERS=y
BR2_PACKAGE_STRACE=y
BR2_PACKAGE_STRESS_NG=y
BR2_PACKAGE_JQ=y
@@ -68,7 +70,6 @@ BR2_PACKAGE_CONNTRACK_TOOLS=y
BR2_PACKAGE_DNSMASQ=y
BR2_PACKAGE_ETHTOOL=y
BR2_PACKAGE_FPING=y
-BR2_PACKAGE_FRR=y
# BR2_PACKAGE_IFUPDOWN_SCRIPTS is not set
BR2_PACKAGE_IPERF3=y
BR2_PACKAGE_IPROUTE2=y
@@ -98,9 +99,6 @@ BR2_PACKAGE_TRACEROUTE=y
BR2_PACKAGE_ULOGD=y
BR2_PACKAGE_WHOIS=y
BR2_PACKAGE_WIREGUARD_TOOLS=y
-BR2_PACKAGE_BASH=y
-BR2_PACKAGE_BASH_COMPLETION=y
-BR2_PACKAGE_NEOFETCH=y
BR2_PACKAGE_SUDO=y
BR2_PACKAGE_TTYD=y
BR2_PACKAGE_GETENT=y
@@ -137,6 +135,7 @@ BR2_PACKAGE_HOST_MTOOLS=y
BR2_PACKAGE_HOST_UBOOT_TOOLS_FIT_SUPPORT=y
BR2_PACKAGE_HOST_UBOOT_TOOLS_FIT_SIGNATURE_SUPPORT=y
BR2_PACKAGE_HOST_UBOOT_TOOLS_FDT_ADD_PUBKEY=y
+BR2_PACKAGE_BANANAPI_BPI_M1=y
BR2_PACKAGE_MICROCHIP_SAMA7G54_EK=y
BR2_PACKAGE_RASPBERRYPI_RPI2=y
IX_VENDOR_HOME="https://www.kernelkit.org"
diff --git a/configs/arm_minimal_defconfig b/configs/arm_minimal_defconfig
index aa140d806..f38f8b719 100644
--- a/configs/arm_minimal_defconfig
+++ b/configs/arm_minimal_defconfig
@@ -10,6 +10,7 @@ BR2_CCACHE_DIR="${BR2_EXTERNAL_INFIX_PATH}/.ccache"
BR2_ENABLE_DEBUG=y
BR2_GLOBAL_PATCH_DIR="${BR2_EXTERNAL_INFIX_PATH}/patches"
BR2_DOWNLOAD_FORCE_CHECK_HASHES=y
+BR2_TIME_BITS_64=y
BR2_TARGET_GENERIC_HOSTNAME="ix"
BR2_TARGET_GENERIC_ISSUE="Infix by KernelKit"
BR2_INIT_FINIT=y
@@ -115,6 +116,7 @@ BR2_PACKAGE_HOST_MTOOLS=y
BR2_PACKAGE_HOST_UBOOT_TOOLS_FIT_SUPPORT=y
BR2_PACKAGE_HOST_UBOOT_TOOLS_FIT_SIGNATURE_SUPPORT=y
BR2_PACKAGE_HOST_UBOOT_TOOLS_FDT_ADD_PUBKEY=y
+BR2_PACKAGE_BANANAPI_BPI_M1=y
BR2_PACKAGE_MICROCHIP_SAMA7G54_EK=y
BR2_PACKAGE_RASPBERRYPI_RPI2=y
IX_VENDOR_HOME="https://www.kernelkit.org"
diff --git a/configs/bpi_m1_boot_defconfig b/configs/bpi_m1_boot_defconfig
new file mode 100644
index 000000000..0d9999ddf
--- /dev/null
+++ b/configs/bpi_m1_boot_defconfig
@@ -0,0 +1,38 @@
+BR2_arm=y
+BR2_cortex_a7=y
+BR2_ARM_FPU_NEON_VFPV4=y
+BR2_TOOLCHAIN_EXTERNAL=y
+BR2_TOOLCHAIN_EXTERNAL_BOOTLIN=y
+BR2_TOOLCHAIN_EXTERNAL_BOOTLIN_ARMV7_EABIHF_GLIBC_STABLE=y
+BR2_DL_DIR="$(BR2_EXTERNAL_INFIX_PATH)/dl"
+BR2_CCACHE=y
+BR2_CCACHE_DIR="$(BR2_EXTERNAL_INFIX_PATH)/.ccache"
+BR2_PACKAGE_OVERRIDE_FILE="$(BR2_EXTERNAL_INFIX_PATH)/local.mk"
+BR2_GLOBAL_PATCH_DIR="$(BR2_EXTERNAL_INFIX_PATH)/patches"
+BR2_SSP_NONE=y
+BR2_INIT_NONE=y
+BR2_SYSTEM_BIN_SH_NONE=y
+# BR2_PACKAGE_BUSYBOX is not set
+# BR2_PACKAGE_IFUPDOWN_SCRIPTS is not set
+# BR2_TARGET_ROOTFS_TAR is not set
+BR2_TARGET_UBOOT=y
+BR2_TARGET_UBOOT_BUILD_SYSTEM_KCONFIG=y
+BR2_TARGET_UBOOT_CUSTOM_VERSION=y
+BR2_TARGET_UBOOT_CUSTOM_VERSION_VALUE="2025.01"
+BR2_TARGET_UBOOT_BOARD_DEFCONFIG="Bananapi"
+BR2_TARGET_UBOOT_CONFIG_FRAGMENT_FILES="$(BR2_EXTERNAL_INFIX_PATH)/board/common/uboot/extras.config $(BR2_EXTERNAL_INFIX_PATH)/board/arm/bananapi-bpi-m1/uboot/extras.config"
+BR2_TARGET_UBOOT_NEEDS_DTC=y
+BR2_TARGET_UBOOT_NEEDS_PYLIBFDT=y
+BR2_TARGET_UBOOT_NEEDS_OPENSSL=y
+BR2_TARGET_UBOOT_FORMAT_CUSTOM=y
+BR2_TARGET_UBOOT_FORMAT_CUSTOM_NAME="u-boot-sunxi-with-spl.bin"
+BR2_TARGET_UBOOT_CUSTOM_DTS_PATH="$(BR2_EXTERNAL_INFIX_PATH)/board/arm/bananapi-bpi-m1/uboot/bpi-m1-env.dtsi"
+BR2_PACKAGE_HOST_BMAP_TOOLS=y
+BR2_PACKAGE_HOST_GENIMAGE=y
+BR2_PACKAGE_HOST_RAUC=y
+BR2_PACKAGE_HOST_UBOOT_TOOLS=y
+BR2_PACKAGE_HOST_UBOOT_TOOLS_FIT_SUPPORT=y
+BR2_PACKAGE_HOST_UBOOT_TOOLS_FIT_SIGNATURE_SUPPORT=y
+BR2_PACKAGE_HOST_UBOOT_TOOLS_FDT_ADD_PUBKEY=y
+IX_TRUSTED_KEYS=y
+IX_TRUSTED_KEYS_DEVELOPMENT=y
diff --git a/doc/ChangeLog.md b/doc/ChangeLog.md
index db6afc3d7..ca6368ebf 100644
--- a/doc/ChangeLog.md
+++ b/doc/ChangeLog.md
@@ -13,7 +13,8 @@ All notable changes to the project are documented in this file.
ietf-rip and ietf-ospf models, selected per control-plane-protocol by the
`ripng`/`ospfv3` type and the IPv6 address-family
- Support building without Frr, which also drops bash. The minimal
- defconfigs are now built this way, with static routing only, issue #1670
+ defconfigs, and the 32-bit Arm build, are now built this way, with
+ static routing only, issue #1670
- Check for a new software release on demand: `upgrade` without a bundle in
the CLI, the Check now button on the WebUI dashboard, or the `check-update`
RPC, see [Unattended Software Updates][unattended]
@@ -24,6 +25,14 @@ All notable changes to the project are documented in this file.
- CLI: `check`, `commit`, and `leave` warn about bridge ports with a missing
or mismatched PVID, which drops untagged frames or puts them in the wrong
VLAN, issue #354
+- Add support for the Banana Pi BPI-M1 (Allwinner A20) to the 32-bit Arm
+ build, with a new `bpi_m1_boot_defconfig` for its U-Boot bootloader
+- New `iitoctl` tool, shows which rule drives each LED and why, and can
+ start locate, the board's LEDs blink as configured: `iitoctl locate on`
+- New `infix-hardware:locate` RPC, blinks the board's locate LEDs to
+ identify the chassis, by default for 60 seconds. Available from the
+ CLI, `locate`, and the Hardware page in the web interface. Not allowed
+ for guest users
### Fixes
@@ -34,6 +43,14 @@ All notable changes to the project are documented in this file.
capabilities, in the CLI, the WebUI, and the operational datastore
- The manufacturer from a VPD was not shown as `mfg-name` of its
`vpd-*` component in the operational datastore
+- Fix FRR mgmtd crash loop on 32-bit Arm, caused by unaligned access to
+ messages received from other daemons
+- Fix mdns-alias failing to publish on 32-bit Arm, every retry ended in
+ "Memory exhausted". The 32-bit Arm builds now use 64-bit `time_t`
+- Fix the LED daemon, iitod, crashing on 32-bit Arm, which left all LEDs
+ off after boot
+- Fix missing hardware status when a sensor label has characters not
+ allowed in a sensor name, e.g., "SoC temperature" on the Allwinner A20
[v26.09.0][] - 2026-09-30
-------------------------
diff --git a/doc/hardware.md b/doc/hardware.md
index c54c22b5a..609db0145 100644
--- a/doc/hardware.md
+++ b/doc/hardware.md
@@ -31,6 +31,34 @@ Which hwmon device or thermal zone belongs to which component is decided
when the data is collected, and is the only place platform specific names
are recognized.
+## Locate
+
+To find a chassis, e.g., in a rack, its LEDs can be set to blink. Which
+LEDs blink, and how, depends on the device. Locate stops by itself after
+60 seconds, unless another duration is given, or when stopped.
+
+admin@example:/> locate
+admin@example:/> locate duration 300
+admin@example:/> locate stop
+admin@example:/> no locate
+
+
+The CLI commands call the `infix-hardware:locate` RPC, which takes
+`enable` and `duration`, in seconds. Over RESTCONF:
+
+```sh
+curl -su admin:admin -X POST -H "Content-Type: application/yang-data+json" \
+ -d '{"infix-hardware:input": {"duration": 300}}' \
+ https://example/restconf/operations/infix-hardware:locate
+```
+
+The web interface has a *Locate* button on the Hardware page.
+
+Users in the `admin` and `operator` NACM groups may call locate, guest
+users may not. On systems where LED control is disabled, the LEDs keep
+their hardware default behavior and the RPC fails with *LED control is
+not available on this device*.
+
## GPS/GNSS Receivers
Infix supports GPS/GNSS receivers for hardware status monitoring and NTP
diff --git a/package/iito/iito.hash b/package/iito/iito.hash
index 6123f4088..bb56625eb 100644
--- a/package/iito/iito.hash
+++ b/package/iito/iito.hash
@@ -1,3 +1,3 @@
# Locally calculated
-sha256 7db4077b7c132170e9c358397cc2a30ae810ce24b8a5b66eb6b31c7c203156c1 iito-1.1.0.tar.gz
+sha256 070ed43b5d73ba43bb8c3b3c2d69bd36681f0e345ba4cf961308cb9d349c6bbe iito-8c671ea6408ba93318c840460ed7dd25af12eadc.tar.gz
sha256 8177f97513213526df2cf6184d8ff986c675afb514d4e68a404010521b880643 COPYING
diff --git a/package/iito/iito.mk b/package/iito/iito.mk
index 0d51f205a..ee87f0120 100644
--- a/package/iito/iito.mk
+++ b/package/iito/iito.mk
@@ -4,11 +4,13 @@
#
################################################################################
-IITO_VERSION = 1.1.0
-IITO_SITE = https://github.com/kernelkit/iito/releases/download/v$(IITO_VERSION)
+IITO_VERSION = 8c671ea6408ba93318c840460ed7dd25af12eadc
+IITO_SITE = $(call github,kernelkit,iito,$(IITO_VERSION))
IITO_LICENSE = GPL-2.0
IITO_LICENSE_FILES = COPYING
IITO_DEPENDENCIES = jansson libev udev
+# No configure script in the git tree
+IITO_AUTORECONF = YES
define IITO_INSTALL_HOOK
$(INSTALL) -D -m 0644 $(IITO_PKGDIR)/iitod.svc $(FINIT_D)/available/iitod.conf
diff --git a/patches/frr/10.5.5/0005-lib-mgmt_msg-hand-handlers-an-aligned-message.patch b/patches/frr/10.5.5/0005-lib-mgmt_msg-hand-handlers-an-aligned-message.patch
new file mode 100644
index 000000000..8ce645cc5
--- /dev/null
+++ b/patches/frr/10.5.5/0005-lib-mgmt_msg-hand-handlers-an-aligned-message.patch
@@ -0,0 +1,65 @@
+From 6088ca29476f8776426348ab413ddc4eccc6760b Mon Sep 17 00:00:00 2001
+From: Joachim Wiberg
+Date: Sun, 4 Oct 2026 15:15:28 +0200
+Subject: [PATCH] lib: mgmt_msg: hand handlers an aligned message
+Organization: Wires
+
+mgmt_msg_procbufs() passes handlers a pointer into the receive stream,
+where messages are packed back to back. A message following one whose
+length is not a multiple of 8 starts unaligned, and the native message
+structs have 64-bit members. On 32-bit Arm GCC may use a VFP store for
+those, e.g., `msg->refer_id = 0` in mgmt_fe_adapter_send_notify(), which
+the kernel cannot fix up, so mgmtd dies with SIGBUS:
+
+ Alignment trap: not handling instruction edc30b02 at [<00441bfc>]
+ Unhandled fault: alignment exception (0x801) at 0x023c3f8e
+
+Copy unaligned messages to an aligned buffer before calling the handler.
+
+---
+ lib/mgmt_msg.c | 27 +++++++++++++++++++++++----
+ 1 file changed, 23 insertions(+), 4 deletions(-)
+
+diff --git a/lib/mgmt_msg.c b/lib/mgmt_msg.c
+index 8f22b31231..c341872dc4 100644
+--- a/lib/mgmt_msg.c
++++ b/lib/mgmt_msg.c
+@@ -180,15 +180,34 @@ bool mgmt_msg_procbufs(struct mgmt_msg_state *ms,
+
+ for (; left > sizeof(struct mgmt_msg_hdr);
+ left -= mhdr->len, data += mhdr->len) {
++ uint8_t *msg, *copy = NULL;
++ size_t msglen;
++
+ mhdr = (struct mgmt_msg_hdr *)data;
+
+ assert(MGMT_MSG_IS_MARKER(mhdr->marker));
++ assert(mhdr->len >= sizeof(struct mgmt_msg_hdr));
+ assert(left >= mhdr->len);
+
+- handle_msg(MGMT_MSG_MARKER_VERSION(mhdr->marker),
+- (uint8_t *)(mhdr + 1),
+- mhdr->len - sizeof(struct mgmt_msg_hdr),
+- user);
++ msg = (uint8_t *)(mhdr + 1);
++ msglen = mhdr->len - sizeof(struct mgmt_msg_hdr);
++
++ /*
++ * Messages are packed back to back, so one following a
++ * message whose length is not a multiple of 8 starts
++ * unaligned. Handlers cast it to structs with 64-bit
++ * members, which traps on 32-bit Arm when the compiler
++ * uses VFP/NEON stores, so give them an aligned copy.
++ */
++ if ((uintptr_t)msg % sizeof(uint64_t)) {
++ copy = XMALLOC(MTYPE_TMP, msglen);
++ memcpy(copy, msg, msglen);
++ msg = copy;
++ }
++
++ handle_msg(MGMT_MSG_MARKER_VERSION(mhdr->marker), msg,
++ msglen, user);
++ XFREE(MTYPE_TMP, copy);
+ ms->nrxm++;
+ nproc++;
+ }
diff --git a/src/confd/src/Makefile.am b/src/confd/src/Makefile.am
index 6ea4efd2a..69ef9dbea 100644
--- a/src/confd/src/Makefile.am
+++ b/src/confd/src/Makefile.am
@@ -53,6 +53,7 @@ confd_plugin_la_SOURCES = \
keystore.c \
system.c \
support.c \
+ locate.c \
schedule.c \
ntp.c \
ptp.c \
diff --git a/src/confd/src/core.c b/src/confd/src/core.c
index 7ebf70d51..b89cf9e02 100644
--- a/src/confd/src/core.c
+++ b/src/confd/src/core.c
@@ -61,6 +61,24 @@ FILE *fopenfp(mode_t mode, const char *group, const char *fmt, ...)
return fp;
}
+/* The event session runs as confd itself, the caller is only known
+ * from the originator data: netopeer2 pushes the NETCONF session id
+ * and then the username, rousette pushes nothing */
+const char *rpc_user(sr_session_ctx_t *session, const char **via)
+{
+ const char *orig = sr_session_get_orig_name(session);
+ const void *data;
+ uint32_t size;
+
+ *via = orig && orig[0] ? orig : "local session";
+
+ if (orig && !strcmp(orig, "netopeer2") &&
+ !sr_session_get_orig_data(session, 1, &size, &data) && size)
+ return data;
+
+ return NULL;
+}
+
/*
* Touch a Finit service .conf file to schedule a synchronized reload.
* Equivalent to 'initctl touch ' but without the fork+exec overhead.
@@ -985,6 +1003,10 @@ int sr_plugin_init_cb(sr_session_ctx_t *session, void **priv)
if (rc)
goto err;
+ rc = locate_rpc_init(&confd);
+ if (rc)
+ goto err;
+
rc = syslog_rpc_init(&confd);
if (rc)
goto err;
diff --git a/src/confd/src/core.h b/src/confd/src/core.h
index 707c6f72a..e784e6a5b 100644
--- a/src/confd/src/core.h
+++ b/src/confd/src/core.h
@@ -215,6 +215,8 @@ int finit_enablef(const char *fmt, ...) __attribute__((format(printf, 1, 2)));
int finit_disablef(const char *fmt, ...) __attribute__((format(printf, 1, 2)));
int finit_deletef(const char *fmt, ...) __attribute__((format(printf, 1, 2)));
int finit_reloadf(const char *fmt, ...) __attribute__((format(printf, 1, 2)));
+
+const char *rpc_user(sr_session_ctx_t *session, const char **via);
FILE *fopenp(const char *path, mode_t mode, const char *group);
FILE *fopenfp(mode_t mode, const char *group, const char *fmt, ...) __attribute__((format(printf, 3, 4)));
@@ -278,6 +280,9 @@ int system_sw_rpc_init(struct confd *confd);
/* support.c */
int support_rpc_init(struct confd *confd);
+/* locate.c */
+int locate_rpc_init(struct confd *confd);
+
/* services.c */
int services_change(sr_session_ctx_t *session, struct lyd_node *config, struct lyd_node *diff, sr_event_t event, struct confd *confd);
diff --git a/src/confd/src/locate.c b/src/confd/src/locate.c
new file mode 100644
index 000000000..d28ccdde0
--- /dev/null
+++ b/src/confd/src/locate.c
@@ -0,0 +1,132 @@
+/* SPDX-License-Identifier: BSD-3-Clause */
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+#include
+#include
+
+#include "core.h"
+
+#define IITOD_SOCKET "/run/iitod.sock"
+#define IITOD_TIMEOUT 2 /* seconds */
+
+static int fail(sr_session_ctx_t *session, const char *msg)
+{
+ sr_session_set_netconf_error(session, "application", "operation-failed",
+ NULL, NULL, msg, 0);
+ return SR_ERR_OPERATION_FAILED;
+}
+
+/*
+ * One request, one reply, over the iitod control socket. Returns the
+ * parsed reply, or NULL with *err set. iitod is disabled on systems
+ * where kernel LED support is unreliable, then there is no socket.
+ */
+static json_t *iitod_call(json_t *req, const char **err)
+{
+ struct sockaddr_un sun = { .sun_family = AF_UNIX, .sun_path = IITOD_SOCKET };
+ struct timeval tv = { .tv_sec = IITOD_TIMEOUT };
+ char buf[4096], *msg;
+ json_t *reply = NULL;
+ size_t len = 0;
+ ssize_t n;
+ int sd;
+
+ sd = socket(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, 0);
+ if (sd < 0) {
+ *err = "failed creating socket";
+ return NULL;
+ }
+
+ /* Do not let a stuck iitod block confd, this also bounds connect() */
+ setsockopt(sd, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv));
+ setsockopt(sd, SOL_SOCKET, SO_SNDTIMEO, &tv, sizeof(tv));
+
+ if (connect(sd, (struct sockaddr *)&sun, sizeof(sun))) {
+ if (errno == ENOENT)
+ *err = "LED control is not available on this device";
+ else if (errno == ECONNREFUSED)
+ *err = "the LED daemon is not running";
+ else
+ *err = "failed connecting to the LED daemon";
+ goto done;
+ }
+
+ msg = json_dumps(req, JSON_COMPACT);
+ if (!msg || dprintf(sd, "%s\n", msg) < 0) {
+ *err = "failed sending request to the LED daemon";
+ free(msg);
+ goto done;
+ }
+ free(msg);
+
+ while (len < sizeof(buf) - 1) {
+ n = read(sd, &buf[len], sizeof(buf) - 1 - len);
+ if (n <= 0)
+ break;
+ len += n;
+ }
+ buf[len] = 0;
+
+ reply = json_loads(buf, 0, NULL);
+ if (!reply)
+ *err = "no valid reply from the LED daemon";
+done:
+ close(sd);
+ return reply;
+}
+
+static int rpc_locate(sr_session_ctx_t *session, uint32_t sub_id, const char *path,
+ const sr_val_t *input, const size_t input_cnt, sr_event_t event,
+ unsigned request_id, sr_val_t **output, size_t *output_cnt, void *priv)
+{
+ const char *err, *user, *via;
+ uint32_t duration = 0;
+ json_t *req, *reply;
+ bool enable = false;
+ int rc = SR_ERR_OK;
+
+ if (event != SR_EV_RPC)
+ return SR_ERR_OK;
+
+ /* sysrepo passes the YANG defaults for leaves not given */
+ for (size_t i = 0; i < input_cnt; i++) {
+ const char *leaf = strrchr(input[i].xpath, '/');
+
+ if (!leaf)
+ continue;
+ if (!strcmp(leaf, "/enable"))
+ enable = input[i].data.bool_val;
+ else if (!strcmp(leaf, "/duration"))
+ duration = input[i].data.uint32_val;
+ }
+
+ user = rpc_user(session, &via);
+ AUDIT("Locate %s by user \"%s\" over %s.", enable ? "started" : "stopped",
+ user ?: "unknown", via);
+
+ /* iitod only arms the timeout when enabling */
+ req = json_pack("{s:s, s:{s:b, s:I}}", "method", "locate", "params",
+ "enable", enable, "timeout", (json_int_t)duration);
+ reply = iitod_call(req, &err);
+ json_decref(req);
+ if (!reply)
+ return fail(session, err);
+
+ if (json_object_get(reply, "error"))
+ rc = fail(session, json_string_value(json_object_get(reply, "error")) ? : "failed");
+
+ json_decref(reply);
+ return rc;
+}
+
+int locate_rpc_init(struct confd *confd)
+{
+ return register_rpc(confd->session, "/infix-hardware:locate",
+ rpc_locate, NULL, &confd->sub);
+}
diff --git a/src/confd/src/support.c b/src/confd/src/support.c
index 089e0f711..6a5c2d313 100644
--- a/src/confd/src/support.c
+++ b/src/confd/src/support.c
@@ -171,24 +171,6 @@ static void cleanup(const char *dir)
WARN("Cannot remove %s: %s", dir, strerror(errno));
}
-/* The event session runs as confd itself, the caller is only known
- * from the originator data: netopeer2 pushes the NETCONF session id
- * and then the username, rousette pushes nothing */
-static const char *rpc_user(sr_session_ctx_t *session, const char **via)
-{
- const char *orig = sr_session_get_orig_name(session);
- const void *data;
- uint32_t size;
-
- *via = orig && orig[0] ? orig : "local session";
-
- if (orig && !strcmp(orig, "netopeer2") &&
- !sr_session_get_orig_data(session, 1, &size, &data) && size)
- return data;
-
- return NULL;
-}
-
static int add_str(sr_val_t **output, size_t *cnt, const char *path,
const char *leaf, sr_val_type_t type, const char *val)
{
diff --git a/src/confd/yang/confd.inc b/src/confd/yang/confd.inc
index 054dca82b..24e265530 100644
--- a/src/confd/yang/confd.inc
+++ b/src/confd/yang/confd.inc
@@ -27,7 +27,7 @@ MODULES=(
"infix-syslog@2026-09-24.yang"
"iana-hardware@2018-03-13.yang"
"ietf-hardware@2018-03-13.yang -e hardware-state -e hardware-sensor"
- "infix-hardware@2026-09-24.yang"
+ "infix-hardware@2026-10-04.yang"
"ieee802-dot1q-types@2022-10-29.yang"
"infix-ip@2026-04-28.yang"
"infix-if-type@2026-01-07.yang"
diff --git a/src/confd/yang/confd/infix-hardware.yang b/src/confd/yang/confd/infix-hardware.yang
index 6769774b6..a35c38c23 100644
--- a/src/confd/yang/confd/infix-hardware.yang
+++ b/src/confd/yang/confd/infix-hardware.yang
@@ -21,6 +21,11 @@ module infix-hardware {
contact "kernelkit@googlegroups.com";
description "Vital Product Data augmentation of ieee-hardware and deviations.";
+ revision 2026-10-04 {
+ description "Add locate RPC, blinks LEDs to identify the chassis.";
+ reference "internal";
+ }
+
revision 2026-09-24 {
description "Constrain the character set of hardware component names.";
reference "internal";
@@ -747,4 +752,32 @@ module infix-hardware {
}
}
}
+
+ rpc locate {
+ description
+ "Blink the LEDs of the chassis to physically identify it, e.g., in
+ a rack. Which LEDs blink, and how, is defined per board. Locate
+ stops by itself after the given duration, or when called again
+ with enable set to false.
+
+ Fails on systems where LED control is disabled, the LEDs then keep
+ their hardware default behavior.";
+
+ input {
+ leaf enable {
+ type boolean;
+ default true;
+ description "Start, or stop, blinking the LEDs.";
+ }
+
+ leaf duration {
+ type uint32 {
+ range "1..86400";
+ }
+ units "seconds";
+ default 60;
+ description "Stop blinking by itself after this many seconds.";
+ }
+ }
+ }
}
diff --git a/src/confd/yang/confd/infix-hardware@2026-09-24.yang b/src/confd/yang/confd/infix-hardware@2026-10-04.yang
similarity index 100%
rename from src/confd/yang/confd/infix-hardware@2026-09-24.yang
rename to src/confd/yang/confd/infix-hardware@2026-10-04.yang
diff --git a/src/klish-plugin-infix/xml/infix.xml b/src/klish-plugin-infix/xml/infix.xml
index 105700037..1f897bf5d 100644
--- a/src/klish-plugin-infix/xml/infix.xml
+++ b/src/klish-plugin-infix/xml/infix.xml
@@ -275,6 +275,24 @@
+
+
+
+
+
+
+
+
+ set -- /infix-hardware:locate
+ if [ -n "$KLISH_PARAM_stop" ]; then
+ set -- "$@" enable false
+ elif [ -n "$KLISH_PARAM_seconds" ]; then
+ set -- "$@" duration "$KLISH_PARAM_seconds"
+ fi
+ doas -u "$KLISH_USER" rpc "$@"
+
+
+
/ietf-system:system-shutdown
@@ -1099,6 +1117,9 @@ echo "Public: $pub"
+
+ doas -u "$KLISH_USER" rpc /infix-hardware:locate enable false
+
diff --git a/src/statd/python/yanger/ietf_hardware.py b/src/statd/python/yanger/ietf_hardware.py
index 9c25bc82c..548bd1b1a 100644
--- a/src/statd/python/yanger/ietf_hardware.py
+++ b/src/statd/python/yanger/ietf_hardware.py
@@ -128,6 +128,7 @@ def normalize_sensor_name(name):
cpu_thermal -> cpu-thermal
s5_temp -> s5-temp
pwmfan -> pwmfan
+ SoC temperature -> SoC-temperature
Strategy:
1. Drop the vendor/chipset prefix of a per-port device, where the
@@ -151,7 +152,11 @@ def normalize_sensor_name(name):
# Remove underscores before trailing numbers (sfp_2 -> sfp2)
name = re.sub(r'_(\d+)$', r'\1', name)
- return name.replace("_", "-")
+ # Labels are free text, e.g. "SoC temperature", but a name must
+ # match the YANG pattern [a-zA-Z0-9_][a-zA-Z0-9_.:+@-]*
+ name = re.sub(r'[^a-zA-Z0-9.:+@]+', '-', name.replace("_", "-"))
+
+ return name.strip("-")
CPU_COMPONENT = "cpu"
diff --git a/src/webui/internal/handlers/hardware.go b/src/webui/internal/handlers/hardware.go
index 8748926f0..30c679d61 100644
--- a/src/webui/internal/handlers/hardware.go
+++ b/src/webui/internal/handlers/hardware.go
@@ -4,6 +4,7 @@ package handlers
import (
"context"
+ "fmt"
"html/template"
"log"
"net/http"
@@ -96,6 +97,19 @@ type HardwareHandler struct {
RC *restconf.Client
}
+// Locate blinks LEDs to identify the chassis, for the duration
+// given by the YANG default of the infix-hardware:locate RPC.
+func (h *HardwareHandler) Locate(w http.ResponseWriter, r *http.Request) {
+ w.Header().Set("Content-Type", "text/html")
+ if err := h.RC.Post(r.Context(), "/operations/infix-hardware:locate"); err != nil {
+ log.Printf("locate: %v", err)
+ fmt.Fprintf(w, `Failed: %s`,
+ template.HTMLEscapeString(err.Error()))
+ return
+ }
+ fmt.Fprint(w, `✓ Locating, LEDs are blinking`)
+}
+
func (h *HardwareHandler) Overview(w http.ResponseWriter, r *http.Request) {
data := hardwarePageData{
PageData: newPageData(w, r, "hardware", "Hardware"),
diff --git a/src/webui/internal/server/server.go b/src/webui/internal/server/server.go
index 6f95b8425..61aee687a 100644
--- a/src/webui/internal/server/server.go
+++ b/src/webui/internal/server/server.go
@@ -309,6 +309,7 @@ func New(
mux.HandleFunc("GET /routing", routing.Overview)
mux.HandleFunc("GET /wifi", wifi.Overview)
mux.HandleFunc("GET /hardware", hw.Overview)
+ mux.HandleFunc("POST /hardware/locate", hw.Locate)
mux.HandleFunc("GET /vpn", vpn.Overview)
mux.HandleFunc("GET /dhcp", dhcp.Overview)
mux.HandleFunc("GET /ntp", ntp.Overview)
diff --git a/src/webui/templates/pages/hardware.html b/src/webui/templates/pages/hardware.html
index cdc598d6e..7983180d7 100644
--- a/src/webui/templates/pages/hardware.html
+++ b/src/webui/templates/pages/hardware.html
@@ -12,7 +12,13 @@
{{/* ── Board ─────────────────────────────────────────────────────────── */}}
{{if .Board.Model}}
-
+
+
| Model | {{.Board.Model}} |
diff --git a/test/case/statd/sensors/cli/show-hardware b/test/case/statd/sensors/cli/show-hardware
index fb22cbb1e..d906fa0a6 100644
--- a/test/case/statd/sensors/cli/show-hardware
+++ b/test/case/statd/sensors/cli/show-hardware
@@ -13,3 +13,4 @@ cpu:
S5 Temp [32m59.5 °C[39m ok
pwmfan 3200 RPM ok
+Soc Temperature [32m41.2 °C[39m ok
diff --git a/test/case/statd/sensors/ietf-hardware.json b/test/case/statd/sensors/ietf-hardware.json
index 144df05d7..fd93712b3 100644
--- a/test/case/statd/sensors/ietf-hardware.json
+++ b/test/case/statd/sensors/ietf-hardware.json
@@ -68,6 +68,19 @@
"value-timestamp": "2026-01-02T20:43:23+00:00",
"oper-status": "ok"
}
+ },
+ {
+ "name": "sun4i-ts-SoC-temperature",
+ "class": "iana-hardware:sensor",
+ "sensor-data": {
+ "value": 41200,
+ "value-type": "celsius",
+ "value-scale": "milli",
+ "value-precision": 0,
+ "value-timestamp": "2026-01-02T20:43:23+00:00",
+ "oper-status": "ok"
+ },
+ "description": "Soc Temperature"
}
]
}
diff --git a/test/case/statd/sensors/operational.json b/test/case/statd/sensors/operational.json
index 144df05d7..fd93712b3 100644
--- a/test/case/statd/sensors/operational.json
+++ b/test/case/statd/sensors/operational.json
@@ -68,6 +68,19 @@
"value-timestamp": "2026-01-02T20:43:23+00:00",
"oper-status": "ok"
}
+ },
+ {
+ "name": "sun4i-ts-SoC-temperature",
+ "class": "iana-hardware:sensor",
+ "sensor-data": {
+ "value": 41200,
+ "value-type": "celsius",
+ "value-scale": "milli",
+ "value-precision": 0,
+ "value-timestamp": "2026-01-02T20:43:23+00:00",
+ "oper-status": "ok"
+ },
+ "description": "Soc Temperature"
}
]
}
diff --git a/test/case/statd/sensors/system/rootfs/sys/class/hwmon/hwmon3/name b/test/case/statd/sensors/system/rootfs/sys/class/hwmon/hwmon3/name
new file mode 100644
index 000000000..80a569b99
--- /dev/null
+++ b/test/case/statd/sensors/system/rootfs/sys/class/hwmon/hwmon3/name
@@ -0,0 +1 @@
+sun4i_ts
diff --git a/test/case/statd/sensors/system/rootfs/sys/class/hwmon/hwmon3/temp1_input b/test/case/statd/sensors/system/rootfs/sys/class/hwmon/hwmon3/temp1_input
new file mode 100644
index 000000000..1a112cfa2
--- /dev/null
+++ b/test/case/statd/sensors/system/rootfs/sys/class/hwmon/hwmon3/temp1_input
@@ -0,0 +1 @@
+41200
diff --git a/test/case/statd/sensors/system/rootfs/sys/class/hwmon/hwmon3/temp1_label b/test/case/statd/sensors/system/rootfs/sys/class/hwmon/hwmon3/temp1_label
new file mode 100644
index 000000000..8d9a877a5
--- /dev/null
+++ b/test/case/statd/sensors/system/rootfs/sys/class/hwmon/hwmon3/temp1_label
@@ -0,0 +1 @@
+SoC temperature
diff --git a/test/case/statd/sensors/system/run/ls_+sys+class+hwmon b/test/case/statd/sensors/system/run/ls_+sys+class+hwmon
index b51997471..324efcb17 100644
--- a/test/case/statd/sensors/system/run/ls_+sys+class+hwmon
+++ b/test/case/statd/sensors/system/run/ls_+sys+class+hwmon
@@ -1,3 +1,4 @@
hwmon0
hwmon1
hwmon2
+hwmon3
diff --git a/test/case/statd/sensors/system/run/ls_+sys+class+hwmon+hwmon3 b/test/case/statd/sensors/system/run/ls_+sys+class+hwmon+hwmon3
new file mode 100644
index 000000000..27c20f9a3
--- /dev/null
+++ b/test/case/statd/sensors/system/run/ls_+sys+class+hwmon+hwmon3
@@ -0,0 +1,3 @@
+name
+temp1_input
+temp1_label
diff --git a/utils/mkimage.sh b/utils/mkimage.sh
index 7738e4277..c942744e9 100755
--- a/utils/mkimage.sh
+++ b/utils/mkimage.sh
@@ -185,6 +185,9 @@ get_bootloader_name()
acer-connect-vero-w6m)
echo "bpi_r3_emmc_boot"
;;
+ bananapi-bpi-m1)
+ echo "bpi_m1_boot"
+ ;;
bananapi-bpi-r3)
if [ "$target" = "emmc" ]; then
echo "bpi_r3_emmc_boot"