| /* | 
 |  * 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; | 
 | } | 
 |  | 
 | int test_anon(void) | 
 | { | 
 | 	unsigned long align; | 
 | 	struct sigaction act = { | 
 | 		.sa_sigaction = segv, | 
 | 		.sa_flags = SA_SIGINFO | 
 | 	}; | 
 | 	void *mallocblock; | 
 | 	unsigned long mallocsize; | 
 |  | 
 | 	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; | 
 |  | 
 | 	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"); | 
 | } |