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 /*
2210053SEvan.Yan@Sun.COM * Copyright 2009 Sun Microsystems, Inc. All rights reserved.
23624Sschwartz * Use is subject to license terms.
24624Sschwartz */
25624Sschwartz
26624Sschwartz #include <sys/types.h>
27624Sschwartz #include <sys/stat.h>
28624Sschwartz #include <sys/cpuvar.h>
29624Sschwartz #include <sys/kmem.h>
30624Sschwartz #include <sys/sunddi.h>
31624Sschwartz #include "px_obj.h"
32624Sschwartz #include <sys/pci_tools.h>
33777Sschwartz #include "px_tools_ext.h"
34624Sschwartz #include "px_tools_var.h"
35624Sschwartz
36624Sschwartz /*
37624Sschwartz * PCI Space definitions.
38624Sschwartz */
39624Sschwartz #define PCI_CONFIG_SPACE (PCI_REG_ADDR_G(PCI_ADDR_CONFIG))
40624Sschwartz #define PCI_IO_SPACE (PCI_REG_ADDR_G(PCI_ADDR_IO))
41624Sschwartz #define PCI_MEM32_SPACE (PCI_REG_ADDR_G(PCI_ADDR_MEM32))
42624Sschwartz #define PCI_MEM64_SPACE (PCI_REG_ADDR_G(PCI_ADDR_MEM64))
43624Sschwartz
44624Sschwartz /*
45624Sschwartz * Config space range for a device. IEEE 1275 spec defines for PCI.
46624Sschwartz * Space for PCI-express is multiplied by PX_PCI_BDF_OFFSET_DELTA
47624Sschwartz */
48624Sschwartz #define DEV_CFG_SPACE_SIZE \
49624Sschwartz (1 << (PCI_REG_FUNC_SHIFT + PX_PCI_BDF_OFFSET_DELTA))
50624Sschwartz
51624Sschwartz /*
52624Sschwartz * Offsets of BARS in config space. First entry of 0 means config space.
53624Sschwartz * Entries here correlate to pcitool_bars_t enumerated type.
54624Sschwartz */
55624Sschwartz uint8_t pci_bars[] = {
56624Sschwartz 0x0,
57624Sschwartz PCI_CONF_BASE0,
58624Sschwartz PCI_CONF_BASE1,
59624Sschwartz PCI_CONF_BASE2,
60624Sschwartz PCI_CONF_BASE3,
61624Sschwartz PCI_CONF_BASE4,
62624Sschwartz PCI_CONF_BASE5,
63624Sschwartz PCI_CONF_ROM
64624Sschwartz };
65624Sschwartz
66624Sschwartz int pci_num_bars = sizeof (pci_bars) / sizeof (pci_bars[0]);
67624Sschwartz
68624Sschwartz
694397Sschwartz /*ARGSUSED*/
70624Sschwartz static int
pxtool_intr_info(dev_info_t * dip,void * arg,int mode)714397Sschwartz pxtool_intr_info(dev_info_t *dip, void *arg, int mode)
72624Sschwartz {
7310053SEvan.Yan@Sun.COM px_t *px_p = DIP_TO_STATE(dip);
7410053SEvan.Yan@Sun.COM px_msi_state_t *msi_state_p = &px_p->px_ib_p->ib_msi_state;
754397Sschwartz pcitool_intr_info_t intr_info;
764397Sschwartz int rval = SUCCESS;
774397Sschwartz
784397Sschwartz /* If we need user_version, and to ret same user version as passed in */
794397Sschwartz if (ddi_copyin(arg, &intr_info, sizeof (pcitool_intr_info_t), mode) !=
804397Sschwartz DDI_SUCCESS) {
81624Sschwartz return (EFAULT);
824397Sschwartz }
834397Sschwartz
844397Sschwartz intr_info.ctlr_version = 0; /* XXX how to get real version? */
854397Sschwartz intr_info.ctlr_type = PCITOOL_CTLR_TYPE_RISC;
8610053SEvan.Yan@Sun.COM if (intr_info.flags & PCITOOL_INTR_FLAG_GET_MSI)
8710053SEvan.Yan@Sun.COM intr_info.num_intr = msi_state_p->msi_cnt;
8810053SEvan.Yan@Sun.COM else
8910053SEvan.Yan@Sun.COM intr_info.num_intr = pxtool_num_inos;
904397Sschwartz
914397Sschwartz intr_info.drvr_version = PCITOOL_VERSION;
924397Sschwartz if (ddi_copyout(&intr_info, arg, sizeof (pcitool_intr_info_t), mode) !=
934397Sschwartz DDI_SUCCESS) {
944397Sschwartz rval = EFAULT;
954397Sschwartz }
964397Sschwartz
974397Sschwartz return (rval);
98624Sschwartz }
994397Sschwartz
1004397Sschwartz
101624Sschwartz /*
102624Sschwartz * Get interrupt information for a given ino.
103624Sschwartz * Returns info only for inos mapped to devices.
104624Sschwartz *
105624Sschwartz * Returned info is valid only when iget.num_devs is returned > 0.
106624Sschwartz * If ino is not enabled or is not mapped to a device,
107624Sschwartz * iget.num_devs will be returned as = 0.
108624Sschwartz */
109624Sschwartz /*ARGSUSED*/
110624Sschwartz static int
pxtool_get_intr(dev_info_t * dip,void * arg,int mode)111624Sschwartz pxtool_get_intr(dev_info_t *dip, void *arg, int mode)
112624Sschwartz {
113624Sschwartz /* Array part isn't used here, but oh well... */
114624Sschwartz pcitool_intr_get_t partial_iget;
11510053SEvan.Yan@Sun.COM pcitool_intr_get_t *iget = &partial_iget;
116624Sschwartz int copyout_rval;
117624Sschwartz sysino_t sysino;
118624Sschwartz intr_valid_state_t intr_valid_state;
119624Sschwartz cpuid_t old_cpu_id;
120624Sschwartz px_t *px_p = DIP_TO_STATE(dip);
121624Sschwartz size_t iget_kmem_alloc_size = 0;
12210053SEvan.Yan@Sun.COM int rval = EIO;
123624Sschwartz
124624Sschwartz /* Read in just the header part, no array section. */
125624Sschwartz if (ddi_copyin(arg, &partial_iget, PCITOOL_IGET_SIZE(0), mode) !=
126624Sschwartz DDI_SUCCESS)
127624Sschwartz return (EFAULT);
128624Sschwartz
12910053SEvan.Yan@Sun.COM iget->status = PCITOOL_IO_ERROR;
13010053SEvan.Yan@Sun.COM
13110053SEvan.Yan@Sun.COM if (iget->flags & PCITOOL_INTR_FLAG_GET_MSI) {
13210053SEvan.Yan@Sun.COM px_msi_state_t *msi_state_p = &px_p->px_ib_p->ib_msi_state;
13310053SEvan.Yan@Sun.COM pci_msi_valid_state_t msi_state;
13410053SEvan.Yan@Sun.COM msiqid_t msiq_id;
135624Sschwartz
13610053SEvan.Yan@Sun.COM if ((iget->msi < msi_state_p->msi_1st_msinum) ||
13710053SEvan.Yan@Sun.COM (iget->msi >= (msi_state_p->msi_1st_msinum +
13810053SEvan.Yan@Sun.COM msi_state_p->msi_cnt))) {
13910053SEvan.Yan@Sun.COM iget->status = PCITOOL_INVALID_MSI;
14010053SEvan.Yan@Sun.COM rval = EINVAL;
14110053SEvan.Yan@Sun.COM goto done_get_intr;
14210053SEvan.Yan@Sun.COM }
14310053SEvan.Yan@Sun.COM
14410053SEvan.Yan@Sun.COM if ((px_lib_msi_getvalid(dip, iget->msi,
14510053SEvan.Yan@Sun.COM &msi_state) != DDI_SUCCESS) ||
14610053SEvan.Yan@Sun.COM (msi_state != PCI_MSI_VALID))
14710053SEvan.Yan@Sun.COM goto done_get_intr;
14810053SEvan.Yan@Sun.COM
14910053SEvan.Yan@Sun.COM if (px_lib_msi_getmsiq(dip, iget->msi,
15010053SEvan.Yan@Sun.COM &msiq_id) != DDI_SUCCESS)
15110053SEvan.Yan@Sun.COM goto done_get_intr;
15210053SEvan.Yan@Sun.COM
15310053SEvan.Yan@Sun.COM iget->ino = px_msiqid_to_devino(px_p, msiq_id);
15410053SEvan.Yan@Sun.COM } else {
15510053SEvan.Yan@Sun.COM iget->msi = (uint32_t)-1;
15610053SEvan.Yan@Sun.COM }
157624Sschwartz
158624Sschwartz /* Validate argument. */
15910053SEvan.Yan@Sun.COM if (iget->ino > pxtool_num_inos) {
16010053SEvan.Yan@Sun.COM iget->status = PCITOOL_INVALID_INO;
16110053SEvan.Yan@Sun.COM rval = EINVAL;
162624Sschwartz goto done_get_intr;
163624Sschwartz }
164624Sschwartz
165624Sschwartz /* Caller wants device information returned. */
16610053SEvan.Yan@Sun.COM if (iget->num_devs_ret > 0) {
167624Sschwartz /*
168624Sschwartz * Allocate room.
169624Sschwartz * Note if num_devs == 0 iget remains pointing to
170624Sschwartz * partial_iget.
171624Sschwartz */
17210053SEvan.Yan@Sun.COM iget_kmem_alloc_size = PCITOOL_IGET_SIZE(iget->num_devs_ret);
17310053SEvan.Yan@Sun.COM iget = kmem_zalloc(iget_kmem_alloc_size, KM_SLEEP);
174624Sschwartz
175624Sschwartz /* Read in whole structure to verify there's room. */
176624Sschwartz if (ddi_copyin(arg, iget, iget_kmem_alloc_size, mode) !=
177624Sschwartz SUCCESS) {
178624Sschwartz
179624Sschwartz /* Be consistent and just return EFAULT here. */
180624Sschwartz kmem_free(iget, iget_kmem_alloc_size);
181624Sschwartz
182624Sschwartz return (EFAULT);
183624Sschwartz }
184624Sschwartz }
185624Sschwartz
186624Sschwartz /* Convert leaf-wide intr to system-wide intr */
18710053SEvan.Yan@Sun.COM if (px_lib_intr_devino_to_sysino(dip, iget->ino, &sysino) !=
18810053SEvan.Yan@Sun.COM DDI_SUCCESS) {
189624Sschwartz iget->status = PCITOOL_IO_ERROR;
190624Sschwartz rval = EIO;
191624Sschwartz goto done_get_intr;
192624Sschwartz }
193624Sschwartz
194624Sschwartz /* Operate only on inos which are already enabled. */
19510053SEvan.Yan@Sun.COM if (px_lib_intr_getvalid(dip, sysino, &intr_valid_state) !=
19610053SEvan.Yan@Sun.COM DDI_SUCCESS) {
197624Sschwartz iget->status = PCITOOL_IO_ERROR;
198624Sschwartz rval = EIO;
199624Sschwartz goto done_get_intr;
200624Sschwartz }
201624Sschwartz
202624Sschwartz /*
203624Sschwartz * Consider all valid inos: those mapped to the root complex itself
204624Sschwartz * as well as those mapped to devices.
205624Sschwartz */
206624Sschwartz if (intr_valid_state == INTR_VALID) {
207624Sschwartz /*
2082973Sgovinda * The following looks up the px_ino and returns
209624Sschwartz * info of devices mapped to this ino.
210624Sschwartz */
21110053SEvan.Yan@Sun.COM iget->num_devs = pxtool_ib_get_ino_devs(px_p, iget->ino,
21210053SEvan.Yan@Sun.COM iget->msi, &iget->num_devs_ret, iget->dev);
213624Sschwartz
21410053SEvan.Yan@Sun.COM if (px_ib_get_intr_target(px_p, iget->ino,
21510053SEvan.Yan@Sun.COM &old_cpu_id) != DDI_SUCCESS) {
216624Sschwartz iget->status = PCITOOL_IO_ERROR;
217624Sschwartz rval = EIO;
218624Sschwartz goto done_get_intr;
219624Sschwartz }
22010053SEvan.Yan@Sun.COM
221624Sschwartz iget->cpu_id = old_cpu_id;
222624Sschwartz }
223624Sschwartz
224624Sschwartz iget->status = PCITOOL_SUCCESS;
225624Sschwartz rval = SUCCESS;
226624Sschwartz
227624Sschwartz done_get_intr:
2284397Sschwartz iget->drvr_version = PCITOOL_VERSION;
229624Sschwartz copyout_rval =
23010053SEvan.Yan@Sun.COM ddi_copyout(iget, arg, PCITOOL_IGET_SIZE(iget->num_devs_ret), mode);
231624Sschwartz
232624Sschwartz if (iget_kmem_alloc_size > 0)
233624Sschwartz kmem_free(iget, iget_kmem_alloc_size);
234624Sschwartz
235624Sschwartz if (copyout_rval != DDI_SUCCESS)
236624Sschwartz rval = EFAULT;
237624Sschwartz
238624Sschwartz return (rval);
239624Sschwartz }
240624Sschwartz
241624Sschwartz
242624Sschwartz /*
243624Sschwartz * Associate a new CPU with a given ino.
244624Sschwartz *
245624Sschwartz * Operate only on inos which are already mapped to devices.
246624Sschwartz */
247624Sschwartz static int
pxtool_set_intr(dev_info_t * dip,void * arg,int mode)248624Sschwartz pxtool_set_intr(dev_info_t *dip, void *arg, int mode)
249624Sschwartz {
250624Sschwartz pcitool_intr_set_t iset;
251624Sschwartz cpuid_t old_cpu_id;
252624Sschwartz sysino_t sysino;
25310053SEvan.Yan@Sun.COM intr_valid_state_t intr_valid_state;
254624Sschwartz px_t *px_p = DIP_TO_STATE(dip);
25510053SEvan.Yan@Sun.COM msiqid_t msiq_id;
25610053SEvan.Yan@Sun.COM int rval = EIO;
25710053SEvan.Yan@Sun.COM int ret = DDI_SUCCESS;
2584397Sschwartz size_t copyinout_size;
259624Sschwartz
2604397Sschwartz bzero(&iset, sizeof (pcitool_intr_set_t));
2614397Sschwartz
2624397Sschwartz /* Version 1 of pcitool_intr_set_t doesn't have flags. */
2634397Sschwartz copyinout_size = (size_t)&iset.flags - (size_t)&iset;
2644397Sschwartz
2654397Sschwartz if (ddi_copyin(arg, &iset, copyinout_size, mode) != DDI_SUCCESS)
266624Sschwartz return (EFAULT);
267624Sschwartz
2684397Sschwartz switch (iset.user_version) {
2694397Sschwartz case PCITOOL_V1:
2704397Sschwartz break;
2714397Sschwartz
2724397Sschwartz case PCITOOL_V2:
2734397Sschwartz copyinout_size = sizeof (pcitool_intr_set_t);
2744397Sschwartz if (ddi_copyin(arg, &iset, copyinout_size, mode) != DDI_SUCCESS)
2754397Sschwartz return (EFAULT);
2764397Sschwartz break;
2774397Sschwartz
2784397Sschwartz default:
2794397Sschwartz iset.status = PCITOOL_OUT_OF_RANGE;
2804397Sschwartz rval = ENOTSUP;
2814397Sschwartz goto done_set_intr;
2824397Sschwartz }
2834397Sschwartz
28410053SEvan.Yan@Sun.COM if (iset.flags & PCITOOL_INTR_FLAG_SET_GROUP) {
2854397Sschwartz iset.status = PCITOOL_IO_ERROR;
2864397Sschwartz rval = ENOTSUP;
2874397Sschwartz goto done_set_intr;
2884397Sschwartz }
2894397Sschwartz
29010053SEvan.Yan@Sun.COM iset.status = PCITOOL_IO_ERROR;
29110053SEvan.Yan@Sun.COM
29210053SEvan.Yan@Sun.COM if (iset.flags & PCITOOL_INTR_FLAG_SET_MSI) {
29310053SEvan.Yan@Sun.COM px_msi_state_t *msi_state_p = &px_p->px_ib_p->ib_msi_state;
29410053SEvan.Yan@Sun.COM pci_msi_valid_state_t msi_state;
29510053SEvan.Yan@Sun.COM
29610053SEvan.Yan@Sun.COM if ((iset.msi < msi_state_p->msi_1st_msinum) ||
29710053SEvan.Yan@Sun.COM (iset.msi >= (msi_state_p->msi_1st_msinum +
29810053SEvan.Yan@Sun.COM msi_state_p->msi_cnt))) {
29910053SEvan.Yan@Sun.COM iset.status = PCITOOL_INVALID_MSI;
30010053SEvan.Yan@Sun.COM rval = EINVAL;
30110053SEvan.Yan@Sun.COM goto done_set_intr;
30210053SEvan.Yan@Sun.COM }
30310053SEvan.Yan@Sun.COM
30410053SEvan.Yan@Sun.COM if ((px_lib_msi_getvalid(dip, iset.msi,
30510053SEvan.Yan@Sun.COM &msi_state) != DDI_SUCCESS) ||
30610053SEvan.Yan@Sun.COM (msi_state != PCI_MSI_VALID))
30710053SEvan.Yan@Sun.COM goto done_set_intr;
30810053SEvan.Yan@Sun.COM
30910053SEvan.Yan@Sun.COM if (px_lib_msi_getmsiq(dip, iset.msi,
31010053SEvan.Yan@Sun.COM &msiq_id) != DDI_SUCCESS)
31110053SEvan.Yan@Sun.COM goto done_set_intr;
31210053SEvan.Yan@Sun.COM
31310053SEvan.Yan@Sun.COM iset.ino = px_msiqid_to_devino(px_p, msiq_id);
31410053SEvan.Yan@Sun.COM } else {
31510053SEvan.Yan@Sun.COM iset.msi = (uint32_t)-1;
31610053SEvan.Yan@Sun.COM }
317624Sschwartz
318624Sschwartz /* Validate input argument. */
31910053SEvan.Yan@Sun.COM if (iset.ino > pxtool_num_inos) {
32010053SEvan.Yan@Sun.COM iset.status = PCITOOL_INVALID_INO;
32110053SEvan.Yan@Sun.COM rval = EINVAL;
32210053SEvan.Yan@Sun.COM goto done_set_intr;
32310053SEvan.Yan@Sun.COM }
32410053SEvan.Yan@Sun.COM
32510053SEvan.Yan@Sun.COM /* Convert leaf-wide intr to system-wide intr */
32610053SEvan.Yan@Sun.COM if (px_lib_intr_devino_to_sysino(dip, iset.ino, &sysino) !=
32710053SEvan.Yan@Sun.COM DDI_SUCCESS)
328624Sschwartz goto done_set_intr;
329624Sschwartz
33010053SEvan.Yan@Sun.COM /* Operate only on inos which are already enabled. */
33110053SEvan.Yan@Sun.COM if ((px_lib_intr_getvalid(dip, sysino, &intr_valid_state) !=
33210053SEvan.Yan@Sun.COM DDI_SUCCESS) || (intr_valid_state == INTR_NOTVALID))
333624Sschwartz goto done_set_intr;
334624Sschwartz
335624Sschwartz /*
33610053SEvan.Yan@Sun.COM * Consider all valid inos: those mapped to the root complex itself
33710053SEvan.Yan@Sun.COM * as well as those mapped to devices.
338624Sschwartz */
33910053SEvan.Yan@Sun.COM if (px_lib_intr_gettarget(dip, sysino, &old_cpu_id) != DDI_SUCCESS)
34010053SEvan.Yan@Sun.COM goto done_set_intr;
341624Sschwartz
34210053SEvan.Yan@Sun.COM if (iset.flags & PCITOOL_INTR_FLAG_SET_MSI) {
34310053SEvan.Yan@Sun.COM ddi_intr_handle_impl_t hdle;
344624Sschwartz
34510053SEvan.Yan@Sun.COM bzero(&hdle, sizeof (ddi_intr_handle_impl_t));
34610053SEvan.Yan@Sun.COM if (pxtool_ib_get_msi_info(px_p, iset.ino, iset.msi,
34710053SEvan.Yan@Sun.COM &hdle) != DDI_SUCCESS) {
34810053SEvan.Yan@Sun.COM iset.status = PCITOOL_INVALID_MSI;
34910053SEvan.Yan@Sun.COM rval = EINVAL;
350624Sschwartz goto done_set_intr;
35110053SEvan.Yan@Sun.COM }
352624Sschwartz
35310053SEvan.Yan@Sun.COM if ((ret = px_ib_set_msix_target(px_p, &hdle, iset.msi,
35410053SEvan.Yan@Sun.COM iset.cpu_id)) == DDI_SUCCESS) {
35510053SEvan.Yan@Sun.COM (void) px_lib_msi_getmsiq(dip, iset.msi, &msiq_id);
35610053SEvan.Yan@Sun.COM iset.ino = px_msiqid_to_devino(px_p, msiq_id);
35710053SEvan.Yan@Sun.COM iset.cpu_id = old_cpu_id;
35810053SEvan.Yan@Sun.COM iset.status = PCITOOL_SUCCESS;
35910053SEvan.Yan@Sun.COM rval = SUCCESS;
36010053SEvan.Yan@Sun.COM goto done_set_intr;
36110053SEvan.Yan@Sun.COM }
36210053SEvan.Yan@Sun.COM } else {
36310053SEvan.Yan@Sun.COM if ((ret = px_ib_set_intr_target(px_p, iset.ino,
36410053SEvan.Yan@Sun.COM iset.cpu_id)) == DDI_SUCCESS) {
36510053SEvan.Yan@Sun.COM iset.cpu_id = old_cpu_id;
36610053SEvan.Yan@Sun.COM iset.status = PCITOOL_SUCCESS;
36710053SEvan.Yan@Sun.COM rval = SUCCESS;
36810053SEvan.Yan@Sun.COM goto done_set_intr;
36910053SEvan.Yan@Sun.COM }
37010053SEvan.Yan@Sun.COM }
371624Sschwartz
37210053SEvan.Yan@Sun.COM switch (ret) {
37310053SEvan.Yan@Sun.COM case DDI_EPENDING:
37410053SEvan.Yan@Sun.COM iset.status = PCITOOL_PENDING_INTRTIMEOUT;
37510053SEvan.Yan@Sun.COM rval = ETIME;
37610053SEvan.Yan@Sun.COM break;
37710053SEvan.Yan@Sun.COM case DDI_EINVAL:
378624Sschwartz iset.status = PCITOOL_INVALID_CPUID;
379624Sschwartz rval = EINVAL;
38010053SEvan.Yan@Sun.COM break;
38110053SEvan.Yan@Sun.COM default:
38210053SEvan.Yan@Sun.COM iset.status = PCITOOL_IO_ERROR;
38310053SEvan.Yan@Sun.COM rval = EIO;
38410053SEvan.Yan@Sun.COM break;
385624Sschwartz }
386624Sschwartz
387624Sschwartz done_set_intr:
3884397Sschwartz iset.drvr_version = PCITOOL_VERSION;
3894397Sschwartz if (ddi_copyout(&iset, arg, copyinout_size, mode) != DDI_SUCCESS)
390624Sschwartz rval = EFAULT;
391624Sschwartz
392624Sschwartz return (rval);
393624Sschwartz }
394624Sschwartz
395624Sschwartz
396624Sschwartz /* Main function for handling interrupt CPU binding requests and queries. */
397624Sschwartz int
pxtool_intr(dev_info_t * dip,void * arg,int cmd,int mode)398624Sschwartz pxtool_intr(dev_info_t *dip, void *arg, int cmd, int mode)
399624Sschwartz {
400624Sschwartz int rval = SUCCESS;
401624Sschwartz
402624Sschwartz switch (cmd) {
403624Sschwartz
4044397Sschwartz /* Get system interrupt information. */
4054397Sschwartz case PCITOOL_SYSTEM_INTR_INFO:
4064397Sschwartz rval = pxtool_intr_info(dip, arg, mode);
407624Sschwartz break;
408624Sschwartz
409624Sschwartz /* Get interrupt information for a given ino. */
410624Sschwartz case PCITOOL_DEVICE_GET_INTR:
411624Sschwartz rval = pxtool_get_intr(dip, arg, mode);
412624Sschwartz break;
413624Sschwartz
414624Sschwartz /* Associate a new CPU with a given ino. */
415624Sschwartz case PCITOOL_DEVICE_SET_INTR:
416624Sschwartz rval = pxtool_set_intr(dip, arg, mode);
417624Sschwartz break;
418624Sschwartz
419624Sschwartz default:
420624Sschwartz rval = ENOTTY;
421624Sschwartz }
422624Sschwartz
423624Sschwartz return (rval);
424624Sschwartz }
425624Sschwartz
426624Sschwartz
427624Sschwartz static int
pxtool_validate_barnum_bdf(pcitool_reg_t * prg)428624Sschwartz pxtool_validate_barnum_bdf(pcitool_reg_t *prg)
429624Sschwartz {
430624Sschwartz int rval = SUCCESS;
431624Sschwartz
432624Sschwartz if (prg->barnum >= (sizeof (pci_bars) / sizeof (pci_bars[0]))) {
433624Sschwartz prg->status = PCITOOL_OUT_OF_RANGE;
434624Sschwartz rval = EINVAL;
435624Sschwartz
436624Sschwartz /* Validate address arguments of bus / dev / func */
437624Sschwartz } else if (((prg->bus_no &
438624Sschwartz (PCI_REG_BUS_M >> PCI_REG_BUS_SHIFT)) != prg->bus_no) ||
439624Sschwartz ((prg->dev_no &
440624Sschwartz (PCI_REG_DEV_M >> PCI_REG_DEV_SHIFT)) != prg->dev_no) ||
441624Sschwartz ((prg->func_no &
442624Sschwartz (PCI_REG_FUNC_M >> PCI_REG_FUNC_SHIFT)) != prg->func_no)) {
443624Sschwartz prg->status = PCITOOL_INVALID_ADDRESS;
444624Sschwartz rval = EINVAL;
445624Sschwartz }
446624Sschwartz
447624Sschwartz return (rval);
448624Sschwartz }
449624Sschwartz
4501064Sschwartz /*
4511064Sschwartz * px_p defines which leaf, space defines which space in that leaf, offset
4521064Sschwartz * defines the offset within the specified space.
4531064Sschwartz *
4541064Sschwartz * This returns the physical address of the corresponding location.
4551064Sschwartz */
4561064Sschwartz static uintptr_t
pxtool_get_phys_addr(px_t * px_p,int space,uint64_t offset)4571064Sschwartz pxtool_get_phys_addr(px_t *px_p, int space, uint64_t offset)
4581064Sschwartz {
4591064Sschwartz uint64_t range_base;
4601064Sschwartz int rval;
4612053Sschwartz pci_regspec_t dev_regspec;
4622053Sschwartz struct regspec xlated_regspec;
4631064Sschwartz dev_info_t *dip = px_p->px_dip;
4641064Sschwartz
4651064Sschwartz /*
4661064Sschwartz * Assume that requested entity is small enough to be on the same page.
4671064Sschwartz * PCItool checks alignment so that this will be true for single
4681064Sschwartz * accesses.
4691064Sschwartz */
4702053Sschwartz dev_regspec.pci_phys_hi = space << PCI_REG_ADDR_SHIFT;
4712053Sschwartz if (space == PCI_CONFIG_SPACE) {
4722053Sschwartz dev_regspec.pci_phys_hi +=
4732053Sschwartz (offset & (PCI_REG_BDFR_M ^ PCI_REG_REG_M));
4742053Sschwartz dev_regspec.pci_phys_low = offset & PCI_REG_REG_M;
4753625Segillett dev_regspec.pci_phys_mid = 0; /* Not used */
4762053Sschwartz } else {
4772053Sschwartz dev_regspec.pci_phys_mid = offset >> 32;
4782053Sschwartz dev_regspec.pci_phys_low = offset & 0xffffffff;
4792053Sschwartz }
4802053Sschwartz dev_regspec.pci_size_hi = 0; /* Not used. */
4812053Sschwartz
4822053Sschwartz /* Note: object is guaranteed to be within a page. */
4832053Sschwartz dev_regspec.pci_size_low = 4;
4842053Sschwartz
4852053Sschwartz rval = px_xlate_reg(px_p, &dev_regspec, &xlated_regspec);
4862053Sschwartz
4871064Sschwartz DBG(DBG_TOOLS, dip,
4882053Sschwartz "space:0x%d, offset:0x%" PRIx64 "\n", space, offset);
4891064Sschwartz
4901064Sschwartz if (rval != DDI_SUCCESS)
4911064Sschwartz return (NULL);
4922053Sschwartz
4932053Sschwartz /* Bustype here returns the high order address bits. */
4942053Sschwartz xlated_regspec.regspec_bustype &= px_get_rng_parent_hi_mask(px_p);
4952053Sschwartz
4962053Sschwartz range_base = (((uint64_t)xlated_regspec.regspec_bustype) << 32) +
4972053Sschwartz xlated_regspec.regspec_addr;
4982053Sschwartz DBG(DBG_TOOLS, dip,
4992053Sschwartz "regspec: hi:0x%x, lo:0x%x, sz:0x%x, range base:0x%" PRIx64 "\n",
5002053Sschwartz xlated_regspec.regspec_bustype, xlated_regspec.regspec_addr,
5012053Sschwartz xlated_regspec.regspec_size, range_base);
5022053Sschwartz
5032053Sschwartz return ((uintptr_t)range_base);
5041064Sschwartz }
5051064Sschwartz
5061064Sschwartz
507624Sschwartz static int
pxtool_get_bar(px_t * px_p,pcitool_reg_t * prg_p,uint64_t * bar_p,uint32_t * space_p)5081064Sschwartz pxtool_get_bar(px_t *px_p, pcitool_reg_t *prg_p, uint64_t *bar_p,
5091064Sschwartz uint32_t *space_p)
510624Sschwartz {
511624Sschwartz int rval;
5121064Sschwartz uint64_t off_in_space;
513624Sschwartz pcitool_reg_t cfg_prg = *prg_p; /* Make local copy. */
514624Sschwartz dev_info_t *dip = px_p->px_dip;
515624Sschwartz
516624Sschwartz *space_p = PCI_MEM32_SPACE;
517624Sschwartz *bar_p = 0;
518624Sschwartz
519624Sschwartz /*
520624Sschwartz * Translate BAR number into offset of the BAR in
521624Sschwartz * the device's config space.
522624Sschwartz */
523624Sschwartz cfg_prg.acc_attr =
524624Sschwartz PCITOOL_ACC_ATTR_SIZE_4 | PCITOOL_ACC_ATTR_ENDN_LTL;
525624Sschwartz
5261064Sschwartz /*
5271064Sschwartz * Note: sun4u acc function uses phys_addr which includes offset.
5281064Sschwartz * sun4v acc function doesn't use phys_addr but uses cfg_prg.offset.
5291064Sschwartz */
5301064Sschwartz cfg_prg.offset = PCI_BAR_OFFSET((*prg_p));
5312053Sschwartz off_in_space = PX_GET_BDF(prg_p) + cfg_prg.offset;
5321064Sschwartz cfg_prg.phys_addr = pxtool_get_phys_addr(px_p, PCI_CONFIG_SPACE,
5331064Sschwartz off_in_space);
5341064Sschwartz
5351064Sschwartz DBG(DBG_TOOLS, dip,
5361064Sschwartz "off_in_space:0x%" PRIx64 ", phys_addr:0x%" PRIx64 ", barnum:%d\n",
5371064Sschwartz off_in_space, cfg_prg.phys_addr, cfg_prg.barnum);
538624Sschwartz
539624Sschwartz /*
540624Sschwartz * Get Bus Address Register (BAR) from config space.
541624Sschwartz * cfg_prg.offset is the offset into config space of the
542624Sschwartz * BAR desired. prg_p->status is modified on error.
543624Sschwartz */
5441064Sschwartz rval = pxtool_pcicfg_access(px_p, &cfg_prg, bar_p, PX_ISREAD);
545624Sschwartz
546624Sschwartz if (rval != SUCCESS) {
547624Sschwartz prg_p->status = cfg_prg.status;
548624Sschwartz return (rval);
549624Sschwartz }
550624Sschwartz
551624Sschwartz DBG(DBG_TOOLS, dip, "bar returned is 0x%" PRIx64 "\n", *bar_p);
552624Sschwartz
553624Sschwartz /*
554624Sschwartz * BAR has bits saying this space is IO space, unless
555624Sschwartz * this is the ROM address register.
556624Sschwartz */
557624Sschwartz if (((PCI_BASE_SPACE_M & *bar_p) == PCI_BASE_SPACE_IO) &&
558624Sschwartz (cfg_prg.offset != PCI_CONF_ROM)) {
559624Sschwartz *space_p = PCI_IO_SPACE;
560624Sschwartz *bar_p &= PCI_BASE_IO_ADDR_M;
561624Sschwartz
562624Sschwartz /*
563624Sschwartz * BAR has bits saying this space is 64 bit memory
564624Sschwartz * space, unless this is the ROM address register.
565624Sschwartz *
566624Sschwartz * The 64 bit address stored in two BAR cells is not
567624Sschwartz * necessarily aligned on an 8-byte boundary.
568624Sschwartz * Need to keep the first 4 bytes read,
569624Sschwartz * and do a separate read of the high 4 bytes.
570624Sschwartz */
571624Sschwartz } else if ((PCI_BASE_TYPE_ALL & *bar_p) &&
572624Sschwartz (cfg_prg.offset != PCI_CONF_ROM)) {
573624Sschwartz
574624Sschwartz uint32_t low_bytes = (uint32_t)(*bar_p & ~PCI_BASE_TYPE_ALL);
575624Sschwartz
576624Sschwartz /* Don't try to read the next 4 bytes past the end of BARs. */
577624Sschwartz if (cfg_prg.offset >= PCI_CONF_BASE5) {
578624Sschwartz prg_p->status = PCITOOL_OUT_OF_RANGE;
579624Sschwartz return (EIO);
580624Sschwartz }
581624Sschwartz
582624Sschwartz /* Access device. prg_p->status is modified on error. */
583624Sschwartz cfg_prg.phys_addr += sizeof (uint32_t);
584624Sschwartz cfg_prg.offset += sizeof (uint32_t);
585624Sschwartz
5861064Sschwartz rval = pxtool_pcicfg_access(px_p, &cfg_prg, bar_p, PX_ISREAD);
587624Sschwartz if (rval != SUCCESS) {
588624Sschwartz prg_p->status = cfg_prg.status;
589624Sschwartz return (rval);
590624Sschwartz }
591624Sschwartz
592624Sschwartz /*
593624Sschwartz * Honor the 64 bit BAR as such, only when the upper 32 bits
594624Sschwartz * store a non-zero value.
595624Sschwartz */
596624Sschwartz if (*bar_p) {
597624Sschwartz *space_p = PCI_MEM64_SPACE;
598624Sschwartz *bar_p = (*bar_p << 32) | low_bytes;
599624Sschwartz } else
600624Sschwartz *bar_p = low_bytes;
601624Sschwartz
602624Sschwartz } else if (cfg_prg.offset == PCI_CONF_ROM) { /* ROM requested */
603624Sschwartz
604624Sschwartz /*
605624Sschwartz * ROM enabled. Filter ROM enable bit from the BAR.
606624Sschwartz * Treat as Mem32 henceforth.
607624Sschwartz */
608624Sschwartz if (!(*bar_p & PCI_BASE_ROM_ENABLE))
609624Sschwartz *bar_p ^= PCI_BASE_ROM_ENABLE;
610624Sschwartz
611624Sschwartz else { /* ROM disabled. */
612624Sschwartz prg_p->status = PCITOOL_ROM_DISABLED;
613624Sschwartz return (EIO);
614624Sschwartz }
615624Sschwartz }
616624Sschwartz
617624Sschwartz /* Accept a bar of 0 only for IO space. */
618624Sschwartz if ((*space_p != PCI_IO_SPACE) && (!(*bar_p))) {
619624Sschwartz prg_p->status = PCITOOL_INVALID_ADDRESS;
620624Sschwartz return (EINVAL);
621624Sschwartz }
622624Sschwartz
623624Sschwartz return (SUCCESS);
624624Sschwartz }
625624Sschwartz
626624Sschwartz
627624Sschwartz /* Perform register accesses on PCI leaf devices. */
628624Sschwartz int
pxtool_dev_reg_ops(dev_info_t * dip,void * arg,int cmd,int mode)629624Sschwartz pxtool_dev_reg_ops(dev_info_t *dip, void *arg, int cmd, int mode)
630624Sschwartz {
631624Sschwartz pcitool_reg_t prg;
632624Sschwartz uint64_t bar;
633624Sschwartz uint32_t space;
6341064Sschwartz uint64_t off_in_space;
635624Sschwartz boolean_t write_flag = B_FALSE;
636624Sschwartz px_t *px_p = DIP_TO_STATE(dip);
637624Sschwartz int rval = 0;
638624Sschwartz
639624Sschwartz if (cmd == PCITOOL_DEVICE_SET_REG)
640624Sschwartz write_flag = B_TRUE;
641624Sschwartz
642624Sschwartz DBG(DBG_TOOLS, dip, "pxtool_dev_reg_ops set/get reg\n");
643624Sschwartz if (ddi_copyin(arg, &prg, sizeof (pcitool_reg_t),
644624Sschwartz mode) != DDI_SUCCESS) {
645624Sschwartz DBG(DBG_TOOLS, dip, "Error reading arguments\n");
646624Sschwartz return (EFAULT);
647624Sschwartz }
648624Sschwartz
649624Sschwartz if ((rval = pxtool_dev_reg_ops_platchk(dip, &prg)) != SUCCESS) {
650624Sschwartz goto done_reg;
651624Sschwartz }
652624Sschwartz
653624Sschwartz DBG(DBG_TOOLS, dip, "raw bus:0x%x, dev:0x%x, func:0x%x\n",
654624Sschwartz prg.bus_no, prg.dev_no, prg.func_no);
655624Sschwartz DBG(DBG_TOOLS, dip, "barnum:0x%x, offset:0x%" PRIx64 ", acc:0x%x\n",
656624Sschwartz prg.barnum, prg.offset, prg.acc_attr);
657624Sschwartz
658624Sschwartz if ((rval = pxtool_validate_barnum_bdf(&prg)) != SUCCESS)
659624Sschwartz goto done_reg;
660624Sschwartz
661624Sschwartz if (prg.barnum == 0) { /* Proper config space desired. */
662624Sschwartz
6631064Sschwartz /* Enforce offset limits. */
6641064Sschwartz if (prg.offset >= DEV_CFG_SPACE_SIZE) {
6651064Sschwartz DBG(DBG_TOOLS, dip,
6661064Sschwartz "Config space offset 0x%" PRIx64 " out of range\n",
6671064Sschwartz prg.offset);
6681064Sschwartz prg.status = PCITOOL_OUT_OF_RANGE;
6691064Sschwartz rval = EINVAL;
6701064Sschwartz goto done_reg;
6711064Sschwartz }
6721064Sschwartz
6731064Sschwartz /*
6741064Sschwartz * For sun4v, config space base won't be known.
6751064Sschwartz * pxtool_get_phys_addr will return zero.
6761064Sschwartz * Note that for sun4v, phys_addr isn't
6771064Sschwartz * used for making config space accesses.
6781064Sschwartz *
6791064Sschwartz * For sun4u, assume that phys_addr will come back valid.
6801064Sschwartz */
6812053Sschwartz /*
6822053Sschwartz * Accessed entity is assumed small enough to be on one page.
6832053Sschwartz *
6842053Sschwartz * Since config space is less than a page and is aligned to
6852053Sschwartz * 0x1000, a device's entire config space will be on a single
6862053Sschwartz * page. Pass the device's base config space address here,
6872053Sschwartz * then add the offset within that space later. This works
6882053Sschwartz * around an issue in px_xlate_reg (called by
6892053Sschwartz * pxtool_get_phys_addr) which accepts only a 256 byte
6902053Sschwartz * range within a device.
6912053Sschwartz */
6922053Sschwartz off_in_space = PX_GET_BDF(&prg);
6932053Sschwartz prg.phys_addr =
6942053Sschwartz pxtool_get_phys_addr(px_p, PCI_CONFIG_SPACE, off_in_space);
6952053Sschwartz prg.phys_addr += prg.offset;
696624Sschwartz
697624Sschwartz DBG(DBG_TOOLS, dip,
6981064Sschwartz "off_in_space:0x%" PRIx64 ", phys_addr:0x%" PRIx64 ", "
6991064Sschwartz "end:%s\n", off_in_space, prg.phys_addr,
700624Sschwartz PCITOOL_ACC_IS_BIG_ENDIAN(prg.acc_attr) ? "big":"ltl");
701624Sschwartz
702624Sschwartz /*
703624Sschwartz * Access device. pr.status is modified.
704624Sschwartz * BDF is assumed valid at this point.
705624Sschwartz */
7061064Sschwartz rval = pxtool_pcicfg_access(px_p, &prg, &prg.data, write_flag);
707624Sschwartz goto done_reg;
708624Sschwartz }
709624Sschwartz
710624Sschwartz /* IO/ MEM/ MEM64 space. */
711624Sschwartz
7121064Sschwartz if ((rval = pxtool_get_bar(px_p, &prg, &bar, &space)) != SUCCESS)
713624Sschwartz goto done_reg;
714624Sschwartz
7151064Sschwartz switch (space) {
7161064Sschwartz case PCI_MEM32_SPACE:
717624Sschwartz
718624Sschwartz DBG(DBG_TOOLS, dip, "32 bit mem space\n");
719624Sschwartz
720624Sschwartz /* Can't write to ROM */
721624Sschwartz if ((PCI_BAR_OFFSET(prg) == PCI_CONF_ROM) && (write_flag)) {
722624Sschwartz prg.status = PCITOOL_ROM_WRITE;
723624Sschwartz rval = EIO;
724624Sschwartz goto done_reg;
725624Sschwartz }
7261064Sschwartz break;
727624Sschwartz
7281064Sschwartz case PCI_IO_SPACE:
7291064Sschwartz DBG(DBG_TOOLS, dip, "IO space\n");
7301064Sschwartz break;
7311064Sschwartz
7321064Sschwartz case PCI_MEM64_SPACE:
7331064Sschwartz DBG(DBG_TOOLS, dip,
7341064Sschwartz "64 bit mem space. 64-bit bar is 0x%" PRIx64 "\n", bar);
7351064Sschwartz break;
7361064Sschwartz
7371064Sschwartz default:
7381064Sschwartz DBG(DBG_TOOLS, dip, "Unknown space!\n");
7391064Sschwartz prg.status = PCITOOL_IO_ERROR;
7401064Sschwartz rval = EIO;
7411064Sschwartz goto done_reg;
742624Sschwartz }
743624Sschwartz
744624Sschwartz /*
7451064Sschwartz * Common code for all IO/MEM range spaces.
7461064Sschwartz *
747624Sschwartz * Use offset provided by caller to index into desired space.
748624Sschwartz * Note that prg.status is modified on error.
749624Sschwartz */
7501064Sschwartz off_in_space = bar + prg.offset;
7511064Sschwartz prg.phys_addr = pxtool_get_phys_addr(px_p, space, off_in_space);
752624Sschwartz
7531064Sschwartz DBG(DBG_TOOLS, dip,
7541064Sschwartz "addr in bar:0x%" PRIx64 ", offset:0x%" PRIx64 ", "
7551064Sschwartz "phys_addr:0x%" PRIx64 "\n", bar, prg.offset, prg.phys_addr);
7561064Sschwartz
7571064Sschwartz rval = pxtool_pciiomem_access(px_p, &prg, &prg.data, write_flag);
758624Sschwartz
759624Sschwartz done_reg:
7604397Sschwartz prg.drvr_version = PCITOOL_VERSION;
761624Sschwartz if (ddi_copyout(&prg, arg, sizeof (pcitool_reg_t),
762624Sschwartz mode) != DDI_SUCCESS) {
763624Sschwartz DBG(DBG_TOOLS, dip, "Error returning arguments.\n");
764624Sschwartz rval = EFAULT;
765624Sschwartz }
766624Sschwartz return (rval);
767624Sschwartz }
768624Sschwartz
769624Sschwartz
770624Sschwartz int
pxtool_init(dev_info_t * dip)771624Sschwartz pxtool_init(dev_info_t *dip)
772624Sschwartz {
773624Sschwartz int instance = ddi_get_instance(dip);
774624Sschwartz
775624Sschwartz if (ddi_create_minor_node(dip, PCI_MINOR_REG, S_IFCHR,
776*10923SEvan.Yan@Sun.COM PCI_MINOR_NUM(instance, PCI_TOOL_REG_MINOR_NUM),
777624Sschwartz DDI_NT_REGACC, 0) != DDI_SUCCESS) {
778624Sschwartz return (DDI_FAILURE);
779624Sschwartz }
780624Sschwartz
781624Sschwartz if (ddi_create_minor_node(dip, PCI_MINOR_INTR, S_IFCHR,
782*10923SEvan.Yan@Sun.COM PCI_MINOR_NUM(instance, PCI_TOOL_INTR_MINOR_NUM),
783624Sschwartz DDI_NT_INTRCTL, 0) != DDI_SUCCESS) {
784624Sschwartz ddi_remove_minor_node(dip, PCI_MINOR_REG);
785624Sschwartz return (DDI_FAILURE);
786624Sschwartz }
787624Sschwartz
788624Sschwartz return (DDI_SUCCESS);
789624Sschwartz }
790624Sschwartz
791624Sschwartz
792624Sschwartz void
pxtool_uninit(dev_info_t * dip)793624Sschwartz pxtool_uninit(dev_info_t *dip)
794624Sschwartz {
795624Sschwartz ddi_remove_minor_node(dip, PCI_MINOR_REG);
796624Sschwartz ddi_remove_minor_node(dip, PCI_MINOR_INTR);
797624Sschwartz }
798