/**************************************************************************** * 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 g_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) { FAR struct shmfs_object_s *sho; ssize_t nread; off_t startpos; off_t endpos; DEBUGASSERT(filep->f_inode->i_private != NULL); sho = filep->f_inode->i_private; if (filep->f_pos > sho->length) { return 0; } /* Handle attempts to read beyond the end of the file. */ startpos = filep->f_pos; nread = buflen; endpos = startpos + buflen; if (endpos > sho->length) { endpos = sho->length; nread = endpos - startpos; } /* Copy data from the memory object to the user buffer */ if (sho->paddr != NULL) { memcpy(buffer, (FAR char *)sho->paddr + startpos, nread); filep->f_pos += nread; } else { DEBUGASSERT(sho->length == 0 && nread == 0); } return nread; } /**************************************************************************** * Name: shmfs_write ****************************************************************************/ static ssize_t shmfs_write(FAR struct file *filep, FAR const char *buffer, size_t buflen) { FAR struct shmfs_object_s *sho; ssize_t nwritten; off_t startpos; off_t endpos; DEBUGASSERT(filep->f_inode->i_private != NULL); sho = filep->f_inode->i_private; /* Handle attempts to write beyond the end of the file */ startpos = filep->f_pos; nwritten = buflen; endpos = startpos + buflen; /* Desn't support shm auto expand, truncate first */ if (endpos > sho->length) { return -EFBIG; } /* Copy data from the user buffer to the memory object */ if (sho->paddr != NULL) { memcpy((FAR char *)sho->paddr + startpos, buffer, nwritten); filep->f_pos += nwritten; } else { DEBUGASSERT(sho->length == 0 && nwritten == 0); } return nwritten; } /**************************************************************************** * 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_add_map ****************************************************************************/ static int shmfs_add_map(FAR struct mm_map_entry_s *entry, FAR struct inode *inode) { entry->munmap = shmfs_munmap; entry->priv.p = (FAR void *)inode; return mm_map_add(get_current_mm(), entry); } /**************************************************************************** * 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; /* 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 = filep->f_inode->i_private; if (object) { ret = shmfs_map_object(object, &entry->vaddr); } else { ret = -EINVAL; } if (ret < 0 || (ret = shmfs_add_map(entry, filep->f_inode)) < 0) { inode_release(filep->f_inode); } } 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) { FAR struct inode *inode; int ret; /* Partial unmap is not supported yet */ if (start != entry->vaddr || length != entry->length) { return -EINVAL; } inode = (FAR struct inode *)entry->priv.p; /* 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(inode); } /* Unkeep the inode when unmapped, decrease refcount */ if (ret == OK) { inode_release(inode); /* Remove the mapping. */ ret = mm_map_remove(get_group_mm(group), entry); } return ret; }