Skip to content

net: use ethaddr= bootarg before random MAC fallback#2168

Closed
tinylabs wants to merge 6 commits into
OpenIPC:masterfrom
tinylabs:kernel-ethaddr-bootargs
Closed

net: use ethaddr= bootarg before random MAC fallback#2168
tinylabs wants to merge 6 commits into
OpenIPC:masterfrom
tinylabs:kernel-ethaddr-bootargs

Conversation

@tinylabs

@tinylabs tinylabs commented Jun 3, 2026

Copy link
Copy Markdown

Summary

Allow ethaddr= from bootargs to seed the MAC address during early network init instead of falling back to a random MAC.

Why

  • Needed for early boot cases like NFS root, useful when booting multiple cameras with NFS with different kernels/rootfs.
  • Avoids random MAC assignment when a valid ethaddr= is already present on the command line.

Changes

  • Added an OpenIPC kernel helper to expose ethaddr= via arch_get_platform_mac_address().
  • Updated femac eth driver to use eth_platform_get_mac_address() before random fallback.
  • Enabled the helper through the kernel config fragment.

@widgetii

widgetii commented Jun 4, 2026

Copy link
Copy Markdown
Member

Thanks for sending this — the use case (deterministic MACs for NFS-rooted lab cameras) is genuinely useful, and the early-param plumbing is clean. A few things to consider before this is ready to merge, mostly around build-time scope.

CI: looks like patch 0016 is in the wrong layer

The current CI run is red on ~40+ boards (every HiSilicon _lite, all SigmaStar, Fullhan, Rockchip, GM, and gk7205v500_lite), while the older Goke family and the *_neo HiSilicon kernels pass. The pattern matches the patchability of 0016-femac-use-platform-mac-fallback.patch:

  • That patch edits drivers/net/ethernet/goke/femac/femac.c, which only exists in the legacy Goke vendor BSP (gk7102/gk7202/gk7205v2xx/gk7605v100).
  • general/package/all-patches/linux/ is the global BR2_GLOBAL_PATCH_DIR, so the patch is applied to every kernel build. On any tree without that file (HiSilicon, SigmaStar, Rockchip, gk7205v500 which is HiSilicon-derived, etc.), patch -p1 fails and the kernel build aborts.

Would it be possible to move the femac patch into the Goke vendor BSP package (e.g. alongside the rest of femac under br-ext-chip-goke/...) so it only applies where the file exists? That alone should turn the matrix mostly green. The global all-patches dir today only carries tree-wide gcc-version shims (0010–0014), which feels like the right convention to preserve.

Scope of CONFIG_OPENIPC_ETHADDR

Right now the symbol is enabled globally via the new kernel fragment, but only the Goke femac driver actually consumes arch_get_platform_mac_address(). On every other vendor's ethernet driver (HiSilicon higmac/femac, SigmaStar GMAC, Rockchip stmmac, …) the random-MAC fallback path is untouched, so the new mechanism is effectively a no-op there.

Two ways to resolve it:

  • Narrow the config gate to the platforms it actually serves (depend on the Goke femac symbol), so the help text matches reality, or
  • Add parallel one-liner patches per vendor driver in the same PR so the feature really is board-wide.

Behavior question on the Goke side

The current diff drops the explicit of_get_mac_address(node) lookup and goes straight to eth_platform_get_mac_address(dev, ndev->dev_addr). On modern mainline that helper does consult OF/DT internally, but on the Goke vendor kernel the femac platform node's fwnode wiring is a bit unusual — could you confirm on a real Goke board that a DT-provided mac-address / local-mac-address is still honored when no ethaddr= is passed? Just want to make sure we're not silently regressing the DT path for users who already have valid MACs in DT/u-boot fixup.

Fleet-collision footgun with the universal u-boot

One thing worth thinking about: OpenIPC's universal u-boot ships ethaddr=00:00:23:34:45:66 baked into its default env. Today that's harmless because the kernel re-randomizes when DT/EEPROM give nothing useful. With this PR, every unprovisioned camera would deterministically come up as 00:00:23:34:45:66 — two on the same L2 segment would ARP-war.

Would you be open to rejecting that specific sentinel inside openipc_early_ethaddr() (treat it like "not set" and fall through to random)? Or at minimum calling it out in the Kconfig help text so deployers know to clear the u-boot default env first.

Smaller nits

  • ndev->dev_addr is const u8 * from Linux 5.15 on, so eth_platform_get_mac_address(dev, ndev->dev_addr) would stop compiling if/when goke's kernel gets bumped. Cheap to future-proof: read into a stack buffer, then eth_hw_addr_set(ndev, mac).
  • openipc_ethaddr_valid is redundant with is_valid_ether_addr() on the buffer — you could drop the flag and write return is_valid_ether_addr(openipc_ethaddr) ? openipc_ethaddr : NULL;.
  • A pr_warn on a malformed ethaddr= string would help users notice typos — right now a bad value silently falls through to random with no breadcrumbs.
  • BR2_LINUX_KERNEL_CONFIG_FRAGMENT_FILES=… (plain assignment) in general/openipc.fragment would clobber any per-board kernel fragment set in the future. Today no board sets it, so it's fine, but += is the safer convention.
  • Worth a quick grep of each target kernel for an existing arch_get_platform_mac_address definition — a couple of vendor BSPs out there carry their own, and you'd get a multiple-definition link error if one of ours does.

Verification suggestion

Once the patch is in the right layer, the smallest useful smoke test would be:

  1. Pick one Goke board (e.g. gk7205v200_lite) and one HiSilicon board (once HiSilicon driver wiring is in).
  2. Boot with ethaddr=02:11:22:33:44:55 on the kernel cmdline; confirm ip link show dev eth0 reflects it.
  3. Boot again with no ethaddr=; confirm DT-provided MAC (if any) is still honored, then random fallback.

Thanks again — happy to re-review as soon as CI is green.

@widgetii widgetii left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Requesting changes — CI is red across ~40+ boards because patch 0016 lands in the global BR2_GLOBAL_PATCH_DIR but only applies cleanly to legacy Goke kernels. Detailed notes in the inline comment above; happy to re-review once the femac patch is moved into the Goke vendor BSP layer and CI is green.

@tinylabs

tinylabs commented Jun 4, 2026

Copy link
Copy Markdown
Author

Thank you for the full analysis @widgetii! I indeed missed a bunch of things.

For context this kernel change is part of a larger change set to make root NFS deployment easier. For my security cam deployment that's my preferred method but I understand it may be of marginal use to others outside the lab. All the other changes for NFS root I've been able to integrate into a buildroot package but unfortunately kernel support for a fixed MAC on boot is blocking me. Ultimately I'd like to be able to append BR2_PACKAGE_OPENIPC_NFS_ROOT=y to general/openipc.fragment and deploy NFS root for any target. I'd be happy to update the wiki article when complete to help other deploy this way if there's interest.

Scope issues

It would be nice to have this functionality global in scope so it doesn't have to be added adhoc to each camera later. I've started to refactor, only keeping the definition arch_get_platform_mac_address global with a copy for neo and non-neo targets. The ethernet drivers are now patched based on what kernel driver is found in the kernel config with the patches applied appropriately by appending patch paths to BR2_GLOBAL_PATCH_DIR. This prevents having to touch each defconfig per target. Currently I have goke and hisilicon (both legacy and neo) targets compiling with the patch.

Validation needed

  • I only have gk7205v300 target currently so that's all I can test on hardware. I have some sigmastar infinity 6c targets coming soon so I will test those as well.
  • I need to double check arch_get_platform_mac_address isn't getting defined for any other kernel to avoid that duplicate definition error.
  • I will try setting the device tree MAC value to ensure that we don't override that when set.

Fleet-collision footgun

Great point I didn't consider. Maybe it would be better to use another bootarg key like macaddr rather than ethaddr. That would prevent the collisions. Alternatively, although a bit hacky, we could check for the default MAC in arch_get_platform_mac_address and treat it like a NULL value so it falls through to reassigning a random MAC.

Smaller nits

All valid, I will fix those. Only one I'm not sure how to approach is the general/openipc.fragment. I don't think buildroot supports append operations in these files.

Again, I appreciate all the feedback. Once I get things in better looking shape we can discuss if it's a good fit for a merge.

@tinylabs

tinylabs commented Jun 8, 2026

Copy link
Copy Markdown
Author

The ethernet drivers vary quite a lot about how they fetch a mac address and default back to a random address. Patching and maintaining them all seems like a tall task. How would you feel about directly patching ipconfig.c but only enabling it when the kernel feature (CONFIG_OPENIPC_ETHADDR) is enabled?

Something like this:

--- ipconfig.c.orig     2026-06-08 10:00:22.383388496 -0600
+++ ipconfig.c  2026-06-08 10:38:37.812834800 -0600
@@ -65,6 +65,10 @@
 #include <net/checksum.h>
 #include <asm/processor.h>
 
+#if defined (CONFIG_OPENIPC_ETHADDR)
+#include <linux/etherdevice.h>
+#endif
+
 #if defined(CONFIG_IP_PNP_DHCP)
 #define IPCONFIG_DHCP
 #endif
@@ -205,6 +209,10 @@
        struct net_device *dev;
        unsigned short oflags;
        unsigned long start, next_msg;
+#if defined (CONFIG_OPENIPC_ETHADDR)
+       bool first_mac_done = false;
+       u8 mac[ETH_ALEN];
+#endif
 
        last = &ic_first_dev;
        rtnl_lock();
@@ -230,6 +238,20 @@
                        able &= ic_proto_enabled;
                        if (ic_proto_enabled && !able)
                                continue;
+
+#if defined (CONFIG_OPENIPC_ETHADDR)
+                       /* Set MAC address from device tree or bootargs */
+                       if (!first_mac_done &&
+                               (able & IC_BOOTP) &&
+                               (dev->type == ARPHRD_ETHER) &&
+                               dev->dev.parent &&
+                               !eth_platform_get_mac_address(dev->dev.parent, mac)) {
+                               ether_addr_copy(dev->dev_addr, mac);
+                               first_mac_done = true;
+                               pr_info("IP-Config: %s MAC set to %pM before open\n",
+                                               dev->name, dev->dev_addr);
+                       }
+#endif
                        oflags = dev->flags;
                        if (dev_change_flags(dev, oflags | IFF_UP) < 0) {
                                pr_err("IP-Config: Failed to open %s\n",

Then in the top-level Makefile we could enable CONFIG_OPENIPC_ETHADDR only if BR2_PACKAGE_OPENIPC_NFS_ROOT is enabled (which contains the userspace overlay changes). I believe this would minimize the patching to just the legacy 4.9.x kernel + the neo 7.x kernel but only enable the functionality if a user is implicitly includes the NFS root BR2 package. Edit: I see that some target kernels are as old as 3.0.8, I would need to check that the patch applies cleanly to all kernels. Ultimately the goal is to avoid touching every ethernet driver.

@widgetii

widgetii commented Jun 9, 2026

Copy link
Copy Markdown
Member

Re-reviewed after the recent commits — really nice work, the restructure is exactly along the lines I'd hoped.

What's improved

  • Patch layering is fixed. 0016-femac-… is gone; SoC-specific edits now live in general/package/all-patches/linux-drivers/{goke-femac,hisi-femac,hisi-higmac}/linux/0020-*.patch, each one a tiny rewrite of that vendor driver's MAC source.
  • Scope was widened, not narrowed — parallel patches for hisi_femac.c and hisi_higmac.c mean the feature now actually works on HiSilicon as advertised.
  • Per-board patch dir auto-detection in the new Makefile logic (the get-br2-var helper + EXTRA_PATCH_DIRS accumulator). Greps each board's kernel config for CONFIG_GOKE_FEMAC|XMEDIA_FEMAC|HISI_FEMAC|HISI_HIGMAC and appends only the matching subdir — clean.
  • -neo variant of the Kconfig+Makefile+openipc_ethaddr.c patch covers the older neo kernels' CONFIG_NET_VENDOR_PACKET_ENGINES context line. Nice catch.
  • make linux-patch-check target is a great DX win — fast feedback loop on the patch stack without a full build.
  • openipc_ethaddr.c cleanups address the smaller items — dropped the redundant _valid flag, added pr_warn on malformed input, filtered the 00:00:23:34:45:66 u-boot default sentinel.

CI red count is misleading

I sampled six failing jobs (t31_lite, hi3520dv200_lite, gk7102_lite, gm8135_lite, hi3516cv100_lite, hi3516cv6xx_ultimate). None fail at the kernel patch step — they all die earlier on tarball hash mismatches for wireguard-linux-compat, wireguard-tools, and squashfs. That's source-mirror drift on the buildroot side, not anything this PR introduced. The 67 passing jobs include the formerly-broken HiSilicon _lite set (hi3516cv6xx_lite, hi3516ev200/300_lite, hi3518ev200/300_lite), which is the real signal that the patch-layering rework did its job.

A re-run once the upstream tarball hashes are refreshed should turn most of those reds green without further code changes.

Remaining small nits (none blocking)

  1. Default-MAC filter could be a bit more robust. In openipc_ethaddr.c:

    if (!strncmp(str, default_mac, 17))

    compares the raw bootarg string, case-sensitive, length hard-coded to 17. A tiny refactor that handles - separators / upper-case / trailing whitespace:

    static const u8 default_mac[ETH_ALEN] = {0x00,0x00,0x23,0x34,0x45,0x66};
    …
    if (ether_addr_equal(mac, default_mac)) {
        pr_warn(\"openipc_ethaddr: ignoring default ethaddr %pM\n\", mac);
        return 0;
    }

    Same intent, fewer ways to slip through.

  2. Mixed indentation in all-patches/linux/0020-openipc-ethaddr-bootarg-fallback.patch. Inside openipc_early_ethaddr() a few of the new lines mix TAB and 4-space (+ if (!mac_pton(...))). Compiles fine but looks broken next to kernel TAB style. The -neo copy is already clean, only the mainline copy needs the whitespace fix.

  3. rg dependency in the Makefile. Most users have it and it's probably in make deps already, but grep -qE '…' is a free portability win in case anyone runs make linux-patch-check on a bare system.

  4. BR2_LINUX_KERNEL_CONFIG_FRAGMENT_FILES=… in general/openipc.fragment is still a plain assignment, so a future per-board kernel fragment would silently clobber it. Today no board sets it so it's cosmetic, but += would be friendlier to future contributors.

  5. get-br2-var returns the last assignment and doesn't merge += chains — fine today because nothing uses += for BR2_GLOBAL_PATCH_DIR, but a short comment on the helper noting that assumption would help future maintainers.

Happy to flip my CHANGES_REQUESTED to approval once the indentation in the global patch is normalized; the rest of the nits are genuinely optional. Thanks for the careful restructure.

@widgetii widgetii dismissed their stale review June 9, 2026 10:40

Structural concerns from the original review are addressed by the recent commits — patch layering, scope, and the openipc_ethaddr.c cleanups all look good (see follow-up comment for details). Dismissing the block. CI still needs to come back green before merge: the current matrix has ~36 reds, but on inspection those are upstream tarball hash drift (wireguard-linux-compat, wireguard-tools, squashfs), not anything in this PR. A re-run once the source hashes are refreshed should clear them.

@tinylabs

tinylabs commented Jun 9, 2026

Copy link
Copy Markdown
Author

Thank you for the review @widgetii. I've changed tack to what I think might be a better approach. Rather than having global patches to linux that apply to every build and touch every ethernet driver I moved the patchset to a buildroot package and finished plumbing in buildroot support for linux kernel extensions from packages. The feature is described here: https://buildroot.org/downloads/manual/adding-packages-linux-kernel-spec-infra.txt.

This approach has a few benefits:

  • Lower risk as it's effectively a nop if BR2_PACKAGE_OPENIPC_NFS_ROOT isn't compiled in.
  • Rather than touching ethernet drivers for each target we directly patch the early kernel network config path with the same logic to configure mac based on device tree then bootargs. If neither succeed we don't touch ethaddr which will use the previously configured value in the driver (often randomly generated).
  • Much easier to manage a single patch file (ipconfig.c) that hasn't changed much between kernel versions. It applies cleanly to both 4.9.37 and 7.x.
  • Easily add NFS support to any target by appending BR2_PACKAGE_OPENIPC_NFS_ROOT=y to general/openipc.fragment without editing defconfig files.

Cons:

  • Slightly noisy in the logs. Driver brings up random MAC which then gets overwritten if patched call succeeds. Logging clearly shows it's overriding due to the patched feature.

Notes:

  • init.d scripts still need some cleanup.
  • Should work on both RW and RO nfs rootfs.
  • Changes outside of new buildroot package are to support package based kernel patching and late overlay from package files.

Current branch is here: master...tinylabs:openipc-firmware:br-package-nfs-root

Let me know your thoughts. I'd like to close this PR and open a new one with the other branch.

@widgetii

widgetii commented Jun 9, 2026

Copy link
Copy Markdown
Member

Thanks for thinking this through again — I think you've landed on the right design. Patching ipconfig.c once at the early-boot network layer instead of N vendor MAC drivers is genuinely the cleaner architecture:

  • It's the kernel-blessed early network path that already handles ip= / nfsroot= semantics, so MAC-from-cmdline lives alongside its natural neighbours rather than as an arch-level weak symbol hijack.
  • One patch covering 4.9.37 through 7.x is a dramatic maintenance win over the per-vendor matrix (and over chasing every new SoC vendor that gets added).
  • Opt-in via BR2_PACKAGE_OPENIPC_NFS_ROOT=y means the failure modes from the global-patch-dir approach simply can't happen — boards that don't enable it see zero kernel diff. That's a big risk reduction.
  • Using Buildroot's linux-kernel-extensions infra is the right Buildroot-native way to attach patches/Kconfig to a package; it's also reusable later for any other kernel-side OpenIPC features we want to ship as opt-in packages.

The noisy log (driver assigns random MAC → ipconfig overwrites) is a fair tradeoff for the architectural simplification; a single pr_info("openipc: overriding MAC from bootarg/DT, was %pM\n", …) from the override path is probably all that's needed.

Happy with closing this PR and opening a fresh one against the br-package-nfs-root branch. A couple of things that would be useful to see in the new PR description / smoke-tests, just so reviewers can sanity-check it without rebuilding the matrix:

  1. Brief note that the package is gated off by default in general/openipc.fragment (or wherever) — i.e. confirm the no-op-when-disabled property.
  2. One example boot log from a real camera with BR2_PACKAGE_OPENIPC_NFS_ROOT=y and ethaddr= on the cmdline, showing the eventual ip link MAC matches.
  3. One example confirming DT-provided MAC still wins when no ethaddr= is passed (i.e. that you didn't accidentally regress the DT path that the older per-driver code preserved).

Looking forward to reviewing the new one. Thanks for the persistence here — this is going to be a much nicer thing to maintain than the original direction.

@tinylabs

tinylabs commented Jun 9, 2026

Copy link
Copy Markdown
Author

Thanks @widgetii! I'll create a new PR after I finish polishing and testing the branch.

@tinylabs tinylabs closed this Jun 9, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants