diff options
Diffstat (limited to 'cpukit/libblock/src/flashdisk.c')
-rw-r--r-- | cpukit/libblock/src/flashdisk.c | 2603 |
1 files changed, 2603 insertions, 0 deletions
diff --git a/cpukit/libblock/src/flashdisk.c b/cpukit/libblock/src/flashdisk.c new file mode 100644 index 0000000000..20a9eb46b9 --- /dev/null +++ b/cpukit/libblock/src/flashdisk.c @@ -0,0 +1,2603 @@ +/* + * flashdisk.c -- Flash disk block device implementation + * + * Copyright (C) 2007 Chris Johns + * + * The license and distribution terms for this file may be + * found in the file LICENSE in this distribution or at + * http://www.rtems.com/license/LICENSE. + * + * $Id$ + */ +/** + * @file + * + * Flash disk driver for RTEMS provides support for block based + * file systems on flash devices. The driver is not a flash file + * system nor does it try to compete with flash file systems. It + * currently does not journal how-ever block sequence numbering + * could be added to allow recovery of a past positions if + * a power down occurred while being updated. + * + * This flash driver provides block device support for most flash + * devices. The driver has been tested on NOR type devices such + * as the AMLV160 or M28W160. Support for NAND type devices may + * require driver changes to allow speedy recover of the block + * mapping data and to also handle the current use of word programming. + * Currently the page descriptors are stored in the first few pages + * of each segment. + * + * The driver supports devices, segments and pages. You provide + * to the driver the device descriptions as a table of device + * descriptors. Each device descriptor contain a table of + * segment descriptions or segment descriptors. The driver uses + * this information to manage the devices. + * + * A device is made up of segments. These are also called + * sectors or blocks. It is the smallest erasable part of a device. + * A device can have differing size segments at different + * offsets in the device. The segment descriptors support repeating + * segments that are continous in the device. The driver breaks the + * segments up into pages. The first pages of a segment contain + * the page descriptors. A page descriptor hold the page flags, + * a CRC for the page of data and the block number the page + * holds. The block can appear in any order in the devices. A + * page is active if it hold a current block of data. If the + * used bit is set the page is counted as used. A page moves + * from erased to active to used then back to erased. If a block + * is written that is already in a page, the block is written to + * a new page the old page is flagged as used. + * + * At initialisation time each segment's page descriptors are + * read into memory and scanned to determine the active pages, + * the used pages and the bad pages. If a segment has any erased + * pages it is queue on the available queue. If the segment has + * no erased pages it is queue on the used queue. + * + * The available queue is sorted from the least number available + * to the most number of available pages. A segment that has just + * been erased will placed at the end of the queue. A segment that + * has only a few available pages will be used sooner and once + * there are no available pages it is queued on the used queue. + * The used queue hold segments that have no available pages and + * is sorted from the least number of active pages to the most + * number of active pages. + * + * The driver is required to compact segments. Compacting takes + * the segment with the most number of available pages from the + * available queue then takes segments with the least number of + * active pages from the used queue until it has enough pages + * to fill the empty segment. As the active pages are moved + * they flagged as used and once the segment has only used pages + * it is erased. + * + * A flash block driver like this never knows if a page is not + * being used by the file-system. A typical file system is not + * design with the idea of erasing a block on a disk once it is + * not being used. The file-system will normally use a flag + * or a location as a marker to say that part of the disk is + * no longer in use. This means a number of blocks could be + * held in active pages but are no in use by the file system. + * The file system may also read blocks that have never been + * written to disk. This complicates the driver and may make + * the wear, usage and erase patterns harsher than a flash + * file system. The driver may also suffer from problems if + * power is lost. + * + * @note + * + * The use of pages can vary. The rtems_fdisk_seg_*_page set + * routines use an absolute page number relative to the segment + * while all other page numbera are relative to the number of + * page descriptor pages a segment has. You need to add the + * number of page descriptor pages (pages_desc) to the page number + * when call the rtems_fdisk_seg_*_page functions. + * + * You must always show the page number as relative in any trace + * or error message as device-segment-page and if you have to + * the page number as absolute use device-segment~page. This + * can be seen in the page copy routine. + * + * The code is like this to avoid needing the pass the pages_desc + * value around. It is only used in selected places and so the + * extra parameter was avoided. + */ + +#if HAVE_CONFIG_H +#include "config.h" +#endif + +#include <rtems.h> +#include <rtems/libio.h> +#include <errno.h> +#include <stdlib.h> +#include <stdio.h> +#include <string.h> +#include <inttypes.h> + +#include "rtems/blkdev.h" +#include "rtems/diskdevs.h" +#include "rtems/flashdisk.h" + +/** + * Control tracing. It can be compiled out of the code for small + * footprint targets. Leave in by default. + */ +#if !defined (RTEMS_FDISK_TRACE) +#define RTEMS_FDISK_TRACE 1 +#endif + +/** + * The start of a segment has a segment control table. This hold the CRC and + * block number for the page. + * + * @todo A sequence number for the block could be added. This would + * mean a larger descriptor size. Need to make the sequence + * large like 20+ bits so a large file system would not have + * more blocks available than the sequence number. + */ +typedef struct rtems_fdisk_page_desc +{ + uint16_t crc; /**< The page's checksum. */ + uint16_t flags; /**< The flags for the page. */ + uint32_t block; /**< The block number. */ +} rtems_fdisk_page_desc; + +/** + * Flag the page as active. + */ +#define RTEMS_FDISK_PAGE_ACTIVE (1 << 0) + +/** + * Flag the page as used. + */ +#define RTEMS_FDISK_PAGE_USED (1 << 1) + +/** + * Flash Segment Control holds the pointer to the segment, number of + * pages, various page stats and the memory copy of the page descriptors. + */ +typedef struct rtems_fdisk_segment_ctl +{ + /** + * Segments with available pages are maintained as a linked list. + */ + struct rtems_fdisk_segment_ctl* next; + + /** + * The descriptor provided by the low-level driver. + */ + const rtems_fdisk_segment_desc* descriptor; + + /** + * The device this segment resides on. + */ + uint32_t device; + + /** + * The segment in the device. This must be within the + * segment descriptor. + */ + uint32_t segment; + + /** + * The in-memory ocpy of the page descriptors found at + * the start of the segment in the flash device. + */ + rtems_fdisk_page_desc* page_descriptors; + + /* + * Page stats. + * + * A bad page does not checksum or is not erased or has invalid flags. + */ + uint32_t pages; /**< Total number of pages in the segment. */ + uint32_t pages_desc; /**< Number of pages used for page descriptors. */ + uint32_t pages_active; /**< Number of pages flagged as active. */ + uint32_t pages_used; /**< Number of pages flagged as used. */ + uint32_t pages_bad; /**< Number of pages detected as bad. */ + + uint32_t failed; /**< The segment has failed. */ + + uint32_t erased; /**< Counter to debugging. Wear support would + remove this. */ +} rtems_fdisk_segment_ctl; + +/** + * Segment control table queue. + */ +typedef struct rtems_fdisk_segment_ctl_queue +{ + rtems_fdisk_segment_ctl* head; + rtems_fdisk_segment_ctl* tail; + uint32_t count; +} rtems_fdisk_segment_ctl_queue; + +/** + * Flash Device Control holds the segment controls + */ +typedef struct rtems_fdisk_device_ctl +{ + rtems_fdisk_segment_ctl* segments; /**< Segment controls. */ + uint32_t segment_count; /**< Segment control count. */ + const rtems_fdisk_device_desc* descriptor; /**< Device descriptor. */ +} rtems_fdisk_device_ctl; + +/** + * The Block control holds the segment and page with the data. + */ +typedef struct rtems_fdisk_block_ctl +{ + rtems_fdisk_segment_ctl* segment; /**< The segment with the block. */ + uint32_t page; /**< The page in the segment. */ +} rtems_fdisk_block_ctl; + +/** + * The virtual block table holds the mapping for blocks as seen by the disk + * drivers to the device, segment and page numbers of the physical device. + */ +typedef struct rtems_flashdisk +{ + rtems_device_major_number major; /**< The driver's major number. */ + rtems_device_minor_number minor; /**< The driver's minor number. */ + + uint32_t flags; /**< configuration flags. */ + + uint32_t compact_segs; /**< Max segs to compact at once. */ + uint32_t avail_compact_segs; /**< The number of segments when + compaction occurs when writing. */ + + uint32_t block_size; /**< The block size for this disk. */ + rtems_fdisk_block_ctl* blocks; /**< The block to segment-page + mappings. */ + uint32_t block_count; /**< The number of avail. blocks. */ + uint32_t unavail_blocks; /**< The number of unavail blocks. */ + + rtems_fdisk_device_ctl* devices; /**< The flash devices for this + disk. */ + uint32_t device_count; /**< The number of flash devices. */ + + rtems_fdisk_segment_ctl_queue available; /**< The queue of segments with + available pages. */ + rtems_fdisk_segment_ctl_queue used; /**< The list of segments with all + pages used. */ + rtems_fdisk_segment_ctl_queue erase; /**< The list of segments to be + erased. */ + rtems_fdisk_segment_ctl_queue failed; /**< The list of segments that failed + when being erased. */ + rtems_id lock; /**< Mutex for threading protection.*/ + + uint8_t* copy_buffer; /**< Copy buf used during compacting */ + + uint32_t info_level; /**< The info trace level. */ +} rtems_flashdisk; + +/** + * The array of flash disks we support. + */ +static rtems_flashdisk* rtems_flashdisks; + +/** + * The number of flash disks we have. + */ +static uint32_t rtems_flashdisk_count; + +/** + * The CRC16 factor table. Created during initialisation. + */ +static uint16_t* rtems_fdisk_crc16_factor; + +/** + * Calculate the CRC16 checksum. + * + * @param _b The byte to checksum. + * @param _c The current checksum. + */ +#define rtems_fdisk_calc_crc16(_b, _c) \ + rtems_fdisk_crc16_factor[((_b) ^ ((_c) & 0xff)) & 0xff] ^ (((_c) >> 8) & 0xff) + +/** + * Generate the CRC table. + * + * @param pattern The seed pattern for the table of factors. + * @relval RTEMS_SUCCESSFUL The table was generated. + * @retval RTEMS_NO_MEMORY The table could not be allocated from the heap. + */ +rtems_status_code +rtems_fdisk_crc16_gen_factors (uint16_t pattern) +{ + uint32_t b; + + rtems_fdisk_crc16_factor = malloc (sizeof (uint16_t) * 256); + if (!rtems_fdisk_crc16_factor) + return RTEMS_NO_MEMORY; + + for (b = 0; b < 256; b++) + { + uint32_t i; + uint16_t v = b; + for (i = 8; i--;) + v = v & 1 ? (v >> 1) ^ pattern : v >> 1; + rtems_fdisk_crc16_factor[b] = v & 0xffff; + } + return RTEMS_SUCCESSFUL; +} + +#if RTEMS_FDISK_TRACE +/** + * Print a message to the flash disk output and flush it. + * + * @param fd The flashdisk control structure. + * @param format The format string. See printf for details. + * @param ... The arguments for the format text. + * @return int The number of bytes written to the output. + */ +static int +rtems_fdisk_printf (const rtems_flashdisk* fd, const char *format, ...) +{ + int ret = 0; + if (fd->info_level >= 3) + { + va_list args; + va_start (args, format); + fprintf (stdout, "fdisk:"); + ret = vfprintf (stdout, format, args); + fprintf (stdout, "\n"); + fflush (stdout); + va_end (args); + } + return ret; +} + +/** + * Print a info message to the flash disk output and flush it. + * + * @param fd The flashdisk control structure. + * @param format The format string. See printf for details. + * @param ... The arguments for the format text. + * @return int The number of bytes written to the output. + */ +static int +rtems_fdisk_info (const rtems_flashdisk* fd, const char *format, ...) +{ + int ret = 0; + if (fd->info_level >= 2) + { + va_list args; + va_start (args, format); + fprintf (stdout, "fdisk:"); + ret = vfprintf (stdout, format, args); + fprintf (stdout, "\n"); + fflush (stdout); + va_end (args); + } + return ret; +} + +/** + * Print a warning to the flash disk output and flush it. + * + * @param fd The flashdisk control structure. + * @param format The format string. See printf for details. + * @param ... The arguments for the format text. + * @return int The number of bytes written to the output. + */ +static int +rtems_fdisk_warning (const rtems_flashdisk* fd, const char *format, ...) +{ + int ret = 0; + if (fd->info_level >= 1) + { + va_list args; + va_start (args, format); + fprintf (stdout, "fdisk:warning:"); + ret = vfprintf (stdout, format, args); + fprintf (stdout, "\n"); + fflush (stdout); + va_end (args); + } + return ret; +} +#endif + +/** + * Print an error to the flash disk output and flush it. + * + * @param format The format string. See printf for details. + * @param ... The arguments for the format text. + * @return int The number of bytes written to the output. + */ +static int +rtems_fdisk_error (const char *format, ...) +{ + int ret; + va_list args; + va_start (args, format); + fprintf (stderr, "fdisk:error:"); + ret = vfprintf (stderr, format, args); + fprintf (stderr, "\n"); + fflush (stderr); + va_end (args); + return ret; +} + +/** + * Print an abort message, flush it then abort the program. + * + * @param format The format string. See printf for details. + * @param ... The arguments for the format text. + */ +static void +rtems_fdisk_abort (const char *format, ...) +{ + va_list args; + va_start (args, format); + fprintf (stderr, "fdisk:abort:"); + vfprintf (stderr, format, args); + fprintf (stderr, "\n"); + fflush (stderr); + va_end (args); + exit (1); +} + +/** + * Initialise the segment control queue. + */ +static void +rtems_fdisk_segment_queue_init (rtems_fdisk_segment_ctl_queue* queue) +{ + queue->head = queue->tail = 0; + queue->count = 0; +} + +/** + * Push to the head of the segment control queue. + */ +static void +rtems_fdisk_segment_queue_push_head (rtems_fdisk_segment_ctl_queue* queue, + rtems_fdisk_segment_ctl* sc) +{ + if (sc) + { + sc->next = queue->head; + queue->head = sc; + + if (queue->tail == 0) + queue->tail = sc; + queue->count++; + } +} + +/** + * Pop the head of the segment control queue. + */ +static rtems_fdisk_segment_ctl* +rtems_fdisk_segment_queue_pop_head (rtems_fdisk_segment_ctl_queue* queue) +{ + if (queue->head) + { + rtems_fdisk_segment_ctl* sc = queue->head; + + queue->head = sc->next; + if (!queue->head) + queue->tail = 0; + + queue->count--; + + sc->next = 0; + + return sc; + } + + return 0; +} + +/** + * Push to the tail of the segment control queue. + */ +static void +rtems_fdisk_segment_queue_push_tail (rtems_fdisk_segment_ctl_queue* queue, + rtems_fdisk_segment_ctl* sc) +{ + if (sc) + { + sc->next = 0; + + if (queue->head) + { + queue->tail->next = sc; + queue->tail = sc; + } + else + { + queue->head = queue->tail = sc; + } + + queue->count++; + } +} + +/** + * Remove from the segment control queue. + */ +static void +rtems_fdisk_segment_queue_remove (rtems_fdisk_segment_ctl_queue* queue, + rtems_fdisk_segment_ctl* sc) +{ + rtems_fdisk_segment_ctl* prev = 0; + rtems_fdisk_segment_ctl* it = queue->head; + + /* + * Do not change sc->next as sc could be on another queue. + */ + + while (it) + { + if (sc == it) + { + if (prev == 0) + { + queue->head = sc->next; + if (queue->head == 0) + queue->tail = 0; + } + else + { + prev->next = sc->next; + if (queue->tail == sc) + queue->tail = prev; + } + sc->next = 0; + queue->count--; + break; + } + + prev = it; + it = it->next; + } +} + +/** + * Insert into the segment control queue before the specific + * segment control item. + */ +static void +rtems_fdisk_segment_queue_insert_before (rtems_fdisk_segment_ctl_queue* queue, + rtems_fdisk_segment_ctl* item, + rtems_fdisk_segment_ctl* sc) +{ + if (item) + { + rtems_fdisk_segment_ctl** prev = &queue->head; + rtems_fdisk_segment_ctl* it = queue->head; + + while (it) + { + if (item == it) + { + sc->next = item; + *prev = sc; + queue->count++; + return; + } + + prev = &it->next; + it = it->next; + } + } + + rtems_fdisk_segment_queue_push_tail (queue, sc); +} + +/** + * Count the number of elements on the list. + */ +static uint32_t +rtems_fdisk_segment_queue_count (rtems_fdisk_segment_ctl_queue* queue) +{ + return queue->count; +} + +/** + * Count the number of elements on the list. + */ +static uint32_t +rtems_fdisk_segment_count_queue (rtems_fdisk_segment_ctl_queue* queue) +{ + rtems_fdisk_segment_ctl* sc = queue->head; + uint32_t count = 0; + + while (sc) + { + count++; + sc = sc->next; + } + + return count; +} + +/** + * See if a segment control is present on this queue. + */ +static bool +rtems_fdisk_segment_queue_present (rtems_fdisk_segment_ctl_queue* queue, + rtems_fdisk_segment_ctl* sc) +{ + rtems_fdisk_segment_ctl* it = queue->head; + + while (it) + { + if (it == sc) + return true; + it = it->next; + } + + return false; +} + +/** + * Format a string with the queue status. + */ +static void +rtems_fdisk_queue_status (rtems_flashdisk* fd, + rtems_fdisk_segment_ctl* sc, + char queues[5]) +{ + queues[0] = rtems_fdisk_segment_queue_present (&fd->available, sc) ? 'A' : '-'; + queues[1] = rtems_fdisk_segment_queue_present (&fd->used, sc) ? 'U' : '-'; + queues[2] = rtems_fdisk_segment_queue_present (&fd->erase, sc) ? 'E' : '-'; + queues[3] = rtems_fdisk_segment_queue_present (&fd->failed, sc) ? 'F' : '-'; + queues[4] = '\0'; +} + +/** + * Check if the page descriptor is erased. + */ +static bool +rtems_fdisk_page_desc_erased (const rtems_fdisk_page_desc* pd) +{ + return ((pd->crc == 0xffff) && + (pd->flags == 0xffff) && + (pd->block == 0xffffffff)) ? true : false; +} + +/** + * Check if the flags are set. The flags are inverted as we can + * only set a flag by changing it from 1 to 0. + */ +static bool +rtems_fdisk_page_desc_flags_set (rtems_fdisk_page_desc* pd, uint16_t flags) +{ + return (pd->flags & flags) == 0 ? true : false; +} + +/** + * Check if the flags are clear. The flags are inverted as we can + * only set a flag by changing it from 1 to 0. + */ +static bool +rtems_fdisk_page_desc_flags_clear (rtems_fdisk_page_desc* pd, uint16_t flags) +{ + return (pd->flags & flags) == flags ? true : false; +} + +/** + * Set the flags. Setting means clear the bit to 0. + */ +static void +rtems_fdisk_page_desc_set_flags (rtems_fdisk_page_desc* pd, uint16_t flags) +{ + pd->flags &= ~flags; +} + +/** + * Get the segment descriptor for a device and segment. There are + * no range checks. + */ +static const rtems_fdisk_segment_desc* +rtems_fdisk_seg_descriptor (const rtems_flashdisk* fd, + uint32_t device, + uint32_t segment) +{ + return fd->devices[device].segments[segment].descriptor; +} + +/** + * Count the segments for a device. + */ +static uint32_t +rtems_fdisk_count_segments (const rtems_fdisk_device_desc* dd) +{ + uint32_t count = 0; + uint32_t segment; + for (segment = 0; segment < dd->segment_count; segment++) + count += dd->segments[segment].count; + return count; +} + +/** + * Calculate the pages in a segment give the segment size and the + * page size. + * + * @param sd The segment descriptor. + * @param page_size The page size in bytes. + */ +static uint32_t +rtems_fdisk_pages_in_segment (const rtems_fdisk_segment_desc* sd, + uint32_t page_size) +{ + return sd->size / page_size; +} + +/** + * Calculate the number of pages needed to hold the page descriptors. + * The calculation need to round up. + * + * The segment control contains the number of pages used as descriptors + * and should be used rather than this call where possible. + */ +static uint32_t +rtems_fdisk_page_desc_pages (const rtems_fdisk_segment_desc* sd, + uint32_t page_size) +{ + uint32_t pages = rtems_fdisk_pages_in_segment (sd, page_size); + uint32_t bytes = pages * sizeof (rtems_fdisk_page_desc); + return ((bytes - 1) / page_size) + 1; +} + +/** + * The number of available pages is the total pages less the + * active, used and bad pages. + */ +static uint32_t +rtems_fdisk_seg_pages_available (const rtems_fdisk_segment_ctl* sc) +{ + return sc->pages - (sc->pages_active + sc->pages_used + sc->pages_bad); +} +/** + * Find the next available page in a segment. + */ +static uint32_t +rtems_fdisk_seg_next_available_page (rtems_fdisk_segment_ctl* sc) +{ + rtems_fdisk_page_desc* pd = &sc->page_descriptors[0]; + uint32_t page; + + for (page = 0; page < sc->pages; page++, pd++) + if (rtems_fdisk_page_desc_erased (pd)) + break; + + return page; +} + +/** + * Find the segment on the queue that has the most free pages. + */ +static rtems_fdisk_segment_ctl* +rtems_fdisk_seg_most_available (const rtems_fdisk_segment_ctl_queue* queue) +{ + rtems_fdisk_segment_ctl* sc = queue->head; + rtems_fdisk_segment_ctl* biggest = queue->head; + + while (sc) + { + if (rtems_fdisk_seg_pages_available (sc) > + rtems_fdisk_seg_pages_available (biggest)) + biggest = sc; + sc = sc->next; + } + + return biggest; +} + +/** + * Is the segment all used ? + */ +#if 0 +static bool +rtems_fdisk_seg_pages_all_used (const rtems_fdisk_segment_ctl* sc) +{ + return sc->pages == (sc->pages_used + sc->pages_bad) ? true : false; +} +#endif + +/** + * Calculate the blocks in a device. This is the number of + * pages less the pages hold page descriptors. This call be used + * early in the initialisation process and does not rely on + * the system being fully initialised. + * + * @param dd The device descriptor. + * @param page_size The page size in bytes. + */ +static uint32_t +rtems_fdisk_blocks_in_device (const rtems_fdisk_device_desc* dd, + uint32_t page_size) +{ + uint32_t count = 0; + uint32_t s; + for (s = 0; s < dd->segment_count; s++) + { + const rtems_fdisk_segment_desc* sd = &dd->segments[s]; + count += + (rtems_fdisk_pages_in_segment (sd, page_size) - + rtems_fdisk_page_desc_pages (sd, page_size)) * sd->count; + } + return count; +} + +/** + * Read a block of data from a segment. + */ +static int +rtems_fdisk_seg_read (const rtems_flashdisk* fd, + uint32_t device, + uint32_t segment, + uint32_t offset, + void* buffer, + uint32_t size) +{ + const rtems_fdisk_segment_desc* sd; + const rtems_fdisk_driver_handlers* ops; + sd = rtems_fdisk_seg_descriptor (fd, device, segment); + ops = fd->devices[device].descriptor->flash_ops; +#if RTEMS_FDISK_TRACE + rtems_fdisk_printf (fd, " seg-read: %02d-%03d: o=%08x s=%d", + device, segment, offset, size); +#endif + return ops->read (sd, device, segment, offset, buffer, size); +} + +/** + * Write a block of data to a segment. It is assumed the + * location in the segment is erased and able to take the + * data. + */ +static int +rtems_fdisk_seg_write (const rtems_flashdisk* fd, + uint32_t device, + uint32_t segment, + uint32_t offset, + const void* buffer, + uint32_t size) +{ + const rtems_fdisk_segment_desc* sd; + const rtems_fdisk_driver_handlers* ops; + sd = rtems_fdisk_seg_descriptor (fd, device, segment); + ops = fd->devices[device].descriptor->flash_ops; +#if RTEMS_FDISK_TRACE + rtems_fdisk_printf (fd, " seg-write: %02d-%03d: o=%08x s=%d", + device, segment, offset, size); +#endif + return ops->write (sd, device, segment, offset, buffer, size); +} + +/** + * Blank check the area of a segment. + */ +static int +rtems_fdisk_seg_blank_check (const rtems_flashdisk* fd, + uint32_t device, + uint32_t segment, + uint32_t offset, + uint32_t size) +{ + const rtems_fdisk_segment_desc* sd; + const rtems_fdisk_driver_handlers* ops; + sd = rtems_fdisk_seg_descriptor (fd, device, segment); + ops = fd->devices[device].descriptor->flash_ops; +#if RTEMS_FDISK_TRACE + rtems_fdisk_printf (fd, " seg-blank: %02d-%03d: o=%08x s=%d", + device, segment, offset, size); +#endif + return ops->blank (sd, device, segment, offset, size); +} +/** + * Verify the data with the data in a segment. + */ +static int +rtems_fdisk_seg_verify (const rtems_flashdisk* fd, + uint32_t device, + uint32_t segment, + uint32_t offset, + const void* buffer, + uint32_t size) +{ + const rtems_fdisk_segment_desc* sd; + const rtems_fdisk_driver_handlers* ops; + sd = rtems_fdisk_seg_descriptor (fd, device, segment); + ops = fd->devices[device].descriptor->flash_ops; +#if RTEMS_FDISK_TRACE + rtems_fdisk_printf (fd, " seg-verify: %02d-%03d: o=%08x s=%d", + device, segment, offset, size); +#endif + return ops->verify (sd, device, segment, offset, buffer, size); +} + +/** + * Blank check a page of data in a segment. + */ +static int +rtems_fdisk_seg_blank_check_page (const rtems_flashdisk* fd, + uint32_t device, + uint32_t segment, + uint32_t page) +{ + return rtems_fdisk_seg_blank_check (fd, device, segment, + page * fd->block_size, fd->block_size); +} + +/** + * Read a page of data from a segment. + */ +static int +rtems_fdisk_seg_read_page (const rtems_flashdisk* fd, + uint32_t device, + uint32_t segment, + uint32_t page, + void* buffer) +{ + return rtems_fdisk_seg_read (fd, device, segment, + page * fd->block_size, buffer, fd->block_size); +} + +/** + * Write a page of data to a segment. + */ +static int +rtems_fdisk_seg_write_page (const rtems_flashdisk* fd, + uint32_t device, + uint32_t segment, + uint32_t page, + const void* buffer) +{ + if ((fd->flags & RTEMS_FDISK_BLANK_CHECK_BEFORE_WRITE)) + { + int ret = rtems_fdisk_seg_blank_check_page (fd, device, segment, page); + if (ret) + return ret; + } + return rtems_fdisk_seg_write (fd, device, segment, + page * fd->block_size, buffer, fd->block_size); +} + +/** + * Verify a page of data with the data in the segment. + */ +static int +rtems_fdisk_seg_verify_page (const rtems_flashdisk* fd, + uint32_t device, + uint32_t segment, + uint32_t page, + const void* buffer) +{ + return rtems_fdisk_seg_verify (fd, device, segment, + page * fd->block_size, buffer, fd->block_size); +} + +/** + * Copy a page of data from one segment to another segment. + */ +static int +rtems_fdisk_seg_copy_page (const rtems_flashdisk* fd, + uint32_t src_device, + uint32_t src_segment, + uint32_t src_page, + uint32_t dst_device, + uint32_t dst_segment, + uint32_t dst_page) +{ + int ret; +#if RTEMS_FDISK_TRACE + rtems_fdisk_printf (fd, " seg-copy-page: %02d-%03d~%03d=>%02d-%03d~%03d", + src_device, src_segment, src_page, + dst_device, dst_segment, dst_page); +#endif + ret = rtems_fdisk_seg_read_page (fd, src_device, src_segment, src_page, + fd->copy_buffer); + if (ret) + return ret; + return rtems_fdisk_seg_write_page (fd, dst_device, dst_segment, dst_page, + fd->copy_buffer); +} + +/** + * Write the page descriptor to a segment. This code assumes the page + * descriptors are located at offset 0 in the segment. + */ +static int +rtems_fdisk_seg_write_page_desc (const rtems_flashdisk* fd, + uint32_t device, + uint32_t segment, + uint32_t page, + const rtems_fdisk_page_desc* page_desc) +{ + uint32_t offset = page * sizeof (rtems_fdisk_page_desc); + if ((fd->flags & RTEMS_FDISK_BLANK_CHECK_BEFORE_WRITE)) + { + int ret = rtems_fdisk_seg_blank_check (fd, device, segment, + offset, + sizeof (rtems_fdisk_page_desc)); + if (ret) + return ret; + } + return rtems_fdisk_seg_write (fd, device, segment, offset, + page_desc, sizeof (rtems_fdisk_page_desc)); +} + +/** + * Write the page descriptor flags to a segment. This code assumes the page + * descriptors are located at offset 0 in the segment. + */ +static int +rtems_fdisk_seg_write_page_desc_flags (const rtems_flashdisk* fd, + uint32_t device, + uint32_t segment, + uint32_t page, + const rtems_fdisk_page_desc* page_desc) +{ + uint32_t offset = ((page * sizeof (rtems_fdisk_page_desc)) + + ((uint8_t*) &page_desc->flags) - ((uint8_t*) page_desc)); + if ((fd->flags & RTEMS_FDISK_BLANK_CHECK_BEFORE_WRITE)) + { + uint16_t flash_flags; + int ret; + ret = rtems_fdisk_seg_read (fd, device, segment, offset, + &flash_flags, sizeof (flash_flags)); + if (ret) + return ret; + if ((flash_flags & page_desc->flags) != page_desc->flags) + { + rtems_fdisk_error (" seg-write-page-flags: %02d-%03d-%03d: " \ + "flags not erased: 0x%04 -> 0x%04x", + device, segment, page, flash_flags, page_desc->flags); + return ret; + } + } + return rtems_fdisk_seg_write (fd, device, segment, offset, + &page_desc->flags, sizeof (page_desc->flags)); +} + +/** + * Erase a segment. + */ +static int +rtems_fdisk_seg_erase (const rtems_flashdisk* fd, + uint32_t device, + uint32_t segment) +{ + const rtems_fdisk_segment_desc* sd; + const rtems_fdisk_driver_handlers* ops; + sd = rtems_fdisk_seg_descriptor (fd, device, segment); + ops = fd->devices[device].descriptor->flash_ops; +#if RTEMS_FDISK_TRACE + rtems_fdisk_printf (fd, " seg-erase: %02d-%03d", device, segment); +#endif + return ops->erase (sd, device, segment); +} + +/** + * Erase a device. + */ +static int +rtems_fdisk_device_erase (const rtems_flashdisk* fd, uint32_t device) +{ + const rtems_fdisk_driver_handlers* ops; + ops = fd->devices[device].descriptor->flash_ops; +#if RTEMS_FDISK_TRACE + rtems_fdisk_printf (fd, " device-erase: %02d", device); +#endif + return ops->erase_device (fd->devices[device].descriptor, device); +} + +/** + * Erase all flash. + */ +static int +rtems_fdisk_erase_flash (const rtems_flashdisk* fd) +{ + uint32_t device; + for (device = 0; device < fd->device_count; device++) + { + int ret; + +#if RTEMS_FDISK_TRACE + rtems_fdisk_info (fd, " erase-flash:%02d", device); +#endif + + ret = rtems_fdisk_device_erase (fd, device); + + if (ret != 0) + return ret; + } + return 0; +} + +/** + * Calculate the checksum of a page in a segment. + */ +static uint16_t +rtems_fdisk_page_checksum (const uint8_t* buffer, uint32_t page_size) +{ + uint16_t cs = 0xffff; + uint32_t i; + + for (i = 0; i < page_size; i++, buffer++) + cs = rtems_fdisk_calc_crc16 (cs, *buffer); + + return cs; +} + +/** + * Erase the segment. + */ +static int +rtems_fdisk_erase_segment (rtems_flashdisk* fd, rtems_fdisk_segment_ctl* sc) +{ + int ret = rtems_fdisk_seg_erase (fd, sc->device, sc->segment); + if (ret) + { + rtems_fdisk_error (" erase-segment:%02d-%03d: " \ + "segment erase failed: %s (%d)", + sc->device, sc->segment, strerror (ret), ret); + sc->failed = true; + if (!rtems_fdisk_segment_queue_present (&fd->failed, sc)) + rtems_fdisk_segment_queue_push_tail (&fd->failed, sc); + return ret; + } + + sc->erased++; + + memset (sc->page_descriptors, 0xff, sc->pages_desc * fd->block_size); + + sc->pages_active = 0; + sc->pages_used = 0; + sc->pages_bad = 0; + + sc->failed = false; + + /* + * Push to the tail of the available queue. It is a very + * simple type of wear reduction. Every other available + * segment will now get a go. + */ + rtems_fdisk_segment_queue_push_tail (&fd->available, sc); + + return 0; +} + +/** + * Erase used segment. + */ +static int +rtems_fdisk_erase_used (rtems_flashdisk* fd) +{ + rtems_fdisk_segment_ctl* sc; + int latched_ret = 0; + + while ((sc = rtems_fdisk_segment_queue_pop_head (&fd->erase))) + { + /* + * The segment will either end up on the available queue or + * the failed queue. + */ + int ret = rtems_fdisk_erase_segment (fd, sc); + if (ret && !latched_ret) + latched_ret = ret; + } + + return latched_ret; +} + +/** + * Queue a segment. This is done after some of the stats for the segment + * have been changed and this may effect the order the segment pages have in + * the queue of available pages. + * + * @param fd The flash disk control table. + * @param sc The segment control table to be reallocated + */ +static void +rtems_fdisk_queue_segment (rtems_flashdisk* fd, rtems_fdisk_segment_ctl* sc) +{ +#if RTEMS_FDISK_TRACE + rtems_fdisk_info (fd, " queue-seg:%02d-%03d: p=%d a=%d u=%d b=%d f=%s n=%s", + sc->device, sc->segment, + sc->pages, sc->pages_active, sc->pages_used, sc->pages_bad, + sc->failed ? "FAILED" : "no", sc->next ? "set" : "null"); +#endif + + /* + * If the segment has failed then check the failed queue and append + * if not failed. + */ + if (sc->failed) + { + if (!rtems_fdisk_segment_queue_present (&fd->failed, sc)) + rtems_fdisk_segment_queue_push_tail (&fd->failed, sc); + return; + } + + /* + * Remove the queue from the available or used queue. + */ + rtems_fdisk_segment_queue_remove (&fd->available, sc); + rtems_fdisk_segment_queue_remove (&fd->used, sc); + + /* + * Are all the pages in the segment used ? + * If they are and the driver has been configured to background + * erase place the segment on the used queue. If not configured + * to background erase perform the erase now. + * + */ + if (rtems_fdisk_seg_pages_available (sc) == 0) + { + if (sc->pages_active) + { + /* + * Keep the used queue sorted by the most number of used + * pages. When we compact we want to move the pages into + * a new segment and cover more than one segment. + */ + rtems_fdisk_segment_ctl* seg = fd->used.head; + + while (seg) + { + if (sc->pages_used > seg->pages_used) + break; + seg = seg->next; + } + + if (seg) + rtems_fdisk_segment_queue_insert_before (&fd->used, seg, sc); + else + rtems_fdisk_segment_queue_push_tail (&fd->used, sc); + } + else + { + if ((fd->flags & RTEMS_FDISK_BACKGROUND_ERASE)) + rtems_fdisk_segment_queue_push_tail (&fd->erase, sc); + else + rtems_fdisk_erase_segment (fd, sc); + } + } + else + { + /* + * The segment has pages available so place back onto the + * available list. The list is sorted from the least number + * of available pages to the most. This approach means + * the pages of a partially filled segment will be filled + * before moving onto another emptier segment. This keeps + * empty segments longer aiding compaction. + * + * The down side is the wear effect as a single segment + * could be used more than segment. This will not be + * addressed until wear support is added. + * + * @note Wear support can be added by having counts for + * for the number of times a segment is erased. This + * available list is then sorted on the least number + * of available pages then empty segments are sorted + * on the least number of erases the segment has. + * + * The erase count can be stored in specially flaged + * pages and contain a counter (32bits?) and 32 bits + * for each segment. When a segment is erased a + * bit is cleared for that segment. When 32 erasers + * has occurred the page is re-written to the flash + * with all the counters updated with the number of + * bits cleared and all bits set back to 1. + */ + rtems_fdisk_segment_ctl* seg = fd->available.head; + + while (seg) + { + if (rtems_fdisk_seg_pages_available (sc) < + rtems_fdisk_seg_pages_available (seg)) + break; + seg = seg->next; + } + + if (seg) + rtems_fdisk_segment_queue_insert_before (&fd->available, seg, sc); + else + rtems_fdisk_segment_queue_push_tail (&fd->available, sc); + } +} + +/** + * Compact the used segments to free what is available. Find the segment + * with the most avalable number of pages and see if we have + * used segments that will fit. The used queue is sorted on the least + * number of active pages. + */ +static int +rtems_fdisk_compact (rtems_flashdisk* fd) +{ + uint32_t compacted_segs = 0; + + while (fd->used.head) + { + rtems_fdisk_segment_ctl* dsc; + rtems_fdisk_segment_ctl* ssc; + uint32_t dst_pages; + uint32_t segments; + uint32_t pages; + +#if RTEMS_FDISK_TRACE + rtems_fdisk_printf (fd, " compacting"); +#endif + + dsc = rtems_fdisk_seg_most_available (&fd->available); + + if (dsc == 0) + { + rtems_fdisk_error ("compacting: no available segments to compact too"); + return EIO; + } + + ssc = fd->used.head; + dst_pages = rtems_fdisk_seg_pages_available (dsc); + segments = 0; + pages = 0; + +#if RTEMS_FDISK_TRACE + rtems_fdisk_printf (fd, " dsc:%02d-%03d: most available", + dsc->device, dsc->segment); +#endif + + /* + * Count the number of segments that have active pages that fit into + * the destination segment. Also limit the number of segments that + * we handle during one compaction. A lower number means less aggressive + * compaction or less delay when compacting but it may mean the disk + * will fill. + */ + + while (ssc && + ((pages + ssc->pages_active) < dst_pages) && + ((compacted_segs + segments) < fd->compact_segs)) + { + pages += ssc->pages_active; + segments++; + ssc = ssc->next; + } + + /* + * We need a source segment and have pages to copy and + * compacting one segment to another is silly. Compaction needs + * to free at least one more segment. + */ + + if (!ssc || (pages == 0) || ((compacted_segs + segments) == 1)) + break; + +#if RTEMS_FDISK_TRACE + rtems_fdisk_printf (fd, " ssc scan: %d-%d: p=%ld, seg=%ld", + ssc->device, ssc->segment, + pages, segments); +#endif + + rtems_fdisk_segment_queue_remove (&fd->available, dsc); + + /* + * We now copy the pages to the new segment. + */ + + while (pages) + { + uint32_t spage; + int ret; + + ssc = rtems_fdisk_segment_queue_pop_head (&fd->used); + + if (ssc) + { + uint32_t used = 0; + uint32_t active = 0; + for (spage = 0; spage < ssc->pages; spage++) + { + rtems_fdisk_page_desc* spd = &ssc->page_descriptors[spage]; + + if (rtems_fdisk_page_desc_flags_set (spd, RTEMS_FDISK_PAGE_ACTIVE) && + !rtems_fdisk_page_desc_flags_set (spd, RTEMS_FDISK_PAGE_USED)) + { + rtems_fdisk_page_desc* dpd; + uint32_t dpage; + + dpage = rtems_fdisk_seg_next_available_page (dsc); + dpd = &dsc->page_descriptors[dpage]; + + active++; + + if (dpage >= dsc->pages) + { + rtems_fdisk_error ("compacting: %02d-%03d: " \ + "no page desc available: %d", + dsc->device, dsc->segment, + rtems_fdisk_seg_pages_available (dsc)); + dsc->failed = true; + rtems_fdisk_queue_segment (fd, dsc); + rtems_fdisk_segment_queue_push_head (&fd->used, ssc); + return EIO; + } + +#if RTEMS_FDISK_TRACE + rtems_fdisk_info (fd, "compacting: %02d-%03d-%03d=>%02d-%03d-%03d", + ssc->device, ssc->segment, spage, + dsc->device, dsc->segment, dpage); +#endif + ret = rtems_fdisk_seg_copy_page (fd, ssc->device, ssc->segment, + spage + ssc->pages_desc, + dsc->device, dsc->segment, + dpage + dsc->pages_desc); + if (ret) + { + rtems_fdisk_error ("compacting: %02d-%03d-%03d=>" \ + "%02d-%03d-%03d: " \ + "copy page failed: %s (%d)", + ssc->device, ssc->segment, spage, + dsc->device, dsc->segment, dpage, + strerror (ret), ret); + dsc->failed = true; + rtems_fdisk_queue_segment (fd, dsc); + rtems_fdisk_segment_queue_push_head (&fd->used, ssc); + return ret; + } + + *dpd = *spd; + + ret = rtems_fdisk_seg_write_page_desc (fd, + dsc->device, dsc->segment, + dpage, dpd); + + if (ret) + { + rtems_fdisk_error ("compacting: %02d-%03d-%03d=>" \ + "%02d-%03d-%03d: copy pd failed: %s (%d)", + ssc->device, ssc->segment, spage, + dsc->device, dsc->segment, dpage, + strerror (ret), ret); + dsc->failed = true; + rtems_fdisk_queue_segment (fd, dsc); + rtems_fdisk_segment_queue_push_head (&fd->used, ssc); + return ret; + } + + dsc->pages_active++; + + /* + * No need to set the used bit on the source page as the + * segment will be erased. Power down could be a problem. + * We do the stats to make sure everything is as it should + * be. + */ + + ssc->pages_active--; + ssc->pages_used++; + + fd->blocks[spd->block].segment = dsc; + fd->blocks[spd->block].page = dpage; + + /* + * Place the segment on to the correct queue. + */ + rtems_fdisk_queue_segment (fd, dsc); + + pages--; + } + else + used++; + } + +#if RTEMS_FDISK_TRACE + rtems_fdisk_printf (fd, "ssc end: %d-%d: p=%ld, a=%ld, u=%ld", + ssc->device, ssc->segment, + pages, active, used); +#endif + if (ssc->pages_active != 0) + { + rtems_fdisk_error ("compacting: ssc pages not 0: %d", + ssc->pages_active); + } + + ret = rtems_fdisk_erase_segment (fd, ssc); + + if (ret) + return ret; + } + } + + compacted_segs += segments; + } + + return 0; +} + +/** + * Recover the block mappings from the devices. + */ +static int +rtems_fdisk_recover_block_mappings (rtems_flashdisk* fd) +{ + uint32_t device; + + /* + * Clear the queues. + */ + rtems_fdisk_segment_queue_init (&fd->available); + rtems_fdisk_segment_queue_init (&fd->used); + rtems_fdisk_segment_queue_init (&fd->erase); + rtems_fdisk_segment_queue_init (&fd->failed); + + /* + * Clear the lock mappings. + */ + memset (fd->blocks, 0, fd->block_count * sizeof (rtems_fdisk_block_ctl)); + + /* + * Scan each segment or each device recovering the valid pages. + */ + for (device = 0; device < fd->device_count; device++) + { + uint32_t segment; + for (segment = 0; segment < fd->devices[device].segment_count; segment++) + { + rtems_fdisk_segment_ctl* sc = &fd->devices[device].segments[segment]; + const rtems_fdisk_segment_desc* sd = sc->descriptor; + rtems_fdisk_page_desc* pd; + uint32_t page; + int ret; + +#if RTEMS_FDISK_TRACE + rtems_fdisk_info (fd, "recover-block-mappings:%02d-%03d", device, segment); +#endif + + sc->pages_desc = rtems_fdisk_page_desc_pages (sd, fd->block_size); + sc->pages = + rtems_fdisk_pages_in_segment (sd, fd->block_size) - sc->pages_desc; + + sc->pages_active = 0; + sc->pages_used = 0; + sc->pages_bad = 0; + + sc->failed = false; + + if (!sc->page_descriptors) + sc->page_descriptors = malloc (sc->pages_desc * fd->block_size); + + if (!sc->page_descriptors) + rtems_fdisk_abort ("no memory for page descriptors"); + + pd = sc->page_descriptors; + + /* + * The page descriptors are always at the start of the segment. Read + * the descriptors off the device into the segment control page + * descriptors. + * + * @todo It may be better to ask the driver to get these value + * so NAND flash could be better supported. + */ + ret = rtems_fdisk_seg_read (fd, device, segment, 0, (void*) pd, + sc->pages_desc * fd->block_size); + + if (ret) + { + rtems_fdisk_error ("recover-block-mappings:%02d-%03d: " \ + "read page desc failed: %s (%d)", + device, segment, strerror (ret), ret); + return ret; + } + + /* + * Check each page in the segement for valid pages. + * Update the stats for the segment so we know how many pages + * are active and how many are used. + * + * If the page is active see if the block is with-in range and + * if the block is a duplicate. + */ + for (page = 0; page < sc->pages; page++, pd++) + { + if (rtems_fdisk_page_desc_erased (pd)) + { + /* + * Is the page erased ? + */ + ret = rtems_fdisk_seg_blank_check_page (fd, device, segment, + page + sc->pages_desc); + + if (ret) + { +#if RTEMS_FDISK_TRACE + rtems_fdisk_warning (fd, "page not blank: %d-%d-%d", + device, segment, page, pd->block); +#endif + rtems_fdisk_page_desc_set_flags (pd, RTEMS_FDISK_PAGE_USED); + + ret = rtems_fdisk_seg_write_page_desc (fd, device, segment, + page, pd); + + if (ret) + { + rtems_fdisk_error ("forcing page to used failed: %d-%d-%d", + device, segment, page); + sc->failed = true; + } + + sc->pages_used++; + } + } + else + { + if (rtems_fdisk_page_desc_flags_set (pd, RTEMS_FDISK_PAGE_USED)) + { + sc->pages_used++; + } + else if (rtems_fdisk_page_desc_flags_set (pd, RTEMS_FDISK_PAGE_ACTIVE)) + { + if (pd->block >= fd->block_count) + { +#if RTEMS_FDISK_TRACE + rtems_fdisk_warning (fd, + "invalid block number: %d-%d-%d: block: %d", + device, segment, page, pd->block); +#endif + sc->pages_bad++; + } + else if (fd->blocks[pd->block].segment) + { + /** + * @todo + * This may need more work later. Maybe a counter is stored with + * each block so we can tell which is the later block when + * duplicates appear. A power down with a failed wirte could cause + * a duplicate. + */ + const rtems_fdisk_segment_ctl* bsc = fd->blocks[pd->block].segment; + rtems_fdisk_error ("duplicate block: %d-%d-%d: " \ + "duplicate: %d-%d-%d", + bsc->device, bsc->segment, + fd->blocks[pd->block].page, + device, segment, page); + sc->pages_bad++; + } + else + { + /** + * @todo + * Add start up crc checks here. + */ + fd->blocks[pd->block].segment = sc; + fd->blocks[pd->block].page = page; + + /* + * The page is active. + */ + sc->pages_active++; + } + } + else + sc->pages_bad++; + } + } + + /* + * Place the segment on to the correct queue. + */ + rtems_fdisk_queue_segment (fd, sc); + } + } + + return 0; +} + +/** + * Read a block. The block is checked to see if the page referenced + * is valid and the page has a valid crc. + * + * @param fd The rtems_flashdisk control table. + * @param block The block number to read. + * @param buffer The buffer to write the data into. + * @return 0 No error. + * @return EIO Invalid block size, block number, segment pointer, crc, + * page flags. + */ +static bool +rtems_fdisk_read_block (rtems_flashdisk* fd, + uint32_t block, + uint8_t* buffer) +{ + rtems_fdisk_block_ctl* bc; + rtems_fdisk_segment_ctl* sc; + rtems_fdisk_page_desc* pd; + +#if RTEMS_FDISK_TRACE + rtems_fdisk_info (fd, "read-block:%d", block); +#endif + + /* + * Broken out to allow info messages when testing. + */ + + if (block >= (fd->block_count - fd->unavail_blocks)) + { + rtems_fdisk_error ("read-block: block out of range: %d", block); + return EIO; + } + + bc = &fd->blocks[block]; + + if (!bc->segment) + { +#if RTEMS_FDISK_TRACE + rtems_fdisk_info (fd, "read-block: no segment mapping: %d", block); +#endif + memset (buffer, fd->block_size, 0xff); + return 0; + } + + sc = fd->blocks[block].segment; + pd = &sc->page_descriptors[bc->page]; + +#if RTEMS_FDISK_TRACE + rtems_fdisk_info (fd, + " read:%d=>%02d-%03d-%03d: p=%d a=%d u=%d b=%d n=%s: " \ + "f=%04x c=%04x b=%d", + block, sc->device, sc->segment, bc->page, + sc->pages, sc->pages_active, sc->pages_used, sc->pages_bad, + sc->next ? "set" : "null", + pd->flags, pd->crc, pd->block); +#endif + + if (rtems_fdisk_page_desc_flags_set (pd, RTEMS_FDISK_PAGE_ACTIVE)) + { + if (rtems_fdisk_page_desc_flags_clear (pd, RTEMS_FDISK_PAGE_USED)) + { + uint16_t cs; + + /* + * We use the segment page offset not the page number used in the + * driver. This skips the page descriptors. + */ + int ret = rtems_fdisk_seg_read_page (fd, sc->device, sc->segment, + bc->page + sc->pages_desc, buffer); + + if (ret) + { +#if RTEMS_FDISK_TRACE + rtems_fdisk_info (fd, + "read-block:%02d-%03d-%03d: read page failed: %s (%d)", + sc->device, sc->segment, bc->page, + strerror (ret), ret); +#endif + return ret; + } + + cs = rtems_fdisk_page_checksum (buffer, fd->block_size); + + if (cs == pd->crc) + return 0; + + rtems_fdisk_error ("read-block: crc failure: %d: buffer:%04x page:%04x", + block, cs, pd->crc); + } + else + { + rtems_fdisk_error ("read-block: block points to used page: %d: %d-%d-%d", + block, sc->device, sc->segment, bc->page); + } + } + else + { + rtems_fdisk_error ("read-block: block page not active: %d: %d-%d-%d", + block, sc->device, sc->segment, bc->page); + } + + return EIO; +} + +/** + * Write a block. The block: + * + * # May never have existed in flash before this write. + * # Exists and needs to be moved to a new page. + * + * If the block does not exist in flash we need to get the next + * segment available to place the page into. The segments with + * available pages are held on the avaliable list sorted on least + * number of available pages as the primary key. Currently there + * is no secondary key. Empty segments are at the end of the list. + * + * If the block already exists we need to set the USED bit in the + * current page's flags. This is a single byte which changes a 1 to + * a 0 and can be done with a single 16 bit write. The driver for + * 8 bit devices should only attempt the write on the changed bit. + * + * @param fd The rtems_flashdisk control table. + * @param block The block number to read. + * @param block_size The size of the block. Must match what we have. + * @param buffer The buffer to write the data into. + * @return 0 No error. + * @return EIO Invalid block size, block number, segment pointer, crc, + * page flags. + */ +static int +rtems_fdisk_write_block (rtems_flashdisk* fd, + uint32_t block, + const uint8_t* buffer) +{ + rtems_fdisk_block_ctl* bc; + rtems_fdisk_segment_ctl* sc; + rtems_fdisk_page_desc* pd; + uint32_t page; + int ret; + +#if RTEMS_FDISK_TRACE + rtems_fdisk_info (fd, "write-block:%d", block); +#endif + + /* + * Broken out to allow info messages when testing. + */ + + if (block >= (fd->block_count - fd->unavail_blocks)) + { + rtems_fdisk_error ("write-block: block out of range: %d", block); + return EIO; + } + + bc = &fd->blocks[block]; + + /* + * Does the page exist in flash ? + */ + if (bc->segment) + { + sc = bc->segment; + pd = &sc->page_descriptors[bc->page]; + +#if RTEMS_FDISK_TRACE + rtems_fdisk_info (fd, " write:%02d-%03d-%03d: flag used", + sc->device, sc->segment, bc->page); +#endif + + /* + * The page exists in flash so see if the page has been changed. + */ + if (rtems_fdisk_seg_verify_page (fd, sc->device, sc->segment, + bc->page + sc->pages_desc, buffer) == 0) + { +#if RTEMS_FDISK_TRACE + rtems_fdisk_info (fd, "write-block:%d=>%02d-%03d-%03d: page verified", + block, sc->device, sc->segment, bc->page); +#endif + return 0; + } + + /* + * The page exists in flash so we need to set the used flag + * in the page descriptor. The descriptor is in memory with the + * segment control block. We can assume this memory copy + * matches the flash device. + */ + + rtems_fdisk_page_desc_set_flags (pd, RTEMS_FDISK_PAGE_USED); + + ret = rtems_fdisk_seg_write_page_desc_flags (fd, sc->device, sc->segment, + bc->page, pd); + + if (ret) + { +#if RTEMS_FDISK_TRACE + rtems_fdisk_info (fd, " write:%02d-%03d-%03d: " \ + "write used page desc failed: %s (%d)", + sc->device, sc->segment, bc->page, + strerror (ret), ret); +#endif + sc->failed = true; + } + else + { + sc->pages_active--; + sc->pages_used++; + } + + /* + * If possible reuse this segment. This will mean the segment + * needs to be removed from the available list and placed + * back if space is still available. + */ + rtems_fdisk_queue_segment (fd, sc); + + /* + * If no background compacting then compact in the forground. + * If we compact we ignore the error as there is little we + * can do from here. The write may will work. + */ + if ((fd->flags & RTEMS_FDISK_BACKGROUND_COMPACT) == 0) + rtems_fdisk_compact (fd); + } + + /* + * Is it time to compact the disk ? + * + * We override the background compaction configruation. + */ + if (rtems_fdisk_segment_count_queue (&fd->available) <= + fd->avail_compact_segs) + rtems_fdisk_compact (fd); + + /* + * Get the next avaliable segment. + */ + sc = rtems_fdisk_segment_queue_pop_head (&fd->available); + + /* + * Is the flash disk full ? + */ + if (!sc) + { + /* + * If compacting is configured for the background do it now + * to see if we can get some space back. + */ + if ((fd->flags & RTEMS_FDISK_BACKGROUND_COMPACT)) + rtems_fdisk_compact (fd); + + /* + * Try again for some free space. + */ + sc = rtems_fdisk_segment_queue_pop_head (&fd->available); + + if (!sc) + { + rtems_fdisk_error ("write-block: no available pages"); + return ENOSPC; + } + } + +#if RTEMS_FDISK_TRACE + if (fd->info_level >= 3) + { + char queues[5]; + rtems_fdisk_queue_status (fd, sc, queues); + rtems_fdisk_info (fd, " write:%d=>%02d-%03d: queue check: %s", + block, sc->device, sc->segment, queues); + } +#endif + + /* + * Find the next avaliable page in the segment. + */ + + pd = sc->page_descriptors; + + for (page = 0; page < sc->pages; page++, pd++) + { + if (rtems_fdisk_page_desc_erased (pd)) + { + pd->crc = rtems_fdisk_page_checksum (buffer, fd->block_size); + pd->block = block; + + bc->segment = sc; + bc->page = page; + + rtems_fdisk_page_desc_set_flags (pd, RTEMS_FDISK_PAGE_ACTIVE); + +#if RTEMS_FDISK_TRACE + rtems_fdisk_info (fd, " write:%d=>%02d-%03d-%03d: write: " \ + "p=%d a=%d u=%d b=%d n=%s: f=%04x c=%04x b=%d", + block, sc->device, sc->segment, page, + sc->pages, sc->pages_active, sc->pages_used, + sc->pages_bad, sc->next ? "set" : "null", + pd->flags, pd->crc, pd->block); +#endif + + /* + * We use the segment page offset not the page number used in the + * driver. This skips the page descriptors. + */ + ret = rtems_fdisk_seg_write_page (fd, sc->device, sc->segment, + page + sc->pages_desc, buffer); + if (ret) + { +#if RTEMS_FDISK_TRACE + rtems_fdisk_info (fd, "write-block:%02d-%03d-%03d: write page failed: " \ + "%s (%d)", sc->device, sc->segment, page, + strerror (ret), ret); +#endif + sc->failed = true; + } + else + { + ret = rtems_fdisk_seg_write_page_desc (fd, sc->device, sc->segment, + page, pd); + if (ret) + { +#if RTEMS_FDISK_TRACE + rtems_fdisk_info (fd, "write-block:%02d-%03d-%03d: " \ + "write page desc failed: %s (%d)", + sc->device, sc->segment, bc->page, + strerror (ret), ret); +#endif + sc->failed = true; + } + else + { + sc->pages_active++; + } + } + + rtems_fdisk_queue_segment (fd, sc); + return ret; + } + } + + rtems_fdisk_error ("write-block: no erased page descs in segment: %d-%d", + sc->device, sc->segment); + + sc->failed = true; + rtems_fdisk_queue_segment (fd, sc); + + return EIO; +} + +/** + * Disk READ request handler. This primitive copies data from the + * flash disk to the supplied buffer and invoke the callout function + * to inform upper layer that reading is completed. + * + * @param req Pointer to the READ block device request info. + * @retval int The ioctl return value. + */ +static int +rtems_fdisk_read (rtems_flashdisk* fd, rtems_blkdev_request* req) +{ + rtems_blkdev_sg_buffer* sg = req->bufs; + uint32_t buf; + int ret = 0; + + for (buf = 0; (ret == 0) && (buf < req->bufnum); buf++, sg++) + { + uint8_t* data; + uint32_t fb; + uint32_t b; + fb = sg->length / fd->block_size; + data = sg->buffer; + for (b = 0; b < fb; b++, data += fd->block_size) + { + ret = rtems_fdisk_read_block (fd, sg->block + b, data); + if (ret) + break; + } + } + + req->status = ret ? RTEMS_IO_ERROR : RTEMS_SUCCESSFUL; + req->req_done (req->done_arg, req->status); + + return ret; +} + +/** + * Flash disk WRITE request handler. This primitive copies data from + * supplied buffer to flash disk and invoke the callout function to inform + * upper layer that writing is completed. + * + * @param req Pointers to the WRITE block device request info. + * @retval int The ioctl return value. + */ +static int +rtems_fdisk_write (rtems_flashdisk* fd, rtems_blkdev_request* req) +{ + rtems_blkdev_sg_buffer* sg = req->bufs; + uint32_t buf; + int ret = 0; + + for (buf = 0; (ret == 0) && (buf < req->bufnum); buf++, sg++) + { + uint8_t* data; + uint32_t fb; + uint32_t b; + fb = sg->length / fd->block_size; + data = sg->buffer; + for (b = 0; b < fb; b++, data += fd->block_size) + { + ret = rtems_fdisk_write_block (fd, sg->block + b, data); + if (ret) + break; + } + } + + req->status = ret ? RTEMS_IO_ERROR : RTEMS_SUCCESSFUL; + req->req_done (req->done_arg, req->status); + + return 0; +} + +/** + * Flash disk erase disk. + * + * @param fd The flashdisk data. + * @retval int The ioctl return value. + */ +static int +rtems_fdisk_erase_disk (rtems_flashdisk* fd) +{ + uint32_t device; + int ret; + +#if RTEMS_FDISK_TRACE + rtems_fdisk_info (fd, "erase-disk"); +#endif + + ret = rtems_fdisk_erase_flash (fd); + + if (ret == 0) + { + for (device = 0; device < fd->device_count; device++) + { + if (!fd->devices[device].segments) + return ENOMEM; + + ret = rtems_fdisk_recover_block_mappings (fd); + if (ret) + break; + } + } + + return ret; +} + +/** + * Flash Disk Monitoring data is return in the monitoring data + * structure. + */ +static int +rtems_fdisk_monitoring_data (rtems_flashdisk* fd, + rtems_fdisk_monitor_data* data) +{ + uint32_t i; + uint32_t j; + + data->block_size = fd->block_size; + data->block_count = fd->block_count; + data->unavail_blocks = fd->unavail_blocks; + data->device_count = fd->device_count; + + data->blocks_used = 0; + for (i = 0; i < fd->block_count; i++) + if (fd->blocks[i].segment) + data->blocks_used++; + + data->segs_available = rtems_fdisk_segment_count_queue (&fd->available); + data->segs_used = rtems_fdisk_segment_count_queue (&fd->used); + data->segs_failed = rtems_fdisk_segment_count_queue (&fd->failed); + + data->segment_count = 0; + data->page_count = 0; + data->pages_desc = 0; + data->pages_active = 0; + data->pages_used = 0; + data->pages_bad = 0; + data->seg_erases = 0; + + for (i = 0; i < fd->device_count; i++) + { + data->segment_count += fd->devices[i].segment_count; + + for (j = 0; j < fd->devices[i].segment_count; j++) + { + rtems_fdisk_segment_ctl* sc = &fd->devices[i].segments[j]; + + data->page_count += sc->pages; + data->pages_desc += sc->pages_desc; + data->pages_active += sc->pages_active; + data->pages_used += sc->pages_used; + data->pages_bad += sc->pages_bad; + data->seg_erases += sc->erased; + } + } + + data->info_level = fd->info_level; + return 0; +} + +/** + * Print to stdout the status of the driver. This is a debugging aid. + */ +static int +rtems_fdisk_print_status (rtems_flashdisk* fd) +{ +#if RTEMS_FDISK_TRACE + uint32_t current_info_level = fd->info_level; + uint32_t total; + uint32_t count; + uint32_t device; + + fd->info_level = 3; + + rtems_fdisk_printf (fd, + "Flash Disk Driver Status : %d.%d", fd->major, fd->minor); + + rtems_fdisk_printf (fd, "Block count\t%d", fd->block_count); + rtems_fdisk_printf (fd, "Unavail blocks\t%d", fd->unavail_blocks); + count = rtems_fdisk_segment_count_queue (&fd->available); + total = count; + rtems_fdisk_printf (fd, "Available queue\t%ld (%ld)", + count, rtems_fdisk_segment_queue_count (&fd->available)); + count = rtems_fdisk_segment_count_queue (&fd->used); + total += count; + rtems_fdisk_printf (fd, "Used queue\t%ld (%ld)", + count, rtems_fdisk_segment_queue_count (&fd->used)); + count = rtems_fdisk_segment_count_queue (&fd->erase); + total += count; + rtems_fdisk_printf (fd, "Erase queue\t%ld (%ld)", + count, rtems_fdisk_segment_queue_count (&fd->erase)); + count = rtems_fdisk_segment_count_queue (&fd->failed); + total += count; + rtems_fdisk_printf (fd, "Failed queue\t%ld (%ld)", + count, rtems_fdisk_segment_queue_count (&fd->failed)); + + count = 0; + for (device = 0; device < fd->device_count; device++) + count += fd->devices[device].segment_count; + + rtems_fdisk_printf (fd, "Queue total\t%ld of %ld, %s", total, count, + total == count ? "ok" : "MISSING"); + + rtems_fdisk_printf (fd, "Device count\t%d", fd->device_count); + + for (device = 0; device < fd->device_count; device++) + { + uint32_t block; + uint32_t seg; + + rtems_fdisk_printf (fd, " Device\t\t%ld", device); + rtems_fdisk_printf (fd, " Segment count\t%ld", + fd->devices[device].segment_count); + + for (seg = 0; seg < fd->devices[device].segment_count; seg++) + { + rtems_fdisk_segment_ctl* sc = &fd->devices[device].segments[seg]; + uint32_t page; + uint32_t erased = 0; + uint32_t active = 0; + uint32_t used = 0; + bool is_active = false; + char queues[5]; + + rtems_fdisk_queue_status (fd, sc, queues); + + for (page = 0; page < sc->pages; page++) + { + if (rtems_fdisk_page_desc_erased (&sc->page_descriptors[page])) + erased++; + else if (rtems_fdisk_page_desc_flags_set (&sc->page_descriptors[page], + RTEMS_FDISK_PAGE_ACTIVE)) + { + if (rtems_fdisk_page_desc_flags_set (&sc->page_descriptors[page], + RTEMS_FDISK_PAGE_USED)) + used++; + else + { + active++; + is_active = true; + } + } + + for (block = 0; block < fd->block_count; block++) + { + if ((fd->blocks[block].segment == sc) && + (fd->blocks[block].page == page) && !is_active) + rtems_fdisk_printf (fd, + " %ld\t not active when mapped by block %ld", + page, block); + } + } + + count = 0; + for (block = 0; block < fd->block_count; block++) + { + if (fd->blocks[block].segment == sc) + count++; + } + + rtems_fdisk_printf (fd, " %3ld %s p:%3ld a:%3ld/%3ld" \ + " u:%3ld/%3ld e:%3ld/%3ld br:%ld", + seg, queues, + sc->pages, sc->pages_active, active, + sc->pages_used, used, erased, + sc->pages - (sc->pages_active + + sc->pages_used + sc->pages_bad), + count); + } + } + + { + rtems_fdisk_segment_ctl* sc = fd->used.head; + int count = 0; + rtems_fdisk_printf (fd, "Used List:"); + while (sc) + { + rtems_fdisk_printf (fd, " %3d %02d:%03d u:%3ld", + count, sc->device, sc->segment, sc->pages_used); + sc = sc->next; + count++; + } + } + fd->info_level = current_info_level; + + return 0; +#else + return ENOSYS; +#endif +} + +/** + * Flash disk IOCTL handler. + * + * @param dd Disk device. + * @param req IOCTL request code. + * @param argp IOCTL argument. + * @retval The IOCTL return value + */ +static int +rtems_fdisk_ioctl (rtems_disk_device *dd, uint32_t req, void* argp) +{ + dev_t dev = rtems_disk_get_device_identifier (dd); + rtems_device_minor_number minor = rtems_filesystem_dev_minor_t (dev); + rtems_blkdev_request* r = argp; + rtems_status_code sc; + + errno = 0; + + sc = rtems_semaphore_obtain (rtems_flashdisks[minor].lock, RTEMS_WAIT, 0); + if (sc != RTEMS_SUCCESSFUL) + errno = EIO; + else + { + errno = 0; + switch (req) + { + case RTEMS_BLKIO_REQUEST: + if ((minor >= rtems_flashdisk_count) || + (rtems_flashdisks[minor].device_count == 0)) + { + errno = ENODEV; + } + else + { + switch (r->req) + { + case RTEMS_BLKDEV_REQ_READ: + errno = rtems_fdisk_read (&rtems_flashdisks[minor], r); + break; + + case RTEMS_BLKDEV_REQ_WRITE: + errno = rtems_fdisk_write (&rtems_flashdisks[minor], r); + break; + + default: + errno = EINVAL; + break; + } + } + break; + + case RTEMS_FDISK_IOCTL_ERASE_DISK: + errno = rtems_fdisk_erase_disk (&rtems_flashdisks[minor]); + break; + + case RTEMS_FDISK_IOCTL_COMPACT: + errno = rtems_fdisk_compact (&rtems_flashdisks[minor]); + break; + + case RTEMS_FDISK_IOCTL_ERASE_USED: + errno = rtems_fdisk_erase_used (&rtems_flashdisks[minor]); + break; + + case RTEMS_FDISK_IOCTL_MONITORING: + errno = rtems_fdisk_monitoring_data (&rtems_flashdisks[minor], + (rtems_fdisk_monitor_data*) argp); + break; + + case RTEMS_FDISK_IOCTL_INFO_LEVEL: + rtems_flashdisks[minor].info_level = (uintptr_t) argp; + break; + + case RTEMS_FDISK_IOCTL_PRINT_STATUS: + errno = rtems_fdisk_print_status (&rtems_flashdisks[minor]); + break; + + default: + rtems_blkdev_ioctl (dd, req, argp); + break; + } + + sc = rtems_semaphore_release (rtems_flashdisks[minor].lock); + if (sc != RTEMS_SUCCESSFUL) + errno = EIO; + } + + return errno == 0 ? 0 : -1; +} + +/** + * Flash disk device driver initialization. + * + * @todo Memory clean up on error is really badly handled. + * + * @param major Flash disk major device number. + * @param minor Minor device number, not applicable. + * @param arg Initialization argument, not applicable. + */ +rtems_device_driver +rtems_fdisk_initialize (rtems_device_major_number major, + rtems_device_minor_number minor, + void* arg __attribute__((unused))) +{ + const rtems_flashdisk_config* c = rtems_flashdisk_configuration; + rtems_flashdisk* fd; + rtems_status_code sc; + + sc = rtems_disk_io_initialize (); + if (sc != RTEMS_SUCCESSFUL) + return sc; + + sc = rtems_fdisk_crc16_gen_factors (0x8408); + if (sc != RTEMS_SUCCESSFUL) + return sc; + + rtems_flashdisks = calloc (rtems_flashdisk_configuration_size, + sizeof (rtems_flashdisk)); + + if (!rtems_flashdisks) + return RTEMS_NO_MEMORY; + + for (minor = 0; minor < rtems_flashdisk_configuration_size; minor++, c++) + { + char name[] = RTEMS_FLASHDISK_DEVICE_BASE_NAME "a"; + dev_t dev = rtems_filesystem_make_dev_t (major, minor); + uint32_t device; + uint32_t blocks = 0; + int ret; + + fd = &rtems_flashdisks[minor]; + + name [sizeof(RTEMS_FLASHDISK_DEVICE_BASE_NAME)] += minor; + + fd->major = major; + fd->minor = minor; + fd->flags = c->flags; + fd->compact_segs = c->compact_segs; + fd->avail_compact_segs = c->avail_compact_segs; + fd->block_size = c->block_size; + fd->unavail_blocks = c->unavail_blocks; + fd->info_level = c->info_level; + + for (device = 0; device < c->device_count; device++) + blocks += rtems_fdisk_blocks_in_device (&c->devices[device], + c->block_size); + + /* + * One copy buffer of a page size. + */ + fd->copy_buffer = malloc (c->block_size); + if (!fd->copy_buffer) + return RTEMS_NO_MEMORY; + + fd->blocks = calloc (blocks, sizeof (rtems_fdisk_block_ctl)); + if (!fd->blocks) + return RTEMS_NO_MEMORY; + + fd->block_count = blocks; + + fd->devices = calloc (c->device_count, sizeof (rtems_fdisk_device_ctl)); + if (!fd->devices) + return RTEMS_NO_MEMORY; + + sc = rtems_semaphore_create (rtems_build_name ('F', 'D', 'S', 'K'), 1, + RTEMS_PRIORITY | RTEMS_BINARY_SEMAPHORE | + RTEMS_INHERIT_PRIORITY, 0, &fd->lock); + if (sc != RTEMS_SUCCESSFUL) + { + rtems_fdisk_error ("disk lock create failed"); + free (fd->copy_buffer); + free (fd->blocks); + free (fd->devices); + return sc; + } + + sc = rtems_disk_create_phys(dev, c->block_size, + blocks - fd->unavail_blocks, + rtems_fdisk_ioctl, NULL, name); + if (sc != RTEMS_SUCCESSFUL) + { + rtems_semaphore_delete (fd->lock); + rtems_disk_delete (dev); + free (fd->copy_buffer); + free (fd->blocks); + free (fd->devices); + rtems_fdisk_error ("disk create phy failed"); + return sc; + } + + for (device = 0; device < c->device_count; device++) + { + rtems_fdisk_segment_ctl* sc; + uint32_t segment_count; + uint32_t segment; + + segment_count = rtems_fdisk_count_segments (&c->devices[device]); + + fd->devices[device].segments = calloc (segment_count, + sizeof (rtems_fdisk_segment_ctl)); + if (!fd->devices[device].segments) + { + rtems_disk_delete (dev); + rtems_semaphore_delete (fd->lock); + free (fd->copy_buffer); + free (fd->blocks); + free (fd->devices); + return RTEMS_NO_MEMORY; + } + + sc = fd->devices[device].segments; + + for (segment = 0; segment < c->devices[device].segment_count; segment++) + { + const rtems_fdisk_segment_desc* sd; + uint32_t seg_segment; + + sd = &c->devices[device].segments[segment]; + + for (seg_segment = 0; seg_segment < sd->count; seg_segment++, sc++) + { + sc->descriptor = sd; + sc->device = device; + sc->segment = seg_segment; + sc->erased = 0; + } + } + + fd->devices[device].segment_count = segment_count; + fd->devices[device].descriptor = &c->devices[device]; + } + + fd->device_count = c->device_count; + + ret = rtems_fdisk_recover_block_mappings (fd); + if (ret) + { + rtems_disk_delete (dev); + rtems_semaphore_delete (fd->lock); + free (fd->copy_buffer); + free (fd->blocks); + free (fd->devices); + rtems_fdisk_error ("recovery of disk failed: %s (%d)", + strerror (ret), ret); + return ret; + } + + ret = rtems_fdisk_compact (fd); + if (ret) + { + rtems_disk_delete (dev); + rtems_semaphore_delete (fd->lock); + free (fd->copy_buffer); + free (fd->blocks); + free (fd->devices); + rtems_fdisk_error ("compacting of disk failed: %s (%d)", + strerror (ret), ret); + return ret; + } + } + + rtems_flashdisk_count = rtems_flashdisk_configuration_size; + + return RTEMS_SUCCESSFUL; +} |