blob: 170902eeae182604c93346474863e2467f983e01 [file] [log] [blame]
/*
* Copyright (c) 2024, Tenstorrent AI ULC
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <sys/mman.h>
#include <zephyr/kernel.h>
#include <zephyr/net/socket.h>
#include <zephyr/posix/fcntl.h>
#include <zephyr/sys/fdtable.h>
#include <zephyr/ztest.h>
#define SHM_SIZE 8
#define VALID_SHM_PATH "/foo"
#define INVALID_SHM_PATH "foo"
#define EMPTY_SHM_PATH ""
#define TOO_SHORT_SHM_PATH "/"
#define INVALID_MODE 0
#define VALID_MODE 0666
#define INVALID_FLAGS 0
#define VALID_FLAGS (O_RDWR | O_CREAT)
#define CREATE_FLAGS VALID_FLAGS
#define OPEN_FLAGS (VALID_FLAGS & ~O_CREAT)
/* account for stdin, stdout, stderr */
#define N (CONFIG_ZVFS_OPEN_MAX - 3)
/* we need to have at least 2 shared memory objects */
BUILD_ASSERT(N >= 2, "CONFIG_ZVFS_OPEN_MAX must be > 4");
#define S_TYPEISSHM(st) (((st)->st_mode & ZVFS_MODE_IFMT) == ZVFS_MODE_IFSHM)
ZTEST(xsi_realtime, test_shm_open)
{
int ret;
int fd[N];
struct stat st;
{
/* degenerate error cases */
zassert_not_ok(shm_open(NULL, INVALID_FLAGS, INVALID_MODE));
zassert_not_ok(shm_open(NULL, INVALID_FLAGS, VALID_MODE));
zassert_not_ok(shm_open(NULL, VALID_FLAGS, INVALID_MODE));
zassert_not_ok(shm_open(NULL, VALID_FLAGS, VALID_MODE));
zassert_not_ok(shm_open(INVALID_SHM_PATH, VALID_FLAGS, VALID_MODE));
zassert_not_ok(shm_open(EMPTY_SHM_PATH, VALID_FLAGS, VALID_MODE));
zassert_not_ok(shm_open(TOO_SHORT_SHM_PATH, VALID_FLAGS, VALID_MODE));
zassert_not_ok(shm_open(VALID_SHM_PATH, INVALID_FLAGS, INVALID_MODE));
zassert_not_ok(shm_open(VALID_SHM_PATH, INVALID_FLAGS, VALID_MODE));
zassert_not_ok(shm_open(VALID_SHM_PATH, VALID_FLAGS, INVALID_MODE));
}
/* open / close 1 file descriptor referring to VALID_SHM_PATH */
fd[0] = shm_open(VALID_SHM_PATH, VALID_FLAGS, VALID_MODE);
zassert_true(fd[0] >= 0, "shm_open(%s, %x, %04o) failed: %d", VALID_SHM_PATH, VALID_FLAGS,
VALID_MODE, errno);
/* should have size 0 and be a shared memory object */
zassert_ok(fstat(fd[0], &st));
zassert_equal(st.st_size, 0);
zassert_true(S_TYPEISSHM(&st));
/* technically, the order of close / shm_unlink can be reversed too */
zassert_ok(close(fd[0]));
ret = shm_unlink(VALID_SHM_PATH);
zassert_true(ret == 0 || (ret == -1 && errno == ENOENT),
"unexpected return / errno from shm_unlink: %d / %d", ret, errno);
/* open / close N file descriptors referring to VALID_SHM_PATH */
for (size_t i = 0; i < N; ++i) {
fd[i] = shm_open(VALID_SHM_PATH, i == 0 ? CREATE_FLAGS : OPEN_FLAGS, VALID_MODE);
zassert_true(fd[i] >= 0, "shm_open(%s, %x, %04o) failed: %d", VALID_SHM_PATH,
VALID_FLAGS, VALID_MODE, errno);
}
zassert_ok(shm_unlink(VALID_SHM_PATH));
for (size_t i = N; i > 0; --i) {
zassert_ok(close(fd[i - 1]));
}
}
ZTEST(xsi_realtime, test_shm_unlink)
{
int fd;
{
/* degenerate error cases */
zassert_not_ok(shm_unlink(NULL));
zassert_not_ok(shm_unlink(INVALID_SHM_PATH));
zassert_not_ok(shm_unlink(EMPTY_SHM_PATH));
zassert_not_ok(shm_unlink(TOO_SHORT_SHM_PATH));
}
/* open / close 1 file descriptor referring to VALID_SHM_PATH */
fd = shm_open(VALID_SHM_PATH, VALID_FLAGS, VALID_MODE);
zassert_true(fd >= 0, "shm_open(%s, %x, %04o) failed: %d", VALID_SHM_PATH, VALID_FLAGS,
VALID_MODE, errno);
/* technically, the order of close / shm_unlink can be reversed too */
zassert_ok(close(fd));
zassert_ok(shm_unlink(VALID_SHM_PATH));
/* should not be able to re-open the same path without O_CREAT */
zassert_not_ok(shm_open(VALID_SHM_PATH, OPEN_FLAGS, VALID_MODE));
}
ZTEST(xsi_realtime, test_shm_read_write)
{
int fd[N];
for (size_t i = 0; i < N; ++i) {
char cbuf = 0xff;
fd[i] = shm_open(VALID_SHM_PATH, i == 0 ? CREATE_FLAGS : OPEN_FLAGS, VALID_MODE);
zassert_true(fd[i] >= 0, "shm_open(%s, %x, %04o) failed: %d", VALID_SHM_PATH,
VALID_FLAGS, VALID_MODE, errno);
if (i == 0) {
/* size 0 on create / zero characters written */
zassert_equal(write(fd[0], "", 1), 0,
"write() should fail on newly create shm fd with size 0");
/* size 0 on create / zero characters read */
zassert_equal(read(fd[0], &cbuf, 1), 0,
"read() should fail on newly create shm fd with size 0");
BUILD_ASSERT(SHM_SIZE >= 1);
zassert_ok(ftruncate(fd[0], SHM_SIZE));
zassert_equal(write(fd[0], "\x42", 1), 1, "write() failed on fd %d: %d\n",
fd[0], errno);
continue;
}
zassert_equal(read(fd[i], &cbuf, 1), 1, "read() failed on fd %d: %d\n", fd[i],
errno);
zassert_equal(cbuf, 0x42,
"Failed to read byte over fd %d: expected: 0x%02x actual: 0x%02x",
fd[i], 0x42, cbuf);
}
for (size_t i = N; i > 0; --i) {
zassert_ok(close(fd[i - 1]));
}
zassert_ok(shm_unlink(VALID_SHM_PATH));
}
ZTEST(xsi_realtime, test_shm_mmap)
{
int fd[N];
void *addr[N];
if (!IS_ENABLED(CONFIG_MMU)) {
ztest_test_skip();
}
for (size_t i = 0; i < N; ++i) {
fd[i] = shm_open(VALID_SHM_PATH, i == 0 ? CREATE_FLAGS : OPEN_FLAGS, VALID_MODE);
zassert_true(fd[i] >= 0, "shm_open(%s, %x, %04o) failed : %d", VALID_SHM_PATH,
VALID_FLAGS, VALID_MODE, errno);
if (i == 0) {
/* cannot map shm of size zero */
zassert_not_ok(mmap(NULL, PAGE_SIZE, PROT_READ | PROT_WRITE, MAP_SHARED,
fd[0], 0));
zassert_ok(ftruncate(fd[0], PAGE_SIZE));
}
addr[i] = mmap(NULL, PAGE_SIZE, PROT_READ | PROT_WRITE, MAP_SHARED, fd[i], 0);
zassert_not_equal(MAP_FAILED, addr[i], "mmap() failed: %d", errno);
if ((i & 1) == 0) {
memset(addr[0], i & 0xff, PAGE_SIZE);
} else {
zassert_mem_equal(addr[i], addr[i - 1], PAGE_SIZE);
}
}
for (size_t i = N; i > 0; --i) {
zassert_ok(close(fd[i - 1]));
}
for (size_t i = N; i > 0; --i) {
zassert_ok(munmap(addr[i - 1], PAGE_SIZE));
/*
* Note: for some reason, in Zephyr, unmapping a physical page once, removes all
* virtual mappings. When that behaviour changes, remove the break below and adjust
* shm.c accordingly.
*/
break;
}
zassert_ok(shm_unlink(VALID_SHM_PATH));
}