/* * libdivecomputer * * Copyright (C) 2013 Jef Driesen * Copyright (C) 2014 Anton Lundin * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, * MA 02110-1301 USA */ #include // memcmp, memcpy #include // malloc, free #include // FILE, fopen #include #include "context-private.h" #include "device-private.h" #include "serial.h" #include "array.h" #include "aes.h" #ifdef _MSC_VER #define snprintf _snprintf #endif #define ISINSTANCE(device) dc_device_isinstance((device), &hw_ostc3_device_vtable) #define SZ_DISPLAY 16 #define SZ_CUSTOMTEXT 60 #define SZ_VERSION (SZ_CUSTOMTEXT + 4) #define SZ_HARDWARE 1 #define SZ_HARDWARE2 5 #define SZ_MEMORY 0x400000 #define SZ_CONFIG 4 #define SZ_FWINFO 4 #define SZ_FIRMWARE 0x01E000 // 120KB #define SZ_FIRMWARE_BLOCK 0x1000 // 4KB #define FIRMWARE_AREA 0x3E0000 #define RB_LOGBOOK_SIZE_COMPACT 16 #define RB_LOGBOOK_SIZE_FULL 256 #define RB_LOGBOOK_COUNT 256 #define S_BLOCK_READ 0x20 #define S_BLOCK_WRITE 0x30 #define S_ERASE 0x42 #define S_READY 0x4C #define READY 0x4D #define S_UPGRADE 0x50 #define HARDWARE2 0x60 #define HEADER 0x61 #define CLOCK 0x62 #define CUSTOMTEXT 0x63 #define DIVE 0x66 #define IDENTITY 0x69 #define HARDWARE 0x6A #define S_FWINFO 0x6B #define DISPLAY 0x6E #define COMPACT 0x6D #define READ 0x72 #define S_UPLOAD 0x73 #define WRITE 0x77 #define RESET 0x78 #define INIT 0xBB #define EXIT 0xFF #define INVALID 0xFFFFFFFF #define UNKNOWN 0x00 #define OSTC3 0x0A #define OSTC4 0x3B #define SPORT 0x12 #define CR 0x05 #define NODELAY 0 typedef enum hw_ostc3_state_t { OPEN, DOWNLOAD, SERVICE, REBOOTING, } hw_ostc3_state_t; typedef struct hw_ostc3_device_t { dc_device_t base; dc_serial_t *port; unsigned int hardware; unsigned int feature; unsigned int model; unsigned char fingerprint[5]; hw_ostc3_state_t state; } hw_ostc3_device_t; typedef struct hw_ostc3_logbook_t { unsigned int size; unsigned int profile; unsigned int fingerprint; unsigned int number; } hw_ostc3_logbook_t; typedef struct hw_ostc3_firmware_t { unsigned char data[SZ_FIRMWARE]; unsigned int checksum; } hw_ostc3_firmware_t; // 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, 0x45, 0x6F, 0xBE, 0x55, 0xFF, 0xE7, 0xF8, 0x31, 0x13, 0x6C, 0xF2, 0xFE }; static dc_status_t hw_ostc3_device_set_fingerprint (dc_device_t *abstract, const unsigned char data[], unsigned int size); static dc_status_t hw_ostc3_device_dump (dc_device_t *abstract, dc_buffer_t *buffer); static dc_status_t hw_ostc3_device_foreach (dc_device_t *abstract, dc_dive_callback_t callback, void *userdata); static dc_status_t hw_ostc3_device_close (dc_device_t *abstract); static const dc_device_vtable_t hw_ostc3_device_vtable = { sizeof(hw_ostc3_device_t), DC_FAMILY_HW_OSTC3, hw_ostc3_device_set_fingerprint, /* set_fingerprint */ NULL, /* read */ NULL, /* write */ hw_ostc3_device_dump, /* dump */ hw_ostc3_device_foreach, /* foreach */ hw_ostc3_device_close /* close */ }; static const hw_ostc3_logbook_t hw_ostc3_logbook_compact = { RB_LOGBOOK_SIZE_COMPACT, /* size */ 0, /* profile */ 3, /* fingerprint */ 13, /* number */ }; static const hw_ostc3_logbook_t hw_ostc3_logbook_full = { RB_LOGBOOK_SIZE_FULL, /* size */ 9, /* profile */ 12, /* fingerprint */ 80, /* number */ }; static int hw_ostc3_strncpy (unsigned char *data, unsigned int size, const char *text) { // Check the maximum length. size_t length = (text ? strlen (text) : 0); if (length > size) { return -1; } // Copy the text. if (length) memcpy (data, text, length); // Pad with spaces. memset (data + length, 0x20, size - length); return 0; } static dc_status_t hw_ostc3_transfer (hw_ostc3_device_t *device, dc_event_progress_t *progress, unsigned char cmd, const unsigned char input[], unsigned int isize, unsigned char output[], unsigned int osize, unsigned int delay) { dc_device_t *abstract = (dc_device_t *) device; dc_status_t status = DC_STATUS_SUCCESS; if (device_is_cancelled (abstract)) return DC_STATUS_CANCELLED; // Get the correct ready byte for the current state. const unsigned char ready = (device->state == SERVICE ? S_READY : READY); // Send the command. unsigned char command[1] = {cmd}; status = dc_serial_write (device->port, command, sizeof (command), NULL); if (status != DC_STATUS_SUCCESS) { ERROR (abstract->context, "Failed to send the command."); return status; } // Read the echo. unsigned char echo[1] = {0}; status = dc_serial_read (device->port, echo, sizeof (echo), NULL); if (status != DC_STATUS_SUCCESS) { ERROR (abstract->context, "Failed to receive the echo."); return status; } // Verify the echo. if (memcmp (echo, command, sizeof (command)) != 0) { if (echo[0] == ready) { ERROR (abstract->context, "Unsupported command."); return DC_STATUS_UNSUPPORTED; } else { ERROR (abstract->context, "Unexpected echo."); return DC_STATUS_PROTOCOL; } } if (input) { // Send the input data packet. unsigned int nbytes = 0; while (nbytes < isize) { // Set the minimum packet size. unsigned int len = 64; // Limit the packet size to the total size. if (nbytes + len > isize) len = isize - nbytes; // Write the packet. status = dc_serial_write (device->port, input + nbytes, len, NULL); if (status != DC_STATUS_SUCCESS) { ERROR (abstract->context, "Failed to send the data packet."); return status; } // Update and emit a progress event. if (progress) { progress->current += len; device_event_emit ((dc_device_t *) device, DC_EVENT_PROGRESS, progress); } nbytes += len; } } if (output) { unsigned int nbytes = 0; while (nbytes < osize) { // Set the minimum packet size. unsigned int len = 1024; // Increase the packet size if more data is immediately available. size_t available = 0; status = dc_serial_get_available (device->port, &available); if (status == DC_STATUS_SUCCESS && available > len) len = available; // Limit the packet size to the total size. if (nbytes + len > osize) len = osize - nbytes; // Read the packet. status = dc_serial_read (device->port, output + nbytes, len, NULL); if (status != DC_STATUS_SUCCESS) { ERROR (abstract->context, "Failed to receive the answer."); return status; } // Update and emit a progress event. if (progress) { progress->current += len; device_event_emit ((dc_device_t *) device, DC_EVENT_PROGRESS, progress); } nbytes += len; } } if (delay) { unsigned int count = delay / 100; for (unsigned int i = 0; i < count; ++i) { size_t available = 0; status = dc_serial_get_available (device->port, &available); if (status == DC_STATUS_SUCCESS && available > 0) break; dc_serial_sleep (device->port, 100); } } if (cmd != EXIT) { // Read the ready byte. unsigned char answer[1] = {0}; status = dc_serial_read (device->port, answer, sizeof (answer), NULL); if (status != DC_STATUS_SUCCESS) { ERROR (abstract->context, "Failed to receive the ready byte."); return status; } // Verify the ready byte. if (answer[0] != ready) { ERROR (abstract->context, "Unexpected ready byte."); return DC_STATUS_PROTOCOL; } } return DC_STATUS_SUCCESS; } dc_status_t hw_ostc3_device_open (dc_device_t **out, dc_context_t *context, const char *name) { dc_status_t status = DC_STATUS_SUCCESS; hw_ostc3_device_t *device = NULL; if (out == NULL) return DC_STATUS_INVALIDARGS; // Allocate memory. device = (hw_ostc3_device_t *) dc_device_allocate (context, &hw_ostc3_device_vtable); if (device == NULL) { ERROR (context, "Failed to allocate memory."); return DC_STATUS_NOMEMORY; } // Set the default values. device->port = NULL; device->hardware = INVALID; device->feature = 0; device->model = 0; memset (device->fingerprint, 0, sizeof (device->fingerprint)); // Open the device. status = dc_serial_open (&device->port, context, name); if (status != DC_STATUS_SUCCESS) { ERROR (context, "Failed to open the serial port."); goto error_free; } // Set the serial communication protocol (115200 8N1). status = dc_serial_configure (device->port, 115200, 8, DC_PARITY_NONE, DC_STOPBITS_ONE, DC_FLOWCONTROL_NONE); if (status != DC_STATUS_SUCCESS) { ERROR (context, "Failed to set the terminal attributes."); goto error_close; } // Set the timeout for receiving data (3000ms). status = dc_serial_set_timeout (device->port, 3000); if (status != DC_STATUS_SUCCESS) { ERROR (context, "Failed to set the timeout."); goto error_close; } // Make sure everything is in a sane state. dc_serial_sleep (device->port, 300); dc_serial_purge (device->port, DC_DIRECTION_ALL); device->state = OPEN; *out = (dc_device_t *) device; return DC_STATUS_SUCCESS; error_close: dc_serial_close (device->port); error_free: dc_device_deallocate ((dc_device_t *) device); return status; } static dc_status_t hw_ostc3_device_id (hw_ostc3_device_t *device, unsigned char data[], unsigned int size) { dc_status_t status = DC_STATUS_SUCCESS; if (size != SZ_HARDWARE && size != SZ_HARDWARE2) return DC_STATUS_INVALIDARGS; // Send the command. unsigned char hardware[SZ_HARDWARE2] = {0}; status = hw_ostc3_transfer (device, NULL, HARDWARE2, NULL, 0, hardware, SZ_HARDWARE2, NODELAY); if (status == DC_STATUS_UNSUPPORTED) { status = hw_ostc3_transfer (device, NULL, HARDWARE, NULL, 0, hardware + 1, SZ_HARDWARE, NODELAY); } if (status != DC_STATUS_SUCCESS) return status; if (size == SZ_HARDWARE2) { memcpy (data, hardware, SZ_HARDWARE2); } else { memcpy (data, hardware + 1, SZ_HARDWARE); } return DC_STATUS_SUCCESS; } static dc_status_t hw_ostc3_device_init_download (hw_ostc3_device_t *device) { dc_device_t *abstract = (dc_device_t *) device; dc_context_t *context = (abstract ? abstract->context : NULL); // Send the init command. dc_status_t status = hw_ostc3_transfer (device, NULL, INIT, NULL, 0, NULL, 0, NODELAY); if (status != DC_STATUS_SUCCESS) { ERROR (context, "Failed to send the command."); return status; } device->state = DOWNLOAD; return DC_STATUS_SUCCESS; } static dc_status_t 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]; // We cant use hw_ostc3_transfer here, due to the different echos status = dc_serial_write (device->port, command, sizeof (command), NULL); if (status != DC_STATUS_SUCCESS) { ERROR (context, "Failed to send the command."); return status; } // Give the device some time to enter service mode dc_serial_sleep (device->port, 100); // Read the response status = dc_serial_read (device->port, output, sizeof (output), NULL); 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."); return DC_STATUS_PROTOCOL; } device->state = SERVICE; return DC_STATUS_SUCCESS; } static dc_status_t hw_ostc3_device_init (hw_ostc3_device_t *device, hw_ostc3_state_t state) { dc_status_t rc = DC_STATUS_SUCCESS; dc_device_t *abstract = (dc_device_t *) device; if (device->state == state) { // No change. rc = DC_STATUS_SUCCESS; } else if (device->state == OPEN) { // Change to download or service mode. if (state == DOWNLOAD) { rc = hw_ostc3_device_init_download(device); } else if (state == SERVICE) { rc = hw_ostc3_device_init_service(device); } else { rc = DC_STATUS_INVALIDARGS; } } else if (device->state == SERVICE && state == DOWNLOAD) { // Switching between service and download mode is not possible. // But in service mode, all download commands are supported too, // so there is no need to change the state. rc = DC_STATUS_SUCCESS; } else { // Not supported. rc = DC_STATUS_INVALIDARGS; } if (rc != DC_STATUS_SUCCESS) return rc; if (device->hardware != INVALID) return DC_STATUS_SUCCESS; // Read the hardware descriptor. unsigned char hardware[SZ_HARDWARE2] = {0, UNKNOWN}; rc = hw_ostc3_device_id (device, hardware, sizeof(hardware)); if (rc != DC_STATUS_SUCCESS && rc != DC_STATUS_UNSUPPORTED) { ERROR (abstract->context, "Failed to read the hardware descriptor."); return rc; } // Cache the descriptor. device->hardware = array_uint16_be(hardware + 0); device->feature = array_uint16_be(hardware + 2); device->model = hardware[4]; return DC_STATUS_SUCCESS; } static dc_status_t hw_ostc3_device_close (dc_device_t *abstract) { dc_status_t status = DC_STATUS_SUCCESS; hw_ostc3_device_t *device = (hw_ostc3_device_t*) abstract; dc_status_t rc = DC_STATUS_SUCCESS; // Send the exit command if (device->state == DOWNLOAD || device->state == SERVICE) { rc = hw_ostc3_transfer (device, NULL, EXIT, NULL, 0, NULL, 0, NODELAY); if (rc != DC_STATUS_SUCCESS) { ERROR (abstract->context, "Failed to send the command."); dc_status_set_error(&status, rc); } } // Close the device. rc = dc_serial_close (device->port); if (rc != DC_STATUS_SUCCESS) { dc_status_set_error(&status, rc); } return status; } static dc_status_t hw_ostc3_device_set_fingerprint (dc_device_t *abstract, const unsigned char data[], unsigned int size) { hw_ostc3_device_t *device = (hw_ostc3_device_t *) abstract; if (size && size != sizeof (device->fingerprint)) return DC_STATUS_INVALIDARGS; if (size) memcpy (device->fingerprint, data, sizeof (device->fingerprint)); else memset (device->fingerprint, 0, sizeof (device->fingerprint)); return DC_STATUS_SUCCESS; } dc_status_t hw_ostc3_device_version (dc_device_t *abstract, unsigned char data[], unsigned int size) { hw_ostc3_device_t *device = (hw_ostc3_device_t *) abstract; if (!ISINSTANCE (abstract)) return DC_STATUS_INVALIDARGS; if (size != SZ_VERSION) return DC_STATUS_INVALIDARGS; dc_status_t rc = hw_ostc3_device_init (device, DOWNLOAD); if (rc != DC_STATUS_SUCCESS) return rc; // Send the command. rc = hw_ostc3_transfer (device, NULL, IDENTITY, NULL, 0, data, size, NODELAY); if (rc != DC_STATUS_SUCCESS) return rc; return DC_STATUS_SUCCESS; } dc_status_t hw_ostc3_device_hardware (dc_device_t *abstract, unsigned char data[], unsigned int size) { hw_ostc3_device_t *device = (hw_ostc3_device_t *) abstract; if (!ISINSTANCE (abstract)) return DC_STATUS_INVALIDARGS; if (size != SZ_HARDWARE && size != SZ_HARDWARE2) return DC_STATUS_INVALIDARGS; dc_status_t rc = hw_ostc3_device_init (device, DOWNLOAD); if (rc != DC_STATUS_SUCCESS) return rc; // Send the command. rc = hw_ostc3_device_id (device, data, size); if (rc != DC_STATUS_SUCCESS) return rc; return DC_STATUS_SUCCESS; } static dc_status_t hw_ostc3_device_foreach (dc_device_t *abstract, dc_dive_callback_t callback, void *userdata) { hw_ostc3_device_t *device = (hw_ostc3_device_t *) abstract; // Enable progress notifications. dc_event_progress_t progress = EVENT_PROGRESS_INITIALIZER; progress.maximum = SZ_MEMORY; device_event_emit (abstract, DC_EVENT_PROGRESS, &progress); dc_status_t rc = hw_ostc3_device_init (device, DOWNLOAD); 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); if (device->hardware != UNKNOWN) { devinfo.model = device->hardware; } else { // Fallback to the serial number. if (devinfo.serial > 10000) devinfo.model = SPORT; else devinfo.model = OSTC3; } device_event_emit (abstract, DC_EVENT_DEVINFO, &devinfo); // Allocate memory. unsigned char *header = (unsigned char *) malloc (RB_LOGBOOK_SIZE_FULL * RB_LOGBOOK_COUNT); if (header == NULL) { ERROR (abstract->context, "Failed to allocate memory."); return DC_STATUS_NOMEMORY; } // Download the compact logbook headers. If the firmware doesn't support // compact headers yet, fallback to downloading the full logbook headers. // This is slower, but also works for older firmware versions. unsigned int compact = 1; rc = hw_ostc3_transfer (device, &progress, COMPACT, NULL, 0, header, RB_LOGBOOK_SIZE_COMPACT * RB_LOGBOOK_COUNT, NODELAY); if (rc == DC_STATUS_UNSUPPORTED) { compact = 0; rc = hw_ostc3_transfer (device, &progress, HEADER, NULL, 0, header, RB_LOGBOOK_SIZE_FULL * RB_LOGBOOK_COUNT, NODELAY); } if (rc != DC_STATUS_SUCCESS) { ERROR (abstract->context, "Failed to read the header."); free (header); return rc; } // Get the correct logbook layout. const hw_ostc3_logbook_t *logbook = NULL; if (compact) { logbook = &hw_ostc3_logbook_compact; } else { logbook = &hw_ostc3_logbook_full; } // Locate the most recent dive. // The device maintains an internal counter which is incremented for every // dive, and the current value at the time of the dive is stored in the // dive header. Thus the most recent dive will have the highest value. unsigned int count = 0; unsigned int latest = 0; unsigned int maximum = 0; for (unsigned int i = 0; i < RB_LOGBOOK_COUNT; ++i) { unsigned int offset = i * logbook->size; // Ignore uninitialized header entries. if (array_isequal (header + offset, logbook->size, 0xFF)) continue; // Get the internal dive number. unsigned int current = array_uint16_le (header + offset + logbook->number); if (current > maximum) { maximum = current; latest = i; } count++; } // Calculate the total and maximum size. unsigned int ndives = 0; unsigned int size = 0; unsigned int maxsize = 0; for (unsigned int i = 0; i < count; ++i) { unsigned int idx = (latest + RB_LOGBOOK_COUNT - i) % RB_LOGBOOK_COUNT; unsigned int offset = idx * logbook->size; // Uninitialized header entries should no longer be present at this // stage, unless the dives are interleaved with empty entries. But // that's something we don't support at all. if (array_isequal (header + offset, logbook->size, 0xFF)) { WARNING (abstract->context, "Unexpected empty header found."); break; } // Calculate the profile length. unsigned int length = RB_LOGBOOK_SIZE_FULL + array_uint24_le (header + offset + logbook->profile) - 3; if (!compact) { // Workaround for a bug in older firmware versions. unsigned int firmware = array_uint16_be (header + offset + 0x30); if (firmware < 93) length -= 3; } // Check the fingerprint data. if (memcmp (header + offset + logbook->fingerprint, device->fingerprint, sizeof (device->fingerprint)) == 0) break; if (length > maxsize) maxsize = length; size += length; ndives++; } // Update and emit a progress event. progress.maximum = (logbook->size * RB_LOGBOOK_COUNT) + size + ndives; device_event_emit (abstract, DC_EVENT_PROGRESS, &progress); // Finish immediately if there are no dives available. if (ndives == 0) { free (header); return DC_STATUS_SUCCESS; } // Allocate enough memory for the largest dive. unsigned char *profile = (unsigned char *) malloc (maxsize); if (profile == NULL) { ERROR (abstract->context, "Failed to allocate memory."); free (header); return DC_STATUS_NOMEMORY; } // Download the dives. for (unsigned int i = 0; i < ndives; ++i) { unsigned int idx = (latest + RB_LOGBOOK_COUNT - i) % RB_LOGBOOK_COUNT; unsigned int offset = idx * logbook->size; // Calculate the profile length. unsigned int length = RB_LOGBOOK_SIZE_FULL + array_uint24_le (header + offset + logbook->profile) - 3; if (!compact) { // Workaround for a bug in older firmware versions. unsigned int firmware = array_uint16_be (header + offset + 0x30); if (firmware < 93) length -= 3; } // Download the dive. unsigned char number[1] = {idx}; rc = hw_ostc3_transfer (device, &progress, DIVE, number, sizeof (number), profile, length, NODELAY); if (rc != DC_STATUS_SUCCESS) { ERROR (abstract->context, "Failed to read the dive."); free (profile); free (header); return rc; } // Verify the header in the logbook and profile are identical. if (!compact && memcmp (profile, header + offset, logbook->size) != 0) { ERROR (abstract->context, "Unexpected profile header."); free (profile); free (header); return rc; } if (callback && !callback (profile, length, profile + 12, sizeof (device->fingerprint), userdata)) break; } free (profile); free (header); return DC_STATUS_SUCCESS; } dc_status_t hw_ostc3_device_clock (dc_device_t *abstract, const dc_datetime_t *datetime) { hw_ostc3_device_t *device = (hw_ostc3_device_t *) abstract; if (!ISINSTANCE (abstract)) return DC_STATUS_INVALIDARGS; if (datetime == NULL) { ERROR (abstract->context, "Invalid parameter specified."); return DC_STATUS_INVALIDARGS; } dc_status_t rc = hw_ostc3_device_init (device, DOWNLOAD); if (rc != DC_STATUS_SUCCESS) return rc; // Send the command. unsigned char packet[6] = { datetime->hour, datetime->minute, datetime->second, datetime->month, datetime->day, datetime->year - 2000}; rc = hw_ostc3_transfer (device, NULL, CLOCK, packet, sizeof (packet), NULL, 0, NODELAY); if (rc != DC_STATUS_SUCCESS) return rc; return DC_STATUS_SUCCESS; } dc_status_t hw_ostc3_device_display (dc_device_t *abstract, const char *text) { hw_ostc3_device_t *device = (hw_ostc3_device_t *) abstract; if (!ISINSTANCE (abstract)) return DC_STATUS_INVALIDARGS; // Pad the data packet with spaces. unsigned char packet[SZ_DISPLAY] = {0}; if (hw_ostc3_strncpy (packet, sizeof (packet), text) != 0) { ERROR (abstract->context, "Invalid parameter specified."); return DC_STATUS_INVALIDARGS; } dc_status_t rc = hw_ostc3_device_init (device, DOWNLOAD); if (rc != DC_STATUS_SUCCESS) return rc; // Send the command. rc = hw_ostc3_transfer (device, NULL, DISPLAY, packet, sizeof (packet), NULL, 0, NODELAY); if (rc != DC_STATUS_SUCCESS) return rc; return DC_STATUS_SUCCESS; } dc_status_t hw_ostc3_device_customtext (dc_device_t *abstract, const char *text) { hw_ostc3_device_t *device = (hw_ostc3_device_t *) abstract; if (!ISINSTANCE (abstract)) return DC_STATUS_INVALIDARGS; // Pad the data packet with spaces. unsigned char packet[SZ_CUSTOMTEXT] = {0}; if (hw_ostc3_strncpy (packet, sizeof (packet), text) != 0) { ERROR (abstract->context, "Invalid parameter specified."); return DC_STATUS_INVALIDARGS; } dc_status_t rc = hw_ostc3_device_init (device, DOWNLOAD); if (rc != DC_STATUS_SUCCESS) return rc; // Send the command. rc = hw_ostc3_transfer (device, NULL, CUSTOMTEXT, packet, sizeof (packet), NULL, 0, NODELAY); if (rc != DC_STATUS_SUCCESS) return rc; return DC_STATUS_SUCCESS; } dc_status_t hw_ostc3_device_config_read (dc_device_t *abstract, unsigned int config, unsigned char data[], unsigned int size) { hw_ostc3_device_t *device = (hw_ostc3_device_t *) abstract; if (!ISINSTANCE (abstract)) return DC_STATUS_INVALIDARGS; dc_status_t rc = hw_ostc3_device_init (device, DOWNLOAD); if (rc != DC_STATUS_SUCCESS) return rc; if (device->hardware == OSTC4 ? size != SZ_CONFIG : size > SZ_CONFIG) { ERROR (abstract->context, "Invalid parameter specified."); return DC_STATUS_INVALIDARGS; } // Send the command. unsigned char command[1] = {config}; rc = hw_ostc3_transfer (device, NULL, READ, command, sizeof (command), data, size, NODELAY); if (rc != DC_STATUS_SUCCESS) return rc; return DC_STATUS_SUCCESS; } dc_status_t hw_ostc3_device_config_write (dc_device_t *abstract, unsigned int config, const unsigned char data[], unsigned int size) { hw_ostc3_device_t *device = (hw_ostc3_device_t *) abstract; if (!ISINSTANCE (abstract)) return DC_STATUS_INVALIDARGS; dc_status_t rc = hw_ostc3_device_init (device, DOWNLOAD); if (rc != DC_STATUS_SUCCESS) return rc; if (device->hardware == OSTC4 ? size != SZ_CONFIG : size > SZ_CONFIG) { ERROR (abstract->context, "Invalid parameter specified."); return DC_STATUS_INVALIDARGS; } // Send the command. unsigned char command[SZ_CONFIG + 1] = {config}; memcpy(command + 1, data, size); rc = hw_ostc3_transfer (device, NULL, WRITE, command, size + 1, NULL, 0, NODELAY); if (rc != DC_STATUS_SUCCESS) return rc; return DC_STATUS_SUCCESS; } dc_status_t hw_ostc3_device_config_reset (dc_device_t *abstract) { hw_ostc3_device_t *device = (hw_ostc3_device_t *) abstract; if (!ISINSTANCE (abstract)) return DC_STATUS_INVALIDARGS; dc_status_t rc = hw_ostc3_device_init (device, DOWNLOAD); if (rc != DC_STATUS_SUCCESS) return rc; // Send the command. rc = hw_ostc3_transfer (device, NULL, RESET, NULL, 0, NULL, 0, NODELAY); if (rc != DC_STATUS_SUCCESS) return rc; return DC_STATUS_SUCCESS; } // This is a variant of fletcher16 with a 16 bit sum instead of an 8 bit sum, // and modulo 2^16 instead of 2^16-1 static unsigned int hw_ostc3_firmware_checksum (const unsigned char data[], unsigned int size) { unsigned short low = 0; unsigned short high = 0; for (unsigned int i = 0; i < size; i++) { low += data[i]; high += low; } return (((unsigned int)high) << 16) + low; } static dc_status_t hw_ostc3_firmware_readline (FILE *fp, dc_context_t *context, unsigned int addr, unsigned char data[], unsigned int size) { unsigned char ascii[39]; unsigned char faddr_byte[3]; unsigned int faddr = 0; int n = 0; if (size > 16) { ERROR (context, "Invalid arguments."); return DC_STATUS_INVALIDARGS; } // Read the start code. while (1) { n = fread (ascii, 1, 1, fp); if (n != 1) { ERROR (context, "Failed to read the start code."); return DC_STATUS_IO; } if (ascii[0] == ':') break; // Ignore CR and LF characters. if (ascii[0] != '\n' && ascii[0] != '\r') { ERROR (context, "Unexpected character (0x%02x).", ascii[0]); return DC_STATUS_DATAFORMAT; } } // Read the payload. n = fread (ascii + 1, 1, 6 + size * 2, fp); if (n != 6 + size * 2) { ERROR (context, "Failed to read the data."); return DC_STATUS_IO; } // Convert the address to binary representation. if (array_convert_hex2bin(ascii + 1, 6, faddr_byte, sizeof(faddr_byte)) != 0) { ERROR (context, "Invalid hexadecimal character."); return DC_STATUS_DATAFORMAT; } // Get the address. faddr = array_uint24_be (faddr_byte); if (faddr != addr) { ERROR (context, "Unexpected address (0x%06x, 0x%06x).", faddr, addr); return DC_STATUS_DATAFORMAT; } // Convert the payload to binary representation. if (array_convert_hex2bin (ascii + 1 + 6, size * 2, data, size) != 0) { ERROR (context, "Invalid hexadecimal character."); return DC_STATUS_DATAFORMAT; } return DC_STATUS_SUCCESS; } static dc_status_t hw_ostc3_firmware_readfile3 (hw_ostc3_firmware_t *firmware, dc_context_t *context, const char *filename) { dc_status_t rc = DC_STATUS_SUCCESS; FILE *fp = NULL; unsigned char iv[16] = {0}; unsigned char tmpbuf[16] = {0}; unsigned char encrypted[16] = {0}; unsigned int bytes = 0, addr = 0; unsigned char checksum[4]; if (firmware == NULL) { ERROR (context, "Invalid arguments."); return DC_STATUS_INVALIDARGS; } // Initialize the buffers. memset (firmware->data, 0xFF, sizeof (firmware->data)); firmware->checksum = 0; fp = fopen (filename, "rb"); if (fp == NULL) { ERROR (context, "Failed to open the file."); return DC_STATUS_IO; } rc = hw_ostc3_firmware_readline (fp, context, 0, iv, sizeof(iv)); if (rc != DC_STATUS_SUCCESS) { ERROR (context, "Failed to parse header."); fclose (fp); return rc; } bytes += 16; // Load the iv for AES-FCB-mode AES128_ECB_encrypt (iv, ostc3_key, tmpbuf); for (addr = 0; addr < SZ_FIRMWARE; addr += 16, bytes += 16) { rc = hw_ostc3_firmware_readline (fp, context, bytes, encrypted, sizeof(encrypted)); if (rc != DC_STATUS_SUCCESS) { ERROR (context, "Failed to parse file data."); fclose (fp); return rc; } // Decrypt AES-FCB data for (unsigned int i = 0; i < 16; i++) firmware->data[addr + i] = encrypted[i] ^ tmpbuf[i]; // Run the next round of encryption AES128_ECB_encrypt (encrypted, ostc3_key, tmpbuf); } // This file format contains a tail with the checksum in rc = hw_ostc3_firmware_readline (fp, context, bytes, checksum, sizeof(checksum)); if (rc != DC_STATUS_SUCCESS) { ERROR (context, "Failed to parse file tail."); fclose (fp); return rc; } fclose (fp); unsigned int csum1 = array_uint32_le (checksum); unsigned int csum2 = hw_ostc3_firmware_checksum (firmware->data, sizeof(firmware->data)); if (csum1 != csum2) { ERROR (context, "Failed to verify file checksum."); return DC_STATUS_DATAFORMAT; } firmware->checksum = csum1; return DC_STATUS_SUCCESS; } static dc_status_t hw_ostc3_firmware_readfile4 (dc_buffer_t *buffer, dc_context_t *context, const char *filename) { FILE *fp = NULL; if (buffer == NULL) { ERROR (context, "Invalid arguments."); return DC_STATUS_INVALIDARGS; } // Open the file. fp = fopen (filename, "rb"); if (fp == NULL) { ERROR (context, "Failed to open the file."); return DC_STATUS_IO; } // Read the entire file into the buffer. size_t n = 0; unsigned char block[1024] = {0}; while ((n = fread (block, 1, sizeof (block), fp)) > 0) { dc_buffer_append (buffer, block, n); } // Close the file. fclose (fp); // Verify the minimum size. size_t size = dc_buffer_get_size (buffer); if (size < 4) { ERROR (context, "Invalid file size."); return DC_STATUS_DATAFORMAT; } // Verify the checksum. const unsigned char *data = dc_buffer_get_data (buffer); unsigned int csum1 = array_uint32_le (data + size - 4); unsigned int csum2 = hw_ostc3_firmware_checksum (data, size - 4); if (csum1 != csum2) { ERROR (context, "Failed to verify file checksum."); return DC_STATUS_DATAFORMAT; } // Remove the checksum. dc_buffer_slice (buffer, 0, size - 4); return DC_STATUS_SUCCESS; } static dc_status_t hw_ostc3_firmware_erase (hw_ostc3_device_t *device, unsigned int addr, unsigned int size) { // Convert size to number of pages, rounded up. unsigned char blocks = ((size + SZ_FIRMWARE_BLOCK - 1) / SZ_FIRMWARE_BLOCK); // 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); } static dc_status_t hw_ostc3_firmware_block_read (hw_ostc3_device_t *device, unsigned int addr, unsigned char block[], unsigned int block_size) { unsigned char buffer[6]; array_uint24_be_set (buffer, addr); array_uint24_be_set (buffer + 3, block_size); return hw_ostc3_transfer (device, NULL, S_BLOCK_READ, buffer, sizeof (buffer), block, block_size, NODELAY); } static dc_status_t hw_ostc3_firmware_block_write (hw_ostc3_device_t *device, unsigned int addr, unsigned char block[], unsigned int block_size) { unsigned char buffer[3 + SZ_FIRMWARE_BLOCK]; // We currenty only support writing max SZ_FIRMWARE_BLOCK sized blocks. if (block_size > SZ_FIRMWARE_BLOCK) return DC_STATUS_INVALIDARGS; 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); } static dc_status_t hw_ostc3_firmware_upgrade (dc_device_t *abstract, unsigned int checksum) { dc_status_t rc = DC_STATUS_SUCCESS; hw_ostc3_device_t *device = (hw_ostc3_device_t *) abstract; dc_context_t *context = (abstract ? abstract->context : NULL); unsigned char buffer[5]; array_uint32_le_set (buffer, checksum); // Compute a one byte checksum, so the device can validate the firmware image. buffer[4] = 0x55; for (unsigned int i = 0; i < 4; i++) { buffer[4] ^= buffer[i]; buffer[4] = (buffer[4]<<1 | buffer[4]>>7); } rc = hw_ostc3_transfer (device, NULL, S_UPGRADE, buffer, sizeof (buffer), NULL, 0, NODELAY); if (rc != DC_STATUS_SUCCESS) { ERROR (context, "Failed to send flash firmware command"); return rc; } // Now the device resets, and if everything is well, it reprograms. device->state = REBOOTING; return DC_STATUS_SUCCESS; } static dc_status_t hw_ostc3_device_fwupdate3 (dc_device_t *abstract, const char *filename) { dc_status_t rc = DC_STATUS_SUCCESS; hw_ostc3_device_t *device = (hw_ostc3_device_t *) abstract; dc_context_t *context = (abstract ? abstract->context : NULL); // Enable progress notifications. // load, erase, upload FZ, verify FZ, reprogram dc_event_progress_t progress = EVENT_PROGRESS_INITIALIZER; progress.maximum = 3 + SZ_FIRMWARE * 2 / SZ_FIRMWARE_BLOCK; device_event_emit (abstract, DC_EVENT_PROGRESS, &progress); // Allocate memory for the firmware data. hw_ostc3_firmware_t *firmware = (hw_ostc3_firmware_t *) malloc (sizeof (hw_ostc3_firmware_t)); if (firmware == NULL) { ERROR (context, "Failed to allocate memory."); return DC_STATUS_NOMEMORY; } // Read the hex file. rc = hw_ostc3_firmware_readfile3 (firmware, context, filename); if (rc != DC_STATUS_SUCCESS) { free (firmware); return rc; } // Device open and firmware loaded progress.current++; device_event_emit (abstract, DC_EVENT_PROGRESS, &progress); hw_ostc3_device_display (abstract, " Erasing FW..."); rc = hw_ostc3_firmware_erase (device, FIRMWARE_AREA, SZ_FIRMWARE); if (rc != DC_STATUS_SUCCESS) { ERROR (context, "Failed to erase old firmware"); free (firmware); return rc; } // Memory erased progress.current++; device_event_emit (abstract, DC_EVENT_PROGRESS, &progress); hw_ostc3_device_display (abstract, " Uploading..."); for (unsigned int len = 0; len < SZ_FIRMWARE; len += SZ_FIRMWARE_BLOCK) { char status[SZ_DISPLAY + 1]; // Status message on the display snprintf (status, sizeof(status), " Uploading %2d%%", (100 * len) / SZ_FIRMWARE); hw_ostc3_device_display (abstract, status); rc = hw_ostc3_firmware_block_write (device, FIRMWARE_AREA + len, firmware->data + len, SZ_FIRMWARE_BLOCK); if (rc != DC_STATUS_SUCCESS) { ERROR (context, "Failed to write block to device"); free(firmware); return rc; } // One block uploaded progress.current++; device_event_emit (abstract, DC_EVENT_PROGRESS, &progress); } hw_ostc3_device_display (abstract, " Verifying..."); for (unsigned int len = 0; len < SZ_FIRMWARE; len += SZ_FIRMWARE_BLOCK) { unsigned char block[SZ_FIRMWARE_BLOCK]; char status[SZ_DISPLAY + 1]; // Status message on the display snprintf (status, sizeof(status), " Verifying %2d%%", (100 * len) / SZ_FIRMWARE); hw_ostc3_device_display (abstract, status); rc = hw_ostc3_firmware_block_read (device, FIRMWARE_AREA + len, block, sizeof (block)); if (rc != DC_STATUS_SUCCESS) { ERROR (context, "Failed to read block."); free (firmware); return rc; } if (memcmp (firmware->data + len, block, sizeof (block)) != 0) { ERROR (context, "Failed verify."); hw_ostc3_device_display (abstract, " Verify FAILED"); free (firmware); return DC_STATUS_PROTOCOL; } // One block verified progress.current++; device_event_emit (abstract, DC_EVENT_PROGRESS, &progress); } hw_ostc3_device_display (abstract, " Programming..."); rc = hw_ostc3_firmware_upgrade (abstract, firmware->checksum); if (rc != DC_STATUS_SUCCESS) { ERROR (context, "Failed to start programing"); free (firmware); return rc; } // Programing done! progress.current++; device_event_emit (abstract, DC_EVENT_PROGRESS, &progress); free (firmware); // Finished! return DC_STATUS_SUCCESS; } static dc_status_t hw_ostc3_device_fwupdate4 (dc_device_t *abstract, const char *filename) { dc_status_t status = DC_STATUS_SUCCESS; hw_ostc3_device_t *device = (hw_ostc3_device_t *) abstract; dc_context_t *context = (abstract ? abstract->context : NULL); // Allocate memory for the firmware data. dc_buffer_t *buffer = dc_buffer_new (0); if (buffer == NULL) { ERROR (context, "Failed to allocate memory."); status = DC_STATUS_NOMEMORY; goto error; } // Read the firmware file. status = hw_ostc3_firmware_readfile4 (buffer, context, filename); if (status != DC_STATUS_SUCCESS) { goto error; } // Enable progress notifications. dc_event_progress_t progress = EVENT_PROGRESS_INITIALIZER; progress.maximum = dc_buffer_get_size (buffer); device_event_emit (abstract, DC_EVENT_PROGRESS, &progress); // Cache the pointer and size. const unsigned char *data = dc_buffer_get_data (buffer); unsigned int size = dc_buffer_get_size (buffer); unsigned int offset = 0; while (offset + 4 <= size) { // Get the length of the firmware blob. unsigned int length = array_uint32_be(data + offset) + 20; if (offset + length > size) { status = DC_STATUS_DATAFORMAT; goto error; } // Get the blob type. unsigned char type = data[offset + 4]; // Estimate the required delay. // After uploading the firmware blob, the device writes the data // to flash memory. Since this takes a significant amount of // time, the ready byte is delayed. Therefore, the standard // timeout is no longer sufficient. The delays are estimated // based on actual measurements of the delay per byte. unsigned int usecs = length; if (type == 0xFF) { // Firmware usecs *= 50; } else if (type == 0xFE) { // RTE usecs *= 500; } else { // Fonts usecs *= 25; } // Read the firmware version info. unsigned char fwinfo[SZ_FWINFO] = {0}; status = hw_ostc3_transfer (device, NULL, S_FWINFO, data + offset + 4, 1, fwinfo, sizeof(fwinfo), NODELAY); if (status != DC_STATUS_SUCCESS) { ERROR (abstract->context, "Failed to read the firmware info."); goto error; } // Upload the firmware blob. // The update is skipped if the two versions are already // identical, or if the blob is not present on the device. if (memcmp(data + offset + 12, fwinfo, sizeof(fwinfo)) != 0 && !array_isequal(fwinfo, sizeof(fwinfo), 0xFF)) { status = hw_ostc3_transfer (device, &progress, S_UPLOAD, data + offset, length, NULL, 0, usecs / 1000); if (status != DC_STATUS_SUCCESS) { goto error; } } else { // Update and emit a progress event. progress.current += length; device_event_emit (abstract, DC_EVENT_PROGRESS, &progress); } offset += length; } error: dc_buffer_free (buffer); return status; } dc_status_t hw_ostc3_device_fwupdate (dc_device_t *abstract, const char *filename) { dc_status_t status = DC_STATUS_SUCCESS; hw_ostc3_device_t *device = (hw_ostc3_device_t *) abstract; if (!ISINSTANCE (abstract)) return DC_STATUS_INVALIDARGS; // Make sure the device is in service mode. status = hw_ostc3_device_init (device, SERVICE); if (status != DC_STATUS_SUCCESS) { return status; } if (device->hardware == OSTC4) { return hw_ostc3_device_fwupdate4 (abstract, filename); } else { return hw_ostc3_device_fwupdate3 (abstract, filename); } } static dc_status_t hw_ostc3_device_dump (dc_device_t *abstract, dc_buffer_t *buffer) { hw_ostc3_device_t *device = (hw_ostc3_device_t *) abstract; // Erase the current contents of the buffer. if (!dc_buffer_clear (buffer)) { ERROR (abstract->context, "Insufficient buffer space available."); return DC_STATUS_NOMEMORY; } // Enable progress notifications. dc_event_progress_t progress = EVENT_PROGRESS_INITIALIZER; progress.maximum = SZ_MEMORY; device_event_emit (abstract, DC_EVENT_PROGRESS, &progress); // Make sure the device is in service mode dc_status_t rc = hw_ostc3_device_init (device, SERVICE); if (rc != DC_STATUS_SUCCESS) { return rc; } // Allocate the required amount of memory. if (!dc_buffer_resize (buffer, SZ_MEMORY)) { ERROR (abstract->context, "Insufficient buffer space available."); return DC_STATUS_NOMEMORY; } unsigned char *data = dc_buffer_get_data (buffer); unsigned int nbytes = 0; while (nbytes < SZ_MEMORY) { // packet size. Can be almost arbetary size. unsigned int len = SZ_FIRMWARE_BLOCK; // Read a block rc = hw_ostc3_firmware_block_read (device, nbytes, data + nbytes, len); if (rc != DC_STATUS_SUCCESS) { ERROR (abstract->context, "Failed to read block."); return rc; } // Update and emit a progress event. progress.current += len; device_event_emit (abstract, DC_EVENT_PROGRESS, &progress); nbytes += len; } return DC_STATUS_SUCCESS; }