diff options
-rw-r--r-- | drivers/char/ipmi/Kconfig | 6 | ||||
-rw-r--r-- | drivers/char/ipmi/Makefile | 1 | ||||
-rw-r--r-- | drivers/char/ipmi/ipmi_powernv.c | 310 |
3 files changed, 317 insertions, 0 deletions
diff --git a/drivers/char/ipmi/Kconfig b/drivers/char/ipmi/Kconfig index 809d28328c6f..6ed9e9fe5233 100644 --- a/drivers/char/ipmi/Kconfig +++ b/drivers/char/ipmi/Kconfig | |||
@@ -70,6 +70,12 @@ config IPMI_SSIF | |||
70 | have a driver that must be accessed over an I2C bus instead of a | 70 | have a driver that must be accessed over an I2C bus instead of a |
71 | standard interface. This module requires I2C support. | 71 | standard interface. This module requires I2C support. |
72 | 72 | ||
73 | config IPMI_POWERNV | ||
74 | depends on PPC_POWERNV | ||
75 | tristate 'POWERNV (OPAL firmware) IPMI interface' | ||
76 | help | ||
77 | Provides a driver for OPAL firmware-based IPMI interfaces. | ||
78 | |||
73 | config IPMI_WATCHDOG | 79 | config IPMI_WATCHDOG |
74 | tristate 'IPMI Watchdog Timer' | 80 | tristate 'IPMI Watchdog Timer' |
75 | help | 81 | help |
diff --git a/drivers/char/ipmi/Makefile b/drivers/char/ipmi/Makefile index 115c08da7117..f3ffde1f5f1f 100644 --- a/drivers/char/ipmi/Makefile +++ b/drivers/char/ipmi/Makefile | |||
@@ -8,5 +8,6 @@ obj-$(CONFIG_IPMI_HANDLER) += ipmi_msghandler.o | |||
8 | obj-$(CONFIG_IPMI_DEVICE_INTERFACE) += ipmi_devintf.o | 8 | obj-$(CONFIG_IPMI_DEVICE_INTERFACE) += ipmi_devintf.o |
9 | obj-$(CONFIG_IPMI_SI) += ipmi_si.o | 9 | obj-$(CONFIG_IPMI_SI) += ipmi_si.o |
10 | obj-$(CONFIG_IPMI_SSIF) += ipmi_ssif.o | 10 | obj-$(CONFIG_IPMI_SSIF) += ipmi_ssif.o |
11 | obj-$(CONFIG_IPMI_POWERNV) += ipmi_powernv.o | ||
11 | obj-$(CONFIG_IPMI_WATCHDOG) += ipmi_watchdog.o | 12 | obj-$(CONFIG_IPMI_WATCHDOG) += ipmi_watchdog.o |
12 | obj-$(CONFIG_IPMI_POWEROFF) += ipmi_poweroff.o | 13 | obj-$(CONFIG_IPMI_POWEROFF) += ipmi_poweroff.o |
diff --git a/drivers/char/ipmi/ipmi_powernv.c b/drivers/char/ipmi/ipmi_powernv.c new file mode 100644 index 000000000000..79524ed2a3cb --- /dev/null +++ b/drivers/char/ipmi/ipmi_powernv.c | |||
@@ -0,0 +1,310 @@ | |||
1 | /* | ||
2 | * PowerNV OPAL IPMI driver | ||
3 | * | ||
4 | * Copyright 2014 IBM Corp. | ||
5 | * | ||
6 | * This program is free software; you can redistribute it and/or modify it | ||
7 | * under the terms of the GNU General Public License as published by the Free | ||
8 | * Software Foundation; either version 2 of the License, or (at your option) | ||
9 | * any later version. | ||
10 | */ | ||
11 | |||
12 | #define pr_fmt(fmt) "ipmi-powernv: " fmt | ||
13 | |||
14 | #include <linux/ipmi_smi.h> | ||
15 | #include <linux/list.h> | ||
16 | #include <linux/module.h> | ||
17 | #include <linux/of.h> | ||
18 | |||
19 | #include <asm/opal.h> | ||
20 | |||
21 | |||
22 | struct ipmi_smi_powernv { | ||
23 | u64 interface_id; | ||
24 | struct ipmi_device_id ipmi_id; | ||
25 | ipmi_smi_t intf; | ||
26 | u64 event; | ||
27 | struct notifier_block event_nb; | ||
28 | |||
29 | /** | ||
30 | * We assume that there can only be one outstanding request, so | ||
31 | * keep the pending message in cur_msg. We protect this from concurrent | ||
32 | * updates through send & recv calls, (and consequently opal_msg, which | ||
33 | * is in-use when cur_msg is set) with msg_lock | ||
34 | */ | ||
35 | spinlock_t msg_lock; | ||
36 | struct ipmi_smi_msg *cur_msg; | ||
37 | struct opal_ipmi_msg *opal_msg; | ||
38 | }; | ||
39 | |||
40 | static int ipmi_powernv_start_processing(void *send_info, ipmi_smi_t intf) | ||
41 | { | ||
42 | struct ipmi_smi_powernv *smi = send_info; | ||
43 | |||
44 | smi->intf = intf; | ||
45 | return 0; | ||
46 | } | ||
47 | |||
48 | static void send_error_reply(struct ipmi_smi_powernv *smi, | ||
49 | struct ipmi_smi_msg *msg, u8 completion_code) | ||
50 | { | ||
51 | msg->rsp[0] = msg->data[0] | 0x4; | ||
52 | msg->rsp[1] = msg->data[1]; | ||
53 | msg->rsp[2] = completion_code; | ||
54 | msg->rsp_size = 3; | ||
55 | ipmi_smi_msg_received(smi->intf, msg); | ||
56 | } | ||
57 | |||
58 | static void ipmi_powernv_send(void *send_info, struct ipmi_smi_msg *msg) | ||
59 | { | ||
60 | struct ipmi_smi_powernv *smi = send_info; | ||
61 | struct opal_ipmi_msg *opal_msg; | ||
62 | unsigned long flags; | ||
63 | int comp, rc; | ||
64 | size_t size; | ||
65 | |||
66 | /* ensure data_len will fit in the opal_ipmi_msg buffer... */ | ||
67 | if (msg->data_size > IPMI_MAX_MSG_LENGTH) { | ||
68 | comp = IPMI_REQ_LEN_EXCEEDED_ERR; | ||
69 | goto err; | ||
70 | } | ||
71 | |||
72 | /* ... and that we at least have netfn and cmd bytes */ | ||
73 | if (msg->data_size < 2) { | ||
74 | comp = IPMI_REQ_LEN_INVALID_ERR; | ||
75 | goto err; | ||
76 | } | ||
77 | |||
78 | spin_lock_irqsave(&smi->msg_lock, flags); | ||
79 | |||
80 | if (smi->cur_msg) { | ||
81 | comp = IPMI_NODE_BUSY_ERR; | ||
82 | goto err_unlock; | ||
83 | } | ||
84 | |||
85 | /* format our data for the OPAL API */ | ||
86 | opal_msg = smi->opal_msg; | ||
87 | opal_msg->version = OPAL_IPMI_MSG_FORMAT_VERSION_1; | ||
88 | opal_msg->netfn = msg->data[0]; | ||
89 | opal_msg->cmd = msg->data[1]; | ||
90 | if (msg->data_size > 2) | ||
91 | memcpy(opal_msg->data, msg->data + 2, msg->data_size - 2); | ||
92 | |||
93 | /* data_size already includes the netfn and cmd bytes */ | ||
94 | size = sizeof(*opal_msg) + msg->data_size - 2; | ||
95 | |||
96 | pr_devel("%s: opal_ipmi_send(0x%llx, %p, %ld)\n", __func__, | ||
97 | smi->interface_id, opal_msg, size); | ||
98 | rc = opal_ipmi_send(smi->interface_id, opal_msg, size); | ||
99 | pr_devel("%s: -> %d\n", __func__, rc); | ||
100 | |||
101 | if (!rc) { | ||
102 | smi->cur_msg = msg; | ||
103 | spin_unlock_irqrestore(&smi->msg_lock, flags); | ||
104 | return; | ||
105 | } | ||
106 | |||
107 | comp = IPMI_ERR_UNSPECIFIED; | ||
108 | err_unlock: | ||
109 | spin_unlock_irqrestore(&smi->msg_lock, flags); | ||
110 | err: | ||
111 | send_error_reply(smi, msg, comp); | ||
112 | } | ||
113 | |||
114 | static int ipmi_powernv_recv(struct ipmi_smi_powernv *smi) | ||
115 | { | ||
116 | struct opal_ipmi_msg *opal_msg; | ||
117 | struct ipmi_smi_msg *msg; | ||
118 | unsigned long flags; | ||
119 | uint64_t size; | ||
120 | int rc; | ||
121 | |||
122 | pr_devel("%s: opal_ipmi_recv(%llx, msg, sz)\n", __func__, | ||
123 | smi->interface_id); | ||
124 | |||
125 | spin_lock_irqsave(&smi->msg_lock, flags); | ||
126 | |||
127 | if (!smi->cur_msg) { | ||
128 | pr_warn("no current message?\n"); | ||
129 | return 0; | ||
130 | } | ||
131 | |||
132 | msg = smi->cur_msg; | ||
133 | opal_msg = smi->opal_msg; | ||
134 | |||
135 | size = cpu_to_be64(sizeof(*opal_msg) + IPMI_MAX_MSG_LENGTH); | ||
136 | |||
137 | rc = opal_ipmi_recv(smi->interface_id, | ||
138 | opal_msg, | ||
139 | &size); | ||
140 | size = be64_to_cpu(size); | ||
141 | pr_devel("%s: -> %d (size %lld)\n", __func__, | ||
142 | rc, rc == 0 ? size : 0); | ||
143 | if (rc) { | ||
144 | spin_unlock_irqrestore(&smi->msg_lock, flags); | ||
145 | ipmi_free_smi_msg(msg); | ||
146 | return 0; | ||
147 | } | ||
148 | |||
149 | if (size < sizeof(*opal_msg)) { | ||
150 | spin_unlock_irqrestore(&smi->msg_lock, flags); | ||
151 | pr_warn("unexpected IPMI message size %lld\n", size); | ||
152 | return 0; | ||
153 | } | ||
154 | |||
155 | if (opal_msg->version != OPAL_IPMI_MSG_FORMAT_VERSION_1) { | ||
156 | spin_unlock_irqrestore(&smi->msg_lock, flags); | ||
157 | pr_warn("unexpected IPMI message format (version %d)\n", | ||
158 | opal_msg->version); | ||
159 | return 0; | ||
160 | } | ||
161 | |||
162 | msg->rsp[0] = opal_msg->netfn; | ||
163 | msg->rsp[1] = opal_msg->cmd; | ||
164 | if (size > sizeof(*opal_msg)) | ||
165 | memcpy(&msg->rsp[2], opal_msg->data, size - sizeof(*opal_msg)); | ||
166 | msg->rsp_size = 2 + size - sizeof(*opal_msg); | ||
167 | |||
168 | smi->cur_msg = NULL; | ||
169 | spin_unlock_irqrestore(&smi->msg_lock, flags); | ||
170 | ipmi_smi_msg_received(smi->intf, msg); | ||
171 | return 0; | ||
172 | } | ||
173 | |||
174 | static void ipmi_powernv_request_events(void *send_info) | ||
175 | { | ||
176 | } | ||
177 | |||
178 | static void ipmi_powernv_set_run_to_completion(void *send_info, | ||
179 | bool run_to_completion) | ||
180 | { | ||
181 | } | ||
182 | |||
183 | static void ipmi_powernv_poll(void *send_info) | ||
184 | { | ||
185 | struct ipmi_smi_powernv *smi = send_info; | ||
186 | |||
187 | ipmi_powernv_recv(smi); | ||
188 | } | ||
189 | |||
190 | static struct ipmi_smi_handlers ipmi_powernv_smi_handlers = { | ||
191 | .owner = THIS_MODULE, | ||
192 | .start_processing = ipmi_powernv_start_processing, | ||
193 | .sender = ipmi_powernv_send, | ||
194 | .request_events = ipmi_powernv_request_events, | ||
195 | .set_run_to_completion = ipmi_powernv_set_run_to_completion, | ||
196 | .poll = ipmi_powernv_poll, | ||
197 | }; | ||
198 | |||
199 | static int ipmi_opal_event(struct notifier_block *nb, | ||
200 | unsigned long events, void *change) | ||
201 | { | ||
202 | struct ipmi_smi_powernv *smi = container_of(nb, | ||
203 | struct ipmi_smi_powernv, event_nb); | ||
204 | |||
205 | if (events & smi->event) | ||
206 | ipmi_powernv_recv(smi); | ||
207 | return 0; | ||
208 | } | ||
209 | |||
210 | static int ipmi_powernv_probe(struct platform_device *pdev) | ||
211 | { | ||
212 | struct ipmi_smi_powernv *ipmi; | ||
213 | struct device *dev; | ||
214 | u32 prop; | ||
215 | int rc; | ||
216 | |||
217 | if (!pdev || !pdev->dev.of_node) | ||
218 | return -ENODEV; | ||
219 | |||
220 | dev = &pdev->dev; | ||
221 | |||
222 | ipmi = devm_kzalloc(dev, sizeof(*ipmi), GFP_KERNEL); | ||
223 | if (!ipmi) | ||
224 | return -ENOMEM; | ||
225 | |||
226 | spin_lock_init(&ipmi->msg_lock); | ||
227 | |||
228 | rc = of_property_read_u32(dev->of_node, "ibm,ipmi-interface-id", | ||
229 | &prop); | ||
230 | if (rc) { | ||
231 | dev_warn(dev, "No interface ID property\n"); | ||
232 | goto err_free; | ||
233 | } | ||
234 | ipmi->interface_id = prop; | ||
235 | |||
236 | rc = of_property_read_u32(dev->of_node, "interrupts", &prop); | ||
237 | if (rc) { | ||
238 | dev_warn(dev, "No interrupts property\n"); | ||
239 | goto err_free; | ||
240 | } | ||
241 | |||
242 | ipmi->event = 1ull << prop; | ||
243 | ipmi->event_nb.notifier_call = ipmi_opal_event; | ||
244 | |||
245 | rc = opal_notifier_register(&ipmi->event_nb); | ||
246 | if (rc) { | ||
247 | dev_warn(dev, "OPAL notifier registration failed (%d)\n", rc); | ||
248 | goto err_free; | ||
249 | } | ||
250 | |||
251 | ipmi->opal_msg = devm_kmalloc(dev, | ||
252 | sizeof(*ipmi->opal_msg) + IPMI_MAX_MSG_LENGTH, | ||
253 | GFP_KERNEL); | ||
254 | if (!ipmi->opal_msg) { | ||
255 | rc = -ENOMEM; | ||
256 | goto err_unregister; | ||
257 | } | ||
258 | |||
259 | /* todo: query actual ipmi_device_id */ | ||
260 | rc = ipmi_register_smi(&ipmi_powernv_smi_handlers, ipmi, | ||
261 | &ipmi->ipmi_id, dev, 0); | ||
262 | if (rc) { | ||
263 | dev_warn(dev, "IPMI SMI registration failed (%d)\n", rc); | ||
264 | goto err_free_msg; | ||
265 | } | ||
266 | |||
267 | dev_set_drvdata(dev, ipmi); | ||
268 | return 0; | ||
269 | |||
270 | err_free_msg: | ||
271 | devm_kfree(dev, ipmi->opal_msg); | ||
272 | err_unregister: | ||
273 | opal_notifier_unregister(&ipmi->event_nb); | ||
274 | err_free: | ||
275 | devm_kfree(dev, ipmi); | ||
276 | return rc; | ||
277 | } | ||
278 | |||
279 | static int ipmi_powernv_remove(struct platform_device *pdev) | ||
280 | { | ||
281 | struct ipmi_smi_powernv *smi = dev_get_drvdata(&pdev->dev); | ||
282 | |||
283 | ipmi_unregister_smi(smi->intf); | ||
284 | opal_notifier_unregister(&smi->event_nb); | ||
285 | return 0; | ||
286 | } | ||
287 | |||
288 | static const struct of_device_id ipmi_powernv_match[] = { | ||
289 | { .compatible = "ibm,opal-ipmi" }, | ||
290 | { }, | ||
291 | }; | ||
292 | |||
293 | |||
294 | static struct platform_driver powernv_ipmi_driver = { | ||
295 | .driver = { | ||
296 | .name = "ipmi-powernv", | ||
297 | .owner = THIS_MODULE, | ||
298 | .of_match_table = ipmi_powernv_match, | ||
299 | }, | ||
300 | .probe = ipmi_powernv_probe, | ||
301 | .remove = ipmi_powernv_remove, | ||
302 | }; | ||
303 | |||
304 | |||
305 | module_platform_driver(powernv_ipmi_driver); | ||
306 | |||
307 | MODULE_DEVICE_TABLE(of, ipmi_powernv_match); | ||
308 | MODULE_DESCRIPTION("powernv IPMI driver"); | ||
309 | MODULE_AUTHOR("Jeremy Kerr <jk@ozlabs.org>"); | ||
310 | MODULE_LICENSE("GPL"); | ||