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
fipe_open(dev_t * devp,int flag,int otyp,cred_t * credp)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
fipe_close(dev_t dev,int flag,int otyp,cred_t * credp)80 fipe_close(dev_t dev, int flag, int otyp, cred_t *credp)
81 {
82 return (0);
83 }
84
85 /*ARGSUSED*/
86 static int
fipe_ioctl(dev_t dev,int cmd,intptr_t arg,int mode,cred_t * credp,int * rvalp)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
fipe_getinfo(dev_info_t * dip,ddi_info_cmd_t infocmd,void * arg,void ** result)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
fipe_validate_dip(dev_info_t * dip)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
fipe_attach(dev_info_t * dip,ddi_attach_cmd_t cmd)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
fipe_detach(dev_info_t * dip,ddi_detach_cmd_t cmd)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
fipe_quiesce(dev_info_t * dip)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
_init(void)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
_info(struct modinfo * modinfop)416 _info(struct modinfo *modinfop)
417 {
418 return (mod_info(&modlinkage, modinfop));
419 }
420
421 int
_fini(void)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 }
433