xref: /onnv-gate/usr/src/uts/sun4/io/px/px_tools.c (revision 4397:8359c43a28c3)
1624Sschwartz /*
2624Sschwartz  * CDDL HEADER START
3624Sschwartz  *
4624Sschwartz  * The contents of this file are subject to the terms of the
51772Sjl139090  * Common Development and Distribution License (the "License").
61772Sjl139090  * You may not use this file except in compliance with the License.
7624Sschwartz  *
8624Sschwartz  * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
9624Sschwartz  * or http://www.opensolaris.org/os/licensing.
10624Sschwartz  * See the License for the specific language governing permissions
11624Sschwartz  * and limitations under the License.
12624Sschwartz  *
13624Sschwartz  * When distributing Covered Code, include this CDDL HEADER in each
14624Sschwartz  * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
15624Sschwartz  * If applicable, add the following below this CDDL HEADER, with the
16624Sschwartz  * fields enclosed by brackets "[]" replaced with your own identifying
17624Sschwartz  * information: Portions Copyright [yyyy] [name of copyright owner]
18624Sschwartz  *
19624Sschwartz  * CDDL HEADER END
20624Sschwartz  */
21624Sschwartz /*
223625Segillett  * Copyright 2007 Sun Microsystems, Inc.  All rights reserved.
23624Sschwartz  * Use is subject to license terms.
24624Sschwartz  */
25624Sschwartz 
26624Sschwartz #pragma ident	"%Z%%M%	%I%	%E% SMI"
27624Sschwartz 
28624Sschwartz #include <sys/types.h>
29624Sschwartz #include <sys/stat.h>
30624Sschwartz #include <sys/cpuvar.h>
31624Sschwartz #include <sys/kmem.h>
32624Sschwartz #include <sys/sunddi.h>
33624Sschwartz #include <sys/hotplug/pci/pcihp.h>
34624Sschwartz #include "px_obj.h"
35624Sschwartz #include <sys/pci_tools.h>
36777Sschwartz #include "px_tools_ext.h"
37624Sschwartz #include "px_tools_var.h"
38624Sschwartz 
39624Sschwartz /*
40624Sschwartz  * PCI Space definitions.
41624Sschwartz  */
42624Sschwartz #define	PCI_CONFIG_SPACE	(PCI_REG_ADDR_G(PCI_ADDR_CONFIG))
43624Sschwartz #define	PCI_IO_SPACE		(PCI_REG_ADDR_G(PCI_ADDR_IO))
44624Sschwartz #define	PCI_MEM32_SPACE		(PCI_REG_ADDR_G(PCI_ADDR_MEM32))
45624Sschwartz #define	PCI_MEM64_SPACE		(PCI_REG_ADDR_G(PCI_ADDR_MEM64))
46624Sschwartz 
47624Sschwartz /*
48624Sschwartz  * Config space range for a device.  IEEE 1275 spec defines for PCI.
49624Sschwartz  * Space for PCI-express is multiplied by PX_PCI_BDF_OFFSET_DELTA
50624Sschwartz  */
51624Sschwartz #define	DEV_CFG_SPACE_SIZE	\
52624Sschwartz 	(1 << (PCI_REG_FUNC_SHIFT + PX_PCI_BDF_OFFSET_DELTA))
53624Sschwartz 
54624Sschwartz /*
55624Sschwartz  * Offsets of BARS in config space.  First entry of 0 means config space.
56624Sschwartz  * Entries here correlate to pcitool_bars_t enumerated type.
57624Sschwartz  */
58624Sschwartz uint8_t pci_bars[] = {
59624Sschwartz 	0x0,
60624Sschwartz 	PCI_CONF_BASE0,
61624Sschwartz 	PCI_CONF_BASE1,
62624Sschwartz 	PCI_CONF_BASE2,
63624Sschwartz 	PCI_CONF_BASE3,
64624Sschwartz 	PCI_CONF_BASE4,
65624Sschwartz 	PCI_CONF_BASE5,
66624Sschwartz 	PCI_CONF_ROM
67624Sschwartz };
68624Sschwartz 
69624Sschwartz int	pci_num_bars = sizeof (pci_bars) / sizeof (pci_bars[0]);
70624Sschwartz 
71624Sschwartz /*
72624Sschwartz  * Validate the cpu_id passed in.
73624Sschwartz  * A value of 1 will be returned for success and zero for failure.
74624Sschwartz  */
75624Sschwartz static int
76624Sschwartz pxtool_validate_cpuid(uint32_t cpuid)
77624Sschwartz {
78624Sschwartz 	extern const int _ncpu;
79624Sschwartz 	extern cpu_t	*cpu[];
80624Sschwartz 
81624Sschwartz 	ASSERT(mutex_owned(&cpu_lock));
82624Sschwartz 
83624Sschwartz 	return ((cpuid < _ncpu) && (cpu[cpuid] && cpu_is_online(cpu[cpuid])));
84624Sschwartz }
85624Sschwartz 
86624Sschwartz 
87*4397Sschwartz /*ARGSUSED*/
88624Sschwartz static int
89*4397Sschwartz pxtool_intr_info(dev_info_t *dip, void *arg, int mode)
90624Sschwartz {
91*4397Sschwartz 	pcitool_intr_info_t intr_info;
92*4397Sschwartz 	int rval = SUCCESS;
93*4397Sschwartz 
94*4397Sschwartz 	/* If we need user_version, and to ret same user version as passed in */
95*4397Sschwartz 	if (ddi_copyin(arg, &intr_info, sizeof (pcitool_intr_info_t), mode) !=
96*4397Sschwartz 	    DDI_SUCCESS) {
97624Sschwartz 		return (EFAULT);
98*4397Sschwartz 	}
99*4397Sschwartz 
100*4397Sschwartz 	intr_info.ctlr_version = 0;	/* XXX how to get real version? */
101*4397Sschwartz 	intr_info.ctlr_type = PCITOOL_CTLR_TYPE_RISC;
102*4397Sschwartz 	intr_info.num_intr = pxtool_num_inos;
103*4397Sschwartz 
104*4397Sschwartz 	intr_info.drvr_version = PCITOOL_VERSION;
105*4397Sschwartz 	if (ddi_copyout(&intr_info, arg, sizeof (pcitool_intr_info_t), mode) !=
106*4397Sschwartz 	    DDI_SUCCESS) {
107*4397Sschwartz 		rval = EFAULT;
108*4397Sschwartz 	}
109*4397Sschwartz 
110*4397Sschwartz 	return (rval);
111624Sschwartz }
112*4397Sschwartz 
113*4397Sschwartz 
114624Sschwartz /*
115624Sschwartz  * Get interrupt information for a given ino.
116624Sschwartz  * Returns info only for inos mapped to devices.
117624Sschwartz  *
118624Sschwartz  * Returned info is valid only when iget.num_devs is returned > 0.
119624Sschwartz  * If ino is not enabled or is not mapped to a device,
120624Sschwartz  * iget.num_devs will be returned as = 0.
121624Sschwartz  */
122624Sschwartz /*ARGSUSED*/
123624Sschwartz static int
124624Sschwartz pxtool_get_intr(dev_info_t *dip, void *arg, int mode)
125624Sschwartz {
126624Sschwartz 	/* Array part isn't used here, but oh well... */
127624Sschwartz 	pcitool_intr_get_t partial_iget;
128624Sschwartz 	uint32_t ino;
129624Sschwartz 	uint8_t num_devs_ret;
130624Sschwartz 	int copyout_rval;
131624Sschwartz 	sysino_t sysino;
132624Sschwartz 	intr_valid_state_t intr_valid_state;
133624Sschwartz 	cpuid_t old_cpu_id;
134624Sschwartz 	px_t *px_p = DIP_TO_STATE(dip);
135624Sschwartz 	pcitool_intr_get_t *iget = &partial_iget;
136624Sschwartz 	size_t	iget_kmem_alloc_size = 0;
137624Sschwartz 	int rval = SUCCESS;
138624Sschwartz 
139624Sschwartz 	/* Read in just the header part, no array section. */
140624Sschwartz 	if (ddi_copyin(arg, &partial_iget, PCITOOL_IGET_SIZE(0), mode) !=
141624Sschwartz 	    DDI_SUCCESS)
142624Sschwartz 		return (EFAULT);
143624Sschwartz 
144624Sschwartz 	ino = partial_iget.ino;
145624Sschwartz 	num_devs_ret = partial_iget.num_devs_ret;
146624Sschwartz 
147624Sschwartz 	partial_iget.num_devs_ret = 0;		/* Assume error for now. */
148624Sschwartz 	partial_iget.status = PCITOOL_INVALID_INO;
149624Sschwartz 	rval = EINVAL;
150624Sschwartz 
151624Sschwartz 	/* Validate argument. */
152624Sschwartz 	if (partial_iget.ino > pxtool_num_inos) {
153624Sschwartz 		goto done_get_intr;
154624Sschwartz 	}
155624Sschwartz 
156624Sschwartz 	/* Caller wants device information returned. */
157624Sschwartz 	if (num_devs_ret > 0) {
158624Sschwartz 
159624Sschwartz 		/*
160624Sschwartz 		 * Allocate room.
161624Sschwartz 		 * Note if num_devs == 0 iget remains pointing to
162624Sschwartz 		 * partial_iget.
163624Sschwartz 		 */
164624Sschwartz 		iget_kmem_alloc_size = PCITOOL_IGET_SIZE(num_devs_ret);
165624Sschwartz 		iget = kmem_alloc(iget_kmem_alloc_size, KM_SLEEP);
166624Sschwartz 
167624Sschwartz 		/* Read in whole structure to verify there's room. */
168624Sschwartz 		if (ddi_copyin(arg, iget, iget_kmem_alloc_size, mode) !=
169624Sschwartz 		    SUCCESS) {
170624Sschwartz 
171624Sschwartz 			/* Be consistent and just return EFAULT here. */
172624Sschwartz 			kmem_free(iget, iget_kmem_alloc_size);
173624Sschwartz 
174624Sschwartz 			return (EFAULT);
175624Sschwartz 		}
176624Sschwartz 	}
177624Sschwartz 
178624Sschwartz 	bzero(iget, PCITOOL_IGET_SIZE(num_devs_ret));
179624Sschwartz 	iget->ino = ino;
180624Sschwartz 	iget->num_devs_ret = num_devs_ret;
181624Sschwartz 
182624Sschwartz 	/* Convert leaf-wide intr to system-wide intr */
183624Sschwartz 	if (px_lib_intr_devino_to_sysino(dip, iget->ino, &sysino) ==
184624Sschwartz 	    DDI_FAILURE) {
185624Sschwartz 		iget->status = PCITOOL_IO_ERROR;
186624Sschwartz 		rval = EIO;
187624Sschwartz 		goto done_get_intr;
188624Sschwartz 	}
189624Sschwartz 
190624Sschwartz 	/* Operate only on inos which are already enabled. */
191624Sschwartz 	if (px_lib_intr_getvalid(dip, sysino, &intr_valid_state) ==
192624Sschwartz 	    DDI_FAILURE) {
193624Sschwartz 		iget->status = PCITOOL_IO_ERROR;
194624Sschwartz 		rval = EIO;
195624Sschwartz 		goto done_get_intr;
196624Sschwartz 	}
197624Sschwartz 
198624Sschwartz 	/*
199624Sschwartz 	 * Consider all valid inos: those mapped to the root complex itself
200624Sschwartz 	 * as well as those mapped to devices.
201624Sschwartz 	 */
202624Sschwartz 	if (intr_valid_state == INTR_VALID) {
203624Sschwartz 
204624Sschwartz 		/*
2052973Sgovinda 		 * The following looks up the px_ino and returns
206624Sschwartz 		 * info of devices mapped to this ino.
207624Sschwartz 		 */
208624Sschwartz 		iget->num_devs = pxtool_ib_get_ino_devs(
209624Sschwartz 		    px_p, ino, &iget->num_devs_ret, iget->dev);
210624Sschwartz 
211624Sschwartz 		if (px_lib_intr_gettarget(dip, sysino, &old_cpu_id) ==
212624Sschwartz 		    DDI_FAILURE) {
213624Sschwartz 			iget->status = PCITOOL_IO_ERROR;
214624Sschwartz 			rval = EIO;
215624Sschwartz 			goto done_get_intr;
216624Sschwartz 		}
217624Sschwartz 		iget->cpu_id = old_cpu_id;
218624Sschwartz 	}
219624Sschwartz 
220624Sschwartz 	iget->status = PCITOOL_SUCCESS;
221624Sschwartz 	rval = SUCCESS;
222624Sschwartz 
223624Sschwartz done_get_intr:
224*4397Sschwartz 	iget->drvr_version = PCITOOL_VERSION;
225624Sschwartz 	copyout_rval =
226624Sschwartz 	    ddi_copyout(iget, arg, PCITOOL_IGET_SIZE(num_devs_ret), mode);
227624Sschwartz 
228624Sschwartz 	if (iget_kmem_alloc_size > 0)
229624Sschwartz 		kmem_free(iget, iget_kmem_alloc_size);
230624Sschwartz 
231624Sschwartz 	if (copyout_rval != DDI_SUCCESS)
232624Sschwartz 		rval = EFAULT;
233624Sschwartz 
234624Sschwartz 	return (rval);
235624Sschwartz }
236624Sschwartz 
237624Sschwartz 
238624Sschwartz /*
239624Sschwartz  * Associate a new CPU with a given ino.
240624Sschwartz  *
241624Sschwartz  * Operate only on inos which are already mapped to devices.
242624Sschwartz  */
243624Sschwartz static int
244624Sschwartz pxtool_set_intr(dev_info_t *dip, void *arg, int mode)
245624Sschwartz {
246624Sschwartz 	pcitool_intr_set_t iset;
247624Sschwartz 	cpuid_t old_cpu_id;
248624Sschwartz 	sysino_t sysino;
249624Sschwartz 	px_t *px_p = DIP_TO_STATE(dip);
250624Sschwartz 	px_ib_t *ib_p = px_p->px_ib_p;
251624Sschwartz 	uint8_t zero = 0;
252624Sschwartz 	int rval = SUCCESS;
253*4397Sschwartz 	size_t copyinout_size;
254624Sschwartz 
255*4397Sschwartz 	bzero(&iset, sizeof (pcitool_intr_set_t));
256*4397Sschwartz 
257*4397Sschwartz 	/* Version 1 of pcitool_intr_set_t doesn't have flags. */
258*4397Sschwartz 	copyinout_size = (size_t)&iset.flags - (size_t)&iset;
259*4397Sschwartz 
260*4397Sschwartz 	if (ddi_copyin(arg, &iset, copyinout_size, mode) != DDI_SUCCESS)
261624Sschwartz 		return (EFAULT);
262624Sschwartz 
263*4397Sschwartz 	switch (iset.user_version) {
264*4397Sschwartz 	case PCITOOL_V1:
265*4397Sschwartz 		break;
266*4397Sschwartz 
267*4397Sschwartz 	case PCITOOL_V2:
268*4397Sschwartz 		copyinout_size = sizeof (pcitool_intr_set_t);
269*4397Sschwartz 		if (ddi_copyin(arg, &iset, copyinout_size, mode) != DDI_SUCCESS)
270*4397Sschwartz 			return (EFAULT);
271*4397Sschwartz 		break;
272*4397Sschwartz 
273*4397Sschwartz 	default:
274*4397Sschwartz 		iset.status = PCITOOL_OUT_OF_RANGE;
275*4397Sschwartz 		rval = ENOTSUP;
276*4397Sschwartz 		goto done_set_intr;
277*4397Sschwartz 	}
278*4397Sschwartz 
279*4397Sschwartz 	if (iset.flags & PCITOOL_INTR_SET_FLAG_GROUP) {
280*4397Sschwartz 		iset.status = PCITOOL_IO_ERROR;
281*4397Sschwartz 		rval = ENOTSUP;
282*4397Sschwartz 		goto done_set_intr;
283*4397Sschwartz 	}
284*4397Sschwartz 
285624Sschwartz 	iset.status = PCITOOL_INVALID_INO;
286624Sschwartz 	rval = EINVAL;
287624Sschwartz 
288624Sschwartz 	/* Validate input argument. */
289624Sschwartz 	if (iset.ino > pxtool_num_inos)
290624Sschwartz 		goto done_set_intr;
291624Sschwartz 
292624Sschwartz 	/* Validate that ino given belongs to a device. */
293624Sschwartz 	if (pxtool_ib_get_ino_devs(px_p, iset.ino, &zero, NULL) == 0)
294624Sschwartz 		goto done_set_intr;
295624Sschwartz 
296624Sschwartz 	/*
297624Sschwartz 	 * Get lock, validate cpu and write new mapreg value.
298624Sschwartz 	 * Return original cpu value to caller via iset.cpu.
299624Sschwartz 	 */
300624Sschwartz 	mutex_enter(&cpu_lock);
301624Sschwartz 	if (pxtool_validate_cpuid(iset.cpu_id)) {
302624Sschwartz 
303624Sschwartz 		DBG(DBG_TOOLS, dip, "Enabling CPU %d\n", iset.cpu_id);
304624Sschwartz 
305624Sschwartz 		if (px_lib_intr_devino_to_sysino(dip, iset.ino, &sysino) ==
306624Sschwartz 		    DDI_FAILURE)
307624Sschwartz 			goto done_set_intr;
308624Sschwartz 
309624Sschwartz 		if (px_lib_intr_gettarget(dip, sysino, &old_cpu_id) ==
310624Sschwartz 		    DDI_FAILURE)
311624Sschwartz 			goto done_set_intr;
312624Sschwartz 
313624Sschwartz 		px_ib_intr_dist_en(dip, iset.cpu_id, iset.ino, B_TRUE);
314624Sschwartz 
315624Sschwartz 		px_ib_log_new_cpu(ib_p, old_cpu_id, iset.cpu_id, iset.ino);
316624Sschwartz 
317624Sschwartz 		iset.cpu_id = old_cpu_id;
318624Sschwartz 		iset.status = PCITOOL_SUCCESS;
319624Sschwartz 		rval = SUCCESS;
320624Sschwartz 
321624Sschwartz 	} else {	/* Invalid cpu.  Restore original register image. */
322624Sschwartz 
323624Sschwartz 		DBG(DBG_TOOLS, dip,
324624Sschwartz 		    "Invalid cpuid: writing orig mapreg value\n");
325624Sschwartz 
326624Sschwartz 		iset.status = PCITOOL_INVALID_CPUID;
327624Sschwartz 		rval = EINVAL;
328624Sschwartz 	}
329624Sschwartz 	mutex_exit(&cpu_lock);
330624Sschwartz 
331624Sschwartz done_set_intr:
332*4397Sschwartz 	iset.drvr_version = PCITOOL_VERSION;
333*4397Sschwartz 	if (ddi_copyout(&iset, arg, copyinout_size, mode) != DDI_SUCCESS)
334624Sschwartz 		rval = EFAULT;
335624Sschwartz 
336624Sschwartz 	return (rval);
337624Sschwartz }
338624Sschwartz 
339624Sschwartz 
340624Sschwartz /* Main function for handling interrupt CPU binding requests and queries. */
341624Sschwartz int
342624Sschwartz pxtool_intr(dev_info_t *dip, void *arg, int cmd, int mode)
343624Sschwartz {
344624Sschwartz 	int rval = SUCCESS;
345624Sschwartz 
346624Sschwartz 	switch (cmd) {
347624Sschwartz 
348*4397Sschwartz 	/* Get system interrupt information. */
349*4397Sschwartz 	case PCITOOL_SYSTEM_INTR_INFO:
350*4397Sschwartz 		rval = pxtool_intr_info(dip, arg, mode);
351624Sschwartz 		break;
352624Sschwartz 
353624Sschwartz 	/* Get interrupt information for a given ino. */
354624Sschwartz 	case PCITOOL_DEVICE_GET_INTR:
355624Sschwartz 		rval = pxtool_get_intr(dip, arg, mode);
356624Sschwartz 		break;
357624Sschwartz 
358624Sschwartz 	/* Associate a new CPU with a given ino. */
359624Sschwartz 	case PCITOOL_DEVICE_SET_INTR:
360624Sschwartz 		rval = pxtool_set_intr(dip, arg, mode);
361624Sschwartz 		break;
362624Sschwartz 
363624Sschwartz 	default:
364624Sschwartz 		rval = ENOTTY;
365624Sschwartz 	}
366624Sschwartz 
367624Sschwartz 	return (rval);
368624Sschwartz }
369624Sschwartz 
370624Sschwartz 
371624Sschwartz static int
372624Sschwartz pxtool_validate_barnum_bdf(pcitool_reg_t *prg)
373624Sschwartz {
374624Sschwartz 	int rval = SUCCESS;
375624Sschwartz 
376624Sschwartz 	if (prg->barnum >= (sizeof (pci_bars) / sizeof (pci_bars[0]))) {
377624Sschwartz 		prg->status = PCITOOL_OUT_OF_RANGE;
378624Sschwartz 		rval = EINVAL;
379624Sschwartz 
380624Sschwartz 	/* Validate address arguments of bus / dev / func */
381624Sschwartz 	} else if (((prg->bus_no &
382624Sschwartz 	    (PCI_REG_BUS_M >> PCI_REG_BUS_SHIFT)) != prg->bus_no) ||
383624Sschwartz 	    ((prg->dev_no &
384624Sschwartz 	    (PCI_REG_DEV_M >> PCI_REG_DEV_SHIFT)) != prg->dev_no) ||
385624Sschwartz 	    ((prg->func_no &
386624Sschwartz 	    (PCI_REG_FUNC_M >> PCI_REG_FUNC_SHIFT)) != prg->func_no)) {
387624Sschwartz 		prg->status = PCITOOL_INVALID_ADDRESS;
388624Sschwartz 		rval = EINVAL;
389624Sschwartz 	}
390624Sschwartz 
391624Sschwartz 	return (rval);
392624Sschwartz }
393624Sschwartz 
3941064Sschwartz /*
3951064Sschwartz  * px_p defines which leaf, space defines which space in that leaf, offset
3961064Sschwartz  * defines the offset within the specified space.
3971064Sschwartz  *
3981064Sschwartz  * This returns the physical address of the corresponding location.
3991064Sschwartz  */
4001064Sschwartz static uintptr_t
4011064Sschwartz pxtool_get_phys_addr(px_t *px_p, int space, uint64_t offset)
4021064Sschwartz {
4031064Sschwartz 	uint64_t range_base;
4041064Sschwartz 	int rval;
4052053Sschwartz 	pci_regspec_t dev_regspec;
4062053Sschwartz 	struct regspec xlated_regspec;
4071064Sschwartz 	dev_info_t *dip = px_p->px_dip;
4081064Sschwartz 
4091064Sschwartz 	/*
4101064Sschwartz 	 * Assume that requested entity is small enough to be on the same page.
4111064Sschwartz 	 * PCItool checks alignment so that this will be true for single
4121064Sschwartz 	 * accesses.
4131064Sschwartz 	 */
4142053Sschwartz 	dev_regspec.pci_phys_hi = space << PCI_REG_ADDR_SHIFT;
4152053Sschwartz 	if (space == PCI_CONFIG_SPACE) {
4162053Sschwartz 		dev_regspec.pci_phys_hi +=
4172053Sschwartz 		    (offset & (PCI_REG_BDFR_M ^ PCI_REG_REG_M));
4182053Sschwartz 		dev_regspec.pci_phys_low = offset & PCI_REG_REG_M;
4193625Segillett 		dev_regspec.pci_phys_mid = 0;	/* Not used */
4202053Sschwartz 	} else {
4212053Sschwartz 		dev_regspec.pci_phys_mid = offset >> 32;
4222053Sschwartz 		dev_regspec.pci_phys_low = offset & 0xffffffff;
4232053Sschwartz 	}
4242053Sschwartz 	dev_regspec.pci_size_hi = 0;	/* Not used. */
4252053Sschwartz 
4262053Sschwartz 	/* Note: object is guaranteed to be within a page. */
4272053Sschwartz 	dev_regspec.pci_size_low = 4;
4282053Sschwartz 
4292053Sschwartz 	rval = px_xlate_reg(px_p, &dev_regspec, &xlated_regspec);
4302053Sschwartz 
4311064Sschwartz 	DBG(DBG_TOOLS, dip,
4322053Sschwartz 	    "space:0x%d, offset:0x%" PRIx64 "\n", space, offset);
4331064Sschwartz 
4341064Sschwartz 	if (rval != DDI_SUCCESS)
4351064Sschwartz 		return (NULL);
4362053Sschwartz 
4372053Sschwartz 	/* Bustype here returns the high order address bits. */
4382053Sschwartz 	xlated_regspec.regspec_bustype &= px_get_rng_parent_hi_mask(px_p);
4392053Sschwartz 
4402053Sschwartz 	range_base = (((uint64_t)xlated_regspec.regspec_bustype) << 32) +
4412053Sschwartz 	    xlated_regspec.regspec_addr;
4422053Sschwartz 	DBG(DBG_TOOLS, dip,
4432053Sschwartz 	    "regspec: hi:0x%x, lo:0x%x, sz:0x%x, range base:0x%" PRIx64 "\n",
4442053Sschwartz 	    xlated_regspec.regspec_bustype, xlated_regspec.regspec_addr,
4452053Sschwartz 	    xlated_regspec.regspec_size, range_base);
4462053Sschwartz 
4472053Sschwartz 	return ((uintptr_t)range_base);
4481064Sschwartz }
4491064Sschwartz 
4501064Sschwartz 
451624Sschwartz static int
4521064Sschwartz pxtool_get_bar(px_t *px_p, pcitool_reg_t *prg_p, uint64_t *bar_p,
4531064Sschwartz     uint32_t *space_p)
454624Sschwartz {
455624Sschwartz 	int rval;
4561064Sschwartz 	uint64_t off_in_space;
457624Sschwartz 	pcitool_reg_t cfg_prg = *prg_p;	/* Make local copy. */
458624Sschwartz 	dev_info_t *dip = px_p->px_dip;
459624Sschwartz 
460624Sschwartz 	*space_p = PCI_MEM32_SPACE;
461624Sschwartz 	*bar_p = 0;
462624Sschwartz 
463624Sschwartz 	/*
464624Sschwartz 	 * Translate BAR number into offset of the BAR in
465624Sschwartz 	 * the device's config space.
466624Sschwartz 	 */
467624Sschwartz 	cfg_prg.acc_attr =
468624Sschwartz 	    PCITOOL_ACC_ATTR_SIZE_4 | PCITOOL_ACC_ATTR_ENDN_LTL;
469624Sschwartz 
4701064Sschwartz 	/*
4711064Sschwartz 	 * Note: sun4u acc function uses phys_addr which includes offset.
4721064Sschwartz 	 * sun4v acc function doesn't use phys_addr but uses cfg_prg.offset.
4731064Sschwartz 	 */
4741064Sschwartz 	cfg_prg.offset = PCI_BAR_OFFSET((*prg_p));
4752053Sschwartz 	off_in_space = PX_GET_BDF(prg_p) + cfg_prg.offset;
4761064Sschwartz 	cfg_prg.phys_addr = pxtool_get_phys_addr(px_p, PCI_CONFIG_SPACE,
4771064Sschwartz 	    off_in_space);
4781064Sschwartz 
4791064Sschwartz 	DBG(DBG_TOOLS, dip,
4801064Sschwartz 	    "off_in_space:0x%" PRIx64 ", phys_addr:0x%" PRIx64 ", barnum:%d\n",
4811064Sschwartz 	    off_in_space, cfg_prg.phys_addr, cfg_prg.barnum);
482624Sschwartz 
483624Sschwartz 	/*
484624Sschwartz 	 * Get Bus Address Register (BAR) from config space.
485624Sschwartz 	 * cfg_prg.offset is the offset into config space of the
486624Sschwartz 	 * BAR desired.  prg_p->status is modified on error.
487624Sschwartz 	 */
4881064Sschwartz 	rval = pxtool_pcicfg_access(px_p, &cfg_prg, bar_p, PX_ISREAD);
489624Sschwartz 
490624Sschwartz 	if (rval != SUCCESS) {
491624Sschwartz 		prg_p->status = cfg_prg.status;
492624Sschwartz 		return (rval);
493624Sschwartz 	}
494624Sschwartz 
495624Sschwartz 	DBG(DBG_TOOLS, dip, "bar returned is 0x%" PRIx64 "\n", *bar_p);
496624Sschwartz 
497624Sschwartz 	/*
498624Sschwartz 	 * BAR has bits saying this space is IO space, unless
499624Sschwartz 	 * this is the ROM address register.
500624Sschwartz 	 */
501624Sschwartz 	if (((PCI_BASE_SPACE_M & *bar_p) == PCI_BASE_SPACE_IO) &&
502624Sschwartz 	    (cfg_prg.offset != PCI_CONF_ROM)) {
503624Sschwartz 		*space_p = PCI_IO_SPACE;
504624Sschwartz 		*bar_p &= PCI_BASE_IO_ADDR_M;
505624Sschwartz 
506624Sschwartz 	/*
507624Sschwartz 	 * BAR has bits saying this space is 64 bit memory
508624Sschwartz 	 * space, unless this is the ROM address register.
509624Sschwartz 	 *
510624Sschwartz 	 * The 64 bit address stored in two BAR cells is not
511624Sschwartz 	 * necessarily aligned on an 8-byte boundary.
512624Sschwartz 	 * Need to keep the first 4 bytes read,
513624Sschwartz 	 * and do a separate read of the high 4 bytes.
514624Sschwartz 	 */
515624Sschwartz 	} else if ((PCI_BASE_TYPE_ALL & *bar_p) &&
516624Sschwartz 	    (cfg_prg.offset != PCI_CONF_ROM)) {
517624Sschwartz 
518624Sschwartz 		uint32_t low_bytes = (uint32_t)(*bar_p & ~PCI_BASE_TYPE_ALL);
519624Sschwartz 
520624Sschwartz 		/* Don't try to read the next 4 bytes past the end of BARs. */
521624Sschwartz 		if (cfg_prg.offset >= PCI_CONF_BASE5) {
522624Sschwartz 			prg_p->status = PCITOOL_OUT_OF_RANGE;
523624Sschwartz 			return (EIO);
524624Sschwartz 		}
525624Sschwartz 
526624Sschwartz 		/* Access device.  prg_p->status is modified on error. */
527624Sschwartz 		cfg_prg.phys_addr += sizeof (uint32_t);
528624Sschwartz 		cfg_prg.offset += sizeof (uint32_t);
529624Sschwartz 
5301064Sschwartz 		rval = pxtool_pcicfg_access(px_p, &cfg_prg, bar_p, PX_ISREAD);
531624Sschwartz 		if (rval != SUCCESS) {
532624Sschwartz 			prg_p->status = cfg_prg.status;
533624Sschwartz 			return (rval);
534624Sschwartz 		}
535624Sschwartz 
536624Sschwartz 		/*
537624Sschwartz 		 * Honor the 64 bit BAR as such, only when the upper 32 bits
538624Sschwartz 		 * store a non-zero value.
539624Sschwartz 		 */
540624Sschwartz 		if (*bar_p) {
541624Sschwartz 			*space_p = PCI_MEM64_SPACE;
542624Sschwartz 			*bar_p = (*bar_p << 32) | low_bytes;
543624Sschwartz 		} else
544624Sschwartz 			*bar_p = low_bytes;
545624Sschwartz 
546624Sschwartz 	} else if (cfg_prg.offset == PCI_CONF_ROM) { /* ROM requested */
547624Sschwartz 
548624Sschwartz 		/*
549624Sschwartz 		 * ROM enabled. Filter ROM enable bit from the BAR.
550624Sschwartz 		 * Treat as Mem32 henceforth.
551624Sschwartz 		 */
552624Sschwartz 		if (!(*bar_p & PCI_BASE_ROM_ENABLE))
553624Sschwartz 			*bar_p ^= PCI_BASE_ROM_ENABLE;
554624Sschwartz 
555624Sschwartz 		else {	/* ROM disabled. */
556624Sschwartz 			prg_p->status = PCITOOL_ROM_DISABLED;
557624Sschwartz 			return (EIO);
558624Sschwartz 		}
559624Sschwartz 	}
560624Sschwartz 
561624Sschwartz 	/* Accept a bar of 0 only for IO space. */
562624Sschwartz 	if ((*space_p != PCI_IO_SPACE) && (!(*bar_p))) {
563624Sschwartz 		prg_p->status = PCITOOL_INVALID_ADDRESS;
564624Sschwartz 		return (EINVAL);
565624Sschwartz 	}
566624Sschwartz 
567624Sschwartz 	return (SUCCESS);
568624Sschwartz }
569624Sschwartz 
570624Sschwartz 
571624Sschwartz /* Perform register accesses on PCI leaf devices. */
572624Sschwartz int
573624Sschwartz pxtool_dev_reg_ops(dev_info_t *dip, void *arg, int cmd, int mode)
574624Sschwartz {
575624Sschwartz 	pcitool_reg_t	prg;
576624Sschwartz 	uint64_t	bar;
577624Sschwartz 	uint32_t	space;
5781064Sschwartz 	uint64_t	off_in_space;
579624Sschwartz 	boolean_t	write_flag = B_FALSE;
580624Sschwartz 	px_t		*px_p = DIP_TO_STATE(dip);
581624Sschwartz 	int		rval = 0;
582624Sschwartz 
583624Sschwartz 	if (cmd == PCITOOL_DEVICE_SET_REG)
584624Sschwartz 		write_flag = B_TRUE;
585624Sschwartz 
586624Sschwartz 	DBG(DBG_TOOLS, dip, "pxtool_dev_reg_ops set/get reg\n");
587624Sschwartz 	if (ddi_copyin(arg, &prg, sizeof (pcitool_reg_t),
588624Sschwartz 	    mode) != DDI_SUCCESS) {
589624Sschwartz 		DBG(DBG_TOOLS, dip, "Error reading arguments\n");
590624Sschwartz 		return (EFAULT);
591624Sschwartz 	}
592624Sschwartz 
593624Sschwartz 	if ((rval = pxtool_dev_reg_ops_platchk(dip, &prg)) != SUCCESS) {
594624Sschwartz 		goto done_reg;
595624Sschwartz 	}
596624Sschwartz 
597624Sschwartz 	DBG(DBG_TOOLS, dip, "raw bus:0x%x, dev:0x%x, func:0x%x\n",
598624Sschwartz 	    prg.bus_no, prg.dev_no, prg.func_no);
599624Sschwartz 	DBG(DBG_TOOLS, dip, "barnum:0x%x, offset:0x%" PRIx64 ", acc:0x%x\n",
600624Sschwartz 	    prg.barnum, prg.offset, prg.acc_attr);
601624Sschwartz 
602624Sschwartz 	if ((rval = pxtool_validate_barnum_bdf(&prg)) != SUCCESS)
603624Sschwartz 		goto done_reg;
604624Sschwartz 
605624Sschwartz 	if (prg.barnum == 0) {	/* Proper config space desired. */
606624Sschwartz 
6071064Sschwartz 		/* Enforce offset limits. */
6081064Sschwartz 		if (prg.offset >= DEV_CFG_SPACE_SIZE) {
6091064Sschwartz 			DBG(DBG_TOOLS, dip,
6101064Sschwartz 			    "Config space offset 0x%" PRIx64 " out of range\n",
6111064Sschwartz 			    prg.offset);
6121064Sschwartz 			prg.status = PCITOOL_OUT_OF_RANGE;
6131064Sschwartz 			rval = EINVAL;
6141064Sschwartz 			goto done_reg;
6151064Sschwartz 		}
6161064Sschwartz 
6171064Sschwartz 		/*
6181064Sschwartz 		 * For sun4v, config space base won't be known.
6191064Sschwartz 		 * pxtool_get_phys_addr will return zero.
6201064Sschwartz 		 * Note that for sun4v, phys_addr isn't
6211064Sschwartz 		 * used for making config space accesses.
6221064Sschwartz 		 *
6231064Sschwartz 		 * For sun4u, assume that phys_addr will come back valid.
6241064Sschwartz 		 */
6252053Sschwartz 		/*
6262053Sschwartz 		 * Accessed entity is assumed small enough to be on one page.
6272053Sschwartz 		 *
6282053Sschwartz 		 * Since config space is less than a page and is aligned to
6292053Sschwartz 		 * 0x1000, a device's entire config space will be on a single
6302053Sschwartz 		 * page.  Pass the device's base config space address here,
6312053Sschwartz 		 * then add the offset within that space later.  This works
6322053Sschwartz 		 * around an issue in px_xlate_reg (called by
6332053Sschwartz 		 * pxtool_get_phys_addr) which accepts only a 256 byte
6342053Sschwartz 		 * range within a device.
6352053Sschwartz 		 */
6362053Sschwartz 		off_in_space = PX_GET_BDF(&prg);
6372053Sschwartz 		prg.phys_addr =
6382053Sschwartz 		    pxtool_get_phys_addr(px_p, PCI_CONFIG_SPACE, off_in_space);
6392053Sschwartz 		prg.phys_addr += prg.offset;
640624Sschwartz 
641624Sschwartz 		DBG(DBG_TOOLS, dip,
6421064Sschwartz 		    "off_in_space:0x%" PRIx64 ", phys_addr:0x%" PRIx64 ", "
6431064Sschwartz 		    "end:%s\n", off_in_space, prg.phys_addr,
644624Sschwartz 		    PCITOOL_ACC_IS_BIG_ENDIAN(prg.acc_attr) ? "big":"ltl");
645624Sschwartz 
646624Sschwartz 		/*
647624Sschwartz 		 * Access device.  pr.status is modified.
648624Sschwartz 		 * BDF is assumed valid at this point.
649624Sschwartz 		 */
6501064Sschwartz 		rval = pxtool_pcicfg_access(px_p, &prg, &prg.data, write_flag);
651624Sschwartz 		goto done_reg;
652624Sschwartz 	}
653624Sschwartz 
654624Sschwartz 	/* IO/ MEM/ MEM64 space. */
655624Sschwartz 
6561064Sschwartz 	if ((rval = pxtool_get_bar(px_p, &prg, &bar, &space)) != SUCCESS)
657624Sschwartz 		goto done_reg;
658624Sschwartz 
6591064Sschwartz 	switch (space) {
6601064Sschwartz 	case PCI_MEM32_SPACE:
661624Sschwartz 
662624Sschwartz 		DBG(DBG_TOOLS, dip, "32 bit mem space\n");
663624Sschwartz 
664624Sschwartz 		/* Can't write to ROM */
665624Sschwartz 		if ((PCI_BAR_OFFSET(prg) == PCI_CONF_ROM) && (write_flag)) {
666624Sschwartz 			prg.status = PCITOOL_ROM_WRITE;
667624Sschwartz 			rval = EIO;
668624Sschwartz 			goto done_reg;
669624Sschwartz 		}
6701064Sschwartz 		break;
671624Sschwartz 
6721064Sschwartz 	case PCI_IO_SPACE:
6731064Sschwartz 		DBG(DBG_TOOLS, dip, "IO space\n");
6741064Sschwartz 		break;
6751064Sschwartz 
6761064Sschwartz 	case PCI_MEM64_SPACE:
6771064Sschwartz 		DBG(DBG_TOOLS, dip,
6781064Sschwartz 		    "64 bit mem space.  64-bit bar is 0x%" PRIx64 "\n", bar);
6791064Sschwartz 		break;
6801064Sschwartz 
6811064Sschwartz 	default:
6821064Sschwartz 		DBG(DBG_TOOLS, dip, "Unknown space!\n");
6831064Sschwartz 		prg.status = PCITOOL_IO_ERROR;
6841064Sschwartz 		rval = EIO;
6851064Sschwartz 		goto done_reg;
686624Sschwartz 	}
687624Sschwartz 
688624Sschwartz 	/*
6891064Sschwartz 	 * Common code for all IO/MEM range spaces.
6901064Sschwartz 	 *
691624Sschwartz 	 * Use offset provided by caller to index into desired space.
692624Sschwartz 	 * Note that prg.status is modified on error.
693624Sschwartz 	 */
6941064Sschwartz 	off_in_space = bar + prg.offset;
6951064Sschwartz 	prg.phys_addr = pxtool_get_phys_addr(px_p, space, off_in_space);
696624Sschwartz 
6971064Sschwartz 	DBG(DBG_TOOLS, dip,
6981064Sschwartz 	    "addr in bar:0x%" PRIx64 ", offset:0x%" PRIx64 ", "
6991064Sschwartz 	    "phys_addr:0x%" PRIx64 "\n", bar, prg.offset, prg.phys_addr);
7001064Sschwartz 
7011064Sschwartz 	rval = pxtool_pciiomem_access(px_p, &prg, &prg.data, write_flag);
702624Sschwartz 
703624Sschwartz done_reg:
704*4397Sschwartz 	prg.drvr_version = PCITOOL_VERSION;
705624Sschwartz 	if (ddi_copyout(&prg, arg, sizeof (pcitool_reg_t),
706624Sschwartz 	    mode) != DDI_SUCCESS) {
707624Sschwartz 		DBG(DBG_TOOLS, dip, "Error returning arguments.\n");
708624Sschwartz 		rval = EFAULT;
709624Sschwartz 	}
710624Sschwartz 	return (rval);
711624Sschwartz }
712624Sschwartz 
713624Sschwartz 
714624Sschwartz int
715624Sschwartz pxtool_init(dev_info_t *dip)
716624Sschwartz {
717624Sschwartz 	int instance = ddi_get_instance(dip);
718624Sschwartz 
719624Sschwartz 	if (ddi_create_minor_node(dip, PCI_MINOR_REG, S_IFCHR,
720624Sschwartz 	    PCIHP_AP_MINOR_NUM(instance, PCI_TOOL_REG_MINOR_NUM),
721624Sschwartz 	    DDI_NT_REGACC, 0) != DDI_SUCCESS) {
722624Sschwartz 		return (DDI_FAILURE);
723624Sschwartz 	}
724624Sschwartz 
725624Sschwartz 	if (ddi_create_minor_node(dip, PCI_MINOR_INTR, S_IFCHR,
726624Sschwartz 	    PCIHP_AP_MINOR_NUM(instance, PCI_TOOL_INTR_MINOR_NUM),
727624Sschwartz 	    DDI_NT_INTRCTL, 0) != DDI_SUCCESS) {
728624Sschwartz 		ddi_remove_minor_node(dip, PCI_MINOR_REG);
729624Sschwartz 		return (DDI_FAILURE);
730624Sschwartz 	}
731624Sschwartz 
732624Sschwartz 	return (DDI_SUCCESS);
733624Sschwartz }
734624Sschwartz 
735624Sschwartz 
736624Sschwartz void
737624Sschwartz pxtool_uninit(dev_info_t *dip)
738624Sschwartz {
739624Sschwartz 	ddi_remove_minor_node(dip, PCI_MINOR_REG);
740624Sschwartz 	ddi_remove_minor_node(dip, PCI_MINOR_INTR);
741624Sschwartz }
742