diff --git a/example/ipcinfo.c b/example/ipcinfo.c index a467226..c47e3db 100644 --- a/example/ipcinfo.c +++ b/example/ipcinfo.c @@ -98,16 +98,19 @@ static void print_chip_temperature() { static void print_serial() { char serial[512]; + bool found = false; const char *vendor = getchipvendor(); 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); } @@ -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[] = { @@ -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) { diff --git a/src/hal/hisi/hal_hisi.h b/src/hal/hisi/hal_hisi.h index dfb31be..dabfe44 100644 --- a/src/hal/hisi/hal_hisi.h +++ b/src/hal/hisi/hal_hisi.h @@ -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); diff --git a/src/hal/hisi/ispreg.c b/src/hal/hisi/ispreg.c index 3ad798c..e725b65 100644 --- a/src/hal/hisi/ispreg.c +++ b/src/hal/hisi/ispreg.c @@ -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; @@ -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], + 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: + 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; @@ -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); } }