diff options
Diffstat (limited to 'arch/powerpc/platforms/pseries/ras.c')
-rw-r--r-- | arch/powerpc/platforms/pseries/ras.c | 83 |
1 files changed, 83 insertions, 0 deletions
diff --git a/arch/powerpc/platforms/pseries/ras.c b/arch/powerpc/platforms/pseries/ras.c index a25c2ac0c9c0..c97d15352f9f 100644 --- a/arch/powerpc/platforms/pseries/ras.c +++ b/arch/powerpc/platforms/pseries/ras.c | |||
@@ -707,6 +707,87 @@ out: | |||
707 | return disposition; | 707 | return disposition; |
708 | } | 708 | } |
709 | 709 | ||
710 | #ifdef CONFIG_MEMORY_FAILURE | ||
711 | |||
712 | static DEFINE_PER_CPU(int, rtas_ue_count); | ||
713 | static DEFINE_PER_CPU(unsigned long, rtas_ue_paddr[MAX_MC_EVT]); | ||
714 | |||
715 | #define UE_EFFECTIVE_ADDR_PROVIDED 0x40 | ||
716 | #define UE_LOGICAL_ADDR_PROVIDED 0x20 | ||
717 | |||
718 | |||
719 | static void pseries_hwpoison_work_fn(struct work_struct *work) | ||
720 | { | ||
721 | unsigned long paddr; | ||
722 | int index; | ||
723 | |||
724 | while (__this_cpu_read(rtas_ue_count) > 0) { | ||
725 | index = __this_cpu_read(rtas_ue_count) - 1; | ||
726 | paddr = __this_cpu_read(rtas_ue_paddr[index]); | ||
727 | memory_failure(paddr >> PAGE_SHIFT, 0); | ||
728 | __this_cpu_dec(rtas_ue_count); | ||
729 | } | ||
730 | } | ||
731 | |||
732 | static DECLARE_WORK(hwpoison_work, pseries_hwpoison_work_fn); | ||
733 | |||
734 | static void queue_ue_paddr(unsigned long paddr) | ||
735 | { | ||
736 | int index; | ||
737 | |||
738 | index = __this_cpu_inc_return(rtas_ue_count) - 1; | ||
739 | if (index >= MAX_MC_EVT) { | ||
740 | __this_cpu_dec(rtas_ue_count); | ||
741 | return; | ||
742 | } | ||
743 | this_cpu_write(rtas_ue_paddr[index], paddr); | ||
744 | schedule_work(&hwpoison_work); | ||
745 | } | ||
746 | |||
747 | static void pseries_do_memory_failure(struct pt_regs *regs, | ||
748 | struct pseries_mc_errorlog *mce_log) | ||
749 | { | ||
750 | unsigned long paddr; | ||
751 | |||
752 | if (mce_log->sub_err_type & UE_LOGICAL_ADDR_PROVIDED) { | ||
753 | paddr = be64_to_cpu(mce_log->logical_address); | ||
754 | } else if (mce_log->sub_err_type & UE_EFFECTIVE_ADDR_PROVIDED) { | ||
755 | unsigned long pfn; | ||
756 | |||
757 | pfn = addr_to_pfn(regs, | ||
758 | be64_to_cpu(mce_log->effective_address)); | ||
759 | if (pfn == ULONG_MAX) | ||
760 | return; | ||
761 | paddr = pfn << PAGE_SHIFT; | ||
762 | } else { | ||
763 | return; | ||
764 | } | ||
765 | queue_ue_paddr(paddr); | ||
766 | } | ||
767 | |||
768 | static void pseries_process_ue(struct pt_regs *regs, | ||
769 | struct rtas_error_log *errp) | ||
770 | { | ||
771 | struct pseries_errorlog *pseries_log; | ||
772 | struct pseries_mc_errorlog *mce_log; | ||
773 | |||
774 | if (!rtas_error_extended(errp)) | ||
775 | return; | ||
776 | |||
777 | pseries_log = get_pseries_errorlog(errp, PSERIES_ELOG_SECT_ID_MCE); | ||
778 | if (!pseries_log) | ||
779 | return; | ||
780 | |||
781 | mce_log = (struct pseries_mc_errorlog *)pseries_log->data; | ||
782 | |||
783 | if (mce_log->error_type == MC_ERROR_TYPE_UE) | ||
784 | pseries_do_memory_failure(regs, mce_log); | ||
785 | } | ||
786 | #else | ||
787 | static inline void pseries_process_ue(struct pt_regs *regs, | ||
788 | struct rtas_error_log *errp) { } | ||
789 | #endif /*CONFIG_MEMORY_FAILURE */ | ||
790 | |||
710 | /* | 791 | /* |
711 | * Process MCE rtas errlog event. | 792 | * Process MCE rtas errlog event. |
712 | */ | 793 | */ |
@@ -765,6 +846,8 @@ static int recover_mce(struct pt_regs *regs, struct rtas_error_log *err) | |||
765 | recovered = 1; | 846 | recovered = 1; |
766 | } | 847 | } |
767 | 848 | ||
849 | pseries_process_ue(regs, err); | ||
850 | |||
768 | /* Queue irq work to log this rtas event later. */ | 851 | /* Queue irq work to log this rtas event later. */ |
769 | irq_work_queue(&mce_errlog_process_work); | 852 | irq_work_queue(&mce_errlog_process_work); |
770 | 853 | ||