| /* | |
| * Copyright IBM Corp. | |
| * | |
| * This program is free software; you can redistribute it and/or modify it | |
| * under the terms of version 2.1 of the GNU Lesser General Public License | |
| * as published by the Free Software Foundation. | |
| * | |
| * This program is distributed in the hope that it would be useful, but | |
| * WITHOUT ANY WARRANTY; without even the implied warranty of | |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. | |
| * | |
| */ | |
| #include <assert.h> | |
| #include <errno.h> | |
| #include <fcntl.h> | |
| #include <signal.h> | |
| #include <stdarg.h> | |
| #include <stdio.h> | |
| #include <stdlib.h> | |
| #include <string.h> | |
| #include <sys/mman.h> | |
| #include <sys/ptrace.h> | |
| #include <sys/syscall.h> | |
| #include <ucontext.h> | |
| #include <unistd.h> | |
| #include "utils.h" | |
| char *file_name; | |
| int in_test; | |
| volatile int faulted; | |
| volatile void *dar; | |
| int errors; | |
| static void segv(int signum, siginfo_t *info, void *ctxt_v) | |
| { | |
| ucontext_t *ctxt = (ucontext_t *)ctxt_v; | |
| struct pt_regs *regs = ctxt->uc_mcontext.regs; | |
| if (!in_test) { | |
| fprintf(stderr, "Segfault outside of test !\n"); | |
| exit(1); | |
| } | |
| faulted = 1; | |
| dar = (void *)regs->dar; | |
| regs->nip += 4; | |
| } | |
| static inline void do_read(const volatile void *addr) | |
| { | |
| int ret; | |
| asm volatile("lwz %0,0(%1); twi 0,%0,0; isync;\n" | |
| : "=r" (ret) : "r" (addr) : "memory"); | |
| } | |
| static inline void do_write(const volatile void *addr) | |
| { | |
| int val = 0x1234567; | |
| asm volatile("stw %0,0(%1); sync; \n" | |
| : : "r" (val), "r" (addr) : "memory"); | |
| } | |
| static inline void check_faulted(void *addr, long page, long subpage, int write) | |
| { | |
| int want_fault = (subpage == ((page + 3) % 16)); | |
| if (write) | |
| want_fault |= (subpage == ((page + 1) % 16)); | |
| if (faulted != want_fault) { | |
| printf("Failed at %p (p=%ld,sp=%ld,w=%d), want=%s, got=%s !\n", | |
| addr, page, subpage, write, | |
| want_fault ? "fault" : "pass", | |
| faulted ? "fault" : "pass"); | |
| ++errors; | |
| } | |
| if (faulted) { | |
| if (dar != addr) { | |
| printf("Fault expected at %p and happened at %p !\n", | |
| addr, dar); | |
| } | |
| faulted = 0; | |
| asm volatile("sync" : : : "memory"); | |
| } | |
| } | |
| static int run_test(void *addr, unsigned long size) | |
| { | |
| unsigned int *map; | |
| long i, j, pages, err; | |
| pages = size / 0x10000; | |
| map = malloc(pages * 4); | |
| assert(map); | |
| /* | |
| * for each page, mark subpage i % 16 read only and subpage | |
| * (i + 3) % 16 inaccessible | |
| */ | |
| for (i = 0; i < pages; i++) { | |
| map[i] = (0x40000000 >> (((i + 1) * 2) % 32)) | | |
| (0xc0000000 >> (((i + 3) * 2) % 32)); | |
| } | |
| err = syscall(__NR_subpage_prot, addr, size, map); | |
| if (err) { | |
| perror("subpage_perm"); | |
| return 1; | |
| } | |
| free(map); | |
| in_test = 1; | |
| errors = 0; | |
| for (i = 0; i < pages; i++) { | |
| for (j = 0; j < 16; j++, addr += 0x1000) { | |
| do_read(addr); | |
| check_faulted(addr, i, j, 0); | |
| do_write(addr); | |
| check_faulted(addr, i, j, 1); | |
| } | |
| } | |
| in_test = 0; | |
| if (errors) { | |
| printf("%d errors detected\n", errors); | |
| return 1; | |
| } | |
| return 0; | |
| } | |
| static int syscall_available(void) | |
| { | |
| int rc; | |
| errno = 0; | |
| rc = syscall(__NR_subpage_prot, 0, 0, 0); | |
| return rc == 0 || (errno != ENOENT && errno != ENOSYS); | |
| } | |
| int test_anon(void) | |
| { | |
| unsigned long align; | |
| struct sigaction act = { | |
| .sa_sigaction = segv, | |
| .sa_flags = SA_SIGINFO | |
| }; | |
| void *mallocblock; | |
| unsigned long mallocsize; | |
| SKIP_IF(!syscall_available()); | |
| if (getpagesize() != 0x10000) { | |
| fprintf(stderr, "Kernel page size must be 64K!\n"); | |
| return 1; | |
| } | |
| sigaction(SIGSEGV, &act, NULL); | |
| mallocsize = 4 * 16 * 1024 * 1024; | |
| FAIL_IF(posix_memalign(&mallocblock, 64 * 1024, mallocsize)); | |
| align = (unsigned long)mallocblock; | |
| if (align & 0xffff) | |
| align = (align | 0xffff) + 1; | |
| mallocblock = (void *)align; | |
| printf("allocated malloc block of 0x%lx bytes at %p\n", | |
| mallocsize, mallocblock); | |
| printf("testing malloc block...\n"); | |
| return run_test(mallocblock, mallocsize); | |
| } | |
| int test_file(void) | |
| { | |
| struct sigaction act = { | |
| .sa_sigaction = segv, | |
| .sa_flags = SA_SIGINFO | |
| }; | |
| void *fileblock; | |
| off_t filesize; | |
| int fd; | |
| SKIP_IF(!syscall_available()); | |
| fd = open(file_name, O_RDWR); | |
| if (fd == -1) { | |
| perror("failed to open file"); | |
| return 1; | |
| } | |
| sigaction(SIGSEGV, &act, NULL); | |
| filesize = lseek(fd, 0, SEEK_END); | |
| if (filesize & 0xffff) | |
| filesize &= ~0xfffful; | |
| fileblock = mmap(NULL, filesize, PROT_READ | PROT_WRITE, | |
| MAP_SHARED, fd, 0); | |
| if (fileblock == MAP_FAILED) { | |
| perror("failed to map file"); | |
| return 1; | |
| } | |
| printf("allocated %s for 0x%lx bytes at %p\n", | |
| file_name, filesize, fileblock); | |
| printf("testing file map...\n"); | |
| return run_test(fileblock, filesize); | |
| } | |
| int main(int argc, char *argv[]) | |
| { | |
| int rc; | |
| rc = test_harness(test_anon, "subpage_prot_anon"); | |
| if (rc) | |
| return rc; | |
| if (argc > 1) | |
| file_name = argv[1]; | |
| else | |
| file_name = "tempfile"; | |
| return test_harness(test_file, "subpage_prot_file"); | |
| } |