Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 25 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -169,6 +169,23 @@ it), Ethernet via DHCP, and `172.32.0.93` over the USB-C gadget. The card is
handed over read-only so `systemd-fsck-root` gets to run, then remounted `rw`
from `/etc/fstab`; the partition and filesystem grow to fill the card.

The gadget is two functions on the one cable. `ssh root@172.32.0.93` over NCM,
and a second login prompt on the ACM port, which the host sees as
`/dev/ttyACM0`:

```bash
tio /dev/ttyACM0 # or: screen /dev/ttyACM0, picocom /dev/ttyACM0
```

The line settings do not matter: CDC ACM carries baud and parity as metadata
and nothing on the board acts on them. `/dev/ttyACM0` is whichever board
enumerated first, so with more than one plugged in, address them by the serial
the gadget reports, which is the SoC's own and does not change:

```bash
tio /dev/serial/by-id/usb-Luckfox_Pico_Max_556abe2b7497589c-if02
```

Debian's desktop-sized housekeeping is masked, not deleted: `apt-daily`,
`apt-daily-upgrade`, `e2scrub`, `fstrim` and `dpkg-db-backup` do not run on
their own. `systemctl unmask` whichever you want back.
Expand Down Expand Up @@ -198,15 +215,17 @@ Upstreams are pinned to exact commits in `scripts/lib.sh`. Nothing floats.

Kernel boots cleanly, userspace starts green, NPU inference works right away.

The Ethernet MAC is `02:00:xx:xx:xx:xx`, derived from the SoC's OTP id, so it
is the same address on every boot and the same board keeps its DHCP lease. The
Type-C port is a peripheral: `luckfox-usb-gadget.service` binds NCM + ACM to it
and the board answers on `172.32.0.93`. Both of those took a devicetree change
and a kernel patch; `&gmac` and `&usbdrd_dwc3` in the DTS say why.

Wrong on a booted board, and not fixed yet:

- the Ethernet MAC is random on every boot (`rk_vendor_read eth mac address
failed`), so the board takes a new DHCP lease each time.
- the USB gadget never comes up: `phy ... illegal mode`, then `no UDC
available; is the controller in peripheral mode?`. The USB-C row in the
table above is what the hardware and the units are for, not something that
works today.
- `Kernel memory protection not selected`.
- the Type-C port is peripheral-only. Host mode is a one-word DTS change, but
the port is also the power inlet, so it is the wrong default.

## Credits

Expand Down
39 changes: 37 additions & 2 deletions board/luckfox-pico-max/kernel/dts/rv1106g3-luckfox-pico-max.dts
Original file line number Diff line number Diff line change
Expand Up @@ -163,8 +163,24 @@

/* ------------------------------------------------------------- ethernet --- */

/* 100M, integrated PHY. MAC address comes from the OTP via nvmem. */
/*
* 100M, integrated PHY.
*
* There is no MAC address anywhere on this board to read. dwmac-rk keeps one
* in Rockchip vendor storage, a reserved area at the end of the boot medium
* that the factory programs; nothing ever programmed this one, and an SD card
* written by mk-image.sh has no such area at all. The driver's answer to that
* was eth_random_addr(), so the board took a new MAC -- and a new DHCP lease
* -- on every boot.
*
* Name the OTP's 16-byte unique id, the same cell rockchip-cpuinfo hashes into
* /proc/cpuinfo's Serial, and the patched driver derives a locally administered
* address from it: stable for the life of the chip, distinct between chips.
* kernel/patches/0003 has the details.
*/
&gmac {
nvmem-cells = <&otp_id>;
nvmem-cell-names = "soc-id";
status = "okay";
};

Expand All @@ -183,8 +199,27 @@
status = "okay";
};

/*
* The Type-C port is the board's only USB port and its power inlet, and the
* connector has no ID pin -- there is no CC controller behind it either, just
* a socket wired to the SoC's one USB 2.0 OTG controller.
*
* dr_mode = "otg" therefore does not mean what it looks like it means. With no
* extcon to consult, dwc3 falls back to the OTG block's CONIDSTS bit, which
* follows the PHY's iddig input, which nothing on this board drives; it reads
* as grounded, dwc3 concludes it is the A-device and brings up xHCI. The port
* then has no VBUS to offer a downstream device and no UDC for a gadget to
* bind to, so it does nothing in either direction -- and usb_add_hcd() offers
* the PHY PHY_MODE_USB_HOST_SS on the way past, which
* rockchip_usb2phy_set_mode() does not handle, hence "illegal mode" in dmesg.
*
* Peripheral is what the hardware is, and is what Luckfox's own devicetree
* says. It gives the gadget in luckfox-usb-gadget.service a UDC to bind to.
* Set this to "host" instead if you power the board through the pin header and
* want to plug devices into the Type-C.
*/
&usbdrd_dwc3 {
dr_mode = "otg";
dr_mode = "peripheral";
status = "okay";
};

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
From: luckfox-linux <build@localhost>
Subject: [PATCH] net: stmmac: dwmac-rk: derive a stable MAC address from the SoC id

rk_get_eth_addr() keeps the board's MAC address in Rockchip vendor
storage, a small reserved area at the end of the boot medium that the
factory programs and rk_vendor_storage.ko reads back. A board that was
never through Rockchip's provisioning, or that boots from a medium with
no vendor partition on it, has nothing there:

rk_gmac-dwmac ffa80000.ethernet: rk_get_eth_addr: rk_vendor_read eth mac address failed (-1)
rk_gmac-dwmac ffa80000.ethernet: rk_get_eth_addr: generate random eth mac address: be:69:2e:54:8a:23
rk_gmac-dwmac ffa80000.ethernet: rk_get_eth_addr: rk_vendor_write eth mac address failed (-1)

The fallback is eth_random_addr() followed by an attempt to write the
result back, and on such a board the write fails for the same reason the
read did. So the address is new on every boot: a fresh DHCP lease, a
fresh entry in the server's table, a different address for anything that
tracked the last one, and no way to reserve one.

Every one of these SoCs does have something unique and permanent in it,
though -- the 16-byte id in OTP that rockchip-cpuinfo already reads to
publish /proc/cpuinfo's Serial. Take a MAC from that when the board asks
for it, by naming the OTP id cell "soc-id" in the ethernet node:

&gmac {
nvmem-cells = <&otp_id>;
nvmem-cell-names = "soc-id";
};

The id is neither a MAC nor an OUI, so it is hashed into the low four
bytes and given a locally administered, unicast prefix rather than being
copied in. The result is stable for the life of the chip, differs
between chips, and differs between the MACs on one chip. Boards with
working vendor storage are unaffected: it is still tried first, and
boards that do not name the cell still get the old random address.

Signed-off-by: luckfox-linux <build@localhost>
---
drivers/net/ethernet/stmicro/stmmac/dwmac-rk.c | 44 ++++++++++++++++++++++++++
1 file changed, 44 insertions(+)

diff --git a/drivers/net/ethernet/stmicro/stmmac/dwmac-rk.c b/drivers/net/ethernet/stmicro/stmmac/dwmac-rk.c
index 4561737aa..0249998d2 100644
--- a/drivers/net/ethernet/stmicro/stmmac/dwmac-rk.c
+++ b/drivers/net/ethernet/stmicro/stmmac/dwmac-rk.c
@@ -10,6 +10,7 @@
#include <linux/stmmac.h>
#include <linux/bitops.h>
#include <linux/clk.h>
+#include <linux/crc32.h>
#include <linux/phy.h>
#include <linux/phy/phy.h>
#include <linux/of_net.h>
@@ -3254,6 +3255,42 @@ int dwmac_rk_get_phy_interface(struct stmmac_priv *priv)
}
EXPORT_SYMBOL(dwmac_rk_get_phy_interface);

+/*
+ * Derive a MAC address from the SoC's OTP unique id, for boards that have no
+ * vendor storage to keep one in. The id is not a MAC and is not an OUI, so
+ * hash it down to the low four bytes and put the result under a locally
+ * administered prefix; the address is then stable for the life of the chip
+ * and distinct between chips, which is all the caller wanted.
+ */
+static int rk_soc_id_eth_addr(struct device *dev, int id, unsigned char *addr)
+{
+ struct nvmem_cell *cell;
+ unsigned char *soc_id;
+ size_t len;
+ u32 hash;
+
+ cell = nvmem_cell_get(dev, "soc-id");
+ if (IS_ERR(cell))
+ return PTR_ERR(cell);
+
+ soc_id = nvmem_cell_read(cell, &len);
+ nvmem_cell_put(cell);
+ if (IS_ERR(soc_id))
+ return PTR_ERR(soc_id);
+
+ hash = crc32(0, soc_id, len);
+ kfree(soc_id);
+
+ addr[0] = 0x02; /* unicast, locally administered */
+ addr[1] = id; /* one address per MAC on the SoC */
+ addr[2] = hash >> 24;
+ addr[3] = hash >> 16;
+ addr[4] = hash >> 8;
+ addr[5] = hash;
+
+ return 0;
+}
+
static void rk_get_eth_addr(void *priv, unsigned char *addr)
{
struct rk_priv_data *bsp_priv = priv;
@@ -3274,6 +3311,13 @@ static void rk_get_eth_addr(void *priv, unsigned char *addr)
!is_valid_ether_addr(&ethaddr[id * ETH_ALEN])) {
dev_err(dev, "%s: rk_vendor_read eth mac address failed (%d)\n",
__func__, ret);
+
+ if (!rk_soc_id_eth_addr(dev, id, addr)) {
+ dev_info(dev, "%s: mac address from soc id: %pM\n",
+ __func__, addr);
+ goto out;
+ }
+
eth_random_addr(&ethaddr[id * ETH_ALEN]);
memcpy(addr, &ethaddr[id * ETH_ALEN], ETH_ALEN);
dev_err(dev, "%s: generate random eth mac address: %pM\n", __func__, addr);
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
From: luckfox-linux <build@localhost>
Subject: [PATCH] phy: rockchip: inno-usb2: do not demand a parent interrupt

The probe takes the muxed per-phy interrupt with platform_get_irq(),
which logs an error when the devicetree does not have one:

rockchip-usb2phy ff3e0000.usb2-phy: error -ENXIO: IRQ index 0 not found

Not having one is legitimate. SoCs that route every port's id, bvalid
and linestate events through a single line describe it on the usb2-phy
node; the rest, the RV1106 among them, give each port its own
interrupts, which rockchip_usb2phy_port_irq_init() picks up from the
port's own node. Both are correct, and the driver already treats the
muxed line as optional -- every use of rphy->irq is guarded by
`rphy->irq > 0`.

Ask with platform_get_irq_optional(), which returns the same -ENXIO
without printing anything.

Signed-off-by: luckfox-linux <build@localhost>
---
drivers/phy/rockchip/phy-rockchip-inno-usb2.c | 8 +++++++-
1 file changed, 7 insertions(+), 1 deletion(-)

diff --git a/drivers/phy/rockchip/phy-rockchip-inno-usb2.c b/drivers/phy/rockchip/phy-rockchip-inno-usb2.c
index 23c707be6..3dc434316 100644
--- a/drivers/phy/rockchip/phy-rockchip-inno-usb2.c
+++ b/drivers/phy/rockchip/phy-rockchip-inno-usb2.c
@@ -2494,7 +2494,13 @@ static int rockchip_usb2phy_probe(struct platform_device *pdev)
rphy->chg_state = USB_CHG_STATE_UNDEFINED;
rphy->chg_type = POWER_SUPPLY_TYPE_UNKNOWN;
rphy->edev_self = false;
- rphy->irq = platform_get_irq(pdev, 0);
+ /*
+ * Optional: SoCs that mux every port's interrupts into one line put it
+ * here, the rest (RV1106 among them) describe them per port instead.
+ * Everything below tests rphy->irq before using it, so ask for it in a
+ * way that does not log an error when it is legitimately absent.
+ */
+ rphy->irq = platform_get_irq_optional(pdev, 0);
platform_set_drvdata(pdev, rphy);

if (!phy_cfgs)
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,15 @@ Description=USB gadget (NCM network + ACM serial) on the Type-C port
After=systemd-modules-load.service
ConditionPathIsDirectory=/sys/kernel/config

# The ACM function on its own is a pipe with nothing on the far end: it gives
# the board a /dev/ttyGS0 and the host a /dev/ttyACM*, and a host that opens it
# sits looking at a blank screen. Put a login prompt there. Ordering matters --
# serial-getty@.service is BindsTo=dev-ttyGS0.device, and the device node only
# exists once `up` has created the function, which is also why `down` takes the
# getty with it.
Wants=serial-getty@ttyGS0.service
Before=serial-getty@ttyGS0.service

[Service]
Type=oneshot
RemainAfterExit=yes
Expand Down
30 changes: 22 additions & 8 deletions rootfs/overlay/usr/local/sbin/luckfox-usb-gadget
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,22 @@ set -eu

G=/sys/kernel/config/usb_gadget/luckfox
IFACE_ADDR=${USB_GADGET_ADDR:-172.32.0.93/24}
# Locally administered MACs, stable across reboots so the host does not invent
# a new interface name every time.
MAC_DEV=${USB_GADGET_MAC_DEV:-2a:66:6c:66:78:01}
MAC_HOST=${USB_GADGET_MAC_HOST:-2a:66:6c:66:78:02}

# Everything identifying about this gadget hangs off the SoC's OTP unique id,
# which /proc/cpuinfo publishes as Serial. It has to be stable across reboots,
# or the host invents a new usb0 name and a new /dev/serial/by-id path every
# time, and it has to differ between boards, or two of these on one laptop are
# indistinguishable. /etc/machine-id is the fallback for a kernel that does not
# publish a serial.
soc_id=$(sed -n 's/^Serial[[:space:]]*:[[:space:]]*//p' /proc/cpuinfo | tr -dc '0-9a-f')
[ "${#soc_id}" -ge 8 ] || soc_id=$(tr -dc '0-9a-f' 2>/dev/null < /etc/machine-id || true)
[ "${#soc_id}" -ge 8 ] || soc_id=6c667801
SERIAL=$(printf %s "$soc_id" | tail -c 16)

# Locally administered, and the two ends differ in the second octet.
suffix=$(printf %s "$soc_id" | tail -c 8 | sed 's/../&:/g; s/:$//')
MAC_DEV=${USB_GADGET_MAC_DEV:-02:66:$suffix}
MAC_HOST=${USB_GADGET_MAC_HOST:-02:67:$suffix}

udc_name() { ls /sys/class/udc 2>/dev/null | head -1; }

Expand All @@ -31,11 +43,13 @@ up() {
echo 0x0100 > "$G/bcdDevice"

mkdir -p "$G/strings/0x409"
echo "Luckfox" > "$G/strings/0x409/manufacturer"
echo "Pico Max" > "$G/strings/0x409/product"
echo "$(cat /proc/sys/kernel/random/boot_id | tr -d - | cut -c1-16)" \
> "$G/strings/0x409/serialnumber"
echo "Luckfox" > "$G/strings/0x409/manufacturer"
echo "Pico Max" > "$G/strings/0x409/product"
echo "$SERIAL" > "$G/strings/0x409/serialnumber"

# Link order below is interface order on the wire: NCM takes 0 and 1, so
# the ACM control interface is 2 and the host's stable name for the tty
# is /dev/serial/by-id/usb-Luckfox_Pico_Max_$SERIAL-if02.
mkdir -p "$G/functions/ncm.usb0"
echo "$MAC_DEV" > "$G/functions/ncm.usb0/dev_addr"
echo "$MAC_HOST" > "$G/functions/ncm.usb0/host_addr"
Expand Down
Loading