commaai/openpilot · error · RuntimeError
chestnut is in use, stop modeld/GPU processes before flashin
Error message
chestnut is in use, stop modeld/GPU processes before flashing
What it means
Raised by claim_interface() in openpilot/system/hardware/chestnut/flash.py when the USBDEVFS_CLAIMINTERFACE (or SETCONFIGURATION/SETINTERFACE) ioctl returns EBUSY. It means another process currently holds the USB interface of the ASM2464 (chestnut) bridge, almost always the GPU driver stack (modeld) that uses the device in normal operation. The flasher refuses to share the device because EP0 control-register access during flashing would corrupt live GPU traffic.
Source
Thrown at openpilot/system/hardware/chestnut/flash.py:141
finally:
os.close(fd)
def claim_interface(path, setup=False):
# unbind usb-storage, which binds to the ROM bootloader
disable_runtime_pm(path)
unbind_drivers(path)
fd = open_device(path)
try:
if setup:
fcntl.ioctl(fd, USBDEVFS_SETCONFIGURATION, struct.pack("I", 1))
fcntl.ioctl(fd, USBDEVFS_CLAIMINTERFACE, struct.pack("I", 0))
if setup:
fcntl.ioctl(fd, USBDEVFS_SETINTERFACE, struct.pack("II", 0, 0))
except OSError as e:
os.close(fd)
if e.errno == errno.EBUSY:
raise RuntimeError("chestnut is in use, stop modeld/GPU processes before flashing") from e
raise
return fd
class Flash:
def __init__(self):
self.fd = -1
def close(self):
if self.fd >= 0:
os.close(self.fd)
self.fd = -1
def connect(self, timeout=5.0):
self.close()
deadline = time.monotonic() + timeout
while time.monotonic() < deadline:
path, vid_pid, product = find_chestnut()View on GitHub (pinned to 516ec1e682)
Solutions
- Stop the processes using the device: systemctl stop the openpilot service or pkill modeld (and anything holding /dev/bus/usb for VID 3801:0001 / add1:0001), then re-run the flasher
- Find who holds the device with lsof / grep on /dev/bus/usb or fuser, and kill that process
- If a kernel driver is still bound, echo the interface into the driver's unbind node (the script's unbind_drivers does this, but only for interfaces with a driver symlink) or unbind it manually and retry
- As a last resort unplug/replug the chestnut enclosure to force a clean enumeration, then retry
Example fix
# before: flashing while modeld runs sudo python3 flash.py # RuntimeError: chestnut is in use... # after: stop users first pkill modeld; sleep 2 sudo python3 flash.py
Defensive patterns
Strategy: validation
Validate before calling
import glob, os
def chestnut_users():
users = []
for d in glob.glob('/sys/bus/usb/devices/*'):
try:
vid = open(d + '/idVendor').read().strip()
pid = open(d + '/idProduct').read().strip()
except OSError:
continue
if (vid, pid) in (('3801', '0001'), ('add1', '0001'), ('174c', '2464'), ('174c', '2463')):
bus = int(open(d + '/busnum').read()); dev = int(open(d + '/devnum').read())
users.append(f'/dev/bus/usb/{bus:03d}/{dev:03d}')
return users # pass each to lsof/fuser before flashing Try / catch
try:
flash.connect()
except RuntimeError as e:
if 'in use' in str(e):
stop_modeld_and_retry() # kill holders, then reconnect
raise Prevention
- Always stop modeld/openpilot services before running the flasher
- Add a preflight step that runs fuser/lsof on the device node and aborts with a clear message
- Never run two flasher instances concurrently
When it happens
Trigger: Calling Flash.connect() or rom_write() (both call claim_interface) while modeld or any process with the chestnut USB interface open is still running. EBUSY comes from the kernel when usbfs cannot claim interface 0 because another driver is bound or another usbfs fd holds a claim.
Common situations: Running the flasher on a live openpilot device without stopping manager/modeld first; a leftover process holding /dev/bus/usb open after a previous aborted flash; usb-storage still bound to the ROM bootloader interface.
Related errors
- flash did not converge within {FLASH_BUDGET:g}s
- expected one chestnut, found {len(found)}
- flash controller timeout
- write failed: {e}
- could not disable USB runtime PM: {control}
AI-assisted analysis of commaai/openpilot@516ec1e682 (2026-08-15).
Data as JSON: /api/errors/5a5009c7233d14a3.
Report an issue: GitHub.