/******************************************************************************* HWDD Memory module Copyright (c) 2011 Network Appliance, Inc. hwdd_memtest.c: initialization and memory specific support routines ********************************************************************************/ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "hwdd_mem.h" #define DRV_VERSION HWDD_MEM_VERSION #define HWDD_DEVNAME "hwdd_mem" #define MODE_ACPI 0 #define MODE_OS 1 //static int hwdd_os_einj_support(void); static int (*memory_test)(u64 virt_start, u64 virt_end, u64, pid_t ppid, void* ptr); /* will hold the pointer to the test algo */ static int (*memory_test_pattern)(u64 virt_start, u64 virt_end, char pattern[], u64, pid_t ppid, void* ptr); static void fill_testable_range(hwdd_user_args *); static int hdd_detail_index = 0; static int hwddmem_major; int max_pattern_length; // holds the maximum pattern length static u64 ioremap_size=MAX_REMAPSIZE; dev_t hwddmem_dev; struct cdev *mem_cdev ; static struct workqueue_struct *hm_wq; static hwdd_memdetails hdd_mem; static inline char *get_test_name(int index); static void e820_print_type(char *buf, u32 type, void* log_ptr); static void hwdd_is_range_usable(u64 start, u64 end, unsigned int *index); static void hwdd_print_e820bios_map(void *); static void hwdd_display_mem_info(void *); static int hwdd_is_testing(u64 start, u64 end); static int hwdd_invoke_memory_test(u64 start, u64 size, u16 test_index, char hex_pattern[], u8 cache_status,char *); static void hwdd_runtest(struct work_struct *work); static int hwdd_memory_test(u64 phys_start, u64 phys_end, u16 test_index, pid_t pid, u8 cache_status, char hex_pattern[],void *ptr); static void hwdd_set_stop_pid(pid_t pid); static unsigned long hwdd_preempt(unsigned long expire); static int hwdd_user_stop(pid_t pid); static int hwdd_memory_test_this_cpu(u64 phys_start, u64 phys_end, u8 test_index, u8 cache_status, char hex_pattern[],char * ); static int set_launch_pid(pid_t pid, u64 phys_start, u64 phys_end, u8 test_index,char *); static int set_pid_status(pid_t pid, u8 status, int err_cnt); static void clear_launch_pid(pid_t pid, u8 status, u8 test_index, char * ); static int hwdd_testrange(u64 phys_start, u64 phys_end, u8 cache_status, u8 test_index,char hex_pattern[],char *); static inline void hwdd_display_type(u8 type); static inline int hwdd_not_std_type(u32 type); static int hwdd_mem_open(struct inode *inode, struct file *fp); static int hwdd_mem_close(struct inode *inode, struct file *fp); static long hwdd_mem_ioctl(struct file *filep, unsigned int cmd, unsigned long arg); extern int hwdd_inject_memerr(u8,u64,u8, void *); //static int hwdd_os_inject_memerr(u8 type, u64 address) //{ /*TODO: Add OS support for error injection*/ // return -1; //} /* * hwdd_os_einj_support, return 1 if OS supports error injection */ //static int hwdd_os_einj_support(void) //{ /*TODO: Add OS support for error injection*/ // return -1; //} /* * get_test_name : returns test name based on given index */ static inline char *get_test_name(int index) { return test_names[index]; } /* * e820_print_type - displays the type of physical range */ static void e820_print_type(char *buf, u32 type, void *log_ptr) { switch (type) { case E820_RAM: case E820_RESERVED_KERN: hwdd_printk(HWDD_INFO,log_ptr," %s (usable)" , buf); break; case E820_RESERVED: hwdd_printk(HWDD_INFO,log_ptr, " %s (reserved)" , buf); break; case E820_ACPI: hwdd_printk(HWDD_INFO,log_ptr, " %s (ACPI data)" , buf); break; case E820_NVS: hwdd_printk(HWDD_INFO,log_ptr, " %s (ACPI NVS)" , buf); break; case E820_UNUSABLE: hwdd_printk(HWDD_INFO,log_ptr, " %s (unusable)" , buf); break; default: hwdd_printk(HWDD_INFO,log_ptr, " %s type %u" , buf, type); break; } } /* * hwdd_is_range_usable - Get the testable memory range from the kernel provided maps * Summary of what we are doing * 1) The current assumption is that, user would reserve memory above 4GB for us using the "mem=4G" option * Dec 4, 2011, changing this to support memory below 4GB also. * 2) The BIOS might reserve the top of memory for NVMEM, so should consider this case (the last mem map for both BIOS and kernel will be same) * 3) For now, we will consider that the only other reservation (other than HWDD) done above 4 GB is by NVMEM, rest all(if any) will be ignored * 3) Proceed as * 3.1) If the total memory present is above 4GB and top of memory is reserved by BIOS then it is NVMEM * 3.2) Start looking at the kernel map from ranges above 4GB, grab any memory marked reserved (other than NVMEM) for mem testing */ static void hwdd_is_range_usable(u64 start, u64 end, unsigned int *index) { int i; for (i = 0; i < e820.nr_map; i++) { if (e820.map[i].addr == start) { /* if it is reserved then claim it as we assume that only we reserve above 4GB in HWDD kernels*/ if (e820.map[i].type == E820_RESERVED) { break; } else { printk(KERN_INFO "Cannot Claim memory !\n"); return ; // cannot claim } /* TODO: Take care of partial ranges (may not be a possibility yet ) */ } } /* claim it */ hdd_mem.mem_ranges[*index].phys_mem_start = start; hdd_mem.mem_ranges[*index].phys_mem_end = end; hdd_mem.mem_ranges[*index].mem_type = HDD_TYPE_DRAM; hdd_mem.index = *index; (*index)++; return ; } /* * hwdd_display_type - Differentiate DRAM and NVMEM */ static inline void hwdd_display_type(u8 type) { switch (type) { case HDD_TYPE_DRAM: printk(KERN_CONT "System RAM"); break; case HDD_TYPE_NVMEM: printk(KERN_CONT "NVMEM"); break; } } /* * hwdd_not_std_type - verify the type, returns 0 if a standard type else -1 */ static inline int hwdd_not_std_type(u32 type) { if ((type == E820_RAM) || (type == E820_RESERVED_KERN) || (type == E820_ACPI) || (type == E820_NVS) || (type == E820_UNUSABLE)) { return 0; } return 1; } /* * hwdd_display_mem_info- Give the detailed information on the memory layout */ static void hwdd_display_mem_info(void *log_ptr) { int i; hwdd_print_e820bios_map(log_ptr); hwdd_printk(HWDD_INFO,log_ptr, "\n Physical Memory Start :%016Lx (%dMB)", hdd_mem.phys_mem_start,(int) (hdd_mem.phys_mem_start/MEG)); hwdd_printk(HWDD_INFO,log_ptr, " Physical Memory End :%016Lx (%dMB)\n", hdd_mem.phys_mem_end,(int) (hdd_mem.phys_mem_end/MEG)); hwdd_printk(HWDD_INFO,log_ptr, " DRAM Start :%016Lx (%dMB)", hdd_mem.phys_mem_start,(int) (hdd_mem.phys_mem_start/MEG)); hwdd_printk(HWDD_INFO,log_ptr, " DRAM End :%016Lx (%dMB)\n", hdd_mem.mem_ranges[hdd_mem.index].phys_mem_end, (int) (hdd_mem.mem_ranges[hdd_mem.index].phys_mem_end/MEG)); if (hdd_mem.mem_ranges[HDD_MAX_RANGE-1].mem_type == HDD_TYPE_NVMEM) { hwdd_printk(HWDD_INFO,log_ptr, " NVMEM Start :%016Lx (%dMB)", hdd_mem.mem_ranges[HDD_MAX_RANGE-1].phys_mem_start, (int)(hdd_mem.mem_ranges[HDD_MAX_RANGE-1].phys_mem_start/MEG)); hwdd_printk(HWDD_INFO,log_ptr, " NVMEM End :%016Lx (%dMB)", hdd_mem.mem_ranges[HDD_MAX_RANGE-1].phys_mem_end, (int)(hdd_mem.mem_ranges[HDD_MAX_RANGE-1].phys_mem_end/MEG)); } hwdd_printk(HWDD_INFO,log_ptr, "\n HWDD Testable Memory Ranges\n"); for (i=0; i 4G and last entry in both mem maps are reserved then it is NVMEM */ if ((e820_saved.map[e820_saved.nr_map -1].addr > GIG4) && ( (e820_saved.map[e820_saved.nr_map -1].type == E820_RESERVED) || (hwdd_not_std_type(e820_saved.map[e820_saved.nr_map -1].type)))) { printk(KERN_INFO "Detected NVMEM, index :%d Start :%016Lx End :%016Lx\n" ,e820_saved.nr_map -1, (unsigned long long) e820_saved.map[e820_saved.nr_map -1].addr, (unsigned long long)(e820_saved.map[e820_saved.nr_map -1].addr + e820_saved.map[e820_saved.nr_map -1].size)); printk(KERN_INFO "%d: %016Lx - %016Lx ", e820_saved.nr_map -1, (unsigned long long) e820_saved.map[e820_saved.nr_map -1].addr, (unsigned long long) (e820_saved.map[e820_saved.nr_map -1].addr + e820_saved.map[e820_saved.nr_map -1].size)); hdd_mem.mem_ranges[HDD_MAX_RANGE -1].phys_mem_start = (unsigned long long) e820_saved.map[e820_saved.nr_map -1].addr; hdd_mem.mem_ranges[HDD_MAX_RANGE -1].phys_mem_end = (unsigned long long)(e820_saved.map[e820_saved.nr_map -1].addr + e820_saved.map[e820_saved.nr_map -1].size); hdd_mem.mem_ranges[HDD_MAX_RANGE -1].mem_type = HDD_TYPE_NVMEM; num_entries --; /* ignore NVMEM */ } else { printk(KERN_INFO "\n No NVMEM Detected \n"); } /* ok, took care of the NVMEM part, now see if we can get any memory for testing Look at the BIOS given ranges, if they are marked usable, look for them in the final map, they should either be not there at all or be marked reserved. */ for (i=0; i < num_entries; i++) { u64 start; if ((e820_saved.map[i].type == E820_RESERVED_KERN) || (e820_saved.map[i].type ==E820_RAM )) { if ((e820_saved.map[i].addr + e820_saved.map[i].size) > (u64)max_low_pfn * PAGE_SIZE) { if (e820_saved.map[i].addr < (u64)max_low_pfn * PAGE_SIZE) start = (u64)max_low_pfn * PAGE_SIZE; else start = e820_saved.map[i].addr; hwdd_is_range_usable(start, (u64)(e820_saved.map[i].addr + e820_saved.map[i].size), &hdd_detail_index); } } } /* display the memory range information */ for (i=0; i phys_addr) { break; } if (hdd_mem.mem_ranges[i].mem_type == HDD_TYPE_DRAM && phys_addr >= hdd_mem.mem_ranges[i].phys_mem_start && phys_addr <= hdd_mem.mem_ranges[i].phys_mem_end) { present_flag = 1; break; } } if (present_flag == 1) { *in_kernel_space = 0; // addr is in userspace return 0; } //check addr is within kernel space ? if (phys_addr >= (u64)max_low_pfn * PAGE_SIZE) { return -1; } /* else { for (i = 0; i < e820.nr_map; i++) { printk(KERN_EMERG "\n e820 start xxx :%Lx and end: %Lx type %x",e820.map[i].addr,(e820.map[i].addr + e820.map[i].size),e820.map[i].type); if (e820.map[i].addr > phys_addr) { break; } if (e820.map[i].type == E820_RESERVED && phys_addr >= e820.map[i].addr && phys_addr <= (e820.map[i].addr + e820.map[i].size)) { present_flag = 1; break; } } } if (present_flag == 0) { return -1; } */ return 0; } /* * hwdd_print_e820bios_map - display the BIOS provided memory map */ static void hwdd_print_e820bios_map(void *log_ptr) { int i, ret; char buf[128]; hwdd_printk(HWDD_INFO,log_ptr, " BIOS Provided Memory Map\n"); for (i = 0; i < e820_saved.nr_map; i++) { ret = snprintf(buf, sizeof(buf), " %016Lx - %016Lx ", (unsigned long long) e820_saved.map[i].addr, (unsigned long long)(e820_saved.map[i].addr + e820_saved.map[i].size)); if (ret < 0 || ret > sizeof(buf)) { hwdd_printk(HWDD_INFO,log_ptr, " %016Lx - %016Lx ", (unsigned long long) e820_saved.map[i].addr, (unsigned long long)(e820_saved.map[i].addr + e820_saved.map[i].size)); continue; } e820_print_type(buf, e820_saved.map[i].type, log_ptr); } } /* * if current 'jiffies' value exceeds 'expire' then preempt ourselves */ static unsigned long hwdd_preempt(unsigned long expire) { if (time_after(jiffies, expire)) { schedule(); return SLEEP_AFTER; } return expire; } /* * hwdd_sleep_checkstop - sleep every x seconds, then check for STOP from user * returns -1 if STOP is issued */ int hwdd_sleep_checkstop(unsigned long *expire, pid_t ppid) { unsigned long p_expire = *expire; *expire = hwdd_preempt(*expire); if ((p_expire != *expire) && hwdd_user_stop(ppid) ) { return FAIL; } return PASS; } /* * hwdd_user_stop - check if the user has issued a STOP */ static int hwdd_user_stop(pid_t pid) { int i; for (i=0; i= hdd_mem.hwdd_launchpid[i].mem_start && start < hdd_mem.hwdd_launchpid[i].mem_end) { printk(KERN_ERR " Given 'start' address is within exising test range!\n"); break; } if (end > hdd_mem.hwdd_launchpid[i].mem_start && end < hdd_mem.hwdd_launchpid[i].mem_end) { printk(KERN_ERR " Given 'end' address is within existing test range !\n"); break; } if(start < hdd_mem.hwdd_launchpid[i].mem_start && end >=hdd_mem.hwdd_launchpid[i].mem_end) { printk(KERN_ERR " Given address range address is within existing test range !\n"); break; } } } spin_unlock(&hdd_mem.hwdd_launchpidlock); if (i== MAX_PROCS) { return PASS; } return FAIL; } /* * hwdd_memory_test_this_cpu - called when the area to test is small and does not make sense to ue multiple CPUs */ static int hwdd_memory_test_this_cpu(u64 phys_start, u64 phys_end, u8 test_index, u8 cache_status,char hex_pattern[],char *display_mode) { int ret; void *ptr; u8 status; /* record the called process sepcific details */ if (set_launch_pid(current->pid, phys_start, phys_end, test_index,display_mode)) { printk(KERN_CRIT "could not launch pid :%i\n", current->pid); return FAIL; } ptr = hwdd_logger_alloc(MEM_DEV, NULL, get_test_name(test_index),display_mode); if(!ptr) { printk(KERN_ERR "\n memory allocation (hwdd_logger_alloc) failed\n"); clear_launch_pid(current->pid, TEST_FAILED, test_index,display_mode); return FAIL ; } /* Invoke the memory test */ ret = hwdd_memory_test(phys_start, phys_end, test_index, current->pid, cache_status,hex_pattern, ptr); hwdd_logger_dealloc(ptr); status = ret ? TEST_FAILED:TEST_PASSED; clear_launch_pid(current->pid, status, test_index,display_mode); return ret; } /* * hwdd_set_stop_pid - called when the user wants to stop the test or when the app terminates */ static void hwdd_set_stop_pid(pid_t pid) { int i; spin_lock(&hdd_mem.hwdd_launchpidlock); for (i=0; i= MAX_PROCS) { spin_unlock(&hdd_mem.hwdd_statuspidlock); return FAIL; } /* search if the pid is already there */ for (i=0; i= MAX_PROCS) { spin_unlock(&hdd_mem.hwdd_launchpidlock); return FAIL; } /* search if the pid is already there */ for (i=0; i= 0) { d_err_cnt = hwdd_get_ecc_info(MEM_DEV, get_test_name(test_index), MEM_THRESHOLD,0) - hdd_mem.hwdd_launchpid[i].r_err_cnt; } ret = set_pid_status(pid, status, d_err_cnt); if (ret) { printk(KERN_ERR "ERROR: Unable to record the completion status\n"); } smp_mb(); /* ensure that status pid array is updated before clearing launch pid */ hdd_mem.hwdd_launchpid[i].pid = 0; if (hdd_mem.hwdd_launchpid[i].stop) { hdd_mem.hwdd_launchpid[i].stop = 0; } hdd_mem.hwdd_launchpid[i].mem_start = 0x0; hdd_mem.hwdd_launchpid[i].mem_end = 0x0; hdd_mem.launch_pid_cnt--; } else { /* update the hwdd_statuspid with the status */ ret = set_pid_status(pid, status, 0); if (ret) { printk(KERN_ERR "ERROR: Unable to record the completion status\n"); } } break; } } spin_unlock(&hdd_mem.hwdd_launchpidlock); } /* * hwdd_testrange - start the preparation to get the tests launched on given memory range * based on the range size, tests will either be spawned on this CPU or get scheduled as * workers in multiple CPUs */ static int hwdd_testrange(u64 phys_start, u64 phys_end, u8 cache_status, u8 test_index,char hex_pattern[],char *display_mode) { u64 total_pgs = ( phys_end - phys_start) / PAGE_SIZE; int num_cpus = num_online_cpus(); u64 pgs_pcpu = total_pgs / num_cpus; u32 pgs_lcpu = total_pgs - (pgs_pcpu * (num_cpus)) ; int cpu; int ret; u64 orig_start = phys_start; int cnt; int lcnt; /* if not every CPU can get atleast one page, run the test on current CPU, may be think of a bigger range? */ /* running dump utility on current cpu */ if ((!pgs_pcpu) || (strcmp(test_names[test_index],"dump") == 0)) { ret = hwdd_memory_test_this_cpu(phys_start, phys_end, test_index, cache_status,hex_pattern,display_mode); return ret; } /* * As the workers are deferred calls and there is a possibility that a worked is not scheduled before * another one completes, if we take the approach of calling set_launch_pid before queueing a worker it * might cause the completed worker to assume that it in the only worker for the pid and clear all statistics. * Hence, we will call set_launch_pid for all workers in the beginning */ cnt = 0; for_each_online_cpu(cpu) { int i; if (set_launch_pid(current->pid, orig_start, phys_end, test_index,display_mode)) { printk(KERN_ERR "could not launch pid :%i\n", current->pid); /* clear previous entires */ for (i=0; ipid, TEST_FAILED, test_index,display_mode); } return FAIL; } cnt++; } lcnt = cnt; cnt = 0; /* * Start hooking the workers */ for_each_online_cpu(cpu) { int ccnt, i; hwdd_test_desc *test_desc = kmalloc(sizeof (hwdd_test_desc), GFP_KERNEL); if (!test_desc) { printk(KERN_ERR "kmalloc() failed !\n"); ccnt = lcnt - cnt; /* clear stats for not-launched stuff */ for (i=0; ipid, TEST_FAILED, test_index,display_mode); } return FAIL ; } /* set up the container structure for worker */ INIT_WORK(&test_desc->work, hwdd_runtest); test_desc->start = phys_start; test_desc->end = (phys_start + (pgs_pcpu * PAGE_SIZE)); test_desc->index = test_index; if (display_mode != NULL) strcpy(test_desc->mode,display_mode); test_desc->cache_status = cache_status; if (hex_pattern != NULL) strcpy(test_desc->hex_pattern,hex_pattern); test_desc->pid = current->pid; /* set the pid to the process which invoked us */ smp_mb(); /* using a barrier */ ret = queue_work_on(cpu, hm_wq, &test_desc->work); if (!ret) { printk(KERN_CRIT "Work was already queued \n:"); ccnt = lcnt - cnt; /* clear stats for not-launched stuff */ for (i=0; ipid, TEST_FAILED, test_index,display_mode); } kfree(test_desc); return FAIL; } phys_start += (pgs_pcpu * PAGE_SIZE); cnt++; } /* run the test if any pages left out */ if (pgs_lcpu || ((phys_end - phys_start) > 0)) { ret = hwdd_memory_test_this_cpu(phys_start, phys_end, test_index, cache_status,hex_pattern,display_mode); return ret; } return PASS; } /* * hwdd_runtest - called when our worker(s) get scheduledi * logging of test result is apps responsibility, point of failure will be logged by module */ static void hwdd_runtest(struct work_struct *work) { int ret; void *ptr; hwdd_test_desc *test_desc = container_of(work, hwdd_test_desc, work); u8 status = TEST_FAILED; ptr = hwdd_logger_alloc(MEM_DEV, NULL, get_test_name(test_desc->index),test_desc->mode); if(!ptr) { printk(KERN_CRIT "\n memory allocation (hwdd_logger_alloc) failed\n"); goto err ; } ret = hwdd_memory_test(test_desc->start,test_desc->end, test_desc->index, test_desc->pid, test_desc->cache_status,test_desc->hex_pattern, ptr); hwdd_logger_dealloc(ptr); status = ret ? TEST_FAILED:TEST_PASSED; err: /* do pid cleanup */ clear_launch_pid(test_desc->pid, status, test_desc->index,test_desc->mode); kfree(test_desc); } /* * hwdd_invoke_memory_test -ioctl RUN_TEST invokes this routine, memory tests are triggered from here * currently we test regions above 4GB, later versions will support memory below 4GB */ static int hwdd_invoke_memory_test(u64 start, u64 size, u16 test_index, char hex_pattern[],u8 cache_status, char *display_mode) { u64 user_start; u64 user_end; int i, ret; user_start = start; user_end = start + size; for (i=0; i= hdd_mem.testable_mem_start && user_start >= hdd_mem.mem_ranges[i].phys_mem_start && user_start < hdd_mem.mem_ranges[i].phys_mem_end ) { /* we have a valid start addr, make sure end address is ok */ if (user_end > hdd_mem.mem_ranges[i].phys_mem_end ) { /* user given end > memory in this range, will have to look in to other ranges */ if ((ret = hwdd_testrange((u64)user_start,(u64)hdd_mem.mem_ranges[i].phys_mem_end ,cache_status, test_index,hex_pattern,display_mode ))) { return ret; } user_start = hdd_mem.mem_ranges[i].phys_mem_end; } else { /* that is it, done with the test */ if ((ret = hwdd_testrange((u64)user_start, (u64)user_end, cache_status, test_index,hex_pattern,display_mode ))) { return ret; } user_start = hdd_mem.mem_ranges[i].phys_mem_end; goto done; } } else if (user_end > hdd_mem.testable_mem_start && user_end > hdd_mem.mem_ranges[i].phys_mem_start && user_end <= hdd_mem.mem_ranges[i].phys_mem_end ) { /* ok user_start does not fall in the range, but user_end does */ if ((ret = hwdd_testrange((u64)hdd_mem.mem_ranges[i].phys_mem_start , (u64)user_end, cache_status, test_index, hex_pattern, display_mode ))) { return ret; } goto done; } } } /* run for NVMEM if present */ if (hdd_mem.mem_ranges[HDD_MAX_RANGE-1].mem_type == HDD_TYPE_NVMEM) { if ( user_start >= hdd_mem.mem_ranges[HDD_MAX_RANGE-1].phys_mem_start && user_start < hdd_mem.mem_ranges[HDD_MAX_RANGE-1].phys_mem_end ) { if (user_end > hdd_mem.mem_ranges[HDD_MAX_RANGE-1].phys_mem_end ) { /* user given end > memory in this range, will have to look in to other ranges */ if ((ret = hwdd_testrange((u64)user_start, (u64)hdd_mem.mem_ranges[HDD_MAX_RANGE-1].phys_mem_end, cache_status, test_index,hex_pattern,display_mode ))) { return ret; } } else { if ((ret = hwdd_testrange((u64)user_start, (u64)user_end, cache_status, test_index,hex_pattern,display_mode ))) { return ret; } goto done; } } } done: return PASS; } /* * hwdd_memory_test- memory algorithm is invoked based on the test index given by the user */ static int hwdd_memory_test(u64 phys_start, u64 phys_end, u16 test_index, pid_t pid, u8 cache_status,char pattern[],void *ptr) { u64 *virt_start, address_diff; u8 cache_test_flag=0; if (test_index >= CACHE_TESTS_START_INDEX && test_index <= CACHE_TESTS_END_INDEX) { cache_test_flag = 1; } switch(test_index) { case RANDOM_ADDRESS_TEST: memory_test = hwdd_mem_randaddr_test; break; case RANDOM_DATA_TEST: memory_test = hwdd_mem_randdata_test; break; case WALKING_DATA_BITS_TEST: memory_test = hwdd_mem_data_walk; break; case WALKING_ADDRESS_TEST: memory_test = hwdd_mem_addr_walk; break; case CHECKER_BOARD_TEST: memory_test = hwdd_test_checker; break; case STUCK_FAULTS_TEST: memory_test = mem_stuck_faults; break; case WALKING_DATA_WORDS_TEST: memory_test = mem_word_walk; break; case WALKING_DATA_BYTES_TEST: memory_test = mem_byte_walk; break; case FILL_MEMORY_WITH_DATA_PATTERN: memory_test_pattern = mem_fill_data; break; case CHECK_MEMORY_WITH_DATA_PATTERN: memory_test_pattern = mem_check_data; max_pattern_length = MAX_PATTERN_LENGTH; break; case BYTE_PATTERNS_TEST: memory_test = mem_byte_pat; break; case PARTIAL_WORDS_TEST: memory_test = mem_partial; break; case ALTERNATING_ADDRESS_TEST: memory_test = mem_alt_addr; break; case PARITY_BITS_TEST: memory_test = mem_parity; break; case MEMORY_DUMP_UTILITY: memory_test = dump_memory_utility; break; case CACHE_DATA_WALK: memory_test = cache_data_walk; break; case CACHE_STUCK_FAULT: memory_test = cache_stuck_faults; break; case CACHE_RAND_RW: memory_test = cache_rand_rw; break; case CACHE_RAND_DATA: memory_test = cache_rand_data; break; case CACHE_RAND_ADDR: memory_test = cache_rand_addr; break; case CACHE_TAG: memory_test = cache_tag; break; default: printk(KERN_ERR "Unknown Test !\n"); return FAIL; } /*TODO: Restore ioremap_size back to its original value for the next cycle, use ioremap_nocache if necessary */ repeat: while( (phys_start + ioremap_size) <= phys_end) { int ret; if(!cache_status) { virt_start =(u64 *)ioremap(phys_start, ioremap_size); }else { virt_start =(u64 *)ioremap_nocache(phys_start, ioremap_size); } if (!virt_start) { if(ioremap_size < MEG){ /* if the size which u want to map is less than 1mb, stop ioremap */ return FAIL; } ioremap_size/=2; continue; } if (memory_test != dump_memory_utility) { if (cache_test_flag == 1) hwdd_printk(HWDD_INFO, ptr," Using memory Start 0x%016Lx End 0x%016Lx",phys_start,phys_start+ioremap_size); else hwdd_printk(HWDD_INFO, ptr," Testing memory Start 0x%016Lx End 0x%016Lx",phys_start,phys_start+ioremap_size); } if ((test_index == FILL_MEMORY_WITH_DATA_PATTERN) || (test_index == CHECK_MEMORY_WITH_DATA_PATTERN)) { ret = memory_test_pattern((u64)virt_start,(u64)(((u64)virt_start)+ ioremap_size),pattern, phys_start, pid, ptr); //passing pattern for user pattern required tests } else { ret = memory_test((u64)virt_start,(u64)(((u64)virt_start)+ ioremap_size), phys_start,pid, ptr); } if (ret) { return ret; } phys_start+=ioremap_size; iounmap(virt_start); } address_diff=(phys_end-phys_start); if (address_diff){ if(!cache_status) { virt_start =(u64 *)ioremap(phys_start, address_diff); /*ioremapping the remaining part of memory i.e phys_end- current phys_start*/ }else { virt_start =(u64 *)ioremap_nocache(phys_start, address_diff); } if (!virt_start) { if(ioremap_size < MEG){ return FAIL; } ioremap_size/=2; goto repeat; } else{ if (memory_test != dump_memory_utility) { if (cache_test_flag == 1) hwdd_printk(HWDD_INFO, ptr," Using memory Start 0x%016Lx End 0x%016Lx",phys_start, (phys_start + address_diff)); else hwdd_printk(HWDD_INFO, ptr," Testing memory Start 0x%016Lx End 0x%016Lx",phys_start, (phys_start + address_diff)); } if ((test_index == FILL_MEMORY_WITH_DATA_PATTERN) || (test_index == CHECK_MEMORY_WITH_DATA_PATTERN)) { return memory_test_pattern((u64)virt_start, (u64)(((u64)virt_start) + address_diff), pattern,phys_start,pid, ptr); } return memory_test((u64)virt_start, (u64)(((u64)virt_start) + address_diff), phys_start,pid, ptr); iounmap(virt_start); } } return PASS; } /* * hwdd_mem_open */ static int hwdd_mem_open(struct inode *inode, struct file *fp) { return PASS; } /* * hwdd_mem_close */ static int hwdd_mem_close(struct inode *inode, struct file *fp) { /* issue STOP to any workers spawned */ hwdd_set_stop_pid(current->pid); return PASS; } /* * hwdd_mem_ioctl :- ioctl interface */ static long hwdd_mem_ioctl(struct file *filep, unsigned int cmd, unsigned long arg) { if (_IOC_TYPE(cmd) != HDD_MEM_MAGIC) { printk(KERN_ERR "HWDD: bad ioctl !\n"); return -EINVAL; } switch (cmd) { case HWDD_DISPLAY_MEM_INFO: { void *log_ptr; char disp_mode[DISPLAY_MODE_LENGTH]; if (copy_from_user(disp_mode,(char *)arg, (sizeof(disp_mode)- 1))) { printk(KERN_ERR "HWDD: copy_from_user failed !\n"); return -EFAULT; } log_ptr = hwdd_logger_alloc(MEM_DEV,NULL,"details",disp_mode); if (log_ptr == NULL) { printk(KERN_ERR "\n memory allocation (hwdd_logger_alloc) failed\n"); return -EFAULT; } hwdd_display_mem_info(log_ptr); hwdd_logger_dealloc(log_ptr); // memerrinject_snb_inject(); break; } // Bandwidth utility accepts cache on/off, remote memory/local memory etc options. So keeping a separate ioctl case HWDD_MEMORY_BANDWIDTH_INFO: { void *log_ptr; char disp_mode[DISPLAY_MODE_LENGTH]; if (copy_from_user(disp_mode,(char *)arg, (sizeof(disp_mode)- 1))) { printk(KERN_ERR "HWDD: copy_from_user failed !\n"); return -EFAULT; } log_ptr = hwdd_logger_alloc(MEM_DEV,NULL,"bandwidth",disp_mode); if (log_ptr == NULL) { printk(KERN_ERR "\n memory allocation (hwdd_logger_alloc) failed\n"); return -EFAULT; } get_memory_bandwidth(log_ptr); hwdd_logger_dealloc(log_ptr); break; } case HWDD_GET_MEM_DEFAULTS: { hwdd_user_args *user_mem_details = (hwdd_user_args *)kmalloc(sizeof(hwdd_user_args), GFP_KERNEL); if(user_mem_details == NULL){ printk(KERN_ERR "memory allocation (user_mem_details) failed!\n"); return -EFAULT; } //first chunk of testable memory is given as default range to test user_mem_details->start= hdd_mem.mem_ranges[0].phys_mem_start; user_mem_details->size = hdd_mem.mem_ranges[0].phys_mem_end - hdd_mem.mem_ranges[0].phys_mem_start; /* give the data back to the app */ if(copy_to_user( (hwdd_user_args*)arg, user_mem_details, sizeof(hwdd_user_args)) ) { printk(KERN_ERR "HWDD: copy_to_user failed !\n"); kfree(user_mem_details); return -EFAULT; } kfree(user_mem_details); break; } case HWDD_ERR_INJ: { hwdd_user_args *user_mem_details; //int einj_mode = MODE_ACPI; u8 in_kernel_space=1; void *log_ptr; int ret; log_ptr = hwdd_logger_alloc(MEM_DEV, NULL, "err-inj",NULL); if (log_ptr == NULL) { printk(KERN_ERR "\n memory allocation (hwdd_logger_alloc) failed\n"); return -EFAULT; } // verify if ACPI support is available else check if OS has this support /* ret = hwdd_acpi_einj_support(); if (ret != 1) { // if no ACPI support, try SMM from kernel ret = hwdd_os_einj_support(); if (ret != 1) { hwdd_printk(HWDD_ERROR, log_ptr, " Error Injection support not available in this platform\n"); hwdd_logger_dealloc(log_ptr); return ret; } einj_mode = MODE_OS; } hwdd_logger_dealloc(log_ptr); */ user_mem_details =(hwdd_user_args *)kmalloc(sizeof(hwdd_user_args), GFP_KERNEL); if(!user_mem_details){ hwdd_printk(HWDD_CONSOLE_ERR, log_ptr, " memory allocation failed \n"); hwdd_logger_dealloc(log_ptr); return -EFAULT; } if (copy_from_user(user_mem_details, (hwdd_user_args*)arg, sizeof(hwdd_user_args))) { hwdd_printk(HWDD_CONSOLE_ERR, log_ptr, " copy_from_user failed \n"); kfree(user_mem_details); hwdd_logger_dealloc(log_ptr); return -EFAULT; } /* as of now, looks like with ACPI, we cannot specify the address to inject errors */ /*if (einj_mode == MODE_ACPI) ret = hwdd_acpi_inject_memerr(user_mem_details->err_type, user_mem_details->err_addr); else ret = hwdd_os_inject_memerr(user_mem_details->err_type, user_mem_details->err_addr); */ ret = hwdd_validate_phys_addr(user_mem_details->err_addr,&in_kernel_space); if (ret) { kfree(user_mem_details); hwdd_printk(HWDD_CONSOLE_ERR, log_ptr, " Invalid DRAM address. Specify valid address\n"); hwdd_logger_dealloc(log_ptr); return -EINVAL; } ret = hwdd_inject_memerr(user_mem_details->err_type, user_mem_details->err_addr,in_kernel_space, log_ptr); hwdd_logger_dealloc(log_ptr); kfree(user_mem_details); return ret; break; } case HWDD_RUN_TEST: { s16 ret_val = 0; u8 is_testable = 0,i; /* * TODO: currently this is implemented as non blocking, app neds to invoke HWDD_TEST_STATUS * to identify the completion of the launched workers, rather we could go with a blocking * implementation here (interruptible sleep) later. */ hwdd_user_args *user_mem_details=(hwdd_user_args *)kmalloc(sizeof(hwdd_user_args ), GFP_KERNEL); if(!user_mem_details){ printk(KERN_ERR "memory allocation (user_mem_details) failed!\n"); return -EFAULT; } if (copy_from_user(user_mem_details, (hwdd_user_args*)arg, sizeof(hwdd_user_args))) { printk(KERN_ERR "HWDD: copy_from_user failed !\n"); kfree(user_mem_details); return -EFAULT; } /* check for cache tests */ if (user_mem_details->test_index >= CACHE_TESTS_START_INDEX && user_mem_details->test_index <= CACHE_TESTS_END_INDEX) { fill_testable_range(user_mem_details); } /* validate the user given ranges against the available testable ranges of memeory */ for (i=0; i<=hdd_mem.index ; i++) { if((user_mem_details->start >= hdd_mem.mem_ranges[i].phys_mem_start) &&((user_mem_details->start + user_mem_details->size) <= hdd_mem.mem_ranges[i].phys_mem_end) ) { is_testable = 1; //yes, within testable range break; } } if(!is_testable) { printk(KERN_EMERG " Specified addresses are not within the testable range. Specify valid addresses\n"); kfree(user_mem_details); return -EINVAL; } /* verify if this range is not already under test */ if (hwdd_is_testing(user_mem_details->start, user_mem_details->start + user_mem_details->size)) { printk(KERN_CRIT " Range of memory is already under test \n"); kfree(user_mem_details); return -EINVAL; } /* get on with the test, the outcome of the test will be logged by the app */ ret_val = hwdd_invoke_memory_test(user_mem_details->start, user_mem_details->size, user_mem_details->test_index,user_mem_details->hex_pattern, user_mem_details->cache_status,user_mem_details->display_mode); if (ret_val != FAIL && ret_val) { //test may fail(eg:check-with-pattern) but condtion reports ioctl cmd failed to run. so eliminating this prob kfree(user_mem_details); return -EFAULT; } kfree(user_mem_details); break; } case HWDD_STOP: hwdd_set_stop_pid(current->pid); break; case HWDD_TEST_STATUS: { int i; u64 status = TEST_FAILED; spin_lock(&hdd_mem.hwdd_launchpidlock); /* if the pid is part of launchpid array then atleast one worker is still running */ for (i=0; ipid) { status = TEST_RUNNING; spin_unlock(&hdd_mem.hwdd_launchpidlock); goto done; } } spin_unlock(&hdd_mem.hwdd_launchpidlock); spin_lock(&hdd_mem.hwdd_statuspidlock); /* we did not find the pid, test might have completed check status array */ for (i=0; ipid) { status = hdd_mem.hwdd_statuspid[i].status ; if (hdd_mem.hwdd_statuspid[i].d_err_cnt) { status &= ~(ECC_MASK << ECC_START); status |= (u64)hdd_mem.hwdd_statuspid[i].d_err_cnt << ECC_START; } /* once the app reads the status, onus is on the app to retain it */ hdd_mem.hwdd_statuspid[i].pid = 0; hdd_mem.hwdd_statuspid[i].status = 0; hdd_mem.status_pid_cnt--; break; } } spin_unlock(&hdd_mem.hwdd_statuspidlock); done: put_user(status, (u64*)arg); } break; default: printk(KERN_ERR "Unsupported ioctl !\n"); return -EINVAL; } return 0; } static void fill_testable_range(hwdd_user_args *user_mem_details) { /* perform all cache test related operations */ user_mem_details->start = hdd_mem.mem_ranges[hdd_mem.index].phys_mem_start; // restricting the testable memory size for cache tag test if (!strcmp(test_names[user_mem_details->test_index], "cache-tag")) { if ((hdd_mem.mem_ranges[hdd_mem.index].phys_mem_start + 100 * LAST_LEVEL_CACHE_SIZE) < hdd_mem.mem_ranges[hdd_mem.index].phys_mem_end) user_mem_details->size = (100 * LAST_LEVEL_CACHE_SIZE); else user_mem_details->size = (hdd_mem.mem_ranges[hdd_mem.index].phys_mem_end - hdd_mem.mem_ranges[hdd_mem.index].phys_mem_start); return; } // Considering double the last level cache size as a memory testing range, just to introduce some kind of stressing on cache user_mem_details->size = (LAST_LEVEL_CACHE_SIZE * 2); } /* * HDD Mem, File Operation Structure */ struct file_operations hwddmem_fops = { .owner = THIS_MODULE, .unlocked_ioctl = hwdd_mem_ioctl, .open = hwdd_mem_open, .release = hwdd_mem_close, }; /* * hwdd_mem_init- HDD Mem, module entry point */ static int __init hwdd_mem_init(void) { int ret; /* determine the initial test range based on the memory maps */ if (hwdd_get_testable_range()) { printk(KERN_ERR "Error: No write testable memory range detected !\n"); return -1; } hwddmem_dev = MKDEV(hwddmem_major, 0); ret = alloc_chrdev_region(&hwddmem_dev, 0, 1, HWDD_DEVNAME); if (ret != 0) { printk(KERN_ERR "alloc_chrdev_region failed !!\n"); return ret; } printk(KERN_INFO "HWDD, Module Major Num :%d\n", MAJOR(hwddmem_dev)); mem_cdev = cdev_alloc(); if (!mem_cdev) { printk(KERN_ERR "cdev_alloc failed !\n"); goto fail1; } mem_cdev->owner = THIS_MODULE; mem_cdev->ops = &hwddmem_fops; if ( cdev_add(mem_cdev, hwddmem_dev, 1) < 0) { printk(KERN_ERR "cdev_add failed \n"); goto fail2; } /* work queue creation */ hm_wq = create_workqueue("hwdd_memwq"); if (!hm_wq) { printk(KERN_ERR "unable to create wq!\n"); goto fail2; } spin_lock_init(&hdd_mem.hwdd_launchpidlock); spin_lock_init(&hdd_mem.hwdd_statuspidlock); return 0; fail2: cdev_del(mem_cdev); fail1: unregister_chrdev_region(hwddmem_dev, 1); return -1; } /* * hwdd_mem_exit- Driver exit cleanup routine */ static void __exit hwdd_mem_exit(void) { cdev_del(mem_cdev); unregister_chrdev_region(hwddmem_dev, 1); destroy_workqueue(hm_wq); } module_init(hwdd_mem_init); module_exit(hwdd_mem_exit); MODULE_AUTHOR("NetApp, "); MODULE_DESCRIPTION("Memory Hardware Diagnostics Driver"); MODULE_LICENSE("GPL"); MODULE_VERSION(DRV_VERSION);