Skip to content

Commit 664ac91

Browse files
authored
anyka: detect the AK37/AK39 SoCs (#217)
ipctool reported nothing on a Victure PC420 because Anyka is not a vendor it can detect. The SoC offers neither of the two keys the detection path expects: these kernels have no device tree, and the AK39 serial driver registers no UART with /proc/iomem, so hw_detect_system() falls through to the generic table and finds nothing that matches. The machine string is what names the part. Every Anyka camera board spells it into MACHINE_START -- "CLOUD39EV3_AK3918EV300_MNBD" on the camera in the issue, "Cloud39EV2_AK3918E80PIN_MNBD", "Aimer39_AK3918_MB_V1.0.0", "AK39EV330" -- so the detector matches that rather than a prefix, and the row in manufacturers[] carries no pattern of its own. The chip-ID word at 0x08000000 is a refinement and never the gate, for two reasons. It cannot name the part: a vendor kernel compares it against the one constant its CONFIG_CPU_AK39xx picked and prints whatever name it was built for, so the same 0x20160100 is announced as AK3916, AK3918 and AK3919 by three different trees. And a kernel with strict devmem filtering can refuse the window, which would otherwise take the whole report down with it. It sets chip_generation, which is the thing that wants a family rather than a part number anyway. Also in the HAL: the per-family I2C addresses the vendor's own sensor drivers in drivers/media/video/plat-anyka use, and a media-memory figure. Linux only ever sees the top of the bank here -- 37 MB of 64 MB on the PC420, the rest held for the ISP and the encoder -- so total DRAM comes from the `memsize` boot argument and the reservation is what /proc/iomem says the kernel did not get. Nobody on the project has an Anyka camera, so anyka_test.c covers the three parsers off hardware the way longse_test covers Longse: the machine string against every MACHINE_START spelling found in vendor sources plus the SoCs that must fall through to their own detectors, the chip-ID table including that its shifted rows cannot swallow its raw ones, and the media-memory arithmetic against the PC420's own /proc/cmdline and /proc/iomem.
1 parent 429e8c3 commit 664ac91

9 files changed

Lines changed: 430 additions & 2 deletions

File tree

.github/workflows/pr-build-check.yml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -130,10 +130,11 @@ jobs:
130130
run: |
131131
cmake -H. -Bbuild -DCMAKE_BUILD_TYPE=Release
132132
cmake --build build --target cYAML_test reginfo_test longse_test \
133-
sensors_test
133+
sensors_test anyka_test
134134
./build/cYAML_test
135135
./build/reginfo_test
136136
./build/longse_test
137+
./build/anyka_test
137138
# Run from the tree root: the fingerprint it compares against is
138139
# src/sensors_golden.txt, and a sensor that stopped being identified
139140
# has to fail here rather than be noticed on a camera in a ceiling.

CLAUDE.md

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,12 @@ CMake knobs worth knowing:
119119
the project has a Longse camera. Note it uses a local `CHECK` macro rather
120120
than `assert()`: the release flags carry `-DNDEBUG`, so an `assert()`-based
121121
test compiles away to nothing and passes unconditionally.
122+
- `./build/anyka_test`: the Anyka HAL's three parsers -- the `/proc/cpuinfo`
123+
machine string, the chip-ID table and the media-memory arithmetic -- fed
124+
fixture files through the path arguments `src/hal/anyka.h` exposes for the
125+
purpose. Same situation as `longse_test` and the same `CHECK` macro: nobody
126+
on the project has an Anyka camera, so this is all the verification that
127+
code gets until a reporter runs it.
122128
- `tools/test_pipeline.sh`: hardware-free end-to-end check of the sensor
123129
driver extraction pipeline (`trace_segment.py` -> `trace_to_driver.py` ->
124130
`gcc -fsyntax-only` -> `trace_diff.py`), plus the `--selftest` of every
@@ -195,6 +201,17 @@ per-generation tables in each subcommand that should support it. Adding a new
195201
vendor also needs an entry in `IPCHW_OPTIONAL_VENDORS` in `CMakeLists.txt`, an
196202
include in `hal/common.h`, and a guarded row in `manufacturers[]`.
197203

204+
Anyka (`src/hal/anyka.c`) is the smallest worked example of that whole shape,
205+
and the one that shows what to do when the SoC hands you no clean key: its
206+
kernels have no device tree and register no UART with `/proc/iomem`, so the
207+
`/proc/cpuinfo` machine string is what names the part
208+
(`CLOUD39EV3_AK3918EV300_MNBD`), while the chip-ID word at `0x08000000` names
209+
only the generation -- one vendor kernel prints the same `0x20160100` as
210+
AK3916, AK3918 or AK3919 depending on which `CONFIG_CPU_AK39xx` it was built
211+
with. Reading the register is therefore a refinement and never the gate, which
212+
is also what lets detection survive a kernel that will not hand over
213+
`/dev/mem`.
214+
198215
Sensor I2C addresses in tables are the 8-bit (write) form; the default
199216
`i2c_change_addr` shifts right by one for the kernel, and some HALs install
200217
`i2c_change_plain_addr` instead. Ingenic gates the sensor clock, which is why

CMakeLists.txt

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ include_directories(./src/)
5151
# directly rather than through the table. Affects libipchw only; the ipctool
5252
# executable is a diagnostic tool and always carries every vendor.
5353
set(IPCHW_OPTIONAL_VENDORS
54-
allwinner bcm fh gm ingenic novatek rockchip sstar tegra xilinx xm)
54+
allwinner anyka bcm fh gm ingenic novatek rockchip sstar tegra xilinx xm)
5555
set(IPCHW_VENDORS "all" CACHE STRING
5656
"Vendor HALs to compile into libipchw: 'all', 'none', or a ;-separated \
5757
subset of: ${IPCHW_OPTIONAL_VENDORS}")
@@ -462,4 +462,13 @@ if(NOT ONLY_LIBRARY)
462462
add_executable(longse_test src/boards/longse_test.c src/boards/longse.c
463463
src/tools.c ${VERSION_OBJ})
464464
target_include_directories(longse_test PRIVATE include src)
465+
466+
# Same reason as longse_test -- nobody here has an Anyka camera. Compiled
467+
# the way ipctool compiles it, cJSON and all, so the report-building half of
468+
# the HAL is covered by the link even though the test only calls the parsing.
469+
add_executable(anyka_test src/hal/anyka_test.c src/hal/anyka.c
470+
src/hal/common.c src/tools.c src/cjson/cJSON.c
471+
${VERSION_OBJ})
472+
target_include_directories(anyka_test PRIVATE include src)
473+
target_link_libraries(anyka_test m Threads::Threads)
465474
endif()

src/chipid.c

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,9 @@ static const manufacturers_t manufacturers[] = {
7070
#ifdef IPCHW_VENDOR_FH
7171
{"FH", fh_detect_cpu, VENDOR_FH, fh_setup_hal},
7272
#endif
73+
#ifdef IPCHW_VENDOR_ANYKA
74+
{NULL /* Generic */, anyka_detect_cpu, VENDOR_ANYKA, anyka_setup_hal},
75+
#endif
7376
#ifdef IPCHW_VENDOR_ROCKCHIP
7477
{NULL /* Generic */, rockchip_detect_cpu, VENDOR_ROCKCHIP, rockchip_setup_hal},
7578
#endif
@@ -326,6 +329,22 @@ const char *getchipfamily() {
326329
case RV1106:
327330
return "rv1106";
328331
#endif
332+
#ifdef IPCHW_VENDOR_ANYKA
333+
case AK39_EV2:
334+
return "ak3918ev200";
335+
case AK39_EV3:
336+
return "ak3918ev300";
337+
case AK39_EV330:
338+
return "ak39ev330";
339+
case AK39_EV300L:
340+
return "ak3918ev300l";
341+
case AK39_AV100:
342+
return "ak3918av100";
343+
case AK37_D:
344+
return "ak37d";
345+
case AK37_E:
346+
return "ak37e";
347+
#endif
329348

330349
default:
331350
return chip_name;

src/chipid.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
#include <stdbool.h>
55

66
#define VENDOR_ALLWINNER "Allwinner"
7+
#define VENDOR_ANYKA "Anyka"
78
#define VENDOR_BCM "Broadcom"
89
#define VENDOR_FH "Fullhan"
910
#define VENDOR_GM "GrainMedia"
@@ -22,6 +23,7 @@ extern char nor_chip_name[128];
2223
extern char nor_chip_id[128];
2324

2425
const char *getchipname();
26+
const char *getchipfamily();
2527

2628
#ifndef STANDALONE_LIBRARY
2729
#include "cjson/cJSON.h"

src/hal/anyka.c

Lines changed: 152 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,152 @@
1+
#include "hal/anyka.h"
2+
3+
#include <stdint.h>
4+
#include <stdlib.h>
5+
#include <string.h>
6+
7+
#include "chipid.h"
8+
#include "hal/common.h"
9+
#include "tools.h"
10+
11+
/* Addresses as the vendor's own sensor drivers in
12+
* drivers/media/video/plat-anyka/ spell them, which is the 8-bit write form
13+
* ipctool's tables use. SOI parts sit at either end: the JX-F2x/H63 drivers
14+
* use 0x80 and the H65 one 0x60. */
15+
static unsigned char sony_addrs[] = {0x34, 0};
16+
static unsigned char soi_addrs[] = {0x80, 0x60, 0};
17+
static unsigned char ssens_addrs[] = {0x60, 0};
18+
static unsigned char onsemi_addrs[] = {0x20, 0};
19+
static unsigned char omni_addrs[] = {0x6c, 0};
20+
static unsigned char gc_addrs[] = {0x42, 0x6e, 0};
21+
static unsigned char superpix_addrs[] = {0x7a, 0x78, 0};
22+
23+
static sensor_addr_t anyka_possible_i2c_addrs[] = {
24+
{SENSOR_SONY, sony_addrs}, {SENSOR_SOI, soi_addrs},
25+
{SENSOR_SMARTSENS, ssens_addrs}, {SENSOR_ONSEMI, onsemi_addrs},
26+
{SENSOR_OMNIVISION, omni_addrs}, {SENSOR_GALAXYCORE, gc_addrs},
27+
{SENSOR_SUPERPIX, superpix_addrs}, {0, NULL}};
28+
29+
/* Raw chip ID as it was read, kept so the report can show the number the
30+
* kernel's own "ANYKA CPU ... (ID 0x...)" banner prints. Zero when /dev/mem
31+
* would not give it up. */
32+
static uint32_t anyka_chip_id;
33+
34+
static const struct {
35+
uint32_t id;
36+
/* The 2019 and later parts moved the ID up into bits 31:8 and match on
37+
* the top 24 bits only, leaving the low byte to vary; the mask falls out
38+
* of the shift. */
39+
uint32_t shift;
40+
int generation;
41+
} anyka_socs[] = {
42+
{0x20120100, 0, AK39_EV2},
43+
{0x20150200, 0, AK39_EV2},
44+
{0x20160100, 0, AK39_EV3},
45+
{0x20160101, 0, AK39_EV330},
46+
{0x20170200, 0, AK37_D},
47+
{0x00201902, 8, AK37_E},
48+
{0x00535335, 8, AK39_AV100},
49+
{0x00535434, 8, AK39_EV300L},
50+
};
51+
52+
int anyka_generation(uint32_t chip_id) {
53+
for (size_t i = 0; i < ARRCNT(anyka_socs); i++) {
54+
uint32_t shift = anyka_socs[i].shift;
55+
56+
if (((chip_id >> shift) & (0xFFFFFFFFu >> shift)) == anyka_socs[i].id)
57+
return anyka_socs[i].generation;
58+
}
59+
return 0;
60+
}
61+
62+
/* These kernels predate device tree on the camera parts -- the 3.4.35 in
63+
* issue #134 has no /proc/device-tree at all -- and the SoC registers no UART
64+
* with /proc/iomem, so the machine string is the only signal that does not
65+
* need /dev/mem. Every Anyka camera board spells the part into it:
66+
* "CLOUD39EV3_AK3918EV300_MNBD", "Cloud39EV2_AK3918E80PIN_MNBD",
67+
* "Aimer39_AK3918_MB_V1.0.0", "AK39EV330". */
68+
bool anyka_machine_name(const char *cpuinfo, char *out, size_t outlen) {
69+
if (!line_from_file(cpuinfo, "Hardware.*:.*([Aa][Kk]3[0-9][0-9A-Za-z]*)",
70+
out, outlen))
71+
return false;
72+
73+
/* line_from_file() leaves no terminator behind when the match fills the
74+
* buffer, and this one gets copied on into chip_name. */
75+
out[outlen - 1] = '\0';
76+
return true;
77+
}
78+
79+
bool anyka_detect_cpu(char *chip_name) {
80+
/* As wide as chip_name, since that is where it lands. */
81+
char machine[128];
82+
uint32_t reg;
83+
84+
if (!anyka_machine_name("/proc/cpuinfo", machine, sizeof(machine)))
85+
return false;
86+
strcpy(chip_name, machine);
87+
88+
/* Only ever a refinement: the board already named the part, and a kernel
89+
* built with strict devmem filtering can refuse the window outright. */
90+
if (mem_reg(AK_REG_CHIP_ID, &reg, OP_READ)) {
91+
anyka_chip_id = reg;
92+
chip_generation = anyka_generation(reg);
93+
}
94+
95+
return true;
96+
}
97+
98+
#ifndef STANDALONE_LIBRARY
99+
/* Total DRAM is not something Linux can see here. The boot loader keeps the
100+
* foot of the bank for the ISP and the encoder and hands the kernel only what
101+
* is left -- 37 MB of a 64 MB part on the camera in issue #134 -- so
102+
* /proc/meminfo is short by exactly the media reservation. The vendor boot
103+
* argument `memsize` carries the real size, which is why the SDK reads it,
104+
* and /proc/iomem says how much of it the kernel ended up with. */
105+
unsigned long anyka_media_mem(const char *cmdline, const char *iomem) {
106+
char buf[256];
107+
unsigned long memsize, start, end;
108+
109+
if (!line_from_file(cmdline, "memsize=([0-9]+)M", buf, sizeof(buf)))
110+
return 0;
111+
memsize = strtoul(buf, NULL, 10) * 1024;
112+
113+
if (!line_from_file(iomem, "^(\\w+)-\\w+ *: *System RAM", buf,
114+
sizeof(buf)))
115+
return 0;
116+
start = strtoul(buf, NULL, 16);
117+
118+
if (!line_from_file(iomem, "^\\w+-(\\w+) *: *System RAM", buf,
119+
sizeof(buf)))
120+
return 0;
121+
end = strtoul(buf, NULL, 16);
122+
123+
if (end <= start)
124+
return 0;
125+
126+
unsigned long kernel_ram = (end - start + 1) / 1024;
127+
return memsize > kernel_ram ? memsize - kernel_ram : 0;
128+
}
129+
130+
static unsigned long anyka_totalmem(unsigned long *media_mem) {
131+
*media_mem = anyka_media_mem("/proc/cmdline", "/proc/iomem");
132+
return *media_mem + kernel_mem();
133+
}
134+
135+
static void anyka_chip_properties(cJSON *j_inner) {
136+
/* getchipfamily() falls back to the model when the generation is unknown,
137+
* and repeating the model as its own family says nothing. */
138+
if (chip_generation)
139+
ADD_PARAM("family", getchipfamily());
140+
141+
if (anyka_chip_id)
142+
ADD_PARAM_FMT("id", "0x%08x", anyka_chip_id);
143+
}
144+
#endif
145+
146+
void anyka_setup_hal() {
147+
possible_i2c_addrs = anyka_possible_i2c_addrs;
148+
#ifndef STANDALONE_LIBRARY
149+
hal_totalmem = anyka_totalmem;
150+
hal_chip_properties = anyka_chip_properties;
151+
#endif
152+
}

src/hal/anyka.h

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
#ifndef HAL_ANYKA_H
2+
#define HAL_ANYKA_H
3+
4+
#include <stdbool.h>
5+
#include <stddef.h>
6+
#include <stdint.h>
7+
8+
/* The chip-ID word sits at the foot of the system controller, which every
9+
* AK37/AK39 memory map puts at the same physical address. /proc/iomem does
10+
* not name it -- the audio driver claims the surrounding 64 KB as
11+
* "akpcm_AnalogCtrlRegs" -- so it has to be spelled out here. */
12+
#define AK_PA_SYSCTRL 0x08000000
13+
#define AK_REG_CHIP_ID (AK_PA_SYSCTRL + 0x00)
14+
15+
/* chip_generation values. The ID word names a generation, never the exact
16+
* part: a vendor kernel compares it against the single constant picked by its
17+
* CONFIG_CPU_AK39xx and prints whichever name it was built for, so the same
18+
* 0x20160100 is announced as AK3916, AK3918 or AK3919 by three different
19+
* boards. The part number comes from the machine string instead. */
20+
#define AK39_EV2 0x3918E200
21+
#define AK39_EV3 0x3918E300
22+
#define AK39_EV330 0x3939E330
23+
#define AK39_EV300L 0x3918E30C
24+
#define AK39_AV100 0x3918A100
25+
#define AK37_D 0x3737D000
26+
#define AK37_E 0x3737E000
27+
28+
bool anyka_detect_cpu(char *chip_name);
29+
void anyka_setup_hal();
30+
31+
/* The parsing, split out and given its inputs as arguments. Nobody on the
32+
* project has an Anyka camera, so anyka_test.c feeding these fixture files is
33+
* the only check this code gets; anyka_detect_cpu() always passes the real
34+
* /proc paths. */
35+
bool anyka_machine_name(const char *cpuinfo, char *out, size_t outlen);
36+
int anyka_generation(uint32_t chip_id);
37+
unsigned long anyka_media_mem(const char *cmdline, const char *iomem);
38+
39+
#endif /* HAL_ANYKA_H */

0 commit comments

Comments
 (0)