alarm-tegra-x1 / rootfs/sbin-init

raw file 7480 bytes
#!/bin/sh
# ALARM PID 1 for the mainline Switch kernel.
#
# Installed into the root filesystem by alarm-build.
#
# The switchroot L4T initramfs is deliberately bypassed (rdinit=/nonexistent in
# the boot script): its userspace `mount` hangs when mounting this same ext4
# filesystem, while the kernel mounts it without trouble from
# prepare_namespace(). So the kernel mounts the root and this script takes over.
#
# The Switch has no wired console and the panel scanout is block-linear and
# unreadable, so everything is logged three ways:
#   * /dev/kmsg  -> lands in dmesg and in the ramoops pstore region
#   * /var/log/alarm-init.log on the root filesystem
#   * the FAT boot partition, which a PC can read with no console at all

LOG=/var/log/alarm-init.log
FAT=/mnt/boot

mkdir -p /var/log 2>/dev/null
: > "$LOG" 2>/dev/null

say() {
	echo "[alarm-init] $*" > /dev/kmsg 2>/dev/null
	echo "[alarm-init] $*" >> "$LOG" 2>/dev/null
}

# Publish the complete state to the FAT partition. Called more than once so the
# state is readable on a PC even if a later step hangs.
publish() {
	mkdir -p "$FAT" 2>/dev/null
	if mount /dev/mmcblk0p1 "$FAT" 2>/dev/null; then
		{
			echo "=== alarm-init reached userspace: $1 ==="
			echo "uptime : $(cat /proc/uptime)"
			echo "kernel : $(cat /proc/version)"
			echo "cmdline: $(cat /proc/cmdline)"
			echo
			echo "--- mounts ---"
			cat /proc/mounts
			echo
			echo "--- UDC ---"
			ls -l /sys/class/udc/ 2>&1
			cat /sys/class/udc/*/state 2>&1
			echo
			echo "--- usb_role ---"
			ls -l /sys/class/usb_role/ 2>&1
			cat /sys/class/usb_role/*/role 2>&1
			echo
			echo "--- ttyGS ---"
			ls -l /dev/ttyGS* 2>&1
			echo
			echo "--- gadget ---"
			ls -l /sys/kernel/config/usb_gadget/ 2>&1
			echo
			echo "--- log ---"
			cat "$LOG" 2>/dev/null
			echo
			echo "--- dmesg ---"
			dmesg
		} > "$FAT/ALARM_USERS.txt" 2>&1
		sync
		umount "$FAT" 2>/dev/null
	fi
}

say "starting"
say "kernel : $(cat /proc/version)"
say "cmdline: $(cat /proc/cmdline)"

# --- filesystems -------------------------------------------------------------
mount -t proc proc /proc 2>/dev/null
mount -t sysfs sysfs /sys 2>/dev/null
mount -t devtmpfs devtmpfs /dev 2>/dev/null
mkdir -p /dev/pts /dev/shm
mount -t devpts devpts /dev/pts 2>/dev/null
[ -e /dev/fd ] || ln -s /proc/self/fd /dev/fd
say "filesystems mounted"

# --- clock ------------------------------------------------------------------
# The Tegra RTC (rtc0) reads 1970 on this board, and a 1970 system clock makes
# *every* package signature look invalid -- pacman reports
# "invalid or corrupted package (PGP signature)" for the whole transaction,
# which looks like a corrupt download or a broken keyring and is neither.
#
# The MAX77620's RTC (rtc1) is battery-backed and holds the real time, so take
# the clock from there when it is available.
if [ -e /dev/rtc1 ]; then
	hwclock -f /dev/rtc1 -s 2>/dev/null
	say "clock from PMIC RTC: $(date 2>/dev/null)"
else
	say "no /dev/rtc1; clock is $(date 2>/dev/null)"
fi

# The ALARM image ships no hostname, so the prompt otherwise shows the literal
# string "(none)".
echo "switch" > /proc/sys/kernel/hostname 2>/dev/null
say "hostname set to $(cat /proc/sys/kernel/hostname 2>/dev/null)"

# --- USB serial console gadget ----------------------------------------------
# The USB-C port is the only console this machine can have.
say "creating USB ACM gadget"
mkdir -p /sys/kernel/config
if mount -t configfs none /sys/kernel/config 2>/dev/null; then
	say "configfs mounted"
else
	say "configfs mount FAILED"
fi
say "UDC devices: $(ls /sys/class/udc/ 2>&1)"

UDC=""
for c in 700d0000.usb 700d0000.xudc; do
	if [ -e "/sys/class/udc/$c" ]; then UDC="$c"; break; fi
done
say "chosen UDC: '${UDC}'"

if [ -n "$UDC" ]; then
	G=/sys/kernel/config/usb_gadget/alarm
	mkdir -p "$G"
	cd "$G" || exit 1
	echo 0x0955 > idVendor
	echo 0x7020 > idProduct
	echo 0x0100 > bcdDevice
	echo 0xEF > bDeviceClass
	echo 0x02 > bDeviceSubClass
	echo 0x01 > bDeviceProtocol
	mkdir -p strings/0x409
	echo "ALARM00000001" > strings/0x409/serialnumber
	echo "Nintendo" > strings/0x409/manufacturer
	echo "Switch mainline" > strings/0x409/product
	mkdir -p configs/c.1/strings/0x409
	echo "ACM + ECM" > configs/c.1/strings/0x409/configuration

	# Serial console.
	mkdir -p functions/acm.GS0
	ln -sf functions/acm.GS0 configs/c.1/ 2>/dev/null

	# Ethernet. This is what gives the PC a network path to the Switch with no
	# WiFi and no PCIe involved at all, which in turn is what makes ssh -- and
	# therefore a headless desktop over VNC -- possible.
	#
	# NCM, not ECM. ECM sends one Ethernet frame per USB transfer and measured
	# ~33 KiB/s here, roughly a thousandth of what USB 2.0 can do. NCM
	# aggregates many frames per transfer and is the same thing the host's
	# cdc_ncm driver expects.
	mkdir -p functions/ncm.usb0

	# Queue multiplier: how much the gadget queues per transfer. It is only
	# writable while the function is unbound, which is why it has to be set
	# here rather than after the gadget is live.
	echo 10 > functions/ncm.usb0/qmult 2>/dev/null

	ln -sf functions/ncm.usb0 configs/c.1/ 2>/dev/null

	say "binding gadget to $UDC"
	echo "$UDC" > UDC
	say "bind exit code: $?"
	cd /
	say "UDC state: $(cat /sys/class/udc/*/state 2>&1)"
	say "ttyGS    : $(ls /dev/ttyGS* 2>&1)"

	# usb0 appears once the host enumerates the NCM function. Give it a fixed
	# address; the host side should use 192.168.7.1/24. No DHCP is involved, so
	# nothing has to be running on either end for this to work.
	n=0
	while [ ! -e /sys/class/net/usb0 ] && [ $n -lt 50 ]; do
		sleep 0.1
		n=$((n + 1))
	done

	if [ -e /sys/class/net/usb0 ]; then
		ip addr add 192.168.7.2/24 dev usb0 2>/dev/null
		ip link set usb0 up 2>/dev/null

		# Make the link actually usable without a manual step every boot: the
		# PC is the gateway (it NATs out). It is NOT a resolver though -- the
		# PC runs no DNS server, so listing it first just makes every lookup
		# wait for a timeout. Use real resolvers and let NAT carry them.
		ip route add default via 192.168.7.1 dev usb0 2>/dev/null
		printf 'nameserver 1.1.1.1\nnameserver 8.8.8.8\n' > /etc/resolv.conf 2>/dev/null

		say "usb0 up: $(ip -4 addr show usb0 2>/dev/null | grep -o 'inet [0-9./]*' | head -1)"
		say "default route: $(ip route show default 2>/dev/null | head -1)"
	else
		say "usb0 did not appear - the host may not have enumerated the ECM function"
	fi
else
	say "NO UDC FOUND - there will be no console"
fi

publish "after gadget setup"

# --- USB role switch --------------------------------------------------------
# The padctl usb2-0 port is mode "otg" with role-switch-default-mode =
# "peripheral", so it should already be a device. Verify it, and force the role
# if it is not: a port left in host mode means the XUDC is never connected to
# the pads and the host sees nothing at all, which matches what we observe.
say "usb_role class: $(ls /sys/class/usb_role/ 2>&1)"
for r in /sys/class/usb_role/*/role; do
	[ -e "$r" ] || continue
	say "role before: $(cat "$r" 2>&1)  ($r)"
	echo device > "$r" 2>/dev/null
	say "role after : $(cat "$r" 2>&1)"
done
say "UDC state after role fix: $(cat /sys/class/udc/*/state 2>&1)"

publish "after role switch"

# --- hand over ---------------------------------------------------------------
say "handing over to /etc/alarm-init.sh"
publish "before handover"
if [ -x /etc/alarm-init.sh ]; then
	exec /etc/alarm-init.sh
fi
say "no /etc/alarm-init.sh, falling back to /bin/sh"
exec /bin/sh