NetConsole: A U-Boot Prompt Over UDP

The serial console on a Thingino camera is usually there. It is just on the inside: pads or an unpopulated header on the PCB, behind a sealed case. Getting to it means opening the camera, and a camera already installed means getting it off the wall or the ceiling first.

That is the real cost of a failed flash or one wrong environment variable. The console is not impossible to reach, it is merely behind a ladder, a screwdriver and a set of test hook clips.

NetConsole removes the hard work. U-Boot brings the Ethernet MAC up early to support tftpboot, and NetConsole reuses that to carry stdin, stdout and stderr as UDP datagrams. The result is a full interactive bootloader prompt on a camera you never have to touch.

Available on the ciao branch since 1 September 2026. Nothing to patch separately: build from ciao and enable one option.

Enabling It

NetConsole is off by default and opt-in per camera. One line in the camera defconfig:

BR2_PACKAGE_THINGINO_UBOOT_NETCONSOLE=y

The build derives the rest from flags the board already sets: wired Ethernet gets the plain redirect, a board with only a USB OTG port gets the USB variant described below. Cameras that do not opt in build a byte-identical stock bootloader and keep the stock one-second bootdelay.

What Gets Enabled

That one symbol pulls in three U-Boot config options:

Symbol Purpose
CONFIG_NETCONSOLE the console-over-UDP driver
CONFIG_CONSOLE_MUX device lists, so serial is kept alongside nc rather than replaced
CONFIG_PREBOOT allows redirecting the console before the autoboot window

Plus the environment that makes it reachable:

preboot=setenv stdout serial,nc;setenv stderr serial,nc;setenv stdin serial,nc
bootdelay=5
ipaddr=192.168.1.10

Two details are worth understanding rather than copying.

The redirect must happen in preboot. By the time bootcmd runs, console_init_r has already rebuilt stdin/stdout/stderr from the real devices and the abort window has closed. preboot is the only hook early enough.

CONFIG_CONSOLE_MUX is not optional. The mux only calls a device’s read function after it has signalled that a character is waiting, so NetConsole’s unbounded read is never entered on a dead line. A bare stdin=nc without the mux can hang forever on a dead network – and a hang you can only clear by opening the case is exactly what NetConsole exists to avoid.

Using It

The client ships with U-Boot:

gcc -o tools/ncb tools/ncb.c            # once
tools/netconsole 255.255.255.255 6666   # broadcast

Aimed at the board’s ipaddr instead, a session looks like this:

$ ./tools/netconsole 192.168.1.10
Board out port: 6666
Board in port: 6666
NOTE: the interrupt signal (normally ^C) has been remapped to ^T
ncb is /usr/bin/ncb
KEY:   ###### Press Ctrl-C now to interrupt boot... loading in 5s ######
isvp_t31# <INTERRUPT>
isvp_t31# version
build: thingino_T31X
Thingino U-Boot for Ingenic 2013.07 (Aug 31 2026 - 00:46:07 CEST)
mipsel-linux-gcc.br_real (Buildroot -g2c96a67d-dirty) 16.2.0
GNU ld (GNU Binutils) 2.45.1
isvp_t31#

The client remaps its own interrupt key to Ctrl-T so that Ctrl-C stays free to travel to U-Boot.

Do not wait for the KEY: ###### Press Ctrl-C now ... line. It is printed once, and on a cold boot it is often on the wire before the link has finished coming up, so it may never reach you even though the window behind it is open. Start pressing Ctrl-C as soon as you trigger the reboot and keep going. One keypress landing inside the window is enough; the extra ones are harmless.

Broadcast Or Unicast

Output is always broadcast. Input can be sent either way, and the choice matters more than it looks.

Unicast (netconsole 192.168.1.10 6666) requires ipaddr to be on your subnet and an ARP entry to be resolvable in both directions.

Broadcast (netconsole 255.255.255.255 6666) needs no ARP entry, no matching subnet, and no cooperation from the switch. It is the recommended default.

Two things follow from that.

Broadcast can show characters twice. Your keystroke is broadcast to the segment, so your listener sees it alongside the board’s echo. It is cosmetic; nothing is sent to the board twice.

Unicast input can freeze while output keeps flowing. On at least one board/switch combination, the switch’s dynamic unicast FDB entry for an idle camera expires, and unicast stops being delivered to that port until the camera transmits again. Broadcast floods regardless, so output survives and input does not. The counters told the tale: every frame the camera sent reached the switch, but the frames addressed to it never left, because the forwarding entry was gone.

If a unicast session goes deaf, you do not need to restart anything:

ping -b 255.255.255.255

Every probe is broadcast, so it reaches the board regardless; the board’s reply re-teaches the switch, and the session resumes. A freshly started arping <ipaddr> works too, but goes deaf once it switches to unicast probes, so the broadcast ping is the one to leave running.

Cameras Without An Ethernet Port

Boards with WiFi and a USB OTG port have no MAC for U-Boot to use, and U-Boot has no WiFi driver. They are still reachable through a USB-Ethernet dongle.

No patch is required. The 2013.07 tree already builds the DWC2 host controller and the ASIX drivers for every board but one, and board_usb_init() already puts the OTG port in host mode. Only the sequencing is new:

preboot=usb start;setenv ethact ${nc_ethact};setenv stdout serial,nc;...
nc_ethact=asx0

usb start enumerates the dongle and registers it as an Ethernet device. setenv ethact then selects it, which is required: the on-chip MAC is registered as device 0 on every board, including boards with no PHY fitted, so without it NetConsole talks to an interface with nothing attached. nc_ethact defaults to asx0 (ASIX AX88772); set it to axg0 for an AX88179.

Because usb start runs inside preboot, enumeration and link-up finish before the countdown starts, so USB costs the abort window nothing. Measured on a Wyze Cam v3 with an AX88772C, the prompt was caught on every boot at bootdelay 2 and above.

Driver Fixes Behind This

NetConsole was the first workload to exercise the jz4775 Ethernet driver properly, and it exposed roughly a dozen defects that also affected any other network use in U-Boot:

  • A speed mask defined as zero, so selecting 10Mb/s set nothing.
  • DMA descriptors and buffers without cache-line alignment, sharing lines with data written through the cached alias.
  • A four-deep transmit ring the driver could not keep in sync with the DMA engine’s pointer, collapsed to a single descriptor.
  • A transmit FIFO never flushed, so the first send after every init timed out.
  • Latched CSR5 status never cleared, so suspended engines ignored poll demands.
  • jz_halt() enabling the transmitter it meant to disable.
  • Console output emitted from inside the init of the interface carrying it, causing the driver to interrupt itself.

These are in ciao as patches 0007-0010 for the 2013.07 tree.

Tested On

Two cameras, both Ingenic T31X, covering the wired and USB paths:

Camera Network path What was verified
vanhua_fjz_t31x_gc4653_eth (T31X, GC4653, PoE) on-chip MAC, wired Ctrl-C into an interactive prompt at both 10M and 100M, clean transmit over a 12 minute window, no duplicate packets, serial still usable alongside
wyze_cam3_t31x_gc2053_rtl8189ftv (T31X, GC2053, WiFi + USB OTG) ASIX AX88772C dongle same prompt over the dongle, printenv ethact reporting asx0, both broadcast and unicast input, bootdelay swept across 18 reboots

The unicast freeze was observed on a MikroTik CRS112 (QCA-8511). It is a property of the switch’s forwarding table rather than of either camera, so treat it as something to expect on any switch, not as a fault of these two.

Limitations And Gotchas

It is an unauthenticated shell. With ncip unset the board accepts console input from any host on the segment, so during the countdown any host on that segment can interrupt boot and reach a U-Boot prompt with full access to flash and the environment. Use it on trusted networks, or pin one client with fw_setenv ncip <host>.

Output stops at bootm. eth_halt() runs before the kernel starts. For kernel messages, use Linux-side netconsole instead. Anything printed before main_loop (SPL, DDR init, the banner) is also out of reach, because the network is not up that early.

Check your neighbour table before blaming the firmware. A stale static ARP entry pointing the camera’s IP at a different MAC silently swallows every unicast packet while broadcast keeps working. The symptom is indistinguishable from a firmware bug; the fix is ip neigh del.

A saved environment wins over compiled-in defaults. A camera that only had its bootloader reflashed keeps its old environment, so preboot and bootdelay may still be the previous values. Check with fw_printenv before concluding the build is wrong.