diff --git a/.github/workflows/pr-build-check.yml b/.github/workflows/pr-build-check.yml index 6b588a5..fc41044 100644 --- a/.github/workflows/pr-build-check.yml +++ b/.github/workflows/pr-build-check.yml @@ -139,7 +139,8 @@ jobs: run: | cmake -H. -Bbuild -DCMAKE_BUILD_TYPE=Release cmake --build build --target cYAML_test reginfo_test longse_test \ - sensors_test anyka_test hal_hisi_test \ + sensors_test anyka_test novatek_test \ + novatek_arm_test novatek_aarch64_test hal_hisi_test \ report_test http_test mtd_alias_test \ nand_layout_test ./build/cYAML_test @@ -150,6 +151,9 @@ jobs: ./build/reginfo_test ./build/longse_test ./build/anyka_test + ./build/novatek_test + ./build/novatek_arm_test + ./build/novatek_aarch64_test ./build/hal_hisi_test # Run from the tree root: the fingerprint it compares against is # src/sensors_golden.txt, and a sensor that stopped being identified diff --git a/CMakeLists.txt b/CMakeLists.txt index 19dc363..3a1169a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -505,6 +505,21 @@ if(NOT ONLY_LIBRARY) target_include_directories(anyka_test PRIVATE include src) target_link_libraries(anyka_test m Threads::Threads) + # Includes novatek.c so only its hardware file paths are redirected. + # Not STANDALONE_LIBRARY: the NA51023 code is ipctool-only, so the test + # is built the way ipctool is. + foreach(_nvt_test novatek_test novatek_arm_test novatek_aarch64_test) + add_executable(${_nvt_test} src/hal/novatek_test.c src/hal/common.c + src/sensors.c src/tools.c src/cjson/cJSON.c + ${VERSION_OBJ}) + target_include_directories(${_nvt_test} PRIVATE include src) + target_compile_definitions(${_nvt_test} PRIVATE IPCHW_VENDOR_NOVATEK) + target_link_libraries(${_nvt_test} m Threads::Threads + "-Wl,--wrap=open" "-Wl,--wrap=disable_printk") + endforeach() + target_compile_definitions(novatek_arm_test PRIVATE NOVATEK_TEST_ARM) + target_compile_definitions(novatek_aarch64_test PRIVATE NOVATEK_TEST_AARCH64) + # The body `ipctool upload` sends: part framing, the backup's layout and # the length that must match it, and openipc.org's answer. Off a camera, # like the others. diff --git a/src/chipid.c b/src/chipid.c index 87f1153..3dc8e82 100644 --- a/src/chipid.c +++ b/src/chipid.c @@ -55,6 +55,9 @@ static const manufacturers_t manufacturers[] = { {"isvp", ingenic_detect_cpu, VENDOR_INGENIC, setup_hal_ingenic}, {"ingenic", ingenic_detect_cpu, VENDOR_INGENIC, setup_hal_ingenic}, #endif +#if defined(IPCHW_VENDOR_NOVATEK) && !defined(STANDALONE_LIBRARY) + {NULL, novatek_detect_mips_cpu, VENDOR_NOVATEK, novatek_setup_hal}, +#endif #endif #ifdef __arm__ #ifdef IPCHW_VENDOR_SSTAR @@ -238,6 +241,11 @@ const char *getchipname() { const char *getchipfamily() { const char *chip_name = getchipname(); switch (chip_generation) { +#if defined(IPCHW_VENDOR_NOVATEK) && !defined(STANDALONE_LIBRARY) && \ + (defined(mips) || defined(__mips__) || defined(__mips)) + case CHIP_NA51023: + return "nt9851x"; +#endif #ifdef IPCHW_HISI_V1 case HISI_V1: return "hi3516cv100"; diff --git a/src/hal/novatek.c b/src/hal/novatek.c index 0c99c22..81e98ee 100644 --- a/src/hal/novatek.c +++ b/src/hal/novatek.c @@ -59,13 +59,51 @@ static bool nvt_get_chip_id() { return true; } +#if (defined(mips) || defined(__mips__) || defined(__mips)) && \ + !defined(STANDALONE_LIBRARY) +static void nvt_refine_na51023(char *chip_name) { + char buf[16]; + + /* The device-tree model names a die shared by more than one part. + * Parse the factory declaration as data; never source it. Missing or + * different profiles retain the NA51023 identity. */ + chip_generation = CHIP_NA51023; + if (line_from_file("/etc/profile_prjcfg", + "^export CHIP=\"(98513)\"[[:space:]]*$", buf, + sizeof(buf))) + strcpy(chip_name, "NT98513"); +} + +bool novatek_detect_mips_cpu(char *chip_name) { + char buf[16]; + + /* Only this MIPS model has a verified identity and rejecting HAL. + * Other Novatek models and the ARM chip-ID proc ABI must not select + * the existing ARM sensor/HDAL backend through MIPS dispatch. */ + if (!line_from_file("/proc/device-tree/model", + "^Novatek (NA51023)([[:space:]]|$)", buf, sizeof(buf))) + return false; + strcpy(chip_name, buf); + nvt_refine_na51023(chip_name); + return true; +} +#endif + bool novatek_detect_cpu(char *chip_name) { char buf[256]; if(nvt_get_chip_id()) return true; - if (!line_from_file("/proc/device-tree/model", "Novatek ([A-Z]+[0-9]+)", + if (!line_from_file("/proc/device-tree/model", +#if (defined(mips) || defined(__mips__) || defined(__mips)) && \ + !defined(STANDALONE_LIBRARY) + "^Novatek ([A-Z]+[0-9]+)([[:space:]]|$)", +#else + /* Keep the existing ARM/AArch64 parser unchanged. + * The new MIPS route has its own strict detector. */ + "Novatek ([A-Z]+[0-9]+)", +#endif buf, sizeof(buf))) { /* NA51055 has a pad table and no part name the ID can give; with * no device-tree model either, the die is the name. */ @@ -76,6 +114,12 @@ bool novatek_detect_cpu(char *chip_name) { strcpy(chip_name, buf); +#if (defined(mips) || defined(__mips__) || defined(__mips)) && \ + !defined(STANDALONE_LIBRARY) + if (!strcmp(buf, "NA51023")) + nvt_refine_na51023(chip_name); +#endif + return true; } @@ -118,7 +162,26 @@ static int novatek_open_i2c_fd(int i2c_adapter_nr) { return universal_open_sensor_fd(adapter_name); } +#if (defined(mips) || defined(__mips__) || defined(__mips)) && \ + !defined(STANDALONE_LIBRARY) +static int novatek_no_sensor_bus(int adapter_nr) { + (void)adapter_nr; + return -1; +} +#endif + void novatek_setup_hal() { +#if (defined(mips) || defined(__mips__) || defined(__mips)) && \ + !defined(STANDALONE_LIBRARY) + /* This MIPS generation delegates media to uITRON. The ARM HDAL memory + * report, sensor I2C bus and register tables below are not its ABI. */ + if (chip_generation == CHIP_NA51023) { + possible_i2c_addrs = NULL; + open_i2c_sensor_fd = novatek_no_sensor_bus; + open_spi_sensor_fd = NULL; + return; + } +#endif possible_i2c_addrs = novatek_possible_i2c_addrs; i2c_change_addr = i2c_changenshift_addr; open_i2c_sensor_fd = novatek_open_i2c_fd; diff --git a/src/hal/novatek.h b/src/hal/novatek.h index e23d1c0..2e6a796 100644 --- a/src/hal/novatek.h +++ b/src/hal/novatek.h @@ -4,6 +4,13 @@ #include bool novatek_detect_cpu(char *chip_name); +/* NA51023 (MIPS, sold as NT98513) is identified by ipctool alone. No camera + * that carries libipchw is one, so the library must not pay for it: ipcinfo + * ships on boards whose rootfs leaves tens of KB spare. */ +#if (defined(mips) || defined(__mips__) || defined(__mips)) && \ + !defined(STANDALONE_LIBRARY) +bool novatek_detect_mips_cpu(char *chip_name); +#endif unsigned long novatek_totalmem(unsigned long *media_mem); void novatek_setup_hal(); @@ -22,6 +29,9 @@ void novatek_setup_hal(); /* TOP + 0xF0 bits [31:16], also /proc/nvt_info/nvt_pinmux/chip_id. One ID * per die; the parts cut from a die are told apart by eFuse/OTP words. */ enum CHIP_ID { + /* Software family discriminator, not a TOP chip-ID register value. + * The MIPS SDK supplies a device-tree model but no verified chip_id. */ + CHIP_NA51023 = 0x51023, CHIP_NA51055 = 0x4821, // NT9852x: NT98520, NT98525 CHIP_NA51084 = 0x5021, // NT98528, NT98529 (the 1.2 GHz part, by OTP) CHIP_NA51089 = 0x7021, // NT98562 (64 MB internal RAM), diff --git a/src/hal/novatek_test.c b/src/hal/novatek_test.c new file mode 100644 index 0000000..6fed234 --- /dev/null +++ b/src/hal/novatek_test.c @@ -0,0 +1,277 @@ +/* Hardware-free identity checks. The NA51023 model and CHIP profile below + * come from an NT98513 firmware dump; they are not a live /proc capture. + * This test redirects file paths, but uses the real line_from_file parser + * and detector. It must not acquire /dev/mem, an I2C bus or a camera. */ +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "tools.h" +/* Read all project/system declarations before the synthetic architecture + * selection, especially the native Linux ioctl definitions. */ +#include "chipid.h" +#include "hal/common.h" +#include "sensors.h" + +static bool fixture_line_from_file(const char *path, const char *re, + char *out, size_t len); +#define line_from_file fixture_line_from_file +/* Select both the HAL and routing table's architecture on native CI. System + * headers are read above before changing these macros. The default is the + * MIPS fixture; two additional test targets cover the existing ARM routes. + * These selections affect only this test translation unit, not production. + * A real MIPS build/QEMU run remains a separate check. */ +#pragma push_macro("__arm__") +#pragma push_macro("__aarch64__") +#pragma push_macro("__mips__") +#pragma push_macro("__mips") +#pragma push_macro("mips") +#undef __arm__ +#undef __aarch64__ +#undef __mips__ +#undef __mips +#undef mips +#if defined(NOVATEK_TEST_ARM) +#define __arm__ 1 +#elif defined(NOVATEK_TEST_AARCH64) +#define __aarch64__ 1 +#else +#define __mips__ 1 +#endif +#include "novatek.c" +#include "../chipid.c" +#pragma pop_macro("mips") +#pragma pop_macro("__mips") +#pragma pop_macro("__mips__") +#pragma pop_macro("__aarch64__") +#pragma pop_macro("__arm__") +#undef line_from_file +#include "sensors.h" + +static int failures; +static int hardware_opens; +static int printk_disables; +static int arm_chip_id_reads; +static int profile_reads; +static char fixture_dir[] = "/tmp/novatek_test.XXXXXX"; + +/* The test link wraps these functions. Catch even an attempted device open, + * rather than silently relying on /dev/i2c-N being absent in CI. */ +int __wrap_open(const char *path, int flags, ...) { + (void)path; + (void)flags; + hardware_opens++; + return -1; +} + +void __wrap_disable_printk(void) { printk_disables++; } + +#define CHECK(cond) \ + do { \ + if (!(cond)) { \ + fprintf(stderr, "FAIL %s:%d: %s\n", __FILE__, __LINE__, #cond); \ + failures++; \ + } \ + } while (0) + +static bool fixture_line_from_file(const char *path, const char *re, + char *out, size_t len) { + const char *name = NULL; + if (!strcmp(path, "/proc/nvt_info/nvt_pinmux/chip_id")) { + arm_chip_id_reads++; + name = "chip_id"; + } + else if (!strcmp(path, "/proc/device-tree/model")) + name = "model"; + else if (!strcmp(path, "/etc/profile_prjcfg")) { + profile_reads++; + name = "profile"; + } + else if (!strcmp(path, "/proc/cpuinfo")) + name = "cpuinfo"; + else if (!strcmp(path, "/proc/iomem")) + return false; + if (!name) { + CHECK(!"unexpected hardware file read"); + return false; + } + char file[256]; + snprintf(file, sizeof(file), "%s/%s", fixture_dir, name); + return line_from_file(file, re, out, len); +} + +static void fixture(const char *name, const char *body) { + char file[256]; + snprintf(file, sizeof(file), "%s/%s", fixture_dir, name); + if (!body) { + unlink(file); + return; + } + FILE *fp = fopen(file, "w"); + if (!fp) { + perror(file); + exit(2); + } + fputs(body, fp); + fclose(fp); +} + +static void expect_name(const char *id, const char *model, + const char *profile, const char *want, bool legacy) { + fixture("chip_id", id); + fixture("model", model); + fixture("profile", profile); + chip_generation = 0; + strcpy(chip_name, "unset"); + bool ok = novatek_detect_cpu(chip_name); + CHECK(ok == (want != NULL)); + if (!ok || !want) + return; + CHECK(!strcmp(chip_name, want)); + if (strcmp(chip_name, want)) + fprintf(stderr, " wanted %s, got %s\n", want, chip_name); + + setup_hal_fallback(); + possible_i2c_addrs = NULL; + novatek_setup_hal(); + /* Recognising an RTOS media platform must not enable the ARM HDAL/I2C + * backend. ARM parts must keep their existing sensor probing. */ + CHECK((possible_i2c_addrs == NULL) == legacy); +} + +#if !defined(NOVATEK_TEST_ARM) && !defined(NOVATEK_TEST_AARCH64) +static void mips_dispatch(void) { + fixture("chip_id", NULL); + fixture("model", "Novatek NA51023 evaluation board"); + fixture("profile", "export CHIP=\"98513\"\n"); + fixture("cpuinfo", "system type\t: NVT NA51023\n" + "machine\t\t: Novatek NA51023 evaluation board\n" + "cpu model\t: MIPS 24KEc V5.5\n"); + chip_generation = 0; + *sysid = '\0'; + arm_chip_id_reads = 0; + const char *name = getchipname(); + CHECK(name && !strcmp(name, "nt98513")); + if (!name) + return; + CHECK(!strcmp(getchipvendor(), "Novatek")); + CHECK(!strcmp(getchipfamily(), "nt9851x")); + CHECK(possible_i2c_addrs == NULL); + CHECK(arm_chip_id_reads == 0); + + sensor_ctx_t sensor; + hardware_opens = 0; + printk_disables = 0; + CHECK(hal_enable_sensor_clock == NULL); + CHECK(!getsensorid(&sensor)); + CHECK(hardware_opens == 0); + CHECK(printk_disables == 0); + + /* The dump gives us the device-tree model, not a live cpuinfo capture. + * Do not make detection depend on the latter's formatting. */ + fixture("cpuinfo", "system type\t: NVT NA51023\n"); + *sysid = '\0'; + name = getchipname(); + CHECK(name && !strcmp(name, "nt98513")); + + /* This architecture has no verified ARM chip-ID proc ABI. Its detector + * must use the MIPS model even if an unrelated ARM ID file is present. */ + fixture("chip_id", "7021\n"); + chip_generation = 0; + *sysid = '\0'; + arm_chip_id_reads = 0; + name = getchipname(); + CHECK(name && !strcmp(name, "nt98513")); + CHECK(arm_chip_id_reads == 0); + CHECK(possible_i2c_addrs == NULL); +} + +static void mips_reject_unsupported(const char *id, const char *model) { + fixture("chip_id", id); + fixture("model", model); + fixture("profile", "export CHIP=\"98513\"\n"); + fixture("cpuinfo", "machine\t: Novatek\n"); + chip_generation = 0; + *sysid = '\0'; + possible_i2c_addrs = NULL; + arm_chip_id_reads = 0; + CHECK(getchipname() == NULL); + CHECK(chip_generation == 0); + CHECK(possible_i2c_addrs == NULL); + CHECK(open_i2c_sensor_fd != novatek_open_i2c_fd); + CHECK(arm_chip_id_reads == 0); +} +#endif + +int main(void) { + if (!mkdtemp(fixture_dir)) { + perror("mkdtemp"); + return 2; + } + const char *model = "Novatek NA51023 evaluation board"; + const char *profile = "export PLATFORM=\"Linux\"\nexport CHIP=\"98513\"\n"; +#if defined(NOVATEK_TEST_ARM) || defined(NOVATEK_TEST_AARCH64) + /* An ARM build must not refine the MIPS-only model from its profile or + * install the MIPS rejecting HAL. Keep the existing ARM ID/model route. */ + profile_reads = 0; + expect_name(NULL, model, profile, "NA51023", false); + CHECK(chip_generation != CHIP_NA51023); + expect_name(NULL, "Novatek NA51023", profile, "NA51023", false); + /* These generic ARM substring matches are inherited behavior. Strict + * complete-token rejection is a guarantee of the new MIPS route only. */ + expect_name(NULL, "Novatek NA51023A evaluation board", profile, "NA51023", false); + expect_name(NULL, "OtherNovatek NA51023 evaluation board", profile, "NA51023", false); + expect_name(NULL, "Novatek NA51055 evaluation board", profile, "NA51055", false); + expect_name("7021\n", NULL, profile, "NT98566", false); + expect_name("5021\n", NULL, NULL, "NT98528", false); + expect_name("8B20\n", NULL, NULL, "NT98332G", false); + expect_name("4821\n", NULL, NULL, "NA51055", false); + CHECK(profile_reads == 0); +#else + expect_name(NULL, model, profile, "NT98513", true); + expect_name(NULL, model, NULL, "NA51023", true); + expect_name(NULL, model, "export CHIP=\"98515\"\n", "NA51023", true); + expect_name(NULL, model, "# export CHIP=\"98513\"\n", "NA51023", true); + expect_name(NULL, model, "export CHIP=\"985130\"\n", "NA51023", true); + expect_name(NULL, model, "export CHIP=\"98513\"; touch /tmp/no\n", + "NA51023", true); + expect_name(NULL, NULL, profile, NULL, false); + expect_name(NULL, "Other vendor NA51023", profile, NULL, false); + expect_name(NULL, "OtherNovatek NA51023 evaluation board", profile, + NULL, false); + expect_name(NULL, "Novatek NA51023A evaluation board", profile, + NULL, false); + expect_name(NULL, "Novatek NA51023", profile, "NT98513", true); + expect_name(NULL, "Novatek NA51023\tboard", profile, "NT98513", true); + expect_name(NULL, "Novatek NA510230 evaluation board", profile, + "NA510230", false); + expect_name(NULL, "Novatek NA51055 evaluation board", profile, + "NA51055", false); + expect_name("7021\n", NULL, profile, "NT98566", false); + expect_name("5021\n", NULL, NULL, "NT98528", false); + expect_name("8B20\n", NULL, NULL, "NT98332G", false); + expect_name("4821\n", NULL, NULL, "NA51055", false); + mips_dispatch(); + mips_reject_unsupported(NULL, "Novatek NA510230 evaluation board"); + mips_reject_unsupported(NULL, "Novatek NA51055 evaluation board"); + mips_reject_unsupported("7021\n", NULL); + mips_reject_unsupported("7021\n", "Novatek NA51055 evaluation board"); +#endif + fixture("chip_id", NULL); + fixture("model", NULL); + fixture("profile", NULL); + fixture("cpuinfo", NULL); + rmdir(fixture_dir); + if (failures) { + fprintf(stderr, "novatek_test: %d failure(s)\n", failures); + return 1; + } + puts("novatek_test: all cases passed"); + return 0; +}