pypicoboot 1.1.2__tar.gz → 1.1.3__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {pypicoboot-1.1.2 → pypicoboot-1.1.3}/PKG-INFO +1 -1
- {pypicoboot-1.1.2 → pypicoboot-1.1.3}/picoboot/_version.py +1 -1
- {pypicoboot-1.1.2 → pypicoboot-1.1.3}/picoboot/picoboot.py +82 -3
- {pypicoboot-1.1.2 → pypicoboot-1.1.3}/picoboot/picobootmonitor.py +2 -2
- {pypicoboot-1.1.2 → pypicoboot-1.1.3}/pypicoboot.egg-info/PKG-INFO +1 -1
- {pypicoboot-1.1.2 → pypicoboot-1.1.3}/LICENSE +0 -0
- {pypicoboot-1.1.2 → pypicoboot-1.1.3}/README.md +0 -0
- {pypicoboot-1.1.2 → pypicoboot-1.1.3}/picoboot/__init__.py +0 -0
- {pypicoboot-1.1.2 → pypicoboot-1.1.3}/picoboot/core/__init__.py +0 -0
- {pypicoboot-1.1.2 → pypicoboot-1.1.3}/picoboot/core/enums.py +0 -0
- {pypicoboot-1.1.2 → pypicoboot-1.1.3}/picoboot/tools/picotool.py +0 -0
- {pypicoboot-1.1.2 → pypicoboot-1.1.3}/picoboot/utils.py +0 -0
- {pypicoboot-1.1.2 → pypicoboot-1.1.3}/pypicoboot.egg-info/SOURCES.txt +0 -0
- {pypicoboot-1.1.2 → pypicoboot-1.1.3}/pypicoboot.egg-info/dependency_links.txt +0 -0
- {pypicoboot-1.1.2 → pypicoboot-1.1.3}/pypicoboot.egg-info/requires.txt +0 -0
- {pypicoboot-1.1.2 → pypicoboot-1.1.3}/pypicoboot.egg-info/top_level.txt +0 -0
- {pypicoboot-1.1.2 → pypicoboot-1.1.3}/pyproject.toml +0 -0
- {pypicoboot-1.1.2 → pypicoboot-1.1.3}/setup.cfg +0 -0
- {pypicoboot-1.1.2 → pypicoboot-1.1.3}/setup.py +0 -0
- {pypicoboot-1.1.2 → pypicoboot-1.1.3}/tests/test_000_init.py +0 -0
|
@@ -16,6 +16,31 @@
|
|
|
16
16
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
17
17
|
*/
|
|
18
18
|
"""
|
|
19
|
+
import os
|
|
20
|
+
import logging
|
|
21
|
+
|
|
22
|
+
def get_logger(name: str):
|
|
23
|
+
env_level = os.getenv("PICOBOOT_LOG", "CRITICAL").upper()
|
|
24
|
+
|
|
25
|
+
valid_levels = ["DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"]
|
|
26
|
+
if env_level not in valid_levels:
|
|
27
|
+
print(f"[logger] Warning: nivell '{env_level}' invàlid. Usant INFO.")
|
|
28
|
+
env_level = "INFO"
|
|
29
|
+
|
|
30
|
+
logger = logging.getLogger(name)
|
|
31
|
+
logger.setLevel(env_level)
|
|
32
|
+
|
|
33
|
+
if not logger.handlers:
|
|
34
|
+
handler = logging.StreamHandler()
|
|
35
|
+
handler.setFormatter(logging.Formatter(
|
|
36
|
+
fmt="%(asctime)s [%(levelname)s] %(name)s: %(message)s",
|
|
37
|
+
datefmt="%Y-%m-%d %H:%M:%S"
|
|
38
|
+
))
|
|
39
|
+
logger.addHandler(handler)
|
|
40
|
+
|
|
41
|
+
return logger
|
|
42
|
+
|
|
43
|
+
logger = get_logger("picoboot")
|
|
19
44
|
|
|
20
45
|
from binascii import hexlify
|
|
21
46
|
from typing import Optional
|
|
@@ -137,15 +162,21 @@ class PicoBootError(Exception):
|
|
|
137
162
|
class PicoBoot:
|
|
138
163
|
|
|
139
164
|
def __init__(self, dev: usb.core.Device, intf, ep_out, ep_in) -> None:
|
|
140
|
-
|
|
165
|
+
logger.info("Initializing PicoBoot device...")
|
|
141
166
|
self.dev = dev
|
|
142
167
|
self.intf = intf
|
|
143
168
|
self.ep_out = ep_out
|
|
144
169
|
self.ep_in = ep_in
|
|
145
170
|
self._token_counter = itertools.count(1)
|
|
171
|
+
logger.debug("Resetting interface...")
|
|
146
172
|
self.interface_reset()
|
|
173
|
+
logger.debug("Guessing flash size...")
|
|
147
174
|
self._memory = self._guess_flash_size()
|
|
175
|
+
logger.debug(f"Detected flash size: {self._memory // 1024} kB")
|
|
176
|
+
logger.debug("Determining model...")
|
|
148
177
|
self._model = self._determine_model()
|
|
178
|
+
logger.debug(f"Detected model: {self._model.name}")
|
|
179
|
+
|
|
149
180
|
class PicoBootObserver(PicoBootMonitorObserver):
|
|
150
181
|
|
|
151
182
|
def __init__(self, device: PicoBoot):
|
|
@@ -154,15 +185,21 @@ class PicoBoot:
|
|
|
154
185
|
def update(self, actions: tuple[list[PicoBoot], list[PicoBoot]]) -> None:
|
|
155
186
|
(connected, disconnected) = actions
|
|
156
187
|
if connected:
|
|
188
|
+
logger.debug("PicoBoot device connected")
|
|
157
189
|
pass
|
|
158
190
|
if disconnected:
|
|
191
|
+
logger.debug("PicoBoot device disconnected")
|
|
159
192
|
self.__device.close()
|
|
160
193
|
|
|
194
|
+
logger.debug("Starting PicoBoot monitor...")
|
|
161
195
|
self.__observer = PicoBootObserver(self)
|
|
196
|
+
logger.debug("PicoBoot monitor started.")
|
|
162
197
|
self.__monitor = PicoBootMonitor(device=self.dev, cls_callback=self.__observer)
|
|
198
|
+
logger.debug("PicoBoot device initialized.")
|
|
163
199
|
|
|
164
200
|
@classmethod
|
|
165
201
|
def open(cls, vid: int = DEFAULT_VID, pid: list[int] = [DEFAULT_PID_RP2040, DEFAULT_PID_RP2350], serial: Optional[str] = None) -> "PicoBoot":
|
|
202
|
+
logger.info(f"Opening PicoBoot device with VID={vid:04x} and PIDs={[f'{p:04x}' for p in pid]}...")
|
|
166
203
|
class find_vidpids(object):
|
|
167
204
|
|
|
168
205
|
def __init__(self, vid: int, pids: list[int]):
|
|
@@ -177,10 +214,12 @@ class PicoBoot:
|
|
|
177
214
|
devices = usb.core.find(find_all=True, custom_match=find_vidpids(vid, pid))
|
|
178
215
|
devices = list(devices) if devices is not None else []
|
|
179
216
|
if not devices:
|
|
217
|
+
logger.error("No device found in PICOBOOT mode")
|
|
180
218
|
raise PicoBootError("No device found in PICOBOOT mode")
|
|
181
219
|
|
|
182
220
|
dev = None
|
|
183
221
|
if serial is None:
|
|
222
|
+
logger.info("No serial number provided, using the first device found.")
|
|
184
223
|
dev = devices[0]
|
|
185
224
|
else:
|
|
186
225
|
for d in devices:
|
|
@@ -190,15 +229,20 @@ class PicoBoot:
|
|
|
190
229
|
continue
|
|
191
230
|
if s == serial:
|
|
192
231
|
dev = d
|
|
232
|
+
logger.debug(f"Using device with serial number: {serial}")
|
|
193
233
|
break
|
|
194
234
|
if dev is None:
|
|
235
|
+
logger.error("No device found with this serial number")
|
|
195
236
|
raise PicoBootError("No device found with this serial number")
|
|
196
237
|
|
|
197
238
|
# Ensure active configuration
|
|
198
239
|
# macOS does not allow detach_kernel_driver, and often returns Access Denied
|
|
199
240
|
try:
|
|
241
|
+
logger.debug("Checking if kernel driver is active and detaching if necessary...")
|
|
200
242
|
if dev.is_kernel_driver_active(1):
|
|
243
|
+
logger.debug("Kernel driver is active, detaching...")
|
|
201
244
|
dev.detach_kernel_driver(1)
|
|
245
|
+
logger.debug("Kernel driver detached.")
|
|
202
246
|
except usb.core.USBError:
|
|
203
247
|
# If it fails, we continue anyway. It's normal on macOS.
|
|
204
248
|
pass
|
|
@@ -207,7 +251,9 @@ class PicoBoot:
|
|
|
207
251
|
pass
|
|
208
252
|
|
|
209
253
|
#dev.set_configuration()
|
|
254
|
+
logger.debug("Getting active configuration...")
|
|
210
255
|
cfg = dev.get_active_configuration()
|
|
256
|
+
logger.debug("Searching for PICOBOOT interface...")
|
|
211
257
|
|
|
212
258
|
intf = None
|
|
213
259
|
for i in cfg:
|
|
@@ -215,31 +261,41 @@ class PicoBoot:
|
|
|
215
261
|
intf = i
|
|
216
262
|
break
|
|
217
263
|
if intf is None:
|
|
264
|
+
logger.error("No interface found with PICOBOOT at the device")
|
|
218
265
|
raise PicoBootError("No interface found with PICOBOOT at the device")
|
|
219
266
|
|
|
220
267
|
#usb.util.claim_interface(dev, intf.bInterfaceNumber)
|
|
221
268
|
|
|
269
|
+
logger.debug("Finding BULK_IN and BULK_OUT endpoints...")
|
|
222
270
|
ep_in = ep_out = None
|
|
223
271
|
for ep in intf.endpoints():
|
|
224
272
|
if usb.util.endpoint_direction(ep.bEndpointAddress) == usb.util.ENDPOINT_IN:
|
|
273
|
+
logger.debug(f"Found BULK_IN endpoint: 0x{ep.bEndpointAddress:02X}")
|
|
225
274
|
ep_in = ep
|
|
226
275
|
else:
|
|
276
|
+
logger.debug(f"Found BULK_OUT endpoint: 0x{ep.bEndpointAddress:02X}")
|
|
227
277
|
ep_out = ep
|
|
228
278
|
|
|
229
279
|
if ep_in is None or ep_out is None:
|
|
280
|
+
logger.error("No PICOBOOT BULK_IN/BULK_OUT endpoints found")
|
|
230
281
|
raise PicoBootError("No PICOBOOT BULK_IN/BULK_OUT endpoints found")
|
|
231
|
-
|
|
282
|
+
logger.info("PICOBOOT device opened successfully.")
|
|
232
283
|
return cls(dev, intf, ep_out, ep_in)
|
|
233
284
|
|
|
234
285
|
def close(self):
|
|
286
|
+
logger.debug("Closing PicoBoot device...")
|
|
235
287
|
if self.dev:
|
|
288
|
+
self.__monitor.stop()
|
|
289
|
+
logger.debug("Releasing USB resources...")
|
|
236
290
|
usb.util.dispose_resources(self.dev)
|
|
291
|
+
logger.debug("PicoBoot device closed.")
|
|
237
292
|
self.dev = None
|
|
238
293
|
|
|
239
294
|
def has_device(self):
|
|
240
295
|
return self.dev is not None
|
|
241
296
|
|
|
242
297
|
def interface_reset(self) -> None:
|
|
298
|
+
logger.debug("Resetting interface...")
|
|
243
299
|
self.dev.ctrl_transfer(
|
|
244
300
|
ControlRequest.BMREQ_RESET,
|
|
245
301
|
ControlRequest.REQ_INTERFACE_RESET,
|
|
@@ -247,8 +303,10 @@ class PicoBoot:
|
|
|
247
303
|
self.intf.bInterfaceNumber,
|
|
248
304
|
None
|
|
249
305
|
)
|
|
306
|
+
logger.debug("Interface reset command sent.")
|
|
250
307
|
|
|
251
308
|
def get_command_status(self) -> dict:
|
|
309
|
+
logger.debug("Getting command status...")
|
|
252
310
|
data = self.dev.ctrl_transfer(
|
|
253
311
|
ControlRequest.BMREQ_GET_STATUS,
|
|
254
312
|
ControlRequest.REQ_GET_COMMAND_STATUS,
|
|
@@ -256,6 +314,7 @@ class PicoBoot:
|
|
|
256
314
|
self.intf.bInterfaceNumber,
|
|
257
315
|
16,
|
|
258
316
|
)
|
|
317
|
+
logger.debug(f"Command status data: {hexlify(data).decode()}")
|
|
259
318
|
b = bytes(data)
|
|
260
319
|
dToken, dStatusCode = struct.unpack_from("<II", b, 0)
|
|
261
320
|
bCmdId = b[8]
|
|
@@ -303,9 +362,10 @@ class PicoBoot:
|
|
|
303
362
|
transfer_length = 0 if data_out is None else len(data_out)
|
|
304
363
|
|
|
305
364
|
token, header = self._build_command(cmd_id, args=args, transfer_length=transfer_length)
|
|
306
|
-
|
|
365
|
+
logger.debug(f"Sending command {cmd_id} (0x{cmd_id:02X}) with token {token} (0x{token:08X}) and transfer_length {transfer_length}")
|
|
307
366
|
|
|
308
367
|
self.ep_out.write(header, timeout=timeout)
|
|
368
|
+
logger.debug(f"Command header sent: {hexlify(header).decode()}")
|
|
309
369
|
|
|
310
370
|
data_in = b""
|
|
311
371
|
|
|
@@ -322,63 +382,79 @@ class PicoBoot:
|
|
|
322
382
|
remaining -= len(chunk)
|
|
323
383
|
data_in = b"".join(chunks)
|
|
324
384
|
if len(data_in) != transfer_length:
|
|
385
|
+
logger.error(f"Expected {transfer_length} bytes, got {len(data_in)}")
|
|
325
386
|
raise PicoBootError(f"Expected {transfer_length} bytes, got {len(data_in)}")
|
|
326
387
|
else:
|
|
327
388
|
if data_out is None or len(data_out) < transfer_length:
|
|
389
|
+
logger.error("data_out missing or too short for OUT command")
|
|
328
390
|
raise ValueError("data_out missing or too short for OUT command")
|
|
329
391
|
self.ep_out.write(data_out[:transfer_length], timeout=timeout)
|
|
330
392
|
|
|
331
393
|
try:
|
|
394
|
+
logger.debug("Waiting for ACK...")
|
|
332
395
|
if is_in:
|
|
333
396
|
self.ep_out.write(b"", timeout=timeout)
|
|
334
397
|
else:
|
|
335
398
|
ack = self.ep_in.read(1, timeout=timeout)
|
|
336
399
|
except usb.core.USBError:
|
|
400
|
+
logger.error("No ACK received after command")
|
|
337
401
|
raise PicoBootError("No ACK received after command")
|
|
338
402
|
|
|
339
403
|
return data_in
|
|
340
404
|
|
|
341
405
|
|
|
342
406
|
def flash_erase(self, addr: int, size: int) -> None:
|
|
407
|
+
logger.debug(f"Erasing flash at address 0x{addr:08X} with size {size} bytes")
|
|
343
408
|
if addr % 4096 != 0 or size % 4096 != 0:
|
|
409
|
+
logger.error("addr i size must be aligned to 4kB")
|
|
344
410
|
raise ValueError("addr i size must be aligned to 4kB")
|
|
345
411
|
args = struct.pack("<II", addr, size)
|
|
346
412
|
self._send_command(CommandID.FLASH_ERASE, args=args, transfer_length=0)
|
|
347
413
|
|
|
348
414
|
def flash_read(self, addr: int, size: int) -> bytes:
|
|
415
|
+
logger.debug(f"Reading flash at address 0x{addr:08X} with size {size} bytes")
|
|
349
416
|
args = struct.pack("<II", addr, size)
|
|
350
417
|
data = self._send_command(CommandID.READ, args=args, transfer_length=size)
|
|
351
418
|
if len(data) != size:
|
|
419
|
+
logger.error(f"READ returned {len(data)} bytes, expected {size}")
|
|
352
420
|
raise PicoBootError(f"READ returned {len(data)} bytes, expected {size}")
|
|
353
421
|
return data
|
|
354
422
|
|
|
355
423
|
def flash_write(self, addr: int, data: bytes) -> None:
|
|
424
|
+
logger.debug(f"Writing flash at address 0x{addr:08X} with size {len(data)} bytes")
|
|
356
425
|
if addr % 256 != 0 or len(data) % 256 != 0:
|
|
426
|
+
logger.error("addr i len(data) must be aligned/multiple of 256 bytes")
|
|
357
427
|
raise ValueError("addr i len(data) must be aligned/multiple of 256 bytes")
|
|
358
428
|
args = struct.pack("<II", addr, len(data))
|
|
359
429
|
self._send_command(CommandID.WRITE, args=args, data_out=data, transfer_length=len(data))
|
|
360
430
|
|
|
361
431
|
def reboot1(self, pc: int = 0, sp: int = 0, delay_ms: int = 0) -> None:
|
|
432
|
+
logger.debug(f"Rebooting device (REBOOT1) with pc=0x{pc:08X}, sp=0x{sp:08X}, delay_ms={delay_ms}")
|
|
362
433
|
args = struct.pack("<III", pc, sp, delay_ms)
|
|
363
434
|
self._send_command(CommandID.REBOOT, args=args, transfer_length=0)
|
|
364
435
|
|
|
365
436
|
def reboot2(self, flags: int = 0, delay_ms: int = 0, p0: int = 0, p1: int = 0) -> None:
|
|
437
|
+
logger.debug(f"Rebooting device (REBOOT2) with flags=0x{flags:08X}, delay_ms={delay_ms}, p0=0x{p0:08X}, p1=0x{p1:08X}")
|
|
366
438
|
args = struct.pack("<IIII", flags, delay_ms, p0, p1)
|
|
367
439
|
self._send_command(CommandID.REBOOT2, args=args, transfer_length=0)
|
|
368
440
|
|
|
369
441
|
def reboot(self, delay_ms: int = 100) -> None:
|
|
442
|
+
logger.debug(f"Rebooting device with delay_ms={delay_ms}")
|
|
370
443
|
if (self.model == Model.RP2040):
|
|
371
444
|
self.reboot1(delay_ms=delay_ms)
|
|
372
445
|
elif (self.model == Model.RP2350):
|
|
373
446
|
self.reboot2(delay_ms=delay_ms)
|
|
374
447
|
|
|
375
448
|
def exit_xip(self) -> None:
|
|
449
|
+
logger.debug("Exiting XIP mode...")
|
|
376
450
|
self._send_command(CommandID.EXIT_XIP, transfer_length=0)
|
|
377
451
|
|
|
378
452
|
def exclusive_access(self) -> None:
|
|
453
|
+
logger.debug("Requesting exclusive access to flash...")
|
|
379
454
|
self._send_command(CommandID.EXCLUSIVE_ACCESS, args=struct.pack("<B", 1), transfer_length=0)
|
|
380
455
|
|
|
381
456
|
def _determine_model(self) -> str:
|
|
457
|
+
logger.debug("Determining device model...")
|
|
382
458
|
if (hasattr(self, "_model")) and (self._model is not None):
|
|
383
459
|
return self._model
|
|
384
460
|
data = self.flash_read(Addresses.BOOTROM_MAGIC, 4)
|
|
@@ -390,6 +466,7 @@ class PicoBoot:
|
|
|
390
466
|
return self._model
|
|
391
467
|
|
|
392
468
|
def _guess_flash_size(self) -> int:
|
|
469
|
+
logger.debug("Guessing flash size...")
|
|
393
470
|
if (hasattr(self, "_memory")) and (self._memory is not None):
|
|
394
471
|
return self._memory
|
|
395
472
|
FLASH_BASE = 0x10000000
|
|
@@ -428,6 +505,7 @@ class PicoBoot:
|
|
|
428
505
|
return self._memory
|
|
429
506
|
|
|
430
507
|
def get_info(self, info_type: InfoType, param0: int = 0, max_len: int = 32) -> bytes:
|
|
508
|
+
logger.debug(f"Getting info of type {info_type} with param0={param0} and max_len={max_len}")
|
|
431
509
|
args = struct.pack("<IIII", info_type, param0, 0, 0)
|
|
432
510
|
data = self._send_command(CommandID.GET_INFO, args=args, transfer_length=max_len)
|
|
433
511
|
return data
|
|
@@ -455,6 +533,7 @@ class PicoBoot:
|
|
|
455
533
|
}
|
|
456
534
|
|
|
457
535
|
def get_info_sys(self, flags: SysInfoFlags = SysInfoFlags.CHIP_INFO | SysInfoFlags.CRITICAL | SysInfoFlags.CPU | SysInfoFlags.FLASH | SysInfoFlags.BOOT_RANDOM | SysInfoFlags.BOOT_INFO) -> dict:
|
|
536
|
+
logger.debug(f"Getting system info with flags: {flags}")
|
|
458
537
|
data = self.get_info(InfoType.SYS, param0=flags, max_len=256)
|
|
459
538
|
if len(data) < 24:
|
|
460
539
|
raise PicoBootError("INFO_SYS response too short")
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|