diff options
Diffstat (limited to 'cpukit/libblock/src/bdbuf.c')
-rw-r--r-- | cpukit/libblock/src/bdbuf.c | 1653 |
1 files changed, 1653 insertions, 0 deletions
diff --git a/cpukit/libblock/src/bdbuf.c b/cpukit/libblock/src/bdbuf.c new file mode 100644 index 0000000000..dd0cdbe83c --- /dev/null +++ b/cpukit/libblock/src/bdbuf.c @@ -0,0 +1,1653 @@ +/* + * 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> + * + * @(#) $Id$ + */ + +#if HAVE_CONFIG_H +#include "config.h" +#endif + +#define __RTEMS_VIOLATE_KERNEL_VISIBILITY__ +#include <rtems.h> +#include <limits.h> +#include <errno.h> +#include <assert.h> + +#include "rtems/bdbuf.h" + +/* Fatal errors: */ +#define BLKDEV_FATAL_ERROR(n) (((uint32_t)'B' << 24) | ((uint32_t)(n) & (uint32_t)0x00FFFFFF)) +#define BLKDEV_FATAL_BDBUF_CONSISTENCY BLKDEV_FATAL_ERROR(1) +#define BLKDEV_FATAL_BDBUF_SWAPOUT BLKDEV_FATAL_ERROR(2) + + +#define SWAPOUT_PRIORITY 15 +#define SWAPOUT_STACK_SIZE (RTEMS_MINIMUM_STACK_SIZE * 2) + +static rtems_task bdbuf_swapout_task(rtems_task_argument unused); + +/* + * The groups of the blocks with the same size are collected in the + * bd_pool. Note that a several of the buffer's groups with the + * same size can exists. + */ +typedef struct bdbuf_pool +{ + bdbuf_buffer *tree; /* Buffer descriptor lookup AVL tree root */ + + Chain_Control free; /* Free buffers list */ + Chain_Control lru; /* Last recently used list */ + + int blksize; /* The size of the blocks (in bytes) */ + int nblks; /* Number of blocks in this pool */ + rtems_id bufget_sema; /* Buffer obtain counting semaphore */ + void *mallocd_bufs; /* Pointer to the malloc'd buffer memory, + or NULL, if buffer memory provided in + buffer configuration */ + bdbuf_buffer *bdbufs; /* Pointer to table of buffer descriptors + allocated for this buffer pool. */ +} bdbuf_pool; + +/* Buffering layer context definition */ +struct bdbuf_context { + bdbuf_pool *pool; /* Table of buffer pools */ + int npools; /* Number of entries in pool table */ + + Chain_Control mod; /* Modified buffers list */ + rtems_id flush_sema; /* Buffer flush semaphore; counting + semaphore; incremented when buffer + flushed to the disk; decremented when + buffer modified */ + rtems_id swapout_task; /* Swapout task ID */ +}; + +/* Block device request with a single buffer provided */ +typedef struct blkdev_request1 { + blkdev_request req; + blkdev_sg_buffer sg[1]; +} blkdev_request1; + +/* The static context of buffering layer */ +static struct bdbuf_context bd_ctx; + +#define SAFE +#ifdef SAFE +typedef rtems_mode preemption_key; + +#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) + +#else + +typedef boolean preemption_key; + +#define DISABLE_PREEMPTION(key) \ + do { \ + (key) = _Thread_Executing->is_preemptible; \ + _Thread_Executing->is_preemptible = 0; \ + } while (0) + +#define ENABLE_PREEMPTION(key) \ + do { \ + _Thread_Executing->is_preemptible = (key); \ + if (_Thread_Evaluate_mode()) \ + _Thread_Dispatch(); \ + } while (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 AVL_MAX_HEIGHT +#define AVL_MAX_HEIGHT 32 +#endif + +/* + * avl_search -- + * Searches for the node with specified dev/block. + * + * PARAMETERS: + * root - pointer to the root node of the AVL-Tree. + * dev, block - search key + * + * RETURNS: + * NULL if node with specified dev/block not found + * non-NULL - pointer to the node with specified dev/block + */ +static bdbuf_buffer * +avl_search(bdbuf_buffer **root, dev_t dev, blkdev_bnum block) +{ + 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; +} + + +/* avl_search_for_sync -- + * Search in AVL tree for first modified buffer belongs to specified + * disk device. + * + * PARAMETERS: + * root - pointer to tree root + * dd - disk device descriptor + * + * RETURNS: + * Block buffer, or NULL if no modified blocks on specified device + * exists. + */ +static bdbuf_buffer * +avl_search_for_sync(bdbuf_buffer **root, disk_device *dd) +{ + dev_t dev = dd->phys_dev->dev; + blkdev_bnum block_start = dd->start; + blkdev_bnum block_end = dd->start + dd->size - 1; + + bdbuf_buffer *buf_stack[AVL_MAX_HEIGHT]; + bdbuf_buffer **buf_prev = buf_stack; + bdbuf_buffer *p = *root; + + while (p != NULL) + { + if ((p->dev < dev) || ((p->dev == dev) && (p->block < block_start))) + { + p = p->avl.right; + } + else if ((p->dev > dev) || ((p->dev == dev) && (p->block > block_end))) + { + p = p->avl.left; + } + else if (p->modified) + { + return p; + } + else + { + if (p->avl.right != NULL) + { + *buf_prev++ = p->avl.right; + } + p = p->avl.left; + } + + if ((p == NULL) && (buf_prev > buf_stack)) + { + p = *--buf_prev; + } + } + + return p; +} + + +/* + * avl_insert -- + * Inserts the specified node to the AVl-Tree. + * + * PARAMETERS: + * root - Pointer to pointer to the root node + * node - Pointer to the node to add. + * + * RETURNS: + * 0 - The node added successfully + * -1 - An error occured + */ +static int +avl_insert(bdbuf_buffer **root, bdbuf_buffer *node) +{ + dev_t dev = node->dev; + blkdev_bnum block = node->block; + + bdbuf_buffer *p = *root; + bdbuf_buffer *q = NULL; + bdbuf_buffer *p1, *p2; + bdbuf_buffer *buf_stack[AVL_MAX_HEIGHT]; + bdbuf_buffer **buf_prev = buf_stack; + + boolean 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; +} + + +/* avl_remove -- + * removes the node from the tree. + * + * PARAMETERS: + * root_addr - Pointer to pointer to the root node + * node - Pointer to the node to remove + * + * RETURNS: + * 0 - Item removed + * -1 - No such item found + */ +static int +avl_remove(bdbuf_buffer **root, const bdbuf_buffer *node) +{ + dev_t dev = node->dev; + blkdev_bnum block = node->block; + + bdbuf_buffer *p = *root; + bdbuf_buffer *q, *r, *s, *p1, *p2; + bdbuf_buffer *buf_stack[AVL_MAX_HEIGHT]; + bdbuf_buffer **buf_prev = buf_stack; + + boolean 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 + { + 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; +} + +/* bdbuf_initialize_pool -- + * Initialize single buffer pool. + * + * PARAMETERS: + * config - buffer pool configuration + * pool - pool number + * + * RETURNS: + * RTEMS_SUCCESSFUL, if buffer pool initialized successfully, or error + * code if error occured. + */ +static rtems_status_code +bdbuf_initialize_pool(rtems_bdbuf_config *config, int pool) +{ + bdbuf_pool *p = bd_ctx.pool + pool; + unsigned char *bufs; + bdbuf_buffer *b; + rtems_status_code rc; + int i; + + p->blksize = config->size; + p->nblks = config->num; + p->tree = NULL; + + Chain_Initialize_empty(&p->free); + Chain_Initialize_empty(&p->lru); + + /* Allocate memory for buffer descriptors */ + p->bdbufs = calloc(config->num, sizeof(bdbuf_buffer)); + if (p->bdbufs == NULL) + { + return RTEMS_NO_MEMORY; + } + + /* Allocate memory for buffers if required */ + if (config->mem_area == NULL) + { + bufs = p->mallocd_bufs = malloc(config->num * config->size); + if (bufs == NULL) + { + free(p->bdbufs); + return RTEMS_NO_MEMORY; + } + } + else + { + bufs = config->mem_area; + p->mallocd_bufs = NULL; + } + + for (i = 0, b = p->bdbufs; i < p->nblks; i++, b++, bufs += p->blksize) + { + b->dev = -1; b->block = 0; + b->buffer = bufs; + b->actual = b->modified = b->in_progress = FALSE; + b->use_count = 0; + b->pool = pool; + _Chain_Append(&p->free, &b->link); + } + + rc = rtems_semaphore_create( + rtems_build_name('B', 'U', 'F', 'G'), + p->nblks, + RTEMS_FIFO | RTEMS_COUNTING_SEMAPHORE | RTEMS_NO_INHERIT_PRIORITY | + RTEMS_NO_PRIORITY_CEILING | RTEMS_LOCAL, + 0, + &p->bufget_sema); + + if (rc != RTEMS_SUCCESSFUL) + { + free(p->bdbufs); + free(p->mallocd_bufs); + return rc; + } + + return RTEMS_SUCCESSFUL; +} + +/* bdbuf_release_pool -- + * Free resources allocated for buffer pool with specified number. + * + * PARAMETERS: + * pool - buffer pool number + * + * RETURNS: + * RTEMS_SUCCESSFUL + */ +static rtems_status_code +bdbuf_release_pool(rtems_bdpool_id pool) +{ + bdbuf_pool *p = bd_ctx.pool + pool; + rtems_semaphore_delete(p->bufget_sema); + free(p->bdbufs); + free(p->mallocd_bufs); + return RTEMS_SUCCESSFUL; +} + +/* rtems_bdbuf_init -- + * Prepare buffering layer to work - initialize buffer descritors + * and (if it is neccessary)buffers. Buffers will be allocated accoriding + * to the configuration table, each entry describes kind of block and + * amount requested. After initialization all blocks is placed into + * free elements lists. + * + * PARAMETERS: + * conf_table - pointer to the buffers configuration table + * size - number of entries in configuration table + * + * RETURNS: + * RTEMS status code (RTEMS_SUCCESSFUL if operation completed successfully + * or error code if error is occured) + */ +rtems_status_code +rtems_bdbuf_init(rtems_bdbuf_config *conf_table, int size) +{ + rtems_bdpool_id i; + rtems_status_code rc; + + if (size <= 0) + return RTEMS_INVALID_SIZE; + + bd_ctx.npools = size; + + /* + * Allocate memory for buffer pool descriptors + */ + bd_ctx.pool = calloc(size, sizeof(bdbuf_pool)); + if (bd_ctx.pool == NULL) + { + return RTEMS_NO_MEMORY; + } + + Chain_Initialize_empty(&bd_ctx.mod); + + /* Initialize buffer pools and roll out if something failed */ + for (i = 0; i < size; i++) + { + rc = bdbuf_initialize_pool(conf_table + i, i); + if (rc != RTEMS_SUCCESSFUL) + { + rtems_bdpool_id j; + for (j = 0; j < i - 1; j++) + { + bdbuf_release_pool(j); + } + return rc; + } + } + + /* Create buffer flush semaphore */ + rc = rtems_semaphore_create( + rtems_build_name('B', 'F', 'L', 'U'), 0, + RTEMS_FIFO | RTEMS_COUNTING_SEMAPHORE | RTEMS_NO_INHERIT_PRIORITY | + RTEMS_NO_PRIORITY_CEILING | RTEMS_LOCAL, 0, + &bd_ctx.flush_sema); + if (rc != RTEMS_SUCCESSFUL) + { + for (i = 0; i < size; i++) + bdbuf_release_pool(i); + free(bd_ctx.pool); + return rc; + } + + /* Create and start swapout task */ + rc = rtems_task_create( + rtems_build_name('B', 'S', 'W', 'P'), + SWAPOUT_PRIORITY, + SWAPOUT_STACK_SIZE, + RTEMS_DEFAULT_MODES | RTEMS_NO_PREEMPT, + RTEMS_DEFAULT_ATTRIBUTES, + &bd_ctx.swapout_task); + if (rc != RTEMS_SUCCESSFUL) + { + rtems_semaphore_delete(bd_ctx.flush_sema); + for (i = 0; i < size; i++) + bdbuf_release_pool(i); + free(bd_ctx.pool); + return rc; + } + + rc = rtems_task_start(bd_ctx.swapout_task, bdbuf_swapout_task, 0); + if (rc != RTEMS_SUCCESSFUL) + { + rtems_task_delete(bd_ctx.swapout_task); + rtems_semaphore_delete(bd_ctx.flush_sema); + for (i = 0; i < size; i++) + bdbuf_release_pool(i); + free(bd_ctx.pool); + return rc; + } + + return RTEMS_SUCCESSFUL; +} + +/* find_or_assign_buffer -- + * Looks for buffer already assigned for this dev/block. If one is found + * obtain block buffer. If specified block already cached (i.e. there's + * block in the _modified_, or _recently_used_), return address + * of appropriate buffer descriptor and increment reference counter to 1. + * If block is not cached, allocate new buffer and return it. Data + * shouldn't be read to the buffer from media; buffer contains arbitrary + * data. This primitive may be blocked if there are no free buffer + * descriptors available and there are no unused non-modified (or + * synchronized with media) buffers available. + * + * PARAMETERS: + * device - device number (constructed of major and minor device number + * block - linear media block number + * ret_buf - address of the variable to store address of found descriptor + * + * RETURNS: + * RTEMS status code (RTEMS_SUCCESSFUL if operation completed successfully + * or error code if error is occured) + * + * SIDE EFFEECTS: + * bufget_sema may be obtained by this primitive + * + * NOTE: + * It is assumed that primitive invoked when thread preemption is disabled. + */ +static rtems_status_code +find_or_assign_buffer(disk_device *dd, + blkdev_bnum block, + bdbuf_buffer **ret_buf) +{ + bdbuf_buffer *bd_buf; + bdbuf_pool *bd_pool; + rtems_status_code rc; + dev_t device; + ISR_Level level; + + int blksize; + + device = dd->dev; + bd_pool = bd_ctx.pool + dd->pool; + blksize = dd->block_size; + +again: + /* Looking for buffer descriptor used for this dev/block. */ + bd_buf = avl_search(&bd_pool->tree, device, block); + + if (bd_buf == NULL) + { + /* Try to obtain semaphore without waiting first. It is the most + frequent case when reasonable number of buffers configured. If + it is failed, obtain semaphore blocking on it. In this case + it should be checked that appropriate buffer hasn't been loaded + by another thread, because this thread is preempted */ + rc = rtems_semaphore_obtain(bd_pool->bufget_sema, RTEMS_NO_WAIT, 0); + if (rc == RTEMS_UNSATISFIED) + { + rc = rtems_semaphore_obtain(bd_pool->bufget_sema, + RTEMS_WAIT, RTEMS_NO_TIMEOUT); + bd_buf = avl_search(&bd_pool->tree, device, block); + if (bd_buf != NULL) + rtems_semaphore_release(bd_pool->bufget_sema); + } + } + + if (bd_buf == NULL) + { + /* Assign new buffer descriptor */ + if (_Chain_Is_empty(&bd_pool->free)) + { + bd_buf = (bdbuf_buffer *)Chain_Get(&bd_pool->lru); + if (bd_buf != NULL) + { + int avl_result; + avl_result = avl_remove(&bd_pool->tree, bd_buf); + if (avl_result != 0) + { + rtems_fatal_error_occurred(BLKDEV_FATAL_BDBUF_CONSISTENCY); + return RTEMS_INTERNAL_ERROR; + } + } + } + else + { + bd_buf = (bdbuf_buffer *)Chain_Get(&(bd_pool->free)); + } + + if (bd_buf == NULL) + { + goto again; + } + else + { + bd_buf->dev = device; + bd_buf->block = block; +#ifdef AVL_GPL + bd_buf->avl.link[0] = NULL; + bd_buf->avl.link[1] = NULL; +#else + bd_buf->avl.left = NULL; + bd_buf->avl.right = NULL; +#endif + bd_buf->use_count = 1; + bd_buf->modified = bd_buf->actual = bd_buf->in_progress = FALSE; + bd_buf->status = RTEMS_SUCCESSFUL; + + if (avl_insert(&bd_pool->tree, bd_buf) != 0) + { + rtems_fatal_error_occurred(BLKDEV_FATAL_BDBUF_CONSISTENCY); + return RTEMS_INTERNAL_ERROR; + } + + *ret_buf = bd_buf; + + return RTEMS_SUCCESSFUL; + } + } + else + { + /* Buffer descriptor already assigned for this dev/block */ + if (bd_buf->use_count == 0) + { + /* If we are removing from lru list, obtain the bufget_sema + * first. If we are removing from mod list, obtain flush sema. + * It should be obtained without blocking because we know + * that our buffer descriptor is in the list. */ + if (bd_buf->modified) + { + rc = rtems_semaphore_obtain(bd_ctx.flush_sema, + RTEMS_NO_WAIT, 0); + } + else + { + rc = rtems_semaphore_obtain(bd_pool->bufget_sema, + RTEMS_NO_WAIT, 0); + } + /* It is possible that we couldn't obtain flush or bufget sema + * although buffer in the appropriate chain is available: + * semaphore may be released to swapout task, but this task + * actually did not start to process it. */ + if (rc == RTEMS_UNSATISFIED) + rc = RTEMS_SUCCESSFUL; + if (rc != RTEMS_SUCCESSFUL) + { + rtems_fatal_error_occurred(BLKDEV_FATAL_BDBUF_CONSISTENCY); + return RTEMS_INTERNAL_ERROR; + } + + /* Buffer descriptor is linked to the lru or mod chain. Remove + it from there. */ + Chain_Extract(&bd_buf->link); + } + bd_buf->use_count++; + while (bd_buf->in_progress != 0) + { + rtems_interrupt_disable(level); + _CORE_mutex_Seize(&bd_buf->transfer_sema, 0, TRUE, + WATCHDOG_NO_TIMEOUT, level); + } + + *ret_buf = bd_buf; + return RTEMS_SUCCESSFUL; + } +} + +/* rtems_bdbuf_get -- + * Obtain block buffer. If specified block already cached (i.e. there's + * block in the _modified_, or _recently_used_), return address + * of appropriate buffer descriptor and increment reference counter to 1. + * If block is not cached, allocate new buffer and return it. Data + * shouldn't be read to the buffer from media; buffer may contains + * arbitrary data. This primitive may be blocked if there are no free + * buffer descriptors available and there are no unused non-modified + * (or synchronized with media) buffers available. + * + * PARAMETERS: + * device - device number (constructed of major and minor device number) + * block - linear media block number + * bd - address of variable to store pointer to the buffer descriptor + * + * RETURNS: + * RTEMS status code (RTEMS_SUCCESSFUL if operation completed successfully + * or error code if error is occured) + * + * SIDE EFFECTS: + * bufget_sema semaphore obtained by this primitive. + */ +rtems_status_code +rtems_bdbuf_get(dev_t device, blkdev_bnum block, bdbuf_buffer **bd) +{ + rtems_status_code rc; + disk_device *dd; + disk_device *pdd; + preemption_key key; + + /* + * Convert logical dev/block to physical one + */ + dd = rtems_disk_lookup(device); + if (dd == NULL) + return RTEMS_INVALID_ID; + + if (block >= dd->size) + { + rtems_disk_release(dd); + return RTEMS_INVALID_NUMBER; + } + + pdd = dd->phys_dev; + block += dd->start; + rtems_disk_release(dd); + + DISABLE_PREEMPTION(key); + rc = find_or_assign_buffer(pdd, block, bd); + ENABLE_PREEMPTION(key); + + if (rc != RTEMS_SUCCESSFUL) + return rc; + + return RTEMS_SUCCESSFUL; +} + +/* bdbuf_initialize_transfer_sema -- + * Initialize transfer_sema mutex semaphore associated with buffer + * descriptor. + */ +static inline void +bdbuf_initialize_transfer_sema(bdbuf_buffer *bd_buf) +{ + CORE_mutex_Attributes mutex_attr; + mutex_attr.lock_nesting_behavior = CORE_MUTEX_NESTING_BLOCKS; + mutex_attr.only_owner_release = FALSE; + mutex_attr.discipline = CORE_MUTEX_DISCIPLINES_FIFO; + mutex_attr.priority_ceiling = 0; + + _CORE_mutex_Initialize(&bd_buf->transfer_sema, + &mutex_attr, CORE_MUTEX_LOCKED); +} + +/* bdbuf_write_transfer_done -- + * Callout function. Invoked by block device driver when data transfer + * to device (write) is completed. This function may be invoked from + * interrupt handler. + * + * PARAMETERS: + * arg - arbitrary argument specified in block device request + * structure (in this case - pointer to the appropriate + * bdbuf_buffer buffer descriptor structure). + * status - I/O completion status + * error - errno error code if status != RTEMS_SUCCESSFUL + * + * RETURNS: + * none + */ +static void +bdbuf_write_transfer_done(void *arg, rtems_status_code status, int error) +{ + bdbuf_buffer *bd_buf = arg; + bd_buf->status = status; + bd_buf->error = RTEMS_IO_ERROR; + bd_buf->in_progress = bd_buf->modified = FALSE; + _CORE_mutex_Surrender(&bd_buf->transfer_sema, 0, NULL); + _CORE_mutex_Flush(&bd_buf->transfer_sema, NULL, + CORE_MUTEX_STATUS_SUCCESSFUL); +} + +/* bdbuf_read_transfer_done -- + * Callout function. Invoked by block device driver when data transfer + * from device (read) is completed. This function may be invoked from + * interrupt handler. + * + * PARAMETERS: + * arg - arbitrary argument specified in block device request + * structure (in this case - pointer to the appropriate + * bdbuf_buffer buffer descriptor structure). + * status - I/O completion status + * error - errno error code if status != RTEMS_SUCCESSFUL + * + * RETURNS: + * none + */ +static void +bdbuf_read_transfer_done(void *arg, rtems_status_code status, int error) +{ + bdbuf_buffer *bd_buf = arg; + bd_buf->status = status; + bd_buf->error = RTEMS_IO_ERROR; + _CORE_mutex_Surrender(&bd_buf->transfer_sema, 0, NULL); + _CORE_mutex_Flush(&bd_buf->transfer_sema, NULL, + CORE_MUTEX_STATUS_SUCCESSFUL); +} + +/* rtems_bdbuf_read -- + * (Similar to the rtems_bdbuf_get, except reading data from media) + * Obtain block buffer. If specified block already cached, return address + * of appropriate buffer and increment reference counter to 1. If block is + * not cached, allocate new buffer and read data to it from the media. + * This primitive may be blocked on waiting until data to be read from + * media, if there are no free buffer descriptors available and there are + * no unused non-modified (or synchronized with media) buffers available. + * + * PARAMETERS: + * device - device number (consists of major and minor device number) + * block - linear media block number + * bd - address of variable to store pointer to the buffer descriptor + * + * RETURNS: + * RTEMS status code (RTEMS_SUCCESSFUL if operation completed successfully + * or error code if error is occured) + * + * SIDE EFFECTS: + * bufget_sema and transfer_sema semaphores obtained by this primitive. + */ +rtems_status_code +rtems_bdbuf_read(dev_t device, + blkdev_bnum block, + bdbuf_buffer **bd) +{ + preemption_key key; + ISR_Level level; + + bdbuf_buffer *bd_buf; + rtems_status_code rc; + int result; + disk_device *dd; + disk_device *pdd; + blkdev_request1 req; + + dd = rtems_disk_lookup(device); + if (dd == NULL) + return RTEMS_INVALID_ID; + + if (block >= dd->size) + { + rtems_disk_release(dd); + return RTEMS_INVALID_NUMBER; + } + + pdd = dd->phys_dev; + block += dd->start; + + DISABLE_PREEMPTION(key); + rc = find_or_assign_buffer(pdd, block, &bd_buf); + + if (rc != RTEMS_SUCCESSFUL) + { + ENABLE_PREEMPTION(key); + rtems_disk_release(dd); + return rc; + } + + if (!bd_buf->actual) + { + bd_buf->in_progress = 1; + + req.req.req = BLKDEV_REQ_READ; + req.req.req_done = bdbuf_read_transfer_done; + req.req.done_arg = bd_buf; + req.req.start = block; + req.req.count = 1; + req.req.bufnum = 1; + req.req.bufs[0].length = dd->block_size; + req.req.bufs[0].buffer = bd_buf->buffer; + + bdbuf_initialize_transfer_sema(bd_buf); + result = dd->ioctl(pdd->dev, BLKIO_REQUEST, &req); + if (result == -1) + { + bd_buf->status = RTEMS_IO_ERROR; + bd_buf->error = errno; + bd_buf->actual = FALSE; + } + else + { + rtems_interrupt_disable(level); + _CORE_mutex_Seize(&bd_buf->transfer_sema, 0, TRUE, + WATCHDOG_NO_TIMEOUT, level); + bd_buf->actual = TRUE; + } + bd_buf->in_progress = FALSE; + } + rtems_disk_release(dd); + + ENABLE_PREEMPTION(key); + + *bd = bd_buf; + + return RTEMS_SUCCESSFUL; +} + + +/* bdbuf_release -- + * Release buffer. Decrease buffer usage counter. If it is zero, further + * processing depends on modified attribute. If buffer was modified, it + * is inserted into mod chain and swapout task waken up. If buffer was + * not modified, it is returned to the end of lru chain making it available + * for further use. + * + * PARAMETERS: + * bd_buf - pointer to the released buffer descriptor. + * + * RETURNS: + * RTEMS_SUCCESSFUL if buffer released successfully, or error code if + * error occured. + * + * NOTE: + * This is internal function. It is assumed that task made non-preemptive + * before its invocation. + */ +static rtems_status_code +bdbuf_release(bdbuf_buffer *bd_buf) +{ + bdbuf_pool *bd_pool; + rtems_status_code rc = RTEMS_SUCCESSFUL; + + if (bd_buf->use_count <= 0) + return RTEMS_INTERNAL_ERROR; + + bd_pool = bd_ctx.pool + bd_buf->pool; + + bd_buf->use_count--; + + if (bd_buf->use_count == 0) + { + if (bd_buf->modified) + { + + /* Buffer was modified. Insert buffer to the modified buffers + * list and initiate flushing. */ + Chain_Append(&bd_ctx.mod, &bd_buf->link); + + /* Release the flush_sema */ + rc = rtems_semaphore_release(bd_ctx.flush_sema); + } + else + { + /* Buffer was not modified. Add this descriptor to the + * end of lru chain and make it available for reuse. */ + Chain_Append(&bd_pool->lru, &bd_buf->link); + rc = rtems_semaphore_release(bd_pool->bufget_sema); + } + } + return rc; +} + + +/* rtems_bdbuf_release -- + * Release buffer allocated before. This primitive decrease the + * usage counter. If it is zero, further destiny of buffer depends on + * 'modified' status. If buffer was modified, it is placed to the end of + * mod list and flush task waken up. If buffer was not modified, + * it is placed to the end of lru list, and bufget_sema released, allowing + * to reuse this buffer. + * + * PARAMETERS: + * bd_buf - pointer to the bdbuf_buffer structure previously obtained using + * get/read primitive. + * + * RETURNS: + * RTEMS status code (RTEMS_SUCCESSFUL if operation completed successfully + * or error code if error is occured) + * + * SIDE EFFECTS: + * flush_sema and bufget_sema semaphores may be released by this primitive. + */ +rtems_status_code +rtems_bdbuf_release(bdbuf_buffer *bd_buf) +{ + preemption_key key; + rtems_status_code rc = RTEMS_SUCCESSFUL; + + if (bd_buf == NULL) + return RTEMS_INVALID_ADDRESS; + + DISABLE_PREEMPTION(key); + + rc = bdbuf_release(bd_buf); + + ENABLE_PREEMPTION(key); + + return rc; +} + +/* rtems_bdbuf_release_modified -- + * Release buffer allocated before, assuming that it is _modified_ by + * it's owner. This primitive decrease usage counter for buffer, mark + * buffer descriptor as modified. If usage counter is 0, insert it at + * end of mod chain and release flush_sema semaphore to activate the + * flush task. + * + * PARAMETERS: + * bd_buf - pointer to the bdbuf_buffer structure previously obtained using + * get/read primitive. + * + * RETURNS: + * RTEMS status code (RTEMS_SUCCESSFUL if operation completed successfully + * or error code if error is occured) + * + * SIDE EFFECTS: + * flush_sema semaphore may be released by this primitive. + */ +rtems_status_code +rtems_bdbuf_release_modified(bdbuf_buffer *bd_buf) +{ + preemption_key key; + rtems_status_code rc = RTEMS_SUCCESSFUL; + + if (bd_buf == NULL) + return RTEMS_INVALID_ADDRESS; + + DISABLE_PREEMPTION(key); + + if (!bd_buf->modified) + { + bdbuf_initialize_transfer_sema(bd_buf); + } + bd_buf->modified = TRUE; + bd_buf->actual = TRUE; + rc = bdbuf_release(bd_buf); + + ENABLE_PREEMPTION(key); + + return rc; +} + +/* rtems_bdbuf_sync -- + * Wait until specified buffer synchronized with disk. Invoked on exchanges + * critical for data consistency on the media. This primitive mark owned + * block as modified, decrease usage counter. If usage counter is 0, + * block inserted to the mod chain and flush_sema semaphore released. + * Finally, primitives blocked on transfer_sema semaphore. + * + * PARAMETERS: + * bd_buf - pointer to the bdbuf_buffer structure previously obtained using + * get/read primitive. + * + * RETURNS: + * RTEMS status code (RTEMS_SUCCESSFUL if operation completed successfully + * or error code if error is occured) + * + * SIDE EFFECTS: + * Primitive may be blocked on transfer_sema semaphore. + */ +rtems_status_code +rtems_bdbuf_sync(bdbuf_buffer *bd_buf) +{ + preemption_key key; + ISR_Level level; + rtems_status_code rc = RTEMS_SUCCESSFUL; + + if (bd_buf == NULL) + return RTEMS_INVALID_ADDRESS; + + DISABLE_PREEMPTION(key); + + if (!bd_buf->modified) + { + bdbuf_initialize_transfer_sema(bd_buf); + } + bd_buf->modified = TRUE; + bd_buf->actual = TRUE; + + rc = bdbuf_release(bd_buf); + + if (rc == RTEMS_SUCCESSFUL) + { + rtems_interrupt_disable(level); + _CORE_mutex_Seize(&bd_buf->transfer_sema, 0, TRUE, + WATCHDOG_NO_TIMEOUT, level); + } + + ENABLE_PREEMPTION(key); + + return rc; +} + +/* rtems_bdbuf_syncdev -- + * Synchronize with disk all buffers containing the blocks belonging to + * specified device. + * + * PARAMETERS: + * dev - block device number + * + * RETURNS: + * 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) +{ + preemption_key key; + ISR_Level level; + + bdbuf_buffer *bd_buf; + disk_device *dd; + bdbuf_pool *pool; + + dd = rtems_disk_lookup(dev); + if (dd == NULL) + return RTEMS_INVALID_ID; + + pool = bd_ctx.pool + dd->pool; + + DISABLE_PREEMPTION(key); + do { + bd_buf = avl_search_for_sync(&pool->tree, dd); + if (bd_buf != NULL /* && bd_buf->modified */) + { + rtems_interrupt_disable(level); + _CORE_mutex_Seize(&bd_buf->transfer_sema, 0, TRUE, + WATCHDOG_NO_TIMEOUT, level); + } + } while (bd_buf != NULL); + ENABLE_PREEMPTION(key); + return rtems_disk_release(dd); +} + +/* bdbuf_swapout_task -- + * Body of task which take care on flushing modified buffers to the + * disk. + */ +static rtems_task +bdbuf_swapout_task(rtems_task_argument unused) +{ + rtems_status_code rc; + int result; + ISR_Level level; + bdbuf_buffer *bd_buf; + bdbuf_pool *bd_pool; + disk_device *dd; + blkdev_request1 req; + + while (1) + { + rc = rtems_semaphore_obtain(bd_ctx.flush_sema, RTEMS_WAIT, 0); + if (rc != RTEMS_SUCCESSFUL) + { + rtems_fatal_error_occurred(BLKDEV_FATAL_BDBUF_SWAPOUT); + } + + bd_buf = (bdbuf_buffer *)Chain_Get(&bd_ctx.mod); + if (bd_buf == NULL) + { + /* It is possible that flush_sema semaphore will be released, but + * buffer to be removed from mod chain before swapout task start + * its processing. */ + continue; + } + + bd_buf->in_progress = TRUE; + bd_buf->use_count++; + bd_pool = bd_ctx.pool + bd_buf->pool; + dd = rtems_disk_lookup(bd_buf->dev); + + req.req.req = BLKDEV_REQ_WRITE; + req.req.req_done = bdbuf_write_transfer_done; + req.req.done_arg = bd_buf; + req.req.start = bd_buf->block + dd->start; + req.req.count = 1; + req.req.bufnum = 1; + req.req.bufs[0].length = dd->block_size; + req.req.bufs[0].buffer = bd_buf->buffer; + + /* transfer_sema initialized when bd_buf inserted in the mod chain + first time */ + result = dd->ioctl(dd->phys_dev->dev, BLKIO_REQUEST, &req); + + rtems_disk_release(dd); + + if (result == -1) + { + bd_buf->status = RTEMS_IO_ERROR; + bd_buf->error = errno; + /* Release tasks waiting on syncing this buffer */ + _CORE_mutex_Flush(&bd_buf->transfer_sema, NULL, + CORE_MUTEX_STATUS_SUCCESSFUL); + } + else + { + if (bd_buf->in_progress) + { + rtems_interrupt_disable(level); + _CORE_mutex_Seize(&bd_buf->transfer_sema, 0, TRUE, 0, level); + } + } + bd_buf->use_count--; + + /* Another task have chance to use this buffer, or even + * modify it. If buffer is not in use, insert it in appropriate chain + * and release semaphore */ + if (bd_buf->use_count == 0) + { + if (bd_buf->modified) + { + Chain_Append(&bd_ctx.mod, &bd_buf->link); + rc = rtems_semaphore_release(bd_ctx.flush_sema); + } + else + { + Chain_Append(&bd_pool->lru, &bd_buf->link); + rc = rtems_semaphore_release(bd_pool->bufget_sema); + } + } + } +} + +/* rtems_bdbuf_find_pool -- + * Find first appropriate buffer pool. This primitive returns the index + * of first buffer pool which block size is greater than or equal to + * specified size. + * + * PARAMETERS: + * block_size - requested block size + * pool - placeholder for result + * + * RETURNS: + * RTEMS status code: RTEMS_SUCCESSFUL if operation completed successfully, + * RTEMS_INVALID_SIZE if specified block size is invalid (not a power + * of 2), RTEMS_NOT_DEFINED if buffer pool for this or greater block size + * is not configured. + */ +rtems_status_code +rtems_bdbuf_find_pool(int block_size, rtems_bdpool_id *pool) +{ + rtems_bdpool_id i; + bdbuf_pool *p; + int cursize = INT_MAX; + rtems_bdpool_id curid = -1; + rtems_boolean found = FALSE; + int j; + + for (j = block_size; (j != 0) && ((j & 1) == 0); j >>= 1); + if (j != 1) + return RTEMS_INVALID_SIZE; + + for (i = 0, p = bd_ctx.pool; i < bd_ctx.npools; i++, p++) + { + if ((p->blksize >= block_size) && + (p->blksize < cursize)) + { + curid = i; + cursize = p->blksize; + found = TRUE; + } + } + + if (found) + { + if (pool != NULL) + *pool = curid; + return RTEMS_SUCCESSFUL; + } + else + { + return RTEMS_NOT_DEFINED; + } +} + +/* rtems_bdbuf_get_pool_info -- + * Obtain characteristics of buffer pool with specified number. + * + * PARAMETERS: + * pool - buffer pool number + * block_size - block size for which buffer pool is configured returned + * there + * blocks - number of buffers in buffer pool returned there + * + * RETURNS: + * RTEMS status code: RTEMS_SUCCESSFUL if operation completed successfully, + * RTEMS_INVALID_NUMBER if appropriate buffer pool is not configured. + * + * NOTE: + * Buffer pools enumerated contiguously starting from 0. + */ +rtems_status_code +rtems_bdbuf_get_pool_info(rtems_bdpool_id pool, int *block_size, + int *blocks) +{ + if (pool >= bd_ctx.npools) + return RTEMS_INVALID_NUMBER; + + if (block_size != NULL) + { + *block_size = bd_ctx.pool[pool].blksize; + } + + if (blocks != NULL) + { + *blocks = bd_ctx.pool[pool].nblks; + } + + return RTEMS_SUCCESSFUL; +} |