Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 19 additions & 3 deletions example/ipcinfo.c
Original file line number Diff line number Diff line change
Expand Up @@ -98,16 +98,19 @@ static void print_chip_temperature() {

static void print_serial() {
char serial[512];
bool found = false;

const char *vendor = getchipvendor();

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pre-existing, but this PR puts ipcinfo -i on every V5 boot's rcS path, so flagging it: getchipvendor() runs setup_hal_hisi(), which writes 0 0 0 0 to /proc/sys/kernel/printk and on HISI_OT force-enables the sensor clock (CRG8464/CRG8472). ipcinfo never calls hal_cleanup() on any exit path (only sensors.c and i2cspi.c do), so the console stays silent and the CRG stays modified for the rest of boot.

One hal_cleanup() before each exit, or an atexit() registered after getchipname(), fixes it.

if (strstr(vendor, VENDOR_HISI) || strstr(vendor, VENDOR_GOKE))
hisi_ev300_get_die_id(serial, sizeof serial);
found = hisi_get_die_id(serial, sizeof serial);
#ifdef IPCHW_VENDOR_SSTAR
if (strstr(vendor, VENDOR_SSTAR))
sstar_get_die_id(serial, sizeof serial);
found = sstar_get_die_id(serial, sizeof serial);
#endif

if (!serial)
// Provisioning scripts derive a MAC from this, so a miss has to be silent
// on stdout and non-zero on exit.
if (!found)
exit(EXIT_FAILURE);
puts(serial);
}
Expand Down Expand Up @@ -206,6 +209,18 @@ static void print_xm_mac() {
exit(EXIT_FAILURE);
}

/* Every reporter here goes through getchipname(), which runs setup_hal_*():
* printk is silenced and, on HISI_OT, the sensor clock is force-enabled.
* ipcinfo exits straight out of the reporters, so without this the console
* stays quiet and the CRG stays modified for the rest of boot -- which
* ethaddr_provision() in rcS would do on every V5 boot. hal_cleanup is only
* set once a HAL has been selected, and both restore paths are idempotent.
*/
static void cleanup_hal(void) {
if (hal_cleanup)
hal_cleanup();
}

int main(int argc, char **argv) {
const char *short_options = "cfvhlstiFSxV";
const struct option long_options[] = {
Expand All @@ -225,6 +240,7 @@ int main(int argc, char **argv) {

int opt;
int long_index = 0;
atexit(cleanup_hal);
while ((opt = getopt_long_only(argc, argv, short_options, long_options,
&long_index)) != -1) {
switch (opt) {
Expand Down
2 changes: 1 addition & 1 deletion src/hal/hisi/hal_hisi.h
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@
#define IS_7205V500 IS_CHIP("7205V500") || IS_CHIP("7205V510") || IS_CHIP("7205V530")


bool hisi_ev300_get_die_id(char *buf, ssize_t len);
bool hisi_get_die_id(char *buf, size_t len);
void hisi_vi_information(sensor_ctx_t *ctx);
unsigned long hisi_totalmem(unsigned long *media_mem);
bool hisi_detect_cpu(char *chip_name, uint32_t SC_CTRL_base);
Expand Down
101 changes: 94 additions & 7 deletions src/hal/hisi/ispreg.c
Original file line number Diff line number Diff line change
Expand Up @@ -685,15 +685,16 @@ static void hisi_ev300_sensor_clock(cJSON *j_inner) {
}
}

bool hisi_ev300_get_die_id(char *buf, ssize_t len) {
if (chip_generation != HISI_V4) {
static bool hisi_ev300_get_die_id(char *buf, size_t len) {
const uint32_t base_id_addr = 0x12020400;
const int words = 6;

if (len < (size_t)words * 8 + 1)
return false;
}

uint32_t base_id_addr = 0x12020400;
char *ptr = buf;
for (uint32_t id_addr = base_id_addr + 5 * 4; id_addr >= base_id_addr;
id_addr -= 4) {
for (uint32_t id_addr = base_id_addr + (words - 1) * 4;
id_addr >= base_id_addr; id_addr -= 4) {
uint32_t val;
if (!mem_reg(id_addr, &val, OP_READ))
return false;
Expand All @@ -714,6 +715,92 @@ bool hisi_ev300_get_die_id(char *buf, ssize_t len) {
return true;
}

/* Per-die identity on V5 (HISI_OT).
*
* V5 has no counterpart to the V4 die-ID block at 0x12020400. The vendor keeps
* the die ID in OTP and reaches it through a bootrom call (otp_get_die_id() in
* gsl/drivers/share_drivers/share_drivers.c), but every OTP row is also
* shadowed into a register window at the same offset it occupies in OTP, so
* the 16 bytes of OTP_DIE_ID appear at OTP_SHADOW_BASE + 0xF0.
*
* Both vendor SDKs that cover the five HISI_OT parts agree on the window:
* Hi3516CV610_SDK_V1.0.2.0, covering 3516CV608/CV610/CV613:
* gsl/include/platform.h OTP_SHADOW_BASE = SCPU_OTPC_BASE_ADDR
* = 0x101E0000
* gsl/drivers/otp/otp.h OTP_DIE_ID 0xF0, 16 bytes
* Hi3519DV500 SDK R11, covering both DV500 parts in one bsp tree (see
* bsp/pub/hi3516dv500_image_glibc and svb.h's OTP_16D/OTP_19D ids):
* gsl/include/platform.h OTP_SHADOW_BASE = SCPU_OTPC_BASE_ADDR
* = 0x101E0000
* gsl/drivers/otp/otp.h OTP_DIE_ID 0xF0, 16 bytes
*
* The window is readable from the non-secure side: the OEM's own hwconf.ko
* ioremaps the ATE chip version register in it from an ordinary kernel module
* (OTP_VERSION_ID_REG, +0x10C on the CV6xx parts and +0x120 on the DV500s).
*/
#define V5_OTP_SHADOW_BASE 0x101E0000u
#define V5_OTP_DIE_ID 0xF0
#define V5_DIE_ID_WORDS 4

static bool hisi_ot_get_die_id(char *buf, size_t len) {
uint32_t id[V5_DIE_ID_WORDS];
uint8_t bytes[sizeof id];

if (len < 2 * sizeof bytes + 1)
return false;

for (size_t i = 0; i < V5_DIE_ID_WORDS; i++) {
if (!mem_reg(V5_OTP_SHADOW_BASE + V5_OTP_DIE_ID + i * 4, &id[i],

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Blocking. This base is verified on one CV608, and the SDK it comes from covers CV608/CV610/CV613, but it runs on every HISI_OT part including 3516DV500 and 3519DV500, and hisi_chip_properties() now performs the read unconditionally on the plain ipctool path.

Two failure modes if 0x101E0000 is not the OTP shadow on one of those:

  • Unbacked window: mem_reg() only guards the mmap; the dereference at tools.c is unguarded and there is no SIGBUS handler, so the whole YAML report dies. See 81ccfa4 (the 3536CV100 bus-error fix) for the in-tree precedent.
  • Backed by some other stable register: it passes the all-zero/all-ones filter below and every board of that model reports the same id, which is the fleet-wide MAC collision this PR sets out to prevent.

Suggest gating the reader on the chip IDs the CV610 SDK covers until a DV500 or 3519DV500 has been measured. The read is harmless, so this is the whole test:

for a in 0x101E00F0 0x101E00F4 0x101E00F8 0x101E00FC 0x101E010C; do devmem $a 32; done

+0x10C should be a 0x24xxxx-style version id and the four die words should differ from the CV608 values in the description. I tried to run it on the lab 3519DV500 and CV608 today but both were unreachable.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The DV500 SDK answers this, and it says the base is not CV610-only.

Hi3519DV500 SDK R11 covers both DV500 parts out of one bsp tree — bsp/pub/hi3516dv500_image_glibc/, bsp/tools/pc/uboot_env/env_text/hi3516dv500/, and svb.h's OTP_16D_VERSION_ID alongside OTP_19D_VERSION_ID — and it defines the same window at the same offset:

a55_linux/source/bsp/components/gsl/include/platform.h:335
    #define SCPU_OTPC_BASE_ADDR   0x101E0000
    #define OTP_SHADOW_BASE       SCPU_OTPC_BASE_ADDR
a55_linux/source/bsp/components/gsl/drivers/otp/otp.h:48
    #define OTP_DIE_ID            0xF0   /* 0xF0~0xFF, 16bytes */
a55_linux/source/bsp/components/gsl/drivers/svb/svb.h:80
    #define OTP_BASE_REG          0x101E0000

Identical to the CV610 SDK's platform.h:310 / otp.h:48. So the two vendor SDKs between them cover all five HISI_OT parts and agree on both the base and the row. A chip-ID allowlist would add a table to maintain against a divergence neither SDK shows, so I'd rather cite the second SDK than gate — I've put those citations in the comment above the reader.

One correction to the probe, which matters if you run it: 0x101E010C is the CV6xx family's version-id offset. On DV500 the register moved — svb.h:81 has OTP_VERSION_ID_REG 0x0120, expecting 0x220101 on 3519DV500 and 0x220102 on 3516DV500 (svb.h:85-86). So on a DV500 the sequence is:

for a in 0x101E00F0 0x101E00F4 0x101E00F8 0x101E00FC 0x101E0120; do devmem $a 32; done

As written it would have read a wrong offset on the lab 3519DV500 and looked like a failure.

If you want belt-and-braces anyway after that, the honest version is a runtime self-check rather than a model list: read the version id and require it to look like its family's (0x24xxxx at +0x10C, 0x22xxxx at +0x120). That verifies the window instead of trusting a table. It does not help with the bus-error case, though — the check read would fault first — so it only buys protection against the "backed by some other stable register" half. Say the word and I'll add it.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks, the DV500 SDK citations settle it: two vendor trees, all five HISI_OT parts, same base and same row. Agreed that a chip-ID allowlist would be a table maintained against a divergence neither SDK shows, so citing the second SDK above the reader is the right form.

Thanks also for the offset correction. +0x120 for the DV500 family is now in our lab notes, so the probe on the 3519DV500 will read the right register when that board comes back.

I'd skip the runtime version-id self-check. It cannot help the bus-error half, and it would reintroduce the per-family table by another route.

Per-die versus per-model stays open until a second gen5 board is read. Our lab CV608 and 3519DV500 were unreachable both days; we will run the probe on whichever comes back first and report here.

OP_READ))
return false;
// Bytes are emitted in OTP order. The shadow words are little-endian,
// so byte i of what otp_get_die_id() would hand back is
// word[i / 4] >> (8 * (i % 4)).
for (size_t b = 0; b < 4; b++)
bytes[i * 4 + b] = (id[i] >> (8 * b)) & 0xFF;
}

for (size_t i = 0; i < sizeof bytes; i++)
snprintf(buf + 2 * i, len - 2 * i, "%02x", bytes[i]);

return true;
}

/* An OTP row that is unfused, locked or unbacked reads all-zeroes or all-ones,
* and a partially fused one reads a mixture of the two -- as does a V4 die-ID
* block on a part that never had one fused, which the trailing-zero strip above
* leaves as a string of zeroes rather than rejecting.
*
* None of those is an identity, and callers turn this string into a MAC
* address: handing one out would give every board in a fleet the same address.
* Rejecting on the digits rather than on whole words keeps the partially fused
* mixture out too, while still accepting a legitimate id that contains a zero
* word.
*/
static bool die_id_is_usable(const char *buf) {
for (const char *p = buf; *p; p++)
if (*p != '0' && *p != 'f')
return true;
return false;
}

bool hisi_get_die_id(char *buf, size_t len) {
bool ok;

switch (chip_generation) {
case HISI_V4:

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The V4 arm skips the all-zero/all-ones rejection that the V5 arm gets at line 757. hisi_ev300_get_die_id() returns true on a block of six zero words (the trailing-'0' strip never finds a non-zero digit, so it never breaks), and ipcinfo -i then prints 48 zeros with exit 0. Same for all-ones.

Since the invariant this PR introduces is "callers derive a MAC from this", the check belongs here in the dispatcher so both arms honour it.

ok = hisi_ev300_get_die_id(buf, len);
break;
case HISI_OT:
ok = hisi_ot_get_die_id(buf, len);
break;
default:
return false;
}

return ok && die_id_is_usable(buf);
}

#define CV300_ISP_AF_CFG_ADDR 0x12200
struct CV300_ISP_AF_CFG {
bool en : 1;
Expand Down Expand Up @@ -927,7 +1014,7 @@ struct PT_OFFSET {

void hisi_chip_properties(cJSON *j_inner) {
char buf[1024];
if (hisi_ev300_get_die_id(buf, sizeof buf)) {
if (hisi_get_die_id(buf, sizeof buf)) {
ADD_PARAM("id", buf);
}
}