{
  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 = {
    crossSystem = {
      config = "armv7l-unknown-linux-musleabihf";
    };
  };

  module = {pkgs, config, lib, lim, ... }:
    let
      openwrt = pkgs.openwrt;
      inherit (lib) mkOption types;
      inherit (pkgs.liminix.services) oneshot;
      inherit (pkgs) liminix;
      mtd_by_name_links = pkgs.liminix.services.oneshot rec  {
        name = "mtd_by_name_links";
        up = ''
          mkdir -p /dev/mtd/by-name
          cd /dev/mtd/by-name
          for i in /sys/class/mtd/mtd*[0-9]; do
            ln -s ../../$(basename $i) $(cat $i/name)
          done
        '';
      };
    in {
      imports = [
        ../../modules/arch/arm.nix
        ../../modules/outputs/tftpboot.nix
        ../../modules/outputs/mbrimage.nix
        ../../modules/outputs/extlinux.nix
      ];

      config = {
        services.mtd-name-links = mtd_by_name_links;
        kernel = {
          src = pkgs.pkgsBuildBuild.fetchurl {
            name = "linux.tar.gz";
            url = "https://cdn.kernel.org/pub/linux/kernel/v5.x/linux-5.15.137.tar.gz";
            hash = "sha256-PkdzUKZ0IpBiWe/RS70J76JKnBFzRblWcKlaIFNxnHQ=";
          };
          extraPatchPhase = ''
          ${pkgs.openwrt.applyPatches.mvebu}
        '';
          config = {
            PCI = "y";
            OF = "y";
            MEMORY = "y"; # for MVEBU_DEVBUS
            DMADEVICES = "y"; # for MV_XOR
            CPU_V7 = "y";
            ARCH_MULTIPLATFORM = "y";
            ARCH_MVEBU = "y";
            ARCH_MULTI_V7= "y";
            PCI_MVEBU = "y";
            AHCI_MVEBU = "y";
            MACH_ARMADA_38X = "y";
            SMP = "y";
	          # this is disabled for the moment because it relies on a GCC
            # plugin that requires gmp.h to build, and I can't see right now
            # how to confgure it to find gmp
            STACKPROTECTOR_PER_TASK = "n";
            NR_CPUS = "4";
            VFP = "y";
            NEON= "y";

            # WARNING: unmet direct dependencies detected for ARCH_WANT_LIBATA_LEDS
            ATA = "y";

            PSTORE = "y";
            PSTORE_RAM = "y";
            PSTORE_CONSOLE = "y";
            PSTORE_DEFLATE_COMPRESS = "n";

            BLOCK = "y";
            MMC="y";
            PWRSEQ_EMMC="y";        # ???
            PWRSEQ_SIMPLE="y";      # ???
            MMC_BLOCK="y";

            MMC_SDHCI= "y";
            MMC_SDHCI_PLTFM= "y";
            MMC_SDHCI_PXAV3= "y";
            MMC_MVSDIO= "y";

            SERIAL_8250 = "y";
            SERIAL_8250_CONSOLE = "y";
            SERIAL_OF_PLATFORM="y";
            SERIAL_MVEBU_UART = "y";
            SERIAL_MVEBU_CONSOLE = "y";

            SERIAL_8250_DMA= "y";
            SERIAL_8250_DW= "y";
            SERIAL_8250_EXTENDED= "y";
            SERIAL_8250_MANY_PORTS= "y";
            SERIAL_8250_SHARE_IRQ= "y";
            OF_ADDRESS= "y";
            OF_MDIO= "y";

            WATCHDOG = "y";        # watchdog is enabled by u-boot
            ORION_WATCHDOG = "y";  # so is non-optional to keep feeding

            MVEBU_DEVBUS = "y"; # "Device Bus controller ...  flash devices such as NOR, NAND, SRAM, and FPGA"
            MVMDIO = "y";
            MVNETA = "y";
            MVNETA_BM = "y";
            MVNETA_BM_ENABLE = "y";
            SRAM = "y"; # mmio-sram is "compatible" for bm_bppi reqd by BM
            PHY_MVEBU_A38X_COMPHY = "y"; # for eth2
            MARVELL_PHY = "y";

            USB_XHCI_MVEBU = "y";
            USB_XHCI_HCD = "y";

            MVPP2 = "y";
            MV_XOR = "y";

            # there is NOR flash on this device, which is used for U-Boot
            # and the rescue system (which we don't interfere with) but
            # also for the U-Boot environment variables (which we might
            # need to meddle with)
            MTD_SPI_NOR = "y";
            SPI = "y";
            SPI_MASTER = "y";
            SPI_ORION = "y";

            NET_DSA = "y";
            NET_DSA_MV88E6XXX = "y"; # depends on PTP_1588_CLOCK_OPTIONAL
          };
        };

        boot = {
          commandLine = [
            "console=ttyS0,115200"
            "pcie_aspm=off" # ath9k pci incompatible with PCIe ASPM
          ];
        };
        filesystem =
          let
            inherit (pkgs.pseudofile) dir symlink;
            firmware = pkgs.stdenv.mkDerivation {
              name = "wlan-firmware";
              phases = ["installPhase"];
              installPhase = ''
                mkdir $out
                cp -r ${pkgs.linux-firmware}/lib/firmware/ath10k/QCA988X $out
              '';
            };
          in dir {
            lib = dir {
              firmware = dir {
                ath10k = symlink firmware;
              };
            };
            etc = dir {
              "fw_env.config" =
                let f = pkgs.writeText "fw_env.config" ''
                  /dev/mtd/by-name/u-boot-env 0x0 0x10000 0x10000
                '';
                in symlink f;
            };
          };

        boot.tftp = {
          loadAddress = lim.parseInt "0x1000000";
          kernelFormat = "zimage";
          compressRoot = true;
        };

        hardware = let
          mac80211 = pkgs.mac80211.override {
            drivers = ["ath9k_pci" "ath10k_pci"];
            klibBuild = config.system.outputs.kernel.modulesupport;
          };
        in {
          defaultOutput = "mtdimage";
          loadAddress = lim.parseInt "0x00800000"; # "0x00008000";
          entryPoint = lim.parseInt "0x00800000"; # "0x00008000";
          rootDevice = "/dev/mmcblk0p1";

          dts = {
            src = "${config.system.outputs.kernel.modulesupport}/arch/arm/boot/dts/armada-385-turris-omnia.dts";
            includes =  [
              "${config.system.outputs.kernel.modulesupport}/arch/arm/boot/dts/"
            ];
          };
          flash.eraseBlockSize = 65536; # only used for tftpboot
          networkInterfaces =
            let
              inherit (config.system.service.network) link;
              inherit (config.system.service) bridge;
            in rec {
              en70000 = link.build {
                # in armada-38x.dtsi this is eth0.
                # It's connected to port 5 of the 88E6176 switch
                devpath = "/devices/platform/soc/soc:internal-regs/f1070000.ethernet";
                # name is unambiguous but not very semantic
                ifname = "en70000";
              };
              en30000 = link.build {
                # in armada-38x.dtsi this is eth1
                # It's connected to port 6 of the 88E6176 switch
                devpath = "/devices/platform/soc/soc:internal-regs/f1030000.ethernet";
                # name is unambiguous but not very semantic
                ifname = "en30000";
              };
              # the default (from the dts? I'm guessing) behavour for
              # lan ports on the switch is to attach them to
              # en30000. It should be possible to do something better,
              # 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.

              wan = link.build {
                # in armada-38x.dtsi this is eth2. It may be connected to
                # an ethernet phy or to the SFP cage, depending on a gpio
                devpath = "/devices/platform/soc/soc:internal-regs/f1034000.ethernet";
                ifname = "wan";
              };

              lan0 = link.build { ifname = "lan0"; };
              lan1 = link.build { ifname = "lan1"; };
              lan2 = link.build { ifname = "lan2"; };
              lan3 = link.build { ifname = "lan3"; };
              lan4 = link.build { ifname = "lan4"; };
              lan5 = link.build { ifname = "lan5"; };
              lan = lan0; # maybe we should build a bridge?

              wlan = link.build {
                ifname = "wlan0";
                dependencies = [ mac80211 ];
              };
              wlan5 = link.build {
                ifname = "wlan1";
                dependencies = [ mac80211 ];
              };
            };
        };
      };
    };
}