468 lines
12 KiB
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;
|
|
}
|