pypicoboot 1.1.1__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.1 → pypicoboot-1.1.3}/PKG-INFO +1 -1
- {pypicoboot-1.1.1 → pypicoboot-1.1.3}/picoboot/_version.py +1 -1
- {pypicoboot-1.1.1 → pypicoboot-1.1.3}/picoboot/picoboot.py +90 -10
- {pypicoboot-1.1.1 → pypicoboot-1.1.3}/picoboot/picobootmonitor.py +2 -2
- {pypicoboot-1.1.1 → pypicoboot-1.1.3}/pypicoboot.egg-info/PKG-INFO +1 -1
- {pypicoboot-1.1.1 → pypicoboot-1.1.3}/LICENSE +0 -0
- {pypicoboot-1.1.1 → pypicoboot-1.1.3}/README.md +0 -0
- {pypicoboot-1.1.1 → pypicoboot-1.1.3}/picoboot/__init__.py +0 -0
- {pypicoboot-1.1.1 → pypicoboot-1.1.3}/picoboot/core/__init__.py +0 -0
- {pypicoboot-1.1.1 → pypicoboot-1.1.3}/picoboot/core/enums.py +0 -0
- {pypicoboot-1.1.1 → pypicoboot-1.1.3}/picoboot/tools/picotool.py +0 -0
- {pypicoboot-1.1.1 → pypicoboot-1.1.3}/picoboot/utils.py +0 -0
- {pypicoboot-1.1.1 → pypicoboot-1.1.3}/pypicoboot.egg-info/SOURCES.txt +0 -0
- {pypicoboot-1.1.1 → pypicoboot-1.1.3}/pypicoboot.egg-info/dependency_links.txt +0 -0
- {pypicoboot-1.1.1 → pypicoboot-1.1.3}/pypicoboot.egg-info/requires.txt +0 -0
- {pypicoboot-1.1.1 → pypicoboot-1.1.3}/pypicoboot.egg-info/top_level.txt +0 -0
- {pypicoboot-1.1.1 → pypicoboot-1.1.3}/pyproject.toml +0 -0
- {pypicoboot-1.1.1 → pypicoboot-1.1.3}/setup.cfg +0 -0
- {pypicoboot-1.1.1 → pypicoboot-1.1.3}/setup.py +0 -0
- {pypicoboot-1.1.1 → 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,32 +185,41 @@ 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":
|
|
166
|
-
|
|
202
|
+
logger.info(f"Opening PicoBoot device with VID={vid:04x} and PIDs={[f'{p:04x}' for p in pid]}...")
|
|
203
|
+
class find_vidpids(object):
|
|
167
204
|
|
|
168
|
-
def __init__(self, pids: list[int]):
|
|
205
|
+
def __init__(self, vid: int, pids: list[int]):
|
|
206
|
+
self._vid = vid
|
|
169
207
|
self._pids = pids
|
|
170
208
|
|
|
171
209
|
def __call__(self, device: usb.core.Device) -> bool:
|
|
172
|
-
if device.idProduct in self._pids:
|
|
210
|
+
if device.idProduct in self._pids and device.idVendor == self._vid:
|
|
173
211
|
return True
|
|
174
212
|
return False
|
|
175
213
|
|
|
176
|
-
devices = usb.core.find(find_all=True, custom_match=
|
|
214
|
+
devices = usb.core.find(find_all=True, custom_match=find_vidpids(vid, pid))
|
|
177
215
|
devices = list(devices) if devices is not None else []
|
|
178
216
|
if not devices:
|
|
217
|
+
logger.error("No device found in PICOBOOT mode")
|
|
179
218
|
raise PicoBootError("No device found in PICOBOOT mode")
|
|
180
219
|
|
|
181
220
|
dev = None
|
|
182
221
|
if serial is None:
|
|
222
|
+
logger.info("No serial number provided, using the first device found.")
|
|
183
223
|
dev = devices[0]
|
|
184
224
|
else:
|
|
185
225
|
for d in devices:
|
|
@@ -189,15 +229,20 @@ class PicoBoot:
|
|
|
189
229
|
continue
|
|
190
230
|
if s == serial:
|
|
191
231
|
dev = d
|
|
232
|
+
logger.debug(f"Using device with serial number: {serial}")
|
|
192
233
|
break
|
|
193
234
|
if dev is None:
|
|
235
|
+
logger.error("No device found with this serial number")
|
|
194
236
|
raise PicoBootError("No device found with this serial number")
|
|
195
237
|
|
|
196
238
|
# Ensure active configuration
|
|
197
239
|
# macOS does not allow detach_kernel_driver, and often returns Access Denied
|
|
198
240
|
try:
|
|
199
|
-
if
|
|
200
|
-
|
|
241
|
+
logger.debug("Checking if kernel driver is active and detaching if necessary...")
|
|
242
|
+
if dev.is_kernel_driver_active(1):
|
|
243
|
+
logger.debug("Kernel driver is active, detaching...")
|
|
244
|
+
dev.detach_kernel_driver(1)
|
|
245
|
+
logger.debug("Kernel driver detached.")
|
|
201
246
|
except usb.core.USBError:
|
|
202
247
|
# If it fails, we continue anyway. It's normal on macOS.
|
|
203
248
|
pass
|
|
@@ -205,8 +250,10 @@ class PicoBoot:
|
|
|
205
250
|
# Also fine on backends that don't implement the function
|
|
206
251
|
pass
|
|
207
252
|
|
|
208
|
-
dev.set_configuration()
|
|
253
|
+
#dev.set_configuration()
|
|
254
|
+
logger.debug("Getting active configuration...")
|
|
209
255
|
cfg = dev.get_active_configuration()
|
|
256
|
+
logger.debug("Searching for PICOBOOT interface...")
|
|
210
257
|
|
|
211
258
|
intf = None
|
|
212
259
|
for i in cfg:
|
|
@@ -214,31 +261,41 @@ class PicoBoot:
|
|
|
214
261
|
intf = i
|
|
215
262
|
break
|
|
216
263
|
if intf is None:
|
|
264
|
+
logger.error("No interface found with PICOBOOT at the device")
|
|
217
265
|
raise PicoBootError("No interface found with PICOBOOT at the device")
|
|
218
266
|
|
|
219
267
|
#usb.util.claim_interface(dev, intf.bInterfaceNumber)
|
|
220
268
|
|
|
269
|
+
logger.debug("Finding BULK_IN and BULK_OUT endpoints...")
|
|
221
270
|
ep_in = ep_out = None
|
|
222
271
|
for ep in intf.endpoints():
|
|
223
272
|
if usb.util.endpoint_direction(ep.bEndpointAddress) == usb.util.ENDPOINT_IN:
|
|
273
|
+
logger.debug(f"Found BULK_IN endpoint: 0x{ep.bEndpointAddress:02X}")
|
|
224
274
|
ep_in = ep
|
|
225
275
|
else:
|
|
276
|
+
logger.debug(f"Found BULK_OUT endpoint: 0x{ep.bEndpointAddress:02X}")
|
|
226
277
|
ep_out = ep
|
|
227
278
|
|
|
228
279
|
if ep_in is None or ep_out is None:
|
|
280
|
+
logger.error("No PICOBOOT BULK_IN/BULK_OUT endpoints found")
|
|
229
281
|
raise PicoBootError("No PICOBOOT BULK_IN/BULK_OUT endpoints found")
|
|
230
|
-
|
|
282
|
+
logger.info("PICOBOOT device opened successfully.")
|
|
231
283
|
return cls(dev, intf, ep_out, ep_in)
|
|
232
284
|
|
|
233
285
|
def close(self):
|
|
286
|
+
logger.debug("Closing PicoBoot device...")
|
|
234
287
|
if self.dev:
|
|
288
|
+
self.__monitor.stop()
|
|
289
|
+
logger.debug("Releasing USB resources...")
|
|
235
290
|
usb.util.dispose_resources(self.dev)
|
|
291
|
+
logger.debug("PicoBoot device closed.")
|
|
236
292
|
self.dev = None
|
|
237
293
|
|
|
238
294
|
def has_device(self):
|
|
239
295
|
return self.dev is not None
|
|
240
296
|
|
|
241
297
|
def interface_reset(self) -> None:
|
|
298
|
+
logger.debug("Resetting interface...")
|
|
242
299
|
self.dev.ctrl_transfer(
|
|
243
300
|
ControlRequest.BMREQ_RESET,
|
|
244
301
|
ControlRequest.REQ_INTERFACE_RESET,
|
|
@@ -246,8 +303,10 @@ class PicoBoot:
|
|
|
246
303
|
self.intf.bInterfaceNumber,
|
|
247
304
|
None
|
|
248
305
|
)
|
|
306
|
+
logger.debug("Interface reset command sent.")
|
|
249
307
|
|
|
250
308
|
def get_command_status(self) -> dict:
|
|
309
|
+
logger.debug("Getting command status...")
|
|
251
310
|
data = self.dev.ctrl_transfer(
|
|
252
311
|
ControlRequest.BMREQ_GET_STATUS,
|
|
253
312
|
ControlRequest.REQ_GET_COMMAND_STATUS,
|
|
@@ -255,6 +314,7 @@ class PicoBoot:
|
|
|
255
314
|
self.intf.bInterfaceNumber,
|
|
256
315
|
16,
|
|
257
316
|
)
|
|
317
|
+
logger.debug(f"Command status data: {hexlify(data).decode()}")
|
|
258
318
|
b = bytes(data)
|
|
259
319
|
dToken, dStatusCode = struct.unpack_from("<II", b, 0)
|
|
260
320
|
bCmdId = b[8]
|
|
@@ -302,9 +362,10 @@ class PicoBoot:
|
|
|
302
362
|
transfer_length = 0 if data_out is None else len(data_out)
|
|
303
363
|
|
|
304
364
|
token, header = self._build_command(cmd_id, args=args, transfer_length=transfer_length)
|
|
305
|
-
|
|
365
|
+
logger.debug(f"Sending command {cmd_id} (0x{cmd_id:02X}) with token {token} (0x{token:08X}) and transfer_length {transfer_length}")
|
|
306
366
|
|
|
307
367
|
self.ep_out.write(header, timeout=timeout)
|
|
368
|
+
logger.debug(f"Command header sent: {hexlify(header).decode()}")
|
|
308
369
|
|
|
309
370
|
data_in = b""
|
|
310
371
|
|
|
@@ -321,63 +382,79 @@ class PicoBoot:
|
|
|
321
382
|
remaining -= len(chunk)
|
|
322
383
|
data_in = b"".join(chunks)
|
|
323
384
|
if len(data_in) != transfer_length:
|
|
385
|
+
logger.error(f"Expected {transfer_length} bytes, got {len(data_in)}")
|
|
324
386
|
raise PicoBootError(f"Expected {transfer_length} bytes, got {len(data_in)}")
|
|
325
387
|
else:
|
|
326
388
|
if data_out is None or len(data_out) < transfer_length:
|
|
389
|
+
logger.error("data_out missing or too short for OUT command")
|
|
327
390
|
raise ValueError("data_out missing or too short for OUT command")
|
|
328
391
|
self.ep_out.write(data_out[:transfer_length], timeout=timeout)
|
|
329
392
|
|
|
330
393
|
try:
|
|
394
|
+
logger.debug("Waiting for ACK...")
|
|
331
395
|
if is_in:
|
|
332
396
|
self.ep_out.write(b"", timeout=timeout)
|
|
333
397
|
else:
|
|
334
398
|
ack = self.ep_in.read(1, timeout=timeout)
|
|
335
399
|
except usb.core.USBError:
|
|
400
|
+
logger.error("No ACK received after command")
|
|
336
401
|
raise PicoBootError("No ACK received after command")
|
|
337
402
|
|
|
338
403
|
return data_in
|
|
339
404
|
|
|
340
405
|
|
|
341
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")
|
|
342
408
|
if addr % 4096 != 0 or size % 4096 != 0:
|
|
409
|
+
logger.error("addr i size must be aligned to 4kB")
|
|
343
410
|
raise ValueError("addr i size must be aligned to 4kB")
|
|
344
411
|
args = struct.pack("<II", addr, size)
|
|
345
412
|
self._send_command(CommandID.FLASH_ERASE, args=args, transfer_length=0)
|
|
346
413
|
|
|
347
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")
|
|
348
416
|
args = struct.pack("<II", addr, size)
|
|
349
417
|
data = self._send_command(CommandID.READ, args=args, transfer_length=size)
|
|
350
418
|
if len(data) != size:
|
|
419
|
+
logger.error(f"READ returned {len(data)} bytes, expected {size}")
|
|
351
420
|
raise PicoBootError(f"READ returned {len(data)} bytes, expected {size}")
|
|
352
421
|
return data
|
|
353
422
|
|
|
354
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")
|
|
355
425
|
if addr % 256 != 0 or len(data) % 256 != 0:
|
|
426
|
+
logger.error("addr i len(data) must be aligned/multiple of 256 bytes")
|
|
356
427
|
raise ValueError("addr i len(data) must be aligned/multiple of 256 bytes")
|
|
357
428
|
args = struct.pack("<II", addr, len(data))
|
|
358
429
|
self._send_command(CommandID.WRITE, args=args, data_out=data, transfer_length=len(data))
|
|
359
430
|
|
|
360
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}")
|
|
361
433
|
args = struct.pack("<III", pc, sp, delay_ms)
|
|
362
434
|
self._send_command(CommandID.REBOOT, args=args, transfer_length=0)
|
|
363
435
|
|
|
364
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}")
|
|
365
438
|
args = struct.pack("<IIII", flags, delay_ms, p0, p1)
|
|
366
439
|
self._send_command(CommandID.REBOOT2, args=args, transfer_length=0)
|
|
367
440
|
|
|
368
441
|
def reboot(self, delay_ms: int = 100) -> None:
|
|
442
|
+
logger.debug(f"Rebooting device with delay_ms={delay_ms}")
|
|
369
443
|
if (self.model == Model.RP2040):
|
|
370
444
|
self.reboot1(delay_ms=delay_ms)
|
|
371
445
|
elif (self.model == Model.RP2350):
|
|
372
446
|
self.reboot2(delay_ms=delay_ms)
|
|
373
447
|
|
|
374
448
|
def exit_xip(self) -> None:
|
|
449
|
+
logger.debug("Exiting XIP mode...")
|
|
375
450
|
self._send_command(CommandID.EXIT_XIP, transfer_length=0)
|
|
376
451
|
|
|
377
452
|
def exclusive_access(self) -> None:
|
|
453
|
+
logger.debug("Requesting exclusive access to flash...")
|
|
378
454
|
self._send_command(CommandID.EXCLUSIVE_ACCESS, args=struct.pack("<B", 1), transfer_length=0)
|
|
379
455
|
|
|
380
456
|
def _determine_model(self) -> str:
|
|
457
|
+
logger.debug("Determining device model...")
|
|
381
458
|
if (hasattr(self, "_model")) and (self._model is not None):
|
|
382
459
|
return self._model
|
|
383
460
|
data = self.flash_read(Addresses.BOOTROM_MAGIC, 4)
|
|
@@ -389,6 +466,7 @@ class PicoBoot:
|
|
|
389
466
|
return self._model
|
|
390
467
|
|
|
391
468
|
def _guess_flash_size(self) -> int:
|
|
469
|
+
logger.debug("Guessing flash size...")
|
|
392
470
|
if (hasattr(self, "_memory")) and (self._memory is not None):
|
|
393
471
|
return self._memory
|
|
394
472
|
FLASH_BASE = 0x10000000
|
|
@@ -427,6 +505,7 @@ class PicoBoot:
|
|
|
427
505
|
return self._memory
|
|
428
506
|
|
|
429
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}")
|
|
430
509
|
args = struct.pack("<IIII", info_type, param0, 0, 0)
|
|
431
510
|
data = self._send_command(CommandID.GET_INFO, args=args, transfer_length=max_len)
|
|
432
511
|
return data
|
|
@@ -454,6 +533,7 @@ class PicoBoot:
|
|
|
454
533
|
}
|
|
455
534
|
|
|
456
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}")
|
|
457
537
|
data = self.get_info(InfoType.SYS, param0=flags, max_len=256)
|
|
458
538
|
if len(data) < 24:
|
|
459
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
|