Skip to content

Commit 6d95f3f

Browse files
committed
rework USB-gadget
USB-gadget has been rewritten and spun-off into a new project https://github.com/macmpi/xg_multi/ Now works with any host OS, including Windows. Provides ethernet, serial console, and mass-storage Note: gadget IDs have changed. To use serial terminal, Modem Manager rule excluding ttyACM0 may need to be updated on Linux hosts: ATTRS{idVendor}=="1d6b" ATTRS{idProduct}=="0104", ENV{ID_MM_DEVICE_IGNORE}="1"
1 parent b3f858c commit 6d95f3f

8 files changed

Lines changed: 46 additions & 63 deletions

File tree

.gitmodules

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
[submodule "xg_multi"]
2+
path = xg_multi
3+
url = ../xg_multi
4+
branch = main

README.md

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -9,13 +9,13 @@ An optional script may also be launched during that same initial bootstrap, to p
99

1010
## Setup procedure:
1111
Please follow [Alpine Linux Wiki](https://wiki.alpinelinux.org/wiki/Installation#Installation_Overview) to download & create installation media for the target platform.\
12-
Tools provided here can be used on any plaform for any install modes (diskless, data disk, system disk).
12+
Tools provided here can be used on any hardware platform to prepare for any install modes (diskless, data disk, system disk).
1313

1414
Just add [**headless.apkovl.tar.gz**](https://is.gd/apkovl_master)[^2] overlay file *as-is* at the root of Alpine Linux boot media (or onto any custom side-media) and boot-up the system.\
1515
With default DCHP-based network interface definitions (and [SSID/pass](#extra-configuration) file if using wifi), system can then be remotely accessed with: `ssh root@<IP>`\
1616
(system IP address may be determined with any IP scanning tools such as `nmap`).
1717

18-
As with Alpine Linux initial bring-up, `root` account has no password initially (change that during target setup!).\
18+
As with Alpine Linux initial bring-up, `root` account has no password initially.\
1919
From there, actual system install can be performed as usual with `setup-alpine` for instance (check [wiki](https://wiki.alpinelinux.org/wiki/Alpine_setup_scripts#setup-alpine) for details).
2020

2121
## Extra configuration:
@@ -26,17 +26,18 @@ Extra files may be added next to `headless.apkovl.tar.gz` to customise boostrapp
2626
- `authorized_keys` (*optional*): provide client's public SSH key to secure `root` ssh login.
2727
- `ssh_host_*_key*` (*optional*): provide server's custom ssh keys to be injected (may be stored), instead of using bundled ones[^2] (not stored). Providing an empty key file will trigger new keys generation (ssh server may take longer to start).
2828
- `opt-out` (*optional*): dummy file to opt-out internet features (connection status, version check, auto-update) and related links usage anonymous [telemetry](https://is.gd/privacy.php).
29-
- `auto-updt` (*optional*): allow apkovl file automatic update with latest from master branch. If it contains *reboot* keyword all in one line, system will reboot after succesful update (unless ssh session is active or `unattended.sh` script is available).
29+
- `auto-updt` (*optional*): enable automatic `headless.apkovl.tar.gz` file update with latest from master branch. If it contains `reboot` keyword all in one line, system will reboot after succesful update (unless ssh session is active or `unattended.sh` script is available).
3030

3131
Main execution steps are logged: `cat /var/log/messages | grep headless`.
3232

3333
## Goody:
34-
Seamless USB-serial & USB-ethernet gadget mode (*e.g. PiZero*):
35-
- Make sure dwc2/dwc3 driver is loaded accordingly, and device configuration is set to `peripheral` mode: this may be hardware (including cable) and/or software driven.\
36-
(on supporting Pi devices, just add `dtoverlay=dwc2,dr_mode=peripheral` in `usercfg.txt` (or `config.txt`) to force by software)
34+
Seamless USB-gadget mode: serial console, ethernet and mass-storage (*e.g. on PiZero*), leveraging [xg_multi](https://github.com/macmpi/xg_multi/).
35+
- Make sure `dwc2` (or `dwc3`) driver is previously loaded on device, and configuration is set to **OTG peripheral** mode: this may be driven by hardware (including cable) and/or software.\
36+
(on supporting Pi devices, just add `dtoverlay=dwc2,dr_mode=peripheral` in `usercfg.txt` (or `config.txt`) to force both by software)
3737
- Plug USB cable into host Computer port before boot.\
38-
Serial terminal can then be connected-to from host Computer (e.g. `cu -l ttyACM0` on Linux. xon/xoff flow control).\
39-
Alternatively, with host Computer set-up to share networking with USB interface as 10.42.0.1 gateway, one can log into device from host with: `ssh root@10.42.0.2`.
38+
-- serial terminal can then be connected-to from host Computer (e.g. `cu -l ttyACM0` on Linux. xon/xoff flow control).\
39+
-- alternatively, with host Computer ECM/RNDIS interface set-up as 10.42.0.1 (sharing internet or not), one can log into device from host with: `ssh root@10.42.0.2`.\
40+
-- volume containing `headless.apkovl.tar.gz` file may be accessed/mounted from host, and config files easily edited. Make sure to unmount properly before removing USB plug.
4041

4142
[^1]: Initial boot fully preserves system's original state (config files & installed packages): a fresh system will therefore come-up as unconfigured.
4243

@@ -54,4 +55,3 @@ Execute `./make.sh` to rebuild `headless.apkovl.tar.gz` after changes.\
5455

5556
## Credits
5657
Thanks for the initial guides & scripts from @sodface and @davidmytton.
57-

headless.apkovl.tar.gz

2.77 KB
Binary file not shown.

headless.apkovl.tar.gz.sha512

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
ea1cea5f22afc342685a0ee7437bf4d61b2d263a6cc13c88c2595852cd1f080701ee6a8228e6d66d3f004c9d33fbbf88e2e889822ecbf33d66778ca392e1cb4c headless.apkovl.tar.gz
1+
f529c18862f34a29148ca38c92b63553d42b4cc8646bad092bad572625d9a12248b8c8f6cb27615cc3802d4b75450c3d05b17bf80578617bb6a7901f179df56d headless.apkovl.tar.gz

make.sh

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ if [ -n "$build_path" ]; then
1818
# prefer timestamp option for touch as it works on directories too
1919
t_stamp="$( TZ=UTC date +%Y%m%d0000.00 )"
2020
cp -a overlay "$build_path"/.
21+
cp xg_multi/xg_multi "$build_path"/overlay/usr/local/bin/.
2122
find "$build_path"/overlay/ -exec sh -c 'TZ=UTC touch -chm -t "$0" "$1"' "$t_stamp" {} \;
2223
# setting modes and owner/groups for runtime (won't affect mtime)
2324
find "$build_path"/overlay/etc -type d -exec chmod 755 {} \;

overlay/etc/modprobe.d/headless_gadget.conf

Lines changed: 0 additions & 6 deletions
This file was deleted.

overlay/usr/local/bin/headless_bootstrap

Lines changed: 30 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -35,8 +35,11 @@ _preserve() {
3535
_restore() {
3636
# Remove element (file, folder, symlink) and replace by
3737
# previous back-up if available.
38+
# shellcheck disable=SC2317 # known special case
3839
[ -z "${1}" ] && return 1
40+
# shellcheck disable=SC2317 # known special case
3941
rm -rf "${1}"
42+
# shellcheck disable=SC2317 # known special case
4043
[ -e "${1}".orig ] && mv -f "${1}".orig "${1}"
4144
}
4245

@@ -62,11 +65,11 @@ cat <<-EOF >>/tmp/.trash/headless_cleanup
6265
_apk del wpa_supplicant
6366
_restore "/etc/network/interfaces"
6467
_restore "/etc/hostname"
65-
rm -f /etc/modprobe.d/headless_gadget.conf
6668
6769
# Remove from boot service to avoid spurious openrc recalls from unattended script.
6870
rm -f /etc/runlevels/default/headless_bootstrap
6971
rm -f /usr/local/bin/headless_bootstrap
72+
rm -f /usr/local/bin/xg_multi
7073
7174
# Run unattended script if available.
7275
install -m755 ${ovlpath}/unattended.sh /tmp/headless_unattended >/dev/null 2>&1 && \
@@ -76,11 +79,11 @@ cat <<-EOF >>/tmp/.trash/headless_cleanup
7679
rm -f /etc/init.d/headless_*
7780
_logger "Clean-up done, enjoy !"
7881
cat /tmp/.trash/banner >/dev/console
79-
if [ -c /dev/ttyGS${gdgt_id} ]; then
82+
if [ -c /dev/ttyGS0 ]; then
8083
# Enabling terminal login into valid serial port:
8184
# no choice than making permanent change to /etc/securetty (Alpine 3.19 already has ttyGS0).
82-
grep -q "ttyGS${gdgt_id}" /etc/securetty || echo "ttyGS${gdgt_id}" >>/etc/securetty
83-
/sbin/getty -L 115200 /dev/ttyGS${gdgt_id} vt100 &
85+
grep -q "ttyGS0" /etc/securetty || echo "ttyGS0" >>/etc/securetty
86+
/sbin/getty -L 115200 /dev/ttyGS0 vt100 &
8487
fi
8588
exit 0
8689
EOF
@@ -122,9 +125,10 @@ fi
122125
# Define sshd-session & al files new location into sshd_config
123126
for f in /tmp/.trash/ssh/sshd-*; do
124127
[ -e "$f" ] || continue # protect failing glob
125-
name=$(echo $(basename $f) | cut -c6-)
126-
initial=$(echo $name | cut -c1 | tr [a-z] [A-Z])
127-
final=$(echo $name | cut -c2-)
128+
name=$(basename "$f" | cut -c6-)
129+
# shellcheck disable=SC2018,SC2019
130+
initial=$(echo "$name" | cut -c1 | tr 'a-z' 'A-Z')
131+
final=$(echo "$name" | cut -c2-)
128132
echo "Sshd${initial}${final}Path $f" >>/etc/ssh/sshd_config
129133
done
130134

@@ -262,7 +266,7 @@ if ! install -m644 "${ovlpath}"/interfaces /etc/network/interfaces >/dev/null 2>
262266
# According to below we could rely on DEVTYPE for wlan devices
263267
# https://lists.freedesktop.org/archives/systemd-devel/2014-January/015999.html
264268
# but...some wlan might still be ill-behaved: use wlan_lst
265-
# shellcheck disable=SC2169 # ash does support string replacement.
269+
# shellcheck disable=SC3060 # ash does support string replacement.
266270
_has_wifi && ! [ "${wlan_lst/$INTERFACE/}" = "$wlan_lst" ] && \
267271
cat <<-EOF >>/etc/network/interfaces
268272
auto $INTERFACE
@@ -271,7 +275,7 @@ if ! install -m644 "${ovlpath}"/interfaces /etc/network/interfaces >/dev/null 2>
271275
EOF
272276
# Ensure considered gadget interface is actually the connected one (may have several).
273277
[ "$DEVTYPE" = "gadget" ] && \
274-
find /sys/class/udc/*/device/gadget."${gdgt_id}"/net/"$INTERFACE" -maxdepth 0 >/dev/null 2>&1 && \
278+
grep -vwq "0" /sys/class/net/"$INTERFACE"/carrier_up_count && \
275279
cat <<-EOF >>/etc/network/interfaces && cat <<-EOF >/etc/resolv.conf
276280
auto $INTERFACE
277281
iface $INTERFACE inet static
@@ -310,37 +314,7 @@ hostname -F /etc/hostname
310314
rc-service networking restart
311315
}
312316

313-
_setup_gadget() {
314-
## Load composite USB Serial/USB Ethernel driver & setup terminal.
315-
_logger "Enabling USB-gadget Serial and Ethernet ports"
316-
# Remove conflicting modules in case they were initially loaded (cmdline.txt).
317-
modprobe -r g_serial g_ether g_cdc
318-
modprobe g_cdc
319-
# Wait for g_cdc to settle and serial ports become available
320-
timeout 1 sh <<-EOF
321-
while ! grep -q "ttyGS" /proc/devices; do sleep 0.2; done
322-
EOF
323317

324-
# Determine which gadget ID is connected with USB cable (assume just one max).
325-
# (setting console to unconnected serial port would block boot)
326-
gdgt_id="$( find /sys/class/udc/*/current_speed -exec \
327-
sh -c 'grep -vq "UNKNOWN" "$0" && find ${0/current_speed/}device/gadget.* -maxdepth 0' {} \; \
328-
| sed 's/\/.*gadget\.//' )"
329-
if [ -c /dev/ttyGS"${gdgt_id}" ]; then
330-
# Default serial config: xon/xoff flow control.
331-
stty -F /dev/ttyGS"${gdgt_id}"
332-
setconsole /dev/ttyGS"${gdgt_id}"
333-
# Notes to users willing to connect from Linux Ubuntu-based host terminal:
334-
# - user on host needs to be part of dialout group (reboot required), and
335-
# - disable spurious AT commands from ModemManager on host-side Gadget serial port
336-
# one may create a /etc/udev/rules.d/99-ttyacms-gadget.rules as per:
337-
# https://linux-tips.com/t/prevent-modem-manager-to-capture-usb-serial-devices/284/2
338-
# ATTRS{idVendor}=="0525" ATTRS{idProduct}=="a4aa", ENV{ID_MM_DEVICE_IGNORE}="1"
339-
else
340-
_logger "USB-gadget port not connected !"
341-
modprobe -r g_cdc
342-
fi
343-
}
344318

345319

346320
#############################################################################
@@ -353,13 +327,6 @@ _logger "Alpine Linux headless bootstrap v$HDLSBSTRP_VERSION by macmpi"
353327
# Help randomness for wpa_supplicant and sshd (urandom until 3.16).
354328
rc-service seedrng restart || rc-service urandom restart
355329

356-
# Setup USB gadget ports if some ports are enabled in peripheral mode.
357-
# Note: we assume dwc2/dwc3 is pre-loaded, we just check mode.
358-
gdgt_id=""
359-
find /sys/class/udc/*/is_a_peripheral -print0 2>/dev/null | \
360-
xargs -0 cat 2>/dev/null | grep -q "0" && \
361-
_setup_gadget
362-
363330
# Determine ovl file location.
364331
# Grab used ovl filename from dmesg.
365332
ovl="$( dmesg | grep -o 'Loading user settings from .*:' | awk '{print $5}' | sed 's/:.*$//' )"
@@ -372,6 +339,22 @@ else
372339
ovl="${ovlpath}/${ovl}"
373340
fi
374341

342+
## Setup USB gadget ports if any connected
343+
/usr/local/bin/xg_multi -V "$( df | grep "$ovlpath" | awk '{print $1}' )" >/dev/null 2>&1
344+
# Setting console to connected serial port when available
345+
if [ -c /dev/ttyGS0 ]; then
346+
# Default serial config: xon/xoff flow control.
347+
stty -F /dev/ttyGS0
348+
setconsole /dev/ttyGS0
349+
# Notes to users willing to connect from Linux Ubuntu-based host terminal:
350+
# - user on host needs to be part of dialout group (reboot required), and
351+
# - disable spurious AT commands from ModemManager on host-side gadget serial port
352+
# one may create a /etc/udev/rules.d/99-ttyacms-gadget.rules as per:
353+
# https://linux-tips.com/t/prevent-modem-manager-to-capture-usb-serial-devices/284/2
354+
# ATTRS{idVendor}=="1d6b" ATTRS{idProduct}=="0104", ENV{ID_MM_DEVICE_IGNORE}="1"
355+
fi
356+
357+
375358
# Create banner file.
376359
warn=""
377360
grep -q "${ovlpath}.*[[:space:]]ro[[:space:],]" /proc/mounts; is_ro=$?
@@ -381,7 +364,7 @@ _is_ro && warn="(remount partition rw!)"
381364
cat <<-EOF >/tmp/.trash/banner
382365
383366
Alpine Linux headless bootstrap v$HDLSBSTRP_VERSION by macmpi
384-
367+
385368
You may want to delete/rename .apkovl file before reboot ${warn}:
386369
${ovl}
387370
(can be done automatically with unattended script - see sample snippet)

xg_multi

Submodule xg_multi added at 13a734f

0 commit comments

Comments
 (0)