alc8800/nixos-module.nix
Tsubajashi bafd15d665 Add AIC8800 USB Wi-Fi driver as a NixOS module
This commit introduces the AIC8800 USB Wi-Fi driver, including firmware installation and configurable udev rules for USB disk ejection and power management. The driver supports kernel versions 6.17 and above, ensuring compatibility with recent changes in the Linux kernel. Documentation is also provided in README.md for usage and configuration.
2025-12-14 04:48:26 +01:00

153 lines
5.2 KiB
Nix

{ 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"
'');
};
}