diff options
author | Linus Torvalds <torvalds@ppc970.osdl.org> | 2005-04-16 18:20:36 -0400 |
---|---|---|
committer | Linus Torvalds <torvalds@ppc970.osdl.org> | 2005-04-16 18:20:36 -0400 |
commit | 1da177e4c3f41524e886b7f1b8a0c1fc7321cac2 (patch) | |
tree | 0bba044c4ce775e45a88a51686b5d9f90697ea9d /drivers/char/ipmi/ipmi_devintf.c |
Linux-2.6.12-rc2v2.6.12-rc2
Initial git repository build. I'm not bothering with the full history,
even though we have it. We can create a separate "historical" git
archive of that later if we want to, and in the meantime it's about
3.2GB when imported into git - space that would just make the early
git days unnecessarily complicated, when we don't have a lot of good
infrastructure for it.
Let it rip!
Diffstat (limited to 'drivers/char/ipmi/ipmi_devintf.c')
-rw-r--r-- | drivers/char/ipmi/ipmi_devintf.c | 582 |
1 files changed, 582 insertions, 0 deletions
diff --git a/drivers/char/ipmi/ipmi_devintf.c b/drivers/char/ipmi/ipmi_devintf.c new file mode 100644 index 000000000000..49d67f5384a2 --- /dev/null +++ b/drivers/char/ipmi/ipmi_devintf.c | |||
@@ -0,0 +1,582 @@ | |||
1 | /* | ||
2 | * ipmi_devintf.c | ||
3 | * | ||
4 | * Linux device interface for the IPMI message handler. | ||
5 | * | ||
6 | * Author: MontaVista Software, Inc. | ||
7 | * Corey Minyard <minyard@mvista.com> | ||
8 | * source@mvista.com | ||
9 | * | ||
10 | * Copyright 2002 MontaVista Software Inc. | ||
11 | * | ||
12 | * This program is free software; you can redistribute it and/or modify it | ||
13 | * under the terms of the GNU General Public License as published by the | ||
14 | * Free Software Foundation; either version 2 of the License, or (at your | ||
15 | * option) any later version. | ||
16 | * | ||
17 | * | ||
18 | * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED | ||
19 | * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF | ||
20 | * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. | ||
21 | * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, | ||
22 | * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, | ||
23 | * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS | ||
24 | * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND | ||
25 | * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR | ||
26 | * TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE | ||
27 | * USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | ||
28 | * | ||
29 | * You should have received a copy of the GNU General Public License along | ||
30 | * with this program; if not, write to the Free Software Foundation, Inc., | ||
31 | * 675 Mass Ave, Cambridge, MA 02139, USA. | ||
32 | */ | ||
33 | |||
34 | #include <linux/config.h> | ||
35 | #include <linux/module.h> | ||
36 | #include <linux/moduleparam.h> | ||
37 | #include <linux/errno.h> | ||
38 | #include <asm/system.h> | ||
39 | #include <linux/sched.h> | ||
40 | #include <linux/poll.h> | ||
41 | #include <linux/spinlock.h> | ||
42 | #include <linux/slab.h> | ||
43 | #include <linux/devfs_fs_kernel.h> | ||
44 | #include <linux/ipmi.h> | ||
45 | #include <asm/semaphore.h> | ||
46 | #include <linux/init.h> | ||
47 | |||
48 | #define IPMI_DEVINTF_VERSION "v33" | ||
49 | |||
50 | struct ipmi_file_private | ||
51 | { | ||
52 | ipmi_user_t user; | ||
53 | spinlock_t recv_msg_lock; | ||
54 | struct list_head recv_msgs; | ||
55 | struct file *file; | ||
56 | struct fasync_struct *fasync_queue; | ||
57 | wait_queue_head_t wait; | ||
58 | struct semaphore recv_sem; | ||
59 | int default_retries; | ||
60 | unsigned int default_retry_time_ms; | ||
61 | }; | ||
62 | |||
63 | static void file_receive_handler(struct ipmi_recv_msg *msg, | ||
64 | void *handler_data) | ||
65 | { | ||
66 | struct ipmi_file_private *priv = handler_data; | ||
67 | int was_empty; | ||
68 | unsigned long flags; | ||
69 | |||
70 | spin_lock_irqsave(&(priv->recv_msg_lock), flags); | ||
71 | |||
72 | was_empty = list_empty(&(priv->recv_msgs)); | ||
73 | list_add_tail(&(msg->link), &(priv->recv_msgs)); | ||
74 | |||
75 | if (was_empty) { | ||
76 | wake_up_interruptible(&priv->wait); | ||
77 | kill_fasync(&priv->fasync_queue, SIGIO, POLL_IN); | ||
78 | } | ||
79 | |||
80 | spin_unlock_irqrestore(&(priv->recv_msg_lock), flags); | ||
81 | } | ||
82 | |||
83 | static unsigned int ipmi_poll(struct file *file, poll_table *wait) | ||
84 | { | ||
85 | struct ipmi_file_private *priv = file->private_data; | ||
86 | unsigned int mask = 0; | ||
87 | unsigned long flags; | ||
88 | |||
89 | poll_wait(file, &priv->wait, wait); | ||
90 | |||
91 | spin_lock_irqsave(&priv->recv_msg_lock, flags); | ||
92 | |||
93 | if (! list_empty(&(priv->recv_msgs))) | ||
94 | mask |= (POLLIN | POLLRDNORM); | ||
95 | |||
96 | spin_unlock_irqrestore(&priv->recv_msg_lock, flags); | ||
97 | |||
98 | return mask; | ||
99 | } | ||
100 | |||
101 | static int ipmi_fasync(int fd, struct file *file, int on) | ||
102 | { | ||
103 | struct ipmi_file_private *priv = file->private_data; | ||
104 | int result; | ||
105 | |||
106 | result = fasync_helper(fd, file, on, &priv->fasync_queue); | ||
107 | |||
108 | return (result); | ||
109 | } | ||
110 | |||
111 | static struct ipmi_user_hndl ipmi_hndlrs = | ||
112 | { | ||
113 | .ipmi_recv_hndl = file_receive_handler, | ||
114 | }; | ||
115 | |||
116 | static int ipmi_open(struct inode *inode, struct file *file) | ||
117 | { | ||
118 | int if_num = iminor(inode); | ||
119 | int rv; | ||
120 | struct ipmi_file_private *priv; | ||
121 | |||
122 | |||
123 | priv = kmalloc(sizeof(*priv), GFP_KERNEL); | ||
124 | if (!priv) | ||
125 | return -ENOMEM; | ||
126 | |||
127 | priv->file = file; | ||
128 | |||
129 | rv = ipmi_create_user(if_num, | ||
130 | &ipmi_hndlrs, | ||
131 | priv, | ||
132 | &(priv->user)); | ||
133 | if (rv) { | ||
134 | kfree(priv); | ||
135 | return rv; | ||
136 | } | ||
137 | |||
138 | file->private_data = priv; | ||
139 | |||
140 | spin_lock_init(&(priv->recv_msg_lock)); | ||
141 | INIT_LIST_HEAD(&(priv->recv_msgs)); | ||
142 | init_waitqueue_head(&priv->wait); | ||
143 | priv->fasync_queue = NULL; | ||
144 | sema_init(&(priv->recv_sem), 1); | ||
145 | |||
146 | /* Use the low-level defaults. */ | ||
147 | priv->default_retries = -1; | ||
148 | priv->default_retry_time_ms = 0; | ||
149 | |||
150 | return 0; | ||
151 | } | ||
152 | |||
153 | static int ipmi_release(struct inode *inode, struct file *file) | ||
154 | { | ||
155 | struct ipmi_file_private *priv = file->private_data; | ||
156 | int rv; | ||
157 | |||
158 | rv = ipmi_destroy_user(priv->user); | ||
159 | if (rv) | ||
160 | return rv; | ||
161 | |||
162 | ipmi_fasync (-1, file, 0); | ||
163 | |||
164 | /* FIXME - free the messages in the list. */ | ||
165 | kfree(priv); | ||
166 | |||
167 | return 0; | ||
168 | } | ||
169 | |||
170 | static int handle_send_req(ipmi_user_t user, | ||
171 | struct ipmi_req *req, | ||
172 | int retries, | ||
173 | unsigned int retry_time_ms) | ||
174 | { | ||
175 | int rv; | ||
176 | struct ipmi_addr addr; | ||
177 | struct kernel_ipmi_msg msg; | ||
178 | |||
179 | if (req->addr_len > sizeof(struct ipmi_addr)) | ||
180 | return -EINVAL; | ||
181 | |||
182 | if (copy_from_user(&addr, req->addr, req->addr_len)) | ||
183 | return -EFAULT; | ||
184 | |||
185 | msg.netfn = req->msg.netfn; | ||
186 | msg.cmd = req->msg.cmd; | ||
187 | msg.data_len = req->msg.data_len; | ||
188 | msg.data = kmalloc(IPMI_MAX_MSG_LENGTH, GFP_KERNEL); | ||
189 | if (!msg.data) | ||
190 | return -ENOMEM; | ||
191 | |||
192 | /* From here out we cannot return, we must jump to "out" for | ||
193 | error exits to free msgdata. */ | ||
194 | |||
195 | rv = ipmi_validate_addr(&addr, req->addr_len); | ||
196 | if (rv) | ||
197 | goto out; | ||
198 | |||
199 | if (req->msg.data != NULL) { | ||
200 | if (req->msg.data_len > IPMI_MAX_MSG_LENGTH) { | ||
201 | rv = -EMSGSIZE; | ||
202 | goto out; | ||
203 | } | ||
204 | |||
205 | if (copy_from_user(msg.data, | ||
206 | req->msg.data, | ||
207 | req->msg.data_len)) | ||
208 | { | ||
209 | rv = -EFAULT; | ||
210 | goto out; | ||
211 | } | ||
212 | } else { | ||
213 | msg.data_len = 0; | ||
214 | } | ||
215 | |||
216 | rv = ipmi_request_settime(user, | ||
217 | &addr, | ||
218 | req->msgid, | ||
219 | &msg, | ||
220 | NULL, | ||
221 | 0, | ||
222 | retries, | ||
223 | retry_time_ms); | ||
224 | out: | ||
225 | kfree(msg.data); | ||
226 | return rv; | ||
227 | } | ||
228 | |||
229 | static int ipmi_ioctl(struct inode *inode, | ||
230 | struct file *file, | ||
231 | unsigned int cmd, | ||
232 | unsigned long data) | ||
233 | { | ||
234 | int rv = -EINVAL; | ||
235 | struct ipmi_file_private *priv = file->private_data; | ||
236 | void __user *arg = (void __user *)data; | ||
237 | |||
238 | switch (cmd) | ||
239 | { | ||
240 | case IPMICTL_SEND_COMMAND: | ||
241 | { | ||
242 | struct ipmi_req req; | ||
243 | |||
244 | if (copy_from_user(&req, arg, sizeof(req))) { | ||
245 | rv = -EFAULT; | ||
246 | break; | ||
247 | } | ||
248 | |||
249 | rv = handle_send_req(priv->user, | ||
250 | &req, | ||
251 | priv->default_retries, | ||
252 | priv->default_retry_time_ms); | ||
253 | break; | ||
254 | } | ||
255 | |||
256 | case IPMICTL_SEND_COMMAND_SETTIME: | ||
257 | { | ||
258 | struct ipmi_req_settime req; | ||
259 | |||
260 | if (copy_from_user(&req, arg, sizeof(req))) { | ||
261 | rv = -EFAULT; | ||
262 | break; | ||
263 | } | ||
264 | |||
265 | rv = handle_send_req(priv->user, | ||
266 | &req.req, | ||
267 | req.retries, | ||
268 | req.retry_time_ms); | ||
269 | break; | ||
270 | } | ||
271 | |||
272 | case IPMICTL_RECEIVE_MSG: | ||
273 | case IPMICTL_RECEIVE_MSG_TRUNC: | ||
274 | { | ||
275 | struct ipmi_recv rsp; | ||
276 | int addr_len; | ||
277 | struct list_head *entry; | ||
278 | struct ipmi_recv_msg *msg; | ||
279 | unsigned long flags; | ||
280 | |||
281 | |||
282 | rv = 0; | ||
283 | if (copy_from_user(&rsp, arg, sizeof(rsp))) { | ||
284 | rv = -EFAULT; | ||
285 | break; | ||
286 | } | ||
287 | |||
288 | /* We claim a semaphore because we don't want two | ||
289 | users getting something from the queue at a time. | ||
290 | Since we have to release the spinlock before we can | ||
291 | copy the data to the user, it's possible another | ||
292 | user will grab something from the queue, too. Then | ||
293 | the messages might get out of order if something | ||
294 | fails and the message gets put back onto the | ||
295 | queue. This semaphore prevents that problem. */ | ||
296 | down(&(priv->recv_sem)); | ||
297 | |||
298 | /* Grab the message off the list. */ | ||
299 | spin_lock_irqsave(&(priv->recv_msg_lock), flags); | ||
300 | if (list_empty(&(priv->recv_msgs))) { | ||
301 | spin_unlock_irqrestore(&(priv->recv_msg_lock), flags); | ||
302 | rv = -EAGAIN; | ||
303 | goto recv_err; | ||
304 | } | ||
305 | entry = priv->recv_msgs.next; | ||
306 | msg = list_entry(entry, struct ipmi_recv_msg, link); | ||
307 | list_del(entry); | ||
308 | spin_unlock_irqrestore(&(priv->recv_msg_lock), flags); | ||
309 | |||
310 | addr_len = ipmi_addr_length(msg->addr.addr_type); | ||
311 | if (rsp.addr_len < addr_len) | ||
312 | { | ||
313 | rv = -EINVAL; | ||
314 | goto recv_putback_on_err; | ||
315 | } | ||
316 | |||
317 | if (copy_to_user(rsp.addr, &(msg->addr), addr_len)) { | ||
318 | rv = -EFAULT; | ||
319 | goto recv_putback_on_err; | ||
320 | } | ||
321 | rsp.addr_len = addr_len; | ||
322 | |||
323 | rsp.recv_type = msg->recv_type; | ||
324 | rsp.msgid = msg->msgid; | ||
325 | rsp.msg.netfn = msg->msg.netfn; | ||
326 | rsp.msg.cmd = msg->msg.cmd; | ||
327 | |||
328 | if (msg->msg.data_len > 0) { | ||
329 | if (rsp.msg.data_len < msg->msg.data_len) { | ||
330 | rv = -EMSGSIZE; | ||
331 | if (cmd == IPMICTL_RECEIVE_MSG_TRUNC) { | ||
332 | msg->msg.data_len = rsp.msg.data_len; | ||
333 | } else { | ||
334 | goto recv_putback_on_err; | ||
335 | } | ||
336 | } | ||
337 | |||
338 | if (copy_to_user(rsp.msg.data, | ||
339 | msg->msg.data, | ||
340 | msg->msg.data_len)) | ||
341 | { | ||
342 | rv = -EFAULT; | ||
343 | goto recv_putback_on_err; | ||
344 | } | ||
345 | rsp.msg.data_len = msg->msg.data_len; | ||
346 | } else { | ||
347 | rsp.msg.data_len = 0; | ||
348 | } | ||
349 | |||
350 | if (copy_to_user(arg, &rsp, sizeof(rsp))) { | ||
351 | rv = -EFAULT; | ||
352 | goto recv_putback_on_err; | ||
353 | } | ||
354 | |||
355 | up(&(priv->recv_sem)); | ||
356 | ipmi_free_recv_msg(msg); | ||
357 | break; | ||
358 | |||
359 | recv_putback_on_err: | ||
360 | /* If we got an error, put the message back onto | ||
361 | the head of the queue. */ | ||
362 | spin_lock_irqsave(&(priv->recv_msg_lock), flags); | ||
363 | list_add(entry, &(priv->recv_msgs)); | ||
364 | spin_unlock_irqrestore(&(priv->recv_msg_lock), flags); | ||
365 | up(&(priv->recv_sem)); | ||
366 | break; | ||
367 | |||
368 | recv_err: | ||
369 | up(&(priv->recv_sem)); | ||
370 | break; | ||
371 | } | ||
372 | |||
373 | case IPMICTL_REGISTER_FOR_CMD: | ||
374 | { | ||
375 | struct ipmi_cmdspec val; | ||
376 | |||
377 | if (copy_from_user(&val, arg, sizeof(val))) { | ||
378 | rv = -EFAULT; | ||
379 | break; | ||
380 | } | ||
381 | |||
382 | rv = ipmi_register_for_cmd(priv->user, val.netfn, val.cmd); | ||
383 | break; | ||
384 | } | ||
385 | |||
386 | case IPMICTL_UNREGISTER_FOR_CMD: | ||
387 | { | ||
388 | struct ipmi_cmdspec val; | ||
389 | |||
390 | if (copy_from_user(&val, arg, sizeof(val))) { | ||
391 | rv = -EFAULT; | ||
392 | break; | ||
393 | } | ||
394 | |||
395 | rv = ipmi_unregister_for_cmd(priv->user, val.netfn, val.cmd); | ||
396 | break; | ||
397 | } | ||
398 | |||
399 | case IPMICTL_SET_GETS_EVENTS_CMD: | ||
400 | { | ||
401 | int val; | ||
402 | |||
403 | if (copy_from_user(&val, arg, sizeof(val))) { | ||
404 | rv = -EFAULT; | ||
405 | break; | ||
406 | } | ||
407 | |||
408 | rv = ipmi_set_gets_events(priv->user, val); | ||
409 | break; | ||
410 | } | ||
411 | |||
412 | case IPMICTL_SET_MY_ADDRESS_CMD: | ||
413 | { | ||
414 | unsigned int val; | ||
415 | |||
416 | if (copy_from_user(&val, arg, sizeof(val))) { | ||
417 | rv = -EFAULT; | ||
418 | break; | ||
419 | } | ||
420 | |||
421 | ipmi_set_my_address(priv->user, val); | ||
422 | rv = 0; | ||
423 | break; | ||
424 | } | ||
425 | |||
426 | case IPMICTL_GET_MY_ADDRESS_CMD: | ||
427 | { | ||
428 | unsigned int val; | ||
429 | |||
430 | val = ipmi_get_my_address(priv->user); | ||
431 | |||
432 | if (copy_to_user(arg, &val, sizeof(val))) { | ||
433 | rv = -EFAULT; | ||
434 | break; | ||
435 | } | ||
436 | rv = 0; | ||
437 | break; | ||
438 | } | ||
439 | |||
440 | case IPMICTL_SET_MY_LUN_CMD: | ||
441 | { | ||
442 | unsigned int val; | ||
443 | |||
444 | if (copy_from_user(&val, arg, sizeof(val))) { | ||
445 | rv = -EFAULT; | ||
446 | break; | ||
447 | } | ||
448 | |||
449 | ipmi_set_my_LUN(priv->user, val); | ||
450 | rv = 0; | ||
451 | break; | ||
452 | } | ||
453 | |||
454 | case IPMICTL_GET_MY_LUN_CMD: | ||
455 | { | ||
456 | unsigned int val; | ||
457 | |||
458 | val = ipmi_get_my_LUN(priv->user); | ||
459 | |||
460 | if (copy_to_user(arg, &val, sizeof(val))) { | ||
461 | rv = -EFAULT; | ||
462 | break; | ||
463 | } | ||
464 | rv = 0; | ||
465 | break; | ||
466 | } | ||
467 | case IPMICTL_SET_TIMING_PARMS_CMD: | ||
468 | { | ||
469 | struct ipmi_timing_parms parms; | ||
470 | |||
471 | if (copy_from_user(&parms, arg, sizeof(parms))) { | ||
472 | rv = -EFAULT; | ||
473 | break; | ||
474 | } | ||
475 | |||
476 | priv->default_retries = parms.retries; | ||
477 | priv->default_retry_time_ms = parms.retry_time_ms; | ||
478 | rv = 0; | ||
479 | break; | ||
480 | } | ||
481 | |||
482 | case IPMICTL_GET_TIMING_PARMS_CMD: | ||
483 | { | ||
484 | struct ipmi_timing_parms parms; | ||
485 | |||
486 | parms.retries = priv->default_retries; | ||
487 | parms.retry_time_ms = priv->default_retry_time_ms; | ||
488 | |||
489 | if (copy_to_user(arg, &parms, sizeof(parms))) { | ||
490 | rv = -EFAULT; | ||
491 | break; | ||
492 | } | ||
493 | |||
494 | rv = 0; | ||
495 | break; | ||
496 | } | ||
497 | } | ||
498 | |||
499 | return rv; | ||
500 | } | ||
501 | |||
502 | |||
503 | static struct file_operations ipmi_fops = { | ||
504 | .owner = THIS_MODULE, | ||
505 | .ioctl = ipmi_ioctl, | ||
506 | .open = ipmi_open, | ||
507 | .release = ipmi_release, | ||
508 | .fasync = ipmi_fasync, | ||
509 | .poll = ipmi_poll, | ||
510 | }; | ||
511 | |||
512 | #define DEVICE_NAME "ipmidev" | ||
513 | |||
514 | static int ipmi_major = 0; | ||
515 | module_param(ipmi_major, int, 0); | ||
516 | MODULE_PARM_DESC(ipmi_major, "Sets the major number of the IPMI device. By" | ||
517 | " default, or if you set it to zero, it will choose the next" | ||
518 | " available device. Setting it to -1 will disable the" | ||
519 | " interface. Other values will set the major device number" | ||
520 | " to that value."); | ||
521 | |||
522 | static void ipmi_new_smi(int if_num) | ||
523 | { | ||
524 | devfs_mk_cdev(MKDEV(ipmi_major, if_num), | ||
525 | S_IFCHR | S_IRUSR | S_IWUSR, | ||
526 | "ipmidev/%d", if_num); | ||
527 | } | ||
528 | |||
529 | static void ipmi_smi_gone(int if_num) | ||
530 | { | ||
531 | devfs_remove("ipmidev/%d", if_num); | ||
532 | } | ||
533 | |||
534 | static struct ipmi_smi_watcher smi_watcher = | ||
535 | { | ||
536 | .owner = THIS_MODULE, | ||
537 | .new_smi = ipmi_new_smi, | ||
538 | .smi_gone = ipmi_smi_gone, | ||
539 | }; | ||
540 | |||
541 | static __init int init_ipmi_devintf(void) | ||
542 | { | ||
543 | int rv; | ||
544 | |||
545 | if (ipmi_major < 0) | ||
546 | return -EINVAL; | ||
547 | |||
548 | printk(KERN_INFO "ipmi device interface version " | ||
549 | IPMI_DEVINTF_VERSION "\n"); | ||
550 | |||
551 | rv = register_chrdev(ipmi_major, DEVICE_NAME, &ipmi_fops); | ||
552 | if (rv < 0) { | ||
553 | printk(KERN_ERR "ipmi: can't get major %d\n", ipmi_major); | ||
554 | return rv; | ||
555 | } | ||
556 | |||
557 | if (ipmi_major == 0) { | ||
558 | ipmi_major = rv; | ||
559 | } | ||
560 | |||
561 | devfs_mk_dir(DEVICE_NAME); | ||
562 | |||
563 | rv = ipmi_smi_watcher_register(&smi_watcher); | ||
564 | if (rv) { | ||
565 | unregister_chrdev(ipmi_major, DEVICE_NAME); | ||
566 | printk(KERN_WARNING "ipmi: can't register smi watcher\n"); | ||
567 | return rv; | ||
568 | } | ||
569 | |||
570 | return 0; | ||
571 | } | ||
572 | module_init(init_ipmi_devintf); | ||
573 | |||
574 | static __exit void cleanup_ipmi(void) | ||
575 | { | ||
576 | ipmi_smi_watcher_unregister(&smi_watcher); | ||
577 | devfs_remove(DEVICE_NAME); | ||
578 | unregister_chrdev(ipmi_major, DEVICE_NAME); | ||
579 | } | ||
580 | module_exit(cleanup_ipmi); | ||
581 | |||
582 | MODULE_LICENSE("GPL"); | ||