| 
									
										
										
										
											2014-07-29 18:40:07 +05:30
										 |  |  | /*
 | 
					
						
							|  |  |  |  * OPAL hypervisor Maintenance interrupt handling support in PowreNV. | 
					
						
							|  |  |  |  * | 
					
						
							|  |  |  |  * This program is free software; you can redistribute it and/or modify | 
					
						
							|  |  |  |  * it under the terms of the GNU General Public License as published by | 
					
						
							|  |  |  |  * the Free Software Foundation; either version 2 of the License, or | 
					
						
							|  |  |  |  * (at your option) any later version. | 
					
						
							|  |  |  |  * | 
					
						
							|  |  |  |  * This program is distributed in the hope that it will be useful, | 
					
						
							|  |  |  |  * but WITHOUT ANY WARRANTY; without even the implied warranty of | 
					
						
							|  |  |  |  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | 
					
						
							|  |  |  |  * GNU General Public License for more details. | 
					
						
							|  |  |  |  * | 
					
						
							|  |  |  |  * You should have received a copy of the GNU General Public License | 
					
						
							|  |  |  |  * along with this program; If not, see <http://www.gnu.org/licenses/>.
 | 
					
						
							|  |  |  |  * | 
					
						
							|  |  |  |  * Copyright 2014 IBM Corporation | 
					
						
							|  |  |  |  * Author: Mahesh Salgaonkar <mahesh@linux.vnet.ibm.com> | 
					
						
							|  |  |  |  */ | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | #undef DEBUG
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | #include <linux/kernel.h>
 | 
					
						
							|  |  |  | #include <linux/init.h>
 | 
					
						
							|  |  |  | #include <linux/of.h>
 | 
					
						
							|  |  |  | #include <linux/mm.h>
 | 
					
						
							|  |  |  | #include <linux/slab.h>
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | #include <asm/opal.h>
 | 
					
						
							|  |  |  | #include <asm/cputable.h>
 | 
					
						
							| 
									
										
										
										
											2014-08-12 17:17:04 +08:00
										 |  |  | #include <asm/machdep.h>
 | 
					
						
							| 
									
										
										
										
											2014-07-29 18:40:07 +05:30
										 |  |  | 
 | 
					
						
							|  |  |  | static int opal_hmi_handler_nb_init; | 
					
						
							|  |  |  | struct OpalHmiEvtNode { | 
					
						
							|  |  |  | 	struct list_head list; | 
					
						
							|  |  |  | 	struct OpalHMIEvent hmi_evt; | 
					
						
							|  |  |  | }; | 
					
						
							|  |  |  | static LIST_HEAD(opal_hmi_evt_list); | 
					
						
							|  |  |  | static DEFINE_SPINLOCK(opal_hmi_evt_lock); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | static void print_hmi_event_info(struct OpalHMIEvent *hmi_evt) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | 	const char *level, *sevstr, *error_info; | 
					
						
							|  |  |  | 	static const char *hmi_error_types[] = { | 
					
						
							|  |  |  | 		"Malfunction Alert", | 
					
						
							|  |  |  | 		"Processor Recovery done", | 
					
						
							|  |  |  | 		"Processor recovery occurred again", | 
					
						
							|  |  |  | 		"Processor recovery occurred for masked error", | 
					
						
							|  |  |  | 		"Timer facility experienced an error", | 
					
						
							|  |  |  | 		"TFMR SPR is corrupted", | 
					
						
							|  |  |  | 		"UPS (Uniterrupted Power System) Overflow indication", | 
					
						
							|  |  |  | 		"An XSCOM operation failure", | 
					
						
							|  |  |  | 		"An XSCOM operation completed", | 
					
						
							|  |  |  | 		"SCOM has set a reserved FIR bit to cause recovery", | 
					
						
							|  |  |  | 		"Debug trigger has set a reserved FIR bit to cause recovery", | 
					
						
							|  |  |  | 		"A hypervisor resource error occurred" | 
					
						
							|  |  |  | 	}; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	/* Print things out */ | 
					
						
							| 
									
										
										
										
											2014-11-20 09:44:36 +05:30
										 |  |  | 	if (hmi_evt->version < OpalHMIEvt_V1) { | 
					
						
							| 
									
										
										
										
											2014-07-29 18:40:07 +05:30
										 |  |  | 		pr_err("HMI Interrupt, Unknown event version %d !\n", | 
					
						
							|  |  |  | 			hmi_evt->version); | 
					
						
							|  |  |  | 		return; | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	switch (hmi_evt->severity) { | 
					
						
							|  |  |  | 	case OpalHMI_SEV_NO_ERROR: | 
					
						
							|  |  |  | 		level = KERN_INFO; | 
					
						
							|  |  |  | 		sevstr = "Harmless"; | 
					
						
							|  |  |  | 		break; | 
					
						
							|  |  |  | 	case OpalHMI_SEV_WARNING: | 
					
						
							|  |  |  | 		level = KERN_WARNING; | 
					
						
							|  |  |  | 		sevstr = ""; | 
					
						
							|  |  |  | 		break; | 
					
						
							|  |  |  | 	case OpalHMI_SEV_ERROR_SYNC: | 
					
						
							|  |  |  | 		level = KERN_ERR; | 
					
						
							|  |  |  | 		sevstr = "Severe"; | 
					
						
							|  |  |  | 		break; | 
					
						
							|  |  |  | 	case OpalHMI_SEV_FATAL: | 
					
						
							|  |  |  | 	default: | 
					
						
							|  |  |  | 		level = KERN_ERR; | 
					
						
							|  |  |  | 		sevstr = "Fatal"; | 
					
						
							|  |  |  | 		break; | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	printk("%s%s Hypervisor Maintenance interrupt [%s]\n", | 
					
						
							|  |  |  | 		level, sevstr, | 
					
						
							|  |  |  | 		hmi_evt->disposition == OpalHMI_DISPOSITION_RECOVERED ? | 
					
						
							|  |  |  | 		"Recovered" : "Not recovered"); | 
					
						
							|  |  |  | 	error_info = hmi_evt->type < ARRAY_SIZE(hmi_error_types) ? | 
					
						
							|  |  |  | 			hmi_error_types[hmi_evt->type] | 
					
						
							|  |  |  | 			: "Unknown"; | 
					
						
							|  |  |  | 	printk("%s Error detail: %s\n", level, error_info); | 
					
						
							|  |  |  | 	printk("%s	HMER: %016llx\n", level, be64_to_cpu(hmi_evt->hmer)); | 
					
						
							|  |  |  | 	if ((hmi_evt->type == OpalHMI_ERROR_TFAC) || | 
					
						
							|  |  |  | 		(hmi_evt->type == OpalHMI_ERROR_TFMR_PARITY)) | 
					
						
							|  |  |  | 		printk("%s	TFMR: %016llx\n", level, | 
					
						
							|  |  |  | 						be64_to_cpu(hmi_evt->tfmr)); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | static void hmi_event_handler(struct work_struct *work) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | 	unsigned long flags; | 
					
						
							|  |  |  | 	struct OpalHMIEvent *hmi_evt; | 
					
						
							|  |  |  | 	struct OpalHmiEvtNode *msg_node; | 
					
						
							|  |  |  | 	uint8_t disposition; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	spin_lock_irqsave(&opal_hmi_evt_lock, flags); | 
					
						
							|  |  |  | 	while (!list_empty(&opal_hmi_evt_list)) { | 
					
						
							|  |  |  | 		msg_node = list_entry(opal_hmi_evt_list.next, | 
					
						
							|  |  |  | 					   struct OpalHmiEvtNode, list); | 
					
						
							|  |  |  | 		list_del(&msg_node->list); | 
					
						
							|  |  |  | 		spin_unlock_irqrestore(&opal_hmi_evt_lock, flags); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		hmi_evt = (struct OpalHMIEvent *) &msg_node->hmi_evt; | 
					
						
							|  |  |  | 		print_hmi_event_info(hmi_evt); | 
					
						
							|  |  |  | 		disposition = hmi_evt->disposition; | 
					
						
							|  |  |  | 		kfree(msg_node); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		/*
 | 
					
						
							|  |  |  | 		 * Check if HMI event has been recovered or not. If not | 
					
						
							|  |  |  | 		 * then we can't continue, invoke panic. | 
					
						
							|  |  |  | 		 */ | 
					
						
							|  |  |  | 		if (disposition != OpalHMI_DISPOSITION_RECOVERED) | 
					
						
							|  |  |  | 			panic("Unrecoverable HMI exception"); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		spin_lock_irqsave(&opal_hmi_evt_lock, flags); | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	spin_unlock_irqrestore(&opal_hmi_evt_lock, flags); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | static DECLARE_WORK(hmi_event_work, hmi_event_handler); | 
					
						
							|  |  |  | /*
 | 
					
						
							|  |  |  |  * opal_handle_hmi_event - notifier handler that queues up HMI events | 
					
						
							|  |  |  |  * to be preocessed later. | 
					
						
							|  |  |  |  */ | 
					
						
							|  |  |  | static int opal_handle_hmi_event(struct notifier_block *nb, | 
					
						
							|  |  |  | 			  unsigned long msg_type, void *msg) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | 	unsigned long flags; | 
					
						
							|  |  |  | 	struct OpalHMIEvent *hmi_evt; | 
					
						
							|  |  |  | 	struct opal_msg *hmi_msg = msg; | 
					
						
							|  |  |  | 	struct OpalHmiEvtNode *msg_node; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	/* Sanity Checks */ | 
					
						
							|  |  |  | 	if (msg_type != OPAL_MSG_HMI_EVT) | 
					
						
							|  |  |  | 		return 0; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	/* HMI event info starts from param[0] */ | 
					
						
							|  |  |  | 	hmi_evt = (struct OpalHMIEvent *)&hmi_msg->params[0]; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	/* Delay the logging of HMI events to workqueue. */ | 
					
						
							|  |  |  | 	msg_node = kzalloc(sizeof(*msg_node), GFP_ATOMIC); | 
					
						
							|  |  |  | 	if (!msg_node) { | 
					
						
							|  |  |  | 		pr_err("HMI: out of memory, Opal message event not handled\n"); | 
					
						
							|  |  |  | 		return -ENOMEM; | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	memcpy(&msg_node->hmi_evt, hmi_evt, sizeof(struct OpalHMIEvent)); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	spin_lock_irqsave(&opal_hmi_evt_lock, flags); | 
					
						
							|  |  |  | 	list_add(&msg_node->list, &opal_hmi_evt_list); | 
					
						
							|  |  |  | 	spin_unlock_irqrestore(&opal_hmi_evt_lock, flags); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	schedule_work(&hmi_event_work); | 
					
						
							|  |  |  | 	return 0; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | static struct notifier_block opal_hmi_handler_nb = { | 
					
						
							|  |  |  | 	.notifier_call	= opal_handle_hmi_event, | 
					
						
							|  |  |  | 	.next		= NULL, | 
					
						
							|  |  |  | 	.priority	= 0, | 
					
						
							|  |  |  | }; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | static int __init opal_hmi_handler_init(void) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | 	int ret; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if (!opal_hmi_handler_nb_init) { | 
					
						
							|  |  |  | 		ret = opal_message_notifier_register( | 
					
						
							|  |  |  | 				OPAL_MSG_HMI_EVT, &opal_hmi_handler_nb); | 
					
						
							|  |  |  | 		if (ret) { | 
					
						
							|  |  |  | 			pr_err("%s: Can't register OPAL event notifier (%d)\n", | 
					
						
							|  |  |  | 			       __func__, ret); | 
					
						
							|  |  |  | 			return ret; | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		opal_hmi_handler_nb_init = 1; | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	return 0; | 
					
						
							|  |  |  | } | 
					
						
							| 
									
										
										
										
											2014-08-12 17:17:04 +08:00
										 |  |  | machine_subsys_initcall(powernv, opal_hmi_handler_init); |