Files
aic8800d80/tests/firmware/test_request_firmware.py
T
kalpak c8f4b69b76 fix(legacy-mcu1): resolve fdrv firmware dirs without CONFIG_PLATFORM_UBUNTU
The chip directory lookup in aic8800_fdrv's aic_request_firmware() is
guarded by CONFIG_PLATFORM_UBUNTU, but the fdrv Makefile never passes
-DCONFIG_PLATFORM_UBUNTU (only aic_load_fw's Makefile does). With
CONFIG_USE_FW_REQUEST=y the Wi-Fi driver therefore requests bare names
such as aic_userconfig_8800d80.txt, which are not found, so the user
config (TX power tables and the rest) is silently skipped.

Guard the lookup with AICWF_USB_SUPPORT only, and make the firmware test
compile each module with the macros its own Makefile defines.
2026-11-02 18:07:27 +04:00

194 lines
8.1 KiB
Python

#!/usr/bin/env python3
"""Exercise the real firmware request/load functions with mocked kernel services."""
from pathlib import Path
import os
import re
import subprocess
import tempfile
import unittest
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]
PRELUDE = r'''
#include <assert.h>
#include <errno.h>
#include <limits.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define CONFIG_USE_FW_REQUEST 1
#define printk(...) ((void)0)
#define AICWFDBG(...) ((void)0)
typedef uint32_t u32;
typedef int MD5_CTX;
static void MD5Init(MD5_CTX *m) {}
static void MD5Update(MD5_CTX *m, unsigned char *b, int size) {}
static void MD5Final(MD5_CTX *m, unsigned char *b) {}
struct firmware { size_t size; const unsigned char *data; };
struct aic_usb_dev { int chipid; };
struct aicwf_bus { struct { struct aic_usb_dev *usb; } bus_priv; };
struct device { void *data; };
struct rwnx_hw { struct device *dev; };
static struct aic_usb_dev usb;
static struct aicwf_bus bus = { .bus_priv.usb = &usb };
static struct device dev = { .data = &bus };
static struct device *expected_device;
static struct rwnx_hw hw;
static const unsigned char bytes[] = { 1, 3, 5, 7, 9 };
static struct firmware fw = { sizeof(bytes), bytes };
static const char *names[2];
static int errors[2], requests, releases, allocation_fail;
static void *dev_get_drvdata(struct device *d) { return d->data; }
static int request_firmware(const struct firmware **out, const char *name,
struct device *d) {
assert(requests < 2 && names[requests]);
assert(!strcmp(name, names[requests]));
assert(d == expected_device);
int err = errors[requests++];
*out = err ? NULL : &fw;
return err;
}
static void release_firmware(const struct firmware *f) {
if (f) { assert(f == &fw); releases++; }
}
static void *vmalloc(size_t size) { return allocation_fail ? NULL : malloc(size); }
static void setup(int chip, const char *name) {
usb.chipid = chip; bus.bus_priv.usb = &usb; dev.data = &bus;
hw.dev = expected_device = &dev;
names[0] = name; names[1] = NULL;
errors[0] = errors[1] = requests = releases = allocation_fail = 0;
fw.size = sizeof(bytes);
}
'''
CHECKS = r'''
static void success(void) {
u32 *out = (void *)1;
assert(load(&out, "fw.bin") == sizeof(bytes));
assert(out && !memcmp(out, bytes, sizeof(bytes)));
assert(releases == 1);
free(out);
}
int main(void) {
for (unsigned int i = 0; i < sizeof(cases) / sizeof(cases[0]); i++) {
char path[256];
snprintf(path, sizeof(path), "%s/fw.bin", cases[i].dir);
#ifdef EXPECT_CHIPDIR
setup(cases[i].chip, path);
#else
setup(cases[i].chip, "fw.bin");
#endif
success(); assert(requests == 1);
}
setup(-1, "fw.bin"); success();
setup(cases[0].chip, "fw.bin"); hw.dev = expected_device = NULL; success();
setup(cases[0].chip, "fw.bin"); dev.data = NULL; success();
setup(cases[0].chip, "fw.bin"); bus.bus_priv.usb = NULL; success();
#ifdef EXPECT_CHIPDIR
char path[256];
snprintf(path, sizeof(path), "%s/fw.bin", cases[0].dir);
setup(cases[0].chip, path);
errors[0] = -ENOENT; names[1] = "fw.bin";
success(); assert(requests == 2);
setup(cases[0].chip, path);
errors[0] = -ENOENT; names[1] = "fw.bin"; errors[1] = -ENOENT;
u32 *out = (void *)1;
assert(load(&out, "fw.bin") == -ENOENT && !out && requests == 2);
setup(cases[0].chip, path); errors[0] = -EACCES; out = (void *)1;
assert(load(&out, "fw.bin") == -EACCES && !out && requests == 1);
setup(cases[0].chip, path); errors[0] = -ENOMEM; out = (void *)1;
assert(load(&out, "fw.bin") == -ENOMEM && !out && requests == 1);
char long_name[300]; memset(long_name, 'a', sizeof(long_name));
long_name[sizeof(long_name) - 1] = 0;
setup(cases[0].chip, NULL); out = (void *)1;
assert(load(&out, long_name) == -ENAMETOOLONG && !out && !requests);
#endif
u32 *buffer = (void *)1;
setup(-1, "fw.bin"); allocation_fail = 1;
assert(load(&buffer, "fw.bin") == -ENOMEM && !buffer && releases == 1);
setup(-1, "fw.bin"); fw.size = 0; buffer = (void *)1;
assert(load(&buffer, "fw.bin") == -EINVAL && !buffer && releases == 1);
setup(-1, "fw.bin"); fw.size = (size_t)INT_MAX + 1; buffer = (void *)1;
assert(load(&buffer, "fw.bin") == -EINVAL && !buffer && releases == 1);
return 0;
}
'''
class RequestFirmwareTests(unittest.TestCase):
def test_paths_device_context_and_failures(self):
variants = {
"aic_load_fw": ("aicbluetooth.c", "aic_load_firmware", {
"aic8800": ["AIC8800", "AIC8801"],
"aic8800DC": ["AIC8800DC", "AIC8800DW"],
"aic8800D80": ["AIC8800D80", "AIC8800D81"],
"aic8800D80X2": ["AIC8800D80X2", "AIC8800D81X2",
"AIC8800D89X2", "AIC8800D40X2"],
}),
"aic8800_fdrv": ("rwnx_platform.c", "rwnx_load_firmware", {
"aic8800": ["AIC8801"],
"aic8800DC": ["AIC8800DC", "AIC8800DW"],
"aic8800D80": ["AIC8800D81"],
"aic8800D80X2": ["AIC8800D81X2", "AIC8800D89X2"],
"aic8800D80N": ["AIC8800D80N"],
"aic8800DLN": ["AIC8800DLN"],
}),
}
for module, (filename, loader, mapping) in variants.items():
folder = ROOT / "drivers/aic8800" / module
source = (folder / filename).read_text()
enums = re.search(r"enum AICWF_IC\s*\{.*?\};",
(folder / "aicwf_usb.h").read_text(), re.S).group()
code = PRELUDE + enums + function(source, "static int aic_request_firmware(")
code += function(source, "static int " + loader + "(")
if module == "aic8800_fdrv":
code += function(source, "int rwnx_request_firmware_common(")
call = "rwnx_request_firmware_common(&hw, out, name)"
else:
call = "aic_load_firmware(out, name, hw.dev)"
code += "\nstatic int load(u32 **out, const char *name) { return " + call + "; }\n"
code += "static const struct { int chip; const char *dir; } cases[] = {\n"
for directory, chips in mapping.items():
for chip in chips:
code += '{PRODUCT_ID_' + chip + ', "' + directory + '"},\n'
code += "};\n" + CHECKS
# The flags the module's own Makefile passes must select the chip
# directories: aic_load_fw defines both macros, aic8800_fdrv only
# AICWF_USB_SUPPORT.
makefile = (folder / "Makefile").read_text()
build = sorted(set(re.findall(
r"^ccflags-\S+\s*\+=\s*-D(CONFIG_PLATFORM_UBUNTU|AICWF_USB_SUPPORT)\b",
makefile, re.M)))
chipdir = (lambda d: "AICWF_USB_SUPPORT" in d) if module == "aic8800_fdrv" else \
(lambda d: "AICWF_USB_SUPPORT" in d and "CONFIG_PLATFORM_UBUNTU" in d)
self.assertTrue(chipdir(build), (module, build))
for defines in ([], ["CONFIG_PLATFORM_UBUNTU", "AICWF_USB_SUPPORT"],
["CONFIG_PLATFORM_UBUNTU"], ["AICWF_USB_SUPPORT"], build):
if chipdir(defines):
defines = defines + ["EXPECT_CHIPDIR"]
with self.subTest(module=module, defines=defines):
with tempfile.TemporaryDirectory(prefix="aic-fw-request-") as temp:
c_file, binary = Path(temp) / "test.c", Path(temp) / "test"
c_file.write_text(code)
subprocess.run([os.environ.get("CC", "cc"), "-std=gnu11",
*["-D" + d for d in defines], "-o", str(binary),
str(c_file)], check=True)
subprocess.run([str(binary)], check=True)
if __name__ == "__main__":
unittest.main()