Compare commits

...

6 Commits

8 changed files with 143 additions and 67 deletions

View File

@ -2,6 +2,27 @@
description = ''
Turris Omnia
************
This is a 32 bit ARMv7 MVEBU device, which is usually shipped with
TurrisOS, an OpenWrt-based system. Rather than reformatting the
builtin storage, we install Liminix on to the existing btrfs
filesystem so that the vendor snapshot/recovery system continues
to work (and provides you an easy rollback if you decide you don't
like Liminix after all).
The install process is designed so that you should not need to open
the device and add a serial console (although it may be handy
for visibility and in case anything goes wrong). In outline
1. build a "recovery" system with useful btrfs tools
2. boot that system using TFTP or a USB stick
3. once booted, mount the real root filesystem on /mnt
4. take a snapshot using schnapps, and then delete everything
5. use min-copy-closure -d /mnt/@ to copy the real configuration
to the device
6. reboot into a fully operational system
Detailed instructions to follow...
'';
system = {
@ -30,7 +51,6 @@
imports = [
../../modules/arch/arm.nix
../../modules/outputs/tftpboot.nix
../../modules/outputs/ext4fs.nix
../../modules/outputs/mbrimage.nix
../../modules/outputs/extlinux.nix
];
@ -133,7 +153,6 @@
"console=ttyS0,115200"
"pcie_aspm=off" # ath9k pci incompatible with PCIe ASPM
];
imageFormat = "fit";
};
filesystem =
let
@ -142,9 +161,9 @@
name = "wlan-firmware";
phases = ["installPhase"];
installPhase = ''
mkdir $out
cp -r ${pkgs.linux-firmware}/lib/firmware/ath10k/QCA988X $out
'';
mkdir $out
cp -r ${pkgs.linux-firmware}/lib/firmware/ath10k/QCA988X $out
'';
};
in dir {
lib = dir {
@ -176,7 +195,7 @@
defaultOutput = "mtdimage";
loadAddress = lim.parseInt "0x00800000"; # "0x00008000";
entryPoint = lim.parseInt "0x00800000"; # "0x00008000";
rootDevice = "/dev/mtdblock0";
rootDevice = "/dev/mmcblk0p1";
dts = {
src = "${config.system.outputs.kernel.modulesupport}/arch/arm/boot/dts/armada-385-turris-omnia.dts";
@ -210,7 +229,7 @@
# per
# https://www.kernel.org/doc/html/latest/networking/dsa/configuration.html#affinity-of-user-ports-to-cpu-ports
# but apparently OpenWrt doesn't either so maybe it's more
# complicated than it looks
# complicated than it looks.
wan = link.build {
# in armada-38x.dtsi this is eth2. It may be connected to

View File

@ -44,10 +44,12 @@ in rec {
../modules/ntp
../modules/ssh
../modules/outputs/btrfs.nix
../modules/outputs/extlinux.nix
];
hostname = "rotuer";
rootfsType = "btrfs";
rootOptions = "subvol=@";
boot.loader.extlinux.enable = true;
services.hostap = svc.hostapd.build {
interface = config.hardware.networkInterfaces.wlan;

View File

@ -47,6 +47,11 @@ in {
"ubifs"
];
};
rootOptions = mkOption {
type = types.nullOr types.str;
default = null;
};
boot = {
commandLine = mkOption {
type = types.listOf types.nonEmptyStr;
@ -95,7 +100,8 @@ in {
"root=${config.hardware.rootDevice}"
"rootfstype=${config.rootfsType}"
"fw_devlink=off"
];
] ++ lib.optional (config.rootOptions != null) "rootflags=${config.rootOptions}";
users.root = {
uid = 0; gid= 0; gecos = "Root of all evaluation";
dir = "/home/root/";

View File

@ -6,6 +6,7 @@
}:
let
inherit (lib) mkIf mkEnableOption mkOption types concatStringsSep;
inherit (pkgs.pseudofile) dir symlink;
cfg = config.boot.loader.extlinux;
o = config.system.outputs;
cmdline = concatStringsSep " " config.boot.commandLine;
@ -17,7 +18,7 @@ in {
};
options.boot.loader.extlinux.enable = mkEnableOption "extlinux";
config = { # mkIf cfg.enable {
config = mkIf cfg.enable {
system.outputs.extlinux = pkgs.runCommand "extlinux" {} ''
mkdir $out
cd $out
@ -27,7 +28,7 @@ in {
mkdir extlinux
cat > extlinux/extlinux.conf << _EOF
menu title Liminix
timeout 100
timeout 40
label Liminix
kernel /boot/kernel
# initrd /boot/initramfs
@ -35,5 +36,8 @@ in {
${if wantsDtb then "fdt /boot/dtb" else ""}
_EOF
'';
filesystem = dir {
boot = symlink config.system.outputs.extlinux;
};
};
}

7
pkgs/preinit/opts.h Normal file
View File

@ -0,0 +1,7 @@
struct root_opts {
char *device;
char *fstype;
char *mount_opts;
};
void parseopts(char * cmdline, struct root_opts *opts);

View File

@ -3,6 +3,9 @@
#include <string.h>
#include <unistd.h>
#include "opts.h"
static int begins_with(char * str, char * prefix)
{
while(*prefix) {
@ -36,34 +39,37 @@ char * pr_u32(int32_t input) {
i+=2;
buf[i] ='\0';
write(2, buf, i);
return buf;
if(write(2, buf, i))
return buf;
else
return NULL;
}
static char *eat_word(char *p)
{
while(*p && (*p != ' ')) p++;
void parseopts(char * cmdline, char **root, char **rootfstype) {
*root = 0;
*rootfstype = 0;
if(*p) {
*p = '\0';
p++;
};
return p;
}
static char * eat_param(char *p, char *param_name, char **out)
{
if(begins_with(p, param_name)) {
*out = p + strlen(param_name);
p = eat_word(p);
};
return p;
}
void parseopts(char * cmdline, struct root_opts *opts) {
for(char *p = cmdline; *p; p++) {
if(begins_with(p, "root=")) {
*root = p + strlen("root=");
while(*p && (*p != ' ')) p++;
if(*p) {
*p = '\0';
p++;
};
};
if(begins_with(p, "rootfstype=")) {
*rootfstype = p + strlen("rootfstype=");
while(*p && (*p != ' ')) p++;
if(*p) {
*p = '\0';
p++;
};
};
p = eat_param(p, "root=", &(opts->device));
p = eat_param(p, "rootfstype=", &(opts->fstype));
p = eat_param(p, "rootflags=", &(opts->mount_opts));
};
}
@ -82,45 +88,59 @@ void parseopts(char * cmdline, char **root, char **rootfstype) {
int main()
{
char * root = "/dev/hda1";
char * rootfstype = "xiafs";
struct root_opts opts = {
.device = "/dev/hda1",
.fstype = "xiafs",
.mount_opts = NULL
};
char *buf;
// finds root= and rootfstype= options
buf = strdup("liminix console=ttyS0,115200 panic=10 oops=panic init=/bin/init loglevel=8 root=/dev/ubi0_4 rootfstype=ubifs fw_devlink=off mtdparts=phram0:18M(rootfs) phram.phram=phram0,0x40400000,18874368,65536 root=/dev/mtdblock0 foo");
parseopts(buf, &root, &rootfstype);
expect_equal(root, "/dev/mtdblock0");
expect_equal(rootfstype, "ubifs");
// finds root= rootfstype= rootflags= options
buf = strdup("liminix console=ttyS0,115200 panic=10 oops=panic init=/bin/init loglevel=8 root=/dev/ubi0_4 rootfstype=ubifs rootflags=subvol=1 fw_devlink=off mtdparts=phram0:18M(rootfs) phram.phram=phram0,0x40400000,18874368,65536 root=/dev/mtdblock0 foo");
memset(&opts, '\0', sizeof opts); parseopts(buf, &opts);
expect_equal(opts.device, "/dev/mtdblock0");
expect_equal(opts.fstype, "ubifs");
expect_equal(opts.mount_opts, "subvol=1");
// in case of duplicates, chooses the latter
// also: works if the option is end of string
buf = strdup("liminix console=ttyS0,115200 panic=10 oops=panic init=/bin/init loglevel=8 root=/dev/ubi0_4 rootfstype=ubifs fw_devlink=off mtdparts=phram0:18M(rootfs) phram.phram=phram0,0x40400000,18874368,65536 root=/dev/mtdblock0");
parseopts(buf, &root, &rootfstype);
expect_equal(root, "/dev/mtdblock0");
expect_equal(rootfstype, "ubifs");
memset(&opts, '\0', sizeof opts); parseopts(buf, &opts);
expect_equal(opts.device, "/dev/mtdblock0");
expect_equal(opts.fstype, "ubifs");
// options may appear in either order
buf = strdup("liminix fw_devlink=off root=/dev/hda1 rootfstype=ubifs foo");
parseopts(buf, &root, &rootfstype);
expect_equal(root, "/dev/hda1");
expect_equal(rootfstype, "ubifs");
memset(&opts, '\0', sizeof opts); parseopts(buf, &opts);
expect_equal(opts.device, "/dev/hda1");
expect_equal(opts.fstype, "ubifs");
// works when rootflags is the last option
buf = strdup("liminix fw_devlink=off root=/dev/hda1 rootfstype=ubifs rootflags=subvol=@");
memset(&opts, '\0', sizeof opts); parseopts(buf, &opts);
expect_equal(opts.device, "/dev/hda1");
expect_equal(opts.fstype, "ubifs");
expect_equal(opts.mount_opts, "subvol=@");
buf = strdup("liminix rootfstype=ubifs fw_devlink=off root=/dev/hda1 foo");
parseopts(buf, &root, &rootfstype);
expect_equal(rootfstype, "ubifs");
expect_equal(root, "/dev/hda1");
memset(&opts, '\0', sizeof opts); parseopts(buf, &opts);
expect_equal(opts.fstype, "ubifs");
expect_equal(opts.device, "/dev/hda1");
// provides NULL for missing options
buf = strdup("liminix rufustype=ubifs fw_devlink=off foot=/dev/hda1");
parseopts(buf, &root, &rootfstype);
if(rootfstype) die("expected null rootfstype, got \"%s\"", rootfstype);
if(root) die("expected null root, got \"%s\"", root);
memset(&opts, '\0', sizeof opts); parseopts(buf, &opts);
if(opts.fstype) die("expected null rootfstype, got \"%s\"", opts.fstype);
if(opts.device) die("expected null root, got \"%s\"", opts.device);
if(opts.mount_opts) die("expected null mount_opts, got \"%s\"", opts.mount_opts);
// provides empty strings for empty options
buf = strdup("liminix rootfstype= fw_devlink=off root= /dev/hda1");
parseopts(buf, &root, &rootfstype);
if(strlen(rootfstype)) die("expected empty rootfstype, got \"%s\"", rootfstype);
if(strlen(root)) die("expected null root, got \"%s\"", root);
memset(&opts, '\0', sizeof opts); parseopts(buf, &opts);
if(strlen(opts.fstype)) die("expected empty rootfstype, got \"%s\"", opts.fstype);
if(strlen(opts.device)) die("expected null root, got \"%s\"", opts.device);
expect_equal("01", pr_u32(1));
expect_equal("ab", pr_u32(0xab));

View File

@ -10,7 +10,7 @@
#include <asm/setup.h> /* for COMMAND_LINE_SIZE */
void parseopts(char * cmdline, char **root, char **rootfstype);
#include "opts.h"
#define ERR(x) write(2, x, strlen(x))
#define AVER(c) do { if(c < 0) { ERR("failed: " #c ": error=0x" ); pr_u32(errno); ERR("\n"); } } while(0)
@ -49,8 +49,11 @@ char buf[COMMAND_LINE_SIZE];
int main(int argc, char *argv[], char *envp[])
{
char *rootdevice = 0;
char *rootfstype = 0;
struct root_opts opts = {
.device = NULL,
.fstype = NULL,
.mount_opts = NULL
};
write(1, banner, strlen(banner));
@ -73,16 +76,20 @@ int main(int argc, char *argv[], char *envp[])
ERR("failed: open(\"/proc/cmdline\")\n");
die();
}
parseopts(buf, &rootdevice, &rootfstype);
parseopts(buf, &opts);
if(rootdevice) {
if(!rootfstype) rootfstype = "jffs2"; /* backward compatibility */
if(opts.device) {
if(!opts.fstype) opts.fstype = "jffs2"; /* backward compatibility */
write(1, "rootdevice ", 11);
write(1, rootdevice, strlen(rootdevice));
write(1, opts.device, strlen(opts.device));
write(1, " (", 2);
write(1, rootfstype, strlen(rootfstype));
write(1, opts.fstype, strlen(opts.fstype));
if(opts.mount_opts) {
write(1, ", opts=", 7);
write(1, opts.mount_opts, strlen(opts.mount_opts));
}
write(1, ")\n", 2);
AVER(mount(rootdevice, "/target/persist", rootfstype, 0, NULL));
AVER(mount(opts.device, "/target/persist", opts.fstype, 0, opts.mount_opts));
AVER(mount("/target/persist/nix", "/target/nix",
"bind", MS_BIND, NULL));

View File

@ -84,8 +84,19 @@ in attrset:
#!/bin/sh -e
prefix=\''${1-/}
src=\''${prefix}$out
mkdir -p \$prefix/persist
cp -v -fP \$src/bin/* \$src/etc/* \$prefix/persist
dest=\$prefix
${# if we are running on a normal mounted system then
# the actual device root is mounted on /persist
# and /nix is bind mounted from /persist/nix
# (see the code in preinit). So we need to check for this
# case otherwise we will install into a ramfs/rootfs
""
}
if test -d $dest/persist; then dest=$dest/persist; fi
cp -v -fP \$src/bin/* \$src/etc/* \$dest
${if attrset ? boot then ''
(cd \$prefix && rm ./boot && ln -sf ${lib.strings.removePrefix "/" attrset.boot.target} ./boot)
'' else ""}
EOF
chmod +x $out/bin/install
'';