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 }