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 2017 Joyent, Inc.
  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
  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 == 0) {
  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 *
 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
 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
 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
 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
 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 && !anyyet) || (events & POLLET)) {
 464                 *phpp = dev->ipmi_pollhead;
 465         }
 466 
 467         *reventsp = revent;
 468         return (0);
 469 }
 470 
 471 /*ARGSUSED*/
 472 static int
 473 ipmi_info(dev_info_t *dip, ddi_info_cmd_t cmd, void *arg, void **resultp)
 474 {
 475         switch (cmd) {
 476         case DDI_INFO_DEVT2DEVINFO:
 477                 *resultp = ipmi_dip;
 478                 return (DDI_SUCCESS);
 479         case DDI_INFO_DEVT2INSTANCE:
 480                 *resultp = NULL;
 481                 return (DDI_SUCCESS);
 482         }
 483         return (DDI_FAILURE);
 484 }
 485 
 486 static void
 487 ipmi_cleanup(dev_info_t *dip)
 488 {
 489         /* poke the taskq so that it can terminate */
 490         IPMI_LOCK(sc);
 491         sc->ipmi_detaching = 1;
 492         cv_signal(&sc->ipmi_request_added);
 493         IPMI_UNLOCK(sc);
 494 
 495         ipmi_shutdown(sc);
 496         ddi_remove_minor_node(dip, NULL);
 497         ipmi_dip = NULL;
 498 
 499         mutex_destroy(&dev_list_lock);
 500         list_destroy(&dev_list);
 501         id_space_destroy(minor_ids);
 502 
 503         sc->ipmi_detaching = 0;
 504 }
 505 
 506 static int
 507 ipmi_attach(dev_info_t *dip, ddi_attach_cmd_t cmd)
 508 {
 509         if (cmd != DDI_ATTACH)
 510                 return (DDI_FAILURE);
 511 
 512         /* this driver only supports one device instance */
 513         if (ddi_get_instance(dip) != 0) {
 514                 cmn_err(CE_WARN,
 515                     "!not attaching to non-zero device instance %d",
 516                     ddi_get_instance(dip));
 517                 return (DDI_FAILURE);
 518         }
 519 
 520         if (get_smbios_ipmi_info() == DDI_FAILURE)
 521                 return (DDI_FAILURE);
 522 
 523         /*
 524          * Support for the other types (SMIC, SSIF) should be added here.
 525          */
 526         switch (sc->ipmi_io_type) {
 527         case SMB_IPMI_T_KCS:
 528                 if (ipmi_kcs_attach(sc) != 0)
 529                         return (DDI_FAILURE);
 530                 break;
 531         default:
 532                 return (DDI_FAILURE);
 533         }
 534         ipmi_found = B_TRUE;
 535 
 536         if (ddi_create_minor_node(dip, "ipmi", S_IFCHR, 0, DDI_PSEUDO,
 537             0) == DDI_FAILURE) {
 538                 cmn_err(CE_WARN, "!attach could not create minor node");
 539                 ddi_remove_minor_node(dip, NULL);
 540                 return (DDI_FAILURE);
 541         }
 542 
 543         ipmi_dip = dip;
 544 
 545         list_create(&dev_list, sizeof (ipmi_device_t),
 546             offsetof(ipmi_device_t, ipmi_node));
 547         mutex_init(&dev_list_lock, NULL, MUTEX_DRIVER, NULL);
 548 
 549         /* Create ID space for open devs.  ID 0 is reserved. */
 550         minor_ids = id_space_create("ipmi_id_space", 1, 128);
 551 
 552         if (ipmi_startup(sc) != B_TRUE) {
 553                 ipmi_cleanup(dip);
 554                 return (DDI_FAILURE);
 555         }
 556 
 557         ipmi_attached = B_TRUE;
 558 
 559         return (DDI_SUCCESS);
 560 }
 561 
 562 static int
 563 ipmi_detach(dev_info_t *dip, ddi_detach_cmd_t cmd)
 564 {
 565         if (cmd != DDI_DETACH)
 566                 return (DDI_FAILURE);
 567 
 568         if (ipmi_found == B_FALSE)
 569                 return (DDI_SUCCESS);
 570 
 571         mutex_enter(&dev_list_lock);
 572         if (!list_is_empty(&dev_list)) {
 573                 mutex_exit(&dev_list_lock);
 574                 return (DDI_FAILURE);
 575         }
 576         mutex_exit(&dev_list_lock);
 577 
 578         ipmi_cleanup(dip);
 579 
 580         ipmi_attached = B_FALSE;
 581         return (DDI_SUCCESS);
 582 }
 583 
 584 static struct cb_ops ipmi_cb_ops = {
 585         ipmi_open,
 586         ipmi_close,
 587         nodev,                  /* strategy */
 588         nodev,                  /* print */
 589         nodev,                  /* dump */
 590         nodev,                  /* read */
 591         nodev,                  /* write */
 592         ipmi_ioctl,
 593         nodev,                  /* devmap */
 594         nodev,                  /* mmap */
 595         nodev,                  /* segmap */
 596         ipmi_poll,
 597         ddi_prop_op,
 598         NULL,                   /* streamtab */
 599         D_NEW | D_MP,           /* flags */
 600         CB_REV,
 601         nodev,                  /* awread */
 602         nodev                   /* awrite */
 603 };
 604 
 605 static struct dev_ops ipmi_ops = {
 606         DEVO_REV,
 607         0,                      /* reference count */
 608         ipmi_info,
 609         nulldev,                /* identify */
 610         nulldev,                /* probe */
 611         ipmi_attach,
 612         ipmi_detach,
 613         nodev,                  /* reset */
 614         &ipmi_cb_ops,
 615         NULL,                   /* bus ops */
 616         NULL,                   /* power */
 617         ddi_quiesce_not_needed,
 618 };
 619 
 620 static struct modldrv md = {
 621         &mod_driverops, "ipmi driver", &ipmi_ops
 622 };
 623 
 624 static struct modlinkage ml = {
 625         MODREV_1, &md, NULL
 626 };
 627 
 628 int
 629 _init(void)
 630 {
 631         return (mod_install(&ml));
 632 }
 633 
 634 int
 635 _fini(void)
 636 {
 637         return (mod_remove(&ml));
 638 }
 639 
 640 int
 641 _info(struct modinfo *mip)
 642 {
 643         return (mod_info(&ml, mip));
 644 }