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 2010 Sun Microsystems, Inc.  All rights reserved.
23  * Use is subject to license terms.
24  */
25 
26 #include <sys/types.h>
27 #include <sys/stat.h>
28 #include <sys/cpuvar.h>
29 #include <sys/conf.h>
30 #include <sys/kmem.h>
31 #include <sys/async.h>
32 #include <sys/sysmacros.h>
33 #include <sys/sunddi.h>
34 #include <sys/sunndi.h>
35 #include <sys/ddi_impldefs.h>
36 #include <sys/open.h>
37 #include <sys/errno.h>
38 #include <sys/file.h>
39 #include <sys/policy.h>
40 #include <sys/pci_tools.h>
41 #include <sys/pci_impl.h>
42 #include <sys/hypervisor_api.h>
43 #include <sys/hotplug/pci/pcihp.h>
44 #include "niumx_var.h"
45 
46 /*
47  * NIUMX PCITool interface
48  */
49 /*LINTLIBRARY*/
50 
51 static int niumx_open(dev_t *devp, int flags, int otyp, cred_t *credp);
52 static int niumx_close(dev_t dev, int flags, int otyp, cred_t *credp);
53 static int niumx_ioctl(dev_t dev, int cmd, intptr_t arg, int mode,
54 						cred_t *credp, int *rvalp);
55 static int niumx_prop_op(dev_t dev, dev_info_t *dip, ddi_prop_op_t prop_op,
56     int flags, char *name, caddr_t valuep, int *lengthp);
57 
58 struct cb_ops niumx_cb_ops = {
59 	niumx_open,			/* open */
60 	niumx_close,			/* close */
61 	nodev,				/* strategy */
62 	nodev,				/* print */
63 	nodev,				/* dump */
64 	nodev,				/* read */
65 	nodev,				/* write */
66 	niumx_ioctl,			/* ioctl */
67 	nodev,				/* devmap */
68 	nodev,				/* mmap */
69 	nodev,				/* segmap */
70 	nochpoll,			/* poll */
71 	niumx_prop_op,			/* cb_prop_op */
72 	NULL,				/* streamtab */
73 	D_NEW | D_MP | D_HOTPLUG,	/* Driver compatibility flag */
74 	CB_REV,				/* rev */
75 	nodev,				/* int (*cb_aread)() */
76 	nodev				/* int (*cb_awrite)() */
77 };
78 
79 static void niumxtool_fill_in_intr_devs(pcitool_intr_dev_t *dev,
80     char *driver_name, char *path_name, int instance);
81 
82 static int niumxtool_intr(dev_info_t *dip, void *arg, int cmd, int mode);
83 
84 int niumx_set_intr_target(niumx_devstate_t *niumxds_p, niudevino_t ino,
85     niucpuid_t cpu_id);
86 
87 extern void *niumx_state;
88 /* ARGSUSED3 */
89 static int
90 niumx_open(dev_t *devp, int flags, int otyp, cred_t *credp)
91 {
92 	niumx_devstate_t *niumxds_p;
93 	minor_t minor = getminor(*devp);
94 
95 	/*
96 	 * Make sure the open is for the right file type.
97 	 */
98 	if (otyp != OTYP_CHR)
99 		return (EINVAL);
100 
101 	/*
102 	 * Get the soft state structure for the device.
103 	 */
104 	niumxds_p = (niumx_devstate_t *)ddi_get_soft_state(niumx_state,
105 	    PCI_MINOR_NUM_TO_INSTANCE(minor));
106 	if (niumxds_p == NULL)
107 		return (ENXIO);
108 
109 	/*
110 	 * Handle the open by tracking the device state.
111 	 */
112 	mutex_enter(&niumxds_p->niumx_mutex);
113 	if (flags & FEXCL) {
114 		if (niumxds_p->niumx_soft_state != NIUMX_SOFT_STATE_CLOSED) {
115 			mutex_exit(&niumxds_p->niumx_mutex);
116 			return (EBUSY);
117 		}
118 		niumxds_p->niumx_soft_state = NIUMX_SOFT_STATE_OPEN_EXCL;
119 	} else {
120 		if (niumxds_p->niumx_soft_state == NIUMX_SOFT_STATE_OPEN_EXCL) {
121 			mutex_exit(&niumxds_p->niumx_mutex);
122 			return (EBUSY);
123 		}
124 		niumxds_p->niumx_soft_state = NIUMX_SOFT_STATE_OPEN;
125 	}
126 
127 	niumxds_p->niumx_open_count++;
128 	mutex_exit(&niumxds_p->niumx_mutex);
129 	return (0);
130 }
131 
132 /* ARGSUSED */
133 static int
134 niumx_close(dev_t dev, int flags, int otyp, cred_t *credp)
135 {
136 	niumx_devstate_t *niumxds_p;
137 	minor_t minor = getminor(dev);
138 
139 	if (otyp != OTYP_CHR)
140 		return (EINVAL);
141 
142 	/*
143 	 * Get the soft state structure for the device.
144 	 */
145 	niumxds_p = (niumx_devstate_t *)ddi_get_soft_state(niumx_state,
146 	    PCI_MINOR_NUM_TO_INSTANCE(minor));
147 
148 	if (niumxds_p == NULL)
149 		return (ENXIO);
150 
151 	mutex_enter(&niumxds_p->niumx_mutex);
152 
153 	niumxds_p->niumx_soft_state = NIUMX_SOFT_STATE_CLOSED;
154 	niumxds_p->niumx_open_count = 0;
155 	mutex_exit(&niumxds_p->niumx_mutex);
156 	return (0);
157 }
158 
159 /* ARGSUSED */
160 int
161 niumx_ioctl(dev_t dev, int cmd, intptr_t arg, int mode, cred_t *credp,
162 	int *rvalp)
163 {
164 	niumx_devstate_t *niumxds_p;
165 	dev_info_t *dip;
166 	int rv = DDI_SUCCESS;
167 	int minor = getminor(dev);
168 
169 	/*
170 	 * Get the soft state structure for the device.
171 	 */
172 	niumxds_p = (niumx_devstate_t *)ddi_get_soft_state(niumx_state,
173 	    PCI_MINOR_NUM_TO_INSTANCE(minor));
174 
175 	if (niumxds_p == NULL) {
176 		return (ENXIO);
177 	}
178 
179 	dip = niumxds_p->dip;
180 
181 	switch (minor & 0xff) {
182 
183 	/*
184 	 * PCI tools.
185 	 */
186 
187 	case PCI_TOOL_INTR_MINOR_NUM:
188 
189 		switch (cmd) {
190 		case PCITOOL_DEVICE_SET_INTR:
191 
192 			/* Require full privileges. */
193 			if (secpolicy_kmdb(credp)) {
194 				rv = EPERM;
195 				break;
196 			}
197 
198 		/*FALLTHRU*/
199 		/* These require no special privileges. */
200 		case PCITOOL_DEVICE_GET_INTR:
201 		case PCITOOL_SYSTEM_INTR_INFO:
202 			rv = niumxtool_intr(dip, (void *)arg, cmd, mode);
203 			break;
204 
205 		default:
206 			rv = ENOTTY;
207 		}
208 		return (rv);
209 
210 	default:
211 		break;
212 	}
213 	return (rv);
214 }
215 
216 static int niumx_prop_op(dev_t dev, dev_info_t *dip, ddi_prop_op_t prop_op,
217     int flags, char *name, caddr_t valuep, int *lengthp)
218 {
219 	return (ddi_prop_op(dev, dip, prop_op, flags, name, valuep, lengthp));
220 }
221 
222 int
223 niumxtool_init(dev_info_t *dip)
224 {
225 	int instance = ddi_get_instance(dip);
226 
227 	if (ddi_create_minor_node(dip, PCI_MINOR_INTR, S_IFCHR,
228 	    PCI_MINOR_NUM(instance, PCI_TOOL_INTR_MINOR_NUM),
229 	    DDI_NT_INTRCTL, 0) != DDI_SUCCESS) {
230 		ddi_remove_minor_node(dip, PCI_MINOR_REG);
231 		return (DDI_FAILURE);
232 	}
233 
234 	return (DDI_SUCCESS);
235 }
236 
237 void
238 niumxtool_uninit(dev_info_t *dip)
239 {
240 	ddi_remove_minor_node(dip, PCI_MINOR_INTR);
241 }
242 
243 static void
244 niumxtool_fill_in_intr_devs(pcitool_intr_dev_t *dev, char *driver_name,
245     char *path_name, int instance)
246 {
247 	(void) strncpy(dev->driver_name, driver_name, MAXMODCONFNAME-1);
248 	dev->driver_name[MAXMODCONFNAME] = '\0';
249 	(void) strncpy(dev->path, path_name, MAXPATHLEN-1);
250 	dev->dev_inst = instance;
251 }
252 
253 /*ARGSUSED*/
254 static int
255 niumxtool_intr_info(dev_info_t *dip, void *arg, int mode)
256 {
257 	pcitool_intr_info_t intr_info;
258 	int rval = DDI_SUCCESS;
259 
260 	/* If we need user_version, and to ret same user version as passed in */
261 	if (ddi_copyin(arg, &intr_info, sizeof (pcitool_intr_info_t), mode) !=
262 	    DDI_SUCCESS) {
263 		return (EFAULT);
264 	}
265 
266 	intr_info.ctlr_version = 0;	/* XXX how to get real version? */
267 	intr_info.ctlr_type = PCITOOL_CTLR_TYPE_RISC;
268 	if (intr_info.flags & PCITOOL_INTR_FLAG_GET_MSI)
269 		intr_info.num_intr = 0;
270 	else
271 		intr_info.num_intr = NIUMX_MAX_INTRS;
272 
273 	intr_info.drvr_version = PCITOOL_VERSION;
274 	if (ddi_copyout(&intr_info, arg, sizeof (pcitool_intr_info_t), mode) !=
275 	    DDI_SUCCESS) {
276 		rval = EFAULT;
277 	}
278 
279 	return (rval);
280 }
281 
282 
283 /*
284  * Get interrupt information for a given ino.
285  * Returns info only for inos mapped to devices.
286  *
287  * Returned info is valid only when iget.num_devs is returned > 0.
288  * If ino is not enabled or is not mapped to a device,
289  * iget.num_devs will be returned as = 0.
290  */
291 /*ARGSUSED*/
292 static int
293 niumxtool_get_intr(dev_info_t *dip, void *arg, int mode)
294 {
295 	/* Array part isn't used here, but oh well... */
296 	pcitool_intr_get_t partial_iget;
297 	pcitool_intr_get_t *iget_p = &partial_iget;
298 	int copyout_rval;
299 	niusysino_t sysino;
300 	niucpuid_t cpu_id;
301 	niumx_devstate_t *niumxds_p;
302 	dev_info_t *ih_dip;
303 	size_t	iget_kmem_alloc_size = 0;
304 	char pathname[MAXPATHLEN];
305 	int rval = EIO;
306 
307 	niumxds_p = (niumx_devstate_t *)
308 	    ddi_get_soft_state(niumx_state, ddi_get_instance(dip));
309 
310 	/* Read in just the header part, no array section. */
311 	if (ddi_copyin(arg, &partial_iget, PCITOOL_IGET_SIZE(0), mode) !=
312 	    DDI_SUCCESS)
313 		return (EFAULT);
314 
315 	iget_p->status = PCITOOL_IO_ERROR;
316 	iget_p->msi = (uint32_t)-1;
317 
318 	if (iget_p->flags & PCITOOL_INTR_FLAG_GET_MSI) {
319 		iget_p->status = PCITOOL_INVALID_MSI;
320 		rval = EINVAL;
321 		goto done_get_intr;
322 	}
323 
324 	/* Validate argument. */
325 	if (iget_p->ino > NIUMX_MAX_INTRS) {
326 		iget_p->status = PCITOOL_INVALID_INO;
327 		rval = EINVAL;
328 		goto done_get_intr;
329 	}
330 
331 	/* Caller wants device information returned. */
332 	if (iget_p->num_devs_ret > 0) {
333 		/*
334 		 * Allocate room.
335 		 * Note if num_devs == 0 iget_p remains pointing to
336 		 * partial_iget.
337 		 */
338 		iget_kmem_alloc_size = PCITOOL_IGET_SIZE(iget_p->num_devs_ret);
339 		iget_p = kmem_zalloc(iget_kmem_alloc_size, KM_SLEEP);
340 
341 		/* Read in whole structure to verify there's room. */
342 		if (ddi_copyin(arg, iget_p, iget_kmem_alloc_size, mode) !=
343 		    DDI_SUCCESS) {
344 
345 			/* Be consistent and just return EFAULT here. */
346 			kmem_free(iget_p, iget_kmem_alloc_size);
347 
348 			return (EFAULT);
349 		}
350 	}
351 
352 	sysino = niumxds_p->niumx_ihtable[iget_p->ino].ih_sysino;
353 	if (sysino == 0) {
354 		iget_p->status = PCITOOL_IO_ERROR;
355 		rval = EIO;
356 		goto done_get_intr;
357 	}
358 
359 	ih_dip = niumxds_p->niumx_ihtable[iget_p->ino].ih_dip;
360 
361 	ddi_pathname(ih_dip, pathname);
362 
363 	niumxtool_fill_in_intr_devs(&iget_p->dev[0],
364 	    (char *)ddi_driver_name(ih_dip),  pathname,
365 	    ddi_get_instance(ih_dip));
366 
367 	if (hvio_intr_gettarget(sysino, &cpu_id) != H_EOK) {
368 		iget_p->status = PCITOOL_IO_ERROR;
369 		rval = EIO;
370 		goto done_get_intr;
371 	}
372 	if (niumxds_p->niumx_ihtable[iget_p->ino].ih_cpuid != cpu_id) {
373 		cmn_err(CE_WARN, "CPU Does not match %x %x", cpu_id,
374 		    niumxds_p->niumx_ihtable[iget_p->ino].ih_cpuid);
375 		iget_p->status = PCITOOL_IO_ERROR;
376 		rval = EIO;
377 		goto done_get_intr;
378 	}
379 	iget_p->num_devs = 1;
380 	iget_p->cpu_id = niumxds_p->niumx_ihtable[iget_p->ino].ih_cpuid;
381 	iget_p->status = PCITOOL_SUCCESS;
382 	rval = DDI_SUCCESS;
383 
384 done_get_intr:
385 	iget_p->drvr_version = PCITOOL_VERSION;
386 	copyout_rval =
387 	    ddi_copyout(iget_p, arg, PCITOOL_IGET_SIZE(iget_p->num_devs_ret),
388 	    mode);
389 
390 	if (iget_kmem_alloc_size > 0)
391 		kmem_free(iget_p, iget_kmem_alloc_size);
392 
393 	if (copyout_rval != DDI_SUCCESS)
394 		rval = EFAULT;
395 
396 	return (rval);
397 }
398 
399 
400 /*
401  * Associate a new CPU with a given ino.
402  *
403  * Operate only on inos which are already mapped to devices.
404  */
405 static int
406 niumxtool_set_intr(dev_info_t *dip, void *arg, int mode)
407 {
408 	pcitool_intr_set_t iset;
409 	niucpuid_t old_cpu_id;
410 	int rval = EIO;
411 	int ret = DDI_SUCCESS;
412 	size_t copyinout_size;
413 	niumx_devstate_t *niumxds_p;
414 
415 	niumxds_p = (niumx_devstate_t *)
416 	    ddi_get_soft_state(niumx_state, ddi_get_instance(dip));
417 
418 	bzero(&iset, sizeof (pcitool_intr_set_t));
419 
420 	/* Version 1 of pcitool_intr_set_t doesn't have flags. */
421 	copyinout_size = (size_t)&iset.flags - (size_t)&iset;
422 
423 	if (ddi_copyin(arg, &iset, copyinout_size, mode) != DDI_SUCCESS)
424 		return (EFAULT);
425 
426 	switch (iset.user_version) {
427 	case PCITOOL_V1:
428 		break;
429 
430 	case PCITOOL_V2:
431 		copyinout_size = sizeof (pcitool_intr_set_t);
432 		if (ddi_copyin(arg, &iset, copyinout_size, mode) != DDI_SUCCESS)
433 			return (EFAULT);
434 		break;
435 
436 	default:
437 		iset.status = PCITOOL_OUT_OF_RANGE;
438 		rval = ENOTSUP;
439 		goto done_set_intr;
440 	}
441 
442 	if (iset.flags & PCITOOL_INTR_FLAG_SET_GROUP) {
443 		iset.status = PCITOOL_IO_ERROR;
444 		rval = ENOTSUP;
445 		goto done_set_intr;
446 	}
447 
448 	iset.status = PCITOOL_IO_ERROR;
449 
450 	iset.msi = (uint32_t)-1;
451 
452 	/* Validate input argument. */
453 	if (iset.ino > NIUMX_MAX_INTRS) {
454 		iset.status = PCITOOL_INVALID_INO;
455 		rval = EINVAL;
456 		goto done_set_intr;
457 	}
458 
459 	old_cpu_id = niumxds_p->niumx_ihtable[iset.ino].ih_cpuid;
460 
461 	if ((ret = niumx_set_intr_target(niumxds_p, iset.ino,
462 	    iset.cpu_id)) == DDI_SUCCESS) {
463 		iset.cpu_id = old_cpu_id;
464 		iset.status = PCITOOL_SUCCESS;
465 		rval = DDI_SUCCESS;
466 		goto done_set_intr;
467 	}
468 
469 	switch (ret) {
470 	case DDI_EPENDING:
471 		iset.status = PCITOOL_PENDING_INTRTIMEOUT;
472 		rval = ETIME;
473 		break;
474 	case DDI_EINVAL:
475 		iset.status = PCITOOL_INVALID_CPUID;
476 		rval = EINVAL;
477 		break;
478 	default:
479 		iset.status = PCITOOL_IO_ERROR;
480 		rval = EIO;
481 		break;
482 	}
483 
484 done_set_intr:
485 	iset.drvr_version = PCITOOL_VERSION;
486 	if (ddi_copyout(&iset, arg, copyinout_size, mode) != DDI_SUCCESS)
487 		rval = EFAULT;
488 
489 	return (rval);
490 }
491 
492 
493 /* Main function for handling interrupt CPU binding requests and queries. */
494 static int
495 niumxtool_intr(dev_info_t *dip, void *arg, int cmd, int mode)
496 {
497 
498 	int rval = DDI_SUCCESS;
499 
500 	switch (cmd) {
501 
502 	/* Get system interrupt information. */
503 	case PCITOOL_SYSTEM_INTR_INFO:
504 		rval = niumxtool_intr_info(dip, arg, mode);
505 		break;
506 
507 	/* Get interrupt information for a given ino. */
508 	case PCITOOL_DEVICE_GET_INTR:
509 		rval = niumxtool_get_intr(dip, arg, mode);
510 		break;
511 
512 	/* Associate a new CPU with a given ino. */
513 	case PCITOOL_DEVICE_SET_INTR:
514 		rval = niumxtool_set_intr(dip, arg, mode);
515 		break;
516 
517 	default:
518 		rval = ENOTTY;
519 	}
520 
521 	return (rval);
522 }
523