diff --git a/INSTALL_SCRIPT.md b/INSTALL_SCRIPT.md index 6fd5873..5ba31c3 100644 --- a/INSTALL_SCRIPT.md +++ b/INSTALL_SCRIPT.md @@ -35,6 +35,15 @@ cannot download dependencies because the database is stale, run `sudo pacman -Syu` yourself, reboot if the kernel was updated, and rerun the installer with headers matching the running kernel. +The USB modules declare the complete firmware set shipped in `fw/`, including +configuration files and extension patches, so initramfs tools can include those +files when they include the modules. Rerun the installer after updating the +driver to rebuild DKMS modules and refresh the current kernel's initramfs. +If the adapter only works after reloading its modules after boot, check that +the initramfs contains its `aic8800*/` firmware directory (issue #117). +Custom firmware paths supplied with `aic_fw_path` still need to be included +in the initramfs by the administrator. + > For more information, please refer to the [README.md](README.md) file.
diff --git a/drivers/aic8800/aic8800_fdrv/rwnx_main.c b/drivers/aic8800/aic8800_fdrv/rwnx_main.c index 4421b9e..b27e6ba 100644 --- a/drivers/aic8800/aic8800_fdrv/rwnx_main.c +++ b/drivers/aic8800/aic8800_fdrv/rwnx_main.c @@ -9725,7 +9725,11 @@ MODULE_IMPORT_NS("VFS_internal_I_am_really_a_filesystem_and_am_NOT_a_driver"); #elif LINUX_VERSION_CODE >= KERNEL_VERSION(5, 4, 0) MODULE_IMPORT_NS(VFS_internal_I_am_really_a_filesystem_and_am_NOT_a_driver); #endif +#ifdef AICWF_USB_SUPPORT +#include "../aicwf_firmware.h" +#else MODULE_FIRMWARE(RWNX_CONFIG_FW_NAME); +#endif MODULE_DESCRIPTION(RW_DRV_DESCRIPTION); MODULE_VERSION(RWNX_VERS_MOD); diff --git a/drivers/aic8800/aic8800_fdrv/rwnx_platform.c b/drivers/aic8800/aic8800_fdrv/rwnx_platform.c index d4da1b2..da41d2b 100644 --- a/drivers/aic8800/aic8800_fdrv/rwnx_platform.c +++ b/drivers/aic8800/aic8800_fdrv/rwnx_platform.c @@ -3972,6 +3972,7 @@ struct device *rwnx_platform_get_dev(struct rwnx_plat *rwnx_plat) } +#ifndef AICWF_USB_SUPPORT #ifndef CONFIG_RWNX_SDM MODULE_FIRMWARE(RWNX_AGC_FW_NAME); MODULE_FIRMWARE(RWNX_FCU_FW_NAME); @@ -3981,5 +3982,6 @@ MODULE_FIRMWARE(RWNX_MAC_FW_NAME); #ifndef CONFIG_RWNX_TL4 MODULE_FIRMWARE(RWNX_MAC_FW_NAME2); #endif +#endif /* !AICWF_USB_SUPPORT */ diff --git a/drivers/aic8800/aic_load_fw/aic_bluetooth_main.c b/drivers/aic8800/aic_load_fw/aic_bluetooth_main.c index c590fb1..bf4585d 100644 --- a/drivers/aic8800/aic_load_fw/aic_bluetooth_main.c +++ b/drivers/aic8800/aic_load_fw/aic_bluetooth_main.c @@ -9,7 +9,6 @@ #include "aicwf_txq_prealloc.h" -#define DRV_CONFIG_FW_NAME "fw.bin" #define DRV_DESCRIPTION "AIC BLUETOOTH" #define DRV_COPYRIGHT "Copyright(c) 2015-2020 AICSemi" #define DRV_AUTHOR "AICSemi" @@ -75,7 +74,7 @@ MODULE_IMPORT_NS("VFS_internal_I_am_really_a_filesystem_and_am_NOT_a_driver"); MODULE_IMPORT_NS(VFS_internal_I_am_really_a_filesystem_and_am_NOT_a_driver); #endif -MODULE_FIRMWARE(DRV_CONFIG_FW_NAME); +#include "../aicwf_firmware.h" MODULE_DESCRIPTION(DRV_DESCRIPTION); MODULE_VERSION(DRV_VERS_MOD); MODULE_AUTHOR(DRV_COPYRIGHT " " DRV_AUTHOR); diff --git a/drivers/aic8800/aic_load_fw/aicwf_usb.c b/drivers/aic8800/aic_load_fw/aicwf_usb.c index 3f550f6..a7d1c85 100644 --- a/drivers/aic8800/aic_load_fw/aicwf_usb.c +++ b/drivers/aic8800/aic_load_fw/aicwf_usb.c @@ -1773,11 +1773,17 @@ static int aicwf_usb_probe(struct usb_interface *intf, const struct usb_device_i goto out_free_bus; } - if (system_config(usb_dev)) { + ret = system_config(usb_dev); + if (ret) { + if (ret > 0) + ret = -EIO; goto out_free_bus; } - if (aicfw_download_fw(usb_dev)){ + ret = aicfw_download_fw(usb_dev); + if (ret) { + if (ret > 0) + ret = -EIO; goto out_free_bus; } diff --git a/drivers/aic8800/aicwf_firmware.h b/drivers/aic8800/aicwf_firmware.h new file mode 100644 index 0000000..97a8796 --- /dev/null +++ b/drivers/aic8800/aicwf_firmware.h @@ -0,0 +1,95 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Files installed below /lib/firmware by install.sh. + * + * Both USB modules may be loaded in an initramfs. Declare the complete matched + * firmware set, including configuration and extension patches, so initramfs + * generators can copy it along with either module. Keep this list in sync with + * fw/; tests/firmware/test_firmware.py checks the installed paths. + */ +#ifndef AICWF_FIRMWARE_H +#define AICWF_FIRMWARE_H + +MODULE_FIRMWARE("aic8800/aic_userconfig.txt"); +MODULE_FIRMWARE("aic8800/fmacfw.bin"); +MODULE_FIRMWARE("aic8800/fmacfw_m2d.bin"); +MODULE_FIRMWARE("aic8800/fmacfw_no_msg_ep.bin"); +MODULE_FIRMWARE("aic8800/fmacfw_no_msg_ep_rf.bin"); +MODULE_FIRMWARE("aic8800/fmacfw_rf.bin"); +MODULE_FIRMWARE("aic8800/fw_adid.bin"); +MODULE_FIRMWARE("aic8800/fw_adid_rf.bin"); +MODULE_FIRMWARE("aic8800/fw_adid_u03.bin"); +MODULE_FIRMWARE("aic8800/fw_ble_scan.bin"); +MODULE_FIRMWARE("aic8800/fw_ble_scan_ad_filter_dcdc.bin"); +MODULE_FIRMWARE("aic8800/fw_ble_scan_ad_filter_ldo.bin"); +MODULE_FIRMWARE("aic8800/fw_patch.bin"); +MODULE_FIRMWARE("aic8800/fw_patch_rf.bin"); +MODULE_FIRMWARE("aic8800/fw_patch_table.bin"); +MODULE_FIRMWARE("aic8800/fw_patch_table_u03.bin"); +MODULE_FIRMWARE("aic8800/fw_patch_u03.bin"); +MODULE_FIRMWARE("aic8800/m2d_ota.bin"); +MODULE_FIRMWARE("aic8800D80/aic_userconfig_8800d80.txt"); +MODULE_FIRMWARE("aic8800D80/calibmode_8800d80.bin"); +MODULE_FIRMWARE("aic8800D80/fmacfw_8800d80_u02.bin"); +MODULE_FIRMWARE("aic8800D80/fmacfw_8800d80_u02_ipc.bin"); +MODULE_FIRMWARE("aic8800D80/fw_adid_8800d80_u02.bin"); +MODULE_FIRMWARE("aic8800D80/fw_ble_scan_ad_filter.bin"); +MODULE_FIRMWARE("aic8800D80/fw_patch_8800d80_u02.bin"); +MODULE_FIRMWARE("aic8800D80/fw_patch_table_8800d80_u02.bin"); +MODULE_FIRMWARE("aic8800D80/lmacfw_rf_8800d80_u02.bin"); +MODULE_FIRMWARE("aic8800D80N/aic_powerlimit_8800d80n.txt"); +MODULE_FIRMWARE("aic8800D80N/aic_userconfig_8800d80n.txt"); +MODULE_FIRMWARE("aic8800D80N/fmacfw_btusb_8800d80n_u02.bin"); +MODULE_FIRMWARE("aic8800D80N/fmacfw_calib_8800d80n_u02.bin"); +MODULE_FIRMWARE("aic8800D80N/fmacfw_cinit_8800d80n_u02.bin"); +MODULE_FIRMWARE("aic8800D80N/fmacfw_patch_8800d80n_u02.bin"); +MODULE_FIRMWARE("aic8800D80N/fmacfw_patch_tbl_8800d80n_u02.bin"); +MODULE_FIRMWARE("aic8800D80N/fw_patch_8800d80n_rf_u02.bin"); +MODULE_FIRMWARE("aic8800D80N/fw_patch_8800d80n_rf_u02_ext0.bin"); +MODULE_FIRMWARE("aic8800D80N/fw_patch_8800d80n_u02.bin"); +MODULE_FIRMWARE("aic8800D80N/fw_patch_8800d80n_u02_ext0.bin"); +MODULE_FIRMWARE("aic8800D80N/fw_patch_table_8800d80n_rf_u02.bin"); +MODULE_FIRMWARE("aic8800D80N/fw_patch_table_8800d80n_u02.bin"); +MODULE_FIRMWARE("aic8800D80N/lmacfw_rf_8800d80n.bin"); +MODULE_FIRMWARE("aic8800D80X2/aic_powerlimit_8800d80x2.txt"); +MODULE_FIRMWARE("aic8800D80X2/aic_userconfig_8800d80x2.txt"); +MODULE_FIRMWARE("aic8800D80X2/aicbt.conf"); +MODULE_FIRMWARE("aic8800D80X2/fmacfw_8800d80x2.bin"); +MODULE_FIRMWARE("aic8800D80X2/fw_adid_8800d80x2_u03.bin"); +MODULE_FIRMWARE("aic8800D80X2/fw_adid_8800d80x2_u05.bin"); +MODULE_FIRMWARE("aic8800D80X2/fw_patch_8800d80x2_u03.bin"); +MODULE_FIRMWARE("aic8800D80X2/fw_patch_8800d80x2_u05.bin"); +MODULE_FIRMWARE("aic8800D80X2/fw_patch_8800d80x2_u05_ext0.bin"); +MODULE_FIRMWARE("aic8800D80X2/fw_patch_8800d80x2_u05_ext1.bin"); +MODULE_FIRMWARE("aic8800D80X2/fw_patch_table_8800d80x2_u03.bin"); +MODULE_FIRMWARE("aic8800D80X2/fw_patch_table_8800d80x2_u05.bin"); +MODULE_FIRMWARE("aic8800D80X2/lmacfw_rf_8800d80x2.bin"); +MODULE_FIRMWARE("aic8800DC/aic_powerlimit_8800dc.txt"); +MODULE_FIRMWARE("aic8800DC/aic_powerlimit_8800dw.txt"); +MODULE_FIRMWARE("aic8800DC/aic_userconfig_8800dc.txt"); +MODULE_FIRMWARE("aic8800DC/aic_userconfig_8800dw.txt"); +MODULE_FIRMWARE("aic8800DC/aic_userconfig_8800dw_u2.txt"); +MODULE_FIRMWARE("aic8800DC/aic_userconfig_8800dw_w311.txt"); +MODULE_FIRMWARE("aic8800DC/fmacfw_calib_8800dc_h_u02.bin"); +MODULE_FIRMWARE("aic8800DC/fmacfw_calib_8800dc_u02.bin"); +MODULE_FIRMWARE("aic8800DC/fmacfw_patch_8800dc_h_u02.bin"); +MODULE_FIRMWARE("aic8800DC/fmacfw_patch_8800dc_ipc_u02.bin"); +MODULE_FIRMWARE("aic8800DC/fmacfw_patch_8800dc_u02.bin"); +MODULE_FIRMWARE("aic8800DC/fmacfw_patch_tbl_8800dc_h_u02.bin"); +MODULE_FIRMWARE("aic8800DC/fmacfw_patch_tbl_8800dc_ipc_u02.bin"); +MODULE_FIRMWARE("aic8800DC/fmacfw_patch_tbl_8800dc_u02.bin"); +MODULE_FIRMWARE("aic8800DC/fw_adid_8800dc_u02.bin"); +MODULE_FIRMWARE("aic8800DC/fw_adid_8800dc_u02h.bin"); +MODULE_FIRMWARE("aic8800DC/fw_patch_8800dc_u02.bin"); +MODULE_FIRMWARE("aic8800DC/fw_patch_8800dc_u02_ext0.bin"); +MODULE_FIRMWARE("aic8800DC/fw_patch_8800dc_u02h.bin"); +MODULE_FIRMWARE("aic8800DC/fw_patch_table_8800dc_u02.bin"); +MODULE_FIRMWARE("aic8800DC/fw_patch_table_8800dc_u02h.bin"); +MODULE_FIRMWARE("aic8800DC/lmacfw_rf_8800dc.bin"); +MODULE_FIRMWARE("aic8800DLN/aic_powerlimit_8800dln.txt"); +MODULE_FIRMWARE("aic8800DLN/aic_userconfig_8800dln.txt"); +MODULE_FIRMWARE("aic8800DLN/fmacfw_gaintbl_8800dln.bin"); +MODULE_FIRMWARE("aic8800DLN/fmacfw_initvar_8800dln.bin"); + +#endif /* AICWF_FIRMWARE_H */ + diff --git a/tests/firmware/test_firmware.py b/tests/firmware/test_firmware.py new file mode 100644 index 0000000..e210294 --- /dev/null +++ b/tests/firmware/test_firmware.py @@ -0,0 +1,126 @@ +#!/usr/bin/env python3 +"""Check initramfs firmware inventory and extracted probe error handling. + +Run with Python 3 and a C compiler. Pass --modules BUILD_DIR to also check the +firmware metadata in built modules using modinfo. No modules are installed. +The probe test mocks USB and allocation, not kernel scheduling or hardware. +""" +from pathlib import Path +import argparse +import os +import re +import subprocess +import tempfile +import unittest + +parser = argparse.ArgumentParser() +parser.add_argument("--modules", type=Path) +args, remaining = parser.parse_known_args() +ROOT = Path(__file__).resolve().parents[2] + + +def function(source, signature): + start = source.index(signature) + brace = source.index("{", start) + end, depth = brace + 1, 1 + while depth: + depth += (source[end] == "{") - (source[end] == "}") + end += 1 + return source[start:end] + + +class FirmwareTests(unittest.TestCase): + def test_declared_files_match_installed_inventory(self): + expected = {p.relative_to(ROOT / "fw").as_posix() + for p in (ROOT / "fw").rglob("*") if p.is_file()} + header = (ROOT / "drivers/aic8800/aicwf_firmware.h").read_text() + declared = re.findall(r'MODULE_FIRMWARE\("([^"]+)"\)', header) + self.assertEqual(len(declared), len(set(declared))) + self.assertEqual(set(declared), expected) + + @unittest.skipUnless(args.modules, "pass --modules to inspect built modules") + def test_built_modules_declare_installed_files(self): + expected = {p.relative_to(ROOT / "fw").as_posix() + for p in (ROOT / "fw").rglob("*") if p.is_file()} + for name in ("aic_load_fw", "aic8800_fdrv"): + with self.subTest(module=name): + module = args.modules / name / (name + ".ko") + declared = subprocess.check_output( + ["modinfo", "-F", "firmware", str(module)], text=True).splitlines() + self.assertEqual(set(declared), expected) + + def test_probe_errors_and_intentional_reenumeration(self): + source = (ROOT / "drivers/aic8800/aic_load_fw/aicwf_usb.c").read_text() + probe = function(source, "static int aicwf_usb_probe(") + # The real post-allocation probe and cleanup, with allocations supplied + # by the fixture, exercises the actual failure labels and return value. + body = probe[probe.index("ret = aicwf_bus_start(bus_if);"):probe.rfind("}")] + prelude = r''' +#include +#include +#include +#include +#define USB_SPEED_HIGH 2 +#define USB_SPEED_SUPER 3 +#define PRODUCT_ID_AIC8801 1 +#define PRODUCT_ID_AIC8800D81 2 +#define PRODUCT_ID_AIC8800D81X2 3 +#define PRODUCT_ID_AIC8800D89X2 4 +#define usb_err(...) ((void)0) +#define printk(...) ((void)0) +#define kfree(...) ((void)0) +struct usb_device { int speed; }; +struct aic_usb_dev { int chipid; bool app_cmp; void *rx_priv; }; +static int config_result, download_result, cleanup, downloads, reboots, fw_loaded; +static struct aic_usb_dev device; +static struct usb_device usb_device; +static int aicwf_bus_start(void *bus) { return 0; } +static void aic_bt_platform_init(struct aic_usb_dev *dev) {} +static void system_reboot(struct aic_usb_dev *dev) { reboots++; } +static void rwnx_send_reboot(struct aic_usb_dev *dev) { reboots++; } +static int system_config(struct aic_usb_dev *dev) { return config_result; } +static int aicfw_download_fw(struct aic_usb_dev *dev) { downloads++; return download_result; } +static void aicwf_bus_deinit(void *dev) { cleanup++; } +static void aicwf_rx_deinit(void *dev) {} +static void aicwf_usb_deinit(void *dev) {} +static void usb_set_intfdata(void *intf, void *data) {} +static int probe(void) { + int ret = 0; + struct aic_usb_dev *usb_dev = &device; + struct usb_device *usb = &usb_device; + void *bus_if = NULL, *dev = NULL, *intf = NULL; +''' + checks = r''' +} +static void setup(void) { + config_result = download_result = cleanup = downloads = reboots = fw_loaded = 0; + device.chipid = 0; device.app_cmp = false; usb_device.speed = USB_SPEED_HIGH; +} +int main(void) { + setup(); assert(probe() == 0 && device.app_cmp && fw_loaded && downloads == 1); + setup(); config_result = -EIO; + assert(probe() == -EIO && cleanup == 1 && downloads == 0 && !fw_loaded); + setup(); config_result = 1; + assert(probe() == -EIO && cleanup == 1 && downloads == 0); + setup(); download_result = -ENOMEM; + assert(probe() == -ENOMEM && cleanup == 1 && !device.app_cmp && !fw_loaded); + setup(); download_result = 1; assert(probe() == -EIO && cleanup == 1); + setup(); device.chipid = PRODUCT_ID_AIC8800D81; + assert(probe() == 0 && reboots == 1 && cleanup == 1 && downloads == 0); + setup(); usb_device.speed = 1; + assert(probe() == 0 && reboots == 1 && cleanup == 1 && downloads == 0); + return 0; +} +''' + with tempfile.TemporaryDirectory(prefix="aic-firmware-test-") as temp: + c_file = Path(temp) / "probe.c" + binary = Path(temp) / "probe" + c_file.write_text(prelude + body + checks) + subprocess.run([os.environ.get("CC", "cc"), "-std=gnu11", "-o", + str(binary), str(c_file)], check=True) + subprocess.run([str(binary)], check=True) + + +if __name__ == "__main__": + unittest.main(argv=[__file__] + remaining) +