xref: /titanic_50/usr/src/uts/i86pc/io/pci/pci_tools.c (revision 8eea8e29cc4374d1ee24c25a07f45af132db3499)
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/ddi.h>
32 #include <sys/sunddi.h>
33 #include <vm/seg_kmem.h>
34 #include <sys/machparam.h>
35 #include <sys/ontrap.h>
36 #include <sys/pci.h>
37 #include <sys/hotplug/pci/pcihp.h>
38 #include <sys/pci_cfgspace.h>
39 #include <sys/pci_tools.h>
40 #include <sys/pci_tools_var.h>
41 #include "pci_var.h"
42 #include <sys/promif.h>
43 
44 #define	SUCCESS	0
45 
46 int pcitool_debug = 0;
47 
48 /*
49  * Offsets of BARS in config space.  First entry of 0 means config space.
50  * Entries here correlate to pcitool_bars_t enumerated type.
51  */
52 static uint8_t pci_bars[] = {
53 	0x0,
54 	PCI_CONF_BASE0,
55 	PCI_CONF_BASE1,
56 	PCI_CONF_BASE2,
57 	PCI_CONF_BASE3,
58 	PCI_CONF_BASE4,
59 	PCI_CONF_BASE5,
60 	PCI_CONF_ROM
61 };
62 
63 static uint64_t pcitool_swap_endian(uint64_t data, int size);
64 static int pcitool_cfg_access(dev_info_t *dip, pcitool_reg_t *prg,
65     boolean_t write_flag);
66 static int pcitool_io_access(dev_info_t *dip, pcitool_reg_t *prg,
67     boolean_t write_flag);
68 static int pcitool_mem_access(dev_info_t *dip, pcitool_reg_t *prg,
69     uint64_t virt_addr, boolean_t write_flag);
70 static uint64_t pcitool_map(uint64_t phys_addr, size_t size, size_t *num_pages);
71 static void pcitool_unmap(uint64_t virt_addr, size_t num_pages);
72 
73 /*
74  * A note about ontrap handling:
75  *
76  * X86 systems on which this module was tested return FFs instead of bus errors
77  * when accessing devices with invalid addresses.  Ontrap handling, which
78  * gracefully handles kernel bus errors, is installed anyway, in case future
79  * X86 platforms require it.
80  */
81 
82 /*
83  * Main function for handling interrupt CPU binding requests and queries.
84  * Need to implement later
85  */
86 /*ARGSUSED*/
87 int
88 pcitool_intr_admn(dev_t dev, void *arg, int cmd, int mode)
89 {
90 	return (ENOTSUP);
91 }
92 
93 
94 /*
95  * Perform register accesses on the nexus device itself.
96  * No explicit PCI nexus device for X86, so not applicable.
97  */
98 /*ARGSUSED*/
99 int
100 pcitool_bus_reg_ops(dev_t dev, void *arg, int cmd, int mode)
101 {
102 	return (ENOTSUP);
103 }
104 
105 /* Swap endianness. */
106 static uint64_t
107 pcitool_swap_endian(uint64_t data, int size)
108 {
109 	typedef union {
110 		uint64_t data64;
111 		uint8_t data8[8];
112 	} data_split_t;
113 
114 	data_split_t orig_data;
115 	data_split_t returned_data;
116 	int i;
117 
118 	orig_data.data64 = data;
119 	returned_data.data64 = 0;
120 
121 	for (i = 0; i < size; i++) {
122 		returned_data.data8[i] = orig_data.data8[size - 1 - i];
123 	}
124 
125 	return (returned_data.data64);
126 }
127 
128 
129 /* Access device.  prg is modified. */
130 /*ARGSUSED*/
131 static int
132 pcitool_cfg_access(dev_info_t *dip, pcitool_reg_t *prg, boolean_t write_flag)
133 {
134 	int size = PCITOOL_ACC_ATTR_SIZE(prg->acc_attr);
135 	boolean_t big_endian = PCITOOL_ACC_IS_BIG_ENDIAN(prg->acc_attr);
136 	int rval = SUCCESS;
137 	uint64_t local_data;
138 
139 	/*
140 	 * NOTE: there is no way to verify whether or not the address is valid.
141 	 * The put functions return void and the get functions return ff on
142 	 * error.
143 	 */
144 	prg->status = PCITOOL_SUCCESS;
145 
146 	if (write_flag) {
147 
148 		if (big_endian) {
149 			local_data = pcitool_swap_endian(prg->data, size);
150 		} else {
151 			local_data = prg->data;
152 		}
153 
154 		switch (size) {
155 		case 1:
156 			(*pci_putb_func)(prg->bus_no, prg->dev_no,
157 			    prg->func_no, prg->offset, local_data);
158 			break;
159 		case 2:
160 			(*pci_putw_func)(prg->bus_no, prg->dev_no,
161 			    prg->func_no, prg->offset, local_data);
162 			break;
163 		case 4:
164 			(*pci_putl_func)(prg->bus_no, prg->dev_no,
165 			    prg->func_no, prg->offset, local_data);
166 			break;
167 		default:
168 			rval = ENOTSUP;
169 			prg->status = PCITOOL_INVALID_SIZE;
170 			break;
171 		}
172 	} else {
173 		switch (size) {
174 		case 1:
175 			local_data = (*pci_getb_func)(prg->bus_no, prg->dev_no,
176 			    prg->func_no, prg->offset);
177 			break;
178 		case 2:
179 			local_data = (*pci_getw_func)(prg->bus_no, prg->dev_no,
180 			    prg->func_no, prg->offset);
181 			break;
182 		case 4:
183 			local_data = (*pci_getl_func)(prg->bus_no, prg->dev_no,
184 			    prg->func_no, prg->offset);
185 			break;
186 		default:
187 			rval = ENOTSUP;
188 			prg->status = PCITOOL_INVALID_SIZE;
189 			break;
190 		}
191 
192 		if (rval == SUCCESS) {
193 			if (big_endian) {
194 				prg->data =
195 				    pcitool_swap_endian(local_data, size);
196 			} else {
197 				prg->data = local_data;
198 			}
199 		}
200 	}
201 	prg->phys_addr = 0;	/* Config space is not memory mapped on X86. */
202 	return (rval);
203 }
204 
205 
206 /*ARGSUSED*/
207 static int
208 pcitool_io_access(dev_info_t *dip, pcitool_reg_t *prg, boolean_t write_flag)
209 {
210 	int port = (int)prg->phys_addr;
211 	size_t size = PCITOOL_ACC_ATTR_SIZE(prg->acc_attr);
212 	boolean_t big_endian = PCITOOL_ACC_IS_BIG_ENDIAN(prg->acc_attr);
213 	int rval = SUCCESS;
214 	on_trap_data_t otd;
215 	uint64_t local_data;
216 
217 
218 	/*
219 	 * on_trap works like setjmp.
220 	 *
221 	 * A non-zero return here means on_trap has returned from an error.
222 	 *
223 	 * A zero return here means that on_trap has just returned from setup.
224 	 */
225 	if (on_trap(&otd, OT_DATA_ACCESS)) {
226 		no_trap();
227 		if (pcitool_debug)
228 			prom_printf(
229 			    "pcitool_mem_access: on_trap caught an error...\n");
230 		prg->status = PCITOOL_INVALID_ADDRESS;
231 		return (EFAULT);
232 	}
233 
234 	if (write_flag) {
235 
236 		if (big_endian) {
237 			local_data = pcitool_swap_endian(prg->data, size);
238 		} else {
239 			local_data = prg->data;
240 		}
241 
242 		if (pcitool_debug)
243 			prom_printf("Writing %ld byte(s) to port 0x%x\n",
244 			    size, port);
245 
246 		switch (size) {
247 		case 1:
248 			outb(port, (uint8_t)local_data);
249 			break;
250 		case 2:
251 			outw(port, (uint16_t)local_data);
252 			break;
253 		case 4:
254 			outl(port, (uint32_t)local_data);
255 			break;
256 		default:
257 			rval = ENOTSUP;
258 			prg->status = PCITOOL_INVALID_SIZE;
259 			break;
260 		}
261 	} else {
262 		if (pcitool_debug)
263 			prom_printf("Reading %ld byte(s) from port 0x%x\n",
264 			    size, port);
265 
266 		switch (size) {
267 		case 1:
268 			local_data = inb(port);
269 			break;
270 		case 2:
271 			local_data = inw(port);
272 			break;
273 		case 4:
274 			local_data = inl(port);
275 			break;
276 		default:
277 			rval = ENOTSUP;
278 			prg->status = PCITOOL_INVALID_SIZE;
279 			break;
280 		}
281 
282 		if (rval == SUCCESS) {
283 			if (big_endian) {
284 				prg->data =
285 				    pcitool_swap_endian(local_data, size);
286 			} else {
287 				prg->data = local_data;
288 			}
289 		}
290 	}
291 
292 	no_trap();
293 	return (rval);
294 }
295 
296 /*ARGSUSED*/
297 static int
298 pcitool_mem_access(dev_info_t *dip, pcitool_reg_t *prg, uint64_t virt_addr,
299     boolean_t write_flag)
300 {
301 	size_t size = PCITOOL_ACC_ATTR_SIZE(prg->acc_attr);
302 	boolean_t big_endian = PCITOOL_ACC_IS_BIG_ENDIAN(prg->acc_attr);
303 	int rval = DDI_SUCCESS;
304 	on_trap_data_t otd;
305 	uint64_t local_data;
306 
307 	/*
308 	 * on_trap works like setjmp.
309 	 *
310 	 * A non-zero return here means on_trap has returned from an error.
311 	 *
312 	 * A zero return here means that on_trap has just returned from setup.
313 	 */
314 	if (on_trap(&otd, OT_DATA_ACCESS)) {
315 		no_trap();
316 		if (pcitool_debug)
317 			prom_printf(
318 			    "pcitool_mem_access: on_trap caught an error...\n");
319 		prg->status = PCITOOL_INVALID_ADDRESS;
320 		return (EFAULT);
321 	}
322 
323 	if (write_flag) {
324 
325 		if (big_endian) {
326 			local_data = pcitool_swap_endian(prg->data, size);
327 		} else {
328 			local_data = prg->data;
329 		}
330 
331 		switch (size) {
332 		case 1:
333 			*((uint8_t *)(uintptr_t)virt_addr) = local_data;
334 			break;
335 		case 2:
336 			*((uint16_t *)(uintptr_t)virt_addr) = local_data;
337 			break;
338 		case 4:
339 			*((uint32_t *)(uintptr_t)virt_addr) = local_data;
340 			break;
341 		case 8:
342 			*((uint64_t *)(uintptr_t)virt_addr) = local_data;
343 			break;
344 		default:
345 			rval = ENOTSUP;
346 			prg->status = PCITOOL_INVALID_SIZE;
347 			break;
348 		}
349 	} else {
350 		switch (size) {
351 		case 1:
352 			local_data = *((uint8_t *)(uintptr_t)virt_addr);
353 			break;
354 		case 2:
355 			local_data = *((uint16_t *)(uintptr_t)virt_addr);
356 			break;
357 		case 4:
358 			local_data = *((uint32_t *)(uintptr_t)virt_addr);
359 			break;
360 		case 8:
361 			local_data = *((uint64_t *)(uintptr_t)virt_addr);
362 			break;
363 		default:
364 			rval = ENOTSUP;
365 			prg->status = PCITOOL_INVALID_SIZE;
366 			break;
367 		}
368 
369 		if (rval == SUCCESS) {
370 			if (big_endian) {
371 				prg->data =
372 				    pcitool_swap_endian(local_data, size);
373 			} else {
374 				prg->data = local_data;
375 			}
376 		}
377 	}
378 
379 	no_trap();
380 	return (rval);
381 }
382 
383 /*
384  * Map up to 2 pages which contain the address we want to access.
385  *
386  * Mapping should span no more than 8 bytes.  With X86 it is possible for an
387  * 8 byte value to start on a 4 byte boundary, so it can cross a page boundary.
388  * We'll never have to map more than two pages.
389  */
390 
391 static uint64_t
392 pcitool_map(uint64_t phys_addr, size_t size, size_t *num_pages)
393 {
394 
395 	uint64_t page_base = phys_addr & ~MMU_PAGEOFFSET;
396 	uint64_t offset = phys_addr & MMU_PAGEOFFSET;
397 	void *virt_base;
398 	uint64_t returned_addr;
399 
400 	if (pcitool_debug)
401 		prom_printf("pcitool_map: Called with PA:0x%p\n",
402 		    (uint8_t *)(uintptr_t)phys_addr);
403 
404 	*num_pages = 1;
405 
406 	/* Desired mapping would span more than two pages. */
407 	if ((offset + size) > (MMU_PAGESIZE * 2)) {
408 		if (pcitool_debug)
409 			prom_printf("boundary violation: "
410 			    "offset:0x%" PRIx64 ", size:%ld, pagesize:0x%x\n",
411 			    offset, size, MMU_PAGESIZE);
412 		return (NULL);
413 
414 	} else if ((offset + size) > MMU_PAGESIZE) {
415 		(*num_pages)++;
416 	}
417 
418 	/* Get page(s) of virtual space. */
419 	virt_base = vmem_alloc(heap_arena, ptob(*num_pages), VM_NOSLEEP);
420 	if (virt_base == NULL) {
421 		if (pcitool_debug)
422 			prom_printf("Couldn't get virtual base address.\n");
423 		return (NULL);
424 	}
425 
426 	if (pcitool_debug)
427 		prom_printf("Got base virtual address:0x%p\n", virt_base);
428 
429 	/* Now map the allocated virtual space to the physical address. */
430 	hat_devload(kas.a_hat, virt_base, mmu_ptob(*num_pages),
431 	    mmu_btop(page_base), PROT_READ | PROT_WRITE | HAT_STRICTORDER,
432 	    HAT_LOAD_LOCK);
433 
434 	returned_addr = ((uintptr_t)(virt_base)) + offset;
435 
436 	if (pcitool_debug)
437 		prom_printf("pcitool_map: returning VA:0x%p\n",
438 		    (void *)(uintptr_t)returned_addr);
439 
440 	return (returned_addr);
441 }
442 
443 /* Unmap the mapped page(s). */
444 static void
445 pcitool_unmap(uint64_t virt_addr, size_t num_pages)
446 {
447 	void *base_virt_addr = (void *)(uintptr_t)(virt_addr & ~MMU_PAGEOFFSET);
448 
449 	hat_unload(kas.a_hat, base_virt_addr, ptob(num_pages),
450 	    HAT_UNLOAD_UNLOCK);
451 	vmem_free(heap_arena, base_virt_addr, ptob(num_pages));
452 }
453 
454 
455 /* Perform register accesses on PCI leaf devices. */
456 int
457 pcitool_dev_reg_ops(dev_t dev, void *arg, int cmd, int mode)
458 {
459 	pci_state_t	*pci_p = PCI_DEV_TO_STATE(dev);
460 	dev_info_t	*dip = pci_p->pci_dip;
461 	boolean_t	write_flag = B_FALSE;
462 	int		rval = 0;
463 	pcitool_reg_t	prg;
464 	uint8_t		size;
465 
466 	uint64_t	base_addr;
467 	uint64_t	virt_addr;
468 	size_t		num_virt_pages;
469 
470 	switch (cmd) {
471 	case (PCITOOL_DEVICE_SET_REG):
472 		write_flag = B_TRUE;
473 
474 	/*FALLTHRU*/
475 	case (PCITOOL_DEVICE_GET_REG):
476 		if (pcitool_debug)
477 			prom_printf("pci_dev_reg_ops set/get reg\n");
478 		if (ddi_copyin(arg, &prg, sizeof (pcitool_reg_t), mode) !=
479 		    DDI_SUCCESS) {
480 			if (pcitool_debug)
481 				prom_printf("Error reading arguments\n");
482 			return (EFAULT);
483 		}
484 
485 		if (prg.barnum >= (sizeof (pci_bars) / sizeof (pci_bars[0]))) {
486 			prg.status = PCITOOL_OUT_OF_RANGE;
487 			rval = EINVAL;
488 			goto done_reg;
489 		}
490 
491 		if (pcitool_debug)
492 			prom_printf("raw bus:0x%x, dev:0x%x, func:0x%x\n",
493 			    prg.bus_no, prg.dev_no, prg.func_no);
494 		/* Validate address arguments of bus / dev / func */
495 		if (((prg.bus_no &
496 		    (PCI_REG_BUS_M >> PCI_REG_BUS_SHIFT)) !=
497 		    prg.bus_no) ||
498 		    ((prg.dev_no &
499 		    (PCI_REG_DEV_M >> PCI_REG_DEV_SHIFT)) !=
500 		    prg.dev_no) ||
501 		    ((prg.func_no &
502 		    (PCI_REG_FUNC_M >> PCI_REG_FUNC_SHIFT)) !=
503 		    prg.func_no)) {
504 			prg.status = PCITOOL_INVALID_ADDRESS;
505 			rval = EINVAL;
506 			goto done_reg;
507 		}
508 
509 		size = PCITOOL_ACC_ATTR_SIZE(prg.acc_attr);
510 
511 		/* Proper config space desired. */
512 		if (prg.barnum == 0) {
513 
514 			if (prg.offset > 0xFF) {
515 				prg.status = PCITOOL_OUT_OF_RANGE;
516 				rval = EINVAL;
517 				goto done_reg;
518 			}
519 
520 			if (pcitool_debug)
521 				prom_printf(
522 				    "config access: offset:0x%" PRIx64 ", "
523 				    "phys_addr:0x%" PRIx64 "\n",
524 				    prg.offset, prg.phys_addr);
525 			/* Access device.  prg is modified. */
526 			rval = pcitool_cfg_access(dip, &prg, write_flag);
527 
528 			if (pcitool_debug)
529 				prom_printf(
530 				    "config access: data:0x%" PRIx64 "\n",
531 				    prg.data);
532 
533 		/* IO/ MEM/ MEM64 space. */
534 		} else {
535 
536 			pcitool_reg_t	prg2;
537 			bcopy(&prg, &prg2, sizeof (pcitool_reg_t));
538 
539 			/*
540 			 * Translate BAR number into offset of the BAR in
541 			 * the device's config space.
542 			 */
543 			prg2.offset = pci_bars[prg2.barnum];
544 			prg2.acc_attr =
545 			    PCITOOL_ACC_ATTR_SIZE_4 | PCITOOL_ACC_ATTR_ENDN_LTL;
546 
547 			if (pcitool_debug)
548 				prom_printf(
549 				    "barnum:%d, bar_offset:0x%" PRIx64 "\n",
550 				    prg2.barnum, prg2.offset);
551 			/*
552 			 * Get Bus Address Register (BAR) from config space.
553 			 * prg2.offset is the offset into config space of the
554 			 * BAR desired.  prg.status is modified on error.
555 			 */
556 			rval = pcitool_cfg_access(dip, &prg2, B_FALSE);
557 			if (rval != SUCCESS) {
558 				if (pcitool_debug)
559 					prom_printf("BAR access failed\n");
560 				prg.status = prg2.status;
561 				goto done_reg;
562 			}
563 			/*
564 			 * Reference proper PCI space based on the BAR.
565 			 * If 64 bit MEM space, need to load other half of the
566 			 * BAR first.
567 			 */
568 
569 			if (pcitool_debug)
570 				prom_printf("bar returned is 0x%" PRIx64 "\n",
571 				    prg2.data);
572 			if (!prg2.data) {
573 				if (pcitool_debug)
574 					prom_printf("BAR data == 0\n");
575 				rval = EINVAL;
576 				prg.status = PCITOOL_INVALID_ADDRESS;
577 				goto done_reg;
578 			}
579 			if (prg2.data == 0xffffffff) {
580 				if (pcitool_debug)
581 					prom_printf("BAR data == -1\n");
582 				rval = EINVAL;
583 				prg.status = PCITOOL_INVALID_ADDRESS;
584 				goto done_reg;
585 			}
586 
587 			/*
588 			 * BAR has bits saying this space is IO space, unless
589 			 * this is the ROM address register.
590 			 */
591 			if (((PCI_BASE_SPACE_M & prg2.data) ==
592 			    PCI_BASE_SPACE_IO) &&
593 			    (prg2.offset != PCI_CONF_ROM)) {
594 				if (pcitool_debug)
595 					prom_printf("IO space\n");
596 
597 				prg2.data &= PCI_BASE_IO_ADDR_M;
598 				prg.phys_addr = prg2.data + prg.offset;
599 
600 				rval = pcitool_io_access(dip, &prg, write_flag);
601 				if ((rval != SUCCESS) && (pcitool_debug))
602 					prom_printf("IO access failed\n");
603 
604 				goto done_reg;
605 
606 
607 			/*
608 			 * BAR has bits saying this space is 64 bit memory
609 			 * space, unless this is the ROM address register.
610 			 *
611 			 * The 64 bit address stored in two BAR cells is not
612 			 * necessarily aligned on an 8-byte boundary.
613 			 * Need to keep the first 4 bytes read,
614 			 * and do a separate read of the high 4 bytes.
615 			 */
616 
617 			} else if ((PCI_BASE_TYPE_ALL & prg2.data) &&
618 			    (prg2.offset != PCI_CONF_ROM)) {
619 
620 				uint32_t low_bytes =
621 				    (uint32_t)(prg2.data & ~PCI_BASE_TYPE_ALL);
622 
623 				/*
624 				 * Don't try to read the next 4 bytes
625 				 * past the end of BARs.
626 				 */
627 				if (prg2.offset >= PCI_CONF_BASE5) {
628 					prg.status = PCITOOL_OUT_OF_RANGE;
629 					rval = EIO;
630 					goto done_reg;
631 				}
632 
633 				/*
634 				 * Access device.
635 				 * prg2.status is modified on error.
636 				 */
637 				prg2.offset += 4;
638 				rval = pcitool_cfg_access(dip, &prg2, B_FALSE);
639 				if (rval != SUCCESS) {
640 					prg.status = prg2.status;
641 					goto done_reg;
642 				}
643 
644 				if (prg2.data == 0xffffffff) {
645 					prg.status = PCITOOL_INVALID_ADDRESS;
646 					prg.status = EFAULT;
647 					goto done_reg;
648 				}
649 
650 				prg2.data = (prg2.data << 32) + low_bytes;
651 				if (pcitool_debug)
652 					prom_printf(
653 					    "64 bit mem space.  "
654 					    "64-bit bar is 0x%" PRIx64 "\n",
655 					    prg2.data);
656 
657 			/* Mem32 space, including ROM */
658 			} else {
659 
660 				if (prg2.offset == PCI_CONF_ROM) {
661 					if (pcitool_debug)
662 						prom_printf(
663 						    "Additional ROM "
664 						    "checking\n");
665 					/* Can't write to ROM */
666 					if (write_flag) {
667 						prg.status = PCITOOL_ROM_WRITE;
668 						rval = EIO;
669 						goto done_reg;
670 
671 					/* ROM disabled for reading */
672 					} else if (!(prg2.data & 0x00000001)) {
673 						prg.status =
674 						    PCITOOL_ROM_DISABLED;
675 						rval = EIO;
676 						goto done_reg;
677 					}
678 				}
679 
680 				if (pcitool_debug)
681 					prom_printf("32 bit mem space\n");
682 			}
683 
684 			/* Common code for all IO/MEM range spaces. */
685 
686 			base_addr = prg2.data;
687 			if (pcitool_debug)
688 				prom_printf(
689 				    "addr portion of bar is 0x%" PRIx64 ", "
690 				    "base=0x%" PRIx64 ", "
691 				    "offset:0x%" PRIx64 "\n",
692 				    prg2.data, base_addr, prg.offset);
693 			/*
694 			 * Use offset provided by caller to index into
695 			 * desired space, then access.
696 			 * Note that prg.status is modified on error.
697 			 */
698 			prg.phys_addr = base_addr + prg.offset;
699 
700 			virt_addr = pcitool_map(prg.phys_addr, size,
701 			    &num_virt_pages);
702 			if (virt_addr == NULL) {
703 				prg.status = PCITOOL_IO_ERROR;
704 				rval = EIO;
705 				goto done_reg;
706 			}
707 
708 			rval = pcitool_mem_access(dip, &prg, virt_addr,
709 			    write_flag);
710 			pcitool_unmap(virt_addr, num_virt_pages);
711 		}
712 done_reg:
713 		if (ddi_copyout(&prg, arg, sizeof (pcitool_reg_t), mode) !=
714 		    DDI_SUCCESS) {
715 			if (pcitool_debug)
716 				prom_printf("Error returning arguments.\n");
717 			rval = EFAULT;
718 		}
719 		break;
720 	default:
721 		rval = ENOTTY;
722 		break;
723 	}
724 	return (rval);
725 }
726