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 */