diff --git a/examples/Makefile.am b/examples/Makefile.am index fc0664d..2283028 100644 --- a/examples/Makefile.am +++ b/examples/Makefile.am @@ -21,6 +21,7 @@ bin_PROGRAMS = \ darwin \ iconhd \ ostc \ + ostc-fwupdate \ frog \ edy \ leonardo \ @@ -72,6 +73,8 @@ iconhd_SOURCES = mares_iconhd_test.c $(COMMON) ostc_SOURCES = hw_ostc_test.c $(COMMON) +ostc_fwupdate_SOURCES = hw_ostc_fwupdate.c $(COMMON) + frog_SOURCES = hw_frog_test.c $(COMMON) edy_SOURCES = cressi_edy_test.c $(COMMON) diff --git a/examples/hw_ostc_fwupdate.c b/examples/hw_ostc_fwupdate.c new file mode 100644 index 0000000..a9e7fde --- /dev/null +++ b/examples/hw_ostc_fwupdate.c @@ -0,0 +1,126 @@ +/* + * libdivecomputer + * + * Copyright (C) 2013 Jef Driesen + * + * 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 // fopen, fwrite, fclose +#include + +#include + +#include "utils.h" +#include "common.h" + +static void +event_cb (dc_device_t *device, dc_event_type_t event, const void *data, void *userdata) +{ + const dc_event_progress_t *progress = (dc_event_progress_t *) data; + + switch (event) { + case DC_EVENT_PROGRESS: + message ("Event: progress %3.2f%% (%u/%u)\n", + 100.0 * (double) progress->current / (double) progress->maximum, + progress->current, progress->maximum); + break; + default: + break; + } +} + +static dc_status_t +fwupdate (const char *name, const char *hexfile) +{ + dc_context_t *context = NULL; + dc_device_t *device = NULL; + + dc_context_new (&context); + dc_context_set_loglevel (context, DC_LOGLEVEL_ALL); + dc_context_set_logfunc (context, logfunc, NULL); + + message ("hw_ostc_device_open\n"); + dc_status_t rc = hw_ostc_device_open (&device, context, name); + if (rc != DC_STATUS_SUCCESS) { + WARNING ("Error opening serial port."); + dc_context_free (context); + return rc; + } + + message ("dc_device_set_events.\n"); + rc = dc_device_set_events (device, DC_EVENT_PROGRESS, event_cb, NULL); + if (rc != DC_STATUS_SUCCESS) { + WARNING ("Error registering the event handler."); + dc_device_close (device); + dc_context_free (context); + return rc; + } + + message ("hw_ostc_device_fwupdate\n"); + rc = hw_ostc_device_fwupdate (device, hexfile); + if (rc != DC_STATUS_SUCCESS) { + WARNING ("Error flashing firmware."); + dc_device_close (device); + dc_context_free (context); + return rc; + } + + message ("dc_device_close\n"); + rc = dc_device_close (device); + if (rc != DC_STATUS_SUCCESS) { + WARNING ("Cannot close device."); + dc_context_free (context); + return rc; + } + + dc_context_free (context); + + return DC_STATUS_SUCCESS; +} + + +int main(int argc, char *argv[]) +{ + message_set_logfile ("OSTC-FWUPDATE.LOG"); + +#ifdef _WIN32 + const char* name = "COM1"; +#else + const char* name = "/dev/ttyUSB0"; +#endif + const char *hexfile = NULL; + + if (argc > 1) { + name = argv[1]; + } + if (argc > 2) { + hexfile = argv[2]; + } + + message ("DEVICE=%s\n", name); + message ("HEXFILE=%s\n", hexfile); + + dc_status_t a = fwupdate (name, hexfile); + + message ("SUMMARY\n"); + message ("-------\n"); + message ("fwupdate: %s\n", errmsg (a)); + + message_set_logfile (NULL); + + return 0; +} diff --git a/include/libdivecomputer/hw_ostc.h b/include/libdivecomputer/hw_ostc.h index 6e0a8c8..4dfb523 100644 --- a/include/libdivecomputer/hw_ostc.h +++ b/include/libdivecomputer/hw_ostc.h @@ -67,6 +67,9 @@ hw_ostc_extract_dives (dc_device_t *device, const unsigned char data[], unsigned dc_status_t hw_ostc_parser_create (dc_parser_t **parser, dc_context_t *context, unsigned int frog); +dc_status_t +hw_ostc_device_fwupdate (dc_device_t *abstract, const char *filename); + #ifdef __cplusplus } #endif /* __cplusplus */ diff --git a/src/Makefile.am b/src/Makefile.am index 3e1417c..2cce288 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -39,6 +39,7 @@ libdivecomputer_la_SOURCES = \ mares_puck.c \ mares_darwin.c mares_darwin_parser.c \ mares_iconhd.c mares_iconhd_parser.c \ + ihex.h ihex.c \ hw_ostc.c hw_ostc_parser.c \ hw_frog.c \ hw_ostc3.c \ diff --git a/src/hw_ostc.c b/src/hw_ostc.c index f95601d..fac8ebc 100644 --- a/src/hw_ostc.c +++ b/src/hw_ostc.c @@ -29,6 +29,7 @@ #include "serial.h" #include "checksum.h" #include "array.h" +#include "ihex.h" #define ISINSTANCE(device) dc_device_isinstance((device), &hw_ostc_device_vtable) @@ -37,6 +38,10 @@ rc == -1 ? DC_STATUS_IO : DC_STATUS_TIMEOUT \ ) +#define C_ARRAY_SIZE(a) (sizeof(a) / sizeof(*(a))) + +#define MAXRETRIES 9 + #define FW_190 0x015A #define SZ_MD2HASH 18 @@ -45,6 +50,13 @@ #define SZ_FW_190 0x8000 #define SZ_FW_NEW 0x10000 +#define SZ_FIRMWARE 0x17F40 +#define SZ_BLOCK 0x40 + +#define ACK 0x4B /* "K" for ok */ +#define NAK 0x4E /* "N" for not ok */ +#define PICTYPE 0x57 /* PIC type (18F4685) */ + #define WIDTH 320 #define HEIGHT 240 #define BLACK 0x00 @@ -56,6 +68,11 @@ typedef struct hw_ostc_device_t { unsigned char fingerprint[5]; } hw_ostc_device_t; +typedef struct hw_ostc_firmware_t { + unsigned char data[SZ_FIRMWARE]; + unsigned char bitmap[SZ_FIRMWARE / SZ_BLOCK]; +} hw_ostc_firmware_t; + static dc_status_t hw_ostc_device_set_fingerprint (dc_device_t *abstract, const unsigned char data[], unsigned int size); static dc_status_t hw_ostc_device_dump (dc_device_t *abstract, dc_buffer_t *buffer); static dc_status_t hw_ostc_device_foreach (dc_device_t *abstract, dc_dive_callback_t callback, void *userdata); @@ -652,3 +669,296 @@ hw_ostc_extract_dives (dc_device_t *abstract, const unsigned char data[], unsign return DC_STATUS_SUCCESS; } + + +static dc_status_t +hw_ostc_firmware_readfile (hw_ostc_firmware_t *firmware, dc_context_t *context, const char *filename) +{ + dc_status_t rc = DC_STATUS_SUCCESS; + + if (firmware == NULL) { + ERROR (context, "Invalid arguments."); + return DC_STATUS_INVALIDARGS; + } + + // Initialize the buffers. + memset (firmware->data, 0xFF, sizeof (firmware->data)); + memset (firmware->bitmap, 0x00, sizeof (firmware->bitmap)); + + // Open the hex file. + dc_ihex_file_t *file = NULL; + rc = dc_ihex_file_open (&file, context, filename); + if (rc != DC_STATUS_SUCCESS) { + ERROR (context, "Failed to open the hex file."); + return rc; + } + + // Read the hex file. + unsigned int lba = 0; + dc_ihex_entry_t entry; + while ((rc = dc_ihex_file_read (file, &entry)) == DC_STATUS_SUCCESS) { + if (entry.type == 0) { + // Data record. + unsigned int address = (lba << 16) + entry.address; + if (address + entry.length > SZ_FIRMWARE) { + WARNING (context, "Ignoring out of range record (0x%08x,%u).", address, entry.length); + continue; + } + + // Copy the record to the buffer. + memcpy (firmware->data + address, entry.data, entry.length); + + // Mark the corresponding blocks in the bitmap. + unsigned int begin = address / SZ_BLOCK; + unsigned int end = (address + entry.length + SZ_BLOCK - 1) / SZ_BLOCK; + for (unsigned int i = begin; i < end; ++i) { + firmware->bitmap[i] = 1; + } + } else if (entry.type == 1) { + // End of file record. + break; + } else if (entry.type == 4) { + // Extended linear address record. + lba = array_uint16_be (entry.data); + } else { + ERROR (context, "Unexpected record type."); + dc_ihex_file_close (file); + return DC_STATUS_DATAFORMAT; + } + } + if (rc != DC_STATUS_SUCCESS && rc != DC_STATUS_DONE) { + ERROR (context, "Failed to read the record."); + dc_ihex_file_close (file); + return rc; + } + + // Close the file. + dc_ihex_file_close (file); + + // Verify the presence of the first block. + if (firmware->bitmap[0] == 0) { + ERROR (context, "No first data block."); + return DC_STATUS_DATAFORMAT; + } + + // Setup the last block. + // Copy the "goto main" instruction, stored in the first 8 bytes of the hex + // file, to the end of the last block at address 0x17F38. This last block + // needs to be present, regardless of whether it's included in the hex file + // or not! + memset (firmware->data + SZ_FIRMWARE - SZ_BLOCK, 0xFF, SZ_BLOCK - 8); + memcpy (firmware->data + SZ_FIRMWARE - 8, firmware->data, 8); + firmware->bitmap[C_ARRAY_SIZE(firmware->bitmap) - 1] = 1; + + // Setup the first block. + // Copy the hardcoded "goto 0x17F40" instruction to the start of the first + // block at address 0x00000. + const unsigned char header[] = {0xA0, 0xEF, 0xBF, 0xF0}; + memcpy (firmware->data, header, sizeof (header)); + + return rc; +} + + +static dc_status_t +hw_ostc_firmware_setup_internal (hw_ostc_device_t *device) +{ + dc_device_t *abstract = (dc_device_t *) device; + + // Send the command. + unsigned char command[1] = {0xC1}; + int n = serial_write (device->port, command, sizeof (command)); + if (n != sizeof (command)) { + ERROR (abstract->context, "Failed to send the command."); + return EXITCODE (n); + } + + // Read the response. + unsigned char answer[2] = {0}; + n = serial_read (device->port, answer, sizeof (answer)); + if (n != sizeof (answer)) { + ERROR (abstract->context, "Failed to receive the response."); + return EXITCODE (n); + } + + // Verify the response. + const unsigned char expected[2] = {PICTYPE, ACK}; + if (memcmp (answer, expected, sizeof (expected)) != 0) { + ERROR (abstract->context, "Unexpected response."); + return DC_STATUS_PROTOCOL; + } + + return DC_STATUS_SUCCESS; +} + + +static dc_status_t +hw_ostc_firmware_setup (hw_ostc_device_t *device, unsigned int maxretries) +{ + dc_status_t rc = DC_STATUS_SUCCESS; + + unsigned int nretries = 0; + while ((rc = hw_ostc_firmware_setup_internal (device)) != DC_STATUS_SUCCESS) { + if (rc != DC_STATUS_TIMEOUT && rc != DC_STATUS_PROTOCOL) + break; + + // Abort if the maximum number of retries is reached. + if (nretries++ >= maxretries) + break; + } + + return rc; +} + + +static dc_status_t +hw_ostc_firmware_write_internal (hw_ostc_device_t *device, unsigned char *data, unsigned int size) +{ + dc_device_t *abstract = (dc_device_t *) device; + + // Send the packet. + int n = serial_write (device->port, data, size); + if (n != size) { + ERROR (abstract->context, "Failed to send the packet."); + return EXITCODE (n); + } + + // Read the response. + unsigned char answer[1] = {0}; + n = serial_read (device->port, answer, sizeof (answer)); + if (n != sizeof (answer)) { + ERROR (abstract->context, "Failed to receive the response."); + return EXITCODE (n); + } + + // Verify the response. + const unsigned char expected[] = {ACK}; + if (memcmp (answer, expected, sizeof (expected)) != 0) { + ERROR (abstract->context, "Unexpected response."); + return DC_STATUS_PROTOCOL; + } + + return DC_STATUS_SUCCESS; +} + + +static dc_status_t +hw_ostc_firmware_write (hw_ostc_device_t *device, unsigned char *data, unsigned int size) +{ + dc_status_t rc = DC_STATUS_SUCCESS; + + unsigned int nretries = 0; + while ((rc = hw_ostc_firmware_write_internal (device, data, size)) != DC_STATUS_SUCCESS) { + if (rc != DC_STATUS_TIMEOUT && rc != DC_STATUS_PROTOCOL) + break; + + // Abort if the maximum number of retries is reached. + if (nretries++ >= MAXRETRIES) + break; + } + + return rc; +} + +/* + * Think twice before modifying the code for updating the ostc firmware! + * It has been carefully developed and tested with assistance from + * Heinrichs-Weikamp, using a special development unit. If you start + * experimenting with a normal unit and accidentally screw up, you might + * brick the device permanently and turn it into an expensive + * paperweight. You have been warned! + */ +dc_status_t +hw_ostc_device_fwupdate (dc_device_t *abstract, const char *filename) +{ + dc_status_t rc = DC_STATUS_SUCCESS; + hw_ostc_device_t *device = (hw_ostc_device_t *) abstract; + dc_context_t *context = (abstract ? abstract->context : NULL); + + if (!ISINSTANCE (abstract)) + return DC_STATUS_INVALIDARGS; + + // Allocate memory for the firmware data. + hw_ostc_firmware_t *firmware = (hw_ostc_firmware_t *) malloc (sizeof (hw_ostc_firmware_t)); + if (firmware == NULL) { + ERROR (context, "Failed to allocate memory."); + return DC_STATUS_NOMEMORY; + } + + // Read the hex file. + rc = hw_ostc_firmware_readfile (firmware, context, filename); + if (rc != DC_STATUS_SUCCESS) { + ERROR (context, "Failed to read the firmware file."); + free (firmware); + return rc; + } + + // Temporary set a relative short timeout. The command to setup the + // bootloader needs to be send repeatedly, until the response packet is + // received. Thus the time between each two attempts is directly controlled + // by the timeout value. + serial_set_timeout (device->port, 300); + + // Setup the bootloader. + const unsigned int baudrates[] = {19200, 115200}; + for (unsigned int i = 0; i < C_ARRAY_SIZE(baudrates); ++i) { + // Adjust the baudrate. + if (serial_configure (device->port, baudrates[i], 8, SERIAL_PARITY_NONE, 1, SERIAL_FLOWCONTROL_NONE) == -1) { + ERROR (abstract->context, "Failed to set the terminal attributes."); + free (firmware); + return DC_STATUS_IO; + } + + // Try to setup the bootloader. + unsigned int maxretries = (i == 0 ? 1 : MAXRETRIES); + rc = hw_ostc_firmware_setup (device, maxretries); + if (rc == DC_STATUS_SUCCESS) + break; + } + if (rc != DC_STATUS_SUCCESS) { + ERROR (abstract->context, "Failed to setup the bootloader."); + free (firmware); + return rc; + } + + // Increase the timeout again. + serial_set_timeout (device->port, 1000); + + // Enable progress notifications. + dc_event_progress_t progress = EVENT_PROGRESS_INITIALIZER; + progress.maximum = C_ARRAY_SIZE(firmware->bitmap); + device_event_emit (abstract, DC_EVENT_PROGRESS, &progress); + + for (unsigned int i = 0; i < C_ARRAY_SIZE(firmware->bitmap); ++i) { + // Skip empty blocks. + if (firmware->bitmap[i] == 0) + continue; + + // Create the packet. + unsigned int address = i * SZ_BLOCK; + unsigned char packet[4 + SZ_BLOCK + 1] = { + (address >> 16) & 0xFF, + (address >> 8) & 0xFF, + (address ) & 0xFF, + SZ_BLOCK + }; + memcpy (packet + 4, firmware->data + address, SZ_BLOCK); + packet[sizeof (packet) - 1] = ~checksum_add_uint8 (packet, 4 + SZ_BLOCK, 0x00) + 1; + + // Send the packet. + rc = hw_ostc_firmware_write (device, packet, sizeof (packet)); + if (rc != DC_STATUS_SUCCESS) { + ERROR (abstract->context, "Failed to send the packet."); + free (firmware); + return rc; + } + + // Update and emit a progress event. + progress.current = i + 1; + device_event_emit (abstract, DC_EVENT_PROGRESS, &progress); + } + + free (firmware); + + return DC_STATUS_SUCCESS; +} diff --git a/src/ihex.c b/src/ihex.c new file mode 100644 index 0000000..5389696 --- /dev/null +++ b/src/ihex.c @@ -0,0 +1,206 @@ +/* + * libdivecomputer + * + * Copyright (C) 2013 Jef Driesen + * + * 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 +#include +#include +#include + +#include "ihex.h" +#include "context-private.h" +#include "checksum.h" +#include "array.h" + +struct dc_ihex_file_t { + dc_context_t *context; + FILE *fp; +}; + +dc_status_t +dc_ihex_file_open (dc_ihex_file_t **result, dc_context_t *context, const char *filename) +{ + dc_ihex_file_t *file = NULL; + + if (result == NULL || filename == NULL) { + ERROR (context, "Invalid arguments."); + return DC_STATUS_INVALIDARGS; + } + + file = malloc (sizeof (dc_ihex_file_t)); + if (file == NULL) { + ERROR (context, "Failed to allocate memory."); + return DC_STATUS_NOMEMORY; + } + + file->context = context; + + file->fp = fopen (filename, "rb"); + if (file->fp == NULL) { + ERROR (context, "Failed to open the file."); + free (file); + return DC_STATUS_IO; + } + + *result = file; + + return DC_STATUS_SUCCESS; +} + +dc_status_t +dc_ihex_file_read (dc_ihex_file_t *file, dc_ihex_entry_t *entry) +{ + unsigned char ascii[9 + 2 * 255 + 2] = {0}; + unsigned char data[4 + 255 + 1] = {0}; + unsigned int type, length, address; + unsigned char csum_a, csum_b; + size_t n; + + if (file == NULL || entry == NULL) { + ERROR (file ? file->context : NULL, "Invalid arguments."); + return DC_STATUS_INVALIDARGS; + } + + /* Read the start code. */ + while (1) { + n = fread (ascii, 1, 1, file->fp); + if (n != 1) { + if (feof (file->fp)) { + return DC_STATUS_DONE; + } else { + ERROR (file->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 (file->context, "Unexpected character (0x%02).", ascii[0]); + return DC_STATUS_DATAFORMAT; + } + } + + /* Read the record length, address and type. */ + n = fread (ascii + 1, 1, 8, file->fp); + if (n != 8) { + ERROR (file->context, "Failed to read the header."); + return DC_STATUS_IO; + } + + /* Convert to binary representation. */ + if (array_convert_hex2bin (ascii + 1, 8, data, 4) != 0) { + ERROR (file->context, "Invalid hexadecimal character."); + return DC_STATUS_DATAFORMAT; + } + + /* Get the record length. */ + length = data[0]; + + /* Read the record payload. */ + n = fread (ascii + 9, 1, 2 * length + 2, file->fp); + if (n != 2 * length + 2) { + ERROR (file->context, "Failed to read the data."); + return DC_STATUS_IO; + } + + /* Convert to binary representation. */ + if (array_convert_hex2bin (ascii + 9, 2 * length + 2, data + 4, length + 1) != 0) { + ERROR (file->context, "Invalid hexadecimal character."); + return DC_STATUS_DATAFORMAT; + } + + /* Verify the checksum. */ + csum_a = data[4 + length]; + csum_b = ~checksum_add_uint8 (data, 4 + length, 0x00) + 1; + if (csum_a != csum_b) { + ERROR (file->context, "Unexpected checksum (0x%02x, 0x%02x).", csum_a, csum_b); + return DC_STATUS_DATAFORMAT; + } + + /* Get the record address. */ + address = array_uint16_be (data + 1); + + /* Get the record type. */ + type = data[3]; + if (type < 0 || type > 5) { + ERROR (file->context, "Invalid record type (0x%02x).", type); + return DC_STATUS_DATAFORMAT; + } + + /* Verify the length and address. */ + if (type != 0) { + unsigned int len = 0; + switch (type) { + case 1: /* End of file record. */ + len = 0; + break; + case 2: /* Extended segment address record. */ + case 4: /* Extended linear address record. */ + len = 2; + break; + case 3: /* Start segment address record. */ + case 5: /* Start linear address record. */ + len = 4; + break; + } + if (length != len || address != 0) { + ERROR (file->context, "Invalid record length or address."); + return DC_STATUS_DATAFORMAT; + } + } + + /* Set the record fields. */ + entry->type = type; + entry->address = address; + entry->length = length; + + /* Copy the record data. */ + memcpy (entry->data, data + 4, entry->length); + memset (entry->data + entry->length, 0, sizeof (entry->data) - entry->length); + + return DC_STATUS_SUCCESS; +} + +dc_status_t +dc_ihex_file_reset (dc_ihex_file_t *file) +{ + if (file == NULL) { + ERROR (NULL, "Invalid arguments."); + return DC_STATUS_INVALIDARGS; + } + + rewind (file->fp); + + return DC_STATUS_SUCCESS; +} + +dc_status_t +dc_ihex_file_close (dc_ihex_file_t *file) +{ + if (file) { + fclose (file->fp); + free (file); + } + + return DC_STATUS_SUCCESS; +} diff --git a/src/ihex.h b/src/ihex.h new file mode 100644 index 0000000..10cc9e6 --- /dev/null +++ b/src/ihex.h @@ -0,0 +1,56 @@ +/* + * libdivecomputer + * + * Copyright (C) 2013 Jef Driesen + * + * 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 + */ + +#ifndef DC_IHEX_H +#define DC_IHEX_H + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + +typedef struct dc_ihex_file_t dc_ihex_file_t; + +typedef struct dc_ihex_entry_t { + unsigned int type; + unsigned int address; + unsigned int length; + unsigned char data[255]; +} dc_ihex_entry_t; + +dc_status_t +dc_ihex_file_open (dc_ihex_file_t **file, dc_context_t *context, const char *filename); + +dc_status_t +dc_ihex_file_read (dc_ihex_file_t *file, dc_ihex_entry_t *entry); + +dc_status_t +dc_ihex_file_reset (dc_ihex_file_t *file); + +dc_status_t +dc_ihex_file_close (dc_ihex_file_t *file); + +#ifdef __cplusplus +} +#endif /* __cplusplus */ +#endif /* DC_IHEX_H */ diff --git a/src/libdivecomputer.symbols b/src/libdivecomputer.symbols index 2188971..525f193 100644 --- a/src/libdivecomputer.symbols +++ b/src/libdivecomputer.symbols @@ -140,6 +140,7 @@ hw_ostc_device_eeprom_write hw_ostc_device_reset hw_ostc_device_screenshot hw_ostc_extract_dives +hw_ostc_device_fwupdate hw_frog_device_open hw_frog_device_version hw_frog_device_clock