| // SPDX-License-Identifier: GPL-2.0 | |
| #define _GNU_SOURCE | |
| #include <stdio.h> | |
| #include <stdlib.h> | |
| #include <string.h> | |
| #include <sys/syscall.h> | |
| #include <sys/ioctl.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 | |
| /* get_user() pointer test cases */ | |
| #define GET_USER_USER 0 | |
| #define GET_USER_KERNEL_TOP 1 | |
| #define GET_USER_KERNEL_BOT 2 | |
| #define GET_USER_KERNEL 3 | |
| #define TEST_MASK 0x7f | |
| #define L5_SIGN_EXT_MASK (0xFFUL << 56) | |
| #define L4_SIGN_EXT_MASK (0x1FFFFUL << 47) | |
| #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 lam_is_available(void) | |
| { | |
| unsigned int cpuinfo[4]; | |
| unsigned long bits = 0; | |
| int ret; | |
| __cpuid_count(0x7, 1, cpuinfo[0], cpuinfo[1], cpuinfo[2], cpuinfo[3]); | |
| /* Check if cpu supports LAM */ | |
| if (!(cpuinfo[0] & (1 << 26))) { | |
| ksft_print_msg("LAM is not supported!\n"); | |
| return 0; | |
| } | |
| /* Return 0 if CONFIG_ADDRESS_MASKING is not set */ | |
| ret = syscall(SYS_arch_prctl, ARCH_GET_MAX_TAG_BITS, &bits); | |
| if (ret) { | |
| ksft_print_msg("LAM is disabled in the kernel!\n"); | |
| return 0; | |
| } | |
| return 1; | |
| } | |
| static inline int la57_enabled(void) | |
| { | |
| int ret; | |
| void *p; | |
| p = mmap((void *)HIGH_ADDR, PAGE_SIZE, PROT_READ | PROT_WRITE, | |
| MAP_PRIVATE | MAP_ANONYMOUS | MAP_FIXED, -1, 0); | |
| ret = p == MAP_FAILED ? 0 : 1; | |
| munmap(p, PAGE_SIZE); | |
| return ret; | |
| } | |
| /* | |
| * 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 metadata 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 (!la57_enabled()) | |
| 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; | |
| } | |
| static int get_user_syscall(struct testcases *test) | |
| { | |
| uint64_t ptr_address, bitmask; | |
| int fd, ret = 0; | |
| void *ptr; | |
| if (la57_enabled()) { | |
| bitmask = L5_SIGN_EXT_MASK; | |
| ptr_address = HIGH_ADDR; | |
| } else { | |
| bitmask = L4_SIGN_EXT_MASK; | |
| ptr_address = LOW_ADDR; | |
| } | |
| ptr = mmap((void *)ptr_address, PAGE_SIZE, PROT_READ | PROT_WRITE, | |
| MAP_PRIVATE | MAP_ANONYMOUS | MAP_FIXED, -1, 0); | |
| if (ptr == MAP_FAILED) { | |
| perror("failed to map byte to pass into get_user"); | |
| return 1; | |
| } | |
| if (set_lam(test->lam) != 0) { | |
| ret = 2; | |
| goto error; | |
| } | |
| fd = memfd_create("lam_ioctl", 0); | |
| if (fd == -1) { | |
| munmap(ptr, PAGE_SIZE); | |
| exit(EXIT_FAILURE); | |
| } | |
| switch (test->later) { | |
| case GET_USER_USER: | |
| /* Control group - properly tagged user pointer */ | |
| ptr = (void *)set_metadata((uint64_t)ptr, test->lam); | |
| break; | |
| case GET_USER_KERNEL_TOP: | |
| /* Kernel address with top bit cleared */ | |
| bitmask &= (bitmask >> 1); | |
| ptr = (void *)((uint64_t)ptr | bitmask); | |
| break; | |
| case GET_USER_KERNEL_BOT: | |
| /* Kernel address with bottom sign-extension bit cleared */ | |
| bitmask &= (bitmask << 1); | |
| ptr = (void *)((uint64_t)ptr | bitmask); | |
| break; | |
| case GET_USER_KERNEL: | |
| /* Try to pass a kernel address */ | |
| ptr = (void *)((uint64_t)ptr | bitmask); | |
| break; | |
| default: | |
| printf("Invalid test case value passed!\n"); | |
| break; | |
| } | |
| /* | |
| * Use FIOASYNC ioctl because it utilizes get_user() internally and is | |
| * very non-invasive to the system. Pass differently tagged pointers to | |
| * get_user() in order to verify that valid user pointers are going | |
| * through and invalid kernel/non-canonical pointers are not. | |
| */ | |
| if (ioctl(fd, FIOASYNC, ptr) != 0) | |
| ret = 1; | |
| close(fd); | |
| error: | |
| munmap(ptr, PAGE_SIZE); | |
| 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) | |
| goto cleanup; | |
| 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) | |
| goto cleanup; | |
| fi->file_sz = file_sz; | |
| fi->file_fd = file_fd; | |
| ring = malloc(sizeof(*ring)); | |
| if (!ring) { | |
| free(fi); | |
| goto cleanup; | |
| } | |
| 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); | |
| cleanup: | |
| close(file_fd); | |
| 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", | |
| }, | |
| { | |
| .later = GET_USER_USER, | |
| .lam = LAM_U57_BITS, | |
| .test_func = get_user_syscall, | |
| .msg = "GET_USER: get_user() and pass a properly tagged user pointer.\n", | |
| }, | |
| { | |
| .later = GET_USER_KERNEL_TOP, | |
| .expected = 1, | |
| .lam = LAM_U57_BITS, | |
| .test_func = get_user_syscall, | |
| .msg = "GET_USER:[Negative] get_user() with a kernel pointer and the top bit cleared.\n", | |
| }, | |
| { | |
| .later = GET_USER_KERNEL_BOT, | |
| .expected = 1, | |
| .lam = LAM_U57_BITS, | |
| .test_func = get_user_syscall, | |
| .msg = "GET_USER:[Negative] get_user() with a kernel pointer and the bottom sign-extension bit cleared.\n", | |
| }, | |
| { | |
| .later = GET_USER_KERNEL, | |
| .expected = 1, | |
| .lam = LAM_U57_BITS, | |
| .test_func = get_user_syscall, | |
| .msg = "GET_USER:[Negative] get_user() and pass a kernel pointer.\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); | |
| close(fd); | |
| 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 (!lam_is_available()) | |
| return KSFT_SKIP; | |
| 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); | |
| ksft_exit_pass(); | |
| } |