xref: /linux/drivers/misc/pvpanic/pvpanic.c (revision c771600c6af14749609b49565ffb4cac2959710d)
16861d27cSMihai Carabas // SPDX-License-Identifier: GPL-2.0+
26861d27cSMihai Carabas /*
36861d27cSMihai Carabas  *  Pvpanic Device Support
46861d27cSMihai Carabas  *
56861d27cSMihai Carabas  *  Copyright (C) 2013 Fujitsu.
66861d27cSMihai Carabas  *  Copyright (C) 2018 ZTE.
76861d27cSMihai Carabas  *  Copyright (C) 2021 Oracle.
86861d27cSMihai Carabas  */
96861d27cSMihai Carabas 
10c1426d39SThomas Weißschuh #include <linux/device.h>
117037f714SAndy Shevchenko #include <linux/errno.h>
127037f714SAndy Shevchenko #include <linux/gfp_types.h>
136861d27cSMihai Carabas #include <linux/io.h>
146861d27cSMihai Carabas #include <linux/kexec.h>
157037f714SAndy Shevchenko #include <linux/kstrtox.h>
167037f714SAndy Shevchenko #include <linux/limits.h>
177037f714SAndy Shevchenko #include <linux/list.h>
186861d27cSMihai Carabas #include <linux/mod_devicetable.h>
196861d27cSMihai Carabas #include <linux/module.h>
20f39650deSAndy Shevchenko #include <linux/panic_notifier.h>
217037f714SAndy Shevchenko #include <linux/platform_device.h>
22*7b51b137SThomas Weißschuh #include <linux/reboot.h>
237037f714SAndy Shevchenko #include <linux/spinlock.h>
247037f714SAndy Shevchenko #include <linux/sysfs.h>
256861d27cSMihai Carabas #include <linux/types.h>
266861d27cSMihai Carabas 
276861d27cSMihai Carabas #include <uapi/misc/pvpanic.h>
286861d27cSMihai Carabas 
296861d27cSMihai Carabas #include "pvpanic.h"
306861d27cSMihai Carabas 
316861d27cSMihai Carabas MODULE_AUTHOR("Mihai Carabas <mihai.carabas@oracle.com>");
326861d27cSMihai Carabas MODULE_DESCRIPTION("pvpanic device driver");
336861d27cSMihai Carabas MODULE_LICENSE("GPL");
346861d27cSMihai Carabas 
35c1426d39SThomas Weißschuh struct pvpanic_instance {
36c1426d39SThomas Weißschuh 	void __iomem *base;
37c1426d39SThomas Weißschuh 	unsigned int capability;
38c1426d39SThomas Weißschuh 	unsigned int events;
39*7b51b137SThomas Weißschuh 	struct sys_off_handler *sys_off;
40c1426d39SThomas Weißschuh 	struct list_head list;
41c1426d39SThomas Weißschuh };
42c1426d39SThomas Weißschuh 
43391e2415SYueHaibing static struct list_head pvpanic_list;
44391e2415SYueHaibing static spinlock_t pvpanic_lock;
456861d27cSMihai Carabas 
466861d27cSMihai Carabas static void
476861d27cSMihai Carabas pvpanic_send_event(unsigned int event)
486861d27cSMihai Carabas {
49b3c0f877SMihai Carabas 	struct pvpanic_instance *pi_cur;
50b3c0f877SMihai Carabas 
51e918c102SGuilherme G. Piccoli 	if (!spin_trylock(&pvpanic_lock))
52e918c102SGuilherme G. Piccoli 		return;
53e918c102SGuilherme G. Piccoli 
54b3c0f877SMihai Carabas 	list_for_each_entry(pi_cur, &pvpanic_list, list) {
55b3c0f877SMihai Carabas 		if (event & pi_cur->capability & pi_cur->events)
56b3c0f877SMihai Carabas 			iowrite8(event, pi_cur->base);
57b3c0f877SMihai Carabas 	}
58b3c0f877SMihai Carabas 	spin_unlock(&pvpanic_lock);
596861d27cSMihai Carabas }
606861d27cSMihai Carabas 
616861d27cSMihai Carabas static int
6284b0f12aSAndy Shevchenko pvpanic_panic_notify(struct notifier_block *nb, unsigned long code, void *unused)
636861d27cSMihai Carabas {
646861d27cSMihai Carabas 	unsigned int event = PVPANIC_PANICKED;
656861d27cSMihai Carabas 
666861d27cSMihai Carabas 	if (kexec_crash_loaded())
676861d27cSMihai Carabas 		event = PVPANIC_CRASH_LOADED;
686861d27cSMihai Carabas 
696861d27cSMihai Carabas 	pvpanic_send_event(event);
706861d27cSMihai Carabas 
716861d27cSMihai Carabas 	return NOTIFY_DONE;
726861d27cSMihai Carabas }
736861d27cSMihai Carabas 
74e918c102SGuilherme G. Piccoli /*
75e918c102SGuilherme G. Piccoli  * Call our notifier very early on panic, deferring the
76e918c102SGuilherme G. Piccoli  * action taken to the hypervisor.
77e918c102SGuilherme G. Piccoli  */
786861d27cSMihai Carabas static struct notifier_block pvpanic_panic_nb = {
796861d27cSMihai Carabas 	.notifier_call = pvpanic_panic_notify,
80e918c102SGuilherme G. Piccoli 	.priority = INT_MAX,
816861d27cSMihai Carabas };
826861d27cSMihai Carabas 
83*7b51b137SThomas Weißschuh static int pvpanic_sys_off(struct sys_off_data *data)
84*7b51b137SThomas Weißschuh {
85*7b51b137SThomas Weißschuh 	pvpanic_send_event(PVPANIC_SHUTDOWN);
86*7b51b137SThomas Weißschuh 
87*7b51b137SThomas Weißschuh 	return NOTIFY_DONE;
88*7b51b137SThomas Weißschuh }
89*7b51b137SThomas Weißschuh 
90*7b51b137SThomas Weißschuh static void pvpanic_synchronize_sys_off_handler(struct device *dev, struct pvpanic_instance *pi)
91*7b51b137SThomas Weißschuh {
92*7b51b137SThomas Weißschuh 	/* The kernel core has logic to fall back to system halt if no
93*7b51b137SThomas Weißschuh 	 * sys_off_handler is registered.
94*7b51b137SThomas Weißschuh 	 * When the pvpanic sys_off_handler is disabled via sysfs the kernel
95*7b51b137SThomas Weißschuh 	 * should use that fallback logic, so the handler needs to be unregistered.
96*7b51b137SThomas Weißschuh 	 */
97*7b51b137SThomas Weißschuh 
98*7b51b137SThomas Weißschuh 	struct sys_off_handler *sys_off;
99*7b51b137SThomas Weißschuh 
100*7b51b137SThomas Weißschuh 	if (!(pi->events & PVPANIC_SHUTDOWN) == !pi->sys_off)
101*7b51b137SThomas Weißschuh 		return;
102*7b51b137SThomas Weißschuh 
103*7b51b137SThomas Weißschuh 	if (!pi->sys_off) {
104*7b51b137SThomas Weißschuh 		sys_off = register_sys_off_handler(SYS_OFF_MODE_POWER_OFF, SYS_OFF_PRIO_LOW,
105*7b51b137SThomas Weißschuh 						   pvpanic_sys_off, NULL);
106*7b51b137SThomas Weißschuh 		if (IS_ERR(sys_off))
107*7b51b137SThomas Weißschuh 			dev_warn(dev, "Could not register sys_off_handler: %pe\n", sys_off);
108*7b51b137SThomas Weißschuh 		else
109*7b51b137SThomas Weißschuh 			pi->sys_off = sys_off;
110*7b51b137SThomas Weißschuh 	} else {
111*7b51b137SThomas Weißschuh 		unregister_sys_off_handler(pi->sys_off);
112*7b51b137SThomas Weißschuh 		pi->sys_off = NULL;
113*7b51b137SThomas Weißschuh 	}
114*7b51b137SThomas Weißschuh }
115*7b51b137SThomas Weißschuh 
116394febc9SChristophe JAILLET static void pvpanic_remove(void *param)
1176861d27cSMihai Carabas {
118b3c0f877SMihai Carabas 	struct pvpanic_instance *pi_cur, *pi_next;
119394febc9SChristophe JAILLET 	struct pvpanic_instance *pi = param;
120b3c0f877SMihai Carabas 
121b3c0f877SMihai Carabas 	spin_lock(&pvpanic_lock);
122b3c0f877SMihai Carabas 	list_for_each_entry_safe(pi_cur, pi_next, &pvpanic_list, list) {
123b3c0f877SMihai Carabas 		if (pi_cur == pi) {
124b3c0f877SMihai Carabas 			list_del(&pi_cur->list);
125b3c0f877SMihai Carabas 			break;
126b3c0f877SMihai Carabas 		}
127b3c0f877SMihai Carabas 	}
128b3c0f877SMihai Carabas 	spin_unlock(&pvpanic_lock);
129*7b51b137SThomas Weißschuh 
130*7b51b137SThomas Weißschuh 	unregister_sys_off_handler(pi->sys_off);
1316861d27cSMihai Carabas }
132394febc9SChristophe JAILLET 
133c1426d39SThomas Weißschuh static ssize_t capability_show(struct device *dev, struct device_attribute *attr, char *buf)
134394febc9SChristophe JAILLET {
135c1426d39SThomas Weißschuh 	struct pvpanic_instance *pi = dev_get_drvdata(dev);
136c1426d39SThomas Weißschuh 
137c1426d39SThomas Weißschuh 	return sysfs_emit(buf, "%x\n", pi->capability);
138c1426d39SThomas Weißschuh }
139c1426d39SThomas Weißschuh static DEVICE_ATTR_RO(capability);
140c1426d39SThomas Weißschuh 
141c1426d39SThomas Weißschuh static ssize_t events_show(struct device *dev, struct device_attribute *attr, char *buf)
142c1426d39SThomas Weißschuh {
143c1426d39SThomas Weißschuh 	struct pvpanic_instance *pi = dev_get_drvdata(dev);
144c1426d39SThomas Weißschuh 
145c1426d39SThomas Weißschuh 	return sysfs_emit(buf, "%x\n", pi->events);
146c1426d39SThomas Weißschuh }
147c1426d39SThomas Weißschuh 
148c1426d39SThomas Weißschuh static ssize_t events_store(struct device *dev, struct device_attribute *attr,
149c1426d39SThomas Weißschuh 			    const char *buf, size_t count)
150c1426d39SThomas Weißschuh {
151c1426d39SThomas Weißschuh 	struct pvpanic_instance *pi = dev_get_drvdata(dev);
152c1426d39SThomas Weißschuh 	unsigned int tmp;
153c1426d39SThomas Weißschuh 	int err;
154c1426d39SThomas Weißschuh 
155c1426d39SThomas Weißschuh 	err = kstrtouint(buf, 16, &tmp);
156c1426d39SThomas Weißschuh 	if (err)
157c1426d39SThomas Weißschuh 		return err;
158c1426d39SThomas Weißschuh 
159c1426d39SThomas Weißschuh 	if ((tmp & pi->capability) != tmp)
160394febc9SChristophe JAILLET 		return -EINVAL;
161394febc9SChristophe JAILLET 
162c1426d39SThomas Weißschuh 	pi->events = tmp;
163*7b51b137SThomas Weißschuh 	pvpanic_synchronize_sys_off_handler(dev, pi);
164c1426d39SThomas Weißschuh 
165c1426d39SThomas Weißschuh 	return count;
166c1426d39SThomas Weißschuh }
167c1426d39SThomas Weißschuh static DEVICE_ATTR_RW(events);
168c1426d39SThomas Weißschuh 
169c1426d39SThomas Weißschuh static struct attribute *pvpanic_dev_attrs[] = {
170c1426d39SThomas Weißschuh 	&dev_attr_capability.attr,
171c1426d39SThomas Weißschuh 	&dev_attr_events.attr,
172c1426d39SThomas Weißschuh 	NULL
173c1426d39SThomas Weißschuh };
174c1426d39SThomas Weißschuh 
175c1426d39SThomas Weißschuh static const struct attribute_group pvpanic_dev_group = {
176c1426d39SThomas Weißschuh 	.attrs = pvpanic_dev_attrs,
177c1426d39SThomas Weißschuh };
178c1426d39SThomas Weißschuh 
179c1426d39SThomas Weißschuh const struct attribute_group *pvpanic_dev_groups[] = {
180c1426d39SThomas Weißschuh 	&pvpanic_dev_group,
181c1426d39SThomas Weißschuh 	NULL
182c1426d39SThomas Weißschuh };
183c1426d39SThomas Weißschuh EXPORT_SYMBOL_GPL(pvpanic_dev_groups);
184c1426d39SThomas Weißschuh 
185c1426d39SThomas Weißschuh int devm_pvpanic_probe(struct device *dev, void __iomem *base)
186c1426d39SThomas Weißschuh {
187c1426d39SThomas Weißschuh 	struct pvpanic_instance *pi;
188c1426d39SThomas Weißschuh 
189c1426d39SThomas Weißschuh 	if (!base)
190c1426d39SThomas Weißschuh 		return -EINVAL;
191c1426d39SThomas Weißschuh 
192c1426d39SThomas Weißschuh 	pi = devm_kmalloc(dev, sizeof(*pi), GFP_KERNEL);
193c1426d39SThomas Weißschuh 	if (!pi)
194c1426d39SThomas Weißschuh 		return -ENOMEM;
195c1426d39SThomas Weißschuh 
196c1426d39SThomas Weißschuh 	pi->base = base;
197*7b51b137SThomas Weißschuh 	pi->capability = PVPANIC_PANICKED | PVPANIC_CRASH_LOADED | PVPANIC_SHUTDOWN;
198c1426d39SThomas Weißschuh 
199c1426d39SThomas Weißschuh 	/* initlize capability by RDPT */
200c1426d39SThomas Weißschuh 	pi->capability &= ioread8(base);
201c1426d39SThomas Weißschuh 	pi->events = pi->capability;
202c1426d39SThomas Weißschuh 
203*7b51b137SThomas Weißschuh 	pi->sys_off = NULL;
204*7b51b137SThomas Weißschuh 	pvpanic_synchronize_sys_off_handler(dev, pi);
205*7b51b137SThomas Weißschuh 
206394febc9SChristophe JAILLET 	spin_lock(&pvpanic_lock);
207394febc9SChristophe JAILLET 	list_add(&pi->list, &pvpanic_list);
208394febc9SChristophe JAILLET 	spin_unlock(&pvpanic_lock);
209394febc9SChristophe JAILLET 
210a99009bcSMihai Carabas 	dev_set_drvdata(dev, pi);
211a99009bcSMihai Carabas 
212394febc9SChristophe JAILLET 	return devm_add_action_or_reset(dev, pvpanic_remove, pi);
213394febc9SChristophe JAILLET }
214394febc9SChristophe JAILLET EXPORT_SYMBOL_GPL(devm_pvpanic_probe);
2156861d27cSMihai Carabas 
216b3c0f877SMihai Carabas static int pvpanic_init(void)
2176861d27cSMihai Carabas {
218b3c0f877SMihai Carabas 	INIT_LIST_HEAD(&pvpanic_list);
219b3c0f877SMihai Carabas 	spin_lock_init(&pvpanic_lock);
220b3c0f877SMihai Carabas 
22184b0f12aSAndy Shevchenko 	atomic_notifier_chain_register(&panic_notifier_list, &pvpanic_panic_nb);
222b3c0f877SMihai Carabas 
223b3c0f877SMihai Carabas 	return 0;
2246861d27cSMihai Carabas }
22533a43041SAndy Shevchenko module_init(pvpanic_init);
226b3c0f877SMihai Carabas 
227b3c0f877SMihai Carabas static void pvpanic_exit(void)
228b3c0f877SMihai Carabas {
22984b0f12aSAndy Shevchenko 	atomic_notifier_chain_unregister(&panic_notifier_list, &pvpanic_panic_nb);
230b3c0f877SMihai Carabas 
231b3c0f877SMihai Carabas }
232b3c0f877SMihai Carabas module_exit(pvpanic_exit);
233