400 lines
7.2 KiB
C
400 lines
7.2 KiB
C
/*
|
|
* Copyright (c) 2024, Tenstorrent AI ULC
|
|
*
|
|
* SPDX-License-Identifier: Apache-2.0
|
|
*/
|
|
|
|
#undef _POSIX_C_SOURCE
|
|
#define _POSIX_C_SOURCE 200809L
|
|
|
|
#include <string.h>
|
|
#include <stdio.h>
|
|
|
|
#include <kernel_arch_interface.h>
|
|
#include <zephyr/kernel.h>
|
|
#include <zephyr/kernel/mm.h>
|
|
#include <zephyr/posix/fcntl.h>
|
|
#include <zephyr/posix/sys/mman.h>
|
|
#include <zephyr/posix/unistd.h>
|
|
#include <zephyr/sys/dlist.h>
|
|
#include <zephyr/sys/fdtable.h>
|
|
#include <zephyr/sys/hash_function.h>
|
|
|
|
#define _page_size COND_CODE_1(CONFIG_MMU, (CONFIG_MMU_PAGE_SIZE), (PAGE_SIZE))
|
|
|
|
static const struct fd_op_vtable shm_vtable;
|
|
|
|
static sys_dlist_t shm_list = SYS_DLIST_STATIC_INIT(&shm_list);
|
|
|
|
struct shm_obj {
|
|
uint8_t *mem;
|
|
sys_dnode_t node;
|
|
size_t refs;
|
|
size_t size;
|
|
uint32_t hash;
|
|
bool unlinked: 1;
|
|
bool mapped: 1;
|
|
};
|
|
|
|
static inline uint32_t hash32(const char *str, size_t n)
|
|
{
|
|
/* we need a hasher that is not sensitive to input alignment */
|
|
return sys_hash32_djb2(str, n);
|
|
}
|
|
|
|
static bool shm_obj_name_valid(const char *name, size_t len)
|
|
{
|
|
if (name == NULL) {
|
|
return false;
|
|
}
|
|
|
|
if (name[0] != '/') {
|
|
return false;
|
|
}
|
|
|
|
if (len < 2) {
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
static struct shm_obj *shm_obj_find(uint32_t key)
|
|
{
|
|
struct shm_obj *shm;
|
|
|
|
SYS_DLIST_FOR_EACH_CONTAINER(&shm_list, shm, node) {
|
|
if (shm->hash == key) {
|
|
return shm;
|
|
}
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
static void shm_obj_add(struct shm_obj *shm)
|
|
{
|
|
sys_dlist_init(&shm->node);
|
|
sys_dlist_append(&shm_list, &shm->node);
|
|
}
|
|
|
|
static void shm_obj_remove(struct shm_obj *shm)
|
|
{
|
|
sys_dlist_remove(&shm->node);
|
|
if (shm->size > 0) {
|
|
if (IS_ENABLED(CONFIG_MMU)) {
|
|
uintptr_t phys = 0;
|
|
|
|
if (arch_page_phys_get(shm->mem, &phys) == 0) {
|
|
k_mem_unmap(shm->mem, ROUND_UP(shm->size, _page_size));
|
|
}
|
|
} else {
|
|
k_free(shm->mem);
|
|
}
|
|
}
|
|
k_free(shm);
|
|
}
|
|
|
|
static int shm_fstat(struct shm_obj *shm, struct stat *st)
|
|
{
|
|
*st = (struct stat){0};
|
|
st->st_mode = ZVFS_MODE_IFSHM;
|
|
st->st_size = shm->size;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int shm_ftruncate(struct shm_obj *shm, off_t length)
|
|
{
|
|
void *virt;
|
|
|
|
if (length < 0) {
|
|
errno = EINVAL;
|
|
return -1;
|
|
}
|
|
|
|
if (length == 0) {
|
|
if (shm->size != 0) {
|
|
/* only allow resizing this once, for consistence */
|
|
errno = EBUSY;
|
|
return -1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
if (IS_ENABLED(CONFIG_MMU)) {
|
|
virt = k_mem_map(ROUND_UP(length, _page_size), K_MEM_PERM_RW);
|
|
} else {
|
|
virt = k_calloc(1, length);
|
|
}
|
|
|
|
if (virt == NULL) {
|
|
errno = ENOMEM;
|
|
return -1;
|
|
}
|
|
|
|
shm->mem = virt;
|
|
shm->size = length;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static off_t shm_lseek(struct shm_obj *shm, off_t offset, int whence, size_t cur)
|
|
{
|
|
size_t addend;
|
|
|
|
switch (whence) {
|
|
case SEEK_SET:
|
|
addend = 0;
|
|
break;
|
|
case SEEK_CUR:
|
|
addend = cur;
|
|
break;
|
|
case SEEK_END:
|
|
addend = shm->size;
|
|
break;
|
|
default:
|
|
errno = EINVAL;
|
|
return -1;
|
|
}
|
|
|
|
if ((INTPTR_MAX - addend) < offset) {
|
|
errno = EOVERFLOW;
|
|
return -1;
|
|
}
|
|
|
|
offset += addend;
|
|
if (offset < 0) {
|
|
errno = EINVAL;
|
|
return -1;
|
|
}
|
|
|
|
return offset;
|
|
}
|
|
|
|
static int shm_mmap(struct shm_obj *shm, void *addr, size_t len, int prot, int flags, off_t off,
|
|
void **virt)
|
|
{
|
|
ARG_UNUSED(addr);
|
|
ARG_UNUSED(prot);
|
|
__ASSERT_NO_MSG(virt != NULL);
|
|
|
|
if ((len == 0) || (off < 0) || ((flags & MAP_FIXED) != 0) ||
|
|
((off & (_page_size - 1)) != 0) || ((len + off) > shm->size)) {
|
|
errno = EINVAL;
|
|
return -1;
|
|
}
|
|
|
|
if (!IS_ENABLED(CONFIG_MMU)) {
|
|
errno = ENOTSUP;
|
|
return -1;
|
|
}
|
|
|
|
if (shm->mem == NULL) {
|
|
errno = ENOMEM;
|
|
return -1;
|
|
}
|
|
|
|
/*
|
|
* Note: due to Zephyr's page mapping algorithm, physical pages can only have 1
|
|
* mapping, so different file handles will have the same virtual memory address
|
|
* underneath.
|
|
*/
|
|
*virt = shm->mem + off;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static ssize_t shm_rw(struct shm_obj *shm, void *buf, size_t size, bool is_write, size_t offset)
|
|
{
|
|
if (offset >= shm->size) {
|
|
size = 0;
|
|
} else {
|
|
size = MIN(size, shm->size - offset);
|
|
}
|
|
|
|
if (size > 0) {
|
|
if (is_write) {
|
|
memcpy(&shm->mem[offset], buf, size);
|
|
} else {
|
|
memcpy(buf, &shm->mem[offset], size);
|
|
}
|
|
}
|
|
|
|
return size;
|
|
}
|
|
|
|
static ssize_t shm_read(void *obj, void *buf, size_t sz, size_t offset)
|
|
{
|
|
return shm_rw((struct shm_obj *)obj, buf, sz, false, offset);
|
|
}
|
|
|
|
static ssize_t shm_write(void *obj, const void *buf, size_t sz, size_t offset)
|
|
{
|
|
return shm_rw((struct shm_obj *)obj, (void *)buf, sz, true, offset);
|
|
}
|
|
|
|
static int shm_close(void *obj)
|
|
{
|
|
struct shm_obj *shm = obj;
|
|
|
|
shm->refs -= (shm->refs > 0) ? 1 : 0;
|
|
if (shm->unlinked && (shm->refs == 0)) {
|
|
shm_obj_remove(shm);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int shm_ioctl(void *obj, unsigned int request, va_list args)
|
|
{
|
|
struct shm_obj *shm = obj;
|
|
|
|
switch (request) {
|
|
case ZFD_IOCTL_LSEEK: {
|
|
off_t offset = va_arg(args, off_t);
|
|
int whence = va_arg(args, int);
|
|
size_t cur = va_arg(args, size_t);
|
|
|
|
return shm_lseek(shm, offset, whence, cur);
|
|
} break;
|
|
case ZFD_IOCTL_MMAP: {
|
|
void *addr = va_arg(args, void *);
|
|
size_t len = va_arg(args, size_t);
|
|
int prot = va_arg(args, int);
|
|
int flags = va_arg(args, int);
|
|
off_t off = va_arg(args, off_t);
|
|
void **maddr = va_arg(args, void **);
|
|
|
|
return shm_mmap(shm, addr, len, prot, flags, off, maddr);
|
|
} break;
|
|
case ZFD_IOCTL_SET_LOCK:
|
|
break;
|
|
case ZFD_IOCTL_STAT: {
|
|
struct stat *st = va_arg(args, struct stat *);
|
|
|
|
return shm_fstat(shm, st);
|
|
} break;
|
|
case ZFD_IOCTL_TRUNCATE: {
|
|
off_t length = va_arg(args, off_t);
|
|
|
|
return shm_ftruncate(shm, length);
|
|
} break;
|
|
default:
|
|
errno = ENOTSUP;
|
|
return -1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static const struct fd_op_vtable shm_vtable = {
|
|
.read_offs = shm_read,
|
|
.write_offs = shm_write,
|
|
.close = shm_close,
|
|
.ioctl = shm_ioctl,
|
|
};
|
|
|
|
int shm_open(const char *name, int oflag, mode_t mode)
|
|
{
|
|
int fd;
|
|
uint32_t key;
|
|
struct shm_obj *shm;
|
|
bool rd = (oflag & O_RDONLY) != 0;
|
|
bool rw = (oflag & O_RDWR) != 0;
|
|
bool creat = (oflag & O_CREAT) != 0;
|
|
bool excl = (oflag & O_EXCL) != 0;
|
|
bool trunc = false; /* (oflag & O_TRUNC) != 0 */
|
|
size_t name_len = (name == NULL) ? 0 : strnlen(name, PATH_MAX);
|
|
|
|
/* revisit when file-based permissions are available */
|
|
if ((mode & 0777) == 0) {
|
|
errno = EINVAL;
|
|
return -1;
|
|
}
|
|
|
|
if (!(rd ^ rw)) {
|
|
errno = EINVAL;
|
|
return -1;
|
|
}
|
|
|
|
if (rd && trunc) {
|
|
errno = EINVAL;
|
|
return -1;
|
|
}
|
|
|
|
if (!shm_obj_name_valid(name, name_len)) {
|
|
errno = EINVAL;
|
|
return -1;
|
|
}
|
|
|
|
fd = zvfs_reserve_fd();
|
|
if (fd < 0) {
|
|
errno = EMFILE;
|
|
return -1;
|
|
}
|
|
|
|
key = hash32(name, name_len);
|
|
shm = shm_obj_find(key);
|
|
if ((shm != NULL) && shm->unlinked) {
|
|
/* we cannot open a shm object that has already been unlinked */
|
|
errno = EACCES;
|
|
return -1;
|
|
}
|
|
|
|
if (creat) {
|
|
if ((shm != NULL) && excl) {
|
|
zvfs_free_fd(fd);
|
|
errno = EEXIST;
|
|
return -1;
|
|
}
|
|
|
|
if (shm == NULL) {
|
|
shm = k_calloc(1, sizeof(*shm));
|
|
if (shm == NULL) {
|
|
zvfs_free_fd(fd);
|
|
errno = ENOSPC;
|
|
return -1;
|
|
}
|
|
|
|
shm->hash = key;
|
|
shm_obj_add(shm);
|
|
}
|
|
} else if (shm == NULL) {
|
|
errno = ENOENT;
|
|
return -1;
|
|
}
|
|
|
|
++shm->refs;
|
|
zvfs_finalize_typed_fd(fd, shm, &shm_vtable, ZVFS_MODE_IFSHM);
|
|
|
|
return fd;
|
|
}
|
|
|
|
int shm_unlink(const char *name)
|
|
{
|
|
uint32_t key;
|
|
struct shm_obj *shm;
|
|
size_t name_len = (name == NULL) ? 0 : strnlen(name, PATH_MAX);
|
|
|
|
if (!shm_obj_name_valid(name, name_len)) {
|
|
errno = EINVAL;
|
|
return -1;
|
|
}
|
|
|
|
key = hash32(name, name_len);
|
|
shm = shm_obj_find(key);
|
|
if ((shm == NULL) || shm->unlinked) {
|
|
errno = ENOENT;
|
|
return -1;
|
|
}
|
|
|
|
shm->unlinked = true;
|
|
if (shm->refs == 0) {
|
|
shm_obj_remove(shm);
|
|
}
|
|
|
|
return 0;
|
|
}
|