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