Документация написана на английском.
Driver review: camera, haptics, torch
Scope and method
Read-only audit. Files read in full unless noted.
| File | Lines | Read |
|---|---|---|
| drivers/camera/hakios.torch.c | 763 | all |
| drivers/haptic/hakios.haptic.c | 1005 | all |
| drivers/camera/hakios.camera.c | 4264 | all except 1568-1750 only skimmed (read-only RPMh/SMMU/PLL printers) |
| xnu/files/bsd/sys/hakios.camera.h | - | all (user ABI) |
| userland/src/camera_device.c (1280-1420), camera_isp.c (840-1033) | - | the parts that talk to the kernel node and parse the .isp file |
| tools/camsensor.py, tools/camtuning.py | - | header comments and pack lines only (to learn the on-disk format) |
hakios.buttons.c (spmi lock), loader_return.c (quiesce call site), hakios.wifi.c (hakios_wifi_file) |
- | the few lines needed |
Method: traced every user-reachable entry (torch write/read, haptic write, camera open/close/read/write; none of the three nodes has an ioctl, all use eno_ioctl/eno_mmap) end to end; followed every table-derived length and count; checked every polling loop for a bound; checked lock use between the service thread and the file operations; checked each boot-arg. Nothing was built or run. No hardware facts are asserted: where an outcome depends on how the chip behaves it is marked NEEDS-HARDWARE.
Findings
| id | severity | confidence | file:line | one line |
|---|---|---|---|---|
| CAM-1 | high | CONFIRMED | hakios.camera.c:3873-3884 vs 3363-3376, 3401-3425 | camdev_read copies from ring with no reference held; the capture thread frees ring under it (use-after-free read, or panic in dc civac) |
| CAM-2 | medium | CONFIRMED | hakios.camera.c:3969-3978, 4019-4035, 3379-3384, 3500-3503, 4000 | any process (node is 0666) picks sensor and any table mode, so can force a huge kernel allocation or a permanent camera shutdown |
| TORCH-1 | medium | CONFIRMED (code path); effect NEEDS-HARDWARE | hakios.torch.c:330, 506-511, 605-633 | the "light off" path is blocked by the sticky broken flag, so after one failed write the LED stays on with no monitoring and no timer |
| TORCH-2 | medium | CONFIRMED (code path); effect NEEDS-HARDWARE | hakios.torch.c:487-499, 697-717, 632, 750 | lit is set only after all enabling writes succeed; a write that lands but fails read-back leaves the LED possibly on with lit == false, so nothing turns it off |
| HAPTIC-1 | medium | CONFIRMED (code path); effect NEEDS-HARDWARE | hakios.haptic.c:961-971, 570-580 | haptic=5 starts serving the motor even when the waveform file did not load |
| CAM-3 | low | PLAUSIBLE / NEEDS-HARDWARE | hakios.camera.c:2989 vs 1830, 3003, 3066 | lens-motor supply is not given back if RPMh did not report "done" in time, unlike the CX/MX rails |
| CAM-4 | low | CONFIRMED | hakios.camera.c:1218-1225, 1269 vs 2708-2710 | power-sequence delays from the table are not clamped (sensor-register delays are): up to ~71 min with rails on, or in the panic path |
| CAM-5 | low | CONFIRMED (arithmetic), PLAUSIBLE (reach) | hakios.camera.c:3316, 3347, 4053, 4036, 3436, 2197 | mode fields from the table only partly range-checked: frame_length < 40 underflows, width/height unbounded |
| CAM-6 | low | CONFIRMED | hakios.camera.c:3906-3938 | keep_picture has no lock: two writers interleave into the same keep buffer and header |
| HAPTIC-2 | low | PLAUSIBLE | hakios.haptic.c:847-866 | the 1 s cap applies per vibration; back-to-back taps from user writes have no rest or duty limit, only the chip's fault flags |
| TORCH-3 | low | CONFIRMED | hakios.torch.c:585-590, 608-618 | the 30 s cap is "ask anew": an unprivileged writer that writes 0 then a level again keeps the light on indefinitely |
| DEV-1 | info | CONFIRMED | torch.c:575, haptic.c:828, camera.c:4000 | all three nodes are mode 0666: any process can use the camera, torch and motor, so user-space permission prompts are the only gate |
Details
CAM-1 camera read vs. session end: use-after-free of the frame ring
camdev_read marks a place READING under ios_lock, drops the lock at 3869, then reads the globals ring, ring_place_bytes and ring_frame_bytes with no lock and copies the frame:
3873 vm_offset_t at = ring + (uint64_t)b * ring_place_bytes;
3879 for (vm_offset_t line = at & ~63ul; line < at + n; line += 64) dc civac
3884 error = uiomove((caddr_t)at, (int)n, uio);
The capture thread ends a session in ios_stop() (3401): it sets every place FREE under the lock (3405-3407, which also overwrites READING), then, without waiting for any reader, runs titan_down(); power_down(); ring_free(); and ring_free() does kmem_free(kernel_map, ring, ring_bytes) and ring = 0 (3370-3374). Nothing counts readers, so a reader mid-copy keeps a pointer into freed memory.
Ways the session ends while a reader is copying (a 12 MB frame is copied in milliseconds, and the code itself logs the time at 3891):
- another process writes
HAKIOS_CAMERA_SENSOR(3969-3978):ios_switch_tois seen by the loop at 3512,ios_stopruns within about 1 ms; - no read activity for
IDLE_MS(1000 ms, 3526):ios_last_readis only refreshed at the start of a read (3810, 3843) and at its end (3899), not during the copy, so a reader that is descheduled or stuck in page faults on a big user buffer for 1 s is declared idle; - any fault (
healthy(), SMMU fault, 1.5 s without a frame, an exposure write NACK) ->give_up->ios_stop.
Result: dc civac on the unmapped VA (a cache-maintenance fault from kernel code with no recovery handler) -> kernel panic; if the range was reused (the next ring_alloc, or any other kmem allocation) uiomove copies unrelated kernel memory out to user space; if ring was already zeroed, at is a near-NULL address. Reachable by any process that can open the 0666 node.
Fix (sketch):
+ static unsigned ios_readers; /* under ios_lock */
camdev_read: under ios_lock, after choosing b:
- place[b].state = PLACE_READING;
+ place[b].state = PLACE_READING; ios_readers++;
+ snapshot at/n/ring_frame_bytes into locals here, still under the lock
... at the end (the existing lock at 3895):
+ ios_readers--; wakeup(&ios_readers);
ios_stop(), before ring_free():
+ lck_mtx_lock(&ios_lock);
+ while (ios_readers != 0) msleep(&ios_readers, &ios_lock, PZERO, "hakios-camera-drain", NULL);
+ lck_mtx_unlock(&ios_lock);
Also refresh ios_last_read while ios_readers != 0 in the idle test.
CAM-2 unprivileged control of sensor and mode; permanent shutdown
HAKIOS_CAMERA_SENSOR accepts any sensor index (< 4) and any mode number c.b (3969-3978). ios_select() honours an explicit mode directly: the 4:3 / 31 fps / 13 MP filter at 4019-4032 only runs when mode_n >= ctab->modes; an explicit mode is accepted if it passes the checks at 4035 (lanes, data type 0x2b, width % 4, fps and frame length non-zero). ios_start() then calls ring_alloc(RAW10_STRIDE(width) * height) (3436) for RING = 3 places (3254, 3383) of kernel memory. If a table has a full-resolution mode (the S5KHM2SD is a 108 MP sensor; the tables are not in the repository, so NEEDS-HARDWARE/IMAGE for the actual sizes) this is hundreds of MB per session from an unprivileged process, and each session start repeats it.
Consequences of failure are permanent: a failed ring_alloc or any other step in ios_start returns false -> give_up("the camera did not start") (3500-3503); ios_select failing for a sensor with no .tab in fw/ (the build only installs tabs for module files present in fw-camera/, userland/build.sh:392-400) -> give_up("a sensor that cannot be used") (4091-4093). broken is never cleared (except in quiesce), every later open/read returns EIO (3785, 3816). The same holds for one transient NACK: exposure_write -> give_up (3356-3358, 3593-3595), sensor_table has no retry (2703), whereas the PMIC and chip-ID reads retry (697, 1432).
Fix (sketch):
camdev_write, HAKIOS_CAMERA_SENSOR:
- ios_switch_mode = c.b;
+ ios_switch_mode = c.b; /* and in ios_select(): */
ios_select(), for an explicit mode_n also require
+ pixels <= 13000000 && fps_milli <= 31000
ios_select failure / ios_start failure for a switch requested from user space:
- give_up(...)
+ fall back to the previous sensor/mode; only give_up on hardware faults
devfs_make_node(..., 0666, ...) -> 0660 with a group the media daemon is in
TORCH-1 the off path is gated by the sticky broken flag
put() begins with if (broken || !allowed || ...) { give_up(...); return false; } (330-333) and every failure inside put() calls give_up(), which sets broken (308-314). dark() is a chain of four put* calls (506-507) joined by &&. So:
- light is lit (
lit == true), user asks a different level,light()->put_bits(FLASH_ITARGET0)writes but the read-back differs (or the read fails) ->broken = true,light()returns false. serve()breaks out (605) and runs the last-ditchif (lit) (void)dark();(632-634).dark()->put_bits(FLASH_EN_LED_CTRL, 1, 0)->get()works (reads are not gated) -> value differs ->put()seesbroken-> false. The channel stays enabled.- The thread says "the light is given up on" and terminates (636, 723). From here nothing checks fault flags, battery temperature or the time limit; the LED stays on at the last current until
hakios_torch_quiesce()at the run's end (which does resetbroken = false, 751).
The same happens when dark() itself loses a write mid-chain (cut path 612-614, trouble path 625: dark runs first, so a failed first step blocks the remaining steps). The retry at 632 is dead code in exactly the situations it is there for. Contrast with camera.c, whose off path uses a force argument (pm8008_put(..., true) at 813, pgpio_put(..., true)) for the same reason.
Cheap trigger: none needed beyond one dropped/failed SPMI transfer; user space only has to change the level while lit (any process, 0666).
NEEDS-HARDWARE: whether a LED stays lit after a failed read-back, and what it dissipates, is not derivable here (the file's own comment says Android's torch has no safety timer, 125 mA channel 0).
Fix (sketch):
-static bool put(uint8_t reg, uint8_t value)
+static bool put(uint8_t reg, uint8_t value, bool force)
- if (broken || !allowed || ...
+ if ((broken && !force) || !allowed || ...
dark(): use force=true for every write, and do not stop at the first failure (try all four, retry each up to 3 times)
serve() end: if (lit) { dark(); } with the forced path; also keep calling dark() every 100 ms until it succeeds or the run ends
TORCH-2 lit is set after the enabling writes
light() sets lit = true only at line 498, after put(FLASH_MODULE_EN, 0x80) and put_bits(FLASH_EN_LED_CTRL, 1, 1) (495-497). If the module-enable write lands and the channel write lands but fails its read-back (or the read fails), put() returns false, lit stays false, broken is set. Then:
- level 2 (697):
light(1000)false -> the wholeifis skipped, nodark()at all (713); - level 3:
serve()endif (lit)skipped (632); hakios_torch_quiesce()if (lit)skipped (750); it only prints "(NOT DARK)" (762) and does not act on it.
The module/channel may therefore stay enabled and unmonitored. NEEDS-HARDWARE for whether the chip actually lights in that state.
Fix: set lit = true before the first enabling write (before put(FLASH_MODULE_EN, ...)), and let dark() clear it only on success; make quiesce call dark() when the read-back says not dark.
HAPTIC-1 haptic=5 serves without waveforms after a load failure
961 if (level >= 3 && configure() && load_waveforms() && level == 4) { ... play_effect }
969 } else if (level == 5 && !broken) { serve(); }
load_waveforms() returns false without give_up() when the file is missing, shorter than 8 bytes, or its checksum does not add up (570-580). For level 5 the else if is then taken and serve() starts the motor service (waveform 1 for taps, 11 looped) although Android's waveforms were never written to the chip's RAM (the header comment promises "configured and loaded as for 3"). Effects played from RAM that holds whatever the chip had after reset/Android: NEEDS-HARDWARE (strength and length of such a waveform are unknown; the gain and the 1 s cap still apply).
Fix:
- } else if (level == 5 && !broken) {
+ } else if (level == 5 && !broken && loaded) {
with loaded set by configure() && load_waveforms() evaluated separately from level == 4, and give_up("the waveforms are not loaded") on that path.
CAM-3 lens-motor supply vote not tracked on timeout
rails() records the vote before asking (rails_voted |= on;, 1830) so a request that RPMh finishes after our 100 ms poll is still given back. vaf() records it only on success: vaf_voted = on && done; (2989). In af_up(), if (!vaf(true)) return; (3003) leaves vaf_voted == false, so af_down() (3066) never sends the off request, and quiesce does not know about it either. If the WRITE_WAITED commands were accepted but completion was not seen in 100 ms (rpmh_write 1796-1806), the DW9800's supply stays on. NEEDS-HARDWARE (whether a late completion happens).
Fix: vaf_voted |= on; ... if (!on) vaf_voted = !done;, i.e. set it before the write like rails().
CAM-4 power-sequence delays are not clamped
power_step() ends with sensor_delay(e->delay_us) (1269). delay_us is a 32-bit field of the table (camsensor.py:131 packs d * 1000 into a <I), sensor_delay() sleeps us / 1000 ms (up to 4,294,967 ms) and the sensor-register path is clamped at 100 ms (2708-2710) but this one is not. A corrupt or odd table keeps the rails/MCLK up for the whole delay during power-up; in power_down() under leaving (panic path) wait_ms becomes spin_us (141-147) and spins with interrupts as they are. The table comes from the system image built by this repo's tool, so this is robustness, not an attack.
Fix: sensor_delay(us > 100000 ? 100000 : us).
CAM-5 mode fields only partly validated
ios_select() checks lanes, data type, width % 4, fps and frame_length != 0 (4035-4036) but:
frame_length < EXPOSURE_MARGIN (40)makesios_ask.lines = frame_length - EXPOSURE_MARGIN(4053) wrap to ~4G (clamped later at 3311) and*frame_lines - EXPOSURE_MARGIN(3316) wrap so the final clamp oflinesis skipped;exposure_writedefault branch computesextra = frame_lines - ios_mode->frame_length(3347), which wraps ifshortest > FRAME_LINES_MAX.- width/height have no upper bound:
RAW10_STRIDE(width) * ios_mode->heightis 32-bit (3436; alsoframes_alloc2084 and 3161) and wraps; the SMMU mapping (sized from the ring) and the IFE's real write size then disagree, the SMMU refuses the overflow (so this is a fault, not corruption). Insmmu_up()a mapping above 1 GiB givesthirds > 512andsecond[... + t](2197) writes past the second-level page into the third-level pages of the same allocation (SMMU table corruption, not a heap overflow).
Reachable only through the table (or CAM-2's mode selection). Fix: reject modes with frame_length <= EXPOSURE_MARGIN, width > 16384, height > 16384 in ios_select() and capture_sensor().
CAM-6 keep_picture unserialised
HAKIOS_CAMERA_KEEP (3959-3960 -> 3906) does bzero(keep header), fills the header, uiomoves into keep + 4096 and updates loader_recording[] with no lock. Two writers (any process) interleave: header of one picture over the data of the other, frames = 1. Memory-safe (same bounds, want <= keep_bytes is checked at 3914). Fix: take ios_lock (or a dedicated mutex) around the body.
HAPTIC-2 no duty-cycle limit across taps
HAPTIC_MAX_MS (349) limits one LOOP state, followed by an equal rest (906-910). A TAP (waveform 1, 856-859) has no rest: iOS (or any process writing to the 0666 node) writing a burst, 30 ms of silence, another burst ... starts a new tap each time onset_us changes (847). Only the chip's fault flags (checked every 50 ms, 925-929) stop it. PLAUSIBLE; whether this can overheat the motor is NEEDS-HARDWARE.
TORCH-3 30 s cap restarts on request
waiting_for_off is cleared when wanted == 0 is sampled (585-587) and since restarts when applied goes from 0 to nonzero (593-596). Any writer that writes 0, waits more than the 20 ms poll, then writes a level re-arms the light for another max_secs. The cap is therefore a per-request cap, not a limit; the fault/temperature checks (100 ms) stay active. Informational unless the intent was a hard on-time limit.
DEV-1 device node permissions
devfs_make_node(..., 0666, ...) for hakios-torch (575), hakios-haptic (828) and hakios-camera (4000). Anything that can open a device node gets frames, light and vibration. This is the precondition for CAM-1, CAM-2, CAM-6, HAPTIC-2 and TORCH-3 being reachable by unprivileged code.
Checked and found fine
Torch
torch_write(520-533): loop reads exactly 4 bytes peruiomove, value clamped to 1000 (529), the current is then computed inlight()as125000 * permille / 1000(no 32-bit overflow: max 1.25e8) and clamped twice (code > ANDROID_ITARGET484). Nothing user-set reaches the ITARGET register unclamped. A trailing 1-3 bytes are left unconsumed (write returns short), no memory issue.torch_read(539-544):uiomoveofMIN(4, resid)from a local; no leak.- Register allow-list in
put()(303-305, 327-333), read-back of every write (349-358), arbiter channel re-check before each write (335-338),flash.idcheck, mapping sizes:ARB_CHANNEL(n)andARB_OWNER(n)at n <= 511 are inside the 0x1100/0x1000 mappings, observer0x80 * 511 + 0x1cis inside 0x10000, write channel mapped 0x1000. spmi_read_once/show_registers/troublebuffers (r[8],more[8],latched[2],data[2]) sized for the counts used;count - 1with count 1-8 only.- Every polling loop has a bound:
wait_done2 ms, retries 3, serve loop 20 ms steps, level-2 loopsecs. - Boot-args:
torch(clamped 1-3, 731-734),torch_max_s(1..120, 687),torch_secs(1..max, 694);now - since >= max_secs * 1000ull(608) is 64-bit. hakios_spmi_lockis a spin lock held for at most one transfer (<= 2 ms); not held acrosssay/sleep.- Light is switched off in the cut path (612) and the trouble path (625) in the right order (dark before give_up), and on quiesce (750-752).
Haptic
haptic_write(689-727): 512-byte stack buffer,bytescapped to that and made even (698-701),uiomovesize matches;-(int32_t)samples[i]cannot overflow in 32 bits; shared state isvolatile64/32-bit, a lost update ofheard_peakis benign. No path blocks.load_waveforms(563-621): size >= 8 checked before any index; checksum over the file; writesfile + 4 + atin chunks of at most 32 withn = min(32, length - at)so never pastsize; read-back compared chunk-wise with a 64-byte local;length = size - 4cannot underflow. The chip's RAM capacity is NEEDS-HARDWARE, but a too-large file fails the compare and gives up.- I2C by hand: clock stretch bound 100 x 5 us (216), bus_take restores the pins when the bus is not at rest, no unbounded loop; register read buffers sized to counts (1, 2);
regs_readleaves caller buffers initialised (callers zero them). - LOOP vibration stop path: cut at
HAPTIC_MAX_MS(902-908); a stop that fails ->give_up-> reset pin low (352-359), which does not depend on I2C; a failed gain write or health check also gives up. Exits fromserve()all go throughbroken.gain_forcannot exceed Android's 0x80 (814-816). - Boot-args:
haptic(clamped 5, 985),haptic_effect(1..10, 965). - Quiesce puts the chip in reset (997-1003).
Camera, kernel side
- Sensor table parse (
ctab_load1106-1143,ctab_block1146-1161,ctab_find): magic,size >= sizeof(head),modes1..16,at + 12 > endbefore each power block header,ctab_tables + 4 > end; every block is re-checkedat + 12 + n * 12 <= endbefore use (n * sizeofis 64-bit so it does not wrap; the pointer arithmetic can only exceedendby < 51 GB, no wrap at kernel VAs); the mode array is covered by thectab_powercheck.sizeof(struct ctab_entry)is 12 andctab_mode48, matching camsensor.py's packing. sensor_table(2676-2714): operation/address/data widths validated per entry (2689),out[4]always enough, per-entry delay clamped to 100 ms.read_chip_id:inforced to 1-4 soid[4]is safe,outis 1 or 2.- All table-driven power kinds are validated (
power_step1233-1268): MCLK only 0/19200/24000, supply kinds only 1-3, others -> give up; the voltage written to an LDO can only be the one insensors[](sensor_ldo/pm8008_putallow-lists 645-653, 688-691; arrays indexed withn <= 7). - Register write allow-lists: camcc (875-879), PM8008i, PMIC GPIO 3 (555-559); read-backs on all of them; off paths for camera use
forceand always run to the end (power_down,ldo_off). - Every polling loop is bounded:
wait_done,wait_bits,wait_reg, SMMU TLB sync (2222), clock branches (1986),rpmh_write(100 ms),ios_session(frame timeout 1.5 s, 3521),wait_standby,bus_unwedge,capture_tpg(2 s),capture_sensor(5 s). - User commands (
camdev_write3942-3981): fixed 24-byte struct, size checked first;HAKIOS_CAMERA_SENSORindex bounds-checked (3970); EXPOSURE values are unclamped when stored but clamped before use in all three consumers (3480, 3591, 3705, 3728); FOCUS clamped to 1023 inaf_move(3039).keep_picturebounds:bytes != 0,d1-3,stride * height <= 2 * bytesin 64-bit,want <= keep_bytes, copy sizemin(bytes, resid). camdev_readheader:bzero(&f)before filling,MIN(sizeof(f), resid), so no uninitialised bytes leave; place indexesb < RING; state reads/writes underios_lock, the lock is not held acrossuiomove/msleepmisuse (msleep releases it).- Locking between ioctl-side and capture thread: shared
place[],ios_ask,ios_givenare underios_lock;ios_switch_to/ios_openetc are volatile flags; lock order is always (ios_lock) or (hakios_smmu_lock) or (spmi spin), never nested with each other. The one gap is CAM-1. - Single reader: a reader cannot be handed the place that is being programmed for the next frame (the next place is the lowest-sequence non-READING place, the reader takes the highest unread), so I found no tearing with one reader; with two simultaneous readers I did not prove either way.
- IFE DMA is confined by an SMMU context that maps only the ring (or
frames_bytes); the order inios_stopstops the write master (paths_down), tears the SMMU context down and only then frees memory. Cache maintenance (dc civacbefore reads,dsb sy) is present where the CPU reads DMA memory. - Idle timeout and frame timeout: a stalled sensor ends the session in 1.5 s (3521-3525).
- Boot-args:
cam(clamped 0-5),cam_sensor(string compared against the fixed names, cannot index out of range),cam_mode(checked againstmodesin both paths),cam_frames(1..300),cam_fake_fps(1..30),cam_keep_mb(<= 64),cam_settle(masked to a byte),cam_vblank(<= 0x3fff),cam_gain/cam_shutterclamped before the registers are written (3091-3103,exposure_limits).cam_afteris unclamped,secs * 1000wraps for absurd values: trusted boot-arg. RPMhcommand DB parse (rpmh_resource):slave->count > 512skipped, data offsets checked against the mapping,levels[]filled up tomaxonly.
Camera, user-space side
isp_load_tuning/tune_set(camera_isp.c:880-960): size window 8 B..16 MB, every count (n <= 6,ccts <= 6,refs <= 16) is checked before it is used as a loop bound or array index, every read is preceded by anend - pcheck,memcpy(t->ref, p, refs * 12)fitsref[16][3]. Gamma lookup is clamped to 0..4095 (line 411), so a NaN or huge gain from a bad file cannot index out of range. File and descriptor are released on every error path.camera_device.cworker: header size handshake and read sizes match the kernel'sring_frame_bytes; EIO stops the worker.
Not checked
- The cellular modem, QMI, IPA (out of scope).
- Hardware facts: register layouts, timings, safe LED current and motor limits, whether reset stops the motor, whether RAM survives a soft reset, RPMh completion timing, SMMU attribute encodings (
CAM_PTE,SMMU_MAIR0): not derivable from the repository. - The sensor tables and the
.ispfiles themselves (fw-camera/is not in the tree): mode sizes, delays, lane assignments are assumed from tools/camsensor.py's description.camsensor.pyandcamtuning.pyparsing of the vendor files (host-side tools) was not reviewed, only their output format. - The 1568-1750 stretch of hakios.camera.c (read-only
show_*printers) anduserland/src/camera_isp.coutside the tuning loader (demosaic, AE, AWB) andcamera_test.c,camera_avtest.m: not read for safety. hakios_wifi_file()/image_file()(hakios.wifi.c): assumed to return a stable read-only pointer into the image; ownership and alignment not verified.ml_io_map_unmappablefailure mode inrpmh_resource(cmd_db NULL dereference if the mapping fails): not verified.- Behaviour with two simultaneous camera readers beyond what is said above; behaviour when
psci_smp_stop_others()leaves a stopped CPU holdingios_lockorhakios_smmu_lock(the quiesce paths skip those locks whenleaving, torch/haptic/camera all do). - Interplay with the audio DSP driver's use of the RPMh TCS 0 and with hakios.speaker.c / buttons for shared pins and the SPMI arbiter beyond the spin lock.
- userland/src/inject_media.c (writes the torch node) and the camera ISP's float handling.
Fix status
Nothing was built or run; each edit was re-read for syntax and types only.
- CAM-1: FIXED (hakios.camera.c). New
ios_readerscount (underios_lock);camdev_readcounts itself in the same lock hold that marks the place READING and takesatand the frame size fromring,ring_place_bytesandring_frame_bytesthere; it leaves the count (andwakeups) in its final lock section. Newios_drain_readers()(waits underios_lockwithmsleepuntil the count is 0) is called beforering_free()inios_stop()and infake_session(). Lock order: the drain holds onlyios_lock(power is already down); nothing else nests with it. The optional "refreshios_last_readwhile a read is in progress" was not done: the drain already makes an idle stop safe. - CAM-2: SKIPPED. Capping the mode's pixels or fps for user-chosen modes changes the documented ABI (
HAKIOS_CAMERA_SENSORtakes any mode), and the real mode sizes live in tables that are not in the tree. Replacinggive_upwith a fall back to the previous sensor and mode is a design change. Node mode is policy (see DEV-1). - TORCH-1: FIXED (hakios.torch.c).
dark()sets aforcingflag while it runs andput()ignores thebrokenlatch only then (all other checks, the allow-list, the arbiter check and the read-back stay).dark()also tries all four steps instead of stopping at the first failure; a clean run does the same writes in the same order. Not done: the repeated dark() retry every 100 ms inserve()(a design change). - TORCH-2: FIXED (hakios.torch.c).
light()setslit = truebefore the module-enable write;dark()still clears it only on success. In the torch=2 path, a failedlight(1000)now callsdark()iflitis set. Quiesce is unchanged: it already callsdark()whenlitis set, which is now true in the failing case. - HAPTIC-1: FIXED (hakios.haptic.c).
loaded = level >= 3 && configure() && load_waveforms()is evaluated apart fromlevel == 4; level 5 callsserve()only whenloaded, otherwisegive_up("the waveforms are not loaded")(chip into reset). - CAM-3: FIXED (hakios.camera.c).
vaf()recordsvaf_voted = onbefore the RPMh request, asrails()does, soaf_down()sends the "off" even if RPMh did not report done in time. Worst case is one extra harmless "off" request. Whether a late completion really happens is hardware behaviour. - CAM-4: FIXED (hakios.camera.c).
power_step()passesdelay_uscapped at 100000 tosensor_delay(), the same cap the sensor-register path uses. - CAM-5: FIXED (hakios.camera.c).
ios_select()rejects modes withframe_length <= EXPOSURE_MARGIN,frame_length > FRAME_LINES_MAX, width or height over 16384;capture_sensor()rejects width/height over 16384 andframe_length <= 40(EXPOSURE_MARGIN is defined later in the file, so the literal 40 as inexposure()). 16384 x 16384 keeps the 32-bit sizes and the SMMU third-level count in range. - CAM-6: FIXED (hakios.camera.c). New
keep_lockmutex (own mutex, notios_lock, so the capture thread never waits on a user copy) held across the header and data writes inkeep_picture(); never nested withios_lock. - HAPTIC-2: SKIPPED. A duty-cycle limit needs the motor's thermal limits (hardware) and is a policy change.
- TORCH-3: SKIPPED. The cap is per request by design; a hard on-time limit is a policy change.
- DEV-1: SKIPPED. Node mode 0666 is policy and was not to be changed.