diff --git a/fs/shm/Kconfig b/fs/shm/Kconfig index b0f75d129fe..7ec28209a0b 100644 --- a/fs/shm/Kconfig +++ b/fs/shm/Kconfig @@ -3,16 +3,16 @@ # see the file kconfig-language.txt in the NuttX tools repository. # -config FS_SHM +config FS_SHMFS bool "Shared memory support" default n - depends on MM_SHM + select ARCH_VMA_MAPPING if BUILD_KERNEL ---help--- Include support for shm_open() and shm_close. -if FS_SHM +if FS_SHMFS -config FS_SHM_VFS_PATH +config FS_SHMFS_VFS_PATH string "Path to shared memory object storage" default "/var/shm" ---help--- diff --git a/fs/shm/Make.defs b/fs/shm/Make.defs index dfcfed0d81a..f686789c6a6 100644 --- a/fs/shm/Make.defs +++ b/fs/shm/Make.defs @@ -18,13 +18,11 @@ # ############################################################################ -# Include POSIX message queue support +ifeq ($(CONFIG_FS_SHMFS),y) -ifeq ($(CONFIG_FS_SHM),y) +CSRCS += shm_open.c shm_unlink.c shmfs.c shmfs_alloc.c -CSRCS += shm_open.c shm_close.c - -# Include POSIX message queue build support +# Include POSIX shm build support DEPPATH += --dep-path shm VPATH += :shm diff --git a/fs/shm/shm_open.c b/fs/shm/shm_open.c new file mode 100644 index 00000000000..3f4df72fb23 --- /dev/null +++ b/fs/shm/shm_open.c @@ -0,0 +1,181 @@ +/**************************************************************************** + * fs/shm/shm_open.c + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. The + * ASF licenses this file to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the + * License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + ****************************************************************************/ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include +#include +#include +#include + +#include "inode/inode.h" +#include "shm/shmfs.h" + +/**************************************************************************** + * Private Functions + ****************************************************************************/ + +static int file_shm_open(FAR struct file *shm, FAR const char *name, + int oflags, mode_t mode) +{ + FAR struct inode *inode; + struct inode_search_s desc; + char fullpath[sizeof(CONFIG_FS_SHMFS_VFS_PATH) + CONFIG_NAME_MAX + 2]; + int ret; + + /* Make sure that a non-NULL name is supplied */ + + if (!shm || !name) + { + return -EINVAL; + } + + /* Remove any number of leading '/' */ + + while (*name == '/') + { + name++; + } + + /* Empty name supplied? */ + + if (*name == '\0') + { + return -EINVAL; + } + + /* Name too long? */ + + if (strnlen(name, CONFIG_NAME_MAX + 1) > CONFIG_NAME_MAX) + { + return -ENAMETOOLONG; + } + + /* Get the full path to the shm object */ + + snprintf(fullpath, sizeof(fullpath), + CONFIG_FS_SHMFS_VFS_PATH "/%s", name); + + /* Get the inode for this shm object */ + + SETUP_SEARCH(&desc, fullpath, false); + + ret = inode_lock(); + if (ret < 0) + { + goto errout_with_search; + } + + ret = inode_find(&desc); + if (ret >= 0) + { + /* Something exists at this path. Get the search results */ + + inode = desc.node; + + /* Verify that the inode is an shm object */ + + if (!INODE_IS_SHM(inode)) + { + ret = -EINVAL; + inode_release(inode); + goto errout_with_sem; + } + + /* It exists and is an shm object. Check if the caller wanted to + * create a new object with this name. + */ + + if ((oflags & (O_CREAT | O_EXCL)) == (O_CREAT | O_EXCL)) + { + ret = -EEXIST; + inode_release(inode); + goto errout_with_sem; + } + } + else + { + /* The shm does not exist. Were we asked to create it? */ + + if ((oflags & O_CREAT) == 0) + { + /* The shm does not exist and O_CREAT is not set */ + + ret = -ENOENT; + goto errout_with_sem; + } + + /* Create an inode in the pseudo-filesystem at this path */ + + ret = inode_reserve(fullpath, mode, &inode); + if (ret < 0) + { + goto errout_with_sem; + } + + INODE_SET_SHM(inode); + inode->u.i_ops = &shmfs_operations; + inode->i_private = NULL; + inode->i_crefs = 1; + } + + /* Associate the inode with a file structure */ + + shm->f_oflags = oflags; + shm->f_pos = 0; + shm->f_inode = inode; + shm->f_priv = NULL; + +errout_with_sem: + inode_unlock(); +errout_with_search: + RELEASE_SEARCH(&desc); + return ret; +} + +/**************************************************************************** + * Public Functions + ****************************************************************************/ + +int shm_open(FAR const char *name, int oflag, mode_t mode) +{ + struct file shm; + int ret; + + ret = file_shm_open(&shm, name, oflag, mode); + if (ret < 0) + { + set_errno(-ret); + return -1; + } + + ret = file_allocate(shm.f_inode, shm.f_oflags, shm.f_pos, shm.f_priv, 0, + false); + if (ret < 0) + { + set_errno(-ret); + file_close(&shm); + return -1; + } + + return ret; +} diff --git a/fs/shm/shm_unlink.c b/fs/shm/shm_unlink.c new file mode 100644 index 00000000000..64c95629c50 --- /dev/null +++ b/fs/shm/shm_unlink.c @@ -0,0 +1,162 @@ +/**************************************************************************** + * fs/shm/shm_unlink.c + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. The + * ASF licenses this file to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the + * License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + ****************************************************************************/ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include +#include +#include + +#include "inode/inode.h" + +/**************************************************************************** + * Private Functions + ****************************************************************************/ + +static int file_shm_unlink(FAR const char *name) +{ + FAR struct inode *inode; + struct inode_search_s desc; + char fullpath[sizeof(CONFIG_FS_SHMFS_VFS_PATH) + CONFIG_NAME_MAX + 2]; + int ret; + + /* Make sure that a non-NULL name is supplied */ + + if (!name) + { + return -ENOENT; + } + + /* Remove any number of leading '/' */ + + while (*name == '/') + { + name++; + } + + /* Empty name supplied? */ + + if (*name == '\0') + { + return -ENOENT; + } + + /* Name too long? */ + + if (strnlen(name, CONFIG_NAME_MAX + 1) > CONFIG_NAME_MAX) + { + return -ENAMETOOLONG; + } + + /* Get the full path to the shm object */ + + snprintf(fullpath, sizeof(fullpath), + CONFIG_FS_SHMFS_VFS_PATH "/%s", name); + + /* Get the inode for this shm object */ + + SETUP_SEARCH(&desc, fullpath, false); + + ret = inode_lock(); + if (ret < 0) + { + goto errout_with_search; + } + + ret = inode_find(&desc); + if (ret < 0) + { + /* There is no inode that includes in this path */ + + goto errout_with_sem; + } + + /* Get the search results */ + + inode = desc.node; + DEBUGASSERT(inode != NULL); + + /* Verify that what we found is, indeed, an shm inode */ + + if (!INODE_IS_SHM(inode)) + { + ret = -ENOENT; + goto errout_with_inode; + } + +#ifndef CONFIG_DISABLE_PSEUDOFS_OPERATIONS + if (inode->u.i_ops->unlink) + { + /* Notify the shmfs driver that it has been unlinked */ + + ret = inode->u.i_ops->unlink(inode); + if (ret < 0) + { + goto errout_with_inode; + } + } +#endif + + /* Remove the old inode from the tree. If we hold a reference count + * on the inode, it will not be deleted now. This will set the + * FSNODEFLAG_DELETED bit in the inode flags. + */ + + ret = inode_remove(fullpath); + + /* inode_remove() should always fail with -EBUSY because we have a + * reference on the inode. -EBUSY means that the inode was, indeed, + * unlinked but it could not be freed because there are references. + */ + + if (ret == -EBUSY) + { + ret = OK; + } + + DEBUGASSERT(ret == OK); + +errout_with_inode: + inode_release(inode); +errout_with_sem: + inode_unlock(); +errout_with_search: + RELEASE_SEARCH(&desc); + return ret; +} + +/**************************************************************************** + * Public Functions + ****************************************************************************/ + +int shm_unlink(FAR const char *name) +{ + int ret = file_shm_unlink(name); + + if (ret < 0) + { + set_errno(-ret); + return -1; + } + + return 0; +} diff --git a/fs/shm/shmfs.c b/fs/shm/shmfs.c new file mode 100644 index 00000000000..0a7cb5953a8 --- /dev/null +++ b/fs/shm/shmfs.c @@ -0,0 +1,383 @@ +/**************************************************************************** + * fs/shm/shmfs.c + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. The + * ASF licenses this file to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the + * License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + ****************************************************************************/ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include + +#include +#include + +#if defined (CONFIG_BUILD_KERNEL) +#include +#include +#include +#endif + +#include "shm/shmfs.h" +#include "inode/inode.h" + +/**************************************************************************** + * Private Function Prototypes + ****************************************************************************/ + +static int shmfs_close(FAR struct file *filep); +static ssize_t shmfs_read(FAR struct file *filep, FAR char *buffer, + size_t buflen); +static ssize_t shmfs_write(FAR struct file *filep, FAR const char *buffer, + size_t buflen); +static int shmfs_truncate(FAR struct file *filep, off_t length); + +#ifndef CONFIG_DISABLE_PSEUDOFS_OPERATIONS +static int shmfs_unlink(FAR struct inode *inode); +#endif + +static int shmfs_mmap(FAR struct file *filep, + FAR struct mm_map_entry_s *entry); +static int shmfs_munmap(FAR struct task_group_s *group, + FAR struct mm_map_entry_s *entry, + FAR void *start, + size_t length); + +/**************************************************************************** + * Public Data + ****************************************************************************/ + +const struct file_operations shmfs_operations = +{ + NULL, /* open */ + shmfs_close, /* close */ + shmfs_read, /* read */ + shmfs_write, /* write */ + NULL, /* seek */ + NULL, /* ioctl */ + shmfs_mmap, /* mmap */ + shmfs_truncate, /* truncate */ + NULL, /* poll */ +#ifndef CONFIG_DISABLE_PSEUDOFS_OPERATIONS + shmfs_unlink /* unlink */ +#endif +}; + +/**************************************************************************** + * Private Functions + ****************************************************************************/ + +/**************************************************************************** + * Name: shmfs_read + ****************************************************************************/ + +static ssize_t shmfs_read(FAR struct file *filep, FAR char *buffer, + size_t buflen) +{ + return -ENOSYS; +} + +/**************************************************************************** + * Name: shmfs_write + ****************************************************************************/ + +static ssize_t shmfs_write(FAR struct file *filep, FAR const char *buffer, + size_t buflen) +{ + return -ENOSYS; +} + +/**************************************************************************** + * Name: shmfs_release + ****************************************************************************/ + +static int shmfs_release(FAR struct inode *inode) +{ + /* If the file has been unlinked previously, delete the contents. + * The inode is released after this call, hence checking if i_crefs <= 1. + */ + + int ret = inode_lock(); + if (ret >= 0) + { + if (inode->i_parent == NULL && + inode->i_crefs <= 1) + { + shmfs_free_object(inode->i_private); + inode->i_private = NULL; + ret = OK; + } + + inode_unlock(); + } + + return ret; +} + +/**************************************************************************** + * Name: shmfs_close + ****************************************************************************/ + +static int shmfs_close(FAR struct file *filep) +{ + /* Release the shmfs object. The object gets deleted if no-one has + * reference to it (either mmap or open file) and the object has been + * unlinked + */ + + return shmfs_release(filep->f_inode); +} + +/**************************************************************************** + * Name: shmfs_truncate + ****************************************************************************/ + +static int shmfs_truncate(FAR struct file *filep, off_t length) +{ + FAR struct shmfs_object_s *object; + int ret; + + if (length == 0) + { + return -EINVAL; + } + + ret = inode_lock(); + if (ret >= 0) + { + object = filep->f_inode->i_private; + if (!object) + { + filep->f_inode->i_private = shmfs_alloc_object(length); + if (!filep->f_inode->i_private) + { + ret = -EFAULT; + } + } + else if (object->length != length) + { + /* This doesn't support resize */ + + ret = -EINVAL; + } + + inode_unlock(); + } + + return ret; +} + +/**************************************************************************** + * Name: shmfs_unlink + ****************************************************************************/ + +#ifndef CONFIG_DISABLE_PSEUDOFS_OPERATIONS +static int shmfs_unlink(FAR struct inode *inode) +{ + int ret = inode_lock(); + + if (ret >= 0) + { + if (inode->i_crefs <= 1) + { + shmfs_free_object(inode->i_private); + inode->i_private = NULL; + } + + inode_unlock(); + } + + return ret; +} +#endif + +/**************************************************************************** + * Name: shmfs_map_object + ****************************************************************************/ + +static int shmfs_map_object(FAR struct shmfs_object_s *object, + FAR void **vaddr) +{ + int ret = OK; + +#ifdef CONFIG_BUILD_KERNEL + /* Map the physical pages of the shm object with MMU. */ + + FAR struct tcb_s *tcb = nxsched_self(); + FAR struct task_group_s *group = tcb->group; + FAR uintptr_t *pages = (FAR uintptr_t *)&object->paddr; + uintptr_t mapaddr; + unsigned int npages; + + /* Find a free vaddr space that satisfies length */ + + mapaddr = (uintptr_t)vm_alloc_region(get_group_mm(group), 0, + object->length); + if (mapaddr == 0) + { + return -ENOMEM; + } + + /* Convert the region size to pages */ + + npages = MM_NPAGES(object->length); + + /* Map the memory to user virtual address space */ + + ret = up_shmat(pages, npages, mapaddr); + if (ret < 0) + { + vm_release_region(get_group_mm(group), (FAR void *)mapaddr, + object->length); + } + else + { + *vaddr = (FAR void *)mapaddr; + } +#else + /* Use the physical address directly */ + + *vaddr = object->paddr; +#endif + + return ret; +} + +/**************************************************************************** + * Name: shmfs_mmap + ****************************************************************************/ + +static int shmfs_mmap(FAR struct file *filep, + FAR struct mm_map_entry_s *entry) +{ + FAR struct shmfs_object_s *object; + int ret = -EINVAL; + + DEBUGASSERT(filep->f_inode != NULL); + + /* We don't support offset at the moment, just mapping the whole object + * object is NULL if it hasn't been truncated yet + */ + + if (entry->offset != 0) + { + return ret; + } + + /* Keep the inode when mmapped, increase refcount */ + + ret = inode_addref(filep->f_inode); + if (ret >= 0) + { + object = (FAR struct shmfs_object_s *)filep->f_inode->i_private; + if (object) + { + ret = shmfs_map_object(object, &entry->vaddr); + } + else + { + ret = -EINVAL; + } + + if (ret < 0) + { + inode_release(filep->f_inode); + } + else + { + entry->munmap = shmfs_munmap; + entry->priv.p = (FAR void *)filep->f_inode; + mm_map_add(entry); + } + } + + return ret; +} + +/**************************************************************************** + * Name: shmfs_unmap_object + ****************************************************************************/ + +static int shmfs_unmap_area(FAR struct task_group_s *group, + FAR void *vaddr, size_t length) +{ + int ret = OK; + +#ifdef CONFIG_BUILD_KERNEL + unsigned int npages; + + /* Convert the region size to pages */ + + if (group) + { + npages = MM_NPAGES(length); + + /* Unmap the memory from user virtual address space */ + + ret = up_shmdt((uintptr_t)vaddr, npages); + + /* Add the virtual memory back to the shared memory pool */ + + vm_release_region(get_group_mm(group), vaddr, length); + } +#endif + + return ret; +} + +/**************************************************************************** + * Name: shmfs_munmap + ****************************************************************************/ + +static int shmfs_munmap(FAR struct task_group_s *group, + FAR struct mm_map_entry_s *entry, + FAR void *start, + size_t length) +{ + int ret; + + /* Partial unmap is not supported yet */ + + if (start != entry->vaddr || length != entry->length) + { + return -EINVAL; + } + + /* Unmap the virtual memory area from the user's address space */ + + ret = shmfs_unmap_area(group, entry->vaddr, entry->length); + + /* Release the shmfs object. The object gets deleted if no-one has + * reference to it (either mmap or open file) and the object has been + * unlinked + */ + + if (ret == OK) + { + ret = shmfs_release((FAR struct inode *)entry->priv.p); + } + + /* Remove the mapping. */ + + if (ret == OK) + { + ret = mm_map_remove(get_group_mm(group), entry); + } + + return ret; +} diff --git a/fs/shm/shmfs.h b/fs/shm/shmfs.h new file mode 100644 index 00000000000..4c4c50cf42c --- /dev/null +++ b/fs/shm/shmfs.h @@ -0,0 +1,70 @@ +/**************************************************************************** + * fs/shm/shmfs.h + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. The + * ASF licenses this file to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the + * License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + ****************************************************************************/ + +#ifndef __FS_SHM_SHMFS_H +#define __FS_SHM_SHMFS_H + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include + +/**************************************************************************** + * Public Data + ****************************************************************************/ + +extern const struct file_operations shmfs_operations; + +/**************************************************************************** + * Public Types + ****************************************************************************/ + +struct shmfs_object_s +{ + /* Total number of bytes needed from physical memory. */ + + size_t length; + + /* Vector of allocations from physical memory. + * + * - In flat and protected builds this is a pointer to the + * allocated memory. + * + * - In kernel build this is start of a malloc'd vector of void pointers + * and the length of the vector is MM_NPAGES(length). + */ + + FAR void *paddr; + + /* The struct continues here as malloc'd array of void pointers + * if CONFIG_BUILD_KERNEL + */ +}; + +/**************************************************************************** + * Public Function Prototypes + ****************************************************************************/ + +FAR struct shmfs_object_s *shmfs_alloc_object(size_t length); + +void shmfs_free_object(FAR struct shmfs_object_s *object); + +#endif diff --git a/fs/shm/shmfs_alloc.c b/fs/shm/shmfs_alloc.c new file mode 100644 index 00000000000..7a396705c67 --- /dev/null +++ b/fs/shm/shmfs_alloc.c @@ -0,0 +1,143 @@ +/**************************************************************************** + * fs/shm/shmfs_alloc.c + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. The + * ASF licenses this file to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the + * License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + ****************************************************************************/ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include +#include +#include + +#include "shm/shmfs.h" + +/**************************************************************************** + * Public Functions + ****************************************************************************/ + +FAR struct shmfs_object_s *shmfs_alloc_object(size_t length) +{ + FAR struct shmfs_object_s *object; + bool allocated = false; + +#if defined(CONFIG_BUILD_FLAT) + /* in FLAT build, allocate the object metadata and the data in the same + * chunk in kernel heap + */ + + object = kmm_zalloc(sizeof(struct shmfs_object_s) + length); + if (object) + { + object->paddr = (FAR char *)object + sizeof(struct shmfs_object_s); + allocated = true; + } + +#elif defined(CONFIG_BUILD_PROTECTED) + /* in PROTECTED build, allocate the shm object in kernel heap, and shared + * memory in user heap + */ + + object = kmm_zalloc(sizeof(struct shmfs_object_s)); + if (object) + { + object->paddr = kumm_zalloc(length); + + if (object->paddr) + { + allocated = true; + } + } + +#elif defined(CONFIG_BUILD_KERNEL) + /* in KERNEL build, allocate the shared memory from page pool and store the + * physical address + */ + + size_t i = 0; + FAR void **pages; + size_t n_pages = MM_NPAGES(length); + + object = kmm_zalloc(sizeof(struct shmfs_object_s) + + (n_pages - 1) * sizeof(object->paddr)); + + if (object) + { + pages = &object->paddr; + for (; i < n_pages; i++) + { + pages[i] = (FAR void *)mm_pgalloc(1); + if (!pages[i]) + { + break; + } + } + } + + if (i == n_pages) + { + allocated = true; + } +#endif + + if (allocated) + { + object->length = length; + } + else + { + /* delete any partial allocation */ + + shmfs_free_object(object); + object = NULL; + } + + return object; +} + +void shmfs_free_object(FAR struct shmfs_object_s *object) +{ +#if defined(CONFIG_BUILD_KERNEL) + size_t i; + size_t n_pages = MM_NPAGES(object->length); + FAR void **pages; +#endif + + if (object) + { +#if defined (CONFIG_BUILD_PROTECTED) + kumm_free(object->paddr); +#elif defined(CONFIG_BUILD_KERNEL) + pages = &object->paddr; + for (i = 0; i < n_pages; i++) + { + if (pages[i]) + { + mm_pgfree((uintptr_t)pages[i], 1); + } + } +#endif + + /* Delete the object metadata + * (and the shared memory in case of FLAT build) + */ + + kmm_free(object); + } +}