Migrate data off pages with correctable memory errors. This patch is ia64 specific. It connects the CPE handler to the page migration code. It is implemented as a kernel loadable module, similar to the mca recovery code (mca_recovery.ko). This allows the feature to be turned off by uninstalling the module. It adds a /sys/firmware/badram/ interface to control migrate_threshold, cpe_polling_threshold, and cmc_polling_threshold. Signed-off-by: Russ Anderson <rja@xxxxxxx> --- arch/ia64/Kconfig | 10 arch/ia64/include/asm/mca.h | 14 arch/ia64/include/asm/page.h | 1 arch/ia64/kernel/Makefile | 1 arch/ia64/kernel/cpe_migrate.c | 628 +++++++++++++++++++++++++++++++++++++++++ arch/ia64/kernel/mca.c | 120 ++++++- 6 files changed, 753 insertions(+), 21 deletions(-) Index: linux-next/arch/ia64/Kconfig =================================================================== --- linux-next.orig/arch/ia64/Kconfig 2009-04-07 18:31:43.494292010 -0500 +++ linux-next/arch/ia64/Kconfig 2009-04-07 18:37:00.045826021 -0500 @@ -512,6 +512,16 @@ config COMPAT_FOR_U64_ALIGNMENT config IA64_MCA_RECOVERY tristate "MCA recovery from errors other than TLB." +config IA64_CPE_MIGRATE + tristate "Migrate data off pages with correctable errors" + depends on MEMORY_FAILURE + default m + help + Migrate data off pages with correctable memory errors. Selecting + Y will build this functionality into the kernel. Selecting M will + build this functionality as a kernel loadable module. Installing + the module will turn on the functionality. + config PERFMON bool "Performance monitor support" help Index: linux-next/arch/ia64/include/asm/mca.h =================================================================== --- linux-next.orig/arch/ia64/include/asm/mca.h 2009-04-07 18:31:43.518295478 -0500 +++ linux-next/arch/ia64/include/asm/mca.h 2009-04-07 18:37:00.061826624 -0500 @@ -137,6 +137,9 @@ extern unsigned long __per_cpu_mca[NR_CP extern int cpe_vector; extern int ia64_cpe_irq; +extern int cpe_poll_enabled; +extern int cpe_poll_threshold; +extern int cmc_poll_threshold; extern void ia64_mca_init(void); extern void ia64_mca_cpu_init(void *); extern void ia64_os_mca_dispatch(void); @@ -150,14 +153,25 @@ extern void ia64_slave_init_handler(void extern void ia64_mca_cmc_vector_setup(void); extern int ia64_reg_MCA_extension(int (*fn)(void *, struct ia64_sal_os_state *)); extern void ia64_unreg_MCA_extension(void); +extern int ia64_reg_CE_extension(int (*fn)(void *)); +extern void ia64_unreg_CE_extension(void); +extern int isolate_lru_page(struct page *page); extern u64 ia64_get_rnat(u64 *); extern void ia64_mca_printk(const char * fmt, ...) __attribute__ ((format (printf, 1, 2))); +extern struct list_head badpagelist; +extern unsigned int total_badpages; + struct ia64_mca_notify_die { struct ia64_sal_os_state *sos; int *monarch_cpu; int *data; +}; + +struct ce_history { + struct list_head list; + unsigned long time; }; DECLARE_PER_CPU(u64, ia64_mca_pal_base); Index: linux-next/arch/ia64/include/asm/page.h =================================================================== --- linux-next.orig/arch/ia64/include/asm/page.h 2009-04-07 18:31:43.526296672 -0500 +++ linux-next/arch/ia64/include/asm/page.h 2009-04-07 18:37:00.077829455 -0500 @@ -121,6 +121,7 @@ extern unsigned long max_low_pfn; #endif #define page_to_phys(page) (page_to_pfn(page) << PAGE_SHIFT) +#define phys_to_page(kaddr) (pfn_to_page(kaddr >> PAGE_SHIFT)) #define virt_to_page(kaddr) pfn_to_page(__pa(kaddr) >> PAGE_SHIFT) #define pfn_to_kaddr(pfn) __va((pfn) << PAGE_SHIFT) Index: linux-next/arch/ia64/kernel/Makefile =================================================================== --- linux-next.orig/arch/ia64/kernel/Makefile 2009-04-07 18:31:43.542298125 -0500 +++ linux-next/arch/ia64/kernel/Makefile 2009-04-07 18:37:00.097832955 -0500 @@ -31,6 +31,7 @@ obj-$(CONFIG_PERFMON) += perfmon_defaul obj-$(CONFIG_IA64_CYCLONE) += cyclone.o obj-$(CONFIG_CPU_FREQ) += cpufreq/ obj-$(CONFIG_IA64_MCA_RECOVERY) += mca_recovery.o +obj-$(CONFIG_IA64_CPE_MIGRATE) += cpe_migrate.o obj-$(CONFIG_KPROBES) += kprobes.o jprobes.o obj-$(CONFIG_DYNAMIC_FTRACE) += ftrace.o obj-$(CONFIG_KEXEC) += machine_kexec.o relocate_kernel.o crash.o Index: linux-next/arch/ia64/kernel/cpe_migrate.c =================================================================== --- /dev/null 1970-01-01 00:00:00.000000000 +0000 +++ linux-next/arch/ia64/kernel/cpe_migrate.c 2009-04-07 18:44:14.364066972 -0500 @@ -0,0 +1,628 @@ +/* + * File: cpe_migrate.c + * Purpose: Migrate data from physical pages with excessive correctable + * errors to new physical pages. Keep the old pages on a discard + * list. + * + * Copyright (C) 2009 SGI - Silicon Graphics Inc. + * Copyright (C) 2009 Russ Anderson <rja@xxxxxxx> + */ + +#include <linux/sysdev.h> +#include <linux/types.h> +#include <linux/sched.h> +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/smp.h> +#include <linux/mm.h> +#include <linux/swap.h> +#include <linux/vmalloc.h> +#include <linux/migrate.h> +#include <linux/page-isolation.h> +#include <linux/memcontrol.h> +#include <linux/kobject.h> +#include <linux/kthread.h> +#include <asm/page.h> +#include <asm/system.h> +#include <asm/sn/sn_cpuid.h> +#include <asm/mca.h> + +struct kobject *badram_kobj; + +struct cpe_info { + struct list_head list; + u64 paddr; + u16 node; + u16 count; +}; +LIST_HEAD(ce_list); + +static int cpe_polling_enabled = 1; +static int record_to_process; +unsigned int migrate_threshold = 1; +static int mstat_cannot_isolate; +static int mstat_failed_to_discard; +static int mstat_already_marked; +static int mstat_already_on_list; + +/* IRQ handler notifies this wait queue on receipt of an IRQ */ +DECLARE_WAIT_QUEUE_HEAD(cpe_activate_IRQ_wq); +static DECLARE_COMPLETION(kthread_cpe_migrated_exited); +int cpe_active; +DEFINE_SPINLOCK(cpe_migrate_lock); + +static void +get_physical_address(void *buffer, u64 *paddr, u16 *node) +{ + sal_log_record_header_t *rh; + sal_log_mem_dev_err_info_t *mdei; + ia64_err_rec_t *err_rec; + sal_log_platform_err_info_t *plat_err; + efi_guid_t guid; + + err_rec = buffer; + rh = &err_rec->sal_elog_header; + *paddr = 0; + *node = 0; + + /* + * Make sure it is a corrected error. + */ + if (rh->severity != sal_log_severity_corrected) + return; + + plat_err = (sal_log_platform_err_info_t *)&err_rec->proc_err; + + guid = plat_err->mem_dev_err.header.guid; + if (efi_guidcmp(guid, SAL_PLAT_MEM_DEV_ERR_SECT_GUID) == 0) { + /* + * Memory cpe + */ + mdei = &plat_err->mem_dev_err; + if (mdei->valid.oem_data) { + if (mdei->valid.physical_addr) + *paddr = mdei->physical_addr; + + if (mdei->valid.node) { + if (ia64_platform_is("sn2")) + *node = nasid_to_cnodeid(mdei->node); + else + *node = mdei->node; + } + } + } +} + +static struct page * +alloc_migrate_page(struct page *ignored, unsigned long node, int **x) +{ + + return alloc_pages_node(node, GFP_HIGHUSER_MOVABLE, 0); +} + +static int +validate_paddr_page(u64 paddr) +{ + struct page *page; + + if (!paddr) + return -EINVAL; + + if (!ia64_phys_addr_valid(paddr)) + return -EINVAL; + + if (!pfn_valid(paddr >> PAGE_SHIFT)) + return -EINVAL; + + page = phys_to_page(paddr); + if (PagePoison(page)) + mstat_already_marked++; + return 0; +} + +static int +ia64_mca_cpe_move_page(u64 paddr, u32 node) +{ + LIST_HEAD(pagelist); + struct page *page; + int ret; + + ret = validate_paddr_page(paddr); + if (ret < 0) + return ret; + + /* + * convert physical address to page number + */ + page = phys_to_page(paddr); + + migrate_prep(); + ret = isolate_lru_page(page); + if (ret) { + mstat_cannot_isolate++; + return ret; + } + list_add_tail(&page->lru, &pagelist); + SetPagePoison(page); /* Mark the page as bad */ + ret = migrate_pages(&pagelist, alloc_migrate_page, node); + if (ret == 0) { + total_badpages++; + list_add_tail(&page->lru, &badpagelist); + } else { + mstat_failed_to_discard++; + /* + * The page failed to migrate and is not on the bad page list. + * Clearing the error bit will allow another attempt to migrate + * if it gets another correctable error. + */ + ClearPagePoison(page); + } + + return 0; +} + +DEFINE_SPINLOCK(cpe_list_lock); + +/* + * cpe_process_queue + * Pulls the physical address off the list and calls the migration code. + * Will process all the addresses on the list. + */ +void +cpe_process_queue(void) +{ + int ret; + struct cpe_info *entry, *entry2; + + if (!spin_trylock(&cpe_list_lock)) + return; + + list_for_each_entry_safe(entry, entry2, &ce_list, list) { + if (entry->count >= migrate_threshold) { + /* + * There is a valid entry that needs processing. + */ + ret = ia64_mca_cpe_move_page(entry->paddr, entry->node); + if (ret <= 0) { + /* + * Even though the return status is negative, + * clear the entry. If the same address has + * another CPE it will be re-added to the list. + */ + list_del(&entry->list); + kfree(entry); + } + } + } + spin_unlock(&cpe_list_lock); + + record_to_process = 0; + return; +} + +/* + * kthread_cpe_migrate + * kthread_cpe_migrate is created at module load time and lives + * until the module is removed. When not active, it will sleep. + */ +static int +kthread_cpe_migrate(void *ignore) +{ + while (cpe_active) { + /* + * wait for work + */ + (void)wait_event_interruptible(cpe_activate_IRQ_wq, + (record_to_process || + !cpe_active)); + cpe_process_queue(); /* process work */ + } + complete(&kthread_cpe_migrated_exited); + return 0; +} + +/* + * cpe_setup_migrate + * Get the physical address out of the CPE record, add it + * to the list of addresses to migrate (if not already on), + * and schedule the back end worker task. This is called + * in interrupt context so cannot directly call the migration + * code. + * + * Inputs + * rec The CPE record + * Outputs + * 1 on Success, -1 on failure + */ +static int +cpe_setup_migrate(void *rec) +{ + u64 paddr; + u16 node; + int ret; + struct cpe_info *entry, *entry2; + + if (!rec) + return -EINVAL; + + get_physical_address(rec, &paddr, &node); + ret = validate_paddr_page(paddr); + if (ret < 0) + return -EINVAL; + + if (!spin_trylock(&cpe_list_lock)) { + /* + * Someone else has the lock. To avoid spinning in interrupt + * handler context, bail. + */ + return 1; + } + + list_for_each_entry_safe(entry, entry2, &ce_list, list) { + if (PAGE_ALIGN(paddr) == entry->paddr) { + if (entry->count++ > migrate_threshold) { + mstat_already_on_list++; + } else { + /* + * Exceeded the migration threshold, + * try to migrate + */ + record_to_process = 1; + wake_up_interruptible(&cpe_activate_IRQ_wq); + } + spin_unlock(&cpe_list_lock); + return 1; + } + } + /* + * New entry, add to the list. + */ + entry = kmalloc(sizeof(struct cpe_info), GFP_KERNEL); + entry->node = node; + entry->paddr = paddr; + entry->count = 1; + list_add_tail(&entry->list, &ce_list); + if (migrate_threshold == 1) { + record_to_process = 1; + wake_up_interruptible(&cpe_activate_IRQ_wq); + } + spin_unlock(&cpe_list_lock); + + return 1; +} + +/* + * ============================================================================= + */ + +/* + * free_one_bad_page + * Free one page from the list of bad pages. + */ +static int +free_one_bad_page(unsigned long paddr) +{ + LIST_HEAD(pagelist); + struct page *page, *page2, *target; + + /* + * Verify page address + */ + target = phys_to_page(paddr); + list_for_each_entry_safe(page, page2, &badpagelist, lru) { + if (page != target) + continue; + + ClearPagePoison(page); /* Mark the page as good */ + total_badpages--; + list_move_tail(&page->lru, &pagelist); + putback_lru_pages(&pagelist); + break; + } + return 0; +} + +/* + * free_all_bad_pages + * Free all of the pages on the bad pages list. + */ +static int +free_all_bad_pages(void) +{ + struct page *page, *page2; + + list_for_each_entry_safe(page, page2, &badpagelist, lru) { + ClearPagePoison(page); /* Mark the page as good */ + total_badpages--; + } + putback_lru_pages(&badpagelist); + return 0; +} + +#define OPT_LEN 16 + +static ssize_t +badpage_store(struct kobject *kobj, + struct kobj_attribute *attr, const char *buf, size_t count) +{ + char optstr[OPT_LEN]; + unsigned long opt; + int len = OPT_LEN; + int err; + + if (count < len) + len = count; + + strlcpy(optstr, buf, len); + + err = strict_strtoul(optstr, 16, &opt); + if (err) + return err; + + if (opt == 0) + free_all_bad_pages(); + else + free_one_bad_page(opt); + + return count; +} + +/* + * badpage_show + * Display the number, size, and addresses of all the pages on the + * bad page list. + * + * Note that sysfs provides buf of PAGE_SIZE length. bufend tracks + * the remaining space in buf to avoid overflowing. + */ +static ssize_t +badpage_show(struct kobject *kobj, + struct kobj_attribute *attr, char *buf) +{ + struct page *page, *page2; + int i = 0, cnt = 0; + char *bufend = buf + PAGE_SIZE; + + cnt = snprintf(buf, bufend - (buf + cnt), + "Memory marked bad: %d kB\n" + "Pages marked bad: %d\n" + "Unable to isolate on LRU: %d\n" + "Unable to migrate: %d\n" + "Already marked bad: %d\n" + "Already on list: %d\n" + "List of bad physical pages\n", + total_badpages << (PAGE_SHIFT - 10), total_badpages, + mstat_cannot_isolate, mstat_failed_to_discard, + mstat_already_marked, mstat_already_on_list + ); + + list_for_each_entry_safe(page, page2, &badpagelist, lru) { + if (bufend - (buf + cnt) < 20) + break; /* Avoid overflowing the buffer */ + cnt += snprintf(buf + cnt, bufend - (buf + cnt), + " 0x%011lx", page_to_phys(page)); + if (!(++i % 5)) + cnt += snprintf(buf + cnt, bufend - (buf + cnt), "\n"); + } + cnt += snprintf(buf + cnt, bufend - (buf + cnt), "\n"); + + return cnt; +} + +static struct kobj_attribute badram_attr = { + .attr = { + .name = "bad_pages", + .mode = S_IWUSR | S_IRUGO, + }, + .show = badpage_show, + .store = badpage_store, +}; + + +static ssize_t +cpe_poll_threshold_store(struct kobject *kobj, + struct kobj_attribute *attr, const char *buf, size_t count) +{ + char optstr[OPT_LEN]; + unsigned long opt; + int len = OPT_LEN; + int err; + + if (count < len) + len = count; + + strlcpy(optstr, buf, len); + + err = strict_strtoul(optstr, 16, &opt); + if (err) + return err; + cpe_poll_threshold = opt; + + return count; +} + +static ssize_t +cpe_poll_threshold_show(struct kobject *kobj, + struct kobj_attribute *attr, char *buf) +{ + int cnt = 0; + char *bufend = buf + PAGE_SIZE; + + cnt = snprintf(buf, bufend - (buf + cnt), "%d\n", cpe_poll_threshold); + return cnt; +} + +static struct kobj_attribute cpe_poll_threshold_attr = { + .attr = { + .name = "cpe_polling_threshold", + .mode = S_IWUSR | S_IRUGO, + }, + .show = cpe_poll_threshold_show, + .store = cpe_poll_threshold_store, +}; + + +static ssize_t +cmc_poll_threshold_store(struct kobject *kobj, + struct kobj_attribute *attr, const char *buf, size_t count) +{ + char optstr[OPT_LEN]; + unsigned long opt; + int len = OPT_LEN; + int err; + + if (count < len) + len = count; + + strlcpy(optstr, buf, len); + + err = strict_strtoul(optstr, 16, &opt); + if (err) + return err; + cmc_poll_threshold = opt; + + return count; +} + +static ssize_t +cmc_poll_threshold_show(struct kobject *kobj, + struct kobj_attribute *attr, char *buf) +{ + int cnt = 0; + char *bufend = buf + PAGE_SIZE; + + cnt = snprintf(buf, bufend - (buf + cnt), "%d\n", cmc_poll_threshold); + return cnt; +} + +static struct kobj_attribute cmc_poll_threshold_attr = { + .attr = { + .name = "cmc_polling_threshold", + .mode = S_IWUSR | S_IRUGO, + }, + .show = cmc_poll_threshold_show, + .store = cmc_poll_threshold_store, +}; + +static ssize_t +migrate_threshold_store(struct kobject *kobj, + struct kobj_attribute *attr, const char *buf, size_t count) +{ + char optstr[OPT_LEN]; + unsigned long opt; + int len = OPT_LEN; + int err; + + if (count < len) + len = count; + + strlcpy(optstr, buf, len); + + err = strict_strtoul(optstr, 16, &opt); + if (err) + return err; + migrate_threshold = opt; + + return count; +} + +static ssize_t +migrate_threshold_show(struct kobject *kobj, + struct kobj_attribute *attr, char *buf) +{ + int cnt = 0; + char *bufend = buf + PAGE_SIZE; + + cnt = snprintf(buf, bufend - (buf + cnt), "%d\n", migrate_threshold); + return cnt; +} + +static struct kobj_attribute migrate_threshold_attr = { + .attr = { + .name = "migrate_threshold", + .mode = S_IWUSR | S_IRUGO, + }, + .show = migrate_threshold_show, + .store = migrate_threshold_store, +}; + +static int __init +cpe_migrate_external_handler_init(void) +{ + int error; + struct task_struct *kthread; + + if (!badram_kobj) + badram_kobj = kobject_create_and_add("badram", firmware_kobj); + if (!badram_kobj) { + printk(KERN_WARNING "kobject_create_and_add badram failed \n"); + return -EINVAL; + } + + error = sysfs_create_file(badram_kobj, &badram_attr.attr); + if (error) + return -EINVAL; + + error = sysfs_create_file(badram_kobj, &cpe_poll_threshold_attr.attr); + if (error) + return -EINVAL; + + error = sysfs_create_file(badram_kobj, &cmc_poll_threshold_attr.attr); + if (error) + return -EINVAL; + + error = sysfs_create_file(badram_kobj, &migrate_threshold_attr.attr); + if (error) + return -EINVAL; + + /* + * set up the kthread + */ + cpe_active = 1; + kthread = kthread_run(kthread_cpe_migrate, NULL, "cpe_migrate"); + if (IS_ERR(kthread)) { + complete(&kthread_cpe_migrated_exited); + return -EFAULT; + } + + /* + * register external ce handler + */ + if (ia64_reg_CE_extension(cpe_setup_migrate)) { + printk(KERN_ERR "ia64_reg_CE_extension failed.\n"); + return -EFAULT; + } + cpe_poll_enabled = cpe_polling_enabled; + + printk(KERN_INFO "Registered badram Driver\n"); + return 0; +} + +static void __exit +cpe_migrate_external_handler_exit(void) +{ + /* unregister external mca handlers */ + ia64_unreg_CE_extension(); + + /* Stop kthread */ + cpe_active = 0; /* tell kthread_cpe_migrate to exit */ + wake_up_interruptible(&cpe_activate_IRQ_wq); + wait_for_completion(&kthread_cpe_migrated_exited); + + sysfs_remove_file(kernel_kobj, &migrate_threshold_attr.attr); + sysfs_remove_file(kernel_kobj, &cpe_poll_threshold_attr.attr); + sysfs_remove_file(kernel_kobj, &cmc_poll_threshold_attr.attr); + sysfs_remove_file(kernel_kobj, &badram_attr.attr); + kobject_put(badram_kobj); +} + +module_init(cpe_migrate_external_handler_init); +module_exit(cpe_migrate_external_handler_exit); + +module_param(cpe_polling_enabled, int, 0644); +MODULE_PARM_DESC(cpe_polling_enabled, + "Enable polling with migration"); + +MODULE_AUTHOR("Russ Anderson <rja@xxxxxxx>"); +MODULE_DESCRIPTION("ia64 Corrected Error page migration driver"); +MODULE_LICENSE("GPL"); Index: linux-next/arch/ia64/kernel/mca.c =================================================================== --- linux-next.orig/arch/ia64/kernel/mca.c 2009-04-07 18:31:43.554299960 -0500 +++ linux-next/arch/ia64/kernel/mca.c 2009-04-07 18:37:00.121835074 -0500 @@ -68,6 +68,9 @@ * * 2007-04-27 Russ Anderson <rja@xxxxxxx> * Support multiple cpus going through OS_MCA in the same event. + * + * 2008-04-22 Russ Anderson <rja@xxxxxxx> + * Migrate data off pages with correctable memory errors. */ #include <linux/jiffies.h> #include <linux/types.h> @@ -163,7 +166,23 @@ static int cmc_polling_enabled = 1; * but encounters problems retrieving CPE logs. This should only be * necessary for debugging. */ -static int cpe_poll_enabled = 1; + +int cpe_poll_enabled = 1; +EXPORT_SYMBOL(cpe_poll_enabled); +LIST_HEAD(cpe_history_list); +EXPORT_SYMBOL(cpe_history_list); +int cpe_poll_threshold; +EXPORT_SYMBOL(cpe_poll_threshold); +LIST_HEAD(cmc_history_list); +EXPORT_SYMBOL(cmc_history_list); +int cmc_poll_threshold; +EXPORT_SYMBOL(cmc_poll_threshold); + +unsigned int total_badpages; +EXPORT_SYMBOL(total_badpages); + +LIST_HEAD(badpagelist); +EXPORT_SYMBOL(badpagelist); extern void salinfo_log_wakeup(int type, u8 *buffer, u64 size, int irqsafe); @@ -523,6 +542,28 @@ int mca_recover_range(unsigned long addr } EXPORT_SYMBOL_GPL(mca_recover_range); +/* Function pointer to Corrected Error memory migration driver */ +int (*ia64_mca_ce_extension)(void *); + +int +ia64_reg_CE_extension(int (*fn)(void *)) +{ + if (ia64_mca_ce_extension) + return 1; + + ia64_mca_ce_extension = fn; + return 0; +} +EXPORT_SYMBOL(ia64_reg_CE_extension); + +void +ia64_unreg_CE_extension(void) +{ + if (ia64_mca_ce_extension) + ia64_mca_ce_extension = NULL; +} +EXPORT_SYMBOL(ia64_unreg_CE_extension); + #ifdef CONFIG_ACPI int cpe_vector = -1; @@ -531,9 +572,8 @@ int ia64_cpe_irq = -1; static irqreturn_t ia64_mca_cpe_int_handler (int cpe_irq, void *arg) { - static unsigned long cpe_history[CPE_HISTORY_LENGTH]; - static int index; static DEFINE_SPINLOCK(cpe_history_lock); + int recover; IA64_MCA_DEBUG("%s: received interrupt vector = %#x on CPU %d\n", __func__, cpe_irq, smp_processor_id()); @@ -544,18 +584,34 @@ ia64_mca_cpe_int_handler (int cpe_irq, v spin_lock(&cpe_history_lock); if (!cpe_poll_enabled && cpe_vector >= 0) { - int i, count = 1; /* we know 1 happened now */ + int i = 0, count = 1; /* we know 1 happened now */ unsigned long now = jiffies; + struct ce_history *entry, *entry2; - for (i = 0; i < CPE_HISTORY_LENGTH; i++) { - if (now - cpe_history[i] <= HZ) + list_for_each_entry_safe(entry, entry2, + &cpe_history_list, list) { + if (now - entry->time <= HZ) count++; + else { /* remove expired entry */ + list_del(&entry->list); + kfree(entry); + } } - IA64_MCA_DEBUG(KERN_INFO "CPE threshold %d/%d\n", count, CPE_HISTORY_LENGTH); - if (count >= CPE_HISTORY_LENGTH) { + IA64_MCA_DEBUG(KERN_INFO "CPE threshold %d/%d\n", + count, cpe_poll_threshold); + if (count >= cpe_poll_threshold) { cpe_poll_enabled = 1; + /* + * Remove all entries from the history list, they will + * be expired when we leave polling mode. + */ + list_for_each_entry_safe(entry, entry2, + &cpe_history_list, list) { + list_del(&entry->list); + kfree(entry); + } spin_unlock(&cpe_history_lock); disable_irq_nosync(local_vector_to_irq(IA64_CPE_VECTOR)); @@ -571,15 +627,20 @@ ia64_mca_cpe_int_handler (int cpe_irq, v /* lock already released, get out now */ goto out; } else { - cpe_history[index++] = now; - if (index == CPE_HISTORY_LENGTH) - index = 0; + /* + * Add this entry to the list. + */ + entry = kmalloc(sizeof(struct ce_history), GFP_KERNEL); + entry->time = now; + list_add_tail(&entry->list, &cpe_history_list); } } spin_unlock(&cpe_history_lock); out: /* Get the CPE error record and log it */ ia64_mca_log_sal_error_record(SAL_INFO_TYPE_CPE); + recover = (ia64_mca_ce_extension && ia64_mca_ce_extension( + IA64_LOG_CURR_BUFFER(SAL_INFO_TYPE_CPE))); return IRQ_HANDLED; } @@ -1372,8 +1433,6 @@ static DECLARE_WORK(cmc_enable_work, ia6 static irqreturn_t ia64_mca_cmc_int_handler(int cmc_irq, void *arg) { - static unsigned long cmc_history[CMC_HISTORY_LENGTH]; - static int index; static DEFINE_SPINLOCK(cmc_history_lock); IA64_MCA_DEBUG("%s: received interrupt vector = %#x on CPU %d\n", @@ -1384,18 +1443,34 @@ ia64_mca_cmc_int_handler(int cmc_irq, vo spin_lock(&cmc_history_lock); if (!cmc_polling_enabled) { - int i, count = 1; /* we know 1 happened now */ + struct ce_history *entry, *entry2; + int i = 0, count = 1; /* we know 1 happened now */ unsigned long now = jiffies; - for (i = 0; i < CMC_HISTORY_LENGTH; i++) { - if (now - cmc_history[i] <= HZ) + list_for_each_entry_safe(entry, entry2, + &cmc_history_list, list) { + if (now - entry->time <= HZ) count++; + else { /* remove expired entry */ + list_del(&entry->list); + kfree(entry); + } } - IA64_MCA_DEBUG(KERN_INFO "CMC threshold %d/%d\n", count, CMC_HISTORY_LENGTH); - if (count >= CMC_HISTORY_LENGTH) { + IA64_MCA_DEBUG(KERN_INFO "CMC threshold %d/%d\n", + count, cmc_poll_threshold); + if (count >= cmc_poll_threshold) { cmc_polling_enabled = 1; + /* + * Remove all entries from the history list, they will + * be expired when we leave polling mode. + */ + list_for_each_entry_safe(entry, entry2, + &cmc_history_list, list) { + list_del(&entry->list); + kfree(entry); + } spin_unlock(&cmc_history_lock); /* If we're being hit with CMC interrupts, we won't * ever execute the schedule_work() below. Need to @@ -1416,9 +1491,12 @@ ia64_mca_cmc_int_handler(int cmc_irq, vo /* lock already released, get out now */ goto out; } else { - cmc_history[index++] = now; - if (index == CMC_HISTORY_LENGTH) - index = 0; + /* + * Add this entry to the list. + */ + entry = kmalloc(sizeof(struct ce_history), GFP_KERNEL); + entry->time = now; + list_add_tail(&entry->list, &cmc_history_list); } } spin_unlock(&cmc_history_lock); -- Russ Anderson, OS RAS/Partitioning Project Lead SGI - Silicon Graphics Inc rja@xxxxxxx -- To unsubscribe from this list: send the line "unsubscribe linux-ia64" in the body of a message to majordomo@xxxxxxxxxxxxxxx More majordomo info at http://vger.kernel.org/majordomo-info.html
![]() |