| /* |
| * memtester version 4 |
| * |
| * Very simple but very effective user-space memory tester. |
| * Originally by Simon Kirby <sim@stormix.com> <sim@neato.org> |
| * Version 2 by Charles Cazabon <charlesc-memtester@pyropus.ca> |
| * Version 3 not publicly released. |
| * Version 4 rewrite: |
| * Copyright (C) 2004-2010 Charles Cazabon <charlesc-memtester@pyropus.ca> |
| * Licensed under the terms of the GNU General Public License version 2 (only). |
| * See the file COPYING for details. |
| * |
| */ |
| |
| #define __version__ "4.2.2" |
| |
| #include <stddef.h> |
| #include <stdlib.h> |
| #include <stdio.h> |
| #include <sys/types.h> |
| #include <sys/stat.h> |
| #include <sys/mman.h> |
| #include <unistd.h> |
| #include <fcntl.h> |
| #include <errno.h> |
| |
| #include "types.h" |
| #include "sizes.h" |
| #include "tests.h" |
| |
| #define EXIT_FAIL_NONSTARTER 0x01 |
| #define EXIT_FAIL_ADDRESSLINES 0x02 |
| #define EXIT_FAIL_OTHERTEST 0x04 |
| |
| struct test tests[] = { |
| { "Random Value", test_random_value }, |
| { "Compare XOR", test_xor_comparison }, |
| { "Compare SUB", test_sub_comparison }, |
| { "Compare MUL", test_mul_comparison }, |
| { "Compare DIV",test_div_comparison }, |
| { "Compare OR", test_or_comparison }, |
| { "Compare AND", test_and_comparison }, |
| { "Sequential Increment", test_seqinc_comparison }, |
| { "Solid Bits", test_solidbits_comparison }, |
| { "Block Sequential", test_blockseq_comparison }, |
| { "Checkerboard", test_checkerboard_comparison }, |
| { "Bit Spread", test_bitspread_comparison }, |
| { "Bit Flip", test_bitflip_comparison }, |
| { "Walking Ones", test_walkbits1_comparison }, |
| { "Walking Zeroes", test_walkbits0_comparison }, |
| #ifdef TEST_NARROW_WRITES |
| { "8-bit Writes", test_8bit_wide_random }, |
| { "16-bit Writes", test_16bit_wide_random }, |
| #endif |
| { NULL, NULL } |
| }; |
| |
| /* Sanity checks and portability helper macros. */ |
| #ifdef _SC_VERSION |
| void check_posix_system(void) { |
| if (sysconf(_SC_VERSION) < 198808L) { |
| fprintf(stderr, "A POSIX system is required. Don't be surprised if " |
| "this craps out.\n"); |
| fprintf(stderr, "_SC_VERSION is %lu\n", sysconf(_SC_VERSION)); |
| } |
| } |
| #else |
| #define check_posix_system() |
| #endif |
| |
| #ifdef _SC_PAGE_SIZE |
| int memtester_pagesize(void) { |
| int pagesize = sysconf(_SC_PAGE_SIZE); |
| if (pagesize == -1) { |
| perror("get page size failed"); |
| exit(EXIT_FAIL_NONSTARTER); |
| } |
| printf("pagesize is %ld\n", (long) pagesize); |
| return pagesize; |
| } |
| #else |
| int memtester_pagesize(void) { |
| printf("sysconf(_SC_PAGE_SIZE) not supported; using pagesize of 8192\n"); |
| return 8192; |
| } |
| #endif |
| |
| /* Some systems don't define MAP_LOCKED. Define it to 0 here |
| so it's just a no-op when ORed with other constants. */ |
| #ifndef MAP_LOCKED |
| #define MAP_LOCKED 0 |
| #endif |
| |
| /* Function declarations */ |
| void usage(char *me); |
| |
| /* Global vars - so tests have access to this information */ |
| int use_phys = 0; |
| off_t physaddrbase = 0; |
| |
| /* Function definitions */ |
| void usage(char *me) { |
| fprintf(stderr, "\nUsage: %s [-p physaddrbase] <mem>[B|K|M|G] [loops]\n", me); |
| exit(EXIT_FAIL_NONSTARTER); |
| } |
| |
| int main(int argc, char **argv) { |
| ul loops, loop, i; |
| size_t pagesize, wantraw, wantmb, wantbytes, wantbytes_orig, bufsize, |
| halflen, count; |
| char *memsuffix, *addrsuffix, *loopsuffix; |
| ptrdiff_t pagesizemask; |
| void volatile *buf, *aligned; |
| ulv *bufa, *bufb; |
| int do_mlock = 1, done_mem = 0; |
| int exit_code = 0; |
| int memfd, opt, memshift; |
| size_t maxbytes = -1; /* addressable memory, in bytes */ |
| size_t maxmb = (maxbytes >> 20) + 1; /* addressable memory, in MB */ |
| |
| printf("memtester version " __version__ " (%d-bit)\n", UL_LEN); |
| printf("Copyright (C) 2010 Charles Cazabon.\n"); |
| printf("Licensed under the GNU General Public License version 2 (only).\n"); |
| printf("\n"); |
| check_posix_system(); |
| pagesize = memtester_pagesize(); |
| pagesizemask = (ptrdiff_t) ~(pagesize - 1); |
| printf("pagesizemask is 0x%tx\n", pagesizemask); |
| |
| while ((opt = getopt(argc, argv, "p:")) != -1) { |
| switch (opt) { |
| case 'p': |
| errno = 0; |
| physaddrbase = (off_t) strtoull(optarg, &addrsuffix, 16); |
| if (errno != 0) { |
| fprintf(stderr, |
| "failed to parse physaddrbase arg; should be hex " |
| "address (0x123...)\n"); |
| usage(argv[0]); /* doesn't return */ |
| } |
| if (*addrsuffix != '\0') { |
| /* got an invalid character in the address */ |
| fprintf(stderr, |
| "failed to parse physaddrbase arg; should be hex " |
| "address (0x123...)\n"); |
| usage(argv[0]); /* doesn't return */ |
| } |
| if (physaddrbase & (pagesize - 1)) { |
| fprintf(stderr, |
| "bad physaddrbase arg; does not start on page " |
| "boundary\n"); |
| usage(argv[0]); /* doesn't return */ |
| } |
| /* okay, got address */ |
| use_phys = 1; |
| break; |
| default: /* '?' */ |
| usage(argv[0]); /* doesn't return */ |
| } |
| } |
| |
| if (optind >= argc) { |
| fprintf(stderr, "need memory argument, in MB\n"); |
| usage(argv[0]); /* doesn't return */ |
| } |
| |
| errno = 0; |
| wantraw = (size_t) strtoul(argv[optind], &memsuffix, 0); |
| if (errno != 0) { |
| fprintf(stderr, "failed to parse memory argument"); |
| usage(argv[0]); /* doesn't return */ |
| } |
| switch (*memsuffix) { |
| case 'G': |
| case 'g': |
| memshift = 30; /* gigabytes */ |
| break; |
| case 'M': |
| case 'm': |
| memshift = 20; /* megabytes */ |
| break; |
| case 'K': |
| case 'k': |
| memshift = 10; /* kilobytes */ |
| break; |
| case 'B': |
| case 'b': |
| memshift = 0; /* bytes*/ |
| break; |
| case '\0': /* no suffix */ |
| memshift = 20; /* megabytes */ |
| break; |
| default: |
| /* bad suffix */ |
| usage(argv[0]); /* doesn't return */ |
| } |
| wantbytes_orig = wantbytes = ((size_t) wantraw << memshift); |
| wantmb = (wantbytes_orig >> 20); |
| optind++; |
| if (wantmb > maxmb) { |
| fprintf(stderr, "This system can only address %llu MB.\n", (ull) maxmb); |
| exit(EXIT_FAIL_NONSTARTER); |
| } |
| if (wantbytes < pagesize) { |
| fprintf(stderr, "bytes %ld < pagesize %ld -- memory argument too large?\n", |
| wantbytes, pagesize); |
| exit(EXIT_FAIL_NONSTARTER); |
| } |
| |
| if (optind >= argc) { |
| loops = 0; |
| } else { |
| errno = 0; |
| loops = strtoul(argv[optind], &loopsuffix, 0); |
| if (errno != 0) { |
| fprintf(stderr, "failed to parse number of loops"); |
| usage(argv[0]); /* doesn't return */ |
| } |
| if (*loopsuffix != '\0') { |
| fprintf(stderr, "loop suffix %c\n", *loopsuffix); |
| usage(argv[0]); /* doesn't return */ |
| } |
| } |
| |
| printf("want %lluMB (%llu bytes)\n", (ull) wantmb, (ull) wantbytes); |
| buf = NULL; |
| |
| if (use_phys) { |
| memfd = open("/dev/mem", O_RDWR | O_SYNC); |
| if (memfd == -1) { |
| perror("failed to open /dev/mem for physical memory"); |
| exit(EXIT_FAIL_NONSTARTER); |
| } |
| buf = (void volatile *) mmap(0, wantbytes, PROT_READ | PROT_WRITE, |
| MAP_SHARED | MAP_LOCKED, memfd, |
| physaddrbase); |
| if (buf == MAP_FAILED) { |
| perror("failed to mmap /dev/mem for physical memory"); |
| exit(EXIT_FAIL_NONSTARTER); |
| } |
| |
| if (mlock((void *) buf, wantbytes) < 0) { |
| fprintf(stderr, "failed to mlock mmap'ed space\n"); |
| do_mlock = 0; |
| } |
| |
| bufsize = wantbytes; /* accept no less */ |
| aligned = buf; |
| done_mem = 1; |
| } |
| |
| while (!done_mem) { |
| while (!buf && wantbytes) { |
| buf = (void volatile *) malloc(wantbytes); |
| if (!buf) wantbytes -= pagesize; |
| } |
| bufsize = wantbytes; |
| printf("got %lluMB (%llu bytes)", (ull) wantbytes >> 20, |
| (ull) wantbytes); |
| fflush(stdout); |
| if (do_mlock) { |
| printf(", trying mlock ..."); |
| fflush(stdout); |
| if ((size_t) buf % pagesize) { |
| /* printf("aligning to page -- was 0x%tx\n", buf); */ |
| aligned = (void volatile *) ((size_t) buf & pagesizemask) + pagesize; |
| /* printf(" now 0x%tx -- lost %d bytes\n", aligned, |
| * (size_t) aligned - (size_t) buf); |
| */ |
| bufsize -= ((size_t) aligned - (size_t) buf); |
| } else { |
| aligned = buf; |
| } |
| /* Try mlock */ |
| if (mlock((void *) aligned, bufsize) < 0) { |
| switch(errno) { |
| case EAGAIN: /* BSDs */ |
| printf("over system/pre-process limit, reducing...\n"); |
| free((void *) buf); |
| buf = NULL; |
| wantbytes -= pagesize; |
| break; |
| case ENOMEM: |
| printf("too many pages, reducing...\n"); |
| free((void *) buf); |
| buf = NULL; |
| wantbytes -= pagesize; |
| break; |
| case EPERM: |
| printf("insufficient permission.\n"); |
| printf("Trying again, unlocked:\n"); |
| do_mlock = 0; |
| free((void *) buf); |
| buf = NULL; |
| wantbytes = wantbytes_orig; |
| break; |
| default: |
| printf("failed for unknown reason.\n"); |
| do_mlock = 0; |
| done_mem = 1; |
| } |
| } else { |
| printf("locked.\n"); |
| done_mem = 1; |
| } |
| } else { |
| done_mem = 1; |
| printf("\n"); |
| } |
| } |
| |
| if (!do_mlock) fprintf(stderr, "Continuing with unlocked memory; testing " |
| "will be slower and less reliable.\n"); |
| |
| halflen = bufsize / 2; |
| count = halflen / sizeof(ul); |
| bufa = (ulv *) aligned; |
| bufb = (ulv *) ((size_t) aligned + halflen); |
| |
| for(loop=1; ((!loops) || loop <= loops); loop++) { |
| printf("Loop %lu", loop); |
| if (loops) { |
| printf("/%lu", loops); |
| } |
| printf(":\n"); |
| printf(" %-20s: ", "Stuck Address"); |
| fflush(stdout); |
| if (!test_stuck_address(aligned, bufsize / sizeof(ul))) { |
| printf("ok\n"); |
| } else { |
| exit_code |= EXIT_FAIL_ADDRESSLINES; |
| } |
| for (i=0;;i++) { |
| if (!tests[i].name) break; |
| printf(" %-20s: ", tests[i].name); |
| if (!tests[i].fp(bufa, bufb, count)) { |
| printf("ok\n"); |
| } else { |
| exit_code |= EXIT_FAIL_OTHERTEST; |
| } |
| fflush(stdout); |
| } |
| printf("\n"); |
| fflush(stdout); |
| } |
| if (do_mlock) munlock((void *) aligned, bufsize); |
| printf("Done.\n"); |
| fflush(stdout); |
| exit(exit_code); |
| } |