Flashing firmware massal via USB DFU (Device Firmware Upgrade) pada mobile device farm sering mengalami kegagalan akibat race condition tingkat hardware dan bus controller. Ketika puluhan worker mengeksekusi flashing secara paralel, fluktuasi state koneksi USB memicu benturan alokasi perangkat, payload transfer terputus di tengah jalan, dan antrian mengalami desinkronisasi.
Akar Masalah Flashing USB DFU di Lingkungan Paralel
Interaksi low-level USB DFU tidak bekerja seperti request I/O jaringan standar. Tiga kegagalan utama yang sering terjadi meliputi:
- Re-enumerasi Bus USB: Saat berpindah dari mode operasional (misal: ADB/fastboot) ke mode DFU, SoC memutus jalur data D+/D- lalu menyambungkannya kembali. Vendor ID (VID) dan Product ID (PID) berubah, serta nomor devpath Linux (
/dev/bus/usb/XXX/YYY) di-reset. Worker lain mendeteksi ini sebagai perangkat baru dan langsung mencoba mengklaimnya. - Premature Lease Expiry: Control/bulk transfer file image firmware (ratusan megabyte hingga gigabyte) memblokir thread worker. Jika distributed lock hanya menggunakan time-to-live (TTL) statis, lock kedaluwarsa sebelum penulisan ROM tuntas, memungkinkan antrian mengirim job baru ke port yang sama.
- Desync State Saat Device Hang: Kegagalan verifikasi bootloader atau kerusakan flash partition menyebabkan perangkat terjebak pada mode Emergency Download (EDL) atau brick. Worker mati karena uncaught timeout, meninggalkan zombie lock di antrian.
Strategi Penguncian Berbasis Physical USB Topology
Jangan pernah mengunci perangkat berdasarkan VID:PID, nomor serial software, atau path devfs yang dinamis. Serial number sering kali hilang saat perangkat berada dalam bootloader DFU low-level. Solusinya: kunci physical USB topology path.
Linux sysfs menyediakan struktur port hierarkis yang statis untuk setiap port pada USB hub, misalnya 1-1.4.2 (Bus 1, Hub port 1.4, port 2). Port fisik ini tidak berubah terlepas dari berapa kali perangkat re-enumerasi atau berganti VID:PID.
# Identifikasi physical port perangkat via udevadm
udevadm info -p /sys/bus/usb/devices/1-1.4.2 | grep -E 'DEVPATH|ID_PATH'Arsitektur State Machine USB DFU
Siklus eksekusi worker harus diikat ke state machine deterministik:
- LOCK_ACQUIRED: Worker mengunci token port fisik di Redis.
- ENTER_DFU: Kirim perintah reboot bootloader/DFU via ADB atau hardware relay testpoint.
- WAIT_ENUMERATION: Worker menunggu udev event mendeteksi kemunculan VID:PID DFU pada port fisik yang sama (maksimum 15 detik).
- TRANSFER_PAYLOAD: Transfer binary firmware. Dynamic heartbeat thread memperpanjang Redis lock setiap interval tertentu selama progress I/O aktif.
- REBOOT_VERIFY: Tunggu re-enumerasi kembali ke runtime OS.
- RELEASE: Lepas lock Redis, tandai job selesai.
Implementasi Worker Lock dengan Dynamic Heartbeat
Implementasi Python minimal berikut menggunakan Redis distributed lock dengan background heartbeat thread dan validasi physical topology.
import time
import threading
import redis
class USBPortLock:
def __init__(self, r_client: redis.Redis, hub_port_path: str, ttl_ms: int = 10000):
self.r = r_client
self.lock_key = f"lock:usb:port:{hub_port_path}"
self.ttl_ms = ttl_ms
self.lock_value = f"worker_{threading.get_ident()}_{time.time()}"
self._running = False
self._renew_thread = None
def acquire(self) -> bool:
# ponytail: pakai set NX sederhana, upgrade ke Redlock jika cluster Redis multi-node
ok = self.r.set(self.lock_key, self.lock_value, nx=True, px=self.ttl_ms)
if not ok:
return False
self._running = True
self._renew_thread = threading.Thread(target=self._heartbeat, daemon=True)
self._renew_thread.start()
return True
def _heartbeat(self):
interval = (self.ttl_ms / 1000.0) / 2.0
while self._running:
time.sleep(interval)
# Perpanjang lease hanya jika value masih cocok
lua_renew = """
if redis.call('get', KEYS[1]) == ARGV[1] then
return redis.call('pexpire', KEYS[1], ARGV[2])
else
return 0
end
"""
if not self.r.eval(lua_renew, 1, self.lock_key, self.lock_value, self.ttl_ms):
self._running = False
break
def release(self):
self._running = False
if self._renew_thread:
self._renew_thread.join(timeout=1.0)
lua_release = """
if redis.call('get', KEYS[1]) == ARGV[1] then
return redis.call('del', KEYS[1])
else
return 0
end
"""
self.r.eval(lua_release, 1, self.lock_key, self.lock_value)
# Runnable self-check test
def test_usb_lock():
# Setup mock/isolated Redis client local
r = redis.Redis(host='localhost', port=6379, db=15, decode_responses=True)
try:
r.ping()
except redis.ConnectionError:
print("Redis tidak terdeteksi, lewati live test.")
return
port_path = "1-1.3"
lock1 = USBPortLock(r, port_path, ttl_ms=2000)
lock2 = USBPortLock(r, port_path, ttl_ms=2000)
assert lock1.acquire() is True, "Lock1 gagal acquire"
assert lock2.acquire() is False, "Race condition: Lock2 berhasil acquire port yang sama"
# Verifikasi dynamic heartbeat memperpanjang TTL
time.sleep(2.5)
assert r.exists(lock1.lock_key) == 1, "Heartbeat gagal memperpanjang lock"
lock1.release()
assert r.exists(lock1.lock_key) == 0, "Lock tidak terhapus setelah release"
assert lock2.acquire() is True, "Lock2 gagal acquire setelah dilepas Lock1"
lock2.release()
print("Test Lock Logika: OK")
if __name__ == "__main__":
test_usb_lock()
[code] → skipped: penanganan failover Redis cluster; add when node Redis di-deploy secara distributed master-replica.
Mekanisme Pembersihan Idempoten (Zombie Lock Recovery)
Ketika perangkat gagal booting dan masuk ke state mati total (DFU failure), worker harus menjalankan prosedur pemulihan hardware sebelum melepas lock:
- USB Port Power Cycling: Gunakan utilitas seperti
uhubctluntuk memutus dan menyambungkan kembali daya VBUS pada port fisik USB hub. - Fencing Token: Setiap job flashing membawa nomor versi inkremental. Perangkat keras yang terhubung lambat tidak boleh memproses paket firmware lama jika worker sudah menganggap task kedaluwarsa.
# Reset power VBUS pada port USB 2 di hub bus 1 secara programmatic
uhubctl -l 1-1 -p 2 -a cycle -d 2Catatan Operasional: Pastikan smart USB hub di device farm Anda mendukung per-port power switching (PPPS). Hub komersial murah sering kali memutus daya ke seluruh port secara bersamaan saat satu port di-reset, yang akan mematikan flashing perangkat lain di hub tersebut.
Komentar
0 komentar
Masuk ke akun kamu untuk ikut berkomentar.
Belum ada komentar
Jadilah yang pertama ikut berdiskusi!