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
niumx_open(dev_t * devp,int flags,int otyp,cred_t * credp)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
niumx_close(dev_t dev,int flags,int otyp,cred_t * credp)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
niumx_ioctl(dev_t dev,int cmd,intptr_t arg,int mode,cred_t * credp,int * rvalp)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 
niumx_prop_op(dev_t dev,dev_info_t * dip,ddi_prop_op_t prop_op,int flags,char * name,caddr_t valuep,int * lengthp)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
niumxtool_init(dev_info_t * dip)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
niumxtool_uninit(dev_info_t * dip)238 niumxtool_uninit(dev_info_t *dip)
239 {
240 	ddi_remove_minor_node(dip, PCI_MINOR_INTR);
241 }
242 
243 static void
niumxtool_fill_in_intr_devs(pcitool_intr_dev_t * dev,char * driver_name,char * path_name,int instance)244 niumxtool_fill_in_intr_devs(pcitool_intr_dev_t *dev, char *driver_name,
245     char *path_name, int instance)
246 {
247 	(void) strlcpy(dev->driver_name, driver_name, MAXMODCONFNAME);
248 	(void) strlcpy(dev->path, path_name, MAXPATHLEN);
249 	dev->dev_inst = instance;
250 }
251 
252 /*ARGSUSED*/
253 static int
niumxtool_intr_info(dev_info_t * dip,void * arg,int mode)254 niumxtool_intr_info(dev_info_t *dip, void *arg, int mode)
255 {
256 	pcitool_intr_info_t intr_info;
257 	int rval = DDI_SUCCESS;
258 
259 	/* If we need user_version, and to ret same user version as passed in */
260 	if (ddi_copyin(arg, &intr_info, sizeof (pcitool_intr_info_t), mode) !=
261 	    DDI_SUCCESS) {
262 		return (EFAULT);
263 	}
264 
265 	intr_info.ctlr_version = 0;	/* XXX how to get real version? */
266 	intr_info.ctlr_type = PCITOOL_CTLR_TYPE_RISC;
267 	if (intr_info.flags & PCITOOL_INTR_FLAG_GET_MSI)
268 		intr_info.num_intr = 0;
269 	else
270 		intr_info.num_intr = NIUMX_MAX_INTRS;
271 
272 	intr_info.drvr_version = PCITOOL_VERSION;
273 	if (ddi_copyout(&intr_info, arg, sizeof (pcitool_intr_info_t), mode) !=
274 	    DDI_SUCCESS) {
275 		rval = EFAULT;
276 	}
277 
278 	return (rval);
279 }
280 
281 
282 /*
283  * Get interrupt information for a given ino.
284  * Returns info only for inos mapped to devices.
285  *
286  * Returned info is valid only when iget.num_devs is returned > 0.
287  * If ino is not enabled or is not mapped to a device,
288  * iget.num_devs will be returned as = 0.
289  */
290 /*ARGSUSED*/
291 static int
niumxtool_get_intr(dev_info_t * dip,void * arg,int mode)292 niumxtool_get_intr(dev_info_t *dip, void *arg, int mode)
293 {
294 	/* Array part isn't used here, but oh well... */
295 	pcitool_intr_get_t partial_iget;
296 	pcitool_intr_get_t *iget_p = &partial_iget;
297 	int copyout_rval;
298 	niusysino_t sysino;
299 	niucpuid_t cpu_id;
300 	niumx_devstate_t *niumxds_p;
301 	dev_info_t *ih_dip;
302 	size_t	iget_kmem_alloc_size = 0;
303 	char pathname[MAXPATHLEN];
304 	int rval = EIO;
305 
306 	niumxds_p = (niumx_devstate_t *)
307 	    ddi_get_soft_state(niumx_state, ddi_get_instance(dip));
308 
309 	/* Read in just the header part, no array section. */
310 	if (ddi_copyin(arg, &partial_iget, PCITOOL_IGET_SIZE(0), mode) !=
311 	    DDI_SUCCESS)
312 		return (EFAULT);
313 
314 	iget_p->status = PCITOOL_IO_ERROR;
315 	iget_p->msi = (uint32_t)-1;
316 
317 	if (iget_p->flags & PCITOOL_INTR_FLAG_GET_MSI) {
318 		iget_p->status = PCITOOL_INVALID_MSI;
319 		rval = EINVAL;
320 		goto done_get_intr;
321 	}
322 
323 	/* Validate argument. */
324 	if (iget_p->ino > NIUMX_MAX_INTRS) {
325 		iget_p->status = PCITOOL_INVALID_INO;
326 		rval = EINVAL;
327 		goto done_get_intr;
328 	}
329 
330 	/* Caller wants device information returned. */
331 	if (iget_p->num_devs_ret > 0) {
332 		/*
333 		 * Allocate room.
334 		 * Note if num_devs == 0 iget_p remains pointing to
335 		 * partial_iget.
336 		 */
337 		iget_kmem_alloc_size = PCITOOL_IGET_SIZE(iget_p->num_devs_ret);
338 		iget_p = kmem_zalloc(iget_kmem_alloc_size, KM_SLEEP);
339 
340 		/* Read in whole structure to verify there's room. */
341 		if (ddi_copyin(arg, iget_p, iget_kmem_alloc_size, mode) !=
342 		    DDI_SUCCESS) {
343 
344 			/* Be consistent and just return EFAULT here. */
345 			kmem_free(iget_p, iget_kmem_alloc_size);
346 
347 			return (EFAULT);
348 		}
349 	}
350 
351 	sysino = niumxds_p->niumx_ihtable[iget_p->ino].ih_sysino;
352 	if (sysino == 0) {
353 		iget_p->status = PCITOOL_IO_ERROR;
354 		rval = EIO;
355 		goto done_get_intr;
356 	}
357 
358 	ih_dip = niumxds_p->niumx_ihtable[iget_p->ino].ih_dip;
359 
360 	ddi_pathname(ih_dip, pathname);
361 
362 	niumxtool_fill_in_intr_devs(&iget_p->dev[0],
363 	    (char *)ddi_driver_name(ih_dip),  pathname,
364 	    ddi_get_instance(ih_dip));
365 
366 	if (hvio_intr_gettarget(sysino, &cpu_id) != H_EOK) {
367 		iget_p->status = PCITOOL_IO_ERROR;
368 		rval = EIO;
369 		goto done_get_intr;
370 	}
371 	if (niumxds_p->niumx_ihtable[iget_p->ino].ih_cpuid != cpu_id) {
372 		cmn_err(CE_WARN, "CPU Does not match %x %x", cpu_id,
373 		    niumxds_p->niumx_ihtable[iget_p->ino].ih_cpuid);
374 		iget_p->status = PCITOOL_IO_ERROR;
375 		rval = EIO;
376 		goto done_get_intr;
377 	}
378 	iget_p->num_devs = 1;
379 	iget_p->cpu_id = niumxds_p->niumx_ihtable[iget_p->ino].ih_cpuid;
380 	iget_p->status = PCITOOL_SUCCESS;
381 	rval = DDI_SUCCESS;
382 
383 done_get_intr:
384 	iget_p->drvr_version = PCITOOL_VERSION;
385 	copyout_rval =
386 	    ddi_copyout(iget_p, arg, PCITOOL_IGET_SIZE(iget_p->num_devs_ret),
387 	    mode);
388 
389 	if (iget_kmem_alloc_size > 0)
390 		kmem_free(iget_p, iget_kmem_alloc_size);
391 
392 	if (copyout_rval != DDI_SUCCESS)
393 		rval = EFAULT;
394 
395 	return (rval);
396 }
397 
398 
399 /*
400  * Associate a new CPU with a given ino.
401  *
402  * Operate only on inos which are already mapped to devices.
403  */
404 static int
niumxtool_set_intr(dev_info_t * dip,void * arg,int mode)405 niumxtool_set_intr(dev_info_t *dip, void *arg, int mode)
406 {
407 	pcitool_intr_set_t iset;
408 	niucpuid_t old_cpu_id;
409 	int rval = EIO;
410 	int ret = DDI_SUCCESS;
411 	size_t copyinout_size;
412 	niumx_devstate_t *niumxds_p;
413 
414 	niumxds_p = (niumx_devstate_t *)
415 	    ddi_get_soft_state(niumx_state, ddi_get_instance(dip));
416 
417 	bzero(&iset, sizeof (pcitool_intr_set_t));
418 
419 	/* Version 1 of pcitool_intr_set_t doesn't have flags. */
420 	copyinout_size = (size_t)&iset.flags - (size_t)&iset;
421 
422 	if (ddi_copyin(arg, &iset, copyinout_size, mode) != DDI_SUCCESS)
423 		return (EFAULT);
424 
425 	switch (iset.user_version) {
426 	case PCITOOL_V1:
427 		break;
428 
429 	case PCITOOL_V2:
430 		copyinout_size = sizeof (pcitool_intr_set_t);
431 		if (ddi_copyin(arg, &iset, copyinout_size, mode) != DDI_SUCCESS)
432 			return (EFAULT);
433 		break;
434 
435 	default:
436 		iset.status = PCITOOL_OUT_OF_RANGE;
437 		rval = ENOTSUP;
438 		goto done_set_intr;
439 	}
440 
441 	if (iset.flags & PCITOOL_INTR_FLAG_SET_GROUP) {
442 		iset.status = PCITOOL_IO_ERROR;
443 		rval = ENOTSUP;
444 		goto done_set_intr;
445 	}
446 
447 	iset.status = PCITOOL_IO_ERROR;
448 
449 	iset.msi = (uint32_t)-1;
450 
451 	/* Validate input argument. */
452 	if (iset.ino > NIUMX_MAX_INTRS) {
453 		iset.status = PCITOOL_INVALID_INO;
454 		rval = EINVAL;
455 		goto done_set_intr;
456 	}
457 
458 	old_cpu_id = niumxds_p->niumx_ihtable[iset.ino].ih_cpuid;
459 
460 	if ((ret = niumx_set_intr_target(niumxds_p, iset.ino,
461 	    iset.cpu_id)) == DDI_SUCCESS) {
462 		iset.cpu_id = old_cpu_id;
463 		iset.status = PCITOOL_SUCCESS;
464 		rval = DDI_SUCCESS;
465 		goto done_set_intr;
466 	}
467 
468 	switch (ret) {
469 	case DDI_EPENDING:
470 		iset.status = PCITOOL_PENDING_INTRTIMEOUT;
471 		rval = ETIME;
472 		break;
473 	case DDI_EINVAL:
474 		iset.status = PCITOOL_INVALID_CPUID;
475 		rval = EINVAL;
476 		break;
477 	default:
478 		iset.status = PCITOOL_IO_ERROR;
479 		rval = EIO;
480 		break;
481 	}
482 
483 done_set_intr:
484 	iset.drvr_version = PCITOOL_VERSION;
485 	if (ddi_copyout(&iset, arg, copyinout_size, mode) != DDI_SUCCESS)
486 		rval = EFAULT;
487 
488 	return (rval);
489 }
490 
491 
492 /* Main function for handling interrupt CPU binding requests and queries. */
493 static int
niumxtool_intr(dev_info_t * dip,void * arg,int cmd,int mode)494 niumxtool_intr(dev_info_t *dip, void *arg, int cmd, int mode)
495 {
496 
497 	int rval = DDI_SUCCESS;
498 
499 	switch (cmd) {
500 
501 	/* Get system interrupt information. */
502 	case PCITOOL_SYSTEM_INTR_INFO:
503 		rval = niumxtool_intr_info(dip, arg, mode);
504 		break;
505 
506 	/* Get interrupt information for a given ino. */
507 	case PCITOOL_DEVICE_GET_INTR:
508 		rval = niumxtool_get_intr(dip, arg, mode);
509 		break;
510 
511 	/* Associate a new CPU with a given ino. */
512 	case PCITOOL_DEVICE_SET_INTR:
513 		rval = niumxtool_set_intr(dip, arg, mode);
514 		break;
515 
516 	default:
517 		rval = ENOTTY;
518 	}
519 
520 	return (rval);
521 }
522