n31: EIC, GPIO, PMIC keys, I2C, USB gadget - #1
Draft
andrew867 wants to merge 42 commits into
Draft
Conversation
N31 GPIO interrupts go through an EIC at 0x39700000, then into the PL192 VIC EXT lines. Group is gpio >> 5. Chain only the parents listed in DT; wiring every EXT0..6 hung boot. GPIO 86 (PMIC nIRQ) is group 2 on VIC EXT3. Vol± stay on SoC GPIO and do not need this chip. Tested on iPod nano 7G.
RetailOS programs pads with a word at 0x3CF001E0: (bank << 16) | (pin << 8) | cmd DIN is bank+0x04, DIR is bank+0x14. The 2-line bcm6345 hack on 0x3CF000A4 is not enough for Vol± or for EIC to_irq. apple,skip-sec-pinmux leaves U-Boot/SEC leftovers alone and only GPIOCMDs the IIS0 pair plus GPIO 86. Replaying the full SEC table from Linux broke buttons and USB on glass. gpio-keys-polled on GPIO 40/41 issues mode 0xFFFE and the pads go quiet. This driver polls DIN itself. Tested on iPod nano 7G: Vol+ / Vol- report KEY_VOLUMEUP/DOWN.
N31 IIC1 never sets the +0x20 INT word. Byte done is IICCON bit 4 (S3C IRQPEND). The old ISR read +0x20, returned IRQ_NONE, and left SCL stretched. Clear IRQPEND to resume the bus. Do not treat STAT bit 0 as NAK; that aborted every PMIC@0x73 transfer with -EIO even when VIC 22 fired. On reads, the first IRQPEND is address-complete and DS still holds the address byte. Skip that one, then take one IRQPEND per RX byte. PIO mode stays available but is not required. IIC1 at 0x3C900000 is the PMIC bus. Tested on iPod nano 7G: D1830 @0x73 and LIS3 @0x18 complete.
Home / Sleep / Play are PMIC bits (regs 7 and 8), not SoC GPIO. OSOS unmasks them and uses GPIO 86 as an active-low nIRQ into the EIC. gpio-keys-polled on these lines hammers I2C and was already disabled in the N31 DTS. Register an input device and a threaded nIRQ. Keep a slow poll so a missed EIC edge still shows up. Do not write register 13 at probe. Bit 0 is the power-off latch and it cuts Vbat. The old default rail sequence did that. Rail bring-up stays behind dlg,apply-sec-rails, which N31 must not set. pm_power_off writes reg 13 bit 0. Sleep held ~2s still uses that path. Tested on iPod nano 7G: Home, Sleep, Play, and Sleep-hold poweroff.
The 8702 analog-stage ramp (CTRL1/CTRL2 walk) is for nano 3G. On nano 7G it drops the Lightning link while DWC2 still probes. Windows then sees VID_0000&PID_0002. N31 matches U-Boot s5l87xx-otg-phy: drop D+ (DCTL SFTDISCON), clear PCGCCTL, then PWR/RSTCON/MODE/CLK. Compatible strings apple,s5l87xx-otgphy and apple,s5l8740-otgphy select that path. apple,s5l8702-otgphy keeps the old ramp for N46. Tested on iPod nano 7G: gadget enumerates after DFU.
N46 and N20 keep apple,s5l87xx-usb → dwc2_set_s5l8702_params (slave PIO, session_valid_gintmsk_quirk). N31 uses apple,s5l8740-usb → dwc2_set_s5l87xx_params. That setter follows RetailOS sub_1B543A: NP=32, first dedicated IN capped at 512 words, buffer DMA, GAHBCFG INCR8. The 8702 quirk masks USBRST/EP0 until SessReqInt, which does not arrive after the 87xx PHY reset, so GET_DESCRIPTOR dies as 0000:0002. RNDIS already leaves cdc_filter=0 until SET. Apply the Windows inbox bits: IAD class/subclass/protocol on the control iface (usbccgp / rndiscmp.inf want EF/04/01 there), INIT reports media connected, LINK_SPEED stays non-zero. Tested on Windows 10/11 against 1d6b:0106 from our gadget: Remote NDIS Compatible Device, SSH and telnet to 192.168.7.2.
N31 boots with WTF/U-Boot leftovers. clk_disable_unused then writes SET_TO_DISABLE gates and peripherals drop. Clear the known PWRCON banks and the CG16 enable bits in the divider regs, and mark the published gates CLK_IS_CRITICAL | CLK_IGNORE_UNUSED. Never write CLKCON+0x00/+0x04 (SYS PLL / DRAM) or +0x50 (fatal latch). The ungate walk is limited to samsung,s5l8740 so N46 keeps the old probe. Tested on iPod nano 7G: I2C, GPIO, LCDIF, and DWC2 stay clocked after late init.
Backlight is a separate MMIO block at 0x3E000000. Level is 1..62 at +0x08. This driver does not touch LCDIF CON or PHTIME. TinyDRM was blitting fb->width * fb->height as a linear buffer. fbcon pitch is wider than 240, so the panel tore. Walk each row with pitches[0]/4. Log CON/PHTIME at probe and leave the values U-Boot wrote. Tested on iPod nano 7G: tty0 shell and backlight at 62.
Replace the 2-line bcm6345 GPIO hack with the banked s5l8740 GPIO and the EIC. Home/Sleep/Play come from D1830 nIRQ on GPIO 86; do not poll those bits over I2C. gpio-keys-polled stays disabled so it cannot GPIOCMD 0xFFFE the Vol pads. USB uses apple,s5l8740-usb / apple,s5l8740-otgphy so N31 does not inherit the nano3 PHY ramp or the 8702 DWC2 quirk. syscon-reboot is disabled: 0x100000 arms the watchdog. No I2S, PL080, CS42, or dlg,apply-sec-rails. Those are not ready. Tested on iPod nano 7G with apple_n31_defconfig.
WTF and U-Boot leave CON/CNT armed at 0x3C800000. A bigger zImage loses the race and resets in the middle of decompress or early device probe. Write CON=0 then CNT=0, twice. Never CLKCON+0x50. Tested on iPod nano 7G: kernel #90 reaches tty0.
Land the in-tree module set used on device (#90+): FMSS CS/META weave scan and FTL helper, Nimbus touch, PL080 DMA, CS42L81/I2S audio stubs, Tristar mux, plus DTS/defconfig wiring and RNDIS/gadget tweaks from the lab tree. Keep FMSS/FTL as loadable modules (CONFIG_FMSS_S5L8740=m).
Peripheral does not keep chaining state between operations. IV needs to be set explicitly before CMD_START.
Fail probe if apple,cpu or CS42 node is missing instead of falling back to string names. Codec DAI remains cs42l81-hifi (no COMP_DUMMY).
Match tools/linux-n31/Kconfig: SND_SOC_APPLE_NANO7 selects SND_SOC_APPLE_CS42L81_SPI; CS42 depends on SPI && SND_SOC.
Sustained IIS0/PL080/ALSA playback at 44.1 kHz, CS42 5707D8 path, MikeyBus jack helper, and current FMSS/FTL sources. Analog HP still unproven on UCA222.
Quiet Tristar I2C -110 storm (poll_ms=0, NACK stops poll), DMA-meta storage path with meta_dma_read default off until CS kick is safe, PMIC/CS42 glass fixes, and nodrm DTS.
Replace retired fmss monolith with nand-s5l8740 FIL and multi-object ftl (core/csmap/vecmap) including CXT→BTOC→L2V recover on CS META. Sync MikeyBus, Tristar, Nimbus, and N31 DTS from live glass bring-up.
Storage bring-up milestone for the iPod nano 7 (S5L8740), plus the code
cleanup that should have accompanied the earlier syncs.
FTL / NAND
----------
Use the SFTL context block as the primary map source instead of replaying
every open superblock. On the glass this takes a cold recover from 498 s to
69 s while mapping more of the volume (642652 -> 811554 LBAs), and only 8 of
1803 superblocks then need replaying.
The key finding is that CXT VBAs are not in the address space
whimory_pack_vba() builds. The FTL counts one superblock as the same virtual
block across every (ce, cau) plane, so a superblock holds 2048 VBAs and the
plane index sits between the page and the slot:
vba = vblock * (pages_per_sb * planes * vbas_per_page)
+ page * (planes * vbas_per_page) + plane * vbas_per_page + slot
Untranslated, those VBAs land on unrelated and often erased pages, which is
why the context looked unusable. A run of consecutive CXT VBAs is only
contiguous here within one 4-slot group, so extents are split at plane
boundaries on import. The TREE is also partitioned by logical range across
the context superblocks, and only the oldest carries the BASE marker, so all
of them are merged rather than taking the first that parses.
Two quadratic paths are gone: whimory_range_update() walked the interval map
from rb_first() on every L2V update, and the packed L2V repacked an entire
root per update while collecting a root walked the whole map. A binary-search
lower bound plus deferring the pack until replay finishes took a full
brute-force recover from 1005 s to 504 s with byte-identical output.
Ranges now coalesce across weaves when they are contiguous in both LBA and
VBA, keeping the older weave so a later claim is never wrongly rejected as
stale. That cut 208134 interval nodes to 52352 and is what made a full replay
fit in 55 MiB of RAM.
Recovery is now a small state machine: a rebuild cannot tear down a live map,
and re-binding an already-registered disk no longer leaves the gendisk at
capacity 0 and fails every read.
Diagnostics are quiet by default; diag=1 restores the bring-up dumps.
PMIC
----
Reports that looked like kernel crashes were the d1830 100 ms I2C button
sweep misreading r7 during NAND activity, emitting KEY_POWER from a single
sample, and userspace powering the device off. Buttons are interrupt-driven
by default now (btn_poll_ms=0) with a confirming re-read (btn_confirm_ms).
DMA
---
s5l_pl080_desc_free() ran dma_free_coherent() after an in_atomic() test.
That test cannot see spinlock context on a non-preempt build, and the driver
openly leaked when it guessed wrong. Descriptors holding a coherent LLI block
are now queued to a workqueue; pool-backed ones are still released inline.
Compile-tested only: audio on this board is broken for unrelated reasons and
is the next work item.
Cleanup
-------
Cross-driver declarations were repeated as bare externs in six files and had
begun to disagree. They now live in include/linux/apple-n31.h.
A previous automated pass had mangled comments across these drivers, leaving
fragments such as a comment opening with a bare colon, and continuation lines
unindented at column 0. Twelve were rewritten as prose and roughly a thousand
continuation lines re-aligned; the resulting .ko files are byte-identical, so
that part is comment-only.
checkpatch on the touched files goes from 2 errors and 172 warnings to
0 errors and 142 warnings. What remains is deliberate: split format strings
and deep nesting in the NAND sequencer, both of which need real refactoring;
msleep values that come from hardware timing; and sysfs_emit false positives
where the trailing newline is inside a %s argument.
Verified on hardware: read-only FAT mount with 50 Fxx directories, 496 music
files, zero read misses and zero VFAT bread failures. An fsck of the volume
reports 702 files, 91 directories, 2.9 GB, clean.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Audio, two independent faults either of which is silence on its own. GPIO 7 was never muxed: sub_BCB60 claims both IIS0 pads together on every TX enable and releases both on disable, but pad 7 was being skipped in the belief it drove the display. It does not -- the panel is driven entirely from the LCDIF, no display code touches GPIO, and RetailOS holds pad 7 at function 3 while playing music with the panel lit. And register 0x0227, the live output gain, sat at its bring-up minimum forever: sub_D2C98 encodes code = dB from -50 to +12 and -50 + (dB+50)/2 below that, floor -63 = -76 dB, which is where the bring-up sequence leaves it and matches the -66 dBFS measured on glass. The mapping added here reproduces sub_D2C98 exactly across its whole input range. The write also sign-extended bit 6 into bit 7 instead of masking to seven bits as sub_400330 does, and the mixer control only reached a software PCM scaler that the DMA path never consulted. Volume and mute now drive the hardware as Headphones Playback Volume/Switch. IIS2 folded into s5l8740-i2s. The two ports share the audio clock gate at CLKCON+0x30 and each wrote it directly, so stopping FM capture idled the clock under music that was still playing; a per-port wanted flag now idles it only when neither wants it. IIS2 also gains the pad group it never had: sub_15DD5C claims GPIO 97/98/119 at function 2 on FM power-on beside programming audio device 2 and kicking RXCOM, and releases them on power-off. Those pins had been described as BCM control lines and given to hci_bcm, which drove the capture bus as GPIOs. I2C pads are muxed for the first time. Nothing in this port ever did, which is invisible on a bus the bootloader leaves configured and fatal on one it does not: i2c1 (PMIC) works while i2c0 (Tristar) reads as noise. sub_5714EE gives the per-bus pairs and sub_1860 fixes the indices. FM is named from BlueTool's own FM_RDS_Command definition rather than raw hex, and gains a read path, RSSI, SNR, RDS group decoding for PS, RT, PI and PTY, and raw register plus arbitrary-HCI passthrough for userspace. ROUTE_PCM was only ever read back and never written, leaving the route to the port IIS2 captures wherever the last owner left it. Also: the touch download follows the stock tail and no longer interleaves 24 KB of diagnostic reads into the HBPP sequence; SPI2 engine setup has a single owner again instead of two drivers programming different dividers; the playback DAI link is nonatomic because its trigger reaches the codec over SPI; screen sleep, LCD power cycling and PMU rail arbitration land, with the rail-held mask stopping the global repair from dropping a rail a driver is using -- the real cause of the white screen. None of this is hardware-validated yet. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
A volume that mounted with files missing, then would not mount, then
mounted but could not open files. Six defects, each found by measuring
rather than by reasoning about the previous one.
The VBA space was bank-major -- every (ce, cau, vblock) triple got its own
superblock index -- while the FTL native space puts the plane between the
page and the slot. CXT VBAs had to be translated on the way in, and a run
of consecutive ones was contiguous only within a 4-slot group. The seed
produced 236675 ranges for 938395 LBAs, under four LBAs per range. Matching
the native layout removes the translation and lets extents stay whole:
3137 ranges.
The CXT diff replay skipped every superblock on the volume. The skip tested
page 0's weave -- the oldest page in the block, written when it was opened
-- so a block appended to right up to power loss still tested older than
the checkpoint. It now tests the newest weave, taken from page 127, and
only when page 127 actually supplied one.
The VBA range check bounded by user_blocks, which is blocks_per_cau less
the VFL tail. That is the right bound for what may be allocated and the
wrong one for what a stored VBA may name; it discarded 81 CXT records for
vblocks 1987..1991, around 2000 LBAs. The VFL is an identity map over
blocks_per_cau, so that is the bound.
struct whimory_bte is {weave_seq_add, aux, lba, span} and 185 BTOC pages
carry an eight-byte header before the array. The parser read the header as
a record, got span=1266 against a 508-VBA superblock, broke on record zero
and declared the page unrecognised -- 127 pages and ~2032 LBAs per
superblock, 255 superblocks. That is what left directory sectors unreadable
on a volume whose FAT mounted fine. btoc_pages_valid 308 -> 493.
A BTOC with no BTE array, and a block no rule classified at all, are now
rebuilt from per-page meta instead of dropped. WHIMORY_SB_UNKNOWN had been
defined and never assigned: 78 blocks hit `continue` before nsb++, so they
never entered sbs[] and were never replayed.
Classify reads one 4112-byte record instead of a 16448-byte page. Slot 0
carries the data and meta it inspects, so a block settles there or
escalates; only an erased, zero or unrecognised slot-0 meta needs the full
page, which is ~80 blocks against ~2270. The pass went from ~57s to ~31s.
The FPart scan had never read the NAND. It called page_read with a meta
buffer, which is refused unless meta_dma_read is set -- and that is
deliberately off because a permanent live CS kick reboots the device. Every
read returned -EOPNOTSUPP: 512 reads in 60ms, and "sig=0, not a native
open" was a verdict about a region nobody had looked at. It uses cs_phys
inside a dma_session now, which needed an slc-capable entry point since the
FPart region is SLC and is found by trying plane 1 first.
Measured after: every file on the volume reads with no kernel error.
Playback ran for a few seconds and then took the whole device down rather than just the audio. The error latch was cleared and nothing else happened. The channel stayed enabled, so whatever raised the error raised it again immediately and the handler cleared it again. On a single core with the watchdog disarmed that is an interrupt storm, which from outside is indistinguishable from a lockup -- and it fits a failure that arrives after seconds of working playback rather than at the first period. An error now disables the channel before anything else, ends the descriptor, and counts; after eight the channel is marked stuck and start() refuses it, because restarting a channel that errors every time resumes the storm. The handler also returned IRQ_HANDLED unconditionally, including when neither engine had a bit set. That tells the kernel every interrupt on the line was ours and dealt with, disabling the spurious-interrupt protection that would otherwise notice a stuck line and mask it. It claims only what it serviced now, so a fault costs the audio and not the device. Not proven to be the cause -- the device was down and could not be tested. Both are defects on their own terms, and both turn a recoverable audio fault into an undebuggable one. The cyclic LLI ring was checked and is correct: the last node wraps to lli_phys, and the early-exit case where sg_left runs out before per_period chunks leaves a dangling next-pointer that the fixup after the loop corrects.
…ck gate The codec produced silence. Several separate reasons, all from the bootloader sequence rather than from guesswork about the part. The analog stage is powered by the sequence the bootloader runs, which is what produces the audible plop and 0x2F=0x80 ready=1. An earlier attempt to raise an "analog LDO" through PMIC registers 21-23 was removed: those are decimal 0x14-0x17, and bit 4 is the top bit of a 5-bit voltage field rather than an enable, so it put +400mV on three rails and locked the device twice. The decomp writes (code & 0x1F) only. Jack detect is gone entirely, from both the prepare path and the play latch. This board does not use the CS42 jack detect, and gating playback on it meant the codec was configured and then never allowed to run. Rate handling: an automatic rate path with an SRC for rates the hardware does not take, so an unsupported rate degrades instead of failing. force_stock_audio_parent is off. It clobbered the SoC CLKCON and took the NAND down with it, which read as a storage fault. Stage markers through the graph bring-up (write_table, settle, settled ok, write 0x500, read 0x528, verify) so a hang inside it can be placed rather than inferred, and cancel_delayed_work_sync in the stop path became cancel_delayed_work -- the sync form deadlocked against the work it was waiting for.
bcm2078-bt is built in and probes around t=2.4s; the PMIC driver is a module userspace loads at about t=7.1s. A driver asking for power through a bespoke hook in that window gets -ENODEV and has no way to wait, so the controller simply never came up and hci0 timed out on 0xFC18. The rail is exposed as a regulator instead, with of_match and regulators_node so a device tree node can name it. That makes the kernel do the waiting: devm_regulator_get returns -EPROBE_DEFER until the PMIC registers, and the consumer is re-probed. It does not fit n31_pmu_rails[], which describes single-register LDOs at 0x17..0x21 -- this one is 0x57 bits 7:6 and 0x58 bit 0 plus 6:4 -- so it carries its own descriptor whose enable and disable defer to d1830_bt_rails(). The UART-to-HCI bridge stays dumb: powering the chip belongs to the chip driver, and the bridge only carries bytes. REG_ON on GPIO 97 was never being asserted; gpio_poke covers that. Also here: the PMIC button poll is load-bearing and stays at 100ms. /proc/interrupts has no PMIC nIRQ -- of_irq never maps GPIO 86 -- so the poll is the mechanism, not a redundant product-behaviour poll. Disabling it on that reading broke Home, Sleep and Play.
…s demux The SPI engine has been seen with two different status encodings. The driver waited on the ROS masks and then, on timeout, accepted the other family -- which turns a genuinely stuck transfer into a slow success and hides it. The encoding is a per-instance property now: AUTO latches on the first transfer and only the latched family is consulted afterwards, so a timeout is a timeout. The spin budget came down from 500000, which is less a timeout than a way to make a stuck engine look like a hang; more than one "lockup" chased here was exactly that. Nimbus reads a report whenever ATTN is asserted rather than requiring the ping to have succeeded first, honours the length the firmware declares instead of a fixed size, and burst-transfers rather than byte-banging. Bootloader status words are recognised as such, 0x4f81 among them, so "the application never started" is distinguishable from "the part is not answering at all". MikeyBus was discarding pkt[2], which is the channel -- the remote stream is channel 4 and was being parsed as though it were something else. It is demuxed by channel now and registers an evdev node. The button map is a placeholder and is marked as one in the source. The decomp gives the vocabulary from the handler names -- HandleMikeyCenter, HandleMikeyVolumeUp, HandleMikeyVolumeDown, HandleMikeyAllUp -- and that last one is the useful inference, since an "all up" event means the byte is a bitmask of held buttons rather than a button code. What the decomp does not give is which bit is which, so the ordering is a guess and says so.
gpio-s5l8740 gains the button wiring and a release timer, and keeps the Bluetooth power control that some board variants route through it behind a switch that is off: the VIC routing is known but the EIC level semantics are not, and asserting a rail on a guess about edge polarity is not something to leave enabled by default. The LCD driver takes CLKCON with devm_ioremap rather than devm_ioremap_resource. The region is shared with the clock controller, so requesting it exclusively made whichever driver probed second fail -- which presented as the display working or the clocks working depending on probe order. Tristar carries the OSOS sub_11C8C ID/VBUS/CONDET reading, read-only with the status poll off. The 3.5mm path on this board is CS42 plus MikeyBus and not a Dx write, and the v36 bits that are still unmapped say so rather than being given a plausible meaning. include/linux/apple-n31.h is the interface the PMIC, GPIO, DMA, NAND and touch drivers share; it grows the rail and bring-up entry points the Bluetooth and touch work needed. A note on the canonical copy of these files: the tree under n7-upstream is a build artifact. rebuild-linux-gate0.sh syncs it from tools/linux-n31, so an edit made there is overwritten on the next build and the change appears to have had no effect. This tree and tools/linux-n31 are the sources.
The PMIC node gains a regulators subnode holding the bt rail, and the Bluetooth nodes gain the supply properties that make the ordering work: bt-supply on the companion and vbat-supply on the controller. Without a DT node naming it, the regulator is registered but unreachable, and the consumer has nothing to defer on. panic=-1 became panic=0. Rebooting instantly on panic meant every oops scrolled off with the reboot and the only symptom left was a device that came back up, which cost real time on the scheduling-while-atomic fault in the SPI wait. Halting leaves the trace on the glass. The nodrm variant is resynced against the main DTS; it had drifted far enough that the two described different hardware in places.
The volume mounted its boot sector and then failed every directory walk:
FAT-fs (s5l8740-ftl): error, fat_get_cluster: invalid cluster chain
The wrong BPB candidate was selected and the whole volume sat six sectors
out. n31_validate_fat_critical() checks that its nine critical sectors
*read*; it never looks at what came back, so both candidates scored 9/9:
BPB_CAND freemyipod#1 fmss_lba=49285 weave=..ad crit=9/9 <- selected, newest
BPB_CAND #2 fmss_lba=49279 weave=..ac crit=9/9
49285 - 49279 = 6, and the FAT belongs to the older one. Sector 0 still
read as a valid BPB and sector 1 as valid FSInfo because both are found by
content and are static. Dumping the FAT region showed the tell: a
well-formed FAT32 chain stepping 0x400 entries per 4096-byte sector,
starting six sectors early, so vfat began its walk mid-table.
Every FAT32 opens entry 0 with the media descriptor in the low byte and the
EOC nibbles above it, and entry 1 all ones. n31_fat_first_sector_ok() reads
L->fat_start and checks exactly that, masked to the 28 bits FAT32 entries
carry, against the media byte in the BPB at offset 0x15. It gates both the
"perfect critical set wins immediately" early-out and the scoring below.
bpb_try fmss=49285 crit=9/9 fatsig=0
bpb_try fmss=49279 crit=9/9 fatsig=1
fat_base_lba=49279 valid=1 selected=2
MOUNT_OK -> Apps iPod_Control n31os System Volume Information
496 files under iPod_Control/Music, full-file reads, 0 errors
n31_fat_semantic_validate() is declared and never called, which is why the
bpb log has always printed itunesdb=0 music_dirs=0 -- stale zeros, not
findings. It would have caught this too.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…ON+0x30
s5l8740_audio_clk_set() did
writel(any ? CLKCON_AUDIO_PLAY : CLKCON_AUDIO_IDLE, clkcon + 0x30);
on every play and every stop. Those constants (0x32190 and 0x1c20) are
whole-register snapshots captured from RetailOS at two moments, so each
write imposes the entire captured clock state of the SoC, including
whatever every other peripheral was doing when the snapshot was taken.
CLKCON+0x30 carries gates that have nothing to do with audio.
The NAND controller is one of them. After enough play/stop transitions the
FMSS reads back FMCTRL0=0 NANDSTAT=0, sub_10453C times out on FMCTRL1 bit
30, and Whimory open fails with -110:
s5l8740-nand: 10453C FMCTRL1 bit30 timeout v=00000000
s5l8740-ftl: Whimory open failed (-110) - NOT registering
/dev/s5l8740-ftl (fil=0 sig=0 vfl=0 ftl=0 l2v=0 lba0=0)
The raw CS path still worked, so the NAND itself was fine -- it was the
controller clock, and storage stayed dead until the next boot.
Stock never writes this register wholesale. sub_41CBD8 sets or clears one
bit and preserves the rest. So restrict the write to the bits that differ
between the two snapshots and preserve everything else from the live
register. Confirmed on hardware: FMCTRL0=0x1 NANDSTAT=0x62, FIL_Init OK.
Also in this change:
- sub_B6620(port, 0) is *(base + 8) |= 6, an OR. The DMA arm wrote a bare
0x6 and cleared every other bit in TXCOM. Equivalent while TXCOM reads
0 first, which it does today, but it silently drops any bit something
else sets; the same function ORs 0x6 into RXCOM on the capture side.
- s5l8740_codec_clk_gate(): sub_4F82F8() returns 9 and case 9 of
sub_41CBD8 is CLKCON+0x0C bit 15, active low. D3280(1) drops that gate
and D3280(3) restores it, which is what the codec 0x0006/0x0007 bit-6
freeze latch brackets. Exported for the codec driver, which owns the
state machine but not the CLKCON mapping.
- The txcon module parameter now writes IIS0+0x04 when set. Sweeping it
used to require defeating the "already programmed" check with clkdiv,
which re-ran the pad mux and the CLKCON writes on every play -- which
is how the NAND got gated and how the device wedged mid-stream.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Nine defects on the prepare/play path, found by decoding sub_D3280,
sub_D2F64, sub_D34C0 and sub_183138 properly rather than transcribing the
bootloader, and by checking every register this driver writes against every
write in the RetailOS image.
The state machine was inside out:
- The analog power-up was the bootloader's sub_1310. OSOS has its own,
sub_D3280(1), differing in five registers -- 0x0225 = 0x33 not 0x19,
0x0220 mask 0x78 = 0x78 not 0x50, plus 0x0229 and 0x0075 writes the
bootloader never makes -- and it leaves 0x0007 bit 6 set where the
bootloader leaves it clear.
- States 1 and 3 ran in the wrong order. They are a matched pair around
the codec clock gate: state 1 takes the freeze latch and stops the
clock, state 3 restarts it and releases the latch. Calling state 3
first released a latch nothing had taken, and state 1 then took it with
no one left to release it. 0x0075 bit 7 inverted the same way.
- state_4_output_on() is a power-DOWN and ran as the last step of every
prepare. sub_3C6244 settles it: a dB-code converter, fed codes 0x40 and
0x41, i.e. -90 dB and -76 dB. It also cleared the analog enable state 1
had just polled 0x002F for, dropped the 2v5 rail, wrote 0x0225 = 0x00
over state 1's 0x33, and clobbered every 44.1 kHz stream's SRC pair
with the 48 kHz native values. Renamed and moved to the stop path.
Hardcoded results of computed functions:
- sub_D2F64 is computed. Mode 271 ("on") applied mode 6's ("off") value to
0x000D, and mode 6 drove 0x0006 bit 2 the same way mode 271 does when
stock drives them oppositely.
- sub_D34C0 is a three-way branch on MEMORY[0x892A038], not a sequence.
This ran the sub_183138 body and another arm's tail every time; they
program different blocks for the same job (0x010B/0x010C vs
0x0223/0x0224), and the tail released a hold 183138 had just raised.
183138 ends muted with the 0x0220 bit-5 hold set by design -- sub_D2F64
is what releases it -- so prepare must run set_rate before
output_path_enable, and does.
Work that did not belong on the play path:
- post_iis_start() wrote 0x0229 = 0x41 and 0xC96F = 0x0E, both standby
values from D3280(4). 0x0229 is written by sub_D3280 and nothing else
in the whole image, and 0xC96F = 0x0E is the 2v5 rail *down*.
- asp_lock() re-ran the entire rate programming on every start, and
asp_clock_pulse() ran its 0x0220 bracket backwards so it finished in
idle. Both left the part muted and held at play time.
- Setting the volume forced the mute state and set the graph commit bit.
Stock's volume path is sub_D2C98 then sub_400330: gain and nothing
else. Split into apply_user_vol() and apply_mute().
- The graph is 80 SPI writes and a 100 ms settle -- 118 ms measured -- and
ran inside the transport START callback, between the application asking
for playback and the DMA being kicked. The stream underran and ALSA
restarted it, giving three STARTs in 260 ms. Built in hw_params now;
START went from 118 ms to 2 ms.
Invented code, removed:
- The ASP lock. 0x002F is read exactly once in the whole image (the
readiness poll in sub_D3280(1), testing bit 7) and bit 6 is never
examined anywhere. This was five attempts of eighty polls on bit 6,
with a module parameter to flip a polarity that had never been
established -- up to two seconds per playback start on a bit stock does
not look at.
- cs42_hsdet_pulse(). Credited to "RE D3280(3)/audio_on" and from
neither; nothing in the image writes 0x0073, 0x0079 or 0x0009. It left
0x0009 -- MCLK control -- rewritten rather than restored.
- All codec headset detection, by request: force_headset, jack_poll_ms,
the poll work and its mid-stream 42D364(0) teardown.
Settled and left alone: the IIS0 TX port object sets a3 = 1, so sub_BCB60
takes its a3 != 0 branch and TXCON = 0x03100099, RXCON = 0x1000 and pads at
function 3 are stock-exact -- matching the live pad readback. The static
graph table matches the image position-for-position across all 80 writes.
sub_174E7C's tap formula matches including the +2/+1 and the 160 divisor.
Not yet producing sustained sound: the IIS0 transmitter still takes one
32-byte burst and stalls with STATUS=0x424. Every register value now
matches stock, so what remains is sequence, not content.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Carried along with the storage and audio work of this session; no behavioural change intended beyond what those commits describe. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
ASoC: s5l8740-i2s: a period must fit in one LLI node The re-arm ring is a single self-linked descriptor, so a period has to be expressible as one transfer. The controller counts in transfer units and stops at PL080_MAX_XFER_WORDS of them, which is 8190 bytes at the 16-bit width this link runs. The PCM advertised period_bytes_max=32768 against a fixed 65536 buffer, so a player was free to negotiate 16384; prep_dma_cyclic() then split the period across three nodes and s5l_pl080_rearm_set_ring() refused it, leaving the stream with no periods at all rather than degrading. Export the per-node limit from the DMA driver and constrain SNDRV_PCM_HW_PARAM_PERIOD_BYTES to it. With the 65536 buffer and integer periods this lands on 4096 bytes and 16 periods. Measured at the analog jack: 1 kHz at 44.1 kHz, -12.23 dBFS at volume 81, steady across every window, 0 underruns. misc: ftl-s5l8740: expose the SysCfg section, not just the decoded tags The parser understood eleven tags and the rest of the section was unreachable. Keep the section verbatim and index every 4-byte-aligned printable group, then publish syscfg, syscfg_raw and touch_cal. A printable group is reported as a candidate rather than a tag: ASCII values match the same test, so known marks the ones the decode already consumes. Rename the touch calibration blob off the isys name across the FTL and the DT, including the last two exported symbols. The bootloader now publishes apple,n31-touch_cal-addr/-size and apple-grape reads the 0x560-byte container from it. n31: per-operation tracing goes behind a runtime flag The pinmux dump ran nine times per hw_params at err level, where no console loglevel could hide it; the re-arm path traced every period for the life of a stream; the trigger path emitted eight unconditional backtraces. Move per-operation tracing to dev_dbg, route the ungated NAND traces through the quiet gate the file already defines, and put dump_stack() behind trace_trigger since dynamic debug cannot gate it. One stream drops from a continuous few lines a second to 21. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
A superblock is not always four banks wide, and this driver assumed it was.
whimory_pack_vba() and whimory_unpack_vba() split an address with
planes = num_ce * num_cau, fixed at four. s_vfl.c does not: sub_4EAD34
builds an address as
vba = (vbn * max_banks * pages_per_sb
+ page * nbanks + bank_ofs) * vbas_per_page + slot
with nbanks = sub_3D1438(vbn), the popcount of that VBN's row in the VFL
bank bitmap, and sub_4EAE40 takes it apart the same way. A superblock
therefore holds nbanks * pages_per_sb * vbas_per_page addresses -- that is
sub_4EFE0C, and it is the bound s_cxt_load.c walks a checkpoint to. Only
the per-VBN stride is constant, at max_banks * pages_per_sb *
vbas_per_page.
Our formula is that arithmetic with nbanks pinned at four, so it is right
for a full superblock and wrong for every other kind. On this unit:
SFTL sb banks 1:1 2:7 3:18 4:1992 (known=2018 partial=26 of 2088)
Twenty-six superblocks decoded to the wrong bank on the wrong page. The
symptom was scattered "sftl lba mismatch" and "read miss" against pages
that had held their contents since long before the checkpoint -- so not
staleness, which is where this had been looked for before.
Measured directly before any of this was written. Reading LBAs 477205..
477260 through the map, where the checkpoint says lba 477213..477243 sits
at vba 0xa0000 span 31:
477213..477220 -> banks 0,1 of page 0 never written
477221..477228 -> banks 2,3 of page 0 hold 477213..477220
477229..477236 -> banks 0,1 of page 1 never written
477237..477243 -> banks 2,3 of page 1 hold 477221..477227
Virtual block 320 spans two banks and packs eight addresses per page, not
sixteen. The three-bank case checks out too, with no free parameters: for
vblock 1818 the physical page at bank 1, page 80, slot 1 is offset
80*3+1 = 241, so vba 1818*2048 + 241*4 + 1 = 3724229 -- which is exactly
the address the checkpoint gives the LBA that page turned out to hold.
The bank map is built during classify from which banks carry a real record
at page 0, and costs no extra I/O. It is sized by blocks_per_cau rather
than user_blocks and the batched pre-pass now sweeps that far, because the
FTL does put data above user_blocks: virtual block 2048 is a three-bank
superblock and was the last mismatch standing. A virtual block with no
entry decodes as all banks, which is what this driver did everywhere
before, so an unscanned region is no worse off.
The checkpoint walk is bounded by the same count now instead of a fixed
2048. It happens to be a four-bank superblock here, so the two agree --
they agree by luck, and each record declares the LBA it continues from, so
reading them in the wrong order does not fail safely.
Also the weave fast-forward, which had never been implemented. s_cxt.c:81
sets
sftl.write.weaveSeq = baseWeaveSeq + save.num_sb * s_g_vbas_per_sb + 1
so weaves between the base and there belong to the checkpoint's own pages
and everything written afterwards is at or above it. The diff skip was
drawing its line at the base, which put the checkpoint's own write span on
the post-checkpoint side. num_sb comes from the BASE payload, whose first
word s_cxt_save.c writes for exactly this. Nothing changes on a cleanly
shut down volume, which is why it had gone unnoticed; on an unclean one it
was replaying superblocks the checkpoint already described.
A checkpoint offset at or past its superblock's end now raises
CXT_BANK_SHORT and is counted. It costs nothing, and it is the one symptom
that separates a bad bank derivation from a bad checkpoint. Zero across
all 6277 extents here, which is what makes the derivation evidence rather
than a guess.
Diagnostics, all of which were consulted while chasing this and all of
which were lying:
s_g_vba_to_sb() and s_g_vba_to_ofs() divided by vbas_per_sb, the
per-bank count, while addresses stride the per-VBN one -- every
VBA_DIAG line named a superblock four times too high.
ftl_cxt_dump walked bank-major while the loader walks native, and
clamped to a quarter of the block. It uses the loader's walker now. The
bank-major walker is deleted rather than left for someone to reach for.
whimory_vba_describe() prints nbanks. Two superblocks with the same
vblk/pg/slot are different physical pages when their bank counts
differ, and nothing said so.
whimory_vfl_banks_in_vbn() is documented for what it is: CAU
substitution, over num_cau entries, with a stub bitmap that is
allocated all-ones and never read from flash. It is not superblock bank
membership and reading it as if it were is part of how this lasted.
Left alone deliberately: the BTOC parsers convert their offsets with
ofs / vbas_per_page, which is the retired per-bank reading -- s_btoc.c:230
asserts the offsets are whole-superblock. It is not a mapping error,
because the BTE is only a page hint and whimory_btoc_confirm_page() takes
each slot's own metadata as authority; the cost is coverage. Widening it
changes which pages the brute-force fallback reads, and that path is the
oracle the CXT path is checked against, so it should not move in the same
change as the thing it verifies. There is a comment saying so.
Verified on the glass, iPod nano 7, 15.9 GB PPN NAND, read-only mount:
LBA window 477205..477260 44 of 56 failed -> 3, all of them the
checkpoint's own declared hole
the seven known-bad LBAs all failed -> all read
strided sweep, stride 1024 3890 reads, 0 mismatches
strided sweep, stride 256 15558 reads, 0 mismatches, 0 read
misses, 0 CXT_BANK_SHORT
ls -R over the whole tree 1435 entries, 0 errors
every music file and app 517 files read end to end, 0 failures
ENOENT on the sweeps is 2.17 percent against 2.19 predicted from the
checkpoint's own 10581 hole LBAs plus a 76454-LBA unmapped tail, so every
unreadable LBA is one the checkpoint says is unmapped.
Recovery stays on the fast path at about seven seconds; cxt_meta_confirm,
scan_blocks and diag are untouched and still off.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…he VFL context
Two changes, both about the same thing: the fast path was already the
default, and everything slow around it was reachable by accident.
--- 1. Refuse the full replay when the checkpoint will not load
The fast path was already the default -- use_cxt and cxt_fast both default
Y, and a boot log confirms it runs: classify at t=30.8, map ready at t=37.9,
seven seconds. What was not guarded is what happens when it does not run.
Every skip test in the diff replay is conditioned on s->cxt_loaded. When
whimory_cxt_fast_load() fails, that stays false, nothing is skipped, and all
7623 superblocks are replayed from per-page metadata. That is the
eight-minute path, and it was announced with a dev_info -- "CXT seed failed;
falling back to full replay" -- in the middle of a boot log nobody is
reading at the time. From outside, the device simply took eight minutes to
mount, occasionally, for no visible reason.
So: require_cxt, default Y. A checkpoint that will not load is now an error
at error level and the recover stops there, naming the superblock count it
refused to replay and the two parameters that ask for it deliberately.
The full replay is not going away. It is built from per-page metadata rather
than from any index, which makes it the oracle a suspect checkpoint gets
checked against, and it is the thing to reach for when the checkpoint decode
is under suspicion -- both of which were needed to find the bank-count bug.
It is just not a boot path, and it should not be reachable by accident.
Measured, with the checkpoint deliberately hidden (scan_blocks=100, so
classify never reaches the block that holds it):
SFTL classified nsb=379 closed=95 open=281 cxt=0 empty=21 unknown=3
CXT fast path unavailable (-2); full replay
CXT fast path failed (-2) and require_cxt is set: refusing to
full-replay 379 superblocks. Set require_cxt=0 (or use_cxt=0) to
rebuild from media.
recover returns the CXT's own -ENOENT in about a third of a second rather
than replaying; the sysfs write reports it as "No such file or directory".
With the checkpoint present the recover is unchanged: same map, same
--- 2. Read the VFL context where FPart says it is
VFL_Open was seventeen seconds of a forty-second mount, and it was looking
for something it had already been told the address of.
It swept the whole system area: every block from blocks_per_cau - vfl_tail
upwards, eight pages each, on all four banks. 4096 page reads. On this unit
it found the context at bank 1 block 2084 after eight and a half seconds and
then kept reading for another eight and a half, because the loop had no
reason to stop -- there is nothing in it that knows what it is looking for
beyond "feed every page to the ingest and see what sticks".
Stock does not do this. s_fpart keeps a directory of special-object copies,
six bytes of {bank, block, type} per entry indexed by type, and sub_4F12DC
walks only the copies of the type it was asked for, taking the highest
generation. Our fpart_locate_special() builds that same directory --
struct fpart_special_entry is the same six bytes -- and
fpart_read_special_by_index() is a transcription of that walk. The FTL has
had both since the FPart signature work; VFL_Open just never used them, and
went off to rediscover 0xc104 by hand.
It reads the directory entries now. The scan behind the directory has
already run for the signature by the time VFL_Open is called, so the lookup
is a cache hit, and the table holds five entries on this unit -- five page
reads against 4096. The objects sit at page 0 of their block, chunk 0 with
the payload at FPART_SPECIAL_HDR, which is where the sweep was finding them
anyway, so n31_vfl_ingest_ctx() does the parsing and the mirror comparison
completely unchanged.
Measured on the device, same NAND, back to back:
before after
VFL_Open 17.0 s 0.37 s
whole recover 40 s 23 s
and the context it loads is identical:
VFL ctx ce=0 cau=1 blk=2084 type=0xc104 accepted: 29/2088 blocks bad
VFL_Open OK remap_ents=2088 bitmap=1 bad_blocks=29 src_bank=1
mirror_diff=0
ctx_hits is the one number that moves, 16 to 2, and that is the point: two
copies of one object rather than sixteen pages that happened to match.
Map correctness unaffected, checked the same way as the bank-count fix --
15558 strided reads across the whole logical space, 0 mismatches, 0 read
misses, 337 ENOENT, which is the checkpoint's own hole and tail count to
within a rounding error.
What is left of the mount is 6.5 s of FPart directory scan, 5.7 s of
classify, and about 4 s of checkpoint load, replay and bind. The FPart scan
reads 512 pages to build a five-entry table and is the obvious next one, but
it is the scan that builds the directory this change depends on, so it
should be looked at on its own.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Every switch that selects the fast path was already at its fast value -- use_cxt, cxt_fast, stock_scan, batch_classify, fast_empty_probe, btoc_page_lazy all default Y, scan_blocks 0, diag N. What was still on by default was work that only diagnostics consume, and it was costing real seconds on every mount. meta0_scan_sbs defaulted to 4. That full-scans four closed superblocks after the map is already built, pages 0 to 126, and hands every page to whimory_note_meta0() -- which increments meta0_hits and, unless diag is set, does nothing else at all. 508 page reads to produce one number in RECOVERY_STATS. Measured across the L2V-to-bind window, 0.81 s against 0.0015 s with it off. Now 0; set it back to 4 when the question "does anything on this volume carry meta lba 0" comes up again. The FPart scan's per-page and per-slot dumps were unconditional. FPART_META_SAMPLE, FPART_SLOTS and FPART_ASSIGN_SCAN are 112-byte hex dumps and the console timestamps put each one at 85 to 95 ms; two dozen of them across a scan is about two seconds of mount spent describing pages the same scan already summarises in FPART_SPECIAL_TYPE and the meta histogram. They are behind diag now. So are the twelve per-hole CXT_HOLE lines, which answer "is the translation ceiling wrong" -- a question you ask once -- where CXT_MAP already reports holes= and hole_lbas= every time. Nothing that says what happened is gone. FIL_Init, the signature, VFL_Open, the classify summary and its two type histograms, the bank histogram, CXT_MAP, CXT_WEAVE, the diff replay line and the recovery stats all still print unconditionally, which is 101 lines for a whole recover. Three consecutive recovers now, wall clock, sysfs write to return: 15 s, 15 s, 15 s against 16 s and 23 s before, where the spread was the hex dumps firing on a fresh insmod. Phase breakdown of the 15: NAND hw_init + geometry 5.9 s s5l8740_nand_hw_init(), FIL side FPart directory scan 2.2 s 512 reads, builds the object table VFL_Open 0.07 s classify 5.4 s 7623 superblocks, batched meta CXT load and seed 1.1 s diff replay and L2V pack 0.5 s bind 0.001 s The largest item left is not in this driver: s5l8740_nand_hw_init() is 5.9 seconds of the 15 and lives in nand-s5l8740.c. That is the next one to look at, and it should be looked at there. Map unchanged and re-verified the same way: 15558 strided reads across the whole logical space, 0 mismatches, 0 read misses, 0 CXT_BANK_SHORT, 337 ENOENT -- the checkpoint's own holes and unmapped tail. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Four things that had been left, and a correction: the five seconds I blamed
on the NAND controller are not there at all.
--- 1. The BTOC parsers walk the whole superblock
The offsets a BTOC carries are whole-superblock VBA offsets -- s_btoc.c:230
asserts vba == s_g_addr_to_vba(wr->sb, wr->nextVbaOfs) -- and the three
parsers turned one into a page with ofs / vbas_per_page while keeping the
caller's (ce, cau). That is the retired per-bank reading twice over: it
names a page nbanks times too early and pins it to whichever plane's page
127 happened to carry the BTOC.
Never a mapping error, because none of these parsers maps anything -- the
BTE only chooses which pages to visit, and whimory_btoc_confirm_page()
applies each slot from that slot's own metadata. It was a coverage error,
and the bounds made it worse: WHIMORY_VBAS_PER_SB and
WHIMORY_DATA_VBAS_PER_SB are per-bank counts, so the walk stopped a quarter
of the way into a four-bank superblock.
whimory_btoc_ofs_to_page() builds the VBA and unpacks it, the same
arithmetic as everything else, and the bounds come from
whimory_sb_vbas()/whimory_sb_data_vbas(). The dedup key is (bank, page)
rather than page, since the same page number on two banks is two pages.
A/B on the same superblock, forced through replay with
force_replay_vblock=100:
before after
btoc_entries_seen 510 2034
which is the full 2032 data addresses instead of 508. Everything else is
identical -- pages_read 1, pages_valid 0, l2v_updates 0, unclaimed 1, the
unknown fallback unchanged at sbs=78 pages=182.
--- 2. The five seconds are in the bind, not in the NAND driver
I said s5l8740_nand_hw_init() was 5.9 s of a 15 s recover. It is 60 ms.
With the NAND driver's own logging on, the whole of fmss_nand_reset() plus
the parameter-page read runs 7701.847 to 7701.908.
The five seconds are after "CS recover OK", in n31_ftl_cs_bind_whimory():
it probes three hardcoded LBAs, and then walks the interval map reading up
to max_try=512 mapped LBAs asking each whether it looks like a boot sector.
On this unit the hardcoded probes have already found both candidates before
the walk starts, so all 512 reads are spent confirming what is known.
The media states the answer. fmss_lba 0 is an MBR:
0x1BE: 80 10 0e 03 | 0c | 26 3d f3 | 40 c0 00 00 | 8a da 3a 00
type 0x0c, start LBA 0x0000c040 = 49216. That sector holds an "MSDOS5.0"
BPB. The volume this driver mounts is 63 sectors further on at 49279 -- the
classic CHS-aligned track offset, recorded as hidden=63 in 49279's own BPB
-- with its backup boot sector at +6 = 49285, exactly as that BPB says.
So n31_bpb_partition_lbas() reads the partition table and offers the
partition start, whatever a base BPB's hidden count redirects to, and a
96-sector window over each: enough for a volume starting at the partition
sector and for one starting a track in, plus a full reserved area either
way. 96 LBAs instead of 512, derived instead of assumed, and the walk is
skipped once there is a candidate to select among.
A/B, same NAND, back to back, bpb_probe_partition=0 against the default:
forced walk 14 s fat_base_lba=49279 crit=9/9 candidates=2
partition probe 9 s fat_base_lba=49279 crit=9/9 candidates=2
with the bind itself going 5.26 s to 0.33 s. bpb_probe_partition=0 now
genuinely forces the old path, so the two are comparable on demand.
--- 3. Both frame-size warnings
whimory_cxt_build_from_sb() carried u32 tag_hist[256], a kilobyte of stack
for a histogram over tags that are 1..6 or CLEAN; it is eight entries and a
counter for anything else now. fpart_scan_region() carried u32 hist[256] on
the stack; it is kcalloc'd, freed on all four exits. 1368 and 1224 bytes
respectively, both now under the limit, and the build is warning-free.
--- 4. The bank map, checked against the VFL table
This was to turn corroboration into verification by loading the
authoritative per-VBN bank table. It is not there to load, and the check
that was supposed to use it is what proves that.
The 0xc104 object, dumped: object_len 0x800 at +0x24, generation 29 at
+0x28, payload at +0x80, every payload row 0xff except the first two, and
those two hold exactly 29 clear bits -- the 29 of "accepted: 29/2088 blocks
bad". So it is a bitmap, one bit per block, padded, and nothing else. All
29 sit in blocks 0..31, while the narrow superblocks measured here are
vblocks 320, 1259, 1818 and 2048. Different things entirely: a superblock
can be narrower than four banks without any of its blocks being
factory-bad.
The remaining assumption was the bitmap's scope -- that it describes the
blocks of the bank it was found on. The check tests exactly that, and it
fails:
SFTL sb banks vs VFL bitmap: checked=2018 flagged_bad=28
still_list_bank1=28 (29 bad recorded)
SFTL the 0xc104 bitmap is not bank 1's block list -- do not read it as one
28 of the blocks it calls bad are in active use on the bank it supposedly
describes. So the per-VBN bank table (s_vfl.c sub_3D1438, one stride-sized
row per VBN) has not been located on media, and 0xc104 is not it. The map
built from classify stands on its own evidence -- every one of the 6277
checkpoint extents fits inside the width it derives, CXT_BANK_SHORT=0 --
and the next person is told in the log not to read this object as a bank
list.
Recover is 9 s, down from 15. Map re-verified the same way: 15558 strided
reads across the whole logical space, 0 mismatches, 0 read misses, 337
ENOENT, which is the checkpoint's own holes and unmapped tail.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Five changes from the ipod tree, all storage, all measured on an iPod nano 7 whose volume RetailOS had left dirty. --- 1. Scan every superblock stock scans (ipod d777883a) A boot came up with no volume: cxt=0, and no 0x1f anywhere in a histogram covering all 7840 blocks classify looked at. The checkpoint had moved into the 128 blocks above user_blocks that classify never enumerated. Stock has no such bound -- s_cxt_diff.c walks sb from 0 to s_g_max_sb -- and the tail is not spare: a raw scan of blocks 1960..1991 on one plane returns 16384 valid data records, and this volume's checkpoint sits at vblock 2005. Three places strided by user_blocks where the block count belongs: classify's bound, whimory_sb_index() (so vblocks from 1960 up aliased onto the next plane and the checkpoint was found but walked at the wrong virtual block, cxt=4 then -ENODATA), and n31_vfl_get_param(NUM_SB), which put sbs[] back to 7840 right after it had been sized correctly. Also: max_open_sbs 4096 -> 8192, and allow_short_map (default N) so a rebuild that drops superblocks or hits the range ceiling fails instead of returning a map it knows is incomplete. This unit has 6029 open superblocks, so the old cap silently produced a map missing 47 per cent of the volume -- and that map was then used to reason about the checkpoint. --- 2. Parse the BTOC slot the BTOC is in (ipod 7f100959) btoc_pages_valid had been 0 on every run this driver ever made. A 16 KiB page is four slots and only one is the table of contents -- the last VBA the superblock used, so slot (used-1) % 4. classify always had this right; whimory_ingest_btoc_page() was handed the page base, so all three parsers were validating slot 0's user data as a BTE array. 0 -> 75 valid, 95716 L2V updates that were being discarded. --- 3. Settle what a superblock is (ipod 092dbbec) Counting BTOCs per virtual block: 2010 carry exactly one, none carry three or four. A superblock is a virtual block striped across its banks, not a (ce, cau, vblock) plane-block, so of 6029 "open superblocks" about two are open and the rest are the non-BTOC banks of closed ones. WHIMORY_SB_MEMBER and sb_fold implement the fold. It halves the recover, 126 s -> 60 s, at an identical mapped_lbas and with range_nodes dropping 15419 -> 5927 -- and takes a 15558-read sweep from 69 wrong to 266. It fails its acceptance gate and ships off. docs/n7g-storage/SFTL-SUPERBLOCK-MODEL.md carries the specification, the evidence and the gates. --- 4 and 5. The mismatches (ipod 40d7d6e8, d1c30704) 69 wrong mappings on the dirty volume are stale checkpoint extents: 157 of 5828 name pages the allocator recycled after the snapshot. whimory_cxt_extent_confirmed() reads each extent's page before seeding and whimory_cxt_extent_repair() re-derives the failures from per-page metadata. That takes the sweep to zero. It costs a page read per extent, 126 s -> 191 s, so cxt_confirm_auto (default Y) enables it only when a superblock is bounded by a weave at or above the checkpoint's own write span. The count taken before seeding and the count the diff reports afterwards are the same number, 362, which is what makes the cheap gate safe to stand on. The decisive check is the volume state: after RetailOS shut down cleanly and wrote a fresh checkpoint, the same sweep returns MISMATCH=0 with confirmation not firing. A decode fault would not care how the volume was shut down. TAG_SB is parsed now, and its `used` field is the superblock's write cursor -- not, as an earlier revision of the design note claimed, its capacity. It still bounds the bank count from below, and that check reports under_counted=0 across 2013 superblocks. The confirm path also records the failing page's weave against the checkpoint's, so "genuinely stale" and "decoded wrong" are distinguishable from a log line rather than from argument. --- verification, clean volume, shipped defaults recover 9 s, confirm=0 strided sweep, 15558 reads MISMATCH=0 MISS=0, unmapped 315 ls -R over the whole tree 1540 entries, 0 errors every music file and app 520 files read end to end, 0 failures boot to mounted volume 21.9 s Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The divisor register on this UART carries two fields. The low bits are the
divisor; bits 19:16 hold 16 minus the oversampling ratio, and that ratio is
programmable from 16 down to 4. We were writing clk/baud into it and leaving
the ratio field zero, so every port on the SoC ran 16 times slow: 0xd0 for a
requested 115200 is 24e6/(16*209), about 7200 baud.
The console was the visible half. printk blocked on a line moving a couple of
hundred bytes a second, which is why booting crawled, and a capture at 115200
saw framing noise rather than text. The two ports behind serdev were the
quiet half: MikeyBus and the Broadcom HCI both sat at the same 0xd0 while
their far ends listened at 115200.
RetailOS sub_1E10 does not divide. It walks the ratios from 16 down, keeps
whichever leaves the divisor nearest a whole number, and packs the two
together:
round((clk / i) / baud - 1) | ((16 - i) << 16)
which is what this now implements. The earlier reading of that function as
clk/115200 is where the wrong value came from, and the 10 that reading
attributed to 2400000 is the ratio, not the divisor -- 2.4 Mbaud is exact at
i=10 on a 24 MHz part, which is why the controller is moved there.
No clock change is involved. 24 MHz was always right.
Measured on an N31 over a DCSD serial capture. Divisors now match the
algorithm exactly at every rate stty asks for:
9600 0x000600f9 i=10 div=249 9600.0 exact
19200 0x0006007c i=10 div=124 19200.0 exact
115200 0x0000000c i=16 div=12 115384.6 +0.16%
230400 0x00030007 i=13 div=7 230769.2 +0.16%
and 0x0000000c and 0x00080019, the same rate reached at two ratios, both put
the line on 115200. Worst case across the table is 0.16%. The full kernel log
now reaches the wire -- 622 lines to t=100s, 100% printable, where it
previously stopped at t=0.017 and dissolved into noise.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
(cherry picked from commit 03717467bd4d8c94ede6667c6d42a76b30103f1f)
max-speed in the device tree becomes oper_speed, and bcm_open hands that to
serdev, which applies it before setup() ever runs. So the first thing the
controller sees is the vendor baud command:
hci0: command 0xfc18 tx timeout
hci0: BCM: failed to write update baudrate (-110)
hci0: BCM: Reset failed (-110)
Both timeouts are on 0xfc18. HCI_Reset is never sent at all -- 0x0c03 does
not appear in the log -- because btbcm_initialize() only runs after the
speed change has already failed.
Stock does it the other way around. sphwBluetooth_Init opens the port at
115200 and sends three commands, whose payloads sit at 0x877810c in the
image:
01 03 0c 00 HCI_Reset
01 01 10 00 Read_Local_Version_Information
01 09 10 00 Read_BD_ADDR
and only then the ten-byte 0xfc18 that moves the link to 2400000. That is
the same order btbcm_initialize() uses, so all this needs is for the early
speed change to be held back, which no_early_set_baudrate already does for
bcm4354.
brcm,bcm4329-bt carries no device data, so the flag was never reachable from
our compatible. Add data against brcm,bcm2078 and let the device tree name
that first, since of_match scores by position in the device's own compatible
list and bcm4329-bt would otherwise win with none.
Whether the part then answers is a separate question -- it has never answered
anything. But a timeout on 0x0c03 rather than 0xfc18 would at least be the
controller declining a reset it was actually sent.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
(cherry picked from commit 69d0141c094f45986997e791e9ae14d6ba6979b6)
Brings this branch level with the sources the device is built from. --- storage BTOC entries are applied from their own lba, span and vba, the way s_cxt_diff.c does it: sub_3F8958 is L2V_Update.c, splitting the span on 0x8000 boundaries and updating the map in memory without reading anything. We had been CS-reading every data page a span covered to recover meta_lba and using that as the key -- about 512 page reads for a full superblock, to rediscover the number the entry already carried. btoc_trust_bte=0 restores the old path. An entry's weave is its superblock's plus its own weaveSeqAdd, so entries in one BTOC no longer share a weave, and the checkpoint base test runs per entry where s_cxt_diff.c puts it. The classify scan reads 256 superblocks per sequencer kick rather than 16. Stock bounds its window at 0x100 and FMSS_DMA_BATCH_MAX is 256, so the controller was being asked for a sixteenth of what it takes: 507 kicks against stock's 32. --- audio Per-transfer logging is dev_dbg unless asked for. The console is on a 115200 UART, printk to it is synchronous, and the PL080 start dump alone is about fourteen milliseconds on the wire -- inside the window that re-arms audio DMA. The re-arm missed, underran, and logged about it, and playback start-stopped roughly twice a second until printk's rate limiter silenced the messages. Probe, removal and anything at warning or above are unchanged. A rate change no longer re-runs the codec bring-up. cs42_set_rate() writes the rate code and nothing else, so a codec already up takes that path with the headphone hold raised across the write. --- bluetooth The power-on follows sphwBluetooth_Init as disassembled rather than as decompiled: the export drops the delay argument the machine code loads, and stock writes pad 70 a second time. The variant byte at 0x8925CAC is 0x05 in this image, so the pad-200 arm is not reachable on this part. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Confirming every checkpoint extent against the page it names was a
workaround for trusting the checkpoint too far. While the diff skipped
everything below the checkpoint's top weave, writes made between the base
and the top were never replayed, and reading each extent back was the only
thing catching the stale mappings that left.
The diff replays from the base now. Measured on a volume with 47 superblocks
newer than its checkpoint, by re-running recovery on the live device with the
parameter both ways:
confirm on confirm off
recovery ~40s 19.0s
mapped_lbas 3901293 3901881
directory entries 1656 1656
files read 119/120 119/120
It maps more, not less. Boot to mount went from 53s to 32.5s, and the file
that fails either way is RadioBuffer, whose clusters were never written.
WHIMORY_PF_SLOTS goes back to 16. Widening it was aimed at the wrong path:
classify runs through whimory_scan_plane_meta(), which already kicks
S5L8740_NAND_BATCH_MAX / 2 blocks and reads two meta records for each,
filling the controller's 256-descriptor batch exactly as stock's scan does.
"batch prefetch served 0 page-0 reads in 0 kicks" is the legacy per-block
path reporting that nothing uses it, which I read as a failure. At 256 it was
a megabyte of buffer for a fallback nobody takes.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Classify enumerates planes. A superblock is a virtual block striped over its
banks and carries exactly one BTOC, written to the last VBA it used on
whichever bank that landed on, so the bank holding it comes back closed and
the other three come back looking open.
On this volume that read as 2029 closed and 6001 open, on a disk 91% full.
There cannot be six thousand partially written superblocks; 2029 x 3 is 6087,
and the shortfall is the superblocks that are not four banks wide. Folding
them gives 8 open, which is what a volume looks like.
The fold has been here and switched off, because it made a read sweep worse.
That measurement was taken while two other faults were live, and the open
rebuild it removes was compensating for both: the diff skipped everything
below the checkpoint's top weave instead of its base, and BTOC entries were
addressed through a bank-major helper that pinned them to one plane. With
those fixed the measurement inverts:
fold off fold on
classified open 6001 8
recovery 19.0s 12.1s
lba mismatches 10 0
files read 159/160 159/160
mapped_lbas 3901881 3901881
Verified on a clean boot with the shipped defaults: mount at 23.4s, 1676
directory entries, 169 of 170 files read, no lba mismatch and no FAT-fs error
at all. The one file that fails is RadioBuffer, whose clusters were never
written.
It also answers why a cleanly ejected volume looked dirty: 47 superblocks
appeared newer than the checkpoint and 143 went through diff replay on a
volume that should have replayed nothing. Most were closed superblocks
wearing an open plane's clothes.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
N31 peripherals that run on a real nano 7G with single commit per device, on top of
s5l87xx.Home, Sleep, Play, Vol±, the panel, backlight, I2C, and Windows RNDIS all work on glass. Kernel
6.14.0 #90.What we tested
zImage:ramdisk:dtb) → tty0#shell0xFFFEand the pads go quietpm_power_off(D1830 reg 13 bit 0)1d6b:0106. SSH and telnet to192.168.7.2I2C completion on this SoC is IICCON bit 4 (IRQPEND), not the
+0x20INT word. STAT bit 0 is not NAK here; treating it as NAK broke PMIC@0x73.What we left out
I2S / CS42 PCM is not in this series. We can program the analog path. The SoC serializer still does not drain the FIFO, so there is nothing useful to review yet.
Also out: PL080 tone, Nimbus, BCM2078, Tristar-as-working, LIS3 “fixes”, and
dlg,apply-sec-rails. That last one writes the power-off latch at probe.Other iPods
N46 and N20 still bind
apple,s5l87xx-usbtodwc2_set_s5l8702_params(slave PIO, session-valid GINTMSK quirk). N31 usesapple,s5l8740-usb/apple,s5l8740-otgphyso it does not inherit the nano3 PHY ramp or that quirk.Clock ungate is limited to
samsung,s5l8740. We do not write CLKCON+0x00/+0x04(SYS / DRAM) or+0x50(fatal latch).Commits
Happy to split any of these into their own PRs if that is easier to review.