1 | // SPDX-License-Identifier: GPL-2.0+ |
2 | /* |
3 | * Pvpanic Device Support |
4 | * |
5 | * Copyright (C) 2013 Fujitsu. |
6 | * Copyright (C) 2018 ZTE. |
7 | * Copyright (C) 2021 Oracle. |
8 | */ |
9 | |
10 | #include <linux/device.h> |
11 | #include <linux/errno.h> |
12 | #include <linux/gfp_types.h> |
13 | #include <linux/io.h> |
14 | #include <linux/kexec.h> |
15 | #include <linux/kstrtox.h> |
16 | #include <linux/limits.h> |
17 | #include <linux/list.h> |
18 | #include <linux/mod_devicetable.h> |
19 | #include <linux/module.h> |
20 | #include <linux/panic_notifier.h> |
21 | #include <linux/platform_device.h> |
22 | #include <linux/spinlock.h> |
23 | #include <linux/sysfs.h> |
24 | #include <linux/types.h> |
25 | |
26 | #include <uapi/misc/pvpanic.h> |
27 | |
28 | #include "pvpanic.h" |
29 | |
30 | MODULE_AUTHOR("Mihai Carabas <mihai.carabas@oracle.com>" ); |
31 | MODULE_DESCRIPTION("pvpanic device driver" ); |
32 | MODULE_LICENSE("GPL" ); |
33 | |
34 | struct pvpanic_instance { |
35 | void __iomem *base; |
36 | unsigned int capability; |
37 | unsigned int events; |
38 | struct list_head list; |
39 | }; |
40 | |
41 | static struct list_head pvpanic_list; |
42 | static spinlock_t pvpanic_lock; |
43 | |
44 | static void |
45 | pvpanic_send_event(unsigned int event) |
46 | { |
47 | struct pvpanic_instance *pi_cur; |
48 | |
49 | if (!spin_trylock(lock: &pvpanic_lock)) |
50 | return; |
51 | |
52 | list_for_each_entry(pi_cur, &pvpanic_list, list) { |
53 | if (event & pi_cur->capability & pi_cur->events) |
54 | iowrite8(event, pi_cur->base); |
55 | } |
56 | spin_unlock(lock: &pvpanic_lock); |
57 | } |
58 | |
59 | static int |
60 | pvpanic_panic_notify(struct notifier_block *nb, unsigned long code, void *unused) |
61 | { |
62 | unsigned int event = PVPANIC_PANICKED; |
63 | |
64 | if (kexec_crash_loaded()) |
65 | event = PVPANIC_CRASH_LOADED; |
66 | |
67 | pvpanic_send_event(event); |
68 | |
69 | return NOTIFY_DONE; |
70 | } |
71 | |
72 | /* |
73 | * Call our notifier very early on panic, deferring the |
74 | * action taken to the hypervisor. |
75 | */ |
76 | static struct notifier_block pvpanic_panic_nb = { |
77 | .notifier_call = pvpanic_panic_notify, |
78 | .priority = INT_MAX, |
79 | }; |
80 | |
81 | static void pvpanic_remove(void *param) |
82 | { |
83 | struct pvpanic_instance *pi_cur, *pi_next; |
84 | struct pvpanic_instance *pi = param; |
85 | |
86 | spin_lock(lock: &pvpanic_lock); |
87 | list_for_each_entry_safe(pi_cur, pi_next, &pvpanic_list, list) { |
88 | if (pi_cur == pi) { |
89 | list_del(entry: &pi_cur->list); |
90 | break; |
91 | } |
92 | } |
93 | spin_unlock(lock: &pvpanic_lock); |
94 | } |
95 | |
96 | static ssize_t capability_show(struct device *dev, struct device_attribute *attr, char *buf) |
97 | { |
98 | struct pvpanic_instance *pi = dev_get_drvdata(dev); |
99 | |
100 | return sysfs_emit(buf, fmt: "%x\n" , pi->capability); |
101 | } |
102 | static DEVICE_ATTR_RO(capability); |
103 | |
104 | static ssize_t events_show(struct device *dev, struct device_attribute *attr, char *buf) |
105 | { |
106 | struct pvpanic_instance *pi = dev_get_drvdata(dev); |
107 | |
108 | return sysfs_emit(buf, fmt: "%x\n" , pi->events); |
109 | } |
110 | |
111 | static ssize_t events_store(struct device *dev, struct device_attribute *attr, |
112 | const char *buf, size_t count) |
113 | { |
114 | struct pvpanic_instance *pi = dev_get_drvdata(dev); |
115 | unsigned int tmp; |
116 | int err; |
117 | |
118 | err = kstrtouint(s: buf, base: 16, res: &tmp); |
119 | if (err) |
120 | return err; |
121 | |
122 | if ((tmp & pi->capability) != tmp) |
123 | return -EINVAL; |
124 | |
125 | pi->events = tmp; |
126 | |
127 | return count; |
128 | } |
129 | static DEVICE_ATTR_RW(events); |
130 | |
131 | static struct attribute *pvpanic_dev_attrs[] = { |
132 | &dev_attr_capability.attr, |
133 | &dev_attr_events.attr, |
134 | NULL |
135 | }; |
136 | |
137 | static const struct attribute_group pvpanic_dev_group = { |
138 | .attrs = pvpanic_dev_attrs, |
139 | }; |
140 | |
141 | const struct attribute_group *pvpanic_dev_groups[] = { |
142 | &pvpanic_dev_group, |
143 | NULL |
144 | }; |
145 | EXPORT_SYMBOL_GPL(pvpanic_dev_groups); |
146 | |
147 | int devm_pvpanic_probe(struct device *dev, void __iomem *base) |
148 | { |
149 | struct pvpanic_instance *pi; |
150 | |
151 | if (!base) |
152 | return -EINVAL; |
153 | |
154 | pi = devm_kmalloc(dev, size: sizeof(*pi), GFP_KERNEL); |
155 | if (!pi) |
156 | return -ENOMEM; |
157 | |
158 | pi->base = base; |
159 | pi->capability = PVPANIC_PANICKED | PVPANIC_CRASH_LOADED; |
160 | |
161 | /* initlize capability by RDPT */ |
162 | pi->capability &= ioread8(base); |
163 | pi->events = pi->capability; |
164 | |
165 | spin_lock(lock: &pvpanic_lock); |
166 | list_add(new: &pi->list, head: &pvpanic_list); |
167 | spin_unlock(lock: &pvpanic_lock); |
168 | |
169 | dev_set_drvdata(dev, data: pi); |
170 | |
171 | return devm_add_action_or_reset(dev, pvpanic_remove, pi); |
172 | } |
173 | EXPORT_SYMBOL_GPL(devm_pvpanic_probe); |
174 | |
175 | static int pvpanic_init(void) |
176 | { |
177 | INIT_LIST_HEAD(list: &pvpanic_list); |
178 | spin_lock_init(&pvpanic_lock); |
179 | |
180 | atomic_notifier_chain_register(nh: &panic_notifier_list, nb: &pvpanic_panic_nb); |
181 | |
182 | return 0; |
183 | } |
184 | module_init(pvpanic_init); |
185 | |
186 | static void pvpanic_exit(void) |
187 | { |
188 | atomic_notifier_chain_unregister(nh: &panic_notifier_list, nb: &pvpanic_panic_nb); |
189 | |
190 | } |
191 | module_exit(pvpanic_exit); |
192 | |