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
10*e9194a95SThomas Weißschuh #include <linux/device.h>
116861d27cSMihai Carabas #include <linux/io.h>
126861d27cSMihai Carabas #include <linux/kernel.h>
136861d27cSMihai Carabas #include <linux/kexec.h>
146861d27cSMihai Carabas #include <linux/mod_devicetable.h>
156861d27cSMihai Carabas #include <linux/module.h>
166861d27cSMihai Carabas #include <linux/platform_device.h>
17f39650deSAndy Shevchenko #include <linux/panic_notifier.h>
186861d27cSMihai Carabas #include <linux/types.h>
19b3c0f877SMihai Carabas #include <linux/cdev.h>
20b3c0f877SMihai Carabas #include <linux/list.h>
216861d27cSMihai Carabas
226861d27cSMihai Carabas #include <uapi/misc/pvpanic.h>
236861d27cSMihai Carabas
246861d27cSMihai Carabas #include "pvpanic.h"
256861d27cSMihai Carabas
266861d27cSMihai Carabas MODULE_AUTHOR("Mihai Carabas <mihai.carabas@oracle.com>");
276861d27cSMihai Carabas MODULE_DESCRIPTION("pvpanic device driver");
286861d27cSMihai Carabas MODULE_LICENSE("GPL");
296861d27cSMihai Carabas
30*e9194a95SThomas Weißschuh struct pvpanic_instance {
31*e9194a95SThomas Weißschuh void __iomem *base;
32*e9194a95SThomas Weißschuh unsigned int capability;
33*e9194a95SThomas Weißschuh unsigned int events;
34*e9194a95SThomas Weißschuh struct list_head list;
35*e9194a95SThomas Weißschuh };
36*e9194a95SThomas Weißschuh
37391e2415SYueHaibing static struct list_head pvpanic_list;
38391e2415SYueHaibing static spinlock_t pvpanic_lock;
396861d27cSMihai Carabas
406861d27cSMihai Carabas static void
pvpanic_send_event(unsigned int event)416861d27cSMihai Carabas pvpanic_send_event(unsigned int event)
426861d27cSMihai Carabas {
43b3c0f877SMihai Carabas struct pvpanic_instance *pi_cur;
44b3c0f877SMihai Carabas
45e918c102SGuilherme G. Piccoli if (!spin_trylock(&pvpanic_lock))
46e918c102SGuilherme G. Piccoli return;
47e918c102SGuilherme G. Piccoli
48b3c0f877SMihai Carabas list_for_each_entry(pi_cur, &pvpanic_list, list) {
49b3c0f877SMihai Carabas if (event & pi_cur->capability & pi_cur->events)
50b3c0f877SMihai Carabas iowrite8(event, pi_cur->base);
51b3c0f877SMihai Carabas }
52b3c0f877SMihai Carabas spin_unlock(&pvpanic_lock);
536861d27cSMihai Carabas }
546861d27cSMihai Carabas
556861d27cSMihai Carabas static int
pvpanic_panic_notify(struct notifier_block * nb,unsigned long code,void * unused)5684b0f12aSAndy Shevchenko pvpanic_panic_notify(struct notifier_block *nb, unsigned long code, void *unused)
576861d27cSMihai Carabas {
586861d27cSMihai Carabas unsigned int event = PVPANIC_PANICKED;
596861d27cSMihai Carabas
606861d27cSMihai Carabas if (kexec_crash_loaded())
616861d27cSMihai Carabas event = PVPANIC_CRASH_LOADED;
626861d27cSMihai Carabas
636861d27cSMihai Carabas pvpanic_send_event(event);
646861d27cSMihai Carabas
656861d27cSMihai Carabas return NOTIFY_DONE;
666861d27cSMihai Carabas }
676861d27cSMihai Carabas
68e918c102SGuilherme G. Piccoli /*
69e918c102SGuilherme G. Piccoli * Call our notifier very early on panic, deferring the
70e918c102SGuilherme G. Piccoli * action taken to the hypervisor.
71e918c102SGuilherme G. Piccoli */
726861d27cSMihai Carabas static struct notifier_block pvpanic_panic_nb = {
736861d27cSMihai Carabas .notifier_call = pvpanic_panic_notify,
74e918c102SGuilherme G. Piccoli .priority = INT_MAX,
756861d27cSMihai Carabas };
766861d27cSMihai Carabas
pvpanic_remove(void * param)77394febc9SChristophe JAILLET static void pvpanic_remove(void *param)
786861d27cSMihai Carabas {
79b3c0f877SMihai Carabas struct pvpanic_instance *pi_cur, *pi_next;
80394febc9SChristophe JAILLET struct pvpanic_instance *pi = param;
81b3c0f877SMihai Carabas
82b3c0f877SMihai Carabas spin_lock(&pvpanic_lock);
83b3c0f877SMihai Carabas list_for_each_entry_safe(pi_cur, pi_next, &pvpanic_list, list) {
84b3c0f877SMihai Carabas if (pi_cur == pi) {
85b3c0f877SMihai Carabas list_del(&pi_cur->list);
86b3c0f877SMihai Carabas break;
87b3c0f877SMihai Carabas }
88b3c0f877SMihai Carabas }
89b3c0f877SMihai Carabas spin_unlock(&pvpanic_lock);
906861d27cSMihai Carabas }
91394febc9SChristophe JAILLET
capability_show(struct device * dev,struct device_attribute * attr,char * buf)92*e9194a95SThomas Weißschuh static ssize_t capability_show(struct device *dev, struct device_attribute *attr, char *buf)
93394febc9SChristophe JAILLET {
94*e9194a95SThomas Weißschuh struct pvpanic_instance *pi = dev_get_drvdata(dev);
95*e9194a95SThomas Weißschuh
96*e9194a95SThomas Weißschuh return sysfs_emit(buf, "%x\n", pi->capability);
97*e9194a95SThomas Weißschuh }
98*e9194a95SThomas Weißschuh static DEVICE_ATTR_RO(capability);
99*e9194a95SThomas Weißschuh
events_show(struct device * dev,struct device_attribute * attr,char * buf)100*e9194a95SThomas Weißschuh static ssize_t events_show(struct device *dev, struct device_attribute *attr, char *buf)
101*e9194a95SThomas Weißschuh {
102*e9194a95SThomas Weißschuh struct pvpanic_instance *pi = dev_get_drvdata(dev);
103*e9194a95SThomas Weißschuh
104*e9194a95SThomas Weißschuh return sysfs_emit(buf, "%x\n", pi->events);
105*e9194a95SThomas Weißschuh }
106*e9194a95SThomas Weißschuh
events_store(struct device * dev,struct device_attribute * attr,const char * buf,size_t count)107*e9194a95SThomas Weißschuh static ssize_t events_store(struct device *dev, struct device_attribute *attr,
108*e9194a95SThomas Weißschuh const char *buf, size_t count)
109*e9194a95SThomas Weißschuh {
110*e9194a95SThomas Weißschuh struct pvpanic_instance *pi = dev_get_drvdata(dev);
111*e9194a95SThomas Weißschuh unsigned int tmp;
112*e9194a95SThomas Weißschuh int err;
113*e9194a95SThomas Weißschuh
114*e9194a95SThomas Weißschuh err = kstrtouint(buf, 16, &tmp);
115*e9194a95SThomas Weißschuh if (err)
116*e9194a95SThomas Weißschuh return err;
117*e9194a95SThomas Weißschuh
118*e9194a95SThomas Weißschuh if ((tmp & pi->capability) != tmp)
119394febc9SChristophe JAILLET return -EINVAL;
120394febc9SChristophe JAILLET
121*e9194a95SThomas Weißschuh pi->events = tmp;
122*e9194a95SThomas Weißschuh
123*e9194a95SThomas Weißschuh return count;
124*e9194a95SThomas Weißschuh }
125*e9194a95SThomas Weißschuh static DEVICE_ATTR_RW(events);
126*e9194a95SThomas Weißschuh
127*e9194a95SThomas Weißschuh static struct attribute *pvpanic_dev_attrs[] = {
128*e9194a95SThomas Weißschuh &dev_attr_capability.attr,
129*e9194a95SThomas Weißschuh &dev_attr_events.attr,
130*e9194a95SThomas Weißschuh NULL
131*e9194a95SThomas Weißschuh };
132*e9194a95SThomas Weißschuh
133*e9194a95SThomas Weißschuh static const struct attribute_group pvpanic_dev_group = {
134*e9194a95SThomas Weißschuh .attrs = pvpanic_dev_attrs,
135*e9194a95SThomas Weißschuh };
136*e9194a95SThomas Weißschuh
137*e9194a95SThomas Weißschuh const struct attribute_group *pvpanic_dev_groups[] = {
138*e9194a95SThomas Weißschuh &pvpanic_dev_group,
139*e9194a95SThomas Weißschuh NULL
140*e9194a95SThomas Weißschuh };
141*e9194a95SThomas Weißschuh EXPORT_SYMBOL_GPL(pvpanic_dev_groups);
142*e9194a95SThomas Weißschuh
devm_pvpanic_probe(struct device * dev,void __iomem * base)143*e9194a95SThomas Weißschuh int devm_pvpanic_probe(struct device *dev, void __iomem *base)
144*e9194a95SThomas Weißschuh {
145*e9194a95SThomas Weißschuh struct pvpanic_instance *pi;
146*e9194a95SThomas Weißschuh
147*e9194a95SThomas Weißschuh if (!base)
148*e9194a95SThomas Weißschuh return -EINVAL;
149*e9194a95SThomas Weißschuh
150*e9194a95SThomas Weißschuh pi = devm_kmalloc(dev, sizeof(*pi), GFP_KERNEL);
151*e9194a95SThomas Weißschuh if (!pi)
152*e9194a95SThomas Weißschuh return -ENOMEM;
153*e9194a95SThomas Weißschuh
154*e9194a95SThomas Weißschuh pi->base = base;
155*e9194a95SThomas Weißschuh pi->capability = PVPANIC_PANICKED | PVPANIC_CRASH_LOADED;
156*e9194a95SThomas Weißschuh
157*e9194a95SThomas Weißschuh /* initlize capability by RDPT */
158*e9194a95SThomas Weißschuh pi->capability &= ioread8(base);
159*e9194a95SThomas Weißschuh pi->events = pi->capability;
160*e9194a95SThomas Weißschuh
161394febc9SChristophe JAILLET spin_lock(&pvpanic_lock);
162394febc9SChristophe JAILLET list_add(&pi->list, &pvpanic_list);
163394febc9SChristophe JAILLET spin_unlock(&pvpanic_lock);
164394febc9SChristophe JAILLET
165a99009bcSMihai Carabas dev_set_drvdata(dev, pi);
166a99009bcSMihai Carabas
167394febc9SChristophe JAILLET return devm_add_action_or_reset(dev, pvpanic_remove, pi);
168394febc9SChristophe JAILLET }
169394febc9SChristophe JAILLET EXPORT_SYMBOL_GPL(devm_pvpanic_probe);
1706861d27cSMihai Carabas
pvpanic_init(void)171b3c0f877SMihai Carabas static int pvpanic_init(void)
1726861d27cSMihai Carabas {
173b3c0f877SMihai Carabas INIT_LIST_HEAD(&pvpanic_list);
174b3c0f877SMihai Carabas spin_lock_init(&pvpanic_lock);
175b3c0f877SMihai Carabas
17684b0f12aSAndy Shevchenko atomic_notifier_chain_register(&panic_notifier_list, &pvpanic_panic_nb);
177b3c0f877SMihai Carabas
178b3c0f877SMihai Carabas return 0;
1796861d27cSMihai Carabas }
18033a43041SAndy Shevchenko module_init(pvpanic_init);
181b3c0f877SMihai Carabas
pvpanic_exit(void)182b3c0f877SMihai Carabas static void pvpanic_exit(void)
183b3c0f877SMihai Carabas {
18484b0f12aSAndy Shevchenko atomic_notifier_chain_unregister(&panic_notifier_list, &pvpanic_panic_nb);
185b3c0f877SMihai Carabas
186b3c0f877SMihai Carabas }
187b3c0f877SMihai Carabas module_exit(pvpanic_exit);
188