From: Russ Anderson Subject: ia64: cpe_migrate.ko causes deadlock. References: bnc#464676 Patch-mainline: not yet, depends on patches.arch/ia64-page-migration schedule_on_each_cpu() deadlocks when called from an event thread. Change cpe_migrate to use a kthread to avoid the problem. Signed-off-by: Russ Anderson Acked-by: Raymund Will --- arch/ia64/kernel/cpe_migrate.c | 72 +++++++++++++++++++++++++++++++---------- 1 file changed, 56 insertions(+), 16 deletions(-) --- a/arch/ia64/kernel/cpe_migrate.c +++ b/arch/ia64/kernel/cpe_migrate.c @@ -22,6 +22,7 @@ #include #include #include +#include #include #include @@ -40,12 +41,15 @@ static struct cpe_info cpe[CE_HISTORY_LE static int cpe_polling_enabled = 1; static int cpe_head; static int cpe_tail; -static int work_scheduled; 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 @@ -160,12 +164,12 @@ ia64_mca_cpe_move_page(u64 paddr, u32 no } /* - * ia64_mca_cpe_migrate - * The worker that does the actual migration. It pulls a - * physical address off the list and calls the migration code. + * cpe_process_queue + * Pulls the physical address off the list and calls the migration code. + * Will process all the addresses on the list. */ -static void -ia64_mca_cpe_migrate(struct work_struct *unused) +void +cpe_process_queue(void) { int ret; u64 paddr; @@ -193,10 +197,36 @@ ia64_mca_cpe_migrate(struct work_struct cpe_tail = 0; } while (cpe_tail != cpe_head); - work_scheduled = 0; + return; +} + +inline int +cpe_list_empty(void) +{ + return (cpe_head == cpe_tail) && (!cpe[cpe_head].paddr); +} + +/* + * 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, + (!cpe_list_empty() || + !cpe_active)); + cpe_process_queue(); /* process work */ + } + complete(&kthread_cpe_migrated_exited); + return 0; } -static DECLARE_WORK(cpe_enable_work, ia64_mca_cpe_migrate); DEFINE_SPINLOCK(cpe_list_lock); /* @@ -228,10 +258,7 @@ cpe_setup_migrate(void *rec) if (ret < 0) return -EINVAL; - if ((cpe_head != cpe_tail) || (cpe[cpe_head].paddr != 0)) - /* - * List not empty - */ + if (!cpe_list_empty()) for (i = 0; i < CE_HISTORY_LENGTH; i++) { if (PAGE_ALIGN(cpe[i].paddr) == PAGE_ALIGN(paddr)) { mstat_already_on_list++; @@ -256,10 +283,7 @@ cpe_setup_migrate(void *rec) } spin_unlock(&cpe_list_lock); - if (!work_scheduled) { - work_scheduled = 1; - schedule_work(&cpe_enable_work); - } + wake_up_interruptible(&cpe_activate_IRQ_wq); return 1; } @@ -396,12 +420,23 @@ static int __init cpe_migrate_external_handler_init(void) { int error; + struct task_struct *kthread; error = sysfs_create_file(kernel_kobj, &badram_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)) { @@ -420,6 +455,11 @@ 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, &badram_attr.attr); }