commaai/openpilot · error · RuntimeError
could not clear SPI block protection
Error message
could not clear SPI block protection
What it means
RuntimeError raised at the end of Flash.init(): the routine tries 5 times to clear the SPI status-register block-protection bits by issuing write-enable + write-status-register (command 0x01) with a zeroed 4-byte buffer, then reads back status(). If status() & 0x1C (block-protect bits) is still nonzero after 5 attempts, protection could not be cleared and any subsequent erase/program would silently fail.
Source
Thrown at openpilot/system/hardware/chestnut/flash.py:227
while time.monotonic() < deadline:
if not self.status() & 1:
return
time.sleep(0.005)
raise TimeoutError("SPI flash WIP timeout")
def init(self):
self.reg_write(0xCC33, 0x04)
self.reg_write(0xCA81, self.reg_read(0xCA81)[0] | 1)
self.reg_write(0xC805, 0x02)
self.reg_write(0xC8A6, 0x04)
for _ in range(5):
self.write_enable()
self.write_buffer(bytes(4))
self.transaction(0x01, length=1, addr_len=0x04, mode=1)
time.sleep(0.01)
if not self.status() & 0x1C:
return
raise RuntimeError("could not clear SPI block protection")
def read(self, addr, length):
out = bytearray()
while len(out) < length:
n = min(4096, length - len(out))
self.transaction(0x03, addr + len(out), max(4096, n))
for off in range(0, n, 255):
out += self.reg_read(0x7000 + off, min(255, n - off))
return bytes(out)
def erase_sector(self, addr):
self.write_enable()
self.transaction(0x20, addr)
self.wait_write_done()
def program(self, addr, data):
self.write_buffer(data + bytes((-len(data)) % 4))
self.write_enable()View on GitHub (pinned to 516ec1e682)
Solutions
- Power-cycle the enclosure (unplug/replug USB-C, or toggle VBUS) and retry the full connect()+init() sequence - a cold reset usually clears latched protection state
- Make sure init() runs on a fresh reconnect (close + claim again) so the controller registers are actually applied
- If it fails on every attempt with a stable link, read status() manually and compare the bit pattern against the flash datasheet - bits that never clear indicate a wrong flash part or dead chip
- Replace the enclosure if protection genuinely cannot be cleared (hardware fault)
Defensive patterns
Strategy: retry
Validate before calling
def protection_cleared(flash) -> bool:
try:
return not flash.status() & 0x1C
except Exception:
return False Try / catch
try:
flash.init()
except RuntimeError as e:
if 'block protection' in str(e):
power_cycle_enclosure()
flash.connect(); flash.init() # one cold-retry Prevention
- Cold-boot the enclosure before flashing to clear latched status state
- Never skip init() after claiming the interface
- Track units that repeatedly fail init - likely hardware, replace rather than loop
When it happens
Trigger: Flash.init() on a device whose status register stays protected: a write-enable that never latched (WEL dropped), a controller in the wrong mode because regs 0xCC33/0xCA81/0xC805/0xC8A6 were not applied to a live claim, or a hardware-protected or wrong-model flash chip.
Common situations: Running init() right after connect() on a half-enumerated device; status reads corrupted by a flaky USB link so the retry loop can never succeed; a flash chip variant with different protection semantics; device needing a power cycle to reset the status register.
Related errors
- flash controller timeout
- SPI flash WIP timeout
- flash did not converge within {FLASH_BUDGET:g}s
- expected one chestnut, found {len(found)}
- chestnut is in use, stop modeld/GPU processes before flashin
AI-assisted analysis of commaai/openpilot@516ec1e682 (2026-08-15).
Data as JSON: /api/errors/7e576cee3afa78b3.
Report an issue: GitHub.