2 * "Copyright (c) 2000-2004 The Regents of the University of California.
5 * Permission to use, copy, modify, and distribute this software and its
6 * documentation for any purpose, without fee, and without written agreement is
7 * hereby granted, provided that the above copyright notice, the following
8 * two paragraphs and the author appear in all copies of this software.
10 * IN NO EVENT SHALL THE UNIVERSITY OF CALIFORNIA BE LIABLE TO ANY PARTY FOR
11 * DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES ARISING OUT
12 * OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN IF THE UNIVERSITY OF
13 * CALIFORNIA HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
15 * THE UNIVERSITY OF CALIFORNIA SPECIFICALLY DISCLAIMS ANY WARRANTIES,
16 * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
17 * AND FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE PROVIDED HEREUNDER IS
18 * ON AN "AS IS" BASIS, AND THE UNIVERSITY OF CALIFORNIA HAS NO OBLIGATION TO
19 * PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS."
21 * Copyright (c) 2002-2006 Intel Corporation
22 * All rights reserved.
24 * This file is distributed under the terms in the attached INTEL-LICENSE
25 * file. If you do not find these files, copies can be found by writing to
26 * Intel Research Berkeley, 2150 Shattuck Avenue, Suite 1300, Berkeley, CA,
27 * 94704. Attention: Intel License Inquiry.
34 * Private component of the AT45DB implementation of the log storage
37 * @author: David Gay <dgay@acm.org>
38 * @author: Jonathan Hui <jwhui@cs.berkeley.edu>
43 interface LogRead[uint8_t logId];
44 interface LogWrite[uint8_t logId];
48 interface At45dbVolume[uint8_t logId];
49 interface Resource[uint8_t logId];
56 - The logId's in the LogRead and LogWrites are shifted left by 1 bit.
57 The low-order bit is 1 for circular logs, 0 for linear ones
58 (see newRequest and endRequest, and the LogStorageC configuration)
60 - Data is written sequentially to the pages of a log volume. Each page
61 ends with a footer (nx_struct pageinfo) recording metadata on the
64 o the "position" of the current page in the log (see below)
65 o the offset of the last record on this page (i.e., the offset
66 at which the last append ended) - only valid if flags & F_LASTVALID
68 x F_SYNC page was synchronised - data after lastRecordOffset
69 is not log data; implies F_LASTVALID
70 x F_CIRCLED this page is not from the first run through the log's
71 pages (never set in linear logs)
72 x F_LASTVALID not set if no record ended on this page
75 - "Positions" are stored in the metadata, used as cookies by
76 currentOffset and seek, and stored in the wpos and rpos fields of the
77 volume state structure. They represent the number of bytes that
78 writing has advanced in the log since the log was erased, with
79 PAGE_SIZE added. Note that this is basically the number of bytes
80 written, except that when a page is synchronised unused bytes in the
81 page count towards increasing the position.
83 As a result, on page p, the following equation holds:
84 (metadata(p).pos - PAGE_SIZE) % volume-size == p * PAGE_SIZE
85 (this also means that the "position" metadata field could be replaced
86 by a count of the number of times writing has cycled through the log,
87 reducing the metadata size)
89 The PAGE_SIZE offset on positions is caused by Invariant 2 below: to
90 ensure that Invariant 2 is respected, at flash erase time, we write a
91 valid page with position 0 to the last block of the flash. As a result,
92 the first writes to the flash, in page 0, are at "position" PAGE_SIZE.
94 - This code is designed to deal with "one-at-a-time" failures (i.e.,
95 the system will not modify any blocks after a previous failed
96 write). This should allow recovery from:
98 o write failure (the underlying PageEEPROM shuts down after any
99 write fails; all pages are flushed before moving on to the next
101 It will not recover from arbitrary data corruption
103 - When sync is called, the current write page is written to flash with an
104 F_SYNC flag and writing continues on the next page (wasting on average
107 - We maintain the following invariants on log volumes, even in the face
108 of the "one-at-a-time" failures described above:
109 1) at least one of the first and last blocks are valid
110 2) the last block, if valid, has the F_SYNC flag
112 - Locating the log boundary page (the page with the greatest position):
114 Invariant 1, the one-at-a-time failure model and the metadata position
115 definition guarantees that the physical flash pages have the following
117 an initial set of V1 valid pages,
118 followed by a set of I invalid pages,
119 followed by a set of V2 valid pages
120 with V1+i+V2=total-number-of-pages, and V1, V2, I >= 0
121 Additionally, the position of all pages in V1 is greater than in V2,
122 and consecutive pages in V1 (respectively V2) have greater positions
123 than their predecessors.
125 From this, it's possible to locate the log boundary page (the page with
126 the greatest position) using the following algorithm:
127 o let basepos=metadata(lastpage).pos, or 0 if the last page is invalid
128 o locate (using a binary search) the page p with the largest position
130 invalid pages can be assumed to have positions less than basepos
131 if there is no such page p, let p = lastpage
133 Once the log boundary page is known, we resume writing at the last
134 page before p with a record boundary (Invariant 2, combined with
135 limiting individual records to volumesize - PAGE_SIZE ensures there
136 will be such a page).
138 - The read pointer has a special "invalid" state which represents the
139 current beginning of the log. In that state, LogRead.currentOffset()
140 returns SEEK_BEGINNING rather than a regular position.
142 The read pointer is invalidated:
144 o after the volume is erased
145 o after the write position "catches up" with the read position
146 o after a failed seek
148 Reads from an invalid pointer:
149 o start reading from the beginning of the flash if we are on the
150 first run through the log volume
151 o start reading at the first valid page after the write page with
152 an F_LASTVALID flag; the read offset is set to the lastRecordOffset
154 if this page has the SYNC flag, we start at the beginning of the
168 nx_uint8_t lastRecordOffset;
174 N = uniqueCount(UQ_LOG_STORAGE),
176 PAGE_SIZE = AT45_PAGE_SIZE - sizeof(nx_struct pageinfo),
177 PERSISTENT_MAGIC = 0x4256,
199 uint8_t client = NO_CLIENT;
202 at45page_t firstPage, lastPage;
204 nx_struct pageinfo metadata;
207 /* The latest request made for this client, and it's arguments */
212 /* Log r/w positions */
213 bool positionKnown : 1;
217 uint32_t wpos; /* Bytes since start of logging */
218 at45page_t wpage; /* Current write page */
219 at45pageoffset_t woffset; /* Offset on current write page */
220 uint32_t rpos; /* Bytes since start of logging */
221 at45page_t rpage; /* Current read page */
222 at45pageoffset_t roffset; /* Offset on current read page */
223 at45pageoffset_t rend; /* Last valid offset on current read page */
226 at45page_t firstVolumePage() {
227 return call At45dbVolume.remap[client](0);
230 at45page_t npages() {
231 return call At45dbVolume.volumeSize[client]();
234 at45page_t lastVolumePage() {
235 return call At45dbVolume.remap[client](npages());
238 void setWritePage(at45page_t page) {
239 if (s[client].circular && page == lastVolumePage())
241 s[client].circled = TRUE;
242 page = firstVolumePage();
244 s[client].wpage = page;
245 s[client].woffset = 0;
248 void invalidateReadPointer() {
249 s[client].rvalid = FALSE;
252 void crcPage(at45page_t page) {
253 call At45db.computeCrc(page, 0,
254 PAGE_SIZE + offsetof(nx_struct pageinfo, crc), 0);
257 void readMetadata(at45page_t page) {
258 call At45db.read(page, PAGE_SIZE, &metadata, sizeof metadata);
261 void writeMetadata(at45page_t page) {
262 call At45db.write(page, PAGE_SIZE, &metadata, sizeof metadata);
265 void wmetadataStart();
268 metadata.flags = F_SYNC | F_LASTVALID;
269 metadata.lastRecordOffset = s[client].woffset;
270 /* rend is now no longer the end of the page */
271 if (s[client].rpage == s[client].wpage)
272 s[client].rend = s[client].woffset;
276 /* ------------------------------------------------------------------ */
277 /* Queue and initiate user requests */
278 /* ------------------------------------------------------------------ */
285 void rmetadataStart();
288 void startRequest() {
289 if (!s[client].positionKnown && s[client].request != R_ERASE)
295 metaState = META_IDLE;
296 switch (s[client].request)
298 case R_ERASE: eraseStart(); break;
299 case R_APPEND: appendStart(); break;
300 case R_SYNC: syncStart(); break;
301 case R_READ: readStart(); break;
302 case R_SEEK: seekStart(); break;
306 void endRequest(error_t ok) {
308 uint8_t request = s[c].request;
309 storage_len_t actualLen = s[c].len - len;
310 void *ptr = s[c].buf - actualLen;
313 s[c].request = R_IDLE;
314 call Resource.release[c]();
316 c = c << 1 | s[c].circular;
319 case R_ERASE: signal LogWrite.eraseDone[c](ok); break;
320 case R_APPEND: signal LogWrite.appendDone[c](ptr, actualLen, recordsLost, ok); break;
321 case R_SYNC: signal LogWrite.syncDone[c](ok); break;
322 case R_READ: signal LogRead.readDone[c](ptr, actualLen, ok); break;
323 case R_SEEK: signal LogRead.seekDone[c](ok); break;
327 /* Enqueue request and request the underlying flash */
328 error_t newRequest(uint8_t newRequest, uint8_t id,
329 uint8_t *buf, storage_len_t length) {
330 s[id >> 1].circular = id & 1;
333 if (s[id].request != R_IDLE)
336 s[id].request = newRequest;
339 call Resource.request[id]();
344 event void Resource.granted[uint8_t id]() {
350 command error_t LogWrite.append[uint8_t id](void* buf, storage_len_t length) {
351 if (len > call LogRead.getSize[id]() - PAGE_SIZE)
352 /* Writes greater than the volume size are invalid.
353 Writes equal to the volume size could break the log volume
354 invariant (see next comment).
355 Writes that span the whole volume could lead to problems
356 at boot time (no valid block with a record boundary).
360 return newRequest(R_APPEND, id, buf, length);
363 command storage_cookie_t LogWrite.currentOffset[uint8_t id]() {
364 return s[id >> 1].wpos;
367 command error_t LogWrite.erase[uint8_t id]() {
368 return newRequest(R_ERASE, id, NULL, 0);
371 command error_t LogWrite.sync[uint8_t id]() {
372 return newRequest(R_SYNC, id, NULL, 0);
375 command error_t LogRead.read[uint8_t id](void* buf, storage_len_t length) {
376 return newRequest(R_READ, id, buf, length);
379 command storage_cookie_t LogRead.currentOffset[uint8_t id]() {
381 return s[id].rvalid ? s[id].rpos : SEEK_BEGINNING;
384 command error_t LogRead.seek[uint8_t id](storage_cookie_t offset) {
385 return newRequest(R_SEEK, id, (void *)(offset >> 16), offset);
388 command storage_len_t LogRead.getSize[uint8_t id]() {
389 return call At45dbVolume.volumeSize[id >> 1]() * (storage_len_t)PAGE_SIZE;
392 /* ------------------------------------------------------------------ */
394 /* ------------------------------------------------------------------ */
396 void eraseMetadataDone() {
397 /* Set write pointer to the beginning of the flash */
398 s[client].wpos = PAGE_SIZE; // last page has offset 0 and is before us
399 s[client].circled = FALSE;
400 setWritePage(firstVolumePage());
402 invalidateReadPointer();
404 s[client].positionKnown = TRUE;
408 void eraseEraseDone() {
409 if (firstPage == lastPage - 1)
411 /* We create a valid, synced last page (see invariants) */
412 metadata.flags = F_SYNC | F_LASTVALID;
413 metadata.lastRecordOffset = 0;
414 setWritePage(firstPage);
415 s[client].circled = FALSE;
420 call At45db.erase(firstPage++, AT45_ERASE);
424 s[client].positionKnown = FALSE; // in case erase fails
425 firstPage = firstVolumePage();
426 lastPage = lastVolumePage();
430 /* ------------------------------------------------------------------ */
431 /* Locate log boundaries */
432 /* ------------------------------------------------------------------ */
434 void locateLastRecord();
436 void locateLastCrcDone(uint16_t crc) {
437 if (crc != metadata.crc)
443 /* We've found the last valid page with a record-end. Set up
444 the read and write positions. */
445 invalidateReadPointer();
447 if (metadata.flags & F_SYNC) /* must start on next page */
449 /* We need to special case the empty log, as we don't want
450 to wrap around in the case of a full, non-circular log
451 with a sync on its last page. */
452 if (firstPage == lastPage && !metadata.pos)
453 setWritePage(firstVolumePage());
455 setWritePage(firstPage + 1);
456 s[client].wpos = metadata.pos + PAGE_SIZE;
460 s[client].wpage = firstPage;
461 s[client].woffset = metadata.lastRecordOffset;
462 s[client].wpos = metadata.pos + metadata.lastRecordOffset;
465 s[client].circled = (metadata.flags & F_CIRCLED) != 0;
466 if (s[client].circled && !s[client].circular) // oops
472 /* And we can now proceed to the real request */
473 s[client].positionKnown = TRUE;
477 void locateLastReadDone() {
478 if (metadata.magic == PERSISTENT_MAGIC && metadata.flags & F_LASTVALID)
484 void locateLastRecord() {
485 if (firstPage == lastPage)
487 /* We walked all the way back to the last page, and it's not
488 valid. The log-volume invariant is not holding. Fail out. */
493 if (firstPage == firstVolumePage())
494 firstPage = lastPage;
498 readMetadata(firstPage);
502 metaState = META_LOCATELAST;
503 /* firstPage is one after last valid page, but the last page with
504 a record end may be some pages earlier. Search for it. */
505 lastPage = lastVolumePage() - 1;
509 at45page_t locateCurrentPage() {
510 return firstPage + ((lastPage - firstPage) >> 1);
513 void locateBinarySearch() {
514 if (lastPage <= firstPage)
517 readMetadata(locateCurrentPage());
520 void locateGreaterThan() {
521 firstPage = locateCurrentPage() + 1;
522 locateBinarySearch();
525 void locateLessThan() {
526 lastPage = locateCurrentPage();
527 locateBinarySearch();
530 void locateCrcDone(uint16_t crc) {
531 if (crc == metadata.crc)
533 s[client].wpos = metadata.pos;
540 void locateReadDone() {
541 if (metadata.magic == PERSISTENT_MAGIC && s[client].wpos < metadata.pos)
542 crcPage(locateCurrentPage());
547 void locateFirstCrcDone(uint16_t crc) {
548 if (metadata.magic == PERSISTENT_MAGIC && crc == metadata.crc)
549 s[client].wpos = metadata.pos;
553 metaState = META_LOCATE;
554 locateBinarySearch();
557 void locateFirstReadDone() {
561 /* Locate log beginning and ending. See description at top of file. */
563 metaState = META_LOCATEFIRST;
564 firstPage = firstVolumePage();
565 lastPage = lastVolumePage() - 1;
566 readMetadata(lastPage);
569 /* ------------------------------------------------------------------ */
571 /* ------------------------------------------------------------------ */
573 void appendContinue() {
574 uint8_t *buf = s[client].buf;
575 at45pageoffset_t offset = s[client].woffset, count;
583 if (s[client].wpage == lastVolumePage())
585 /* We reached the end of a linear log */
590 if (offset + len <= PAGE_SIZE)
593 count = PAGE_SIZE - offset;
595 s[client].buf += count;
596 s[client].wpos += count;
597 s[client].woffset += count;
600 /* We normally lose data at the point we make the first write to a
601 page in a log that has circled. */
602 if (offset == 0 && s[client].circled)
605 call At45db.write(s[client].wpage, offset, buf, count);
608 void appendWriteDone() {
609 if (s[client].woffset == PAGE_SIZE) /* Time to write metadata */
615 void appendMetadataDone() { // metadata of previous page flushed
616 /* Setup metadata in case we overflow this page too */
621 void appendSyncDone() {
622 s[client].wpos = metadata.pos + PAGE_SIZE;
627 storage_len_t vlen = (storage_len_t)npages() * PAGE_SIZE;
631 /* If request would span the end of the flash, sync, to maintain the
632 invariant that the last flash page is synced and that either
633 the first or last pages are valid.
635 Note that >= in the if below means we won't write a record that
636 would end on the last byte of the last page, as this would mean that
637 we would not sync the last page, breaking the log volume
639 if ((s[client].wpos - PAGE_SIZE) % vlen >= vlen - len)
643 /* Set lastRecordOffset in case we need to write metadata (see
645 metadata.lastRecordOffset = s[client].woffset;
646 metadata.flags = F_LASTVALID;
651 /* ------------------------------------------------------------------ */
653 /* ------------------------------------------------------------------ */
656 if (s[client].woffset == 0) /* we can't lose any writes */
662 void syncMetadataDone() {
663 /* Write position reflect the absolute position in the flash, not
664 user-bytes written. So update wpos to reflect sync effects. */
665 s[client].wpos = metadata.pos + PAGE_SIZE;
669 /* ------------------------------------------------------------------ */
670 /* Write block metadata */
671 /* ------------------------------------------------------------------ */
673 void wmetadataStart() {
674 /* The caller ensures that metadata.flags (except F_CIRCLED) and
675 metadata.lastRecordOffset are set correctly. */
676 metaState = META_WRITE;
677 firstPage = s[client].wpage; // remember page to commit
678 metadata.pos = s[client].wpos - s[client].woffset;
679 metadata.magic = PERSISTENT_MAGIC;
680 if (s[client].circled)
681 metadata.flags |= F_CIRCLED;
683 call At45db.computeCrc(firstPage, 0, PAGE_SIZE, 0);
685 /* We move to the next page now. If writing the metadata fails, we'll
686 simply leave the invalid page in place. Trying to recover seems
687 complicated, and of little benefit (note that in practice, At45dbC
688 shuts down after a failed write, so nothing is really going to
689 happen after that anyway). */
690 setWritePage(s[client].wpage + 1);
692 /* Invalidate read pointer if we reach it's page */
693 if (s[client].wpage == s[client].rpage)
694 invalidateReadPointer();
697 void wmetadataCrcDone(uint16_t crc) {
700 // Include metadata in crc
701 md = (uint8_t *)&metadata;
702 for (i = 0; i < offsetof(nx_struct pageinfo, crc); i++)
703 crc = crcByte(crc, md[i]);
707 writeMetadata(firstPage);
710 void wmetadataWriteDone() {
711 metaState = META_IDLE;
712 if (metadata.flags & F_SYNC)
713 call At45db.sync(firstPage);
715 call At45db.flush(firstPage);
718 /* ------------------------------------------------------------------ */
720 /* ------------------------------------------------------------------ */
722 void readContinue() {
723 uint8_t *buf = s[client].buf;
724 at45pageoffset_t offset = s[client].roffset, count;
725 at45pageoffset_t end = s[client].rend;
733 if (!s[client].rvalid)
735 if (s[client].circled)
736 /* Find a valid page after wpage, skipping invalid pages */
737 s[client].rpage = s[client].wpage;
740 /* resume reading at the beginning of the first page */
741 s[client].rvalid = TRUE;
742 s[client].rpage = lastVolumePage() - 1;
749 if (s[client].rpage == s[client].wpage)
750 end = s[client].woffset;
754 if ((s[client].rpage + 1 == lastVolumePage() && !s[client].circular) ||
755 s[client].rpage == s[client].wpage)
756 endRequest(SUCCESS); // end of log
762 if (offset + len <= end)
765 count = end - offset;
767 s[client].buf += count;
769 s[client].rpos += count;
770 s[client].roffset = offset + count;
772 call At45db.read(s[client].rpage, offset, buf, count);
779 /* ------------------------------------------------------------------ */
780 /* Read block metadata */
781 /* ------------------------------------------------------------------ */
783 void continueReadAt(at45pageoffset_t roffset) {
784 /* Resume reading at firstPage whose metadata is currently available
785 in the metadata variable */
786 metaState = META_IDLE;
787 s[client].rpos = metadata.pos + roffset;
788 s[client].rpage = firstPage;
789 s[client].roffset = roffset;
791 metadata.flags & F_SYNC ? metadata.lastRecordOffset : PAGE_SIZE;
792 s[client].rvalid = TRUE;
796 void rmetadataContinue() {
797 if (++firstPage == lastVolumePage())
798 firstPage = firstVolumePage();
799 if (firstPage == s[client].wpage)
800 if (!s[client].rvalid)
801 /* We cannot find a record boundary to start at (we've just
802 walked through the whole log...). Give up. */
806 /* The current write page has no metadata yet, so we fake it */
808 metadata.pos = s[client].wpos - s[client].woffset;
812 readMetadata(firstPage);
815 void rmetadataReadDone() {
816 if (metadata.magic == PERSISTENT_MAGIC)
822 void rmetadataCrcDone(uint16_t crc) {
823 if (!s[client].rvalid)
824 if (crc == metadata.crc && metadata.flags & F_LASTVALID)
825 continueReadAt(metadata.lastRecordOffset);
829 if (crc == metadata.crc)
835 void rmetadataStart() {
836 metaState = META_READ;
837 firstPage = s[client].rpage;
841 /* ------------------------------------------------------------------ */
843 /* ------------------------------------------------------------------ */
845 void seekCrcDone(uint16_t crc) {
846 if (metadata.magic == PERSISTENT_MAGIC && crc == metadata.crc &&
847 metadata.pos == s[client].rpos - s[client].roffset)
849 s[client].rvalid = TRUE;
850 if (metadata.flags & F_SYNC)
851 s[client].rend = metadata.lastRecordOffset;
856 void seekReadDone() {
857 crcPage(s[client].rpage);
860 /* Move to position specified by cookie. */
862 uint32_t offset = (uint32_t)(uint16_t)s[client].buf << 16 | s[client].len;
864 invalidateReadPointer(); // default to beginning of log
866 /* The write positions are offset by PAGE_SIZE (see emptyLog) */
868 if (offset == SEEK_BEGINNING)
871 if (offset > s[client].wpos || offset < PAGE_SIZE)
877 /* Cookies are just flash positions which continue incrementing as
878 you circle around and around. So we can just check the requested
879 page's metadata.pos field matches the cookie's value */
880 s[client].rpos = offset;
881 s[client].roffset = (offset - PAGE_SIZE) % PAGE_SIZE;
882 s[client].rpage = firstVolumePage() + ((offset - PAGE_SIZE) / PAGE_SIZE) % npages();
883 s[client].rend = PAGE_SIZE; // default to no sync flag
885 // The last page's metadata isn't written to flash yet. Special case it.
886 if (s[client].rpage == s[client].wpage)
888 /* If we're seeking within the current write page, just go there.
889 Otherwise, we're asking for an old version of the current page
890 so just keep the invalidated read pointer, i.e., read from
892 if (offset >= s[client].wpos - s[client].woffset)
893 s[client].rvalid = TRUE;
898 metaState = META_SEEK;
899 readMetadata(s[client].rpage);
903 /* ------------------------------------------------------------------ */
904 /* Dispatch HAL operations to current user op */
905 /* ------------------------------------------------------------------ */
907 event void At45db.eraseDone(error_t error) {
908 if (client != NO_CLIENT)
909 if (error != SUCCESS)
915 event void At45db.writeDone(error_t error) {
916 if (client != NO_CLIENT)
917 if (error != SUCCESS)
922 case META_WRITE: wmetadataWriteDone(); break;
923 case META_IDLE: appendWriteDone(); break;
927 event void At45db.syncDone(error_t error) {
928 if (client != NO_CLIENT)
929 if (error != SUCCESS)
931 else switch (s[client].request)
933 case R_ERASE: eraseMetadataDone(); break;
934 case R_APPEND: appendSyncDone(); break;
935 case R_SYNC: syncMetadataDone(); break;
939 event void At45db.flushDone(error_t error) {
940 if (client != NO_CLIENT)
941 if (error != SUCCESS)
944 appendMetadataDone();
947 event void At45db.readDone(error_t error) {
948 if (client != NO_CLIENT)
949 if (error != SUCCESS)
954 case META_LOCATEFIRST: locateFirstReadDone(); break;
955 case META_LOCATE: locateReadDone(); break;
956 case META_LOCATELAST: locateLastReadDone(); break;
957 case META_SEEK: seekReadDone(); break;
958 case META_READ: rmetadataReadDone(); break;
959 case META_IDLE: readContinue(); break;
963 event void At45db.computeCrcDone(error_t error, uint16_t crc) {
964 if (client != NO_CLIENT)
965 if (error != SUCCESS)
970 case META_LOCATEFIRST: locateFirstCrcDone(crc); break;
971 case META_LOCATE: locateCrcDone(crc); break;
972 case META_LOCATELAST: locateLastCrcDone(crc); break;
973 case META_SEEK: seekCrcDone(crc); break;
974 case META_WRITE: wmetadataCrcDone(crc); break;
975 case META_READ: rmetadataCrcDone(crc); break;
979 event void At45db.copyPageDone(error_t error) { }
981 default event void LogWrite.appendDone[uint8_t logId](void* buf, storage_len_t l, bool rLost, error_t error) { }
982 default event void LogWrite.eraseDone[uint8_t logId](error_t error) { }
983 default event void LogWrite.syncDone[uint8_t logId](error_t error) { }
984 default event void LogRead.readDone[uint8_t logId](void* buf, storage_len_t l, error_t error) { }
985 default event void LogRead.seekDone[uint8_t logId](error_t error) {}
987 default command at45page_t At45dbVolume.remap[uint8_t logId](at45page_t volumePage) {return 0;}
988 default command at45page_t At45dbVolume.volumeSize[uint8_t logId]() {return 0;}
989 default async command error_t Resource.request[uint8_t logId]() {return SUCCESS;}
990 default async command error_t Resource.release[uint8_t logId]() { return FAIL; }