1242 lines
26 KiB
C
1242 lines
26 KiB
C
// SPDX-License-Identifier: GPL-2.0
|
|
#define _GNU_SOURCE
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <sys/syscall.h>
|
|
#include <time.h>
|
|
#include <signal.h>
|
|
#include <setjmp.h>
|
|
#include <sys/mman.h>
|
|
#include <sys/utsname.h>
|
|
#include <sys/wait.h>
|
|
#include <sys/stat.h>
|
|
#include <fcntl.h>
|
|
#include <inttypes.h>
|
|
#include <sched.h>
|
|
|
|
#include <sys/uio.h>
|
|
#include <linux/io_uring.h>
|
|
#include "../kselftest.h"
|
|
|
|
#ifndef __x86_64__
|
|
# error This test is 64-bit only
|
|
#endif
|
|
|
|
/* LAM modes, these definitions were copied from kernel code */
|
|
#define LAM_NONE 0
|
|
#define LAM_U57_BITS 6
|
|
|
|
#define LAM_U57_MASK (0x3fULL << 57)
|
|
/* arch prctl for LAM */
|
|
#define ARCH_GET_UNTAG_MASK 0x4001
|
|
#define ARCH_ENABLE_TAGGED_ADDR 0x4002
|
|
#define ARCH_GET_MAX_TAG_BITS 0x4003
|
|
#define ARCH_FORCE_TAGGED_SVA 0x4004
|
|
|
|
/* Specified test function bits */
|
|
#define FUNC_MALLOC 0x1
|
|
#define FUNC_BITS 0x2
|
|
#define FUNC_MMAP 0x4
|
|
#define FUNC_SYSCALL 0x8
|
|
#define FUNC_URING 0x10
|
|
#define FUNC_INHERITE 0x20
|
|
#define FUNC_PASID 0x40
|
|
|
|
#define TEST_MASK 0x7f
|
|
|
|
#define LOW_ADDR (0x1UL << 30)
|
|
#define HIGH_ADDR (0x3UL << 48)
|
|
|
|
#define MALLOC_LEN 32
|
|
|
|
#define PAGE_SIZE (4 << 10)
|
|
|
|
#define STACK_SIZE 65536
|
|
|
|
#define barrier() ({ \
|
|
__asm__ __volatile__("" : : : "memory"); \
|
|
})
|
|
|
|
#define URING_QUEUE_SZ 1
|
|
#define URING_BLOCK_SZ 2048
|
|
|
|
/* Pasid test define */
|
|
#define LAM_CMD_BIT 0x1
|
|
#define PAS_CMD_BIT 0x2
|
|
#define SVA_CMD_BIT 0x4
|
|
|
|
#define PAS_CMD(cmd1, cmd2, cmd3) (((cmd3) << 8) | ((cmd2) << 4) | ((cmd1) << 0))
|
|
|
|
struct testcases {
|
|
unsigned int later;
|
|
int expected; /* 2: SIGSEGV Error; 1: other errors */
|
|
unsigned long lam;
|
|
uint64_t addr;
|
|
uint64_t cmd;
|
|
int (*test_func)(struct testcases *test);
|
|
const char *msg;
|
|
};
|
|
|
|
/* Used by CQ of uring, source file handler and file's size */
|
|
struct file_io {
|
|
int file_fd;
|
|
off_t file_sz;
|
|
struct iovec iovecs[];
|
|
};
|
|
|
|
struct io_uring_queue {
|
|
unsigned int *head;
|
|
unsigned int *tail;
|
|
unsigned int *ring_mask;
|
|
unsigned int *ring_entries;
|
|
unsigned int *flags;
|
|
unsigned int *array;
|
|
union {
|
|
struct io_uring_cqe *cqes;
|
|
struct io_uring_sqe *sqes;
|
|
} queue;
|
|
size_t ring_sz;
|
|
};
|
|
|
|
struct io_ring {
|
|
int ring_fd;
|
|
struct io_uring_queue sq_ring;
|
|
struct io_uring_queue cq_ring;
|
|
};
|
|
|
|
int tests_cnt;
|
|
jmp_buf segv_env;
|
|
|
|
static void segv_handler(int sig)
|
|
{
|
|
ksft_print_msg("Get segmentation fault(%d).", sig);
|
|
|
|
siglongjmp(segv_env, 1);
|
|
}
|
|
|
|
static inline int cpu_has_lam(void)
|
|
{
|
|
unsigned int cpuinfo[4];
|
|
|
|
__cpuid_count(0x7, 1, cpuinfo[0], cpuinfo[1], cpuinfo[2], cpuinfo[3]);
|
|
|
|
return (cpuinfo[0] & (1 << 26));
|
|
}
|
|
|
|
/* Check 5-level page table feature in CPUID.(EAX=07H, ECX=00H):ECX.[bit 16] */
|
|
static inline int cpu_has_la57(void)
|
|
{
|
|
unsigned int cpuinfo[4];
|
|
|
|
__cpuid_count(0x7, 0, cpuinfo[0], cpuinfo[1], cpuinfo[2], cpuinfo[3]);
|
|
|
|
return (cpuinfo[2] & (1 << 16));
|
|
}
|
|
|
|
/*
|
|
* Set tagged address and read back untag mask.
|
|
* check if the untagged mask is expected.
|
|
*
|
|
* @return:
|
|
* 0: Set LAM mode successfully
|
|
* others: failed to set LAM
|
|
*/
|
|
static int set_lam(unsigned long lam)
|
|
{
|
|
int ret = 0;
|
|
uint64_t ptr = 0;
|
|
|
|
if (lam != LAM_U57_BITS && lam != LAM_NONE)
|
|
return -1;
|
|
|
|
/* Skip check return */
|
|
syscall(SYS_arch_prctl, ARCH_ENABLE_TAGGED_ADDR, lam);
|
|
|
|
/* Get untagged mask */
|
|
syscall(SYS_arch_prctl, ARCH_GET_UNTAG_MASK, &ptr);
|
|
|
|
/* Check mask returned is expected */
|
|
if (lam == LAM_U57_BITS)
|
|
ret = (ptr != ~(LAM_U57_MASK));
|
|
else if (lam == LAM_NONE)
|
|
ret = (ptr != -1ULL);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static unsigned long get_default_tag_bits(void)
|
|
{
|
|
pid_t pid;
|
|
int lam = LAM_NONE;
|
|
int ret = 0;
|
|
|
|
pid = fork();
|
|
if (pid < 0) {
|
|
perror("Fork failed.");
|
|
} else if (pid == 0) {
|
|
/* Set LAM mode in child process */
|
|
if (set_lam(LAM_U57_BITS) == 0)
|
|
lam = LAM_U57_BITS;
|
|
else
|
|
lam = LAM_NONE;
|
|
exit(lam);
|
|
} else {
|
|
wait(&ret);
|
|
lam = WEXITSTATUS(ret);
|
|
}
|
|
|
|
return lam;
|
|
}
|
|
|
|
/*
|
|
* Set tagged address and read back untag mask.
|
|
* check if the untag mask is expected.
|
|
*/
|
|
static int get_lam(void)
|
|
{
|
|
uint64_t ptr = 0;
|
|
int ret = -1;
|
|
/* Get untagged mask */
|
|
if (syscall(SYS_arch_prctl, ARCH_GET_UNTAG_MASK, &ptr) == -1)
|
|
return -1;
|
|
|
|
/* Check mask returned is expected */
|
|
if (ptr == ~(LAM_U57_MASK))
|
|
ret = LAM_U57_BITS;
|
|
else if (ptr == -1ULL)
|
|
ret = LAM_NONE;
|
|
|
|
|
|
return ret;
|
|
}
|
|
|
|
/* According to LAM mode, set metadata in high bits */
|
|
static uint64_t set_metadata(uint64_t src, unsigned long lam)
|
|
{
|
|
uint64_t metadata;
|
|
|
|
srand(time(NULL));
|
|
|
|
switch (lam) {
|
|
case LAM_U57_BITS: /* Set metadata in bits 62:57 */
|
|
/* Get a random non-zero value as metadata */
|
|
metadata = (rand() % ((1UL << LAM_U57_BITS) - 1) + 1) << 57;
|
|
metadata |= (src & ~(LAM_U57_MASK));
|
|
break;
|
|
default:
|
|
metadata = src;
|
|
break;
|
|
}
|
|
|
|
return metadata;
|
|
}
|
|
|
|
/*
|
|
* Set metadata in user pointer, compare new pointer with original pointer.
|
|
* both pointers should point to the same address.
|
|
*
|
|
* @return:
|
|
* 0: value on the pointer with metadate and value on original are same
|
|
* 1: not same.
|
|
*/
|
|
static int handle_lam_test(void *src, unsigned int lam)
|
|
{
|
|
char *ptr;
|
|
|
|
strcpy((char *)src, "USER POINTER");
|
|
|
|
ptr = (char *)set_metadata((uint64_t)src, lam);
|
|
if (src == ptr)
|
|
return 0;
|
|
|
|
/* Copy a string into the pointer with metadata */
|
|
strcpy((char *)ptr, "METADATA POINTER");
|
|
|
|
return (!!strcmp((char *)src, (char *)ptr));
|
|
}
|
|
|
|
|
|
int handle_max_bits(struct testcases *test)
|
|
{
|
|
unsigned long exp_bits = get_default_tag_bits();
|
|
unsigned long bits = 0;
|
|
|
|
if (exp_bits != LAM_NONE)
|
|
exp_bits = LAM_U57_BITS;
|
|
|
|
/* Get LAM max tag bits */
|
|
if (syscall(SYS_arch_prctl, ARCH_GET_MAX_TAG_BITS, &bits) == -1)
|
|
return 1;
|
|
|
|
return (exp_bits != bits);
|
|
}
|
|
|
|
/*
|
|
* Test lam feature through dereference pointer get from malloc.
|
|
* @return 0: Pass test. 1: Get failure during test 2: Get SIGSEGV
|
|
*/
|
|
static int handle_malloc(struct testcases *test)
|
|
{
|
|
char *ptr = NULL;
|
|
int ret = 0;
|
|
|
|
if (test->later == 0 && test->lam != 0)
|
|
if (set_lam(test->lam) == -1)
|
|
return 1;
|
|
|
|
ptr = (char *)malloc(MALLOC_LEN);
|
|
if (ptr == NULL) {
|
|
perror("malloc() failure\n");
|
|
return 1;
|
|
}
|
|
|
|
/* Set signal handler */
|
|
if (sigsetjmp(segv_env, 1) == 0) {
|
|
signal(SIGSEGV, segv_handler);
|
|
ret = handle_lam_test(ptr, test->lam);
|
|
} else {
|
|
ret = 2;
|
|
}
|
|
|
|
if (test->later != 0 && test->lam != 0)
|
|
if (set_lam(test->lam) == -1 && ret == 0)
|
|
ret = 1;
|
|
|
|
free(ptr);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int handle_mmap(struct testcases *test)
|
|
{
|
|
void *ptr;
|
|
unsigned int flags = MAP_PRIVATE | MAP_ANONYMOUS | MAP_FIXED;
|
|
int ret = 0;
|
|
|
|
if (test->later == 0 && test->lam != 0)
|
|
if (set_lam(test->lam) != 0)
|
|
return 1;
|
|
|
|
ptr = mmap((void *)test->addr, PAGE_SIZE, PROT_READ | PROT_WRITE,
|
|
flags, -1, 0);
|
|
if (ptr == MAP_FAILED) {
|
|
if (test->addr == HIGH_ADDR)
|
|
if (!cpu_has_la57())
|
|
return 3; /* unsupport LA57 */
|
|
return 1;
|
|
}
|
|
|
|
if (test->later != 0 && test->lam != 0)
|
|
if (set_lam(test->lam) != 0)
|
|
ret = 1;
|
|
|
|
if (ret == 0) {
|
|
if (sigsetjmp(segv_env, 1) == 0) {
|
|
signal(SIGSEGV, segv_handler);
|
|
ret = handle_lam_test(ptr, test->lam);
|
|
} else {
|
|
ret = 2;
|
|
}
|
|
}
|
|
|
|
munmap(ptr, PAGE_SIZE);
|
|
return ret;
|
|
}
|
|
|
|
static int handle_syscall(struct testcases *test)
|
|
{
|
|
struct utsname unme, *pu;
|
|
int ret = 0;
|
|
|
|
if (test->later == 0 && test->lam != 0)
|
|
if (set_lam(test->lam) != 0)
|
|
return 1;
|
|
|
|
if (sigsetjmp(segv_env, 1) == 0) {
|
|
signal(SIGSEGV, segv_handler);
|
|
pu = (struct utsname *)set_metadata((uint64_t)&unme, test->lam);
|
|
ret = uname(pu);
|
|
if (ret < 0)
|
|
ret = 1;
|
|
} else {
|
|
ret = 2;
|
|
}
|
|
|
|
if (test->later != 0 && test->lam != 0)
|
|
if (set_lam(test->lam) != -1 && ret == 0)
|
|
ret = 1;
|
|
|
|
return ret;
|
|
}
|
|
|
|
int sys_uring_setup(unsigned int entries, struct io_uring_params *p)
|
|
{
|
|
return (int)syscall(__NR_io_uring_setup, entries, p);
|
|
}
|
|
|
|
int sys_uring_enter(int fd, unsigned int to, unsigned int min, unsigned int flags)
|
|
{
|
|
return (int)syscall(__NR_io_uring_enter, fd, to, min, flags, NULL, 0);
|
|
}
|
|
|
|
/* Init submission queue and completion queue */
|
|
int mmap_io_uring(struct io_uring_params p, struct io_ring *s)
|
|
{
|
|
struct io_uring_queue *sring = &s->sq_ring;
|
|
struct io_uring_queue *cring = &s->cq_ring;
|
|
|
|
sring->ring_sz = p.sq_off.array + p.sq_entries * sizeof(unsigned int);
|
|
cring->ring_sz = p.cq_off.cqes + p.cq_entries * sizeof(struct io_uring_cqe);
|
|
|
|
if (p.features & IORING_FEAT_SINGLE_MMAP) {
|
|
if (cring->ring_sz > sring->ring_sz)
|
|
sring->ring_sz = cring->ring_sz;
|
|
|
|
cring->ring_sz = sring->ring_sz;
|
|
}
|
|
|
|
void *sq_ptr = mmap(0, sring->ring_sz, PROT_READ | PROT_WRITE,
|
|
MAP_SHARED | MAP_POPULATE, s->ring_fd,
|
|
IORING_OFF_SQ_RING);
|
|
|
|
if (sq_ptr == MAP_FAILED) {
|
|
perror("sub-queue!");
|
|
return 1;
|
|
}
|
|
|
|
void *cq_ptr = sq_ptr;
|
|
|
|
if (!(p.features & IORING_FEAT_SINGLE_MMAP)) {
|
|
cq_ptr = mmap(0, cring->ring_sz, PROT_READ | PROT_WRITE,
|
|
MAP_SHARED | MAP_POPULATE, s->ring_fd,
|
|
IORING_OFF_CQ_RING);
|
|
if (cq_ptr == MAP_FAILED) {
|
|
perror("cpl-queue!");
|
|
munmap(sq_ptr, sring->ring_sz);
|
|
return 1;
|
|
}
|
|
}
|
|
|
|
sring->head = sq_ptr + p.sq_off.head;
|
|
sring->tail = sq_ptr + p.sq_off.tail;
|
|
sring->ring_mask = sq_ptr + p.sq_off.ring_mask;
|
|
sring->ring_entries = sq_ptr + p.sq_off.ring_entries;
|
|
sring->flags = sq_ptr + p.sq_off.flags;
|
|
sring->array = sq_ptr + p.sq_off.array;
|
|
|
|
/* Map a queue as mem map */
|
|
s->sq_ring.queue.sqes = mmap(0, p.sq_entries * sizeof(struct io_uring_sqe),
|
|
PROT_READ | PROT_WRITE, MAP_SHARED | MAP_POPULATE,
|
|
s->ring_fd, IORING_OFF_SQES);
|
|
if (s->sq_ring.queue.sqes == MAP_FAILED) {
|
|
munmap(sq_ptr, sring->ring_sz);
|
|
if (sq_ptr != cq_ptr) {
|
|
ksft_print_msg("failed to mmap uring queue!");
|
|
munmap(cq_ptr, cring->ring_sz);
|
|
return 1;
|
|
}
|
|
}
|
|
|
|
cring->head = cq_ptr + p.cq_off.head;
|
|
cring->tail = cq_ptr + p.cq_off.tail;
|
|
cring->ring_mask = cq_ptr + p.cq_off.ring_mask;
|
|
cring->ring_entries = cq_ptr + p.cq_off.ring_entries;
|
|
cring->queue.cqes = cq_ptr + p.cq_off.cqes;
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* Init io_uring queues */
|
|
int setup_io_uring(struct io_ring *s)
|
|
{
|
|
struct io_uring_params para;
|
|
|
|
memset(¶, 0, sizeof(para));
|
|
s->ring_fd = sys_uring_setup(URING_QUEUE_SZ, ¶);
|
|
if (s->ring_fd < 0)
|
|
return 1;
|
|
|
|
return mmap_io_uring(para, s);
|
|
}
|
|
|
|
/*
|
|
* Get data from completion queue. the data buffer saved the file data
|
|
* return 0: success; others: error;
|
|
*/
|
|
int handle_uring_cq(struct io_ring *s)
|
|
{
|
|
struct file_io *fi = NULL;
|
|
struct io_uring_queue *cring = &s->cq_ring;
|
|
struct io_uring_cqe *cqe;
|
|
unsigned int head;
|
|
off_t len = 0;
|
|
|
|
head = *cring->head;
|
|
|
|
do {
|
|
barrier();
|
|
if (head == *cring->tail)
|
|
break;
|
|
/* Get the entry */
|
|
cqe = &cring->queue.cqes[head & *s->cq_ring.ring_mask];
|
|
fi = (struct file_io *)cqe->user_data;
|
|
if (cqe->res < 0)
|
|
break;
|
|
|
|
int blocks = (int)(fi->file_sz + URING_BLOCK_SZ - 1) / URING_BLOCK_SZ;
|
|
|
|
for (int i = 0; i < blocks; i++)
|
|
len += fi->iovecs[i].iov_len;
|
|
|
|
head++;
|
|
} while (1);
|
|
|
|
*cring->head = head;
|
|
barrier();
|
|
|
|
return (len != fi->file_sz);
|
|
}
|
|
|
|
/*
|
|
* Submit squeue. specify via IORING_OP_READV.
|
|
* the buffer need to be set metadata according to LAM mode
|
|
*/
|
|
int handle_uring_sq(struct io_ring *ring, struct file_io *fi, unsigned long lam)
|
|
{
|
|
int file_fd = fi->file_fd;
|
|
struct io_uring_queue *sring = &ring->sq_ring;
|
|
unsigned int index = 0, cur_block = 0, tail = 0, next_tail = 0;
|
|
struct io_uring_sqe *sqe;
|
|
|
|
off_t remain = fi->file_sz;
|
|
int blocks = (int)(remain + URING_BLOCK_SZ - 1) / URING_BLOCK_SZ;
|
|
|
|
while (remain) {
|
|
off_t bytes = remain;
|
|
void *buf;
|
|
|
|
if (bytes > URING_BLOCK_SZ)
|
|
bytes = URING_BLOCK_SZ;
|
|
|
|
fi->iovecs[cur_block].iov_len = bytes;
|
|
|
|
if (posix_memalign(&buf, URING_BLOCK_SZ, URING_BLOCK_SZ))
|
|
return 1;
|
|
|
|
fi->iovecs[cur_block].iov_base = (void *)set_metadata((uint64_t)buf, lam);
|
|
remain -= bytes;
|
|
cur_block++;
|
|
}
|
|
|
|
next_tail = *sring->tail;
|
|
tail = next_tail;
|
|
next_tail++;
|
|
|
|
barrier();
|
|
|
|
index = tail & *ring->sq_ring.ring_mask;
|
|
|
|
sqe = &ring->sq_ring.queue.sqes[index];
|
|
sqe->fd = file_fd;
|
|
sqe->flags = 0;
|
|
sqe->opcode = IORING_OP_READV;
|
|
sqe->addr = (unsigned long)fi->iovecs;
|
|
sqe->len = blocks;
|
|
sqe->off = 0;
|
|
sqe->user_data = (uint64_t)fi;
|
|
|
|
sring->array[index] = index;
|
|
tail = next_tail;
|
|
|
|
if (*sring->tail != tail) {
|
|
*sring->tail = tail;
|
|
barrier();
|
|
}
|
|
|
|
if (sys_uring_enter(ring->ring_fd, 1, 1, IORING_ENTER_GETEVENTS) < 0)
|
|
return 1;
|
|
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* Test LAM in async I/O and io_uring, read current binery through io_uring
|
|
* Set metadata in pointers to iovecs buffer.
|
|
*/
|
|
int do_uring(unsigned long lam)
|
|
{
|
|
struct io_ring *ring;
|
|
struct file_io *fi;
|
|
struct stat st;
|
|
int ret = 1;
|
|
char path[PATH_MAX] = {0};
|
|
|
|
/* get current process path */
|
|
if (readlink("/proc/self/exe", path, PATH_MAX - 1) <= 0)
|
|
return 1;
|
|
|
|
int file_fd = open(path, O_RDONLY);
|
|
|
|
if (file_fd < 0)
|
|
return 1;
|
|
|
|
if (fstat(file_fd, &st) < 0)
|
|
return 1;
|
|
|
|
off_t file_sz = st.st_size;
|
|
|
|
int blocks = (int)(file_sz + URING_BLOCK_SZ - 1) / URING_BLOCK_SZ;
|
|
|
|
fi = malloc(sizeof(*fi) + sizeof(struct iovec) * blocks);
|
|
if (!fi)
|
|
return 1;
|
|
|
|
fi->file_sz = file_sz;
|
|
fi->file_fd = file_fd;
|
|
|
|
ring = malloc(sizeof(*ring));
|
|
if (!ring)
|
|
return 1;
|
|
|
|
memset(ring, 0, sizeof(struct io_ring));
|
|
|
|
if (setup_io_uring(ring))
|
|
goto out;
|
|
|
|
if (handle_uring_sq(ring, fi, lam))
|
|
goto out;
|
|
|
|
ret = handle_uring_cq(ring);
|
|
|
|
out:
|
|
free(ring);
|
|
|
|
for (int i = 0; i < blocks; i++) {
|
|
if (fi->iovecs[i].iov_base) {
|
|
uint64_t addr = ((uint64_t)fi->iovecs[i].iov_base);
|
|
|
|
switch (lam) {
|
|
case LAM_U57_BITS: /* Clear bits 62:57 */
|
|
addr = (addr & ~(LAM_U57_MASK));
|
|
break;
|
|
}
|
|
free((void *)addr);
|
|
fi->iovecs[i].iov_base = NULL;
|
|
}
|
|
}
|
|
|
|
free(fi);
|
|
|
|
return ret;
|
|
}
|
|
|
|
int handle_uring(struct testcases *test)
|
|
{
|
|
int ret = 0;
|
|
|
|
if (test->later == 0 && test->lam != 0)
|
|
if (set_lam(test->lam) != 0)
|
|
return 1;
|
|
|
|
if (sigsetjmp(segv_env, 1) == 0) {
|
|
signal(SIGSEGV, segv_handler);
|
|
ret = do_uring(test->lam);
|
|
} else {
|
|
ret = 2;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int fork_test(struct testcases *test)
|
|
{
|
|
int ret, child_ret;
|
|
pid_t pid;
|
|
|
|
pid = fork();
|
|
if (pid < 0) {
|
|
perror("Fork failed.");
|
|
ret = 1;
|
|
} else if (pid == 0) {
|
|
ret = test->test_func(test);
|
|
exit(ret);
|
|
} else {
|
|
wait(&child_ret);
|
|
ret = WEXITSTATUS(child_ret);
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int handle_execve(struct testcases *test)
|
|
{
|
|
int ret, child_ret;
|
|
int lam = test->lam;
|
|
pid_t pid;
|
|
|
|
pid = fork();
|
|
if (pid < 0) {
|
|
perror("Fork failed.");
|
|
ret = 1;
|
|
} else if (pid == 0) {
|
|
char path[PATH_MAX] = {0};
|
|
|
|
/* Set LAM mode in parent process */
|
|
if (set_lam(lam) != 0)
|
|
return 1;
|
|
|
|
/* Get current binary's path and the binary was run by execve */
|
|
if (readlink("/proc/self/exe", path, PATH_MAX - 1) <= 0)
|
|
exit(-1);
|
|
|
|
/* run binary to get LAM mode and return to parent process */
|
|
if (execlp(path, path, "-t 0x0", NULL) < 0) {
|
|
perror("error on exec");
|
|
exit(-1);
|
|
}
|
|
} else {
|
|
wait(&child_ret);
|
|
ret = WEXITSTATUS(child_ret);
|
|
if (ret != LAM_NONE)
|
|
return 1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int handle_inheritance(struct testcases *test)
|
|
{
|
|
int ret, child_ret;
|
|
int lam = test->lam;
|
|
pid_t pid;
|
|
|
|
/* Set LAM mode in parent process */
|
|
if (set_lam(lam) != 0)
|
|
return 1;
|
|
|
|
pid = fork();
|
|
if (pid < 0) {
|
|
perror("Fork failed.");
|
|
return 1;
|
|
} else if (pid == 0) {
|
|
/* Set LAM mode in parent process */
|
|
int child_lam = get_lam();
|
|
|
|
exit(child_lam);
|
|
} else {
|
|
wait(&child_ret);
|
|
ret = WEXITSTATUS(child_ret);
|
|
|
|
if (lam != ret)
|
|
return 1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int thread_fn_get_lam(void *arg)
|
|
{
|
|
return get_lam();
|
|
}
|
|
|
|
static int thread_fn_set_lam(void *arg)
|
|
{
|
|
struct testcases *test = arg;
|
|
|
|
return set_lam(test->lam);
|
|
}
|
|
|
|
static int handle_thread(struct testcases *test)
|
|
{
|
|
char stack[STACK_SIZE];
|
|
int ret, child_ret;
|
|
int lam = 0;
|
|
pid_t pid;
|
|
|
|
/* Set LAM mode in parent process */
|
|
if (!test->later) {
|
|
lam = test->lam;
|
|
if (set_lam(lam) != 0)
|
|
return 1;
|
|
}
|
|
|
|
pid = clone(thread_fn_get_lam, stack + STACK_SIZE,
|
|
SIGCHLD | CLONE_FILES | CLONE_FS | CLONE_VM, NULL);
|
|
if (pid < 0) {
|
|
perror("Clone failed.");
|
|
return 1;
|
|
}
|
|
|
|
waitpid(pid, &child_ret, 0);
|
|
ret = WEXITSTATUS(child_ret);
|
|
|
|
if (lam != ret)
|
|
return 1;
|
|
|
|
if (test->later) {
|
|
if (set_lam(test->lam) != 0)
|
|
return 1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int handle_thread_enable(struct testcases *test)
|
|
{
|
|
char stack[STACK_SIZE];
|
|
int ret, child_ret;
|
|
int lam = test->lam;
|
|
pid_t pid;
|
|
|
|
pid = clone(thread_fn_set_lam, stack + STACK_SIZE,
|
|
SIGCHLD | CLONE_FILES | CLONE_FS | CLONE_VM, test);
|
|
if (pid < 0) {
|
|
perror("Clone failed.");
|
|
return 1;
|
|
}
|
|
|
|
waitpid(pid, &child_ret, 0);
|
|
ret = WEXITSTATUS(child_ret);
|
|
|
|
if (lam != ret)
|
|
return 1;
|
|
|
|
return 0;
|
|
}
|
|
static void run_test(struct testcases *test, int count)
|
|
{
|
|
int i, ret = 0;
|
|
|
|
for (i = 0; i < count; i++) {
|
|
struct testcases *t = test + i;
|
|
|
|
/* fork a process to run test case */
|
|
tests_cnt++;
|
|
ret = fork_test(t);
|
|
|
|
/* return 3 is not support LA57, the case should be skipped */
|
|
if (ret == 3) {
|
|
ksft_test_result_skip("%s", t->msg);
|
|
continue;
|
|
}
|
|
|
|
if (ret != 0)
|
|
ret = (t->expected == ret);
|
|
else
|
|
ret = !(t->expected);
|
|
|
|
ksft_test_result(ret, "%s", t->msg);
|
|
}
|
|
}
|
|
|
|
static struct testcases uring_cases[] = {
|
|
{
|
|
.later = 0,
|
|
.lam = LAM_U57_BITS,
|
|
.test_func = handle_uring,
|
|
.msg = "URING: LAM_U57. Dereferencing pointer with metadata\n",
|
|
},
|
|
{
|
|
.later = 1,
|
|
.expected = 1,
|
|
.lam = LAM_U57_BITS,
|
|
.test_func = handle_uring,
|
|
.msg = "URING:[Negative] Disable LAM. Dereferencing pointer with metadata.\n",
|
|
},
|
|
};
|
|
|
|
static struct testcases malloc_cases[] = {
|
|
{
|
|
.later = 0,
|
|
.lam = LAM_U57_BITS,
|
|
.test_func = handle_malloc,
|
|
.msg = "MALLOC: LAM_U57. Dereferencing pointer with metadata\n",
|
|
},
|
|
{
|
|
.later = 1,
|
|
.expected = 2,
|
|
.lam = LAM_U57_BITS,
|
|
.test_func = handle_malloc,
|
|
.msg = "MALLOC:[Negative] Disable LAM. Dereferencing pointer with metadata.\n",
|
|
},
|
|
};
|
|
|
|
static struct testcases bits_cases[] = {
|
|
{
|
|
.test_func = handle_max_bits,
|
|
.msg = "BITS: Check default tag bits\n",
|
|
},
|
|
};
|
|
|
|
static struct testcases syscall_cases[] = {
|
|
{
|
|
.later = 0,
|
|
.lam = LAM_U57_BITS,
|
|
.test_func = handle_syscall,
|
|
.msg = "SYSCALL: LAM_U57. syscall with metadata\n",
|
|
},
|
|
{
|
|
.later = 1,
|
|
.expected = 1,
|
|
.lam = LAM_U57_BITS,
|
|
.test_func = handle_syscall,
|
|
.msg = "SYSCALL:[Negative] Disable LAM. Dereferencing pointer with metadata.\n",
|
|
},
|
|
};
|
|
|
|
static struct testcases mmap_cases[] = {
|
|
{
|
|
.later = 1,
|
|
.expected = 0,
|
|
.lam = LAM_U57_BITS,
|
|
.addr = HIGH_ADDR,
|
|
.test_func = handle_mmap,
|
|
.msg = "MMAP: First mmap high address, then set LAM_U57.\n",
|
|
},
|
|
{
|
|
.later = 0,
|
|
.expected = 0,
|
|
.lam = LAM_U57_BITS,
|
|
.addr = HIGH_ADDR,
|
|
.test_func = handle_mmap,
|
|
.msg = "MMAP: First LAM_U57, then High address.\n",
|
|
},
|
|
{
|
|
.later = 0,
|
|
.expected = 0,
|
|
.lam = LAM_U57_BITS,
|
|
.addr = LOW_ADDR,
|
|
.test_func = handle_mmap,
|
|
.msg = "MMAP: First LAM_U57, then Low address.\n",
|
|
},
|
|
};
|
|
|
|
static struct testcases inheritance_cases[] = {
|
|
{
|
|
.expected = 0,
|
|
.lam = LAM_U57_BITS,
|
|
.test_func = handle_inheritance,
|
|
.msg = "FORK: LAM_U57, child process should get LAM mode same as parent\n",
|
|
},
|
|
{
|
|
.expected = 0,
|
|
.lam = LAM_U57_BITS,
|
|
.test_func = handle_thread,
|
|
.msg = "THREAD: LAM_U57, child thread should get LAM mode same as parent\n",
|
|
},
|
|
{
|
|
.expected = 1,
|
|
.lam = LAM_U57_BITS,
|
|
.test_func = handle_thread_enable,
|
|
.msg = "THREAD: [NEGATIVE] Enable LAM in child.\n",
|
|
},
|
|
{
|
|
.expected = 1,
|
|
.later = 1,
|
|
.lam = LAM_U57_BITS,
|
|
.test_func = handle_thread,
|
|
.msg = "THREAD: [NEGATIVE] Enable LAM in parent after thread created.\n",
|
|
},
|
|
{
|
|
.expected = 0,
|
|
.lam = LAM_U57_BITS,
|
|
.test_func = handle_execve,
|
|
.msg = "EXECVE: LAM_U57, child process should get disabled LAM mode\n",
|
|
},
|
|
};
|
|
|
|
static void cmd_help(void)
|
|
{
|
|
printf("usage: lam [-h] [-t test list]\n");
|
|
printf("\t-t test list: run tests specified in the test list, default:0x%x\n", TEST_MASK);
|
|
printf("\t\t0x1:malloc; 0x2:max_bits; 0x4:mmap; 0x8:syscall; 0x10:io_uring; 0x20:inherit;\n");
|
|
printf("\t-h: help\n");
|
|
}
|
|
|
|
/* Check for file existence */
|
|
uint8_t file_Exists(const char *fileName)
|
|
{
|
|
struct stat buffer;
|
|
|
|
uint8_t ret = (stat(fileName, &buffer) == 0);
|
|
|
|
return ret;
|
|
}
|
|
|
|
/* Sysfs idxd files */
|
|
const char *dsa_configs[] = {
|
|
"echo 1 > /sys/bus/dsa/devices/dsa0/wq0.1/group_id",
|
|
"echo shared > /sys/bus/dsa/devices/dsa0/wq0.1/mode",
|
|
"echo 10 > /sys/bus/dsa/devices/dsa0/wq0.1/priority",
|
|
"echo 16 > /sys/bus/dsa/devices/dsa0/wq0.1/size",
|
|
"echo 15 > /sys/bus/dsa/devices/dsa0/wq0.1/threshold",
|
|
"echo user > /sys/bus/dsa/devices/dsa0/wq0.1/type",
|
|
"echo MyApp1 > /sys/bus/dsa/devices/dsa0/wq0.1/name",
|
|
"echo 1 > /sys/bus/dsa/devices/dsa0/engine0.1/group_id",
|
|
"echo dsa0 > /sys/bus/dsa/drivers/idxd/bind",
|
|
/* bind files and devices, generated a device file in /dev */
|
|
"echo wq0.1 > /sys/bus/dsa/drivers/user/bind",
|
|
};
|
|
|
|
/* DSA device file */
|
|
const char *dsaDeviceFile = "/dev/dsa/wq0.1";
|
|
/* file for io*/
|
|
const char *dsaPasidEnable = "/sys/bus/dsa/devices/dsa0/pasid_enabled";
|
|
|
|
/*
|
|
* DSA depends on kernel cmdline "intel_iommu=on,sm_on"
|
|
* return pasid_enabled (0: disable 1:enable)
|
|
*/
|
|
int Check_DSA_Kernel_Setting(void)
|
|
{
|
|
char command[256] = "";
|
|
char buf[256] = "";
|
|
char *ptr;
|
|
int rv = -1;
|
|
|
|
snprintf(command, sizeof(command) - 1, "cat %s", dsaPasidEnable);
|
|
|
|
FILE *cmd = popen(command, "r");
|
|
|
|
if (cmd) {
|
|
while (fgets(buf, sizeof(buf) - 1, cmd) != NULL);
|
|
|
|
pclose(cmd);
|
|
rv = strtol(buf, &ptr, 16);
|
|
}
|
|
|
|
return rv;
|
|
}
|
|
|
|
/*
|
|
* Config DSA's sysfs files as shared DSA's WQ.
|
|
* Generated a device file /dev/dsa/wq0.1
|
|
* Return: 0 OK; 1 Failed; 3 Skip(SVA disabled).
|
|
*/
|
|
int Dsa_Init_Sysfs(void)
|
|
{
|
|
uint len = ARRAY_SIZE(dsa_configs);
|
|
const char **p = dsa_configs;
|
|
|
|
if (file_Exists(dsaDeviceFile) == 1)
|
|
return 0;
|
|
|
|
/* check the idxd driver */
|
|
if (file_Exists(dsaPasidEnable) != 1) {
|
|
printf("Please make sure idxd driver was loaded\n");
|
|
return 3;
|
|
}
|
|
|
|
/* Check SVA feature */
|
|
if (Check_DSA_Kernel_Setting() != 1) {
|
|
printf("Please enable SVA.(Add intel_iommu=on,sm_on in kernel cmdline)\n");
|
|
return 3;
|
|
}
|
|
|
|
/* Check the idxd device file on /dev/dsa/ */
|
|
for (int i = 0; i < len; i++) {
|
|
if (system(p[i]))
|
|
return 1;
|
|
}
|
|
|
|
/* After config, /dev/dsa/wq0.1 should be generated */
|
|
return (file_Exists(dsaDeviceFile) != 1);
|
|
}
|
|
|
|
/*
|
|
* Open DSA device file, triger API: iommu_sva_alloc_pasid
|
|
*/
|
|
void *allocate_dsa_pasid(void)
|
|
{
|
|
int fd;
|
|
void *wq;
|
|
|
|
fd = open(dsaDeviceFile, O_RDWR);
|
|
if (fd < 0) {
|
|
perror("open");
|
|
return MAP_FAILED;
|
|
}
|
|
|
|
wq = mmap(NULL, 0x1000, PROT_WRITE,
|
|
MAP_SHARED | MAP_POPULATE, fd, 0);
|
|
if (wq == MAP_FAILED)
|
|
perror("mmap");
|
|
|
|
return wq;
|
|
}
|
|
|
|
int set_force_svm(void)
|
|
{
|
|
int ret = 0;
|
|
|
|
ret = syscall(SYS_arch_prctl, ARCH_FORCE_TAGGED_SVA);
|
|
|
|
return ret;
|
|
}
|
|
|
|
int handle_pasid(struct testcases *test)
|
|
{
|
|
uint tmp = test->cmd;
|
|
uint runed = 0x0;
|
|
int ret = 0;
|
|
void *wq = NULL;
|
|
|
|
ret = Dsa_Init_Sysfs();
|
|
if (ret != 0)
|
|
return ret;
|
|
|
|
for (int i = 0; i < 3; i++) {
|
|
int err = 0;
|
|
|
|
if (tmp & 0x1) {
|
|
/* run set lam mode*/
|
|
if ((runed & 0x1) == 0) {
|
|
err = set_lam(LAM_U57_BITS);
|
|
runed = runed | 0x1;
|
|
} else
|
|
err = 1;
|
|
} else if (tmp & 0x4) {
|
|
/* run force svm */
|
|
if ((runed & 0x4) == 0) {
|
|
err = set_force_svm();
|
|
runed = runed | 0x4;
|
|
} else
|
|
err = 1;
|
|
} else if (tmp & 0x2) {
|
|
/* run allocate pasid */
|
|
if ((runed & 0x2) == 0) {
|
|
runed = runed | 0x2;
|
|
wq = allocate_dsa_pasid();
|
|
if (wq == MAP_FAILED)
|
|
err = 1;
|
|
} else
|
|
err = 1;
|
|
}
|
|
|
|
ret = ret + err;
|
|
if (ret > 0)
|
|
break;
|
|
|
|
tmp = tmp >> 4;
|
|
}
|
|
|
|
if (wq != MAP_FAILED && wq != NULL)
|
|
if (munmap(wq, 0x1000))
|
|
printf("munmap failed %d\n", errno);
|
|
|
|
if (runed != 0x7)
|
|
ret = 1;
|
|
|
|
return (ret != 0);
|
|
}
|
|
|
|
/*
|
|
* Pasid test depends on idxd and SVA, kernel should enable iommu and sm.
|
|
* command line(intel_iommu=on,sm_on)
|
|
*/
|
|
static struct testcases pasid_cases[] = {
|
|
{
|
|
.expected = 1,
|
|
.cmd = PAS_CMD(LAM_CMD_BIT, PAS_CMD_BIT, SVA_CMD_BIT),
|
|
.test_func = handle_pasid,
|
|
.msg = "PASID: [Negative] Execute LAM, PASID, SVA in sequence\n",
|
|
},
|
|
{
|
|
.expected = 0,
|
|
.cmd = PAS_CMD(LAM_CMD_BIT, SVA_CMD_BIT, PAS_CMD_BIT),
|
|
.test_func = handle_pasid,
|
|
.msg = "PASID: Execute LAM, SVA, PASID in sequence\n",
|
|
},
|
|
{
|
|
.expected = 1,
|
|
.cmd = PAS_CMD(PAS_CMD_BIT, LAM_CMD_BIT, SVA_CMD_BIT),
|
|
.test_func = handle_pasid,
|
|
.msg = "PASID: [Negative] Execute PASID, LAM, SVA in sequence\n",
|
|
},
|
|
{
|
|
.expected = 0,
|
|
.cmd = PAS_CMD(PAS_CMD_BIT, SVA_CMD_BIT, LAM_CMD_BIT),
|
|
.test_func = handle_pasid,
|
|
.msg = "PASID: Execute PASID, SVA, LAM in sequence\n",
|
|
},
|
|
{
|
|
.expected = 0,
|
|
.cmd = PAS_CMD(SVA_CMD_BIT, LAM_CMD_BIT, PAS_CMD_BIT),
|
|
.test_func = handle_pasid,
|
|
.msg = "PASID: Execute SVA, LAM, PASID in sequence\n",
|
|
},
|
|
{
|
|
.expected = 0,
|
|
.cmd = PAS_CMD(SVA_CMD_BIT, PAS_CMD_BIT, LAM_CMD_BIT),
|
|
.test_func = handle_pasid,
|
|
.msg = "PASID: Execute SVA, PASID, LAM in sequence\n",
|
|
},
|
|
};
|
|
|
|
int main(int argc, char **argv)
|
|
{
|
|
int c = 0;
|
|
unsigned int tests = TEST_MASK;
|
|
|
|
tests_cnt = 0;
|
|
|
|
if (!cpu_has_lam()) {
|
|
ksft_print_msg("Unsupported LAM feature!\n");
|
|
return -1;
|
|
}
|
|
|
|
while ((c = getopt(argc, argv, "ht:")) != -1) {
|
|
switch (c) {
|
|
case 't':
|
|
tests = strtoul(optarg, NULL, 16);
|
|
if (tests && !(tests & TEST_MASK)) {
|
|
ksft_print_msg("Invalid argument!\n");
|
|
return -1;
|
|
}
|
|
break;
|
|
case 'h':
|
|
cmd_help();
|
|
return 0;
|
|
default:
|
|
ksft_print_msg("Invalid argument\n");
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* When tests is 0, it is not a real test case;
|
|
* the option used by test case(execve) to check the lam mode in
|
|
* process generated by execve, the process read back lam mode and
|
|
* check with lam mode in parent process.
|
|
*/
|
|
if (!tests)
|
|
return (get_lam());
|
|
|
|
/* Run test cases */
|
|
if (tests & FUNC_MALLOC)
|
|
run_test(malloc_cases, ARRAY_SIZE(malloc_cases));
|
|
|
|
if (tests & FUNC_BITS)
|
|
run_test(bits_cases, ARRAY_SIZE(bits_cases));
|
|
|
|
if (tests & FUNC_MMAP)
|
|
run_test(mmap_cases, ARRAY_SIZE(mmap_cases));
|
|
|
|
if (tests & FUNC_SYSCALL)
|
|
run_test(syscall_cases, ARRAY_SIZE(syscall_cases));
|
|
|
|
if (tests & FUNC_URING)
|
|
run_test(uring_cases, ARRAY_SIZE(uring_cases));
|
|
|
|
if (tests & FUNC_INHERITE)
|
|
run_test(inheritance_cases, ARRAY_SIZE(inheritance_cases));
|
|
|
|
if (tests & FUNC_PASID)
|
|
run_test(pasid_cases, ARRAY_SIZE(pasid_cases));
|
|
|
|
ksft_set_plan(tests_cnt);
|
|
|
|
return ksft_exit_pass();
|
|
}
|