{ config, lib, pkgs, aic8800-src ? null, ... }: with lib; let cfg = config.hardware.aic8800; # Get source from function argument, specialArgs, or throw error aic8800Src = if aic8800-src != null then "${aic8800-src}/usr/src/AIC8800" else if builtins.hasAttr "_module" config && builtins.hasAttr "args" config._module && builtins.hasAttr "aic8800-src" config._module.args then "${config._module.args.aic8800-src}/usr/src/AIC8800" else throw '' aic8800-src must be provided either: 1. As a function argument when importing the module, or 2. Via specialArgs in your flake.nix: specialArgs = { aic8800-src = inputs.aic8800-src; }; ''; aic8800Firmware = pkgs.stdenvNoCC.mkDerivation { pname = "aic8800-firmware"; version = "2022-12-19"; src = aic8800Src; installPhase = '' runHook preInstall mkdir -p $out/lib/firmware cp -r fw/aic8800D80 $out/lib/firmware/ runHook postInstall ''; meta = with lib; { description = "Firmware blobs for AIC8800 USB adapters"; license = licenses.unfreeRedistributableFirmware; platforms = platforms.linux; }; }; aic8800Driver = config.boot.kernelPackages.callPackage ({ stdenv, lib, kernel, python3 }: stdenv.mkDerivation { pname = "aic8800-driver"; version = "2022-12-19"; src = aic8800Src; nativeBuildInputs = (kernel.moduleBuildDependencies or [ ]) ++ [ python3 ]; postPatch = '' python3 <<'EOF' from pathlib import Path header = Path('drivers/aic8800/aic8800_fdrv/aicwf_usb.h') lines = header.read_text().splitlines() define_line = '#define USB_PRODUCT_ID_AIC8800D80 0x8d80' if all('USB_PRODUCT_ID_AIC8800D80' not in line for line in lines): out_lines = [] inserted = 0 for line in lines: out_lines.append(line) if line.strip().startswith('#define USB_PRODUCT_ID_AIC8800D81'): out_lines.append(define_line) inserted += 1 if inserted == 0: raise SystemExit('marker for USB product IDs missing') header.write_text('\n'.join(out_lines) + '\n') table = Path('drivers/aic8800/aic8800_fdrv/aicwf_usb.c') table_text = table.read_text() table_anchor = '{USB_DEVICE_AND_INTERFACE_INFO(USB_VENDOR_ID_AIC, USB_PRODUCT_ID_AIC8800D81, 0xff, 0xff, 0xff)},\n' table_entry = '{USB_DEVICE_AND_INTERFACE_INFO(USB_VENDOR_ID_AIC, USB_PRODUCT_ID_AIC8800D80, 0xff, 0xff, 0xff)},\n' if table_entry not in table_text: if table_anchor not in table_text: raise SystemExit('USB table anchor missing') table.write_text(table_text.replace(table_anchor, table_anchor + table_entry, 1)) EOF ''; hardeningDisable = [ "pic" "format" ]; buildPhase = '' runHook preBuild make -C drivers/aic8800 \ KDIR=${kernel.dev}/lib/modules/${kernel.modDirVersion}/build \ ARCH=${stdenv.hostPlatform.linuxArch} runHook postBuild ''; installPhase = '' runHook preInstall modDir=$out/lib/modules/${kernel.modDirVersion}/kernel/drivers/net/wireless install -Dm644 drivers/aic8800/aic_load_fw/aic_load_fw.ko \ "$modDir/aic_load_fw.ko" install -Dm644 drivers/aic8800/aic8800_fdrv/aic8800_fdrv.ko \ "$modDir/aic8800_fdrv.ko" mkdir -p $out/lib/firmware cp -r --no-preserve=mode,ownership fw/aic8800D80 $out/lib/firmware/ runHook postInstall ''; meta = with lib; { description = "UGREEN / AIC8800 USB Wi-Fi driver"; homepage = "https://www.ugreen.com"; license = licenses.unfreeRedistributableFirmware; platforms = platforms.linux; }; }) { inherit (pkgs) python3; }; in { options.hardware.aic8800 = { enable = mkEnableOption "AIC8800 USB Wi-Fi driver support"; enableUdevRules = mkOption { type = types.bool; default = true; description = "Enable udev rules for automatic USB disk ejection and power management"; }; }; config = mkIf cfg.enable { boot.extraModulePackages = [ aic8800Driver ]; boot.kernelModules = lib.mkAfter [ "aic_load_fw" "aic8800_fdrv" ]; hardware.firmware = [ aic8800Firmware ]; services.udev.extraRules = mkIf cfg.enableUdevRules (lib.mkAfter '' # UGREEN AX900 shows up as a tiny storage device; ejecting it flips to Wi-Fi mode KERNEL=="sd*", ACTION=="add", SUBSYSTEM=="block", \ ATTRS{idVendor}=="a69c", ATTRS{idProduct}=="5721", \ SYMLINK+="aicudisk", RUN+="${pkgs.util-linux}/bin/eject /dev/%k" KERNEL=="sd*", ACTION=="add", SUBSYSTEM=="block", \ ATTRS{idVendor}=="a69c", ATTRS{idProduct}=="5723", \ SYMLINK+="tendaudisk", RUN+="${pkgs.util-linux}/bin/eject /dev/%k" KERNEL=="sd*", ACTION=="add", SUBSYSTEM=="block", \ ATTRS{idVendor}=="a69c", ATTRS{idProduct}=="5724", \ SYMLINK+="ugreenudisk", RUN+="${pkgs.util-linux}/bin/eject /dev/%k" # Keep the Wi-Fi interface awake to avoid round-trip spikes after autosuspend ACTION=="add", SUBSYSTEM=="usb", ATTR{idVendor}=="a69c", ATTR{idProduct}=="8d80", \ TEST=="power/control", ATTR{power/control}="on" ''); }; }