/* * Copyright (C) 2016 Eistec AB * * This file is subject to the terms and conditions of the GNU Lesser * General Public License v2.1. See the file LICENSE in the top level * directory for more details. */ /** * @ingroup sys_fs_devfs * @{ * * @file * @brief DevFS implementation * * @author Joakim NohlgÄrd * * @} */ /* Required for strnlen in string.h, when building with -std=c99 */ #define _DEFAULT_SOURCE 1 #include #include #include #include #include "fs/devfs.h" #include "vfs.h" #include "mutex.h" #define ENABLE_DEBUG (0) #include "debug.h" /** * @internal * @brief DevFS list head * * DevFS operates as a singleton, the same files show up in all mounted instances. */ static clist_node_t _devfs_list; /** * @internal * @brief mutex to protect the DevFS list from corruption */ static mutex_t _devfs_mutex = MUTEX_INIT; /* No need for file system operations, no extra work to be done on * mount/umount. unlink is not permitted, use devfs_unregister instead */ /* File operations */ /* open is overloaded to allow searching for the correct device */ static int devfs_open(vfs_file_t *filp, const char *name, int flags, mode_t mode, const char *abs_path); /* A minimal fcntl is also provided to enable SETFL handling */ static int devfs_fcntl(vfs_file_t *filp, int cmd, int arg); /* Directory operations */ static int devfs_opendir(vfs_DIR *dirp, const char *dirname, const char *abs_path); static int devfs_readdir(vfs_DIR *dirp, vfs_dirent_t *entry); static int devfs_closedir(vfs_DIR *dirp); static const vfs_file_ops_t devfs_file_ops = { .open = devfs_open, .fcntl = devfs_fcntl, }; static const vfs_dir_ops_t devfs_dir_ops = { .opendir = devfs_opendir, .readdir = devfs_readdir, .closedir = devfs_closedir, }; const vfs_file_system_t devfs_file_system = { .f_op = &devfs_file_ops, .d_op = &devfs_dir_ops, }; static int devfs_open(vfs_file_t *filp, const char *name, int flags, mode_t mode, const char *abs_path) { DEBUG("devfs_open: %p, \"%s\", 0x%x, 0%03lo, \"%s\"\n", (void *)filp, name, flags, (unsigned long)mode, abs_path); /* linear search through the device list */ mutex_lock(&_devfs_mutex); clist_node_t *it = _devfs_list.next; if (it == NULL) { /* list empty */ mutex_unlock(&_devfs_mutex); return -ENOENT; } do { it = it->next; devfs_t *devp = container_of(it, devfs_t, list_entry); if (strcmp(devp->path, name) == 0) { mutex_unlock(&_devfs_mutex); DEBUG("devfs_open: Found :)\n"); /* Add private data from DevFS node */ filp->private_data.ptr = devp->private_data; /* Replace f_op with the operations provided by the device driver */ filp->f_op = devp->f_op; /* Chain the open() method for the specific device */ if (filp->f_op->open != NULL) { return filp->f_op->open(filp, name, flags, mode, abs_path); } return 0; } } while (it != _devfs_list.next); mutex_unlock(&_devfs_mutex); DEBUG("devfs_open: Not found :(\n"); return -ENOENT; } static int devfs_fcntl(vfs_file_t *filp, int cmd, int arg) { DEBUG("devfs_fcntl: %p, 0x%x, 0x%x\n", (void *)filp, cmd, arg); switch (cmd) { /* F_GETFL is handled directly by vfs_fcntl */ case F_SETFL: DEBUG("devfs_fcntl: SETFL: %d\n", arg); filp->flags = arg; return filp->flags; default: return -EINVAL; } } static int devfs_opendir(vfs_DIR *dirp, const char *dirname, const char *abs_path) { (void) abs_path; DEBUG("devfs_opendir: %p, \"%s\", \"%s\"\n", (void *)dirp, dirname, abs_path); if (strncmp(dirname, "/", 2) != 0) { /* We keep it simple and only support a flat file system, only a root directory */ return -ENOENT; } dirp->private_data.ptr = NULL; return 0; } static int devfs_readdir(vfs_DIR *dirp, vfs_dirent_t *entry) { DEBUG("devfs_readdir: %p, %p\n", (void *)dirp, (void *)entry); mutex_lock(&_devfs_mutex); clist_node_t *it = dirp->private_data.ptr; if (it == _devfs_list.next) { /* end of list was reached */ mutex_unlock(&_devfs_mutex); return 0; } if (it == NULL) { /* first readdir after opendir */ it = _devfs_list.next; if (it == NULL) { /* empty list */ mutex_unlock(&_devfs_mutex); return 0; } } it = it->next; dirp->private_data.ptr = it; mutex_unlock(&_devfs_mutex); devfs_t *devp = container_of(it, devfs_t, list_entry); if (devp->path == NULL) { /* skip past the broken entry and try again */ return -EAGAIN; } size_t len = strnlen(devp->path, VFS_NAME_MAX + 1); if (len > VFS_NAME_MAX) { /* name does not fit in vfs_dirent_t buffer */ /* skip past the broken entry and try again */ return -EAGAIN; } /* clear the dirent */ memset(entry, 0, sizeof(*entry)); /* copy the string, including terminating null */ memcpy(&entry->d_name[0], devp->path, len + 1); return 1; } static int devfs_closedir(vfs_DIR *dirp) { /* Just an example, it's not necessary to define closedir if there is * nothing to clean up */ (void) dirp; DEBUG("devfs_closedir: %p\n", (void *)dirp); return 0; } int devfs_register(devfs_t *devp) { DEBUG("devfs_register: %p\n", (void *)devp); if (devp == NULL) { return -EINVAL; } DEBUG("devfs_register: \"%s\" -> (%p, %p)\n", devp->path, (void *)devp->f_op, devp->private_data); if (devp->path == NULL) { return -EINVAL; } if (devp->f_op == NULL) { return -EINVAL; } mutex_lock(&_devfs_mutex); clist_node_t *it = _devfs_list.next; if (it != NULL) { /* list not empty */ do { it = it->next; if (it == &devp->list_entry) { /* Already registered */ mutex_unlock(&_devfs_mutex); DEBUG("devfs_register: %p already registered\n", (void *)devp); return -EEXIST; } devfs_t *devit = container_of(it, devfs_t, list_entry); if (strcmp(devit->path, devp->path) == 0) { /* Path already registered */ mutex_unlock(&_devfs_mutex); DEBUG("devfs_register: \"%s\" occupied\n", devp->path); return -EEXIST; } } while(it != _devfs_list.next); } /* insert last in list */ clist_rpush(&_devfs_list, &devp->list_entry); mutex_unlock(&_devfs_mutex); return 0; } int devfs_unregister(devfs_t *devp) { DEBUG("devfs_unregister: %p\n", (void *)devp); if (devp == NULL) { return -EINVAL; } mutex_lock(&_devfs_mutex); /* find devp in the list and remove it */ clist_node_t *node = clist_remove(&_devfs_list, &devp->list_entry); mutex_unlock(&_devfs_mutex); if (node == NULL) { /* not found */ DEBUG("devfs_unregister: ERR not registered!\n"); return -ENOENT; } return 0; }