/* * creates a testfile, 'mmap's it, and checks its content reading * page back to front. If a data error is found, the same page is read * over and over again, until data is eventually correct after some time. * * This points out a cache problem in the ARM linux kernel * Using the cache in Write-Through mode (kernel command line option: cachepolicy=writethrough) * or CONFIG_XSCALE_CACHE_ERRATA=y in older kernels prevents this problem * * (C) Lothar Wassmann, * */ #include #include #include #include #include #include #include #include #include #define PAGE_SIZE 4096 #define PAGE_SIZE_INT ((PAGE_SIZE)/sizeof(unsigned long)) #define PAGE_MASK ((PAGE_SIZE)-1) size_t file_size = 256 * PAGE_SIZE; unsigned long *buf=NULL; const char* fn="testfile"; void usage(const char* name) { printf("%s [filename]\n", name); printf("\trequires to be defined in /etc/fstab\n"); printf("\t will be unmounted and remounted during the test\n"); } int create_file(const char* name, size_t size) { int ret=0; int i; int fd; fd = open(name, O_CREAT|O_RDWR|O_SYNC|O_TRUNC, S_IWUSR|S_IRUSR|S_IRGRP|S_IROTH); if (fd < 0) { fprintf(stderr, "Failed to open '%s' for writing, errno=%d\n", name, errno); return errno; } for (i = size / sizeof(*buf); i > 0; i--) { buf[i-1] = i; } write(fd, buf, size); memset(buf, 0x55, size); close(fd); return ret; } int do_check(int fd, void *mapptr, size_t size) { const int num_pages=size/PAGE_SIZE; volatile unsigned char *ptr=mapptr; int errors = 0; int soft = 0; int page; printf("Checking data from %08lx to %08lx\n", (unsigned long)(ptr + size), (unsigned long)ptr); for (page = num_pages - 1; page >= 0; page--) { volatile unsigned long *pp=(volatile unsigned long *)&ptr[page*PAGE_SIZE]; int offs; int page_errs=0; int err_offs=-1; for (offs = 0; offs < PAGE_SIZE; offs += sizeof(unsigned long)) { volatile unsigned long *lp=&pp[offs/sizeof(unsigned long)]; unsigned long data=*lp; unsigned long ref=(((page*PAGE_SIZE)+offs)/sizeof(data)) + 1; if (data != ref) { const int max_tries=100000; int retries=max_tries; unsigned long new_data=*lp; errors++; page_errs++; while ((new_data != ref) && (--retries > 0)) { if (data != new_data) { fprintf(stderr, "Data @ page %03x:%03x (%08lx) changed to %08lx(%08lx)\n", page, offs, (unsigned long)lp, new_data, ref); } data = new_data; new_data = *lp; } if (new_data == ref) { fprintf(stderr, "Data @ page %03x:%03x (%08lx) OK after %d retries: %08lx\n", page, offs, (unsigned long)lp, max_tries - retries, new_data); soft++; } else { if (err_offs != offs) { fprintf(stderr, "Data error @ page %03x:%03x (%08lx): %08lx -> %08lx\n", page, offs, (unsigned long)lp, ref, data); err_offs = offs; } // retry the same page again, until data is correct offs = 0; } } } if (page_errs) { page = num_pages; } } fprintf(stderr, "Errors reverse check: %d; soft: %d; total bytes %d in %d pages\n", errors, soft, size, num_pages); return errors; } int check_file(const char* name, size_t size) { int ret=0; int fd; void *ptr=NULL; int errors=0; int last_errors=0; fd = open(name, O_RDONLY|O_SYNC); if (fd < 0) { fprintf(stderr, "Failed to open '%s' for reading\n", name); return errno; } ptr = mmap(NULL, size, PROT_READ, MAP_PRIVATE, fd, 0); if (ptr == MAP_FAILED) { close(fd); return -ENOMEM; } printf("Checking file '%s'\n", name); do { last_errors = errors; errors = do_check(fd, ptr, size); if (errors != 0) { ret = errors; } } while (errors > 0 && errors != last_errors); if (munmap(ptr, size) != 0) { fprintf(stderr, "Failed to unmap %08lx\n", (unsigned long)ptr); if (ret == 0) { ret = -ENOMEM; } } close(fd); if (buf != NULL) { memset(buf, 0x55, size); } if (ret == 0) { printf("check successful\n"); } else { printf("check failed\n"); } return ret; } int main(int argc, char *argv[]) { int rc=0; char fname[100]; char mount[44]; char umount[44]; if (argc < 2) { // first argument is required usage(argv[0]); return 1; } if (argc > 2) { // take optional second argument as filename fn = argv[2]; } sprintf(fname, "%s/%s", argv[1], fn); sprintf(mount, "mount %s", argv[1]); sprintf(umount, "umount %s", argv[1]); file_size &= ~PAGE_MASK; // round size to page boundary buf = malloc(file_size); if (buf == NULL) { fprintf(stderr, "Failed to allocate buffer\n"); rc = -ENOMEM; } printf("Mounting '%s'\n", argv[1]); system(mount); while (rc == 0) { printf("Opening '%s'\n", fname); rc = create_file(fname, file_size); if (rc != 0) { fprintf(stderr, "Failed to create file '%s', rc=%d\n", fname, rc); break; } printf("Unmounting '%s'\n", argv[1]); system(umount); printf("Remounting '%s'\n", argv[1]); system(mount); rc = check_file(fname, file_size); } if (buf != NULL) { free(buf); } return rc; }