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_sm2andburns_sm2have 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.