--- /dev/null
+/*
+ * Copyright (c) 2009 Communication Group and Eislab at
+ * Lulea University of Technology
+ *
+ * Contact: Laurynas Riliskis, LTU
+ * Mail: laurynas.riliskis@ltu.se
+ * All rights reserved.
+ *
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * - Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the
+ * distribution.
+ * - Neither the name of Communication Group at Lulea University of Technology
+ * nor the names of its contributors may be used to endorse or promote
+ * products derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+ * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL STANFORD
+ * UNIVERSITY OR ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
+ * OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/*
+ * Copyright (c) 2008 Stanford University.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * - Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the
+ * distribution.
+ * - Neither the name of the Stanford University nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+ * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL STANFORD
+ * UNIVERSITY OR ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
+ * OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/**
+ * This file contains M16c/62p mcu-specific routines for implementing
+ * threads in TinyOS
+ *
+ * @author Henrik Makitaavola <henrik.makitaavola@gmail.com>
+ * @author Kevin Klues <klueska@cs.stanford.edu>
+ */
+
+typedef struct thread_regs {
+ uint16_t r0;
+ uint16_t r1;
+ uint16_t r2;
+ uint16_t r3;
+ uint16_t a0;
+ uint16_t a1;
+ uint16_t fb;
+ uint16_t flg;
+ uint16_t mem0;
+ uint16_t mem2;
+ uint16_t mem4;
+ uint16_t mem6;
+ uint16_t mem8;
+ uint16_t mem10;
+ uint16_t mem12;
+ uint16_t mem14;
+} thread_regs_t;
+
+/**
+ * Memory is addressed by 16 bits on M16c/62p but because
+ * is can be addressed in 8 bit units we typedef the
+ * stack pointer as a uint8_t.
+ */
+typedef uint8_t* stack_ptr_t;
+
+#define STACK_TOP(stack, size) \
+ (&(((uint8_t*)stack)[size - 1]))
+
+//Save stack pointer
+#define SAVE_STACK_PTR(t) \
+ asm volatile ("stc sp, %0": "=r"(t->stack_ptr));
+
+//Save status register
+#define SAVE_STATUS(t) \
+ asm volatile ("stc flg, %0": "=r"(t->regs.flg));
+
+//Save General Purpose Registers
+#define SAVE_GPR(t) \
+ asm volatile ("mov.w r0, %0 \n\t" : "=r" ((t)->regs.r0) : ); \
+ asm volatile ("mov.w r1, %0 \n\t" : "=r" ((t)->regs.r1) : ); \
+ asm volatile ("mov.w r2, %0 \n\t" : "=r" ((t)->regs.r2) : ); \
+ asm volatile ("mov.w r3, %0 \n\t" : "=r" ((t)->regs.r3) : ); \
+ asm volatile ("mov.w a0, %0 \n\t" : "=r" ((t)->regs.a0) : ); \
+ asm volatile ("mov.w a1, %0 \n\t" : "=r" ((t)->regs.a1) : ); \
+ asm volatile ("stc fb, %0 \n\t" : "=r" ((t)->regs.fb) : ); \
+ asm volatile ("mov.w mem0, %0 \n\t" : "=r" ((t)->regs.mem0) : ); \
+ asm volatile ("mov.w mem2, %0 \n\t" : "=r" ((t)->regs.mem2) : ); \
+ asm volatile ("mov.w mem4, %0 \n\t" : "=r" ((t)->regs.mem4) : ); \
+ asm volatile ("mov.w mem6, %0 \n\t" : "=r" ((t)->regs.mem6) : ); \
+ asm volatile ("mov.w mem8, %0 \n\t" : "=r" ((t)->regs.mem8) : ); \
+ asm volatile ("mov.w mem10, %0 \n\t" : "=r" ((t)->regs.mem10) : ); \
+ asm volatile ("mov.w mem12, %0 \n\t" : "=r" ((t)->regs.mem12) : ); \
+ asm volatile ("mov.w mem14, %0 \n\t" : "=r" ((t)->regs.mem14) : );
+
+//Restore stack pointer
+#define RESTORE_STACK_PTR(t) \
+ asm volatile ("ldc %0, sp \n\t" :: "r" ((t)->stack_ptr))
+
+//Restore status register
+#define RESTORE_STATUS(t) \
+ asm volatile ("ldc %0, flg \n\t" :: "r" (t->regs.flg) );
+
+//Restore the general purpose registers
+#define RESTORE_GPR(t) \
+ asm volatile ("mov.w %0, r0 \n\t" :: "r" ((t)->regs.r0) ); \
+ asm volatile ("mov.w %0, r1 \n\t" :: "r" ((t)->regs.r1) ); \
+ asm volatile ("mov.w %0, r2 \n\t" :: "r" ((t)->regs.r2) ); \
+ asm volatile ("mov.w %0, r3 \n\t" :: "r" ((t)->regs.r3) ); \
+ asm volatile ("mov.w %0, a0 \n\t" :: "r" ((t)->regs.a0) ); \
+ asm volatile ("mov.w %0, a1 \n\t" :: "r" ((t)->regs.a1) ); \
+ asm volatile ("ldc %0, fb \n\t" :: "r" ((t)->regs.fb) ); \
+ asm volatile ("mov.w %0, mem0 \n\t" :: "r" ((t)->regs.mem0) ); \
+ asm volatile ("mov.w %0, mem2 \n\t" :: "r" ((t)->regs.mem2) ); \
+ asm volatile ("mov.w %0, mem4 \n\t" :: "r" ((t)->regs.mem4) ); \
+ asm volatile ("mov.w %0, mem6 \n\t" :: "r" ((t)->regs.mem6) ); \
+ asm volatile ("mov.w %0, mem8 \n\t" :: "r" ((t)->regs.mem8) ); \
+ asm volatile ("mov.w %0, mem10 \n\t" :: "r" ((t)->regs.mem10) ); \
+ asm volatile ("mov.w %0, mem12 \n\t" :: "r" ((t)->regs.mem12) ); \
+ asm volatile ("mov.w %0, mem14 \n\t" :: "r" ((t)->regs.mem14) );
+
+#define SAVE_TCB(t) \
+ SAVE_GPR(t); \
+ SAVE_STATUS(t); \
+ SAVE_STACK_PTR(t)
+
+#define RESTORE_TCB(t) \
+ RESTORE_STACK_PTR(t); \
+ RESTORE_STATUS(t); \
+ RESTORE_GPR(t)
+
+#define SWITCH_CONTEXTS(from, to) \
+ SAVE_TCB(from); \
+ RESTORE_TCB(to)
+
+#define PREPARE_THREAD(t, start_function) \
+ t->stack_ptr[0] = 0; \
+ t->stack_ptr[-1] = (uint8_t)((uint16_t)&start_function >> 8) & 0xFF; \
+ t->stack_ptr[-2] = (uint8_t)((uint16_t)&start_function) & 0xFF; \
+ t->stack_ptr -= 2; \
+ SAVE_STATUS(t)
--- /dev/null
+// $Id$
+
+/*
+ * "Copyright (c) 2000-2003 The Regents of the University of California.
+ * All rights reserved.
+ *
+ * Permission to use, copy, modify, and distribute this software and its
+ * documentation for any purpose, without fee, and without written agreement is
+ * hereby granted, provided that the above copyright notice, the following
+ * two paragraphs and the author appear in all copies of this software.
+ *
+ * IN NO EVENT SHALL THE UNIVERSITY OF CALIFORNIA BE LIABLE TO ANY PARTY FOR
+ * DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES ARISING OUT
+ * OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN IF THE UNIVERSITY OF
+ * CALIFORNIA HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * THE UNIVERSITY OF CALIFORNIA SPECIFICALLY DISCLAIMS ANY WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
+ * AND FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE PROVIDED HEREUNDER IS
+ * ON AN "AS IS" BASIS, AND THE UNIVERSITY OF CALIFORNIA HAS NO OBLIGATION TO
+ * PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS."
+ *
+ * Copyright (c) 2002-2005 Intel Corporation
+ * All rights reserved.
+ *
+ * This file is distributed under the terms in the attached INTEL-LICENSE
+ * file. If you do not find these files, copies can be found by writing to
+ * Intel Research Berkeley, 2150 Shattuck Avenue, Suite 1300, Berkeley, CA,
+ * 94704. Attention: Intel License Inquiry.
+ */
+
+#include "crc.h"
+#include "At45db.h"
+#include "Timer.h"
+
+/**
+ * Private componenent for the Atmel's AT45DB HAL.
+ *
+ * @author David Gay
+ */
+
+module At45dbP @safe() {
+ provides {
+ interface Init;
+ interface At45db;
+ }
+ uses {
+ interface HplAt45db;
+ interface BusyWait<TMicro, uint16_t>;
+ }
+}
+implementation
+{
+#define CHECKARGS
+
+#if 0
+ uint8_t work[20];
+ uint8_t woffset;
+
+ void wdbg(uint8_t x) {
+ work[woffset++] = x;
+ if (woffset == sizeof work)
+ woffset = 0;
+ }
+#else
+#define wdbg(n)
+#endif
+
+ enum { // requests
+ IDLE,
+ R_READ,
+ R_READCRC,
+ R_WRITE,
+ R_ERASE,
+ R_COPY,
+ R_SYNC,
+ R_SYNCALL,
+ R_FLUSH,
+ R_FLUSHALL,
+ BROKEN // Write failed. Fail all subsequent requests.
+ };
+ uint8_t request;
+ at45pageoffset_t reqOffset, reqBytes;
+ uint8_t * COUNT_NOK(reqBytes) reqBuf;
+ at45page_t reqPage;
+
+ enum {
+ P_READ,
+ P_READCRC,
+ P_WRITE,
+ P_FLUSH,
+ P_FILL,
+ P_ERASE,
+ P_COMPARE,
+ P_COMPARE_CHECK
+ };
+
+ struct {
+ at45page_t page;
+ bool busy : 1;
+ bool clean : 1;
+ bool erased : 1;
+ uint8_t unchecked : 2;
+ } buffer[2];
+ uint8_t selected; // buffer used by the current op
+ uint8_t checking;
+ bool flashBusy;
+
+ // Select a command for the current buffer
+#define OPN(n, name) ((n) ? name ## 1 : name ## 2)
+#define OP(name) OPN(selected, name)
+
+ command error_t Init.init() {
+ request = IDLE;
+ flashBusy = TRUE;
+
+ // pretend we're on an invalid non-existent page
+ buffer[0].page = buffer[1].page = AT45_MAX_PAGES;
+ buffer[0].busy = buffer[1].busy = FALSE;
+ buffer[0].clean = buffer[1].clean = TRUE;
+ buffer[0].unchecked = buffer[1].unchecked = 0;
+ buffer[0].erased = buffer[1].erased = FALSE;
+
+ return SUCCESS;
+ }
+
+ void flashIdle() {
+ flashBusy = buffer[0].busy = buffer[1].busy = FALSE;
+ }
+
+ void requestDone(error_t result, uint16_t computedCrc, uint8_t newState);
+ void handleRWRequest();
+
+ task void taskSuccess() {
+ requestDone(SUCCESS, 0, IDLE);
+ }
+ task void taskFail() {
+ requestDone(FAIL, 0, IDLE);
+ }
+
+ void checkBuffer(uint8_t buf) {
+ if (flashBusy)
+ {
+ call HplAt45db.waitIdle();
+ return;
+ }
+ call HplAt45db.compare(OPN(buf, AT45_C_COMPARE_BUFFER), buffer[buf].page);
+ checking = buf;
+ }
+
+ void flushBuffer() {
+ if (flashBusy)
+ {
+ call HplAt45db.waitIdle();
+ return;
+ }
+ call HplAt45db.flush(buffer[selected].erased ?
+ OP(AT45_C_QFLUSH_BUFFER) :
+ OP(AT45_C_FLUSH_BUFFER),
+ buffer[selected].page);
+ }
+
+ event void HplAt45db.waitIdleDone() {
+ flashIdle();
+ // Eager compare - this steals the current command
+#if 0
+ if ((buffer[0].unchecked || buffer[1].unchecked) &&
+ cmdPhase != P_COMPARE)
+ checkBuffer(buffer[0].unchecked ? 0 : 1);
+ else
+#endif
+ handleRWRequest();
+ }
+
+ event void HplAt45db.waitCompareDone(bool ok) {
+ flashIdle();
+
+ if (ok)
+ buffer[checking].unchecked = 0;
+ else if (buffer[checking].unchecked < 2)
+ buffer[checking].clean = FALSE;
+ else
+ {
+ requestDone(FAIL, 0, BROKEN);
+ return;
+ }
+ handleRWRequest();
+ }
+
+ event void HplAt45db.readDone() {
+ requestDone(SUCCESS, 0, IDLE);
+ }
+
+ event void HplAt45db.writeDone() {
+ buffer[selected].clean = FALSE;
+ buffer[selected].unchecked = 0;
+ requestDone(SUCCESS, 0, IDLE);
+ }
+
+ event void HplAt45db.crcDone(uint16_t crc) {
+ requestDone(SUCCESS, crc, IDLE);
+ }
+
+ event void HplAt45db.flushDone() {
+ flashBusy = TRUE;
+ buffer[selected].clean = buffer[selected].busy = TRUE;
+ buffer[selected].unchecked++;
+ buffer[selected].erased = FALSE;
+ handleRWRequest();
+ }
+
+ event void HplAt45db.compareDone() {
+ flashBusy = TRUE;
+ buffer[checking].busy = TRUE;
+ // The 10us wait makes old mica motes (Atmega 103) happy, for
+ // some mysterious reason (w/o this wait, the first compare
+ // always fails, even though the compare after the rewrite
+ // succeeds...)
+ call BusyWait.wait(10);
+ call HplAt45db.waitCompare();
+ }
+
+ event void HplAt45db.fillDone() {
+ flashBusy = TRUE;
+ buffer[selected].page = reqPage;
+ buffer[selected].clean = buffer[selected].busy = TRUE;
+ buffer[selected].erased = FALSE;
+ handleRWRequest();
+ }
+
+ event void HplAt45db.eraseDone() {
+ flashBusy = TRUE;
+ // The buffer contains garbage, but we don't care about the state
+ // of bits on this page anyway (if we do, we'll perform a
+ // subsequent write)
+ buffer[selected].page = AT45_MAX_PAGES;
+ buffer[selected].clean = TRUE;
+ buffer[selected].erased = TRUE;
+ requestDone(SUCCESS, 0, IDLE);
+ }
+
+ void syncOrFlushAll(uint8_t newReq);
+
+ void handleRWRequest() {
+ if (reqPage == buffer[selected].page)
+ switch (request)
+ {
+ case R_ERASE:
+ switch (reqOffset)
+ {
+ case AT45_ERASE:
+ if (flashBusy)
+ call HplAt45db.waitIdle();
+ else
+ call HplAt45db.erase(AT45_C_ERASE_PAGE, reqPage);
+ break;
+ case AT45_PREVIOUSLY_ERASED:
+ // We believe the user...
+ buffer[selected].erased = TRUE;
+ /* Fallthrough */
+ case AT45_DONT_ERASE:
+ // The buffer contains garbage, but we don't care about the state
+ // of bits on this page anyway (if we do, we'll perform a
+ // subsequent write)
+ buffer[selected].clean = TRUE;
+ requestDone(SUCCESS, 0, IDLE);
+ break;
+ }
+ break;
+
+ case R_COPY:
+ if (!buffer[selected].clean) // flush any modifications
+ flushBuffer();
+ else
+ {
+ // Just redesignate as destination page, and mark it dirty.
+ // It will eventually be flushed, completing the copy.
+ buffer[selected].page = reqOffset;
+ buffer[selected].clean = FALSE;
+ post taskSuccess();
+ }
+ break;
+
+ case R_SYNC: case R_SYNCALL:
+ if (buffer[selected].clean && buffer[selected].unchecked)
+ {
+ checkBuffer(selected);
+ return;
+ }
+ /* fall through */
+ case R_FLUSH: case R_FLUSHALL:
+ if (!buffer[selected].clean)
+ flushBuffer();
+ else if (request == R_FLUSH || request == R_SYNC)
+ post taskSuccess();
+ else
+ {
+ // Check for more dirty pages
+ uint8_t oreq = request;
+
+ request = IDLE;
+ syncOrFlushAll(oreq);
+ }
+ break;
+
+ case R_READ:
+ if (buffer[selected].busy)
+ call HplAt45db.waitIdle();
+ else
+ call HplAt45db.readBuffer(OP(AT45_C_READ_BUFFER), reqOffset,
+ reqBuf, reqBytes);
+ break;
+
+ case R_READCRC:
+ if (buffer[selected].busy)
+ call HplAt45db.waitIdle();
+ else
+ /* Hack: baseCrc was stored in reqBuf */
+ call HplAt45db.crc(OP(AT45_C_READ_BUFFER), 0, reqOffset, reqBytes,
+ (uint16_t)reqBuf);
+ break;
+
+ case R_WRITE:
+ if (buffer[selected].busy)
+ call HplAt45db.waitIdle();
+ else
+ call HplAt45db.write(OP(AT45_C_WRITE_BUFFER), 0, reqOffset,
+ reqBuf, reqBytes);
+ break;
+ }
+ else if (!buffer[selected].clean)
+ flushBuffer();
+ else if (buffer[selected].unchecked)
+ checkBuffer(selected);
+ else
+ {
+ // just get the new page (except for erase)
+ if (request == R_ERASE)
+ {
+ buffer[selected].page = reqPage;
+ handleRWRequest();
+ }
+ else if (flashBusy)
+ call HplAt45db.waitIdle();
+ else
+ call HplAt45db.fill(OP(AT45_C_FILL_BUFFER), reqPage);
+ }
+ }
+
+ void requestDone(error_t result, uint16_t computedCrc, uint8_t newState) {
+ uint8_t orequest = request;
+
+ request = newState;
+ switch (orequest)
+ {
+ case R_READ: signal At45db.readDone(result); break;
+ case R_READCRC: signal At45db.computeCrcDone(result, computedCrc); break;
+ case R_WRITE: signal At45db.writeDone(result); break;
+ case R_SYNC: case R_SYNCALL: signal At45db.syncDone(result); break;
+ case R_FLUSH: case R_FLUSHALL: signal At45db.flushDone(result); break;
+ case R_ERASE: signal At45db.eraseDone(result); break;
+ case R_COPY: signal At45db.copyPageDone(result); break;
+ }
+ }
+
+ void newRequest(uint8_t req, at45page_t page, at45pageoffset_t offset,
+ void * COUNT_NOK(n) reqdata, at45pageoffset_t n) {
+ request = req;
+
+ reqBuf = NULL;
+ reqBytes = n;
+ reqBuf = reqdata;
+ reqPage = page;
+ reqOffset = offset;
+
+ if (page == buffer[0].page)
+ selected = 0;
+ else if (page == buffer[1].page)
+ selected = 1;
+ else
+ selected = !selected; // LRU with 2 buffers...
+
+#ifdef CHECKARGS
+ if (page >= AT45_MAX_PAGES || offset >= AT45_PAGE_SIZE ||
+ n > AT45_PAGE_SIZE || offset + n > AT45_PAGE_SIZE)
+ post taskFail();
+ else
+#endif
+ handleRWRequest();
+ }
+
+ command void At45db.read(at45page_t page, at45pageoffset_t offset,
+ void *reqdata, at45pageoffset_t n) {
+ newRequest(R_READ, page, offset, reqdata, n);
+ }
+
+ command void At45db.computeCrc(at45page_t page,
+ at45pageoffset_t offset,
+ at45pageoffset_t n,
+ uint16_t baseCrc) {
+ /* This is a hack (store crc in reqBuf), but it saves 2 bytes of RAM */
+ newRequest(R_READCRC, page, offset, TCAST(uint8_t * COUNT(n), baseCrc), n);
+ }
+
+ command void At45db.write(at45page_t page, at45pageoffset_t offset,
+ void *reqdata, at45pageoffset_t n) {
+ newRequest(R_WRITE, page, offset, reqdata, n);
+ }
+
+
+ command void At45db.erase(at45page_t page, uint8_t eraseKind) {
+ newRequest(R_ERASE, page, eraseKind, NULL, 0);
+ }
+
+ command void At45db.copyPage(at45page_t from, at45page_t to) {
+ /* Assumes at45pageoffset_t can hold an at45page_t. A little icky */
+ newRequest(R_COPY, from, to, NULL, 0);
+ }
+
+ void syncOrFlush(at45page_t page, uint8_t newReq) {
+ request = newReq;
+
+ if (buffer[0].page == page)
+ selected = 0;
+ else if (buffer[1].page == page)
+ selected = 1;
+ else
+ {
+ post taskSuccess();
+ return;
+ }
+
+ buffer[selected].unchecked = 0;
+ handleRWRequest();
+ }
+
+ command void At45db.sync(at45page_t page) {
+ syncOrFlush(page, R_SYNC);
+ }
+
+ command void At45db.flush(at45page_t page) {
+ syncOrFlush(page, R_FLUSH);
+ }
+
+ void syncOrFlushAll(uint8_t newReq) {
+ request = newReq;
+
+ if (!buffer[0].clean)
+ selected = 0;
+ else if (!buffer[1].clean)
+ selected = 1;
+ else
+ {
+ post taskSuccess();
+ return;
+ }
+
+ buffer[selected].unchecked = 0;
+ handleRWRequest();
+ }
+
+ command void At45db.syncAll() {
+ syncOrFlushAll(R_SYNCALL);
+ }
+
+ command void At45db.flushAll() {
+ syncOrFlushAll(R_FLUSHALL);
+ }
+}
--- /dev/null
+/*
+ * "Copyright (c) 2000-2004 The Regents of the University of California.
+ * All rights reserved.
+ *
+ * Permission to use, copy, modify, and distribute this software and its
+ * documentation for any purpose, without fee, and without written agreement is
+ * hereby granted, provided that the above copyright notice, the following
+ * two paragraphs and the author appear in all copies of this software.
+ *
+ * IN NO EVENT SHALL THE UNIVERSITY OF CALIFORNIA BE LIABLE TO ANY PARTY FOR
+ * DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES ARISING OUT
+ * OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN IF THE UNIVERSITY OF
+ * CALIFORNIA HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * THE UNIVERSITY OF CALIFORNIA SPECIFICALLY DISCLAIMS ANY WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
+ * AND FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE PROVIDED HEREUNDER IS
+ * ON AN "AS IS" BASIS, AND THE UNIVERSITY OF CALIFORNIA HAS NO OBLIGATION TO
+ * PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS."
+ *
+ * Copyright (c) 2002-2006 Intel Corporation
+ * All rights reserved.
+ *
+ * This file is distributed under the terms in the attached INTEL-LICENSE
+ * file. If you do not find these files, copies can be found by writing to
+ * Intel Research Berkeley, 2150 Shattuck Avenue, Suite 1300, Berkeley, CA,
+ * 94704. Attention: Intel License Inquiry.
+ */
+
+#include <Storage.h>
+#include <crc.h>
+
+/**
+ * Private component of the AT45DB implementation of the log storage
+ * abstraction.
+ *
+ * @author: David Gay <dgay@acm.org>
+ * @author: Jonathan Hui <jwhui@cs.berkeley.edu>
+ */
+
+module LogStorageP @safe() {
+ provides {
+ interface LogRead[uint8_t logId];
+ interface LogWrite[uint8_t logId];
+ }
+ uses {
+ interface At45db;
+ interface At45dbVolume[uint8_t logId];
+ interface Resource[uint8_t logId];
+ }
+}
+implementation
+{
+ /* Some design notes.
+
+ - The logId's in the LogRead and LogWrites are shifted left by 1 bit.
+ The low-order bit is 1 for circular logs, 0 for linear ones
+ (see newRequest and endRequest, and the LogStorageC configuration)
+
+ - Data is written sequentially to the pages of a log volume. Each page
+ ends with a footer (nx_struct pageinfo) recording metadata on the
+ current page:
+ o a cookie
+ o the "position" of the current page in the log (see below)
+ o the offset of the last record on this page (i.e., the offset
+ at which the last append ended) - only valid if flags & F_LASTVALID
+ o flags:
+ x F_SYNC page was synchronised - data after lastRecordOffset
+ is not log data; implies F_LASTVALID
+ x F_CIRCLED this page is not from the first run through the log's
+ pages (never set in linear logs)
+ x F_LASTVALID not set if no record ended on this page
+ o a CRC
+
+ - "Positions" are stored in the metadata, used as cookies by
+ currentOffset and seek, and stored in the wpos and rpos fields of the
+ volume state structure. They represent the number of bytes that
+ writing has advanced in the log since the log was erased, with
+ PAGE_SIZE added. Note that this is basically the number of bytes
+ written, except that when a page is synchronised unused bytes in the
+ page count towards increasing the position.
+
+ As a result, on page p, the following equation holds:
+ (metadata(p).pos - PAGE_SIZE) % volume-size == p * PAGE_SIZE
+ (this also means that the "position" metadata field could be replaced
+ by a count of the number of times writing has cycled through the log,
+ reducing the metadata size)
+
+ The PAGE_SIZE offset on positions is caused by Invariant 2 below: to
+ ensure that Invariant 2 is respected, at flash erase time, we write a
+ valid page with position 0 to the last block of the flash. As a result,
+ the first writes to the flash, in page 0, are at "position" PAGE_SIZE.
+
+ - This code is designed to deal with "one-at-a-time" failures (i.e.,
+ the system will not modify any blocks after a previous failed
+ write). This should allow recovery from:
+ o arbitrary reboots
+ o write failure (the underlying PageEEPROM shuts down after any
+ write fails; all pages are flushed before moving on to the next
+ page)
+ It will not recover from arbitrary data corruption
+
+ - When sync is called, the current write page is written to flash with an
+ F_SYNC flag and writing continues on the next page (wasting on average
+ half a flasg page)
+
+ - We maintain the following invariants on log volumes, even in the face
+ of the "one-at-a-time" failures described above:
+ 1) at least one of the first and last blocks are valid
+ 2) the last block, if valid, has the F_SYNC flag
+
+ - Locating the log boundary page (the page with the greatest position):
+
+ Invariant 1, the one-at-a-time failure model and the metadata position
+ definition guarantees that the physical flash pages have the following
+ properties:
+ an initial set of V1 valid pages,
+ followed by a set of I invalid pages,
+ followed by a set of V2 valid pages
+ with V1+i+V2=total-number-of-pages, and V1, V2, I >= 0
+ Additionally, the position of all pages in V1 is greater than in V2,
+ and consecutive pages in V1 (respectively V2) have greater positions
+ than their predecessors.
+
+ From this, it's possible to locate the log boundary page (the page with
+ the greatest position) using the following algorithm:
+ o let basepos=metadata(lastpage).pos, or 0 if the last page is invalid
+ o locate (using a binary search) the page p with the largest position
+ greater than basepos
+ invalid pages can be assumed to have positions less than basepos
+ if there is no such page p, let p = lastpage
+
+ Once the log boundary page is known, we resume writing at the last
+ page before p with a record boundary (Invariant 2, combined with
+ limiting individual records to volumesize - PAGE_SIZE ensures there
+ will be such a page).
+
+ - The read pointer has a special "invalid" state which represents the
+ current beginning of the log. In that state, LogRead.currentOffset()
+ returns SEEK_BEGINNING rather than a regular position.
+
+ The read pointer is invalidated:
+ o at boot time
+ o after the volume is erased
+ o after the write position "catches up" with the read position
+ o after a failed seek
+
+ Reads from an invalid pointer:
+ o start reading from the beginning of the flash if we are on the
+ first run through the log volume
+ o start reading at the first valid page after the write page with
+ an F_LASTVALID flag; the read offset is set to the lastRecordOffset
+ value
+ if this page has the SYNC flag, we start at the beginning of the
+ next page
+ */
+
+
+ enum {
+ F_SYNC = 1,
+ F_CIRCLED = 2,
+ F_LASTVALID = 4
+ };
+
+ nx_struct pageinfo {
+ nx_uint16_t magic;
+ nx_uint32_t pos;
+ nx_uint16_t lastRecordOffset;
+ nx_uint8_t flags;
+ nx_uint16_t crc;
+ };
+
+ enum {
+ N = uniqueCount(UQ_LOG_STORAGE),
+ NO_CLIENT = 0xff,
+ PAGE_SIZE = AT45_PAGE_SIZE - sizeof(nx_struct pageinfo),
+ PERSISTENT_MAGIC = 0x4256,
+ };
+
+ enum {
+ R_IDLE,
+ R_ERASE,
+ R_APPEND,
+ R_SYNC,
+ R_READ,
+ R_SEEK
+ };
+
+ enum {
+ META_IDLE,
+ META_LOCATEFIRST,
+ META_LOCATE,
+ META_LOCATELAST,
+ META_SEEK,
+ META_READ,
+ META_WRITE
+ };
+
+ uint8_t client = NO_CLIENT;
+ uint8_t metaState;
+ bool recordsLost;
+ at45page_t firstPage, lastPage;
+ storage_len_t pos;
+ nx_struct pageinfo metadata;
+
+ struct {
+ /* The latest request made for this client, and it's arguments */
+ uint8_t request;
+ uint8_t *COUNT_NOK(len) buf;
+ storage_len_t len;
+
+ /* Log r/w positions */
+ bool positionKnown : 1;
+ bool circular : 1;
+ bool circled : 1;
+ bool rvalid : 1;
+ uint32_t wpos; /* Bytes since start of logging */
+ at45page_t wpage; /* Current write page */
+ at45pageoffset_t woffset; /* Offset on current write page */
+ uint32_t rpos; /* Bytes since start of logging */
+ at45page_t rpage; /* Current read page */
+ at45pageoffset_t roffset; /* Offset on current read page */
+ at45pageoffset_t rend; /* Last valid offset on current read page */
+ } s[N];
+
+ at45page_t firstVolumePage() {
+ return call At45dbVolume.remap[client](0);
+ }
+
+ at45page_t npages() {
+ return call At45dbVolume.volumeSize[client]();
+ }
+
+ at45page_t lastVolumePage() {
+ return call At45dbVolume.remap[client](npages());
+ }
+
+ void setWritePage(at45page_t page) {
+ if (s[client].circular && page == lastVolumePage())
+ {
+ s[client].circled = TRUE;
+ page = firstVolumePage();
+ }
+ s[client].wpage = page;
+ s[client].woffset = 0;
+ }
+
+ void invalidateReadPointer() {
+ s[client].rvalid = FALSE;
+ }
+
+ void crcPage(at45page_t page) {
+ call At45db.computeCrc(page, 0,
+ PAGE_SIZE + offsetof(nx_struct pageinfo, crc), 0);
+ }
+
+ void readMetadata(at45page_t page) {
+ call At45db.read(page, PAGE_SIZE, &metadata, sizeof metadata);
+ }
+
+ void writeMetadata(at45page_t page) {
+ call At45db.write(page, PAGE_SIZE, &metadata, sizeof metadata);
+ }
+
+ void wmetadataStart();
+
+ void sync() {
+ metadata.flags = F_SYNC | F_LASTVALID;
+ metadata.lastRecordOffset = s[client].woffset;
+ /* rend is now no longer the end of the page */
+ if (s[client].rpage == s[client].wpage)
+ s[client].rend = s[client].woffset;
+ wmetadataStart();
+ }
+
+ /* ------------------------------------------------------------------ */
+ /* Queue and initiate user requests */
+ /* ------------------------------------------------------------------ */
+
+ void eraseStart();
+ void appendStart();
+ void syncStart();
+ void readStart();
+ void locateStart();
+ void rmetadataStart();
+ void seekStart();
+
+ void startRequest() {
+ if (!s[client].positionKnown && s[client].request != R_ERASE)
+ {
+ locateStart();
+ return;
+ }
+
+ metaState = META_IDLE;
+ switch (s[client].request)
+ {
+ case R_ERASE: eraseStart(); break;
+ case R_APPEND: appendStart(); break;
+ case R_SYNC: syncStart(); break;
+ case R_READ: readStart(); break;
+ case R_SEEK: seekStart(); break;
+ }
+ }
+
+ void endRequest(error_t ok) {
+ uint8_t c = client;
+ uint8_t request = s[c].request;
+ storage_len_t actualLen = pos;
+ void *ptr = s[c].buf;
+
+ client = NO_CLIENT;
+ s[c].request = R_IDLE;
+ call Resource.release[c]();
+
+ c = c << 1 | s[c].circular;
+ switch (request)
+ {
+ case R_ERASE: signal LogWrite.eraseDone[c](ok); break;
+ case R_APPEND: signal LogWrite.appendDone[c](ptr, actualLen, recordsLost, ok); break;
+ case R_SYNC: signal LogWrite.syncDone[c](ok); break;
+ case R_READ: signal LogRead.readDone[c](ptr, actualLen, ok); break;
+ case R_SEEK: signal LogRead.seekDone[c](ok); break;
+ }
+ }
+
+ /* Enqueue request and request the underlying flash */
+ error_t newRequest(uint8_t newRequest, uint8_t id,
+ uint8_t *COUNT_NOK(length) buf, storage_len_t length) {
+ s[id >> 1].circular = id & 1;
+ id >>= 1;
+
+ if (s[id].request != R_IDLE)
+ return EBUSY;
+
+ s[id].request = newRequest;
+ s[id].buf = NULL;
+ s[id].len = length;
+ s[id].buf = buf;
+ call Resource.request[id]();
+
+ return SUCCESS;
+ }
+
+ event void Resource.granted[uint8_t id]() {
+ client = id;
+ pos = 0;
+ startRequest();
+ }
+
+ command error_t LogWrite.append[uint8_t id](void* buf, storage_len_t length) {
+ if (length > call LogRead.getSize[id]() - PAGE_SIZE)
+ /* Writes greater than the volume size are invalid.
+ Writes equal to the volume size could break the log volume
+ invariant (see next comment).
+ Writes that span the whole volume could lead to problems
+ at boot time (no valid block with a record boundary).
+ Refuse them all. */
+ return EINVAL;
+ else
+ return newRequest(R_APPEND, id, buf, length);
+ }
+
+ command storage_cookie_t LogWrite.currentOffset[uint8_t id]() {
+ return s[id >> 1].wpos;
+ }
+
+ command error_t LogWrite.erase[uint8_t id]() {
+ return newRequest(R_ERASE, id, NULL, 0);
+ }
+
+ command error_t LogWrite.sync[uint8_t id]() {
+ return newRequest(R_SYNC, id, NULL, 0);
+ }
+
+ command error_t LogRead.read[uint8_t id](void* buf, storage_len_t length) {
+ return newRequest(R_READ, id, buf, length);
+ }
+
+ command storage_cookie_t LogRead.currentOffset[uint8_t id]() {
+ id >>= 1;
+ return s[id].rvalid ? s[id].rpos : SEEK_BEGINNING;
+ }
+
+ command error_t LogRead.seek[uint8_t id](storage_cookie_t offset) {
+ return newRequest(R_SEEK, id, TCAST(void *COUNT(offset), ((uint16_t)(offset >> 16))), offset);
+ }
+
+ command storage_len_t LogRead.getSize[uint8_t id]() {
+ return call At45dbVolume.volumeSize[id >> 1]() * (storage_len_t)PAGE_SIZE;
+ }
+
+ /* ------------------------------------------------------------------ */
+ /* Erase */
+ /* ------------------------------------------------------------------ */
+
+ void eraseMetadataDone() {
+ /* Set write pointer to the beginning of the flash */
+ s[client].wpos = PAGE_SIZE; // last page has offset 0 and is before us
+ s[client].circled = FALSE;
+ setWritePage(firstVolumePage());
+
+ invalidateReadPointer();
+
+ s[client].positionKnown = TRUE;
+ endRequest(SUCCESS);
+ }
+
+ void eraseEraseDone() {
+ if (firstPage == lastPage - 1)
+ {
+ /* We create a valid, synced last page (see invariants) */
+ metadata.flags = F_SYNC | F_LASTVALID;
+ metadata.lastRecordOffset = 0;
+ setWritePage(firstPage);
+ s[client].circled = FALSE;
+ s[client].wpos = 0;
+ wmetadataStart();
+ }
+ else
+ call At45db.erase(firstPage++, AT45_ERASE);
+ }
+
+ void eraseStart() {
+ s[client].positionKnown = FALSE; // in case erase fails
+ firstPage = firstVolumePage();
+ lastPage = lastVolumePage();
+ eraseEraseDone();
+ }
+
+ /* ------------------------------------------------------------------ */
+ /* Locate log boundaries */
+ /* ------------------------------------------------------------------ */
+
+ void locateLastRecord();
+
+ void locateLastCrcDone(uint16_t crc) {
+ if (crc != metadata.crc)
+ {
+ locateLastRecord();
+ return;
+ }
+
+ /* We've found the last valid page with a record-end. Set up
+ the read and write positions. */
+ invalidateReadPointer();
+
+ if (metadata.flags & F_SYNC) /* must start on next page */
+ {
+ /* We need to special case the empty log, as we don't want
+ to wrap around in the case of a full, non-circular log
+ with a sync on its last page. */
+ if (firstPage == lastPage && !metadata.pos)
+ setWritePage(firstVolumePage());
+ else
+ setWritePage(firstPage + 1);
+ s[client].wpos = metadata.pos + PAGE_SIZE;
+ }
+ else
+ {
+ s[client].wpage = firstPage;
+ s[client].woffset = metadata.lastRecordOffset;
+ s[client].wpos = metadata.pos + metadata.lastRecordOffset;
+ }
+
+ s[client].circled = (metadata.flags & F_CIRCLED) != 0;
+ if (s[client].circled && !s[client].circular) // oops
+ {
+ endRequest(FAIL);
+ return;
+ }
+
+ /* And we can now proceed to the real request */
+ s[client].positionKnown = TRUE;
+ startRequest();
+ }
+
+ void locateLastReadDone() {
+ if (metadata.magic == PERSISTENT_MAGIC && metadata.flags & F_LASTVALID)
+ crcPage(firstPage);
+ else
+ locateLastRecord();
+ }
+
+ void locateLastRecord() {
+ if (firstPage == lastPage)
+ {
+ /* We walked all the way back to the last page, and it's not
+ valid. The log-volume invariant is not holding. Fail out. */
+ endRequest(FAIL);
+ return;
+ }
+
+ if (firstPage == firstVolumePage())
+ firstPage = lastPage;
+ else
+ firstPage--;
+
+ readMetadata(firstPage);
+ }
+
+ void located() {
+ metaState = META_LOCATELAST;
+ /* firstPage is one after last valid page, but the last page with
+ a record end may be some pages earlier. Search for it. */
+ lastPage = lastVolumePage() - 1;
+ locateLastRecord();
+ }
+
+ at45page_t locateCurrentPage() {
+ return firstPage + ((lastPage - firstPage) >> 1);
+ }
+
+ void locateBinarySearch() {
+ if (lastPage <= firstPage)
+ located();
+ else
+ readMetadata(locateCurrentPage());
+ }
+
+ void locateGreaterThan() {
+ firstPage = locateCurrentPage() + 1;
+ locateBinarySearch();
+ }
+
+ void locateLessThan() {
+ lastPage = locateCurrentPage();
+ locateBinarySearch();
+ }
+
+ void locateCrcDone(uint16_t crc) {
+ if (crc == metadata.crc)
+ {
+ s[client].wpos = metadata.pos;
+ locateGreaterThan();
+ }
+ else
+ locateLessThan();
+ }
+
+ void locateReadDone() {
+ if (metadata.magic == PERSISTENT_MAGIC && s[client].wpos < metadata.pos)
+ crcPage(locateCurrentPage());
+ else
+ locateLessThan();
+ }
+
+ void locateFirstCrcDone(uint16_t crc) {
+ if (metadata.magic == PERSISTENT_MAGIC && crc == metadata.crc)
+ s[client].wpos = metadata.pos;
+ else
+ s[client].wpos = 0;
+
+ metaState = META_LOCATE;
+ locateBinarySearch();
+ }
+
+ void locateFirstReadDone() {
+ crcPage(lastPage);
+ }
+
+ /* Locate log beginning and ending. See description at top of file. */
+ void locateStart() {
+ metaState = META_LOCATEFIRST;
+ firstPage = firstVolumePage();
+ lastPage = lastVolumePage() - 1;
+ readMetadata(lastPage);
+ }
+
+ /* ------------------------------------------------------------------ */
+ /* Append */
+ /* ------------------------------------------------------------------ */
+
+ void appendContinue() {
+ uint8_t *buf = s[client].buf + pos;
+ at45pageoffset_t offset = s[client].woffset, count;
+ storage_len_t len = s[client].len - pos;
+
+ if (len == 0)
+ {
+ endRequest(SUCCESS);
+ return;
+ }
+
+ if (s[client].wpage == lastVolumePage())
+ {
+ /* We reached the end of a linear log */
+ endRequest(ESIZE);
+ return;
+ }
+
+ if (offset + len <= PAGE_SIZE)
+ count = len;
+ else
+ count = PAGE_SIZE - offset;
+
+ s[client].wpos += count;
+ s[client].woffset += count;
+ pos += count;
+
+ /* We normally lose data at the point we make the first write to a
+ page in a log that has circled. */
+ if (offset == 0 && s[client].circled)
+ recordsLost = TRUE;
+
+ call At45db.write(s[client].wpage, offset, buf, count);
+ }
+
+ void appendWriteDone() {
+ if (s[client].woffset == PAGE_SIZE) /* Time to write metadata */
+ wmetadataStart();
+ else
+ endRequest(SUCCESS);
+ }
+
+ void appendMetadataDone() { // metadata of previous page flushed
+ /* Setup metadata in case we overflow this page too */
+ metadata.flags = 0;
+ appendContinue();
+ }
+
+ void appendSyncDone() {
+ s[client].wpos = metadata.pos + PAGE_SIZE;
+ appendStart();
+ }
+
+ void appendStart() {
+ storage_len_t len = s[client].len - pos;
+ storage_len_t vlen = (storage_len_t)npages() * PAGE_SIZE;
+
+ recordsLost = FALSE;
+
+ /* If request would span the end of the flash, sync, to maintain the
+ invariant that the last flash page is synced and that either
+ the first or last pages are valid.
+
+ Note that >= in the if below means we won't write a record that
+ would end on the last byte of the last page, as this would mean that
+ we would not sync the last page, breaking the log volume
+ invariant */
+ if ((s[client].wpos - PAGE_SIZE) % vlen >= vlen - len)
+ sync();
+ else
+ {
+ /* Set lastRecordOffset in case we need to write metadata (see
+ wmetadataStart) */
+ metadata.lastRecordOffset = s[client].woffset;
+ metadata.flags = F_LASTVALID;
+ appendContinue();
+ }
+ }
+
+ /* ------------------------------------------------------------------ */
+ /* Sync */
+ /* ------------------------------------------------------------------ */
+
+ void syncStart() {
+ if (s[client].woffset == 0) /* we can't lose any writes */
+ endRequest(SUCCESS);
+ else
+ sync();
+ }
+
+ void syncMetadataDone() {
+ /* Write position reflect the absolute position in the flash, not
+ user-bytes written. So update wpos to reflect sync effects. */
+ s[client].wpos = metadata.pos + PAGE_SIZE;
+ endRequest(SUCCESS);
+ }
+
+ /* ------------------------------------------------------------------ */
+ /* Write block metadata */
+ /* ------------------------------------------------------------------ */
+
+ void wmetadataStart() {
+ /* The caller ensures that metadata.flags (except F_CIRCLED) and
+ metadata.lastRecordOffset are set correctly. */
+ metaState = META_WRITE;
+ firstPage = s[client].wpage; // remember page to commit
+ metadata.pos = s[client].wpos - s[client].woffset;
+ metadata.magic = PERSISTENT_MAGIC;
+ if (s[client].circled)
+ metadata.flags |= F_CIRCLED;
+
+ call At45db.computeCrc(firstPage, 0, PAGE_SIZE, 0);
+
+ /* We move to the next page now. If writing the metadata fails, we'll
+ simply leave the invalid page in place. Trying to recover seems
+ complicated, and of little benefit (note that in practice, At45dbC
+ shuts down after a failed write, so nothing is really going to
+ happen after that anyway). */
+ setWritePage(s[client].wpage + 1);
+
+ /* Invalidate read pointer if we reach it's page */
+ if (s[client].wpage == s[client].rpage)
+ invalidateReadPointer();
+ }
+
+ void wmetadataCrcDone(uint16_t crc) {
+ uint8_t i, *md;
+
+ // Include metadata in crc
+ md = (uint8_t *)&metadata;
+ for (i = 0; i < offsetof(nx_struct pageinfo, crc); i++)
+ crc = crcByte(crc, md[i]);
+ metadata.crc = crc;
+
+ // And save it
+ writeMetadata(firstPage);
+ }
+
+ void wmetadataWriteDone() {
+ metaState = META_IDLE;
+ if (metadata.flags & F_SYNC)
+ call At45db.sync(firstPage);
+ else
+ call At45db.flush(firstPage);
+ }
+
+ /* ------------------------------------------------------------------ */
+ /* Read */
+ /* ------------------------------------------------------------------ */
+
+ void readContinue() {
+ uint8_t *buf = s[client].buf + pos;
+ at45pageoffset_t offset = s[client].roffset, count;
+ at45pageoffset_t end = s[client].rend;
+ storage_len_t len = s[client].len - pos;
+
+ if (len == 0)
+ {
+ endRequest(SUCCESS);
+ return;
+ }
+
+ if (!s[client].rvalid)
+ {
+ if (s[client].circled)
+ /* Find a valid page after wpage, skipping invalid pages */
+ s[client].rpage = s[client].wpage;
+ else
+ {
+ /* resume reading at the beginning of the first page */
+ s[client].rvalid = TRUE;
+ s[client].rpage = lastVolumePage() - 1;
+ }
+
+ rmetadataStart();
+ return;
+ }
+
+ if (s[client].rpage == s[client].wpage)
+ end = s[client].woffset;
+
+ if (offset == end)
+ {
+ if ((s[client].rpage + 1 == lastVolumePage() && !s[client].circular) ||
+ s[client].rpage == s[client].wpage)
+ endRequest(SUCCESS); // end of log
+ else
+ rmetadataStart();
+ return;
+ }
+
+ if (offset + len <= end)
+ count = len;
+ else
+ count = end - offset;
+
+ pos += count;
+ s[client].rpos += count;
+ s[client].roffset = offset + count;
+
+ call At45db.read(s[client].rpage, offset, buf, count);
+ }
+
+ void readStart() {
+ readContinue();
+ }
+
+ /* ------------------------------------------------------------------ */
+ /* Read block metadata */
+ /* ------------------------------------------------------------------ */
+
+ void continueReadAt(at45pageoffset_t roffset) {
+ /* Resume reading at firstPage whose metadata is currently available
+ in the metadata variable */
+ metaState = META_IDLE;
+ s[client].rpos = metadata.pos + roffset;
+ s[client].rpage = firstPage;
+ s[client].roffset = roffset;
+ s[client].rend =
+ metadata.flags & F_SYNC ? metadata.lastRecordOffset : PAGE_SIZE;
+ s[client].rvalid = TRUE;
+ readContinue();
+ }
+
+ void rmetadataContinue() {
+ if (++firstPage == lastVolumePage())
+ firstPage = firstVolumePage();
+ if (firstPage == s[client].wpage)
+ if (!s[client].rvalid)
+ /* We cannot find a record boundary to start at (we've just
+ walked through the whole log...). Give up. */
+ endRequest(SUCCESS);
+ else
+ {
+ /* The current write page has no metadata yet, so we fake it */
+ metadata.flags = 0;
+ metadata.pos = s[client].wpos - s[client].woffset;
+ continueReadAt(0);
+ }
+ else
+ readMetadata(firstPage);
+ }
+
+ void rmetadataReadDone() {
+ if (metadata.magic == PERSISTENT_MAGIC)
+ crcPage(firstPage);
+ else
+ endRequest(SUCCESS);
+ }
+
+ void rmetadataCrcDone(uint16_t crc) {
+ if (!s[client].rvalid)
+ if (crc == metadata.crc && metadata.flags & F_LASTVALID)
+ continueReadAt(metadata.lastRecordOffset);
+ else
+ rmetadataContinue();
+ else
+ if (crc == metadata.crc)
+ continueReadAt(0);
+ else
+ endRequest(SUCCESS);
+ }
+
+ void rmetadataStart() {
+ metaState = META_READ;
+ firstPage = s[client].rpage;
+ rmetadataContinue();
+ }
+
+ /* ------------------------------------------------------------------ */
+ /* Seek. */
+ /* ------------------------------------------------------------------ */
+
+ void seekCrcDone(uint16_t crc) {
+ if (metadata.magic == PERSISTENT_MAGIC && crc == metadata.crc &&
+ metadata.pos == s[client].rpos - s[client].roffset)
+ {
+ s[client].rvalid = TRUE;
+ if (metadata.flags & F_SYNC)
+ s[client].rend = metadata.lastRecordOffset;
+ }
+ endRequest(SUCCESS);
+ }
+
+ void seekReadDone() {
+ crcPage(s[client].rpage);
+ }
+
+ /* Move to position specified by cookie. */
+ void seekStart() {
+ uint32_t offset = (uint32_t)(uint16_t)s[client].buf << 16 | s[client].len;
+
+ invalidateReadPointer(); // default to beginning of log
+
+ /* The write positions are offset by PAGE_SIZE (see emptyLog) */
+
+ if (offset == SEEK_BEGINNING)
+ offset = PAGE_SIZE;
+
+ if (offset > s[client].wpos || offset < PAGE_SIZE)
+ {
+ endRequest(EINVAL);
+ return;
+ }
+
+ /* Cookies are just flash positions which continue incrementing as
+ you circle around and around. So we can just check the requested
+ page's metadata.pos field matches the cookie's value */
+ s[client].rpos = offset;
+ s[client].roffset = (offset - PAGE_SIZE) % PAGE_SIZE;
+ s[client].rpage = firstVolumePage() + ((offset - PAGE_SIZE) / PAGE_SIZE) % npages();
+ s[client].rend = PAGE_SIZE; // default to no sync flag
+
+ // The last page's metadata isn't written to flash yet. Special case it.
+ if (s[client].rpage == s[client].wpage)
+ {
+ /* If we're seeking within the current write page, just go there.
+ Otherwise, we're asking for an old version of the current page
+ so just keep the invalidated read pointer, i.e., read from
+ the beginning. */
+ if (offset >= s[client].wpos - s[client].woffset)
+ s[client].rvalid = TRUE;
+ endRequest(SUCCESS);
+ }
+ else
+ {
+ metaState = META_SEEK;
+ readMetadata(s[client].rpage);
+ }
+ }
+
+ /* ------------------------------------------------------------------ */
+ /* Dispatch HAL operations to current user op */
+ /* ------------------------------------------------------------------ */
+
+ event void At45db.eraseDone(error_t error) {
+ if (client != NO_CLIENT)
+ if (error != SUCCESS)
+ endRequest(FAIL);
+ else
+ eraseEraseDone();
+ }
+
+ event void At45db.writeDone(error_t error) {
+ if (client != NO_CLIENT)
+ if (error != SUCCESS)
+ endRequest(FAIL);
+ else
+ switch (metaState)
+ {
+ case META_WRITE: wmetadataWriteDone(); break;
+ case META_IDLE: appendWriteDone(); break;
+ }
+ }
+
+ event void At45db.syncDone(error_t error) {
+ if (client != NO_CLIENT)
+ if (error != SUCCESS)
+ endRequest(FAIL);
+ else switch (s[client].request)
+ {
+ case R_ERASE: eraseMetadataDone(); break;
+ case R_APPEND: appendSyncDone(); break;
+ case R_SYNC: syncMetadataDone(); break;
+ }
+ }
+
+ event void At45db.flushDone(error_t error) {
+ if (client != NO_CLIENT)
+ if (error != SUCCESS)
+ endRequest(FAIL);
+ else
+ appendMetadataDone();
+ }
+
+ event void At45db.readDone(error_t error) {
+ if (client != NO_CLIENT)
+ if (error != SUCCESS)
+ endRequest(FAIL);
+ else
+ switch (metaState)
+ {
+ case META_LOCATEFIRST: locateFirstReadDone(); break;
+ case META_LOCATE: locateReadDone(); break;
+ case META_LOCATELAST: locateLastReadDone(); break;
+ case META_SEEK: seekReadDone(); break;
+ case META_READ: rmetadataReadDone(); break;
+ case META_IDLE: readContinue(); break;
+ }
+ }
+
+ event void At45db.computeCrcDone(error_t error, uint16_t crc) {
+ if (client != NO_CLIENT)
+ if (error != SUCCESS)
+ endRequest(FAIL);
+ else
+ switch (metaState)
+ {
+ case META_LOCATEFIRST: locateFirstCrcDone(crc); break;
+ case META_LOCATE: locateCrcDone(crc); break;
+ case META_LOCATELAST: locateLastCrcDone(crc); break;
+ case META_SEEK: seekCrcDone(crc); break;
+ case META_WRITE: wmetadataCrcDone(crc); break;
+ case META_READ: rmetadataCrcDone(crc); break;
+ }
+ }
+
+ event void At45db.copyPageDone(error_t error) { }
+
+ default event void LogWrite.appendDone[uint8_t logId](void* buf, storage_len_t l, bool rLost, error_t error) { }
+ default event void LogWrite.eraseDone[uint8_t logId](error_t error) { }
+ default event void LogWrite.syncDone[uint8_t logId](error_t error) { }
+ default event void LogRead.readDone[uint8_t logId](void* buf, storage_len_t l, error_t error) { }
+ default event void LogRead.seekDone[uint8_t logId](error_t error) {}
+
+ default command at45page_t At45dbVolume.remap[uint8_t logId](at45page_t volumePage) {return 0;}
+ default command at45page_t At45dbVolume.volumeSize[uint8_t logId]() {return 0;}
+ default async command error_t Resource.request[uint8_t logId]() {return SUCCESS;}
+ default async command error_t Resource.release[uint8_t logId]() { return FAIL; }
+}