summaryrefslogtreecommitdiffstats
path: root/cpukit/libblock/src/flashdisk.c
diff options
context:
space:
mode:
Diffstat (limited to 'cpukit/libblock/src/flashdisk.c')
-rw-r--r--cpukit/libblock/src/flashdisk.c2603
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;
+}