commaai/openpilot · error · RuntimeError

chestnut did not re-enumerate after reset

Error message

chestnut did not re-enumerate after reset

What it means

RuntimeError in rom_write(): after issuing USBDEVFS_RESET on the ROM bootloader device and sleeping 3s, find_chestnut() still finds no device. The ROM bootloader requires a port reset before bulk transfers; the expected flow is reset, re-enumeration, then claim with SETCONFIGURATION. If nothing re-appears within 3 seconds, the post-reset re-enumeration failed.

Source

Thrown at openpilot/system/hardware/chestnut/flash.py:360

    os.fsync(f.fileno())
  return data


def rom_write(image, config):
  # the ROM bootloader implements only the BOT protocol, and requires a port reset before bulk transfers
  path, _, _ = find_chestnut()
  if path is None:
    raise RuntimeError("chestnut disappeared before recovery")
  unbind_drivers(path)
  fd = open_device(path)
  try:
    fcntl.ioctl(fd, USBDEVFS_RESET)
  finally:
    os.close(fd)
  time.sleep(3)
  path, _, _ = find_chestnut()
  if path is None:
    raise RuntimeError("chestnut did not re-enumerate after reset")
  fd = claim_interface(path, setup=True)
  for ep in (0x02, 0x81):
    fcntl.ioctl(fd, USBDEVFS_CLEAR_HALT, struct.pack("I", ep))
  tag = 0

  def bulk(ep, payload, timeout):
    buf = ctypes.create_string_buffer(bytes(payload), len(payload))
    fcntl.ioctl(fd, USBDEVFS_BULK, Bulk(ep, len(payload), timeout, ctypes.cast(buf, ctypes.c_void_p)))
    return buf.raw

  def cmd(cdb, data=b"", timeout=30000):
    nonlocal tag
    tag += 1
    bulk(0x02, struct.pack("<IIIBBB16s", 0x43425355, tag, len(data), 0, 0, len(cdb), cdb), timeout)
    if data:
      bulk(0x02, data, timeout)
    try:
      csw = bulk(0x81, bytes(13), timeout)

View on GitHub (pinned to 516ec1e682)

Solutions

  1. Wait a few seconds and check lsusb manually - if the device shows up late, re-run; consider increasing the sleep locally for slow hosts
  2. Check that nothing (usb-storage) grabbed the re-enumerated ROM interface before the next claim - unbind if needed
  3. Try a different USB port, direct connection preferred; some hubs handle USBDEVFS_RESET poorly
  4. If the device never comes back after reset, power-cycle the enclosure physically and start recovery over; repeated failure suggests hardware fault or insufficient power
Defensive patterns

Strategy: retry

Validate before calling

import time
from flash import find_chestnut

def waits_for_renumeration(seconds=10) -> bool:
    end = time.monotonic() + seconds
    while time.monotonic() < end:
        if find_chestnut()[0] is not None:
            return True
        time.sleep(0.5)
    return False

Try / catch

try:
    rom_write(image, config)
except RuntimeError as e:
    if 're-enumerate' in str(e):
        if waits_for_renumeration(15):
            rom_write(image, config)
        else:
            power_cycle_enclosure()

Prevention

When it happens

Trigger: rom_write() executes ioctl(fd, USBDEVFS_RESET), closes the fd, sleeps 3s, rescans: None means raise. Happens when the reset power-cycles the device into a state that does not re-enumerate (dead flash config, power problem), when 3s is too short for a slow device/host combination, or when the reset made the host re-bind a driver that hides the device.

Common situations: Slow hubs or controllers handling USBDEVFS_RESET poorly; device drawing power renegotiation after reset; insufficient bus power; a device that only comes back after a full power cycle.

Related errors


AI-assisted analysis of commaai/openpilot@516ec1e682 (2026-08-15). Data as JSON: /api/errors/f71fe0564cb9a695. Report an issue: GitHub.