diff --git a/gp2040ce_bintools/storage.py b/gp2040ce_bintools/storage.py index 78fde59..77680ad 100644 --- a/gp2040ce_bintools/storage.py +++ b/gp2040ce_bintools/storage.py @@ -17,6 +17,7 @@ from gp2040ce_bintools.rp2040 import get_bootsel_endpoints, read logger = logging.getLogger(__name__) BOARD_CONFIG_BINARY_LOCATION = 0x1F8000 +BOARD_CONFIG_BOOTSEL_ADDRESS = 0x10000000 + BOARD_CONFIG_BINARY_LOCATION STORAGE_SIZE = 16384 USER_CONFIG_BINARY_LOCATION = 0x1FC000 USER_CONFIG_BOOTSEL_ADDRESS = 0x10000000 + USER_CONFIG_BINARY_LOCATION @@ -124,13 +125,15 @@ def get_config_footer(content: bytes) -> tuple[int, int, str]: return config_size, config_crc, config_magic -def get_config_from_file(filename: str, whole_board: bool = False, allow_no_file: bool = False) -> Message: +def get_config_from_file(filename: str, whole_board: bool = False, allow_no_file: bool = False, + board_config: bool = False) -> Message: """Read the specified file (memory dump or whole board dump) and get back its config section. Args: filename: the filename of the file to open and read whole_board: optional, if true, attempt to find the storage section from its normal location on a board allow_no_file: if true, attempting to open a nonexistent file returns an empty config, else it errors + board_config: if true, the board config is provided instead of the user config Returns: the parsed configuration """ @@ -144,7 +147,10 @@ def get_config_from_file(filename: str, whole_board: bool = False, allow_no_file return config_pb2.Config() if whole_board: - return get_config(get_user_storage_section(content)) + if board_config: + return get_config(get_board_storage_section(content)) + else: + return get_config(get_user_storage_section(content)) else: return get_config(content) @@ -165,6 +171,15 @@ def get_config_from_usb(address: int) -> tuple[Message, object, object]: return get_config(bytes(storage)), endpoint_out, endpoint_in +def get_board_config_from_usb() -> tuple[Message, object, object]: + """Read the board configuration from the detected USB device. + + Returns: + the parsed configuration, along with the USB out and in endpoints for reference + """ + return get_config_from_usb(BOARD_CONFIG_BOOTSEL_ADDRESS) + + def get_user_config_from_usb() -> tuple[Message, object, object]: """Read the user configuration from the detected USB device. @@ -286,6 +301,8 @@ def visualize(): ) parser.add_argument('--whole-board', action='store_true', help="indicate the binary file is a whole board dump") parser.add_argument('--json', action='store_true', help="print the config out as a JSON document") + parser.add_argument('--board-config', action='store_true', default=False, + help="display the board config rather than the user config") group = parser.add_mutually_exclusive_group(required=True) group.add_argument('--usb', action='store_true', help="retrieve the config from a RP2040 board connected over USB " "and in BOOTSEL mode") @@ -295,9 +312,12 @@ def visualize(): args, _ = parser.parse_known_args() if args.usb: - config, _, _ = get_user_config_from_usb() + if args.board_config: + config, _, _ = get_board_config_from_usb() + else: + config, _, _ = get_user_config_from_usb() else: - config = get_config_from_file(args.filename, whole_board=args.whole_board) + config = get_config_from_file(args.filename, whole_board=args.whole_board, board_config=args.board_config) if args.json: print(MessageToJson(config)) diff --git a/tests/test-files/test-whole-board-with-board-config.bin b/tests/test-files/test-whole-board-with-board-config.bin new file mode 100644 index 0000000..973b52b Binary files /dev/null and b/tests/test-files/test-whole-board-with-board-config.bin differ diff --git a/tests/test_storage.py b/tests/test_storage.py index 48f6548..797a797 100644 --- a/tests/test_storage.py +++ b/tests/test_storage.py @@ -94,6 +94,15 @@ def test_get_config_from_file_whole_board_dump(): assert config.addonOptions.bootselButtonOptions.enabled is False +@with_pb2s +def test_get_board_config_from_file_whole_board_dump(): + """Test that we can open a storage dump file and find its config.""" + filename = os.path.join(HERE, 'test-files', 'test-whole-board-with-board-config.bin') + config = storage.get_config_from_file(filename, whole_board=True, board_config=True) + assert config.boardVersion == 'v0.7.6-15-g71f4512' + assert config.addonOptions.bootselButtonOptions.enabled is False + + @with_pb2s def test_get_config_from_file_file_not_fonud_ok(): """If we allow opening a file that doesn't exist (e.g. for the editor), check we get an empty config.""" @@ -164,6 +173,24 @@ def test_pad_config_to_storage_raises(config_binary): _ = storage.pad_config_to_storage_size(config_binary * 5) +@with_pb2s +def test_get_board_config_from_usb(config_binary): + """Test we attempt to read from the proper location over USB.""" + mock_out = mock.MagicMock() + mock_out.device.idVendor = 0xbeef + mock_out.device.idProduct = 0xcafe + mock_out.device.bus = 1 + mock_out.device.address = 2 + mock_in = mock.MagicMock() + with mock.patch('gp2040ce_bintools.storage.get_bootsel_endpoints', return_value=(mock_out, mock_in)) as mock_get: + with mock.patch('gp2040ce_bintools.storage.read', return_value=config_binary) as mock_read: + config, _, _ = storage.get_board_config_from_usb() + + mock_get.assert_called_once() + mock_read.assert_called_with(mock_out, mock_in, 0x101F8000, 16384) + assert config == storage.get_config(config_binary) + + @with_pb2s def test_get_user_config_from_usb(config_binary): """Test we attempt to read from the proper location over USB."""