Commit | Line | Data |
---|---|---|
1da177e4 LT |
1 | /* |
2 | * ipmi_poweroff.c | |
3 | * | |
4 | * MontaVista IPMI Poweroff extension to sys_reboot | |
5 | * | |
6 | * Author: MontaVista Software, Inc. | |
7 | * Steven Dake <sdake@mvista.com> | |
8 | * Corey Minyard <cminyard@mvista.com> | |
9 | * source@mvista.com | |
10 | * | |
11 | * Copyright 2002,2004 MontaVista Software Inc. | |
12 | * | |
13 | * This program is free software; you can redistribute it and/or modify it | |
14 | * under the terms of the GNU General Public License as published by the | |
15 | * Free Software Foundation; either version 2 of the License, or (at your | |
16 | * option) any later version. | |
17 | * | |
18 | * | |
19 | * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED | |
20 | * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF | |
21 | * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. | |
22 | * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, | |
23 | * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, | |
24 | * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS | |
25 | * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND | |
26 | * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR | |
27 | * TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE | |
28 | * USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | |
29 | * | |
30 | * You should have received a copy of the GNU General Public License along | |
31 | * with this program; if not, write to the Free Software Foundation, Inc., | |
32 | * 675 Mass Ave, Cambridge, MA 02139, USA. | |
33 | */ | |
1da177e4 | 34 | #include <linux/module.h> |
3b625943 CM |
35 | #include <linux/moduleparam.h> |
36 | #include <linux/proc_fs.h> | |
1da177e4 | 37 | #include <linux/string.h> |
77cf3973 | 38 | #include <linux/completion.h> |
e933b6d6 | 39 | #include <linux/pm.h> |
77cf3973 | 40 | #include <linux/kdev_t.h> |
1da177e4 LT |
41 | #include <linux/ipmi.h> |
42 | #include <linux/ipmi_smi.h> | |
43 | ||
44 | #define PFX "IPMI poweroff: " | |
1da177e4 | 45 | |
b2c03941 CM |
46 | static void ipmi_po_smi_gone(int if_num); |
47 | static void ipmi_po_new_smi(int if_num, struct device *device); | |
48 | ||
3b625943 CM |
49 | /* Definitions for controlling power off (if the system supports it). It |
50 | * conveniently matches the IPMI chassis control values. */ | |
51 | #define IPMI_CHASSIS_POWER_DOWN 0 /* power down, the default. */ | |
52 | #define IPMI_CHASSIS_POWER_CYCLE 0x02 /* power cycle */ | |
53 | ||
54 | /* the IPMI data command */ | |
8c702e16 | 55 | static int poweroff_powercycle; |
3b625943 | 56 | |
b2c03941 CM |
57 | /* Which interface to use, -1 means the first we see. */ |
58 | static int ifnum_to_use = -1; | |
59 | ||
60 | /* Our local state. */ | |
0c8204b3 | 61 | static int ready; |
b2c03941 CM |
62 | static ipmi_user_t ipmi_user; |
63 | static int ipmi_ifnum; | |
0c8204b3 | 64 | static void (*specific_poweroff_func)(ipmi_user_t user); |
b2c03941 CM |
65 | |
66 | /* Holds the old poweroff function so we can restore it on removal. */ | |
67 | static void (*old_poweroff_func)(void); | |
68 | ||
69 | static int set_param_ifnum(const char *val, struct kernel_param *kp) | |
70 | { | |
71 | int rv = param_set_int(val, kp); | |
72 | if (rv) | |
73 | return rv; | |
74 | if ((ifnum_to_use < 0) || (ifnum_to_use == ipmi_ifnum)) | |
75 | return 0; | |
76 | ||
77 | ipmi_po_smi_gone(ipmi_ifnum); | |
78 | ipmi_po_new_smi(ifnum_to_use, NULL); | |
79 | return 0; | |
80 | } | |
81 | ||
82 | module_param_call(ifnum_to_use, set_param_ifnum, param_get_int, | |
83 | &ifnum_to_use, 0644); | |
84 | MODULE_PARM_DESC(ifnum_to_use, "The interface number to use for the watchdog " | |
85 | "timer. Setting to -1 defaults to the first registered " | |
86 | "interface"); | |
87 | ||
3b625943 | 88 | /* parameter definition to allow user to flag power cycle */ |
a9d014af | 89 | module_param(poweroff_powercycle, int, 0644); |
36c7dc44 CM |
90 | MODULE_PARM_DESC(poweroff_powercycle, |
91 | " Set to non-zero to enable power cycle instead of power" | |
92 | " down. Power cycle is contingent on hardware support," | |
93 | " otherwise it defaults back to power down."); | |
3b625943 | 94 | |
1da177e4 LT |
95 | /* Stuff from the get device id command. */ |
96 | static unsigned int mfg_id; | |
97 | static unsigned int prod_id; | |
98 | static unsigned char capabilities; | |
168524d6 | 99 | static unsigned char ipmi_version; |
1da177e4 | 100 | |
36c7dc44 CM |
101 | /* |
102 | * We use our own messages for this operation, we don't let the system | |
103 | * allocate them, since we may be in a panic situation. The whole | |
104 | * thing is single-threaded, anyway, so multiple messages are not | |
105 | * required. | |
106 | */ | |
bda4c30a | 107 | static atomic_t dummy_count = ATOMIC_INIT(0); |
1da177e4 LT |
108 | static void dummy_smi_free(struct ipmi_smi_msg *msg) |
109 | { | |
bda4c30a | 110 | atomic_dec(&dummy_count); |
1da177e4 LT |
111 | } |
112 | static void dummy_recv_free(struct ipmi_recv_msg *msg) | |
113 | { | |
bda4c30a | 114 | atomic_dec(&dummy_count); |
1da177e4 | 115 | } |
36c7dc44 | 116 | static struct ipmi_smi_msg halt_smi_msg = { |
1da177e4 LT |
117 | .done = dummy_smi_free |
118 | }; | |
36c7dc44 | 119 | static struct ipmi_recv_msg halt_recv_msg = { |
1da177e4 LT |
120 | .done = dummy_recv_free |
121 | }; | |
122 | ||
123 | ||
124 | /* | |
25985edc | 125 | * Code to send a message and wait for the response. |
1da177e4 LT |
126 | */ |
127 | ||
128 | static void receive_handler(struct ipmi_recv_msg *recv_msg, void *handler_data) | |
129 | { | |
77cf3973 | 130 | struct completion *comp = recv_msg->user_msg_data; |
1da177e4 | 131 | |
77cf3973 CM |
132 | if (comp) |
133 | complete(comp); | |
1da177e4 LT |
134 | } |
135 | ||
36c7dc44 | 136 | static struct ipmi_user_hndl ipmi_poweroff_handler = { |
1da177e4 LT |
137 | .ipmi_recv_hndl = receive_handler |
138 | }; | |
139 | ||
140 | ||
141 | static int ipmi_request_wait_for_response(ipmi_user_t user, | |
142 | struct ipmi_addr *addr, | |
143 | struct kernel_ipmi_msg *send_msg) | |
144 | { | |
77cf3973 CM |
145 | int rv; |
146 | struct completion comp; | |
1da177e4 | 147 | |
77cf3973 | 148 | init_completion(&comp); |
1da177e4 | 149 | |
77cf3973 | 150 | rv = ipmi_request_supply_msgs(user, addr, 0, send_msg, &comp, |
1da177e4 LT |
151 | &halt_smi_msg, &halt_recv_msg, 0); |
152 | if (rv) | |
153 | return rv; | |
154 | ||
77cf3973 | 155 | wait_for_completion(&comp); |
1da177e4 LT |
156 | |
157 | return halt_recv_msg.msg.data[0]; | |
158 | } | |
159 | ||
bda4c30a | 160 | /* Wait for message to complete, spinning. */ |
1da177e4 LT |
161 | static int ipmi_request_in_rc_mode(ipmi_user_t user, |
162 | struct ipmi_addr *addr, | |
163 | struct kernel_ipmi_msg *send_msg) | |
164 | { | |
77cf3973 | 165 | int rv; |
1da177e4 | 166 | |
bda4c30a | 167 | atomic_set(&dummy_count, 2); |
1da177e4 LT |
168 | rv = ipmi_request_supply_msgs(user, addr, 0, send_msg, NULL, |
169 | &halt_smi_msg, &halt_recv_msg, 0); | |
bda4c30a CM |
170 | if (rv) { |
171 | atomic_set(&dummy_count, 0); | |
1da177e4 | 172 | return rv; |
bda4c30a CM |
173 | } |
174 | ||
175 | /* | |
176 | * Spin until our message is done. | |
177 | */ | |
178 | while (atomic_read(&dummy_count) > 0) { | |
179 | ipmi_poll_interface(user); | |
180 | cpu_relax(); | |
181 | } | |
1da177e4 LT |
182 | |
183 | return halt_recv_msg.msg.data[0]; | |
184 | } | |
185 | ||
186 | /* | |
187 | * ATCA Support | |
188 | */ | |
189 | ||
190 | #define IPMI_NETFN_ATCA 0x2c | |
191 | #define IPMI_ATCA_SET_POWER_CMD 0x11 | |
192 | #define IPMI_ATCA_GET_ADDR_INFO_CMD 0x01 | |
193 | #define IPMI_PICMG_ID 0 | |
194 | ||
7d8ba966 CM |
195 | #define IPMI_NETFN_OEM 0x2e |
196 | #define IPMI_ATCA_PPS_GRACEFUL_RESTART 0x11 | |
197 | #define IPMI_ATCA_PPS_IANA "\x00\x40\x0A" | |
198 | #define IPMI_MOTOROLA_MANUFACTURER_ID 0x0000A1 | |
199 | #define IPMI_MOTOROLA_PPS_IPMC_PRODUCT_ID 0x0051 | |
200 | ||
0c8204b3 | 201 | static void (*atca_oem_poweroff_hook)(ipmi_user_t user); |
7d8ba966 | 202 | |
36c7dc44 | 203 | static void pps_poweroff_atca(ipmi_user_t user) |
7d8ba966 | 204 | { |
36c7dc44 CM |
205 | struct ipmi_system_interface_addr smi_addr; |
206 | struct kernel_ipmi_msg send_msg; | |
207 | int rv; | |
208 | /* | |
209 | * Configure IPMI address for local access | |
210 | */ | |
211 | smi_addr.addr_type = IPMI_SYSTEM_INTERFACE_ADDR_TYPE; | |
212 | smi_addr.channel = IPMI_BMC_CHANNEL; | |
213 | smi_addr.lun = 0; | |
214 | ||
215 | printk(KERN_INFO PFX "PPS powerdown hook used"); | |
216 | ||
217 | send_msg.netfn = IPMI_NETFN_OEM; | |
218 | send_msg.cmd = IPMI_ATCA_PPS_GRACEFUL_RESTART; | |
219 | send_msg.data = IPMI_ATCA_PPS_IANA; | |
220 | send_msg.data_len = 3; | |
221 | rv = ipmi_request_in_rc_mode(user, | |
222 | (struct ipmi_addr *) &smi_addr, | |
223 | &send_msg); | |
224 | if (rv && rv != IPMI_UNKNOWN_ERR_COMPLETION_CODE) { | |
225 | printk(KERN_ERR PFX "Unable to send ATCA ," | |
226 | " IPMI error 0x%x\n", rv); | |
227 | } | |
7d8ba966 CM |
228 | return; |
229 | } | |
230 | ||
36c7dc44 | 231 | static int ipmi_atca_detect(ipmi_user_t user) |
1da177e4 LT |
232 | { |
233 | struct ipmi_system_interface_addr smi_addr; | |
234 | struct kernel_ipmi_msg send_msg; | |
235 | int rv; | |
236 | unsigned char data[1]; | |
237 | ||
36c7dc44 CM |
238 | /* |
239 | * Configure IPMI address for local access | |
240 | */ | |
241 | smi_addr.addr_type = IPMI_SYSTEM_INTERFACE_ADDR_TYPE; | |
242 | smi_addr.channel = IPMI_BMC_CHANNEL; | |
243 | smi_addr.lun = 0; | |
1da177e4 LT |
244 | |
245 | /* | |
246 | * Use get address info to check and see if we are ATCA | |
247 | */ | |
248 | send_msg.netfn = IPMI_NETFN_ATCA; | |
249 | send_msg.cmd = IPMI_ATCA_GET_ADDR_INFO_CMD; | |
250 | data[0] = IPMI_PICMG_ID; | |
251 | send_msg.data = data; | |
252 | send_msg.data_len = sizeof(data); | |
253 | rv = ipmi_request_wait_for_response(user, | |
254 | (struct ipmi_addr *) &smi_addr, | |
255 | &send_msg); | |
7d8ba966 | 256 | |
36c7dc44 CM |
257 | printk(KERN_INFO PFX "ATCA Detect mfg 0x%X prod 0x%X\n", |
258 | mfg_id, prod_id); | |
259 | if ((mfg_id == IPMI_MOTOROLA_MANUFACTURER_ID) | |
260 | && (prod_id == IPMI_MOTOROLA_PPS_IPMC_PRODUCT_ID)) { | |
261 | printk(KERN_INFO PFX | |
262 | "Installing Pigeon Point Systems Poweroff Hook\n"); | |
7d8ba966 CM |
263 | atca_oem_poweroff_hook = pps_poweroff_atca; |
264 | } | |
1da177e4 LT |
265 | return !rv; |
266 | } | |
267 | ||
36c7dc44 | 268 | static void ipmi_poweroff_atca(ipmi_user_t user) |
1da177e4 LT |
269 | { |
270 | struct ipmi_system_interface_addr smi_addr; | |
271 | struct kernel_ipmi_msg send_msg; | |
272 | int rv; | |
273 | unsigned char data[4]; | |
274 | ||
36c7dc44 CM |
275 | /* |
276 | * Configure IPMI address for local access | |
277 | */ | |
278 | smi_addr.addr_type = IPMI_SYSTEM_INTERFACE_ADDR_TYPE; | |
279 | smi_addr.channel = IPMI_BMC_CHANNEL; | |
280 | smi_addr.lun = 0; | |
1da177e4 LT |
281 | |
282 | printk(KERN_INFO PFX "Powering down via ATCA power command\n"); | |
283 | ||
284 | /* | |
285 | * Power down | |
286 | */ | |
287 | send_msg.netfn = IPMI_NETFN_ATCA; | |
288 | send_msg.cmd = IPMI_ATCA_SET_POWER_CMD; | |
289 | data[0] = IPMI_PICMG_ID; | |
290 | data[1] = 0; /* FRU id */ | |
291 | data[2] = 0; /* Power Level */ | |
292 | data[3] = 0; /* Don't change saved presets */ | |
293 | send_msg.data = data; | |
36c7dc44 | 294 | send_msg.data_len = sizeof(data); |
1da177e4 LT |
295 | rv = ipmi_request_in_rc_mode(user, |
296 | (struct ipmi_addr *) &smi_addr, | |
297 | &send_msg); | |
36c7dc44 CM |
298 | /* |
299 | * At this point, the system may be shutting down, and most | |
300 | * serial drivers (if used) will have interrupts turned off | |
301 | * it may be better to ignore IPMI_UNKNOWN_ERR_COMPLETION_CODE | |
302 | * return code | |
303 | */ | |
304 | if (rv && rv != IPMI_UNKNOWN_ERR_COMPLETION_CODE) { | |
1da177e4 LT |
305 | printk(KERN_ERR PFX "Unable to send ATCA powerdown message," |
306 | " IPMI error 0x%x\n", rv); | |
307 | goto out; | |
308 | } | |
309 | ||
36c7dc44 | 310 | if (atca_oem_poweroff_hook) |
adf535ee | 311 | atca_oem_poweroff_hook(user); |
1da177e4 LT |
312 | out: |
313 | return; | |
314 | } | |
315 | ||
316 | /* | |
317 | * CPI1 Support | |
318 | */ | |
319 | ||
320 | #define IPMI_NETFN_OEM_1 0xf8 | |
321 | #define OEM_GRP_CMD_SET_RESET_STATE 0x84 | |
322 | #define OEM_GRP_CMD_SET_POWER_STATE 0x82 | |
323 | #define IPMI_NETFN_OEM_8 0xf8 | |
324 | #define OEM_GRP_CMD_REQUEST_HOTSWAP_CTRL 0x80 | |
325 | #define OEM_GRP_CMD_GET_SLOT_GA 0xa3 | |
326 | #define IPMI_NETFN_SENSOR_EVT 0x10 | |
327 | #define IPMI_CMD_GET_EVENT_RECEIVER 0x01 | |
328 | ||
329 | #define IPMI_CPI1_PRODUCT_ID 0x000157 | |
330 | #define IPMI_CPI1_MANUFACTURER_ID 0x0108 | |
331 | ||
36c7dc44 | 332 | static int ipmi_cpi1_detect(ipmi_user_t user) |
1da177e4 LT |
333 | { |
334 | return ((mfg_id == IPMI_CPI1_MANUFACTURER_ID) | |
335 | && (prod_id == IPMI_CPI1_PRODUCT_ID)); | |
336 | } | |
337 | ||
36c7dc44 | 338 | static void ipmi_poweroff_cpi1(ipmi_user_t user) |
1da177e4 LT |
339 | { |
340 | struct ipmi_system_interface_addr smi_addr; | |
341 | struct ipmi_ipmb_addr ipmb_addr; | |
342 | struct kernel_ipmi_msg send_msg; | |
343 | int rv; | |
344 | unsigned char data[1]; | |
345 | int slot; | |
346 | unsigned char hotswap_ipmb; | |
347 | unsigned char aer_addr; | |
348 | unsigned char aer_lun; | |
349 | ||
36c7dc44 CM |
350 | /* |
351 | * Configure IPMI address for local access | |
352 | */ | |
353 | smi_addr.addr_type = IPMI_SYSTEM_INTERFACE_ADDR_TYPE; | |
354 | smi_addr.channel = IPMI_BMC_CHANNEL; | |
355 | smi_addr.lun = 0; | |
1da177e4 LT |
356 | |
357 | printk(KERN_INFO PFX "Powering down via CPI1 power command\n"); | |
358 | ||
359 | /* | |
360 | * Get IPMI ipmb address | |
361 | */ | |
362 | send_msg.netfn = IPMI_NETFN_OEM_8 >> 2; | |
363 | send_msg.cmd = OEM_GRP_CMD_GET_SLOT_GA; | |
364 | send_msg.data = NULL; | |
365 | send_msg.data_len = 0; | |
366 | rv = ipmi_request_in_rc_mode(user, | |
367 | (struct ipmi_addr *) &smi_addr, | |
368 | &send_msg); | |
369 | if (rv) | |
370 | goto out; | |
371 | slot = halt_recv_msg.msg.data[1]; | |
372 | hotswap_ipmb = (slot > 9) ? (0xb0 + 2 * slot) : (0xae + 2 * slot); | |
373 | ||
374 | /* | |
375 | * Get active event receiver | |
376 | */ | |
377 | send_msg.netfn = IPMI_NETFN_SENSOR_EVT >> 2; | |
378 | send_msg.cmd = IPMI_CMD_GET_EVENT_RECEIVER; | |
379 | send_msg.data = NULL; | |
380 | send_msg.data_len = 0; | |
381 | rv = ipmi_request_in_rc_mode(user, | |
382 | (struct ipmi_addr *) &smi_addr, | |
383 | &send_msg); | |
384 | if (rv) | |
385 | goto out; | |
386 | aer_addr = halt_recv_msg.msg.data[1]; | |
387 | aer_lun = halt_recv_msg.msg.data[2]; | |
388 | ||
389 | /* | |
390 | * Setup IPMB address target instead of local target | |
391 | */ | |
392 | ipmb_addr.addr_type = IPMI_IPMB_ADDR_TYPE; | |
393 | ipmb_addr.channel = 0; | |
394 | ipmb_addr.slave_addr = aer_addr; | |
395 | ipmb_addr.lun = aer_lun; | |
396 | ||
397 | /* | |
398 | * Send request hotswap control to remove blade from dpv | |
399 | */ | |
400 | send_msg.netfn = IPMI_NETFN_OEM_8 >> 2; | |
401 | send_msg.cmd = OEM_GRP_CMD_REQUEST_HOTSWAP_CTRL; | |
402 | send_msg.data = &hotswap_ipmb; | |
403 | send_msg.data_len = 1; | |
404 | ipmi_request_in_rc_mode(user, | |
405 | (struct ipmi_addr *) &ipmb_addr, | |
406 | &send_msg); | |
407 | ||
408 | /* | |
409 | * Set reset asserted | |
410 | */ | |
411 | send_msg.netfn = IPMI_NETFN_OEM_1 >> 2; | |
412 | send_msg.cmd = OEM_GRP_CMD_SET_RESET_STATE; | |
413 | send_msg.data = data; | |
414 | data[0] = 1; /* Reset asserted state */ | |
415 | send_msg.data_len = 1; | |
416 | rv = ipmi_request_in_rc_mode(user, | |
417 | (struct ipmi_addr *) &smi_addr, | |
418 | &send_msg); | |
419 | if (rv) | |
420 | goto out; | |
421 | ||
422 | /* | |
423 | * Power down | |
424 | */ | |
425 | send_msg.netfn = IPMI_NETFN_OEM_1 >> 2; | |
426 | send_msg.cmd = OEM_GRP_CMD_SET_POWER_STATE; | |
427 | send_msg.data = data; | |
428 | data[0] = 1; /* Power down state */ | |
429 | send_msg.data_len = 1; | |
430 | rv = ipmi_request_in_rc_mode(user, | |
431 | (struct ipmi_addr *) &smi_addr, | |
432 | &send_msg); | |
433 | if (rv) | |
434 | goto out; | |
435 | ||
436 | out: | |
437 | return; | |
438 | } | |
439 | ||
168524d6 CM |
440 | /* |
441 | * ipmi_dell_chassis_detect() | |
442 | * Dell systems with IPMI < 1.5 don't set the chassis capability bit | |
443 | * but they can handle a chassis poweroff or powercycle command. | |
444 | */ | |
445 | ||
446 | #define DELL_IANA_MFR_ID {0xA2, 0x02, 0x00} | |
36c7dc44 | 447 | static int ipmi_dell_chassis_detect(ipmi_user_t user) |
168524d6 CM |
448 | { |
449 | const char ipmi_version_major = ipmi_version & 0xF; | |
450 | const char ipmi_version_minor = (ipmi_version >> 4) & 0xF; | |
8a3628d5 | 451 | const char mfr[3] = DELL_IANA_MFR_ID; |
168524d6 CM |
452 | if (!memcmp(mfr, &mfg_id, sizeof(mfr)) && |
453 | ipmi_version_major <= 1 && | |
454 | ipmi_version_minor < 5) | |
455 | return 1; | |
456 | return 0; | |
457 | } | |
458 | ||
1da177e4 LT |
459 | /* |
460 | * Standard chassis support | |
461 | */ | |
462 | ||
463 | #define IPMI_NETFN_CHASSIS_REQUEST 0 | |
464 | #define IPMI_CHASSIS_CONTROL_CMD 0x02 | |
465 | ||
36c7dc44 | 466 | static int ipmi_chassis_detect(ipmi_user_t user) |
1da177e4 LT |
467 | { |
468 | /* Chassis support, use it. */ | |
469 | return (capabilities & 0x80); | |
470 | } | |
471 | ||
36c7dc44 | 472 | static void ipmi_poweroff_chassis(ipmi_user_t user) |
1da177e4 LT |
473 | { |
474 | struct ipmi_system_interface_addr smi_addr; | |
475 | struct kernel_ipmi_msg send_msg; | |
476 | int rv; | |
477 | unsigned char data[1]; | |
478 | ||
36c7dc44 CM |
479 | /* |
480 | * Configure IPMI address for local access | |
481 | */ | |
482 | smi_addr.addr_type = IPMI_SYSTEM_INTERFACE_ADDR_TYPE; | |
483 | smi_addr.channel = IPMI_BMC_CHANNEL; | |
484 | smi_addr.lun = 0; | |
1da177e4 | 485 | |
3b625943 CM |
486 | powercyclefailed: |
487 | printk(KERN_INFO PFX "Powering %s via IPMI chassis control command\n", | |
8c702e16 | 488 | (poweroff_powercycle ? "cycle" : "down")); |
1da177e4 LT |
489 | |
490 | /* | |
491 | * Power down | |
492 | */ | |
493 | send_msg.netfn = IPMI_NETFN_CHASSIS_REQUEST; | |
494 | send_msg.cmd = IPMI_CHASSIS_CONTROL_CMD; | |
8c702e16 CM |
495 | if (poweroff_powercycle) |
496 | data[0] = IPMI_CHASSIS_POWER_CYCLE; | |
497 | else | |
498 | data[0] = IPMI_CHASSIS_POWER_DOWN; | |
1da177e4 LT |
499 | send_msg.data = data; |
500 | send_msg.data_len = sizeof(data); | |
501 | rv = ipmi_request_in_rc_mode(user, | |
502 | (struct ipmi_addr *) &smi_addr, | |
503 | &send_msg); | |
504 | if (rv) { | |
8c702e16 CM |
505 | if (poweroff_powercycle) { |
506 | /* power cycle failed, default to power down */ | |
507 | printk(KERN_ERR PFX "Unable to send chassis power " \ | |
508 | "cycle message, IPMI error 0x%x\n", rv); | |
509 | poweroff_powercycle = 0; | |
510 | goto powercyclefailed; | |
3b625943 | 511 | } |
1da177e4 | 512 | |
8c702e16 CM |
513 | printk(KERN_ERR PFX "Unable to send chassis power " \ |
514 | "down message, IPMI error 0x%x\n", rv); | |
515 | } | |
1da177e4 LT |
516 | } |
517 | ||
518 | ||
519 | /* Table of possible power off functions. */ | |
520 | struct poweroff_function { | |
521 | char *platform_type; | |
522 | int (*detect)(ipmi_user_t user); | |
523 | void (*poweroff_func)(ipmi_user_t user); | |
524 | }; | |
525 | ||
526 | static struct poweroff_function poweroff_functions[] = { | |
527 | { .platform_type = "ATCA", | |
528 | .detect = ipmi_atca_detect, | |
529 | .poweroff_func = ipmi_poweroff_atca }, | |
530 | { .platform_type = "CPI1", | |
531 | .detect = ipmi_cpi1_detect, | |
532 | .poweroff_func = ipmi_poweroff_cpi1 }, | |
168524d6 CM |
533 | { .platform_type = "chassis", |
534 | .detect = ipmi_dell_chassis_detect, | |
535 | .poweroff_func = ipmi_poweroff_chassis }, | |
1da177e4 LT |
536 | /* Chassis should generally be last, other things should override |
537 | it. */ | |
538 | { .platform_type = "chassis", | |
539 | .detect = ipmi_chassis_detect, | |
540 | .poweroff_func = ipmi_poweroff_chassis }, | |
541 | }; | |
542 | #define NUM_PO_FUNCS (sizeof(poweroff_functions) \ | |
543 | / sizeof(struct poweroff_function)) | |
544 | ||
545 | ||
1da177e4 | 546 | /* Called on a powerdown request. */ |
36c7dc44 | 547 | static void ipmi_poweroff_function(void) |
1da177e4 LT |
548 | { |
549 | if (!ready) | |
550 | return; | |
551 | ||
552 | /* Use run-to-completion mode, since interrupts may be off. */ | |
1da177e4 | 553 | specific_poweroff_func(ipmi_user); |
1da177e4 LT |
554 | } |
555 | ||
556 | /* Wait for an IPMI interface to be installed, the first one installed | |
557 | will be grabbed by this code and used to perform the powerdown. */ | |
50c812b2 | 558 | static void ipmi_po_new_smi(int if_num, struct device *device) |
1da177e4 LT |
559 | { |
560 | struct ipmi_system_interface_addr smi_addr; | |
561 | struct kernel_ipmi_msg send_msg; | |
562 | int rv; | |
563 | int i; | |
564 | ||
565 | if (ready) | |
566 | return; | |
567 | ||
b2c03941 CM |
568 | if ((ifnum_to_use >= 0) && (ifnum_to_use != if_num)) |
569 | return; | |
570 | ||
3b625943 CM |
571 | rv = ipmi_create_user(if_num, &ipmi_poweroff_handler, NULL, |
572 | &ipmi_user); | |
1da177e4 LT |
573 | if (rv) { |
574 | printk(KERN_ERR PFX "could not create IPMI user, error %d\n", | |
575 | rv); | |
576 | return; | |
577 | } | |
578 | ||
b2c03941 CM |
579 | ipmi_ifnum = if_num; |
580 | ||
36c7dc44 CM |
581 | /* |
582 | * Do a get device ide and store some results, since this is | |
1da177e4 | 583 | * used by several functions. |
36c7dc44 CM |
584 | */ |
585 | smi_addr.addr_type = IPMI_SYSTEM_INTERFACE_ADDR_TYPE; | |
586 | smi_addr.channel = IPMI_BMC_CHANNEL; | |
587 | smi_addr.lun = 0; | |
1da177e4 LT |
588 | |
589 | send_msg.netfn = IPMI_NETFN_APP_REQUEST; | |
590 | send_msg.cmd = IPMI_GET_DEVICE_ID_CMD; | |
591 | send_msg.data = NULL; | |
592 | send_msg.data_len = 0; | |
593 | rv = ipmi_request_wait_for_response(ipmi_user, | |
594 | (struct ipmi_addr *) &smi_addr, | |
595 | &send_msg); | |
596 | if (rv) { | |
597 | printk(KERN_ERR PFX "Unable to send IPMI get device id info," | |
598 | " IPMI error 0x%x\n", rv); | |
599 | goto out_err; | |
600 | } | |
601 | ||
602 | if (halt_recv_msg.msg.data_len < 12) { | |
603 | printk(KERN_ERR PFX "(chassis) IPMI get device id info too," | |
604 | " short, was %d bytes, needed %d bytes\n", | |
605 | halt_recv_msg.msg.data_len, 12); | |
606 | goto out_err; | |
607 | } | |
608 | ||
609 | mfg_id = (halt_recv_msg.msg.data[7] | |
610 | | (halt_recv_msg.msg.data[8] << 8) | |
611 | | (halt_recv_msg.msg.data[9] << 16)); | |
612 | prod_id = (halt_recv_msg.msg.data[10] | |
613 | | (halt_recv_msg.msg.data[11] << 8)); | |
614 | capabilities = halt_recv_msg.msg.data[6]; | |
168524d6 | 615 | ipmi_version = halt_recv_msg.msg.data[5]; |
1da177e4 LT |
616 | |
617 | ||
618 | /* Scan for a poweroff method */ | |
e8b33617 | 619 | for (i = 0; i < NUM_PO_FUNCS; i++) { |
1da177e4 LT |
620 | if (poweroff_functions[i].detect(ipmi_user)) |
621 | goto found; | |
622 | } | |
623 | ||
624 | out_err: | |
625 | printk(KERN_ERR PFX "Unable to find a poweroff function that" | |
626 | " will work, giving up\n"); | |
627 | ipmi_destroy_user(ipmi_user); | |
628 | return; | |
629 | ||
630 | found: | |
631 | printk(KERN_INFO PFX "Found a %s style poweroff function\n", | |
632 | poweroff_functions[i].platform_type); | |
633 | specific_poweroff_func = poweroff_functions[i].poweroff_func; | |
634 | old_poweroff_func = pm_power_off; | |
635 | pm_power_off = ipmi_poweroff_function; | |
636 | ready = 1; | |
637 | } | |
638 | ||
639 | static void ipmi_po_smi_gone(int if_num) | |
640 | { | |
b2c03941 CM |
641 | if (!ready) |
642 | return; | |
643 | ||
644 | if (ipmi_ifnum != if_num) | |
645 | return; | |
646 | ||
647 | ready = 0; | |
648 | ipmi_destroy_user(ipmi_user); | |
649 | pm_power_off = old_poweroff_func; | |
1da177e4 LT |
650 | } |
651 | ||
36c7dc44 | 652 | static struct ipmi_smi_watcher smi_watcher = { |
1da177e4 LT |
653 | .owner = THIS_MODULE, |
654 | .new_smi = ipmi_po_new_smi, | |
655 | .smi_gone = ipmi_po_smi_gone | |
656 | }; | |
657 | ||
658 | ||
3b625943 | 659 | #ifdef CONFIG_PROC_FS |
8c702e16 CM |
660 | #include <linux/sysctl.h> |
661 | ||
662 | static ctl_table ipmi_table[] = { | |
894d2491 | 663 | { .procname = "poweroff_powercycle", |
8c702e16 CM |
664 | .data = &poweroff_powercycle, |
665 | .maxlen = sizeof(poweroff_powercycle), | |
666 | .mode = 0644, | |
6d456111 | 667 | .proc_handler = proc_dointvec }, |
8c702e16 CM |
668 | { } |
669 | }; | |
3b625943 | 670 | |
8c702e16 | 671 | static ctl_table ipmi_dir_table[] = { |
894d2491 | 672 | { .procname = "ipmi", |
8c702e16 CM |
673 | .mode = 0555, |
674 | .child = ipmi_table }, | |
675 | { } | |
676 | }; | |
3b625943 | 677 | |
8c702e16 | 678 | static ctl_table ipmi_root_table[] = { |
894d2491 | 679 | { .procname = "dev", |
8c702e16 CM |
680 | .mode = 0555, |
681 | .child = ipmi_dir_table }, | |
682 | { } | |
683 | }; | |
684 | ||
685 | static struct ctl_table_header *ipmi_table_header; | |
3b625943 CM |
686 | #endif /* CONFIG_PROC_FS */ |
687 | ||
1da177e4 LT |
688 | /* |
689 | * Startup and shutdown functions. | |
690 | */ | |
0e6b9e8c | 691 | static int __init ipmi_poweroff_init(void) |
1da177e4 | 692 | { |
8c702e16 | 693 | int rv; |
1da177e4 | 694 | |
36c7dc44 CM |
695 | printk(KERN_INFO "Copyright (C) 2004 MontaVista Software -" |
696 | " IPMI Powerdown via sys_reboot.\n"); | |
1da177e4 | 697 | |
8c702e16 CM |
698 | if (poweroff_powercycle) |
699 | printk(KERN_INFO PFX "Power cycle is enabled.\n"); | |
3b625943 | 700 | |
8c702e16 | 701 | #ifdef CONFIG_PROC_FS |
0b4d4147 | 702 | ipmi_table_header = register_sysctl_table(ipmi_root_table); |
8c702e16 CM |
703 | if (!ipmi_table_header) { |
704 | printk(KERN_ERR PFX "Unable to register powercycle sysctl\n"); | |
705 | rv = -ENOMEM; | |
706 | goto out_err; | |
3b625943 | 707 | } |
8c702e16 | 708 | #endif |
3b625943 | 709 | |
1da177e4 | 710 | rv = ipmi_smi_watcher_register(&smi_watcher); |
be4f1bb2 AB |
711 | |
712 | #ifdef CONFIG_PROC_FS | |
3b625943 | 713 | if (rv) { |
8c702e16 | 714 | unregister_sysctl_table(ipmi_table_header); |
1da177e4 | 715 | printk(KERN_ERR PFX "Unable to register SMI watcher: %d\n", rv); |
3b625943 CM |
716 | goto out_err; |
717 | } | |
718 | ||
3b625943 | 719 | out_err: |
1aa16eea | 720 | #endif |
1da177e4 LT |
721 | return rv; |
722 | } | |
723 | ||
724 | #ifdef MODULE | |
0e6b9e8c | 725 | static void __exit ipmi_poweroff_cleanup(void) |
1da177e4 LT |
726 | { |
727 | int rv; | |
728 | ||
3b625943 | 729 | #ifdef CONFIG_PROC_FS |
8c702e16 | 730 | unregister_sysctl_table(ipmi_table_header); |
3b625943 CM |
731 | #endif |
732 | ||
1da177e4 LT |
733 | ipmi_smi_watcher_unregister(&smi_watcher); |
734 | ||
735 | if (ready) { | |
736 | rv = ipmi_destroy_user(ipmi_user); | |
737 | if (rv) | |
738 | printk(KERN_ERR PFX "could not cleanup the IPMI" | |
739 | " user: 0x%x\n", rv); | |
740 | pm_power_off = old_poweroff_func; | |
741 | } | |
742 | } | |
743 | module_exit(ipmi_poweroff_cleanup); | |
744 | #endif | |
745 | ||
746 | module_init(ipmi_poweroff_init); | |
747 | MODULE_LICENSE("GPL"); | |
1fdd75bd CM |
748 | MODULE_AUTHOR("Corey Minyard <minyard@mvista.com>"); |
749 | MODULE_DESCRIPTION("IPMI Poweroff extension to sys_reboot"); |