pypicoboot 1.1.2__tar.gz → 1.1.4__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.4}/PKG-INFO +1 -1
- {pypicoboot-1.1.2 → pypicoboot-1.1.4}/picoboot/_version.py +1 -1
- {pypicoboot-1.1.2 → pypicoboot-1.1.4}/picoboot/picoboot.py +88 -4
- {pypicoboot-1.1.2 → pypicoboot-1.1.4}/picoboot/picobootmonitor.py +2 -2
- {pypicoboot-1.1.2 → pypicoboot-1.1.4}/pypicoboot.egg-info/PKG-INFO +1 -1
- {pypicoboot-1.1.2 → pypicoboot-1.1.4}/LICENSE +0 -0
- {pypicoboot-1.1.2 → pypicoboot-1.1.4}/README.md +0 -0
- {pypicoboot-1.1.2 → pypicoboot-1.1.4}/picoboot/__init__.py +0 -0
- {pypicoboot-1.1.2 → pypicoboot-1.1.4}/picoboot/core/__init__.py +0 -0
- {pypicoboot-1.1.2 → pypicoboot-1.1.4}/picoboot/core/enums.py +0 -0
- {pypicoboot-1.1.2 → pypicoboot-1.1.4}/picoboot/tools/picotool.py +0 -0
- {pypicoboot-1.1.2 → pypicoboot-1.1.4}/picoboot/utils.py +0 -0
- {pypicoboot-1.1.2 → pypicoboot-1.1.4}/pypicoboot.egg-info/SOURCES.txt +0 -0
- {pypicoboot-1.1.2 → pypicoboot-1.1.4}/pypicoboot.egg-info/dependency_links.txt +0 -0
- {pypicoboot-1.1.2 → pypicoboot-1.1.4}/pypicoboot.egg-info/requires.txt +0 -0
- {pypicoboot-1.1.2 → pypicoboot-1.1.4}/pypicoboot.egg-info/top_level.txt +0 -0
- {pypicoboot-1.1.2 → pypicoboot-1.1.4}/pyproject.toml +0 -0
- {pypicoboot-1.1.2 → pypicoboot-1.1.4}/setup.cfg +0 -0
- {pypicoboot-1.1.2 → pypicoboot-1.1.4}/setup.py +0 -0
- {pypicoboot-1.1.2 → pypicoboot-1.1.4}/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]
|
|
@@ -302,10 +361,16 @@ class PicoBoot:
|
|
|
302
361
|
if transfer_length is None:
|
|
303
362
|
transfer_length = 0 if data_out is None else len(data_out)
|
|
304
363
|
|
|
305
|
-
|
|
306
|
-
|
|
364
|
+
logger.debug(f"Preparing to send command {cmd_id} (0x{cmd_id:02X}) with args length {len(args)} and transfer_length {transfer_length}")
|
|
365
|
+
try:
|
|
366
|
+
token, header = self._build_command(cmd_id, args=args, transfer_length=transfer_length)
|
|
367
|
+
except ValueError as e:
|
|
368
|
+
logger.error(f"Error building command: {e}")
|
|
369
|
+
raise
|
|
370
|
+
logger.debug(f"Sending command {cmd_id} (0x{cmd_id:02X}) with token {token} (0x{token:08X}) and transfer_length {transfer_length}")
|
|
307
371
|
|
|
308
372
|
self.ep_out.write(header, timeout=timeout)
|
|
373
|
+
logger.debug(f"Command header sent: {hexlify(header).decode()}")
|
|
309
374
|
|
|
310
375
|
data_in = b""
|
|
311
376
|
|
|
@@ -322,63 +387,79 @@ class PicoBoot:
|
|
|
322
387
|
remaining -= len(chunk)
|
|
323
388
|
data_in = b"".join(chunks)
|
|
324
389
|
if len(data_in) != transfer_length:
|
|
390
|
+
logger.error(f"Expected {transfer_length} bytes, got {len(data_in)}")
|
|
325
391
|
raise PicoBootError(f"Expected {transfer_length} bytes, got {len(data_in)}")
|
|
326
392
|
else:
|
|
327
393
|
if data_out is None or len(data_out) < transfer_length:
|
|
394
|
+
logger.error("data_out missing or too short for OUT command")
|
|
328
395
|
raise ValueError("data_out missing or too short for OUT command")
|
|
329
396
|
self.ep_out.write(data_out[:transfer_length], timeout=timeout)
|
|
330
397
|
|
|
331
398
|
try:
|
|
399
|
+
logger.debug("Waiting for ACK...")
|
|
332
400
|
if is_in:
|
|
333
401
|
self.ep_out.write(b"", timeout=timeout)
|
|
334
402
|
else:
|
|
335
403
|
ack = self.ep_in.read(1, timeout=timeout)
|
|
336
404
|
except usb.core.USBError:
|
|
405
|
+
logger.error("No ACK received after command")
|
|
337
406
|
raise PicoBootError("No ACK received after command")
|
|
338
407
|
|
|
339
408
|
return data_in
|
|
340
409
|
|
|
341
410
|
|
|
342
411
|
def flash_erase(self, addr: int, size: int) -> None:
|
|
412
|
+
logger.debug(f"Erasing flash at address 0x{addr:08X} with size {size} bytes")
|
|
343
413
|
if addr % 4096 != 0 or size % 4096 != 0:
|
|
414
|
+
logger.error("addr i size must be aligned to 4kB")
|
|
344
415
|
raise ValueError("addr i size must be aligned to 4kB")
|
|
345
416
|
args = struct.pack("<II", addr, size)
|
|
346
417
|
self._send_command(CommandID.FLASH_ERASE, args=args, transfer_length=0)
|
|
347
418
|
|
|
348
419
|
def flash_read(self, addr: int, size: int) -> bytes:
|
|
420
|
+
logger.debug(f"Reading flash at address 0x{addr:08X} with size {size} bytes")
|
|
349
421
|
args = struct.pack("<II", addr, size)
|
|
350
422
|
data = self._send_command(CommandID.READ, args=args, transfer_length=size)
|
|
351
423
|
if len(data) != size:
|
|
424
|
+
logger.error(f"READ returned {len(data)} bytes, expected {size}")
|
|
352
425
|
raise PicoBootError(f"READ returned {len(data)} bytes, expected {size}")
|
|
353
426
|
return data
|
|
354
427
|
|
|
355
428
|
def flash_write(self, addr: int, data: bytes) -> None:
|
|
429
|
+
logger.debug(f"Writing flash at address 0x{addr:08X} with size {len(data)} bytes")
|
|
356
430
|
if addr % 256 != 0 or len(data) % 256 != 0:
|
|
431
|
+
logger.error("addr i len(data) must be aligned/multiple of 256 bytes")
|
|
357
432
|
raise ValueError("addr i len(data) must be aligned/multiple of 256 bytes")
|
|
358
433
|
args = struct.pack("<II", addr, len(data))
|
|
359
434
|
self._send_command(CommandID.WRITE, args=args, data_out=data, transfer_length=len(data))
|
|
360
435
|
|
|
361
436
|
def reboot1(self, pc: int = 0, sp: int = 0, delay_ms: int = 0) -> None:
|
|
437
|
+
logger.debug(f"Rebooting device (REBOOT1) with pc=0x{pc:08X}, sp=0x{sp:08X}, delay_ms={delay_ms}")
|
|
362
438
|
args = struct.pack("<III", pc, sp, delay_ms)
|
|
363
439
|
self._send_command(CommandID.REBOOT, args=args, transfer_length=0)
|
|
364
440
|
|
|
365
441
|
def reboot2(self, flags: int = 0, delay_ms: int = 0, p0: int = 0, p1: int = 0) -> None:
|
|
442
|
+
logger.debug(f"Rebooting device (REBOOT2) with flags=0x{flags:08X}, delay_ms={delay_ms}, p0=0x{p0:08X}, p1=0x{p1:08X}")
|
|
366
443
|
args = struct.pack("<IIII", flags, delay_ms, p0, p1)
|
|
367
444
|
self._send_command(CommandID.REBOOT2, args=args, transfer_length=0)
|
|
368
445
|
|
|
369
446
|
def reboot(self, delay_ms: int = 100) -> None:
|
|
447
|
+
logger.debug(f"Rebooting device with delay_ms={delay_ms}")
|
|
370
448
|
if (self.model == Model.RP2040):
|
|
371
449
|
self.reboot1(delay_ms=delay_ms)
|
|
372
450
|
elif (self.model == Model.RP2350):
|
|
373
451
|
self.reboot2(delay_ms=delay_ms)
|
|
374
452
|
|
|
375
453
|
def exit_xip(self) -> None:
|
|
454
|
+
logger.debug("Exiting XIP mode...")
|
|
376
455
|
self._send_command(CommandID.EXIT_XIP, transfer_length=0)
|
|
377
456
|
|
|
378
457
|
def exclusive_access(self) -> None:
|
|
458
|
+
logger.debug("Requesting exclusive access to flash...")
|
|
379
459
|
self._send_command(CommandID.EXCLUSIVE_ACCESS, args=struct.pack("<B", 1), transfer_length=0)
|
|
380
460
|
|
|
381
461
|
def _determine_model(self) -> str:
|
|
462
|
+
logger.debug("Determining device model...")
|
|
382
463
|
if (hasattr(self, "_model")) and (self._model is not None):
|
|
383
464
|
return self._model
|
|
384
465
|
data = self.flash_read(Addresses.BOOTROM_MAGIC, 4)
|
|
@@ -390,6 +471,7 @@ class PicoBoot:
|
|
|
390
471
|
return self._model
|
|
391
472
|
|
|
392
473
|
def _guess_flash_size(self) -> int:
|
|
474
|
+
logger.debug("Guessing flash size...")
|
|
393
475
|
if (hasattr(self, "_memory")) and (self._memory is not None):
|
|
394
476
|
return self._memory
|
|
395
477
|
FLASH_BASE = 0x10000000
|
|
@@ -428,6 +510,7 @@ class PicoBoot:
|
|
|
428
510
|
return self._memory
|
|
429
511
|
|
|
430
512
|
def get_info(self, info_type: InfoType, param0: int = 0, max_len: int = 32) -> bytes:
|
|
513
|
+
logger.debug(f"Getting info of type {info_type} with param0={param0} and max_len={max_len}")
|
|
431
514
|
args = struct.pack("<IIII", info_type, param0, 0, 0)
|
|
432
515
|
data = self._send_command(CommandID.GET_INFO, args=args, transfer_length=max_len)
|
|
433
516
|
return data
|
|
@@ -455,6 +538,7 @@ class PicoBoot:
|
|
|
455
538
|
}
|
|
456
539
|
|
|
457
540
|
def get_info_sys(self, flags: SysInfoFlags = SysInfoFlags.CHIP_INFO | SysInfoFlags.CRITICAL | SysInfoFlags.CPU | SysInfoFlags.FLASH | SysInfoFlags.BOOT_RANDOM | SysInfoFlags.BOOT_INFO) -> dict:
|
|
541
|
+
logger.debug(f"Getting system info with flags: {flags}")
|
|
458
542
|
data = self.get_info(InfoType.SYS, param0=flags, max_len=256)
|
|
459
543
|
if len(data) < 24:
|
|
460
544
|
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
|