Skip to content

soc: apple: add Apple SEP driver - #7

Open
DjDeveloperr wants to merge 26 commits into
omacom:asahifrom
aurora-silicon:feat/sep
Open

DjDeveloperr wants to merge 26 commits into
omacom:asahifrom
aurora-silicon:feat/sep

Conversation

@DjDeveloperr

@DjDeveloperr DjDeveloperr commented Sep 18, 2026

Copy link
Copy Markdown

Adds support for Touch ID and Secure Enclave backed keyctl & hwrng.

TODO: Newer devices like M4 / later and Neo seem to use a new power sequence for Touch ID sensor, not GPIO but SMC functions.

Before you start

This touches the Secure Enclave. Use a machine you can DFU-restore ideally.

It's verified to be safe on M2 Pro MBP and M1 Air so far and harmless to SEP anti-replay state and works nicely with macOS, but being experimental, I recommend to be careful.

With writes enabled, xART advances a device-wide anti-replay epoch, rapid reboot / delete / re-provision loops drift it and risk needing a DFU restore. Enrol through fprintd only (mixing tools splits host and enclave state); the keybag and ref-key are always recovered together.


1. Copy the calibration blob from macOS

Update:

You can just extract it from the NVMe using this script (verified on M2 Pro):

extract-mesa-calibration.py

sudo python3 extract-mesa-calibration.py --output /tmp/mesa_calibration.bin

Or, use the old way:

The sensor won't match a finger without its per-device factory calibration, and we can't extract it at runtime so copy it out of macOS once.

In macOS, enrol a finger in Touch ID once (to populate the cache), then:

sudo cp /var/root/mesa_calibration.bin /Users/Shared/

Finally, to install:

In Asahi:

sudo install -Dm644 /tmp/mesa_calibration.bin /lib/firmware/apple/mesa_calibration.bin

Per-device, do not reuse. A blob from another machine loads, but every match then fails. One machine, one blob.

2. Device-tree pins — skip on j414s

The sensor is always on spi2, chip-select 0, with identical electrical constants on every Mac. Only the SPI2 pin group and the power/IRQ GPIOs are per-board and can't be probed.

On any other machine, read them once and add an spi2 controller + mesa@0 node to that machine's .dts, modelled on the committed t8103 / t8112 nodes:

./mesa_spi2_pinmux.py <ssh-alias> adt.bin    # SPI2 pin group
# power + IRQ from ADT /arm-io/spi2/mesa: function-mesa_pwr, interrupt-parent

mesa-spi2-report.py

3. Build the kernel

Start from your running config so the rest of the machine keeps working, then enable the driver and build the DTBs alongside the image:

git clone https://github.com/aurora-silicon/linux -b feat/sep
cd linux
zcat /proc/config.gz > .config
scripts/config --module APPLE_SEP        # CONFIG_APPLE_SEP=m
make LLVM=1 olddefconfig
make LLVM=1 -j"$(nproc)" Image modules dtbs
sudo make LLVM=1 modules_install

LLVM=1 is required — the SEP transport, key store and xART logic are
Rust, so the build needs clang and the kernel's Rust toolchain.

4. Update m1n1 (stage 2) + DTB

The bootloader hands the SEP off to Linux by a warm attach. A stock m1n1 asks the SEP for randomness during boot, which burns its one-shot session before Linux ever attaches. Boot through the Aurora-patched m1n1 and point it at the DTB you just built:

sudo update-m1n1 /path/to/aurora-m1n1.bin    # or your distro's m1n1 update flow

SET_SHMEM is one-shot per machine reset, the SEP attaches once per boot.
Only a full reboot gives another attempt.

5. Install the loader + fprintd

The driver finds the xART gigalocker itself inside the iBoot system container. Just install the loader service and the fingerprint stack (both in tools/aurora-sep/):

sudo install -Dm755 load-driver /usr/local/sbin/aurora-sep-load
sudo install -Dm644 aurora-sep.service /etc/systemd/system/
sudo systemctl enable aurora-sep.service

# fingerprint stack: patch + build libfprint 1.94.100, then the device policy
sudo install -Dm644 fprintd-aurora.conf \
  /etc/systemd/system/fprintd.service.d/aurora.conf

The service runs modprobe apple_sep xart_writes=1 provision_keybag=1; the driver auto-locates the gigalocker and provisions the identity keybag on the first boot.

6. Boot the new kernel once

Boot it as a one-shot GRUB entry, so a bad build falls back on the next reboot instead of stranding you:

sudo grub-reboot "Asahi Linux (feat/sep)"    # next boot only
sudo reboot

7. Check it came up, then enrol

Each line is the signal that the layer beneath it worked:

check meaning
xART: ... auto-located raw-extent owner (base 0x...); N slots, M live records The driver found the gigalocker on its own.
sks: endpoint 0x12 (key store) up; 12 EPs The Secure Enclave key store attached — the chain's gate. Fewer than 12 EPs is a fault.
sks: identity keybag provisioned First boot creates the keybag + ref-key; later boots restore it.
ls /dev/sep-bio · systemctl is-active aurora-sep.service Touch ID device published, service self-test passed.
fprintd-list "$USER"    # -> "Apple secure enclave fingerprint sensor"
fprintd-enroll          # touch through the stages
fprintd-verify          # then reboot, and fprintd-verify again

When it doesn't come up

sks: endpoint 0x12 (key store) not advertised; N EPs up: stuck below 12 EPs. First confirm xART: ... auto-located ... M live records appears (the driver found the store). A clean attach climbs 7 → 11 → 12; the driver waits for the key-store endpoint, so a healthy machine reaches 12.

sensor: calibration blob ... could not be loaded (ENOENT): the per-device blob is missing. Redo step 1; it must be exactly at /lib/firmware/apple/mesa_calibration.bin.

identity keybag state is ambiguous: EEXIST / provisioning failed: the keybag / ref-key pair is broken. They are a bound pair; delete both and reboot:

sudo rm /var/lib/aurora-sep-keybag.bin /var/lib/aurora-sep-refkey.bin
sudo reboot

enrol "unknown error" / verify: ... enclave holds no template: stale enclave state from a previous enrol. Wipe the per-enrol state (keeping the keybag) and enrol fresh:

sudo tools/aurora-sep/sep-fresh-test.sh    # DELETE_ALL + catacombs + fprintd DB; keeps the keybag

Sensor never binds / no power line: non-j414s only: wrong power GPIO in your DTS (step 2). The sensor answers sixteen zero bytes when it isn't powered.

No endpoints / warm attach fails: a stock m1n1 poked the SEP first; install the Aurora-patched build (step 4). Also seen if the SEP was already attached this boot — reboot for a fresh attempt.

Nothing changes after modprobe -r / modprobe apple_sep: SET_SHMEM is one-shot per reset, so a reload can't re-attach. Reboot to test a change.

DjDeveloperr and others added 26 commits September 15, 2026 17:10
Add a Rust driver for the Apple SEP (Secure Enclave Processor) on Apple
silicon Macs, built as the CONFIG_APPLE_SEP module apple-sep. It attaches
to a running SEP over the AP mailbox, drives the Touch ID fingerprint
sensor, and exposes the enclave's key services to Linux.

The driver is organised by SEP endpoint:
  - sep.rs      transport, endpoint bring-up, mailbox and probe (crate root)
  - sbio.rs     Touch ID: enrol, verify and match over /dev/sep-bio
  - sks.rs      the key store: key-bag recovery and lock state
  - refkey.rs   machine ref-key sealing, signing and attestation
  - fv.rs       the device-bound FileVault key hierarchy
  - proto.rs    the SEP wire protocol

Touch ID enrol and verify are exposed through a /dev/sep-bio character
device; the enclave matches and no biometric image ever crosses to
userspace. A SEP-backed trusted key source lets keyctl seal keys to the
enclave, which needs the trusted-keys core change included here to
register a runtime trusted-key source.

The C shims bridge kernel interfaces whose ABI is awkward to restate in
Rust (struct hwrng, the file and crypto APIs, the trusted-key framework);
no protocol logic lives in them.

Assisted-by: GPT-5.6 Sol
Assisted-by: Opus 5
Signed-off-by: Diljit Singh <dj@aurorasilicon.org>
Serve SEP anti-replay records from the existing device-wide xART extent
instead of a Linux-private seeded store. Validate the entire slot grid and
both root records before SEP registration, preserve the store's
copy-on-write ordering, and require both an explicit module parameter and
a writable block mapping before any write. Keep Linux-only biometric
metadata in a separate namespaced host store.

Assisted-by: GPT-5.6 Sol
Assisted-by: Opus 5
Signed-off-by: Diljit Singh <dj@aurorasilicon.org>
Unwrap a FileVault volume's key through the enclave and load the volume's
class keys into the enclave's volatile state, then drive the storage
controller's inline AES-XTS with the wrapped key so an encrypted APFS
volume is read and written with no plaintext key material on the
application processor. The volume and key-bag records come from plaintext
APFS metadata; only wrapped keys cross to the enclave.

Assisted-by: GPT-5.6 Sol
Assisted-by: Opus 5
Signed-off-by: Diljit Singh <dj@aurorasilicon.org>
The match check was stricter than the enclave requires and rejected valid
results. Accept a match when the stored identity's user id matches.

Assisted-by: GPT-5.6 Sol
Assisted-by: Opus 5
Signed-off-by: Diljit Singh <dj@aurorasilicon.org>
The driver opened its backing-store files with the credentials of the task
that triggered the access. A keyctl(2) call from an unprivileged process
reaches key unsealing in that process's context, which then could not read
the driver's own root-owned 0600 key-bag file, so unsealing failed with
-74. Open the stores under kernel credentials from the initial namespace
so access never depends on the calling task.

Assisted-by: GPT-5.6 Sol
Assisted-by: Opus 5
Signed-off-by: Diljit Singh <dj@aurorasilicon.org>
The biometric character device was exposed before the shared anti-replay
store and the key store had come up, so an early open could race enclave
setup. Publish /dev/sep-bio only once shared-xART and the key store are
ready.

Assisted-by: GPT-5.6 Sol
Assisted-by: Opus 5
Signed-off-by: Diljit Singh <dj@aurorasilicon.org>
Reference-count the enclave class keys a FileVault volume loads. Repeated
mounts of volumes that share a key bag now load the class keys once, and
the volatile keys are unloaded from the enclave only when the last volume
using them is released. Key material stays resident no longer than a
mounted volume needs it, and keys another mount still depends on are not
unloaded early.

Assisted-by: GPT-5.6 Sol
Assisted-by: Opus 5
Signed-off-by: Diljit Singh <dj@aurorasilicon.org>
Remove counters and fields that nothing reads. No functional change.

Assisted-by: GPT-5.6 Sol
Assisted-by: Opus 5
Signed-off-by: Diljit Singh <dj@aurorasilicon.org>
Concurrent key-store (SKS) out-of-line exchanges could interleave their
mailbox traffic and out-of-line buffer registrations and wedge the
endpoint when fingerprint matching and a FileVault mount ran at once. Hold
a mutex across each complete SKS exchange so only one is in flight at a
time.

Assisted-by: GPT-5.6 Sol
Assisted-by: Opus 5
Signed-off-by: Diljit Singh <dj@aurorasilicon.org>
When the sensor node carries no power GPIO the shim fell back to a fixed
pinctrl node and line number specific to j414s. That fallback is
machine-blind: on a sibling board whose sensor sits on a different line it
would drive the wrong line. Take the fallback only on j414s; every other
machine must describe the power GPIO in its device-tree node.

Assisted-by: GPT-5.6 Sol
Assisted-by: Opus 5
Signed-off-by: Diljit Singh <dj@aurorasilicon.org>
Sensor bring-up located its SPI controller by a fixed address and so only
found the bus on j414s. Look up the apple,mesa-fingerprint node wherever
the device tree describes it and enable that node and its parent
controller directly, taking the power and interrupt GPIOs from the node.
Any machine that describes the sensor in its device tree can then bring it
up. When no such node exists the driver still creates one at the caller's
fixed controller address, so j414s is unchanged.

Assisted-by: GPT-5.6 Sol
Assisted-by: Opus 5
Signed-off-by: Diljit Singh <dj@aurorasilicon.org>
The match path rejected any sensor whose reported identifier was not
0x3352, the revision fitted up to M4. Newer machines ship a 0x335e part
that is identical over the wire. Accept both revisions so matching is not
refused on those machines.

Assisted-by: GPT-5.6 Sol
Assisted-by: Opus 5
Signed-off-by: Diljit Singh <dj@aurorasilicon.org>
The MacBook Air (M2) and MacBook Pro 13" (M2) carry the Mesa fingerprint
sensor on spi2, chip-select 0, like every other Apple laptop. t8112 only
described spi1, spi3 and spi4, so the Apple SEP driver had no controller to
bring the sensor up on these machines.

Add the spi2 controller node, mirroring the sibling spi1/spi3 controllers
(reg base + 0x8000, AIC IRQ 750, ps_spi2 power domain), and the spi2_pins
pinmux group. The pad numbers (64-67: SDI, SDO, SCK and the hardware chip
select) were read back from the live AP pinctrl on a J415 and cross-checked
against the T8112 ADT, where spi2 sits between spi1's CS pad 49 and spi3's
CS pad 96. The node stays disabled; the SEP driver enables it when it brings
up the sensor.

Signed-off-by: Diljit Singh <dj@aurorasilicon.org>
Assisted-by: GPT-5.6 Sol
Assisted-by: Opus 5
Add a binding for the Mesa fingerprint sensor on the SPI2 bus of Apple
laptops, driven by the Secure Enclave through a /dev/sep-bio character
device. The sensor uses the SPI controller's native chip select; a board
describes its power line (enable-gpios), an optional data-ready line
(interrupts), and the per-device calibration firmware name.

Also document the apple,dma-range DART property, used to place the SEP
firmware mapping on the M1 boot path.

Signed-off-by: Chromatischer <dominik@hildania.de>
[dj: adapted for aurora feat/sep, clean-room]
Signed-off-by: Diljit Singh <dj@aurorasilicon.org>
Assisted-by: GPT-5.6 Sol
Assisted-by: Opus 5
Describe the SPI2 controller and the Mesa fingerprint sensor on T8103, and
enable them on the MacBook Air (M1, J313).

The controller sits at the SPI cluster base + 0x8000 (reg 0x235108000, AIC
IRQ 616, ps_spi2 power domain), mirroring the sibling SPI nodes. Its pin
group is the three signal pads (128 CLK, 129 MOSI, 130 MISO), read back from
live hardware as peripheral function 1; SPI2 has no chip-select pad and uses
the controller's native chip select. The sensor's power line is GPIO 108
(active high); the data-ready line is left out because the driver polls.

Signed-off-by: Chromatischer <dominik@hildania.de>
[dj: adapted for aurora feat/sep, clean-room]
Signed-off-by: Diljit Singh <dj@aurorasilicon.org>
Assisted-by: GPT-5.6 Sol
Assisted-by: Opus 5
Name every SoC-specific fact the driver needs — the shared-memory capacity
and first-item fourcc, the boot handshake, the OS-identity source and the
sensor transport — in one typed profile, and refuse an unsupported SoC rather
than run a handshake with the wrong geometry.

Two targets are modelled: T8103/J313 (M1) boots the SEP with the boot endpoint
handshake over a 0x30000 window whose first item is CNIP; T6020/J414s (M2 Pro)
does the warm registration over a 0x40000 window whose first item is CINP. The
shared-memory build takes the capacity and first-item spelling from the
profile; T6020's values are unchanged.

Signed-off-by: Chromatischer <dominik@hildania.de>
[dj: adapted for aurora feat/sep - dropped the storage-backend enum (single
 in-kernel owner), identity is /chosen only, kept the driver modular; clean-room]
Signed-off-by: Diljit Singh <dj@aurorasilicon.org>
Assisted-by: GPT-5.6 Sol
Assisted-by: Opus 5
The T8103 SEP is not brought up by the warm registration message. On that
target, send TZ0 on the boot endpoint and let its acknowledgements drive the
handoff: map the firmware reserved region, submit the firmware address (IMG4)
and the shared-memory table (SET_SHMEM), and mark the registration complete on
the IMG4 acknowledgement. The warm target keeps the single registration
message; the profile selects which path runs, and only the warm target enables
the SEP and its DART with a runtime changeset - the cold-boot target's DART is
already enabled before probe and is described statically.

The firmware mapping is created once and released exactly once in remove(),
gated on the teardown flag so a boot message racing removal cannot map firmware
or hand memory to the peer after teardown.

Signed-off-by: Chromatischer <dominik@hildania.de>
[dj: ported onto aurora feat/sep - reused the existing teardown flag and warm
 attach path, profile-gated the runtime DT enable, clean-room]
Signed-off-by: Diljit Singh <dj@aurorasilicon.org>
Assisted-by: GPT-5.6 Sol
Assisted-by: Opus 5
The Mesa calibration blob is per-device factory data, so its name is board
data, not a build constant. Read it from the sensor node's firmware-name
property, falling back to the built-in default when the property is absent so
the runtime-fabricated node keeps working.

Signed-off-by: Chromatischer <dominik@hildania.de>
[dj: kept the default fallback so the fabricated j414s node still loads; clean-room]
Signed-off-by: Diljit Singh <dj@aurorasilicon.org>
Assisted-by: GPT-5.6 Sol
Assisted-by: Opus 5
Log once when the key-store endpoint (0x12) comes up and its out-of-line
buffers are registered, and enrich the "not advertised" diagnostic to show
how far the endpoint ladder reached: the live endpoint count and which of
control/xarm/sbio/scrd are present. An incomplete xART bring-up is then
diagnosable from dmesg alone, without instrumenting the driver.

Signed-off-by: Diljit Singh <dj@aurorasilicon.org>
Assisted-by: GPT-5.6 Sol
Assisted-by: Opus 5
Assisted-by: Claude Opus 4.8
The identity keybag CREATE_KEYBAG request carries the bag type in a word
the strict T8103 enclave reads as the type and the lenient T6020 enclave
does not. T6020 takes the variant in the first word (the proven
encoding, hardware-verified for enrol, match and reboot); T8103 rejects
that and wants the type in the third word, as macOS sends it. Carry the
three field values in the platform profile so each SoC sends what its
enclave requires, leaving the T6020 wire encoding byte-for-byte
unchanged. Drop the now-unused variant constant, create-flags helper and
first-identity handle.

Verified on j414s (T6020): the identity keybag still provisions.

Signed-off-by: Diljit Singh <dj@aurorasilicon.org>
Assisted-by: GPT-5.6 Sol
Assisted-by: Opus 5
Assisted-by: Claude Opus 4.8
Add an in-kernel path that opens the iBoot system container directly and
serves the STORE_SIZE gigalocker window at a configured sector, so the
driver reaches the shared anti-replay store on its own.

It is additive and fail-closed: the xart_start_sector module parameter
selects the raw extent, the logical store is always exactly STORE_SIZE so a
larger backing device serves only its extent, and a wrong window still fails
the existing root-record check rather than feeding SEP an unrelated store.

Verified on j414s: with xart_start_sector at the gigalocker's first sector
the store opens with the expected slot count, record count and revision, and
the key store comes up.

Signed-off-by: Diljit Singh <dj@aurorasilicon.org>
Assisted-by: GPT-5.6 Sol
Assisted-by: Opus 5
Assisted-by: Claude Opus 4.8
Drop the last manual step from the in-kernel raw-extent owner: instead of a
hand-supplied start sector, the driver finds the gigalocker itself. It reads
the iBoot system container sequentially and searches, at block-aligned
offsets, for a root record's signature (a 1 or 2 key kind with an all-zero
UUID, the shape of Key::root).

A root signature alone does not pin the base. A window shifted a few slots
off the true origin still keeps both root records inside its 6 MiB extent, so
it passes the two-root test yet silently drops the live records that fall
outside the shift -- and, with writes on, would repair (write) at that wrong
origin. The driver therefore probes every candidate base a hit implies
read-only: through a writable handle, because the block layer only grants a
read-only handle to a read-only device and the container is writable, but
with repair disarmed so nothing is written at an unconfirmed origin. It then
pins the base that recovers the most live records. Only the true origin
captures the whole record set, and container noise never forges a CRC-valid
record, so max live records -- ties broken to fewer malformed, then the
higher base -- is exact. Only the pinned base is re-opened writable, so
repair runs at the confirmed origin alone.

open_based gains open_based_ext, which decouples the block-handle writability
from whether repair is armed, so discovery of the writable container never
mutates it. With automatic location in place, xart_start_sector becomes an
explicit override (default 0 = automatic) and the driver has no external
dependency for reaching the store.

Boot-verified on j414s (T6020): the scan pins the true base 0x4d4000, reads
all 10 live records with 0 malformed and 0 repaired (no errant write), and
the key store (EP 0x12) reaches 12 endpoints.

Signed-off-by: Diljit Singh <dj@aurorasilicon.org>
Assisted-by: GPT-5.6 Sol
Assisted-by: Opus 5
Assisted-by: Claude Opus 4.8
The SEP endpoint ladder climbs 7 -> 11 (sbio, scrd) -> 12 (the key store,
EP 0x12). tick_exchange declared the persistent-state exchange done as soon
as the endpoint count passed a hardcoded pre-exchange baseline (7), but the
SEP advertises the key store a beat after it falls briefly quiet following
scrd. On some boots the driver therefore reached run_bringup at 11
endpoints, found EP 0x12 absent, and stranded the boot with no key store --
enable_sks fails with no retry, so keybag and ref-key operations (and thus
Touch ID) are unavailable for the whole boot.

Wait through quiescence until the key store itself is advertised, bounded by
the same EXCHANGE_TIMEOUT_MS. A boot that already has it exits immediately,
so healthy boots are unaffected; the hardcoded endpoint count, which is not
portable across SoCs, is dropped in favour of the endpoint that actually
matters.

Boot-verified on j414s (T6020): the key store (EP 0x12) came up at 12
endpoints on 5 of 5 reboots, up from roughly 2 in 3 before the change.

Signed-off-by: Diljit Singh <dj@aurorasilicon.org>
Assisted-by: GPT-5.6 Sol
Assisted-by: Opus 5
Assisted-by: Claude Opus 4.8
The capture loop polled the sensor's status over SPI every 2 ms to spot a
ready frame. Add an opt-in data-ready interrupt (capture_irq=1; default 0
keeps polling) that wakes the loop the instant the sensor asserts its
data-ready line. The SPI status read still gates every frame, so a missed or
spurious interrupt costs at most one poll interval and the poll path is
untouched when the option is off.

The interrupt is configured once, while the sensor is patched and idle -- the
only safe moment, since reconfiguring the line mid-capture is what drops the
sensor's firmware patch -- and then left alone for the driver's life. On
j414s it is the pin adjacent to the power line (GPIO 121 -> IRQ 144); other
boards describe it as the SPI node's interrupt. It is edge-triggered on both
edges: the line idles low, so a level trigger would storm.

Boot-verified on j414s (T6020): with capture_irq=1 the data-ready IRQ 144 is
configured, fingerprint verify captures and matches through the interrupt,
the IRQ fires a handful of times per touch (not a level storm), and the
sensor keeps its firmware patch (no STATE_NEEDS_PATCH).

Signed-off-by: Diljit Singh <dj@aurorasilicon.org>
Assisted-by: GPT-5.6 Sol
Assisted-by: Opus 5
Assisted-by: Claude Opus 4.8
An enrol capture the enclave rejects (partial or unusable contact) silently
looped for another frame with no feedback, so the person had no idea their
touch had not counted. Set the hold-still guidance on that path so the
enrol poll reports it and userspace can prompt for a firmer press, matching
the guidance already surfaced from the armed and reading sensor states.

Signed-off-by: Diljit Singh <dj@aurorasilicon.org>
Assisted-by: GPT-5.6 Sol
Assisted-by: Opus 5
Assisted-by: Claude Opus 4.8
Land the interrupt path as the default from hardware validation: it is no
longer behind the capture_irq parameter (removed), and it is set up whenever
the sensor exposes a data-ready line, with SPI polling kept only as the
fallback for a machine that does not describe one.

Two fixes the enrolment stress test surfaced:
- Trigger on the rising edge only. The line idles low, so a level trigger
  storms and both-edges also fires on the deassert -- a spurious wake that,
  mid-enrolment (the finger lifts and repositions between the eight stages),
  spun the capture loop.
- Bound the capture wait by wall-clock time, not a fixed attempt count. A
  stray edge returns the wait early, so an attempt budget would be spent in
  milliseconds and time the capture out before a frame lands; the time bound
  degrades, at worst, to the poll path's duration.

The wait now blocks on the data-ready line for up to ENROL_IRQ_WAIT_MS
(250 ms) rather than re-reading SPI status every 2 ms, waking the instant a
frame is ready.

Boot-verified on j414s (T6020): fprintd verify and a full eight-stage enrol
both capture and match through the interrupt, with no STATE_NEEDS_PATCH and
no level storm.

Signed-off-by: Diljit Singh <dj@aurorasilicon.org>
Assisted-by: GPT-5.6 Sol
Assisted-by: Opus 5
Assisted-by: Claude Opus 4.8
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant