incubator-nuttx/fs/shm/shmfs.c

468 lines
12 KiB
C

/****************************************************************************
* 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 <assert.h>
#include <nuttx/fs/ioctl.h>
#include <nuttx/mm/map.h>
#if defined (CONFIG_BUILD_KERNEL)
#include <nuttx/arch.h>
#include <nuttx/pgalloc.h>
#include <nuttx/sched.h>
#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;
}