diff options
Diffstat (limited to 'cpukit/libblock')
37 files changed, 15167 insertions, 0 deletions
diff --git a/cpukit/libblock/.cvsignore b/cpukit/libblock/.cvsignore new file mode 100644 index 0000000000..282522db03 --- /dev/null +++ b/cpukit/libblock/.cvsignore @@ -0,0 +1,2 @@ +Makefile +Makefile.in diff --git a/cpukit/libblock/Makefile.am b/cpukit/libblock/Makefile.am new file mode 100644 index 0000000000..15c3852da4 --- /dev/null +++ b/cpukit/libblock/Makefile.am @@ -0,0 +1,37 @@ +## +## $Id$ +## + +include $(top_srcdir)/automake/multilib.am +include $(top_srcdir)/automake/compile.am + +noinst_LIBRARIES = libblock.a +libblock_a_SOURCES = src/bdbuf.c \ + src/blkdev.c \ + src/blkdev-ops.c \ + src/diskdevs.c \ + src/flashdisk.c \ + src/ramdisk-driver.c \ + src/ramdisk-init.c \ + src/ramdisk-config.c \ + src/ide_part_table.c \ + src/nvdisk.c \ + src/nvdisk-sram.c \ + src/bdpart-create.c \ + src/bdpart-dump.c \ + src/bdpart-mount.c \ + src/bdpart-read.c \ + src/bdpart-register.c \ + src/bdpart-sort.c \ + src/bdpart-write.c \ + src/media-path.c \ + src/media.c \ + src/media-server.c \ + src/media-desc.c \ + src/media-dev-ident.c \ + include/rtems/bdbuf.h include/rtems/blkdev.h \ + include/rtems/diskdevs.h include/rtems/flashdisk.h \ + include/rtems/ramdisk.h include/rtems/nvdisk.h include/rtems/nvdisk-sram.h \ + include/rtems/ide_part_table.h + +include $(top_srcdir)/automake/local.am diff --git a/cpukit/libblock/README b/cpukit/libblock/README new file mode 100644 index 0000000000..671cc7f645 --- /dev/null +++ b/cpukit/libblock/README @@ -0,0 +1,14 @@ +# +# $Id$ +# + +This directory contains the block device (HDD, CDROMs, etc) support code. +It includes: + - block device driver interface + - generic open/close/read/write/ioctl primitives for block device drivers + - disk I/O buffering + - logical disk support + - RAM disk block device driver + +Victor V. Vengerov, <vvv@oktet.ru> +November, 7 2001 diff --git a/cpukit/libblock/include/rtems/bdbuf.h b/cpukit/libblock/include/rtems/bdbuf.h new file mode 100644 index 0000000000..66a9bbf59b --- /dev/null +++ b/cpukit/libblock/include/rtems/bdbuf.h @@ -0,0 +1,594 @@ +/** + * @file + * + * @ingroup rtems_bdbuf + * + * Block device buffer management. + */ + +/* + * Copyright (C) 2001 OKTET Ltd., St.-Petersburg, Russia + * Author: Victor V. Vengerov <vvv@oktet.ru> + * + * Copyright (C) 2008,2009 Chris Johns <chrisj@rtems.org> + * Rewritten to remove score mutex access. Fixes many performance + * issues. + * Change to support demand driven variable buffer sizes. + * + * Copyright (c) 2009 embedded brains GmbH. + * + * @(#) bdbuf.h,v 1.9 2005/02/02 00:06:18 joel Exp + */ + +#ifndef _RTEMS_BDBUF_H +#define _RTEMS_BDBUF_H + +#include <rtems.h> +#include <rtems/libio.h> +#include <rtems/chain.h> + +#include <rtems/blkdev.h> +#include <rtems/diskdevs.h> + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @defgroup rtems_libblock Block Device Library + * + * Block device modules. + */ + +/** + * @defgroup rtems_bdbuf Block Device Buffer Management + * + * @ingroup rtems_libblock + * + * The Block Device Buffer Management implements a cache between the disk + * devices and file systems. The code provides read ahead and write queuing to + * the drivers and fast cache look-up using an AVL tree. + * + * The block size used by a file system can be set at runtime and must be a + * multiple of the disk device block size. The disk device's physical block + * size is called the media block size. The file system can set the block size + * it uses to a larger multiple of the media block size. The driver must be + * able to handle buffers sizes larger than one media block. + * + * The user configures the amount of memory to be used as buffers in the cache, + * and the minimum and maximum buffer size. The cache will allocate additional + * memory for the buffer descriptors and groups. There are enough buffer + * descriptors allocated so all the buffer memory can be used as minimum sized + * buffers. + * + * The cache is a single pool of buffers. The buffer memory is divided into + * groups where the size of buffer memory allocated to a group is the maximum + * buffer size. A group's memory can be divided down into small buffer sizes + * that are a multiple of 2 of the minimum buffer size. A group is the minimum + * allocation unit for buffers of a specific size. If a buffer of maximum size + * is request the group will have a single buffer. If a buffer of minimum size + * is requested the group is divided into minimum sized buffers and the + * remaining buffers are held ready for use. A group keeps track of which + * buffers are with a file system or driver and groups who have buffer in use + * cannot be realloced. Groups with no buffers in use can be taken and + * realloced to a new size. This is how buffers of different sizes move around + * the cache. + + * The buffers are held in various lists in the cache. All buffers follow this + * state machine: + * + * @dot + * digraph state { + * size="16,8"; + * f [label="FREE",style="filled",fillcolor="aquamarine"]; + * e [label="EMPTY",style="filled",fillcolor="seagreen"]; + * c [label="CACHED",style="filled",fillcolor="chartreuse"]; + * ac [label="ACCESS CACHED",style="filled",fillcolor="royalblue"]; + * am [label="ACCESS MODIFIED",style="filled",fillcolor="royalblue"]; + * ae [label="ACCESS EMPTY",style="filled",fillcolor="royalblue"]; + * ap [label="ACCESS PURGED",style="filled",fillcolor="royalblue"]; + * t [label="TRANSFER",style="filled",fillcolor="red"]; + * tp [label="TRANSFER PURGED",style="filled",fillcolor="red"]; + * s [label="SYNC",style="filled",fillcolor="red"]; + * m [label="MODIFIED",style="filled",fillcolor="gold"]; + * i [label="INITIAL"]; + * + * legend_transfer [label="Transfer Wake-Up",fontcolor="red",shape="none"]; + * legend_access [label="Access Wake-Up",fontcolor="royalblue",shape="none"]; + * + * i -> f [label="Init"]; + * f -> e [label="Buffer Recycle"]; + * e -> ae [label="Get"]; + * e -> t [label="Read"]; + * e -> f [label="Nobody Waits"]; + * c -> ac [label="Get\nRead"]; + * c -> e [label="Buffer Recycle\nPurge"]; + * c -> f [label="Reallocate\nBlock Size Changed"]; + * t -> c [label="Transfer Done",color="red",fontcolor="red"]; + * t -> e [label="Transfer Error",color="red",fontcolor="red"]; + * t -> tp [label="Purge"]; + * tp -> e [label="Transfer Done\nTransfer Error",color="red",fontcolor="red"]; + * m -> t [label="Swapout"]; + * m -> s [label="Block Size Changed"]; + * m -> am [label="Get\nRead"]; + * m -> e [label="Purge"]; + * ac -> m [label="Release Modified",color="royalblue",fontcolor="royalblue"]; + * ac -> s [label="Sync",color="royalblue",fontcolor="royalblue"]; + * ac -> c [label="Release",color="royalblue",fontcolor="royalblue"]; + * ac -> ap [label="Purge"]; + * am -> m [label="Release\nRelease Modified",color="royalblue",fontcolor="royalblue"]; + * am -> s [label="Sync",color="royalblue",fontcolor="royalblue"]; + * am -> ap [label="Purge"]; + * ae -> m [label="Release Modified",color="royalblue",fontcolor="royalblue"]; + * ae -> s [label="Sync",color="royalblue",fontcolor="royalblue"]; + * ae -> e [label="Release",color="royalblue",fontcolor="royalblue"]; + * ae -> ap [label="Purge"]; + * ap -> e [label="Release\nRelease Modified\nSync",color="royalblue",fontcolor="royalblue"]; + * s -> t [label="Swapout"]; + * s -> e [label="Purge",color="red",fontcolor="red"]; + * } + * @enddot + * + * Empty or cached buffers are added to the LRU list and removed from this + * queue when a caller requests a buffer. This is referred to as getting a + * buffer in the code and the event get in the state diagram. The buffer is + * assigned to a block and inserted to the AVL based on the block/device key. + * If the block is to be read by the user and not in the cache it is transfered + * from the disk into memory. If no buffers are on the LRU list the modified + * list is checked. If buffers are on the modified the swap out task will be + * woken. The request blocks until a buffer is available for recycle. + * + * A block being accessed is given to the file system layer and not accessible + * to another requester until released back to the cache. The same goes to a + * buffer in the transfer state. The transfer state means being read or + * written. If the file system has modifed the block and releases it as + * modified it placed on the cache's modified list and a hold timer + * initialised. The buffer is held for the hold time before being written to + * disk. Buffers are held for a configurable period of time on the modified + * list as a write sets the state to transfer and this locks the buffer out + * from the file system until the write completes. Buffers are often accessed + * and modified in a series of small updates so if sent to the disk when + * released as modified the user would have to block waiting until it had been + * written. This would be a performance problem. + * + * The code performs multiple block reads and writes. Multiple block reads or + * read ahead increases performance with hardware that supports it. It also + * helps with a large cache as the disk head movement is reduced. It however + * is a speculative operation so excessive use can remove valuable and needed + * blocks from the cache. + * + * The cache has the following lists of buffers: + * - LRU: Accessed or transfered buffers released in least recently used + * order. Empty buffers will be placed to the front. + * - Modified: Buffers waiting to be written to disk. + * - Sync: Buffers to be synchronized with the disk. + * + * A cache look-up will be performed to find a suitable buffer. A suitable + * buffer is one that matches the same allocation size as the device the buffer + * is for. The a buffer's group has no buffers in use with the file system or + * driver the group is reallocated. This means the buffers in the group are + * invalidated, resized and placed on the LRU queue. There is a performance + * issue with this design. The reallocation of a group may forced recently + * accessed buffers out of the cache when they should not. The design should be + * change to have groups on a LRU list if they have no buffers in use. + * + * @{ + */ + +/** + * @brief State of a buffer of the cache. + * + * The state has several implications. Depending on the state a buffer can be + * in the AVL tree, in a list, in use by an entity and a group user or not. + * + * <table> + * <tr> + * <th>State</th><th>Valid Data</th><th>AVL Tree</th> + * <th>LRU List</th><th>Modified List</th><th>Synchronization List</th> + * <th>Group User</th><th>External User</th> + * </tr> + * <tr> + * <td>FREE</td><td></td><td></td> + * <td>X</td><td></td><td></td><td></td><td></td> + * </tr> + * <tr> + * <td>EMPTY</td><td></td><td>X</td> + * <td></td><td></td><td></td><td></td><td></td> + * </tr> + * <tr> + * <td>CACHED</td><td>X</td><td>X</td> + * <td>X</td><td></td><td></td><td></td><td></td> + * </tr> + * <tr> + * <td>ACCESS CACHED</td><td>X</td><td>X</td> + * <td></td><td></td><td></td><td>X</td><td>X</td> + * </tr> + * <tr> + * <td>ACCESS MODIFIED</td><td>X</td><td>X</td> + * <td></td><td></td><td></td><td>X</td><td>X</td> + * </tr> + * <tr> + * <td>ACCESS EMPTY</td><td></td><td>X</td> + * <td></td><td></td><td></td><td>X</td><td>X</td> + * </tr> + * <tr> + * <td>ACCESS PURGED</td><td></td><td>X</td> + * <td></td><td></td><td></td><td>X</td><td>X</td> + * </tr> + * <tr> + * <td>MODIFIED</td><td>X</td><td>X</td> + * <td></td><td>X</td><td></td><td>X</td><td></td> + * </tr> + * <tr> + * <td>SYNC</td><td>X</td><td>X</td> + * <td></td><td></td><td>X</td><td>X</td><td></td> + * </tr> + * <tr> + * <td>TRANSFER</td><td>X</td><td>X</td> + * <td></td><td></td><td></td><td>X</td><td>X</td> + * </tr> + * <tr> + * <td>TRANSFER PURGED</td><td></td><td>X</td> + * <td></td><td></td><td></td><td>X</td><td>X</td> + * </tr> + * </table> + */ +typedef enum +{ + /** + * @brief Free. + */ + RTEMS_BDBUF_STATE_FREE = 0, + + /** + * @brief Empty. + */ + RTEMS_BDBUF_STATE_EMPTY, + + /** + * @brief Cached. + */ + RTEMS_BDBUF_STATE_CACHED, + + /** + * @brief Accessed by upper layer with cached data. + */ + RTEMS_BDBUF_STATE_ACCESS_CACHED, + + /** + * @brief Accessed by upper layer with modified data. + */ + RTEMS_BDBUF_STATE_ACCESS_MODIFIED, + + /** + * @brief Accessed by upper layer with invalid data. + */ + RTEMS_BDBUF_STATE_ACCESS_EMPTY, + + /** + * @brief Accessed by upper layer with purged data. + */ + RTEMS_BDBUF_STATE_ACCESS_PURGED, + + /** + * @brief Modified by upper layer. + */ + RTEMS_BDBUF_STATE_MODIFIED, + + /** + * @brief Scheduled for synchronization. + */ + RTEMS_BDBUF_STATE_SYNC, + + /** + * @brief In transfer by block device driver. + */ + RTEMS_BDBUF_STATE_TRANSFER, + + /** + * @brief In transfer by block device driver and purged. + */ + RTEMS_BDBUF_STATE_TRANSFER_PURGED +} rtems_bdbuf_buf_state; + +/** + * Forward reference to the block. + */ +struct rtems_bdbuf_group; +typedef struct rtems_bdbuf_group rtems_bdbuf_group; + +/** + * To manage buffers we using buffer descriptors (BD). A BD holds a buffer plus + * a range of other information related to managing the buffer in the cache. To + * speed-up buffer lookup descriptors are organized in AVL-Tree. The fields + * 'dev' and 'block' are search keys. + */ +typedef struct rtems_bdbuf_buffer +{ + rtems_chain_node link; /**< Link the BD onto a number of lists. */ + + struct rtems_bdbuf_avl_node + { + struct rtems_bdbuf_buffer* left; /**< Left Child */ + struct rtems_bdbuf_buffer* right; /**< Right Child */ + signed char cache; /**< Cache */ + signed char bal; /**< The balance of the sub-tree */ + } avl; + + dev_t dev; /**< device number */ + + rtems_blkdev_bnum block; /**< block number on the device */ + + unsigned char* buffer; /**< Pointer to the buffer memory area */ + + volatile rtems_bdbuf_buf_state state; /**< State of the buffer. */ + + volatile uint32_t waiters; /**< The number of threads waiting on this + * buffer. */ + rtems_bdbuf_group* group; /**< Pointer to the group of BDs this BD is + * part of. */ + volatile uint32_t hold_timer; /**< Timer to indicate how long a buffer + * has been held in the cache modified. */ + + int references; /**< Allow reference counting by owner. */ + void* user; /**< User data. */ +} rtems_bdbuf_buffer; + +/** + * A group is a continuous block of buffer descriptors. A group covers the + * maximum configured buffer size and is the allocation size for the buffers to + * a specific buffer size. If you allocate a buffer to be a specific size, all + * buffers in the group, if there are more than 1 will also be that size. The + * number of buffers in a group is a multiple of 2, ie 1, 2, 4, 8, etc. + */ +struct rtems_bdbuf_group +{ + rtems_chain_node link; /**< Link the groups on a LRU list if they + * have no buffers in use. */ + size_t bds_per_group; /**< The number of BD allocated to this + * group. This value must be a multiple of + * 2. */ + uint32_t users; /**< How many users the block has. */ + rtems_bdbuf_buffer* bdbuf; /**< First BD this block covers. */ +}; + +/** + * Buffering configuration definition. See confdefs.h for support on using this + * structure. + */ +typedef struct rtems_bdbuf_config { + uint32_t max_read_ahead_blocks; /**< Number of blocks to read + * ahead. */ + uint32_t max_write_blocks; /**< Number of blocks to write + * at once. */ + rtems_task_priority swapout_priority; /**< Priority of the swap out + * task. */ + uint32_t swapout_period; /**< Period swapout checks buf + * timers. */ + uint32_t swap_block_hold; /**< Period a buffer is held. */ + size_t swapout_workers; /**< The number of worker + * threads for the swapout + * task. */ + rtems_task_priority swapout_worker_priority; /**< Priority of the swap out + * task. */ + size_t size; /**< Size of memory in the + * cache */ + uint32_t buffer_min; /**< Minimum buffer size. */ + uint32_t buffer_max; /**< Maximum buffer size + * supported. It is also the + * allocation size. */ +} rtems_bdbuf_config; + +/** + * External reference to the configuration. + * + * The configuration is provided by the application. + */ +extern const rtems_bdbuf_config rtems_bdbuf_configuration; + +/** + * The max_read_ahead_blocks value is altered if there are fewer buffers + * than this defined max. This stops thrashing in the cache. + */ +#define RTEMS_BDBUF_MAX_READ_AHEAD_BLOCKS_DEFAULT 0 + +/** + * Default maximum number of blocks to write at once. + */ +#define RTEMS_BDBUF_MAX_WRITE_BLOCKS_DEFAULT 16 + +/** + * Default swap-out task priority. + */ +#define RTEMS_BDBUF_SWAPOUT_TASK_PRIORITY_DEFAULT 15 + +/** + * Default swap-out task swap period in milli seconds. + */ +#define RTEMS_BDBUF_SWAPOUT_TASK_SWAP_PERIOD_DEFAULT 250 + +/** + * Default swap-out task block hold time in milli seconds. + */ +#define RTEMS_BDBUF_SWAPOUT_TASK_BLOCK_HOLD_DEFAULT 1000 + +/** + * Default swap-out worker tasks. Currently disabled. + */ +#define RTEMS_BDBUF_SWAPOUT_WORKER_TASKS_DEFAULT 0 + +/** + * Default swap-out worker task priority. The same as the swapout task. + */ +#define RTEMS_BDBUF_SWAPOUT_WORKER_TASK_PRIORITY_DEFAULT \ + RTEMS_BDBUF_SWAPOUT_TASK_PRIORITY_DEFAULT + +/** + * Default size of memory allocated to the cache. + */ +#define RTEMS_BDBUF_CACHE_MEMORY_SIZE_DEFAULT (64 * 512) + +/** + * Default minimum size of buffers. + */ +#define RTEMS_BDBUF_BUFFER_MIN_SIZE_DEFAULT (512) + +/** + * Default maximum size of buffers. + */ +#define RTEMS_BDBUF_BUFFER_MAX_SIZE_DEFAULT (4096) + +/** + * Prepare buffering layer to work - initialize buffer descritors and (if it is + * neccessary) buffers. After initialization all blocks is placed into the + * ready state. + * + * @return RTEMS status code (RTEMS_SUCCESSFUL if operation completed + * successfully or error code if error is occured) + */ +rtems_status_code +rtems_bdbuf_init (void); + +/** + * Get block buffer for data to be written into. The buffers is set to the + * access or modifed access state. If the buffer is in the cache and modified + * the state is access modified else the state is access. This buffer contents + * are not initialised if the buffer is not already in the cache. If the block + * is already resident in memory it is returned how-ever if not in memory the + * buffer is not read from disk. This call is used when writing the whole block + * on a disk rather than just changing a part of it. If there is no buffers + * available this call will block. A buffer obtained with this call will not be + * involved in a transfer request and will not be returned to another user + * until released. If the buffer is already with a user when this call is made + * the call is blocked until the buffer is returned. The highest priority + * waiter will obtain the buffer first. + * + * The block number is the linear block number. This is relative to the start + * of the partition on the media. + * + * @param device Device number (constructed of major and minor device number) + * @param block Linear media block number + * @param bd Reference to the buffer descriptor pointer. + * + * @return RTEMS status code (RTEMS_SUCCESSFUL if operation completed + * successfully or error code if error is occured) + */ +rtems_status_code +rtems_bdbuf_get (dev_t device, rtems_blkdev_bnum block, rtems_bdbuf_buffer** bd); + +/** + * Get the block buffer and if not already in the cache read from the disk. If + * specified block already cached return. The buffer is set to the access or + * modifed access state. If the buffer is in the cache and modified the state + * is access modified else the state is access. If block is already being read + * from disk for being written to disk this call blocks. If the buffer is + * waiting to be written it is removed from modified queue and returned to the + * user. If the buffer is not in the cache a new buffer is obtained and the + * data read from disk. The call may block until these operations complete. A + * buffer obtained with this call will not be involved in a transfer request + * and will not be returned to another user until released. If the buffer is + * already with a user when this call is made the call is blocked until the + * buffer is returned. The highest priority waiter will obtain the buffer + * first. + * + * @param device Device number (constructed of major and minor device number) + * @param block Linear media block number + * @param bd Reference to the buffer descriptor pointer. + * + * @return RTEMS status code (RTEMS_SUCCESSFUL if operation completed + * successfully or error code if error is occured) + */ +rtems_status_code +rtems_bdbuf_read (dev_t device, rtems_blkdev_bnum block, rtems_bdbuf_buffer** bd); + +/** + * Release the buffer obtained by a read call back to the cache. If the buffer + * was obtained by a get call and was not already in the cache the release + * modified call should be used. A buffer released with this call obtained by a + * get call may not be in sync with the contents on disk. If the buffer was in + * the cache and modified before this call it will be returned to the modified + * queue. The buffers is returned to the end of the LRU list. + * + * @param bd Reference to the buffer descriptor. + * + * @return RTEMS status code (RTEMS_SUCCESSFUL if operation completed + * successfully or error code if error is occured) + */ +rtems_status_code +rtems_bdbuf_release (rtems_bdbuf_buffer* bd); + +/** + * Release the buffer allocated with a get or read call placing it on the + * modidied list. If the buffer was not released modified before the hold + * timer is set to the configuration value. If the buffer had been released + * modified before but not written to disk the hold timer is not updated. The + * buffer will be written to disk when the hold timer has expired, there are + * not more buffers available in the cache and a get or read buffer needs one + * or a sync call has been made. If the buffer is obtained with a get or read + * before the hold timer has expired the buffer will be returned to the user. + * + * @param bd Reference to the buffer descriptor. + * + * @return RTEMS status code (RTEMS_SUCCESSFUL if operation completed + * successfully or error code if error is occured) + */ +rtems_status_code +rtems_bdbuf_release_modified (rtems_bdbuf_buffer* bd); + +/** + * Release the buffer as modified and wait until it has been synchronized with + * the disk by writing it. This buffer will be the first to be transfer to disk + * and other buffers may also be written if the maximum number of blocks in a + * requests allows it. + * + * @note This code does not lock the sync mutex and stop additions to the + * modified queue. + + * @param bd Reference to the buffer descriptor. + * + * @return RTEMS status code (RTEMS_SUCCESSFUL if operation completed + * successfully or error code if error is occured) + */ +rtems_status_code +rtems_bdbuf_sync (rtems_bdbuf_buffer* bd); + +/** + * Synchronize all modified buffers for this device with the disk and wait + * until the transfers have completed. The sync mutex for the cache is locked + * stopping the addition of any further modifed buffers. It is only the + * currently modified buffers that are written. + * + * @note Nesting calls to sync multiple devices will be handled sequentially. A + * nested call will be blocked until the first sync request has complete. + * + * @param dev Block device number + * + * @return RTEMS status code (RTEMS_SUCCESSFUL if operation completed + * successfully or error code if error is occured) + */ +rtems_status_code +rtems_bdbuf_syncdev (dev_t dev); + +/** + * @brief Purges all buffers that matches the device identifier @a dev. + * + * This may result in loss of data. + */ +void +rtems_bdbuf_purge_dev (dev_t dev); + +/** + * @brief Purges all buffers that matches the device major number @a major. + * + * This may result in loss of data. + */ +void +rtems_bdbuf_purge_major (rtems_device_major_number major); + +/** @} */ + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/cpukit/libblock/include/rtems/bdpart.h b/cpukit/libblock/include/rtems/bdpart.h new file mode 100644 index 0000000000..907866eb43 --- /dev/null +++ b/cpukit/libblock/include/rtems/bdpart.h @@ -0,0 +1,409 @@ +/** + * @file + * + * @ingroup rtems_bdpart + * + * Block device partition management. + */ + +/* + * Copyright (c) 2009, 2010 + * embedded brains GmbH + * Obere Lagerstr. 30 + * D-82178 Puchheim + * Germany + * <rtems@embedded-brains.de> + * + * 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. + */ + +#ifndef RTEMS_BDPART_H +#define RTEMS_BDPART_H + +#include <uuid/uuid.h> + +#include <rtems.h> +#include <rtems/blkdev.h> + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + +/** + * @defgroup rtems_bdpart Block Device Partition Management + * + * @ingroup rtems_libblock + * + * This module provides functions to manage partitions of a disk device. + * + * A @ref rtems_disk "disk" is a set of blocks which are identified by a + * consecutive set of non-negative integers starting at zero. There are also + * logical disks which contain a subset of consecutive disk blocks. The + * logical disks are used to represent the partitions of a disk. The disk + * devices are accessed via the @ref rtems_disk "block device buffer module". + * + * The partition format on the physical disk will be converted to an internal + * representation. It is possible to convert the internal representation into + * a specific output format and write it to the physical disk. One of the + * constrains for the internal representation was to support the GPT format + * easily. + * + * Currently two physical partition formats are supported. These are the MBR + * and the GPT format. Please note that the GPT support is not implemented. + * With MBR format we mean the partition format of the wide spread IBM + * PC-compatible systems. The GPT format is defined in the Extensible Firmware + * Interface (EFI). + * + * The most common task will be to read the partition information of a disk and + * register logical disks for each partition. This can be done with the + * rtems_bdpart_register_from_disk() function. Afterwards you can + * @ref rtems_fsmount "mount" the file systems within the partitions. + * + * You can read the partition information from a disk with rtems_bdpart_read() + * and write it to the disk with rtems_bdpart_write(). + * + * To create a partition table from scratch for a disk use + * rtems_bdpart_create(). + * + * You can access some disk functions with the shell command @c fdisk. + * + * References used to create this module: + * - <a href="http://en.wikipedia.org/wiki/UUID">Universally Unique Identifier</a> + * - <a href="http://en.wikipedia.org/wiki/Globally_Unique_Identifier">Globally Unique Identifier</a> + * - <a href="http://en.wikipedia.org/wiki/Disk_partitioning">Disk Paritioning</a> + * - <a href="http://en.wikipedia.org/wiki/GUID_Partition_Table">GUID Partition Table</a> + * - <a href="http://en.wikipedia.org/wiki/Master_boot_record">Master Boot Record</a> + * - <a href="http://en.wikipedia.org/wiki/Extended_boot_record">Extended Boot Record</a> + * - <a href="http://en.wikipedia.org/wiki/Cylinder-head-sector">Cylinder Head Sector</a> + * - <a href="http://www.win.tue.nl/~aeb/partitions/partition_types-1.html">Partition Types</a> + * + * @{ + */ + +/** + * @name MBR Partition Types and Flags + * + * @{ + */ + +#define RTEMS_BDPART_MBR_EMPTY 0x0U + +#define RTEMS_BDPART_MBR_FAT_12 0x1U + +#define RTEMS_BDPART_MBR_FAT_16 0x4U + +#define RTEMS_BDPART_MBR_FAT_16_LBA 0xeU + +#define RTEMS_BDPART_MBR_FAT_32 0xbU + +#define RTEMS_BDPART_MBR_FAT_32_LBA 0xcU + +#define RTEMS_BDPART_MBR_EXTENDED 0x5U + +#define RTEMS_BDPART_MBR_DATA 0xdaU + +#define RTEMS_BDPART_MBR_GPT 0xeeU + +#define RTEMS_BDPART_MBR_FLAG_ACTIVE 0x80U + +/** @} */ + +/** + * Recommended maximum partition table size. + */ +#define RTEMS_BDPART_PARTITION_NUMBER_HINT 16 + +/** + * Partition description. + */ +typedef struct rtems_bdpart_partition { + /** + * Block index for partition begin. + */ + rtems_blkdev_bnum begin; + + /** + * Block index for partition end (this block is not a part of the partition). + */ + rtems_blkdev_bnum end; + + /** + * Partition type. + */ + uuid_t type; + + /** + * Partition ID. + */ + uuid_t id; + + /** + * Partition flags. + */ + uint64_t flags; +} rtems_bdpart_partition; + +/** + * Disk format for the partition tables. + */ +typedef enum { + /** + * Type value for MBR format. + */ + RTEMS_BDPART_FORMAT_MBR, + + /** + * Type value for GPT format. + */ + RTEMS_BDPART_FORMAT_GPT +} rtems_bdpart_format_type; + +/** + * Disk format description. + */ +typedef union { + /** + * Format type. + */ + rtems_bdpart_format_type type; + + /** + * MBR format fields. + */ + struct { + rtems_bdpart_format_type type; + + /** + * Disk ID in MBR at offset 440. + */ + uint32_t disk_id; + + /** + * This option is used for partition table creation and validation checks + * before a write to the disk. It ensures that the first primary + * partition and the logical partitions start at head one and sector one + * under the virtual one head and 63 sectors geometry. Each begin and + * end of a partition will be aligned to the virtual cylinder boundary. + */ + bool dos_compatibility; + } mbr; + + /** + * GPT format fields. + */ + struct { + rtems_bdpart_format_type type; + + /** + * Disk ID in GPT header. + */ + uuid_t disk_id; + } gpt; +} rtems_bdpart_format; + +/** + * Reads the partition information from the physical disk device with name + * @a disk_name. + * + * The partition information will be stored in the partition table + * @a partitions with a maximum of @a count partitions. The number of actual + * partitions will be stored in @a count. If there are more partitions than + * space for storage an error status will be returned. The partition table + * format recognized on the disk will be stored in @a format. + */ +rtems_status_code rtems_bdpart_read( + const char *disk_name, + rtems_bdpart_format *format, + rtems_bdpart_partition *partitions, + size_t *count +); + +/** + * Sorts the partition table @a partitions with @a count partitions to have + * ascending begin blocks + */ +void rtems_bdpart_sort( rtems_bdpart_partition *partitions, size_t count); + +/** + * Writes the partition table to the physical disk device with name + * @a disk_name. + * + * The partition table @a partitions with @a count partitions will be written + * to the disk. The output format for the partition table on the disk is + * specified by @a format. There are some consistency checks applied to the + * partition table. The partition table must be sorted such that the begin + * blocks are in ascending order. This can be done with the + * rtems_bdpart_sort() function. The partitions must not overlap. The + * partitions must have a positive size. The partitions must be within the + * disk. Depending on the output format there are additional constrains. + */ +rtems_status_code rtems_bdpart_write( + const char *disk_name, + const rtems_bdpart_format *format, + const rtems_bdpart_partition *partitions, + size_t count +); + +/** + * Creates a partition table in @a partitions with @a count partitions for the + * physical disk device with name @a disk_name. + * + * The array of positive integer weights in @a distribution must have exactly + * @a count elements. The weights in the distribution array are summed up. + * Each weight is then divided by the sum to obtain the disk fraction which + * forms the corresponding partition. The partition boundaries are generated + * with respect to the output format in @a format. + */ +rtems_status_code rtems_bdpart_create( + const char *disk_name, + const rtems_bdpart_format *format, + rtems_bdpart_partition *partitions, + const unsigned *distribution, + size_t count +); + +/** + * Registers the partitions as logical disks for the physical disk device with + * name @a disk_name. + * + * For each partition of the partition table @a partitions with @a count + * partitions a logical disk is registered. The partition number equals the + * partition table index plus one. The name of the logical disk device is the + * concatenation of the physical disk device name and the partition number. + */ +rtems_status_code rtems_bdpart_register( + const char *disk_name, + const rtems_bdpart_partition *partitions, + size_t count +); + +/** + * Reads the partition table from the disk device with name @a disk_name and + * registers the partitions as logical disks. + * + * @see rtems_bdpart_register() and rtems_fsmount(). + */ +rtems_status_code rtems_bdpart_register_from_disk( const char *disk_name); + +/** + * Deletes the logical disks associated with the partitions of the disk device + * with name @a disk_name. + * + * The partition table @a partitions with @a count partitions will be used to + * determine which disks need to be deleted. It may be obtained from + * rtems_bdpart_read(). + */ +rtems_status_code rtems_bdpart_unregister( + const char *disk_name, + const rtems_bdpart_partition *partitions, + size_t count +); + +/** + * Mounts all supported file systems inside the logical disks derived from the + * partitions of the physical disk device with name @a disk_name. + * + * For each partition in the partition table @a partitions with @a count + * partitions it will be checked if it contains a supported file system. In + * this case a mount point derived from the disk name will be created in the + * mount base path @a mount_base. The file system will be mounted there. The + * partition number equals the partition table index plus one. The mount point + * name for each partition will be the concatenation of the mount base path, + * the disk device file name and the parition number. + * + * @see rtems_bdpart_read(). + */ +rtems_status_code rtems_bdpart_mount( + const char *disk_name, + const rtems_bdpart_partition *partitions, + size_t count, + const char *mount_base +); + +/** + * Unmounts all file systems mounted with rtems_bdpart_mount(). + */ +rtems_status_code rtems_bdpart_unmount( + const char *disk_name, + const rtems_bdpart_partition *partitions, + size_t count, + const char *mount_base +); + +/** + * Prints the partition table @a partitions with @a count partitions to + * standard output. + */ +void rtems_bdpart_dump( const rtems_bdpart_partition *partitions, size_t count); + +/** + * Returns the partition type for the MBR partition type value @a mbr_type in + * @a type. + */ +void rtems_bdpart_to_partition_type( uint8_t mbr_type, uuid_t type); + +/** + * Converts the partition type in @a type to the MBR partition type. + * + * The result will be stored in @a mbr_type. Returns @c true in case of a + * successful convertion and otherwise @c false. Both arguments must not be + * @c NULL. + */ +bool rtems_bdpart_to_mbr_partition_type( + const uuid_t type, + uint8_t *mbr_type +); + +/** @} */ + +#define RTEMS_BDPART_MBR_CYLINDER_SIZE 63 + +#define RTEMS_BDPART_NUMBER_SIZE 4 + +#define RTEMS_BDPART_BLOCK_SIZE 512 + +#define RTEMS_BDPART_MBR_TABLE_ENTRY_SIZE 16 + +#define RTEMS_BDPART_MBR_OFFSET_TABLE_0 446 + +#define RTEMS_BDPART_MBR_OFFSET_TABLE_1 \ + (RTEMS_BDPART_MBR_OFFSET_TABLE_0 + RTEMS_BDPART_MBR_TABLE_ENTRY_SIZE) + +#define RTEMS_BDPART_MBR_OFFSET_DISK_ID 440 + +#define RTEMS_BDPART_MBR_OFFSET_SIGNATURE_0 510 + +#define RTEMS_BDPART_MBR_OFFSET_SIGNATURE_1 511 + +#define RTEMS_BDPART_MBR_SIGNATURE_0 0x55U + +#define RTEMS_BDPART_MBR_SIGNATURE_1 0xaaU + +#define RTEMS_BDPART_MBR_OFFSET_BEGIN 8 + +#define RTEMS_BDPART_MBR_OFFSET_SIZE 12 + +#define RTEMS_BDPART_MBR_OFFSET_TYPE 4 + +#define RTEMS_BDPART_MBR_OFFSET_FLAGS 0 + +static inline uint8_t rtems_bdpart_mbr_partition_type( + const uuid_t type +) +{ + return type [0]; +} + +rtems_status_code rtems_bdpart_get_disk_data( + const char *disk_name, + dev_t *disk, + rtems_blkdev_bnum *disk_end +); + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + +#endif /* RTEMS_BDPART_H */ diff --git a/cpukit/libblock/include/rtems/blkdev.h b/cpukit/libblock/include/rtems/blkdev.h new file mode 100644 index 0000000000..e9fa86b248 --- /dev/null +++ b/cpukit/libblock/include/rtems/blkdev.h @@ -0,0 +1,276 @@ +/** + * @file + * + * @ingroup rtems_blkdev + * + * Block device management. + */ + +/* + * Copyright (C) 2001 OKTET Ltd., St.-Petersburg, Russia + * Author: Victor V. Vengerov <vvv@oktet.ru> + * + * @(#) $Id$ + */ + +#ifndef _RTEMS_BLKDEV_H +#define _RTEMS_BLKDEV_H + +#include <rtems.h> +#include <rtems/diskdevs.h> +#include <sys/ioctl.h> + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @defgroup rtems_blkdev Block Device Management + * + * @ingroup rtems_libblock + * + * Interface between device drivers and the + * @ref rtems_bdbuf "block device buffer module". + * + * The heart of the block device driver is the @ref RTEMS_BLKIO_REQUEST IO + * control. This call puts IO @ref rtems_blkdev_request "requests" to the block + * device for asynchronous processing. When a driver executes a request, it + * invokes the request done callback function to finish the request. + * + * @{ + */ + +/** + * Block device request type. + * + * @warning The sync request is an IO one and only used from the cache. Use the + * Block IO when operating at the device level. We need a sync request + * to avoid requests looping for ever. + */ +typedef enum rtems_blkdev_request_op { + RTEMS_BLKDEV_REQ_READ, /**< Read the requested blocks of data. */ + RTEMS_BLKDEV_REQ_WRITE, /**< Write the requested blocks of data. */ + RTEMS_BLKDEV_REQ_SYNC /**< Sync any data with the media. */ +} rtems_blkdev_request_op; + +/** + * @brief Block device request done callback function type. + * + * The first parameter @a arg must be the argument provided by the block device + * request structure @ref rtems_blkdev_request. + * + * The second parameter @a status should contain the status of the operation: + * - @c RTEMS_SUCCESSFUL Operation was successful. + * - @c RTEMS_IO_ERROR Some sort of input or output error. + * - @c RTEMS_UNSATISFIED Media no more present. + */ +typedef void (*rtems_blkdev_request_cb)(void *arg, rtems_status_code status); + +/** + * Block device scatter or gather buffer structure. + */ +typedef struct rtems_blkdev_sg_buffer { + /** + * Block index. + */ + rtems_blkdev_bnum block; + + /** + * Buffer length. + */ + uint32_t length; + + /** + * Buffer pointer. + */ + void *buffer; + + /** + * User pointer. + */ + void *user; +} rtems_blkdev_sg_buffer; + +/** + * The block device request structure is used to read or write a number of + * blocks from or to the device. + * + * TODO: The use of these req blocks is not a great design. The req is a + * struct with a single 'bufs' declared in the req struct and the + * others are added in the outer level struct. This relies on the + * structs joining as a single array and that assumes the compiler + * packs the structs. Why not just place on a list ? The BD has a + * node that can be used. + */ +typedef struct rtems_blkdev_request { + /** + * Block device operation (read or write). + */ + rtems_blkdev_request_op req; + + /** + * Request done callback function. + */ + rtems_blkdev_request_cb req_done; + + /** + * Argument to be passed to callback function. + */ + void *done_arg; + + /** + * Last IO operation completion status. + */ + rtems_status_code status; + + /** + * Number of blocks for this request. + */ + uint32_t bufnum; + + /** + * The task requesting the IO operation. + */ + rtems_id io_task; + + /** + * List of scatter or gather buffers. + */ + rtems_blkdev_sg_buffer bufs[0]; +} rtems_blkdev_request; + +/** + * The start block in a request. + * + * Only valid if the driver has returned the @ref RTEMS_BLKIO_CAPABILITIES of + * @ref RTEMS_BLKDEV_CAP_MULTISECTOR_CONT. + */ +#define RTEMS_BLKDEV_START_BLOCK(req) (req->bufs[0].block) + +/** + * @name IO Control Request Codes + * + * @{ + */ + +#define RTEMS_BLKIO_REQUEST _IOWR('B', 1, rtems_blkdev_request) +#define RTEMS_BLKIO_GETMEDIABLKSIZE _IOR('B', 2, uint32_t) +#define RTEMS_BLKIO_GETBLKSIZE _IOR('B', 3, uint32_t) +#define RTEMS_BLKIO_SETBLKSIZE _IOW('B', 4, uint32_t) +#define RTEMS_BLKIO_GETSIZE _IOR('B', 5, rtems_blkdev_bnum) +#define RTEMS_BLKIO_SYNCDEV _IO('B', 6) +#define RTEMS_BLKIO_DELETED _IO('B', 7) +#define RTEMS_BLKIO_CAPABILITIES _IO('B', 8) + +/** @} */ + +/** + * Only consecutive multi-sector buffer requests are supported. + * + * This option means the cache will only supply multiple buffers that are + * inorder so the ATA multi-sector command for example can be used. This is a + * hack to work around the current ATA driver. + */ +#define RTEMS_BLKDEV_CAP_MULTISECTOR_CONT (1 << 0) + +/** + * The driver will accept a sync call. A sync call is made to a driver + * after a bdbuf cache sync has finished. + */ +#define RTEMS_BLKDEV_CAP_SYNC (1 << 1) + +/** + * The device driver interface conventions suppose that a driver may contain an + * initialize, open, close, read, write and IO control entry points. These + * primitives (except initialize) can be implemented in a generic fashion based + * upon the supplied block device driver IO control handler. Every block device + * driver should provide an initialize entry point, which registers the + * appropriate IO control handler. + */ +#define RTEMS_GENERIC_BLOCK_DEVICE_DRIVER_ENTRIES \ + rtems_blkdev_generic_open, \ + rtems_blkdev_generic_close, \ + rtems_blkdev_generic_read, \ + rtems_blkdev_generic_write, \ + rtems_blkdev_generic_ioctl + +/** + * Generic block device read primitive. + * + * Implemented using block device buffer management primitives. + */ +rtems_device_driver +rtems_blkdev_generic_read( + rtems_device_major_number major, + rtems_device_minor_number minor, + void * arg +); + +/** + * Generic block device write primitive. + * + * Implemented using block device buffer management primitives. + */ +rtems_device_driver +rtems_blkdev_generic_write( + rtems_device_major_number major, + rtems_device_minor_number minor, + void * arg +); + +/** + * Generic block device open primitive. + * + * Implemented using block device buffer management primitives. + */ +rtems_device_driver +rtems_blkdev_generic_open( + rtems_device_major_number major, + rtems_device_minor_number minor, + void * arg +); + +/** + * Generic block device close primitive. + * + * Implemented using block device buffer management primitives. + */ +rtems_device_driver +rtems_blkdev_generic_close( + rtems_device_major_number major, + rtems_device_minor_number minor, + void * arg +); + +/** + * Generic block device IO control primitive. + * + * Implemented using block device buffer management primitives. + */ +rtems_device_driver +rtems_blkdev_generic_ioctl( + rtems_device_major_number major, + rtems_device_minor_number minor, + void * arg +); + +/** + * Common IO control primitive. + * + * Use this in all block devices to handle the common set of ioctl requests. + */ +int +rtems_blkdev_ioctl(rtems_disk_device *dd, uint32_t req, void *argp); + +/** + * @brief Generic block operations driver address table. + */ +extern const rtems_driver_address_table rtems_blkdev_generic_ops; + +/** @} */ + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/cpukit/libblock/include/rtems/diskdevs.h b/cpukit/libblock/include/rtems/diskdevs.h new file mode 100644 index 0000000000..8884999d95 --- /dev/null +++ b/cpukit/libblock/include/rtems/diskdevs.h @@ -0,0 +1,341 @@ +/** + * @file + * + * @ingroup rtems_disk + * + * @brief Block device disk management API. + */ + +/* + * Copyright (C) 2001 OKTET Ltd., St.-Petersburg, Russia + * Author: Victor V. Vengerov <vvv@oktet.ru> + * + * @(#) $Id$ + */ + +#ifndef _RTEMS_DISKDEVS_H +#define _RTEMS_DISKDEVS_H + +#include <rtems.h> +#include <rtems/libio.h> +#include <stdlib.h> + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct rtems_disk_device rtems_disk_device; + +/** + * @defgroup rtems_disk Block Device Disk Management + * + * @ingroup rtems_libblock + * + * @brief This module provides functions to manage disk devices. + * + * A disk is a set of blocks which are identified by a consecutive set of + * non-negative integers starting at zero. There are also logical disks which + * contain a subset of consecutive disk blocks. The logical disks are used to + * represent the partitions of a disk. The disk devices are accessed via the + * @ref rtems_bdbuf "block device buffer module". + * + * @{ + */ + +/** + * @brief Block device block index type. + */ +typedef uint32_t rtems_blkdev_bnum; + +/** + * @brief Block device IO control handler type. + */ +typedef int (*rtems_block_device_ioctl)( + rtems_disk_device *dd, + uint32_t req, + void *argp +); + +/** + * @brief Description of a disk device (logical and physical disks). + * + * An array of pointer tables to rtems_disk_device structures is maintained. + * The first table will be indexed by the major number and the second table + * will be indexed by the minor number. This allows quick lookup using a data + * structure of moderated size. + */ +struct rtems_disk_device { + /** + * @brief Device identifier (concatenation of major and minor number). + */ + dev_t dev; + + /** + * @brief Physical device identifier (equals the @c dev entry if it specifies a + * physical device). + */ + rtems_disk_device *phys_dev; + + /** + * @brief Driver capabilities. + */ + uint32_t capabilities; + + /** + * @brief Disk device name. + */ + char *name; + + /** + * @brief Usage counter. + * + * Devices cannot be deleted if they are in use. + */ + unsigned uses; + + /** + * @brief Start block number. + * + * Equals zero for physical devices. It is a block offset to the related + * physical device for logical device. + */ + rtems_blkdev_bnum start; + + /** + * @brief Size of the physical or logical disk in blocks. + */ + rtems_blkdev_bnum size; + + /** + * @brief Device block size in bytes. + * + * This is the minimum transfer unit. It can be any size. + */ + uint32_t block_size; + + /** + * @brief Device media block size in bytes. + * + * This is the media transfer unit the hardware defaults to. + */ + uint32_t media_block_size; + + /** + * @brief IO control handler for this disk. + */ + rtems_block_device_ioctl ioctl; + + /** + * @brief Private data for the disk driver. + */ + void *driver_data; + + /** + * @brief Indicates that this disk should be deleted as soon as the last user + * releases this disk. + */ + bool deleted; +}; + +/** + * @name Disk Device Data + * + * @{ + */ + +static inline dev_t rtems_disk_get_device_identifier( + const rtems_disk_device *dd +) +{ + return dd->dev; +} + +static inline rtems_device_major_number rtems_disk_get_major_number( + const rtems_disk_device *dd +) +{ + return rtems_filesystem_dev_major_t(dd->dev); +} + +static inline rtems_device_minor_number rtems_disk_get_minor_number( + const rtems_disk_device *dd +) +{ + return rtems_filesystem_dev_minor_t(dd->dev); +} + +static inline void *rtems_disk_get_driver_data( + const rtems_disk_device *dd +) +{ + return dd->driver_data; +} + +static inline uint32_t rtems_disk_get_media_block_size( + const rtems_disk_device *dd +) +{ + return dd->media_block_size; +} + +/** @} */ + +/** + * @name Disk Device Maintainance + * + * @{ + */ + +/** + * @brief Creates a physical disk with device identifier @a dev. + * + * The block size @a block_size must be positive. The disk will have + * @a block_count blocks. The block index starts with zero. The associated disk + * device driver will be invoked via the IO control handler @a handler. A + * device node will be registered in the file system with absolute path @a + * name, if @a name is not @c NULL. This function is usually invoked from a + * block device driver during initialization when a physical device is detected + * in the system. The device driver provides an IO control handler to allow + * block device operations. + * + * @retval RTEMS_SUCCESSFUL Successful operation. + * @retval RTEMS_NOT_CONFIGURED Cannot lock disk device operation mutex. + * @retval RTEMS_INVALID_ADDRESS IO control handler is @c NULL. + * @retval RTEMS_INVALID_NUMBER Block size is zero. + * @retval RTEMS_NO_MEMORY Not enough memory. + * @retval RTEMS_RESOURCE_IN_USE Disk device descriptor is already in use. + * @retval RTEMS_UNSATISFIED Cannot create device node. + */ +rtems_status_code rtems_disk_create_phys( + dev_t dev, + uint32_t block_size, + rtems_blkdev_bnum block_count, + rtems_block_device_ioctl handler, + void *driver_data, + const char *name +); + +/** + * @brief Creates a logical disk with device identifier @a dev. + * + * A logical disk manages a subset of consecutive blocks contained in the + * physical disk with identifier @a phys. The start block index of the logical + * disk device is @a begin_block. The block count of the logcal disk will be + * @a block_count. The blocks must be within the range of blocks managed by + * the associated physical disk device. A device node will be registered in + * the file system with absolute path @a name, if @a name is not @c NULL. The + * block size and IO control handler are inherited by the physical disk. + * + * @retval RTEMS_SUCCESSFUL Successful operation. + * @retval RTEMS_NOT_CONFIGURED Cannot lock disk device operation mutex. + * @retval RTEMS_INVALID_ID Specified physical disk identifier does not + * correspond to a physical disk. + * @retval RTEMS_INVALID_NUMBER Begin block or block count are out of range. + * @retval RTEMS_NO_MEMORY Not enough memory. + * @retval RTEMS_RESOURCE_IN_USE Disk device descriptor for logical disk + * identifier is already in use. + * @retval RTEMS_UNSATISFIED Cannot create device node. + */ +rtems_status_code rtems_disk_create_log( + dev_t dev, + dev_t phys, + rtems_blkdev_bnum begin_block, + rtems_blkdev_bnum block_count, + const char *name +); + +/** + * @brief Deletes a physical or logical disk device with identifier @a dev. + * + * Marks the disk device as deleted. When a physical disk device is deleted, + * all corresponding logical disk devices will marked as deleted too. Disks + * that are marked as deleted and have a usage counter of zero will be deleted. + * The corresponding device nodes will be removed from the file system. In + * case of a physical disk deletion the IO control handler will be invoked with + * a RTEMS_BLKIO_DELETED request. Disks that are still in use will be deleted + * upon release. + * + * @retval RTEMS_SUCCESSFUL Successful operation. + * @retval RTEMS_NOT_CONFIGURED Cannot lock disk device operation mutex. + * @retval RTEMS_INVALID_ID No disk for specified device identifier. + */ +rtems_status_code rtems_disk_delete(dev_t dev); + +/** + * @brief Returns the disk device descriptor for the device identifier @a dev. + * + * Increments usage counter by one. You should release the disk device + * descriptor with rtems_disk_release(). + * + * @return Pointer to the disk device descriptor or @c NULL if no corresponding + * disk exists. + */ +rtems_disk_device *rtems_disk_obtain(dev_t dev); + +/** + * @brief Releases the disk device descriptor @a dd. + * + * Decrements usage counter by one. + * + * @retval RTEMS_SUCCESSFUL Successful operation. + */ +rtems_status_code rtems_disk_release(rtems_disk_device *dd); + +/** @} */ + +/** + * @name Disk Management + * + * @{ + */ + +/** + * @brief Initializes the disk device management. + * + * This functions returns successful if the disk device management is already + * initialized. There is no protection against concurrent access. + * + * @retval RTEMS_SUCCESSFUL Successful initialization. + * @retval RTEMS_NO_MEMORY Not enough memory or no semaphore available. + * @retval RTEMS_UNSATISFIED Block device buffer initialization failed. + */ +rtems_status_code rtems_disk_io_initialize(void); + +/** + * @brief Releases all resources allocated for disk device management. + * + * There is no protection against concurrent access. If parts of the system + * are still in use the behaviour is undefined. + * + * @retval RTEMS_SUCCESSFUL Successful operation. + */ +rtems_status_code rtems_disk_io_done(void); + +/** @} */ + +/** @} */ + +/** + * @brief Disk device iterator. + * + * Returns the next disk device descriptor with a device identifier larger than + * @a dev. If there is no such device, @c NULL will be returned. Use minus + * one to start the search. + * + * @code + * rtems_status_code sc = RTEMS_SUCCESSFUL; + * rtems_disk_device *dd = (dev_t) -1; + * + * while (sc == RTEMS_SUCCESSFUL && (dd = rtems_disk_next(dev)) != NULL) { + * dev = rtems_disk_get_device_identifier(dd); + * sc = rtems_disk_release(dd); + * } + * @endcode + */ +rtems_disk_device *rtems_disk_next(dev_t dev); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/cpukit/libblock/include/rtems/flashdisk.h b/cpukit/libblock/include/rtems/flashdisk.h new file mode 100644 index 0000000000..c24b9b08be --- /dev/null +++ b/cpukit/libblock/include/rtems/flashdisk.h @@ -0,0 +1,366 @@ +/* + * flashdisk.h -- 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$ + */ + +#if !defined (_RTEMS_FLASHDISK_H_) +#define _RTEMS_FLASHDISK_H_ + +#include <stdint.h> +#include <sys/ioctl.h> + +#include <rtems.h> + +/** + * The base name of the flash disks. + */ +#define RTEMS_FLASHDISK_DEVICE_BASE_NAME "/dev/fdd" + +/** + * Flash disk specific ioctl request types. To use open the + * device and issue the ioctl call. + * + * @code + * int fd = open ("/dev/flashdisk0", O_WRONLY, 0); + * if (fd < 0) + * { + * printf ("driver open failed: %s\n", strerror (errno)); + * exit (1); + * } + * if (ioctl (fd, RTEMS_FDISK_IOCTL_ERASE_DISK) < 0) + * { + * printf ("driver erase failed: %s\n", strerror (errno)); + * exit (1); + * } + * close (fd); + * @endcode + */ +#define RTEMS_FDISK_IOCTL_ERASE_DISK _IO('B', 128) +#define RTEMS_FDISK_IOCTL_COMPACT _IO('B', 129) +#define RTEMS_FDISK_IOCTL_ERASE_USED _IO('B', 130) +#define RTEMS_FDISK_IOCTL_MONITORING _IO('B', 131) +#define RTEMS_FDISK_IOCTL_INFO_LEVEL _IO('B', 132) +#define RTEMS_FDISK_IOCTL_PRINT_STATUS _IO('B', 133) + +/** + * Flash Disk Monitoring Data allows a user to obtain + * the current status of the disk. + */ +typedef struct rtems_fdisk_monitor_data +{ + uint32_t block_size; + uint32_t block_count; + uint32_t unavail_blocks; + uint32_t device_count; + uint32_t segment_count; + uint32_t page_count; + uint32_t blocks_used; + uint32_t segs_available; + uint32_t segs_used; + uint32_t segs_failed; + uint32_t seg_erases; + uint32_t pages_desc; + uint32_t pages_active; + uint32_t pages_used; + uint32_t pages_bad; + uint32_t info_level; +} rtems_fdisk_monitor_data; + +/** + * Flash Segment Descriptor holds, number of continuous segments in the + * device of this type, the base segment number in the device, the + * address offset of the base segment in the device, and the size of + * segment. + * + * Typically this structure is part of a table of segments in the + * device which is referenced in the flash disk configuration table. + * The reference is kept in the driver and used all the time to + * manage the flash device, therefore it must always exist. + */ +typedef struct rtems_fdisk_segment_desc +{ + uint16_t count; /**< Number of segments of this type in a row. */ + uint16_t segment; /**< The base segment number. */ + uint32_t offset; /**< Address offset of base segment in device. */ + uint32_t size; /**< Size of the segment in bytes. */ +} rtems_fdisk_segment_desc; + +/** + * Return the number of kilo-bytes. + */ +#define RTEMS_FDISK_KBYTES(_k) (UINT32_C(1024) * (_k)) + +/** + * Forward declaration of the device descriptor. + */ +struct rtems_fdisk_device_desc; + +/** + * Flash Low Level driver handlers. + + * Typically this structure is part of a table of handlers in the + * device which is referenced in the flash disk configuration table. + * The reference is kept in the driver and used all the time to + * manage the flash device, therefore it must always exist. + */ +typedef struct rtems_fdisk_driver_handlers +{ + /** + * Read data from the device into the buffer. Return an errno + * error number if the device cannot be read. A segment descriptor + * can describe more than one segment in a device if the device has + * repeating segments. The segment number is the device segment to + * access and the segment descriptor must reference the segment + * being requested. For example the segment number must resided in + * the range [base, base + count). + * + * @param sd The segment descriptor. + * @param device The device to read data from. + * @param segment The segment within the device to read. + * @param offset The offset in the segment to read. + * @param buffer The buffer to read the data into. + * @param size The amount of data to read. + * @retval 0 No error. + * @retval EIO The read did not complete. + */ + int (*read) (const rtems_fdisk_segment_desc* sd, + uint32_t device, + uint32_t segment, + uint32_t offset, + void* buffer, + uint32_t size); + + /** + * Write data from the buffer to the device. Return an errno + * error number if the device cannot be written to. A segment + * descriptor can describe more than segment in a device if the + * device has repeating segments. The segment number is the device + * segment to access and the segment descriptor must reference + * the segment being requested. For example the segment number must + * resided in the range [base, base + count). + * + * @param sd The segment descriptor. + * @param device The device to write data from. + * @param segment The segment within the device to write to. + * @param offset The offset in the segment to write. + * @param buffer The buffer to write the data from. + * @param size The amount of data to write. + * @retval 0 No error. + * @retval EIO The write did not complete or verify. + */ + int (*write) (const rtems_fdisk_segment_desc* sd, + uint32_t device, + uint32_t segment, + uint32_t offset, + const void* buffer, + uint32_t size); + + /** + * Blank a segment in the device. Return an errno error number + * if the device cannot be read or is not blank. A segment descriptor + * can describe more than segment in a device if the device has + * repeating segments. The segment number is the device segment to + * access and the segment descriptor must reference the segment + * being requested. For example the segment number must resided in + * the range [base, base + count). + * + * @param sd The segment descriptor. + * @param device The device to read data from. + * @param segment The segment within the device to read. + * @param offset The offset in the segment to checl. + * @param size The amount of data to check. + * @retval 0 No error. + * @retval EIO The segment is not blank. + */ + int (*blank) (const rtems_fdisk_segment_desc* sd, + uint32_t device, + uint32_t segment, + uint32_t offset, + uint32_t size); + + /** + * Verify data in the buffer to the data in the device. Return an + * errno error number if the device cannot be read. A segment + * descriptor can describe more than segment in a device if the + * device has repeating segments. The segment number is the + * segment to access and the segment descriptor must reference + * the device segment being requested. For example the segment number + * must resided in the range [base, base + count). + * + * @param sd The segment descriptor. + * @param device The device to verify data in. + * @param segment The segment within the device to verify. + * @param offset The offset in the segment to verify. + * @param buffer The buffer to verify the data in the device with. + * @param size The amount of data to verify. + * @retval 0 No error. + * @retval EIO The data did not verify. + */ + int (*verify) (const rtems_fdisk_segment_desc* sd, + uint32_t device, + uint32_t segment, + uint32_t offset, + const void* buffer, + uint32_t size); + + /** + * Erase the segment. Return an errno error number if the + * segment cannot be erased. A segment descriptor can describe + * more than segment in a device if the device has repeating + * segments. The segment number is the device segment to access and + * the segment descriptor must reference the segment being requested. + * + * @param sd The segment descriptor. + * @param device The device to erase the segment of. + * @param segment The segment within the device to erase. + * @retval 0 No error. + * @retval EIO The segment was not erased. + */ + int (*erase) (const rtems_fdisk_segment_desc* sd, + uint32_t device, + uint32_t segment); + + /** + * Erase the device. Return an errno error number if the + * segment cannot be erased. A segment descriptor can describe + * more than segment in a device if the device has repeating + * segments. The segment number is the segment to access and + * the segment descriptor must reference the segment being requested. + * + * @param sd The segment descriptor. + * @param device The device to erase. + * @retval 0 No error. + * @retval EIO The device was not erased. + */ + int (*erase_device) (const struct rtems_fdisk_device_desc* dd, + uint32_t device); + +} rtems_fdisk_driver_handlers; + +/** + * Flash Device Descriptor holds the segments in a device. The + * placing of the segments in a device decriptor allows the + * low level driver to share the segment descriptors for a + * number of devices. + * + * Typically this structure is part of a table of segments in the + * device which is referenced in the flash disk configuration table. + * The reference is kept in the driver and used all the time to + * manage the flash device, therefore it must always exist. + */ +typedef struct rtems_fdisk_device_desc +{ + uint32_t segment_count; /**< Number of segments. */ + const rtems_fdisk_segment_desc* segments; /**< Array of segments. */ + const rtems_fdisk_driver_handlers* flash_ops; /**< Device handlers. */ +} rtems_fdisk_device_desc; + +/** + * RTEMS Flash Disk configuration table used to initialise the + * driver. + * + * The unavailable blocks count is the number of blocks less than the + * available number of blocks the file system is given. This means there + * will always be that number of blocks available when the file system + * thinks the disk is full. The compaction code needs blocks to compact + * with so you will never be able to have all the blocks allocated to the + * file system and be able to full the disk. + * + * The compacting segment count is the number of segments that are + * moved into a new segment. A high number will mean more segments with + * low active page counts and high used page counts will be moved into + * avaliable pages how-ever this extends the compaction time due to + * time it takes the erase the pages. There is no pont making this number + * greater than the maximum number of pages in a segment. + * + * The available compacting segment count is the level when compaction occurs + * when writing. If you set this to 0 then compaction will fail because + * there will be no segments to compact into. + * + * The info level can be 0 for off with error, and abort messages allowed. + * Level 1 is warning messages, level 1 is informational messages, and level 3 + * is debugging type prints. The info level can be turned off with a compile + * time directive on the command line to the compiler of: + * + * -DRTEMS_FDISK_TRACE=0 + */ +typedef struct rtems_flashdisk_config +{ + uint32_t block_size; /**< The block size. */ + uint32_t device_count; /**< The number of devices. */ + const rtems_fdisk_device_desc* devices; /**< The device descriptions. */ + uint32_t flags; /**< Set of flags to control + driver. */ + uint32_t unavail_blocks; /**< Number of blocks not + available to the file sys. */ + uint32_t compact_segs; /**< Max number of segs to + compact in one pass. */ + uint32_t avail_compact_segs; /**< The number of segments + when compaction occurs + when writing. */ + uint32_t info_level; /**< Default info level. */ +} rtems_flashdisk_config; + +/* + * Driver flags. + */ + +/** + * Leave the erasing of used segment to the background handler. + */ +#define RTEMS_FDISK_BACKGROUND_ERASE (1 << 0) + +/** + * Leave the compacting of of used segment to the background handler. + */ +#define RTEMS_FDISK_BACKGROUND_COMPACT (1 << 1) + +/** + * Check the pages during initialisation to see which pages are + * valid and which are not. This could slow down initialising the + * disk driver. + */ +#define RTEMS_FDISK_CHECK_PAGES (1 << 2) + +/** + * Blank check the flash device before writing to them. This is needed if + * you think you have a driver or device problem. + */ +#define RTEMS_FDISK_BLANK_CHECK_BEFORE_WRITE (1 << 3) + +/** + * Flash disk device driver initialization. Place in a table as the + * initialisation entry and remainder of the entries are the + * RTEMS block device generic handlers. + * + * @param major Flash disk major device number. + * @param minor Minor device number, not applicable. + * @param arg Initialization argument, not applicable. + * @return The rtems_device_driver is actually just + * rtems_status_code. + */ +rtems_device_driver +rtems_fdisk_initialize (rtems_device_major_number major, + rtems_device_minor_number minor, + void* arg); + +/** + * External reference to the configuration. Please supply. + * Support is present in confdefs.h for providing this variable. + */ +extern const rtems_flashdisk_config rtems_flashdisk_configuration[]; + +/** + * External reference to the number of configurations. Please supply. + * Support is present in confdefs.h for providing this variable. + */ +extern uint32_t rtems_flashdisk_configuration_size; + +#endif diff --git a/cpukit/libblock/include/rtems/ide_part_table.h b/cpukit/libblock/include/rtems/ide_part_table.h new file mode 100644 index 0000000000..c6fe587e02 --- /dev/null +++ b/cpukit/libblock/include/rtems/ide_part_table.h @@ -0,0 +1,217 @@ +/** + * @file rtems/ide_part_table.h + * + * Support for "MS-DOS-style" partition tables + */ + +/* + * Copyright (C) 2002 OKTET Ltd., St.-Petersburg, Russia + * + * Author: Konstantin Abramenko <Konstantin.Abramenko@oktet.ru> + * Alexander Kukuta <Alexander.Kukuta@oktet.ru> + * + * 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$ + * + *****************************************************************************/ + +#ifndef _RTEMS_IDE_PART_TABLE_H +#define _RTEMS_IDE_PART_TABLE_H + +#include <rtems/chain.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <errno.h> +#include <sys/ioctl.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <unistd.h> +#include <fcntl.h> +#include <rtems.h> +#include <rtems/blkdev.h> +#include <rtems/libio.h> +#include <rtems/libio_.h> +#include <rtems/bdbuf.h> +#include <rtems/seterr.h> + +/* Minor base number for all logical devices */ +#define RTEMS_IDE_SECTOR_BITS 9 +#define RTEMS_IDE_SECTOR_SIZE 512 +#define RTEMS_IDE_PARTITION_DESCRIPTOR_SIZE 16 +#define RTEMS_IDE_PARTITION_MAX_PARTITION_NUMBER 63 +#define RTEMS_IDE_PARTITION_MAX_SUB_PARTITION_NUMBER 4 +#define RTEMS_IDE_PARTITION_DEV_NAME_LENGTH_MAX 16 + +#define RTEMS_IDE_PARTITION_MSDOS_SIGNATURE_DATA1 0x55 +#define RTEMS_IDE_PARTITION_MSDOS_SIGNATURE_DATA2 0xaa +#define RTEMS_IDE_PARTITION_MSDOS_SIGNATURE_OFFSET 0x1fe +#define RTEMS_IDE_PARTITION_TABLE_OFFSET 0x1be +#define RTEMS_IDE_PARTITION_TABLE_SIZE (4 * 16) +#define RTEMS_IDE_PARTITION_BOOTABLE_OFFSET 0 +#define RTEMS_IDE_PARTITION_SYS_TYPE_OFFSET 4 +#define RTEMS_IDE_PARTITION_START_OFFSET 8 +#define RTEMS_IDE_PARTITION_SIZE_OFFSET 12 + +/* + * Conversion from and to little-endian byte order. (no-op on i386/i486) + */ + +#if (CPU_BIG_ENDIAN == TRUE) +# define LE_TO_CPU_U16(v) CPU_swap_u16(v) +# define LE_TO_CPU_U32(v) CPU_swap_u32(v) +# define CPU_TO_LE_U16(v) CPU_swap_u16(v) +# define CPU_TO_LE_U32(v) CPU_swap_u32(v) +#else +# define LE_TO_CPU_U16(v) (v) +# define LE_TO_CPU_U32(v) (v) +# define CPU_TO_LE_U16(v) (v) +# define CPU_TO_LE_U32(v) (v) +#endif + + +/* + * sector_data_t -- + * corresponds to the sector on the device + */ +typedef struct rtems_sector_data_s +{ + uint32_t sector_num; /* sector number on the device */ + uint8_t data[0]; /* raw sector data */ +} rtems_sector_data_t; + + +/* + * Enum partition types + * see list at http://ata-atapi.com/hiwtab.htm + * + * @todo Should these have RTEMS before them. + */ +enum { + EMPTY_PARTITION = 0x00, + DOS_FAT12_PARTITION = 0x01, + DOS_FAT16_PARTITION = 0x04, + EXTENDED_PARTITION = 0x05, + DOS_P32MB_PARTITION = 0x06, + FAT32_PARTITION = 0x0B, + FAT32_LBA_PARTITION = 0x0C, + FAT16_LBA_PARTITION = 0x0E, + DM6_PARTITION = 0x54, + EZD_PARTITION = 0x55, + DM6_AUX1PARTITION = 0x51, + DM6_AUX3PARTITION = 0x53, + LINUX_SWAP = 0x82, + LINUX_NATIVE = 0x83, + LINUX_EXTENDED = 0x85 +}; + + +/* Forward declaration */ +struct rtems_disk_desc_s; + +/* + * part_desc_t -- + * contains all neccessary information about partition + */ +typedef struct rtems_part_desc_s { + uint8_t bootable; /* is the partition active */ + uint8_t sys_type; /* type of partition */ + uint8_t log_id; /* logical number of partition */ + uint32_t start; /* first partition sector, in absolute + * numeration */ + uint32_t size; /* size in sectors */ + uint32_t end; /* last partition sector, end = start + size - 1 */ + struct rtems_disk_desc_s *disk_desc; /* descriptor of disk, partition + * contains in */ + struct rtems_part_desc_s *ext_part; /* extended partition containing this + * one */ + + /* partitions, containing in this one */ + struct rtems_part_desc_s *sub_part[RTEMS_IDE_PARTITION_MAX_SUB_PARTITION_NUMBER]; +} rtems_part_desc_t; + + + +typedef struct rtems_disk_desc_s { + dev_t dev; /* device number */ + + /* device name in /dev filesystem */ + char dev_name[RTEMS_IDE_PARTITION_DEV_NAME_LENGTH_MAX]; + + uint32_t sector_size; /* size of sector */ + uint32_t sector_bits; /* the base-2 logarithm of sector_size */ + uint32_t lba_size; /* total amount of sectors in lba address mode */ + int last_log_id; /* used for logical disks enumerating */ + + /* primary partition descriptors */ + rtems_part_desc_t *partitions[RTEMS_IDE_PARTITION_MAX_PARTITION_NUMBER]; +} rtems_disk_desc_t; + +#ifdef __cplusplus +extern "C" { +#endif + +/* + * rtems_ide_part_table_free -- + * frees disk descriptor structure + * + * PARAMETERS: + * disk_desc - disc descriptor structure to free + * + * RETURNS: + * N/A + */ +/** + * @deprecated Use the @ref rtems_bdpart "block device partition module" instead. + */ +void rtems_ide_part_table_free( + rtems_disk_desc_t *disk_desc +) RTEMS_COMPILER_DEPRECATED_ATTRIBUTE; + + +/* + * rtems_ide_part_table_get -- + * reads partition table structure from the device + * and creates disk description structure + * + * PARAMETERS: + * dev_name - path to physical device in /dev filesystem + * disk_desc - returned disc description structure + * + * RETURNS: + * RTEMS_SUCCESSFUL if success, or -1 and corresponding errno else + */ +/** + * @deprecated Use the @ref rtems_bdpart "block device partition module" instead. + */ +rtems_status_code rtems_ide_part_table_get( + const char *dev_name, + rtems_disk_desc_t *disk_desc +) RTEMS_COMPILER_DEPRECATED_ATTRIBUTE; + + +/* + * rtems_ide_part_table_initialize -- + * initializes logical devices on the physical IDE drive + * + * PARAMETERS: + * dev_name - path to physical device in /dev filesystem + * + * RETURNS: + * RTEMS_SUCCESSFUL if success, or -1 and corresponding errno else + */ +/** + * @deprecated Use the @ref rtems_bdpart "block device partition module" instead. + */ +rtems_status_code rtems_ide_part_table_initialize( + char *dev_name +) RTEMS_COMPILER_DEPRECATED_ATTRIBUTE; + +#ifdef __cplusplus +} +#endif + +#endif /* _RTEMS_IDE_PART_TABLE_H */ diff --git a/cpukit/libblock/include/rtems/media.h b/cpukit/libblock/include/rtems/media.h new file mode 100644 index 0000000000..038357c16d --- /dev/null +++ b/cpukit/libblock/include/rtems/media.h @@ -0,0 +1,521 @@ +/** + * @file + * + * @ingroup RTEMSMedia + * + * @brief Media API. + */ + +/* + * Copyright (c) 2009, 2010 embedded brains GmbH. All rights reserved. + * + * embedded brains GmbH + * Obere Lagerstr. 30 + * 82178 Puchheim + * Germany + * <rtems@embedded-brains.de> + * + * 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. + */ + +#ifndef RTEMS_MEDIA_H +#define RTEMS_MEDIA_H + +#include <rtems.h> + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + +/** + * @defgroup RTEMSMedia Media Manager + * + * @brief Removable media support. + * + * The media manager may be used to maintain the life cycle of a removable + * media. Currently only disk devices are supported. The initiator posts an + * event to the media manager and it will respond with appropriate default + * actions. For example a disk attach will lead to inspection of the partition + * table and mounted file systems. Clients can register listeners to react to + * events. + * @{ + */ + +#define RTEMS_MEDIA_MOUNT_BASE "/media" + +#define RTEMS_MEDIA_DELIMITER '-' + +/** + * Disk life cycle events: + * @dot + * digraph disk_events { + * "DISK ATTACH" -> "PARTITION INQUIRY"; + * "DISK ATTACH" -> "MOUNT"; + * "PARTITION INQUIRY" -> "PARTITION ATTACH"; + * "PARTITION INQUIRY" -> "DISK DETACH"; + * "PARTITION ATTACH" -> "MOUNT"; + * "MOUNT" -> "UNMOUNT"; + * "UNMOUNT" -> "PARTITION DETACH"; + * "UNMOUNT" -> "DISK DETACH"; + * "PARTITION DETACH" -> "DISK DETACH"; + * } + * @enddot + */ +typedef enum { + RTEMS_MEDIA_EVENT_DISK_ATTACH, + RTEMS_MEDIA_EVENT_DISK_DETACH, + RTEMS_MEDIA_EVENT_MOUNT, + RTEMS_MEDIA_EVENT_UNMOUNT, + RTEMS_MEDIA_EVENT_PARTITION_INQUIRY, + RTEMS_MEDIA_EVENT_PARTITION_ATTACH, + RTEMS_MEDIA_EVENT_PARTITION_DETACH, + RTEMS_MEDIA_EVENT_ERROR +} rtems_media_event; + +/** + * Normal state transition: + * @dot + * digraph state { + * INQUIRY -> READY [label="all listeners\nreturned successful"]; + * INQUIRY -> ABORTED [label="otherwise"]; + * READY -> SUCCESS [label="the worker\nreturned successful"]; + * READY -> FAILED [label="otherwise"]; + * } + * @enddot + */ +typedef enum { + RTEMS_MEDIA_STATE_INQUIRY, + RTEMS_MEDIA_STATE_READY, + RTEMS_MEDIA_STATE_ABORTED, + RTEMS_MEDIA_STATE_SUCCESS, + RTEMS_MEDIA_STATE_FAILED, + RTEMS_MEDIA_ERROR_DISK_UNKNOWN, + RTEMS_MEDIA_ERROR_DISK_EXISTS, + RTEMS_MEDIA_ERROR_DISK_OR_PARTITION_UNKNOWN, + RTEMS_MEDIA_ERROR_DISK_OR_PARTITION_EXISTS, + RTEMS_MEDIA_ERROR_PARTITION_UNKNOWN, + RTEMS_MEDIA_ERROR_PARTITION_ORPHAN, + RTEMS_MEDIA_ERROR_PARTITION_DETACH_WITH_MOUNT, + RTEMS_MEDIA_ERROR_PARTITION_WITH_UNKNOWN_DISK, + RTEMS_MEDIA_ERROR_MOUNT_POINT_UNKNOWN, + RTEMS_MEDIA_ERROR_MOUNT_POINT_EXISTS, + RTEMS_MEDIA_ERROR_MOUNT_POINT_ORPHAN +} rtems_media_state; + +/** + * @brief Event listener. + * + * The listener will be called with the @a listener_arg passed to + * rtems_media_listener_add(). + * + * Source and destination values for each event and state: + * <table> + * <tr><th>Event</th><th>State</th><th>Source</th><th>Destination</th></tr> + * <tr> + * <td rowspan="5">RTEMS_MEDIA_EVENT_DISK_ATTACH</td> + * <td>RTEMS_MEDIA_STATE_INQUIRY</td><td>driver name</td><td>NULL</td> + * </tr> + * <tr> + * <td>RTEMS_MEDIA_STATE_READY</td><td>driver name</td><td>NULL</td> + * </tr> + * <tr> + * <td>RTEMS_MEDIA_STATE_ABORTED</td><td>driver name</td><td>NULL</td> + * </tr> + * <tr> + * <td>RTEMS_MEDIA_STATE_SUCCESS</td><td>driver name</td><td>disk path</td> + * </tr> + * <tr> + * <td>RTEMS_MEDIA_STATE_FAILED</td><td>driver name</td><td>NULL</td> + * </tr> + * <tr> + * <td rowspan="5">RTEMS_MEDIA_EVENT_DISK_DETACH</td> + * <td>RTEMS_MEDIA_STATE_INQUIRY</td><td>disk path</td><td>NULL</td> + * </tr> + * <tr> + * <td>RTEMS_MEDIA_STATE_READY</td><td>disk path</td><td>NULL</td> + * </tr> + * <tr> + * <td>RTEMS_MEDIA_STATE_ABORTED</td><td>disk path</td><td>NULL</td> + * </tr> + * <tr> + * <td>RTEMS_MEDIA_STATE_SUCCESS</td><td>disk path</td><td>NULL</td> + * </tr> + * <tr> + * <td>RTEMS_MEDIA_STATE_FAILED</td><td>disk path</td><td>NULL</td> + * </tr> + * <tr> + * <td rowspan="5">RTEMS_MEDIA_EVENT_PARTITION_INQUIRY</td> + * <td>RTEMS_MEDIA_STATE_INQUIRY</td><td>disk path</td><td>NULL</td> + * </tr> + * <tr> + * <td>RTEMS_MEDIA_STATE_READY</td><td>disk path</td><td>NULL</td> + * </tr> + * <tr> + * <td>RTEMS_MEDIA_STATE_ABORTED</td><td>disk path</td><td>NULL</td> + * </tr> + * <tr> + * <td>RTEMS_MEDIA_STATE_SUCCESS</td><td>disk path</td><td>NULL</td> + * </tr> + * <tr> + * <td>RTEMS_MEDIA_STATE_FAILED</td><td>disk path</td><td>NULL</td> + * </tr> + * <tr> + * <td rowspan="5">RTEMS_MEDIA_EVENT_PARTITION_ATTACH</td> + * <td>RTEMS_MEDIA_STATE_INQUIRY</td><td>disk path</td><td>NULL</td> + * </tr> + * <tr> + * <td>RTEMS_MEDIA_STATE_READY</td><td>disk path</td><td>NULL</td> + * </tr> + * <tr> + * <td>RTEMS_MEDIA_STATE_ABORTED</td><td>disk path</td><td>NULL</td> + * </tr> + * <tr> + * <td>RTEMS_MEDIA_STATE_SUCCESS</td> + * <td>disk path</td><td>partition path</td> + * </tr> + * <tr> + * <td>RTEMS_MEDIA_STATE_FAILED</td><td>disk path</td><td>NULL</td> + * </tr> + * <tr> + * <td rowspan="5">RTEMS_MEDIA_EVENT_PARTITION_DETACH</td> + * <td>RTEMS_MEDIA_STATE_INQUIRY</td><td>partition path</td><td>NULL</td> + * </tr> + * <tr> + * <td>RTEMS_MEDIA_STATE_READY</td><td>partition path</td><td>NULL</td> + * </tr> + * <tr> + * <td>RTEMS_MEDIA_STATE_ABORTED</td><td>partition path</td><td>NULL</td> + * </tr> + * <tr> + * <td>RTEMS_MEDIA_STATE_SUCCESS</td><td>partition path</td><td>NULL</td> + * </tr> + * <tr> + * <td>RTEMS_MEDIA_STATE_FAILED</td><td>partition path</td><td>NULL</td> + * </tr> + * <tr> + * <td rowspan="5">RTEMS_MEDIA_EVENT_MOUNT</td> + * <td>RTEMS_MEDIA_STATE_INQUIRY</td> + * <td>disk or partition path</td><td>NULL</td> + * </tr> + * <tr> + * <td>RTEMS_MEDIA_STATE_READY</td> + * <td>disk or partition path</td><td>NULL</td> + * </tr> + * <tr> + * <td>RTEMS_MEDIA_STATE_ABORTED</td> + * <td>disk or partition path</td><td>NULL</td> + * </tr> + * <tr> + * <td>RTEMS_MEDIA_STATE_SUCCESS</td> + * <td>disk or partition path</td><td>mount path</td> + * </tr> + * <tr> + * <td>RTEMS_MEDIA_STATE_FAILED</td> + * <td>disk or partition path</td><td>NULL</td> + * </tr> + * <tr> + * <td rowspan="5">RTEMS_MEDIA_EVENT_UNMOUNT</td> + * <td>RTEMS_MEDIA_STATE_INQUIRY</td><td>mount path</td><td>NULL</td> + * </tr> + * <tr> + * <td>RTEMS_MEDIA_STATE_READY</td><td>mount path</td><td>NULL</td> + * </tr> + * <tr> + * <td>RTEMS_MEDIA_STATE_ABORTED</td><td>mount path</td><td>NULL</td> + * </tr> + * <tr> + * <td>RTEMS_MEDIA_STATE_SUCCESS</td><td>mount path</td><td>NULL</td> + * </tr> + * <tr> + * <td>RTEMS_MEDIA_STATE_FAILED</td><td>mount path</td><td>NULL</td> + * </tr> + * <tr> + * <td rowspan="11">RTEMS_MEDIA_EVENT_ERROR</td> + * <td>RTEMS_MEDIA_ERROR_DISK_UNKNOWN</td><td>disk path</td><td>NULL</td> + * </tr> + * <tr> + * <td>RTEMS_MEDIA_ERROR_DISK_EXISTS</td><td>disk path</td><td>NULL</td> + * </tr> + * <tr> + * <td>RTEMS_MEDIA_ERROR_DISK_OR_PARTITION_UNKNOWN</td> + * <td>disk or partition path</td><td>NULL</td> + * </tr> + * <tr> + * <td>RTEMS_MEDIA_ERROR_DISK_OR_PARTITION_EXISTS</td> + * <td>disk or partition path</td><td>NULL</td> + * </tr> + * <tr> + * <td>RTEMS_MEDIA_ERROR_PARTITION_UNKNOWN</td> + * <td>partition path</td><td>NULL</td> + * </tr> + * <tr> + * <td>RTEMS_MEDIA_ERROR_PARTITION_ORPHAN</td> + * <td>partition path</td><td>disk path</td> + * </tr> + * <tr> + * <td>RTEMS_MEDIA_ERROR_PARTITION_DETACH_WITH_MOUNT</td> + * <td>partition path</td><td>mount path</td> + * </tr> + * <tr> + * <td>RTEMS_MEDIA_ERROR_PARTITION_WITH_UNKNOWN_DISK</td> + * <td>partition path</td><td>disk path</td> + * </tr> + * <tr> + * <td>RTEMS_MEDIA_ERROR_MOUNT_POINT_UNKNOWN</td> + * <td>mount path</td><td>NULL</td> + * </tr> + * <tr> + * <td>RTEMS_MEDIA_ERROR_MOUNT_POINT_EXISTS</td> + * <td>mount path</td><td>NULL</td> + * </tr> + * <tr> + * <td>RTEMS_MEDIA_ERROR_MOUNT_POINT_ORPHAN</td> + * <td>mount path</td><td>disk path</td> + * </tr> + * </table> + * + * @retval RTEMS_SUCCESSFUL Successful operation. + * @retval RTEMS_IO_ERROR In the inquiry state this will abort the action. + */ +typedef rtems_status_code (*rtems_media_listener)( + rtems_media_event event, + rtems_media_state state, + const char *src, + const char *dest, + void *listener_arg +); + +/** + * @brief Do the work corresponding to an event. + * + * The @a state will be + * - RTEMS_MEDIA_STATE_READY, or + * - RTEMS_MEDIA_STATE_ABORTED. + * + * It will be called with the @a src and @a worker_arg arguments passed to + * rtems_media_post_event(). + * + * The destination shall be returned in @a dest in case of success. It shall + * be allocated with malloc(). + * + * @retval RTEMS_SUCCESSFUL Successful operation. + * @retval RTEMS_IO_ERROR Failure. + */ +typedef rtems_status_code (*rtems_media_worker)( + rtems_media_state state, + const char *src, + char **dest, + void *worker_arg +); + +/** + * @name Base + * + * @{ + */ + +/** + * @brief Initializes the media manager. + * + * Calling this function more than once will have no effects. There is no + * protection against concurrent access. + * + * @retval RTEMS_SUCCESSFUL Successful operation. + * @retval RTEMS_NO_MEMORY Not enough resources. + */ +rtems_status_code rtems_media_initialize(void); + +/** + * @brief Adds the @a listener with argument @a listener_arg. + * + * @retval RTEMS_SUCCESSFUL Successful operation. + * @retval RTEMS_NO_MEMORY Not enough memory. + * @retval RTEMS_TOO_MANY Such a listener is already present. + */ +rtems_status_code rtems_media_listener_add( + rtems_media_listener listener, + void *listener_arg +); + +/** + * @brief Removes the @a listener with argument @a listener_arg. + * + * @retval RTEMS_SUCCESSFUL Successful operation. + * @retval RTEMS_INVALID_ID No such listener is present. + */ +rtems_status_code rtems_media_listener_remove( + rtems_media_listener listener, + void *listener_arg +); + +/** + * @brief Posts the @a event with source @a src. + * + * The @a worker will be called with the @a worker_arg argument. + * + * The destination will be returned in @a dest in case of success. It will be + * allocated with malloc() and should be freed if not needed anymore. + * + * The work will be done by the calling thread. You can avoid this if you use + * the media server via rtems_media_server_post_event(). + * + * @retval RTEMS_SUCCESSFUL Successful operation. + * @retval RTEMS_UNSATISFIED One or more listeners aborted the action. + * @retval RTEMS_IO_ERROR The worker returned with an error status. + */ +rtems_status_code rtems_media_post_event( + rtems_media_event event, + const char *src, + char **dest, + rtems_media_worker worker, + void *worker_arg +); + +/** @} */ + +/** + * @name Server + * + * @{ + */ + +/** + * @brief Initializes the media manager and media server. + * + * It creates a server task with the @a priority, @a stack_size, @a modes, and + * @a attributes parameters. + * + * Calling this function more than once will have no effects. There is no + * protection against concurrent access. + * + * @retval RTEMS_SUCCESSFUL Successful operation. + * @retval RTEMS_NO_MEMORY Not enough resources. + */ +rtems_status_code rtems_media_server_initialize( + rtems_task_priority priority, + size_t stack_size, + rtems_mode modes, + rtems_attribute attributes +); + +/** + * @brief Sends an event message to the media server. + * + * @see See rtems_media_post_event(). + * + * @retval RTEMS_SUCCESSFUL Successful operation. + * @retval RTEMS_NO_MEMORY Not enough resources to notify the media server. + * @retval RTEMS_NOT_CONFIGURED Media server is not initialized. + */ +rtems_status_code rtems_media_server_post_event( + rtems_media_event event, + const char *src, + rtems_media_worker worker, + void *worker_arg +); + +/** + * @brief See rtems_media_server_post_event(). + */ +static inline rtems_status_code rtems_media_server_disk_attach( + const char *driver_name, + rtems_media_worker worker, + void *worker_arg +) +{ + return rtems_media_server_post_event( + RTEMS_MEDIA_EVENT_DISK_ATTACH, + driver_name, + worker, + worker_arg + ); +} + +/** + * @brief See rtems_media_server_post_event(). + */ +static inline rtems_status_code rtems_media_server_disk_detach( + const char *disk_path +) +{ + return rtems_media_server_post_event( + RTEMS_MEDIA_EVENT_DISK_DETACH, + disk_path, + NULL, + NULL + ); +} + +/** @} */ + +/** + * @name Path Construction + * + * @{ + */ + +/** + * @brief Creates a new path as "prefix/name-major". + * + * @return New string, or @c NULL if no memory is available. + */ +char *rtems_media_create_path( + const char *prefix, + const char *name, + rtems_device_major_number major +); + +/** + * @brief Replaces the prefix of the @a path with @a new_prefix. + * + * The prefix is everything up to the last '/'. + * + * @return New string, or @c NULL if no memory is available. + */ +char *rtems_media_replace_prefix(const char *new_prefix, const char *path); + +/** + * @brief Appends the @a minor number to the @a path resulting in "path-minor". + * + * @return New string, or @c NULL if no memory is available. + */ +char *rtems_media_append_minor( + const char *path, + rtems_device_minor_number minor +); + +/** @} */ + +/** + * @name Support + * + * @{ + */ + +/** + * @brief Returns the device identifier for the device located at + * @a device_path in @a device_identifier. + * + * @retval RTEMS_SUCCESSFUL Successful operation. + * @retval RTEMS_INVALID_ID No device at this path. + */ +rtems_status_code rtems_media_get_device_identifier( + const char *device_path, + dev_t *device_identifier +); + +const char *rtems_media_event_description(rtems_media_event event); + +const char *rtems_media_state_description(rtems_media_state state); + +/** @} */ + +/** @} */ + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + +#endif /* RTEMS_MEDIA_H */ diff --git a/cpukit/libblock/include/rtems/nvdisk-sram.h b/cpukit/libblock/include/rtems/nvdisk-sram.h new file mode 100644 index 0000000000..450cfd8602 --- /dev/null +++ b/cpukit/libblock/include/rtems/nvdisk-sram.h @@ -0,0 +1,25 @@ +/* + * $Id$ + * + * RTEMS Project (http://www.rtems.org/) + * + * Copyright 2007 Chris Johns (chrisj@rtems.org) + */ + +/** + * NV Disk Static RAM Device Driver. + * + * This driver maps an NV disk to static RAM. You can use this + */ + +#if !defined (_RTEMS_NVDISK_SRAM_H_) +#define _RTEMS_NVDISK_SRAM_H_ + +#include <rtems/nvdisk.h> + +/** + * The handlers for the NV Disk SRAM driver. + */ +extern const rtems_nvdisk_driver_handlers rtems_nvdisk_sram_handlers; + +#endif diff --git a/cpukit/libblock/include/rtems/nvdisk.h b/cpukit/libblock/include/rtems/nvdisk.h new file mode 100644 index 0000000000..48d8ba0b16 --- /dev/null +++ b/cpukit/libblock/include/rtems/nvdisk.h @@ -0,0 +1,209 @@ +/* + * nvdisk.h -- Non-volatile 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$ + */ + +/** + * The Non-volatile disk provides a simple directly mapped disk + * driver with checksums for each. It is designed to provied a + * disk that can survive a restart. Examples are EEPROM devices + * which have byte writeable locations, or a battery backed up + * RAM disk. + * + * The low level driver provides the physical access to the + * hardware. + */ +#if !defined (_RTEMS_NVDISK_H_) +#define _RTEMS_NVDISK_H_ + +#include <stdint.h> +#include <sys/ioctl.h> + +#include <rtems.h> + +/** + * The base name of the nv disks. + */ +#define RTEMS_NVDISK_DEVICE_BASE_NAME "/dev/nvd" + +/** + * NV disk specific ioctl request types. To use open the + * device and issue the ioctl call. + * + * @code + * int fd = open ("/dev/nvdisk0", O_WRONLY, 0); + * if (fd < 0) + * { + * printf ("driver open failed: %s\n", strerror (errno)); + * exit (1); + * } + * if (ioctl (fd, RTEMS_NVDISK_IOCTL_ERASE_DISK) < 0) + * { + * printf ("driver erase failed: %s\n", strerror (errno)); + * exit (1); + * } + * close (fd); + * @endcode + */ +#define RTEMS_NVDISK_IOCTL_ERASE_DISK _IO('B', 128) +#define RTEMS_NVDISK_IOCTL_MONITORING _IO('B', 129) +#define RTEMS_NVDISK_IOCTL_INFO_LEVEL _IO('B', 130) +#define RTEMS_NVDISK_IOCTL_PRINT_STATUS _IO('B', 131) + +/** + * NV Disk Monitoring Data allows a user to obtain + * the current status of the disk. + */ +typedef struct rtems_nvdisk_monitor_data +{ + uint32_t block_size; + uint32_t block_count; + uint32_t page_count; + uint32_t pages_available; + uint32_t pages_used; + uint32_t info_level; +} rtems_nvdisk_monitor_data; + +/** + * Return the number of kilo-bytes. + */ +#define RTEMS_NVDISK_KBYTES(_k) ((_k) * 1024) + +/** + * NV Low Level driver handlers. + + * Typically this structure is part of a table of handlers in the + * device which is referenced in the nvdisk configuration table. + * The reference is kept in the driver and used all the time to + * manage the nv device, therefore it must always exist. + */ +typedef struct rtems_nvdisk_driver_handlers +{ + /** + * Read data from the device into the buffer. Return an errno + * error number if the data cannot be read. + * + * @param device The device to read data from. + * @param flags Device specific flags for the driver. + * @param base The base address of the device. + * @param offset The offset in the segment to read. + * @param buffer The buffer to read the data into. + * @param size The amount of data to read. + * @retval 0 No error. + * @retval EIO The read did not complete. + */ + int (*read) (uint32_t device, uint32_t flags, void* base, + uint32_t offset, void* buffer, size_t size); + + /** + * Write data from the buffer to the device. Return an errno + * error number if the device cannot be written to. + * + * @param device The device to write data to. + * @param flags Device specific flags for the driver. + * @param base The base address of the device. + * @param offset The offset in the device to write to. + * @param buffer The buffer to write the data from. + * @param size The amount of data to write. + * @retval 0 No error. + * @retval EIO The write did not complete or verify. + */ + int (*write) (uint32_t device, uint32_t flags, void* base, + uint32_t offset, const void* buffer, size_t size); + + /** + * Verify data in the buffer to the data in the device. Return an + * errno error number if the device cannot be read or the data verified. + * + * @param device The device to verify the data with. + * @param flags Device specific flags for the driver. + * @param base The base address of the device. + * @param offset The offset in the device to verify. + * @param buffer The buffer to verify the data in the device with. + * @param size The amount of data to verify. + * @retval 0 No error. + * @retval EIO The data did not verify. + */ + int (*verify) (uint32_t device, uint32_t flags, void* base, + uint32_t offset, const void* buffer, size_t size); + +} rtems_nvdisk_driver_handlers; + +/** + * NV Device Descriptor holds the description of a device that is + * part of the NV disk. + * + * Typically this structure is part of a table of the device which + * is referenced in the nvdisk configuration table. + * The reference is kept in the driver and used all the time to + * manage the nv device, therefore it must always exist. + */ +typedef struct rtems_nvdisk_device_desc +{ + uint32_t flags; /**< Private user flags. */ + void* base; /**< Base address of the device. */ + uint32_t size; /**< Size of the device. */ + const rtems_nvdisk_driver_handlers* nv_ops; /**< Device handlers. */ +} rtems_nvdisk_device_desc; + +/** + * RTEMS Non-Volatile Disk configuration table used to initialise the + * driver. + */ +typedef struct rtems_nvdisk_config +{ + uint32_t block_size; /**< The block size. */ + uint32_t device_count; /**< The number of devices. */ + const rtems_nvdisk_device_desc* devices; /**< The device descriptions. */ + uint32_t flags; /**< Set of flags to control + driver. */ + uint32_t info_level; /**< Default info level. */ +} rtems_nvdisk_config; + +/* + * Driver flags. + */ + +/** + * Check the pages during initialisation to see which pages are + * valid and which are not. This could slow down initialising the + * disk driver. + */ +#define RTEMS_NVDISK_CHECK_PAGES (1 << 0) + +/** + * Non-volatile disk device driver initialization. Place in a table as the + * initialisation entry and remainder of the entries are the RTEMS block + * device generic handlers. + * + * @param major NV disk major device number. + * @param minor Minor device number, not applicable. + * @param arg Initialization argument, not applicable. + * @return The rtems_device_driver is actually just + * rtems_status_code. + */ +rtems_device_driver +rtems_nvdisk_initialize (rtems_device_major_number major, + rtems_device_minor_number minor, + void* arg); + +/** + * External reference to the configuration. Please supply. + * Support is present in confdefs.h for providing this variable. + */ +extern const rtems_nvdisk_config rtems_nvdisk_configuration[]; + +/** + * External reference to the number of configurations. Please supply. + * Support is present in confdefs.h for providing this variable. + */ +extern uint32_t rtems_nvdisk_configuration_size; + +#endif diff --git a/cpukit/libblock/include/rtems/ramdisk.h b/cpukit/libblock/include/rtems/ramdisk.h new file mode 100644 index 0000000000..71efc9fda0 --- /dev/null +++ b/cpukit/libblock/include/rtems/ramdisk.h @@ -0,0 +1,243 @@ +/** + * @file + * + * @ingroup rtems_ramdisk + * + * @brief RAM disk block device API. + */ + +/* + * Copyright (C) 2001 OKTET Ltd., St.-Petersburg, Russia + * Author: Victor V. Vengerov <vvv@oktet.ru> + * + * @(#) $Id$ + */ + +#ifndef _RTEMS_RAMDISK_H +#define _RTEMS_RAMDISK_H + + +#include <rtems.h> +#include <rtems/blkdev.h> + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @defgroup rtems_ramdisk RAM Disk Device + * + * @ingroup rtems_blkdev + * + * @{ + */ + +/** + * @name Static Configuration + * + * @{ + */ + +/** + * @brief RAM disk configuration table entry. + */ +typedef struct rtems_ramdisk_config { + /** + * @brief RAM disk block size. + */ + uint32_t block_size; + + /** + * @brief Number of blocks on this RAM disk. + */ + rtems_blkdev_bnum block_num; + + /** + * @brief RAM disk location or @c NULL if RAM disk memory should be allocated + * dynamically. + */ + void *location; +} rtems_ramdisk_config; + +/** + * @brief External reference to the RAM disk configuration table describing + * each RAM disk in the system. + * + * The configuration table is provided by the application. + */ +extern rtems_ramdisk_config rtems_ramdisk_configuration []; + +/** + * @brief External reference the size of the RAM disk configuration table @ref + * rtems_ramdisk_configuration. + * + * The configuration table size is provided by the application. + */ +extern size_t rtems_ramdisk_configuration_size; + +/** + * @brief RAM disk driver initialization entry point. + */ +rtems_device_driver ramdisk_initialize( + rtems_device_major_number major, + rtems_device_minor_number minor, + void *arg +); + +/** + * RAM disk driver table entry. + */ +#define RAMDISK_DRIVER_TABLE_ENTRY \ + { \ + .initialization_entry = ramdisk_initialize, \ + RTEMS_GENERIC_BLOCK_DEVICE_DRIVER_ENTRIES \ + } + +#define RAMDISK_DEVICE_BASE_NAME "/dev/rd" + +/** @} */ + +/** + * @name Runtime Configuration + * + * @{ + */ + +/** + * @brief RAM disk descriptor. + */ +typedef struct ramdisk { + /** + * @brief RAM disk block size, the media size. + */ + uint32_t block_size; + + /** + * @brief Number of blocks on this RAM disk. + */ + rtems_blkdev_bnum block_num; + + /** + * @brief RAM disk memory area. + */ + void *area; + + /** + * @brief RAM disk is initialized. + */ + bool initialized; + + /** + * @brief Indicates if memory is allocated by malloc() for this RAM disk. + */ + bool malloced; + + /** + * @brief Trace enable. + */ + bool trace; +} ramdisk; + +extern const rtems_driver_address_table ramdisk_ops; + +int ramdisk_ioctl(rtems_disk_device *dd, uint32_t req, void *argp); + +/** + * @brief Allocates and initializes a RAM disk descriptor. + * + * The block size will be @a block_size. The block count will be @a + * block_count. The disk storage area begins at @a area_begin. If @a + * area_begin is @c NULL, the memory will be allocated and zeroed. Sets the + * trace enable to @a trace. + * + * @return Pointer to allocated and initialized ramdisk structure, or @c NULL + * if no memory is available. + * + * @note + * Runtime configuration example: + * @code + * #include <rtems.h> + * #include <rtems/libio.h> + * #include <rtems/ramdisk.h> + * + * rtems_status_code create_ramdisk( + * const char *disk_name_path, + * uint32_t block_size, + * rtems_blkdev_bnum block_count + * ) + * { + * rtems_status_code sc = RTEMS_SUCCESSFUL; + * rtems_device_major_number major = 0; + * ramdisk *rd = NULL; + * dev_t dev = 0; + * + * sc = rtems_io_register_driver(0, &ramdisk_ops, &major); + * if (sc != RTEMS_SUCCESSFUL) { + * return RTEMS_UNSATISFIED; + * } + * + * rd = ramdisk_allocate(NULL, block_size, block_count, false); + * if (rd == NULL) { + * rtems_io_unregister_driver(major); + * + * return RTEMS_UNSATISFIED; + * } + * + * dev = rtems_filesystem_make_dev_t(major, 0); + * + * sc = rtems_disk_create_phys( + * dev, + * block_size, + * block_count, + * ramdisk_ioctl, + * rd, + * disk_name_path + * ); + * if (sc != RTEMS_SUCCESSFUL) { + * ramdisk_free(rd); + * rtems_io_unregister_driver(major); + * + * return RTEMS_UNSATISFIED; + * } + * + * return RTEMS_SUCCESSFUL; + * } + * @endcode + */ +ramdisk *ramdisk_allocate( + void *area_begin, + uint32_t block_size, + rtems_blkdev_bnum block_count, + bool trace +); + +void ramdisk_free(ramdisk *rd); + +/** + * @brief Allocates, initializes and registers a RAM disk. + * + * The block size will be @a block_size. The block count will be @a + * block_count. The disk storage will be allocated. Sets the trace enable to + * @a trace. Registers a device node with disk name path @a disk. The + * registered device number will be returned in @a dev. + * + * @retval RTEMS_SUCCESSFUL Successful operation. + * @retval RTEMS_UNSATISFIED Something is wrong. + */ +rtems_status_code ramdisk_register( + uint32_t block_size, + rtems_blkdev_bnum block_count, + bool trace, + const char *disk, + dev_t *dev +); + +/** @} */ + +/** @} */ + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/cpukit/libblock/src/bdbuf.c b/cpukit/libblock/src/bdbuf.c new file mode 100644 index 0000000000..841c03bdbe --- /dev/null +++ b/cpukit/libblock/src/bdbuf.c @@ -0,0 +1,2989 @@ +/** + * @file + * + * @ingroup rtems_bdbuf + * + * Block device buffer management. + */ + +/* + * Disk I/O buffering + * Buffer managment + * + * Copyright (C) 2001 OKTET Ltd., St.-Peterburg, Russia + * Author: Andrey G. Ivanov <Andrey.Ivanov@oktet.ru> + * Victor V. Vengerov <vvv@oktet.ru> + * Alexander Kukuta <kam@oktet.ru> + * + * Copyright (C) 2008,2009 Chris Johns <chrisj@rtems.org> + * Rewritten to remove score mutex access. Fixes many performance + * issues. + * + * Copyright (c) 2009 embedded brains GmbH. + * + * @(#) bdbuf.c,v 1.14 2004/04/17 08:15:17 ralf Exp + */ + +/** + * Set to 1 to enable debug tracing. + */ +#define RTEMS_BDBUF_TRACE 0 + +#if HAVE_CONFIG_H +#include "config.h" +#endif +#include <limits.h> +#include <errno.h> +#include <stdio.h> +#include <string.h> +#include <inttypes.h> + +#include <rtems.h> +#include <rtems/error.h> +#include <rtems/malloc.h> + +#include "rtems/bdbuf.h" + +#define BDBUF_INVALID_DEV ((dev_t) -1) + +/* + * Simpler label for this file. + */ +#define bdbuf_config rtems_bdbuf_configuration + +/** + * A swapout transfer transaction data. This data is passed to a worked thread + * to handle the write phase of the transfer. + */ +typedef struct rtems_bdbuf_swapout_transfer +{ + rtems_chain_control bds; /**< The transfer list of BDs. */ + dev_t dev; /**< The device the transfer is for. */ + bool syncing; /**< The data is a sync'ing. */ + rtems_blkdev_request* write_req; /**< The write request array. */ + uint32_t bufs_per_bd; /**< Number of buffers per bd. */ +} rtems_bdbuf_swapout_transfer; + +/** + * Swapout worker thread. These are available to take processing from the + * main swapout thread and handle the I/O operation. + */ +typedef struct rtems_bdbuf_swapout_worker +{ + rtems_chain_node link; /**< The threads sit on a chain when + * idle. */ + rtems_id id; /**< The id of the task so we can wake + * it. */ + volatile bool enabled; /**< The worker is enabled. */ + rtems_bdbuf_swapout_transfer transfer; /**< The transfer data for this + * thread. */ +} rtems_bdbuf_swapout_worker; + +/** + * Buffer waiters synchronization. + */ +typedef struct rtems_bdbuf_waiters { + volatile unsigned count; + rtems_id sema; +} rtems_bdbuf_waiters; + +/** + * The BD buffer cache. + */ +typedef struct rtems_bdbuf_cache +{ + rtems_id swapout; /**< Swapout task ID */ + volatile bool swapout_enabled; /**< Swapout is only running if + * enabled. Set to false to kill the + * swap out task. It deletes itself. */ + rtems_chain_control swapout_workers; /**< The work threads for the swapout + * task. */ + + rtems_bdbuf_buffer* bds; /**< Pointer to table of buffer + * descriptors. */ + void* buffers; /**< The buffer's memory. */ + size_t buffer_min_count; /**< Number of minimum size buffers + * that fit the buffer memory. */ + size_t max_bds_per_group; /**< The number of BDs of minimum + * buffer size that fit in a group. */ + uint32_t flags; /**< Configuration flags. */ + + rtems_id lock; /**< The cache lock. It locks all + * cache data, BD and lists. */ + rtems_id sync_lock; /**< Sync calls block writes. */ + volatile bool sync_active; /**< True if a sync is active. */ + volatile rtems_id sync_requester; /**< The sync requester. */ + volatile dev_t sync_device; /**< The device to sync and + * BDBUF_INVALID_DEV not a device + * sync. */ + + rtems_bdbuf_buffer* tree; /**< Buffer descriptor lookup AVL tree + * root. There is only one. */ + rtems_chain_control lru; /**< Least recently used list */ + rtems_chain_control modified; /**< Modified buffers list */ + rtems_chain_control sync; /**< Buffers to sync list */ + + rtems_bdbuf_waiters access_waiters; /**< Wait for a buffer in + * ACCESS_CACHED, ACCESS_MODIFIED or + * ACCESS_EMPTY + * state. */ + rtems_bdbuf_waiters transfer_waiters; /**< Wait for a buffer in TRANSFER + * state. */ + rtems_bdbuf_waiters buffer_waiters; /**< Wait for a buffer and no one is + * available. */ + + size_t group_count; /**< The number of groups. */ + rtems_bdbuf_group* groups; /**< The groups. */ + + bool initialised; /**< Initialised state. */ +} rtems_bdbuf_cache; + +/** + * Fatal errors + */ +#define RTEMS_BLKDEV_FATAL_ERROR(n) \ + (((uint32_t)'B' << 24) | ((uint32_t)(n) & (uint32_t)0x00FFFFFF)) + +#define RTEMS_BLKDEV_FATAL_BDBUF_STATE_11 RTEMS_BLKDEV_FATAL_ERROR(1) +#define RTEMS_BLKDEV_FATAL_BDBUF_STATE_4 RTEMS_BLKDEV_FATAL_ERROR(2) +#define RTEMS_BLKDEV_FATAL_BDBUF_STATE_5 RTEMS_BLKDEV_FATAL_ERROR(3) +#define RTEMS_BLKDEV_FATAL_BDBUF_STATE_6 RTEMS_BLKDEV_FATAL_ERROR(4) +#define RTEMS_BLKDEV_FATAL_BDBUF_STATE_7 RTEMS_BLKDEV_FATAL_ERROR(5) +#define RTEMS_BLKDEV_FATAL_BDBUF_STATE_8 RTEMS_BLKDEV_FATAL_ERROR(6) +#define RTEMS_BLKDEV_FATAL_BDBUF_STATE_9 RTEMS_BLKDEV_FATAL_ERROR(7) +#define RTEMS_BLKDEV_FATAL_BDBUF_STATE_10 RTEMS_BLKDEV_FATAL_ERROR(8) +#define RTEMS_BLKDEV_FATAL_BDBUF_TREE_RM RTEMS_BLKDEV_FATAL_ERROR(9) +#define RTEMS_BLKDEV_FATAL_BDBUF_SWAPOUT RTEMS_BLKDEV_FATAL_ERROR(10) +#define RTEMS_BLKDEV_FATAL_BDBUF_SYNC_LOCK RTEMS_BLKDEV_FATAL_ERROR(11) +#define RTEMS_BLKDEV_FATAL_BDBUF_SYNC_UNLOCK RTEMS_BLKDEV_FATAL_ERROR(12) +#define RTEMS_BLKDEV_FATAL_BDBUF_CACHE_LOCK RTEMS_BLKDEV_FATAL_ERROR(13) +#define RTEMS_BLKDEV_FATAL_BDBUF_CACHE_UNLOCK RTEMS_BLKDEV_FATAL_ERROR(14) +#define RTEMS_BLKDEV_FATAL_BDBUF_PREEMPT_DIS RTEMS_BLKDEV_FATAL_ERROR(15) +#define RTEMS_BLKDEV_FATAL_BDBUF_CACHE_WAIT_2 RTEMS_BLKDEV_FATAL_ERROR(16) +#define RTEMS_BLKDEV_FATAL_BDBUF_PREEMPT_RST RTEMS_BLKDEV_FATAL_ERROR(17) +#define RTEMS_BLKDEV_FATAL_BDBUF_CACHE_WAIT_TO RTEMS_BLKDEV_FATAL_ERROR(18) +#define RTEMS_BLKDEV_FATAL_BDBUF_CACHE_WAKE RTEMS_BLKDEV_FATAL_ERROR(19) +#define RTEMS_BLKDEV_FATAL_BDBUF_SO_WAKE RTEMS_BLKDEV_FATAL_ERROR(20) +#define RTEMS_BLKDEV_FATAL_BDBUF_SO_NOMEM RTEMS_BLKDEV_FATAL_ERROR(21) +#define RTEMS_BLKDEV_FATAL_BDBUF_SO_WK_CREATE RTEMS_BLKDEV_FATAL_ERROR(22) +#define RTEMS_BLKDEV_FATAL_BDBUF_SO_WK_START RTEMS_BLKDEV_FATAL_ERROR(23) +#define BLKDEV_FATAL_BDBUF_SWAPOUT_RE RTEMS_BLKDEV_FATAL_ERROR(24) +#define BLKDEV_FATAL_BDBUF_SWAPOUT_TS RTEMS_BLKDEV_FATAL_ERROR(25) +#define RTEMS_BLKDEV_FATAL_BDBUF_WAIT_EVNT RTEMS_BLKDEV_FATAL_ERROR(26) +#define RTEMS_BLKDEV_FATAL_BDBUF_RECYCLE RTEMS_BLKDEV_FATAL_ERROR(27) +#define RTEMS_BLKDEV_FATAL_BDBUF_STATE_0 RTEMS_BLKDEV_FATAL_ERROR(28) +#define RTEMS_BLKDEV_FATAL_BDBUF_STATE_1 RTEMS_BLKDEV_FATAL_ERROR(29) +#define RTEMS_BLKDEV_FATAL_BDBUF_STATE_2 RTEMS_BLKDEV_FATAL_ERROR(30) +#define RTEMS_BLKDEV_FATAL_BDBUF_DISK_REL RTEMS_BLKDEV_FATAL_ERROR(31) + +/** + * The events used in this code. These should be system events rather than + * application events. + */ +#define RTEMS_BDBUF_TRANSFER_SYNC RTEMS_EVENT_1 +#define RTEMS_BDBUF_SWAPOUT_SYNC RTEMS_EVENT_2 + +/** + * The swap out task size. Should be more than enough for most drivers with + * tracing turned on. + */ +#define SWAPOUT_TASK_STACK_SIZE (8 * 1024) + +/** + * Lock semaphore attributes. This is used for locking type mutexes. + * + * @warning Priority inheritance is on. + */ +#define RTEMS_BDBUF_CACHE_LOCK_ATTRIBS \ + (RTEMS_PRIORITY | RTEMS_BINARY_SEMAPHORE | \ + RTEMS_INHERIT_PRIORITY | RTEMS_NO_PRIORITY_CEILING | RTEMS_LOCAL) + +/** + * Waiter semaphore attributes. + * + * @warning Do not configure as inherit priority. If a driver is in the driver + * initialisation table this locked semaphore will have the IDLE task + * as the holder and a blocking task will raise the priority of the + * IDLE task which can cause unsual side effects. + */ +#define RTEMS_BDBUF_CACHE_WAITER_ATTRIBS \ + (RTEMS_PRIORITY | RTEMS_SIMPLE_BINARY_SEMAPHORE | \ + RTEMS_NO_INHERIT_PRIORITY | RTEMS_NO_PRIORITY_CEILING | RTEMS_LOCAL) + +/** + * Waiter timeout. Set to non-zero to find some info on a waiter that is + * waiting too long. + */ +#define RTEMS_BDBUF_WAIT_TIMEOUT RTEMS_NO_TIMEOUT +#if !defined (RTEMS_BDBUF_WAIT_TIMEOUT) +#define RTEMS_BDBUF_WAIT_TIMEOUT \ + (TOD_MICROSECONDS_TO_TICKS (20000000)) +#endif + +/* + * The swap out task. + */ +static rtems_task rtems_bdbuf_swapout_task(rtems_task_argument arg); + +/** + * The Buffer Descriptor cache. + */ +static rtems_bdbuf_cache bdbuf_cache; + +#if RTEMS_BDBUF_TRACE +/** + * If true output the trace message. + */ +bool rtems_bdbuf_tracer; + +/** + * Return the number of items on the list. + * + * @param list The chain control. + * @return uint32_t The number of items on the list. + */ +uint32_t +rtems_bdbuf_list_count (rtems_chain_control* list) +{ + rtems_chain_node* node = rtems_chain_first (list); + uint32_t count = 0; + while (!rtems_chain_is_tail (list, node)) + { + count++; + node = rtems_chain_next (node); + } + return count; +} + +/** + * Show the usage for the bdbuf cache. + */ +void +rtems_bdbuf_show_usage (void) +{ + uint32_t group; + uint32_t total = 0; + uint32_t val; + + for (group = 0; group < bdbuf_cache.group_count; group++) + total += bdbuf_cache.groups[group].users; + printf ("bdbuf:group users=%lu", total); + val = rtems_bdbuf_list_count (&bdbuf_cache.lru); + printf (", lru=%lu", val); + total = val; + val = rtems_bdbuf_list_count (&bdbuf_cache.modified); + printf (", mod=%lu", val); + total += val; + val = rtems_bdbuf_list_count (&bdbuf_cache.sync); + printf (", sync=%lu", val); + total += val; + printf (", total=%lu\n", total); +} + +/** + * Show the users for a group of a bd. + * + * @param where A label to show the context of output. + * @param bd The bd to show the users of. + */ +void +rtems_bdbuf_show_users (const char* where, rtems_bdbuf_buffer* bd) +{ + const char* states[] = + { "EM", "FR", "CH", "AC", "AM", "MD", "SY", "TR" }; + + printf ("bdbuf:users: %15s: [%" PRIu32 " (%s)] %td:%td = %" PRIu32 " %s\n", + where, + bd->block, states[bd->state], + bd->group - bdbuf_cache.groups, + bd - bdbuf_cache.bds, + bd->group->users, + bd->group->users > 8 ? "<<<<<<<" : ""); +} +#else +#define rtems_bdbuf_tracer (0) +#define rtems_bdbuf_show_usage() ((void) 0) +#define rtems_bdbuf_show_users(_w, _b) ((void) 0) +#endif + +/** + * The default maximum height of 32 allows for AVL trees having between + * 5,704,880 and 4,294,967,295 nodes, depending on order of insertion. You may + * change this compile-time constant as you wish. + */ +#ifndef RTEMS_BDBUF_AVL_MAX_HEIGHT +#define RTEMS_BDBUF_AVL_MAX_HEIGHT (32) +#endif + +static void +rtems_bdbuf_fatal (rtems_bdbuf_buf_state state, uint32_t error) +{ + rtems_fatal_error_occurred ((((uint32_t) state) << 16) | error); +} + +/** + * Searches for the node with specified dev/block. + * + * @param root pointer to the root node of the AVL-Tree + * @param dev device search key + * @param block block search key + * @retval NULL node with the specified dev/block is not found + * @return pointer to the node with specified dev/block + */ +static rtems_bdbuf_buffer * +rtems_bdbuf_avl_search (rtems_bdbuf_buffer** root, + dev_t dev, + rtems_blkdev_bnum block) +{ + rtems_bdbuf_buffer* p = *root; + + while ((p != NULL) && ((p->dev != dev) || (p->block != block))) + { + if ((p->dev < dev) || ((p->dev == dev) && (p->block < block))) + { + p = p->avl.right; + } + else + { + p = p->avl.left; + } + } + + return p; +} + +/** + * Inserts the specified node to the AVl-Tree. + * + * @param root pointer to the root node of the AVL-Tree + * @param node Pointer to the node to add. + * @retval 0 The node added successfully + * @retval -1 An error occured + */ +static int +rtems_bdbuf_avl_insert(rtems_bdbuf_buffer** root, + rtems_bdbuf_buffer* node) +{ + dev_t dev = node->dev; + rtems_blkdev_bnum block = node->block; + + rtems_bdbuf_buffer* p = *root; + rtems_bdbuf_buffer* q; + rtems_bdbuf_buffer* p1; + rtems_bdbuf_buffer* p2; + rtems_bdbuf_buffer* buf_stack[RTEMS_BDBUF_AVL_MAX_HEIGHT]; + rtems_bdbuf_buffer** buf_prev = buf_stack; + + bool modified = false; + + if (p == NULL) + { + *root = node; + node->avl.left = NULL; + node->avl.right = NULL; + node->avl.bal = 0; + return 0; + } + + while (p != NULL) + { + *buf_prev++ = p; + + if ((p->dev < dev) || ((p->dev == dev) && (p->block < block))) + { + p->avl.cache = 1; + q = p->avl.right; + if (q == NULL) + { + q = node; + p->avl.right = q = node; + break; + } + } + else if ((p->dev != dev) || (p->block != block)) + { + p->avl.cache = -1; + q = p->avl.left; + if (q == NULL) + { + q = node; + p->avl.left = q; + break; + } + } + else + { + return -1; + } + + p = q; + } + + q->avl.left = q->avl.right = NULL; + q->avl.bal = 0; + modified = true; + buf_prev--; + + while (modified) + { + if (p->avl.cache == -1) + { + switch (p->avl.bal) + { + case 1: + p->avl.bal = 0; + modified = false; + break; + + case 0: + p->avl.bal = -1; + break; + + case -1: + p1 = p->avl.left; + if (p1->avl.bal == -1) /* simple LL-turn */ + { + p->avl.left = p1->avl.right; + p1->avl.right = p; + p->avl.bal = 0; + p = p1; + } + else /* double LR-turn */ + { + p2 = p1->avl.right; + p1->avl.right = p2->avl.left; + p2->avl.left = p1; + p->avl.left = p2->avl.right; + p2->avl.right = p; + if (p2->avl.bal == -1) p->avl.bal = +1; else p->avl.bal = 0; + if (p2->avl.bal == +1) p1->avl.bal = -1; else p1->avl.bal = 0; + p = p2; + } + p->avl.bal = 0; + modified = false; + break; + + default: + break; + } + } + else + { + switch (p->avl.bal) + { + case -1: + p->avl.bal = 0; + modified = false; + break; + + case 0: + p->avl.bal = 1; + break; + + case 1: + p1 = p->avl.right; + if (p1->avl.bal == 1) /* simple RR-turn */ + { + p->avl.right = p1->avl.left; + p1->avl.left = p; + p->avl.bal = 0; + p = p1; + } + else /* double RL-turn */ + { + p2 = p1->avl.left; + p1->avl.left = p2->avl.right; + p2->avl.right = p1; + p->avl.right = p2->avl.left; + p2->avl.left = p; + if (p2->avl.bal == +1) p->avl.bal = -1; else p->avl.bal = 0; + if (p2->avl.bal == -1) p1->avl.bal = +1; else p1->avl.bal = 0; + p = p2; + } + p->avl.bal = 0; + modified = false; + break; + + default: + break; + } + } + q = p; + if (buf_prev > buf_stack) + { + p = *--buf_prev; + + if (p->avl.cache == -1) + { + p->avl.left = q; + } + else + { + p->avl.right = q; + } + } + else + { + *root = p; + break; + } + }; + + return 0; +} + + +/** + * Removes the node from the tree. + * + * @param root Pointer to pointer to the root node + * @param node Pointer to the node to remove + * @retval 0 Item removed + * @retval -1 No such item found + */ +static int +rtems_bdbuf_avl_remove(rtems_bdbuf_buffer** root, + const rtems_bdbuf_buffer* node) +{ + dev_t dev = node->dev; + rtems_blkdev_bnum block = node->block; + + rtems_bdbuf_buffer* p = *root; + rtems_bdbuf_buffer* q; + rtems_bdbuf_buffer* r; + rtems_bdbuf_buffer* s; + rtems_bdbuf_buffer* p1; + rtems_bdbuf_buffer* p2; + rtems_bdbuf_buffer* buf_stack[RTEMS_BDBUF_AVL_MAX_HEIGHT]; + rtems_bdbuf_buffer** buf_prev = buf_stack; + + bool modified = false; + + memset (buf_stack, 0, sizeof(buf_stack)); + + while (p != NULL) + { + *buf_prev++ = p; + + if ((p->dev < dev) || ((p->dev == dev) && (p->block < block))) + { + p->avl.cache = 1; + p = p->avl.right; + } + else if ((p->dev != dev) || (p->block != block)) + { + p->avl.cache = -1; + p = p->avl.left; + } + else + { + /* node found */ + break; + } + } + + if (p == NULL) + { + /* there is no such node */ + return -1; + } + + q = p; + + buf_prev--; + if (buf_prev > buf_stack) + { + p = *(buf_prev - 1); + } + else + { + p = NULL; + } + + /* at this moment q - is a node to delete, p is q's parent */ + if (q->avl.right == NULL) + { + r = q->avl.left; + if (r != NULL) + { + r->avl.bal = 0; + } + q = r; + } + else + { + rtems_bdbuf_buffer **t; + + r = q->avl.right; + + if (r->avl.left == NULL) + { + r->avl.left = q->avl.left; + r->avl.bal = q->avl.bal; + r->avl.cache = 1; + *buf_prev++ = q = r; + } + else + { + t = buf_prev++; + s = r; + + while (s->avl.left != NULL) + { + *buf_prev++ = r = s; + s = r->avl.left; + r->avl.cache = -1; + } + + s->avl.left = q->avl.left; + r->avl.left = s->avl.right; + s->avl.right = q->avl.right; + s->avl.bal = q->avl.bal; + s->avl.cache = 1; + + *t = q = s; + } + } + + if (p != NULL) + { + if (p->avl.cache == -1) + { + p->avl.left = q; + } + else + { + p->avl.right = q; + } + } + else + { + *root = q; + } + + modified = true; + + while (modified) + { + if (buf_prev > buf_stack) + { + p = *--buf_prev; + } + else + { + break; + } + + if (p->avl.cache == -1) + { + /* rebalance left branch */ + switch (p->avl.bal) + { + case -1: + p->avl.bal = 0; + break; + case 0: + p->avl.bal = 1; + modified = false; + break; + + case +1: + p1 = p->avl.right; + + if (p1->avl.bal >= 0) /* simple RR-turn */ + { + p->avl.right = p1->avl.left; + p1->avl.left = p; + + if (p1->avl.bal == 0) + { + p1->avl.bal = -1; + modified = false; + } + else + { + p->avl.bal = 0; + p1->avl.bal = 0; + } + p = p1; + } + else /* double RL-turn */ + { + p2 = p1->avl.left; + + p1->avl.left = p2->avl.right; + p2->avl.right = p1; + p->avl.right = p2->avl.left; + p2->avl.left = p; + + if (p2->avl.bal == +1) p->avl.bal = -1; else p->avl.bal = 0; + if (p2->avl.bal == -1) p1->avl.bal = 1; else p1->avl.bal = 0; + + p = p2; + p2->avl.bal = 0; + } + break; + + default: + break; + } + } + else + { + /* rebalance right branch */ + switch (p->avl.bal) + { + case +1: + p->avl.bal = 0; + break; + + case 0: + p->avl.bal = -1; + modified = false; + break; + + case -1: + p1 = p->avl.left; + + if (p1->avl.bal <= 0) /* simple LL-turn */ + { + p->avl.left = p1->avl.right; + p1->avl.right = p; + if (p1->avl.bal == 0) + { + p1->avl.bal = 1; + modified = false; + } + else + { + p->avl.bal = 0; + p1->avl.bal = 0; + } + p = p1; + } + else /* double LR-turn */ + { + p2 = p1->avl.right; + + p1->avl.right = p2->avl.left; + p2->avl.left = p1; + p->avl.left = p2->avl.right; + p2->avl.right = p; + + if (p2->avl.bal == -1) p->avl.bal = 1; else p->avl.bal = 0; + if (p2->avl.bal == +1) p1->avl.bal = -1; else p1->avl.bal = 0; + + p = p2; + p2->avl.bal = 0; + } + break; + + default: + break; + } + } + + if (buf_prev > buf_stack) + { + q = *(buf_prev - 1); + + if (q->avl.cache == -1) + { + q->avl.left = p; + } + else + { + q->avl.right = p; + } + } + else + { + *root = p; + break; + } + + } + + return 0; +} + +static void +rtems_bdbuf_set_state (rtems_bdbuf_buffer *bd, rtems_bdbuf_buf_state state) +{ + bd->state = state; +} + +/** + * Change the block number for the block size to the block number for the media + * block size. We have to use 64bit maths. There is no short cut here. + * + * @param block The logical block number in the block size terms. + * @param block_size The block size. + * @param media_block_size The block size of the media. + * @return rtems_blkdev_bnum The media block number. + */ +static rtems_blkdev_bnum +rtems_bdbuf_media_block (rtems_blkdev_bnum block, + size_t block_size, + size_t media_block_size) +{ + return (rtems_blkdev_bnum) + ((((uint64_t) block) * block_size) / media_block_size); +} + +/** + * Lock the mutex. A single task can nest calls. + * + * @param lock The mutex to lock. + * @param fatal_error_code The error code if the call fails. + */ +static void +rtems_bdbuf_lock (rtems_id lock, uint32_t fatal_error_code) +{ + rtems_status_code sc = rtems_semaphore_obtain (lock, + RTEMS_WAIT, + RTEMS_NO_TIMEOUT); + if (sc != RTEMS_SUCCESSFUL) + rtems_fatal_error_occurred (fatal_error_code); +} + +/** + * Unlock the mutex. + * + * @param lock The mutex to unlock. + * @param fatal_error_code The error code if the call fails. + */ +static void +rtems_bdbuf_unlock (rtems_id lock, uint32_t fatal_error_code) +{ + rtems_status_code sc = rtems_semaphore_release (lock); + if (sc != RTEMS_SUCCESSFUL) + rtems_fatal_error_occurred (fatal_error_code); +} + +/** + * Lock the cache. A single task can nest calls. + */ +static void +rtems_bdbuf_lock_cache (void) +{ + rtems_bdbuf_lock (bdbuf_cache.lock, RTEMS_BLKDEV_FATAL_BDBUF_CACHE_LOCK); +} + +/** + * Unlock the cache. + */ +static void +rtems_bdbuf_unlock_cache (void) +{ + rtems_bdbuf_unlock (bdbuf_cache.lock, RTEMS_BLKDEV_FATAL_BDBUF_CACHE_UNLOCK); +} + +/** + * Lock the cache's sync. A single task can nest calls. + */ +static void +rtems_bdbuf_lock_sync (void) +{ + rtems_bdbuf_lock (bdbuf_cache.sync_lock, RTEMS_BLKDEV_FATAL_BDBUF_SYNC_LOCK); +} + +/** + * Unlock the cache's sync lock. Any blocked writers are woken. + */ +static void +rtems_bdbuf_unlock_sync (void) +{ + rtems_bdbuf_unlock (bdbuf_cache.sync_lock, + RTEMS_BLKDEV_FATAL_BDBUF_SYNC_UNLOCK); +} + +static void +rtems_bdbuf_group_obtain (rtems_bdbuf_buffer *bd) +{ + ++bd->group->users; +} + +static void +rtems_bdbuf_group_release (rtems_bdbuf_buffer *bd) +{ + --bd->group->users; +} + +static rtems_mode +rtems_bdbuf_disable_preemption (void) +{ + rtems_status_code sc = RTEMS_SUCCESSFUL; + rtems_mode prev_mode = 0; + + sc = rtems_task_mode (RTEMS_NO_PREEMPT, RTEMS_PREEMPT_MASK, &prev_mode); + if (sc != RTEMS_SUCCESSFUL) + rtems_fatal_error_occurred (RTEMS_BLKDEV_FATAL_BDBUF_PREEMPT_DIS); + + return prev_mode; +} + +static void +rtems_bdbuf_restore_preemption (rtems_mode prev_mode) +{ + rtems_status_code sc = RTEMS_SUCCESSFUL; + + sc = rtems_task_mode (prev_mode, RTEMS_ALL_MODE_MASKS, &prev_mode); + if (sc != RTEMS_SUCCESSFUL) + rtems_fatal_error_occurred (RTEMS_BLKDEV_FATAL_BDBUF_PREEMPT_RST); +} + +/** + * Wait until woken. Semaphores are used so a number of tasks can wait and can + * be woken at once. Task events would require we maintain a list of tasks to + * be woken and this would require storage and we do not know the number of + * tasks that could be waiting. + * + * While we have the cache locked we can try and claim the semaphore and + * therefore know when we release the lock to the cache we will block until the + * semaphore is released. This may even happen before we get to block. + * + * A counter is used to save the release call when no one is waiting. + * + * The function assumes the cache is locked on entry and it will be locked on + * exit. + */ +static void +rtems_bdbuf_anonymous_wait (rtems_bdbuf_waiters *waiters) +{ + rtems_status_code sc; + rtems_mode prev_mode; + + /* + * Indicate we are waiting. + */ + ++waiters->count; + + /* + * Disable preemption then unlock the cache and block. There is no POSIX + * condition variable in the core API so this is a work around. + * + * The issue is a task could preempt after the cache is unlocked because it is + * blocking or just hits that window, and before this task has blocked on the + * semaphore. If the preempting task flushes the queue this task will not see + * the flush and may block for ever or until another transaction flushes this + * semaphore. + */ + prev_mode = rtems_bdbuf_disable_preemption (); + + /* + * Unlock the cache, wait, and lock the cache when we return. + */ + rtems_bdbuf_unlock_cache (); + + sc = rtems_semaphore_obtain (waiters->sema, RTEMS_WAIT, RTEMS_BDBUF_WAIT_TIMEOUT); + + if (sc == RTEMS_TIMEOUT) + rtems_fatal_error_occurred (RTEMS_BLKDEV_FATAL_BDBUF_CACHE_WAIT_TO); + + if (sc != RTEMS_UNSATISFIED) + rtems_fatal_error_occurred (RTEMS_BLKDEV_FATAL_BDBUF_CACHE_WAIT_2); + + rtems_bdbuf_lock_cache (); + + rtems_bdbuf_restore_preemption (prev_mode); + + --waiters->count; +} + +static void +rtems_bdbuf_wait (rtems_bdbuf_buffer *bd, rtems_bdbuf_waiters *waiters) +{ + rtems_bdbuf_group_obtain (bd); + ++bd->waiters; + rtems_bdbuf_anonymous_wait (waiters); + --bd->waiters; + rtems_bdbuf_group_release (bd); +} + +/** + * Wake a blocked resource. The resource has a counter that lets us know if + * there are any waiters. + */ +static void +rtems_bdbuf_wake (const rtems_bdbuf_waiters *waiters) +{ + rtems_status_code sc = RTEMS_SUCCESSFUL; + + if (waiters->count > 0) + { + sc = rtems_semaphore_flush (waiters->sema); + if (sc != RTEMS_SUCCESSFUL) + rtems_fatal_error_occurred (RTEMS_BLKDEV_FATAL_BDBUF_CACHE_WAKE); + } +} + +static void +rtems_bdbuf_wake_swapper (void) +{ + rtems_status_code sc = rtems_event_send (bdbuf_cache.swapout, + RTEMS_BDBUF_SWAPOUT_SYNC); + if (sc != RTEMS_SUCCESSFUL) + rtems_fatal_error_occurred (RTEMS_BLKDEV_FATAL_BDBUF_SO_WAKE); +} + +static bool +rtems_bdbuf_has_buffer_waiters (void) +{ + return bdbuf_cache.buffer_waiters.count; +} + +static void +rtems_bdbuf_remove_from_tree (rtems_bdbuf_buffer *bd) +{ + if (rtems_bdbuf_avl_remove (&bdbuf_cache.tree, bd) != 0) + rtems_bdbuf_fatal (bd->state, RTEMS_BLKDEV_FATAL_BDBUF_TREE_RM); +} + +static void +rtems_bdbuf_remove_from_tree_and_lru_list (rtems_bdbuf_buffer *bd) +{ + switch (bd->state) + { + case RTEMS_BDBUF_STATE_FREE: + break; + case RTEMS_BDBUF_STATE_CACHED: + rtems_bdbuf_remove_from_tree (bd); + break; + default: + rtems_bdbuf_fatal (bd->state, RTEMS_BLKDEV_FATAL_BDBUF_STATE_10); + } + + rtems_chain_extract (&bd->link); +} + +static void +rtems_bdbuf_make_free_and_add_to_lru_list (rtems_bdbuf_buffer *bd) +{ + rtems_bdbuf_set_state (bd, RTEMS_BDBUF_STATE_FREE); + rtems_chain_prepend (&bdbuf_cache.lru, &bd->link); +} + +static void +rtems_bdbuf_make_empty (rtems_bdbuf_buffer *bd) +{ + rtems_bdbuf_set_state (bd, RTEMS_BDBUF_STATE_EMPTY); +} + +static void +rtems_bdbuf_make_cached_and_add_to_lru_list (rtems_bdbuf_buffer *bd) +{ + rtems_bdbuf_set_state (bd, RTEMS_BDBUF_STATE_CACHED); + rtems_chain_append (&bdbuf_cache.lru, &bd->link); +} + +static void +rtems_bdbuf_discard_buffer (rtems_bdbuf_buffer *bd) +{ + rtems_bdbuf_make_empty (bd); + + if (bd->waiters == 0) + { + rtems_bdbuf_remove_from_tree (bd); + rtems_bdbuf_make_free_and_add_to_lru_list (bd); + } +} + +static void +rtems_bdbuf_add_to_modified_list_after_access (rtems_bdbuf_buffer *bd) +{ + if (bdbuf_cache.sync_active && bdbuf_cache.sync_device == bd->dev) + { + rtems_bdbuf_unlock_cache (); + + /* + * Wait for the sync lock. + */ + rtems_bdbuf_lock_sync (); + + rtems_bdbuf_unlock_sync (); + rtems_bdbuf_lock_cache (); + } + + /* + * Only the first modified release sets the timer and any further user + * accesses do not change the timer value which should move down. This + * assumes the user's hold of the buffer is much less than the time on the + * modified list. Resetting the timer on each access which could result in a + * buffer never getting to 0 and never being forced onto disk. This raises a + * difficult question. Is a snapshot of a block that is changing better than + * nothing being written? We have tended to think we should hold changes for + * only a specific period of time even if still changing and get onto disk + * and letting the file system try and recover this position if it can. + */ + if (bd->state == RTEMS_BDBUF_STATE_ACCESS_CACHED + || bd->state == RTEMS_BDBUF_STATE_ACCESS_EMPTY) + bd->hold_timer = bdbuf_config.swap_block_hold; + + rtems_bdbuf_set_state (bd, RTEMS_BDBUF_STATE_MODIFIED); + rtems_chain_append (&bdbuf_cache.modified, &bd->link); + + if (bd->waiters) + rtems_bdbuf_wake (&bdbuf_cache.access_waiters); + else if (rtems_bdbuf_has_buffer_waiters ()) + rtems_bdbuf_wake_swapper (); +} + +static void +rtems_bdbuf_add_to_lru_list_after_access (rtems_bdbuf_buffer *bd) +{ + rtems_bdbuf_group_release (bd); + rtems_bdbuf_make_cached_and_add_to_lru_list (bd); + + if (bd->waiters) + rtems_bdbuf_wake (&bdbuf_cache.access_waiters); + else + rtems_bdbuf_wake (&bdbuf_cache.buffer_waiters); +} + +/** + * Compute the number of BDs per group for a given buffer size. + * + * @param size The buffer size. It can be any size and we scale up. + */ +static size_t +rtems_bdbuf_bds_per_group (size_t size) +{ + size_t bufs_per_size; + size_t bds_per_size; + + if (size > bdbuf_config.buffer_max) + return 0; + + bufs_per_size = ((size - 1) / bdbuf_config.buffer_min) + 1; + + for (bds_per_size = 1; + bds_per_size < bufs_per_size; + bds_per_size <<= 1) + ; + + return bdbuf_cache.max_bds_per_group / bds_per_size; +} + +static void +rtems_bdbuf_discard_buffer_after_access (rtems_bdbuf_buffer *bd) +{ + rtems_bdbuf_group_release (bd); + rtems_bdbuf_discard_buffer (bd); + + if (bd->waiters) + rtems_bdbuf_wake (&bdbuf_cache.access_waiters); + else + rtems_bdbuf_wake (&bdbuf_cache.buffer_waiters); +} + +/** + * Reallocate a group. The BDs currently allocated in the group are removed + * from the ALV tree and any lists then the new BD's are prepended to the ready + * list of the cache. + * + * @param group The group to reallocate. + * @param new_bds_per_group The new count of BDs per group. + * @return A buffer of this group. + */ +static rtems_bdbuf_buffer * +rtems_bdbuf_group_realloc (rtems_bdbuf_group* group, size_t new_bds_per_group) +{ + rtems_bdbuf_buffer* bd; + size_t b; + size_t bufs_per_bd; + + if (rtems_bdbuf_tracer) + printf ("bdbuf:realloc: %tu: %zd -> %zd\n", + group - bdbuf_cache.groups, group->bds_per_group, + new_bds_per_group); + + bufs_per_bd = bdbuf_cache.max_bds_per_group / group->bds_per_group; + + for (b = 0, bd = group->bdbuf; + b < group->bds_per_group; + b++, bd += bufs_per_bd) + rtems_bdbuf_remove_from_tree_and_lru_list (bd); + + group->bds_per_group = new_bds_per_group; + bufs_per_bd = bdbuf_cache.max_bds_per_group / new_bds_per_group; + + for (b = 1, bd = group->bdbuf + bufs_per_bd; + b < group->bds_per_group; + b++, bd += bufs_per_bd) + rtems_bdbuf_make_free_and_add_to_lru_list (bd); + + if (b > 1) + rtems_bdbuf_wake (&bdbuf_cache.buffer_waiters); + + return group->bdbuf; +} + +static void +rtems_bdbuf_setup_empty_buffer (rtems_bdbuf_buffer *bd, + dev_t dev, + rtems_blkdev_bnum block) +{ + bd->dev = dev; + bd->block = block; + bd->avl.left = NULL; + bd->avl.right = NULL; + bd->waiters = 0; + + if (rtems_bdbuf_avl_insert (&bdbuf_cache.tree, bd) != 0) + rtems_fatal_error_occurred (RTEMS_BLKDEV_FATAL_BDBUF_RECYCLE); + + rtems_bdbuf_make_empty (bd); +} + +static rtems_bdbuf_buffer * +rtems_bdbuf_get_buffer_from_lru_list (dev_t dev, + rtems_blkdev_bnum block, + size_t bds_per_group) +{ + rtems_chain_node *node = rtems_chain_first (&bdbuf_cache.lru); + + while (!rtems_chain_is_tail (&bdbuf_cache.lru, node)) + { + rtems_bdbuf_buffer *bd = (rtems_bdbuf_buffer *) node; + rtems_bdbuf_buffer *empty_bd = NULL; + + if (rtems_bdbuf_tracer) + printf ("bdbuf:next-bd: %tu (%td:%" PRId32 ") %zd -> %zd\n", + bd - bdbuf_cache.bds, + bd->group - bdbuf_cache.groups, bd->group->users, + bd->group->bds_per_group, bds_per_group); + + /* + * If nobody waits for this BD, we may recycle it. + */ + if (bd->waiters == 0) + { + if (bd->group->bds_per_group == bds_per_group) + { + rtems_bdbuf_remove_from_tree_and_lru_list (bd); + + empty_bd = bd; + } + else if (bd->group->users == 0) + empty_bd = rtems_bdbuf_group_realloc (bd->group, bds_per_group); + } + + if (empty_bd != NULL) + { + rtems_bdbuf_setup_empty_buffer (empty_bd, dev, block); + + return empty_bd; + } + + node = rtems_chain_next (node); + } + + return NULL; +} + +/** + * Initialise the cache. + * + * @return rtems_status_code The initialisation status. + */ +rtems_status_code +rtems_bdbuf_init (void) +{ + rtems_bdbuf_group* group; + rtems_bdbuf_buffer* bd; + uint8_t* buffer; + size_t b; + size_t cache_aligment; + rtems_status_code sc; + rtems_mode prev_mode; + + if (rtems_bdbuf_tracer) + printf ("bdbuf:init\n"); + + if (rtems_interrupt_is_in_progress()) + return RTEMS_CALLED_FROM_ISR; + + /* + * Check the configuration table values. + */ + if ((bdbuf_config.buffer_max % bdbuf_config.buffer_min) != 0) + return RTEMS_INVALID_NUMBER; + + /* + * We use a special variable to manage the initialisation incase we have + * completing threads doing this. You may get errors if the another thread + * makes a call and we have not finished initialisation. + */ + prev_mode = rtems_bdbuf_disable_preemption (); + if (bdbuf_cache.initialised) + { + rtems_bdbuf_restore_preemption (prev_mode); + return RTEMS_RESOURCE_IN_USE; + } + + memset(&bdbuf_cache, 0, sizeof(bdbuf_cache)); + bdbuf_cache.initialised = true; + rtems_bdbuf_restore_preemption (prev_mode); + + /* + * For unspecified cache alignments we use the CPU alignment. + */ + cache_aligment = 32; /* FIXME rtems_cache_get_data_line_size() */ + if (cache_aligment <= 0) + cache_aligment = CPU_ALIGNMENT; + + bdbuf_cache.sync_device = BDBUF_INVALID_DEV; + + rtems_chain_initialize_empty (&bdbuf_cache.swapout_workers); + rtems_chain_initialize_empty (&bdbuf_cache.lru); + rtems_chain_initialize_empty (&bdbuf_cache.modified); + rtems_chain_initialize_empty (&bdbuf_cache.sync); + + /* + * Create the locks for the cache. + */ + sc = rtems_semaphore_create (rtems_build_name ('B', 'D', 'C', 'l'), + 1, RTEMS_BDBUF_CACHE_LOCK_ATTRIBS, 0, + &bdbuf_cache.lock); + if (sc != RTEMS_SUCCESSFUL) + goto error; + + rtems_bdbuf_lock_cache (); + + sc = rtems_semaphore_create (rtems_build_name ('B', 'D', 'C', 's'), + 1, RTEMS_BDBUF_CACHE_LOCK_ATTRIBS, 0, + &bdbuf_cache.sync_lock); + if (sc != RTEMS_SUCCESSFUL) + goto error; + + sc = rtems_semaphore_create (rtems_build_name ('B', 'D', 'C', 'a'), + 0, RTEMS_BDBUF_CACHE_WAITER_ATTRIBS, 0, + &bdbuf_cache.access_waiters.sema); + if (sc != RTEMS_SUCCESSFUL) + goto error; + + sc = rtems_semaphore_create (rtems_build_name ('B', 'D', 'C', 't'), + 0, RTEMS_BDBUF_CACHE_WAITER_ATTRIBS, 0, + &bdbuf_cache.transfer_waiters.sema); + if (sc != RTEMS_SUCCESSFUL) + goto error; + + sc = rtems_semaphore_create (rtems_build_name ('B', 'D', 'C', 'b'), + 0, RTEMS_BDBUF_CACHE_WAITER_ATTRIBS, 0, + &bdbuf_cache.buffer_waiters.sema); + if (sc != RTEMS_SUCCESSFUL) + goto error; + + /* + * Compute the various number of elements in the cache. + */ + bdbuf_cache.buffer_min_count = + bdbuf_config.size / bdbuf_config.buffer_min; + bdbuf_cache.max_bds_per_group = + bdbuf_config.buffer_max / bdbuf_config.buffer_min; + bdbuf_cache.group_count = + bdbuf_cache.buffer_min_count / bdbuf_cache.max_bds_per_group; + + /* + * Allocate the memory for the buffer descriptors. + */ + bdbuf_cache.bds = calloc (sizeof (rtems_bdbuf_buffer), + bdbuf_cache.buffer_min_count); + if (!bdbuf_cache.bds) + goto error; + + /* + * Allocate the memory for the buffer descriptors. + */ + bdbuf_cache.groups = calloc (sizeof (rtems_bdbuf_group), + bdbuf_cache.group_count); + if (!bdbuf_cache.groups) + goto error; + + /* + * Allocate memory for buffer memory. The buffer memory will be cache + * aligned. It is possible to free the memory allocated by rtems_memalign() + * with free(). Return 0 if allocated. + * + * The memory allocate allows a + */ + if (rtems_memalign ((void **) &bdbuf_cache.buffers, + cache_aligment, + bdbuf_cache.buffer_min_count * bdbuf_config.buffer_min) != 0) + goto error; + + /* + * The cache is empty after opening so we need to add all the buffers to it + * and initialise the groups. + */ + for (b = 0, group = bdbuf_cache.groups, + bd = bdbuf_cache.bds, buffer = bdbuf_cache.buffers; + b < bdbuf_cache.buffer_min_count; + b++, bd++, buffer += bdbuf_config.buffer_min) + { + bd->dev = BDBUF_INVALID_DEV; + bd->group = group; + bd->buffer = buffer; + + rtems_chain_append (&bdbuf_cache.lru, &bd->link); + + if ((b % bdbuf_cache.max_bds_per_group) == + (bdbuf_cache.max_bds_per_group - 1)) + group++; + } + + for (b = 0, + group = bdbuf_cache.groups, + bd = bdbuf_cache.bds; + b < bdbuf_cache.group_count; + b++, + group++, + bd += bdbuf_cache.max_bds_per_group) + { + group->bds_per_group = bdbuf_cache.max_bds_per_group; + group->bdbuf = bd; + } + + /* + * Create and start swapout task. This task will create and manage the worker + * threads. + */ + bdbuf_cache.swapout_enabled = true; + + sc = rtems_task_create (rtems_build_name('B', 'S', 'W', 'P'), + bdbuf_config.swapout_priority ? + bdbuf_config.swapout_priority : + RTEMS_BDBUF_SWAPOUT_TASK_PRIORITY_DEFAULT, + SWAPOUT_TASK_STACK_SIZE, + RTEMS_PREEMPT | RTEMS_NO_TIMESLICE | RTEMS_NO_ASR, + RTEMS_LOCAL | RTEMS_NO_FLOATING_POINT, + &bdbuf_cache.swapout); + if (sc != RTEMS_SUCCESSFUL) + goto error; + + sc = rtems_task_start (bdbuf_cache.swapout, + rtems_bdbuf_swapout_task, + (rtems_task_argument) &bdbuf_cache); + if (sc != RTEMS_SUCCESSFUL) + goto error; + + rtems_bdbuf_unlock_cache (); + + return RTEMS_SUCCESSFUL; + +error: + + if (bdbuf_cache.swapout != 0) + rtems_task_delete (bdbuf_cache.swapout); + + free (bdbuf_cache.buffers); + free (bdbuf_cache.groups); + free (bdbuf_cache.bds); + + rtems_semaphore_delete (bdbuf_cache.buffer_waiters.sema); + rtems_semaphore_delete (bdbuf_cache.access_waiters.sema); + rtems_semaphore_delete (bdbuf_cache.transfer_waiters.sema); + rtems_semaphore_delete (bdbuf_cache.sync_lock); + + if (bdbuf_cache.lock != 0) + { + rtems_bdbuf_unlock_cache (); + rtems_semaphore_delete (bdbuf_cache.lock); + } + + bdbuf_cache.initialised = false; + + return RTEMS_UNSATISFIED; +} + +static void +rtems_bdbuf_wait_for_event (rtems_event_set event) +{ + rtems_status_code sc = RTEMS_SUCCESSFUL; + rtems_event_set out = 0; + + sc = rtems_event_receive (event, + RTEMS_EVENT_ALL | RTEMS_WAIT, + RTEMS_NO_TIMEOUT, + &out); + + if (sc != RTEMS_SUCCESSFUL || out != event) + rtems_fatal_error_occurred (RTEMS_BLKDEV_FATAL_BDBUF_WAIT_EVNT); +} + +static void +rtems_bdbuf_wait_for_access (rtems_bdbuf_buffer *bd) +{ + while (true) + { + switch (bd->state) + { + case RTEMS_BDBUF_STATE_MODIFIED: + rtems_bdbuf_group_release (bd); + /* Fall through */ + case RTEMS_BDBUF_STATE_CACHED: + rtems_chain_extract (&bd->link); + /* Fall through */ + case RTEMS_BDBUF_STATE_EMPTY: + return; + case RTEMS_BDBUF_STATE_ACCESS_CACHED: + case RTEMS_BDBUF_STATE_ACCESS_EMPTY: + case RTEMS_BDBUF_STATE_ACCESS_MODIFIED: + case RTEMS_BDBUF_STATE_ACCESS_PURGED: + rtems_bdbuf_wait (bd, &bdbuf_cache.access_waiters); + break; + case RTEMS_BDBUF_STATE_SYNC: + case RTEMS_BDBUF_STATE_TRANSFER: + case RTEMS_BDBUF_STATE_TRANSFER_PURGED: + rtems_bdbuf_wait (bd, &bdbuf_cache.transfer_waiters); + break; + default: + rtems_bdbuf_fatal (bd->state, RTEMS_BLKDEV_FATAL_BDBUF_STATE_7); + } + } +} + +static void +rtems_bdbuf_request_sync_for_modified_buffer (rtems_bdbuf_buffer *bd) +{ + rtems_bdbuf_set_state (bd, RTEMS_BDBUF_STATE_SYNC); + rtems_chain_extract (&bd->link); + rtems_chain_append (&bdbuf_cache.sync, &bd->link); + rtems_bdbuf_wake_swapper (); +} + +/** + * @brief Waits until the buffer is ready for recycling. + * + * @retval @c true Buffer is valid and may be recycled. + * @retval @c false Buffer is invalid and has to searched again. + */ +static bool +rtems_bdbuf_wait_for_recycle (rtems_bdbuf_buffer *bd) +{ + while (true) + { + switch (bd->state) + { + case RTEMS_BDBUF_STATE_FREE: + return true; + case RTEMS_BDBUF_STATE_MODIFIED: + rtems_bdbuf_request_sync_for_modified_buffer (bd); + break; + case RTEMS_BDBUF_STATE_CACHED: + case RTEMS_BDBUF_STATE_EMPTY: + if (bd->waiters == 0) + return true; + else + { + /* + * It is essential that we wait here without a special wait count and + * without the group in use. Otherwise we could trigger a wait ping + * pong with another recycle waiter. The state of the buffer is + * arbitrary afterwards. + */ + rtems_bdbuf_anonymous_wait (&bdbuf_cache.buffer_waiters); + return false; + } + case RTEMS_BDBUF_STATE_ACCESS_CACHED: + case RTEMS_BDBUF_STATE_ACCESS_EMPTY: + case RTEMS_BDBUF_STATE_ACCESS_MODIFIED: + case RTEMS_BDBUF_STATE_ACCESS_PURGED: + rtems_bdbuf_wait (bd, &bdbuf_cache.access_waiters); + break; + case RTEMS_BDBUF_STATE_SYNC: + case RTEMS_BDBUF_STATE_TRANSFER: + case RTEMS_BDBUF_STATE_TRANSFER_PURGED: + rtems_bdbuf_wait (bd, &bdbuf_cache.transfer_waiters); + break; + default: + rtems_bdbuf_fatal (bd->state, RTEMS_BLKDEV_FATAL_BDBUF_STATE_8); + } + } +} + +static void +rtems_bdbuf_wait_for_sync_done (rtems_bdbuf_buffer *bd) +{ + while (true) + { + switch (bd->state) + { + case RTEMS_BDBUF_STATE_CACHED: + case RTEMS_BDBUF_STATE_EMPTY: + case RTEMS_BDBUF_STATE_MODIFIED: + case RTEMS_BDBUF_STATE_ACCESS_CACHED: + case RTEMS_BDBUF_STATE_ACCESS_EMPTY: + case RTEMS_BDBUF_STATE_ACCESS_MODIFIED: + case RTEMS_BDBUF_STATE_ACCESS_PURGED: + return; + case RTEMS_BDBUF_STATE_SYNC: + case RTEMS_BDBUF_STATE_TRANSFER: + case RTEMS_BDBUF_STATE_TRANSFER_PURGED: + rtems_bdbuf_wait (bd, &bdbuf_cache.transfer_waiters); + break; + default: + rtems_bdbuf_fatal (bd->state, RTEMS_BLKDEV_FATAL_BDBUF_STATE_9); + } + } +} + +static void +rtems_bdbuf_wait_for_buffer (void) +{ + if (!rtems_chain_is_empty (&bdbuf_cache.modified)) + rtems_bdbuf_wake_swapper (); + + rtems_bdbuf_anonymous_wait (&bdbuf_cache.buffer_waiters); +} + +static void +rtems_bdbuf_sync_after_access (rtems_bdbuf_buffer *bd) +{ + rtems_bdbuf_set_state (bd, RTEMS_BDBUF_STATE_SYNC); + + rtems_chain_append (&bdbuf_cache.sync, &bd->link); + + if (bd->waiters) + rtems_bdbuf_wake (&bdbuf_cache.access_waiters); + + rtems_bdbuf_wake_swapper (); + rtems_bdbuf_wait_for_sync_done (bd); + + /* + * We may have created a cached or empty buffer which may be recycled. + */ + if (bd->waiters == 0 + && (bd->state == RTEMS_BDBUF_STATE_CACHED + || bd->state == RTEMS_BDBUF_STATE_EMPTY)) + { + if (bd->state == RTEMS_BDBUF_STATE_EMPTY) + { + rtems_bdbuf_remove_from_tree (bd); + rtems_bdbuf_make_free_and_add_to_lru_list (bd); + } + rtems_bdbuf_wake (&bdbuf_cache.buffer_waiters); + } +} + +static rtems_bdbuf_buffer * +rtems_bdbuf_get_buffer_for_read_ahead (dev_t dev, + rtems_blkdev_bnum block, + size_t bds_per_group) +{ + rtems_bdbuf_buffer *bd = NULL; + + bd = rtems_bdbuf_avl_search (&bdbuf_cache.tree, dev, block); + + if (bd == NULL) + { + bd = rtems_bdbuf_get_buffer_from_lru_list (dev, block, bds_per_group); + + if (bd != NULL) + rtems_bdbuf_group_obtain (bd); + } + else + /* + * The buffer is in the cache. So it is already available or in use, and + * thus no need for a read ahead. + */ + bd = NULL; + + return bd; +} + +static rtems_bdbuf_buffer * +rtems_bdbuf_get_buffer_for_access (dev_t dev, + rtems_blkdev_bnum block, + size_t bds_per_group) +{ + rtems_bdbuf_buffer *bd = NULL; + + do + { + bd = rtems_bdbuf_avl_search (&bdbuf_cache.tree, dev, block); + + if (bd != NULL) + { + if (bd->group->bds_per_group != bds_per_group) + { + if (rtems_bdbuf_wait_for_recycle (bd)) + { + rtems_bdbuf_remove_from_tree_and_lru_list (bd); + rtems_bdbuf_make_free_and_add_to_lru_list (bd); + rtems_bdbuf_wake (&bdbuf_cache.buffer_waiters); + } + bd = NULL; + } + } + else + { + bd = rtems_bdbuf_get_buffer_from_lru_list (dev, block, bds_per_group); + + if (bd == NULL) + rtems_bdbuf_wait_for_buffer (); + } + } + while (bd == NULL); + + rtems_bdbuf_wait_for_access (bd); + rtems_bdbuf_group_obtain (bd); + + return bd; +} + +static rtems_status_code +rtems_bdbuf_obtain_disk (dev_t dev, + rtems_blkdev_bnum block, + rtems_disk_device **dd_ptr, + rtems_blkdev_bnum *media_block_ptr, + size_t *bds_per_group_ptr) +{ + rtems_disk_device *dd = NULL; + + if (!bdbuf_cache.initialised) + return RTEMS_NOT_CONFIGURED; + + /* + * Do not hold the cache lock when obtaining the disk table. + */ + dd = rtems_disk_obtain (dev); + if (dd == NULL) + return RTEMS_INVALID_ID; + + *dd_ptr = dd; + + if (media_block_ptr != NULL) + { + /* + * Compute the media block number. Drivers work with media block number not + * the block number a BD may have as this depends on the block size set by + * the user. + */ + rtems_blkdev_bnum mb = rtems_bdbuf_media_block (block, + dd->block_size, + dd->media_block_size); + if (mb >= dd->size) + { + rtems_disk_release(dd); + return RTEMS_INVALID_NUMBER; + } + + *media_block_ptr = mb + dd->start; + } + + if (bds_per_group_ptr != NULL) + { + size_t bds_per_group = rtems_bdbuf_bds_per_group (dd->block_size); + + if (bds_per_group == 0) + { + rtems_disk_release (dd); + return RTEMS_INVALID_NUMBER; + } + + *bds_per_group_ptr = bds_per_group; + } + + return RTEMS_SUCCESSFUL; +} + +static void +rtems_bdbuf_release_disk (rtems_disk_device *dd) +{ + rtems_status_code sc = RTEMS_SUCCESSFUL; + + sc = rtems_disk_release (dd); + if (sc != RTEMS_SUCCESSFUL) + rtems_fatal_error_occurred (RTEMS_BLKDEV_FATAL_BDBUF_DISK_REL); +} + +rtems_status_code +rtems_bdbuf_get (dev_t dev, + rtems_blkdev_bnum block, + rtems_bdbuf_buffer **bd_ptr) +{ + rtems_status_code sc = RTEMS_SUCCESSFUL; + rtems_disk_device *dd = NULL; + rtems_bdbuf_buffer *bd = NULL; + rtems_blkdev_bnum media_block = 0; + size_t bds_per_group = 0; + + sc = rtems_bdbuf_obtain_disk (dev, block, &dd, &media_block, &bds_per_group); + if (sc != RTEMS_SUCCESSFUL) + return sc; + + rtems_bdbuf_lock_cache (); + + /* + * Print the block index relative to the physical disk. + */ + if (rtems_bdbuf_tracer) + printf ("bdbuf:get: %" PRIu32 " (%" PRIu32 ") (dev = %08x)\n", + media_block, block, (unsigned) dev); + + bd = rtems_bdbuf_get_buffer_for_access (dev, media_block, bds_per_group); + + switch (bd->state) + { + case RTEMS_BDBUF_STATE_CACHED: + rtems_bdbuf_set_state (bd, RTEMS_BDBUF_STATE_ACCESS_CACHED); + break; + case RTEMS_BDBUF_STATE_EMPTY: + rtems_bdbuf_set_state (bd, RTEMS_BDBUF_STATE_ACCESS_EMPTY); + break; + case RTEMS_BDBUF_STATE_MODIFIED: + /* + * To get a modified buffer could be considered a bug in the caller + * because you should not be getting an already modified buffer but user + * may have modified a byte in a block then decided to seek the start and + * write the whole block and the file system will have no record of this + * so just gets the block to fill. + */ + rtems_bdbuf_set_state (bd, RTEMS_BDBUF_STATE_ACCESS_MODIFIED); + break; + default: + rtems_bdbuf_fatal (bd->state, RTEMS_BLKDEV_FATAL_BDBUF_STATE_2); + break; + } + + if (rtems_bdbuf_tracer) + { + rtems_bdbuf_show_users ("get", bd); + rtems_bdbuf_show_usage (); + } + + rtems_bdbuf_unlock_cache (); + + rtems_bdbuf_release_disk (dd); + + *bd_ptr = bd; + + return RTEMS_SUCCESSFUL; +} + +/** + * Call back handler called by the low level driver when the transfer has + * completed. This function may be invoked from interrupt handler. + * + * @param arg Arbitrary argument specified in block device request + * structure (in this case - pointer to the appropriate + * block device request structure). + * @param status I/O completion status + */ +static void +rtems_bdbuf_transfer_done (void* arg, rtems_status_code status) +{ + rtems_blkdev_request* req = (rtems_blkdev_request*) arg; + + req->status = status; + + rtems_event_send (req->io_task, RTEMS_BDBUF_TRANSFER_SYNC); +} + +static void +rtems_bdbuf_create_read_request (const rtems_disk_device *dd, + rtems_blkdev_bnum media_block, + size_t bds_per_group, + rtems_blkdev_request *req, + rtems_bdbuf_buffer **bd_ptr) +{ + rtems_bdbuf_buffer *bd = NULL; + rtems_blkdev_bnum media_block_end = dd->start + dd->size; + rtems_blkdev_bnum media_block_count = dd->block_size / dd->media_block_size; + dev_t dev = dd->dev; + uint32_t block_size = dd->block_size; + uint32_t transfer_index = 1; + uint32_t transfer_count = bdbuf_config.max_read_ahead_blocks + 1; + + if (media_block_end - media_block < transfer_count) + transfer_count = media_block_end - media_block; + + req->req = RTEMS_BLKDEV_REQ_READ; + req->req_done = rtems_bdbuf_transfer_done; + req->done_arg = req; + req->io_task = rtems_task_self (); + req->status = RTEMS_RESOURCE_IN_USE; + req->bufnum = 0; + + bd = rtems_bdbuf_get_buffer_for_access (dev, media_block, bds_per_group); + + *bd_ptr = bd; + + req->bufs [0].user = bd; + req->bufs [0].block = media_block; + req->bufs [0].length = block_size; + req->bufs [0].buffer = bd->buffer; + + if (rtems_bdbuf_tracer) + rtems_bdbuf_show_users ("read", bd); + + switch (bd->state) + { + case RTEMS_BDBUF_STATE_CACHED: + case RTEMS_BDBUF_STATE_MODIFIED: + return; + case RTEMS_BDBUF_STATE_EMPTY: + rtems_bdbuf_set_state (bd, RTEMS_BDBUF_STATE_TRANSFER); + break; + default: + rtems_bdbuf_fatal (bd->state, RTEMS_BLKDEV_FATAL_BDBUF_STATE_1); + break; + } + + while (transfer_index < transfer_count) + { + media_block += media_block_count; + + bd = rtems_bdbuf_get_buffer_for_read_ahead (dev, media_block, + bds_per_group); + + if (bd == NULL) + break; + + rtems_bdbuf_set_state (bd, RTEMS_BDBUF_STATE_TRANSFER); + + req->bufs [transfer_index].user = bd; + req->bufs [transfer_index].block = media_block; + req->bufs [transfer_index].length = block_size; + req->bufs [transfer_index].buffer = bd->buffer; + + if (rtems_bdbuf_tracer) + rtems_bdbuf_show_users ("read-ahead", bd); + + ++transfer_index; + } + + req->bufnum = transfer_index; +} + +static rtems_status_code +rtems_bdbuf_execute_transfer_request (const rtems_disk_device *dd, + rtems_blkdev_request *req, + bool cache_locked) +{ + rtems_status_code sc = RTEMS_SUCCESSFUL; + int result = 0; + uint32_t transfer_index = 0; + bool wake_transfer_waiters = false; + bool wake_buffer_waiters = false; + + if (cache_locked) + rtems_bdbuf_unlock_cache (); + + result = dd->ioctl (dd->phys_dev, RTEMS_BLKIO_REQUEST, req); + + if (result == 0) + { + rtems_bdbuf_wait_for_event (RTEMS_BDBUF_TRANSFER_SYNC); + sc = req->status; + } + else + sc = RTEMS_IO_ERROR; + + rtems_bdbuf_lock_cache (); + + for (transfer_index = 0; transfer_index < req->bufnum; ++transfer_index) + { + rtems_bdbuf_buffer *bd = req->bufs [transfer_index].user; + bool waiters = bd->waiters; + + if (waiters) + wake_transfer_waiters = true; + else + wake_buffer_waiters = true; + + rtems_bdbuf_group_release (bd); + + if (sc == RTEMS_SUCCESSFUL && bd->state == RTEMS_BDBUF_STATE_TRANSFER) + rtems_bdbuf_make_cached_and_add_to_lru_list (bd); + else + rtems_bdbuf_discard_buffer (bd); + + if (rtems_bdbuf_tracer) + rtems_bdbuf_show_users ("transfer", bd); + } + + if (wake_transfer_waiters) + rtems_bdbuf_wake (&bdbuf_cache.transfer_waiters); + + if (wake_buffer_waiters) + rtems_bdbuf_wake (&bdbuf_cache.buffer_waiters); + + if (!cache_locked) + rtems_bdbuf_unlock_cache (); + + if (sc == RTEMS_SUCCESSFUL || sc == RTEMS_UNSATISFIED) + return sc; + else + return RTEMS_IO_ERROR; +} + +rtems_status_code +rtems_bdbuf_read (dev_t dev, + rtems_blkdev_bnum block, + rtems_bdbuf_buffer **bd_ptr) +{ + rtems_status_code sc = RTEMS_SUCCESSFUL; + rtems_disk_device *dd = NULL; + rtems_blkdev_request *req = NULL; + rtems_bdbuf_buffer *bd = NULL; + rtems_blkdev_bnum media_block = 0; + size_t bds_per_group = 0; + + sc = rtems_bdbuf_obtain_disk (dev, block, &dd, &media_block, &bds_per_group); + if (sc != RTEMS_SUCCESSFUL) + return sc; + + /* + * TODO: This type of request structure is wrong and should be removed. + */ +#define bdbuf_alloc(size) __builtin_alloca (size) + + req = bdbuf_alloc (sizeof (rtems_blkdev_request) + + sizeof (rtems_blkdev_sg_buffer) * + (bdbuf_config.max_read_ahead_blocks + 1)); + + if (rtems_bdbuf_tracer) + printf ("bdbuf:read: %" PRIu32 " (%" PRIu32 ") (dev = %08x)\n", + media_block + dd->start, block, (unsigned) dev); + + rtems_bdbuf_lock_cache (); + rtems_bdbuf_create_read_request (dd, media_block, bds_per_group, req, &bd); + + if (req->bufnum > 0) + { + sc = rtems_bdbuf_execute_transfer_request (dd, req, true); + if (sc == RTEMS_SUCCESSFUL) + { + rtems_chain_extract (&bd->link); + rtems_bdbuf_group_obtain (bd); + } + } + + if (sc == RTEMS_SUCCESSFUL) + { + switch (bd->state) + { + case RTEMS_BDBUF_STATE_CACHED: + rtems_bdbuf_set_state (bd, RTEMS_BDBUF_STATE_ACCESS_CACHED); + break; + case RTEMS_BDBUF_STATE_MODIFIED: + rtems_bdbuf_set_state (bd, RTEMS_BDBUF_STATE_ACCESS_MODIFIED); + break; + default: + rtems_bdbuf_fatal (bd->state, RTEMS_BLKDEV_FATAL_BDBUF_STATE_4); + break; + } + + if (rtems_bdbuf_tracer) + { + rtems_bdbuf_show_users ("read", bd); + rtems_bdbuf_show_usage (); + } + + *bd_ptr = bd; + } + else + *bd_ptr = NULL; + + rtems_bdbuf_unlock_cache (); + rtems_bdbuf_release_disk (dd); + + return sc; +} + +static rtems_status_code +rtems_bdbuf_check_bd_and_lock_cache (rtems_bdbuf_buffer *bd, const char *kind) +{ + if (!bdbuf_cache.initialised) + return RTEMS_NOT_CONFIGURED; + if (bd == NULL) + return RTEMS_INVALID_ADDRESS; + if (rtems_bdbuf_tracer) + { + printf ("bdbuf:%s: %" PRIu32 "\n", kind, bd->block); + rtems_bdbuf_show_users (kind, bd); + } + rtems_bdbuf_lock_cache(); + + return RTEMS_SUCCESSFUL; +} + +rtems_status_code +rtems_bdbuf_release (rtems_bdbuf_buffer *bd) +{ + rtems_status_code sc = RTEMS_SUCCESSFUL; + + sc = rtems_bdbuf_check_bd_and_lock_cache (bd, "release"); + if (sc != RTEMS_SUCCESSFUL) + return sc; + + switch (bd->state) + { + case RTEMS_BDBUF_STATE_ACCESS_CACHED: + rtems_bdbuf_add_to_lru_list_after_access (bd); + break; + case RTEMS_BDBUF_STATE_ACCESS_EMPTY: + case RTEMS_BDBUF_STATE_ACCESS_PURGED: + rtems_bdbuf_discard_buffer_after_access (bd); + break; + case RTEMS_BDBUF_STATE_ACCESS_MODIFIED: + rtems_bdbuf_add_to_modified_list_after_access (bd); + break; + default: + rtems_bdbuf_fatal (bd->state, RTEMS_BLKDEV_FATAL_BDBUF_STATE_0); + break; + } + + if (rtems_bdbuf_tracer) + rtems_bdbuf_show_usage (); + + rtems_bdbuf_unlock_cache (); + + return RTEMS_SUCCESSFUL; +} + +rtems_status_code +rtems_bdbuf_release_modified (rtems_bdbuf_buffer *bd) +{ + rtems_status_code sc = RTEMS_SUCCESSFUL; + + sc = rtems_bdbuf_check_bd_and_lock_cache (bd, "release modified"); + if (sc != RTEMS_SUCCESSFUL) + return sc; + + switch (bd->state) + { + case RTEMS_BDBUF_STATE_ACCESS_CACHED: + case RTEMS_BDBUF_STATE_ACCESS_EMPTY: + case RTEMS_BDBUF_STATE_ACCESS_MODIFIED: + rtems_bdbuf_add_to_modified_list_after_access (bd); + break; + case RTEMS_BDBUF_STATE_ACCESS_PURGED: + rtems_bdbuf_discard_buffer_after_access (bd); + break; + default: + rtems_bdbuf_fatal (bd->state, RTEMS_BLKDEV_FATAL_BDBUF_STATE_6); + break; + } + + if (rtems_bdbuf_tracer) + rtems_bdbuf_show_usage (); + + rtems_bdbuf_unlock_cache (); + + return RTEMS_SUCCESSFUL; +} + +rtems_status_code +rtems_bdbuf_sync (rtems_bdbuf_buffer *bd) +{ + rtems_status_code sc = RTEMS_SUCCESSFUL; + + sc = rtems_bdbuf_check_bd_and_lock_cache (bd, "sync"); + if (sc != RTEMS_SUCCESSFUL) + return sc; + + switch (bd->state) + { + case RTEMS_BDBUF_STATE_ACCESS_CACHED: + case RTEMS_BDBUF_STATE_ACCESS_EMPTY: + case RTEMS_BDBUF_STATE_ACCESS_MODIFIED: + rtems_bdbuf_sync_after_access (bd); + break; + case RTEMS_BDBUF_STATE_ACCESS_PURGED: + rtems_bdbuf_discard_buffer_after_access (bd); + break; + default: + rtems_bdbuf_fatal (bd->state, RTEMS_BLKDEV_FATAL_BDBUF_STATE_5); + break; + } + + if (rtems_bdbuf_tracer) + rtems_bdbuf_show_usage (); + + rtems_bdbuf_unlock_cache (); + + return RTEMS_SUCCESSFUL; +} + +rtems_status_code +rtems_bdbuf_syncdev (dev_t dev) +{ + rtems_status_code sc = RTEMS_SUCCESSFUL; + rtems_disk_device *dd = NULL; + + if (rtems_bdbuf_tracer) + printf ("bdbuf:syncdev: %08x\n", (unsigned) dev); + + sc = rtems_bdbuf_obtain_disk (dev, 0, &dd, NULL, NULL); + if (sc != RTEMS_SUCCESSFUL) + return sc; + + /* + * Take the sync lock before locking the cache. Once we have the sync lock we + * can lock the cache. If another thread has the sync lock it will cause this + * thread to block until it owns the sync lock then it can own the cache. The + * sync lock can only be obtained with the cache unlocked. + */ + rtems_bdbuf_lock_sync (); + rtems_bdbuf_lock_cache (); + + /* + * Set the cache to have a sync active for a specific device and let the swap + * out task know the id of the requester to wake when done. + * + * The swap out task will negate the sync active flag when no more buffers + * for the device are held on the "modified for sync" queues. + */ + bdbuf_cache.sync_active = true; + bdbuf_cache.sync_requester = rtems_task_self (); + bdbuf_cache.sync_device = dev; + + rtems_bdbuf_wake_swapper (); + rtems_bdbuf_unlock_cache (); + rtems_bdbuf_wait_for_event (RTEMS_BDBUF_TRANSFER_SYNC); + rtems_bdbuf_unlock_sync (); + rtems_bdbuf_release_disk (dd); + + return RTEMS_SUCCESSFUL; +} + +static int +rtems_bdbuf_null_disk_ioctl (rtems_disk_device *dd, uint32_t req, void *arg) +{ + return -1; +} + +/** + * Swapout transfer to the driver. The driver will break this I/O into groups + * of consecutive write requests is multiple consecutive buffers are required + * by the driver. The cache is not locked. + * + * @param transfer The transfer transaction. + */ +static void +rtems_bdbuf_swapout_write (rtems_bdbuf_swapout_transfer* transfer) +{ + rtems_chain_node *node; + static rtems_disk_device null_disk = { + .phys_dev = &null_disk, + .capabilities = 0, + .ioctl = rtems_bdbuf_null_disk_ioctl + }; + + if (rtems_bdbuf_tracer) + printf ("bdbuf:swapout transfer: %08x\n", (unsigned) transfer->dev); + + /* + * If there are buffers to transfer to the media transfer them. + */ + if (!rtems_chain_is_empty (&transfer->bds)) + { + /* + * The last block number used when the driver only supports + * continuous blocks in a single request. + */ + uint32_t last_block = 0; + + /* + * Number of buffers per bd. This is used to detect the next + * block. + */ + uint32_t bufs_per_bd = 0; + + /* + * Obtain the disk device. The cache's mutex has been released to avoid a + * dead lock. + */ + rtems_disk_device *dd = rtems_disk_obtain (transfer->dev); + + if (dd == NULL) + dd = &null_disk; + + bufs_per_bd = dd->block_size / bdbuf_config.buffer_min; + + /* + * Take as many buffers as configured and pass to the driver. Note, the + * API to the drivers has an array of buffers and if a chain was passed + * we could have just passed the list. If the driver API is updated it + * should be possible to make this change with little effect in this + * code. The array that is passed is broken in design and should be + * removed. Merging members of a struct into the first member is + * trouble waiting to happen. + */ + transfer->write_req->status = RTEMS_RESOURCE_IN_USE; + transfer->write_req->bufnum = 0; + + while ((node = rtems_chain_get(&transfer->bds)) != NULL) + { + rtems_bdbuf_buffer* bd = (rtems_bdbuf_buffer*) node; + bool write = false; + + /* + * If the device only accepts sequential buffers and this is not the + * first buffer (the first is always sequential, and the buffer is not + * sequential then put the buffer back on the transfer chain and write + * the committed buffers. + */ + + if (rtems_bdbuf_tracer) + printf ("bdbuf:swapout write: bd:%" PRIu32 ", bufnum:%" PRIu32 " mode:%s\n", + bd->block, transfer->write_req->bufnum, + dd->phys_dev->capabilities & + RTEMS_BLKDEV_CAP_MULTISECTOR_CONT ? "MULIT" : "SCAT"); + + if ((dd->phys_dev->capabilities & RTEMS_BLKDEV_CAP_MULTISECTOR_CONT) && + transfer->write_req->bufnum && + (bd->block != (last_block + bufs_per_bd))) + { + rtems_chain_prepend (&transfer->bds, &bd->link); + write = true; + } + else + { + rtems_blkdev_sg_buffer* buf; + buf = &transfer->write_req->bufs[transfer->write_req->bufnum]; + transfer->write_req->bufnum++; + buf->user = bd; + buf->block = bd->block; + buf->length = dd->block_size; + buf->buffer = bd->buffer; + last_block = bd->block; + } + + /* + * Perform the transfer if there are no more buffers, or the transfer + * size has reached the configured max. value. + */ + + if (rtems_chain_is_empty (&transfer->bds) || + (transfer->write_req->bufnum >= bdbuf_config.max_write_blocks)) + write = true; + + if (write) + { + rtems_bdbuf_execute_transfer_request (dd, transfer->write_req, false); + + transfer->write_req->status = RTEMS_RESOURCE_IN_USE; + transfer->write_req->bufnum = 0; + } + } + + if (dd != &null_disk) + { + /* + * If sync'ing and the deivce is capability of handling a sync IO control + * call perform the call. + */ + if (transfer->syncing && + (dd->phys_dev->capabilities & RTEMS_BLKDEV_CAP_SYNC)) + { + /* int result = */ dd->ioctl (dd->phys_dev, RTEMS_BLKDEV_REQ_SYNC, NULL); + /* How should the error be handled ? */ + } + + rtems_disk_release (dd); + } + } +} + +/** + * Process the modified list of buffers. There is a sync or modified list that + * needs to be handled so we have a common function to do the work. + * + * @param dev The device to handle. If BDBUF_INVALID_DEV no device is selected + * so select the device of the first buffer to be written to disk. + * @param chain The modified chain to process. + * @param transfer The chain to append buffers to be written too. + * @param sync_active If true this is a sync operation so expire all timers. + * @param update_timers If true update the timers. + * @param timer_delta It update_timers is true update the timers by this + * amount. + */ +static void +rtems_bdbuf_swapout_modified_processing (dev_t* dev, + rtems_chain_control* chain, + rtems_chain_control* transfer, + bool sync_active, + bool update_timers, + uint32_t timer_delta) +{ + if (!rtems_chain_is_empty (chain)) + { + rtems_chain_node* node = rtems_chain_head (chain); + bool sync_all; + + node = node->next; + + /* + * A sync active with no valid dev means sync all. + */ + if (sync_active && (*dev == BDBUF_INVALID_DEV)) + sync_all = true; + else + sync_all = false; + + while (!rtems_chain_is_tail (chain, node)) + { + rtems_bdbuf_buffer* bd = (rtems_bdbuf_buffer*) node; + + /* + * Check if the buffer's hold timer has reached 0. If a sync is active + * or someone waits for a buffer written force all the timers to 0. + * + * @note Lots of sync requests will skew this timer. It should be based + * on TOD to be accurate. Does it matter ? + */ + if (sync_all || (sync_active && (*dev == bd->dev)) + || rtems_bdbuf_has_buffer_waiters ()) + bd->hold_timer = 0; + + if (bd->hold_timer) + { + if (update_timers) + { + if (bd->hold_timer > timer_delta) + bd->hold_timer -= timer_delta; + else + bd->hold_timer = 0; + } + + if (bd->hold_timer) + { + node = node->next; + continue; + } + } + + /* + * This assumes we can set dev_t to BDBUF_INVALID_DEV which is just an + * assumption. Cannot use the transfer list being empty the sync dev + * calls sets the dev to use. + */ + if (*dev == BDBUF_INVALID_DEV) + *dev = bd->dev; + + if (bd->dev == *dev) + { + rtems_chain_node* next_node = node->next; + rtems_chain_node* tnode = rtems_chain_tail (transfer); + + /* + * The blocks on the transfer list are sorted in block order. This + * means multi-block transfers for drivers that require consecutive + * blocks perform better with sorted blocks and for real disks it may + * help lower head movement. + */ + + rtems_bdbuf_set_state (bd, RTEMS_BDBUF_STATE_TRANSFER); + + rtems_chain_extract (node); + + tnode = tnode->previous; + + while (node && !rtems_chain_is_head (transfer, tnode)) + { + rtems_bdbuf_buffer* tbd = (rtems_bdbuf_buffer*) tnode; + + if (bd->block > tbd->block) + { + rtems_chain_insert (tnode, node); + node = NULL; + } + else + tnode = tnode->previous; + } + + if (node) + rtems_chain_prepend (transfer, node); + + node = next_node; + } + else + { + node = node->next; + } + } + } +} + +/** + * Process the cache's modified buffers. Check the sync list first then the + * modified list extracting the buffers suitable to be written to disk. We have + * a device at a time. The task level loop will repeat this operation while + * there are buffers to be written. If the transfer fails place the buffers + * back on the modified list and try again later. The cache is unlocked while + * the buffers are being written to disk. + * + * @param timer_delta It update_timers is true update the timers by this + * amount. + * @param update_timers If true update the timers. + * @param transfer The transfer transaction data. + * + * @retval true Buffers where written to disk so scan again. + * @retval false No buffers where written to disk. + */ +static bool +rtems_bdbuf_swapout_processing (unsigned long timer_delta, + bool update_timers, + rtems_bdbuf_swapout_transfer* transfer) +{ + rtems_bdbuf_swapout_worker* worker; + bool transfered_buffers = false; + + rtems_bdbuf_lock_cache (); + + /* + * If a sync is active do not use a worker because the current code does not + * cleaning up after. We need to know the buffers have been written when + * syncing to release sync lock and currently worker threads do not return to + * here. We do not know the worker is the last in a sequence of sync writes + * until after we have it running so we do not know to tell it to release the + * lock. The simplest solution is to get the main swap out task perform all + * sync operations. + */ + if (bdbuf_cache.sync_active) + worker = NULL; + else + { + worker = (rtems_bdbuf_swapout_worker*) + rtems_chain_get (&bdbuf_cache.swapout_workers); + if (worker) + transfer = &worker->transfer; + } + + rtems_chain_initialize_empty (&transfer->bds); + transfer->dev = BDBUF_INVALID_DEV; + transfer->syncing = bdbuf_cache.sync_active; + + /* + * When the sync is for a device limit the sync to that device. If the sync + * is for a buffer handle process the devices in the order on the sync + * list. This means the dev is BDBUF_INVALID_DEV. + */ + if (bdbuf_cache.sync_active) + transfer->dev = bdbuf_cache.sync_device; + + /* + * If we have any buffers in the sync queue move them to the modified + * list. The first sync buffer will select the device we use. + */ + rtems_bdbuf_swapout_modified_processing (&transfer->dev, + &bdbuf_cache.sync, + &transfer->bds, + true, false, + timer_delta); + + /* + * Process the cache's modified list. + */ + rtems_bdbuf_swapout_modified_processing (&transfer->dev, + &bdbuf_cache.modified, + &transfer->bds, + bdbuf_cache.sync_active, + update_timers, + timer_delta); + + /* + * We have all the buffers that have been modified for this device so the + * cache can be unlocked because the state of each buffer has been set to + * TRANSFER. + */ + rtems_bdbuf_unlock_cache (); + + /* + * If there are buffers to transfer to the media transfer them. + */ + if (!rtems_chain_is_empty (&transfer->bds)) + { + if (worker) + { + rtems_status_code sc = rtems_event_send (worker->id, + RTEMS_BDBUF_SWAPOUT_SYNC); + if (sc != RTEMS_SUCCESSFUL) + rtems_fatal_error_occurred (RTEMS_BLKDEV_FATAL_BDBUF_SO_WAKE); + } + else + { + rtems_bdbuf_swapout_write (transfer); + } + + transfered_buffers = true; + } + + if (bdbuf_cache.sync_active && !transfered_buffers) + { + rtems_id sync_requester; + rtems_bdbuf_lock_cache (); + sync_requester = bdbuf_cache.sync_requester; + bdbuf_cache.sync_active = false; + bdbuf_cache.sync_requester = 0; + rtems_bdbuf_unlock_cache (); + if (sync_requester) + rtems_event_send (sync_requester, RTEMS_BDBUF_TRANSFER_SYNC); + } + + return transfered_buffers; +} + +/** + * Allocate the write request and initialise it for good measure. + * + * @return rtems_blkdev_request* The write reference memory. + */ +static rtems_blkdev_request* +rtems_bdbuf_swapout_writereq_alloc (void) +{ + /* + * @note chrisj The rtems_blkdev_request and the array at the end is a hack. + * I am disappointment at finding code like this in RTEMS. The request should + * have been a rtems_chain_control. Simple, fast and less storage as the node + * is already part of the buffer structure. + */ + rtems_blkdev_request* write_req = + malloc (sizeof (rtems_blkdev_request) + + (bdbuf_config.max_write_blocks * sizeof (rtems_blkdev_sg_buffer))); + + if (!write_req) + rtems_fatal_error_occurred (RTEMS_BLKDEV_FATAL_BDBUF_SO_NOMEM); + + write_req->req = RTEMS_BLKDEV_REQ_WRITE; + write_req->req_done = rtems_bdbuf_transfer_done; + write_req->done_arg = write_req; + write_req->io_task = rtems_task_self (); + + return write_req; +} + +/** + * The swapout worker thread body. + * + * @param arg A pointer to the worker thread's private data. + * @return rtems_task Not used. + */ +static rtems_task +rtems_bdbuf_swapout_worker_task (rtems_task_argument arg) +{ + rtems_bdbuf_swapout_worker* worker = (rtems_bdbuf_swapout_worker*) arg; + + while (worker->enabled) + { + rtems_bdbuf_wait_for_event (RTEMS_BDBUF_SWAPOUT_SYNC); + + rtems_bdbuf_swapout_write (&worker->transfer); + + rtems_bdbuf_lock_cache (); + + rtems_chain_initialize_empty (&worker->transfer.bds); + worker->transfer.dev = BDBUF_INVALID_DEV; + + rtems_chain_append (&bdbuf_cache.swapout_workers, &worker->link); + + rtems_bdbuf_unlock_cache (); + } + + free (worker->transfer.write_req); + free (worker); + + rtems_task_delete (RTEMS_SELF); +} + +/** + * Open the swapout worker threads. + */ +static void +rtems_bdbuf_swapout_workers_open (void) +{ + rtems_status_code sc; + size_t w; + + rtems_bdbuf_lock_cache (); + + for (w = 0; w < bdbuf_config.swapout_workers; w++) + { + rtems_bdbuf_swapout_worker* worker; + + worker = malloc (sizeof (rtems_bdbuf_swapout_worker)); + if (!worker) + rtems_fatal_error_occurred (RTEMS_BLKDEV_FATAL_BDBUF_SO_NOMEM); + + rtems_chain_append (&bdbuf_cache.swapout_workers, &worker->link); + worker->enabled = true; + worker->transfer.write_req = rtems_bdbuf_swapout_writereq_alloc (); + + rtems_chain_initialize_empty (&worker->transfer.bds); + worker->transfer.dev = BDBUF_INVALID_DEV; + + sc = rtems_task_create (rtems_build_name('B', 'D', 'o', 'a' + w), + (bdbuf_config.swapout_priority ? + bdbuf_config.swapout_priority : + RTEMS_BDBUF_SWAPOUT_TASK_PRIORITY_DEFAULT), + SWAPOUT_TASK_STACK_SIZE, + RTEMS_PREEMPT | RTEMS_NO_TIMESLICE | RTEMS_NO_ASR, + RTEMS_LOCAL | RTEMS_NO_FLOATING_POINT, + &worker->id); + if (sc != RTEMS_SUCCESSFUL) + rtems_fatal_error_occurred (RTEMS_BLKDEV_FATAL_BDBUF_SO_WK_CREATE); + + sc = rtems_task_start (worker->id, + rtems_bdbuf_swapout_worker_task, + (rtems_task_argument) worker); + if (sc != RTEMS_SUCCESSFUL) + rtems_fatal_error_occurred (RTEMS_BLKDEV_FATAL_BDBUF_SO_WK_START); + } + + rtems_bdbuf_unlock_cache (); +} + +/** + * Close the swapout worker threads. + */ +static void +rtems_bdbuf_swapout_workers_close (void) +{ + rtems_chain_node* node; + + rtems_bdbuf_lock_cache (); + + node = rtems_chain_first (&bdbuf_cache.swapout_workers); + while (!rtems_chain_is_tail (&bdbuf_cache.swapout_workers, node)) + { + rtems_bdbuf_swapout_worker* worker = (rtems_bdbuf_swapout_worker*) node; + worker->enabled = false; + rtems_event_send (worker->id, RTEMS_BDBUF_SWAPOUT_SYNC); + node = rtems_chain_next (node); + } + + rtems_bdbuf_unlock_cache (); +} + +/** + * Body of task which takes care on flushing modified buffers to the disk. + * + * @param arg A pointer to the global cache data. Use the global variable and + * not this. + * @return rtems_task Not used. + */ +static rtems_task +rtems_bdbuf_swapout_task (rtems_task_argument arg) +{ + rtems_bdbuf_swapout_transfer transfer; + uint32_t period_in_ticks; + const uint32_t period_in_msecs = bdbuf_config.swapout_period;; + uint32_t timer_delta; + + transfer.write_req = rtems_bdbuf_swapout_writereq_alloc (); + rtems_chain_initialize_empty (&transfer.bds); + transfer.dev = BDBUF_INVALID_DEV; + transfer.syncing = false; + + /* + * Localise the period. + */ + period_in_ticks = RTEMS_MICROSECONDS_TO_TICKS (period_in_msecs * 1000); + + /* + * This is temporary. Needs to be changed to use the real time clock. + */ + timer_delta = period_in_msecs; + + /* + * Create the worker threads. + */ + rtems_bdbuf_swapout_workers_open (); + + while (bdbuf_cache.swapout_enabled) + { + rtems_event_set out; + rtems_status_code sc; + + /* + * Only update the timers once in the processing cycle. + */ + bool update_timers = true; + + /* + * If we write buffers to any disk perform a check again. We only write a + * single device at a time and the cache may have more than one device's + * buffers modified waiting to be written. + */ + bool transfered_buffers; + + do + { + transfered_buffers = false; + + /* + * Extact all the buffers we find for a specific device. The device is + * the first one we find on a modified list. Process the sync queue of + * buffers first. + */ + if (rtems_bdbuf_swapout_processing (timer_delta, + update_timers, + &transfer)) + { + transfered_buffers = true; + } + + /* + * Only update the timers once. + */ + update_timers = false; + } + while (transfered_buffers); + + sc = rtems_event_receive (RTEMS_BDBUF_SWAPOUT_SYNC, + RTEMS_EVENT_ALL | RTEMS_WAIT, + period_in_ticks, + &out); + + if ((sc != RTEMS_SUCCESSFUL) && (sc != RTEMS_TIMEOUT)) + rtems_fatal_error_occurred (BLKDEV_FATAL_BDBUF_SWAPOUT_RE); + } + + rtems_bdbuf_swapout_workers_close (); + + free (transfer.write_req); + + rtems_task_delete (RTEMS_SELF); +} + +static void +rtems_bdbuf_purge_list (rtems_chain_control *purge_list) +{ + bool wake_buffer_waiters = false; + rtems_chain_node *node = NULL; + + while ((node = rtems_chain_get (purge_list)) != NULL) + { + rtems_bdbuf_buffer *bd = (rtems_bdbuf_buffer *) node; + + if (bd->waiters == 0) + wake_buffer_waiters = true; + + rtems_bdbuf_discard_buffer (bd); + } + + if (wake_buffer_waiters) + rtems_bdbuf_wake (&bdbuf_cache.buffer_waiters); +} + +typedef bool (*rtems_bdbuf_purge_compare)(dev_t a, dev_t b); + +static void +rtems_bdbuf_gather_for_purge (rtems_chain_control *purge_list, + rtems_bdbuf_purge_compare compare, + dev_t dev) +{ + rtems_bdbuf_buffer *stack [RTEMS_BDBUF_AVL_MAX_HEIGHT]; + rtems_bdbuf_buffer **prev = stack; + rtems_bdbuf_buffer *cur = bdbuf_cache.tree; + + *prev = NULL; + + while (cur != NULL) + { + if ((*compare) (cur->dev, dev)) + { + switch (cur->state) + { + case RTEMS_BDBUF_STATE_FREE: + case RTEMS_BDBUF_STATE_EMPTY: + case RTEMS_BDBUF_STATE_ACCESS_PURGED: + case RTEMS_BDBUF_STATE_TRANSFER_PURGED: + break; + case RTEMS_BDBUF_STATE_SYNC: + rtems_bdbuf_wake (&bdbuf_cache.transfer_waiters); + /* Fall through */ + case RTEMS_BDBUF_STATE_MODIFIED: + rtems_bdbuf_group_release (cur); + /* Fall through */ + case RTEMS_BDBUF_STATE_CACHED: + rtems_chain_extract (&cur->link); + rtems_chain_append (purge_list, &cur->link); + break; + case RTEMS_BDBUF_STATE_TRANSFER: + rtems_bdbuf_set_state (cur, RTEMS_BDBUF_STATE_TRANSFER_PURGED); + break; + case RTEMS_BDBUF_STATE_ACCESS_CACHED: + case RTEMS_BDBUF_STATE_ACCESS_EMPTY: + case RTEMS_BDBUF_STATE_ACCESS_MODIFIED: + rtems_bdbuf_set_state (cur, RTEMS_BDBUF_STATE_ACCESS_PURGED); + break; + default: + rtems_fatal_error_occurred (RTEMS_BLKDEV_FATAL_BDBUF_STATE_11); + } + } + + if (cur->avl.left != NULL) + { + /* Left */ + ++prev; + *prev = cur; + cur = cur->avl.left; + } + else if (cur->avl.right != NULL) + { + /* Right */ + ++prev; + *prev = cur; + cur = cur->avl.right; + } + else + { + while (*prev != NULL && cur == (*prev)->avl.right) + { + /* Up */ + cur = *prev; + --prev; + } + if (*prev != NULL) + /* Right */ + cur = (*prev)->avl.right; + else + /* Finished */ + cur = NULL; + } + } +} + +static void +rtems_bdbuf_purge (rtems_bdbuf_purge_compare compare, dev_t dev) +{ + rtems_chain_control purge_list; + + rtems_chain_initialize_empty (&purge_list); + rtems_bdbuf_lock_cache (); + rtems_bdbuf_gather_for_purge (&purge_list, compare, dev); + rtems_bdbuf_purge_list (&purge_list); + rtems_bdbuf_unlock_cache (); +} + +static bool +rtems_bdbuf_purge_compare_dev (dev_t a, dev_t b) +{ + return a == b; +} + +void +rtems_bdbuf_purge_dev (dev_t dev) +{ + rtems_bdbuf_purge (rtems_bdbuf_purge_compare_dev, dev); +} + +static bool +rtems_bdbuf_purge_compare_major (dev_t a, dev_t b) +{ + return rtems_filesystem_dev_major_t (a) == rtems_filesystem_dev_major_t (b); +} + +void +rtems_bdbuf_purge_major (rtems_device_major_number major) +{ + dev_t dev = rtems_filesystem_make_dev_t (major, 0); + + rtems_bdbuf_purge (rtems_bdbuf_purge_compare_major, dev); +} diff --git a/cpukit/libblock/src/bdpart-create.c b/cpukit/libblock/src/bdpart-create.c new file mode 100644 index 0000000000..dc93522c19 --- /dev/null +++ b/cpukit/libblock/src/bdpart-create.c @@ -0,0 +1,160 @@ +/** + * @file + * + * @ingroup rtems_bdpart + * + * Block device partition management. + */ + +/* + * Copyright (c) 2009, 2010 + * embedded brains GmbH + * Obere Lagerstr. 30 + * D-82178 Puchheim + * Germany + * <rtems@embedded-brains.de> + * + * 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. + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include <rtems.h> +#include <rtems/bdpart.h> + +rtems_status_code rtems_bdpart_create( + const char *disk_name, + const rtems_bdpart_format *format, + rtems_bdpart_partition *pt, + const unsigned *dist, + size_t count +) +{ + rtems_status_code sc = RTEMS_SUCCESSFUL; + bool dos_compatibility = format != NULL + && format->type == RTEMS_BDPART_FORMAT_MBR + && format->mbr.dos_compatibility; + rtems_blkdev_bnum disk_end = 0; + rtems_blkdev_bnum pos = 0; + rtems_blkdev_bnum dist_sum = 0; + rtems_blkdev_bnum record_space = + dos_compatibility ? RTEMS_BDPART_MBR_CYLINDER_SIZE : 1; + rtems_blkdev_bnum overhead = 0; + rtems_blkdev_bnum free_space = 0; + dev_t disk = 0; + size_t i = 0; + + /* Check if we have something to do */ + if (count == 0) { + /* Nothing to do */ + return RTEMS_SUCCESSFUL; + } + + /* Check parameter */ + if (format == NULL || pt == NULL || dist == NULL) { + return RTEMS_INVALID_ADDRESS; + } + + /* Get disk data */ + sc = rtems_bdpart_get_disk_data( disk_name, &disk, &disk_end); + if (sc != RTEMS_SUCCESSFUL) { + return sc; + } + + /* Get distribution sum and check for overflow */ + for (i = 0; i < count; ++i) { + unsigned prev_sum = dist_sum; + + dist_sum += dist [i]; + + if (dist_sum < prev_sum) { + return RTEMS_INVALID_NUMBER; + } + + if (dist [i] == 0) { + return RTEMS_INVALID_NUMBER; + } + } + + /* Check format */ + if (format->type != RTEMS_BDPART_FORMAT_MBR) { + return RTEMS_NOT_IMPLEMENTED; + } + + /* Align end of disk on cylinder boundary if necessary */ + if (dos_compatibility) { + disk_end -= (disk_end % record_space); + } + + /* + * We need at least space for the MBR and the compatibility space for the + * first primary partition. + */ + overhead += record_space; + + /* + * In case we need an extended partition and logical partitions we have to + * account for the space of each EBR. + */ + if (count > 4) { + overhead += (count - 3) * record_space; + } + + /* + * Account space to align every partition on cylinder boundaries if + * necessary. + */ + if (dos_compatibility) { + overhead += (count - 1) * record_space; + } + + /* Check disk space */ + if ((overhead + count) > disk_end) { + return RTEMS_IO_ERROR; + } + + /* Begin of first primary partition */ + pos = record_space; + + /* Space for partitions */ + free_space = disk_end - overhead; + + for (i = 0; i < count; ++i) { + rtems_bdpart_partition *p = pt + i; + + /* Partition size */ + rtems_blkdev_bnum s = free_space * dist [i]; + if (s < free_space || s < dist [i]) { + /* TODO: Calculate without overflow */ + return RTEMS_INVALID_NUMBER; + } + s /= dist_sum; + + /* Ensure that the partition is not empty */ + if (s == 0) { + s = 1; + } + + /* Align partition upwards */ + s += record_space - (s % record_space); + + /* Partition begin and end */ + p->begin = pos; + pos += s; + p->end = pos; + + /* Reserve space for the EBR if necessary */ + if (count > 4 && i > 2) { + p->begin += record_space; + } + } + + /* Expand the last partition to the disk end */ + pt [count - 1].end = disk_end; + + return RTEMS_SUCCESSFUL; +} diff --git a/cpukit/libblock/src/bdpart-dump.c b/cpukit/libblock/src/bdpart-dump.c new file mode 100644 index 0000000000..abd85b6037 --- /dev/null +++ b/cpukit/libblock/src/bdpart-dump.c @@ -0,0 +1,97 @@ +/** + * @file + * + * @ingroup rtems_bdpart + * + * Block device partition management. + */ + +/* + * Copyright (c) 2009, 2010 + * embedded brains GmbH + * Obere Lagerstr. 30 + * D-82178 Puchheim + * Germany + * <rtems@embedded-brains.de> + * + * 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. + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include <stdio.h> +#include <inttypes.h> + +#include <rtems.h> +#include <rtems/bdpart.h> + +static void rtems_bdpart_type_to_string( + const uuid_t type, + char str [37] +) +{ + uuid_unparse_lower( type, str); +} + +void rtems_bdpart_dump( const rtems_bdpart_partition *pt, size_t count) +{ + size_t i = 0; + + printf( + "-------------------------------------------------------------------------------\n" + " PARTITION TABLE\n" + "------------+------------+-----------------------------------------------------\n" + " BEGIN | END | TYPE\n" + "------------+------------+-----------------------------------------------------\n" + ); + + for (i = 0; i < count; ++i) { + const rtems_bdpart_partition *p = pt + i; + const char *type = NULL; + char type_buffer [52]; + uint8_t type_mbr = 0; + + if (rtems_bdpart_to_mbr_partition_type( p->type, &type_mbr)) { + switch (type_mbr) { + case RTEMS_BDPART_MBR_FAT_12: + type = "FAT 12"; + break; + case RTEMS_BDPART_MBR_FAT_16: + type = "FAT 16"; + break; + case RTEMS_BDPART_MBR_FAT_16_LBA: + type = "FAT 16 LBA"; + break; + case RTEMS_BDPART_MBR_FAT_32: + type = "FAT 32"; + break; + case RTEMS_BDPART_MBR_FAT_32_LBA: + type = "FAT 32 LBA"; + break; + case RTEMS_BDPART_MBR_DATA: + type = "DATA"; + break; + default: + snprintf( type_buffer, sizeof( type_buffer), "0x%02" PRIx8, type_mbr); + type = type_buffer; + break; + } + } else { + rtems_bdpart_type_to_string( p->type, type_buffer); + type = type_buffer; + } + + printf( + " %10" PRIu32 " | %10" PRIu32 " |%52s\n", + p->begin, + p->end, + type + ); + } + + puts( "------------+------------+-----------------------------------------------------"); +} diff --git a/cpukit/libblock/src/bdpart-mount.c b/cpukit/libblock/src/bdpart-mount.c new file mode 100644 index 0000000000..aef15dfbaa --- /dev/null +++ b/cpukit/libblock/src/bdpart-mount.c @@ -0,0 +1,184 @@ +/** + * @file + * + * @ingroup rtems_bdpart + * + * Block device partition management. + */ + +/* + * Copyright (c) 2009, 2010 + * embedded brains GmbH + * Obere Lagerstr. 30 + * D-82178 Puchheim + * Germany + * <rtems@embedded-brains.de> + * + * 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. + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include <rtems.h> +#include <rtems/bdpart.h> +#include <rtems/libio.h> + +rtems_status_code rtems_bdpart_mount( + const char *disk_name, + const rtems_bdpart_partition *pt __attribute__((unused)), + size_t count, + const char *mount_base +) +{ + rtems_status_code esc = RTEMS_SUCCESSFUL; + const char *disk_file_name = strrchr( disk_name, '/'); + char *logical_disk_name = NULL; + char *logical_disk_marker = NULL; + char *mount_point = NULL; + char *mount_marker = NULL; + size_t disk_file_name_size = 0; + size_t disk_name_size = strlen( disk_name); + size_t mount_base_size = strlen( mount_base); + size_t i = 0; + + /* Create logical disk name base */ + logical_disk_name = malloc( disk_name_size + RTEMS_BDPART_NUMBER_SIZE); + if (logical_disk_name == NULL) { + return RTEMS_NO_MEMORY; + } + strncpy( logical_disk_name, disk_name, disk_name_size); + + /* Get disk file name */ + if (disk_file_name != NULL) { + disk_file_name += 1; + disk_file_name_size = strlen( disk_file_name); + } else { + disk_file_name = disk_name; + disk_file_name_size = disk_name_size; + } + + /* Create mount point base */ + mount_point = malloc( mount_base_size + 1 + disk_file_name_size + RTEMS_BDPART_NUMBER_SIZE); + if (mount_point == NULL) { + esc = RTEMS_NO_MEMORY; + goto cleanup; + } + strncpy( mount_point, mount_base, mount_base_size); + mount_point [mount_base_size] = '/'; + strncpy( mount_point + mount_base_size + 1, disk_file_name, disk_file_name_size); + + /* Markers */ + logical_disk_marker = logical_disk_name + disk_name_size; + mount_marker = mount_point + mount_base_size + 1 + disk_file_name_size; + + /* Mount supported file systems for each partition */ + for (i = 0; i < count; ++i) { + /* Create logical disk name */ + int rv = snprintf( logical_disk_marker, RTEMS_BDPART_NUMBER_SIZE, "%zu", i + 1); + if (rv >= RTEMS_BDPART_NUMBER_SIZE) { + esc = RTEMS_INVALID_NAME; + goto cleanup; + } + + /* Create mount point */ + strncpy( mount_marker, logical_disk_marker, RTEMS_BDPART_NUMBER_SIZE); + rv = rtems_mkdir( mount_point, S_IRWXU | S_IRWXG | S_IRWXO); + if (rv != 0) { + esc = RTEMS_IO_ERROR; + goto cleanup; + } + + /* Mount */ + rv = mount( + logical_disk_name, + mount_point, + "msdos", + 0, + NULL + ); + if (rv != 0) { + rmdir( mount_point); + } + } + +cleanup: + + free( logical_disk_name); + free( mount_point); + + return esc; +} + +rtems_status_code rtems_bdpart_unmount( + const char *disk_name, + const rtems_bdpart_partition *pt __attribute__((unused)), + size_t count, + const char *mount_base +) +{ + rtems_status_code esc = RTEMS_SUCCESSFUL; + const char *disk_file_name = strrchr( disk_name, '/'); + char *mount_point = NULL; + char *mount_marker = NULL; + size_t disk_file_name_size = 0; + size_t disk_name_size = strlen( disk_name); + size_t mount_base_size = strlen( mount_base); + size_t i = 0; + + /* Get disk file name */ + if (disk_file_name != NULL) { + disk_file_name += 1; + disk_file_name_size = strlen( disk_file_name); + } else { + disk_file_name = disk_name; + disk_file_name_size = disk_name_size; + } + + /* Create mount point base */ + mount_point = malloc( mount_base_size + 1 + disk_file_name_size + RTEMS_BDPART_NUMBER_SIZE); + if (mount_point == NULL) { + esc = RTEMS_NO_MEMORY; + goto cleanup; + } + strncpy( mount_point, mount_base, mount_base_size); + mount_point [mount_base_size] = '/'; + strncpy( mount_point + mount_base_size + 1, disk_file_name, disk_file_name_size); + + /* Marker */ + mount_marker = mount_point + mount_base_size + 1 + disk_file_name_size; + + /* Mount supported file systems for each partition */ + for (i = 0; i < count; ++i) { + /* Create mount point */ + int rv = snprintf( mount_marker, RTEMS_BDPART_NUMBER_SIZE, "%zu", i + 1); + if (rv >= RTEMS_BDPART_NUMBER_SIZE) { + esc = RTEMS_INVALID_NAME; + goto cleanup; + } + + /* Unmount */ + rv = unmount( mount_point); + if (rv == 0) { + /* Remove mount point */ + rv = rmdir( mount_point); + if (rv != 0) { + esc = RTEMS_IO_ERROR; + goto cleanup; + } + } + } + +cleanup: + + free( mount_point); + + return esc; +} diff --git a/cpukit/libblock/src/bdpart-read.c b/cpukit/libblock/src/bdpart-read.c new file mode 100644 index 0000000000..3560dbd3a3 --- /dev/null +++ b/cpukit/libblock/src/bdpart-read.c @@ -0,0 +1,348 @@ +/** + * @file + * + * @ingroup rtems_bdpart + * + * Block device partition management. + */ + +/* + * Copyright (c) 2009, 2010 + * embedded brains GmbH + * Obere Lagerstr. 30 + * D-82178 Puchheim + * Germany + * <rtems@embedded-brains.de> + * + * 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. + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include <rtems.h> +#include <rtems/bdbuf.h> +#include <rtems/bdpart.h> +#include <rtems/endian.h> + +#define RTEMS_BDPART_MBR_PARTITION_TYPE( type) \ + { \ + (type), 0xa2U, 0x2eU, 0x38U, \ + 0x38U, 0xb5U, 0xdeU, 0x11U, \ + 0xbcU, 0x13U, 0x00U, 0x1dU, \ + 0x09U, 0xb0U, 0x5fU, 0xa4U \ + } + +static const uuid_t RTEMS_BDPART_MBR_MASTER_TYPE = + RTEMS_BDPART_MBR_PARTITION_TYPE( RTEMS_BDPART_MBR_EMPTY); + +void rtems_bdpart_to_partition_type( uint8_t mbr_type, uuid_t type) +{ + type [0] = mbr_type; + memcpy( type + 1, RTEMS_BDPART_MBR_MASTER_TYPE + 1, sizeof( uuid_t) - 1); +} + +bool rtems_bdpart_to_mbr_partition_type( + const uuid_t type, + uint8_t *mbr_type +) +{ + *mbr_type = rtems_bdpart_mbr_partition_type( type); + + return memcmp( + type + 1, + RTEMS_BDPART_MBR_MASTER_TYPE + 1, + sizeof( uuid_t) - 1 + ) == 0; +} + +/* + * FIXME: This code should the deviceio interface and not the bdbug interface. + */ +rtems_status_code rtems_bdpart_get_disk_data( + const char *disk_name, + dev_t *disk, + rtems_blkdev_bnum *disk_end +) +{ + rtems_status_code sc = RTEMS_SUCCESSFUL; + int rv = 0; + rtems_blkdev_bnum disk_begin = 0; + rtems_blkdev_bnum block_size = 0; + rtems_disk_device *dd = NULL; + struct stat st; + + /* Get disk handle */ + rv = stat( disk_name, &st); + if (rv != 0) { + return RTEMS_INVALID_NAME; + } + *disk = st.st_rdev; + + /* Get disk begin, end and block size */ + dd = rtems_disk_obtain( *disk); + if (dd == NULL) { + return RTEMS_INVALID_NAME; + } + disk_begin = dd->start; + *disk_end = dd->size; + block_size = dd->block_size; + sc = rtems_disk_release( dd); + if (sc != RTEMS_SUCCESSFUL) { + return sc; + } + + /* Check block size */ + if (block_size < RTEMS_BDPART_BLOCK_SIZE) { + return RTEMS_IO_ERROR; + } + + /* Check that we have do not have a logical disk */ + if (disk_begin != 0) { + return RTEMS_IO_ERROR; + } + + return RTEMS_SUCCESSFUL; +} + +static bool rtems_bdpart_is_valid_record( const uint8_t *data) +{ + return data [RTEMS_BDPART_MBR_OFFSET_SIGNATURE_0] + == RTEMS_BDPART_MBR_SIGNATURE_0 + && data [RTEMS_BDPART_MBR_OFFSET_SIGNATURE_1] + == RTEMS_BDPART_MBR_SIGNATURE_1; +} + +static rtems_blkdev_bnum rtems_bdpart_next_ebr( const uint8_t *data) +{ + rtems_blkdev_bnum begin = + rtems_uint32_from_little_endian( data + RTEMS_BDPART_MBR_OFFSET_BEGIN); + uint8_t type = data [RTEMS_BDPART_MBR_OFFSET_TYPE]; + + if (type == RTEMS_BDPART_MBR_EXTENDED) { + return begin; + } else { + return 0; + } +} + +static rtems_status_code rtems_bdpart_read_mbr_partition( + const uint8_t *data, + rtems_bdpart_partition **p, + const rtems_bdpart_partition *p_end, + rtems_blkdev_bnum *ep_begin +) +{ + rtems_blkdev_bnum begin = + rtems_uint32_from_little_endian( data + RTEMS_BDPART_MBR_OFFSET_BEGIN); + rtems_blkdev_bnum size = + rtems_uint32_from_little_endian( data + RTEMS_BDPART_MBR_OFFSET_SIZE); + rtems_blkdev_bnum end = begin + size; + uint8_t type = data [RTEMS_BDPART_MBR_OFFSET_TYPE]; + + if (type == RTEMS_BDPART_MBR_EMPTY) { + return RTEMS_SUCCESSFUL; + } else if (*p == p_end) { + return RTEMS_TOO_MANY; + } else if (begin >= end) { + return RTEMS_IO_ERROR; + } else if (type == RTEMS_BDPART_MBR_EXTENDED) { + if (ep_begin != NULL) { + *ep_begin = begin; + } + } else { + /* Increment partition index */ + ++(*p); + + /* Clear partition */ + memset( *p, 0, sizeof( rtems_bdpart_partition)); + + /* Set values */ + (*p)->begin = begin; + (*p)->end = end; + rtems_bdpart_to_partition_type( type, (*p)->type); + (*p)->flags = data [RTEMS_BDPART_MBR_OFFSET_FLAGS]; + } + + return RTEMS_SUCCESSFUL; +} + +static rtems_status_code rtems_bdpart_read_record( + dev_t disk, + rtems_blkdev_bnum index, + rtems_bdbuf_buffer **block +) +{ + rtems_status_code sc = RTEMS_SUCCESSFUL; + + /* Release previous block if necessary */ + if (*block != NULL) { + sc = rtems_bdbuf_release( *block); + if (sc != RTEMS_SUCCESSFUL) { + return sc; + } + } + + /* Read the record block */ + sc = rtems_bdbuf_read( disk, index, block); + if (sc != RTEMS_SUCCESSFUL) { + return sc; + } + + /* just in case block did not get filled in */ + if ( *block == NULL ) { + return RTEMS_INVALID_ADDRESS; + } + + /* Check MBR signature */ + if (!rtems_bdpart_is_valid_record( (*block)->buffer)) { + return RTEMS_IO_ERROR; + } + + return RTEMS_SUCCESSFUL; +} + +rtems_status_code rtems_bdpart_read( + const char *disk_name, + rtems_bdpart_format *format, + rtems_bdpart_partition *pt, + size_t *count +) +{ + rtems_status_code sc = RTEMS_SUCCESSFUL; + rtems_status_code esc = RTEMS_SUCCESSFUL; + rtems_bdbuf_buffer *block = NULL; + rtems_bdpart_partition *p = pt - 1; + const rtems_bdpart_partition *p_end = pt + (count != NULL ? *count : 0); + rtems_blkdev_bnum ep_begin = 0; /* Extended partition begin */ + rtems_blkdev_bnum ebr = 0; /* Extended boot record block index */ + rtems_blkdev_bnum disk_end = 0; + dev_t disk = 0; + size_t i = 0; + const uint8_t *data = NULL; + + /* Check parameter */ + if (format == NULL || pt == NULL || count == NULL) { + return RTEMS_INVALID_ADDRESS; + } + + /* Set count to a save value */ + *count = 0; + + /* Get disk data */ + sc = rtems_bdpart_get_disk_data( disk_name, &disk, &disk_end); + if (sc != RTEMS_SUCCESSFUL) { + return sc; + } + + /* Read MBR */ + sc = rtems_bdpart_read_record( disk, 0, &block); + if (sc != RTEMS_SUCCESSFUL) { + esc = sc; + goto cleanup; + } + + /* Read the first partition entry */ + data = block->buffer + RTEMS_BDPART_MBR_OFFSET_TABLE_0; + sc = rtems_bdpart_read_mbr_partition( data, &p, p_end, &ep_begin); + if (sc != RTEMS_SUCCESSFUL) { + esc = sc; + goto cleanup; + } + + /* Determine if we have a MBR or GPT format */ + if (rtems_bdpart_mbr_partition_type( p->type) == RTEMS_BDPART_MBR_GPT) { + esc = RTEMS_NOT_IMPLEMENTED; + goto cleanup; + } + + /* Set format */ + format->type = RTEMS_BDPART_FORMAT_MBR; + format->mbr.disk_id = rtems_uint32_from_little_endian( + block->buffer + RTEMS_BDPART_MBR_OFFSET_DISK_ID + ); + format->mbr.dos_compatibility = true; + + /* Iterate through the rest of the primary partition table */ + for (i = 1; i < 4; ++i) { + data += RTEMS_BDPART_MBR_TABLE_ENTRY_SIZE; + + sc = rtems_bdpart_read_mbr_partition( data, &p, p_end, &ep_begin); + if (sc != RTEMS_SUCCESSFUL) { + esc = sc; + goto cleanup; + } + } + + /* Iterate through the logical partitions within the extended partition */ + ebr = ep_begin; + while (ebr != 0) { + rtems_blkdev_bnum tmp = 0; + + /* Read EBR */ + sc = rtems_bdpart_read_record( disk, ebr, &block); + if (sc != RTEMS_SUCCESSFUL) { + esc = sc; + goto cleanup; + } + + /* Read first partition entry */ + sc = rtems_bdpart_read_mbr_partition( + block->buffer + RTEMS_BDPART_MBR_OFFSET_TABLE_0, + &p, + p_end, + NULL + ); + if (sc != RTEMS_SUCCESSFUL) { + esc = sc; + goto cleanup; + } + + /* Adjust partition begin */ + tmp = p->begin + ebr; + if (tmp > p->begin) { + p->begin = tmp; + } else { + esc = RTEMS_IO_ERROR; + goto cleanup; + } + + /* Adjust partition end */ + tmp = p->end + ebr; + if (tmp > p->end) { + p->end = tmp; + } else { + esc = RTEMS_IO_ERROR; + goto cleanup; + } + + /* Read second partition entry for next EBR block */ + ebr = rtems_bdpart_next_ebr( + block->buffer + RTEMS_BDPART_MBR_OFFSET_TABLE_1 + ); + if (ebr != 0) { + /* Adjust partition EBR block index */ + tmp = ebr + ep_begin; + if (tmp > ebr) { + ebr = tmp; + } else { + esc = RTEMS_IO_ERROR; + goto cleanup; + } + } + } + + /* Return partition count */ + *count = (size_t) (p - pt + 1); + +cleanup: + + if (block != NULL) { + rtems_bdbuf_release( block); + } + + return esc; +} diff --git a/cpukit/libblock/src/bdpart-register.c b/cpukit/libblock/src/bdpart-register.c new file mode 100644 index 0000000000..172c8b4964 --- /dev/null +++ b/cpukit/libblock/src/bdpart-register.c @@ -0,0 +1,163 @@ +/** + * @file + * + * @ingroup rtems_bdpart + * + * Block device partition management. + */ + +/* + * Copyright (c) 2009, 2010 + * embedded brains GmbH + * Obere Lagerstr. 30 + * D-82178 Puchheim + * Germany + * <rtems@embedded-brains.de> + * + * 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. + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include <rtems.h> +#include <rtems/bdpart.h> + +rtems_status_code rtems_bdpart_register( + const char *disk_name, + const rtems_bdpart_partition *pt, + size_t count +) +{ + rtems_status_code sc = RTEMS_SUCCESSFUL; + rtems_status_code esc = RTEMS_SUCCESSFUL; + rtems_device_major_number major = 0; + rtems_device_minor_number minor = 0; + rtems_blkdev_bnum disk_end = 0; + dev_t disk = 0; + dev_t logical_disk = 0; + char *logical_disk_name = NULL; + char *logical_disk_marker = NULL; + size_t disk_name_size = strlen( disk_name); + size_t i = 0; + + /* Get disk data */ + sc = rtems_bdpart_get_disk_data( disk_name, &disk, &disk_end); + if (sc != RTEMS_SUCCESSFUL) { + return sc; + } + + /* Get the disk device identifier */ + rtems_filesystem_split_dev_t( disk, major, minor); + + /* Create logical disk name */ + logical_disk_name = malloc( disk_name_size + RTEMS_BDPART_NUMBER_SIZE); + if (logical_disk_name == NULL) { + return RTEMS_NO_MEMORY; + } + strncpy( logical_disk_name, disk_name, disk_name_size); + logical_disk_marker = logical_disk_name + disk_name_size; + + /* Create a logical disk for each partition */ + for (i = 0; i < count; ++i) { + const rtems_bdpart_partition *p = pt + i; + int rv = 0; + + /* New minor number */ + ++minor; + + /* Create a new device identifier */ + logical_disk = rtems_filesystem_make_dev_t( major, minor); + + /* Set partition number for logical disk name */ + rv = snprintf( logical_disk_marker, RTEMS_BDPART_NUMBER_SIZE, "%zu", i + 1); + if (rv >= RTEMS_BDPART_NUMBER_SIZE) { + esc = RTEMS_INVALID_NAME; + goto cleanup; + } + + /* Create logical disk */ + sc = rtems_disk_create_log( + logical_disk, + disk, + p->begin, + p->end - p->begin, + logical_disk_name + ); + if (sc != RTEMS_SUCCESSFUL) { + esc = sc; + goto cleanup; + } + } + +cleanup: + + free( logical_disk_name); + + return esc; +} + +rtems_status_code rtems_bdpart_register_from_disk( const char *disk_name) +{ + rtems_status_code sc = RTEMS_SUCCESSFUL; + rtems_bdpart_format format; + rtems_bdpart_partition pt [RTEMS_BDPART_PARTITION_NUMBER_HINT]; + size_t count = RTEMS_BDPART_PARTITION_NUMBER_HINT; + + /* Read partitions */ + sc = rtems_bdpart_read( disk_name, &format, pt, &count); + if (sc != RTEMS_SUCCESSFUL) { + return sc; + } + + /* Register partitions */ + return rtems_bdpart_register( disk_name, pt, count); +} + +rtems_status_code rtems_bdpart_unregister( + const char *disk_name, + const rtems_bdpart_partition *pt __attribute__((unused)), + size_t count +) +{ + rtems_status_code sc = RTEMS_SUCCESSFUL; + rtems_device_major_number major = 0; + rtems_device_minor_number minor = 0; + rtems_blkdev_bnum disk_end = 0; + dev_t disk = 0; + dev_t logical_disk = 0; + size_t i = 0; + + /* Get disk data */ + sc = rtems_bdpart_get_disk_data( disk_name, &disk, &disk_end); + if (sc != RTEMS_SUCCESSFUL) { + return sc; + } + + /* Get the disk device identifier */ + rtems_filesystem_split_dev_t( disk, major, minor); + + /* Create a logical disk for each partition */ + for (i = 0; i < count; ++i) { + /* New minor number */ + ++minor; + + /* Get the device identifier */ + logical_disk = rtems_filesystem_make_dev_t( major, minor); + + /* Delete logical disk */ + sc = rtems_disk_delete( logical_disk); + if (sc != RTEMS_SUCCESSFUL) { + return sc; + } + } + + return RTEMS_SUCCESSFUL; +} diff --git a/cpukit/libblock/src/bdpart-sort.c b/cpukit/libblock/src/bdpart-sort.c new file mode 100644 index 0000000000..1114b7a492 --- /dev/null +++ b/cpukit/libblock/src/bdpart-sort.c @@ -0,0 +1,48 @@ +/** + * @file + * + * @ingroup rtems_bdpart + * + * Block device partition management. + */ + +/* + * Copyright (c) 2009, 2010 + * embedded brains GmbH + * Obere Lagerstr. 30 + * D-82178 Puchheim + * Germany + * <rtems@embedded-brains.de> + * + * 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. + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include <stdlib.h> + +#include <rtems.h> +#include <rtems/bdpart.h> + +static int rtems_bdpart_partition_compare( const void *aa, const void *bb) +{ + const rtems_bdpart_partition *a = aa; + const rtems_bdpart_partition *b = bb; + + if (a->begin < b->begin) { + return -1; + } else if (a->begin == b->begin) { + return 0; + } else { + return 1; + } +} + +void rtems_bdpart_sort( rtems_bdpart_partition *pt, size_t count) +{ + qsort( pt, count, sizeof( *pt), rtems_bdpart_partition_compare); +} diff --git a/cpukit/libblock/src/bdpart-write.c b/cpukit/libblock/src/bdpart-write.c new file mode 100644 index 0000000000..92e80a24ec --- /dev/null +++ b/cpukit/libblock/src/bdpart-write.c @@ -0,0 +1,302 @@ +/** + * @file + * + * @ingroup rtems_bdpart + * + * Block device partition management. + */ + +/* + * Copyright (c) 2009, 2010 + * embedded brains GmbH + * Obere Lagerstr. 30 + * D-82178 Puchheim + * Germany + * <rtems@embedded-brains.de> + * + * 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. + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include <string.h> + +#include <rtems.h> +#include <rtems/bdbuf.h> +#include <rtems/bdpart.h> +#include <rtems/endian.h> + +static void rtems_bdpart_write_mbr_partition( + uint8_t *data, + uint32_t begin, + uint32_t size, + uint8_t type, + uint8_t flags +) +{ + rtems_uint32_to_little_endian( begin, data + RTEMS_BDPART_MBR_OFFSET_BEGIN); + rtems_uint32_to_little_endian( size, data + RTEMS_BDPART_MBR_OFFSET_SIZE); + data [RTEMS_BDPART_MBR_OFFSET_TYPE] = type; + data [RTEMS_BDPART_MBR_OFFSET_FLAGS] = flags; +} + +static rtems_status_code rtems_bdpart_new_record( + dev_t disk, + rtems_blkdev_bnum index, + rtems_bdbuf_buffer **block +) +{ + rtems_status_code sc = RTEMS_SUCCESSFUL; + + /* Synchronize previous block if necessary */ + if (*block != NULL) { + sc = rtems_bdbuf_sync( *block); + if (sc != RTEMS_SUCCESSFUL) { + return sc; + } + } + + /* Read the new record block (this accounts for disk block sizes > 512) */ + sc = rtems_bdbuf_read( disk, index, block); + if (sc != RTEMS_SUCCESSFUL) { + return sc; + } + + /* just in case block did not get filled in */ + if ( *block == NULL ) { + return RTEMS_INVALID_ADDRESS; + } + + /* Clear record */ + memset( (*block)->buffer, 0, RTEMS_BDPART_BLOCK_SIZE); + + /* Write signature */ + (*block)->buffer [RTEMS_BDPART_MBR_OFFSET_SIGNATURE_0] = + RTEMS_BDPART_MBR_SIGNATURE_0; + (*block)->buffer [RTEMS_BDPART_MBR_OFFSET_SIGNATURE_1] = + RTEMS_BDPART_MBR_SIGNATURE_1; + + return RTEMS_SUCCESSFUL; +} + +rtems_status_code rtems_bdpart_write( + const char *disk_name, + const rtems_bdpart_format *format, + const rtems_bdpart_partition *pt, + size_t count +) +{ + rtems_status_code sc = RTEMS_SUCCESSFUL; + rtems_status_code esc = RTEMS_SUCCESSFUL; + bool dos_compatibility = format != NULL + && format->type == RTEMS_BDPART_FORMAT_MBR + && format->mbr.dos_compatibility; + rtems_bdbuf_buffer *block = NULL; + rtems_blkdev_bnum disk_end = 0; + rtems_blkdev_bnum record_space = + dos_compatibility ? RTEMS_BDPART_MBR_CYLINDER_SIZE : 1; + dev_t disk = 0; + size_t ppc = 0; /* Primary partition count */ + size_t i = 0; + uint8_t *data = NULL; + + /* Check if we have something to do */ + if (count == 0) { + /* Nothing to do */ + return RTEMS_SUCCESSFUL; + } + + /* Check parameter */ + if (format == NULL || pt == NULL) { + return RTEMS_INVALID_ADDRESS; + } + + /* Get disk data */ + sc = rtems_bdpart_get_disk_data( disk_name, &disk, &disk_end); + if (sc != RTEMS_SUCCESSFUL) { + return sc; + } + + /* Align end of disk on cylinder boundary if necessary */ + if (dos_compatibility) { + disk_end -= (disk_end % record_space); + } + + /* Check that we have a consistent partition table */ + for (i = 0; i < count; ++i) { + const rtems_bdpart_partition *p = pt + i; + + /* Check that begin and end are proper within the disk */ + if (p->begin >= disk_end || p->end > disk_end) { + esc = RTEMS_INVALID_NUMBER; + goto cleanup; + } + + /* Check that begin and end are valid */ + if (p->begin >= p->end) { + esc = RTEMS_INVALID_NUMBER; + goto cleanup; + } + + /* Check that partitions do not overlap */ + if (i > 0 && pt [i - 1].end > p->begin) { + esc = RTEMS_INVALID_NUMBER; + goto cleanup; + } + } + + /* Check format */ + if (format->type != RTEMS_BDPART_FORMAT_MBR) { + esc = RTEMS_NOT_IMPLEMENTED; + goto cleanup; + } + + /* + * Set primary partition count. If we have more than four partitions we need + * an extended partition which will contain the partitions of number four and + * above as logical partitions. If we have four or less partitions we can + * use the primary partition table. + */ + ppc = count <= 4 ? count : 3; + + /* + * Check that the first primary partition starts at head one and sector one + * under the virtual one head and 63 sectors geometry if necessary. + */ + if (dos_compatibility && pt [0].begin != RTEMS_BDPART_MBR_CYLINDER_SIZE) { + esc = RTEMS_INVALID_NUMBER; + goto cleanup; + } + + /* + * Check that we have enough space for the EBRs. The partitions with number + * four and above are logical partitions if we have more than four partitions + * in total. The logical partitions are contained in the extended partition. + * Each logical partition is described via one EBR preceding the partition. + * The space for the EBR and maybe some space which is needed for DOS + * compatibility resides between the partitions. So there have to be gaps of + * the appropriate size between the partitions. + */ + for (i = ppc; i < count; ++i) { + if ((pt [i].begin - pt [i - 1].end) < record_space) { + esc = RTEMS_INVALID_NUMBER; + goto cleanup; + } + } + + /* Check that we can convert the parition descriptions to the MBR format */ + for (i = 0; i < count; ++i) { + uint8_t type = 0; + + const rtems_bdpart_partition *p = pt + i; + + /* Check type */ + if (!rtems_bdpart_to_mbr_partition_type( p->type, &type)) { + esc = RTEMS_INVALID_ID; + goto cleanup; + } + + /* Check flags */ + if (p->flags > 0xffU) { + esc = RTEMS_INVALID_ID; + goto cleanup; + } + + /* Check ID */ + /* TODO */ + } + + /* New MBR */ + sc = rtems_bdpart_new_record( disk, 0, &block); + if (sc != RTEMS_SUCCESSFUL) { + esc = sc; + goto cleanup; + } + + /* Write disk ID */ + rtems_uint32_to_little_endian( + format->mbr.disk_id, + block->buffer + RTEMS_BDPART_MBR_OFFSET_DISK_ID + ); + + /* Write primary partition table */ + data = block->buffer + RTEMS_BDPART_MBR_OFFSET_TABLE_0; + for (i = 0; i < ppc; ++i) { + const rtems_bdpart_partition *p = pt + i; + + /* Write partition entry */ + rtems_bdpart_write_mbr_partition( + data, + p->begin, + p->end - p->begin, + rtems_bdpart_mbr_partition_type( p->type), + (uint8_t) p->flags + ); + + data += RTEMS_BDPART_MBR_TABLE_ENTRY_SIZE; + } + + /* Write extended partition with logical partitions if necessary */ + if (ppc != count) { + rtems_blkdev_bnum ebr = 0; /* Extended boot record block index */ + + /* Begin of extended partition */ + rtems_blkdev_bnum ep_begin = pt [ppc].begin - record_space; + + /* Write extended partition */ + rtems_bdpart_write_mbr_partition( + data, + ep_begin, + disk_end - ep_begin, + RTEMS_BDPART_MBR_EXTENDED, + 0 + ); + + /* Write logical partitions */ + for (i = ppc; i < count; ++i) { + const rtems_bdpart_partition *p = pt + i; + + /* Write second partition entry */ + if (i > ppc) { + rtems_blkdev_bnum begin = p->begin - record_space; + + rtems_bdpart_write_mbr_partition( + block->buffer + RTEMS_BDPART_MBR_OFFSET_TABLE_1, + begin - ep_begin, + disk_end - begin, + RTEMS_BDPART_MBR_EXTENDED, + 0 + ); + } + + /* New EBR */ + ebr = p->begin - record_space; + sc = rtems_bdpart_new_record( disk, ebr, &block); + if (sc != RTEMS_SUCCESSFUL) { + esc = sc; + goto cleanup; + } + + /* Write first partition entry */ + rtems_bdpart_write_mbr_partition( + block->buffer + RTEMS_BDPART_MBR_OFFSET_TABLE_0, + record_space, + p->end - p->begin, + rtems_bdpart_mbr_partition_type( p->type), + (uint8_t) p->flags + ); + } + } + +cleanup: + + if (block != NULL) { + rtems_bdbuf_sync( block); + } + + return esc; +} diff --git a/cpukit/libblock/src/blkdev-ops.c b/cpukit/libblock/src/blkdev-ops.c new file mode 100644 index 0000000000..6cc0c3009e --- /dev/null +++ b/cpukit/libblock/src/blkdev-ops.c @@ -0,0 +1,32 @@ +/** + * @file + * + * @ingroup rtems_blkdev + * + * @brief Block device management. + */ + +/* + * Copyright (c) 2011 embedded brains GmbH. All rights reserved. + * + * embedded brains GmbH + * Obere Lagerstr. 30 + * 82178 Puchheim + * Germany + * <rtems@embedded-brains.de> + * + * 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. + */ + +#include <rtems/blkdev.h> + +const rtems_driver_address_table rtems_blkdev_generic_ops = { + .initialization_entry = NULL, + .open_entry = rtems_blkdev_generic_open, + .close_entry = rtems_blkdev_generic_close, + .read_entry = rtems_blkdev_generic_read, + .write_entry = rtems_blkdev_generic_write, + .control_entry = rtems_blkdev_generic_ioctl +}; diff --git a/cpukit/libblock/src/blkdev.c b/cpukit/libblock/src/blkdev.c new file mode 100644 index 0000000000..10c14dfaf2 --- /dev/null +++ b/cpukit/libblock/src/blkdev.c @@ -0,0 +1,263 @@ +/** + * @file + * + * @ingroup rtems_blkdev + * + * Block device management. + */ + +/* + * Copyright (C) 2001 OKTET Ltd., St.-Petersburg, Russia + * Author: Victor V. Vengerov <vvv@oktet.ru> + * + * @(#) $Id$ + */ + +#if HAVE_CONFIG_H +#include "config.h" +#endif + +#include <errno.h> +#include <string.h> + +#include <rtems.h> +#include <rtems/libio.h> +#include <sys/ioctl.h> + +#include "rtems/diskdevs.h" +#include "rtems/bdbuf.h" + +/* rtems_blkdev_generic_read -- + * Generic block device read primitive. Implemented using block device + * buffer management primitives. + */ +rtems_device_driver +rtems_blkdev_generic_read( + rtems_device_major_number major __attribute__((unused)), + rtems_device_minor_number minor __attribute__((unused)), + void * arg) +{ + rtems_status_code rc = RTEMS_SUCCESSFUL; + rtems_libio_rw_args_t *args = arg; + rtems_libio_t *iop = args->iop; + rtems_disk_device *dd = iop->data1; + uint32_t block_size = dd->block_size; + char *buf = args->buffer; + uint32_t count = args->count; + rtems_blkdev_bnum block = (rtems_blkdev_bnum) (args->offset / block_size); + uint32_t blkofs = (uint32_t) (args->offset % block_size); + dev_t dev = dd->dev; + + args->bytes_moved = 0; + + while (count > 0) + { + rtems_bdbuf_buffer *diskbuf; + uint32_t copy; + + rc = rtems_bdbuf_read(dev, block, &diskbuf); + if (rc != RTEMS_SUCCESSFUL) + break; + copy = block_size - blkofs; + if (copy > count) + copy = count; + memcpy(buf, (char *)diskbuf->buffer + blkofs, copy); + rc = rtems_bdbuf_release(diskbuf); + args->bytes_moved += copy; + if (rc != RTEMS_SUCCESSFUL) + break; + count -= copy; + buf += copy; + blkofs = 0; + block++; + } + + return rc; +} + +/* rtems_blkdev_generic_write -- + * Generic block device write primitive. Implemented using block device + * buffer management primitives. + */ +rtems_device_driver +rtems_blkdev_generic_write( + rtems_device_major_number major __attribute__((unused)), + rtems_device_minor_number minor __attribute__((unused)), + void * arg) +{ + rtems_status_code rc = RTEMS_SUCCESSFUL; + rtems_libio_rw_args_t *args = arg; + rtems_libio_t *iop = args->iop; + rtems_disk_device *dd = iop->data1; + uint32_t block_size = dd->block_size; + char *buf = args->buffer; + uint32_t count = args->count; + rtems_blkdev_bnum block = (rtems_blkdev_bnum) (args->offset / block_size); + uint32_t blkofs = (uint32_t) (args->offset % block_size); + dev_t dev = dd->dev; + + args->bytes_moved = 0; + + while (count > 0) + { + rtems_bdbuf_buffer *diskbuf; + uint32_t copy; + + if ((blkofs == 0) && (count >= block_size)) + rc = rtems_bdbuf_get(dev, block, &diskbuf); + else + rc = rtems_bdbuf_read(dev, block, &diskbuf); + if (rc != RTEMS_SUCCESSFUL) + break; + + copy = block_size - blkofs; + if (copy > count) + copy = count; + memcpy((char *)diskbuf->buffer + blkofs, buf, copy); + args->bytes_moved += copy; + + rc = rtems_bdbuf_release_modified(diskbuf); + if (rc != RTEMS_SUCCESSFUL) + break; + + count -= copy; + buf += copy; + blkofs = 0; + block++; + } + + return rc; +} + +/* blkdev_generic_open -- + * Generic block device open primitive. + */ +rtems_device_driver +rtems_blkdev_generic_open( + rtems_device_major_number major, + rtems_device_minor_number minor, + void * arg) +{ + rtems_libio_open_close_args_t *oc = arg; + rtems_libio_t *iop = oc->iop; + dev_t dev = rtems_filesystem_make_dev_t(major, minor); + rtems_disk_device *dd = rtems_disk_obtain(dev); + + iop->data1 = dd; + + if (dd != NULL) + return RTEMS_SUCCESSFUL; + else + return RTEMS_UNSATISFIED; +} + + +/* blkdev_generic_close -- + * Generic block device close primitive. + */ +rtems_device_driver +rtems_blkdev_generic_close( + rtems_device_major_number major __attribute__((unused)), + rtems_device_minor_number minor __attribute__((unused)), + void * arg) +{ + rtems_libio_open_close_args_t *oc = arg; + rtems_libio_t *iop = oc->iop; + rtems_disk_device *dd = iop->data1; + + rtems_disk_release(dd); + + return RTEMS_SUCCESSFUL; +} + +/* blkdev_generic_ioctl -- + * Generic block device ioctl primitive. + */ +rtems_device_driver +rtems_blkdev_generic_ioctl( + rtems_device_major_number major __attribute__((unused)), + rtems_device_minor_number minor __attribute__((unused)), + void * arg) +{ + rtems_libio_ioctl_args_t *args = arg; + rtems_libio_t *iop = args->iop; + rtems_disk_device *dd = iop->data1; + int rc; + + switch (args->command) + { + case RTEMS_BLKIO_GETMEDIABLKSIZE: + *((uint32_t *) args->buffer) = dd->media_block_size; + args->ioctl_return = 0; + break; + + case RTEMS_BLKIO_GETBLKSIZE: + *((uint32_t *) args->buffer) = dd->block_size; + args->ioctl_return = 0; + break; + + case RTEMS_BLKIO_SETBLKSIZE: + dd->block_size = *((uint32_t *) args->buffer); + args->ioctl_return = 0; + break; + + case RTEMS_BLKIO_GETSIZE: + *((rtems_blkdev_bnum *) args->buffer) = dd->size; + args->ioctl_return = 0; + break; + + case RTEMS_BLKIO_SYNCDEV: + rc = rtems_bdbuf_syncdev(dd->dev); + args->ioctl_return = (uint32_t) (rc == RTEMS_SUCCESSFUL ? 0 : -1); + break; + + case RTEMS_BLKIO_REQUEST: + /* + * It is not allowed to directly access the driver circumventing + * the cache. + */ + args->ioctl_return = (uint32_t) -1; + break; + + default: + args->ioctl_return = (uint32_t) dd->ioctl(dd->phys_dev, + args->command, + args->buffer); + break; + } + + return RTEMS_SUCCESSFUL; +} + +int +rtems_blkdev_ioctl(rtems_disk_device *dd, uint32_t req, void *argp) +{ + size_t *arg_size = argp; + int rc = 0; + + switch (req) + { + case RTEMS_BLKIO_GETMEDIABLKSIZE: + *arg_size = dd->media_block_size; + break; + + case RTEMS_BLKIO_GETBLKSIZE: + *arg_size = dd->block_size; + break; + + case RTEMS_BLKIO_SETBLKSIZE: + dd->block_size = *arg_size; + break; + + case RTEMS_BLKIO_GETSIZE: + *arg_size = dd->size; + break; + + default: + errno = EINVAL; + rc = -1; + break; + } + + return rc; +} diff --git a/cpukit/libblock/src/diskdevs.c b/cpukit/libblock/src/diskdevs.c new file mode 100644 index 0000000000..08a6a9bf8c --- /dev/null +++ b/cpukit/libblock/src/diskdevs.c @@ -0,0 +1,589 @@ +/** + * @file + * + * @ingroup rtems_disk + * + * @brief Block device disk management implementation. + */ + +/* + * Copyright (C) 2001 OKTET Ltd., St.-Petersburg, Russia + * Author: Victor V. Vengerov <vvv@oktet.ru> + * + * Copyright (c) 2009 embedded brains GmbH. + * + * @(#) $Id$ + */ + +#if HAVE_CONFIG_H +#include "config.h" +#endif + +#include <stdlib.h> +#include <unistd.h> +#include <string.h> + +#include <rtems.h> +#include <rtems/libio.h> +#include <rtems/diskdevs.h> +#include <rtems/blkdev.h> +#include <rtems/bdbuf.h> + +#define DISKTAB_INITIAL_SIZE 8 + +/* Table of disk devices having the same major number */ +typedef struct rtems_disk_device_table { + rtems_disk_device **minor; /* minor-indexed disk device table */ + rtems_device_minor_number size; /* Number of entries in the table */ +} rtems_disk_device_table; + +/* Pointer to [major].minor[minor] indexed array of disk devices */ +static rtems_disk_device_table *disktab; + +/* Number of allocated entries in disktab table */ +static rtems_device_major_number disktab_size; + +/* Mutual exclusion semaphore for disk devices table */ +static rtems_id diskdevs_mutex; + +/* diskdevs data structures protection flag. + * Normally, only table lookup operations performed. It is quite fast, so + * it is possible to done lookups when interrupts are disabled, avoiding + * obtaining the semaphore. This flags sets immediately after entering in + * mutex-protected section and cleared before leaving this section in + * "big" primitives like add/delete new device etc. Lookup function first + * disable interrupts and check this flag. If it is set, lookup function + * will be blocked on semaphore and lookup operation will be performed in + * semaphore-protected code. If it is not set (very-very frequent case), + * we can do lookup safely, enable interrupts and return result. + */ +static volatile bool diskdevs_protected; + +static rtems_status_code +disk_lock(void) +{ + rtems_status_code sc = RTEMS_SUCCESSFUL; + + sc = rtems_semaphore_obtain(diskdevs_mutex, RTEMS_WAIT, RTEMS_NO_TIMEOUT); + if (sc == RTEMS_SUCCESSFUL) { + diskdevs_protected = true; + + return RTEMS_SUCCESSFUL; + } else { + return RTEMS_NOT_CONFIGURED; + } +} + +static void +disk_unlock(void) +{ + rtems_status_code sc = RTEMS_SUCCESSFUL; + + diskdevs_protected = false; + + sc = rtems_semaphore_release(diskdevs_mutex); + if (sc != RTEMS_SUCCESSFUL) { + /* FIXME: Error number */ + rtems_fatal_error_occurred(0xdeadbeef); + } +} + +static rtems_disk_device * +get_disk_entry(dev_t dev, bool lookup_only) +{ + rtems_device_major_number major = 0; + rtems_device_minor_number minor = 0; + + rtems_filesystem_split_dev_t(dev, major, minor); + + if (major < disktab_size && disktab != NULL) { + rtems_disk_device_table *dtab = disktab + major; + + if (minor < dtab->size && dtab->minor != NULL) { + rtems_disk_device *dd = dtab->minor [minor]; + + if (dd != NULL && !lookup_only) { + if (!dd->deleted) { + ++dd->uses; + } else { + dd = NULL; + } + } + + return dd; + } + } + + return NULL; +} + +static rtems_disk_device ** +create_disk_table_entry(dev_t dev) +{ + rtems_device_major_number major = 0; + rtems_device_minor_number minor = 0; + + rtems_filesystem_split_dev_t(dev, major, minor); + + if (major >= disktab_size) { + rtems_disk_device_table *table = disktab; + rtems_device_major_number old_size = disktab_size; + rtems_device_major_number new_size = 2 * old_size; + + if (major >= new_size) { + new_size = major + 1; + } + + table = realloc(table, new_size * sizeof(*table)); + if (table == NULL) { + return NULL; + } + + memset(table + old_size, 0, (new_size - old_size) * sizeof(*table)); + disktab = table; + disktab_size = new_size; + } + + if (disktab [major].minor == NULL || minor >= disktab[major].size) { + rtems_disk_device **table = disktab [major].minor; + rtems_device_minor_number old_size = disktab [major].size; + rtems_device_minor_number new_size = 0; + + if (old_size == 0) { + new_size = DISKTAB_INITIAL_SIZE; + } else { + new_size = 2 * old_size; + } + if (minor >= new_size) { + new_size = minor + 1; + } + + table = realloc(table, new_size * sizeof(*table)); + if (table == NULL) { + return NULL; + } + + memset(table + old_size, 0, (new_size - old_size) * sizeof(*table)); + disktab [major].minor = table; + disktab [major].size = new_size; + } + + return disktab [major].minor + minor; +} + +static rtems_status_code +create_disk(dev_t dev, const char *name, rtems_disk_device **dd_ptr) +{ + rtems_disk_device **dd_entry = create_disk_table_entry(dev); + rtems_disk_device *dd = NULL; + char *alloc_name = NULL; + + if (dd_entry == NULL) { + return RTEMS_NO_MEMORY; + } + + if (*dd_entry != NULL) { + return RTEMS_RESOURCE_IN_USE; + } + + dd = malloc(sizeof(*dd)); + if (dd == NULL) { + return RTEMS_NO_MEMORY; + } + + if (name != NULL) { + alloc_name = strdup(name); + + if (alloc_name == NULL) { + free(dd); + + return RTEMS_NO_MEMORY; + } + } + + if (name != NULL) { + if (mknod(alloc_name, 0777 | S_IFBLK, dev) < 0) { + free(alloc_name); + free(dd); + return RTEMS_UNSATISFIED; + } + } + + dd->dev = dev; + dd->name = alloc_name; + dd->uses = 0; + dd->deleted = false; + + *dd_entry = dd; + *dd_ptr = dd; + + return RTEMS_SUCCESSFUL; +} + +rtems_status_code rtems_disk_create_phys( + dev_t dev, + uint32_t block_size, + rtems_blkdev_bnum block_count, + rtems_block_device_ioctl handler, + void *driver_data, + const char *name +) +{ + rtems_disk_device *dd = NULL; + rtems_status_code sc = RTEMS_SUCCESSFUL; + + if (handler == NULL) { + return RTEMS_INVALID_ADDRESS; + } + + if (block_size == 0) { + return RTEMS_INVALID_NUMBER; + } + + sc = disk_lock(); + if (sc != RTEMS_SUCCESSFUL) { + return sc; + } + + sc = create_disk(dev, name, &dd); + if (sc != RTEMS_SUCCESSFUL) { + disk_unlock(); + + return sc; + } + + dd->phys_dev = dd; + dd->start = 0; + dd->size = block_count; + dd->block_size = dd->media_block_size = block_size; + dd->ioctl = handler; + dd->driver_data = driver_data; + + if ((*handler)(dd, RTEMS_BLKIO_CAPABILITIES, &dd->capabilities) < 0) { + dd->capabilities = 0; + } + + disk_unlock(); + + return RTEMS_SUCCESSFUL; +} + +static bool +is_physical_disk(const rtems_disk_device *dd) +{ + return dd->phys_dev == dd; +} + +rtems_status_code rtems_disk_create_log( + dev_t dev, + dev_t phys, + rtems_blkdev_bnum begin_block, + rtems_blkdev_bnum block_count, + const char *name +) +{ + rtems_status_code sc = RTEMS_SUCCESSFUL; + rtems_disk_device *physical_disk = NULL; + rtems_disk_device *dd = NULL; + rtems_blkdev_bnum end_block = begin_block + block_count; + + sc = disk_lock(); + if (sc != RTEMS_SUCCESSFUL) { + return sc; + } + + physical_disk = get_disk_entry(phys, true); + if (physical_disk == NULL || !is_physical_disk(physical_disk)) { + disk_unlock(); + + return RTEMS_INVALID_ID; + } + + if ( + begin_block >= physical_disk->size + || end_block <= begin_block + || end_block > physical_disk->size + ) { + disk_unlock(); + + return RTEMS_INVALID_NUMBER; + } + + sc = create_disk(dev, name, &dd); + if (sc != RTEMS_SUCCESSFUL) { + disk_unlock(); + + return sc; + } + + dd->phys_dev = physical_disk; + dd->start = begin_block; + dd->size = block_count; + dd->block_size = dd->media_block_size = physical_disk->block_size; + dd->ioctl = physical_disk->ioctl; + dd->driver_data = physical_disk->driver_data; + + ++physical_disk->uses; + + disk_unlock(); + + return RTEMS_SUCCESSFUL; +} + +static void +free_disk_device(rtems_disk_device *dd) +{ + if (is_physical_disk(dd)) { + (*dd->ioctl)(dd, RTEMS_BLKIO_DELETED, NULL); + } + if (dd->name != NULL) { + unlink(dd->name); + free(dd->name); + } + free(dd); +} + +static void +rtems_disk_cleanup(rtems_disk_device *disk_to_remove) +{ + rtems_disk_device *const physical_disk = disk_to_remove->phys_dev; + rtems_device_major_number major = 0; + rtems_device_minor_number minor = 0; + + if (physical_disk->deleted) { + dev_t dev = physical_disk->dev; + unsigned deleted_count = 0; + + for (major = 0; major < disktab_size; ++major) { + rtems_disk_device_table *dtab = disktab + major; + + for (minor = 0; minor < dtab->size; ++minor) { + rtems_disk_device *dd = dtab->minor [minor]; + + if (dd != NULL && dd->phys_dev->dev == dev && dd != physical_disk) { + if (dd->uses == 0) { + ++deleted_count; + dtab->minor [minor] = NULL; + free_disk_device(dd); + } else { + dd->deleted = true; + } + } + } + } + + physical_disk->uses -= deleted_count; + if (physical_disk->uses == 0) { + rtems_filesystem_split_dev_t(physical_disk->dev, major, minor); + disktab [major].minor [minor] = NULL; + free_disk_device(physical_disk); + } + } else { + if (disk_to_remove->uses == 0) { + --physical_disk->uses; + rtems_filesystem_split_dev_t(disk_to_remove->dev, major, minor); + disktab [major].minor [minor] = NULL; + free_disk_device(disk_to_remove); + } + } +} + +rtems_status_code +rtems_disk_delete(dev_t dev) +{ + rtems_status_code sc = RTEMS_SUCCESSFUL; + rtems_disk_device *dd = NULL; + + sc = disk_lock(); + if (sc != RTEMS_SUCCESSFUL) { + return sc; + } + + dd = get_disk_entry(dev, true); + if (dd == NULL) { + disk_unlock(); + + return RTEMS_INVALID_ID; + } + + dd->deleted = true; + rtems_disk_cleanup(dd); + + disk_unlock(); + + return RTEMS_SUCCESSFUL; +} + +rtems_disk_device * +rtems_disk_obtain(dev_t dev) +{ + rtems_status_code sc = RTEMS_SUCCESSFUL; + rtems_disk_device *dd = NULL; + rtems_interrupt_level level; + + rtems_interrupt_disable(level); + if (!diskdevs_protected) { + /* Frequent and quickest case */ + dd = get_disk_entry(dev, false); + rtems_interrupt_enable(level); + } else { + rtems_interrupt_enable(level); + + sc = disk_lock(); + if (sc == RTEMS_SUCCESSFUL) { + dd = get_disk_entry(dev, false); + disk_unlock(); + } + } + + return dd; +} + +rtems_status_code +rtems_disk_release(rtems_disk_device *dd) +{ + rtems_interrupt_level level; + dev_t dev = dd->dev; + unsigned uses = 0; + bool deleted = false; + + rtems_interrupt_disable(level); + uses = --dd->uses; + deleted = dd->deleted; + rtems_interrupt_enable(level); + + if (uses == 0 && deleted) { + rtems_disk_delete(dev); + } + + return RTEMS_SUCCESSFUL; +} + +rtems_disk_device * +rtems_disk_next(dev_t dev) +{ + rtems_status_code sc = RTEMS_SUCCESSFUL; + rtems_disk_device_table *dtab = NULL; + rtems_device_major_number major = 0; + rtems_device_minor_number minor = 0; + + if (dev != (dev_t) -1) { + rtems_filesystem_split_dev_t(dev, major, minor); + + /* If minor wraps around */ + if ((minor + 1) < minor) { + /* If major wraps around */ + if ((major + 1) < major) { + return NULL; + } + ++major; + minor = 0; + } else { + ++minor; + } + } + + sc = disk_lock(); + if (sc != RTEMS_SUCCESSFUL) { + return NULL; + } + + if (major >= disktab_size) { + disk_unlock(); + + return NULL; + } + + dtab = disktab + major; + while (true) { + if (dtab->minor == NULL || minor >= dtab->size) { + minor = 0; + ++major; + if (major >= disktab_size) { + disk_unlock(); + + return NULL; + } + dtab = disktab + major; + } else if (dtab->minor [minor] == NULL) { + ++minor; + } else { + ++dtab->minor [minor]->uses; + disk_unlock(); + + return dtab->minor [minor]; + } + } +} + +rtems_status_code +rtems_disk_io_initialize(void) +{ + rtems_status_code sc = RTEMS_SUCCESSFUL; + rtems_device_major_number size = DISKTAB_INITIAL_SIZE; + + if (disktab_size > 0) { + return RTEMS_SUCCESSFUL; + } + + disktab = calloc(size, sizeof(rtems_disk_device_table)); + if (disktab == NULL) { + return RTEMS_NO_MEMORY; + } + + diskdevs_protected = false; + sc = rtems_semaphore_create( + rtems_build_name('D', 'D', 'E', 'V'), + 1, + RTEMS_FIFO | RTEMS_BINARY_SEMAPHORE | RTEMS_NO_INHERIT_PRIORITY + | RTEMS_NO_PRIORITY_CEILING | RTEMS_LOCAL, + 0, + &diskdevs_mutex + ); + if (sc != RTEMS_SUCCESSFUL) { + free(disktab); + + return RTEMS_NO_MEMORY; + } + + sc = rtems_bdbuf_init(); + if (sc != RTEMS_SUCCESSFUL) { + rtems_semaphore_delete(diskdevs_mutex); + free(disktab); + + return RTEMS_UNSATISFIED; + } + + disktab_size = size; + + return RTEMS_SUCCESSFUL; +} + +rtems_status_code +rtems_disk_io_done(void) +{ + rtems_device_major_number major = 0; + rtems_device_minor_number minor = 0; + + for (major = 0; major < disktab_size; ++major) { + rtems_disk_device_table *dtab = disktab + major; + + for (minor = 0; minor < dtab->size; ++minor) { + rtems_disk_device *dd = dtab->minor [minor]; + + if (dd != NULL) { + free_disk_device(dd); + } + } + free(dtab->minor); + } + free(disktab); + + rtems_semaphore_delete(diskdevs_mutex); + + diskdevs_mutex = RTEMS_ID_NONE; + disktab = NULL; + disktab_size = 0; + + return RTEMS_SUCCESSFUL; +} 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; +} diff --git a/cpukit/libblock/src/ide_part_table.c b/cpukit/libblock/src/ide_part_table.c new file mode 100644 index 0000000000..3543284dfc --- /dev/null +++ b/cpukit/libblock/src/ide_part_table.c @@ -0,0 +1,594 @@ +/***************************************************************************** + * + * ide_part_table.c + * + * The implementation of library supporting "MS-DOS-style" partition table + * + * + * Copyright (C) 2002 OKTET Ltd., St.-Petersburg, Russia + * + * Author: Konstantin Abramenko <Konstantin.Abramenko@oktet.ru> + * Alexander Kukuta <Alexander.Kukuta@oktet.ru> + * + * 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$ + * + *****************************************************************************/ + +#if HAVE_CONFIG_H +#include "config.h" +#endif + +#include <string.h> + +#include <rtems/ide_part_table.h> + +/* + * get_sector -- + * gets sector from the disk + * + * PARAMETERS: + * dev - device number + * sector_num - number of sector to read + * sector - returned pointer to pointer to allocated + * sector_data_t structure + * + * RETURNS: + * RTEMS_SUCCESSFUL, if success; + * RTEMS_NO_MEMORY, if canot allocate memory for sector data; + * other error codes returned by rtems_bdbuf_read(). + * + * NOTES: + * get_sector() operates with device via bdbuf library, + * and does not support devices with sector size other than 512 bytes + */ +static rtems_status_code +get_sector(dev_t dev, uint32_t sector_num, rtems_sector_data_t **sector) +{ + rtems_sector_data_t *s; + rtems_bdbuf_buffer *buf; + rtems_status_code rc; + + if (sector == NULL) + { + return RTEMS_INTERNAL_ERROR; + } + + s = (rtems_sector_data_t *) malloc(sizeof(rtems_sector_data_t) + RTEMS_IDE_SECTOR_SIZE); + if (s == NULL) + { + return RTEMS_NO_MEMORY; + } + + rc = rtems_bdbuf_read(dev, sector_num, &buf); + if (rc != RTEMS_SUCCESSFUL) + { + free(s); + return rc; + } + + memcpy(s->data, buf->buffer, RTEMS_IDE_SECTOR_SIZE); + s->sector_num = sector_num; + + *sector = s; + + rtems_bdbuf_release(buf); + + return RTEMS_SUCCESSFUL; +} + + +/* + * msdos_signature_check -- + * checks if the partition table sector has msdos signature + * + * PARAMETERS: + * sector - sector to check + * + * RETURNS: + * true if sector has msdos signature, false otherwise + */ +static bool +msdos_signature_check (rtems_sector_data_t *sector) +{ + uint8_t *p = sector->data + RTEMS_IDE_PARTITION_MSDOS_SIGNATURE_OFFSET; + + return ((p[0] == RTEMS_IDE_PARTITION_MSDOS_SIGNATURE_DATA1) && + (p[1] == RTEMS_IDE_PARTITION_MSDOS_SIGNATURE_DATA2)); +} + + +/* + * is_extended -- + * checks if the partition type is extended + * + * PARAMETERS: + * type - type of partition to check + * + * RETURNS: + * true if partition type is extended, false otherwise + */ +static bool +is_extended(uint8_t type) +{ + return ((type == EXTENDED_PARTITION) || (type == LINUX_EXTENDED)); +} + +/* + * is_fat_partition -- + * checks if the partition type is defined for FAT + * + * PARAMETERS: + * type - type of partition to check + * + * RETURNS: + * true if partition type is extended, false otherwise + */ +static bool +is_fat_partition(uint8_t type) +{ + static const uint8_t fat_part_types[] = { + DOS_FAT12_PARTITION,DOS_FAT16_PARTITION, + DOS_P32MB_PARTITION, + FAT32_PARTITION ,FAT32_LBA_PARTITION, + FAT16_LBA_PARTITION + }; + + return (NULL != memchr(fat_part_types,type,sizeof(fat_part_types))); +} + + +/* + * data_to_part_desc -- + * parses raw partition table sector data + * to partition description structure + * + * PARAMETERS: + * data - raw partition table sector data + * new_part_desc - pointer to returned partition description structure + * + * RETURNS: + * RTEMS_SUCCESSFUL, if success; + * RTEMS_NO_MEMOTY, if cannot allocate memory for part_desc_t strucure; + * RTEMS_INTERNAL_ERROR, if other error occurs. + */ +static rtems_status_code +data_to_part_desc(uint8_t *data, rtems_part_desc_t **new_part_desc) +{ + rtems_part_desc_t *part_desc; + uint32_t temp; + + if (new_part_desc == NULL) + { + return RTEMS_INTERNAL_ERROR; + } + + *new_part_desc = NULL; + + if ((part_desc = calloc(1, sizeof(rtems_part_desc_t))) == NULL) + { + return RTEMS_NO_MEMORY; + } + + part_desc->bootable = *(data + RTEMS_IDE_PARTITION_BOOTABLE_OFFSET); + part_desc->sys_type = *(data + RTEMS_IDE_PARTITION_SYS_TYPE_OFFSET); + + /* read the offset start position and partition size in sectors */ + + /* due to incorrect data alignment one have to align data first */ + memcpy(&temp, data + RTEMS_IDE_PARTITION_START_OFFSET, sizeof(uint32_t)); + part_desc->start = LE_TO_CPU_U32(temp); + + memcpy(&temp, data + RTEMS_IDE_PARTITION_SIZE_OFFSET, sizeof(uint32_t)); + part_desc->size = LE_TO_CPU_U32(temp); + + /* + * use partitions that are + * - extended + * or + * - FAT type and non-zero + */ + if (is_extended(part_desc->sys_type) || + ((is_fat_partition(part_desc->sys_type)) && (part_desc->size != 0))) { + *new_part_desc = part_desc; + } + else { + /* empty partition */ + free(part_desc); + } + return RTEMS_SUCCESSFUL; +} + + +/* + * read_extended_partition -- + * recursively reads extended partition sector from the device + * and constructs the partition table tree + * + * PARAMETERS: + * start - start sector of primary extended partition, used for + * calculation of absolute partition sector address + * ext_part - description of extended partition to process + * + * RETURNS: + * RTEMS_SUCCESSFUL if success, + * RTEMS_NO_MEMOTY if cannot allocate memory for part_desc_t strucure, + * RTEMS_INTERNAL_ERROR if other error occurs. + */ +static rtems_status_code +read_extended_partition(uint32_t start, rtems_part_desc_t *ext_part) +{ + int i; + dev_t dev; + rtems_sector_data_t *sector = NULL; + uint32_t here; + uint8_t *data; + rtems_part_desc_t *new_part_desc; + rtems_status_code rc; + + if ((ext_part == NULL) || (ext_part->disk_desc == NULL)) + { + return RTEMS_INTERNAL_ERROR; + } + + dev = ext_part->disk_desc->dev; + + /* get start sector of current extended partition */ + here = ext_part->start; + + /* get first extended partition sector */ + + rc = get_sector(dev, here, §or); + if (rc != RTEMS_SUCCESSFUL) + { + if (sector) + free(sector); + return rc; + } + + if (!msdos_signature_check(sector)) + { + free(sector); + return RTEMS_INTERNAL_ERROR; + } + + /* read and process up to 4 logical partition descriptors */ + + data = sector->data + RTEMS_IDE_PARTITION_TABLE_OFFSET; + + for (i = 0; i < RTEMS_IDE_PARTITION_MAX_SUB_PARTITION_NUMBER; i++) + { + /* if data_to_part_desc fails skip this partition + * and parse the next one + */ + rc = data_to_part_desc(data, &new_part_desc); + if (rc != RTEMS_SUCCESSFUL) + { + free(sector); + return rc; + } + + if (new_part_desc == NULL) + { + data += RTEMS_IDE_PARTITION_DESCRIPTOR_SIZE; + continue; + } + + ext_part->sub_part[i] = new_part_desc; + new_part_desc->ext_part = ext_part; + new_part_desc->disk_desc = ext_part->disk_desc; + + if (is_extended(new_part_desc->sys_type)) + { + new_part_desc->log_id = EMPTY_PARTITION; + new_part_desc->start += start; + read_extended_partition(start, new_part_desc); + } + else + { + rtems_disk_desc_t *disk_desc = new_part_desc->disk_desc; + disk_desc->partitions[disk_desc->last_log_id] = new_part_desc; + new_part_desc->log_id = ++disk_desc->last_log_id; + new_part_desc->start += here; + new_part_desc->end = new_part_desc->start + new_part_desc->size - 1; + } + data += RTEMS_IDE_PARTITION_DESCRIPTOR_SIZE; + } + + free(sector); + + return RTEMS_SUCCESSFUL; +} + + +/* + * read_mbr -- + * reads Master Boot Record (sector 0) of physical device and + * constructs disk description structure + * + * PARAMETERS: + * disk_desc - returned disc description structure + * + * RETURNS: + * RTEMS_SUCCESSFUL if success, + * RTEMS_INTERNAL_ERROR otherwise + */ +static rtems_status_code +read_mbr(rtems_disk_desc_t *disk_desc) +{ + int part_num; + rtems_sector_data_t *sector = NULL; + rtems_part_desc_t *part_desc; + uint8_t *data; + rtems_status_code rc; + dev_t dev = disk_desc->dev; + + /* get MBR sector */ + rc = get_sector(dev, 0, §or); + if (rc != RTEMS_SUCCESSFUL) + { + if (sector) + free(sector); + return rc; + } + + /* check if the partition table structure is MS-DOS style */ + if (!msdos_signature_check(sector)) + { + free(sector); + return RTEMS_INTERNAL_ERROR; + } + + /* read and process 4 primary partition descriptors */ + + data = sector->data + RTEMS_IDE_PARTITION_TABLE_OFFSET; + + for (part_num = 0; + part_num < RTEMS_IDE_PARTITION_MAX_SUB_PARTITION_NUMBER; + part_num++) + { + rc = data_to_part_desc(data, &part_desc); + if (rc != RTEMS_SUCCESSFUL) + { + free(sector); + return rc; + } + + if (part_desc != NULL) + { + part_desc->log_id = part_num + 1; + part_desc->disk_desc = disk_desc; + part_desc->end = part_desc->start + part_desc->size - 1; + disk_desc->partitions[part_num] = part_desc; + } + else + { + disk_desc->partitions[part_num] = NULL; + } + + data += RTEMS_IDE_PARTITION_DESCRIPTOR_SIZE; + } + + free(sector); + + disk_desc->last_log_id = RTEMS_IDE_PARTITION_MAX_SUB_PARTITION_NUMBER; + + /* There cannot be more than one extended partition, + but we are to process each primary partition */ + for (part_num = 0; + part_num < RTEMS_IDE_PARTITION_MAX_SUB_PARTITION_NUMBER; + part_num++) + { + part_desc = disk_desc->partitions[part_num]; + if (part_desc != NULL && is_extended(part_desc->sys_type)) + { + read_extended_partition(part_desc->start, part_desc); + } + } + + return RTEMS_SUCCESSFUL; +} + + +/* + * partition free -- + * frees partition description structure + * + * PARAMETERS: + * part_desc - returned disc description structure + * + * RETURNS: + * N/A + */ +static void +partition_free(rtems_part_desc_t *part_desc) +{ + int part_num; + + if (part_desc == NULL) + return; + + if (is_extended(part_desc->sys_type)) + { + for (part_num = 0; + part_num < RTEMS_IDE_PARTITION_MAX_SUB_PARTITION_NUMBER; + part_num++) + { + partition_free(part_desc->sub_part[part_num]); + } + } + + free(part_desc); +} + + +/* + * partition_table_free - frees disk descriptor structure + * + * PARAMETERS: + * disk_desc - disc descriptor structure to free + * + * RETURNS: + * N/A + */ +static void +partition_table_free(rtems_disk_desc_t *disk_desc) +{ + int part_num; + + for (part_num = 0; + part_num < RTEMS_IDE_PARTITION_MAX_SUB_PARTITION_NUMBER; + part_num++) + { + partition_free(disk_desc->partitions[part_num]); + } + + free(disk_desc); +} + + +/* + * partition_table_get - reads partition table structure from the device + * and creates disk description structure + * + * PARAMETERS: + * dev_name - path to physical device in /dev filesystem + * disk_desc - returned disc description structure + * + * RETURNS: + * RTEMS_SUCCESSFUL if success, + * RTEMS_INTERNAL_ERROR otherwise + */ +static rtems_status_code +partition_table_get(const char *dev_name, rtems_disk_desc_t *disk_desc) +{ + struct stat dev_stat; + rtems_status_code rc; + + rc = stat(dev_name, &dev_stat); + if (rc != RTEMS_SUCCESSFUL) + { + return RTEMS_INTERNAL_ERROR; + } + + strncpy (disk_desc->dev_name, dev_name, 15); + disk_desc->dev = dev_stat.st_rdev; + disk_desc->sector_size = (dev_stat.st_blksize) ? dev_stat.st_blksize : + RTEMS_IDE_SECTOR_SIZE; + + rc = read_mbr(disk_desc); + + return rc; +} + + +/* + * rtems_ide_part_table_free - frees disk descriptor structure + * + * PARAMETERS: + * disk_desc - disc descriptor structure to free + * + * RETURNS: + * N/A + */ +void +rtems_ide_part_table_free(rtems_disk_desc_t *disk_desc) +{ + partition_table_free( disk_desc ); +} + + +/* + * rtems_ide_part_table_get - reads partition table structure from the device + * and creates disk description structure + * + * PARAMETERS: + * dev_name - path to physical device in /dev filesystem + * disk_desc - returned disc description structure + * + * RETURNS: + * RTEMS_SUCCESSFUL if success, + * RTEMS_INTERNAL_ERROR otherwise + */ +rtems_status_code +rtems_ide_part_table_get(const char *dev_name, rtems_disk_desc_t *disk_desc) +{ + return partition_table_get( dev_name, disk_desc ); +} + + +/* + * rtems_ide_part_table_initialize - initializes logical devices + * on the physical IDE drive + * + * PARAMETERS: + * dev_name - path to physical device in /dev filesystem + * + * RETURNS: + * RTEMS_SUCCESSFUL if success, + * RTEMS_NO_MEMOTY if cannot have not enough memory, + * RTEMS_INTERNAL_ERROR if other error occurs. + */ +rtems_status_code +rtems_ide_part_table_initialize(char *dev_name) +{ + int part_num; + dev_t dev; + rtems_disk_desc_t *disk_desc; + rtems_device_major_number major; + rtems_device_minor_number minor; + rtems_status_code rc; + rtems_part_desc_t *part_desc; + + /* logical device name /dev/hdxyy */ + char name[RTEMS_IDE_PARTITION_DEV_NAME_LENGTH_MAX]; + + disk_desc = (rtems_disk_desc_t *) calloc(1, sizeof(rtems_disk_desc_t)); + if (disk_desc == NULL) + { + return RTEMS_NO_MEMORY; + } + + /* get partition table */ + rc = partition_table_get(dev_name, disk_desc); + if (rc != RTEMS_SUCCESSFUL) + { + free(disk_desc); + return rc; + } + + /* To avoid device numbers conflicts we have to use for logic disk the same + * device major number as ATA device has, and minor number that equals to + * sum of logic disk partition number and the minor number of physical disk + */ + + rtems_filesystem_split_dev_t (disk_desc->dev, major, minor); + + /* create logical disks on the physical one */ + for (part_num = 0; part_num < disk_desc->last_log_id; part_num++) + { + sprintf(name, "%s%d", dev_name, part_num + 1); + dev = rtems_filesystem_make_dev_t(major, ++minor); + + part_desc = disk_desc->partitions[part_num]; + if (part_desc == NULL) + { + continue; + } + + rc = rtems_disk_create_log(dev, disk_desc->dev, part_desc->start, + part_desc->size, name); + if (rc != RTEMS_SUCCESSFUL) + { + fprintf(stdout,"Cannot create device %s, error code %d\n", name, rc); + continue; + } + } + + partition_table_free(disk_desc); + + return RTEMS_SUCCESSFUL; +} diff --git a/cpukit/libblock/src/media-desc.c b/cpukit/libblock/src/media-desc.c new file mode 100644 index 0000000000..005f0f7329 --- /dev/null +++ b/cpukit/libblock/src/media-desc.c @@ -0,0 +1,72 @@ +/** + * @file + * + * @ingroup RTEMSMedia + * + * @brief Media implementation. + */ + +/* + * Copyright (c) 2009, 2010 embedded brains GmbH. All rights reserved. + * + * embedded brains GmbH + * Obere Lagerstr. 30 + * 82178 Puchheim + * Germany + * <rtems@embedded-brains.de> + * + * 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. + */ + +#include <rtems/media.h> + +static const char *const rtems_media_event_desc_table [] = { + [RTEMS_MEDIA_EVENT_DISK_ATTACH] = "DISK ATTACH", + [RTEMS_MEDIA_EVENT_DISK_DETACH] = "DISK DETACH", + [RTEMS_MEDIA_EVENT_MOUNT] = "MOUNT", + [RTEMS_MEDIA_EVENT_UNMOUNT] = "UNMOUNT", + [RTEMS_MEDIA_EVENT_PARTITION_INQUIRY] = "PARTITION INQUIRY", + [RTEMS_MEDIA_EVENT_PARTITION_ATTACH] = "PARTITION ATTACH", + [RTEMS_MEDIA_EVENT_PARTITION_DETACH] = "PARTITION DETACH" +}; + +static const char *const rtems_media_state_desc_table [] = { + [RTEMS_MEDIA_STATE_INQUIRY] = "INQUIRY", + [RTEMS_MEDIA_STATE_ABORTED] = "ABORTED", + [RTEMS_MEDIA_STATE_READY] = "SUCCESS", + [RTEMS_MEDIA_STATE_FAILED] = "FAILED", + [RTEMS_MEDIA_STATE_SUCCESS] = "SUCCESS", + [RTEMS_MEDIA_ERROR_DISK_UNKNOWN] = "ERROR DISK UNKNOWN", + [RTEMS_MEDIA_ERROR_DISK_EXISTS] = "ERROR DISK EXISTS", + [RTEMS_MEDIA_ERROR_DISK_OR_PARTITION_UNKNOWN] = "ERROR DISK OR PARTITION UNKNOWN", + [RTEMS_MEDIA_ERROR_DISK_OR_PARTITION_EXISTS] = "ERROR DISK OR PARTITION EXISTS", + [RTEMS_MEDIA_ERROR_PARTITION_UNKNOWN] = "ERROR PARTITION UNKNOWN", + [RTEMS_MEDIA_ERROR_PARTITION_ORPHAN] = "ERROR PARTITION ORPHAN", + [RTEMS_MEDIA_ERROR_PARTITION_DETACH_WITH_MOUNT] = "ERROR PARTITION DETACH WITH MOUNT", + [RTEMS_MEDIA_ERROR_PARTITION_WITH_UNKNOWN_DISK] = "ERROR PARTITION WITH UNKNOWN DISK", + [RTEMS_MEDIA_ERROR_MOUNT_POINT_UNKNOWN] = "ERROR MOUNT POINT UNKNOWN", + [RTEMS_MEDIA_ERROR_MOUNT_POINT_EXISTS] = "ERROR MOUNT POINT EXISTS", + [RTEMS_MEDIA_ERROR_MOUNT_POINT_ORPHAN] = "ERROR MOUNT POINT ORPHAN" +}; + +#define TC(table) (sizeof(table) / sizeof(table [0])) + +const char *rtems_media_event_description(rtems_media_event event) +{ + if ((size_t) event < TC(rtems_media_event_desc_table)) { + return rtems_media_event_desc_table [event]; + } else { + return "INVALID"; + } +} + +const char *rtems_media_state_description(rtems_media_state state) +{ + if ((size_t) state < TC(rtems_media_state_desc_table)) { + return rtems_media_state_desc_table [state]; + } else { + return "INVALID"; + } +} diff --git a/cpukit/libblock/src/media-dev-ident.c b/cpukit/libblock/src/media-dev-ident.c new file mode 100644 index 0000000000..7c0f298424 --- /dev/null +++ b/cpukit/libblock/src/media-dev-ident.c @@ -0,0 +1,47 @@ +/** + * @file + * + * @ingroup RTEMSMedia + * + * @brief Media implementation. + */ + +/* + * Copyright (c) 2009, 2010 embedded brains GmbH. All rights reserved. + * + * embedded brains GmbH + * Obere Lagerstr. 30 + * 82178 Puchheim + * Germany + * <rtems@embedded-brains.de> + * + * 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. + */ + +#include <sys/types.h> +#include <sys/stat.h> +#include <unistd.h> + +#include <rtems.h> + +#include <rtems/media.h> + +rtems_status_code rtems_media_get_device_identifier( + const char *device_path, + dev_t *device_identifier +) +{ + int rv = 0; + struct stat st; + + rv = stat(device_path, &st); + if (rv != 0) { + return RTEMS_INVALID_ID; + } + + *device_identifier = st.st_rdev; + + return RTEMS_SUCCESSFUL; +} diff --git a/cpukit/libblock/src/media-path.c b/cpukit/libblock/src/media-path.c new file mode 100644 index 0000000000..1668145294 --- /dev/null +++ b/cpukit/libblock/src/media-path.c @@ -0,0 +1,86 @@ +/** + * @file + * + * @ingroup RTEMSMedia + * + * @brief Media implementation. + */ + +/* + * Copyright (c) 2009, 2010 embedded brains GmbH. All rights reserved. + * + * embedded brains GmbH + * Obere Lagerstr. 30 + * 82178 Puchheim + * Germany + * <rtems@embedded-brains.de> + * + * 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. + */ + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <inttypes.h> +#include <assert.h> + +#include <rtems/media.h> + +char *rtems_media_create_path( + const char *prefix, + const char *name, + rtems_device_major_number major +) +{ + size_t const size = strlen(prefix) + 1 + strlen(name) + 1 + 10 + 1; + char *const s = malloc(size); + + if (s != NULL) { +#ifndef NDEBUG + int rv = +#endif + snprintf(s, size, "%s/%s-%" PRIu32, prefix, name, major); + assert(rv < (int) size); + } + + return s; +} + +char *rtems_media_replace_prefix(const char *new_prefix, const char *path) +{ + const char *const name_try = strrchr(path, '/'); + const char *const name = (name_try == NULL) ? path : name_try + 1; + size_t const new_prefix_len = strlen(new_prefix); + size_t const name_size = strlen(name) + 1; + size_t const size = new_prefix_len + 1 + name_size; + char *const s = malloc(size); + + if (s != NULL) { + memcpy(s, new_prefix, new_prefix_len); + s [new_prefix_len] = '/'; + memcpy(s + new_prefix_len + 1, name, name_size); + } + + return s; +} + +char *rtems_media_append_minor( + const char *path, + rtems_device_minor_number minor +) +{ + size_t const size = strlen(path) + 1 + 10 + 1; + char *const s = malloc(size); + + if (s != NULL) { +#ifndef NDEBUG + int rv = +#endif + snprintf(s, size, "%s-%" PRIu32, path, minor); + assert(rv < (int) size); + } + + return s; +} diff --git a/cpukit/libblock/src/media-server.c b/cpukit/libblock/src/media-server.c new file mode 100644 index 0000000000..66f46abee9 --- /dev/null +++ b/cpukit/libblock/src/media-server.c @@ -0,0 +1,152 @@ +/** + * @file + * + * @ingroup RTEMSMedia + * + * @brief Media implementation. + */ + +/* + * Copyright (c) 2009, 2010 embedded brains GmbH. All rights reserved. + * + * embedded brains GmbH + * Obere Lagerstr. 30 + * 82178 Puchheim + * Germany + * <rtems@embedded-brains.de> + * + * 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. + */ + +#include <string.h> +#include <stdlib.h> +#include <assert.h> + +#include <rtems.h> +#include <rtems/chain.h> +#include <rtems/media.h> + +#define EVENT RTEMS_EVENT_13 + +typedef struct { + rtems_chain_node node; + rtems_media_event event; + const char *src; + rtems_media_worker worker; + void *worker_arg; +} message; + +static RTEMS_CHAIN_DEFINE_EMPTY(message_chain); + +static rtems_id server_id = RTEMS_ID_NONE; + +static void media_server(rtems_task_argument arg __attribute__((unused))) +{ + rtems_status_code sc = RTEMS_SUCCESSFUL; + + while (true) { + message *msg = NULL; + + sc = rtems_chain_get_with_wait( + &message_chain, + EVENT, + RTEMS_NO_TIMEOUT, + (rtems_chain_node **) &msg + ); + assert(sc == RTEMS_SUCCESSFUL); + assert(msg != NULL); + + rtems_media_post_event( + msg->event, + msg->src, + NULL, + msg->worker, + msg->worker_arg + ); + + free(msg); + } +} + +rtems_status_code rtems_media_server_initialize( + rtems_task_priority priority, + size_t stack_size, + rtems_mode modes, + rtems_attribute attributes +) +{ + rtems_status_code sc = RTEMS_SUCCESSFUL; + + if (server_id == RTEMS_ID_NONE) { + sc = rtems_media_initialize(); + if (sc != RTEMS_SUCCESSFUL) { + goto error; + } + + sc = rtems_task_create( + rtems_build_name('M', 'D', 'I', 'A'), + priority, + stack_size, + modes, + attributes, + &server_id + ); + if (sc != RTEMS_SUCCESSFUL) { + goto error; + } + + sc = rtems_task_start(server_id, media_server, 0); + if (sc != RTEMS_SUCCESSFUL) { + goto error; + } + } + + return RTEMS_SUCCESSFUL; + +error: + + if (server_id != RTEMS_ID_NONE) { + rtems_task_delete(server_id); + } + + return RTEMS_NO_MEMORY; +} + +rtems_status_code rtems_media_server_post_event( + rtems_media_event event, + const char *src, + rtems_media_worker worker, + void *worker_arg +) +{ + rtems_status_code sc = RTEMS_SUCCESSFUL; + size_t src_size = strlen(src) + 1; + message *msg = malloc(sizeof(*msg) + src_size); + + if (msg != NULL) { + char *s = (char *) msg + sizeof(*msg); + + memcpy(s, src, src_size); + + msg->event = event; + msg->src = s; + msg->worker = worker; + msg->worker_arg = worker_arg; + + sc = rtems_chain_append_with_notification( + &message_chain, + &msg->node, + server_id, + EVENT + ); + if (sc != RTEMS_SUCCESSFUL) { + sc = RTEMS_NOT_CONFIGURED; + } + } else { + sc = RTEMS_NO_MEMORY; + } + + return sc; +} diff --git a/cpukit/libblock/src/media.c b/cpukit/libblock/src/media.c new file mode 100644 index 0000000000..b4ddd6ad7c --- /dev/null +++ b/cpukit/libblock/src/media.c @@ -0,0 +1,1012 @@ +/** + * @file + * + * @ingroup RTEMSMedia + * + * @brief Media implementation. + */ + +/* + * Copyright (c) 2009, 2010 embedded brains GmbH. All rights reserved. + * + * embedded brains GmbH + * Obere Lagerstr. 30 + * 82178 Puchheim + * Germany + * <rtems@embedded-brains.de> + * + * 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. + */ + +#include <string.h> +#include <assert.h> + +#include <rtems.h> +#include <rtems/bdbuf.h> +#include <rtems/diskdevs.h> +#include <rtems/bdpart.h> +#include <rtems/libio.h> + +#include <rtems/media.h> + +typedef struct { + rtems_bdpart_partition *partitions; + size_t *count; +} partition_table; + +typedef struct { + dev_t physical_disk; + dev_t logical_disk; + rtems_blkdev_bnum begin; + rtems_blkdev_bnum count; +} partition; + +typedef struct media_item { + rtems_chain_node node; + struct media_item *parent; + char *disk_path; + char *mount_path; +} media_item; + +typedef struct listener_item { + rtems_chain_node node; + rtems_media_listener listener; + void *listener_arg; +} listener_item; + +static RTEMS_CHAIN_DEFINE_EMPTY(listener_item_chain); + +static RTEMS_CHAIN_DEFINE_EMPTY(media_item_chain); + +static rtems_id media_mutex = RTEMS_ID_NONE; + +static rtems_status_code lock(void) +{ + rtems_status_code sc = RTEMS_SUCCESSFUL; + + sc = rtems_semaphore_obtain(media_mutex, RTEMS_WAIT, RTEMS_NO_TIMEOUT); + if (sc != RTEMS_SUCCESSFUL) { + sc = RTEMS_NOT_CONFIGURED; + } + + return sc; +} + +static void unlock(void) +{ + rtems_status_code sc = RTEMS_SUCCESSFUL; + + sc = rtems_semaphore_release(media_mutex); + assert(sc == RTEMS_SUCCESSFUL); +} + +static listener_item *find_listener( + rtems_media_listener listener, + void *listener_arg +) +{ + rtems_chain_node *node = rtems_chain_first(&listener_item_chain); + + while (!rtems_chain_is_tail(&listener_item_chain, node)) { + listener_item *item = (listener_item *) node; + + if (item->listener == listener && item->listener_arg == listener_arg) { + return item; + } + + node = rtems_chain_next(node); + } + + return NULL; +} + +rtems_status_code rtems_media_listener_add( + rtems_media_listener listener, + void *listener_arg +) +{ + rtems_status_code sc = RTEMS_SUCCESSFUL; + + sc = lock(); + if (sc == RTEMS_SUCCESSFUL) { + listener_item *item = find_listener(listener, listener_arg); + + if (item == NULL) { + item = malloc(sizeof(*item)); + if (item != NULL) { + item->listener = listener; + item->listener_arg = listener_arg; + rtems_chain_append_unprotected(&listener_item_chain, &item->node); + } else { + sc = RTEMS_NO_MEMORY; + } + } else { + sc = RTEMS_TOO_MANY; + } + + unlock(); + } + + return sc; +} + +rtems_status_code rtems_media_listener_remove( + rtems_media_listener listener, + void *listener_arg +) +{ + rtems_status_code sc = RTEMS_SUCCESSFUL; + + sc = lock(); + if (sc == RTEMS_SUCCESSFUL) { + listener_item *item = find_listener(listener, listener_arg); + + if (item != NULL) { + rtems_chain_extract_unprotected(&item->node); + free(item); + } else { + sc = RTEMS_INVALID_ID; + } + + unlock(); + } + + return sc; +} + +static rtems_status_code notify( + rtems_media_event event, + rtems_media_state state, + const char *src, + const char *dest +) +{ + rtems_status_code sc = RTEMS_SUCCESSFUL; + rtems_status_code rsc = RTEMS_SUCCESSFUL; + rtems_chain_node *node = rtems_chain_first(&listener_item_chain); + + while (!rtems_chain_is_tail(&listener_item_chain, node)) { + listener_item *item = (listener_item *) node; + + sc = (*item->listener)(event, state, src, dest, item->listener_arg); + if (sc != RTEMS_SUCCESSFUL) { + rsc = sc; + } + + node = rtems_chain_next(node); + } + + return rsc; +} + +static void error( + rtems_media_state state, + const char *src, + const char *dest +) +{ + notify(RTEMS_MEDIA_EVENT_ERROR, state, src, dest); +} + +static media_item *get_media_item( + const char *disk_path, + const char *mount_path +) +{ + rtems_chain_node *node = rtems_chain_first(&media_item_chain); + + while (!rtems_chain_is_tail(&media_item_chain, node)) { + media_item *item = (media_item *) node; + + if ( + (disk_path == NULL || strcmp(disk_path, item->disk_path) == 0) + && (mount_path == NULL || strcmp(mount_path, item->mount_path) == 0) + ) { + return item; + } + + node = rtems_chain_next(node); + } + + return NULL; +} + +static void free_item(media_item *item) +{ + rtems_chain_extract(&item->node); + free(item->mount_path); + free(item); +} + +static void create_item( + media_item *parent, + const char *disk_path, + const char *mount_path +) +{ + size_t disk_path_size = strlen(disk_path) + 1; + media_item *item = malloc(sizeof(*item) + disk_path_size); + + if (item != NULL) { + if (mount_path != NULL) { + item->mount_path = strdup(mount_path); + + if (item->mount_path == NULL) { + free(item); + + return; + } + } else { + item->mount_path = NULL; + } + + item->parent = parent; + item->disk_path = (char *) item + sizeof(*item); + memcpy(item->disk_path, disk_path, disk_path_size); + rtems_chain_append(&media_item_chain, &item->node); + } +} + +static void remove_mount_point(const char *mount_path) +{ + media_item *item = get_media_item(NULL, mount_path); + + if (item != NULL) { + free(item->mount_path); + item->mount_path = NULL; + } else { + error(RTEMS_MEDIA_ERROR_MOUNT_POINT_UNKNOWN, mount_path, NULL); + } +} + +static void remove_partition(const char *partition_path) +{ + media_item *item = get_media_item(partition_path, NULL); + + if (item != NULL) { + if (item->mount_path != NULL) { + error( + RTEMS_MEDIA_ERROR_PARTITION_DETACH_WITH_MOUNT, + partition_path, + item->mount_path + ); + } + free_item(item); + } else { + error(RTEMS_MEDIA_ERROR_PARTITION_UNKNOWN, partition_path, NULL); + } +} + +static void remove_disk(const char *disk_path) +{ + media_item *item = get_media_item(disk_path, NULL); + + if (item != NULL) { + rtems_chain_node *node = rtems_chain_first(&media_item_chain); + + while (!rtems_chain_is_tail(&media_item_chain, node)) { + media_item *child = (media_item *) node; + + node = rtems_chain_next(node); + + if (child->parent == item) { + if (child->mount_path != NULL) { + error( + RTEMS_MEDIA_ERROR_MOUNT_POINT_ORPHAN, + child->mount_path, + disk_path + ); + } + error(RTEMS_MEDIA_ERROR_PARTITION_ORPHAN, child->disk_path, disk_path); + free_item(child); + } + } + + free_item(item); + } else { + error(RTEMS_MEDIA_ERROR_DISK_UNKNOWN, disk_path, NULL); + } +} + +static void add_disk(const char *disk_path) +{ + media_item *item = get_media_item(disk_path, NULL); + + if (item != NULL) { + error(RTEMS_MEDIA_ERROR_DISK_EXISTS, disk_path, NULL); + remove_disk(disk_path); + } + + create_item(NULL, disk_path, NULL); +} + +static void add_partition(const char *disk_path, const char *partition_path) +{ + media_item *item = get_media_item(partition_path, NULL); + media_item *parent = get_media_item(disk_path, NULL); + + if (item != NULL) { + error(RTEMS_MEDIA_ERROR_DISK_OR_PARTITION_EXISTS, partition_path, NULL); + remove_disk(partition_path); + } + + if (parent != NULL) { + create_item(parent, partition_path, NULL); + } else { + error( + RTEMS_MEDIA_ERROR_PARTITION_WITH_UNKNOWN_DISK, + partition_path, + disk_path + ); + } +} + +static void add_mount_point(const char *disk_path, const char *mount_path) +{ + media_item *item = get_media_item(disk_path, NULL); + + if (item != NULL) { + if (item->mount_path != NULL) { + error(RTEMS_MEDIA_ERROR_MOUNT_POINT_EXISTS, item->mount_path, NULL); + free(item->mount_path); + } + item->mount_path = strdup(mount_path); + } else { + error(RTEMS_MEDIA_ERROR_DISK_OR_PARTITION_UNKNOWN, disk_path, NULL); + } +} + +static bool is_add_state(rtems_media_state state) +{ + return state == RTEMS_MEDIA_STATE_SUCCESS; +} + +static bool is_remove_state(rtems_media_state state) +{ + return state == RTEMS_MEDIA_STATE_SUCCESS + || state == RTEMS_MEDIA_STATE_FAILED; +} + +static rtems_status_code remember_event( + rtems_media_event event, + rtems_media_state state, + const char *src, + const char *dest +) +{ + switch (event) { + case RTEMS_MEDIA_EVENT_DISK_ATTACH: + if (is_add_state(state)) { + add_disk(dest); + } + break; + case RTEMS_MEDIA_EVENT_PARTITION_ATTACH: + if (is_add_state(state)) { + add_partition(src, dest); + } + break; + case RTEMS_MEDIA_EVENT_MOUNT: + if (is_add_state(state)) { + add_mount_point(src, dest); + } + break; + case RTEMS_MEDIA_EVENT_UNMOUNT: + if (is_remove_state(state)) { + remove_mount_point(src); + } + break; + case RTEMS_MEDIA_EVENT_PARTITION_DETACH: + if (is_remove_state(state)) { + remove_partition(src); + } + break; + case RTEMS_MEDIA_EVENT_DISK_DETACH: + if (is_remove_state(state)) { + remove_disk(src); + } + break; + default: + break; + } + + return RTEMS_SUCCESSFUL; +} + +static rtems_status_code process_event( + rtems_media_event event, + const char *src, + char **dest_ptr, + rtems_media_worker worker, + void *worker_arg +) +{ + rtems_status_code sc = RTEMS_SUCCESSFUL; + rtems_media_state state = RTEMS_MEDIA_STATE_FAILED; + char *dest = NULL; + + sc = notify(event, RTEMS_MEDIA_STATE_INQUIRY, src, NULL); + if (sc == RTEMS_SUCCESSFUL) { + state = RTEMS_MEDIA_STATE_READY; + } else { + state = RTEMS_MEDIA_STATE_ABORTED; + } + + sc = (*worker)(state, src, &dest, worker_arg); + if (state == RTEMS_MEDIA_STATE_READY) { + if (sc == RTEMS_SUCCESSFUL) { + state = RTEMS_MEDIA_STATE_SUCCESS; + } else { + state = RTEMS_MEDIA_STATE_FAILED; + } + } + + notify(event, state, src, dest); + remember_event(event, state, src, dest); + + if (state == RTEMS_MEDIA_STATE_SUCCESS) { + sc = RTEMS_SUCCESSFUL; + } else if (state == RTEMS_MEDIA_STATE_ABORTED) { + sc = RTEMS_UNSATISFIED; + } else { + sc = RTEMS_IO_ERROR; + } + + if (dest_ptr != NULL && sc == RTEMS_SUCCESSFUL) { + *dest_ptr = dest; + } else { + free(dest); + } + + return sc; +} + +static rtems_status_code mount_worker( + rtems_media_state state, + const char *src, + char **dest, + void *worker_arg +) +{ + int rv = 0; + + if (state == RTEMS_MEDIA_STATE_READY) { + char *mount_path = NULL; + + if (worker_arg == NULL) { + mount_path = rtems_media_replace_prefix(RTEMS_MEDIA_MOUNT_BASE, src); + } else { + mount_path = strdup(worker_arg); + } + + if (mount_path == NULL) { + return RTEMS_IO_ERROR; + } + + rv = rtems_mkdir(mount_path, S_IRWXU | S_IRWXG | S_IRWXO); + if (rv != 0) { + free(mount_path); + + return RTEMS_IO_ERROR; + } + + rv = mount( + src, + mount_path, + RTEMS_FILESYSTEM_TYPE_DOSFS, + RTEMS_FILESYSTEM_READ_WRITE, + NULL + ); + if (rv != 0) { + rmdir(mount_path); + free(mount_path); + + return RTEMS_IO_ERROR; + } + + *dest = mount_path; + } + + return RTEMS_SUCCESSFUL; +} + +static rtems_status_code do_mount( + const char *src, + char **dest_ptr, + rtems_media_worker worker, + void *worker_arg +) +{ + if (worker == NULL) { + worker = mount_worker; + } + + return process_event( + RTEMS_MEDIA_EVENT_MOUNT, + src, + dest_ptr, + worker, + worker_arg + ); +} + +static rtems_status_code do_partition_attach( + const char *src, + char **dest_ptr, + rtems_media_worker worker, + void *worker_arg +) +{ + rtems_status_code sc = RTEMS_SUCCESSFUL; + char *part_path = NULL; + + if (worker != NULL) { + sc = process_event( + RTEMS_MEDIA_EVENT_PARTITION_ATTACH, + src, + &part_path, + worker, + worker_arg + ); + + if (sc == RTEMS_SUCCESSFUL) { + sc = do_mount(part_path, NULL, NULL, NULL); + } + } else { + sc = RTEMS_INVALID_ADDRESS; + } + + if (dest_ptr != NULL && sc == RTEMS_SUCCESSFUL) { + *dest_ptr = part_path; + } else { + free(part_path); + } + + return sc; +} + +static rtems_status_code partition_attach_worker( + rtems_media_state state, + const char *src, + char **dest, + void *worker_arg +) +{ + rtems_status_code sc = RTEMS_SUCCESSFUL; + + if (state == RTEMS_MEDIA_STATE_READY) { + partition *part = worker_arg; + rtems_device_minor_number minor = + rtems_filesystem_dev_minor_t(part->logical_disk); + char *part_path = rtems_media_append_minor(src, minor); + + if (part_path == NULL) { + return RTEMS_IO_ERROR; + } + + sc = rtems_disk_create_log( + part->logical_disk, + part->physical_disk, + part->begin, + part->count, + part_path + ); + if (sc != RTEMS_SUCCESSFUL) { + free(part_path); + + return RTEMS_IO_ERROR; + } + + *dest = part_path; + } + + return RTEMS_SUCCESSFUL; +} + +static rtems_status_code attach_and_mount_partitions( + const char *disk_path, + rtems_bdpart_partition *partitions, + size_t count +) +{ + rtems_status_code sc = RTEMS_SUCCESSFUL; + rtems_device_major_number major = 0; + rtems_device_minor_number minor = 0; + dev_t dev = 0; + size_t i = 0; + + sc = rtems_media_get_device_identifier(disk_path, &dev); + if (sc != RTEMS_SUCCESSFUL) { + return RTEMS_INVALID_ID; + } + + major = rtems_filesystem_dev_major_t(dev); + minor = rtems_filesystem_dev_minor_t(dev) + 1; + + for (i = 0; i < count; ++i, ++minor) { + partition part_desc = { + .physical_disk = dev, + .logical_disk = rtems_filesystem_make_dev_t(major, minor), + .begin = partitions [i].begin, + .count = partitions [i].end - partitions [i].begin + }; + char *part_path = NULL; + + sc = process_event( + RTEMS_MEDIA_EVENT_PARTITION_ATTACH, + disk_path, + &part_path, + partition_attach_worker, + &part_desc + ); + + if (sc == RTEMS_SUCCESSFUL) { + sc = do_mount(part_path, NULL, NULL, NULL); + } + + free(part_path); + } + + return sc; +} + +static rtems_status_code partition_inquiry_worker( + rtems_media_state state, + const char *src, + char **dest __attribute__((unused)), + void *worker_arg +) +{ + rtems_status_code sc = RTEMS_SUCCESSFUL; + + if (state == RTEMS_MEDIA_STATE_READY) { + partition_table *pt = worker_arg; + rtems_bdpart_format format; + + sc = rtems_bdpart_read(src, &format, pt->partitions, pt->count); + if (sc != RTEMS_SUCCESSFUL || *pt->count == 0) { + return RTEMS_IO_ERROR; + } + } + + return RTEMS_SUCCESSFUL; +} + +static rtems_status_code do_partition_inquiry( + const char *src, + char **dest_ptr, + rtems_media_worker worker, + void *worker_arg +) +{ + rtems_status_code sc = RTEMS_SUCCESSFUL; + + if (worker == NULL) { + rtems_bdpart_partition partitions [RTEMS_BDPART_PARTITION_NUMBER_HINT]; + size_t count = RTEMS_BDPART_PARTITION_NUMBER_HINT; + partition_table pt = { + .partitions = partitions, + .count = &count + }; + + sc = process_event( + RTEMS_MEDIA_EVENT_PARTITION_INQUIRY, + src, + dest_ptr, + partition_inquiry_worker, + &pt + ); + + if (sc == RTEMS_SUCCESSFUL) { + sc = attach_and_mount_partitions(src, partitions, count); + } + } else { + sc = process_event( + RTEMS_MEDIA_EVENT_PARTITION_INQUIRY, + src, + dest_ptr, + worker, + worker_arg + ); + } + + return sc; +} + +static rtems_status_code do_disk_attach( + const char *src, + char **dest_ptr, + rtems_media_worker worker, + void *worker_arg +) +{ + rtems_status_code sc = RTEMS_SUCCESSFUL; + rtems_status_code rsc = RTEMS_SUCCESSFUL; + char *disk_path = NULL; + + if (worker != NULL) { + rsc = process_event( + RTEMS_MEDIA_EVENT_DISK_ATTACH, + src, + &disk_path, + worker, + worker_arg + ); + + if (rsc == RTEMS_SUCCESSFUL) { + sc = do_mount(disk_path, NULL, NULL, NULL); + + if (sc != RTEMS_SUCCESSFUL) { + do_partition_inquiry(disk_path, NULL, NULL, NULL); + } + } + } else { + rsc = RTEMS_INVALID_ADDRESS; + } + + if (dest_ptr != NULL && rsc == RTEMS_SUCCESSFUL) { + *dest_ptr = disk_path; + } else { + free(disk_path); + } + + return rsc; +} + +static rtems_status_code unmount_worker( + rtems_media_state state, + const char *src, + char **dest __attribute__((unused)), + void *worker_arg __attribute__((unused)) +) +{ + rtems_status_code sc = RTEMS_SUCCESSFUL; + + if (state == RTEMS_MEDIA_STATE_READY) { + int rv = unmount(src); + + if (rv == 0) { + rv = rmdir(src); + if (rv != 0) { + sc = RTEMS_IO_ERROR; + } + } else { + sc = RTEMS_IO_ERROR; + } + } + + return sc; +} + +static rtems_status_code do_unmount( + const char *src, + char **dest_ptr, + rtems_media_worker worker, + void *worker_arg +) +{ + if (worker == NULL) { + worker = unmount_worker; + worker_arg = NULL; + } + + return process_event( + RTEMS_MEDIA_EVENT_UNMOUNT, + src, + dest_ptr, + worker, + worker_arg + ); +} + +static rtems_status_code disk_detach_worker( + rtems_media_state state, + const char *src, + char **dest __attribute__((unused)), + void *worker_arg __attribute__((unused)) +) +{ + rtems_status_code sc = RTEMS_SUCCESSFUL; + rtems_status_code rsc = RTEMS_SUCCESSFUL; + + if (state == RTEMS_MEDIA_STATE_READY) { + dev_t dev = 0; + + sc = rtems_media_get_device_identifier(src, &dev); + if (sc != RTEMS_SUCCESSFUL) { + return RTEMS_IO_ERROR; + } + + sc = rtems_bdbuf_syncdev(dev); + if (sc != RTEMS_SUCCESSFUL) { + rsc = RTEMS_IO_ERROR; + } + + sc = rtems_disk_delete(dev); + if (sc != RTEMS_SUCCESSFUL) { + rsc = RTEMS_IO_ERROR; + } + + rtems_bdbuf_purge_dev(dev); + + if (rtems_filesystem_dev_minor_t(dev) == 0) { + sc = rtems_io_unregister_driver(rtems_filesystem_dev_major_t(dev)); + if (sc != RTEMS_SUCCESSFUL) { + rsc = RTEMS_IO_ERROR; + } + } + } + + return rsc; +} + +static rtems_status_code detach_item(rtems_media_event event, media_item *item) +{ + rtems_status_code sc = RTEMS_SUCCESSFUL; + rtems_status_code rsc = RTEMS_SUCCESSFUL; + + if (item->mount_path != NULL) { + sc = do_unmount(item->mount_path, NULL, NULL, NULL); + if (sc != RTEMS_SUCCESSFUL) { + rsc = RTEMS_IO_ERROR; + } + } + + sc = process_event(event, item->disk_path, NULL, disk_detach_worker, NULL); + if (sc != RTEMS_SUCCESSFUL) { + rsc = RTEMS_IO_ERROR; + } + + return rsc; +} + +static rtems_status_code detach_parent_item(media_item *parent) +{ + rtems_status_code sc = RTEMS_SUCCESSFUL; + rtems_status_code rsc = RTEMS_SUCCESSFUL; + + rtems_chain_node *node = rtems_chain_first(&media_item_chain); + + while (!rtems_chain_is_tail(&media_item_chain, node)) { + media_item *child = (media_item *) node; + + node = rtems_chain_next(node); + + if (child->parent == parent) { + sc = detach_item(RTEMS_MEDIA_EVENT_PARTITION_DETACH, child); + if (sc != RTEMS_SUCCESSFUL) { + rsc = RTEMS_IO_ERROR; + } + } + } + + sc = detach_item(RTEMS_MEDIA_EVENT_DISK_DETACH, parent); + if (sc != RTEMS_SUCCESSFUL) { + rsc = RTEMS_IO_ERROR; + } + + return rsc; +} + +static rtems_status_code do_disk_detach( + const char *src, + char **dest_ptr, + rtems_media_worker worker, + void *worker_arg +) +{ + if (worker == NULL) { + media_item *parent = get_media_item(src, NULL); + + if (parent != NULL) { + return detach_parent_item(parent); + } + + worker = disk_detach_worker; + worker_arg = NULL; + } + + return process_event( + RTEMS_MEDIA_EVENT_DISK_DETACH, + src, + dest_ptr, + worker, + worker_arg + ); +} + +static rtems_status_code do_partition_detach( + const char *src, + char **dest_ptr, + rtems_media_worker worker, + void *worker_arg +) +{ + if (worker == NULL) { + media_item *item = get_media_item(src, NULL); + + if (item != NULL) { + return detach_item(RTEMS_MEDIA_EVENT_PARTITION_DETACH, item); + } + + worker = disk_detach_worker; + worker_arg = NULL; + } + + return process_event( + RTEMS_MEDIA_EVENT_PARTITION_DETACH, + src, + dest_ptr, + worker, + worker_arg + ); +} + +rtems_status_code rtems_media_post_event( + rtems_media_event event, + const char *src, + char **dest_ptr, + rtems_media_worker worker, + void *worker_arg +) +{ + rtems_status_code sc = RTEMS_SUCCESSFUL; + + sc = lock(); + if (sc != RTEMS_SUCCESSFUL) { + return sc; + } + + switch (event) { + case RTEMS_MEDIA_EVENT_DISK_ATTACH: + sc = do_disk_attach(src, dest_ptr, worker, worker_arg); + break; + case RTEMS_MEDIA_EVENT_DISK_DETACH: + sc = do_disk_detach(src, dest_ptr, worker, worker_arg); + break; + case RTEMS_MEDIA_EVENT_MOUNT: + sc = do_mount(src, dest_ptr, worker, worker_arg); + break; + case RTEMS_MEDIA_EVENT_UNMOUNT: + sc = do_unmount(src, dest_ptr, worker, worker_arg); + break; + case RTEMS_MEDIA_EVENT_PARTITION_INQUIRY: + sc = do_partition_inquiry(src, dest_ptr, worker, worker_arg); + break; + case RTEMS_MEDIA_EVENT_PARTITION_ATTACH: + sc = do_partition_attach(src, dest_ptr, worker, worker_arg); + break; + case RTEMS_MEDIA_EVENT_PARTITION_DETACH: + sc = do_partition_detach(src, dest_ptr, worker, worker_arg); + break; + default: + sc = RTEMS_INVALID_ID; + break; + } + + unlock(); + + return sc; +} + +rtems_status_code rtems_media_initialize(void) +{ + rtems_status_code sc = RTEMS_SUCCESSFUL; + + if (media_mutex == RTEMS_ID_NONE) { + sc = rtems_semaphore_create( + rtems_build_name('M', 'D', 'I', 'A'), + 1, + RTEMS_LOCAL | RTEMS_PRIORITY + | RTEMS_INHERIT_PRIORITY | RTEMS_BINARY_SEMAPHORE, + 0, + &media_mutex + ); + if (sc != RTEMS_SUCCESSFUL) { + sc = RTEMS_NO_MEMORY; + } + } + + return sc; +} diff --git a/cpukit/libblock/src/nvdisk-sram.c b/cpukit/libblock/src/nvdisk-sram.c new file mode 100644 index 0000000000..b31f83cfed --- /dev/null +++ b/cpukit/libblock/src/nvdisk-sram.c @@ -0,0 +1,68 @@ +/* + * $Id$ + * + * RTEMS Project (http://www.rtems.org/) + * + * Copyright 2007 Chris Johns (chrisj@rtems.org) + */ +/** + * Provide SRAM support for the NV Disk. + */ + +#if HAVE_CONFIG_H +#include "config.h" +#endif + +#include <stdio.h> +#include <errno.h> + +#include <rtems.h> + +#include <rtems/nvdisk-sram.h> + +#ifndef NVDISK_SRAM_ERROR_TRACE +#define NVDISK_SRAM_ERROR_TRACE (0) +#endif + +static int +rtems_nvdisk_sram_read (uint32_t device __attribute__((unused)), + uint32_t flags __attribute__((unused)), + void* base, + uint32_t offset, + void* buffer, + size_t size) +{ + memcpy (buffer, (base + offset), size); + return 0; +} + +static int +rtems_nvdisk_sram_write (uint32_t device __attribute__((unused)), + uint32_t flags __attribute__((unused)), + void* base, + uint32_t offset, + const void* buffer, + size_t size) +{ + memcpy ((base + offset), buffer, size); + return 0; +} + +static int +rtems_nvdisk_sram_verify (uint32_t device __attribute__((unused)), + uint32_t flags __attribute__((unused)), + void* base, + uint32_t offset, + const void* buffer, + size_t size) +{ + return memcmp ((base + offset), buffer, size) == 0 ? 0 : EIO; +} + + +const rtems_nvdisk_driver_handlers rtems_nvdisk_sram_handlers = +{ + read: rtems_nvdisk_sram_read, + write: rtems_nvdisk_sram_write, + verify: rtems_nvdisk_sram_verify +}; diff --git a/cpukit/libblock/src/nvdisk.c b/cpukit/libblock/src/nvdisk.c new file mode 100644 index 0000000000..26c0f40309 --- /dev/null +++ b/cpukit/libblock/src/nvdisk.c @@ -0,0 +1,845 @@ +/* + * nvdisk.c -- Non-volatile 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$ + */ + +#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/nvdisk.h" + +/** + * @note + * + * The use of pages can vary. The rtems_nvdisk_*_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_nvdisk_*_page functions. + * + * You must always show the page number as relative in any trace + * or error message as device-page and if you have to + * the page number as absolute use device~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. + */ + +/** + * Control tracing. It can be compiled out of the code for small + * footprint targets. Leave in by default. + */ +#if !defined (RTEMS_NVDISK_TRACE) +#define RTEMS_NVDISK_TRACE 0 +#endif + +/** + * NV Device Control holds the segment controls + */ +typedef struct rtems_nvdisk_device_ctl +{ + /** + * The device this segment resides on. + */ + uint32_t device; + + /** + * Total number of pages in the device. + */ + uint32_t pages; + + /** + * Number of pages used for page checksums. + */ + uint32_t pages_desc; + + /** + * First block number for this device. + */ + uint32_t block_base; + + /** + * Device descriptor. + */ + const rtems_nvdisk_device_desc* descriptor; +} rtems_nvdisk_device_ctl; + +/** + * The NV disk control structure for a single disk. There is one + * for each minor disk in the system. + */ +typedef struct rtems_mvdisk +{ + 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 block_size; /**< The block size for this disk. */ + uint32_t block_count; /**< The number of available blocks. */ + rtems_nvdisk_device_ctl* devices; /**< The NV devices for this disk. */ + uint32_t device_count; /**< The number of NV devices. */ + uint32_t cs_pages; /**< The num of pages of checksums. */ + rtems_id lock; /**< Mutex for threading protection.*/ + uint32_t info_level; /**< The info trace level. */ +} rtems_nvdisk; + +/** + * The array of NV disks we support. + */ +static rtems_nvdisk* rtems_nvdisks; + +/** + * The number of NV disks we have. + */ +static uint32_t rtems_nvdisk_count; + +/** + * The CRC16 factor table. Created during initialisation. + */ +static uint16_t* rtems_nvdisk_crc16_factor; + +/** + * Calculate the CRC16 checksum. + * + * @param _b The byte to checksum. + * @param _c The current checksum. + */ +#define rtems_nvdisk_calc_crc16(_b, _c) \ + rtems_nvdisk_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_nvdisk_crc16_gen_factors (uint16_t pattern) +{ + uint32_t b; + + rtems_nvdisk_crc16_factor = malloc (sizeof (uint16_t) * 256); + if (!rtems_nvdisk_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_nvdisk_crc16_factor[b] = v & 0xffff; + } + return RTEMS_SUCCESSFUL; +} + +#if RTEMS_NVDISK_TRACE +/** + * Print a message to the nvdisk output and flush it. + * + * @param nvd The nvdisk 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_nvdisk_printf (const rtems_nvdisk* nvd, const char *format, ...) +{ + int ret = 0; + if (nvd->info_level >= 3) + { + va_list args; + va_start (args, format); + fprintf (stdout, "nvdisk:"); + ret = vfprintf (stdout, format, args); + fprintf (stdout, "\n"); + fflush (stdout); + va_end (args); + } + return ret; +} + +/** + * Print a info message to the nvdisk output and flush it. + * + * @param nvd The nvdisk 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_nvdisk_info (const rtems_nvdisk* nvd, const char *format, ...) +{ + int ret = 0; + if (nvd->info_level >= 2) + { + va_list args; + va_start (args, format); + fprintf (stdout, "nvdisk:"); + ret = vfprintf (stdout, format, args); + fprintf (stdout, "\n"); + fflush (stdout); + va_end (args); + } + return ret; +} + +/** + * Print a warning to the nvdisk output and flush it. + * + * @param nvd The nvdisk 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_nvdisk_warning (const rtems_nvdisk* nvd, const char *format, ...) +{ + int ret = 0; + if (nvd->info_level >= 1) + { + va_list args; + va_start (args, format); + fprintf (stdout, "nvdisk:warning:"); + ret = vfprintf (stdout, format, args); + fprintf (stdout, "\n"); + fflush (stdout); + va_end (args); + } + return ret; +} +#endif + +/** + * Print an error to the nvdisk 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_nvdisk_error (const char *format, ...) +{ + int ret; + va_list args; + va_start (args, format); + fprintf (stderr, "nvdisk:error:"); + ret = vfprintf (stderr, format, args); + fprintf (stderr, "\n"); + fflush (stderr); + va_end (args); + return ret; +} + +/** + * Get the descriptor for a device. + */ +static const rtems_nvdisk_device_desc* +rtems_nvdisk_device_descriptor (const rtems_nvdisk* nvd, uint32_t device) +{ + return nvd->devices[device].descriptor; +} + +/** + * Read a block of data from a device. + */ +static int +rtems_nvdisk_device_read (const rtems_nvdisk* nvd, + uint32_t device, + uint32_t offset, + void* buffer, + uint32_t size) +{ + const rtems_nvdisk_device_desc* dd; + const rtems_nvdisk_driver_handlers* ops; + dd = rtems_nvdisk_device_descriptor (nvd, device); + ops = nvd->devices[device].descriptor->nv_ops; +#if RTEMS_NVDISK_TRACE + rtems_nvdisk_printf (nvd, " dev-read: %02d-%08x: s=%d", + device, offset, size); +#endif + return ops->read (device, dd->flags, dd->base, offset, buffer, size); +} + +/** + * Write a block of data to a device. + */ +static int +rtems_nvdisk_device_write (const rtems_nvdisk* nvd, + uint32_t device, + uint32_t offset, + const void* buffer, + uint32_t size) +{ + const rtems_nvdisk_device_desc* dd; + const rtems_nvdisk_driver_handlers* ops; + dd = rtems_nvdisk_device_descriptor (nvd, device); + ops = nvd->devices[device].descriptor->nv_ops; +#if RTEMS_NVDISK_TRACE + rtems_nvdisk_printf (nvd, " dev-write: %02d-%08x: s=%d", + device, offset, size); +#endif + return ops->write (device, dd->flags, dd->base, offset, buffer, size); +} + +#if NOT_USED +/** + * Verify the data with the data in a segment. + */ +static int +rtems_nvdisk_device_verify (const rtems_nvdisk* nvd, + uint32_t device, + uint32_t offset, + const void* buffer, + uint32_t size) +{ + const rtems_nvdisk_device_desc* dd; + const rtems_nvdisk_driver_handlers* ops; + dd = rtems_nvdisk_device_descriptor (nvd, device); + ops = nvd->devices[device].descriptor->nv_ops; +#if RTEMS_NVDISK_TRACE + rtems_nvdisk_printf (nvd, " seg-verify: %02d-%08x: s=%d", + device, offset, size); +#endif + return ops->verify (device, dd->flags, dd->base, offset, buffer, size); +} +#endif + +/** + * Read a page of data from the device. + */ +static int +rtems_nvdisk_read_page (const rtems_nvdisk* nvd, + uint32_t device, + uint32_t page, + void* buffer) +{ + return rtems_nvdisk_device_read (nvd, device, + page * nvd->block_size, buffer, + nvd->block_size); +} + +/** + * Write a page of data to a device. + */ +static int +rtems_nvdisk_write_page (const rtems_nvdisk* nvd, + uint32_t device, + uint32_t page, + const void* buffer) +{ + return rtems_nvdisk_device_write (nvd, device, + page * nvd->block_size, + buffer, nvd->block_size); +} + +/** + * Read the checksum from the device. + */ +static int +rtems_nvdisk_read_checksum (const rtems_nvdisk* nvd, + uint32_t device, + uint32_t page, + uint16_t* cs) +{ + return rtems_nvdisk_device_read (nvd, device, + page * sizeof (uint16_t), + cs, sizeof (uint16_t)); +} + +/** + * Write the checksum to the device. + */ +static int +rtems_nvdisk_write_checksum (const rtems_nvdisk* nvd, + uint32_t device, + uint32_t page, + const uint16_t cs) +{ + return rtems_nvdisk_device_write (nvd, device, + page * sizeof (uint16_t), + &cs, sizeof (uint16_t)); +} + +/** + * Calculate the pages in a device give the device descriptor and the + * page size. + * + * @param dd The device descriptor. + * @param page_size The page size in bytes. + */ +static uint32_t +rtems_nvdisk_pages_in_device (const rtems_nvdisk* nvd, + const rtems_nvdisk_device_desc* dd) +{ + return dd->size / nvd->block_size; +} + +/** + * Calculate the number of pages needed to hold the page descriptors. + * The calculation need to round up. + */ +static uint32_t +rtems_nvdisk_page_desc_pages (const rtems_nvdisk* nvd, + const rtems_nvdisk_device_desc* dd) +{ + uint32_t pages = rtems_nvdisk_pages_in_device (nvd, dd); + uint32_t bytes = pages * sizeof (uint16_t); + return ((bytes - 1) / nvd->block_size) + 1; +} + +/** + * Calculate the checksum of a page. + */ +static uint16_t +rtems_nvdisk_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_nvdisk_calc_crc16 (cs, *buffer); + + return cs; +} + +/** + * Map a block to a device. + */ +static rtems_nvdisk_device_ctl* +rtems_nvdisk_get_device (rtems_nvdisk* nvd, uint32_t block) +{ + uint32_t device; + + if (block >= nvd->block_count) + { + rtems_nvdisk_error ("read-block: bad block: %d", block); + return NULL; + } + + for (device = 0; device < nvd->device_count; device++) + { + rtems_nvdisk_device_ctl* dc = &nvd->devices[device]; + if ((block >= dc->block_base) && + (block < (dc->block_base + dc->pages - dc->pages_desc))) + return dc; + } + + rtems_nvdisk_error ("map-block:%d: no device/page map found", block); + + return NULL; +} + +/** + * Get the page for a block in a device. + */ +static uint32_t +rtems_nvdisk_get_page (rtems_nvdisk_device_ctl* dc, + uint32_t block) +{ + return block - dc->block_base; +} + +/** + * Read a block. The block is checked to see if the page referenced + * is valid and the page has a valid crc. + * + * @param nvd The rtems_nvdisk 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 number or crc. + */ +static int +rtems_nvdisk_read_block (rtems_nvdisk* nvd, uint32_t block, uint8_t* buffer) +{ + rtems_nvdisk_device_ctl* dc; + uint32_t page; + uint16_t crc; + uint16_t cs; + int ret; + + dc = rtems_nvdisk_get_device (nvd, block); + + if (!dc) + return EIO; + + page = rtems_nvdisk_get_page (dc, block); + +#if RTEMS_NVDISK_TRACE + rtems_nvdisk_info (nvd, " read-block:%d=>%02d-%03d, cs:%04x", + block, dc->device, page, crc); +#endif + + ret = rtems_nvdisk_read_checksum (nvd, dc->device, page, &crc); + + if (ret) + return ret; + + if (crc == 0xffff) + { +#if RTEMS_NVDISK_TRACE + rtems_nvdisk_warning (nvd, "read-block: crc not set: %d", block); +#endif + memset (buffer, 0, nvd->block_size); + return 0; + } + + ret = rtems_nvdisk_read_page (nvd, dc->device, page + dc->pages_desc, buffer); + + if (ret) + return ret; + + cs = rtems_nvdisk_page_checksum (buffer, nvd->block_size); + + if (cs != crc) + { + rtems_nvdisk_error ("read-block: crc failure: %d: buffer:%04x page:%04x", + block, cs, crc); + return EIO; + } + + return 0; +} + +/** + * Write a block. + * + * @param nvd The rtems_nvdisk 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_nvdisk_write_block (rtems_nvdisk* nvd, + uint32_t block, + const unsigned char* buffer) +{ + rtems_nvdisk_device_ctl* dc; + uint32_t page; + uint16_t cs; + int ret; + + dc = rtems_nvdisk_get_device (nvd, block); + + if (!dc) + return EIO; + + page = rtems_nvdisk_get_page (dc, block); + + cs = rtems_nvdisk_page_checksum (buffer, nvd->block_size); + +#if RTEMS_NVDISK_TRACE + rtems_nvdisk_info (nvd, " write-block:%d=>%02d-%03d", block, dc->device, page); +#endif + + ret = rtems_nvdisk_write_page (nvd, dc->device, page + dc->pages_desc, buffer); + + if (ret) + return ret; + + return rtems_nvdisk_write_checksum (nvd, dc->device, page, cs); +} + +/** + * 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_nvdisk_read (rtems_nvdisk* nvd, rtems_blkdev_request* req) +{ + rtems_blkdev_sg_buffer* sg = req->bufs; + uint32_t bufs; + int ret = 0; + +#if RTEMS_NVDISK_TRACE + rtems_nvdisk_info (nvd, "read: blocks=%d", req->bufnum); +#endif + + for (bufs = 0; (ret == 0) && (bufs < req->bufnum); bufs++, sg++) + { + uint8_t* data; + uint32_t nvb; + uint32_t b; + nvb = sg->length / nvd->block_size; + data = sg->buffer; + for (b = 0; b < nvb; b++, data += nvd->block_size) + { + ret = rtems_nvdisk_read_block (nvd, 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 NV 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_nvdisk_write (rtems_nvdisk* nvd, rtems_blkdev_request* req) +{ + rtems_blkdev_sg_buffer* sg = req->bufs; + uint32_t bufs; + int ret = 0; + +#if RTEMS_NVDISK_TRACE + rtems_nvdisk_info (nvd, "write: blocks=%d", req->bufnum); +#endif + + for (bufs = 0; (ret == 0) && (bufs < req->bufnum); bufs++, sg++) + { + uint8_t* data; + uint32_t nvb; + uint32_t b; + nvb = sg->length / nvd->block_size; + data = sg->buffer; + for (b = 0; b < nvb; b++, data += nvd->block_size) + { + ret = rtems_nvdisk_write_block (nvd, 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; +} + +/** + * NV disk erase disk sets all the checksums for 0xffff. + * + * @param nvd The nvdisk data. + * @retval int The ioctl return value. + */ +static int +rtems_nvdisk_erase_disk (rtems_nvdisk* nvd) +{ + uint32_t device; + +#if RTEMS_NVDISK_TRACE + rtems_nvdisk_info (nvd, "erase-disk"); +#endif + + for (device = 0; device < nvd->device_count; device++) + { + rtems_nvdisk_device_ctl* dc = &nvd->devices[device]; + uint32_t page; + for (page = 0; page < (dc->pages - dc->pages_desc); page++) + { + int ret = rtems_nvdisk_write_checksum (nvd, dc->device, page, 0xffff); + if (ret) + return ret; + } + } + + return 0; +} + +/** + * NV disk IOCTL handler. + * + * @param dd Disk device. + * @param req IOCTL request code. + * @param argp IOCTL argument. + * @retval The IOCTL return value + */ +static int +rtems_nvdisk_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; + + if (minor >= rtems_nvdisk_count) + { + errno = ENODEV; + return -1; + } + + if (rtems_nvdisks[minor].device_count == 0) + { + errno = ENODEV; + return -1; + } + + errno = 0; + + sc = rtems_semaphore_obtain (rtems_nvdisks[minor].lock, RTEMS_WAIT, 0); + if (sc != RTEMS_SUCCESSFUL) + errno = EIO; + else + { + errno = 0; + switch (req) + { + case RTEMS_BLKIO_REQUEST: + switch (r->req) + { + case RTEMS_BLKDEV_REQ_READ: + errno = rtems_nvdisk_read (&rtems_nvdisks[minor], r); + break; + + case RTEMS_BLKDEV_REQ_WRITE: + errno = rtems_nvdisk_write (&rtems_nvdisks[minor], r); + break; + + default: + errno = EINVAL; + break; + } + break; + + case RTEMS_NVDISK_IOCTL_ERASE_DISK: + errno = rtems_nvdisk_erase_disk (&rtems_nvdisks[minor]); + break; + + case RTEMS_NVDISK_IOCTL_INFO_LEVEL: + rtems_nvdisks[minor].info_level = (uintptr_t) argp; + break; + + default: + rtems_blkdev_ioctl (dd, req, argp); + break; + } + + sc = rtems_semaphore_release (rtems_nvdisks[minor].lock); + if (sc != RTEMS_SUCCESSFUL) + errno = EIO; + } + + return errno == 0 ? 0 : -1; +} + +/** + * NV disk device driver initialization. + * + * @todo Memory clean up on error is really badly handled. + * + * @param major NV disk major device number. + * @param minor Minor device number, not applicable. + * @param arg Initialization argument, not applicable. + */ +rtems_device_driver +rtems_nvdisk_initialize (rtems_device_major_number major, + rtems_device_minor_number minor, + void* arg __attribute__((unused))) +{ + const rtems_nvdisk_config* c = rtems_nvdisk_configuration; + rtems_nvdisk* nvd; + rtems_status_code sc; + + sc = rtems_disk_io_initialize (); + if (sc != RTEMS_SUCCESSFUL) + return sc; + + sc = rtems_nvdisk_crc16_gen_factors (0x8408); + if (sc != RTEMS_SUCCESSFUL) + return sc; + + rtems_nvdisks = calloc (rtems_nvdisk_configuration_size, + sizeof (rtems_nvdisk)); + + if (!rtems_nvdisks) + return RTEMS_NO_MEMORY; + + for (minor = 0; minor < rtems_nvdisk_configuration_size; minor++, c++) + { + char name[] = RTEMS_NVDISK_DEVICE_BASE_NAME "a"; + dev_t dev = rtems_filesystem_make_dev_t (major, minor); + uint32_t device; + uint32_t blocks = 0; + + nvd = &rtems_nvdisks[minor]; + + name [sizeof(RTEMS_NVDISK_DEVICE_BASE_NAME)] += minor; + + nvd->major = major; + nvd->minor = minor; + nvd->flags = c->flags; + nvd->block_size = c->block_size; + nvd->info_level = c->info_level; + + nvd->devices = calloc (c->device_count, sizeof (rtems_nvdisk_device_ctl)); + if (!nvd->devices) + return RTEMS_NO_MEMORY; + + for (device = 0; device < c->device_count; device++) + { + rtems_nvdisk_device_ctl* dc = &nvd->devices[device]; + + dc->device = device; + dc->pages = rtems_nvdisk_pages_in_device (nvd, &c->devices[device]); + dc->pages_desc = rtems_nvdisk_page_desc_pages (nvd, &c->devices[device]); + dc->block_base = blocks; + + blocks += dc->pages - dc->pages_desc; + + dc->descriptor = &c->devices[device]; + } + + nvd->block_count = blocks; + nvd->device_count = c->device_count; + + sc = rtems_disk_create_phys(dev, c->block_size, blocks, + rtems_nvdisk_ioctl, NULL, name); + if (sc != RTEMS_SUCCESSFUL) + { + rtems_nvdisk_error ("disk create phy failed"); + return sc; + } + + sc = rtems_semaphore_create (rtems_build_name ('N', 'V', 'D', 'K'), 1, + RTEMS_PRIORITY | RTEMS_BINARY_SEMAPHORE | + RTEMS_INHERIT_PRIORITY, 0, &nvd->lock); + if (sc != RTEMS_SUCCESSFUL) + { + rtems_nvdisk_error ("disk lock create failed"); + return sc; + } + } + + rtems_nvdisk_count = rtems_nvdisk_configuration_size; + + return RTEMS_SUCCESSFUL; +} diff --git a/cpukit/libblock/src/ramdisk-config.c b/cpukit/libblock/src/ramdisk-config.c new file mode 100644 index 0000000000..255240668b --- /dev/null +++ b/cpukit/libblock/src/ramdisk-config.c @@ -0,0 +1,90 @@ +/** + * @file + * + * @ingroup rtems_ramdisk + * + * @brief RAM disk block device implementation. + */ + +/* + * Copyright (C) 2001 OKTET Ltd., St.-Petersburg, Russia + * Author: Victor V. Vengerov <vvv@oktet.ru> + * + * @(#) $Id$ + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include <stdlib.h> + +#include <rtems.h> +#include <rtems/libio.h> +#include <rtems/ramdisk.h> + +rtems_device_driver +ramdisk_initialize( + rtems_device_major_number major, + rtems_device_minor_number minor __attribute__((unused)), + void *arg __attribute__((unused))) +{ + rtems_device_minor_number i; + rtems_ramdisk_config *c = rtems_ramdisk_configuration; + struct ramdisk *r; + rtems_status_code rc; + + rc = rtems_disk_io_initialize(); + if (rc != RTEMS_SUCCESSFUL) + return rc; + + /* + * Coverity Id 27 notes that this calloc() is a resource leak. + * + * This is allocating memory for a RAM disk which will persist for + * the life of the system. RTEMS has no "de-initialize" driver call + * so there is no corresponding free(r). Coverity is correct that + * it is never freed but this is not a problem. + */ + r = calloc(rtems_ramdisk_configuration_size, sizeof(struct ramdisk)); + r->trace = false; + for (i = 0; i < rtems_ramdisk_configuration_size; i++, c++, r++) + { + dev_t dev = rtems_filesystem_make_dev_t(major, i); + char name [] = RAMDISK_DEVICE_BASE_NAME "a"; + name [sizeof(RAMDISK_DEVICE_BASE_NAME)] += i; + r->block_size = c->block_size; + r->block_num = c->block_num; + if (c->location == NULL) + { + r->malloced = true; + r->area = malloc(r->block_size * r->block_num); + if (r->area == NULL) /* No enough memory for this disk */ + { + r->initialized = false; + continue; + } + else + { + r->initialized = true; + } + } + else + { + r->malloced = false; + r->initialized = true; + r->area = c->location; + } + rc = rtems_disk_create_phys(dev, c->block_size, c->block_num, + ramdisk_ioctl, r, name); + if (rc != RTEMS_SUCCESSFUL) + { + if (r->malloced) + { + free(r->area); + } + r->initialized = false; + } + } + return RTEMS_SUCCESSFUL; +} diff --git a/cpukit/libblock/src/ramdisk-driver.c b/cpukit/libblock/src/ramdisk-driver.c new file mode 100644 index 0000000000..2bc8d27962 --- /dev/null +++ b/cpukit/libblock/src/ramdisk-driver.c @@ -0,0 +1,134 @@ +/** + * @file + * + * @ingroup rtems_ramdisk + * + * @brief RAM disk block device implementation. + */ + +/* + * Copyright (C) 2001 OKTET Ltd., St.-Petersburg, Russia + * Author: Victor V. Vengerov <vvv@oktet.ru> + * + * @(#) $Id$ + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +/* FIXME: How to set this define? */ +#if !defined(RTEMS_RAMDISK_TRACE) + #define RTEMS_RAMDISK_TRACE 0 +#endif + +#include <stdlib.h> +#include <errno.h> +#include <string.h> + +#if RTEMS_RAMDISK_TRACE + #include <stdio.h> +#endif + +#include <rtems.h> +#include <rtems/ramdisk.h> + +#if RTEMS_RAMDISK_TRACE + static void + rtems_ramdisk_printf (const ramdisk *rd, const char *format, ...) + { + if (rd->trace) + { + va_list args; + va_start (args, format); + printf ("ramdisk:"); + vprintf (format, args); + printf ("\n"); + } + } +#endif + +static int +ramdisk_read(struct ramdisk *rd, rtems_blkdev_request *req) +{ + uint8_t *from = rd->area; + uint32_t i; + rtems_blkdev_sg_buffer *sg; + +#if RTEMS_RAMDISK_TRACE + rtems_ramdisk_printf (rd, "ramdisk read: start=%d, blocks=%d", + req->bufs[0].block, req->bufnum); +#endif + + for (i = 0, sg = req->bufs; i < req->bufnum; i++, sg++) + { +#if RTEMS_RAMDISK_TRACE + rtems_ramdisk_printf (rd, "ramdisk read: buf=%d block=%d length=%d off=%d addr=%p", + i, sg->block, sg->length, sg->block * rd->block_size, + from + (sg->block * rd->block_size)); +#endif + memcpy(sg->buffer, from + (sg->block * rd->block_size), sg->length); + } + req->status = RTEMS_SUCCESSFUL; + req->req_done(req->done_arg, RTEMS_SUCCESSFUL); + return 0; +} + +static int +ramdisk_write(struct ramdisk *rd, rtems_blkdev_request *req) +{ + uint8_t *to = rd->area; + uint32_t i; + rtems_blkdev_sg_buffer *sg; + +#if RTEMS_RAMDISK_TRACE + rtems_ramdisk_printf (rd, "ramdisk write: start=%d, blocks=%d", + req->bufs[0].block, req->bufnum); +#endif + for (i = 0, sg = req->bufs; i < req->bufnum; i++, sg++) + { +#if RTEMS_RAMDISK_TRACE + rtems_ramdisk_printf (rd, "ramdisk write: buf=%d block=%d length=%d off=%d addr=%p", + i, sg->block, sg->length, sg->block * rd->block_size, + to + (sg->block * rd->block_size)); +#endif + memcpy(to + (sg->block * rd->block_size), sg->buffer, sg->length); + } + req->status = RTEMS_SUCCESSFUL; + req->req_done(req->done_arg, RTEMS_SUCCESSFUL); + return 0; +} + +int +ramdisk_ioctl(rtems_disk_device *dd, uint32_t req, void *argp) +{ + switch (req) + { + case RTEMS_BLKIO_REQUEST: + { + rtems_blkdev_request *r = argp; + struct ramdisk *rd = rtems_disk_get_driver_data(dd); + + switch (r->req) + { + case RTEMS_BLKDEV_REQ_READ: + return ramdisk_read(rd, r); + + case RTEMS_BLKDEV_REQ_WRITE: + return ramdisk_write(rd, r); + + default: + errno = EINVAL; + return -1; + } + break; + } + + default: + return rtems_blkdev_ioctl (dd, req, argp); + break; + } + + errno = EINVAL; + return -1; +} diff --git a/cpukit/libblock/src/ramdisk-init.c b/cpukit/libblock/src/ramdisk-init.c new file mode 100644 index 0000000000..fb8def0d83 --- /dev/null +++ b/cpukit/libblock/src/ramdisk-init.c @@ -0,0 +1,126 @@ +/** + * @file + * + * @ingroup rtems_ramdisk + * + * @brief RAM disk block device implementation. + */ + +/* + * Copyright (c) 2009 + * embedded brains GmbH + * Obere Lagerstr. 30 + * D-82178 Puchheim + * Germany + * <rtems@embedded-brains.de> + * + * 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$ + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include <stdlib.h> + +#include <rtems.h> +#include <rtems/ramdisk.h> + +const rtems_driver_address_table ramdisk_ops = { + .initialization_entry = NULL, + RTEMS_GENERIC_BLOCK_DEVICE_DRIVER_ENTRIES +}; + +ramdisk *ramdisk_allocate( + void *area_begin, + uint32_t block_size, + rtems_blkdev_bnum block_count, + bool trace +) +{ + struct ramdisk *rd = malloc(sizeof(struct ramdisk)); + + if (rd == NULL) { + return NULL; + } + + if (area_begin == NULL) { + area_begin = calloc(block_count, block_size); + if (area_begin == NULL) { + free(rd); + + return NULL; + } + rd->malloced = true; + } else { + rd->malloced = false; + } + rd->block_size = block_size; + rd->block_num = block_count; + rd->area = area_begin; + rd->trace = trace; + rd->initialized = true; + + return rd; +} + +void ramdisk_free(ramdisk *rd) +{ + if (rd != NULL) { + if (rd->malloced) { + free(rd->area); + } + free(rd); + } +} + +rtems_status_code ramdisk_register( + uint32_t block_size, + rtems_blkdev_bnum block_count, + bool trace, + const char *disk, + dev_t *dev_ptr +) +{ + rtems_status_code sc = RTEMS_SUCCESSFUL; + rtems_device_major_number major = 0; + ramdisk *rd = NULL; + dev_t dev = 0; + + sc = rtems_io_register_driver(0, &ramdisk_ops, &major); + if (sc != RTEMS_SUCCESSFUL) { + return RTEMS_UNSATISFIED; + } + + rd = ramdisk_allocate(NULL, block_size, block_count, trace); + if (rd == NULL) { + rtems_io_unregister_driver(major); + + return RTEMS_UNSATISFIED; + } + + dev = rtems_filesystem_make_dev_t(major, 0); + + sc = rtems_disk_create_phys( + dev, + block_size, + block_count, + ramdisk_ioctl, + rd, + disk + ); + if (sc != RTEMS_SUCCESSFUL) { + ramdisk_free(rd); + rtems_io_unregister_driver(major); + + return RTEMS_UNSATISFIED; + } + + *dev_ptr = dev; + + return RTEMS_SUCCESSFUL; +} diff --git a/cpukit/libblock/src/show_bdbuf.c b/cpukit/libblock/src/show_bdbuf.c new file mode 100644 index 0000000000..74ff9922aa --- /dev/null +++ b/cpukit/libblock/src/show_bdbuf.c @@ -0,0 +1,909 @@ +/*===============================================================*\ +| Project: RTEMS bdbuf inspector | ++-----------------------------------------------------------------+ +| File: show_bdbuf.c ++-----------------------------------------------------------------+ +| Copyright (c) 2005 | +| Embedded Brains GmbH | +| Obere Lagerstr. 30 | +| D-82178 Puchheim | +| Germany | +| rtems@embedded-brains.de | ++-----------------------------------------------------------------+ +| 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. | +| | ++-----------------------------------------------------------------+ +| this file contains functions to enable the monitor | +| to show bdbuf information | +| | +| XXX!!! ATTETION!!! XXX!!! | +| | +| This module inspects the bdbuf data structures, | +| assuming they are static, but in fact they are used very | +| dynamically. Therefore the results show MAY BE INCORRECT in | +| some cases. And, to cure this a bit, this module may block | +| preemption for a rather long time and therefore it may | +| BREAK THE REALTIME BEHAVIOUR OF YOUR SYSTEM (when in use) | ++-----------------------------------------------------------------+ +| date history ID | +| ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | +| 26.09.06 creation doe | +|*****************************************************************| +\*===============================================================*/ + +#if HAVE_CONFIG_H +#include "config.h" +#endif + +#include <rtems.h> +#include <rtems/monitor.h> +#include <rtems/bdbuf.h> +#include <string.h> +#include <ctype.h> +#include <stdio.h> +#include <rtems/libio.h> +#include <inttypes.h> + +typedef struct { + bool bdbuf_modified; + bool bdbuf_in_progress; + bool bdbuf_actual; + bool bdbuf_used; + bool bdbuf_all; + rtems_bdpool_id pool_id; +} show_bdbuf_filter_t; + +typedef struct { + bool show_all; + bool show_node_chain; + bool show_dev; + bool show_blocknum; + bool show_error; + bool show_state; + bool show_use_count; + bool show_pool_id; + bool show_sema; +} show_bdbuf_selector_t; + +typedef enum {bdbuf_chain_ident_none, + bdbuf_chain_ident_free, + bdbuf_chain_ident_lru, + bdbuf_chain_ident_mod} bdbuf_chain_identifier_t; + +typedef struct { + rtems_bdpool_id pool_id; + int index; + bdbuf_chain_identifier_t in_chain; + dev_t dev; + blkdev_bnum blknum; + rtems_status_code status; + int error; + bool modified; + bool in_progress; + bool actual; + int use_count; + const CORE_mutex_Control *sema; +} show_bdbuf_bdbuf_info_t; + +typedef rtems_mode preemption_key_t; +#define DISABLE_PREEMPTION(key) \ + do { \ + rtems_task_mode(RTEMS_NO_PREEMPT, RTEMS_PREEMPT_MASK, &(key)); \ + } while (0) + +#define ENABLE_PREEMPTION(key) \ + do { \ + rtems_mode temp; \ + rtems_task_mode((key), RTEMS_PREEMPT_MASK, &temp); \ + } while (0) + +/*=========================================================================*\ +| Function: | +\*-------------------------------------------------------------------------*/ +rtems_status_code rtems_bdbuf_show_follow_chain_node_to_head +( +/*-------------------------------------------------------------------------*\ +| Purpose: | +| follow a given chain to its head | +| XXX: this is executed with preemption disabled | ++---------------------------------------------------------------------------+ +| Input Parameters: | +\*-------------------------------------------------------------------------*/ + const Chain_Node *the_node, /* input: node to track to its head */ + Chain_Control **the_head /* storage for pointer to chain head */ +) +/*-------------------------------------------------------------------------*\ +| Return Value: | +| rtems_status_code | +\*=========================================================================*/ +{ + rtems_status_code rc = RTEMS_SUCCESSFUL; + preemption_key_t preempt_key; + bool preempt_disabled = false; + /* + * disable preemption + */ + if (rc == RTEMS_SUCCESSFUL) { + DISABLE_PREEMPTION(preempt_key); + } + /* + * follow node to its head + * XXX: this is highly dependent on the chain implementation + * in score/src/chain.c and friends + */ + while (the_node->previous != NULL) { + the_node = the_node->previous; + } + /* + * reenable preemption, if disabled + */ + if (preempt_disabled) { + ENABLE_PREEMPTION(preempt_key); + } + /* + * XXX: this depends n the chain implementation in + * score/include/rtems/score/chain.h: + * Chain_Control is overlayed by two Cohain_Nodes + */ + *the_head = (Chain_Control *)the_node; + + return rc; +} + +/*=========================================================================*\ +| Function: | +\*-------------------------------------------------------------------------*/ +rtems_status_code rtems_bdbuf_show_determine_chain_of_bdbuf +( +/*-------------------------------------------------------------------------*\ +| Purpose: | +| find out, which chain this bdbuf is linked in | ++---------------------------------------------------------------------------+ +| Input Parameters: | +\*-------------------------------------------------------------------------*/ + const bdbuf_buffer *the_bdbuf, /* this is the bdbuf structure */ + const bdbuf_pool *curr_pool, /* the pool this buffer belongs to */ + bdbuf_chain_identifier_t *chn_ident /* result: identifier for chain */ +) +/*-------------------------------------------------------------------------*\ +| Return Value: | +| rtems_status_code | +\*=========================================================================*/ +{ + rtems_status_code rc = RTEMS_SUCCESSFUL; + Chain_Control *the_chain_control; + + + *chn_ident = bdbuf_chain_ident_none; + if (rc == RTEMS_SUCCESSFUL) { + rc = rtems_bdbuf_show_follow_chain_node_to_head(&(the_bdbuf->link), + &(the_chain_control)); + } + if (rc == RTEMS_SUCCESSFUL) { + if (the_chain_control == &(curr_pool->free)) { + *chn_ident = bdbuf_chain_ident_free; + } + else if (the_chain_control == &(curr_pool->lru)) { + *chn_ident = bdbuf_chain_ident_lru; + } + else if (the_chain_control == &(rtems_bdbuf_ctx.mod)) { + *chn_ident = bdbuf_chain_ident_mod; + } + } + return rc; +} + +/*=========================================================================*\ +| Function: | +\*-------------------------------------------------------------------------*/ +rtems_status_code rtems_bdbuf_show_getargs +( +/*-------------------------------------------------------------------------*\ +| Purpose: | +| analyze cmd arguments | ++---------------------------------------------------------------------------+ +| Input Parameters: | +\*-------------------------------------------------------------------------*/ + int argc, + char **argv, + show_bdbuf_filter_t *filter, + show_bdbuf_selector_t *selector +) +/*-------------------------------------------------------------------------*\ +| Return Value: | +| rtems_status_code | +\*=========================================================================*/ +{ + rtems_status_code sc = RTEMS_SUCCESSFUL; + int arg_error = 0; + int i; + char *tmp_ptr; + int nm_argc = 0; + /* + * set filter and selector to default + */ + memset(filter,0,sizeof(*filter)); + filter->bdbuf_all = true; + memset(selector,0,sizeof(*selector)); + selector->show_all = true; + + /* + * scan arguments + */ + for (i = 1; + (i < argc) && (arg_error == 0); + i++) { + if (argv[i][0] == '-') { + /* + * modifier arguments + */ + switch(tolower(argv[i][1])) { + /* + * selection, which bdbufs to show + */ + case 'm': /* only show bdbufs modified */ + filter->bdbuf_modified = true ; + filter->bdbuf_all = false; + break; + case 'i': /* only show bdbufs in progress*/ + filter->bdbuf_in_progress = true ; + filter->bdbuf_all = false; + break; + case 'v': /* only show bdbufs, which have valid data*/ + filter->bdbuf_actual = true ; + filter->bdbuf_all = false; + break; + case 'u': /* only show bdbufs, which are in use */ + filter->bdbuf_used = true ; + filter->bdbuf_all = false; + break; + case 'p': /* only show bdbufs, which belong to pool <n> */ + filter->pool_id = strtol(argv[i]+2,&tmp_ptr,0); + if (tmp_ptr == argv[i]+2) { /* no conversion performed... */ + arg_error = i; + } + filter->bdbuf_all = false; + break; + /* + * selection, what fields to show + */ + case 'n': /* show bdbuf node_chain */ + selector->show_node_chain = true ; + selector->show_all = false; + break; + case 'd': /* show device */ + selector->show_dev = true ; + selector->show_all = false; + break; + case 'b': /* show blocknum */ + selector->show_blocknum = true ; + selector->show_all = false; + break; + case 'e': /* show bdbuf error status */ + selector->show_error = true ; + selector->show_all = false; + break; + case 's': /* show bdbuf state */ + selector->show_state = true ; + selector->show_all = false; + break; + case 'c': /* show bdbuf use count */ + selector->show_use_count = true ; + selector->show_all = false; + break; + case 'l': /* show bdbuf pool id */ + selector->show_pool_id = true ; + selector->show_all = false; + break; + case 't': /* show bdbuf transfer sema */ + selector->show_sema = true ; + break; + default: + arg_error = i; + break; + } + } + else { + /* + * non-modifier arguments + */ + switch(++nm_argc) { + default: /* no further arguments defined */ + arg_error = i; + break; + } + } + } + if (arg_error) { + printf("%s: unknown argument %s\n",argv[0],argv[arg_error]); + sc = RTEMS_NOT_DEFINED; + } + return sc; +} + +/*=========================================================================*\ +| Function: | +\*-------------------------------------------------------------------------*/ +rtems_status_code rtems_bdbuf_show_get_bufpool +( +/*-------------------------------------------------------------------------*\ +| Purpose: | +| get buffer pool information | +| XXX: this should be coupled closer to the bdbuf.c module | ++---------------------------------------------------------------------------+ +| Input Parameters: | +\*-------------------------------------------------------------------------*/ + struct bdbuf_pool **pool_base_pptr, + int *pool_cnt_ptr +) +/*-------------------------------------------------------------------------*\ +| Return Value: | +| rtems_status_code | +\*=========================================================================*/ +{ + rtems_status_code rc = RTEMS_SUCCESSFUL; +#if 0 + rtems_status_code pool_rc = RTEMS_SUCCESSFUL; + struct bdbuf_pool *curr_pool,*pool_base, *pool_top; + int pool_cnt; + int pool_probe_size; + /* + * get first buffer pool + * XXX: this is highly dependent on how pools are defined + * and maintained in bdbuf.c + */ + if (rc == RTEMS_SUCCESSFUL) { + /* + * try all possible pool sizes, get highest/lowest pool address + */ + pool_base = NULL; + pool_top = NULL; + curr_pool = NULL; + for (pool_probe_size = 1; + pool_probe_size < (INT_MAX>>1) && (pool_rc == RTEMS_SUCCESSFUL); + pool_probe_size <<= 1) { + pool_rc = rtems_bdbuf_find_pool(pool_probe_size,&curr_pool); + if (pool_rc == RTEMS_SUCCESSFUL) { + if (pool_base > curr_pool) { + pool_base = curr_pool; + } + if (pool_top < curr_pool) { + pool_top = curr_pool; + } + } + } + if (pool_base == NULL) { + rc = RTEMS_UNSATISFIED; + } + else { + pool_cnt = (pool_top - pool_base) + 1; + } + } + if (rc == RTEMS_SUCCESSFUL) { + *pool_base_pptr = pool_base; + *pool_cnt_ptr = pool_cnt; + } +#else + if (rc == RTEMS_SUCCESSFUL) { + *pool_base_pptr = rtems_bdbuf_ctx.pool; + *pool_cnt_ptr = rtems_bdbuf_ctx.npools; + } +#endif + return rc; +} + +/*=========================================================================*\ +| Function: | +\*-------------------------------------------------------------------------*/ +rtems_status_code rtems_bdbuf_show_pool_header +( +/*-------------------------------------------------------------------------*\ +| Purpose: | +| print buffer pool information | ++---------------------------------------------------------------------------+ +| Input Parameters: | +\*-------------------------------------------------------------------------*/ + int pool_idx, + bdbuf_pool *pool_ptr +) +/*-------------------------------------------------------------------------*\ +| Return Value: | +| rtems_status_code | +\*=========================================================================*/ +{ + + rtems_status_code rc = RTEMS_SUCCESSFUL; + + if (rc == RTEMS_SUCCESSFUL) { + printf("------------------------------------------------------------------------------\n"); + printf(" pool #%03d: blksize=%5u nblks=%5u buf_mem=0x%08" PRIxPTR " bdbuf_mem=0x%08" PRIxPTR "\n", + pool_idx, + pool_ptr->blksize, + pool_ptr->nblks, + (intptr_t) pool_ptr->mallocd_bufs, + (intptr_t) pool_ptr->bdbufs); + printf("------------------------------------------------------------------------------\n"); + } + return rc; +} + +/*=========================================================================*\ +| Function: | +\*-------------------------------------------------------------------------*/ +rtems_status_code rtems_show_bdbuf_get_bdbuf_info +( +/*-------------------------------------------------------------------------*\ +| Purpose: | +| get buffer pool information | +| XXX: this should be coupled closer to the bdbuf.c module | ++---------------------------------------------------------------------------+ +| Input Parameters: | +\*-------------------------------------------------------------------------*/ + const bdbuf_buffer *the_bdbuf, /* this is the bdbuf structure */ + int bdbuf_idx, /* index of bdbuf */ + const bdbuf_pool *curr_pool, /* the pool this buffer belongs to */ + show_bdbuf_bdbuf_info_t *bdbuf_info /* struct to store info of bdbuf */ +) +/*-------------------------------------------------------------------------*\ +| Return Value: | +| rtems_status_code | +\*=========================================================================*/ +{ + + rtems_status_code rc = RTEMS_SUCCESSFUL; + + /* + * determine the chain we are in + */ + if (rc == RTEMS_SUCCESSFUL) { + rc = rtems_bdbuf_show_determine_chain_of_bdbuf(the_bdbuf,curr_pool, + &(bdbuf_info->in_chain)); + if (rc != RTEMS_SUCCESSFUL) { + bdbuf_info->in_chain = bdbuf_chain_ident_none; + rc = RTEMS_SUCCESSFUL; + } + } + + if (rc == RTEMS_SUCCESSFUL) { + bdbuf_info->index = bdbuf_idx; + bdbuf_info->dev = the_bdbuf->dev; + bdbuf_info->blknum = the_bdbuf->block; + bdbuf_info->status = the_bdbuf->status; + bdbuf_info->error = the_bdbuf->error; + bdbuf_info->modified = the_bdbuf->modified; + bdbuf_info->in_progress = the_bdbuf->in_progress; + bdbuf_info->actual = the_bdbuf->actual; + bdbuf_info->use_count = the_bdbuf->use_count; + bdbuf_info->sema = &(the_bdbuf->transfer_sema); + bdbuf_info->pool_id = the_bdbuf->pool; + } + return rc; +} + +/*=========================================================================*\ +| Function: | +\*-------------------------------------------------------------------------*/ +rtems_status_code rtems_show_bdbuf_match_filter +( +/*-------------------------------------------------------------------------*\ +| Purpose: | +| match bdbuf info with given filter | ++---------------------------------------------------------------------------+ +| Input Parameters: | +\*-------------------------------------------------------------------------*/ + const show_bdbuf_bdbuf_info_t *bdbuf_info, /* struct to store info of bdbuf */ + const show_bdbuf_filter_t *filter, + bool *is_match +) +/*-------------------------------------------------------------------------*\ +| Return Value: | +| rtems_status_code | +\*=========================================================================*/ +{ + + rtems_status_code rc = RTEMS_SUCCESSFUL; + bool unmatch = false; + + if (rc == RTEMS_SUCCESSFUL) { + if (filter->bdbuf_all) { + unmatch = false; + } + else { + unmatch = ((filter->bdbuf_modified && !bdbuf_info->modified) || + (filter->bdbuf_in_progress && !bdbuf_info->in_progress) || + (filter->bdbuf_actual && !bdbuf_info->actual) || + (filter->bdbuf_used && !(bdbuf_info->use_count > 0))); + + } + *is_match = !unmatch; + } + return rc; +} + +/*=========================================================================*\ +| Function: | +\*-------------------------------------------------------------------------*/ +rtems_status_code rtems_show_bdbuf_print_wait_chain +( +/*-------------------------------------------------------------------------*\ +| Purpose: | +| list tasks waiting in "transfer_sema" chain | ++---------------------------------------------------------------------------+ +| Input Parameters: | +\*-------------------------------------------------------------------------*/ + bdbuf_buffer *the_bdbuf /* this is the bdbuf structure */ +) +/*-------------------------------------------------------------------------*\ +| Return Value: | +| rtems_status_code | +\*=========================================================================*/ +{ + rtems_status_code rc = RTEMS_SUCCESSFUL; + Chain_Control *the_chain_head; + const Chain_Node *the_chain_node; + int thread_cnt = 0; + const Thread_Control *the_thread; + Objects_Id thread_id; + Objects_Name thread_name; + uint32_t thread_name_nonstring; + /* + * get head of (fifo) wait chain + */ + if (rc == RTEMS_SUCCESSFUL) { + the_chain_head = &(the_bdbuf->transfer_sema.Wait_queue.Queues.Fifo); + the_chain_node = _Chain_First(the_chain_head); + } + /* + * walk through thread chain + */ + while ((rc == RTEMS_SUCCESSFUL) && + (the_chain_node != _Chain_Tail( the_chain_head ))) { + thread_cnt++; + the_thread = (const Thread_Control *)the_chain_node; + + thread_id = the_thread->Object.id; + thread_name = the_thread->Object.name; + thread_name_nonstring = (uint32_t)thread_name.name_u32; + printf("%20s %3d (0x%08" PRIx32 ") %c%c%c%c\n", + ((thread_cnt == 1) ? "Threads waiting:" : ""), + thread_cnt,thread_id, + (char)((thread_name_nonstring >> 24) & 0xff), + (char)((thread_name_nonstring >> 16) & 0xff), + (char)((thread_name_nonstring >> 8) & 0xff), + (char)((thread_name_nonstring >> 0) & 0xff)); + + the_chain_node = the_chain_node->next; + } + + return rc; +} + +/*=========================================================================*\ +| Function: | +\*-------------------------------------------------------------------------*/ +rtems_status_code rtems_show_bdbuf_print +( +/*-------------------------------------------------------------------------*\ +| Purpose: | +| print requested bdbuffer information | ++---------------------------------------------------------------------------+ +| Input Parameters: | +\*-------------------------------------------------------------------------*/ + const show_bdbuf_bdbuf_info_t *bdbuf_info, /* info of bdbuf */ + show_bdbuf_selector_t * selector, /* selector, what to show */ + bool print_header /* true: print header, not info */ +) +/*-------------------------------------------------------------------------*\ +| Return Value: | +| rtems_status_code | +\*=========================================================================*/ +{ + + rtems_status_code rc = RTEMS_SUCCESSFUL; + + /* + * 6 chars: print index of buffer + */ + if (rc == RTEMS_SUCCESSFUL) { + if (print_header) { + printf("INDEX "); + } + else { + printf("%5u ",bdbuf_info->index); + } + } + /* + * 3 chars: print info about the pool id of this buffer + */ + if ((rc == RTEMS_SUCCESSFUL) && + ((selector->show_all) || + (selector->show_use_count))) { + if (print_header) { + printf("PL "); + } + else { + printf("%2u ",bdbuf_info->pool_id); + } + } + + /* + * 4 chars: print info about chain (lru/free/mod) of this buffer + */ + if ((rc == RTEMS_SUCCESSFUL) && + ((selector->show_all) || + (selector->show_node_chain))) { + if (print_header) { + printf("CHN "); + } + else { + printf("%3s ", + ((bdbuf_info->in_chain == bdbuf_chain_ident_free) ? "FRE" + : (bdbuf_info->in_chain == bdbuf_chain_ident_lru) ? "LRU" + : (bdbuf_info->in_chain == bdbuf_chain_ident_mod) ? "MOD" + : "???")); + } + } + + /* + * 7 chars: print info about device of this buffer + */ + if ((rc == RTEMS_SUCCESSFUL) && + ((selector->show_all) || + (selector->show_dev))) { + if (print_header) { + printf("DEVICE "); + } + else { + printf("%3" PRIu32 "%2" PRIu32, + ((bdbuf_info->dev == -1) + ? 0 : rtems_filesystem_dev_major_t(bdbuf_info->dev)), + ((bdbuf_info->dev == -1) + ? 0 : rtems_filesystem_dev_minor_t(bdbuf_info->dev))); + } + } + + /* + * 7 chars: print info about block number of this buffer + */ + if ((rc == RTEMS_SUCCESSFUL) && + ((selector->show_all) || + (selector->show_blocknum))) { + if (print_header) { + printf("BLOCK "); + } + else { + printf("%6" PRIu32,bdbuf_info->blknum); + } + } + + /* + * 4 chars: print info about use count of this buffer + */ + if ((rc == RTEMS_SUCCESSFUL) && + ((selector->show_all) || + (selector->show_use_count))) { + if (print_header) { + printf("USE "); + } + else { + printf("%3u ",bdbuf_info->use_count); + } + } + + /* + * 4 chars: print info about state of this buffer + */ + if ((rc == RTEMS_SUCCESSFUL) && + ((selector->show_all) || + (selector->show_state))) { + if (print_header) { + printf("STA "); + } + else { + printf("%c%c%c ", + (bdbuf_info->modified ? 'M' : '.'), + (bdbuf_info->in_progress ? 'P' : '.'), + (bdbuf_info->actual ? 'A' : '.')); + } + } + + /* + * 42 chars: print info about error of this buffer + */ + if ((rc == RTEMS_SUCCESSFUL) && + ((selector->show_all) || + (selector->show_error))) { + if (print_header) { + printf("%20s:%-10s ","RTEMS STATUS","ERRNO"); + } + else { + printf("%20s:%-10s ", + ((bdbuf_info->status == RTEMS_SUCCESSFUL) + ? "SUCCESSFUL" : rtems_status_text(bdbuf_info->status)), + ((bdbuf_info->status == RTEMS_SUCCESSFUL) + ? "" : strerror(bdbuf_info->error))); + } + } + /* + * FIXME: add info about waiting chain + */ + printf("\n"); + return rc; +} + +/*=========================================================================*\ +| Function: | +\*-------------------------------------------------------------------------*/ +void rtems_bdbuf_show_fnc +( +/*-------------------------------------------------------------------------*\ +| Purpose: | +| list all bdbufs with their content | ++---------------------------------------------------------------------------+ +| Input Parameters: | +\*-------------------------------------------------------------------------*/ + int argc, + char **argv, + rtems_monitor_command_arg_t* command_arg, + bool verbose +) +/*-------------------------------------------------------------------------*\ +| Return Value: | +| rtems_status_code | +\*=========================================================================*/ +{ + rtems_status_code rc = RTEMS_SUCCESSFUL; + show_bdbuf_filter_t filter; + show_bdbuf_selector_t selector; + show_bdbuf_bdbuf_info_t bdbuf_info; + + bdbuf_pool *curr_pool,*pool_base; + int pool_cnt,pool_idx; + int bdbuf_idx; + bool bdbuf_matches; + int matched_cnt,un_matched_cnt; + + /* + * analyze command line options + */ + if (rc == RTEMS_SUCCESSFUL) { + rc = rtems_bdbuf_show_getargs (argc,argv, + &filter,&selector); + } + + /* + * get buffer pool information + */ + if (rc == RTEMS_SUCCESSFUL) { + rc = rtems_bdbuf_show_get_bufpool(&pool_base,&pool_cnt); + if (rc != RTEMS_SUCCESSFUL) { + printf("%s: ERROR: no buffer pool found\n",argv[0]); + } + } + /* + * for all or selected buffer pool(s) + */ + for (pool_idx = 0; + (rc == RTEMS_SUCCESSFUL) && (pool_idx < pool_cnt); + pool_idx++) { + if ((filter.pool_id < 0) || + (filter.pool_id == pool_idx)) { + curr_pool = pool_base + pool_idx; + /* + * print pool header + */ + if (rc == RTEMS_SUCCESSFUL) { + rc = rtems_bdbuf_show_pool_header(pool_idx,curr_pool); + } + if (rc == RTEMS_SUCCESSFUL) { + matched_cnt = 0; + un_matched_cnt = 0; + /* + * print header for bdbuf + */ + if (rc == RTEMS_SUCCESSFUL) { + rc = rtems_show_bdbuf_print(NULL,&selector, + true); + } + /* + * for all bdbufs in this pool + */ + for (bdbuf_idx = 0; + ((rc == RTEMS_SUCCESSFUL) && + (bdbuf_idx < curr_pool->nblks)); + bdbuf_idx++) { + /* + * get infos about bdbuf + */ + if (rc == RTEMS_SUCCESSFUL) { + rc = rtems_show_bdbuf_get_bdbuf_info + (&(curr_pool->bdbufs[bdbuf_idx]), + bdbuf_idx, + curr_pool, + &bdbuf_info); + } + /* + * check, if bdbuf matches selection criteria + */ + if (rc == RTEMS_SUCCESSFUL) { + rc = rtems_show_bdbuf_match_filter(&bdbuf_info,&filter, + &bdbuf_matches); + } + /* + * print info about bdbuf + */ + if (rc == RTEMS_SUCCESSFUL) { + if (bdbuf_matches) { + rc = rtems_show_bdbuf_print(&bdbuf_info,&selector, + false); + if ((rc == RTEMS_SUCCESSFUL) && + selector.show_sema) { + rc = rtems_show_bdbuf_print_wait_chain(&(curr_pool->bdbufs[bdbuf_idx])); + } + matched_cnt++; + } + else { + un_matched_cnt++; + } + } + } + /* + * print match statistics and footer + */ + if (rc == RTEMS_SUCCESSFUL) { + printf("%d bdbufs printed, %d bdbufs suppressed\n", + matched_cnt,un_matched_cnt); + } + } + } + } +} + +static rtems_monitor_command_entry_t rtems_show_bdbuf_cmds[] = { + { + "bdbuf_show", + "usage: bdbuf_show\n", + 0, + rtems_bdbuf_show_fnc, + { 0 }, + 0 + } +}; + +#ifndef ARRAY_CNT +#define ARRAY_CNT(arr) (sizeof((arr))/sizeof((arr)[0])) +#endif + +/*=========================================================================*\ +| Function: | +\*-------------------------------------------------------------------------*/ +rtems_status_code rtems_bdbuf_show_init +( +/*-------------------------------------------------------------------------*\ +| Purpose: | +| add command(s) to monitor | ++---------------------------------------------------------------------------+ +| Input Parameters: | +\*-------------------------------------------------------------------------*/ + void /* none up to now */ +) +/*-------------------------------------------------------------------------*\ +| Return Value: | +| rtems_status_code | +\*=========================================================================*/ +{ + rtems_status_code sc = RTEMS_SUCCESSFUL; + int item; + + for (item = 0; + (sc == RTEMS_SUCCESSFUL) && (item < ARRAY_CNT(rtems_show_bdbuf_cmds)); + item++) { + if (0 == rtems_monitor_insert_cmd (&rtems_show_bdbuf_cmds[item])) { + sc = RTEMS_INVALID_NAME; + } + } + return sc; +} |