pci_tools.c revision 5c59319b8761ccd4b952eec8d5caecf298024607
/*
* CDDL HEADER START
*
* The contents of this file are subject to the terms of the
* Common Development and Distribution License (the "License").
* You may not use this file except in compliance with the License.
*
* You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
* See the License for the specific language governing permissions
* and limitations under the License.
*
* When distributing Covered Code, include this CDDL HEADER in each
* file and include the License file at usr/src/OPENSOLARIS.LICENSE.
* If applicable, add the following below this CDDL HEADER, with the
* fields enclosed by brackets "[]" replaced with your own identifying
* information: Portions Copyright [yyyy] [name of copyright owner]
*
* CDDL HEADER END
*/
/*
* Copyright 2009 Sun Microsystems, Inc. All rights reserved.
* Use is subject to license terms.
*/
#include <vm/seg_kmem.h>
#include <sys/machparam.h>
#include <sys/pci_cfgspace.h>
#include <sys/pci_tools.h>
#include <sys/x86_archext.h>
#ifdef __xpv
#include <sys/hypervisor.h>
#endif
#define PCIEX_BDF_OFFSET_DELTA 4
#define SUCCESS 0
int pcitool_debug = 0;
/*
* Offsets of BARS in config space. First entry of 0 means config space.
* Entries here correlate to pcitool_bars_t enumerated type.
*/
0x0,
};
/* Max offset allowed into config space for a particular device. */
/* Extern declarations */
psm_intr_op_t, int *);
int
{
/* Create pcitool nodes for register access and interrupt routing. */
DDI_NT_REGACC, 0) != DDI_SUCCESS) {
return (DDI_FAILURE);
}
DDI_NT_INTRCTL, 0) != DDI_SUCCESS) {
return (DDI_FAILURE);
}
if (is_pciex)
return (DDI_SUCCESS);
}
void
{
}
/*ARGSUSED*/
static int
{
/* Version 1 of pcitool_intr_set_t doesn't have flags. */
return (EFAULT);
switch (iset.user_version) {
case PCITOOL_V1:
break;
case PCITOOL_V2:
copyinout_size = sizeof (pcitool_intr_set_t);
return (EFAULT);
break;
default:
goto done_set_intr;
}
goto done_set_intr;
}
goto done_set_intr;
}
/*
* For this locally-declared and used handle, ih_private will contain a
* CPU value, not an ihdl_plat_t as used for global interrupt handling.
*/
if (pcitool_debug)
prom_printf("user version:%d, flags:0x%x\n",
&result);
} else {
&result);
}
if (ret != PSM_SUCCESS) {
switch (result) {
case EIO: /* Error making the change */
break;
case ENXIO: /* Couldn't convert vector to irq */
break;
case EINVAL: /* CPU out of range */
break;
case ENOTSUP: /* Requested PSM intr ops missing */
break;
}
}
/* Return original CPU. */
return (rval);
}
/* It is assumed that dip != NULL */
static void
{
}
/*ARGSUSED*/
static int
{
/* Array part isn't used here, but oh well... */
int copyout_rval;
int circ;
int i;
/* Read in just the header part, no array section. */
return (EFAULT);
/* Validate argument. */
partial_iget.num_devs_ret = 0;
goto done_get_intr;
}
/*
* For this locally-declared and used handle, ih_private will contain a
* pointer to apic_get_intr_t, not an ihdl_plat_t as used for
* global interrupt handling.
*/
/* Caller wants device information returned. */
if (num_devs_ret > 0) {
/*
* Allocate room.
* If num_devs_ret == 0 iget remains pointing to partial_iget.
*/
/* Read in whole structure to verify there's room. */
SUCCESS) {
/* Be consistent and just return EFAULT here. */
return (EFAULT);
}
}
/*
* Lock device tree branch from the pci root nexus on down if info will
* be extracted from dips returned from the tree.
*/
}
/* Call psm_intr_ops(PSM_INTR_OP_GET_INTR) to get information. */
iget->num_devs_ret = 0;
goto done_get_intr;
}
/*
* Fill in the pcitool_intr_get_t to be returned,
* with the CPU, num_devs_ret and num_devs.
*/
/* Number of devices returned by apic. */
/* Device info was returned. */
/*
* num devs returned is num devs ret by apic,
* space permitting.
*/
/*
* Loop thru list of dips and extract driver, name and instance.
* Fill in the pcitool_intr_dev_t's with this info.
*/
for (i = 0; i < iget->num_devs_ret; i++)
/* Free kmem_alloc'ed memory of the apic_get_intr_t */
}
}
if (iget_kmem_alloc_size > 0)
if (copyout_rval != DDI_SUCCESS)
return (rval);
}
/*ARGSUSED*/
static int
{
/* If we need user_version, and to ret same user version as passed in */
DDI_SUCCESS) {
if (pcitool_debug)
prom_printf("Error reading arguments\n");
return (EFAULT);
}
/* For UPPC systems, psm_intr_ops has no entry for APIC_TYPE. */
intr_info.ctlr_version = 0;
} else {
APIC_PCPLUSMP_NAME) == 0)
else
}
DDI_SUCCESS) {
if (pcitool_debug)
prom_printf("Error returning arguments.\n");
}
return (rval);
}
/*
* Main function for handling interrupt CPU binding requests and queries.
* Need to implement later
*/
/*ARGSUSED*/
int
{
int rval;
switch (cmd) {
/* Associate a new CPU with a given vector */
case PCITOOL_DEVICE_SET_INTR:
break;
case PCITOOL_DEVICE_GET_INTR:
break;
case PCITOOL_SYSTEM_INTR_INFO:
break;
default:
}
return (rval);
}
/*
* A note about ontrap handling:
*
* X86 systems on which this module was tested return FFs instead of bus errors
* when accessing devices with invalid addresses. Ontrap handling, which
* gracefully handles kernel bus errors, is installed anyway, in case future
* X86 platforms require it.
*/
/*
* Perform register accesses on the nexus device itself.
* No explicit PCI nexus device for X86, so not applicable.
*/
/*ARGSUSED*/
int
{
return (ENOTSUP);
}
/* Swap endianness. */
static uint64_t
{
typedef union {
} data_split_t;
int i;
returned_data.data64 = 0;
for (i = 0; i < size; i++) {
}
return (returned_data.data64);
}
/*
* Access device. prg is modified.
*
* Extended config space is available only through memory-mapped access.
* Standard config space on pci express devices is available either way,
* so do it memory-mapped here too, for simplicity, if allowed by MCFG.
* If anything fails, return EINVAL so caller can try I/O access.
*/
/*ARGSUSED*/
static int
{
/*
* We must have a four-element property; base addr [0] must
* be nonzero. Also, segment [1] must be 0 for now; we don't
* handle nonzero segments (or create a property containing
* them)
*/
return (EINVAL);
}
return (EINVAL);
} else {
return (EINVAL);
}
return (EINVAL);
return (rval);
}
/* Access device. prg is modified. */
/*ARGSUSED*/
static int
{
/*
* NOTE: there is no way to verify whether or not the address is
* valid other than that it is within the maximum offset. The
* put functions return void and the get functions return ff on
* error.
*/
return (ENOTSUP);
}
if (write_flag) {
if (big_endian) {
} else {
}
switch (size) {
case 1:
break;
case 2:
break;
case 4:
break;
default:
break;
}
} else {
switch (size) {
case 1:
break;
case 2:
break;
case 4:
break;
default:
break;
}
if (big_endian) {
} else {
}
}
}
return (rval);
}
/*ARGSUSED*/
static int
{
/*
* on_trap works like setjmp.
*
* A non-zero return here means on_trap has returned from an error.
*
* A zero return here means that on_trap has just returned from setup.
*/
no_trap();
if (pcitool_debug)
"pcitool_io_access: on_trap caught an error...\n");
return (EFAULT);
}
if (write_flag) {
if (big_endian) {
} else {
}
if (pcitool_debug)
prom_printf("Writing %ld byte(s) to port 0x%x\n",
switch (size) {
case 1:
break;
case 2:
break;
case 4:
break;
default:
break;
}
} else {
if (pcitool_debug)
prom_printf("Reading %ld byte(s) from port 0x%x\n",
switch (size) {
case 1:
break;
case 2:
break;
case 4:
break;
default:
break;
}
if (big_endian) {
} else {
}
}
}
no_trap();
return (rval);
}
/*ARGSUSED*/
static int
{
int rval = DDI_SUCCESS;
/*
* on_trap works like setjmp.
*
* A non-zero return here means on_trap has returned from an error.
*
* A zero return here means that on_trap has just returned from setup.
*/
no_trap();
if (pcitool_debug)
"pcitool_mem_access: on_trap caught an error...\n");
return (EFAULT);
}
if (write_flag) {
if (big_endian) {
} else {
}
switch (size) {
case 1:
break;
case 2:
break;
case 4:
break;
case 8:
break;
default:
break;
}
} else {
switch (size) {
case 1:
break;
case 2:
break;
case 4:
break;
case 8:
break;
default:
break;
}
if (big_endian) {
} else {
}
}
}
no_trap();
return (rval);
}
/*
* Map up to 2 pages which contain the address we want to access.
*
* Mapping should span no more than 8 bytes. With X86 it is possible for an
* 8 byte value to start on a 4 byte boundary, so it can cross a page boundary.
* We'll never have to map more than two pages.
*/
static uint64_t
{
void *virt_base;
if (pcitool_debug)
prom_printf("pcitool_map: Called with PA:0x%p\n",
*num_pages = 1;
/* Desired mapping would span more than two pages. */
if (pcitool_debug)
prom_printf("boundary violation: "
return (NULL);
(*num_pages)++;
}
/* Get page(s) of virtual space. */
if (pcitool_debug)
prom_printf("Couldn't get virtual base address.\n");
return (NULL);
}
if (pcitool_debug)
#ifdef __xpv
/*
* We should only get here if we are dom0.
* We're using a real device so we need to translate the MA to a PFN.
*/
#else
#endif
/* Now map the allocated virtual space to the physical address. */
if (pcitool_debug)
prom_printf("pcitool_map: returning VA:0x%p\n",
(void *)(uintptr_t)returned_addr);
return (returned_addr);
}
/* Unmap the mapped page(s). */
static void
{
}
/* Perform register accesses on PCI leaf devices. */
int
{
int rval = 0;
switch (cmd) {
case (PCITOOL_DEVICE_SET_REG):
write_flag = B_TRUE;
/*FALLTHRU*/
case (PCITOOL_DEVICE_GET_REG):
if (pcitool_debug)
prom_printf("pci_dev_reg_ops set/get reg\n");
DDI_SUCCESS) {
if (pcitool_debug)
prom_printf("Error reading arguments\n");
return (EFAULT);
}
goto done_reg;
}
if (pcitool_debug)
prom_printf("raw bus:0x%x, dev:0x%x, func:0x%x\n",
/* Validate address arguments of bus / dev / func */
(PCI_REG_BUS_M >> PCI_REG_BUS_SHIFT)) !=
(PCI_REG_DEV_M >> PCI_REG_DEV_SHIFT)) !=
(PCI_REG_FUNC_M >> PCI_REG_FUNC_SHIFT)) !=
goto done_reg;
}
/* Proper config space desired. */
if (pcitool_debug)
goto done_reg;
}
/*
* Access device. prg is modified.
* First, check for AMD northbridges for I/O access
* (This fix will move in future to pcitool user-land)
* Next, check for PCIe devices and do
* memory-mapped access
* Lastly, check for PCI devices and do I/O access
*/
} else if (max_cfg_size == PCIE_CONF_HDR_SIZE) {
/* Not valid for MMIO; try IO */
}
} else {
}
if (pcitool_debug)
/* IO/ MEM/ MEM64 space. */
} else {
/*
* Translate BAR number into offset of the BAR in
* the device's config space.
*/
if (pcitool_debug)
/*
* Get Bus Address Register (BAR) from config space.
* prg2.offset is the offset into config space of the
* BAR desired. prg.status is modified on error.
*/
if (pcitool_debug)
prom_printf("BAR access failed\n");
goto done_reg;
}
/*
* Reference proper PCI space based on the BAR.
* If 64 bit MEM space, need to load other half of the
* BAR first.
*/
if (pcitool_debug)
if (pcitool_debug)
prom_printf("BAR data == 0\n");
goto done_reg;
}
if (pcitool_debug)
prom_printf("BAR data == -1\n");
goto done_reg;
}
/*
* BAR has bits saying this space is IO space, unless
* this is the ROM address register.
*/
if (pcitool_debug)
prom_printf("IO space\n");
prom_printf("IO access failed\n");
goto done_reg;
/*
* BAR has bits saying this space is 64 bit memory
* space, unless this is the ROM address register.
*
* The 64 bit address stored in two BAR cells is not
* necessarily aligned on an 8-byte boundary.
* Need to keep the first 4 bytes read,
* and do a separate read of the high 4 bytes.
*/
/*
* Don't try to read the next 4 bytes
* past the end of BARs.
*/
goto done_reg;
}
/*
* Access device.
* prg2.status is modified on error.
*/
goto done_reg;
}
goto done_reg;
}
if (pcitool_debug)
"64 bit mem space. "
/* Mem32 space, including ROM */
} else {
if (pcitool_debug)
"Additional ROM "
"checking\n");
/* Can't write to ROM */
if (write_flag) {
goto done_reg;
/* ROM disabled for reading */
goto done_reg;
}
}
if (pcitool_debug)
prom_printf("32 bit mem space\n");
}
if (pcitool_debug)
/*
* Use offset provided by caller to index into
* desired space, then access.
* Note that prg.status is modified on error.
*/
goto done_reg;
}
}
DDI_SUCCESS) {
if (pcitool_debug)
prom_printf("Error returning arguments.\n");
}
break;
default:
break;
}
return (rval);
}