
11 changed files with 588 additions and 0 deletions
@ -0,0 +1,2 @@
|
||||
MODULE=devfs
|
||||
include $(RIOTBASE)/Makefile.base |
@ -0,0 +1,37 @@
|
||||
/*
|
||||
* 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 auto_init_fs |
||||
* @{ |
||||
* |
||||
* @file |
||||
* @brief Automatic mount of DevFS on /dev |
||||
* |
||||
* @author Joakim Nohlgård <joakim.nohlgard@eistec.se> |
||||
* |
||||
* @} |
||||
*/ |
||||
|
||||
#include "vfs.h" |
||||
#include "fs/devfs.h" |
||||
|
||||
#define ENABLE_DEBUG (0) |
||||
#include "debug.h" |
||||
|
||||
static vfs_mount_t _devfs_auto_init_mount = { |
||||
.fs = &devfs_file_system, |
||||
.mount_point = "/dev", |
||||
}; |
||||
|
||||
void auto_init_devfs(void) |
||||
{ |
||||
DEBUG("auto_init_devfs: mounting /dev\n"); |
||||
vfs_mount(&_devfs_auto_init_mount); |
||||
} |
@ -0,0 +1,242 @@
|
||||
/*
|
||||
* 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 fs_devfs |
||||
* @{ |
||||
* |
||||
* @file |
||||
* @brief DevFS implementation |
||||
* |
||||
* @author Joakim Nohlgård <joakim.nohlgard@eistec.se> |
||||
* |
||||
* @} |
||||
*/ |
||||
|
||||
/* Required for strnlen in string.h, when building with -std=c99 */ |
||||
#define _DEFAULT_SOURCE 1 |
||||
#include <string.h> |
||||
#include <unistd.h> |
||||
#include <fcntl.h> |
||||
#include <errno.h> |
||||
|
||||
#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; |
||||
} |
@ -0,0 +1,94 @@
|
||||
/*
|
||||
* 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. |
||||
*/ |
||||
|
||||
/**
|
||||
* @defgroup fs_devfs DevFS device file system |
||||
* @ingroup fs |
||||
* @brief Dynamic device file system |
||||
* |
||||
* This file system implementation allows devices to register file names for |
||||
* easier access to device drivers from shell commands etc. |
||||
* |
||||
* The idea is similar to the /dev directory on Unix. |
||||
* |
||||
* @{ |
||||
* @file |
||||
* @brief DevFS public API |
||||
* @author Joakim Nohlgård <joakim.nohlgard@eistec.se> |
||||
*/ |
||||
|
||||
#ifndef DEVFS_H_ |
||||
#define DEVFS_H_ |
||||
|
||||
#include "clist.h" |
||||
#include "vfs.h" |
||||
|
||||
#ifdef __cplusplus |
||||
extern "C" { |
||||
#endif |
||||
|
||||
/**
|
||||
* @brief DevFS node typedef |
||||
*/ |
||||
typedef struct devfs devfs_t; |
||||
|
||||
/**
|
||||
* @brief A device "file" consists of a file name and an opaque pointer to device driver private data |
||||
* |
||||
* The file system is implemented as a linked list. |
||||
*/ |
||||
struct devfs { |
||||
clist_node_t list_entry; /**< List item entry */ |
||||
const char *path; /**< File system relative path to this node */ |
||||
const vfs_file_ops_t *f_op; /**< Pointer to file operations table for this device */ |
||||
void *private_data; /**< Pointer to device driver specific data */ |
||||
}; |
||||
|
||||
/**
|
||||
* @brief DevFS file system driver |
||||
* |
||||
* For use with vfs_mount |
||||
*/ |
||||
extern const vfs_file_system_t devfs_file_system; |
||||
|
||||
/**
|
||||
* @brief Register a node in DevFS |
||||
* |
||||
* The node will immediately become available to @c vfs_open, if DevFS is already |
||||
* mounted somewhere. |
||||
* |
||||
* If DevFS is not mounted, the node will be registered and will become |
||||
* available to @c vfs_open when DevFS is mounted. |
||||
* |
||||
* @param[in] node DevFS node to register |
||||
* |
||||
* @return 0 on success |
||||
* @return <0 on error |
||||
*/ |
||||
int devfs_register(devfs_t *node); |
||||
|
||||
/**
|
||||
* @brief Remove a registration from DevFS |
||||
* |
||||
* The node will no longer be available to @c vfs_open, but any already opened FDs |
||||
* will remain open. |
||||
* |
||||
* @param[in] node DevFS node to unregister |
||||
* |
||||
* @return 0 on success |
||||
* @return <0 on error |
||||
*/ |
||||
int devfs_unregister(devfs_t *node); |
||||
|
||||
#ifdef __cplusplus |
||||
} |
||||
#endif |
||||
|
||||
#endif |
||||
|
||||
/** @} */ |
@ -0,0 +1 @@
|
||||
include $(RIOTBASE)/Makefile.base |
@ -0,0 +1,2 @@
|
||||
USEMODULE += vfs
|
||||
USEMODULE += devfs
|
@ -0,0 +1,160 @@
|
||||
/*
|
||||
* 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. |
||||
*/ |
||||
|
||||
/**
|
||||
* @{ |
||||
* |
||||
* @file |
||||
* @brief Unittests for DevFS |
||||
* |
||||
* @author Joakim Nohlgård <joakim.nohlgard@eistec.se> |
||||
*/ |
||||
#include <unistd.h> |
||||
#include <sys/types.h> |
||||
#include <sys/stat.h> |
||||
#include <fcntl.h> |
||||
|
||||
#include "fs/devfs.h" |
||||
|
||||
#include "embUnit/embUnit.h" |
||||
|
||||
#include "tests-devfs.h" |
||||
|
||||
static int _mock_open(vfs_file_t *filp, const char *name, int flags, mode_t mode, const char *abs_path); |
||||
static ssize_t _mock_read(vfs_file_t *filp, void *dest, size_t nbytes); |
||||
static ssize_t _mock_write(vfs_file_t *filp, const void *src, size_t nbytes); |
||||
|
||||
static volatile int _mock_open_calls = 0; |
||||
static volatile int _mock_read_calls = 0; |
||||
static volatile int _mock_write_calls = 0; |
||||
|
||||
static int _mock_private_data; |
||||
|
||||
static const vfs_file_ops_t _mock_devfs_ops = { |
||||
.open = _mock_open, |
||||
.read = _mock_read, |
||||
.write = _mock_write, |
||||
}; |
||||
|
||||
static int _mock_private_data_tag = 4321; |
||||
|
||||
static devfs_t _mock_devfs_node = { |
||||
.path = "/mock0", |
||||
.f_op = &_mock_devfs_ops, |
||||
.private_data = &_mock_private_data_tag, |
||||
}; |
||||
|
||||
static vfs_mount_t _test_devfs_mount = { |
||||
.fs = &devfs_file_system, |
||||
.mount_point = "/test", |
||||
.private_data = &_mock_private_data, |
||||
}; |
||||
|
||||
static int _mock_open(vfs_file_t *filp, const char *name, int flags, mode_t mode, const char *abs_path) |
||||
{ |
||||
(void) name; |
||||
(void) flags; |
||||
(void) mode; |
||||
(void) abs_path; |
||||
if (filp->private_data.ptr != &_mock_private_data_tag) { |
||||
return -4321; |
||||
} |
||||
int *np = filp->mp->private_data; |
||||
++(*np); |
||||
++_mock_open_calls; |
||||
return 0; |
||||
} |
||||
|
||||
static ssize_t _mock_read(vfs_file_t *filp, void *dest, size_t nbytes) |
||||
{ |
||||
(void) dest; |
||||
(void) nbytes; |
||||
if (filp->private_data.ptr != &_mock_private_data_tag) { |
||||
return -4321; |
||||
} |
||||
int *np = filp->mp->private_data; |
||||
++(*np); |
||||
++_mock_read_calls; |
||||
return 0; |
||||
} |
||||
|
||||
static ssize_t _mock_write(vfs_file_t *filp, const void *src, size_t nbytes) |
||||
{ |
||||
(void) src; |
||||
(void) nbytes; |
||||
if (filp->private_data.ptr != &_mock_private_data_tag) { |
||||
return -4321; |
||||
} |
||||
int *np = filp->mp->private_data; |
||||
++(*np); |
||||
++_mock_write_calls; |
||||
return 0; |
||||
} |
||||
|
||||
static void test_devfs_register(void) |
||||
{ |
||||
int res = devfs_register(NULL); |
||||
TEST_ASSERT(res < 0); |
||||
|
||||
res = devfs_register(&_mock_devfs_node); |
||||
TEST_ASSERT(res == 0); |
||||
|
||||
res = devfs_register(&_mock_devfs_node); |
||||
TEST_ASSERT(res < 0); |
||||
|
||||
res = devfs_unregister(&_mock_devfs_node); |
||||
TEST_ASSERT(res == 0); |
||||
|
||||
res = devfs_unregister(&_mock_devfs_node); |
||||
TEST_ASSERT(res < 0); |
||||
} |
||||
|
||||
static void test_devfs_mount_open(void) |
||||
{ |
||||
_mock_private_data = 12345; |
||||
int res; |
||||
res = vfs_mount(&_test_devfs_mount); |
||||
TEST_ASSERT_EQUAL_INT(0, res); |
||||
TEST_ASSERT_EQUAL_INT(_mock_private_data, 12345); |
||||
|
||||
res = devfs_register(&_mock_devfs_node); |
||||
TEST_ASSERT_EQUAL_INT(0, res); |
||||
|
||||
int count = _mock_open_calls; |
||||
int fd = vfs_open("/test/mock0", O_RDWR, 0); |
||||
TEST_ASSERT(fd >= 0); |
||||
TEST_ASSERT_EQUAL_INT(count + 1, _mock_open_calls); |
||||
TEST_ASSERT_EQUAL_INT(_mock_private_data, 12346); |
||||
|
||||
res = vfs_close(fd); |
||||
TEST_ASSERT_EQUAL_INT(0, res); |
||||
|
||||
res = devfs_unregister(&_mock_devfs_node); |
||||
TEST_ASSERT_EQUAL_INT(0, res); |
||||
|
||||
res = vfs_umount(&_test_devfs_mount); |
||||
TEST_ASSERT_EQUAL_INT(0, res); |
||||
} |
||||
|
||||
Test *tests_devfs_tests(void) |
||||
{ |
||||
EMB_UNIT_TESTFIXTURES(fixtures) { |
||||
new_TestFixture(test_devfs_register), |
||||
new_TestFixture(test_devfs_mount_open), |
||||
}; |
||||
|
||||
EMB_UNIT_TESTCALLER(devfs_tests, NULL, NULL, fixtures); |
||||
|
||||
return (Test *)&devfs_tests; |
||||
} |
||||
|
||||
void tests_devfs(void) |
||||
{ |
||||
TESTS_RUN(tests_devfs_tests()); |
||||
} |
||||
/** @} */ |
@ -0,0 +1,37 @@
|
||||
/*
|
||||
* 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. |
||||
*/ |
||||
|
||||
/**
|
||||
* @addtogroup unittests |
||||
* @{ |
||||
* |
||||
* @file |
||||
* @brief Unittests for DevFS |
||||
* |
||||
* @author Joakim Nohlgård <joakim.nohlgard@eistec.se> |
||||
*/ |
||||
#ifndef TESTS_DEVFS_H |
||||
#define TESTS_DEVFS_H |
||||
|
||||
#include "embUnit.h" |
||||
|
||||
#ifdef __cplusplus |
||||
extern "C" { |
||||
#endif |
||||
|
||||
/**
|
||||
* @brief The entry point of this test suite. |
||||
*/ |
||||
void tests_devfs(void); |
||||
|
||||
#ifdef __cplusplus |
||||
} |
||||
#endif |
||||
|
||||
#endif /* TESTS_DEVFS_H */ |
||||
/** @} */ |
Loading…
Reference in new issue