10Sstevel@tonic-gate /*
20Sstevel@tonic-gate  * CDDL HEADER START
30Sstevel@tonic-gate  *
40Sstevel@tonic-gate  * The contents of this file are subject to the terms of the
50Sstevel@tonic-gate  * Common Development and Distribution License, Version 1.0 only
60Sstevel@tonic-gate  * (the "License").  You may not use this file except in compliance
70Sstevel@tonic-gate  * with the License.
80Sstevel@tonic-gate  *
90Sstevel@tonic-gate  * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
100Sstevel@tonic-gate  * or http://www.opensolaris.org/os/licensing.
110Sstevel@tonic-gate  * See the License for the specific language governing permissions
120Sstevel@tonic-gate  * and limitations under the License.
130Sstevel@tonic-gate  *
140Sstevel@tonic-gate  * When distributing Covered Code, include this CDDL HEADER in each
150Sstevel@tonic-gate  * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
160Sstevel@tonic-gate  * If applicable, add the following below this CDDL HEADER, with the
170Sstevel@tonic-gate  * fields enclosed by brackets "[]" replaced with your own identifying
180Sstevel@tonic-gate  * information: Portions Copyright [yyyy] [name of copyright owner]
190Sstevel@tonic-gate  *
200Sstevel@tonic-gate  * CDDL HEADER END
210Sstevel@tonic-gate  */
220Sstevel@tonic-gate /*
230Sstevel@tonic-gate  * Copyright 2005 Sun Microsystems, Inc.  All rights reserved.
240Sstevel@tonic-gate  * Use is subject to license terms.
250Sstevel@tonic-gate  */
260Sstevel@tonic-gate 
270Sstevel@tonic-gate #pragma ident	"%Z%%M%	%I%	%E% SMI"
280Sstevel@tonic-gate 
290Sstevel@tonic-gate #include <sys/types.h>
300Sstevel@tonic-gate #include <sys/mkdev.h>
31117Sschwartz #include <sys/stat.h>
320Sstevel@tonic-gate #include <sys/sunddi.h>
330Sstevel@tonic-gate #include <vm/seg_kmem.h>
340Sstevel@tonic-gate #include <sys/machparam.h>
35*916Sschwartz #include <sys/sunndi.h>
360Sstevel@tonic-gate #include <sys/ontrap.h>
37*916Sschwartz #include <sys/psm.h>
38881Sjohnny #include <sys/pcie.h>
390Sstevel@tonic-gate #include <sys/hotplug/pci/pcihp.h>
400Sstevel@tonic-gate #include <sys/pci_cfgspace.h>
410Sstevel@tonic-gate #include <sys/pci_tools.h>
42777Sschwartz #include "pci_tools_ext.h"
43*916Sschwartz #include <io/pcplusmp/apic.h>
44*916Sschwartz #include <io/pci/pci_var.h>
450Sstevel@tonic-gate #include <sys/promif.h>
460Sstevel@tonic-gate 
47777Sschwartz #define	PCIEX_BDF_OFFSET_DELTA	4
48777Sschwartz #define	PCIEX_REG_FUNC_SHIFT	(PCI_REG_FUNC_SHIFT + PCIEX_BDF_OFFSET_DELTA)
49777Sschwartz #define	PCIEX_REG_DEV_SHIFT	(PCI_REG_DEV_SHIFT + PCIEX_BDF_OFFSET_DELTA)
50777Sschwartz #define	PCIEX_REG_BUS_SHIFT	(PCI_REG_BUS_SHIFT + PCIEX_BDF_OFFSET_DELTA)
51777Sschwartz 
520Sstevel@tonic-gate #define	SUCCESS	0
530Sstevel@tonic-gate 
540Sstevel@tonic-gate int pcitool_debug = 0;
550Sstevel@tonic-gate 
560Sstevel@tonic-gate /*
570Sstevel@tonic-gate  * Offsets of BARS in config space.  First entry of 0 means config space.
580Sstevel@tonic-gate  * Entries here correlate to pcitool_bars_t enumerated type.
590Sstevel@tonic-gate  */
600Sstevel@tonic-gate static uint8_t pci_bars[] = {
610Sstevel@tonic-gate 	0x0,
620Sstevel@tonic-gate 	PCI_CONF_BASE0,
630Sstevel@tonic-gate 	PCI_CONF_BASE1,
640Sstevel@tonic-gate 	PCI_CONF_BASE2,
650Sstevel@tonic-gate 	PCI_CONF_BASE3,
660Sstevel@tonic-gate 	PCI_CONF_BASE4,
670Sstevel@tonic-gate 	PCI_CONF_BASE5,
680Sstevel@tonic-gate 	PCI_CONF_ROM
690Sstevel@tonic-gate };
700Sstevel@tonic-gate 
71777Sschwartz /* Max offset allowed into config space for a particular device. */
72777Sschwartz static uint64_t max_cfg_size = PCI_CONF_HDR_SIZE;
73777Sschwartz 
740Sstevel@tonic-gate static uint64_t pcitool_swap_endian(uint64_t data, int size);
75777Sschwartz static int pcitool_pciex_cfg_access(dev_info_t *dip, pcitool_reg_t *prg,
76777Sschwartz     boolean_t write_flag);
770Sstevel@tonic-gate static int pcitool_cfg_access(dev_info_t *dip, pcitool_reg_t *prg,
780Sstevel@tonic-gate     boolean_t write_flag);
790Sstevel@tonic-gate static int pcitool_io_access(dev_info_t *dip, pcitool_reg_t *prg,
800Sstevel@tonic-gate     boolean_t write_flag);
810Sstevel@tonic-gate static int pcitool_mem_access(dev_info_t *dip, pcitool_reg_t *prg,
820Sstevel@tonic-gate     uint64_t virt_addr, boolean_t write_flag);
830Sstevel@tonic-gate static uint64_t pcitool_map(uint64_t phys_addr, size_t size, size_t *num_pages);
840Sstevel@tonic-gate static void pcitool_unmap(uint64_t virt_addr, size_t num_pages);
850Sstevel@tonic-gate 
86*916Sschwartz /* Extern decalrations */
87*916Sschwartz extern int	(*psm_intr_ops)(dev_info_t *, ddi_intr_handle_impl_t *,
88*916Sschwartz 		    psm_intr_op_t, int *);
89*916Sschwartz 
90117Sschwartz int
91777Sschwartz pcitool_init(dev_info_t *dip, boolean_t is_pciex)
92117Sschwartz {
93117Sschwartz 	int instance = ddi_get_instance(dip);
94117Sschwartz 
95117Sschwartz 	/* Create pcitool nodes for register access and interrupt routing. */
96117Sschwartz 
97117Sschwartz 	if (ddi_create_minor_node(dip, PCI_MINOR_REG, S_IFCHR,
98117Sschwartz 	    PCIHP_AP_MINOR_NUM(instance, PCI_TOOL_REG_MINOR_NUM),
99117Sschwartz 	    DDI_NT_REGACC, 0) != DDI_SUCCESS) {
100117Sschwartz 		return (DDI_FAILURE);
101117Sschwartz 	}
102117Sschwartz 
103117Sschwartz 	if (ddi_create_minor_node(dip, PCI_MINOR_INTR, S_IFCHR,
104117Sschwartz 	    PCIHP_AP_MINOR_NUM(instance, PCI_TOOL_INTR_MINOR_NUM),
105117Sschwartz 	    DDI_NT_INTRCTL, 0) != DDI_SUCCESS) {
106117Sschwartz 		ddi_remove_minor_node(dip, PCI_MINOR_REG);
107117Sschwartz 		return (DDI_FAILURE);
108117Sschwartz 	}
109117Sschwartz 
110777Sschwartz 	if (is_pciex)
111777Sschwartz 		max_cfg_size = PCIE_CONF_HDR_SIZE;
112777Sschwartz 
113117Sschwartz 	return (DDI_SUCCESS);
114117Sschwartz }
115117Sschwartz 
116117Sschwartz void
117117Sschwartz pcitool_uninit(dev_info_t *dip)
118117Sschwartz {
119117Sschwartz 	ddi_remove_minor_node(dip, PCI_MINOR_INTR);
120117Sschwartz 	ddi_remove_minor_node(dip, PCI_MINOR_REG);
121117Sschwartz }
122117Sschwartz 
123117Sschwartz 
124*916Sschwartz /* Return the number of interrupts on a pci bus. */
125*916Sschwartz static int
126*916Sschwartz pcitool_intr_get_max_ino(uint32_t *arg, int mode)
127*916Sschwartz {
128*916Sschwartz 	uint32_t num_intr = APIC_MAX_VECTOR;
129*916Sschwartz 
130*916Sschwartz 	if (ddi_copyout(&num_intr, arg, sizeof (uint32_t), mode) !=
131*916Sschwartz 	    DDI_SUCCESS)
132*916Sschwartz 		return (EFAULT);
133*916Sschwartz 	else
134*916Sschwartz 		return (SUCCESS);
135*916Sschwartz }
136*916Sschwartz 
137*916Sschwartz 
138*916Sschwartz /*ARGSUSED*/
139*916Sschwartz static int
140*916Sschwartz pcitool_set_intr(dev_info_t *dip, void *arg, int mode)
141*916Sschwartz {
142*916Sschwartz 	ddi_intr_handle_impl_t info_hdl;
143*916Sschwartz 	pcitool_intr_set_t iset;
144*916Sschwartz 	uint32_t old_cpu;
145*916Sschwartz 	int ret, result;
146*916Sschwartz 	int rval = SUCCESS;
147*916Sschwartz 
148*916Sschwartz 	if (ddi_copyin(arg, &iset, sizeof (pcitool_intr_set_t), mode) !=
149*916Sschwartz 	    DDI_SUCCESS)
150*916Sschwartz 		return (EFAULT);
151*916Sschwartz 
152*916Sschwartz 	if (iset.ino > APIC_MAX_VECTOR) {
153*916Sschwartz 		rval = EINVAL;
154*916Sschwartz 		iset.status = PCITOOL_INVALID_INO;
155*916Sschwartz 		goto done_set_intr;
156*916Sschwartz 	}
157*916Sschwartz 
158*916Sschwartz 	iset.status = PCITOOL_SUCCESS;
159*916Sschwartz 
160*916Sschwartz 	if ((old_cpu = pci_get_cpu_from_vecirq(iset.ino, IS_VEC)) == -1) {
161*916Sschwartz 		iset.status = PCITOOL_IO_ERROR;
162*916Sschwartz 		rval = EINVAL;
163*916Sschwartz 		goto done_set_intr;
164*916Sschwartz 	}
165*916Sschwartz 
166*916Sschwartz 	old_cpu &= ~PSMGI_CPU_USER_BOUND;
167*916Sschwartz 
168*916Sschwartz 	/*
169*916Sschwartz 	 * For this locally-declared and used handle, ih_private will contain a
170*916Sschwartz 	 * CPU value, not an ihdl_plat_t as used for global interrupt handling.
171*916Sschwartz 	 */
172*916Sschwartz 	info_hdl.ih_vector = iset.ino;
173*916Sschwartz 	info_hdl.ih_private = (void *)(uintptr_t)iset.cpu_id;
174*916Sschwartz 	ret = (*psm_intr_ops)(NULL, &info_hdl, PSM_INTR_OP_SET_CPU, &result);
175*916Sschwartz 
176*916Sschwartz 	iset.drvr_version = PCITOOL_DRVR_VERSION;
177*916Sschwartz 	if (ret != PSM_SUCCESS) {
178*916Sschwartz 		switch (result) {
179*916Sschwartz 		case EIO:		/* Error making the change */
180*916Sschwartz 			rval = EIO;
181*916Sschwartz 			iset.status = PCITOOL_IO_ERROR;
182*916Sschwartz 			break;
183*916Sschwartz 		case ENXIO:		/* Couldn't convert vector to irq */
184*916Sschwartz 			rval = EINVAL;
185*916Sschwartz 			iset.status = PCITOOL_INVALID_INO;
186*916Sschwartz 			break;
187*916Sschwartz 		case EINVAL:		/* CPU out of range */
188*916Sschwartz 			rval = EINVAL;
189*916Sschwartz 			iset.status = PCITOOL_INVALID_CPUID;
190*916Sschwartz 			break;
191*916Sschwartz 		}
192*916Sschwartz 	}
193*916Sschwartz 
194*916Sschwartz 	/* Return original CPU. */
195*916Sschwartz 	iset.cpu_id = old_cpu;
196*916Sschwartz 
197*916Sschwartz done_set_intr:
198*916Sschwartz 	if (ddi_copyout(&iset, arg, sizeof (pcitool_intr_set_t), mode) !=
199*916Sschwartz 	    DDI_SUCCESS)
200*916Sschwartz 		rval = EFAULT;
201*916Sschwartz 	return (rval);
202*916Sschwartz }
203*916Sschwartz 
204*916Sschwartz 
205*916Sschwartz /* It is assumed that dip != NULL */
206*916Sschwartz static void
207*916Sschwartz pcitool_get_intr_dev_info(dev_info_t *dip, pcitool_intr_dev_t *devs)
208*916Sschwartz {
209*916Sschwartz 	(void) strncpy(devs->driver_name,
210*916Sschwartz 	    ddi_driver_name(dip), MAXMODCONFNAME-1);
211*916Sschwartz 	devs->driver_name[MAXMODCONFNAME] = '\0';
212*916Sschwartz 	(void) ddi_pathname(dip, devs->path);
213*916Sschwartz 	devs->dev_inst = ddi_get_instance(dip);
214*916Sschwartz }
215*916Sschwartz 
216*916Sschwartz 
217*916Sschwartz /*ARGSUSED*/
218*916Sschwartz static int
219*916Sschwartz pcitool_get_intr(dev_info_t *dip, void *arg, int mode)
220*916Sschwartz {
221*916Sschwartz 	/* Array part isn't used here, but oh well... */
222*916Sschwartz 	pcitool_intr_get_t partial_iget;
223*916Sschwartz 	pcitool_intr_get_t *iget = &partial_iget;
224*916Sschwartz 	size_t	iget_kmem_alloc_size = 0;
225*916Sschwartz 	uint8_t num_devs_ret;
226*916Sschwartz 	int copyout_rval;
227*916Sschwartz 	int rval = SUCCESS;
228*916Sschwartz 	int circ;
229*916Sschwartz 	int i;
230*916Sschwartz 
231*916Sschwartz 	ddi_intr_handle_impl_t info_hdl;
232*916Sschwartz 	apic_get_intr_t intr_info;
233*916Sschwartz 
234*916Sschwartz 	/* Read in just the header part, no array section. */
235*916Sschwartz 	if (ddi_copyin(arg, &partial_iget, PCITOOL_IGET_SIZE(0), mode) !=
236*916Sschwartz 	    DDI_SUCCESS)
237*916Sschwartz 		return (EFAULT);
238*916Sschwartz 
239*916Sschwartz 	/* Validate argument. */
240*916Sschwartz 	if (partial_iget.ino > APIC_MAX_VECTOR) {
241*916Sschwartz 		partial_iget.status = PCITOOL_INVALID_INO;
242*916Sschwartz 		partial_iget.num_devs_ret = 0;
243*916Sschwartz 		rval = EINVAL;
244*916Sschwartz 		goto done_get_intr;
245*916Sschwartz 	}
246*916Sschwartz 
247*916Sschwartz 	num_devs_ret = partial_iget.num_devs_ret;
248*916Sschwartz 	intr_info.avgi_dip_list = NULL;
249*916Sschwartz 	intr_info.avgi_req_flags =
250*916Sschwartz 	    PSMGI_REQ_CPUID | PSMGI_REQ_NUM_DEVS | PSMGI_INTRBY_VEC;
251*916Sschwartz 	/*
252*916Sschwartz 	 * For this locally-declared and used handle, ih_private will contain a
253*916Sschwartz 	 * pointer to apic_get_intr_t, not an ihdl_plat_t as used for
254*916Sschwartz 	 * global interrupt handling.
255*916Sschwartz 	 */
256*916Sschwartz 	info_hdl.ih_private = &intr_info;
257*916Sschwartz 	info_hdl.ih_vector = partial_iget.ino;
258*916Sschwartz 
259*916Sschwartz 	/* Caller wants device information returned. */
260*916Sschwartz 	if (num_devs_ret > 0) {
261*916Sschwartz 
262*916Sschwartz 		intr_info.avgi_req_flags |= PSMGI_REQ_GET_DEVS;
263*916Sschwartz 
264*916Sschwartz 		/*
265*916Sschwartz 		 * Allocate room.
266*916Sschwartz 		 * If num_devs_ret == 0 iget remains pointing to partial_iget.
267*916Sschwartz 		 */
268*916Sschwartz 		iget_kmem_alloc_size = PCITOOL_IGET_SIZE(num_devs_ret);
269*916Sschwartz 		iget = kmem_alloc(iget_kmem_alloc_size, KM_SLEEP);
270*916Sschwartz 
271*916Sschwartz 		/* Read in whole structure to verify there's room. */
272*916Sschwartz 		if (ddi_copyin(arg, iget, iget_kmem_alloc_size, mode) !=
273*916Sschwartz 		    SUCCESS) {
274*916Sschwartz 
275*916Sschwartz 			/* Be consistent and just return EFAULT here. */
276*916Sschwartz 			kmem_free(iget, iget_kmem_alloc_size);
277*916Sschwartz 
278*916Sschwartz 			return (EFAULT);
279*916Sschwartz 		}
280*916Sschwartz 	}
281*916Sschwartz 
282*916Sschwartz 	bzero(iget, PCITOOL_IGET_SIZE(num_devs_ret));
283*916Sschwartz 	iget->ino = info_hdl.ih_vector;
284*916Sschwartz 
285*916Sschwartz 	/*
286*916Sschwartz 	 * Lock device tree branch from the pci root nexus on down if info will
287*916Sschwartz 	 * be extracted from dips returned from the tree.
288*916Sschwartz 	 */
289*916Sschwartz 	if (intr_info.avgi_req_flags & PSMGI_REQ_GET_DEVS) {
290*916Sschwartz 		ndi_devi_enter(dip, &circ);
291*916Sschwartz 	}
292*916Sschwartz 
293*916Sschwartz 	/* Call psm_intr_ops(PSM_INTR_OP_GET_INTR) to get information. */
294*916Sschwartz 	if ((rval = (*psm_intr_ops)(NULL, &info_hdl,
295*916Sschwartz 	    PSM_INTR_OP_GET_INTR, NULL)) != PSM_SUCCESS) {
296*916Sschwartz 		iget->status = PCITOOL_IO_ERROR;
297*916Sschwartz 		iget->num_devs_ret = 0;
298*916Sschwartz 		rval = EINVAL;
299*916Sschwartz 		goto done_get_intr;
300*916Sschwartz 	}
301*916Sschwartz 
302*916Sschwartz 	/*
303*916Sschwartz 	 * Fill in the pcitool_intr_get_t to be returned,
304*916Sschwartz 	 * with the CPU, num_devs_ret and num_devs.
305*916Sschwartz 	 */
306*916Sschwartz 	iget->cpu_id = intr_info.avgi_cpu_id & ~PSMGI_CPU_USER_BOUND;
307*916Sschwartz 
308*916Sschwartz 	/* Number of devices returned by apic. */
309*916Sschwartz 	iget->num_devs = intr_info.avgi_num_devs;
310*916Sschwartz 
311*916Sschwartz 	/* Device info was returned. */
312*916Sschwartz 	if (intr_info.avgi_req_flags & PSMGI_REQ_GET_DEVS) {
313*916Sschwartz 
314*916Sschwartz 		/*
315*916Sschwartz 		 * num devs returned is num devs ret by apic,
316*916Sschwartz 		 * space permitting.
317*916Sschwartz 		 */
318*916Sschwartz 		iget->num_devs_ret = min(num_devs_ret, intr_info.avgi_num_devs);
319*916Sschwartz 
320*916Sschwartz 		/*
321*916Sschwartz 		 * Loop thru list of dips and extract driver, name and instance.
322*916Sschwartz 		 * Fill in the pcitool_intr_dev_t's with this info.
323*916Sschwartz 		 */
324*916Sschwartz 		for (i = 0; i < iget->num_devs_ret; i++)
325*916Sschwartz 			pcitool_get_intr_dev_info(intr_info.avgi_dip_list[i],
326*916Sschwartz 			    &iget->dev[i]);
327*916Sschwartz 
328*916Sschwartz 		/* Free kmem_alloc'ed memory of the apic_get_intr_t */
329*916Sschwartz 		kmem_free(intr_info.avgi_dip_list,
330*916Sschwartz 		    intr_info.avgi_num_devs * sizeof (dev_info_t *));
331*916Sschwartz 	}
332*916Sschwartz 
333*916Sschwartz done_get_intr:
334*916Sschwartz 
335*916Sschwartz 	if (intr_info.avgi_req_flags & PSMGI_REQ_GET_DEVS) {
336*916Sschwartz 		ndi_devi_exit(dip, circ);
337*916Sschwartz 	}
338*916Sschwartz 
339*916Sschwartz 	iget->drvr_version = PCITOOL_DRVR_VERSION;
340*916Sschwartz 	copyout_rval = ddi_copyout(iget, arg,
341*916Sschwartz 	    PCITOOL_IGET_SIZE(num_devs_ret), mode);
342*916Sschwartz 
343*916Sschwartz 	if (iget_kmem_alloc_size > 0)
344*916Sschwartz 		kmem_free(iget, iget_kmem_alloc_size);
345*916Sschwartz 
346*916Sschwartz 	if (copyout_rval != DDI_SUCCESS)
347*916Sschwartz 		rval = EFAULT;
348*916Sschwartz 
349*916Sschwartz 	return (rval);
350*916Sschwartz }
351*916Sschwartz 
352*916Sschwartz 
353*916Sschwartz /*
354*916Sschwartz  * Main function for handling interrupt CPU binding requests and queries.
355*916Sschwartz  * Need to implement later
356*916Sschwartz  */
357*916Sschwartz /*ARGSUSED*/
358*916Sschwartz int
359*916Sschwartz pcitool_intr_admn(dev_info_t *dip, void *arg, int cmd, int mode)
360*916Sschwartz {
361*916Sschwartz 	int rval;
362*916Sschwartz 
363*916Sschwartz 	switch (cmd) {
364*916Sschwartz 
365*916Sschwartz 	/* Associate a new CPU with a given vector */
366*916Sschwartz 	case PCITOOL_DEVICE_SET_INTR:
367*916Sschwartz 		rval = pcitool_set_intr(dip, arg, mode);
368*916Sschwartz 		break;
369*916Sschwartz 
370*916Sschwartz 	case PCITOOL_DEVICE_GET_INTR:
371*916Sschwartz 		rval = pcitool_get_intr(dip, arg, mode);
372*916Sschwartz 		break;
373*916Sschwartz 
374*916Sschwartz 	case PCITOOL_DEVICE_NUM_INTR:
375*916Sschwartz 		rval = pcitool_intr_get_max_ino(arg, mode);
376*916Sschwartz 		break;
377*916Sschwartz 
378*916Sschwartz 	default:
379*916Sschwartz 		rval = ENOTSUP;
380*916Sschwartz 	}
381*916Sschwartz 
382*916Sschwartz 	return (rval);
383*916Sschwartz }
384*916Sschwartz 
385*916Sschwartz 
3860Sstevel@tonic-gate /*
3870Sstevel@tonic-gate  * A note about ontrap handling:
3880Sstevel@tonic-gate  *
3890Sstevel@tonic-gate  * X86 systems on which this module was tested return FFs instead of bus errors
3900Sstevel@tonic-gate  * when accessing devices with invalid addresses.  Ontrap handling, which
3910Sstevel@tonic-gate  * gracefully handles kernel bus errors, is installed anyway, in case future
3920Sstevel@tonic-gate  * X86 platforms require it.
3930Sstevel@tonic-gate  */
3940Sstevel@tonic-gate 
3950Sstevel@tonic-gate /*
3960Sstevel@tonic-gate  * Perform register accesses on the nexus device itself.
3970Sstevel@tonic-gate  * No explicit PCI nexus device for X86, so not applicable.
3980Sstevel@tonic-gate  */
399*916Sschwartz 
4000Sstevel@tonic-gate /*ARGSUSED*/
4010Sstevel@tonic-gate int
402777Sschwartz pcitool_bus_reg_ops(dev_info_t *dip, void *arg, int cmd, int mode)
4030Sstevel@tonic-gate {
4040Sstevel@tonic-gate 	return (ENOTSUP);
4050Sstevel@tonic-gate }
4060Sstevel@tonic-gate 
4070Sstevel@tonic-gate /* Swap endianness. */
4080Sstevel@tonic-gate static uint64_t
4090Sstevel@tonic-gate pcitool_swap_endian(uint64_t data, int size)
4100Sstevel@tonic-gate {
4110Sstevel@tonic-gate 	typedef union {
4120Sstevel@tonic-gate 		uint64_t data64;
4130Sstevel@tonic-gate 		uint8_t data8[8];
4140Sstevel@tonic-gate 	} data_split_t;
4150Sstevel@tonic-gate 
4160Sstevel@tonic-gate 	data_split_t orig_data;
4170Sstevel@tonic-gate 	data_split_t returned_data;
4180Sstevel@tonic-gate 	int i;
4190Sstevel@tonic-gate 
4200Sstevel@tonic-gate 	orig_data.data64 = data;
4210Sstevel@tonic-gate 	returned_data.data64 = 0;
4220Sstevel@tonic-gate 
4230Sstevel@tonic-gate 	for (i = 0; i < size; i++) {
4240Sstevel@tonic-gate 		returned_data.data8[i] = orig_data.data8[size - 1 - i];
4250Sstevel@tonic-gate 	}
4260Sstevel@tonic-gate 
4270Sstevel@tonic-gate 	return (returned_data.data64);
4280Sstevel@tonic-gate }
4290Sstevel@tonic-gate 
4300Sstevel@tonic-gate 
431777Sschwartz /*
432777Sschwartz  * Access device.  prg is modified.
433777Sschwartz  *
434777Sschwartz  * Extended config space is available only through memory-mapped access.
435777Sschwartz  * Standard config space on pci express devices is available either way,
436777Sschwartz  * so do it memory-mapped here too, for simplicity.
437777Sschwartz  */
438777Sschwartz /*ARGSUSED*/
439777Sschwartz static int
440777Sschwartz pcitool_pciex_cfg_access(dev_info_t *dip, pcitool_reg_t *prg,
441777Sschwartz     boolean_t write_flag)
442777Sschwartz {
443777Sschwartz 	int rval = SUCCESS;
444777Sschwartz 	uint64_t virt_addr;
445777Sschwartz 	size_t	num_virt_pages;
446777Sschwartz 
447777Sschwartz 	prg->status = PCITOOL_SUCCESS;
448777Sschwartz 
449777Sschwartz 	prg->phys_addr = ddi_prop_get_int64(DDI_DEV_T_ANY, dip, 0,
450881Sjohnny 	    "ecfga-base-address", 0);
451777Sschwartz 	if (prg->phys_addr == 0) {
452777Sschwartz 		prg->status = PCITOOL_IO_ERROR;
453777Sschwartz 		return (EIO);
454777Sschwartz 	}
455777Sschwartz 
456777Sschwartz 	prg->phys_addr += prg->offset +
457777Sschwartz 	    ((prg->bus_no << PCIEX_REG_BUS_SHIFT) |
458777Sschwartz 	    (prg->dev_no << PCIEX_REG_DEV_SHIFT) |
459777Sschwartz 	    (prg->func_no << PCIEX_REG_FUNC_SHIFT));
460777Sschwartz 
461777Sschwartz 	virt_addr = pcitool_map(prg->phys_addr,
462777Sschwartz 	    PCITOOL_ACC_ATTR_SIZE(prg->acc_attr), &num_virt_pages);
463777Sschwartz 	if (virt_addr == NULL) {
464777Sschwartz 		prg->status = PCITOOL_IO_ERROR;
465777Sschwartz 		return (EIO);
466777Sschwartz 	}
467777Sschwartz 
468777Sschwartz 	rval = pcitool_mem_access(dip, prg, virt_addr, write_flag);
469777Sschwartz 	pcitool_unmap(virt_addr, num_virt_pages);
470777Sschwartz 	return (rval);
471777Sschwartz }
472777Sschwartz 
4730Sstevel@tonic-gate /* Access device.  prg is modified. */
4740Sstevel@tonic-gate /*ARGSUSED*/
4750Sstevel@tonic-gate static int
4760Sstevel@tonic-gate pcitool_cfg_access(dev_info_t *dip, pcitool_reg_t *prg, boolean_t write_flag)
4770Sstevel@tonic-gate {
4780Sstevel@tonic-gate 	int size = PCITOOL_ACC_ATTR_SIZE(prg->acc_attr);
4790Sstevel@tonic-gate 	boolean_t big_endian = PCITOOL_ACC_IS_BIG_ENDIAN(prg->acc_attr);
4800Sstevel@tonic-gate 	int rval = SUCCESS;
4810Sstevel@tonic-gate 	uint64_t local_data;
4820Sstevel@tonic-gate 
4830Sstevel@tonic-gate 	/*
4840Sstevel@tonic-gate 	 * NOTE: there is no way to verify whether or not the address is valid.
4850Sstevel@tonic-gate 	 * The put functions return void and the get functions return ff on
4860Sstevel@tonic-gate 	 * error.
4870Sstevel@tonic-gate 	 */
4880Sstevel@tonic-gate 	prg->status = PCITOOL_SUCCESS;
4890Sstevel@tonic-gate 
4900Sstevel@tonic-gate 	if (write_flag) {
4910Sstevel@tonic-gate 
4920Sstevel@tonic-gate 		if (big_endian) {
4930Sstevel@tonic-gate 			local_data = pcitool_swap_endian(prg->data, size);
4940Sstevel@tonic-gate 		} else {
4950Sstevel@tonic-gate 			local_data = prg->data;
4960Sstevel@tonic-gate 		}
4970Sstevel@tonic-gate 
4980Sstevel@tonic-gate 		switch (size) {
4990Sstevel@tonic-gate 		case 1:
5000Sstevel@tonic-gate 			(*pci_putb_func)(prg->bus_no, prg->dev_no,
5010Sstevel@tonic-gate 			    prg->func_no, prg->offset, local_data);
5020Sstevel@tonic-gate 			break;
5030Sstevel@tonic-gate 		case 2:
5040Sstevel@tonic-gate 			(*pci_putw_func)(prg->bus_no, prg->dev_no,
5050Sstevel@tonic-gate 			    prg->func_no, prg->offset, local_data);
5060Sstevel@tonic-gate 			break;
5070Sstevel@tonic-gate 		case 4:
5080Sstevel@tonic-gate 			(*pci_putl_func)(prg->bus_no, prg->dev_no,
5090Sstevel@tonic-gate 			    prg->func_no, prg->offset, local_data);
5100Sstevel@tonic-gate 			break;
5110Sstevel@tonic-gate 		default:
5120Sstevel@tonic-gate 			rval = ENOTSUP;
5130Sstevel@tonic-gate 			prg->status = PCITOOL_INVALID_SIZE;
5140Sstevel@tonic-gate 			break;
5150Sstevel@tonic-gate 		}
5160Sstevel@tonic-gate 	} else {
5170Sstevel@tonic-gate 		switch (size) {
5180Sstevel@tonic-gate 		case 1:
5190Sstevel@tonic-gate 			local_data = (*pci_getb_func)(prg->bus_no, prg->dev_no,
5200Sstevel@tonic-gate 			    prg->func_no, prg->offset);
5210Sstevel@tonic-gate 			break;
5220Sstevel@tonic-gate 		case 2:
5230Sstevel@tonic-gate 			local_data = (*pci_getw_func)(prg->bus_no, prg->dev_no,
5240Sstevel@tonic-gate 			    prg->func_no, prg->offset);
5250Sstevel@tonic-gate 			break;
5260Sstevel@tonic-gate 		case 4:
5270Sstevel@tonic-gate 			local_data = (*pci_getl_func)(prg->bus_no, prg->dev_no,
5280Sstevel@tonic-gate 			    prg->func_no, prg->offset);
5290Sstevel@tonic-gate 			break;
5300Sstevel@tonic-gate 		default:
5310Sstevel@tonic-gate 			rval = ENOTSUP;
5320Sstevel@tonic-gate 			prg->status = PCITOOL_INVALID_SIZE;
5330Sstevel@tonic-gate 			break;
5340Sstevel@tonic-gate 		}
5350Sstevel@tonic-gate 
5360Sstevel@tonic-gate 		if (rval == SUCCESS) {
5370Sstevel@tonic-gate 			if (big_endian) {
5380Sstevel@tonic-gate 				prg->data =
5390Sstevel@tonic-gate 				    pcitool_swap_endian(local_data, size);
5400Sstevel@tonic-gate 			} else {
5410Sstevel@tonic-gate 				prg->data = local_data;
5420Sstevel@tonic-gate 			}
5430Sstevel@tonic-gate 		}
5440Sstevel@tonic-gate 	}
5450Sstevel@tonic-gate 	prg->phys_addr = 0;	/* Config space is not memory mapped on X86. */
5460Sstevel@tonic-gate 	return (rval);
5470Sstevel@tonic-gate }
5480Sstevel@tonic-gate 
5490Sstevel@tonic-gate 
5500Sstevel@tonic-gate /*ARGSUSED*/
5510Sstevel@tonic-gate static int
5520Sstevel@tonic-gate pcitool_io_access(dev_info_t *dip, pcitool_reg_t *prg, boolean_t write_flag)
5530Sstevel@tonic-gate {
5540Sstevel@tonic-gate 	int port = (int)prg->phys_addr;
5550Sstevel@tonic-gate 	size_t size = PCITOOL_ACC_ATTR_SIZE(prg->acc_attr);
5560Sstevel@tonic-gate 	boolean_t big_endian = PCITOOL_ACC_IS_BIG_ENDIAN(prg->acc_attr);
5570Sstevel@tonic-gate 	int rval = SUCCESS;
5580Sstevel@tonic-gate 	on_trap_data_t otd;
5590Sstevel@tonic-gate 	uint64_t local_data;
5600Sstevel@tonic-gate 
5610Sstevel@tonic-gate 
5620Sstevel@tonic-gate 	/*
5630Sstevel@tonic-gate 	 * on_trap works like setjmp.
5640Sstevel@tonic-gate 	 *
5650Sstevel@tonic-gate 	 * A non-zero return here means on_trap has returned from an error.
5660Sstevel@tonic-gate 	 *
5670Sstevel@tonic-gate 	 * A zero return here means that on_trap has just returned from setup.
5680Sstevel@tonic-gate 	 */
5690Sstevel@tonic-gate 	if (on_trap(&otd, OT_DATA_ACCESS)) {
5700Sstevel@tonic-gate 		no_trap();
5710Sstevel@tonic-gate 		if (pcitool_debug)
5720Sstevel@tonic-gate 			prom_printf(
5730Sstevel@tonic-gate 			    "pcitool_mem_access: on_trap caught an error...\n");
5740Sstevel@tonic-gate 		prg->status = PCITOOL_INVALID_ADDRESS;
5750Sstevel@tonic-gate 		return (EFAULT);
5760Sstevel@tonic-gate 	}
5770Sstevel@tonic-gate 
5780Sstevel@tonic-gate 	if (write_flag) {
5790Sstevel@tonic-gate 
5800Sstevel@tonic-gate 		if (big_endian) {
5810Sstevel@tonic-gate 			local_data = pcitool_swap_endian(prg->data, size);
5820Sstevel@tonic-gate 		} else {
5830Sstevel@tonic-gate 			local_data = prg->data;
5840Sstevel@tonic-gate 		}
5850Sstevel@tonic-gate 
5860Sstevel@tonic-gate 		if (pcitool_debug)
5870Sstevel@tonic-gate 			prom_printf("Writing %ld byte(s) to port 0x%x\n",
5880Sstevel@tonic-gate 			    size, port);
5890Sstevel@tonic-gate 
5900Sstevel@tonic-gate 		switch (size) {
5910Sstevel@tonic-gate 		case 1:
5920Sstevel@tonic-gate 			outb(port, (uint8_t)local_data);
5930Sstevel@tonic-gate 			break;
5940Sstevel@tonic-gate 		case 2:
5950Sstevel@tonic-gate 			outw(port, (uint16_t)local_data);
5960Sstevel@tonic-gate 			break;
5970Sstevel@tonic-gate 		case 4:
5980Sstevel@tonic-gate 			outl(port, (uint32_t)local_data);
5990Sstevel@tonic-gate 			break;
6000Sstevel@tonic-gate 		default:
6010Sstevel@tonic-gate 			rval = ENOTSUP;
6020Sstevel@tonic-gate 			prg->status = PCITOOL_INVALID_SIZE;
6030Sstevel@tonic-gate 			break;
6040Sstevel@tonic-gate 		}
6050Sstevel@tonic-gate 	} else {
6060Sstevel@tonic-gate 		if (pcitool_debug)
6070Sstevel@tonic-gate 			prom_printf("Reading %ld byte(s) from port 0x%x\n",
6080Sstevel@tonic-gate 			    size, port);
6090Sstevel@tonic-gate 
6100Sstevel@tonic-gate 		switch (size) {
6110Sstevel@tonic-gate 		case 1:
6120Sstevel@tonic-gate 			local_data = inb(port);
6130Sstevel@tonic-gate 			break;
6140Sstevel@tonic-gate 		case 2:
6150Sstevel@tonic-gate 			local_data = inw(port);
6160Sstevel@tonic-gate 			break;
6170Sstevel@tonic-gate 		case 4:
6180Sstevel@tonic-gate 			local_data = inl(port);
6190Sstevel@tonic-gate 			break;
6200Sstevel@tonic-gate 		default:
6210Sstevel@tonic-gate 			rval = ENOTSUP;
6220Sstevel@tonic-gate 			prg->status = PCITOOL_INVALID_SIZE;
6230Sstevel@tonic-gate 			break;
6240Sstevel@tonic-gate 		}
6250Sstevel@tonic-gate 
6260Sstevel@tonic-gate 		if (rval == SUCCESS) {
6270Sstevel@tonic-gate 			if (big_endian) {
6280Sstevel@tonic-gate 				prg->data =
6290Sstevel@tonic-gate 				    pcitool_swap_endian(local_data, size);
6300Sstevel@tonic-gate 			} else {
6310Sstevel@tonic-gate 				prg->data = local_data;
6320Sstevel@tonic-gate 			}
6330Sstevel@tonic-gate 		}
6340Sstevel@tonic-gate 	}
6350Sstevel@tonic-gate 
6360Sstevel@tonic-gate 	no_trap();
6370Sstevel@tonic-gate 	return (rval);
6380Sstevel@tonic-gate }
6390Sstevel@tonic-gate 
6400Sstevel@tonic-gate /*ARGSUSED*/
6410Sstevel@tonic-gate static int
6420Sstevel@tonic-gate pcitool_mem_access(dev_info_t *dip, pcitool_reg_t *prg, uint64_t virt_addr,
643117Sschwartz 	boolean_t write_flag)
6440Sstevel@tonic-gate {
6450Sstevel@tonic-gate 	size_t size = PCITOOL_ACC_ATTR_SIZE(prg->acc_attr);
6460Sstevel@tonic-gate 	boolean_t big_endian = PCITOOL_ACC_IS_BIG_ENDIAN(prg->acc_attr);
6470Sstevel@tonic-gate 	int rval = DDI_SUCCESS;
6480Sstevel@tonic-gate 	on_trap_data_t otd;
6490Sstevel@tonic-gate 	uint64_t local_data;
6500Sstevel@tonic-gate 
6510Sstevel@tonic-gate 	/*
6520Sstevel@tonic-gate 	 * on_trap works like setjmp.
6530Sstevel@tonic-gate 	 *
6540Sstevel@tonic-gate 	 * A non-zero return here means on_trap has returned from an error.
6550Sstevel@tonic-gate 	 *
6560Sstevel@tonic-gate 	 * A zero return here means that on_trap has just returned from setup.
6570Sstevel@tonic-gate 	 */
6580Sstevel@tonic-gate 	if (on_trap(&otd, OT_DATA_ACCESS)) {
6590Sstevel@tonic-gate 		no_trap();
6600Sstevel@tonic-gate 		if (pcitool_debug)
6610Sstevel@tonic-gate 			prom_printf(
6620Sstevel@tonic-gate 			    "pcitool_mem_access: on_trap caught an error...\n");
6630Sstevel@tonic-gate 		prg->status = PCITOOL_INVALID_ADDRESS;
6640Sstevel@tonic-gate 		return (EFAULT);
6650Sstevel@tonic-gate 	}
6660Sstevel@tonic-gate 
6670Sstevel@tonic-gate 	if (write_flag) {
6680Sstevel@tonic-gate 
6690Sstevel@tonic-gate 		if (big_endian) {
6700Sstevel@tonic-gate 			local_data = pcitool_swap_endian(prg->data, size);
6710Sstevel@tonic-gate 		} else {
6720Sstevel@tonic-gate 			local_data = prg->data;
6730Sstevel@tonic-gate 		}
6740Sstevel@tonic-gate 
6750Sstevel@tonic-gate 		switch (size) {
6760Sstevel@tonic-gate 		case 1:
6770Sstevel@tonic-gate 			*((uint8_t *)(uintptr_t)virt_addr) = local_data;
6780Sstevel@tonic-gate 			break;
6790Sstevel@tonic-gate 		case 2:
6800Sstevel@tonic-gate 			*((uint16_t *)(uintptr_t)virt_addr) = local_data;
6810Sstevel@tonic-gate 			break;
6820Sstevel@tonic-gate 		case 4:
6830Sstevel@tonic-gate 			*((uint32_t *)(uintptr_t)virt_addr) = local_data;
6840Sstevel@tonic-gate 			break;
6850Sstevel@tonic-gate 		case 8:
6860Sstevel@tonic-gate 			*((uint64_t *)(uintptr_t)virt_addr) = local_data;
6870Sstevel@tonic-gate 			break;
6880Sstevel@tonic-gate 		default:
6890Sstevel@tonic-gate 			rval = ENOTSUP;
6900Sstevel@tonic-gate 			prg->status = PCITOOL_INVALID_SIZE;
6910Sstevel@tonic-gate 			break;
6920Sstevel@tonic-gate 		}
6930Sstevel@tonic-gate 	} else {
6940Sstevel@tonic-gate 		switch (size) {
6950Sstevel@tonic-gate 		case 1:
6960Sstevel@tonic-gate 			local_data = *((uint8_t *)(uintptr_t)virt_addr);
6970Sstevel@tonic-gate 			break;
6980Sstevel@tonic-gate 		case 2:
6990Sstevel@tonic-gate 			local_data = *((uint16_t *)(uintptr_t)virt_addr);
7000Sstevel@tonic-gate 			break;
7010Sstevel@tonic-gate 		case 4:
7020Sstevel@tonic-gate 			local_data = *((uint32_t *)(uintptr_t)virt_addr);
7030Sstevel@tonic-gate 			break;
7040Sstevel@tonic-gate 		case 8:
7050Sstevel@tonic-gate 			local_data = *((uint64_t *)(uintptr_t)virt_addr);
7060Sstevel@tonic-gate 			break;
7070Sstevel@tonic-gate 		default:
7080Sstevel@tonic-gate 			rval = ENOTSUP;
7090Sstevel@tonic-gate 			prg->status = PCITOOL_INVALID_SIZE;
7100Sstevel@tonic-gate 			break;
7110Sstevel@tonic-gate 		}
7120Sstevel@tonic-gate 
7130Sstevel@tonic-gate 		if (rval == SUCCESS) {
7140Sstevel@tonic-gate 			if (big_endian) {
7150Sstevel@tonic-gate 				prg->data =
7160Sstevel@tonic-gate 				    pcitool_swap_endian(local_data, size);
7170Sstevel@tonic-gate 			} else {
7180Sstevel@tonic-gate 				prg->data = local_data;
7190Sstevel@tonic-gate 			}
7200Sstevel@tonic-gate 		}
7210Sstevel@tonic-gate 	}
7220Sstevel@tonic-gate 
7230Sstevel@tonic-gate 	no_trap();
7240Sstevel@tonic-gate 	return (rval);
7250Sstevel@tonic-gate }
7260Sstevel@tonic-gate 
7270Sstevel@tonic-gate /*
7280Sstevel@tonic-gate  * Map up to 2 pages which contain the address we want to access.
7290Sstevel@tonic-gate  *
7300Sstevel@tonic-gate  * Mapping should span no more than 8 bytes.  With X86 it is possible for an
7310Sstevel@tonic-gate  * 8 byte value to start on a 4 byte boundary, so it can cross a page boundary.
7320Sstevel@tonic-gate  * We'll never have to map more than two pages.
7330Sstevel@tonic-gate  */
7340Sstevel@tonic-gate 
7350Sstevel@tonic-gate static uint64_t
7360Sstevel@tonic-gate pcitool_map(uint64_t phys_addr, size_t size, size_t *num_pages)
7370Sstevel@tonic-gate {
7380Sstevel@tonic-gate 
7390Sstevel@tonic-gate 	uint64_t page_base = phys_addr & ~MMU_PAGEOFFSET;
7400Sstevel@tonic-gate 	uint64_t offset = phys_addr & MMU_PAGEOFFSET;
7410Sstevel@tonic-gate 	void *virt_base;
7420Sstevel@tonic-gate 	uint64_t returned_addr;
7430Sstevel@tonic-gate 
7440Sstevel@tonic-gate 	if (pcitool_debug)
7450Sstevel@tonic-gate 		prom_printf("pcitool_map: Called with PA:0x%p\n",
7460Sstevel@tonic-gate 		    (uint8_t *)(uintptr_t)phys_addr);
7470Sstevel@tonic-gate 
7480Sstevel@tonic-gate 	*num_pages = 1;
7490Sstevel@tonic-gate 
7500Sstevel@tonic-gate 	/* Desired mapping would span more than two pages. */
7510Sstevel@tonic-gate 	if ((offset + size) > (MMU_PAGESIZE * 2)) {
7520Sstevel@tonic-gate 		if (pcitool_debug)
7530Sstevel@tonic-gate 			prom_printf("boundary violation: "
754777Sschwartz 			    "offset:0x%" PRIx64 ", size:%ld, pagesize:0x%lx\n",
755777Sschwartz 			    offset, (uintptr_t)size, (uintptr_t)MMU_PAGESIZE);
7560Sstevel@tonic-gate 		return (NULL);
7570Sstevel@tonic-gate 
7580Sstevel@tonic-gate 	} else if ((offset + size) > MMU_PAGESIZE) {
7590Sstevel@tonic-gate 		(*num_pages)++;
7600Sstevel@tonic-gate 	}
7610Sstevel@tonic-gate 
7620Sstevel@tonic-gate 	/* Get page(s) of virtual space. */
7630Sstevel@tonic-gate 	virt_base = vmem_alloc(heap_arena, ptob(*num_pages), VM_NOSLEEP);
7640Sstevel@tonic-gate 	if (virt_base == NULL) {
7650Sstevel@tonic-gate 		if (pcitool_debug)
7660Sstevel@tonic-gate 			prom_printf("Couldn't get virtual base address.\n");
7670Sstevel@tonic-gate 		return (NULL);
7680Sstevel@tonic-gate 	}
7690Sstevel@tonic-gate 
7700Sstevel@tonic-gate 	if (pcitool_debug)
7710Sstevel@tonic-gate 		prom_printf("Got base virtual address:0x%p\n", virt_base);
7720Sstevel@tonic-gate 
7730Sstevel@tonic-gate 	/* Now map the allocated virtual space to the physical address. */
7740Sstevel@tonic-gate 	hat_devload(kas.a_hat, virt_base, mmu_ptob(*num_pages),
7750Sstevel@tonic-gate 	    mmu_btop(page_base), PROT_READ | PROT_WRITE | HAT_STRICTORDER,
7760Sstevel@tonic-gate 	    HAT_LOAD_LOCK);
7770Sstevel@tonic-gate 
7780Sstevel@tonic-gate 	returned_addr = ((uintptr_t)(virt_base)) + offset;
7790Sstevel@tonic-gate 
7800Sstevel@tonic-gate 	if (pcitool_debug)
7810Sstevel@tonic-gate 		prom_printf("pcitool_map: returning VA:0x%p\n",
7820Sstevel@tonic-gate 		    (void *)(uintptr_t)returned_addr);
7830Sstevel@tonic-gate 
7840Sstevel@tonic-gate 	return (returned_addr);
7850Sstevel@tonic-gate }
7860Sstevel@tonic-gate 
7870Sstevel@tonic-gate /* Unmap the mapped page(s). */
7880Sstevel@tonic-gate static void
7890Sstevel@tonic-gate pcitool_unmap(uint64_t virt_addr, size_t num_pages)
7900Sstevel@tonic-gate {
7910Sstevel@tonic-gate 	void *base_virt_addr = (void *)(uintptr_t)(virt_addr & ~MMU_PAGEOFFSET);
7920Sstevel@tonic-gate 
7930Sstevel@tonic-gate 	hat_unload(kas.a_hat, base_virt_addr, ptob(num_pages),
7940Sstevel@tonic-gate 	    HAT_UNLOAD_UNLOCK);
7950Sstevel@tonic-gate 	vmem_free(heap_arena, base_virt_addr, ptob(num_pages));
7960Sstevel@tonic-gate }
7970Sstevel@tonic-gate 
7980Sstevel@tonic-gate 
7990Sstevel@tonic-gate /* Perform register accesses on PCI leaf devices. */
8000Sstevel@tonic-gate int
801777Sschwartz pcitool_dev_reg_ops(dev_info_t *dip, void *arg, int cmd, int mode)
8020Sstevel@tonic-gate {
8030Sstevel@tonic-gate 	boolean_t	write_flag = B_FALSE;
8040Sstevel@tonic-gate 	int		rval = 0;
8050Sstevel@tonic-gate 	pcitool_reg_t	prg;
8060Sstevel@tonic-gate 	uint8_t		size;
8070Sstevel@tonic-gate 
8080Sstevel@tonic-gate 	uint64_t	base_addr;
8090Sstevel@tonic-gate 	uint64_t	virt_addr;
8100Sstevel@tonic-gate 	size_t		num_virt_pages;
8110Sstevel@tonic-gate 
8120Sstevel@tonic-gate 	switch (cmd) {
8130Sstevel@tonic-gate 	case (PCITOOL_DEVICE_SET_REG):
8140Sstevel@tonic-gate 		write_flag = B_TRUE;
8150Sstevel@tonic-gate 
8160Sstevel@tonic-gate 	/*FALLTHRU*/
8170Sstevel@tonic-gate 	case (PCITOOL_DEVICE_GET_REG):
8180Sstevel@tonic-gate 		if (pcitool_debug)
8190Sstevel@tonic-gate 			prom_printf("pci_dev_reg_ops set/get reg\n");
8200Sstevel@tonic-gate 		if (ddi_copyin(arg, &prg, sizeof (pcitool_reg_t), mode) !=
8210Sstevel@tonic-gate 		    DDI_SUCCESS) {
8220Sstevel@tonic-gate 			if (pcitool_debug)
8230Sstevel@tonic-gate 				prom_printf("Error reading arguments\n");
8240Sstevel@tonic-gate 			return (EFAULT);
8250Sstevel@tonic-gate 		}
8260Sstevel@tonic-gate 
8270Sstevel@tonic-gate 		if (prg.barnum >= (sizeof (pci_bars) / sizeof (pci_bars[0]))) {
8280Sstevel@tonic-gate 			prg.status = PCITOOL_OUT_OF_RANGE;
8290Sstevel@tonic-gate 			rval = EINVAL;
8300Sstevel@tonic-gate 			goto done_reg;
8310Sstevel@tonic-gate 		}
8320Sstevel@tonic-gate 
8330Sstevel@tonic-gate 		if (pcitool_debug)
8340Sstevel@tonic-gate 			prom_printf("raw bus:0x%x, dev:0x%x, func:0x%x\n",
8350Sstevel@tonic-gate 			    prg.bus_no, prg.dev_no, prg.func_no);
8360Sstevel@tonic-gate 		/* Validate address arguments of bus / dev / func */
8370Sstevel@tonic-gate 		if (((prg.bus_no &
8380Sstevel@tonic-gate 		    (PCI_REG_BUS_M >> PCI_REG_BUS_SHIFT)) !=
8390Sstevel@tonic-gate 		    prg.bus_no) ||
8400Sstevel@tonic-gate 		    ((prg.dev_no &
8410Sstevel@tonic-gate 		    (PCI_REG_DEV_M >> PCI_REG_DEV_SHIFT)) !=
8420Sstevel@tonic-gate 		    prg.dev_no) ||
8430Sstevel@tonic-gate 		    ((prg.func_no &
8440Sstevel@tonic-gate 		    (PCI_REG_FUNC_M >> PCI_REG_FUNC_SHIFT)) !=
8450Sstevel@tonic-gate 		    prg.func_no)) {
8460Sstevel@tonic-gate 			prg.status = PCITOOL_INVALID_ADDRESS;
8470Sstevel@tonic-gate 			rval = EINVAL;
8480Sstevel@tonic-gate 			goto done_reg;
8490Sstevel@tonic-gate 		}
8500Sstevel@tonic-gate 
8510Sstevel@tonic-gate 		size = PCITOOL_ACC_ATTR_SIZE(prg.acc_attr);
8520Sstevel@tonic-gate 
8530Sstevel@tonic-gate 		/* Proper config space desired. */
8540Sstevel@tonic-gate 		if (prg.barnum == 0) {
8550Sstevel@tonic-gate 
856777Sschwartz 			if (pcitool_debug)
857777Sschwartz 				prom_printf(
858777Sschwartz 				    "config access: offset:0x%" PRIx64 ", "
859777Sschwartz 				    "phys_addr:0x%" PRIx64 "\n",
860777Sschwartz 				    prg.offset, prg.phys_addr);
861777Sschwartz 
862777Sschwartz 			if (prg.offset >= max_cfg_size) {
8630Sstevel@tonic-gate 				prg.status = PCITOOL_OUT_OF_RANGE;
8640Sstevel@tonic-gate 				rval = EINVAL;
8650Sstevel@tonic-gate 				goto done_reg;
8660Sstevel@tonic-gate 			}
8670Sstevel@tonic-gate 
8680Sstevel@tonic-gate 			/* Access device.  prg is modified. */
869777Sschwartz 			if (max_cfg_size == PCIE_CONF_HDR_SIZE)
870777Sschwartz 				rval = pcitool_pciex_cfg_access(dip, &prg,
871777Sschwartz 				    write_flag);
872777Sschwartz 			else
873777Sschwartz 				rval = pcitool_cfg_access(dip, &prg,
874777Sschwartz 				    write_flag);
8750Sstevel@tonic-gate 
8760Sstevel@tonic-gate 			if (pcitool_debug)
8770Sstevel@tonic-gate 				prom_printf(
8780Sstevel@tonic-gate 				    "config access: data:0x%" PRIx64 "\n",
8790Sstevel@tonic-gate 				    prg.data);
8800Sstevel@tonic-gate 
8810Sstevel@tonic-gate 		/* IO/ MEM/ MEM64 space. */
8820Sstevel@tonic-gate 		} else {
8830Sstevel@tonic-gate 
8840Sstevel@tonic-gate 			pcitool_reg_t	prg2;
8850Sstevel@tonic-gate 			bcopy(&prg, &prg2, sizeof (pcitool_reg_t));
8860Sstevel@tonic-gate 
8870Sstevel@tonic-gate 			/*
8880Sstevel@tonic-gate 			 * Translate BAR number into offset of the BAR in
8890Sstevel@tonic-gate 			 * the device's config space.
8900Sstevel@tonic-gate 			 */
8910Sstevel@tonic-gate 			prg2.offset = pci_bars[prg2.barnum];
8920Sstevel@tonic-gate 			prg2.acc_attr =
8930Sstevel@tonic-gate 			    PCITOOL_ACC_ATTR_SIZE_4 | PCITOOL_ACC_ATTR_ENDN_LTL;
8940Sstevel@tonic-gate 
8950Sstevel@tonic-gate 			if (pcitool_debug)
8960Sstevel@tonic-gate 				prom_printf(
8970Sstevel@tonic-gate 				    "barnum:%d, bar_offset:0x%" PRIx64 "\n",
8980Sstevel@tonic-gate 				    prg2.barnum, prg2.offset);
8990Sstevel@tonic-gate 			/*
9000Sstevel@tonic-gate 			 * Get Bus Address Register (BAR) from config space.
9010Sstevel@tonic-gate 			 * prg2.offset is the offset into config space of the
9020Sstevel@tonic-gate 			 * BAR desired.  prg.status is modified on error.
9030Sstevel@tonic-gate 			 */
9040Sstevel@tonic-gate 			rval = pcitool_cfg_access(dip, &prg2, B_FALSE);
9050Sstevel@tonic-gate 			if (rval != SUCCESS) {
9060Sstevel@tonic-gate 				if (pcitool_debug)
9070Sstevel@tonic-gate 					prom_printf("BAR access failed\n");
9080Sstevel@tonic-gate 				prg.status = prg2.status;
9090Sstevel@tonic-gate 				goto done_reg;
9100Sstevel@tonic-gate 			}
9110Sstevel@tonic-gate 			/*
9120Sstevel@tonic-gate 			 * Reference proper PCI space based on the BAR.
9130Sstevel@tonic-gate 			 * If 64 bit MEM space, need to load other half of the
9140Sstevel@tonic-gate 			 * BAR first.
9150Sstevel@tonic-gate 			 */
9160Sstevel@tonic-gate 
9170Sstevel@tonic-gate 			if (pcitool_debug)
9180Sstevel@tonic-gate 				prom_printf("bar returned is 0x%" PRIx64 "\n",
9190Sstevel@tonic-gate 				    prg2.data);
9200Sstevel@tonic-gate 			if (!prg2.data) {
9210Sstevel@tonic-gate 				if (pcitool_debug)
9220Sstevel@tonic-gate 					prom_printf("BAR data == 0\n");
9230Sstevel@tonic-gate 				rval = EINVAL;
9240Sstevel@tonic-gate 				prg.status = PCITOOL_INVALID_ADDRESS;
9250Sstevel@tonic-gate 				goto done_reg;
9260Sstevel@tonic-gate 			}
9270Sstevel@tonic-gate 			if (prg2.data == 0xffffffff) {
9280Sstevel@tonic-gate 				if (pcitool_debug)
9290Sstevel@tonic-gate 					prom_printf("BAR data == -1\n");
9300Sstevel@tonic-gate 				rval = EINVAL;
9310Sstevel@tonic-gate 				prg.status = PCITOOL_INVALID_ADDRESS;
9320Sstevel@tonic-gate 				goto done_reg;
9330Sstevel@tonic-gate 			}
9340Sstevel@tonic-gate 
9350Sstevel@tonic-gate 			/*
9360Sstevel@tonic-gate 			 * BAR has bits saying this space is IO space, unless
9370Sstevel@tonic-gate 			 * this is the ROM address register.
9380Sstevel@tonic-gate 			 */
9390Sstevel@tonic-gate 			if (((PCI_BASE_SPACE_M & prg2.data) ==
9400Sstevel@tonic-gate 			    PCI_BASE_SPACE_IO) &&
9410Sstevel@tonic-gate 			    (prg2.offset != PCI_CONF_ROM)) {
9420Sstevel@tonic-gate 				if (pcitool_debug)
9430Sstevel@tonic-gate 					prom_printf("IO space\n");
9440Sstevel@tonic-gate 
9450Sstevel@tonic-gate 				prg2.data &= PCI_BASE_IO_ADDR_M;
9460Sstevel@tonic-gate 				prg.phys_addr = prg2.data + prg.offset;
9470Sstevel@tonic-gate 
9480Sstevel@tonic-gate 				rval = pcitool_io_access(dip, &prg, write_flag);
9490Sstevel@tonic-gate 				if ((rval != SUCCESS) && (pcitool_debug))
9500Sstevel@tonic-gate 					prom_printf("IO access failed\n");
9510Sstevel@tonic-gate 
9520Sstevel@tonic-gate 				goto done_reg;
9530Sstevel@tonic-gate 
9540Sstevel@tonic-gate 
9550Sstevel@tonic-gate 			/*
9560Sstevel@tonic-gate 			 * BAR has bits saying this space is 64 bit memory
9570Sstevel@tonic-gate 			 * space, unless this is the ROM address register.
9580Sstevel@tonic-gate 			 *
9590Sstevel@tonic-gate 			 * The 64 bit address stored in two BAR cells is not
9600Sstevel@tonic-gate 			 * necessarily aligned on an 8-byte boundary.
9610Sstevel@tonic-gate 			 * Need to keep the first 4 bytes read,
9620Sstevel@tonic-gate 			 * and do a separate read of the high 4 bytes.
9630Sstevel@tonic-gate 			 */
9640Sstevel@tonic-gate 
9650Sstevel@tonic-gate 			} else if ((PCI_BASE_TYPE_ALL & prg2.data) &&
9660Sstevel@tonic-gate 			    (prg2.offset != PCI_CONF_ROM)) {
9670Sstevel@tonic-gate 
9680Sstevel@tonic-gate 				uint32_t low_bytes =
9690Sstevel@tonic-gate 				    (uint32_t)(prg2.data & ~PCI_BASE_TYPE_ALL);
9700Sstevel@tonic-gate 
9710Sstevel@tonic-gate 				/*
9720Sstevel@tonic-gate 				 * Don't try to read the next 4 bytes
9730Sstevel@tonic-gate 				 * past the end of BARs.
9740Sstevel@tonic-gate 				 */
9750Sstevel@tonic-gate 				if (prg2.offset >= PCI_CONF_BASE5) {
9760Sstevel@tonic-gate 					prg.status = PCITOOL_OUT_OF_RANGE;
9770Sstevel@tonic-gate 					rval = EIO;
9780Sstevel@tonic-gate 					goto done_reg;
9790Sstevel@tonic-gate 				}
9800Sstevel@tonic-gate 
9810Sstevel@tonic-gate 				/*
9820Sstevel@tonic-gate 				 * Access device.
9830Sstevel@tonic-gate 				 * prg2.status is modified on error.
9840Sstevel@tonic-gate 				 */
9850Sstevel@tonic-gate 				prg2.offset += 4;
9860Sstevel@tonic-gate 				rval = pcitool_cfg_access(dip, &prg2, B_FALSE);
9870Sstevel@tonic-gate 				if (rval != SUCCESS) {
9880Sstevel@tonic-gate 					prg.status = prg2.status;
9890Sstevel@tonic-gate 					goto done_reg;
9900Sstevel@tonic-gate 				}
9910Sstevel@tonic-gate 
9920Sstevel@tonic-gate 				if (prg2.data == 0xffffffff) {
9930Sstevel@tonic-gate 					prg.status = PCITOOL_INVALID_ADDRESS;
9940Sstevel@tonic-gate 					prg.status = EFAULT;
9950Sstevel@tonic-gate 					goto done_reg;
9960Sstevel@tonic-gate 				}
9970Sstevel@tonic-gate 
9980Sstevel@tonic-gate 				prg2.data = (prg2.data << 32) + low_bytes;
9990Sstevel@tonic-gate 				if (pcitool_debug)
10000Sstevel@tonic-gate 					prom_printf(
10010Sstevel@tonic-gate 					    "64 bit mem space.  "
10020Sstevel@tonic-gate 					    "64-bit bar is 0x%" PRIx64 "\n",
10030Sstevel@tonic-gate 					    prg2.data);
10040Sstevel@tonic-gate 
10050Sstevel@tonic-gate 			/* Mem32 space, including ROM */
10060Sstevel@tonic-gate 			} else {
10070Sstevel@tonic-gate 
10080Sstevel@tonic-gate 				if (prg2.offset == PCI_CONF_ROM) {
10090Sstevel@tonic-gate 					if (pcitool_debug)
10100Sstevel@tonic-gate 						prom_printf(
10110Sstevel@tonic-gate 						    "Additional ROM "
10120Sstevel@tonic-gate 						    "checking\n");
10130Sstevel@tonic-gate 					/* Can't write to ROM */
10140Sstevel@tonic-gate 					if (write_flag) {
10150Sstevel@tonic-gate 						prg.status = PCITOOL_ROM_WRITE;
10160Sstevel@tonic-gate 						rval = EIO;
10170Sstevel@tonic-gate 						goto done_reg;
10180Sstevel@tonic-gate 
10190Sstevel@tonic-gate 					/* ROM disabled for reading */
10200Sstevel@tonic-gate 					} else if (!(prg2.data & 0x00000001)) {
10210Sstevel@tonic-gate 						prg.status =
10220Sstevel@tonic-gate 						    PCITOOL_ROM_DISABLED;
10230Sstevel@tonic-gate 						rval = EIO;
10240Sstevel@tonic-gate 						goto done_reg;
10250Sstevel@tonic-gate 					}
10260Sstevel@tonic-gate 				}
10270Sstevel@tonic-gate 
10280Sstevel@tonic-gate 				if (pcitool_debug)
10290Sstevel@tonic-gate 					prom_printf("32 bit mem space\n");
10300Sstevel@tonic-gate 			}
10310Sstevel@tonic-gate 
10320Sstevel@tonic-gate 			/* Common code for all IO/MEM range spaces. */
10330Sstevel@tonic-gate 
10340Sstevel@tonic-gate 			base_addr = prg2.data;
10350Sstevel@tonic-gate 			if (pcitool_debug)
10360Sstevel@tonic-gate 				prom_printf(
10370Sstevel@tonic-gate 				    "addr portion of bar is 0x%" PRIx64 ", "
10380Sstevel@tonic-gate 				    "base=0x%" PRIx64 ", "
10390Sstevel@tonic-gate 				    "offset:0x%" PRIx64 "\n",
10400Sstevel@tonic-gate 				    prg2.data, base_addr, prg.offset);
10410Sstevel@tonic-gate 			/*
10420Sstevel@tonic-gate 			 * Use offset provided by caller to index into
10430Sstevel@tonic-gate 			 * desired space, then access.
10440Sstevel@tonic-gate 			 * Note that prg.status is modified on error.
10450Sstevel@tonic-gate 			 */
10460Sstevel@tonic-gate 			prg.phys_addr = base_addr + prg.offset;
10470Sstevel@tonic-gate 
10480Sstevel@tonic-gate 			virt_addr = pcitool_map(prg.phys_addr, size,
10490Sstevel@tonic-gate 			    &num_virt_pages);
10500Sstevel@tonic-gate 			if (virt_addr == NULL) {
10510Sstevel@tonic-gate 				prg.status = PCITOOL_IO_ERROR;
10520Sstevel@tonic-gate 				rval = EIO;
10530Sstevel@tonic-gate 				goto done_reg;
10540Sstevel@tonic-gate 			}
10550Sstevel@tonic-gate 
10560Sstevel@tonic-gate 			rval = pcitool_mem_access(dip, &prg, virt_addr,
10570Sstevel@tonic-gate 			    write_flag);
10580Sstevel@tonic-gate 			pcitool_unmap(virt_addr, num_virt_pages);
10590Sstevel@tonic-gate 		}
10600Sstevel@tonic-gate done_reg:
10610Sstevel@tonic-gate 		if (ddi_copyout(&prg, arg, sizeof (pcitool_reg_t), mode) !=
10620Sstevel@tonic-gate 		    DDI_SUCCESS) {
10630Sstevel@tonic-gate 			if (pcitool_debug)
10640Sstevel@tonic-gate 				prom_printf("Error returning arguments.\n");
10650Sstevel@tonic-gate 			rval = EFAULT;
10660Sstevel@tonic-gate 		}
10670Sstevel@tonic-gate 		break;
10680Sstevel@tonic-gate 	default:
10690Sstevel@tonic-gate 		rval = ENOTTY;
10700Sstevel@tonic-gate 		break;
10710Sstevel@tonic-gate 	}
10720Sstevel@tonic-gate 	return (rval);
10730Sstevel@tonic-gate }
1074