summaryrefslogtreecommitdiffstats
path: root/cpukit/libblock/src/diskdevs.c
diff options
context:
space:
mode:
Diffstat (limited to 'cpukit/libblock/src/diskdevs.c')
-rw-r--r--cpukit/libblock/src/diskdevs.c589
1 files changed, 589 insertions, 0 deletions
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;
+}