xref: /illumos-gate/usr/src/uts/i86pc/io/fipe/fipe_drv.c (revision 129b3e6c5b0ac55b5021a4c38db6387b6acdaaf1)
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 }
433