diff --git a/.github/workflows/build-boot.yml b/.github/workflows/build-boot.yml index 26b62920c..6c3a058c2 100644 --- a/.github/workflows/build-boot.yml +++ b/.github/workflows/build-boot.yml @@ -17,6 +17,7 @@ jobs: matrix: defconfig: - aarch64_qemu_boot + - bpi_m1_boot - bpi_r3_sd_boot - bpi_r3_emmc_boot - bpi_r4_sd_boot diff --git a/.github/workflows/build-image.yml b/.github/workflows/build-image.yml index d13fe1415..8c0625200 100644 --- a/.github/workflows/build-image.yml +++ b/.github/workflows/build-image.yml @@ -10,6 +10,7 @@ on: options: - raspberrypi-rpi2 - raspberrypi-rpi64 + - bananapi-bpi-m1 - bananapi-bpi-r3 - bananapi-bpi-r4 - bananapi-bpi-r64 @@ -69,6 +70,11 @@ jobs: targets="sdcard" bootloader_sdcard=rpi64-boot ;; + bananapi-bpi-m1) + arch=arm + targets="sdcard" + bootloader_sdcard=bpi-m1-boot + ;; bananapi-bpi-r3) arch=aarch64 targets="sdcard emmc" diff --git a/Makefile b/Makefile index 16d5d6f7d..a801128a8 100644 --- a/Makefile +++ b/Makefile @@ -11,6 +11,10 @@ override O := $(if $(filter /%,$O),$O,$(CURDIR)/$O) config := $(O)/.config bmake = $(MAKE) -C buildroot O=$(O) $1 + +# Goals depend on each other, e.g. 'make foo_defconfig apply-mirror', +# so run them in order. Like Buildroot, -j still applies to each goal. +.NOTPARALLEL: SNIPPETS_DIR := $(CURDIR)/configs/snippets MERGE_CONFIG := $(CURDIR)/buildroot/support/kconfig/merge_config.sh diff --git a/board/arm/Config.in b/board/arm/Config.in index 7d30b9695..d465cee1f 100644 --- a/board/arm/Config.in +++ b/board/arm/Config.in @@ -1,5 +1,6 @@ if BR2_arm +source "$BR2_EXTERNAL_INFIX_PATH/board/arm/bananapi-bpi-m1/Config.in" source "$BR2_EXTERNAL_INFIX_PATH/board/arm/microchip-sama7g54-ek/Config.in" source "$BR2_EXTERNAL_INFIX_PATH/board/arm/raspberrypi-rpi2/Config.in" diff --git a/board/arm/README.md b/board/arm/README.md index 6058d7ee3..0768b6bef 100644 --- a/board/arm/README.md +++ b/board/arm/README.md @@ -4,5 +4,6 @@ Arm 32-bit Board Specific Documentation ---------------------------- +- [Banana Pi BPI-M1 (32-bit)](bananapi-bpi-m1/) - [Microchip SAMA7G54-EK (32-bit)](microchip-sama7g54-ek/) - [Raspberry Pi 2 Model B (32-bit)](raspberrypi-rpi2/) diff --git a/board/arm/bananapi-bpi-m1/Config.in b/board/arm/bananapi-bpi-m1/Config.in new file mode 100644 index 000000000..e9545ba29 --- /dev/null +++ b/board/arm/bananapi-bpi-m1/Config.in @@ -0,0 +1,9 @@ +config BR2_PACKAGE_BANANAPI_BPI_M1 + bool "Banana Pi BPI-M1" + depends on BR2_arm + select SDCARD_AUX + select BR2_PACKAGE_INPUT_EVENT_DAEMON + help + Support for the Banana Pi BPI-M1 single-board computer, the + first Banana Pi, with an Allwinner A20 dual-core Cortex-A7, + 1 GiB DDR3, Gigabit Ethernet, SATA and an SD card slot. diff --git a/board/arm/bananapi-bpi-m1/LICENSE b/board/arm/bananapi-bpi-m1/LICENSE new file mode 100644 index 000000000..8cdb30a3a --- /dev/null +++ b/board/arm/bananapi-bpi-m1/LICENSE @@ -0,0 +1,13 @@ +Copyright (c) 2026 The KernelKit Authors + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. diff --git a/board/arm/bananapi-bpi-m1/README.md b/board/arm/bananapi-bpi-m1/README.md new file mode 100644 index 000000000..f9e383566 --- /dev/null +++ b/board/arm/bananapi-bpi-m1/README.md @@ -0,0 +1,114 @@ +Banana Pi BPI-M1 +================ + +The [BPI-M1][1] is the first Banana Pi board, powered by the Allwinner +A20 dual-core Cortex-A7 @ 1 GHz with 1 GB DDR3 RAM. + +The board features: + +- Gigabit Ethernet +- SD card slot (full size) +- SATA 2.0 port +- 2x USB 2.0 host, 1x micro USB OTG +- HDMI, composite video and audio out +- 26-pin GPIO header + +> [!NOTE] +> The BPI-M1+ is a different board with its own device tree and is not +> covered by this BSP. + +How to Build +------------ + +There are no pre-built images for ARM 32-bit, so build both Infix and +the bootloader from source. + +1. Build the bootloader + + make O=x-boot-bpi-m1 bpi_m1_boot_defconfig + make O=x-boot-bpi-m1 + +2. Build Infix + + make O=x-arm arm_defconfig + make O=x-arm + +3. Create the SD card image + + ./utils/mkimage.sh -b x-boot-bpi-m1 -r x-arm bananapi-bpi-m1 + +To test only the bootloader, e.g., to verify the console, create a +boot-only image after step 1: + + ./utils/mkimage.sh -B -b x-boot-bpi-m1 bananapi-bpi-m1 + +Flashing to SD Card +------------------- + +[Flash the image][0] to an SD card (at least 2 GB): + +```bash +sudo bmaptool copy x-boot-bpi-m1/images/infix-bpi-m1-sdcard.img /dev/sdX +``` + +> [!WARNING] +> Ensure `/dev/sdX` is the correct device for your SD card and not used +> by the host system! Use `lsblk` to verify. + +Booting the Board +----------------- + +1. Insert the flashed SD card +2. Connect an Ethernet cable +3. Power up the board, 5V DC via the micro USB port +4. Find the assigned IP and SSH in, default login: `admin` / `admin` + +The board has no user button, so U-Boot always runs in developer mode. +Use `bootmenu` on the console for factory reset. + +LEDs and Buttons +---------------- + +| **Stage** | **USR** (green) | +|----------------|-----------------| +| U-Boot | on | +| System loading | 1 Hz | +| System up | on | +| Locate | 10 Hz | +| Fail safe | 5 Hz | +| Panic | 5 Hz | + +The Ethernet port LEDs show link and activity. + +The power button powers on the board, and a short press when running +powers it off. The reset button is a hardware reset. + +Unsupported Features +-------------------- + +The following hardware has no driver enabled in Infix: + +- Analog audio and composite video out +- HDMI audio, not supported by mainline Linux on the A20 +- IR receiver +- Micro USB OTG port +- Mali-400 GPU and video decoder + +Console Port +------------ + +UART pins + +The debug console is UART0, TX and RX on header J11 and GND on the +neighboring header J12: + +- Baud rate: 115200 +- Data bits: 8 +- Parity: None +- Stop bits: 1 + +> [!WARNING] +> Use only 3.3V serial adapters. + +[0]: https://www.kernelkit.org/posts/flashing-sdcard/ +[1]: https://docs.banana-pi.org/en/BPI-M1/BananaPi_BPI-M1 diff --git a/board/arm/bananapi-bpi-m1/bananapi-bpi-m1.hash b/board/arm/bananapi-bpi-m1/bananapi-bpi-m1.hash new file mode 100644 index 000000000..2b1e74432 --- /dev/null +++ b/board/arm/bananapi-bpi-m1/bananapi-bpi-m1.hash @@ -0,0 +1,2 @@ +# Locally calculated +sha256 d48246c717b505cc11df95171f2fd548b389e1a463f1af4c68d0b69fe0d1009b LICENSE diff --git a/board/arm/bananapi-bpi-m1/bananapi-bpi-m1.mk b/board/arm/bananapi-bpi-m1/bananapi-bpi-m1.mk new file mode 100644 index 000000000..7259b6fa7 --- /dev/null +++ b/board/arm/bananapi-bpi-m1/bananapi-bpi-m1.mk @@ -0,0 +1,84 @@ +# Banana Pi BPI-M1 kernel configuration fixups +define BANANAPI_BPI_M1_LINUX_CONFIG_FIXUPS + # Allwinner A20 SoC + $(call KCONFIG_ENABLE_OPT,CONFIG_ARCH_SUNXI) + $(call KCONFIG_ENABLE_OPT,CONFIG_MACH_SUN7I) + $(call KCONFIG_ENABLE_OPT,CONFIG_SUNXI_SRAM) + + # Serial console + $(call KCONFIG_ENABLE_OPT,CONFIG_SERIAL_8250) + $(call KCONFIG_ENABLE_OPT,CONFIG_SERIAL_8250_CONSOLE) + $(call KCONFIG_ENABLE_OPT,CONFIG_SERIAL_8250_DW) + + # Network: GMAC with Realtek PHY + $(call KCONFIG_ENABLE_OPT,CONFIG_NET_VENDOR_STMICRO) + $(call KCONFIG_ENABLE_OPT,CONFIG_STMMAC_ETH) + $(call KCONFIG_ENABLE_OPT,CONFIG_STMMAC_PLATFORM) + $(call KCONFIG_ENABLE_OPT,CONFIG_DWMAC_SUNXI) + $(call KCONFIG_ENABLE_OPT,CONFIG_REALTEK_PHY) + + # MMC/SD and SATA + $(call KCONFIG_ENABLE_OPT,CONFIG_MMC_SUNXI) + $(call KCONFIG_ENABLE_OPT,CONFIG_AHCI_SUNXI) + + # AXP209 PMIC, supplies the CPU and USB VBUS regulators + $(call KCONFIG_ENABLE_OPT,CONFIG_I2C) + $(call KCONFIG_ENABLE_OPT,CONFIG_I2C_MV64XXX) + $(call KCONFIG_ENABLE_OPT,CONFIG_MFD_AXP20X_I2C) + $(call KCONFIG_ENABLE_OPT,CONFIG_REGULATOR_AXP20X) + + # USB Host, the PHY depends on POWER_SUPPLY, with keyboard and mouse + $(call KCONFIG_ENABLE_OPT,CONFIG_POWER_SUPPLY) + $(call KCONFIG_ENABLE_OPT,CONFIG_PHY_SUN4I_USB) + $(call KCONFIG_SET_OPT,CONFIG_USB_EHCI_HCD,m) + $(call KCONFIG_SET_OPT,CONFIG_USB_EHCI_HCD_PLATFORM,m) + $(call KCONFIG_SET_OPT,CONFIG_USB_OHCI_HCD,m) + $(call KCONFIG_SET_OPT,CONFIG_USB_OHCI_HCD_PLATFORM,m) + $(call KCONFIG_ENABLE_OPT,CONFIG_USB_HID) + $(call KCONFIG_SET_OPT,CONFIG_HID_GENERIC,m) + + # Power button on the AXP209, KEY_POWER for input-event-daemon + $(call KCONFIG_ENABLE_OPT,CONFIG_INPUT_MISC) + $(call KCONFIG_ENABLE_OPT,CONFIG_INPUT_AXP20X_PEK) + + # SPI header + $(call KCONFIG_ENABLE_OPT,CONFIG_SPI_SUN4I) + + # Temperature: SoC sensor in the touchscreen controller, used by + # the cpu-thermal zone to throttle, and AXP209 die temp over IIO + $(call KCONFIG_ENABLE_OPT,CONFIG_TOUCHSCREEN_SUN4I) + $(call KCONFIG_ENABLE_OPT,CONFIG_CPU_THERMAL) + $(call KCONFIG_ENABLE_OPT,CONFIG_IIO) + $(call KCONFIG_ENABLE_OPT,CONFIG_AXP20X_ADC) + $(call KCONFIG_ENABLE_OPT,CONFIG_SENSORS_IIO_HWMON) + + # LEDs: green user LED on GPIO, RTL8211E PHY LEDs offloaded to + # the PHY with the netdev trigger + $(call KCONFIG_ENABLE_OPT,CONFIG_LEDS_GPIO) + $(call KCONFIG_ENABLE_OPT,CONFIG_LEDS_TRIGGER_NETDEV) + + # HDMI output with framebuffer console, no HDMI audio in mainline + $(call KCONFIG_ENABLE_OPT,CONFIG_DRM) + $(call KCONFIG_SET_OPT,CONFIG_DRM_SUN4I,m) + $(call KCONFIG_SET_OPT,CONFIG_DRM_SUN4I_HDMI,m) + $(call KCONFIG_ENABLE_OPT,CONFIG_DRM_SUN4I_HDMI_CEC) + $(call KCONFIG_SET_OPT,CONFIG_DRM_SUN4I_BACKEND,m) + $(call KCONFIG_ENABLE_OPT,CONFIG_DRM_FBDEV_EMULATION) + $(call KCONFIG_ENABLE_OPT,CONFIG_FB) + $(call KCONFIG_ENABLE_OPT,CONFIG_FRAMEBUFFER_CONSOLE) + + # Watchdog, RTC and SID (unique chip ID) + $(call KCONFIG_ENABLE_OPT,CONFIG_WATCHDOG) + $(call KCONFIG_ENABLE_OPT,CONFIG_SUNXI_WATCHDOG) + $(call KCONFIG_ENABLE_OPT,CONFIG_RTC_CLASS) + $(call KCONFIG_ENABLE_OPT,CONFIG_RTC_DRV_SUNXI) + $(call KCONFIG_ENABLE_OPT,CONFIG_NVMEM) + $(call KCONFIG_ENABLE_OPT,CONFIG_NVMEM_SUNXI_SID) + + # Crypto hardware acceleration + $(call KCONFIG_ENABLE_OPT,CONFIG_CRYPTO_DEV_ALLWINNER) + $(call KCONFIG_ENABLE_OPT,CONFIG_CRYPTO_DEV_SUN4I_SS) +endef + +$(eval $(ix-board)) +$(eval $(generic-package)) diff --git a/board/arm/bananapi-bpi-m1/dts/Makefile b/board/arm/bananapi-bpi-m1/dts/Makefile new file mode 100644 index 000000000..eeac3f151 --- /dev/null +++ b/board/arm/bananapi-bpi-m1/dts/Makefile @@ -0,0 +1 @@ +dtb-y += allwinner/sun7i-a20-bananapi.dtb diff --git a/board/arm/bananapi-bpi-m1/dts/allwinner/infix.dtsi b/board/arm/bananapi-bpi-m1/dts/allwinner/infix.dtsi new file mode 100644 index 000000000..16cfafc86 --- /dev/null +++ b/board/arm/bananapi-bpi-m1/dts/allwinner/infix.dtsi @@ -0,0 +1,13 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Common Infix OS defaults + */ + +/ { + chosen { + infix { + /* Default admin user password: 'admin' */ + factory-password-hash = "$5$mI/zpOAqZYKLC2WU$i7iPzZiIjOjrBF3NyftS9CCq8dfYwHwrmUK097Jca9A"; + }; + }; +}; diff --git a/board/arm/bananapi-bpi-m1/dts/allwinner/sun7i-a20-bananapi.dts b/board/arm/bananapi-bpi-m1/dts/allwinner/sun7i-a20-bananapi.dts new file mode 100644 index 000000000..662940942 --- /dev/null +++ b/board/arm/bananapi-bpi-m1/dts/allwinner/sun7i-a20-bananapi.dts @@ -0,0 +1,21 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Infix OS device tree for Banana Pi BPI-M1 + */ + +#include +#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}}
-
Board
+
Board + +
+
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 59.5 °C ok pwmfan 3200 RPM ok +Soc Temperature 41.2 °C 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"
Model{{.Board.Model}}