Skip to content

Development Setups

Two boot methods are preferred during development. Both keep the installed firmware unchanged.

Prerequisite: the device must already be running stock firmware 15.00.20 or newer. Both setups boot through the stock bootloader and load a device tree, which older stock versions cannot do. See Prerequisites.

Note: One machine serves every device, whichever processor module it carries — the bootloader works out which board it is on and loads that board's device tree.

Warning: A booted device runs an SSH server and accepts a root login with no password. Keep it on a network you control — see the security note.

USB drive: libretouch-usb

Needs a USB-A socket, and a board device tree. Not every SoundTouch has one — rhino_sm2, binky_sm2, bardeen_sm2 and burns_sm2 have only the micro-USB service port, and on those the RNDIS setup below is the only option. This route also needs the board's own device tree: the boot-only trees cannot switch the shared USB controller to the USB-A socket. See USB ports.

Build the libretouch-usb machine and write its image to a USB drive — see Create a USB installation. The build produces …rootfs.wic.xz and a matching .wic.bmap, not a plain .wic. This is the simpler setup when the device is nearby and a USB drive is convenient.

MACHINE=libretouch-usb bitbake libretouch-image

Booting it is two U-Boot variables and one command. The recommended setup leaves bootcmd alone and starts each USB boot by hand from the serial console — see Why not bootcmd for the reasoning, and Unattended boot for the case that needs the opposite.

First boot

Insert the USB drive and interrupt U-Boot through the serial console. The stock bootloader accepts exactly one key for this — press U repeatedly while it counts down:

Autoboot in 5 seconds ('U' to stop):
U-Boot#

Any other key is ignored and the device boots on regardless. At the U-Boot# prompt, enter:

setenv usbbootargs 'console=ttyO3,115200n8 root=/dev/sda2'
setenv usbboot 'if usb start && fatload usb 0:1 ${loadaddr} ${variant}_${module_type}.dtb && fdt addr ${loadaddr} && fatload usb 0:1 ${kloadaddr} uImage && fatload usb 0:1 0x83000000 initramfs.bin; then setenv bootargs ${usbbootargs}; fixsig; bootm ${kloadaddr} 0x83000000 ${loadaddr}; fi; echo USB boot failed, booting from NAND; run nand_boot'
run usbboot

setenv without saveenv keeps this in RAM, so nothing on the device has been changed yet. If the USB drive does not come up, the command falls through to run nand_boot and the device boots from internal flash as usual.

Keep the definitions, not the trigger

Retyping those two lines every boot is unnecessary. Once libretouch is up, store them — and only them:

ln -s /usr/bin/fw_printenv /mnt/nv/fw_setenv
/mnt/nv/fw_setenv usbbootargs 'console=ttyO3,115200n8 root=/dev/sda2'
/mnt/nv/fw_setenv usbboot 'if usb start && fatload usb 0:1 ${loadaddr} ${variant}_${module_type}.dtb && fdt addr ${loadaddr} && fatload usb 0:1 ${kloadaddr} uImage && fatload usb 0:1 0x83000000 initramfs.bin; then setenv bootargs ${usbbootargs}; fixsig; bootm ${kloadaddr} 0x83000000 ${loadaddr}; fi; echo USB boot failed, booting from NAND; run nand_boot'

(The same thing can be done from a shell on the stock system instead — see service access — if you would rather not boot from USB first.)

Every USB boot from then on is one command at the U-Boot prompt:

run usbboot

Both variables are inert: nothing runs them unless you do. The device still boots from internal flash on its own, exactly as before, and there is nothing to undo when you are finished with it.

Why fw_setenv and not saveenv

U-Boot can store the environment itself: type the two setenv lines at the prompt, then saveenv, and they survive a reboot without involving Linux at all. It works, and on a board with a redundant environment it would be the obvious choice. Prefer fw_setenv here anyway.

saveenv writes the whole environment, not the variables you meant. It takes whatever the running U-Boot session currently holds and commits all of it. By the time you reach for it you have usually also been experimenting with bootargs, load addresses or bootcmd, and every one of those scratch values becomes permanent alongside the two you wanted. fw_setenv writes exactly the variables you name.

That includes variables U-Boot is supposed to work out for itself, and saving them turns them into overrides. Several environment variables are not stored in normal operation at all: the bootloader derives them from the board on every boot and sets them in RAM. variant is one — read from the board-ID resistor on SCM, or from the manufacturing record on SM2 — along with variant_mode, country_code and region_code. For each of them, a value already present in the stored environment wins: U-Boot reports variant override - … and uses yours instead of asking the hardware.

saveenv writes the derived values into the environment, so from the next boot onwards they are that override and the board is never consulted again. Nothing looks wrong, because the saved value is correct on the day you save it. It stops being correct when the board's own answer changes — a re-flashed manufacturing record, or the same environment on a different board. And it matters, because usbboot loads ${variant}_${module_type}.dtb: a stale variant means quietly booting the wrong device tree, or none.

printenv variant cannot tell you which is happening — the variable is in the environment either way, derived or stored. The console can: a boot that reports variant override - … is using a stored value, and one that only greets you with the variant it worked out is not. (module_type is exempt throughout — it comes from the SoC strap unconditionally and cannot be overridden this way.) fw_setenv avoids the whole problem by writing only the names you give it.

It quietly turns a trial into a modification. Keeping the first boot in RAM is the reason you can try a USB image on someone's device and hand it back untouched. One saveenv ends that, and nothing tells you it has.

It spends flash for nothing. The environment lives in NAND.U-Boot-Env, a single 128 KiB erase block with no redundant second copy, so every save is an erase-and-rewrite of that one block. The NAND in these devices is old and degrades; an interactive saveenv habit costs a dozen cycles per debugging session where writing once from Linux costs one. And it buys no durability in return — any bootloader update erases the whole partition regardless, as below.

Use saveenv when there is no other way to persist a value: a board that cannot boot far enough to run fw_setenv. That is a recovery situation, not a development one.

Why not bootcmd

Setting bootcmd=run usbboot would make USB the automatic choice, and it is not dangerous — the fallback above still applies. The problem is that it is the one route with a silent failure mode.

The device keeps two complete copies of the system, Ping and Pong, and each update installs into the copy that is not running. Which one boots is decided by a signature inside each kernel image, and the bootloader checks that before it looks at bootcmd:

Signatures (Ping / Pong) What the bootloader autoboots
Linux / Linpv — resting state after booting Ping bootcmd — USB boot works
Linpv / Linux — resting state after booting Pong run nand_boot2, silently
either slot flagged Linxt — an update waiting to boot run nand_boot or run nand_boot2
both Linux, or neither bootcmd — USB boot works

So on a device that has taken an over-the-air update and last landed in Pong, bootcmd is ignored on every boot and nothing says so — it looks as though USB boot simply stopped working. It stays that way until a later update moves the device back to Ping. whichk at the U-Boot prompt reports the state, and printenv bootcmd confirms the stored value is still yours.

Note what this is not: the bootloader stores what you write, and nothing rewrites the environment behind your back. bootcmd is bypassed, not overwritten. And this affects autoboot only — a command typed at the prompt always runs, which is why run usbboot is immune to all of it.

Unattended boot

If no serial console is attached, or the device has to come back on USB after an unattended reboot, there is no alternative: you cannot interrupt U-Boot without a console, so bootcmd is the only way in.

/mnt/nv/fw_setenv bootcmd 'run usbboot'

Read Why not bootcmd first, and know whichk before you rely on this. To go back to booting from internal flash, restore the default — that is the whole procedure, there is nothing else to undo:

/mnt/nv/fw_setenv bootcmd 'run nand_boot'

After a bootloader update

Updating the bootloader erases the environment. sweup uboot, sweup uboot preserve, run update_uboot, run update_all and the keypad emergency update all finish with Resetting UBOOT env, which restores the default bootcmd=run nand_boot; along with everything else. Re-apply the fw_setenv commands above afterwards. See the U-Boot update mechanism.

Updating a USB setup

All kernel modules are installed in the initramfs, so when you modify something kernel-related, you only have to update the files in the FAT boot partition and do not have to update anything in the root filesystem.

USB RNDIS network boot: libretouch-net

USB RNDIS network boot loads the boot components through the Micro-USB setup port and uses that same link for the NFS root filesystem. It is the preferred development and recovery method when a USB drive is inconvenient.

MACHINE=libretouch-net bitbake libretouch-image

Do not use the Micro-USB setup port and the USB-A port at the same time: they share one USB controller.

See USB RNDIS network boot for the complete host setup, including a bridge for multiple USB devices, dnsmasq configuration, TFTP artifacts, and the NFS root export.

Hybrid USB + NFS boot: libretouch-usb

After setting up booting via USB drive, you can add a file /etc/cmdline to the USB driver's rootfs containing an nfsroot=... kernel parameter. The initramfs will parse that and switch the root filesystem to NFS if it can mount it, otherwise it will use the USB rootfs.

Installing development packages with opkg

libretouch-image uses IPK packages and includes opkg. The image does not publish a package feed itself: make the packages produced by the development build available from the development host, then point the device at that host. The device must have IP connectivity to the host (for example through the Ethernet connection used for development). This is suitable for the writable development images; do not use it with a read-only production root filesystem without arranging persistent writable storage for opkg's database and package files.

Build the package to install and regenerate the package indexes. Run this in the build environment, using the same machine as the image on the device:

MACHINE=libretouch-usb bitbake <recipe> package-index

Serve the resulting feed from the build directory. Keep this command running while packages are being installed, and replace 192.0.2.10 below with the development host's address reachable by the device:

python3 -m http.server 8080 --directory tmp/deploy/ipk

On the device, use the development helper with the reachable address of the host. It writes /etc/opkg/base-feeds.conf for the project's all and cortexa8 feeds, then refreshes the package index:

setup-opkg http://192.0.2.10:8080
opkg install <package-name>

setup-opkg must succeed before installation; it downloads the Packages.gz index from both feed directories. Use opkg list to confirm that a package is visible and opkg list-installed to inspect the installed set. On later package builds, rerun bitbake package-index, then run opkg update before installing or upgrading the changed package.