Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -187,7 +187,7 @@ adapter (it needs to toggle, whereas `BOOT0` does not).

## Not currently supported

* Command-line argument for write protection/unprotection.
* Command-line argument for write protection.
* STM8 devices (ST `UM0560`).
* Other bootloader protocols (e.g. I2C, HEX -> implemented in `stm32flash`).

Expand Down
4 changes: 4 additions & 0 deletions src/stm32loader/args.py
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,10 @@ def parse_arguments(arguments):
"-x", "--protect", action="store_true", help="Protect flash against readout."
)

parser.add_argument(
"--write-unprotect", action="store_true", help="Disable write protection before flashing"
)

parser.add_argument("-w", "--write", action="store_true", help="Write file content to flash.")

parser.add_argument(
Expand Down
4 changes: 4 additions & 0 deletions src/stm32loader/bootloader.py
Original file line number Diff line number Diff line change
Expand Up @@ -772,8 +772,12 @@ def write_protect(self, pages):

def write_unprotect(self):
"""Disable write protection of the flash memory."""
self.debug(10, "Disabling write protection")
self.command(self.Command.WRITE_UNPROTECT, "Write unprotect")
self._wait_for_ack("0x73 write unprotect failed")

time.sleep(0.1)
self.reset_from_system_memory()
self.debug(10, " Write Unprotect done")

def readout_protect(self):
Expand Down
13 changes: 11 additions & 2 deletions src/stm32loader/emulated/fake.py
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,10 @@ def receive(self):
command_bytes = yield
command_value = struct.unpack("B", command_bytes)[0]

# No CRC is sent for SYNCHRONIZE
if command_value == self.Command.SYNCHRONIZE:
continue

# Receive CRC byte.
yield
self.ack()
Expand Down Expand Up @@ -112,8 +116,12 @@ def receive(self):
# Record data in flash memory.
flash_offset = address - 0x_0800_0000
self.flash_memory[flash_offset : flash_offset + byte_count] = data

elif command_value == self.Command.WRITE_UNPROTECT.value:
pass

else:
raise NotImplementedError()
raise NotImplementedError(hex(command_value))

def write(self, data):
# Send to coroutine.
Expand All @@ -132,11 +140,12 @@ def read(self, length=1): # pylint: disable=unused-argument
class FakeConfiguration:
"""Represent a configuration for test purposes."""

def __init__(self, erase, write, verify, firmware_file, family=None):
def __init__(self, erase, write, verify, write_unprotect, firmware_file, family=None):
self.erase = erase
self.write = write
self.verify = verify
self.data_file = firmware_file
self.write_unprotect = write_unprotect
self.unprotect = False
self.protect = False
self.length = None
Expand Down
10 changes: 10 additions & 0 deletions src/stm32loader/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,16 @@ def perform_commands(self):
self.debug(0, "Quit")
self.stm32.reset_from_flash()
sys.exit(1)

if self.configuration.write_unprotect:
try:
self.stm32.write_unprotect()
except bootloader.CommandError:
self.debug(0, "Flash write unprotect failed")
self.debug(0, "Quit")
self.stm32.reset_from_flash()
sys.exit(1)

if self.configuration.erase:
try:
if self.configuration.length is None:
Expand Down
7 changes: 6 additions & 1 deletion tests/integration/test_full_cycle.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,12 @@
def test_erase_write_verify_passes():
loader = Stm32Loader()
loader.configuration = FakeConfiguration(
erase=True, write=True, verify=True, firmware_file=FIRMWARE_FILE, family=None
erase=True,
write=True,
verify=True,
write_unprotect=True,
firmware_file=FIRMWARE_FILE,
family=None,
)
loader.connection = FakeConnection()
loader.stm32 = Stm32Bootloader(loader.connection, device_family="F1", verbosity=5)
Expand Down
4 changes: 4 additions & 0 deletions tests/unit/test_arguments.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,3 +43,7 @@ def test_parse_arguments_erase_without_port_complains_about_missing_argument(pro
pytest.skip("Not sure why nothing is captured in some pytest runs?")
assert "arguments are required: -p/--port" in error_output
assert "STM32LOADER_SERIAL_PORT" in error_output


def test_parse_arguments_write_unprotect(program):
program.parse_arguments(["--write-unprotect"])
8 changes: 8 additions & 0 deletions tests/unit/test_bootloader.py
Original file line number Diff line number Diff line change
Expand Up @@ -205,6 +205,14 @@ def test_write_protect_sends_page_addresses_and_checksum(bootloader, write):
assert write.data_was_written(b"\x01\x08\x08")


def test_write_unprotect_sends_command(bootloader, write):
bootloader.write_unprotect()
assert write.data_was_written(b"\x73"), write.written_data
assert write.data_was_written(bytearray([Stm32Bootloader.Command.SYNCHRONIZE])), (
write.written_data
)


def test_verify_data_with_identical_data_passes():
Stm32Bootloader.verify_data(b"\x05", b"\x05")

Expand Down