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  * Copyright (c) 2009, Intel Corporation.
  23  * All rights reserved.
  24  */
  25 
  26 #include <sys/conf.h>
  27 #include <sys/cmn_err.h>
  28 #include <sys/ddi.h>
  29 #include <sys/file.h>
  30 #include <sys/modctl.h>
  31 #include <sys/pci.h>
  32 #include <sys/policy.h>
  33 #include <sys/stat.h>
  34 #include <sys/sunddi.h>
  35 #include <sys/synch.h>
  36 #include <sys/fipe.h>
  37 
  38 /* Configurable through /etc/system. */
  39 int                     fipe_allow_attach = 1;
  40 int                     fipe_allow_detach = 1;
  41 
  42 static kmutex_t         fipe_drv_lock;
  43 static dev_info_t       *fipe_drv_dip;
  44 
  45 /*
  46  * PCI device ID for supported hardware.
  47  * For memory controller devices in Intel 5000/7300 series chipset, PCI vendor
  48  * id and PCI device id is read only, PCI subvendor id and PCI subsystem id is
  49  * write-once. So we could only rely on PCI vendor id and PCI device id here.
  50  * For all PCI functions (0,1,2,3) in device 0x10 on bus 0, they will have the
  51  * same PCI (vendor_id, device_id, subvendor_id, subsystem_id, class_id).
  52  * We only need to access PCI device (0, 0x10, 1), all other PCI functions will
  53  * be filtered out by unit address.
  54  */
  55 static struct fipe_pci_id {
  56         uint16_t                venid;
  57         uint16_t                devid;
  58         uint16_t                subvenid;
  59         uint16_t                subsysid;
  60         char                    *unitaddr;
  61 } fipe_mc_pciids[] = {
  62         { 0x8086, 0x25f0, 0xffff, 0xffff, "10,1" },     /* Intel 5000P/V/X/Z */
  63         { 0x8086, 0x360c, 0xffff, 0xffff, "10,1" }      /* Intel 7300 NB */
  64 };
  65 
  66 /*ARGSUSED*/
  67 static int
  68 fipe_open(dev_t *devp, int flag, int otyp, cred_t *credp)
  69 {
  70         if (otyp != OTYP_CHR) {
  71                 cmn_err(CE_NOTE, "!fipe: invalid otyp %d in open.", otyp);
  72                 return (EINVAL);
  73         }
  74 
  75         return (0);
  76 }
  77 
  78 /*ARGSUSED*/
  79 static int
  80 fipe_close(dev_t dev, int flag, int otyp, cred_t *credp)
  81 {
  82         return (0);
  83 }
  84 
  85 /*ARGSUSED*/
  86 static int
  87 fipe_ioctl(dev_t dev, int cmd, intptr_t arg, int mode, cred_t *credp,
  88     int *rvalp)
  89 {
  90         int rc = 0;
  91         fipe_pm_policy_t policy;
  92 
  93         /* First check permission. */
  94         if (secpolicy_power_mgmt(credp) != 0) {
  95                 return (EPERM);
  96         }
  97 
  98         switch (cmd) {
  99         case FIPE_IOCTL_START:
 100                 if ((mode & FWRITE) == 0) {
 101                         rc = EBADF;
 102                 } else {
 103                         mutex_enter(&fipe_drv_lock);
 104                         rc = fipe_start();
 105                         mutex_exit(&fipe_drv_lock);
 106                         rc =  (rc == 0) ? 0 : ENXIO;
 107                 }
 108                 break;
 109 
 110         case FIPE_IOCTL_STOP:
 111                 if ((mode & FWRITE) == 0) {
 112                         rc = EBADF;
 113                 } else {
 114                         mutex_enter(&fipe_drv_lock);
 115                         rc = fipe_stop();
 116                         mutex_exit(&fipe_drv_lock);
 117                         rc =  (rc == 0) ? 0 : ENXIO;
 118                 }
 119                 break;
 120 
 121         case FIPE_IOCTL_GET_PMPOLICY:
 122                 if ((mode & FREAD) == 0) {
 123                         rc = EBADF;
 124                 } else {
 125                         mutex_enter(&fipe_drv_lock);
 126                         policy = fipe_get_pmpolicy();
 127                         mutex_exit(&fipe_drv_lock);
 128                         rc = ddi_copyout(&policy, (void *)arg,
 129                             sizeof (policy), mode);
 130                         rc = (rc >= 0) ? 0 : EFAULT;
 131                 }
 132                 break;
 133 
 134         case FIPE_IOCTL_SET_PMPOLICY:
 135                 if ((mode & FWRITE) == 0) {
 136                         rc = EBADF;
 137                 } else {
 138                         mutex_enter(&fipe_drv_lock);
 139                         rc = fipe_set_pmpolicy((fipe_pm_policy_t)arg);
 140                         mutex_exit(&fipe_drv_lock);
 141                         rc =  (rc == 0) ? 0 : ENXIO;
 142                 }
 143                 break;
 144 
 145         default:
 146                 cmn_err(CE_NOTE, "!fipe: unknown ioctl command %d.", cmd);
 147                 rc = ENOTSUP;
 148                 break;
 149         }
 150 
 151         return (rc);
 152 }
 153 
 154 /*ARGSUSED*/
 155 static int
 156 fipe_getinfo(dev_info_t *dip, ddi_info_cmd_t infocmd, void *arg, void **result)
 157 {
 158         switch (infocmd) {
 159         case DDI_INFO_DEVT2DEVINFO:
 160                 if (fipe_drv_dip != NULL) {
 161                         *result = fipe_drv_dip;
 162                         return (DDI_SUCCESS);
 163                 } else {
 164                         *result = NULL;
 165                         return (DDI_FAILURE);
 166                 }
 167 
 168         case DDI_INFO_DEVT2INSTANCE:
 169                 if (fipe_drv_dip != NULL) {
 170                         *result = (void *)(uintptr_t)
 171                             ddi_get_instance(fipe_drv_dip);
 172                         return (DDI_SUCCESS);
 173                 } else {
 174                         *result = NULL;
 175                         return (DDI_FAILURE);
 176                 }
 177 
 178         default:
 179                 *result = NULL;
 180                 return (DDI_FAILURE);
 181         }
 182 }
 183 
 184 /* Validate whether it's supported hardware. */
 185 static int
 186 fipe_validate_dip(dev_info_t *dip)
 187 {
 188         int i, rc = -1;
 189         char *unitaddr;
 190         struct fipe_pci_id *ip;
 191         ddi_acc_handle_t handle;
 192         uint16_t venid, devid, subvenid, subsysid;
 193 
 194         /* Get device unit address, it's "devid,funcid" in hexadecimal. */
 195         if (ddi_prop_lookup_string(DDI_DEV_T_ANY, dip, DDI_PROP_DONTPASS,
 196             "unit-address", &unitaddr) != DDI_PROP_SUCCESS) {
 197                 cmn_err(CE_CONT, "?fipe: failed to get deivce unit address.");
 198                 return (-1);
 199         }
 200         if (pci_config_setup(dip, &handle) != DDI_SUCCESS) {
 201                 cmn_err(CE_CONT, "?fipe: failed to setup pcicfg handler.");
 202                 ddi_prop_free(unitaddr);
 203                 return (-1);
 204         }
 205         venid = pci_config_get16(handle, PCI_CONF_VENID);
 206         devid = pci_config_get16(handle, PCI_CONF_DEVID);
 207         subvenid = pci_config_get16(handle, PCI_CONF_SUBVENID);
 208         subsysid = pci_config_get16(handle, PCI_CONF_SUBSYSID);
 209 
 210         /* Validate device. */
 211         for (rc = -1, i = 0, ip = &fipe_mc_pciids[0];
 212             i < sizeof (fipe_mc_pciids) / sizeof (fipe_mc_pciids[0]);
 213             i++, ip++) {
 214                 if ((ip->venid == 0xffffu || ip->venid == venid) &&
 215                     (ip->devid == 0xffffu || ip->devid == devid) &&
 216                     (ip->subvenid == 0xffffu || ip->subvenid == subvenid) &&
 217                     (ip->subsysid == 0xffffu || ip->subsysid == subsysid) &&
 218                     (ip->unitaddr == NULL ||
 219                     strcmp(ip->unitaddr, unitaddr) == 0)) {
 220                         rc = 0;
 221                         break;
 222                 }
 223         }
 224 
 225         pci_config_teardown(&handle);
 226         ddi_prop_free(unitaddr);
 227 
 228         return (rc);
 229 }
 230 
 231 static int
 232 fipe_attach(dev_info_t *dip, ddi_attach_cmd_t cmd)
 233 {
 234         char *ptr;
 235         int ignore = 0, rc = DDI_FAILURE;
 236 
 237         mutex_enter(&fipe_drv_lock);
 238         switch (cmd) {
 239         case DDI_ATTACH:
 240                 /* Check whether it has been disabled by user. */
 241                 if (ddi_prop_lookup_string(DDI_DEV_T_ANY, dip, 0,
 242                     "disable_fipe_pm", &ptr) == DDI_SUCCESS) {
 243                         if (strcasecmp(ptr, "true") == 0 ||
 244                             strcasecmp(ptr, "yes") == 0) {
 245                                 fipe_allow_attach = 0;
 246                         }
 247                         ddi_prop_free(ptr);
 248                 }
 249                 if (fipe_allow_attach == 0) {
 250                         cmn_err(CE_WARN,
 251                             "fipe: driver has been disabled by user.");
 252                         ignore = 1;
 253                         break;
 254                 }
 255 
 256                 /* Filter out unwanted PCI functions. */
 257                 if ((ignore = fipe_validate_dip(dip)) != 0) {
 258                         break;
 259                 /* There should be only one MC device in system. */
 260                 } else if (fipe_drv_dip != NULL) {
 261                         cmn_err(CE_NOTE,
 262                             "!fipe: more than one hardware instances found.");
 263                         break;
 264                 }
 265                 fipe_drv_dip = dip;
 266 
 267                 /* Initialize and start power management subsystem. */
 268                 if (fipe_init(fipe_drv_dip) != 0) {
 269                         fipe_drv_dip = NULL;
 270                         break;
 271                 } else if (fipe_start() != 0) {
 272                         (void) fipe_fini();
 273                         fipe_drv_dip = NULL;
 274                         break;
 275                 }
 276 
 277                 /* Ignore error from creating minor node. */
 278                 if (ddi_create_minor_node(dip, "fipe", S_IFCHR, 0,
 279                     "ddi_mem_pm", 0) != DDI_SUCCESS) {
 280                         cmn_err(CE_CONT,
 281                             "?fipe: failed to create device minor node.\n");
 282                 }
 283 
 284                 rc = DDI_SUCCESS;
 285                 break;
 286 
 287         case DDI_RESUME:
 288                 if (fipe_resume() == 0) {
 289                         rc = DDI_SUCCESS;
 290                 }
 291                 break;
 292 
 293         default:
 294                 break;
 295         }
 296         mutex_exit(&fipe_drv_lock);
 297 
 298         if (ignore == 0 && rc != DDI_SUCCESS) {
 299                 cmn_err(CE_NOTE, "!fipe: failed to attach or resume device.");
 300         }
 301 
 302         return (rc);
 303 }
 304 
 305 /*ARGSUSED*/
 306 static int
 307 fipe_detach(dev_info_t *dip, ddi_detach_cmd_t cmd)
 308 {
 309         int rc = DDI_FAILURE;
 310 
 311         mutex_enter(&fipe_drv_lock);
 312         switch (cmd) {
 313         case DDI_DETACH:
 314                 if (fipe_allow_detach == 0 || dip != fipe_drv_dip) {
 315                         break;
 316                 }
 317                 if (fipe_stop() != 0) {
 318                         break;
 319                 } else if (fipe_fini() != 0) {
 320                         (void) fipe_start();
 321                         break;
 322                 }
 323                 ddi_remove_minor_node(dip, NULL);
 324                 fipe_drv_dip = NULL;
 325                 rc = DDI_SUCCESS;
 326                 break;
 327 
 328         case DDI_SUSPEND:
 329                 if (fipe_suspend() == 0) {
 330                         rc = DDI_SUCCESS;
 331                 }
 332                 break;
 333 
 334         default:
 335                 break;
 336         }
 337         mutex_exit(&fipe_drv_lock);
 338 
 339         if (rc != DDI_SUCCESS) {
 340                 cmn_err(CE_NOTE, "!fipe: failed to detach or suspend device.");
 341         }
 342 
 343         return (rc);
 344 }
 345 
 346 static int
 347 fipe_quiesce(dev_info_t *dip)
 348 {
 349         if (dip != fipe_drv_dip) {
 350                 return (DDI_SUCCESS);
 351         }
 352         /* Quiesce hardware by stopping power management subsystem. */
 353         if (fipe_suspend() != 0) {
 354                 cmn_err(CE_NOTE, "!fipe: failed to quiesce device.");
 355                 return (DDI_FAILURE);
 356         }
 357 
 358         return (DDI_SUCCESS);
 359 }
 360 
 361 static struct cb_ops fipe_cb_ops = {
 362         fipe_open,
 363         fipe_close,
 364         nodev,          /* not a block driver */
 365         nodev,          /* no print routine */
 366         nodev,          /* no dump routine */
 367         nodev,          /* no read routine */
 368         nodev,          /* no write routine */
 369         fipe_ioctl,
 370         nodev,          /* no devmap routine */
 371         nodev,          /* no mmap routine */
 372         nodev,          /* no segmap routine */
 373         nochpoll,       /* no chpoll routine */
 374         ddi_prop_op,
 375         0,              /* not a STREAMS driver */
 376         D_NEW | D_MP,   /* safe for multi-thread/multi-processor */
 377 };
 378 
 379 static struct dev_ops fipe_ops = {
 380         DEVO_REV,               /* devo_rev */
 381         0,                      /* devo_refcnt */
 382         fipe_getinfo,           /* devo_getinfo */
 383         nulldev,                /* devo_identify */
 384         nulldev,                /* devo_probe */
 385         fipe_attach,            /* devo_attach */
 386         fipe_detach,            /* devo_detach */
 387         nodev,                  /* devo_reset */
 388         &fipe_cb_ops,               /* devo_cb_ops */
 389         NULL,                   /* devo_bus_ops */
 390         NULL,                   /* devo_power */
 391         &fipe_quiesce,              /* devo_quiesce */
 392 };
 393 
 394 static struct modldrv modldrv = {
 395         &mod_driverops,
 396         "Intel 5000/7300 memory controller driver",
 397         &fipe_ops
 398 };
 399 
 400 static struct modlinkage modlinkage = {
 401         MODREV_1,
 402         (void *)&modldrv,
 403         NULL
 404 };
 405 
 406 int
 407 _init(void)
 408 {
 409         fipe_drv_dip = NULL;
 410         mutex_init(&fipe_drv_lock, NULL, MUTEX_DRIVER, NULL);
 411 
 412         return (mod_install(&modlinkage));
 413 }
 414 
 415 int
 416 _info(struct modinfo *modinfop)
 417 {
 418         return (mod_info(&modlinkage, modinfop));
 419 }
 420 
 421 int
 422 _fini(void)
 423 {
 424         int err;
 425 
 426         if ((err = mod_remove(&modlinkage)) == 0) {
 427                 mutex_destroy(&fipe_drv_lock);
 428                 fipe_drv_dip = NULL;
 429         }
 430 
 431         return (err);
 432 }