# K230 second-core feasibility This source and boot-record audit includes a bounded physical CPU0 identity measurement recorded below. It does not claim that the second core can be enabled safely. The diagnostic firmware was restored; the normal kernel, device tree, OpenSBI, and stage 1 are unchanged. ## Silicon and vendor architecture Canaan's primary documents describe two heterogeneous Xuantie C908 CPUs: - [K230 product datasheet, CPU subsystem](https://github.com/kendryte/k230_docs/blob/main/zh/00_hardware/K230_datasheet.md): CPU0 is a 64-bit RISC-V core up to 800 MHz with 32 KiB L1 caches and 128 KiB L2; CPU1 is a 64-bit RISC-V core up to 1.6 GHz with RVV 1.0, 32 KiB L1 caches, and 256 KiB L2. The datasheet also documents separate CPU timers and a mailbox capable of CPU0/CPU1 interrupts. - [K230 power-management guide](https://github.com/kendryte/k230_docs/blob/main/en/01_software/board/mpp/K230_PM_Usage_Guide.md): the SDK's documented split assigns CPU0 to Linux and CPU1 to RT-Smart; the large core also owns display, multimedia, and AI power domains. - [K230 SDK user manual](https://github.com/kendryte/k230_docs/blob/main/en/01_software/board/K230_SDK_User_Manual.md): the SDK is organized as a Linux little-core side and an RT-Smart big-core side. - [K230 RVV description](https://www.kendryte.com/k230/en/v1.7/02_applications/tutorials/K230_RVV_optimization_performance_description.html): confirms the C908 dual-core design and identifies RVV 1.0 as the large-core feature. These documents establish hardware capability and the vendor SDK's AMP design. They do not establish which physical core the current Xuantie Linux image uses, or that Linux can safely take the other core. ## What the pinned image exposes The pinned Linux source is `nix/kernel-src.nix`, from `ruyisdk/linux-xuantie-kernel` revision `7d4e1f444f461dbe3833bd99a4640e7b6c2cd529`. The evaluated source path was `/nix/store/hn11x8zd5linl193d8q0k9k99b46zqbm-linux-xuantie-k230-src`. `arch/riscv/boot/dts/canaan/k230.dtsi:28-69` declares only `cpu@0`, with `reg = <0>`, `thead,c908`, Sv39, and one cache description. There is no `cpu@1`. Its PLIC at `:210-218` references only `cpu0_intc`, and its CLINT at `:255-259` references only CPU0 software/timer interrupt lines. The project image is the K230 Linux SDK/Xuantie Linux path, not a complete Linux-plus-RT-Smart image. `nix/stage1.nix:140-142` carries OpenSBI, its U-Boot header, and the kernel-side boot files; it does not carry an RT-Smart payload. `nix/kernel.nix` uses the vendor `k230` kernel defconfig and sets SMP support, but the DT presents one hart. The [boot-path comparison](../evidence/boot-path-differences.md) and [hardware userspace evidence](../evidence/hardware-userspace.md) record `nproc = 1` and only processor 0. Fresh physical evidence in `docs/evidence/cpu-readiness.txt` records `CONFIG_SMP=y`, `NR_CPUS=64`, `RISCV_SBI=y`, only CPU0 present/possible, live DT only `cpu@0`, and a Linux hart reporting `thead,c908`, vector support, and a 256 KiB L2. That is consistent with the datasheet's big-core description, so it is unsafe to label logical hart 0 as physical CPU0 or CPU1 without a register/firmware observation. The current boot transcript says: ```text Platform HART Count : 1 Platform IPI Device : aclint-mswi Platform Timer Device : aclint-mtimer @ 27000000Hz Platform HSM Device : --- Domain0 HARTs : 0* [ 0.015...] smp: Brought up 1 node, 1 CPU ``` See `docs/evidence/boot-from-source-cold.txt:104-125,230-231`. The source and logs establish one enumerated HART, not the physical-core identity and not that RT-Smart is running on the other core in this image. ## OpenSBI and SMP barriers The [official OpenSBI README](https://github.com/riscv-software-src/opensbi/blob/master/README.md) and [official SBI HSM specification](https://github.com/riscv-non-isa/riscv-sbi-doc/blob/master/src/ext-hsm.adoc) describe HSM hart start/stop/status calls required for normal Linux multi-hart boot. In the pinned OpenSBI source `/nix/store/k6kih6q1vh5nashsgaz96ch012742s8z-source`, `lib/sbi/sbi_ecall_hsm.c:19-67` implements the HSM SBI calls and `lib/sbi/sbi_init.c:98-100` prints `Platform HSM Device`. That line reports a platform-specific HSM device pointer; `---` is not proof that the SBI HSM extension is absent. The current log proves only that one HART was enumerated and assigned to the root domain. Adding a `cpu@1` node alone is therefore insufficient: 1. **Physical mapping and ownership are unknown.** The current carried image has no RT-Smart payload. The SDK's CPU0/Linux, CPU1/RT-Smart mapping is a reference architecture, not proof for this image. The live hart's RVV and L2 size make big-core execution plausible, but no source or current log maps logical hart 0 to a physical core. Display, multimedia, KPU, mailbox, and power-domain ownership must be established separately. 2. **HART discovery and start:** OpenSBI must enumerate a second unique hart, assign it to Linux, and provide a valid hart-start path. The current `/chosen/opensbi-domains` node (`k230.dtsi:21-26`) does not describe a second-HART domain or ownership contract. 3. **Interrupts and timers:** the K230 DTS PLIC and CLINT target CPU0 only. CPU1 needs verified local interrupt, software/timer interrupt, and K230 timer/IPI wiring. The datasheet mailbox proves an inter-core mechanism, but not a Linux SBI IPI implementation. 4. **ISA and MMU:** CPU1 has RVV 1.0 and a different cache topology. Linux needs an accurate CPU1 ISA/MMU/cache description and a common ISA baseline for SMP code. 5. **Coherency and shared memory:** the repository has no proof of the cache coherency contract needed for Linux shared page tables and SMP atomics. This must be established from the K230 programming/reference material or a controlled firmware experiment before any Linux shared-memory test. ## Options The lowest-risk option is to leave this single-hart Linux image unchanged. The vendor SDK's AMP split is a separate reference architecture; this image contains no RT-Smart payload, and its physical-core mapping and peripheral ownership remain unknown. A Linux SMP option needs a coordinated patch family, not one DTS edit: - DT CPU1 with verified hart ID, ISA, MMU, cache, local interrupt, and timer data; - PLIC/CLINT or K230 interrupt-controller support for both harts; - OpenSBI domain and hart-start support with generic HSM calls verified; - kernel SMP bring-up with common-ISA handling; and - an ownership decision for display, multimedia, KPU, mailbox, and power domains after removing the current single-hart/SDK assumptions. An AMP Linux-plus-RT-Smart option would require adding and validating the RT-Smart payload and its ownership contract. It is not evidence about this image, and no ready Linux-side API for it is present here. ## Minimal recoverable experiment Do not experiment on the only working card. Preserve the current image and use a second card or reversible UMS image. Before attempting Linux SMP or shared memory: 1. Establish the physical-to-hart map and ownership from the K230 reference manual and a controlled firmware/register observation. Confirm unique hart IDs, reset release, interrupt/timer routes, cache hierarchy, and the coherency/atomic-sharing contract. 2. Only then prepare an experimental DT with CPU1 and those verified descriptions; do not infer routes from the CPU count. 3. Generic SBI HSM is already compiled and detected in this image (`docs/evidence/boot-from-source-cold.txt:173`). Investigate whether the verified second hart can use OpenSBI's existing raw-IPI start path or needs a platform HSM start device, then build a variant that enumerates it. Retain the Canaan MAEE quirk and a rollback image. Treat `Platform HSM Device: ---` as informational, not as the acceptance test. 4. Boot with early serial logging and require HART count 2, Linux `smp: Brought up 1 node, 2 CPUs`, and successful CPU status checks. Stop on any hang, missing HART, or interrupt/timer failure. Only after that test shared-memory/cache behavior and peripheral ownership. 5. Restore the known-good stage-1/kernel image over the proven UMS path after any failed experiment. This would prove only that Linux can start a uniquely identified second hart after the coherency and ownership prerequisites are established. It would not prove RT-Smart AMP operation, display ownership, or the full vendor SDK. No board experiment was run for this audit. ## 2026-09 evidence update and decision The primary [K230 Technical Reference Manual v0.3.1 (TRM)](https://kendryte-download.canaan-creative.com/developer/k230/HDK/K230%E7%A1%AC%E4%BB%B6%E6%96%87%E6%A1%A3/K230_Technical_Reference_Manual_V0.3.1_20241118.pdf) resolves part of the physical mapping which the earlier audit deliberately left open. Section 1.3.2 identifies CPU0 as the 800 MHz/128 KiB-L2 small core and CPU1 as the 1.6 GHz/256 KiB-L2 RVV big core. Section 1.4.1 says BootROM starts CPU0 after reset deassertion and CPU0 controls CPU1 reset deassertion. That establishes reset ownership, but it does **not** assign Linux hart IDs or describe a Linux secondary-hart release protocol. The current Linux hart reports RVV and a 256 KiB L2 while the TRM assigns both features to CPU1. This makes CPU1 execution a strong inference for the current Linux handoff. It remains an inference, not a basis to add a guessed `cpu@1`: the physical-core-to-hart mapping is not in the captured handoff. ### What the vendor register map proves—and does not The TRM provides real CPU1 controls: - RMU is at `0x91101000`; `CPU1_RST_CTL` is offset `0x0c`. Its reset value is `0x00002001`; bit 0 is `cpu1_reset_req` (one means reset). Bit 4 requests an L2 flush and bits 12--13 are write-one-to-clear reset-done flags (TRM section 2.1.4). - PWR is at `0x91103000`; CPU1 control/status are offsets `0x18`/`0x1c` (TRM section 2.3.4). The CPU power-flow text requires a completed NOC low-power handshake before a power transition (section 2.3.3). - The interrupt table lists distinct mailbox sources in both CPU0-to-CPU1 and CPU1-to-CPU0 directions (TRM section 2.4). Those facts rule out a speculative register poke: a write would be an actual reset or power transition, and reads of the W1C-containing reset register must not be turned into read-modify-write operations. The TRM does not publish a CPU1 reset vector, physical hart ID, PLIC context mapping, ACLINT wiring, or an SBI HSM implementation for this device. It also does not establish the condition Linux SMP actually needs: coherent shared memory for page tables, spinlocks, and secondary-start data. The existing DT's `dma-noncoherent` property concerns DMA; it neither proves nor disproves CPU-to-CPU cache coherence. The TRM describes per-C908 cache control and an instruction-cache/data-cache snoop control, but contains no CPU0/CPU1 coherency or shared-atomic contract. This is a blocking absence of evidence, not a demonstrated hardware impossibility. ### Linux SMP is a different project from AMP The pinned Linux code makes the integration requirements concrete. Its [`cpu_ops_sbi.c`](https://github.com/ruyisdk/linux-xuantie-kernel/blob/7d4e1f444f461dbe3833bd99a4640e7b6c2cd529/arch/riscv/kernel/cpu_ops_sbi.c) starts a secondary with `SBI_EXT_HSM_HART_START`; its [`cpu.c`](https://github.com/ruyisdk/linux-xuantie-kernel/blob/7d4e1f444f461dbe3833bd99a4640e7b6c2cd529/arch/riscv/kernel/cpu.c) requires a valid ISA description for each DT CPU; and the K230 DTS supplies only one CPU interrupt controller to its PLIC and CLINT. Therefore Linux SMP needs a verified second hart, HSM start path, timer/IPI routes, CPU ISA/MMU and cache description, and coherency contract before a DT change is even useful. AMP has a different boundary: separate firmware images, mailbox notifications, explicitly managed shared buffers, and exclusive peripheral ownership. Canaan documents separate Linux little-core and RT-Smart big-core SDK images, which is evidence for an AMP model, not for Linux SMP. This image has neither the RT-Smart payload nor a Linux-side video-offload protocol, so AMP cannot yet improve the running video workload. A second core can help video only after a measured offload design exists; two cores alone do not improve a single-thread renderer or prove shared-memory safety. ### Proposed safe next step: passive handoff audit No boot configuration, device tree, CPU online state, or MMIO register is changed by this proposal. The integration operator should collect the following from a known-good board when it is free; this investigation does not take the serial device. 1. Preserve a cold-boot console capture containing OpenSBI platform HART count, domain HART list, HSM/IPI/timer device lines, and Linux's `smp:` result. This separates a firmware one-hart declaration from a Linux secondary-start failure. 2. Capture the live `cpus` DT subtree, PLIC/CLINT `interrupts-extended`, `/sys/devices/system/cpu/{possible,present,online}`, and per-CPU ISA/cache reports. These no-write facts identify the handoff Linux actually received. 3. Only with the operator's established **read-only** MMIO capture method, record values at `0x9110100c`, `0x91103018`, and `0x9110301c`. Do not use a tool mode that writes, performs read-modify-write, clears W1C status, or probes adjacent registers. These values may show reset/power state; they cannot establish hart identity or coherency. 4. Obtain a Canaan statement or shipped stage-1 source proving the CPU1 release sequence and vector, physical hart IDs, PLIC/ACLINT contexts, and CPU-to-CPU coherency contract. An undocumented release sequence is a stop condition. Only after step 4 should a separate rollback-card SMP proposal exist. It must first make OpenSBI enumerate and HSM-start both verified harts, then add the complete DT routes, then enable Linux SMP. Its acceptance criteria begin with two OpenSBI harts, `smp: Brought up 1 node, 2 CPUs`, timer/IPI stress, and a shared-memory atomic test. Video benchmarking comes after correctness, not before it. No board experiment was performed for this update. ## Vendor AMP source establishes a release contract, not SMP support A further source review found the vendor's concrete Linux-to-RT-Smart AMP path in [`k230-amp.c`](https://github.com/kendryte/k230_sdk/blob/main/src/little/linux/drivers/misc/canaan/k230-amp.c) and its [`amp_test`](https://github.com/kendryte/k230_sdk/blob/main/src/little/buildroot-ext/package/amp_app/src/amp_test.c) client. This is primary vendor source, but it is not part of this NixOS image. The client opens `/dev/k230-amp`, maps a device-tree-selected RT-Smart memory region, loads a firmware image there, then invokes `AMP_CMD_BOOT`. The driver writes that region back from the running Linux CPU's cache, programs the CPU1 reset-vector register, and performs the CPU1 reset-control writes. Mapping the region first holds the large core in reset. The companion README calls this "small-core Linux controls big-core restart". That is direct evidence that the vendor AMP boundary requires an explicit firmware payload, memory reservation, cache maintenance, reset vector, and reset protocol. It does not establish Linux SMP. The driver hardcodes vendor addresses and does not create a Linux CPU, an OpenSBI domain, a PLIC/ACLINT route, or shared Linux page-table coherency. Its actual reset writes are why this project will not use it as a probe or copy it into the image. The vendor's generic OpenSBI platform independently counts harts by iterating `/cpus` in [`platform/generic/platform.c`](https://github.com/kendryte/k230_sdk/blob/main/src/common/opensbi/platform/generic/platform.c). The pinned handoff supplies only `cpu@0`, so the observed OpenSBI platform count of one follows from the handoff description. OpenSBI's generic HSM ecall handler being linked only means it can dispatch `HART_START` for an assigned, known hart; it does not manufacture CPU1 release support. The publicly documented DRAM RAW/WAR coherency guarantees apply to the memory controller interfaces and are conditional on configuration for AXI. They are not a documented CPU0/CPU1 cache-coherency or atomic-sharing contract. This remains a blocker for Linux SMP, while the AMP source shows cache maintenance must be part of any separately designed shared-buffer protocol. ## 2026-09-26 pinned stage-1 audit: Linux runs on physical CPU1 The exact stage-1 source pinned by `nix/k230-sdk-src.nix` is Canaan's `k230_linux_sdk` revision `1104236db4d1e47873bd68924f912747b820228c`. `nix/uboot-k230.nix` copies its U-Boot overlay into the built source and uses `k230_canmv_v3_defconfig`. In that overlay, `board/canaan/common/sdk_autoconf.h:16` defines `CONFIG_LINUX_RUN_CORE_ID 1`; `board/canaan/common/k230_img.c:53` includes the header. Its `k230_boot_uboot_uimage()` branch at lines 276-284 calls `de_reset_big_core(image_get_load(pUh))` and parks the current core in `wfi`. The release function at lines 149-170 writes CPU1's reset-vector register `0x91102104`, then performs the documented `CPU1_RST_CTL` sequence at `0x9110100c`. `board/canaan/common/k230_spl.c:164-166` selects the U-Boot on-big-core path for the same configuration. This is shipped stage-1 source for a physical CPU1 release, and the live hart's RVV/256 KiB-L2 observation corroborates it. The extra core we want is physical CPU0, the 800 MHz non-RVV core. This does **not** prove the physical CPU0 hart ID. The same U-Boot overlay's `arch/riscv/dts/k230.dtsi:46-57` calls the single described, vector-capable hart `cpu@0`, gives it `reg = <0>`, and lists 800 MHz. That contradicts the physical CPU1 profile and shows the device-tree label/clock cannot be used as a physical-core map. The physical `mhartid` values of both CPUs require a direct, controlled firmware observation or a Canaan statement. U-Boot's `arch/riscv/cpu/k230/cpu.c:125-160` also has a `boot_baremetal` path whose CPU0 branch writes `0x91102100` and `0x91101004`; it demonstrates a physical CPU0 reset vector/release address but does not set up OpenSBI, interrupts, timers, cache sharing, or Linux SMP. There is stronger, adverse evidence about the hart map: in the [Linux K230 DTS review](https://lkml.rescloud.iu.edu/2403.3/00159.html), the patch author reports that the physical big core's `CSR.MHARTID` is 0, despite its physical CPU1 name. A [QEMU K230 board author reporting the physical topology](https://www.mail-archive.com/qemu-devel@nongnu.org/msg1188749.html) states that **both** physical cores have `mhartid` 0. These are first-hand developer reports, not a measurement on this board, but they directly undermine the assumed unique-hart-ID prerequisite of conventional SBI HSM and Linux SMP. They must be tested on a disposable board experiment before any design assigns the extra core `reg = <1>`. If the collision is confirmed, the existing SMP release option cannot proceed as written; AMP with a separate CPU0 payload and explicit shared-memory protocol is the plausible way to execute a workload on the extra physical core. The pinned Linux tree at revision `7d4e1f444f461dbe3833bd99a4640e7b6c2cd529` has an internal SBI HSM hart-status call in `arch/riscv/kernel/cpu_ops_sbi.c:52-63`, but its only caller is the CPU hotplug `cpu_is_stopped` path at lines 107-119. The read-only source search `rg -n 'sbi_hsm|hart_status|debugfs' /arch/riscv` found no Linux userspace/debugfs passthrough that queries an unenumerated hart's HSM status without CPU up/down. That says the proposed Linux-side query is unavailable in this pinned tree; it says nothing about whether firmware can answer the ecall. The pinned K230 Linux DTS still names only one PLIC/ACLINT target. Neither the pinned stage-1 overlay nor its OpenSBI generic-platform overlay defines a CPU0 PLIC context and ACLINT timer/IPI route for a joint Linux handoff. Thus task 3.1 is partly closed (physical CPU0 vector and release source) but remains blocked on the physical hart ID and interrupt topology. No reset, power, or vector write was performed in this audit. ## 2026-09-26 Linux SMP path audit (physical CPU0 is the target) The requested outcome is **one Linux kernel scheduling ordinary processes on both physical cores**. A separate CPU0 heartbeat or AMP payload cannot satisfy it. `the-small-core-runs-a-recoverable-heartbeat` is suspended after that scope correction. Pinned stage 1 (`k230_img.c:276-285`) already starts physical CPU1 and leaves CPU0 in WFI; Linux is on CPU1. The remaining physical core to bring into the same kernel is CPU0. The pinned OpenSBI 1.4 generic platform is not ready for two physical cores that report the same `mhartid`: - `platform/generic/platform.c:95-132` collects DT CPU `reg` into its hart-index-to-ID table. `lib/utils/fdt/fdt_helper.c:263-284` confirms that `reg` is the ID it reads. - `include/sbi/riscv_asm.h:166` defines `current_hartid()` as a read of `CSR_MHARTID`; `lib/sbi/sbi_scratch.c:24-32` resolves a hart ID to the **first** matching index. - `firmware/fw_base.S:441-462` reads `CSR_MHARTID` on warm entry and picks the first matching hart index before setting its M-mode scratch and stack. Thus duplicate IDs would reuse the boot hart's stack/scratch, not merely produce an incorrect display name. - `lib/sbi/sbi_hsm.c:300-361` indexes target scratch/HSM state by hart ID, then sends a raw IPI to that target index. A duplicate ID cannot identify CPU0 as a separate HSM target. Linux's `arch/riscv/kernel/smpboot.c:149-170` also treats the boot hart's ID as unique and skips a DT CPU with the same `reg`; its `cpu_ops_sbi.c:66-80` sends `SBI_EXT_HSM_HART_START` using the mapped ID. The [K230 QEMU author](https://www.mail-archive.com/qemu-devel@nongnu.org/msg1188749.html) reports that both physical cores read `mhartid=0`. The [Linux DTS author](https://lkml.rescloud.iu.edu/2403.3/00159.html) reports that physical CPU1 reads 0 and says inter-core cache coherence is unknown. These are firsthand reports, **not measurements on this board**. A fake `cpu@1` node would be unsafe even if a two-CPU DT compiled. A K230-specific virtual ID would need an early physical-core discriminator and consistent use in OpenSBI warm entry, scratch, domain, HSM, IPI, timer, and Linux handoff. No such implementation exists in the pinned vendor overlay: `platform/generic/thead/thead-generic.c` only applies C908 errata and PMU setup. A grep for another physical core-ID CSR in the pinned U-Boot and OpenSBI overlays found no defined candidate. This is an audited absence within those overlays, not proof that the silicon has no such CSR. The narrowest next physical identity measurement is source-reachable at an already executing point. The optional, default-off patch `nix/patches/second-core/spl-cpu0-identity.patch` reads `mhartid` and `misa` in CPU0 SPL immediately before its existing CPU1 release, prints them, and leaves the release/WFI behavior untouched. CPU1's existing OpenSBI boot banner reports its logical ID; an equivalent M-mode CPU1 readout would settle both physical CSR values. This diagnostic is not a second program or a Linux feature. It still requires a separately reviewed experimental stage-1 build and the board operator's recovery/serial session; it does not justify a reset write or SMP boot by itself. The pinned Linux DT `arch/riscv/boot/dts/canaan/k230.dtsi:210-218,255-259` supplies PLIC and CLINT routes only to its single CPU interrupt controller. The vendor OpenSBI overlay has no CPU0-specific PLIC/ACLINT mapping for a joint handoff. U-Boot's `CSR_SMPEN` write in `arch/riscv/cpu/k230/cpu.c:39-49` is not a CPU0/CPU1 cache-coherence contract: the pinned source does not define its semantics or show an inter-core cache test. The vendor AMP driver cache-flushes shared data, so initial Linux shared page tables and locks cannot safely be the first coherence experiment. A controlled pre-Linux atomic/cache and timer/IPI diagnostic or a Canaan contract must close those gates before any two-CPU Linux boot. Once closed, the first Linux SMP image should use a scalar common ISA across kernel and userspace; RVV can return only with a proven per-hart scheduling and userspace capability design. Canaan's public [small-core Linux DT](https://raw.githubusercontent.com/kendryte/k230_sdk/main/src/little/linux/arch/riscv/boot/dts/kendryte/k230.dtsi) at `src/little/linux/arch/riscv/boot/dts/kendryte/k230.dtsi:18-63` separately describes physical CPU0 as its sole logical `cpu@0`, with PLIC at `0xf00000000` and CLINT at `0xf04000000`, each wired only to that one local interrupt controller. This public `main` source was read during the 2026-09-26 audit but is **not** this project's pinned SDK revision and is not a combined two-core DT. The matching base addresses in the pinned big-core DT do not establish whether the hardware maps separate PLIC/ CLINT instances into per-core address spaces or exposes usable second contexts to one joint Linux image. It cannot justify inventing a second `interrupts-extended` entry. ## 2026-09-27 physical CPU0 identity measurement The default-off CPU0 SPL diagnostic was run on the board and its two raw SPL slots were restored byte-for-byte afterward. The [physical trial](../evidence/second-core/cpu0-identity-physical-trial.md) recorded `CPU0_SPL_IDENTITY mhartid=0x0 misa=0x800000000094112f` before the existing CPU1 release, followed by one-hart OpenSBI and one-CPU Linux. The restored image then booted normally. This turns the pinned source's physical CPU0 identity **sampling point** into a first-hand `mhartid=0` observation. Source tracing also resolves what the CPU1-side `Boot HART ID : 0` means. The pinned OpenSBI 1.4 `include/sbi/riscv_asm.h:166` defines `current_hartid()` as `csr_read(CSR_MHARTID)`. Canaan's pinned overlay `lib/sbi/sbi_init.c:534` assigns that value to `hartid`, `:394` passes it to `sbi_boot_print_hart()`, and `:169` prints `Boot HART ID` from it. The pinned stage-1 `k230_img.c:276-285` releases CPU1 into this boot path while CPU0 parks; the observed 256 KiB L2/RVV profile corroborates that physical assignment. Therefore the board's CPU1-side banner is also a first-hand CSR read of zero, with its physical attribution grounded in the stage-1 source. No new CPU1-specific marker was added. Both physical cores reporting hardware `mhartid=0` rules out a DT-only second CPU node under this generic OpenSBI's current scratch/HSM indexing. A safe virtual-ID scheme or other supported two-hart mapping, CPU0 interrupt/timer routing, and the shared cached-memory/atomic contract remain open before any Linux SMP attempt. ## TRM mailbox register map (task 1.1, 2026-10-12) **Result: the TRM publishes a mailbox register map. It is in TRM section 2.9 "Mailbox", not section 2.4.** Section 2.4 is "Interrupt System" (PDF page 104); earlier text here and in `openspec/changes/the-small-core-runs-as-a-coprocessor/` that attributed the mailbox to "section 2.4" meant only the interrupt table, which names the mailbox interrupt sources. The map below is read from the primary document; nothing is inferred from `th1520-mailbox.c`. Source: K230 Technical Reference Manual v0.3.1 (2024-11-18), the URL linked above, downloaded 2026-10-12 (SHA-256 `4a18fdbd1cd25c5a33ca4d54b78c95e0e338305bceb17397133370fa10c1e5f0`) and text-extracted with `pdftotext -layout`. The PDF is not stored in the repo or the Nix store (the repo keeps no vendor documents); PDF page numbers below are physical page indexes of that file. Section 2.9 starts at PDF page 172; the register descriptions run through page 185. - Base: `0x9110_4000`, 4 KiB, block name `mailbox` in the SoC address map (TRM section 1 address table). Lives inside SYSCTL; APB 3.0, 32-bit registers, clocked by `mailbox_pclk` (100 MHz). Clock gate: `mailbox_pclk_enable`, bit 4 of the CMU register at offset 0x50 (PDF page 55; `docs/research/k230-clock-gates-vendor-vs-mainline.md:109` lists the same `0x50/4`). Reset: bit 17 `mailbox_reset` in the RMU `SOC_CTL_RST_CTL` register (PDF page 18, reset value 0x1). Mainline `reset-k230.c` already names this as `RST_MAILBOX` (offset 0x20, bit 17, software-done type); no mainline K230 mailbox driver or DT node exists (`grep -i mailbox k230.dtsi` in the mainline tree finds none). - Function (2.9.1): hardware interlock (mutex) registers for CPU0/CPU1 hand-shake and shared-storage arbitration, mutual interrupts, and some submodule configuration/status registers. - Four interrupt-group register sets (groups 0-3), each ten 32-bit registers (five per direction); group 0 in full, groups 1-3 repeat it at the offsets below: | Offset | Register | Meaning | | --- | --- | --- | | 0x0000 | CPU2DSP_INT_EN0 | cpu0_sys to cpu1_sys enable. bits 31:16 `intr_raw_en`; bit 1 `intr_soft_rst` clears all counts (both directions); bit 0 `intr_en` | | 0x0004 | CPU2DSP_INT_SET0 | write-only; writing N (0..15) adds 1 to interrupt count N | | 0x0008 | CPU2DSP_INT_CLEAR0 | write-only; writing N subtracts 1 from count N | | 0x000C | CPU2DSP_INT_STATUS0 | read-only; 2-bit count for each of 16 sources (bits 1:0 = source 0 ... 31:30 = source 15) | | 0x0010 | CPU2DSP_INT_ERR0 | read-only; 31:16 overflow flags (count already 3), 15:0 underflow flags | | 0x0014 | DSP2CPU_INT_EN0 | cpu1_sys to cpu0_sys, same fields | | 0x0018 | DSP2CPU_INT_SET0 | same as above | | 0x001C | DSP2CPU_INT_CLEAR0 | same as above | | 0x0020 | DSP2CPU_INT_STATUS0 | same as above | | 0x0024 | DSP2CPU_INT_ERR0 | same as above | | 0x0028-0x009C | `*_INT_*1`, `*2`, `*3` | groups 1-3, same layout, each group is 0x28 bytes (CPU2DSP block at 0x28/0x50/0x78, DSP2CPU block at 0x3C/0x64/0x8C) | | 0x00A0-0x029C | MAILBOX0..MAILBOX127 | 128 interlock registers; bit 0 `Lock` (1 locked, 0 not), RW, reset 0 | | 0x02A0-0x02F8 | UART_LP_REQ_*, QOS_CTRL0-4, NOC_WR_BUFFERABLE_CTRL, DDR_*, MIPI_TX_BISTON, SSI_SLEEP, PVDET_*, OTP_HS_MSC_SEL, DDRC_SELFREF_TYPE, CODEC_AD2DA_LOOP, TIMER_EN | unrelated SYSCTL configuration registers that share the block; a mailbox driver must not touch them | - Interrupt sources (TRM section 2.4 table, PDF page 104 onward): `MB_CPU12CPU0_0` = bit 93, `MB_CPU12CPU0_1` = bit 94, `MB_CPU02CPU1_0` = bit 95, `MB_CPU02CPU1_1` = bit 96. Only two interrupt lines exist per direction although four register groups are listed; which groups drive which line is **not stated** in the sections read. **What this does not establish (kept UNVERIFIED):** - Which register group maps to which of the two interrupt lines per direction; whether the `intr_raw_en` bits gate individual sources and how `intr_en` composes with them (the TRM text is a machine-translation with several truncated cells). - Whether the lock registers are test-and-set on read or a plain RW bit; the TRM only says "1: locked, 0: not locked" (2.9.1 says hardware mutual exclusion but gives no read-to-acquire semantics). No driver may rely on a guess. - That CPU0 firmware can take the interrupt: CPU0's PLIC wiring and the mailbox line routing to it are not in this document. The cache/coherency situation of the interlock registers is moot (device registers), but the shared data buffers remain under the non-coherent contract in design.md Decision 3. - Nothing here was exercised on the board. A mailbox driver or firmware that touches these registers needs its own task and board proof; the change keeps using a polled shared-memory slot until then. Cross-check, naming only: `drivers/mailbox/th1520-mailbox.c` also has per-direction status/set/clear groups, but the K230 counters (two bits per source, 16 sources) and the 128 interlock registers are K230's own and are not borrowed from it.