Merge pull request #1356 from kernelkit/nanopi

Misc. fixes for NanoPi R2S
This commit is contained in:
Joachim Wiberg
2026-01-19 07:47:17 +01:00
committed by GitHub
7 changed files with 182 additions and 57 deletions
@@ -4,6 +4,7 @@ config BR2_PACKAGE_FRIENDLYARM_NANOPI_R2S
select SDCARD_AUX
select BR2_PACKAGE_INPUT_EVENT_DAEMON
select BR2_PACKAGE_LINUX_FIRMWARE_RTL_815X
select BR2_PACKAGE_INPUT_EVENT_DAEMON
help
FriendlyElec NanoPi R2S is a compact router board based on
the Rockchip RK3328 SoC with dual Gigabit Ethernet ports.
@@ -4,6 +4,9 @@ define FRIENDLYARM_NANOPI_R2S_LINUX_CONFIG_FIXUPS
$(call KCONFIG_ENABLE_OPT,CONFIG_ROCKCHIP_IOMMU)
$(call KCONFIG_ENABLE_OPT,CONFIG_ROCKCHIP_PM_DOMAINS)
$(call KCONFIG_ENABLE_OPT,CONFIG_ROCKCHIP_IODOMAIN)
# TODO: for some reason this just locks up the device
# so we'll have to rely on the softdog :-(
# $(call KCONFIG_ENABLE_OPT,CONFIG_DW_WATCHDOG)
# PHY drivers
$(call KCONFIG_ENABLE_OPT,CONFIG_PHY_ROCKCHIP_EMMC)
@@ -51,6 +54,10 @@ define FRIENDLYARM_NANOPI_R2S_LINUX_CONFIG_FIXUPS
$(call KCONFIG_SET_OPT,CONFIG_NVMEM_ROCKCHIP_EFUSE,m)
$(call KCONFIG_SET_OPT,CONFIG_NVMEM_ROCKCHIP_OTP,m)
# Input layer
$(call KCONFIG_ENABLE_OPT,CONFIG_INPUT_MISC)
$(call KCONFIG_ENABLE_OPT,CONFIG_INPUT_RK805_PWRKEY)
# Network: STMMAC Ethernet (WAN port - RK3328 GMAC with RTL8211E PHY)
$(call KCONFIG_ENABLE_OPT,CONFIG_NET_VENDOR_STMICRO)
$(call KCONFIG_ENABLE_OPT,CONFIG_STMMAC_ETH)
@@ -0,0 +1,131 @@
From c4463ec64f9732bd67b8cfd2b73adc10e50d1178 Mon Sep 17 00:00:00 2001
From: Joachim Wiberg <troglobit@gmail.com>
Date: Sun, 18 Jan 2026 23:26:37 +0100
Subject: [PATCH] initctl: escape special characters in JSON output
Organization: Wires
Strings like command, description, and environment may contain characters
that need escaping for valid JSON, e.g., embedded quotes in command line
arguments like -V "NanoPi R2S".
Add json_escape() helper to handle quotes, backslashes, and control chars.
Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
---
src/initctl.c | 78 +++++++++++++++++++++++++++++++++++++++++++++------
1 file changed, 69 insertions(+), 9 deletions(-)
diff --git a/src/initctl.c b/src/initctl.c
index 25c52cf..435e2da 100644
--- a/src/initctl.c
+++ b/src/initctl.c
@@ -1036,6 +1036,62 @@ static int svc_compare(svc_t *svc, char *arg)
return 0;
}
+/*
+ * Escape a string for safe JSON output. Handles quotes, backslashes,
+ * and control characters. Returns pointer to static buffer.
+ */
+static char *json_escape(const char *str)
+{
+ static char buf[1024];
+ char *ptr = buf;
+ size_t left = sizeof(buf) - 1;
+
+ if (!str)
+ return "";
+
+ while (*str && left > 1) {
+ char c = *str++;
+
+ switch (c) {
+ case '"':
+ case '\\':
+ *ptr++ = '\\';
+ *ptr++ = c;
+ left -= 2;
+ break;
+ case '\n':
+ *ptr++ = '\\';
+ *ptr++ = 'n';
+ left -= 2;
+ break;
+ case '\r':
+ *ptr++ = '\\';
+ *ptr++ = 'r';
+ left -= 2;
+ break;
+ case '\t':
+ *ptr++ = '\\';
+ *ptr++ = 't';
+ left -= 2;
+ break;
+ default:
+ if ((unsigned char)c < 0x20) {
+ /* Other control chars: use \uXXXX */
+ int n = snprintf(ptr, left, "\\u%04x", (unsigned char)c);
+ ptr += n;
+ left -= n;
+ } else {
+ *ptr++ = c;
+ left--;
+ }
+ break;
+ }
+ }
+ *ptr = '\0';
+
+ return buf;
+}
+
static int json_status_one(FILE *fp, svc_t *svc, char *indent, int prev)
{
long now = jiffies();
@@ -1051,14 +1107,16 @@ static int json_status_one(FILE *fp, svc_t *svc, char *indent, int prev)
fprintf(fp,
"%s"
"%s{\n"
- "%s \"identity\": \"%s\",\n"
- "%s \"description\": \"%s\",\n"
+ "%s \"identity\": \"%s\",\n",
+ prev ? ",\n" : indent, prev ? indent : "",
+ indent, svc_ident(svc, NULL, 0));
+ fprintf(fp,
+ "%s \"description\": \"%s\",\n",
+ indent, json_escape(svc->desc));
+ fprintf(fp,
"%s \"type\": \"%s\",\n"
"%s \"forking\": %s,\n"
"%s \"status\": \"%s\",\n",
- prev ? ",\n" : indent, prev ? indent : "",
- indent, svc_ident(svc, NULL, 0),
- indent, svc->desc,
indent, svc_typestr(svc),
indent, svc->forking ? "true" : "false",
indent, svc_status(svc));
@@ -1080,15 +1138,17 @@ static int json_status_one(FILE *fp, svc_t *svc, char *indent, int prev)
}
fprintf(fp,
- "%s \"origin\": \"%s\",\n"
+ "%s \"origin\": \"%s\",\n",
+ indent, svc->file[0] ? svc->file : "built-in");
+ svc_command(svc, buf, sizeof(buf), 0);
+ fprintf(fp,
"%s \"command\": \"%s\",\n",
- indent, svc->file[0] ? svc->file : "built-in",
- indent, svc_command(svc, buf, sizeof(buf), 0));
+ indent, json_escape(buf));
svc_environ(svc, buf, sizeof(buf), 0);
if (buf[0])
fprintf(fp,
- "%s \"environment\": \"%s\",\n", indent, buf);
+ "%s \"environment\": \"%s\",\n", indent, json_escape(buf));
svc_cond(svc, buf, sizeof(buf), 0);
if (buf[0])
--
2.43.0
+24 -37
View File
@@ -161,7 +161,7 @@ class PadDhcpServer:
class PadSensor:
name = 30
value = 20
value = 22
status = 10
@classmethod
@@ -186,14 +186,6 @@ class PadLldp:
port_id = 20
class PadDiskUsage:
mount = 18
size = 12
used = 12
avail = 12
percent = 6
def format_memory_bytes(bytes_val):
"""Convert bytes to human-readable format"""
if bytes_val == 0:
@@ -221,11 +213,6 @@ def format_uptime_seconds(seconds):
else:
return f"{seconds // 86400}d"
@classmethod
def table_width(cls):
"""Total width of disk usage table"""
return cls.mount + cls.size + cls.used + cls.avail + cls.percent
class Column:
"""Column definition for SimpleTable"""
@@ -2134,7 +2121,7 @@ def show_hardware(json):
sensors = [c for c in components if c.get("class") == "iana-hardware:sensor"]
wifi_radios = [c for c in components if c.get("class") == "infix-hardware:wifi"]
width = max(PadSensor.table_width(), 100)
width = max(PadSensor.table_width(), 62)
# Display full-width inverted heading
print(Decore.invert(f"{'HARDWARE COMPONENTS':<{width}}"))
@@ -2158,12 +2145,12 @@ def show_hardware(json):
Decore.title("WiFi radios", width)
radios_table = SimpleTable([
Column('NAME'),
Column('MANUFACTURER'),
Column('NAME', flexible=True),
Column('MANUFACTURER', flexible=True),
Column('BANDS', 'right'),
Column('STANDARDS', 'right'),
Column('MAX AP', 'right')
])
], min_width=width)
for component in wifi_radios:
phy = component.get("name", "")
@@ -2208,10 +2195,10 @@ def show_hardware(json):
Decore.title("USB Ports", width)
usb_table = SimpleTable([
Column('NAME'),
Column('NAME', flexible=True),
Column('STATE'),
Column('OPER')
])
], min_width=width)
for component in usb_ports:
port = USBport(component)
@@ -2939,7 +2926,7 @@ def show_system(json):
except (ValueError, KeyError):
pass
width = PadDiskUsage.table_width()
width = 62
print(Decore.invert(f"{'SYSTEM INFORMATION':<{width}}"))
print(f"{'OS Name':<20}: {platform.get('os-name', 'Unknown')}")
print(f"{'OS Version':<20}: {platform.get('os-version', 'Unknown')}")
@@ -3022,23 +3009,23 @@ def show_system(json):
disk_filtered = [d for d in disk if d.get("mount") != "/"]
if disk_filtered:
Decore.title("Disk Usage", width)
hdr = (f"{'MOUNTPOINT':<{PadDiskUsage.mount}}"
f"{'SIZE':>{PadDiskUsage.size}}"
f"{'USED':>{PadDiskUsage.used}}"
f"{'AVAIL':>{PadDiskUsage.avail}}"
f"{'USE%':>{PadDiskUsage.percent}}")
print(Decore.invert(hdr))
disk_table = SimpleTable([
Column('MOUNTPOINT', flexible=True),
Column('SIZE', 'right'),
Column('USED', 'right'),
Column('AVAIL', 'right'),
Column('USE%', 'right')
], min_width=width)
for d in disk_filtered:
mount = d.get("mount", "?")
size = d.get("size", "?")
used = d.get("used", "?")
avail = d.get("available", "?")
percent = d.get("percent", "?")
print(f"{mount:<{PadDiskUsage.mount}}"
f"{size:>{PadDiskUsage.size}}"
f"{used:>{PadDiskUsage.used}}"
f"{avail:>{PadDiskUsage.avail}}"
f"{percent:>{PadDiskUsage.percent}}")
disk_table.row(
d.get("mount", "?"),
d.get("size", "?"),
d.get("used", "?"),
d.get("available", "?"),
d.get("percent", "?")
)
disk_table.print()
def show_dhcp_server(json, stats):
+13 -14
View File
@@ -183,22 +183,21 @@ def add_services(out):
services = []
for d in data:
if "pid" not in d or "status" not in d or "identity" not in d or "description" not in d:
try:
services.append({
"pid": d["pid"],
"name": d["identity"],
"status": d["status"],
"description": d["description"],
"statistics": {
"memory-usage": str(d.get("memory", 0)),
"uptime": str(d.get("uptime", 0)),
"restart-count": int(d.get("restarts", 0))
}
})
except KeyError:
continue
entry = {
"pid": d["pid"],
"name": d["identity"],
"status": d["status"],
"description": d["description"],
"statistics": {
"memory-usage": str(d.get("memory", 0)),
"uptime": str(d.get("uptime", 0)),
"restart-count": int(d.get("restarts", 0))
}
}
services.append(entry)
insert(out, "infix-system:services", "service", services)
def add_software(out):
+6 -6
View File
@@ -1,11 +1,11 @@
HARDWARE COMPONENTS 
────────────────────────────────────────────────────────────────────────────────────────────────────
HARDWARE COMPONENTS 
──────────────────────────────────────────────────────────────
Board Information
Model : Standard PC (i440FX + PIIX, 1996)
Manufacturer : QEMU
Base MAC Address : 00:a0:85:00:03:00
────────────────────────────────────────────────────────────────────────────────────────────────────
──────────────────────────────────────────────────────────────
USB Ports
NAME STATE OPER 
USB1 locked enabled
USB2 locked enabled
NAME STATE OPER 
USB1 locked enabled
USB2 locked enabled