From fa972852a3f2a9caf8da598a269f7f04f42cef4a Mon Sep 17 00:00:00 2001 From: Dmitry Ilyin <6576495+widgetii@users.noreply.github.com> Date: Tue, 2 Jun 2026 18:07:54 +0300 Subject: [PATCH 1/4] chipid: enable HiSilicon V5 dispatch on aarch64 The V5 family (Hi3516CV610 / Hi3516CV613 / Hi3516DV500 / Hi3519DV500) all share UART0 @ 0x11040000 and SC_CTRL @ 0x11020000. The case was previously gated by `#ifdef __arm__` together with the legacy V1..V4 dispatches, which made the 64-bit V5 chips (Hi3516DV500, Hi3519DV500) fall through to generic_detect_cpu() on aarch64. Lift the V5 case to the top of hw_detect_system()'s switch so it's reachable from both ARMv7 (Hi3516CV610/613) and aarch64 (Hi3516DV500/3519DV500). Legacy V1..V4 cases stay 32-bit-only. Verified on Hi3519DV500 (aarch64): SCSYSID0 at 0x11020EE0 reads 0x3519D500, which hal_hisi.c's existing chip table already maps to "3519DV500" / HISI_OT. Co-Authored-By: Claude Opus 4.7 (1M context) --- src/chipid.c | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/chipid.c b/src/chipid.c index 3000812..e742d64 100644 --- a/src/chipid.c +++ b/src/chipid.c @@ -108,16 +108,16 @@ static bool detect_and_set(const char *manufacturer, static bool hw_detect_system() { long uart_base = get_uart0_address(); switch (uart_base) { -#ifdef __arm__ - // xm510 - case 0x10030000: - return detect_and_set("Xiongmai", xm_detect_cpu, setup_hal_xm, 0); - // hi3516cv610 + // hi3516cv610 (ARMv7) / hi3519dv500 (aarch64) — HiSilicon V5 family case 0x11040000: { int ret = detect_and_set(VENDOR_HISI, hisi_detect_cpu, setup_hal_hisi, 0x11020000); return ret; } +#ifdef __arm__ + // xm510 + case 0x10030000: + return detect_and_set("Xiongmai", xm_detect_cpu, setup_hal_xm, 0); // hi3516cv300 case 0x12100000: // hi3516ev200 From 5180b2d42018097cedebcb6babbdc6efc3509d18 Mon Sep 17 00:00:00 2001 From: Dmitry Ilyin <6576495+widgetii@users.noreply.github.com> Date: Tue, 2 Jun 2026 18:08:04 +0300 Subject: [PATCH 2/4] backup, tools: fix 64-bit and null-deref bugs surfaced by aarch64 build Three pre-existing bugs invisible on 32-bit ARM / musl but fatal on aarch64 / glibc: backup.c: `uint32_t payload` is passed by address to fread_to_buf() which takes `size_t *`. ARMv7 size_t == uint32_t so the mismatch never fired; on aarch64 size_t == uint64_t and the build fails outright. Switch the local to size_t. tools.c (get_god_pid): `strncpy(shortname, maxname, shortsz)` is called unconditionally, but the only in-tree caller (get_god_app) passes (NULL, 0). aarch64 glibc faults on strncpy with NULL dst even when n == 0. Guard with `if (shortname && shortsz)`. tools.c (get_pid_cmdline): after fopen("/proc/PID/cmdline") fails (common when a PID exits mid-/proc walk), the function falls through to fclose(fp) on fp == NULL. Restructure to early-return on NULL fp. Co-Authored-By: Claude Opus 4.7 (1M context) --- src/backup.c | 2 +- src/tools.c | 15 ++++++++------- 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/src/backup.c b/src/backup.c index ff18ba0..46a845d 100644 --- a/src/backup.c +++ b/src/backup.c @@ -1002,7 +1002,7 @@ static int do_upgrade(const char *filename, bool force) { goff = strtol(offset->valuestring, 0, 16); printf("Using offset: %#X\n", goff); } - uint32_t payload; + size_t payload; const cJSON *parts = cJSON_GetObjectItemCaseSensitive(json, "partitions"); ASSERT_JSON(parts); diff --git a/src/tools.c b/src/tools.c index 322eb81..29be1b4 100644 --- a/src/tools.c +++ b/src/tools.c @@ -286,16 +286,16 @@ bool get_pid_cmdline(pid_t godpid, char *cmdname) { snprintf(sname, sizeof(sname), "/proc/%d/cmdline", godpid); FILE *fp = fopen(sname, "r"); - if (fp && fgets(sname, sizeof(sname), fp)) { + if (!fp) + return false; + bool ok = false; + if (fgets(sname, sizeof(sname), fp)) { if (cmdname) strcpy(cmdname, sname); - - fclose(fp); - return true; + ok = true; } - fclose(fp); - return false; + return ok; } static unsigned long time_by_proc(const char *filename, char *shortname, @@ -355,6 +355,7 @@ pid_t get_god_pid(char *shortname, size_t shortsz) { }; closedir(dir); - strncpy(shortname, maxname, shortsz); + if (shortname && shortsz) + strncpy(shortname, maxname, shortsz); return godpid; } From 79ae4adc09b2955bff794d5d9a3a65745bf0d5fe Mon Sep 17 00:00:00 2001 From: Dmitry Ilyin <6576495+widgetii@users.noreply.github.com> Date: Tue, 2 Jun 2026 18:09:07 +0300 Subject: [PATCH 3/4] mtd: stream-hash UBI volumes to avoid OOM on large rootfs cb_mtd_info() computed each UBI volume's SHA1 by calling read_ubi_volume() with a single malloc(data_bytes), then SHA1'ing the buffer in one shot. On targets whose rootfs UBI volume approaches system RAM size the malloc OOMs the process: Hi3519DV500 has a data_bytes = 0x1D008000 (~464 MB) rootfs vol on 512 MB total, which makes a bare ipctool exit 137 immediately (kernel oom-killer log: "Killed process N (ipctool) total-vm:495608kB, anon-rss:450564kB"). Add sha1_ubi_volume() that streams the volume in 64 KB chunks through SHA1Init / SHA1Update / SHA1Final, and switch cb_mtd_info() to it. read_ubi_volume() is left untouched because backup.c legitimately needs the full buffer to dump volume contents. Co-Authored-By: Claude Opus 4.7 (1M context) --- src/mtd.c | 52 +++++++++++++++++++++++++++++++++++++++++++++------- src/mtd.h | 2 ++ 2 files changed, 47 insertions(+), 7 deletions(-) diff --git a/src/mtd.c b/src/mtd.c index 56d4d51..b7cbadd 100644 --- a/src/mtd.c +++ b/src/mtd.c @@ -227,6 +227,46 @@ char *read_ubi_volume(int ubi_num, int vol_id, size_t data_bytes, return buf; } +bool sha1_ubi_volume(int ubi_num, int vol_id, size_t data_bytes, + unsigned char digest[20], size_t *out_len) { + char devpath[64]; + snprintf(devpath, sizeof(devpath), "/dev/ubi%d_%d", ubi_num, vol_id); + + int fd = open(devpath, O_RDONLY); + if (fd == -1) + return false; + + enum { CHUNK = 64 * 1024 }; + unsigned char *buf = malloc(CHUNK); + if (!buf) { + close(fd); + return false; + } + + SHA1_CTX ctx; + SHA1Init(&ctx); + + size_t total = 0; + while (total < data_bytes) { + size_t want = data_bytes - total; + if (want > CHUNK) + want = CHUNK; + ssize_t n = read(fd, buf, want); + if (n <= 0) + break; + SHA1Update(&ctx, buf, (uint32_t)n); + total += (size_t)n; + } + SHA1Final(digest, &ctx); + + free(buf); + close(fd); + + if (out_len) + *out_len = total; + return total > 0; +} + static bool uenv_detected; static bool examine_part(int part_num, size_t size, size_t erasesize, @@ -345,15 +385,13 @@ static bool cb_mtd_info(int i, const char *name, struct mtd_info_user *mtd, ADD_PARAM_FMT("data_bytes", "0x%llx", vols[v].data_bytes); size_t out_len = 0; - char *vdata = read_ubi_volume(ubi_num, vols[v].vol_id, - vols[v].data_bytes, &out_len); - if (vdata && out_len > 0) { - char digest[21] = {0}; - SHA1(digest, vdata, out_len); - uint32_t sha1v = ntohl(*(uint32_t *)&digest); + unsigned char digest[20] = {0}; + if (sha1_ubi_volume(ubi_num, vols[v].vol_id, + vols[v].data_bytes, digest, &out_len) && + out_len > 0) { + uint32_t sha1v = ntohl(*(uint32_t *)digest); ADD_PARAM_FMT("sha1", "%.8x", sha1v); } - free(vdata); } } cJSON_AddItemToObject(j_inner, "ubi_volumes", j_vols); diff --git a/src/mtd.h b/src/mtd.h index 729d639..4e8e96f 100644 --- a/src/mtd.h +++ b/src/mtd.h @@ -67,5 +67,7 @@ int find_ubi_for_mtd(int mtd_num); int enum_ubi_volumes(int ubi_num, ubi_vol_info_t *vols, int max_vols); char *read_ubi_volume(int ubi_num, int vol_id, size_t data_bytes, size_t *out_len); +bool sha1_ubi_volume(int ubi_num, int vol_id, size_t data_bytes, + unsigned char digest[20], size_t *out_len); #endif /* MTD_H */ From e56110cb8319a62a847a3a11d85f77da0efc8143 Mon Sep 17 00:00:00 2001 From: Dmitry Ilyin <6576495+widgetii@users.noreply.github.com> Date: Tue, 2 Jun 2026 18:10:23 +0300 Subject: [PATCH 4/4] =?UTF-8?q?clocks:=20add=20V5=20family=20(3516CV610=20?= =?UTF-8?q?/=203519DV500)=20=E2=80=94=204=20PLLs=20+=20DDR=20+=20SSMOD=20+?= =?UTF-8?q?=20HPM?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Decodes the full clock tree of the HiSilicon V5 (HISI_OT) family. Same clocks_family_v5 covers both 32-bit (3516CV610/3516CV613) and 64-bit (3516DV500/3519DV500) variants since they share the same CRG / PHY / HPM register map. Wired through HISI_OT in clocks.c's families[] dispatch. PLLs (CRG @ 0x11010000): APLL (CPU) ctrl 0x000/0x004, lock 0x038 VPLL (Video) ctrl 0x080/0x084, lock 0x0B8 DPLL (DDR) ctrl 0x180/0x184, lock 0x1B8 EPLL (Ethernet) ctrl 0x200/0x204, lock 0x238 Same V4 field layout (FRACDIV[23:0], POSTDIV1[26:24], POSTDIV2[30:28], FBDIV[11:0], REFDIV[17:12]); lock bit 4 = apll_lock_final per the vendor's apll_lock_status union in bsp/components/gsl/boot/reset.c. DDR data rate is a 3-stage chain (CRG ddr_cksel → DPLL → PHY dficlk_ratio) that doesn't fit struct mux_info, so this also adds an optional `extra(cJSON *root, bool brief)` callback to clock_family. V5's v5_extra() decodes: - DDR data rate = phy_clk_mhz * (1 << dficlk_ratio) with ddr_cksel @ CRG+0x2000 [18:16], dficlk_ratio @ DDR_PHY0+0x78 [1:0] - DRAM type @ DDR_PHY0+0x2C [3:0] - SSMOD (DPLL SSC) @ CRG+0x190 (cken bit 0, disable bit 2) - per-site HPM @ HPM_BASE 0x1102B000 (NPU/MDA/CORE, 4 samples each) Canonical HPM binning value @ SYSCTRL+0x340 [9:0] goes through the standard hpm_info table. Ground-truth on Hi3519DV500 demo board: APLL 750 MHz, DPLL 664 MHz, DDR4-1333 (1328 MT/s), SSMOD active, HPM core_hpm_value=300 (high). Co-Authored-By: Claude Opus 4.7 (1M context) --- CMakeLists.txt | 1 + src/clocks.c | 7 +- src/clocks.h | 4 + src/hal/hisi/clocks_v5.c | 285 +++++++++++++++++++++++++++++++++++++++ 4 files changed, 296 insertions(+), 1 deletion(-) create mode 100644 src/hal/hisi/clocks_v5.c diff --git a/CMakeLists.txt b/CMakeLists.txt index e615343..9b6eee8 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -94,6 +94,7 @@ set(IPCTOOL_SRC src/firmware.h src/hal/hisi/clocks_v4.c src/hal/hisi/clocks_v4a.c + src/hal/hisi/clocks_v5.c src/hal/hisi/ethernet.c src/hal/hisi/ethernet.h src/hal/hisi/ispreg.c diff --git a/src/clocks.c b/src/clocks.c index c4dfc43..5ee2f56 100644 --- a/src/clocks.c +++ b/src/clocks.c @@ -16,12 +16,14 @@ extern const struct clock_family clocks_family_v4; extern const struct clock_family clocks_family_v4a; +extern const struct clock_family clocks_family_v5; -/* TODO: add V1/V2/V3/V3A/OT/3536C/3536D tables — they share the +/* TODO: add V1/V2/V3/V3A/3536C/3536D tables — they share the * CRG-table approach but use different bases and bit layouts. */ static const struct clock_family *const families[] = { &clocks_family_v4, &clocks_family_v4a, + &clocks_family_v5, }; static const struct clock_family *family_for_chip(int chip_id) { @@ -292,6 +294,9 @@ cJSON *clocks_build_json(bool brief) { cJSON_AddItemToObject(j_inner, fam->hpms[i].name, h); } + if (fam->extra) + fam->extra(j_inner, brief); + if (!brief) { cJSON *running = build_cpu_running(); if (running) diff --git a/src/clocks.h b/src/clocks.h index ed76cb7..8b5bb28 100644 --- a/src/clocks.h +++ b/src/clocks.h @@ -76,6 +76,10 @@ struct clock_family { size_t n_muxes; const struct hpm_info *hpms; size_t n_hpms; + /* Optional family-specific extras (e.g. composite DDR rate that + * depends on a CRG mux + a PHY register). Called after the standard + * pll/mux/hpm sections; cJSON children appended into the same root. */ + void (*extra)(cJSON *root, bool brief); }; /* Builds the cJSON tree for the current chip. Returns NULL on unsupported diff --git a/src/hal/hisi/clocks_v5.c b/src/hal/hisi/clocks_v5.c new file mode 100644 index 0000000..49240eb --- /dev/null +++ b/src/hal/hisi/clocks_v5.c @@ -0,0 +1,285 @@ +/* Clock / PLL register map for the Hisilicon V5 / HISI_OT family + * (Hi3516CV610 / Hi3516CV613 / Hi3516DV500 / Hi3519DV500). + * + * CRG layout (per Hi3516CV610_SDK_V1.0.2.0 + * smp/a7_linux/source/bsp/components/gsl/include/platform.h + * and .../drivers/secure_driver/include/platform.h): + * + * REG_BASE_CRG = 0x11010000 + * PERI_CRG_PLL0 = 0x000 APLL_CONFIG_0 (CPU PLL) + * PERI_CRG_PLL1 = 0x004 APLL_CONFIG_1 + * APLL_LOCK_REG = 0x038 + * VPLL_CONFIG_0 = 0x080 (Video PLL) + * VPLL_CONFIG_1 = 0x084 + * VPLL_LOCK_REG = 0x0B8 (sys_ctrl.c "wait vpll effect") + * DPLL_CONFIG_0 = 0x180 (DDR PLL) + * DPLL_CONFIG_1 = 0x184 + * DPLL_LOCK_REG = 0x1B8 + * EPLL_CONFIG_0 = 0x200 (Ethernet PLL) + * EPLL_CONFIG_1 = 0x204 (PERI_CRG_PLL129) + * EPLL_LOCK_REG = 0x238 + * + * Lock register layout (per `apll_lock_status` union in + * smp/a7_linux/source/bsp/components/gsl/boot/reset.c): + * bit 0 = apll_lock (instantaneous) + * bit 4 = apll_lock_final (stable -- the bit we report) + * + * PLL field layout: identical to V4 (`struct hi3516a_pll_clock`): + * ctrl_reg1 (+0x00): FRACDIV[23:0], POSTDIV1[26:24], POSTDIV2[30:28] + * ctrl_reg2 (+0x04): FBDIV[11:0], REFDIV[17:12] + * f = 24 MHz * (FBDIV + FRACDIV/2^24) / (REFDIV * POSTDIV1 * POSTDIV2) + * + * Ground-truthed on Hi3519DV500 demo board (OS08A10 sensor): + * APLL @ 0x11010000=0x12800000, 0x11010004=0x103e -> 750.000 MHz (CPU) + * VPLL @ 0x11010080=0x1289374b, 0x11010084=0x1041 -> 786.432 MHz + * DPLL @ 0x11010180=0x12555555, 0x11010184=0x1037 -> 664.000 MHz (DDR) + * EPLL @ 0x11010200=0x12155555, 0x11010204=0x1034 -> 625.000 MHz (ETH) + * + * Beyond the four PLLs this file also decodes (via v5_extra): + * - DDR data rate: DPLL post-divided * (1 << dficlk_ratio) where + * dficlk_ratio lives in DDR_PHY0+0x78 bits[1:0]; ddr_cksel at + * CRG+0x2000 bits[18:16] selects between 24 MHz and clk_dpll_pst + * - DRAM type from DDR_PHY0+0x2C bits[3:0] + * - SSMOD (DPLL spread-spectrum) at CRG+0x190 bits[0],[2] + * - per-site live HPM samples (NPU / MDA / CORE) at HPM_BASE 0x1102B000 + * The canonical per-die HPM binning value (HPM_STORAGE_REG @ SYSCTRL+0x340 + * bits[9:0]) is surfaced through the standard hpm_info table. + */ + +#include "cjson/cJSON.h" +#include "clocks.h" +#include "hal/hisi/hal_hisi.h" +#include "tools.h" + +#define V5_CRG_BASE 0x11010000u +/* DDR PHY0 base (DDR_REG_BASE_PHY0 in vendor SDK). */ +#define V5_DDR_PHY0_BASE 0x11150000u +/* SYSCTRL base (REG_SYSCTRL_BASE / DDR_REG_BASE_SYSCTRL in vendor SDK). */ +#define V5_SYSCTRL_BASE 0x11020000u +/* HPM register block (HPM_BASE_ADDR in svb.h). */ +#define V5_HPM_BASE 0x1102B000u + +/* DPLL spread-spectrum modulation control (CRG+0x190). + * bit[0] = ssmod_cken + * bit[2] = ssmod_disable (1 = SSMOD off) + * Definitions from the V5 u-boot patch at + * open_source/u-boot/u-boot-2022.07.patch around line 30246. */ +#define V5_SSMOD_CTRL_OFF 0x190 + +#define V5_PLL(name_str, label_str, off_ctrl1, off_lock) \ + { \ + .name = (name_str), \ + .label = (label_str), \ + .ctrl_reg1 = V5_CRG_BASE + (off_ctrl1), \ + .frac_shift = 0, \ + .frac_width = 24, \ + .postdiv1_shift = 24, \ + .postdiv1_width = 3, \ + .postdiv2_shift = 28, \ + .postdiv2_width = 3, \ + .ctrl_reg2 = V5_CRG_BASE + (off_ctrl1) + 4, \ + .fbdiv_shift = 0, \ + .fbdiv_width = 12, \ + .refdiv_shift = 12, \ + .refdiv_width = 6, \ + .input_khz = 24000, \ + .lock_reg = V5_CRG_BASE + (off_lock), \ + .lock_bit = 4, /* apll_lock_final */ \ + } + +static const struct pll_info v5_plls[] = { + V5_PLL("cpu_pll", "CPU PLL (APLL)", 0x000, 0x038), + V5_PLL("video_pll", "Video PLL (VPLL)", 0x080, 0x0B8), + V5_PLL("ddr_pll", "DDR PLL (DPLL)", 0x180, 0x1B8), + V5_PLL("eth_pll", "Ethernet PLL (EPLL)", 0x200, 0x238), +}; + +/* Per-die HPM (Hisilicon Process Monitor) binning value. + * + * Three HPM measurement sites exist on V5 (NPU at HPM_BASE+0x18/+0x1C, + * MDA at +0x28/+0x2C, CORE at +0x38/+0x3C), each holding two 10-bit + * readings per register (u_hpm_reg layout: bits[9:0]=hpm0, + * bits[25:16]=hpm1). The instantaneous CORE readings are emitted as a + * per-site fingerprint by v5_extra(). + * + * The canonical value mapped here is HPM_STORAGE_REG @ SYSCTRL+0x340 + * (u_hpm_storage_reg.core_hpm_value, bits[9:0]) -- this is the binning + * value boot software writes once it has averaged the CORE readings. + * + * Binning thresholds from svb.h: + * CORE_HPM_BOUND_20 = 222 (SVB_20 cut-off) + * CORE_HPM_BOUND_10 = 230 (SVB_10 cut-off) + * CORE_HPM_BOUND_10_ESMT = 210 + * The exact bound chosen depends on the SVB version (SVB_VER_REG bits[5:2]). + * Here we use a generic window 210..310 and let clocks.c's hpm_bin() + * tri-state it; the precise SVB-version-aware threshold can be layered on + * top by anyone who needs the manufacturing meaning. */ +static const struct hpm_info v5_hpms[] = { + { + .name = "hpm", + .label = "HPM core (per-die)", + .reg = V5_SYSCTRL_BASE + 0x340, /* HPM_STORAGE_REG */ + .value_shift = 0, + .value_mask = 0x03FF, + .window_min = 50, + .window_max = 600, + .bin_min = 210, /* CORE_HPM_BOUND_10_ESMT */ + .bin_max = 310, + .aux_reg = V5_HPM_BASE + 0x38, /* HPM_CORE_REG0 (live read) */ + .aux_name = "hpm_core_reg0", + }, +}; + +/* DRAM type encoding -- PHY+0x2C bits[3:0] (PHY_DRAMCFG_TYPE_MASK). + * Two encodings coexist in vendor headers: + * 0..6 follow the [2:0] historical table (DDR1..LPDDR4) + * 0xA is DDR4 (introduced with [3:0] mask). */ +static const char *v5_dram_type_name(uint32_t t) { + switch (t) { + case 0x0: + return "DDR1"; + case 0x1: + return "DDR2"; + case 0x2: + return "DDR3"; + case 0x3: + return "DDR3L"; + case 0x4: + return "LPDDR1"; + case 0x5: + return "LPDDR2/LPDDR3"; + case 0x6: + return "LPDDR4"; + case 0xa: + return "DDR4"; + default: + return "unknown"; + } +} + +/* Decode the actual DDR data rate. The chain is: + * ddr_cksel (CRG+0x2000 bits [18:16]) -- 0 = 24 MHz, 1 = clk_dpll_pst + * DPLL VCO (CRG+0x180 / 0x184, post-divided) -- already decoded as + * ddr_pll dficlk_ratio (DDR_PHY0+0x78 bits [1:0]) -- 0=1:1, 1=1:2, 2=1:4 DRAM + * data rate (MT/s) = phy_clk_mhz * (1 << dficlk_ratio). In the typical config + * (DDR3/DDR4 -> 1:2, LPDDR4 -> 1:4) the multiplier matches the DDR burst + * factor, so this is also "post-divided PLL frequency times the bus + * double-data-rate". */ +static void v5_extra(cJSON *root, bool brief) { + uint32_t crg_cksel = 0; + uint32_t phy_ctrl0 = 0; + uint32_t phy_dramcfg = 0; + uint32_t dpll0 = 0, dpll1 = 0; + bool ok = mem_reg(V5_CRG_BASE + 0x2000, &crg_cksel, OP_READ) && + mem_reg(V5_CRG_BASE + 0x0180, &dpll0, OP_READ) && + mem_reg(V5_CRG_BASE + 0x0184, &dpll1, OP_READ) && + mem_reg(V5_DDR_PHY0_BASE + 0x0078, &phy_ctrl0, OP_READ) && + mem_reg(V5_DDR_PHY0_BASE + 0x002C, &phy_dramcfg, OP_READ); + if (!ok) + return; + + uint32_t ddr_cksel = (crg_cksel >> 16) & 0x7; + uint32_t dficlk_ratio = phy_ctrl0 & 0x3; + uint32_t dram_type = phy_dramcfg & 0xF; + + unsigned fbdiv = dpll1 & 0xFFF; + unsigned refdiv = (dpll1 >> 12) & 0x3F; + if (!refdiv) + refdiv = 1; + unsigned p1 = (dpll0 >> 24) & 0x7; + if (!p1) + p1 = 1; + unsigned p2 = (dpll0 >> 28) & 0x7; + if (!p2) + p2 = 1; + unsigned fracdiv = dpll0 & 0xFFFFFF; + double dpll_mhz = 24000.0 * (fbdiv + (double)fracdiv / (double)(1u << 24)) / + (double)(refdiv * p1 * p2) / 1000.0; + + /* Source: at boot/training time ddr_cksel can momentarily switch to + * 24 MHz; in normal operation it stays on the DPLL post-divider. */ + double phy_clk_mhz = (ddr_cksel == 0) ? 24.0 : dpll_mhz; + double data_rate_mtps = phy_clk_mhz * (double)(1u << dficlk_ratio); + + cJSON *ddr = cJSON_CreateObject(); + cJSON *j_inner = ddr; + ADD_PARAM("dram_type", v5_dram_type_name(dram_type)); + ADD_PARAM_NUM("data_rate_mtps", data_rate_mtps); + if (!brief) { + ADD_PARAM_FMT("phy_ctrl0", "0x%08x", phy_ctrl0); + ADD_PARAM_FMT("dramcfg", "0x%08x", phy_dramcfg); + ADD_PARAM_FMT("crg_ddr_cksel", "0x%08x", crg_cksel); + ADD_PARAM_NUM("dficlk_ratio", dficlk_ratio); + ADD_PARAM("source", ddr_cksel == 0 ? "24MHz" + : ddr_cksel == 1 ? "clk_dpll_pst" + : "unknown"); + } + cJSON_AddItemToObject(root, "ddr", ddr); + + /* SSMOD (DPLL spread-spectrum) status. */ + uint32_t ssmod_raw = 0; + if (mem_reg(V5_CRG_BASE + V5_SSMOD_CTRL_OFF, &ssmod_raw, OP_READ)) { + bool cken = ssmod_raw & 0x1; + bool disable = (ssmod_raw >> 2) & 0x1; + bool active = cken && !disable; + cJSON *ss = cJSON_CreateObject(); + { + cJSON *j_inner = ss; + cJSON_AddItemToObject(j_inner, "enabled", cJSON_CreateBool(active)); + if (!brief) { + ADD_PARAM_FMT("reg", "0x%08x", V5_CRG_BASE + V5_SSMOD_CTRL_OFF); + ADD_PARAM_FMT("raw", "0x%08x", ssmod_raw); + cJSON_AddItemToObject(j_inner, "ssmod_cken", + cJSON_CreateBool(cken)); + cJSON_AddItemToObject(j_inner, "ssmod_disable", + cJSON_CreateBool(disable)); + } + } + cJSON_AddItemToObject(root, "ssmod", ss); + } + + /* Per-site HPM live readings -- only in full output. The canonical + * core_hpm_value at HPM_STORAGE_REG is already surfaced by the + * generic hpm decoder via v5_hpms[]. */ + if (!brief) { + struct site { + const char *name; + uint32_t reg0; + uint32_t reg1; + } sites[] = { + {"npu", V5_HPM_BASE + 0x18, V5_HPM_BASE + 0x1C}, + {"mda", V5_HPM_BASE + 0x28, V5_HPM_BASE + 0x2C}, + {"core", V5_HPM_BASE + 0x38, V5_HPM_BASE + 0x3C}, + }; + cJSON *sites_j = cJSON_CreateObject(); + for (size_t i = 0; i < sizeof(sites) / sizeof(sites[0]); i++) { + uint32_t r0 = 0, r1 = 0; + if (!mem_reg(sites[i].reg0, &r0, OP_READ) || + !mem_reg(sites[i].reg1, &r1, OP_READ)) + continue; + cJSON *site = cJSON_CreateObject(); + { + cJSON *j_inner = site; + /* u_hpm_reg: bits[9:0]=hpm0, bits[25:16]=hpm1 */ + ADD_PARAM_NUM("reg0_hpm0", r0 & 0x3FF); + ADD_PARAM_NUM("reg0_hpm1", (r0 >> 16) & 0x3FF); + ADD_PARAM_NUM("reg1_hpm0", r1 & 0x3FF); + ADD_PARAM_NUM("reg1_hpm1", (r1 >> 16) & 0x3FF); + } + cJSON_AddItemToObject(sites_j, sites[i].name, site); + } + cJSON_AddItemToObject(root, "hpm_sites", sites_j); + } +} + +const struct clock_family clocks_family_v5 = { + .chip_id = HISI_OT, + .label = "Hisilicon V5 (3516CV610 / 3519DV500)", + .plls = v5_plls, + .n_plls = sizeof(v5_plls) / sizeof(v5_plls[0]), + .muxes = NULL, + .n_muxes = 0, + .hpms = v5_hpms, + .n_hpms = sizeof(v5_hpms) / sizeof(v5_hpms[0]), + .extra = v5_extra, +};