xref: /titanic_41/usr/src/uts/intel/io/ipmi/ipmi_main.c (revision 653d2d91a355b2a5df7db09e17c965ddc5d3ea47)
1 /*
2  * CDDL HEADER START
3  *
4  * The contents of this file are subject to the terms of the
5  * Common Development and Distribution License (the "License").
6  * You may not use this file except in compliance with the License.
7  *
8  * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
9  * or http://www.opensolaris.org/os/licensing.
10  * See the License for the specific language governing permissions
11  * and limitations under the License.
12  *
13  * When distributing Covered Code, include this CDDL HEADER in each
14  * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
15  * If applicable, add the following below this CDDL HEADER, with the
16  * fields enclosed by brackets "[]" replaced with your own identifying
17  * information: Portions Copyright [yyyy] [name of copyright owner]
18  *
19  * CDDL HEADER END
20  */
21 
22 /*
23  * Copyright (c) 2013, Joyent, Inc.  All rights reserved.
24  * Copyright 2013 Nexenta Systems, Inc.  All rights reserved.
25  */
26 
27 /*
28  * The ipmi driver is an openipmi compatible IPMI driver based on the FreeBSD
29  * driver.
30  *
31  * The current implementation has several limitations:
32  * 1) It only does discovery through the SMBIOS.  The FreeBSD driver has
33  *    several additional ways to discover the IPMI device (acpi, bus checking,
34  *    etc.).  This support could be ported if necessary.
35  * 2) The driver currently only supports the IPMI KCS_MODE mode (reported
36  *    through the SMBIOS as SMBIOS SMB_IPMI_T_KCS). Support for the other modes
37  *    (BT_MODE, SMIC_MODE, SSIF_MODE) could be ported if necessary.
38  * 3) The driver does not currently set up an IPMI watchdog.  This also could
39  *    be ported if necessary.
40  */
41 
42 #include <sys/devops.h>
43 #include <sys/conf.h>
44 #include <sys/modctl.h>
45 #include <sys/types.h>
46 #include <sys/file.h>
47 #include <sys/errno.h>
48 #include <sys/open.h>
49 #include <sys/cred.h>
50 #include <sys/uio.h>
51 #include <sys/stat.h>
52 #include <sys/cmn_err.h>
53 #include <sys/ddi.h>
54 #include <sys/sunddi.h>
55 #include <sys/smbios.h>
56 #include <sys/smbios_impl.h>
57 #include <sys/policy.h>
58 #include <sys/ipmi.h>
59 #include "ipmivars.h"
60 
61 static dev_info_t		*ipmi_dip;
62 static boolean_t		ipmi_attached = B_FALSE;
63 static boolean_t		ipmi_found = B_FALSE;
64 static struct ipmi_softc	softc;
65 static struct ipmi_softc	*sc = &softc;
66 static list_t			dev_list;
67 static id_space_t		*minor_ids;
68 static kmutex_t			dev_list_lock;
69 
70 #define	PTRIN(p)	((void *)(uintptr_t)(p))
71 #define	PTROUT(p)	((uintptr_t)(p))
72 
73 /*
74  * Use the SMBIOS info to determine if the system has an IPMI.
75  */
76 static int
get_smbios_ipmi_info(void)77 get_smbios_ipmi_info(void)
78 {
79 	smbios_ipmi_t ipmi;
80 
81 	if (ksmbios == NULL || smbios_info_ipmi(ksmbios, &ipmi) == SMB_ERR)
82 		return (DDI_FAILURE);
83 
84 	cmn_err(CE_CONT, "!SMBIOS type 0x%x, addr 0x%llx", ipmi.smbip_type,
85 	    (long long unsigned int)(ipmi.smbip_addr));
86 
87 	/*
88 	 * Some systems have a bios that will report an IPMI device even when
89 	 * it is not installed. In this case we see 0x0 as the base address.
90 	 * If we see this address, assume the device is not really present.
91 	 */
92 	if (ipmi.smbip_addr == NULL) {
93 		cmn_err(CE_WARN, "!SMBIOS: Invalid base address");
94 		return (DDI_FAILURE);
95 	}
96 
97 	sc->ipmi_io_type = ipmi.smbip_type;
98 	switch (ipmi.smbip_type) {
99 	case SMB_IPMI_T_KCS:
100 	case SMB_IPMI_T_SMIC:
101 		sc->ipmi_io_address = ipmi.smbip_addr;
102 		sc->ipmi_io_mode = (ipmi.smbip_flags & SMB_IPMI_F_IOADDR) ?
103 		    1 : 0;
104 		sc->ipmi_io_spacing = ipmi.smbip_regspacing;
105 		break;
106 	case SMB_IPMI_T_SSIF:
107 		if ((ipmi.smbip_addr & 0xffffffffffffff00) != 0) {
108 			cmn_err(CE_WARN, "!SMBIOS: Invalid SSIF SMBus address, "
109 			    "using BMC I2C slave address instead");
110 			sc->ipmi_io_address = ipmi.smbip_i2c;
111 		} else {
112 			sc->ipmi_io_address = ipmi.smbip_addr;
113 		}
114 		break;
115 	default:
116 		return (DDI_FAILURE);
117 	}
118 
119 	if (ipmi.smbip_intr > 15) {
120 		cmn_err(CE_WARN, "!SMBIOS: Non-ISA IRQ %d for IPMI",
121 		    ipmi.smbip_intr);
122 		return (DDI_FAILURE);
123 	}
124 
125 	sc->ipmi_io_irq = ipmi.smbip_intr;
126 	return (DDI_SUCCESS);
127 }
128 
129 static ipmi_device_t *
lookup_ipmidev_by_dev(dev_t dev)130 lookup_ipmidev_by_dev(dev_t dev)
131 {
132 	ipmi_device_t	*p;
133 
134 	mutex_enter(&dev_list_lock);
135 	for (p = list_head(&dev_list); p; p = list_next(&dev_list, p)) {
136 		if (dev == p->ipmi_dev) {
137 			mutex_exit(&dev_list_lock);
138 			return (p);
139 		}
140 	}
141 	mutex_exit(&dev_list_lock);
142 	return (NULL);
143 }
144 
145 /*
146  * Each open returns a new pseudo device.
147  */
148 /*ARGSUSED*/
149 static int
ipmi_open(dev_t * devp,int flag,int otyp,cred_t * cred)150 ipmi_open(dev_t *devp, int flag, int otyp, cred_t *cred)
151 {
152 	minor_t minor;
153 	ipmi_device_t *dev;
154 
155 	if (ipmi_attached == B_FALSE)
156 		return (ENXIO);
157 
158 	if (ipmi_found == B_FALSE)
159 		return (ENODEV);
160 
161 	/* exclusive opens are not supported */
162 	if (flag & FEXCL)
163 		return (ENOTSUP);
164 
165 	if ((minor = (minor_t)id_alloc_nosleep(minor_ids)) == 0)
166 		return (ENODEV);
167 
168 	/* Initialize the per file descriptor data. */
169 	dev = kmem_zalloc(sizeof (ipmi_device_t), KM_SLEEP);
170 
171 	dev->ipmi_pollhead = kmem_zalloc(sizeof (pollhead_t), KM_SLEEP);
172 
173 	TAILQ_INIT(&dev->ipmi_completed_requests);
174 	dev->ipmi_address = IPMI_BMC_SLAVE_ADDR;
175 	dev->ipmi_lun = IPMI_BMC_SMS_LUN;
176 	*devp = makedevice(getmajor(*devp), minor);
177 	dev->ipmi_dev = *devp;
178 	cv_init(&dev->ipmi_cv, NULL, CV_DEFAULT, NULL);
179 
180 	mutex_enter(&dev_list_lock);
181 	list_insert_head(&dev_list, dev);
182 	mutex_exit(&dev_list_lock);
183 
184 	return (0);
185 }
186 
187 /*ARGSUSED*/
188 static int
ipmi_close(dev_t dev,int flag,int otyp,cred_t * cred)189 ipmi_close(dev_t dev, int flag, int otyp, cred_t *cred)
190 {
191 	ipmi_device_t *dp;
192 	struct ipmi_request *req, *next;
193 
194 	if ((dp = lookup_ipmidev_by_dev(dev)) == NULL)
195 		return (ENODEV);
196 
197 	IPMI_LOCK(sc);
198 	/* remove any pending requests */
199 	req = TAILQ_FIRST(&sc->ipmi_pending_requests);
200 	while (req != NULL) {
201 		next = TAILQ_NEXT(req, ir_link);
202 
203 		if (req->ir_owner == dp) {
204 			TAILQ_REMOVE(&sc->ipmi_pending_requests, req, ir_link);
205 			ipmi_free_request(req);
206 		}
207 		req = next;
208 	}
209 
210 	dp->ipmi_status |= IPMI_CLOSING;
211 	while (dp->ipmi_status & IPMI_BUSY)
212 		cv_wait(&dp->ipmi_cv, &sc->ipmi_lock);
213 	IPMI_UNLOCK(sc);
214 
215 	/* remove any requests in queue of stuff completed */
216 	while ((req = TAILQ_FIRST(&dp->ipmi_completed_requests)) != NULL) {
217 		TAILQ_REMOVE(&dp->ipmi_completed_requests, req, ir_link);
218 		ipmi_free_request(req);
219 	}
220 
221 	mutex_enter(&dev_list_lock);
222 	list_remove(&dev_list, dp);
223 	mutex_exit(&dev_list_lock);
224 	id_free(minor_ids, getminor(dev));
225 	cv_destroy(&dp->ipmi_cv);
226 	kmem_free(dp->ipmi_pollhead, sizeof (pollhead_t));
227 	kmem_free(dp, sizeof (ipmi_device_t));
228 
229 	return (0);
230 }
231 
232 /*ARGSUSED*/
233 static int
ipmi_ioctl(dev_t dv,int cmd,intptr_t data,int flags,cred_t * cr,int * rvalp)234 ipmi_ioctl(dev_t dv, int cmd, intptr_t data, int flags, cred_t *cr, int *rvalp)
235 {
236 	struct ipmi_device *dev;
237 	struct ipmi_request *kreq;
238 	struct ipmi_req req;
239 	struct ipmi_recv recv;
240 	struct ipmi_recv32 recv32;
241 	struct ipmi_addr addr;
242 	int error, len;
243 	model_t model;
244 	int orig_cmd = 0;
245 	uchar_t	t_lun;
246 
247 	if (secpolicy_sys_config(cr, B_FALSE) != 0)
248 		return (EPERM);
249 
250 	if ((dev = lookup_ipmidev_by_dev(dv)) == NULL)
251 		return (ENODEV);
252 
253 	model = get_udatamodel();
254 	if (model == DATAMODEL_NATIVE) {
255 		switch (cmd) {
256 		case IPMICTL_SEND_COMMAND:
257 			if (copyin((void *)data, &req, sizeof (req)))
258 				return (EFAULT);
259 			break;
260 		case IPMICTL_RECEIVE_MSG_TRUNC:
261 		case IPMICTL_RECEIVE_MSG:
262 			if (copyin((void *)data, &recv, sizeof (recv)))
263 				return (EFAULT);
264 			break;
265 		}
266 	} else {
267 		/* Convert 32-bit structures to native. */
268 		struct ipmi_req32 req32;
269 
270 		switch (cmd) {
271 		case IPMICTL_SEND_COMMAND_32:
272 			if (copyin((void *)data, &req32, sizeof (req32)))
273 				return (EFAULT);
274 
275 			req.addr = PTRIN(req32.addr);
276 			req.addr_len = req32.addr_len;
277 			req.msgid = req32.msgid;
278 			req.msg.netfn = req32.msg.netfn;
279 			req.msg.cmd = req32.msg.cmd;
280 			req.msg.data_len = req32.msg.data_len;
281 			req.msg.data = PTRIN(req32.msg.data);
282 
283 			cmd = IPMICTL_SEND_COMMAND;
284 			break;
285 
286 		case IPMICTL_RECEIVE_MSG_TRUNC_32:
287 		case IPMICTL_RECEIVE_MSG_32:
288 			if (copyin((void *)data, &recv32, sizeof (recv32)))
289 				return (EFAULT);
290 
291 			recv.addr = PTRIN(recv32.addr);
292 			recv.addr_len = recv32.addr_len;
293 			recv.msg.data_len = recv32.msg.data_len;
294 			recv.msg.data = PTRIN(recv32.msg.data);
295 
296 			orig_cmd = cmd;
297 			cmd = (cmd == IPMICTL_RECEIVE_MSG_TRUNC_32) ?
298 			    IPMICTL_RECEIVE_MSG_TRUNC : IPMICTL_RECEIVE_MSG;
299 			break;
300 		}
301 	}
302 
303 	switch (cmd) {
304 	case IPMICTL_SEND_COMMAND:
305 		/* Check that we didn't get a ridiculous length */
306 		if (req.msg.data_len > IPMI_MAX_RX)
307 			return (EINVAL);
308 
309 		kreq = ipmi_alloc_request(dev, req.msgid,
310 		    IPMI_ADDR(req.msg.netfn, 0), req.msg.cmd,
311 		    req.msg.data_len, IPMI_MAX_RX);
312 		/* This struct is the same for 32/64 */
313 		if (req.msg.data_len > 0 &&
314 		    copyin(req.msg.data, kreq->ir_request, req.msg.data_len)) {
315 			ipmi_free_request(kreq);
316 			return (EFAULT);
317 		}
318 		IPMI_LOCK(sc);
319 		dev->ipmi_requests++;
320 		error = sc->ipmi_enqueue_request(sc, kreq);
321 		IPMI_UNLOCK(sc);
322 		if (error)
323 			return (error);
324 		break;
325 
326 	case IPMICTL_RECEIVE_MSG_TRUNC:
327 	case IPMICTL_RECEIVE_MSG:
328 		/* This struct is the same for 32/64 */
329 		if (copyin(recv.addr, &addr, sizeof (addr)))
330 			return (EFAULT);
331 
332 		IPMI_LOCK(sc);
333 		kreq = TAILQ_FIRST(&dev->ipmi_completed_requests);
334 		if (kreq == NULL) {
335 			IPMI_UNLOCK(sc);
336 			return (EAGAIN);
337 		}
338 		addr.channel = IPMI_BMC_CHANNEL;
339 		recv.recv_type = IPMI_RESPONSE_RECV_TYPE;
340 		recv.msgid = kreq->ir_msgid;
341 		recv.msg.netfn = IPMI_REPLY_ADDR(kreq->ir_addr) >> 2;
342 		recv.msg.cmd = kreq->ir_command;
343 		error = kreq->ir_error;
344 		if (error) {
345 			TAILQ_REMOVE(&dev->ipmi_completed_requests, kreq,
346 			    ir_link);
347 			dev->ipmi_requests--;
348 			IPMI_UNLOCK(sc);
349 			ipmi_free_request(kreq);
350 			return (error);
351 		}
352 		len = kreq->ir_replylen + 1;
353 		if (recv.msg.data_len < len && cmd == IPMICTL_RECEIVE_MSG) {
354 			IPMI_UNLOCK(sc);
355 			return (EMSGSIZE);
356 		}
357 		TAILQ_REMOVE(&dev->ipmi_completed_requests, kreq, ir_link);
358 		dev->ipmi_requests--;
359 		IPMI_UNLOCK(sc);
360 		len = min(recv.msg.data_len, len);
361 		recv.msg.data_len = (unsigned short)len;
362 
363 		if (orig_cmd == IPMICTL_RECEIVE_MSG_TRUNC_32 ||
364 		    orig_cmd == IPMICTL_RECEIVE_MSG_32) {
365 			/* Update changed fields in 32-bit structure. */
366 			recv32.recv_type = recv.recv_type;
367 			recv32.msgid = (int32_t)recv.msgid;
368 			recv32.msg.netfn = recv.msg.netfn;
369 			recv32.msg.cmd = recv.msg.cmd;
370 			recv32.msg.data_len = recv.msg.data_len;
371 
372 			error = copyout(&recv32, (void *)data, sizeof (recv32));
373 		} else {
374 			error = copyout(&recv, (void *)data, sizeof (recv));
375 		}
376 
377 		/* This struct is the same for 32/64 */
378 		if (error == 0)
379 			error = copyout(&addr, recv.addr, sizeof (addr));
380 		if (error == 0)
381 			error = copyout(&kreq->ir_compcode, recv.msg.data, 1);
382 		if (error == 0)
383 			error = copyout(kreq->ir_reply, recv.msg.data + 1,
384 			    len - 1);
385 		ipmi_free_request(kreq);
386 
387 		if (error)
388 			return (EFAULT);
389 
390 		break;
391 
392 	case IPMICTL_SET_MY_ADDRESS_CMD:
393 		IPMI_LOCK(sc);
394 		if (copyin((void *)data, &dev->ipmi_address,
395 		    sizeof (dev->ipmi_address))) {
396 			IPMI_UNLOCK(sc);
397 			return (EFAULT);
398 		}
399 		IPMI_UNLOCK(sc);
400 		break;
401 
402 	case IPMICTL_GET_MY_ADDRESS_CMD:
403 		IPMI_LOCK(sc);
404 		if (copyout(&dev->ipmi_address, (void *)data,
405 		    sizeof (dev->ipmi_address))) {
406 			IPMI_UNLOCK(sc);
407 			return (EFAULT);
408 		}
409 		IPMI_UNLOCK(sc);
410 		break;
411 
412 	case IPMICTL_SET_MY_LUN_CMD:
413 		IPMI_LOCK(sc);
414 		if (copyin((void *)data, &t_lun, sizeof (t_lun))) {
415 			IPMI_UNLOCK(sc);
416 			return (EFAULT);
417 		}
418 		dev->ipmi_lun = t_lun & 0x3;
419 		IPMI_UNLOCK(sc);
420 		break;
421 
422 	case IPMICTL_GET_MY_LUN_CMD:
423 		IPMI_LOCK(sc);
424 		if (copyout(&dev->ipmi_lun, (void *)data,
425 		    sizeof (dev->ipmi_lun))) {
426 			IPMI_UNLOCK(sc);
427 			return (EFAULT);
428 		}
429 		IPMI_UNLOCK(sc);
430 		break;
431 
432 	case IPMICTL_SET_GETS_EVENTS_CMD:
433 		break;
434 
435 	case IPMICTL_REGISTER_FOR_CMD:
436 	case IPMICTL_UNREGISTER_FOR_CMD:
437 		return (EINVAL);
438 
439 	default:
440 		return (EINVAL);
441 	}
442 
443 	return (0);
444 }
445 
446 static int
ipmi_poll(dev_t dv,short events,int anyyet,short * reventsp,pollhead_t ** phpp)447 ipmi_poll(dev_t dv, short events, int anyyet, short *reventsp,
448     pollhead_t **phpp)
449 {
450 	struct ipmi_device *dev;
451 	short revent = 0;
452 
453 	if ((dev = lookup_ipmidev_by_dev(dv)) == NULL)
454 		return (ENODEV);
455 
456 	if (events & (POLLIN | POLLRDNORM)) {
457 		if (!TAILQ_EMPTY(&dev->ipmi_completed_requests))
458 			revent |= events & (POLLIN | POLLRDNORM);
459 		if (dev->ipmi_requests == 0)
460 			revent |= POLLERR;
461 	}
462 
463 	if (revent == 0) {
464 		/* nothing has occurred */
465 		if (!anyyet)
466 			*phpp = dev->ipmi_pollhead;
467 	}
468 
469 	*reventsp = revent;
470 	return (0);
471 }
472 
473 /*ARGSUSED*/
474 static int
ipmi_info(dev_info_t * dip,ddi_info_cmd_t cmd,void * arg,void ** resultp)475 ipmi_info(dev_info_t *dip, ddi_info_cmd_t cmd, void *arg, void **resultp)
476 {
477 	switch (cmd) {
478 	case DDI_INFO_DEVT2DEVINFO:
479 		*resultp = ipmi_dip;
480 		return (DDI_SUCCESS);
481 	case DDI_INFO_DEVT2INSTANCE:
482 		*resultp = NULL;
483 		return (DDI_SUCCESS);
484 	}
485 	return (DDI_FAILURE);
486 }
487 
488 static void
ipmi_cleanup(dev_info_t * dip)489 ipmi_cleanup(dev_info_t *dip)
490 {
491 	/* poke the taskq so that it can terminate */
492 	IPMI_LOCK(sc);
493 	sc->ipmi_detaching = 1;
494 	cv_signal(&sc->ipmi_request_added);
495 	IPMI_UNLOCK(sc);
496 
497 	ipmi_shutdown(sc);
498 	ddi_remove_minor_node(dip, NULL);
499 	ipmi_dip = NULL;
500 
501 	mutex_destroy(&dev_list_lock);
502 	list_destroy(&dev_list);
503 	id_space_destroy(minor_ids);
504 
505 	sc->ipmi_detaching = 0;
506 }
507 
508 static int
ipmi_attach(dev_info_t * dip,ddi_attach_cmd_t cmd)509 ipmi_attach(dev_info_t *dip, ddi_attach_cmd_t cmd)
510 {
511 	if (cmd != DDI_ATTACH)
512 		return (DDI_FAILURE);
513 
514 	/* this driver only supports one device instance */
515 	if (ddi_get_instance(dip) != 0) {
516 		cmn_err(CE_WARN,
517 		    "!not attaching to non-zero device instance %d",
518 		    ddi_get_instance(dip));
519 		return (DDI_FAILURE);
520 	}
521 
522 	if (get_smbios_ipmi_info() == DDI_FAILURE)
523 		return (DDI_FAILURE);
524 
525 	/*
526 	 * Support for the other types (SMIC, SSIF) should be added here.
527 	 */
528 	switch (sc->ipmi_io_type) {
529 	case SMB_IPMI_T_KCS:
530 		if (ipmi_kcs_attach(sc) != 0)
531 			return (DDI_FAILURE);
532 		break;
533 	default:
534 		return (DDI_FAILURE);
535 	}
536 	ipmi_found = B_TRUE;
537 
538 	if (ddi_create_minor_node(dip, "ipmi", S_IFCHR, 0, DDI_PSEUDO,
539 	    0) == DDI_FAILURE) {
540 		cmn_err(CE_WARN, "!attach could not create minor node");
541 		ddi_remove_minor_node(dip, NULL);
542 		return (DDI_FAILURE);
543 	}
544 
545 	ipmi_dip = dip;
546 
547 	list_create(&dev_list, sizeof (ipmi_device_t),
548 	    offsetof(ipmi_device_t, ipmi_node));
549 	mutex_init(&dev_list_lock, NULL, MUTEX_DRIVER, NULL);
550 
551 	/* Create ID space for open devs.  ID 0 is reserved. */
552 	minor_ids = id_space_create("ipmi_id_space", 1, 128);
553 
554 	if (ipmi_startup(sc) != B_TRUE) {
555 		ipmi_cleanup(dip);
556 		return (DDI_FAILURE);
557 	}
558 
559 	ipmi_attached = B_TRUE;
560 
561 	return (DDI_SUCCESS);
562 }
563 
564 static int
ipmi_detach(dev_info_t * dip,ddi_detach_cmd_t cmd)565 ipmi_detach(dev_info_t *dip, ddi_detach_cmd_t cmd)
566 {
567 	if (cmd != DDI_DETACH)
568 		return (DDI_FAILURE);
569 
570 	if (ipmi_found == B_FALSE)
571 		return (DDI_SUCCESS);
572 
573 	mutex_enter(&dev_list_lock);
574 	if (!list_is_empty(&dev_list)) {
575 		mutex_exit(&dev_list_lock);
576 		return (DDI_FAILURE);
577 	}
578 	mutex_exit(&dev_list_lock);
579 
580 	ipmi_cleanup(dip);
581 
582 	ipmi_attached = B_FALSE;
583 	return (DDI_SUCCESS);
584 }
585 
586 static struct cb_ops ipmi_cb_ops = {
587 	ipmi_open,
588 	ipmi_close,
589 	nodev,			/* strategy */
590 	nodev,			/* print */
591 	nodev,			/* dump */
592 	nodev,			/* read */
593 	nodev,			/* write */
594 	ipmi_ioctl,
595 	nodev,			/* devmap */
596 	nodev,			/* mmap */
597 	nodev,			/* segmap */
598 	ipmi_poll,
599 	ddi_prop_op,
600 	NULL,			/* streamtab */
601 	D_NEW | D_MP,		/* flags */
602 	CB_REV,
603 	nodev,			/* awread */
604 	nodev			/* awrite */
605 };
606 
607 static struct dev_ops ipmi_ops = {
608 	DEVO_REV,
609 	0,			/* reference count */
610 	ipmi_info,
611 	nulldev,		/* identify */
612 	nulldev,		/* probe */
613 	ipmi_attach,
614 	ipmi_detach,
615 	nodev,			/* reset */
616 	&ipmi_cb_ops,
617 	NULL,			/* bus ops */
618 	NULL,			/* power */
619 	ddi_quiesce_not_needed,
620 };
621 
622 static struct modldrv md = {
623 	&mod_driverops, "ipmi driver", &ipmi_ops
624 };
625 
626 static struct modlinkage ml = {
627 	MODREV_1, &md, NULL
628 };
629 
630 int
_init(void)631 _init(void)
632 {
633 	return (mod_install(&ml));
634 }
635 
636 int
_fini(void)637 _fini(void)
638 {
639 	return (mod_remove(&ml));
640 }
641 
642 int
_info(struct modinfo * mip)643 _info(struct modinfo *mip)
644 {
645 	return (mod_info(&ml, mip));
646 }
647