xref: /titanic_50/usr/src/uts/i86pc/io/pci/pci_tools.c (revision 9b5097ee22b7d249db813b466eda136ffc2c21fa)
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 2009 Sun Microsystems, Inc.  All rights reserved.
23  * Use is subject to license terms.
24  */
25 
26 #include <sys/types.h>
27 #include <sys/mkdev.h>
28 #include <sys/stat.h>
29 #include <sys/sunddi.h>
30 #include <vm/seg_kmem.h>
31 #include <sys/machparam.h>
32 #include <sys/sunndi.h>
33 #include <sys/ontrap.h>
34 #include <sys/psm.h>
35 #include <sys/pcie.h>
36 #include <sys/pci_cfgspace.h>
37 #include <sys/pci_tools.h>
38 #include <io/pci/pci_tools_ext.h>
39 #include <sys/apic.h>
40 #include <io/pci/pci_var.h>
41 #include <sys/pci_impl.h>
42 #include <sys/promif.h>
43 #include <sys/x86_archext.h>
44 #include <sys/cpuvar.h>
45 #include <sys/pci_cfgacc.h>
46 
47 #ifdef __xpv
48 #include <sys/hypervisor.h>
49 #endif
50 
51 #define	PCIEX_BDF_OFFSET_DELTA	4
52 #define	PCIEX_REG_FUNC_SHIFT	(PCI_REG_FUNC_SHIFT + PCIEX_BDF_OFFSET_DELTA)
53 #define	PCIEX_REG_DEV_SHIFT	(PCI_REG_DEV_SHIFT + PCIEX_BDF_OFFSET_DELTA)
54 #define	PCIEX_REG_BUS_SHIFT	(PCI_REG_BUS_SHIFT + PCIEX_BDF_OFFSET_DELTA)
55 
56 #define	SUCCESS	0
57 
58 extern uint64_t mcfg_mem_base;
59 int pcitool_debug = 0;
60 
61 /*
62  * Offsets of BARS in config space.  First entry of 0 means config space.
63  * Entries here correlate to pcitool_bars_t enumerated type.
64  */
65 static uint8_t pci_bars[] = {
66 	0x0,
67 	PCI_CONF_BASE0,
68 	PCI_CONF_BASE1,
69 	PCI_CONF_BASE2,
70 	PCI_CONF_BASE3,
71 	PCI_CONF_BASE4,
72 	PCI_CONF_BASE5,
73 	PCI_CONF_ROM
74 };
75 
76 /* Max offset allowed into config space for a particular device. */
77 static uint64_t max_cfg_size = PCI_CONF_HDR_SIZE;
78 
79 static uint64_t pcitool_swap_endian(uint64_t data, int size);
80 static int pcitool_cfg_access(pcitool_reg_t *prg, boolean_t write_flag,
81     boolean_t io_access);
82 static int pcitool_io_access(pcitool_reg_t *prg, boolean_t write_flag);
83 static int pcitool_mem_access(pcitool_reg_t *prg, uint64_t virt_addr,
84     boolean_t write_flag);
85 static uint64_t pcitool_map(uint64_t phys_addr, size_t size, size_t *num_pages);
86 static void pcitool_unmap(uint64_t virt_addr, size_t num_pages);
87 
88 /* Extern declarations */
89 extern int	(*psm_intr_ops)(dev_info_t *, ddi_intr_handle_impl_t *,
90 		    psm_intr_op_t, int *);
91 
92 int
93 pcitool_init(dev_info_t *dip, boolean_t is_pciex)
94 {
95 	int instance = ddi_get_instance(dip);
96 
97 	/* Create pcitool nodes for register access and interrupt routing. */
98 
99 	if (ddi_create_minor_node(dip, PCI_MINOR_REG, S_IFCHR,
100 	    PCI_MINOR_NUM(instance, PCI_TOOL_REG_MINOR_NUM),
101 	    DDI_NT_REGACC, 0) != DDI_SUCCESS) {
102 		return (DDI_FAILURE);
103 	}
104 
105 	if (ddi_create_minor_node(dip, PCI_MINOR_INTR, S_IFCHR,
106 	    PCI_MINOR_NUM(instance, PCI_TOOL_INTR_MINOR_NUM),
107 	    DDI_NT_INTRCTL, 0) != DDI_SUCCESS) {
108 		ddi_remove_minor_node(dip, PCI_MINOR_REG);
109 		return (DDI_FAILURE);
110 	}
111 
112 	if (is_pciex)
113 		max_cfg_size = PCIE_CONF_HDR_SIZE;
114 
115 	return (DDI_SUCCESS);
116 }
117 
118 void
119 pcitool_uninit(dev_info_t *dip)
120 {
121 	ddi_remove_minor_node(dip, PCI_MINOR_INTR);
122 	ddi_remove_minor_node(dip, PCI_MINOR_REG);
123 }
124 
125 /*ARGSUSED*/
126 static int
127 pcitool_set_intr(dev_info_t *dip, void *arg, int mode)
128 {
129 	ddi_intr_handle_impl_t info_hdl;
130 	pcitool_intr_set_t iset;
131 	uint32_t old_cpu;
132 	int ret, result;
133 	size_t copyinout_size;
134 	int rval = SUCCESS;
135 
136 	/* Version 1 of pcitool_intr_set_t doesn't have flags. */
137 	copyinout_size = (size_t)&iset.flags - (size_t)&iset;
138 
139 	if (ddi_copyin(arg, &iset, copyinout_size, mode) != DDI_SUCCESS)
140 		return (EFAULT);
141 
142 	switch (iset.user_version) {
143 	case PCITOOL_V1:
144 		break;
145 
146 	case PCITOOL_V2:
147 		copyinout_size = sizeof (pcitool_intr_set_t);
148 		if (ddi_copyin(arg, &iset, copyinout_size, mode) != DDI_SUCCESS)
149 			return (EFAULT);
150 		break;
151 
152 	default:
153 		iset.status = PCITOOL_OUT_OF_RANGE;
154 		rval = ENOTSUP;
155 		goto done_set_intr;
156 	}
157 
158 	if (iset.flags & PCITOOL_INTR_FLAG_SET_MSI) {
159 		rval = ENOTSUP;
160 		iset.status = PCITOOL_IO_ERROR;
161 		goto done_set_intr;
162 	}
163 
164 	if (iset.ino > APIC_MAX_VECTOR) {
165 		rval = EINVAL;
166 		iset.status = PCITOOL_INVALID_INO;
167 		goto done_set_intr;
168 	}
169 
170 	iset.status = PCITOOL_SUCCESS;
171 
172 	if ((old_cpu = pci_get_cpu_from_vecirq(iset.ino, IS_VEC)) == -1) {
173 		iset.status = PCITOOL_IO_ERROR;
174 		rval = EINVAL;
175 		goto done_set_intr;
176 	}
177 
178 
179 	old_cpu &= ~PSMGI_CPU_USER_BOUND;
180 
181 	/*
182 	 * For this locally-declared and used handle, ih_private will contain a
183 	 * CPU value, not an ihdl_plat_t as used for global interrupt handling.
184 	 */
185 	info_hdl.ih_vector = iset.ino;
186 	info_hdl.ih_private = (void *)(uintptr_t)iset.cpu_id;
187 	info_hdl.ih_flags = PSMGI_INTRBY_VEC;
188 	if (pcitool_debug)
189 		prom_printf("user version:%d, flags:0x%x\n",
190 		    iset.user_version, iset.flags);
191 
192 	result = ENOTSUP;
193 	if ((iset.user_version >= PCITOOL_V2) &&
194 	    (iset.flags & PCITOOL_INTR_FLAG_SET_GROUP)) {
195 		ret = (*psm_intr_ops)(NULL, &info_hdl, PSM_INTR_OP_GRP_SET_CPU,
196 		    &result);
197 	} else {
198 		ret = (*psm_intr_ops)(NULL, &info_hdl, PSM_INTR_OP_SET_CPU,
199 		    &result);
200 	}
201 
202 	if (ret != PSM_SUCCESS) {
203 		switch (result) {
204 		case EIO:		/* Error making the change */
205 			rval = EIO;
206 			iset.status = PCITOOL_IO_ERROR;
207 			break;
208 		case ENXIO:		/* Couldn't convert vector to irq */
209 			rval = EINVAL;
210 			iset.status = PCITOOL_INVALID_INO;
211 			break;
212 		case EINVAL:		/* CPU out of range */
213 			rval = EINVAL;
214 			iset.status = PCITOOL_INVALID_CPUID;
215 			break;
216 		case ENOTSUP:		/* Requested PSM intr ops missing */
217 			rval = ENOTSUP;
218 			iset.status = PCITOOL_IO_ERROR;
219 			break;
220 		}
221 	}
222 
223 	/* Return original CPU. */
224 	iset.cpu_id = old_cpu;
225 
226 done_set_intr:
227 	iset.drvr_version = PCITOOL_VERSION;
228 	if (ddi_copyout(&iset, arg, copyinout_size, mode) != DDI_SUCCESS)
229 		rval = EFAULT;
230 	return (rval);
231 }
232 
233 
234 /* It is assumed that dip != NULL */
235 static void
236 pcitool_get_intr_dev_info(dev_info_t *dip, pcitool_intr_dev_t *devs)
237 {
238 	(void) strncpy(devs->driver_name,
239 	    ddi_driver_name(dip), MAXMODCONFNAME-2);
240 	devs->driver_name[MAXMODCONFNAME-1] = '\0';
241 	(void) ddi_pathname(dip, devs->path);
242 	devs->dev_inst = ddi_get_instance(dip);
243 }
244 
245 static int
246 pcitool_get_intr(dev_info_t *dip, void *arg, int mode)
247 {
248 	/* Array part isn't used here, but oh well... */
249 	pcitool_intr_get_t partial_iget;
250 	pcitool_intr_get_t *iget = &partial_iget;
251 	size_t	iget_kmem_alloc_size = 0;
252 	uint8_t num_devs_ret;
253 	int copyout_rval;
254 	int rval = SUCCESS;
255 	int circ;
256 	int i;
257 
258 	ddi_intr_handle_impl_t info_hdl;
259 	apic_get_intr_t intr_info;
260 
261 	/* Read in just the header part, no array section. */
262 	if (ddi_copyin(arg, &partial_iget, PCITOOL_IGET_SIZE(0), mode) !=
263 	    DDI_SUCCESS)
264 		return (EFAULT);
265 
266 	if (partial_iget.flags & PCITOOL_INTR_FLAG_GET_MSI) {
267 		partial_iget.status = PCITOOL_IO_ERROR;
268 		partial_iget.num_devs_ret = 0;
269 		rval = ENOTSUP;
270 		goto done_get_intr;
271 	}
272 
273 	/* Validate argument. */
274 	if (partial_iget.ino > APIC_MAX_VECTOR) {
275 		partial_iget.status = PCITOOL_INVALID_INO;
276 		partial_iget.num_devs_ret = 0;
277 		rval = EINVAL;
278 		goto done_get_intr;
279 	}
280 
281 	num_devs_ret = partial_iget.num_devs_ret;
282 	intr_info.avgi_dip_list = NULL;
283 	intr_info.avgi_req_flags =
284 	    PSMGI_REQ_CPUID | PSMGI_REQ_NUM_DEVS | PSMGI_INTRBY_VEC;
285 	/*
286 	 * For this locally-declared and used handle, ih_private will contain a
287 	 * pointer to apic_get_intr_t, not an ihdl_plat_t as used for
288 	 * global interrupt handling.
289 	 */
290 	info_hdl.ih_private = &intr_info;
291 	info_hdl.ih_vector = partial_iget.ino;
292 
293 	/* Caller wants device information returned. */
294 	if (num_devs_ret > 0) {
295 
296 		intr_info.avgi_req_flags |= PSMGI_REQ_GET_DEVS;
297 
298 		/*
299 		 * Allocate room.
300 		 * If num_devs_ret == 0 iget remains pointing to partial_iget.
301 		 */
302 		iget_kmem_alloc_size = PCITOOL_IGET_SIZE(num_devs_ret);
303 		iget = kmem_alloc(iget_kmem_alloc_size, KM_SLEEP);
304 
305 		/* Read in whole structure to verify there's room. */
306 		if (ddi_copyin(arg, iget, iget_kmem_alloc_size, mode) !=
307 		    SUCCESS) {
308 
309 			/* Be consistent and just return EFAULT here. */
310 			kmem_free(iget, iget_kmem_alloc_size);
311 
312 			return (EFAULT);
313 		}
314 	}
315 
316 	bzero(iget, PCITOOL_IGET_SIZE(num_devs_ret));
317 	iget->ino = info_hdl.ih_vector;
318 
319 	/*
320 	 * Lock device tree branch from the pci root nexus on down if info will
321 	 * be extracted from dips returned from the tree.
322 	 */
323 	if (intr_info.avgi_req_flags & PSMGI_REQ_GET_DEVS) {
324 		ndi_devi_enter(dip, &circ);
325 	}
326 
327 	/* Call psm_intr_ops(PSM_INTR_OP_GET_INTR) to get information. */
328 	if ((rval = (*psm_intr_ops)(NULL, &info_hdl,
329 	    PSM_INTR_OP_GET_INTR, NULL)) != PSM_SUCCESS) {
330 		iget->status = PCITOOL_IO_ERROR;
331 		iget->num_devs_ret = 0;
332 		rval = EINVAL;
333 		goto done_get_intr;
334 	}
335 
336 	/*
337 	 * Fill in the pcitool_intr_get_t to be returned,
338 	 * with the CPU, num_devs_ret and num_devs.
339 	 */
340 	iget->cpu_id = intr_info.avgi_cpu_id & ~PSMGI_CPU_USER_BOUND;
341 
342 	/* Number of devices returned by apic. */
343 	iget->num_devs = intr_info.avgi_num_devs;
344 
345 	/* Device info was returned. */
346 	if (intr_info.avgi_req_flags & PSMGI_REQ_GET_DEVS) {
347 
348 		/*
349 		 * num devs returned is num devs ret by apic,
350 		 * space permitting.
351 		 */
352 		iget->num_devs_ret = min(num_devs_ret, intr_info.avgi_num_devs);
353 
354 		/*
355 		 * Loop thru list of dips and extract driver, name and instance.
356 		 * Fill in the pcitool_intr_dev_t's with this info.
357 		 */
358 		for (i = 0; i < iget->num_devs_ret; i++)
359 			pcitool_get_intr_dev_info(intr_info.avgi_dip_list[i],
360 			    &iget->dev[i]);
361 
362 		/* Free kmem_alloc'ed memory of the apic_get_intr_t */
363 		kmem_free(intr_info.avgi_dip_list,
364 		    intr_info.avgi_num_devs * sizeof (dev_info_t *));
365 	}
366 
367 done_get_intr:
368 
369 	if (intr_info.avgi_req_flags & PSMGI_REQ_GET_DEVS) {
370 		ndi_devi_exit(dip, circ);
371 	}
372 
373 	iget->drvr_version = PCITOOL_VERSION;
374 	copyout_rval = ddi_copyout(iget, arg,
375 	    PCITOOL_IGET_SIZE(num_devs_ret), mode);
376 
377 	if (iget_kmem_alloc_size > 0)
378 		kmem_free(iget, iget_kmem_alloc_size);
379 
380 	if (copyout_rval != DDI_SUCCESS)
381 		rval = EFAULT;
382 
383 	return (rval);
384 }
385 
386 /*ARGSUSED*/
387 static int
388 pcitool_intr_info(dev_info_t *dip, void *arg, int mode)
389 {
390 	pcitool_intr_info_t intr_info;
391 	ddi_intr_handle_impl_t info_hdl;
392 	int rval = SUCCESS;
393 
394 	/* If we need user_version, and to ret same user version as passed in */
395 	if (ddi_copyin(arg, &intr_info, sizeof (pcitool_intr_info_t), mode) !=
396 	    DDI_SUCCESS) {
397 		if (pcitool_debug)
398 			prom_printf("Error reading arguments\n");
399 		return (EFAULT);
400 	}
401 
402 	if (intr_info.flags & PCITOOL_INTR_FLAG_GET_MSI)
403 		return (ENOTSUP);
404 
405 	/* For UPPC systems, psm_intr_ops has no entry for APIC_TYPE. */
406 	if ((rval = (*psm_intr_ops)(NULL, &info_hdl,
407 	    PSM_INTR_OP_APIC_TYPE, NULL)) != PSM_SUCCESS) {
408 		intr_info.ctlr_type = PCITOOL_CTLR_TYPE_UPPC;
409 		intr_info.ctlr_version = 0;
410 
411 	} else {
412 		intr_info.ctlr_version = (uint32_t)info_hdl.ih_ver;
413 		if (strcmp((char *)info_hdl.ih_private,
414 		    APIC_PCPLUSMP_NAME) == 0)
415 			intr_info.ctlr_type = PCITOOL_CTLR_TYPE_PCPLUSMP;
416 		else
417 			intr_info.ctlr_type = PCITOOL_CTLR_TYPE_UNKNOWN;
418 	}
419 
420 	intr_info.num_intr = APIC_MAX_VECTOR;
421 	intr_info.drvr_version = PCITOOL_VERSION;
422 	if (ddi_copyout(&intr_info, arg, sizeof (pcitool_intr_info_t), mode) !=
423 	    DDI_SUCCESS) {
424 		if (pcitool_debug)
425 			prom_printf("Error returning arguments.\n");
426 		rval = EFAULT;
427 	}
428 
429 	return (rval);
430 }
431 
432 
433 
434 /*
435  * Main function for handling interrupt CPU binding requests and queries.
436  * Need to implement later
437  */
438 int
439 pcitool_intr_admn(dev_info_t *dip, void *arg, int cmd, int mode)
440 {
441 	int rval;
442 
443 	switch (cmd) {
444 
445 	/* Associate a new CPU with a given vector */
446 	case PCITOOL_DEVICE_SET_INTR:
447 		rval = pcitool_set_intr(dip, arg, mode);
448 		break;
449 
450 	case PCITOOL_DEVICE_GET_INTR:
451 		rval = pcitool_get_intr(dip, arg, mode);
452 		break;
453 
454 	case PCITOOL_SYSTEM_INTR_INFO:
455 		rval = pcitool_intr_info(dip, arg, mode);
456 		break;
457 
458 	default:
459 		rval = ENOTSUP;
460 	}
461 
462 	return (rval);
463 }
464 
465 /*
466  * Perform register accesses on the nexus device itself.
467  * No explicit PCI nexus device for X86, so not applicable.
468  */
469 
470 /*ARGSUSED*/
471 int
472 pcitool_bus_reg_ops(dev_info_t *dip, void *arg, int cmd, int mode)
473 {
474 	return (ENOTSUP);
475 }
476 
477 /* Swap endianness. */
478 static uint64_t
479 pcitool_swap_endian(uint64_t data, int size)
480 {
481 	typedef union {
482 		uint64_t data64;
483 		uint8_t data8[8];
484 	} data_split_t;
485 
486 	data_split_t orig_data;
487 	data_split_t returned_data;
488 	int i;
489 
490 	orig_data.data64 = data;
491 	returned_data.data64 = 0;
492 
493 	for (i = 0; i < size; i++) {
494 		returned_data.data8[i] = orig_data.data8[size - 1 - i];
495 	}
496 
497 	return (returned_data.data64);
498 }
499 
500 /*
501  * A note about ontrap handling:
502  *
503  * X86 systems on which this module was tested return FFs instead of bus errors
504  * when accessing devices with invalid addresses.  Ontrap handling, which
505  * gracefully handles kernel bus errors, is installed anyway for I/O and mem
506  * space accessing (not for pci config space), in case future X86 platforms
507  * require it.
508  */
509 
510 /* Access device.  prg is modified. */
511 static int
512 pcitool_cfg_access(pcitool_reg_t *prg, boolean_t write_flag,
513     boolean_t io_access)
514 {
515 	int size = PCITOOL_ACC_ATTR_SIZE(prg->acc_attr);
516 	boolean_t big_endian = PCITOOL_ACC_IS_BIG_ENDIAN(prg->acc_attr);
517 	int rval = SUCCESS;
518 	uint64_t local_data;
519 	pci_cfgacc_req_t req;
520 	uint32_t max_offset;
521 
522 	if ((size <= 0) || (size > 8) || ((size & (size - 1)) != 0)) {
523 		prg->status = PCITOOL_INVALID_SIZE;
524 		return (ENOTSUP);
525 	}
526 
527 	/*
528 	 * NOTE: there is no way to verify whether or not the address is
529 	 * valid other than that it is within the maximum offset.  The
530 	 * put functions return void and the get functions return -1 on error.
531 	 */
532 
533 	if (io_access)
534 		max_offset = 0xFF;
535 	else
536 		max_offset = 0xFFF;
537 	if (prg->offset + size - 1 > max_offset) {
538 		prg->status = PCITOOL_INVALID_ADDRESS;
539 		return (ENOTSUP);
540 	}
541 
542 	prg->status = PCITOOL_SUCCESS;
543 
544 	req.rcdip = NULL;
545 	req.bdf = PCI_GETBDF(prg->bus_no, prg->dev_no, prg->func_no);
546 	req.offset = prg->offset;
547 	req.size = size;
548 	req.write = write_flag;
549 	req.ioacc = io_access;
550 	if (write_flag) {
551 		if (big_endian) {
552 			local_data = pcitool_swap_endian(prg->data, size);
553 		} else {
554 			local_data = prg->data;
555 		}
556 		VAL64(&req) = local_data;
557 		pci_cfgacc_acc(&req);
558 	} else {
559 		pci_cfgacc_acc(&req);
560 		local_data = VAL64(&req);
561 		if (big_endian) {
562 			prg->data =
563 			    pcitool_swap_endian(local_data, size);
564 		} else {
565 			prg->data = local_data;
566 		}
567 	}
568 	/*
569 	 * Check if legacy IO config access is used, in which case
570 	 * only first 256 bytes are valid.
571 	 */
572 	if (req.ioacc && (prg->offset + size - 1 > 0xFF)) {
573 		prg->status = PCITOOL_INVALID_ADDRESS;
574 		return (ENOTSUP);
575 	}
576 
577 	/* Set phys_addr only if MMIO is used */
578 	prg->phys_addr = 0;
579 	if (!req.ioacc && mcfg_mem_base != 0) {
580 		prg->phys_addr = mcfg_mem_base + prg->offset +
581 		    ((prg->bus_no << PCIEX_REG_BUS_SHIFT) |
582 		    (prg->dev_no << PCIEX_REG_DEV_SHIFT) |
583 		    (prg->func_no << PCIEX_REG_FUNC_SHIFT));
584 	}
585 
586 	return (rval);
587 }
588 
589 static int
590 pcitool_io_access(pcitool_reg_t *prg, boolean_t write_flag)
591 {
592 	int port = (int)prg->phys_addr;
593 	size_t size = PCITOOL_ACC_ATTR_SIZE(prg->acc_attr);
594 	boolean_t big_endian = PCITOOL_ACC_IS_BIG_ENDIAN(prg->acc_attr);
595 	int rval = SUCCESS;
596 	on_trap_data_t otd;
597 	uint64_t local_data;
598 
599 
600 	/*
601 	 * on_trap works like setjmp.
602 	 *
603 	 * A non-zero return here means on_trap has returned from an error.
604 	 *
605 	 * A zero return here means that on_trap has just returned from setup.
606 	 */
607 	if (on_trap(&otd, OT_DATA_ACCESS)) {
608 		no_trap();
609 		if (pcitool_debug)
610 			prom_printf(
611 			    "pcitool_io_access: on_trap caught an error...\n");
612 		prg->status = PCITOOL_INVALID_ADDRESS;
613 		return (EFAULT);
614 	}
615 
616 	if (write_flag) {
617 
618 		if (big_endian) {
619 			local_data = pcitool_swap_endian(prg->data, size);
620 		} else {
621 			local_data = prg->data;
622 		}
623 
624 		if (pcitool_debug)
625 			prom_printf("Writing %ld byte(s) to port 0x%x\n",
626 			    size, port);
627 
628 		switch (size) {
629 		case 1:
630 			outb(port, (uint8_t)local_data);
631 			break;
632 		case 2:
633 			outw(port, (uint16_t)local_data);
634 			break;
635 		case 4:
636 			outl(port, (uint32_t)local_data);
637 			break;
638 		default:
639 			rval = ENOTSUP;
640 			prg->status = PCITOOL_INVALID_SIZE;
641 			break;
642 		}
643 	} else {
644 		if (pcitool_debug)
645 			prom_printf("Reading %ld byte(s) from port 0x%x\n",
646 			    size, port);
647 
648 		switch (size) {
649 		case 1:
650 			local_data = inb(port);
651 			break;
652 		case 2:
653 			local_data = inw(port);
654 			break;
655 		case 4:
656 			local_data = inl(port);
657 			break;
658 		default:
659 			rval = ENOTSUP;
660 			prg->status = PCITOOL_INVALID_SIZE;
661 			break;
662 		}
663 
664 		if (rval == SUCCESS) {
665 			if (big_endian) {
666 				prg->data =
667 				    pcitool_swap_endian(local_data, size);
668 			} else {
669 				prg->data = local_data;
670 			}
671 		}
672 	}
673 
674 	no_trap();
675 	return (rval);
676 }
677 
678 static int
679 pcitool_mem_access(pcitool_reg_t *prg, uint64_t virt_addr, boolean_t write_flag)
680 {
681 	size_t size = PCITOOL_ACC_ATTR_SIZE(prg->acc_attr);
682 	boolean_t big_endian = PCITOOL_ACC_IS_BIG_ENDIAN(prg->acc_attr);
683 	int rval = DDI_SUCCESS;
684 	on_trap_data_t otd;
685 	uint64_t local_data;
686 
687 	/*
688 	 * on_trap works like setjmp.
689 	 *
690 	 * A non-zero return here means on_trap has returned from an error.
691 	 *
692 	 * A zero return here means that on_trap has just returned from setup.
693 	 */
694 	if (on_trap(&otd, OT_DATA_ACCESS)) {
695 		no_trap();
696 		if (pcitool_debug)
697 			prom_printf(
698 			    "pcitool_mem_access: on_trap caught an error...\n");
699 		prg->status = PCITOOL_INVALID_ADDRESS;
700 		return (EFAULT);
701 	}
702 
703 	if (write_flag) {
704 
705 		if (big_endian) {
706 			local_data = pcitool_swap_endian(prg->data, size);
707 		} else {
708 			local_data = prg->data;
709 		}
710 
711 		switch (size) {
712 		case 1:
713 			*((uint8_t *)(uintptr_t)virt_addr) = local_data;
714 			break;
715 		case 2:
716 			*((uint16_t *)(uintptr_t)virt_addr) = local_data;
717 			break;
718 		case 4:
719 			*((uint32_t *)(uintptr_t)virt_addr) = local_data;
720 			break;
721 		case 8:
722 			*((uint64_t *)(uintptr_t)virt_addr) = local_data;
723 			break;
724 		default:
725 			rval = ENOTSUP;
726 			prg->status = PCITOOL_INVALID_SIZE;
727 			break;
728 		}
729 	} else {
730 		switch (size) {
731 		case 1:
732 			local_data = *((uint8_t *)(uintptr_t)virt_addr);
733 			break;
734 		case 2:
735 			local_data = *((uint16_t *)(uintptr_t)virt_addr);
736 			break;
737 		case 4:
738 			local_data = *((uint32_t *)(uintptr_t)virt_addr);
739 			break;
740 		case 8:
741 			local_data = *((uint64_t *)(uintptr_t)virt_addr);
742 			break;
743 		default:
744 			rval = ENOTSUP;
745 			prg->status = PCITOOL_INVALID_SIZE;
746 			break;
747 		}
748 
749 		if (rval == SUCCESS) {
750 			if (big_endian) {
751 				prg->data =
752 				    pcitool_swap_endian(local_data, size);
753 			} else {
754 				prg->data = local_data;
755 			}
756 		}
757 	}
758 
759 	no_trap();
760 	return (rval);
761 }
762 
763 /*
764  * Map up to 2 pages which contain the address we want to access.
765  *
766  * Mapping should span no more than 8 bytes.  With X86 it is possible for an
767  * 8 byte value to start on a 4 byte boundary, so it can cross a page boundary.
768  * We'll never have to map more than two pages.
769  */
770 
771 static uint64_t
772 pcitool_map(uint64_t phys_addr, size_t size, size_t *num_pages)
773 {
774 
775 	uint64_t page_base = phys_addr & ~MMU_PAGEOFFSET;
776 	uint64_t offset = phys_addr & MMU_PAGEOFFSET;
777 	void *virt_base;
778 	uint64_t returned_addr;
779 	pfn_t pfn;
780 
781 	if (pcitool_debug)
782 		prom_printf("pcitool_map: Called with PA:0x%p\n",
783 		    (void *)(uintptr_t)phys_addr);
784 
785 	*num_pages = 1;
786 
787 	/* Desired mapping would span more than two pages. */
788 	if ((offset + size) > (MMU_PAGESIZE * 2)) {
789 		if (pcitool_debug)
790 			prom_printf("boundary violation: "
791 			    "offset:0x%" PRIx64 ", size:%ld, pagesize:0x%lx\n",
792 			    offset, (uintptr_t)size, (uintptr_t)MMU_PAGESIZE);
793 		return (NULL);
794 
795 	} else if ((offset + size) > MMU_PAGESIZE) {
796 		(*num_pages)++;
797 	}
798 
799 	/* Get page(s) of virtual space. */
800 	virt_base = vmem_alloc(heap_arena, ptob(*num_pages), VM_NOSLEEP);
801 	if (virt_base == NULL) {
802 		if (pcitool_debug)
803 			prom_printf("Couldn't get virtual base address.\n");
804 		return (NULL);
805 	}
806 
807 	if (pcitool_debug)
808 		prom_printf("Got base virtual address:0x%p\n", virt_base);
809 
810 #ifdef __xpv
811 	/*
812 	 * We should only get here if we are dom0.
813 	 * We're using a real device so we need to translate the MA to a PFN.
814 	 */
815 	ASSERT(DOMAIN_IS_INITDOMAIN(xen_info));
816 	pfn = xen_assign_pfn(mmu_btop(page_base));
817 #else
818 	pfn = btop(page_base);
819 #endif
820 
821 	/* Now map the allocated virtual space to the physical address. */
822 	hat_devload(kas.a_hat, virt_base, mmu_ptob(*num_pages), pfn,
823 	    PROT_READ | PROT_WRITE | HAT_STRICTORDER,
824 	    HAT_LOAD_LOCK);
825 
826 	returned_addr = ((uintptr_t)(virt_base)) + offset;
827 
828 	if (pcitool_debug)
829 		prom_printf("pcitool_map: returning VA:0x%p\n",
830 		    (void *)(uintptr_t)returned_addr);
831 
832 	return (returned_addr);
833 }
834 
835 /* Unmap the mapped page(s). */
836 static void
837 pcitool_unmap(uint64_t virt_addr, size_t num_pages)
838 {
839 	void *base_virt_addr = (void *)(uintptr_t)(virt_addr & ~MMU_PAGEOFFSET);
840 
841 	hat_unload(kas.a_hat, base_virt_addr, ptob(num_pages),
842 	    HAT_UNLOAD_UNLOCK);
843 	vmem_free(heap_arena, base_virt_addr, ptob(num_pages));
844 }
845 
846 
847 /* Perform register accesses on PCI leaf devices. */
848 /*ARGSUSED*/
849 int
850 pcitool_dev_reg_ops(dev_info_t *dip, void *arg, int cmd, int mode)
851 {
852 	boolean_t	write_flag = B_FALSE;
853 	boolean_t	io_access = B_TRUE;
854 	int		rval = 0;
855 	pcitool_reg_t	prg;
856 	uint8_t		size;
857 
858 	uint64_t	base_addr;
859 	uint64_t	virt_addr;
860 	size_t		num_virt_pages;
861 
862 	switch (cmd) {
863 	case (PCITOOL_DEVICE_SET_REG):
864 		write_flag = B_TRUE;
865 
866 	/*FALLTHRU*/
867 	case (PCITOOL_DEVICE_GET_REG):
868 		if (pcitool_debug)
869 			prom_printf("pci_dev_reg_ops set/get reg\n");
870 		if (ddi_copyin(arg, &prg, sizeof (pcitool_reg_t), mode) !=
871 		    DDI_SUCCESS) {
872 			if (pcitool_debug)
873 				prom_printf("Error reading arguments\n");
874 			return (EFAULT);
875 		}
876 
877 		if (prg.barnum >= (sizeof (pci_bars) / sizeof (pci_bars[0]))) {
878 			prg.status = PCITOOL_OUT_OF_RANGE;
879 			rval = EINVAL;
880 			goto done_reg;
881 		}
882 
883 		if (pcitool_debug)
884 			prom_printf("raw bus:0x%x, dev:0x%x, func:0x%x\n",
885 			    prg.bus_no, prg.dev_no, prg.func_no);
886 		/* Validate address arguments of bus / dev / func */
887 		if (((prg.bus_no &
888 		    (PCI_REG_BUS_M >> PCI_REG_BUS_SHIFT)) !=
889 		    prg.bus_no) ||
890 		    ((prg.dev_no &
891 		    (PCI_REG_DEV_M >> PCI_REG_DEV_SHIFT)) !=
892 		    prg.dev_no) ||
893 		    ((prg.func_no &
894 		    (PCI_REG_FUNC_M >> PCI_REG_FUNC_SHIFT)) !=
895 		    prg.func_no)) {
896 			prg.status = PCITOOL_INVALID_ADDRESS;
897 			rval = EINVAL;
898 			goto done_reg;
899 		}
900 
901 		size = PCITOOL_ACC_ATTR_SIZE(prg.acc_attr);
902 
903 		/* Proper config space desired. */
904 		if (prg.barnum == 0) {
905 
906 			if (pcitool_debug)
907 				prom_printf(
908 				    "config access: offset:0x%" PRIx64 ", "
909 				    "phys_addr:0x%" PRIx64 "\n",
910 				    prg.offset, prg.phys_addr);
911 
912 			if (prg.offset >= max_cfg_size) {
913 				prg.status = PCITOOL_OUT_OF_RANGE;
914 				rval = EINVAL;
915 				goto done_reg;
916 			}
917 			if (max_cfg_size == PCIE_CONF_HDR_SIZE)
918 				io_access = B_FALSE;
919 
920 			rval = pcitool_cfg_access(&prg, write_flag, io_access);
921 			if (pcitool_debug)
922 				prom_printf(
923 				    "config access: data:0x%" PRIx64 "\n",
924 				    prg.data);
925 
926 		/* IO/ MEM/ MEM64 space. */
927 		} else {
928 
929 			pcitool_reg_t	prg2;
930 			bcopy(&prg, &prg2, sizeof (pcitool_reg_t));
931 
932 			/*
933 			 * Translate BAR number into offset of the BAR in
934 			 * the device's config space.
935 			 */
936 			prg2.offset = pci_bars[prg2.barnum];
937 			prg2.acc_attr =
938 			    PCITOOL_ACC_ATTR_SIZE_4 | PCITOOL_ACC_ATTR_ENDN_LTL;
939 
940 			if (pcitool_debug)
941 				prom_printf(
942 				    "barnum:%d, bar_offset:0x%" PRIx64 "\n",
943 				    prg2.barnum, prg2.offset);
944 			/*
945 			 * Get Bus Address Register (BAR) from config space.
946 			 * prg2.offset is the offset into config space of the
947 			 * BAR desired.  prg.status is modified on error.
948 			 */
949 			rval = pcitool_cfg_access(&prg2, B_FALSE, B_TRUE);
950 			if (rval != SUCCESS) {
951 				if (pcitool_debug)
952 					prom_printf("BAR access failed\n");
953 				prg.status = prg2.status;
954 				goto done_reg;
955 			}
956 			/*
957 			 * Reference proper PCI space based on the BAR.
958 			 * If 64 bit MEM space, need to load other half of the
959 			 * BAR first.
960 			 */
961 
962 			if (pcitool_debug)
963 				prom_printf("bar returned is 0x%" PRIx64 "\n",
964 				    prg2.data);
965 			if (!prg2.data) {
966 				if (pcitool_debug)
967 					prom_printf("BAR data == 0\n");
968 				rval = EINVAL;
969 				prg.status = PCITOOL_INVALID_ADDRESS;
970 				goto done_reg;
971 			}
972 			if (prg2.data == 0xffffffff) {
973 				if (pcitool_debug)
974 					prom_printf("BAR data == -1\n");
975 				rval = EINVAL;
976 				prg.status = PCITOOL_INVALID_ADDRESS;
977 				goto done_reg;
978 			}
979 
980 			/*
981 			 * BAR has bits saying this space is IO space, unless
982 			 * this is the ROM address register.
983 			 */
984 			if (((PCI_BASE_SPACE_M & prg2.data) ==
985 			    PCI_BASE_SPACE_IO) &&
986 			    (prg2.offset != PCI_CONF_ROM)) {
987 				if (pcitool_debug)
988 					prom_printf("IO space\n");
989 
990 				prg2.data &= PCI_BASE_IO_ADDR_M;
991 				prg.phys_addr = prg2.data + prg.offset;
992 
993 				rval = pcitool_io_access(&prg, write_flag);
994 				if ((rval != SUCCESS) && (pcitool_debug))
995 					prom_printf("IO access failed\n");
996 
997 				goto done_reg;
998 
999 
1000 			/*
1001 			 * BAR has bits saying this space is 64 bit memory
1002 			 * space, unless this is the ROM address register.
1003 			 *
1004 			 * The 64 bit address stored in two BAR cells is not
1005 			 * necessarily aligned on an 8-byte boundary.
1006 			 * Need to keep the first 4 bytes read,
1007 			 * and do a separate read of the high 4 bytes.
1008 			 */
1009 
1010 			} else if ((PCI_BASE_TYPE_ALL & prg2.data) &&
1011 			    (prg2.offset != PCI_CONF_ROM)) {
1012 
1013 				uint32_t low_bytes =
1014 				    (uint32_t)(prg2.data & ~PCI_BASE_TYPE_ALL);
1015 
1016 				/*
1017 				 * Don't try to read the next 4 bytes
1018 				 * past the end of BARs.
1019 				 */
1020 				if (prg2.offset >= PCI_CONF_BASE5) {
1021 					prg.status = PCITOOL_OUT_OF_RANGE;
1022 					rval = EIO;
1023 					goto done_reg;
1024 				}
1025 
1026 				/*
1027 				 * Access device.
1028 				 * prg2.status is modified on error.
1029 				 */
1030 				prg2.offset += 4;
1031 				rval = pcitool_cfg_access(&prg2,
1032 				    B_FALSE, B_TRUE);
1033 				if (rval != SUCCESS) {
1034 					prg.status = prg2.status;
1035 					goto done_reg;
1036 				}
1037 
1038 				if (prg2.data == 0xffffffff) {
1039 					prg.status = PCITOOL_INVALID_ADDRESS;
1040 					prg.status = EFAULT;
1041 					goto done_reg;
1042 				}
1043 
1044 				prg2.data = (prg2.data << 32) + low_bytes;
1045 				if (pcitool_debug)
1046 					prom_printf(
1047 					    "64 bit mem space.  "
1048 					    "64-bit bar is 0x%" PRIx64 "\n",
1049 					    prg2.data);
1050 
1051 			/* Mem32 space, including ROM */
1052 			} else {
1053 
1054 				if (prg2.offset == PCI_CONF_ROM) {
1055 					if (pcitool_debug)
1056 						prom_printf(
1057 						    "Additional ROM "
1058 						    "checking\n");
1059 					/* Can't write to ROM */
1060 					if (write_flag) {
1061 						prg.status = PCITOOL_ROM_WRITE;
1062 						rval = EIO;
1063 						goto done_reg;
1064 
1065 					/* ROM disabled for reading */
1066 					} else if (!(prg2.data & 0x00000001)) {
1067 						prg.status =
1068 						    PCITOOL_ROM_DISABLED;
1069 						rval = EIO;
1070 						goto done_reg;
1071 					}
1072 				}
1073 
1074 				if (pcitool_debug)
1075 					prom_printf("32 bit mem space\n");
1076 			}
1077 
1078 			/* Common code for all IO/MEM range spaces. */
1079 
1080 			base_addr = prg2.data;
1081 			if (pcitool_debug)
1082 				prom_printf(
1083 				    "addr portion of bar is 0x%" PRIx64 ", "
1084 				    "base=0x%" PRIx64 ", "
1085 				    "offset:0x%" PRIx64 "\n",
1086 				    prg2.data, base_addr, prg.offset);
1087 			/*
1088 			 * Use offset provided by caller to index into
1089 			 * desired space, then access.
1090 			 * Note that prg.status is modified on error.
1091 			 */
1092 			prg.phys_addr = base_addr + prg.offset;
1093 
1094 			virt_addr = pcitool_map(prg.phys_addr, size,
1095 			    &num_virt_pages);
1096 			if (virt_addr == NULL) {
1097 				prg.status = PCITOOL_IO_ERROR;
1098 				rval = EIO;
1099 				goto done_reg;
1100 			}
1101 
1102 			rval = pcitool_mem_access(&prg, virt_addr, write_flag);
1103 			pcitool_unmap(virt_addr, num_virt_pages);
1104 		}
1105 done_reg:
1106 		prg.drvr_version = PCITOOL_VERSION;
1107 		if (ddi_copyout(&prg, arg, sizeof (pcitool_reg_t), mode) !=
1108 		    DDI_SUCCESS) {
1109 			if (pcitool_debug)
1110 				prom_printf("Error returning arguments.\n");
1111 			rval = EFAULT;
1112 		}
1113 		break;
1114 	default:
1115 		rval = ENOTTY;
1116 		break;
1117 	}
1118 	return (rval);
1119 }
1120