From 6132ae255035f2c5336d3796db67e8930964d901 Mon Sep 17 00:00:00 2001 From: Jef Driesen Date: Mon, 4 May 2009 09:03:04 +0000 Subject: [PATCH] Share the parsing code between the backends. The memory layout of all Oceanic devices is very similar, which allows to share the parsing code between the different backends. Differences in the layout are passed by means of a new layout descriptor structure. Memory buffers are now allocated dynamically to support devices with different amounts of memory. --- msvc/libdivecomputer.vcproj | 6 +- src/Makefile.am | 1 + src/oceanic_common.c | 373 ++++++++++++++++++++++++++++++++++++ src/oceanic_common.h | 57 ++++++ 4 files changed, 436 insertions(+), 1 deletion(-) create mode 100644 src/oceanic_common.c create mode 100644 src/oceanic_common.h diff --git a/msvc/libdivecomputer.vcproj b/msvc/libdivecomputer.vcproj index 7c32bfd..b267ddd 100644 --- a/msvc/libdivecomputer.vcproj +++ b/msvc/libdivecomputer.vcproj @@ -208,6 +208,10 @@ RelativePath="..\src\oceanic_atom2.c" > + + @@ -359,7 +363,7 @@ > // memcpy, memmove +#include // malloc, free +#include // assert + +#include "oceanic_common.h" +#include "device-private.h" +#include "ringbuffer.h" +#include "array.h" +#include "utils.h" + +#define WARNING(expr) \ +{ \ + message ("%s:%d: %s\n", __FILE__, __LINE__, expr); \ +} + +#define PAGESIZE 0x10 + +#define RB_LOGBOOK_DISTANCE(a,b,l) ringbuffer_distance (a, b, l->rb_logbook_begin, l->rb_logbook_end) +#define RB_LOGBOOK_INCR(a,b,l) ringbuffer_increment (a, b, l->rb_logbook_begin, l->rb_logbook_end) + +#define RB_PROFILE_DISTANCE(a,b,l) ringbuffer_distance (a, b, l->rb_profile_begin, l->rb_profile_end) +#define RB_PROFILE_INCR(a,b,l) ringbuffer_increment (a, b, l->rb_profile_begin, l->rb_profile_end) + + +static unsigned int +ifloor (unsigned int x, unsigned int n) +{ + // Round down to next lower multiple. + return (x / n) * n; +} + + +static unsigned int +iceil (unsigned int x, unsigned int n) +{ + // Round up to next higher multiple. + return ((x + n - 1) / n) * n; +} + + +static unsigned char +bcd (unsigned char value) +{ + unsigned char lower = (value ) & 0x0F; + unsigned char upper = (value >> 4) & 0x0F; + + return lower + 10 * upper; +} + + +static unsigned int +get_profile_first (const unsigned char data[], const oceanic_common_layout_t *layout) +{ + unsigned int value; + + if (layout->mode == 0) { + value = array_uint16_le (data + 5); + } else { + value = array_uint16_le (data + 4); + } + + return (value & 0x0FFF) * PAGESIZE; +} + + +static unsigned int +get_profile_last (const unsigned char data[], const oceanic_common_layout_t *layout) +{ + unsigned int value; + + if (layout->mode == 0) { + value = array_uint16_le (data + 6) >> 4; + } else { + value = array_uint16_le (data + 6); + } + + return (value & 0x0FFF) * PAGESIZE; +} + + +device_status_t +oceanic_common_device_foreach (device_t *abstract, const oceanic_common_layout_t *layout, const unsigned char fingerprint[], dive_callback_t callback, void *userdata) +{ + assert (abstract != NULL); + assert (layout != NULL); + assert (fingerprint != NULL); + + // Enable progress notifications. + device_progress_t progress = DEVICE_PROGRESS_INITIALIZER; + progress.maximum = 2 * PAGESIZE + + (layout->rb_profile_end - layout->rb_profile_begin) + + (layout->rb_logbook_end - layout->rb_logbook_begin); + device_event_emit (abstract, DEVICE_EVENT_PROGRESS, &progress); + + // Read the device id. + unsigned char id[PAGESIZE] = {0}; + device_status_t rc = device_read (abstract, layout->cf_devinfo, id, sizeof (id)); + if (rc != DEVICE_STATUS_SUCCESS) { + WARNING ("Cannot read device id."); + return rc; + } + + // Update and emit a progress event. + progress.current += PAGESIZE; + device_event_emit (abstract, DEVICE_EVENT_PROGRESS, &progress); + + // Emit a device info event. + device_devinfo_t devinfo; + devinfo.model = array_uint16_be (id + 8); + devinfo.firmware = 0; + devinfo.serial = bcd (id[10]) * 10000 + bcd (id[11]) * 100 + bcd (id[12]); + device_event_emit (abstract, DEVICE_EVENT_DEVINFO, &devinfo); + + // Read the pointer data. + unsigned char pointers[PAGESIZE] = {0}; + rc = device_read (abstract, layout->cf_pointers, pointers, sizeof (pointers)); + if (rc != DEVICE_STATUS_SUCCESS) { + WARNING ("Cannot read pointers."); + return rc; + } + + // Get the logbook pointers. + unsigned int rb_logbook_first = array_uint16_le (pointers + 4); + unsigned int rb_logbook_last = array_uint16_le (pointers + 6); + + // Convert the first/last pointers to begin/end/count pointers. + unsigned int rb_logbook_entry_begin, rb_logbook_entry_end, + rb_logbook_entry_size; + if (rb_logbook_first == layout->rb_logbook_empty && + rb_logbook_last == layout->rb_logbook_empty) + { + // Empty ringbuffer. + rb_logbook_entry_begin = layout->rb_logbook_begin; + rb_logbook_entry_end = layout->rb_logbook_begin; + rb_logbook_entry_size = 0; + } else { + // Non-empty ringbuffer. + if (layout->mode == 0) { + rb_logbook_entry_begin = rb_logbook_first; + rb_logbook_entry_end = RB_LOGBOOK_INCR (rb_logbook_last, PAGESIZE / 2, layout); + rb_logbook_entry_size = RB_LOGBOOK_DISTANCE (rb_logbook_first, rb_logbook_last, layout) + PAGESIZE / 2; + } else { + rb_logbook_entry_begin = rb_logbook_first; + rb_logbook_entry_end = rb_logbook_last; + rb_logbook_entry_size = RB_LOGBOOK_DISTANCE (rb_logbook_first, rb_logbook_last, layout); + // In a typical ringbuffer implementation with only two begin/end + // pointers, there is no distinction possible between an empty and + // a full ringbuffer. Fortunately, the empty ringbuffer is stored + // differently, and we can detect the difference correctly. + if (rb_logbook_first == rb_logbook_last) + rb_logbook_entry_size = layout->rb_logbook_end - layout->rb_logbook_begin; + } + } + + // Check whether the ringbuffer is full. + int full = (rb_logbook_entry_size == (layout->rb_logbook_end - layout->rb_logbook_begin)); + + // Align the pointers to page boundaries. + unsigned int rb_logbook_page_begin, rb_logbook_page_end, + rb_logbook_page_size; + if (full) { + // Full ringbuffer. + rb_logbook_page_begin = iceil (rb_logbook_entry_end, PAGESIZE); + rb_logbook_page_end = rb_logbook_page_begin; + rb_logbook_page_size = rb_logbook_entry_size; + } else { + // Non-full ringbuffer. + rb_logbook_page_begin = ifloor (rb_logbook_entry_begin, PAGESIZE); + rb_logbook_page_end = iceil (rb_logbook_entry_end, PAGESIZE); + rb_logbook_page_size = rb_logbook_entry_size + + (rb_logbook_entry_begin - rb_logbook_page_begin) + + (rb_logbook_page_end - rb_logbook_entry_end); + } + + // Check whether the last entry is not aligned to a page boundary. + int unaligned = (rb_logbook_entry_end != rb_logbook_page_end); + + // Update and emit a progress event. + progress.current += PAGESIZE; + progress.maximum = 2 * PAGESIZE + + (layout->rb_profile_end - layout->rb_profile_begin) + + rb_logbook_page_size; + device_event_emit (abstract, DEVICE_EVENT_PROGRESS, &progress); + + // Memory buffer for the logbook entries. + unsigned char *logbooks = (unsigned char *) malloc (rb_logbook_page_size); + if (logbooks == NULL) + return DEVICE_STATUS_MEMORY; + + // Since entries are not necessary aligned on page boundaries, + // the memory buffer may contain padding entries on both sides. + // The memory area which contains the valid entries is marked + // with a number of additional variables. + unsigned int begin = 0; + unsigned int end = rb_logbook_page_size; + if (!full) { + begin += rb_logbook_entry_begin - rb_logbook_page_begin; + end -= rb_logbook_page_end - rb_logbook_entry_end; + } + + // The logbook ringbuffer is read backwards to retrieve the most recent + // entries first. If an already downloaded entry is identified (by means + // of its fingerprint), the transfer is aborted immediately to reduce + // the transfer time. When necessary, padding entries are downloaded + // (but not processed) to align all read requests on page boundaries. + unsigned int current = end; + unsigned int offset = rb_logbook_page_size; + unsigned int address = rb_logbook_page_end; + unsigned int npages = rb_logbook_page_size / PAGESIZE; + for (unsigned int i = 0; i < npages; ++i) { + // Move to the start of the current page. + if (address == layout->rb_logbook_begin) + address = layout->rb_logbook_end; + address -= PAGESIZE; + offset -= PAGESIZE; + + // Read the logbook page. + rc = device_read (abstract, address, logbooks + offset, PAGESIZE); + if (rc != DEVICE_STATUS_SUCCESS) { + free (logbooks); + return rc; + } + + // Update and emit a progress event. + progress.current += PAGESIZE; + device_event_emit (abstract, DEVICE_EVENT_PROGRESS, &progress); + + // A full ringbuffer needs some special treatment to avoid + // having to download the first/last page twice. When a full + // ringbuffer is not aligned to page boundaries, this page + // will contain both the most recent and oldest entry. + if (full && unaligned) { + if (i == 0) { + // After downloading the first page, move both the oldest + // and most recent entries to their correct location. + unsigned int oldest = rb_logbook_page_end - rb_logbook_entry_end; + unsigned int newest = PAGESIZE - oldest; + // Move the oldest entries down to the start of the buffer. + memcpy (logbooks, logbooks + offset + newest, oldest); + // Move the newest entries up to the end of the buffer. + memmove (logbooks + offset + oldest, logbooks + offset, newest); + // Adjust the current page offset to the new position. + offset += oldest; + } else if (i == npages - 1) { + // After downloading the last page, pretend we have also + // downloaded those oldest entries from the first page. + offset = 0; + } + } + + // Process the logbook entries. + int abort = 0; + while (current != offset && current != begin) { + // Move to the start of the current entry. + current -= PAGESIZE / 2; + + // Compare the fingerprint to identify previously downloaded entries. + if (memcmp (logbooks + current, fingerprint, PAGESIZE / 2) == 0) { + begin = current + PAGESIZE / 2; + abort = 1; + break; + } + } + + // Stop reading pages too. + if (abort) + break; + } + + // Exit if there are no (new) dives. + if (begin == end) { + free (logbooks); + return DEVICE_STATUS_SUCCESS; + } + + // Calculate the total amount of bytes in the profile ringbuffer, + // based on the pointers in the first and last logbook entry. + unsigned int rb_profile_first = get_profile_first (logbooks + begin, layout); + unsigned int rb_profile_last = get_profile_last (logbooks + end - PAGESIZE / 2, layout); + unsigned int rb_profile_end = RB_PROFILE_INCR (rb_profile_last, PAGESIZE, layout); + unsigned int rb_profile_size = RB_PROFILE_DISTANCE (rb_profile_first, rb_profile_last, layout) + PAGESIZE; + + // At this point, we know the exact amount of data + // that needs to be transfered for the profiles. + progress.maximum = progress.current + rb_profile_size; + + // Memory buffer for the profile data. + unsigned char *profiles = (unsigned char *) malloc (rb_profile_size + PAGESIZE / 2); + if (profiles == NULL) { + free (logbooks); + return DEVICE_STATUS_MEMORY; + } + + // Traverse the logbook ringbuffer backwards to retrieve the most recent + // dives first. The logbook ringbuffer is linearized at this point, so + // we do not have to take into account any memory wrapping near the end + // of the memory buffer. + current = end; + offset = rb_profile_size + PAGESIZE / 2; + address = rb_profile_end; + while (current != begin) { + // Move to the start of the current entry. + current -= PAGESIZE / 2; + + // Get the profile pointers. + unsigned int rb_entry_first = get_profile_first (logbooks + current, layout); + unsigned int rb_entry_last = get_profile_last (logbooks + current, layout); + unsigned int rb_entry_end = RB_PROFILE_INCR (rb_entry_last, PAGESIZE, layout); + unsigned int rb_entry_size = RB_PROFILE_DISTANCE (rb_entry_first, rb_entry_last, layout) + PAGESIZE; + + // Make sure the profiles are continuous. + assert (address == rb_entry_end); + + // Read the profile data. + npages = rb_entry_size / PAGESIZE; + for (unsigned int i = 0; i < npages; ++i) { + // Move to the start of the current page. + if (address == layout->rb_profile_begin) + address = layout->rb_profile_end; + address -= PAGESIZE; + offset -= PAGESIZE; + + // Read the profile page. + rc = device_read (abstract, address, profiles + offset, PAGESIZE); + if (rc != DEVICE_STATUS_SUCCESS) { + free (logbooks); + free (profiles); + return rc; + } + + // Update and emit a progress event. + progress.current += PAGESIZE; + device_event_emit (abstract, DEVICE_EVENT_PROGRESS, &progress); + } + + // Prepend the logbook entry to the profile data. The memory buffer + // is large enough to store this entry, but it will be overwritten + // when the next profile is downloaded. + memcpy (profiles + offset - PAGESIZE / 2, logbooks + current, PAGESIZE / 2); + + if (callback && !callback (profiles + offset - PAGESIZE / 2, rb_entry_size + PAGESIZE / 2, userdata)) { + free (logbooks); + free (profiles); + return DEVICE_STATUS_SUCCESS; + } + } + + free (logbooks); + free (profiles); + + return DEVICE_STATUS_SUCCESS; +} diff --git a/src/oceanic_common.h b/src/oceanic_common.h new file mode 100644 index 0000000..450a9e3 --- /dev/null +++ b/src/oceanic_common.h @@ -0,0 +1,57 @@ +/* + * libdivecomputer + * + * Copyright (C) 2009 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 OCEANIC_COMMON_H +#define OCEANIC_COMMON_H + +#include "device.h" + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + +typedef struct oceanic_common_layout_t { + // Device info. + unsigned int cf_devinfo; + // Ringbuffer pointers. + unsigned int cf_pointers; + // Logbook ringbuffer. + unsigned int rb_logbook_empty; + unsigned int rb_logbook_begin; + unsigned int rb_logbook_end; + // Profile ringbuffer + unsigned int rb_profile_empty; + unsigned int rb_profile_begin; + unsigned int rb_profile_end; + // The pointer mode indicates how the global ringbuffer pointers + // should be interpreted (a first/last or a begin/end pair), and + // how the profile pointers are stored in each logbook entry (two + // 12-bit values or two 16-bit values with each 4 bits padding). + unsigned int mode; +} oceanic_common_layout_t; + +device_status_t +oceanic_common_device_foreach (device_t *abstract, const oceanic_common_layout_t *layout, const unsigned char fingerprint[], dive_callback_t callback, void *userdata); + +#ifdef __cplusplus +} +#endif /* __cplusplus */ +#endif /* OCEANIC_COMMON_H */