4 * This file is part of BeRTOS.
6 * Bertos is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation; either version 2 of the License, or
9 * (at your option) any later version.
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
16 * You should have received a copy of the GNU General Public License
17 * along with this program; if not, write to the Free Software
18 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
20 * As a special exception, you may use this file as part of a free software
21 * library without restriction. Specifically, if other files instantiate
22 * templates or use macros or inline functions from this file, or you compile
23 * this file and link it with other files to produce an executable, this
24 * file does not by itself cause the resulting executable to be covered by
25 * the GNU General Public License. This exception does not however
26 * invalidate any other reasons why the executable file might be covered by
27 * the GNU General Public License.
29 * Copyright 2007 Develer S.r.l. (http://www.develer.com/)
33 * \brief BattFS: a filesystem for embedded platforms (implementation).
37 * \author Francesco Sacchi <batt@develer.com>
43 #include <cfg/debug.h>
44 #include <cfg/macros.h> /* MIN, MAX */
45 #include <cpu/byteorder.h> /* cpu_to_xx */
48 #include <string.h> /* memset, memmove */
52 * Convert from memory representation to disk structure.
53 * \note filesystem is in little-endian format.
55 INLINE void battfs_to_disk(struct BattFsPageHeader *hdr, uint8_t *buf)
57 STATIC_ASSERT(BATTFS_HEADER_LEN == 10);
61 buf[2] = hdr->fill >> 8;
64 buf[4] = hdr->pgoff >> 8;
67 * Sequence number is at least 1 bit longer than page address.
68 * Needed to take care of wraparonds.
71 buf[6] = hdr->seq >> 8;
74 * First bit used by seq.
75 * Unused bits are set to 1.
77 buf[7] = (hdr->seq >> 16) ? 0xFF : 0xFE;
80 * This field must be the last one!
81 * This is needed because if the page is only partially
82 * written, we can use this to detect it.
85 buf[9] = hdr->fcs >> 8;
89 * Convert from disk structure to memory representation.
90 * \note filesystem is in little-endian format.
92 INLINE void disk_to_battfs(uint8_t *buf, struct BattFsPageHeader *hdr)
94 STATIC_ASSERT(BATTFS_HEADER_LEN == 10);
96 hdr->fill = buf[2] << 8 | buf[1];
97 hdr->pgoff = buf[4] << 8 | buf[3];
98 hdr->seq = (seq_t)(buf[7] & 0x01) << 16 | buf[6] << 8 | buf[5];
99 hdr->fcs = buf[9] << 8 | buf[8];
103 * Compute the fcs of the header.
105 static fcs_t computeFcs(struct BattFsPageHeader *hdr)
107 uint8_t buf[BATTFS_HEADER_LEN];
110 battfs_to_disk(hdr, buf);
112 /* fcs is at the end of whole header */
113 rotating_update(buf, BATTFS_HEADER_LEN - sizeof(fcs_t), &cks);
119 * Read header of page \a page.
120 * \return true on success, false otherwise.
122 static bool battfs_readHeader(struct BattFsSuper *disk, pgcnt_t page, struct BattFsPageHeader *hdr)
124 uint8_t buf[BATTFS_HEADER_LEN];
126 * Read header from disk.
127 * Header is actually a footer, and so
128 * resides at page end.
130 if (disk->read(disk, page, disk->page_size - BATTFS_HEADER_LEN, buf, BATTFS_HEADER_LEN)
131 != BATTFS_HEADER_LEN)
133 TRACEMSG("Error: page[%d]\n", page);
138 disk_to_battfs(buf, hdr);
144 * Write header of page \a page.
145 * \return true on success, false otherwise.
147 static bool battfs_writeHeader(struct BattFsSuper *disk, pgcnt_t page, struct BattFsPageHeader *hdr)
149 uint8_t buf[BATTFS_HEADER_LEN];
152 battfs_to_disk(hdr, buf);
155 * write header to disk.
156 * Header is actually a footer, and so
157 * resides at page end.
159 if (disk->write(disk, page, disk->page_size - BATTFS_HEADER_LEN, buf, BATTFS_HEADER_LEN)
160 != BATTFS_HEADER_LEN)
162 TRACEMSG("Error: page[%d]\n", page);
169 * Count the number of pages from
170 * inode 0 to \a inode in \a filelen_table.
172 static pgcnt_t countPages(pgoff_t *filelen_table, inode_t inode)
176 for (inode_t i = 0; i < inode; i++)
177 cnt += filelen_table[i];
183 * Move all pages in page allocation array from \a src to \a src + \a offset.
184 * The number of pages moved is page_count - MAX(dst, src).
186 static void movePages(struct BattFsSuper *disk, pgcnt_t src, int offset)
188 pgcnt_t dst = src + offset;
189 memmove(&disk->page_array[dst], &disk->page_array[src], (disk->page_count - MAX(dst, src)) * sizeof(pgcnt_t));
193 /* Fill empty space in array with sentinel */
194 for (pgcnt_t page = disk->page_count + offset; page < disk->page_count; page++)
195 disk->page_array[page] = PAGE_UNSET_SENTINEL;
202 * Insert \a page at the bottom of page allocation array of \a disk.
204 static void insertFreePage(struct BattFsSuper *disk, pgcnt_t page)
206 pgcnt_t free_pos = disk->page_count - 1;
207 ASSERT(disk->page_array[free_pos] == PAGE_UNSET_SENTINEL);
208 ASSERT(page <= free_pos);
210 disk->page_array[free_pos] = page;
214 * Mark \a page of \a disk as free.
215 * \note free_next of \a disk is used as \a page free marker
216 * and is increased by 1.
218 static bool battfs_markFree(struct BattFsSuper *disk, struct BattFsPageHeader *hdr, pgcnt_t page)
220 uint8_t buf[BATTFS_HEADER_LEN];
222 hdr->mark = disk->free_next;
223 hdr->fcs_free = computeFcsFree(hdr);
224 battfs_to_disk(hdr, buf);
226 if (!disk->write(disk, page, disk->page_size - BATTFS_HEADER_LEN, buf, BATTFS_HEADER_LEN))
228 TRACEMSG("error marking page [%d]\n", page);
239 * Determine free_start and free_next blocks for \a disk
240 * using \a minl, \a maxl, \a minh, \a maxh.
242 * Mark_t is a type that has at least 1 bit more than
243 * pgaddr_t. So all free blocks can be numbered using
244 * at most half numbers of a mark_t type.
245 * The free blocks algorithm increments by 1 the disk->free_next
246 * every time a page becomes free. So the free block sequence is
247 * guaranteed to be countiguous.
248 * Only wrap arounds may happen, but due to half size sequence limitation,
249 * there are only 4 possible situations:
252 * |------lower half------|-------upper half-------|
254 * 1) |------minl*****maxl---|------------------------|
255 * 2) |------minl********maxl|minh******maxh----------|
256 * 3) |----------------------|----minh*******maxh-----|
257 * 4) |minl******maxl--------|------------minh****maxh|
260 * Situations 1 and 3 are easy to detect, while 2 and 4 require more care.
262 static void findFreeStartNext(struct BattFsSuper *disk, mark_t minl, mark_t maxl, mark_t minh, mark_t maxh)
264 /* Determine free_start & free_next */
267 /* Valid interval found in lower half */
270 /* Valid interval also found in upper half */
271 if (maxl == minh - 1)
273 /* Interval starts in lower half and ends in upper */
274 disk->free_start = minl;
275 disk->free_next = maxh;
279 /* Interval starts in upper half and ends in lower */
281 ASSERT(maxh == (MAX_PAGE_ADDR | MARK_HALF_SIZE));
283 disk->free_start = minh;
284 disk->free_next = maxl;
290 * Upper interval is invalid.
294 disk->free_start = minl;
295 disk->free_next = maxl;
298 else if (maxh >= minh)
301 * Lower interval is invalid.
304 disk->free_start = minh;
305 disk->free_next = maxh;
310 * No valid interval found.
311 * Hopefully the disk is brand new (or full).
313 TRACEMSG("No valid marked free block found, new disk or disk full\n");
314 disk->free_start = 0;
315 disk->free_next = -1; //to be increased later
318 /* free_next should contain the first usable address */
321 TRACEMSG("Free markers:\n minl %u\n maxl %u\n minh %u\n maxh %u\n free_start %u\n free_next %u\n",
322 minl, maxl, minh, maxh, disk->free_start, disk->free_next);
327 * Count number of pages per file on \a disk.
328 * This information is registered in \a filelen_table.
329 * Array index represent file inode, while value contained
330 * is the number of pages used by that file.
332 * \return true if ok, false on disk read errors.
333 * \note The whole disk is scanned once.
335 static bool countDiskFilePages(struct BattFsSuper *disk, pgoff_t *filelen_table)
337 BattFsPageHeader hdr;
338 disk->free_page_start = 0;
340 /* Count the number of disk page per file */
341 for (pgcnt_t page = 0; page < disk->page_count; page++)
343 if (!battfs_readHeader(disk, page, &hdr))
346 /* Increase free space */
347 disk->free_bytes += disk->page_size - BATTFS_HEADER_LEN;
349 /* Check header FCS */
350 if (hdr.fcs == computeFcs(&hdr))
352 ASSERT(hdr.fill <= disk->page_size - BATTFS_HEADER_LEN);
354 /* Page is valid and is owned by a file */
355 filelen_table[hdr.inode]++;
357 /* Keep trace of free space */
358 disk->free_bytes -= hdr.fill;
359 disk->free_page_start++;
367 * Fill page allocation array of \a disk
368 * using file lenghts in \a filelen_table.
370 * The page allocation array is an array containings all file infos.
371 * Is ordered by file, and within each file is ordered by page offset
373 * e.g. : at page array[0] you will find page address of the first page
374 * of the first file (if present).
375 * Free blocks are allocated after the last file, starting from invalid ones
376 * and continuing with the marked free ones.
378 * \return true if ok, false on disk read errors.
379 * \note The whole disk is scanned once.
381 static bool fillPageArray(struct BattFsSuper *disk, pgoff_t *filelen_table)
383 BattFsPageHeader hdr;
384 pgcnt_t curr_free_page = disk->free_page_start;
385 /* Fill page allocation array */
386 for (pgcnt_t page = 0; page < disk->page_count; page++)
388 if (!battfs_readHeader(disk, page, &hdr))
391 /* Check header FCS */
392 if (hdr.fcs == computeFcs(&hdr))
394 /* Compute array position */
395 pgcnt_t array_pos_start = countPages(filelen_table, hdr.inode);
396 pgcnt_t array_pos = array_pos_start + hdr.pgoff;
398 /* Find the first free position */
399 while (disk->page_array[array_pos] != PAGE_UNSET_SENTINEL)
401 ASSERT(array_pos < array_pos_start + filelen_table[hdr.inode + 1]);
405 disk->page_array[array_pos] = page;
409 /* Invalid page, keep as free */
410 ASSERT(disk->page_array[curr_free_page] == PAGE_UNSET_SENTINEL);
411 LOG_INFO("Page %d invalid, keeping as free\n", page);
412 disk->page_array[curr_free_page++] = page;
419 * Find the latest version of a page, starting from the
420 * page supplied by \a page_array.
421 * The pages are read from the disk until a different
422 * inode or page offset is found.
423 * The lastest version of the page is moved in the first
424 * position of \a page_array.
425 * \return the number of old versions of the page or PAGE_ERROR
426 * on disk read errors.
428 static pgcnt_t findLastVersion(pgcnt_t *page_array)
430 pgcnt_t *array_start = page_array;
431 BattFsPageHeader hdr;
432 if (!battfs_readHeader(disk, *page_array++, &hdr))
435 /* Free space: early bailout */
436 if (hdr.fcs != computeFcs(&hdr))
440 * If the first page is valid,
441 * inode and pg_off in the array are taken
442 * as the current page markers.
444 inode_t curr_inode = hdr.inode;
445 pgoff_t curr_pgoff = hdr.pgoff;
447 /* Temps used to find the sequence number range */
448 seq_t minl = HALF_SEQ - 1;
450 seq_t minh = FULL_SEQ;
451 seq_t maxh = HALF_SEQ;
452 pgcnt_t lpos = 0, hpos = 0, dup_cnt = 0;
455 * Find min and max values for the two
456 * half of seq_num range.
457 * With this we can find seqnum wraparounds.
458 * seq_t is a type that has at least 1 bit more than
459 * pgaddr_t. So all version of a page blocks can be numbered using
460 * at most half numbers of a seq_t type.
461 * The sequence number algorithm increments by 1 the previous seq_num
462 * every time a page is rewritten. So the sequence is
463 * guaranteed to be countiguous.
464 * Only wrap arounds may happen, but due to half size sequence limitation,
465 * there are only 4 possible situations:
468 * |------lower half------|-------upper half-------|
470 * 1) |------minl*****maxl---|------------------------|
471 * 2) |------minl********maxl|minh******maxh----------|
472 * 3) |----------------------|----minh*******maxh-----|
473 * 4) |minl******maxl--------|------------minh****maxh|
476 * Situations 1 and 3 are easy to detect, while 2 and 4 require more care.
480 if (hdr.seq < SEQ_HALF_SIZE)
482 minl = MIN(minl, hdr.seq);
491 minh = MIN(minh, hdr.seq);
499 if (!battfs_readHeader(disk, *page_array++, &hdr))
503 while (curr_inode == hdr.inode && curr_pgoff == hdr.pgoff && hdr.fcs == computeFcs(&hdr))
506 /* Return early if there is only one version of the current page */
510 /* Find the position in the array of the last version of the page */
511 pgcnt_t last_ver = hpos;
514 /* Valid interval found in lower half */
517 /* Valid interval also found in upper half */
518 if (maxl != minh - 1)
520 /* Interval starts in upper half and ends in lower */
522 ASSERT(maxh == FULL_SEQ);
529 * Upper interval is invalid.
535 /* Put last page version at array start position */
536 SWAP(array_start[0], array_start[last_ver]);
542 * Collect old pages, removing empty spaces from \a pg_array, for a maximum len of \a pg_len.
543 * Once the collect task is completed, copy \a old_cnt pages from \a old_pages at the
544 * end of free space in pg_array.
546 void collectOldPages(pgcnt_t *pg_array, pgcnt_t pg_len, pgcnt_t *old_pages, pgcnt_t old_cnt)
551 for (pgcnt_t curr_page = 0; curr_page < pg_len; pg_len++)
555 if (pg_array[curr_page] == PAGE_UNSET_SENTINEL)
559 pg_array[curr_page - gap] = pg_array[curr_page];
565 if (pg_array[curr_page] != PAGE_UNSET_SENTINEL)
566 pg_array[curr_page - gap] = pg_array[curr_page];
574 ASSERT(gap == old_cnt);
575 pg_array += pg_len - old_cnt;
577 memcpy(pg_array, old_pages, old_cnt * sizeof(pgcnt_t));
581 * This function scan the page array of \a disk looking for
582 * old versions of the same page.
584 * Only the last version is kept as valid, the old ones are inserted
585 * in the free blocks heap.
586 * \return true if ok, false on disk read errors.
587 * \note The whole disk is scanned once.
589 static bool dropOldPages(struct BattFsSuper *disk)
591 #define OLD_PAGE_BUFLEN 64
592 pgcnt_t old_pages[OLD_PAGE_BUFLEN];
595 pgcnt_t *curr_page = disk->page_array;
596 pgcnt_t *collect_start = disk->page_array;
597 pgcnt_t collect_len = disk->page_count;
602 dup_pages = findLastVersion(curr_page);
603 if (dup_pages == PAGE_ERROR)
605 /* The first page is the last version */
609 if (old_cnt >= OLD_PAGE_BUFLEN)
611 collectOldPages(collect_start, collect_len, old_pages, old_cnt);
612 collect_len -= old_cnt;
613 disk->free_bytes += old_cnt * (disk->page_size - BATTFS_HEADER_LEN);
614 disk->free_page_start -= old_cnt;
615 curr_page -= old_cnt;
616 collect_start = curr_page;
620 old_pages[old_cnt++] = *curr_page;
621 *curr_page++ = PAGE_UNSET_SENTINEL;
624 while (curr_page < disk->page_array + disk->free_page_start);
626 collectOldPages(collect_start, collect_len, old_pages, old_cnt);
627 disk->free_bytes += old_cnt * (disk->page_size - BATTFS_HEADER_LEN);
628 disk->free_page_start -= old_cnt;
635 * Initialize and mount disk described by
637 * \return false on errors, true otherwise.
639 bool battfs_init(struct BattFsSuper *disk)
641 pgoff_t filelen_table[BATTFS_MAX_FILES];
646 /* Init disk device */
647 if (!disk->open(disk))
649 TRACEMSG("open error\n");
653 /* Disk open must set all of these */
658 ASSERT(disk->page_size);
659 ASSERT(disk->page_count);
660 ASSERT(disk->page_count < PAGE_UNSET_SENTINEL - 1);
661 ASSERT(disk->page_array);
663 memset(filelen_table, 0, BATTFS_MAX_FILES * sizeof(pgoff_t));
665 disk->free_bytes = 0;
666 disk->disk_size = (disk_size_t)(disk->page_size - BATTFS_HEADER_LEN) * disk->page_count;
668 /* Count pages per file */
669 if (!countDiskFilePages(disk, filelen_table))
671 TRACEMSG("error counting file pages\n");
675 /* Once here, we have filelen_table filled with file lengths */
677 /* Fill page array with sentinel */
678 for (pgcnt_t page = 0; page < disk->page_count; page++)
679 disk->page_array[page] = PAGE_UNSET_SENTINEL;
681 /* Fill page allocation array using filelen_table */
682 if (!fillPageArray(disk, filelen_table))
684 TRACEMSG("error filling page array\n");
688 if (!dropOldPages(disk))
690 LOG_ERR("error dropping old pages\n");
694 /* Init list for opened files. */
695 LIST_INIT(&disk->file_opened_list);
701 * \return 0 if ok, EOF on errors.
703 static int battfs_flush(struct KFile *fd)
712 * \return 0 if ok, EOF on errors.
714 static int battfs_fileclose(struct KFile *fd)
716 BattFS *fdb = BATTFSKFILE(fd);
724 * Read from file \a fd \a size bytes in \a buf.
725 * \return The number of bytes read.
727 static size_t battfs_read(struct KFile *fd, void *_buf, size_t size)
729 BattFS *fdb = BATTFSKFILE(fd);
730 uint8_t *buf = (uint8_t *)_buf;
732 size_t total_read = 0;
734 pgaddr_t addr_offset;
737 size = MIN((kfile_off_t)size, fd->size - fd->seek_pos);
741 pg_offset = fd->seek_pos / (fdb->disk->page_size - BATTFS_HEADER_LEN);
742 addr_offset = fd->seek_pos % (fdb->disk->page_size - BATTFS_HEADER_LEN);
743 read_len = MIN(size, (size_t)(fdb->disk->page_size - BATTFS_HEADER_LEN - addr_offset));
746 if (fdb->disk->read(fdb->disk, fdb->start[pg_offset], addr_offset, buf, read_len) != read_len)
748 #warning TODO set error?
752 fd->seek_pos += read_len;
753 total_read += read_len;
761 * Search file \a inode in \a disk using a binary search.
762 * \return pointer to file start in disk->page_array
763 * if file exists, NULL otherwise.
765 static pgcnt_t *findFile(BattFsSuper *disk, inode_t inode)
767 BattFsPageHeader hdr;
768 pgcnt_t first = 0, page, last = disk->page_count -1;
771 while (first <= last)
773 page = (first + last) / 2;
775 if (!battfs_readHeader(disk, disk->page_array[page], &hdr))
778 fcs = computeFcs(&hdr);
779 if (hdr.fcs == fcs && hdr.inode == inode)
780 return (&disk->page_array[page]) - hdr.pgoff;
781 else if (hdr.fcs == fcs && hdr.inode < inode)
791 * \return true if file \a inode exists on \a disk, false otherwise.
793 bool battfs_fileExists(BattFsSuper *disk, inode_t inode)
795 return findFile(disk, inode) != NULL;
799 * Count size of file \a inode on \a disk, starting at pointer \a start
800 * in disk->page_array. Size is written in \a size.
801 * \return true if all s ok, false on disk read errors.
803 static bool countFileSize(BattFsSuper *disk, pgcnt_t *start, inode_t inode, file_size_t *size)
806 BattFsPageHeader hdr;
810 if (!battfs_readHeader(disk, *start++, &hdr))
812 if (hdr.fcs == computeFcs(&hdr) && hdr.inode == inode)
820 * Open file \a inode from \a disk in \a mode.
821 * File context is stored in \a fd.
822 * \return true if ok, false otherwise.
824 bool battfs_fileopen(BattFsSuper *disk, BattFS *fd, inode_t inode, filemode_t mode)
828 memset(fd, 0, sizeof(*fd));
830 /* Search file start point in disk page array */
831 fd->start = findFile(disk, inode);
832 if (fd->start == NULL)
834 if (!(mode & BATTFS_CREATE))
837 /* File does not exist, create it */
838 BattFsPageHeader hdr;
843 hdr.mark = MARK_PAGE_VALID;
844 hdr.fcs_free = FCS_FREE_VALID;
845 hdr.fcs = computeFcs(&hdr);
846 #warning TODO: get a free block and write on disk!
850 if (!countFileSize(disk, fd->start, inode, &fd->fd.size))
853 /* Reset seek position */
856 /* Insert file handle in list, ordered by inode, ascending. */
857 FOREACH_NODE(n, &disk->file_opened_list)
859 BattFS *file = containerof(n, BattFS, link);
860 if (file->inode >= inode)
863 INSERT_BEFORE(&fd->link, n);
870 fd->fd.close = battfs_fileclose;
871 fd->fd.flush = battfs_flush;
872 fd->fd.read = battfs_read;
873 fd->fd.reopen = kfile_genericReopen;
874 fd->fd.seek = kfile_genericSeek;
876 #warning TODO battfs_write, battfs_error, battfs_clearerr
878 fd->fd.write = battfs_write;
879 fd->fd.error = battfs_error;
880 fd->fd.clearerr = battfs_clearerr;
883 DB(fd->fd._type = KFT_BATTFS);
891 bool battfs_close(struct BattFsSuper *disk)
896 /* Close all open files */
897 FOREACH_NODE(n, &disk->file_opened_list)
899 BattFS *file = containerof(n, BattFS, link);
900 res += battfs_fileclose(&file->fd);
904 return disk->close(disk) && (res == 0);
908 bool battfs_writeTestBlock(struct BattFsSuper *disk, pgcnt_t page, inode_t inode, seq_t seq, fill_t fill, pgoff_t pgoff, mark_t mark)
910 BattFsPageHeader hdr;
916 hdr.mark = MARK_PAGE_VALID;
917 hdr.fcs_free = FCS_FREE_VALID;
918 hdr.fcs = computeFcs(&hdr);
919 if (mark != MARK_PAGE_VALID)
922 hdr.fcs_free = computeFcsFree(&hdr);
925 if (!battfs_writeHeader(disk, page, &hdr))
927 TRACEMSG("error writing hdr\n");