diff --git a/fs/Kconfig b/fs/Kconfig index b175276faf..12a68e36b1 100644 --- a/fs/Kconfig +++ b/fs/Kconfig @@ -9,6 +9,26 @@ config DISABLE_MOUNTPOINT bool "Disable support for mount points" default n +config FS_AUTOMOUNTER + bool "Auto-mounter" + default n + depends on !DISABLE_MOUNTPOINT + ---help--- + The automounter provides an OS-internal mechanism for automatically + mounting and unmounting removable media as the media is inserted and + removed. See include/nuttx/fs/automout.h for interfacing details. + +config FS_AUTOMOUNTER_DEBUG + bool "Auto-mounter debug" + default n + depends on FS_AUTOMOUNTER && DEBUG + ---help--- + Normally, the auto-mounter will generate debug output when sub-system + level file system debug is enabled. This option will select debug + output from the logic related to the auto-mount feature even when file + system debug is not enable. This is useful primarily for in vivo + unit testing of the auto-mount feature. + config DISABLE_PSEUDOFS_OPERATIONS bool "Disable pseudo-filesystem operations" default y if DEFAULT_SMALL diff --git a/fs/Makefile b/fs/Makefile index 3acfe606d3..ebe1ee32b3 100644 --- a/fs/Makefile +++ b/fs/Makefile @@ -106,6 +106,10 @@ ifneq ($(CONFIG_DISABLE_MOUNTPOINT),y) CSRCS += fs_fsync.c fs_mount.c fs_umount.c fs_foreachmountpoint.c +ifeq ($(CONFIG_FS_AUTOMOUNTER),y) +CSRCS += fs_automount.c +endif + include fat/Make.defs include romfs/Make.defs include nxffs/Make.defs diff --git a/fs/fs_automount.c b/fs/fs_automount.c new file mode 100644 index 0000000000..965140a1ca --- /dev/null +++ b/fs/fs_automount.c @@ -0,0 +1,613 @@ +/**************************************************************************** + * fs/fs_automount.c + * + * Copyright (C) 2014 Gregory Nutt. All rights reserved. + * Author: Gregory Nutt + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * 3. Neither the name NuttX nor the names of its contributors may be + * used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS + * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED + * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN + * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + ****************************************************************************/ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include + +#if defined(CONFIG_FS_AUTOMOUNTER_DEBUG) && !defined(CONFIG_DEBUG_FS) +# define CONFIG_DEBUG_FS 1 +#endif + +#include + +#include +#include +#include +#include +#include + +#include +#include +#include + +#include "fs_internal.h" + +/**************************************************************************** + * Pre-processor Definitions + ****************************************************************************/ +/* Configuration ************************************************************ + * + * CONFIG_FS_AUTOMOUNTER - Enables AUTOMOUNT support + */ + +/* Pre-requisites */ + +#ifndef CONFIG_SCHED_WORKQUEUE +# error Work queue support is required (CONFIG_SCHED_WORKQUEUE) +#endif + +/* Return Values */ + +#define OK_EXIST 0 +#define OK_NOENT 1 + +/**************************************************************************** + * Private Types + ****************************************************************************/ +/* This structure describes the state of the automounter */ + +struct automounter_state_s +{ + FAR const struct automount_lower_s *lower; /* Board level interfaces */ + struct work_s work; /* Work queue support */ + WDOG_ID wdog; /* Delay to retry un-mounts */ + bool mounted; /* True: Volume has been mounted */ + bool inserted; /* True: Media has been inserted */ +}; + +/**************************************************************************** + * Private Function Prototypes + ****************************************************************************/ + +static int automount_findinode(FAR const char *path); +static void automount_mount(FAR struct automounter_state_s *priv); +static int automount_unmount(FAR struct automounter_state_s *priv); +static void automount_timeout(int argc, uint32_t arg1, ...); +static void automount_worker(FAR void *arg); +static int automount_interrupt(FAR const struct automount_lower_s *lower, + FAR void *arg, bool inserted); + +/**************************************************************************** + * Private Data + ****************************************************************************/ + +/**************************************************************************** + * Public Data + ****************************************************************************/ + +/**************************************************************************** + * Private Functions + ****************************************************************************/ + +/**************************************************************************** + * Name: automount_findinode + * + * Description: + * Find the mountpoint inode in the inode tree. + * + * Input Parameters: + * mntpath - Mountpoint path + * + * Returned Value: + * OK_EXIST if the inode exists + * OK_NOENT if the indoe does not exist + * Negated errno if some failure occurs + * + ****************************************************************************/ + +static int automount_findinode(FAR const char *path) +{ + FAR struct inode *node; + FAR const char *srchpath; + FAR const char *relpath; + int ret; + + /* Make sure that we were given an absolute path */ + + DEBUGASSERT(path && path[0] == '/'); + + /* Get exclusive access to the in-memory inode tree. */ + + inode_semtake(); + + /* Find the inode */ + + srchpath = path; + node = inode_search(&srchpath, (FAR struct inode**)NULL, + (FAR struct inode**)NULL, &relpath); + /* Did we find it? */ + + if (!node) + { + /* No.. Not found */ + + ret = OK_NOENT; + } + + /* Yes.. is it a mount point? */ + + else if (INODE_IS_MOUNTPT(node)) + { + /* Yes.. we found a mountpoint at this path */ + + ret = OK_EXIST; + } + else + { + /* No.. then somethin is in the way */ + + ret = -ENOTDIR; + } + + /* Relinquish our exclusive access to the inode try and return the result */ + + inode_semgive(); + return ret; +} + +/**************************************************************************** + * Name: automount_mount + * + * Description: + * Media has been inserted, mount the volume. + * + * Input Parameters: + * priv - A reference to out private state structure + * + * Returned Value: + * None + * + ****************************************************************************/ + +static void automount_mount(FAR struct automounter_state_s *priv) +{ + FAR const struct automount_lower_s *lower = priv->lower; + int ret; + + /* Check if the something is already mounted at the mountpoint. */ + + ret = automount_findinode(lower->mountpoint); + switch (ret) + { + case OK_EXIST: + /* REVISIT: What should we do in this case? I think that this would + * happen only if a previous unmount failed? I suppose that we should + * try to unmount again because the mount might be stale. + */ + + fdbg("WARNING: Mountpoint %s already exists\n", lower->mountpoint); + ret = automount_unmount(priv); + if (ret < 0) + { + /* We failed to unmount (again?). Complain and abort. */ + + fdbg("ERROR: automount_unmount failed: %d\n", ret); + return; + } + + /* We successfully unmounted the file system. Fall through to + * mount it again. + */ + + case OK_NOENT: + /* If we get here, then the volume must not be mounted */ + + DEBUGASSERT(!priv->mounted); + + /* Mount the file system */ + + ret = mount(lower->blockdev, lower->mountpoint, lower->fstype, + 0, NULL); + if (ret < 0) + { + int errcode = get_errno(); + DEBUGASSERT(errcode > 0); + + fdbg("ERROR: Mount failed: %d\n", errcode); + UNUSED(errcode); + return; + } + + /* Indicate that the volume is mounted */ + + priv->mounted = true; + break; + + default: + fdbg("ERROR: automount_findinode failed: %d\n", ret); + break; + } +} + +/**************************************************************************** + * Name: automount_unmount + * + * Description: + * Media has been removed, unmount the volume. + * + * Input Parameters: + * priv - A reference to out private state structure + * + * Returned Value: + * OK if the volume was successfully mounted. A negated errno value + * otherwise. + * + ****************************************************************************/ + +static int automount_unmount(FAR struct automounter_state_s *priv) +{ + FAR const struct automount_lower_s *lower = priv->lower; + int ret; + + /* Check if the something is already mounted at the mountpoint. */ + + ret = automount_findinode(lower->mountpoint); + switch (ret) + { + case OK_EXIST: + /* If we get here, then the volume must be mounted */ + + DEBUGASSERT(priv->mounted); + + /* Un-mount the volume */ + + ret = umount(lower->mountpoint); + if (ret < 0) + { + int errcode = get_errno(); + DEBUGASSERT(errcode > 0); + + /* We expect the error to be EBUSY meaning that the volume could + * not be unmounted because there are currently reference via open + * files or directories. + */ + + if (errcode == EBUSY) + { + fvdbg("WARNING: Volume is busy, try again later\n"); + + /* Start a timer to retry the umount after a delay */ + + ret = wd_start(priv->wdog, lower->udelay, automount_timeout, 1, + (uint32_t)((uintptr_t)priv)); + if (ret < 0) + { + errcode = get_errno(); + DEBUGASSERT(errcode > 0); + + fdbg("ERROR: wd_start failed: %d\n", errcode); + return -ret; + } + } + + /* Other errors are fatal */ + + else + { + fvdbg("ERROR: umount failed: %d\n", errcode); + return -errcode; + } + } + + /* Successfully unmounted */ + + priv->mounted = false; + return OK; + + case OK_NOENT: + /* I suppose this is okay */ + + return OK; + + default: + fdbg("ERROR: automount_findinode failed: %d\n", ret); + return ret; + } +} + +/**************************************************************************** + * Name: automount_timeout + * + * Description: + * A previous unmount failed because the volume was busy... busy meaning + * the volume could not be unmounted because there are open references + * the files or directories in the volume. When this failure occurred, + * the unmount logic setup a delay and this function is called as a result + * of that delay timeout. + * + * This function will attempt the unmount again. + * + * Input Parameters: + * Standard wdog timeout parameters + * + * Returned Value: + * None + * + ****************************************************************************/ + +static void automount_timeout(int argc, uint32_t arg1, ...) +{ + FAR struct automounter_state_s *priv = + (FAR struct automounter_state_s *)((uintptr_t)arg1); + int ret; + + DEBUGASSERT(argc == 1 && priv); + + /* Check the state of things. This timeout at the interrupt level and + * will cancel the timeout if there is any change in the insertion + * state. So we should still have the saved state as NOT inserted and + * there should be no pending work. + */ + + fllvdbg("inserted=%d\n", priv->inserted); + DEBUGASSERT(!priv->inserted && work_available(&priv->work)); + + /* Queue work to occur immediately. */ + + ret = work_queue(LPWORK, &priv->work, automount_worker, priv, 0); + if (ret < 0) + { + /* NOTE: Currently, work_cancel only returns success */ + + fdbg("ERROR: Failed to schedule work: %d\n", ret); + } +} + +/**************************************************************************** + * Name: automount_worker + * + * Description: + * Performs automount actions on the worker thread. + * + * Input Parameters: + * arg - Work argument set by work_queue() + * + * Returned Value: + * None + * + ****************************************************************************/ + +static void automount_worker(FAR void *arg) +{ + FAR struct automounter_state_s *priv = (FAR struct automounter_state_s *)arg; + FAR const struct automount_lower_s *lower; + + DEBUGASSERT(priv && priv->lower); + lower = priv->lower; + + /* Disable interrupts. We are commit now and everything must remain + * stable. + */ + + AUTOMOUNT_DISABLE(lower); + + /* Are we mounting or unmounting? */ + + if (priv->inserted) + { + /* We are mounting */ + + automount_mount(priv); + } + else + { + /* We are unmounting */ + + (void)automount_unmount(priv); + } + + /* Re-enable interrupts */ + + AUTOMOUNT_ENABLE(lower); +} + +/**************************************************************************** + * Name: automount_interrupt + * + * Description: + * Called (probably from the interrupt level) when a media change event + * has been detected. + * + * Input Parameters: + * lower - Persistent board configuration data + * arg - Data associated with the automounter + * inserted - True: Media has been inserted. False: media has been removed + * + * Returned Value: + * OK is returned on success; a negated errno value is returned on failure. + * + * Assumptions: + * Interrupts are disabled so that there is no race condition with the + * timer expiry. + * + ****************************************************************************/ + +static int automount_interrupt(FAR const struct automount_lower_s *lower, + FAR void *arg, bool inserted) +{ + FAR struct automounter_state_s *priv = (FAR struct automounter_state_s *)arg; + int ret; + + DEBUGASSERT(lower && priv && priv->lower == lower); + + fllvdbg("inserted=%d\n", inserted); + + /* Cancel any pending work. We could get called multiple times if, for + * example there is bounce in the detection mechanism. Work is performed + * the low priority work queue if it is available. + */ + + ret = work_cancel(LPWORK, &priv->work); + if (ret < 0) + { + /* NOTE: Currently, work_cancel only returns success */ + + fdbg("ERROR: Failed to cancel work: %d\n", ret); + } + + /* Set the media insertion/removal state */ + + priv->inserted = inserted; + + /* Queue work to occur after a delay. The delays performs debouncing: + * If the insertion/removal detection logic has "chatter", then we may + * receive this interrupt numerous times. Each time, the previous work + * will be cancelled (above) and the new work will scheduled with the + * delay. So the final mount operation will not be performed until the + * insertion state is stable for that delay. + */ + + ret = work_queue(LPWORK, &priv->work, automount_worker, priv, + priv->lower->ddelay); + if (ret < 0) + { + /* NOTE: Currently, work_cancel only returns success */ + + fdbg("ERROR: Failed to schedule work: %d\n", ret); + } + else + { + /* Cancel any retry delays */ + + (void)wd_cancel(priv->wdog); + } + + return OK; +} + +/**************************************************************************** + * Public Functions + ****************************************************************************/ + +/**************************************************************************** + * Name: automount_initialize + * + * Description: + * Configure the automounter. + * + * Input Parameters: + * lower - Persistent board configuration data + * + * Returned Value: + * A void* handle. The only use for this handle is with auto_uninitialize(). + * NULL is returned on any failure. + * + ****************************************************************************/ + +FAR void *automount_initialize(FAR const struct automount_lower_s *lower) +{ + FAR struct automounter_state_s *priv; + int ret; + + fvdbg("lower=%p\n", lower); + DEBUGASSERT(lower); + + /* Allocate an automounter state structure */ + + priv = (FAR struct automounter_state_s *) + kzalloc(sizeof(struct automounter_state_s)); + + if (!priv) + { + fdbg("ERROR: Failed to allocate state structure\n"); + return NULL; + } + + /* Initialize the automounter state structure */ + + priv->lower = lower; + + /* Get a timer to handle unmount retries */ + + priv->wdog = wd_create(); + if (!priv->wdog) + { + fdbg("ERROR: Failed to create a timer\n"); + auto_uninitialize(priv); + return NULL; + } + + /* Attach and enable automounter interrupts */ + + ret = AUTOMOUNT_ATTACH(lower, automount_interrupt, priv); + if (ret < 0) + { + fdbg("ERROR: Failed to attach automount interrupt: %d\n", ret); + auto_uninitialize(priv); + return NULL; + } + + AUTOMOUNT_ENABLE(lower); + return priv; +} + +/**************************************************************************** + * Name: auto_uninitialize + * + * Description: + * Stop the automounter and free resources that it used. + * + * Input Parameters: + * handle - The value previously returned by automount_initialize(); + * + * Returned Value: + * None + * + ****************************************************************************/ + +void auto_uninitialize(FAR void *handle) +{ + FAR struct automounter_state_s *priv = (FAR struct automounter_state_s *)handle; + FAR const struct automount_lower_s *lower; + + DEBUGASSERT(priv && priv->lower); + lower = priv->lower; + + /* Disable and detach interrupts */ + + AUTOMOUNT_DISABLE(lower); + (void)AUTOMOUNT_DETACH(lower); + + /* Release resources */ + + (void)wd_delete(priv->wdog); + + /* And free the state structure */ + + kfree(priv); +} diff --git a/include/nuttx/fs/automount.h b/include/nuttx/fs/automount.h new file mode 100644 index 0000000000..ab4a511991 --- /dev/null +++ b/include/nuttx/fs/automount.h @@ -0,0 +1,191 @@ +/**************************************************************************** + * include/nuttx/audio/automount.h + * + * Copyright (C) 2014 Gregory Nutt. All rights reserved. + * Author: Gregory Nutt + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * 3. Neither the name NuttX nor the names of its contributors may be + * used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS + * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED + * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN + * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + ****************************************************************************/ + +#ifndef __INCLUDE_NUTTX_AUDIO_AUTOMOUNT_H +#define __INCLUDE_NUTTX_AUDIO_AUTOMOUNT_H + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include +#include + +#include + +#ifdef CONFIG_FS_AUTOMOUNTER + +/**************************************************************************** + * Pre-processor Definitions + ****************************************************************************/ +/* Configuration ************************************************************ + * Automounter configuration + * CONFIG_FS_AUTOMOUNTER - Enables automount support + * + * Prequisites: + * CONFIG_SCHED_WORKQUEUE - Work queue support is required + * And others that would only matter if you are working in a very minimal + * configuration. + */ + +/* Helper macros ************************************************************/ + +#define AUTOMOUNT_ATTACH(s,isr,arg) ((s)->attach(s,isr,arg)) +#define AUTOMOUNT_DETACH(s) ((s)->attach(s,NULL,NULL)) +#define AUTOMOUNT_ENABLE(s) ((s)->enable(s,true)) +#define AUTOMOUNT_DISABLE(s) ((s)->enable(s,false)) + +/**************************************************************************** + * Public Types + ****************************************************************************/ +/* This is the type of the automount media change handler. The lower level + * code will intercept the interrupt and provide the upper level with the + * private data that was provided when the interrupt was attached and will + * also provide an indication if the media was inserted or removed. + */ + +struct automount_lower_s; /* Forward reference. Defined below */ + +typedef CODE int + (*automount_handler_t)(FAR const struct automount_lower_s *lower, + FAR void *arg, bool inserted); + +/* A reference to a structure of this type must be passed to the FS + * automounter. This structure provides information about the volume to be + * mounted and provides board-specific hooks. + * + * Memory for this structure is provided by the caller. It is not copied + * by the automounter and is presumed to persist while the automounter + * is active. + */ + +struct automount_lower_s +{ + /* Volume characterization */ + + FAR const char *fstype; /* Type of file system */ + FAR const char *blockdev; /* Path to the block device */ + FAR const char *mountpoint; /* Location to mount the volume */ + + /* Debounce delay in system clock ticks. Automount operation will not + * be performed until the insertion/removal state has been unchanges + * for this duration. + */ + + uint32_t ddelay; + + /* Unmount delay time in sysem clock ticks. If a volume has open + * references at the time that the media is removed, then we will be + * unable to unmount it. In that case, hopefully, the clients of the + * mount will eventually fail with file access errors and eventually close + * their references. So, at some time later, it should be possible to + * unmount the volume. This delay specifies the time between umount + * retries. + */ + + uint32_t udelay; + + /* Interrupt related operations all hidden behind callbacks to isolate the + * automounter from differences in interrupt handling by varying boards + * and MCUs. Board interrupts should be configured both insertion and + * removal of the media can be detected. + * + * attach - Attach or detach the media change interrupt handler to the + * board level interrupt + * enable - Enable or disable the media change interrupt + */ + + CODE int (*attach)(FAR const struct automount_lower_s *lower, + automount_handler_t isr, FAR void *arg); + CODE void (*enable)(FAR const struct automount_lower_s *lower, + bool enable); +}; + +/**************************************************************************** + * Public Data + ****************************************************************************/ + +#ifdef __cplusplus +#define EXTERN extern "C" +extern "C" +{ +#else +#define EXTERN extern +#endif + +/**************************************************************************** + * Public Function Prototypes + ****************************************************************************/ + +/**************************************************************************** + * Name: automount_initialize + * + * Description: + * Configure the automounter. + * + * Input Parameters: + * lower - Persistent board configuration data + * + * Returned Value: + * A void* handle. The only use for this handle is with auto_uninitialize(). + * NULL is returned on any failure. + * + ****************************************************************************/ + +FAR void *automount_initialize(FAR const struct automount_lower_s *lower); + +/**************************************************************************** + * Name: auto_uninitialize + * + * Description: + * Stop the automounter and free resources that it used. + * + * Input Parameters: + * handle - The value previously returned by automount_initialize(); + * + * Returned Value: + * None + * + ****************************************************************************/ + +void auto_uninitialize(FAR void *handle); + +#undef EXTERN +#ifdef __cplusplus +} +#endif + +#endif /* CONFIG_FS_AUTOMOUNTER */ +#endif /* __INCLUDE_NUTTX_AUDIO_AUTOMOUNT_H */