/* * Copyright (c) 2024, Tenstorrent AI ULC * * SPDX-License-Identifier: Apache-2.0 */ #undef _POSIX_C_SOURCE #define _POSIX_C_SOURCE 200809L #include #include #include #include #include #include #include #include #include #include #include #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; }