From 94cd864dba9d9451e6f2892bdbcddbca8c3499d7 Mon Sep 17 00:00:00 2001 From: Ralph Lembcke Date: Tue, 10 Mar 2020 21:26:04 +0100 Subject: [PATCH 1/6] Fix some typos in the comments --- src/hw_ostc3.c | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/hw_ostc3.c b/src/hw_ostc3.c index 58e18ea..b2cd152 100644 --- a/src/hw_ostc3.c +++ b/src/hw_ostc3.c @@ -113,8 +113,8 @@ typedef struct hw_ostc3_firmware_t { unsigned int checksum; } hw_ostc3_firmware_t; -// This key is used both for the Ostc3 and its cousin, -// the Ostc Sport. +// This key is used both for the OSTC3 and its cousin, +// the OSTC Sport. // The Frog uses a similar protocol, and with another key. static const unsigned char ostc3_key[16] = { 0xF1, 0xE9, 0xB0, 0x30, @@ -317,7 +317,7 @@ hw_ostc3_transfer (hw_ostc3_device_t *device, } if (output) { - // Read the ouput data packet. + // Read the output data packet. status = hw_ostc3_read (device, progress, output, osize); if (status != DC_STATUS_SUCCESS) { ERROR (abstract->context, "Failed to receive the answer."); @@ -1231,7 +1231,7 @@ hw_ostc3_firmware_block_write (hw_ostc3_device_t *device, unsigned int addr, con { unsigned char buffer[3 + SZ_FIRMWARE_BLOCK]; - // We currenty only support writing max SZ_FIRMWARE_BLOCK sized blocks. + // We currently only support writing max SZ_FIRMWARE_BLOCK sized blocks. if (block_size > SZ_FIRMWARE_BLOCK) return DC_STATUS_INVALIDARGS; @@ -1599,7 +1599,7 @@ hw_ostc3_device_dump (dc_device_t *abstract, dc_buffer_t *buffer) unsigned int nbytes = 0; while (nbytes < SZ_MEMORY) { - // packet size. Can be almost arbetary size. + // packet size. Can be almost arbitrary size. unsigned int len = SZ_FIRMWARE_BLOCK; // Read a block From d1b865d192afc9efde337b5cff8a239366f15565 Mon Sep 17 00:00:00 2001 From: Jef Driesen Date: Tue, 10 Mar 2020 21:48:36 +0100 Subject: [PATCH 2/6] Send the service init command one byte at a time The hwOS firmware reads the service init command one byte at a time, and sends the echo immediately after each byte. Reported-by: Ralph Lembcke --- src/hw_ostc3.c | 53 ++++++++++++++++++++++++++++++-------------------- 1 file changed, 32 insertions(+), 21 deletions(-) diff --git a/src/hw_ostc3.c b/src/hw_ostc3.c index b2cd152..81fd2e0 100644 --- a/src/hw_ostc3.c +++ b/src/hw_ostc3.c @@ -69,6 +69,7 @@ #define S_UPLOAD 0x73 #define WRITE 0x77 #define RESET 0x78 +#define S_INIT 0xAA #define INIT 0xBB #define EXIT 0xFF @@ -456,33 +457,43 @@ hw_ostc3_device_init_service (hw_ostc3_device_t *device) { dc_status_t status = DC_STATUS_SUCCESS; dc_device_t *abstract = (dc_device_t *) device; - dc_context_t *context = (abstract ? abstract->context : NULL); - unsigned char command[] = {0xAA, 0xAB, 0xCD, 0xEF}; - unsigned char output[5]; + const unsigned char command[] = {S_INIT, 0xAB, 0xCD, 0xEF}; + unsigned char answer[5] = {0}; - // We cant use hw_ostc3_transfer here, due to the different echos - status = hw_ostc3_write (device, NULL, command, sizeof (command)); + for (size_t i = 0; i < 4; ++i) { + // Send the command. + status = hw_ostc3_write (device, NULL, command + i, 1); + if (status != DC_STATUS_SUCCESS) { + ERROR (abstract->context, "Failed to send the command."); + return status; + } + + // Read the answer. + status = hw_ostc3_read (device, NULL, answer + i, 1); + if (status != DC_STATUS_SUCCESS) { + ERROR (abstract->context, "Failed to receive the answer."); + return status; + } + + // Verify the answer. + const unsigned char expected = (i == 0 ? 0x4B : command[i]); + if (answer[i] != expected) { + ERROR (abstract->context, "Unexpected answer byte."); + return DC_STATUS_PROTOCOL; + } + } + + // Read the ready byte. + status = hw_ostc3_read (device, NULL, answer + 4, 1); if (status != DC_STATUS_SUCCESS) { - ERROR (context, "Failed to send the command."); + ERROR (abstract->context, "Failed to receive the ready byte."); return status; } - // Give the device some time to enter service mode - dc_iostream_sleep (device->iostream, 100); - - // Read the response - status = hw_ostc3_read (device, NULL, output, sizeof (output)); - if (status != DC_STATUS_SUCCESS) { - ERROR (context, "Failed to receive the echo."); - return status; - } - - // Verify the response to service mode - if (output[0] != 0x4B || output[1] != 0xAB || - output[2] != 0xCD || output[3] != 0xEF || - output[4] != S_READY) { - ERROR (context, "Failed to verify echo."); + // Verify the ready byte. + if (answer[4] != S_READY) { + ERROR (abstract->context, "Unexpected ready byte."); return DC_STATUS_PROTOCOL; } From 7b9b6b4005dce9f2fac1028648a6b8dee930dcd0 Mon Sep 17 00:00:00 2001 From: Jef Driesen Date: Tue, 10 Mar 2020 22:02:12 +0100 Subject: [PATCH 3/6] Add an extra delay after erasing a flash memory page Erasing a flash memory page is a relative slow operation and takes a significant amount of time. Therefore, the ready byte is delayed, and the standard timeout is no longer sufficient. Estimate the required delay and wait. Reported-by: Ralph Lembcke --- src/hw_ostc3.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/hw_ostc3.c b/src/hw_ostc3.c index 81fd2e0..552ed54 100644 --- a/src/hw_ostc3.c +++ b/src/hw_ostc3.c @@ -1219,12 +1219,16 @@ hw_ostc3_firmware_erase (hw_ostc3_device_t *device, unsigned int addr, unsigned // Convert size to number of pages, rounded up. unsigned char blocks = ((size + SZ_FIRMWARE_BLOCK - 1) / SZ_FIRMWARE_BLOCK); + // Estimate the required delay. Erasing a 4K flash memory page + // takes around 25 milliseconds. + unsigned int delay = blocks * 25; + // Erase just the needed pages. unsigned char buffer[4]; array_uint24_be_set (buffer, addr); buffer[3] = blocks; - return hw_ostc3_transfer (device, NULL, S_ERASE, buffer, sizeof (buffer), NULL, 0, NODELAY); + return hw_ostc3_transfer (device, NULL, S_ERASE, buffer, sizeof (buffer), NULL, 0, delay); } static dc_status_t From da4a8a90c77a5f3e4633a92d7820213517927e58 Mon Sep 17 00:00:00 2001 From: Jef Driesen Date: Tue, 10 Mar 2020 22:58:48 +0100 Subject: [PATCH 4/6] Add an extra delay after writing to the flash memory The S_BLOCK_WRITE (0x30) command sends a stream of bytes to the dive computer. Because the payload has no fixed length and there is no length field included, the hwOS firmware detects the end of the stream by means of a 400ms timeout. Therefore the ready byte is always delayed by this 400ms timeout. The same remark applies to the DISPLAY (0x6E) and CUSTOMTEXT (0x63) commands. But because libdivecomputer always pad the text with zeros and sends the maximum payload size, we won't hit the timeout. Reported-by: Ralph Lembcke --- src/hw_ostc3.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/hw_ostc3.c b/src/hw_ostc3.c index 552ed54..0b980e7 100644 --- a/src/hw_ostc3.c +++ b/src/hw_ostc3.c @@ -81,6 +81,7 @@ #define CR 0x05 #define NODELAY 0 +#define TIMEOUT 400 typedef enum hw_ostc3_state_t { OPEN, @@ -1253,7 +1254,7 @@ hw_ostc3_firmware_block_write (hw_ostc3_device_t *device, unsigned int addr, con array_uint24_be_set (buffer, addr); memcpy (buffer + 3, block, block_size); - return hw_ostc3_transfer (device, NULL, S_BLOCK_WRITE, buffer, 3 + block_size, NULL, 0, NODELAY); + return hw_ostc3_transfer (device, NULL, S_BLOCK_WRITE, buffer, 3 + block_size, NULL, 0, TIMEOUT); } static dc_status_t From dff6d0c5144740ea47b92293306a3bb03a2a62b7 Mon Sep 17 00:00:00 2001 From: Jef Driesen Date: Thu, 12 Mar 2020 21:41:17 +0100 Subject: [PATCH 5/6] Read and cache the firmware version information By reading the firmware version information immediately after entering download or service mode, we can identify the specific firmware version and adapt to minor differences in the communication protocol. --- src/hw_ostc3.c | 34 ++++++++++++++++++++-------------- 1 file changed, 20 insertions(+), 14 deletions(-) diff --git a/src/hw_ostc3.c b/src/hw_ostc3.c index 0b980e7..e348514 100644 --- a/src/hw_ostc3.c +++ b/src/hw_ostc3.c @@ -96,6 +96,8 @@ typedef struct hw_ostc3_device_t { unsigned int hardware; unsigned int feature; unsigned int model; + unsigned int serial; + unsigned int firmware; unsigned char fingerprint[5]; hw_ostc3_state_t state; unsigned char cache[20]; @@ -372,6 +374,8 @@ hw_ostc3_device_open (dc_device_t **out, dc_context_t *context, dc_iostream_t *i device->hardware = INVALID; device->feature = 0; device->model = 0; + device->serial = 0; + device->firmware = 0; memset (device->fingerprint, 0, sizeof (device->fingerprint)); memset (device->cache, 0, sizeof (device->cache)); device->available = 0; @@ -546,10 +550,24 @@ hw_ostc3_device_init (hw_ostc3_device_t *device, hw_ostc3_state_t state) return rc; } + // Read the version information. + unsigned char version[SZ_VERSION] = {0}; + rc = hw_ostc3_transfer (device, NULL, IDENTITY, NULL, 0, version, sizeof(version), NODELAY); + if (rc != DC_STATUS_SUCCESS) { + ERROR (abstract->context, "Failed to read the version information."); + return rc; + } + // Cache the descriptor. device->hardware = array_uint16_be(hardware + 0); device->feature = array_uint16_be(hardware + 2); device->model = hardware[4]; + device->serial = array_uint16_le (version + 0); + if (device->hardware == OSTC4) { + device->firmware = array_uint16_le (version + 2); + } else { + device->firmware = array_uint16_be (version + 2); + } return DC_STATUS_SUCCESS; } @@ -654,22 +672,10 @@ hw_ostc3_device_foreach (dc_device_t *abstract, dc_dive_callback_t callback, voi if (rc != DC_STATUS_SUCCESS) return rc; - // Download the version data. - unsigned char id[SZ_VERSION] = {0}; - rc = hw_ostc3_device_version (abstract, id, sizeof (id)); - if (rc != DC_STATUS_SUCCESS) { - ERROR (abstract->context, "Failed to read the version."); - return rc; - } - // Emit a device info event. dc_event_devinfo_t devinfo; - if (device->hardware == OSTC4) { - devinfo.firmware = array_uint16_le (id + 2); - } else { - devinfo.firmware = array_uint16_be (id + 2); - } - devinfo.serial = array_uint16_le (id + 0); + devinfo.firmware = device->firmware; + devinfo.serial = device->serial; if (device->hardware != UNKNOWN) { devinfo.model = device->hardware; } else { From 9e92381be48866f3f13a11e98d59962575bb5ad3 Mon Sep 17 00:00:00 2001 From: Jef Driesen Date: Thu, 12 Mar 2020 22:09:50 +0100 Subject: [PATCH 6/6] Use a more robust command to write flash memory The S_BLOCK_WRITE (0x30) command sends a stream of bytes to the dive computer. Because the payload has no fixed length and there is no length field included, the hwOS firmware detects the end of the stream by means of a 400ms timeout. The main disadvantage of this approach is that a short hiccup in the communication will be incorrectly detected as the end of the stream. Hence only a part of the data will get written to the flash memory, and the remainder of the data will get interpreted as the next commands. To avoid this problem, the hwOS firmware v3.09 and later supports a new S_BLOCK_WRITE2 (0x31) command, which uses a fixed size payload of 256 bytes. Reported-by: Ralph Lembcke --- src/hw_ostc3.c | 42 +++++++++++++++++++++++++++++++++++++++++- 1 file changed, 41 insertions(+), 1 deletion(-) diff --git a/src/hw_ostc3.c b/src/hw_ostc3.c index e348514..3e01f88 100644 --- a/src/hw_ostc3.c +++ b/src/hw_ostc3.c @@ -43,6 +43,7 @@ #define SZ_FWINFO 4 #define SZ_FIRMWARE 0x01E000 // 120KB #define SZ_FIRMWARE_BLOCK 0x1000 // 4KB +#define SZ_FIRMWARE_BLOCK2 0x0100 // 256B #define FIRMWARE_AREA 0x3E0000 #define RB_LOGBOOK_SIZE_COMPACT 16 @@ -51,6 +52,7 @@ #define S_BLOCK_READ 0x20 #define S_BLOCK_WRITE 0x30 +#define S_BLOCK_WRITE2 0x31 #define S_ERASE 0x42 #define S_READY 0x4C #define READY 0x4D @@ -1249,7 +1251,7 @@ hw_ostc3_firmware_block_read (hw_ostc3_device_t *device, unsigned int addr, unsi } static dc_status_t -hw_ostc3_firmware_block_write (hw_ostc3_device_t *device, unsigned int addr, const unsigned char block[], unsigned int block_size) +hw_ostc3_firmware_block_write1 (hw_ostc3_device_t *device, unsigned int addr, const unsigned char block[], unsigned int block_size) { unsigned char buffer[3 + SZ_FIRMWARE_BLOCK]; @@ -1263,6 +1265,44 @@ hw_ostc3_firmware_block_write (hw_ostc3_device_t *device, unsigned int addr, con return hw_ostc3_transfer (device, NULL, S_BLOCK_WRITE, buffer, 3 + block_size, NULL, 0, TIMEOUT); } +static dc_status_t +hw_ostc3_firmware_block_write2 (hw_ostc3_device_t *device, unsigned int address, const unsigned char data[], unsigned int size) +{ + dc_status_t status = DC_STATUS_SUCCESS; + + if ((address % SZ_FIRMWARE_BLOCK2 != 0) || + (size % SZ_FIRMWARE_BLOCK2 != 0)) { + return DC_STATUS_INVALIDARGS; + } + + unsigned int nbytes = 0; + while (nbytes < size) { + unsigned char buffer[3 + SZ_FIRMWARE_BLOCK2]; + array_uint24_be_set (buffer, address); + memcpy (buffer + 3, data + nbytes, SZ_FIRMWARE_BLOCK2); + + status = hw_ostc3_transfer (device, NULL, S_BLOCK_WRITE2, buffer, sizeof(buffer), NULL, 0, NODELAY); + if (status != DC_STATUS_SUCCESS) { + return status; + } + + address += SZ_FIRMWARE_BLOCK2; + nbytes += SZ_FIRMWARE_BLOCK2; + } + + return DC_STATUS_SUCCESS; +} + +static dc_status_t +hw_ostc3_firmware_block_write (hw_ostc3_device_t *device, unsigned int address, const unsigned char data[], unsigned int size) +{ + if (device->firmware >= 0x0309) { + return hw_ostc3_firmware_block_write2 (device, address, data, size); + } else { + return hw_ostc3_firmware_block_write1 (device, address, data, size); + } +} + static dc_status_t hw_ostc3_firmware_upgrade (dc_device_t *abstract, unsigned int checksum) {