#include <sys/sysmacros.h>
#include <sys/types.h>
#include <sys/mkdev.h>
#include <sys/stat.h>
#include <sys/sunddi.h>
#include <vm/seg_kmem.h>
#include <sys/machparam.h>
#include <sys/sunndi.h>
#include <sys/ontrap.h>
#include <sys/psm.h>
#include <sys/pcie.h>
#include <sys/pci_cfgspace.h>
#include <sys/pci_tools.h>
#include <io/pci/pci_tools_ext.h>
#include <sys/apic.h>
#include <sys/apix.h>
#include <io/pci/pci_var.h>
#include <sys/pci_impl.h>
#include <sys/promif.h>
#include <sys/x86_archext.h>
#include <sys/cpuvar.h>
#include <sys/pci_cfgacc.h>
#ifdef __xpv
#include <sys/hypervisor.h>
#endif
#define PCIEX_BDF_OFFSET_DELTA 4
#define PCIEX_REG_FUNC_SHIFT (PCI_REG_FUNC_SHIFT + PCIEX_BDF_OFFSET_DELTA)
#define PCIEX_REG_DEV_SHIFT (PCI_REG_DEV_SHIFT + PCIEX_BDF_OFFSET_DELTA)
#define PCIEX_REG_BUS_SHIFT (PCI_REG_BUS_SHIFT + PCIEX_BDF_OFFSET_DELTA)
#define SUCCESS 0
extern uint64_t mcfg_mem_base;
extern uint_t pci_iocfg_max_offset;
int pcitool_debug = 0;
static uint64_t max_cfg_size = PCI_CONF_HDR_SIZE;
static uint64_t pcitool_swap_endian(uint64_t data, int size);
static int pcitool_cfg_access(pcitool_reg_t *prg, boolean_t write_flag,
boolean_t io_access);
static int pcitool_io_access(pcitool_reg_t *prg, boolean_t write_flag);
static int pcitool_mem_access(pcitool_reg_t *prg, uint64_t virt_addr,
boolean_t write_flag);
static uint64_t pcitool_map(uint64_t phys_addr, size_t size, size_t *num_pages);
static void pcitool_unmap(uint64_t virt_addr, size_t num_pages);
extern int (*psm_intr_ops)(dev_info_t *, ddi_intr_handle_impl_t *,
psm_intr_op_t, int *);
int
pcitool_init(dev_info_t *dip, boolean_t is_pciex)
{
int instance = ddi_get_instance(dip);
if (ddi_create_minor_node(dip, PCI_MINOR_REG, S_IFCHR,
PCI_MINOR_NUM(instance, PCI_TOOL_REG_MINOR_NUM),
DDI_NT_REGACC, 0) != DDI_SUCCESS) {
return (DDI_FAILURE);
}
if (ddi_create_minor_node(dip, PCI_MINOR_INTR, S_IFCHR,
PCI_MINOR_NUM(instance, PCI_TOOL_INTR_MINOR_NUM),
DDI_NT_INTRCTL, 0) != DDI_SUCCESS) {
ddi_remove_minor_node(dip, PCI_MINOR_REG);
return (DDI_FAILURE);
}
if (is_pciex)
max_cfg_size = PCIE_CONF_HDR_SIZE;
return (DDI_SUCCESS);
}
void
pcitool_uninit(dev_info_t *dip)
{
ddi_remove_minor_node(dip, PCI_MINOR_INTR);
ddi_remove_minor_node(dip, PCI_MINOR_REG);
}
static int
pcitool_set_intr(dev_info_t *dip, void *arg, int mode)
{
ddi_intr_handle_impl_t info_hdl;
pcitool_intr_set_t iset;
uint32_t old_cpu;
int ret, result;
size_t copyinout_size;
int rval = SUCCESS;
apic_get_type_t type_info;
copyinout_size = (size_t)&iset.flags - (size_t)&iset;
if (ddi_copyin(arg, &iset, copyinout_size, mode) != DDI_SUCCESS)
return (EFAULT);
switch (iset.user_version) {
case PCITOOL_V1:
break;
case PCITOOL_V2:
copyinout_size = sizeof (pcitool_intr_set_t);
if (ddi_copyin(arg, &iset, copyinout_size, mode) != DDI_SUCCESS)
return (EFAULT);
break;
default:
iset.status = PCITOOL_OUT_OF_RANGE;
rval = ENOTSUP;
goto done_set_intr;
}
if (iset.flags & PCITOOL_INTR_FLAG_SET_MSI) {
rval = ENOTSUP;
iset.status = PCITOOL_IO_ERROR;
goto done_set_intr;
}
info_hdl.ih_private = &type_info;
if ((*psm_intr_ops)(NULL, &info_hdl,
PSM_INTR_OP_APIC_TYPE, NULL) != PSM_SUCCESS) {
rval = ENOTSUP;
iset.status = PCITOOL_IO_ERROR;
goto done_set_intr;
}
if (strcmp(type_info.avgi_type, APIC_APIX_NAME) == 0) {
if (iset.old_cpu > type_info.avgi_num_cpu) {
rval = EINVAL;
iset.status = PCITOOL_INVALID_CPUID;
goto done_set_intr;
}
old_cpu = iset.old_cpu;
} else {
if ((old_cpu =
pci_get_cpu_from_vecirq(iset.ino, IS_VEC)) == -1) {
iset.status = PCITOOL_IO_ERROR;
rval = EINVAL;
goto done_set_intr;
}
}
if (iset.ino > type_info.avgi_num_intr) {
rval = EINVAL;
iset.status = PCITOOL_INVALID_INO;
goto done_set_intr;
}
iset.status = PCITOOL_SUCCESS;
old_cpu &= ~PSMGI_CPU_USER_BOUND;
if (strcmp(type_info.avgi_type, APIC_APIX_NAME) == 0) {
info_hdl.ih_vector = APIX_VIRTVECTOR(old_cpu, iset.ino);
} else {
info_hdl.ih_vector = iset.ino;
}
info_hdl.ih_private = (void *)(uintptr_t)iset.cpu_id;
info_hdl.ih_flags = PSMGI_INTRBY_VEC;
if (pcitool_debug)
prom_printf("user version:%d, flags:0x%x\n",
iset.user_version, iset.flags);
result = ENOTSUP;
if ((iset.user_version >= PCITOOL_V2) &&
(iset.flags & PCITOOL_INTR_FLAG_SET_GROUP)) {
ret = (*psm_intr_ops)(NULL, &info_hdl, PSM_INTR_OP_GRP_SET_CPU,
&result);
} else {
ret = (*psm_intr_ops)(NULL, &info_hdl, PSM_INTR_OP_SET_CPU,
&result);
}
if (ret != PSM_SUCCESS) {
switch (result) {
case EIO:
rval = EIO;
iset.status = PCITOOL_IO_ERROR;
break;
case ENXIO:
rval = EINVAL;
iset.status = PCITOOL_INVALID_INO;
break;
case EINVAL:
rval = EINVAL;
iset.status = PCITOOL_INVALID_CPUID;
break;
case ENOTSUP:
rval = ENOTSUP;
iset.status = PCITOOL_IO_ERROR;
break;
}
}
iset.cpu_id = old_cpu;
if (strcmp(type_info.avgi_type, APIC_APIX_NAME) == 0) {
iset.ino = APIX_VIRTVEC_VECTOR(info_hdl.ih_vector);
}
done_set_intr:
iset.drvr_version = PCITOOL_VERSION;
if (ddi_copyout(&iset, arg, copyinout_size, mode) != DDI_SUCCESS)
rval = EFAULT;
return (rval);
}
static void
pcitool_get_intr_dev_info(dev_info_t *dip, pcitool_intr_dev_t *devs)
{
(void) strncpy(devs->driver_name,
ddi_driver_name(dip), MAXMODCONFNAME-2);
devs->driver_name[MAXMODCONFNAME-1] = '\0';
(void) ddi_pathname(dip, devs->path);
devs->dev_inst = ddi_get_instance(dip);
}
static int
pcitool_get_intr(dev_info_t *dip, void *arg, int mode)
{
pcitool_intr_get_t partial_iget;
pcitool_intr_get_t *iget = &partial_iget;
size_t iget_kmem_alloc_size = 0;
uint8_t num_devs_ret = 0;
int copyout_rval;
int rval = SUCCESS;
int i;
ddi_intr_handle_impl_t info_hdl;
apic_get_intr_t intr_info;
apic_get_type_t type_info;
if (ddi_copyin(arg, &partial_iget, PCITOOL_IGET_SIZE(0), mode) !=
DDI_SUCCESS)
return (EFAULT);
if (partial_iget.flags & PCITOOL_INTR_FLAG_GET_MSI) {
partial_iget.status = PCITOOL_IO_ERROR;
partial_iget.num_devs_ret = 0;
rval = ENOTSUP;
goto done_get_intr;
}
info_hdl.ih_private = &type_info;
if ((*psm_intr_ops)(NULL, &info_hdl,
PSM_INTR_OP_APIC_TYPE, NULL) != PSM_SUCCESS) {
iget->status = PCITOOL_IO_ERROR;
iget->num_devs_ret = 0;
rval = EINVAL;
goto done_get_intr;
}
if (strcmp(type_info.avgi_type, APIC_APIX_NAME) == 0) {
if (partial_iget.cpu_id > type_info.avgi_num_cpu) {
partial_iget.status = PCITOOL_INVALID_CPUID;
partial_iget.num_devs_ret = 0;
rval = EINVAL;
goto done_get_intr;
}
}
if ((partial_iget.ino & APIX_VIRTVEC_VECMASK) >
type_info.avgi_num_intr) {
partial_iget.status = PCITOOL_INVALID_INO;
partial_iget.num_devs_ret = 0;
rval = EINVAL;
goto done_get_intr;
}
num_devs_ret = partial_iget.num_devs_ret;
intr_info.avgi_dip_list = NULL;
intr_info.avgi_req_flags =
PSMGI_REQ_CPUID | PSMGI_REQ_NUM_DEVS | PSMGI_INTRBY_VEC;
info_hdl.ih_private = &intr_info;
if (strcmp(type_info.avgi_type, APIC_APIX_NAME) == 0) {
info_hdl.ih_vector =
APIX_VIRTVECTOR(partial_iget.cpu_id, partial_iget.ino);
} else {
info_hdl.ih_vector = partial_iget.ino;
}
if (num_devs_ret > 0) {
intr_info.avgi_req_flags |= PSMGI_REQ_GET_DEVS;
iget_kmem_alloc_size = PCITOOL_IGET_SIZE(num_devs_ret);
iget = kmem_alloc(iget_kmem_alloc_size, KM_SLEEP);
if (ddi_copyin(arg, iget, iget_kmem_alloc_size, mode) !=
SUCCESS) {
kmem_free(iget, iget_kmem_alloc_size);
return (EFAULT);
}
}
bzero(iget, PCITOOL_IGET_SIZE(num_devs_ret));
iget->ino = info_hdl.ih_vector;
if (intr_info.avgi_req_flags & PSMGI_REQ_GET_DEVS) {
ndi_devi_enter(dip);
}
if ((rval = (*psm_intr_ops)(NULL, &info_hdl,
PSM_INTR_OP_GET_INTR, NULL)) != PSM_SUCCESS) {
iget->status = PCITOOL_IO_ERROR;
iget->num_devs_ret = 0;
rval = EINVAL;
goto done_get_intr;
}
if (intr_info.avgi_cpu_id == IRQ_UNBOUND ||
intr_info.avgi_cpu_id == IRQ_UNINIT)
iget->cpu_id = 0;
else
iget->cpu_id = intr_info.avgi_cpu_id & ~PSMGI_CPU_USER_BOUND;
iget->num_devs = intr_info.avgi_num_devs;
if (intr_info.avgi_req_flags & PSMGI_REQ_GET_DEVS) {
iget->num_devs_ret = min(num_devs_ret, intr_info.avgi_num_devs);
for (i = 0; i < iget->num_devs_ret; i++)
pcitool_get_intr_dev_info(intr_info.avgi_dip_list[i],
&iget->dev[i]);
kmem_free(intr_info.avgi_dip_list,
intr_info.avgi_num_devs * sizeof (dev_info_t *));
}
done_get_intr:
if (intr_info.avgi_req_flags & PSMGI_REQ_GET_DEVS) {
ndi_devi_exit(dip);
}
iget->drvr_version = PCITOOL_VERSION;
copyout_rval = ddi_copyout(iget, arg,
PCITOOL_IGET_SIZE(num_devs_ret), mode);
if (iget_kmem_alloc_size > 0)
kmem_free(iget, iget_kmem_alloc_size);
if (copyout_rval != DDI_SUCCESS)
rval = EFAULT;
return (rval);
}
static int
pcitool_intr_info(dev_info_t *dip, void *arg, int mode)
{
pcitool_intr_info_t intr_info;
ddi_intr_handle_impl_t info_hdl;
int rval = SUCCESS;
apic_get_type_t type_info;
if (ddi_copyin(arg, &intr_info, sizeof (pcitool_intr_info_t), mode) !=
DDI_SUCCESS) {
if (pcitool_debug)
prom_printf("Error reading arguments\n");
return (EFAULT);
}
if (intr_info.flags & PCITOOL_INTR_FLAG_GET_MSI)
return (ENOTSUP);
info_hdl.ih_private = &type_info;
if ((rval = (*psm_intr_ops)(NULL, &info_hdl,
PSM_INTR_OP_APIC_TYPE, NULL)) != PSM_SUCCESS) {
intr_info.ctlr_type = PCITOOL_CTLR_TYPE_UPPC;
intr_info.ctlr_version = 0;
intr_info.num_intr = APIC_MAX_VECTOR;
} else {
intr_info.ctlr_version = (uint32_t)info_hdl.ih_ver;
intr_info.num_cpu = type_info.avgi_num_cpu;
if (strcmp(type_info.avgi_type,
APIC_PCPLUSMP_NAME) == 0) {
intr_info.ctlr_type = PCITOOL_CTLR_TYPE_PCPLUSMP;
intr_info.num_intr = type_info.avgi_num_intr;
} else if (strcmp(type_info.avgi_type,
APIC_APIX_NAME) == 0) {
intr_info.ctlr_type = PCITOOL_CTLR_TYPE_APIX;
intr_info.num_intr = type_info.avgi_num_intr;
} else {
intr_info.ctlr_type = PCITOOL_CTLR_TYPE_UNKNOWN;
intr_info.num_intr = APIC_MAX_VECTOR;
}
}
intr_info.drvr_version = PCITOOL_VERSION;
if (ddi_copyout(&intr_info, arg, sizeof (pcitool_intr_info_t), mode) !=
DDI_SUCCESS) {
if (pcitool_debug)
prom_printf("Error returning arguments.\n");
rval = EFAULT;
}
return (rval);
}
int
pcitool_intr_admn(dev_info_t *dip, void *arg, int cmd, int mode)
{
int rval;
switch (cmd) {
case PCITOOL_DEVICE_SET_INTR:
rval = pcitool_set_intr(dip, arg, mode);
break;
case PCITOOL_DEVICE_GET_INTR:
rval = pcitool_get_intr(dip, arg, mode);
break;
case PCITOOL_SYSTEM_INTR_INFO:
rval = pcitool_intr_info(dip, arg, mode);
break;
default:
rval = ENOTSUP;
}
return (rval);
}
int
pcitool_bus_reg_ops(dev_info_t *dip, void *arg, int cmd, int mode)
{
return (ENOTSUP);
}
static uint64_t
pcitool_swap_endian(uint64_t data, int size)
{
typedef union {
uint64_t data64;
uint8_t data8[8];
} data_split_t;
data_split_t orig_data;
data_split_t returned_data;
int i;
orig_data.data64 = data;
returned_data.data64 = 0;
for (i = 0; i < size; i++) {
returned_data.data8[i] = orig_data.data8[size - 1 - i];
}
return (returned_data.data64);
}
static int
pcitool_cfg_access(pcitool_reg_t *prg, boolean_t write_flag,
boolean_t io_access)
{
int size = PCITOOL_ACC_ATTR_SIZE(prg->acc_attr);
boolean_t big_endian = PCITOOL_ACC_IS_BIG_ENDIAN(prg->acc_attr);
int rval = SUCCESS;
uint64_t local_data;
pci_cfgacc_req_t req;
uint32_t max_offset;
if ((size <= 0) || (size > 8) || !ISP2(size)) {
prg->status = PCITOOL_INVALID_SIZE;
return (ENOTSUP);
}
if (io_access)
max_offset = pci_iocfg_max_offset;
else
max_offset = 0xFFF;
if (prg->offset + size - 1 > max_offset) {
prg->status = PCITOOL_INVALID_ADDRESS;
return (ENOTSUP);
}
prg->status = PCITOOL_SUCCESS;
req.rcdip = NULL;
req.bdf = PCI_GETBDF(prg->bus_no, prg->dev_no, prg->func_no);
req.offset = prg->offset;
req.size = size;
req.write = write_flag;
req.ioacc = io_access;
if (write_flag) {
if (big_endian) {
local_data = pcitool_swap_endian(prg->data, size);
} else {
local_data = prg->data;
}
VAL64(&req) = local_data;
pci_cfgacc_acc(&req);
} else {
pci_cfgacc_acc(&req);
switch (size) {
case 1:
local_data = VAL8(&req);
break;
case 2:
local_data = VAL16(&req);
break;
case 4:
local_data = VAL32(&req);
break;
case 8:
local_data = VAL64(&req);
break;
default:
prg->status = PCITOOL_INVALID_ADDRESS;
return (ENOTSUP);
}
if (big_endian) {
prg->data =
pcitool_swap_endian(local_data, size);
} else {
prg->data = local_data;
}
}
if (req.ioacc && (prg->offset + size - 1 > pci_iocfg_max_offset)) {
prg->status = PCITOOL_INVALID_ADDRESS;
return (ENOTSUP);
}
prg->phys_addr = 0;
if (!req.ioacc && mcfg_mem_base != 0) {
prg->phys_addr = mcfg_mem_base + prg->offset +
((prg->bus_no << PCIEX_REG_BUS_SHIFT) |
(prg->dev_no << PCIEX_REG_DEV_SHIFT) |
(prg->func_no << PCIEX_REG_FUNC_SHIFT));
}
return (rval);
}
static int
pcitool_io_access(pcitool_reg_t *prg, boolean_t write_flag)
{
int port = (int)prg->phys_addr;
size_t size = PCITOOL_ACC_ATTR_SIZE(prg->acc_attr);
boolean_t big_endian = PCITOOL_ACC_IS_BIG_ENDIAN(prg->acc_attr);
volatile int rval = SUCCESS;
on_trap_data_t otd;
volatile uint64_t local_data;
if (on_trap(&otd, OT_DATA_ACCESS)) {
no_trap();
if (pcitool_debug)
prom_printf(
"pcitool_io_access: on_trap caught an error...\n");
prg->status = PCITOOL_INVALID_ADDRESS;
return (EFAULT);
}
if (write_flag) {
if (big_endian) {
local_data = pcitool_swap_endian(prg->data, size);
} else {
local_data = prg->data;
}
if (pcitool_debug)
prom_printf("Writing %ld byte(s) to port 0x%x\n",
size, port);
switch (size) {
case 1:
outb(port, (uint8_t)local_data);
break;
case 2:
outw(port, (uint16_t)local_data);
break;
case 4:
outl(port, (uint32_t)local_data);
break;
default:
rval = ENOTSUP;
prg->status = PCITOOL_INVALID_SIZE;
break;
}
} else {
if (pcitool_debug)
prom_printf("Reading %ld byte(s) from port 0x%x\n",
size, port);
switch (size) {
case 1:
local_data = inb(port);
break;
case 2:
local_data = inw(port);
break;
case 4:
local_data = inl(port);
break;
default:
rval = ENOTSUP;
prg->status = PCITOOL_INVALID_SIZE;
break;
}
if (rval == SUCCESS) {
if (big_endian) {
prg->data =
pcitool_swap_endian(local_data, size);
} else {
prg->data = local_data;
}
}
}
no_trap();
return (rval);
}
static int
pcitool_mem_access(pcitool_reg_t *prg, uint64_t virt_addr, boolean_t write_flag)
{
size_t size = PCITOOL_ACC_ATTR_SIZE(prg->acc_attr);
boolean_t big_endian = PCITOOL_ACC_IS_BIG_ENDIAN(prg->acc_attr);
volatile int rval = DDI_SUCCESS;
on_trap_data_t otd;
volatile uint64_t local_data;
if (on_trap(&otd, OT_DATA_ACCESS)) {
no_trap();
if (pcitool_debug)
prom_printf(
"pcitool_mem_access: on_trap caught an error...\n");
prg->status = PCITOOL_INVALID_ADDRESS;
return (EFAULT);
}
if (write_flag) {
if (big_endian) {
local_data = pcitool_swap_endian(prg->data, size);
} else {
local_data = prg->data;
}
switch (size) {
case 1:
*((uint8_t *)(uintptr_t)virt_addr) = local_data;
break;
case 2:
*((uint16_t *)(uintptr_t)virt_addr) = local_data;
break;
case 4:
*((uint32_t *)(uintptr_t)virt_addr) = local_data;
break;
case 8:
*((uint64_t *)(uintptr_t)virt_addr) = local_data;
break;
default:
rval = ENOTSUP;
prg->status = PCITOOL_INVALID_SIZE;
break;
}
} else {
switch (size) {
case 1:
local_data = *((uint8_t *)(uintptr_t)virt_addr);
break;
case 2:
local_data = *((uint16_t *)(uintptr_t)virt_addr);
break;
case 4:
local_data = *((uint32_t *)(uintptr_t)virt_addr);
break;
case 8:
local_data = *((uint64_t *)(uintptr_t)virt_addr);
break;
default:
rval = ENOTSUP;
prg->status = PCITOOL_INVALID_SIZE;
break;
}
if (rval == SUCCESS) {
if (big_endian) {
prg->data =
pcitool_swap_endian(local_data, size);
} else {
prg->data = local_data;
}
}
}
no_trap();
return (rval);
}
static uint64_t
pcitool_map(uint64_t phys_addr, size_t size, size_t *num_pages)
{
uint64_t page_base = phys_addr & ~MMU_PAGEOFFSET;
uint64_t offset = phys_addr & MMU_PAGEOFFSET;
void *virt_base;
uint64_t returned_addr;
pfn_t pfn;
if (pcitool_debug)
prom_printf("pcitool_map: Called with PA:0x%p\n",
(void *)(uintptr_t)phys_addr);
*num_pages = 1;
if ((offset + size) > (MMU_PAGESIZE * 2)) {
if (pcitool_debug)
prom_printf("boundary violation: "
"offset:0x%" PRIx64 ", size:%ld, pagesize:0x%lx\n",
offset, (uintptr_t)size, (uintptr_t)MMU_PAGESIZE);
return (0);
} else if ((offset + size) > MMU_PAGESIZE) {
(*num_pages)++;
}
virt_base = vmem_alloc(heap_arena, ptob(*num_pages), VM_NOSLEEP);
if (virt_base == NULL) {
if (pcitool_debug)
prom_printf("Couldn't get virtual base address.\n");
return (0);
}
if (pcitool_debug)
prom_printf("Got base virtual address:0x%p\n", virt_base);
#ifdef __xpv
ASSERT(DOMAIN_IS_INITDOMAIN(xen_info));
pfn = xen_assign_pfn(mmu_btop(page_base));
#else
pfn = btop(page_base);
#endif
hat_devload(kas.a_hat, virt_base, mmu_ptob(*num_pages), pfn,
PROT_READ | PROT_WRITE | HAT_STRICTORDER,
HAT_LOAD_LOCK);
returned_addr = ((uintptr_t)(virt_base)) + offset;
if (pcitool_debug)
prom_printf("pcitool_map: returning VA:0x%p\n",
(void *)(uintptr_t)returned_addr);
return (returned_addr);
}
static void
pcitool_unmap(uint64_t virt_addr, size_t num_pages)
{
void *base_virt_addr = (void *)(uintptr_t)(virt_addr & ~MMU_PAGEOFFSET);
hat_unload(kas.a_hat, base_virt_addr, ptob(num_pages),
HAT_UNLOAD_UNLOCK);
vmem_free(heap_arena, base_virt_addr, ptob(num_pages));
}
static int
pcitool_bar_find(uint8_t bar, boolean_t bridge, boolean_t cfg_io,
pcitool_reg_t *cfg, uint64_t *pa, boolean_t *io_bar)
{
uint8_t nbar = bridge ? PCI_BCNF_BASE_NUM : PCI_BASE_NUM;
uint32_t raw[PCI_BASE_NUM];
for (uint8_t i = 0; i < nbar; i++) {
cfg->acc_attr = PCITOOL_ACC_ATTR_SIZE_4 |
PCITOOL_ACC_ATTR_ENDN_LTL;
cfg->offset = PCI_CONF_BASE0 + i * 4;
int ret = pcitool_cfg_access(cfg, B_FALSE, cfg_io);
if (ret != 0) {
return (ret);
}
raw[i] = (uint32_t)cfg->data;
}
for (uint8_t i = 0; i < nbar; i++) {
boolean_t io = (raw[i] & PCI_BASE_SPACE_M) == PCI_BASE_SPACE_IO;
uint8_t idx = i;
uint64_t addr;
if (raw[idx] == PCI_EINVAL32)
continue;
if (!io && (raw[idx] & PCI_BASE_TYPE_M) == PCI_BASE_TYPE_ALL) {
i++;
if (i == nbar) {
cfg->status = PCITOOL_OUT_OF_RANGE;
return (EIO);
}
}
if (bar != idx)
continue;
*io_bar = io;
if (io) {
addr = raw[idx] & PCI_BASE_IO_ADDR_M;
} else {
switch (raw[idx] & PCI_BASE_TYPE_M) {
case PCI_BASE_TYPE_MEM:
case PCI_BASE_TYPE_LOW:
addr = raw[idx] & PCI_BASE_M_ADDR_M;
break;
case PCI_BASE_TYPE_ALL:
if (raw[idx + 1] == PCI_EINVAL32) {
cfg->status = PCITOOL_INVALID_ADDRESS;
return (EINVAL);
}
addr = raw[idx] & PCI_BASE_M_ADDR_M;
addr |= (uint64_t)(raw[idx + 1] &
PCI_BASE_M_ADDR_M) << 32;
break;
case PCI_BASE_TYPE_RES:
cfg->status = PCITOOL_INVALID_ADDRESS;
return (EINVAL);
}
}
if (addr == 0) {
cfg->status = PCITOOL_INVALID_ADDRESS;
return (EINVAL);
}
*pa = addr;
return (0);
}
cfg->status = PCITOOL_INVALID_ADDRESS;
return (EINVAL);
}
typedef struct {
const pcitool_reg_t *pbwc_reg;
uint64_t pbwc_size;
} pcitool_bar_walk_cb_t;
static int
pcitool_bar_walk_cb(dev_info_t *dip, void *arg)
{
const char *drv;
pcitool_bar_walk_cb_t *cb = arg;
int *regs, ret;
uint_t nreg;
ret = DDI_WALK_PRUNECHILD;
if ((drv = ddi_driver_name(dip)) != NULL &&
(strcmp(drv, "pcieb") == 0 || strcmp(drv, "pci_pci") == 0)) {
ret = DDI_WALK_CONTINUE;
}
if (ddi_prop_lookup_int_array(DDI_DEV_T_ANY, dip, DDI_PROP_DONTPASS,
"reg", ®s, &nreg) != DDI_PROP_SUCCESS) {
return (ret);
}
if (nreg == 0 || nreg % 5 != 0) {
ddi_prop_free(regs);
return (ret);
}
nreg /= 5;
uint32_t bdf = (uint32_t)regs[0];
if (PCI_REG_BUS_G(bdf) != cb->pbwc_reg->bus_no ||
PCI_REG_DEV_G(bdf) != cb->pbwc_reg->dev_no ||
PCI_REG_FUNC_G(bdf) != cb->pbwc_reg->func_no) {
ddi_prop_free(regs);
return (ret);
}
uint32_t targ = PCI_CONF_BASE0 + ((cb->pbwc_reg->barnum - 1) *
sizeof (uint32_t));
const pci_regspec_t *rsp = (pci_regspec_t *)regs;
for (int i = 0; i < nreg; i++, rsp++) {
uint32_t check = PCI_REG_REG_G(rsp->pci_phys_hi);
if (check != targ)
continue;
cb->pbwc_size = (uint64_t)rsp->pci_size_hi << 32;
cb->pbwc_size |= rsp->pci_size_low;
ddi_prop_free(regs);
return (DDI_WALK_TERMINATE);
}
ddi_prop_free(regs);
return (ret);
}
int
pcitool_dev_reg_ops(dev_info_t *dip, void *arg, int cmd, int mode)
{
boolean_t write_flag = B_FALSE;
boolean_t cfgspace_io = B_TRUE;
int rval = 0;
pcitool_reg_t prg;
uint8_t size;
uint64_t base_addr;
if (cmd != PCITOOL_DEVICE_SET_REG && cmd != PCITOOL_DEVICE_GET_REG) {
return (ENOTTY);
}
if (cmd == PCITOOL_DEVICE_SET_REG) {
write_flag = B_TRUE;
}
if (ddi_copyin(arg, &prg, sizeof (pcitool_reg_t), mode) !=
DDI_SUCCESS) {
return (EFAULT);
}
if (prg.barnum > PCITOOL_ROM) {
prg.status = PCITOOL_OUT_OF_RANGE;
rval = EINVAL;
goto copyout;
}
if (((prg.bus_no & (PCI_REG_BUS_M >> PCI_REG_BUS_SHIFT)) !=
prg.bus_no) ||
((prg.dev_no & (PCI_REG_DEV_M >> PCI_REG_DEV_SHIFT)) !=
prg.dev_no) ||
((prg.func_no & (PCI_REG_FUNC_M >> PCI_REG_FUNC_SHIFT)) !=
prg.func_no)) {
prg.status = PCITOOL_INVALID_ADDRESS;
rval = EINVAL;
goto copyout;
}
if (max_cfg_size == PCIE_CONF_HDR_SIZE)
cfgspace_io = B_FALSE;
if (prg.barnum == PCITOOL_CONFIG) {
if (prg.offset >= max_cfg_size) {
prg.status = PCITOOL_OUT_OF_RANGE;
rval = EINVAL;
goto copyout;
}
rval = pcitool_cfg_access(&prg, write_flag, cfgspace_io);
goto copyout;
}
pcitool_reg_t cfg;
boolean_t bridge;
bcopy(&prg, &cfg, sizeof (pcitool_reg_t));
cfg.acc_attr = PCITOOL_ACC_ATTR_SIZE_1 | PCITOOL_ACC_ATTR_ENDN_LTL;
cfg.offset = PCI_CONF_HEADER;
rval = pcitool_cfg_access(&cfg, B_FALSE, cfgspace_io);
if (rval != 0) {
prg.status = cfg.status;
goto copyout;
}
switch (cfg.data & PCI_HEADER_TYPE_M) {
case PCI_HEADER_ZERO:
bridge = B_FALSE;
break;
case PCI_HEADER_PPB:
bridge = B_TRUE;
break;
case PCI_HEADER_CARDBUS:
default:
prg.status = PCITOOL_UNKNOWN_HEADER_TYPE;
break;
}
if (bridge && prg.barnum > PCI_BCNF_BASE_NUM) {
prg.status = PCITOOL_OUT_OF_RANGE;
rval = EINVAL;
goto copyout;
}
boolean_t io_space = B_FALSE;
if (prg.barnum != PCITOOL_ROM) {
rval = pcitool_bar_find(prg.barnum - 1, bridge, cfgspace_io,
&cfg, &base_addr, &io_space);
if (rval != 0) {
prg.status = cfg.status;
goto copyout;
}
} else {
cfg.acc_attr = PCITOOL_ACC_ATTR_SIZE_4 |
PCITOOL_ACC_ATTR_ENDN_LTL;
cfg.offset = PCI_CONF_ROM;
rval = pcitool_cfg_access(&cfg, B_FALSE, cfgspace_io);
if (rval != 0) {
prg.status = cfg.status;
goto copyout;
}
if (write_flag) {
prg.status = PCITOOL_ROM_WRITE;
rval = EIO;
goto copyout;
}
if ((cfg.data & PCI_BASE_ROM_ENABLE) == 0) {
prg.status = PCITOOL_ROM_DISABLED;
rval = EIO;
goto copyout;
}
base_addr = cfg.data & PCI_BASE_ROM_ADDR_M;
}
pcitool_bar_walk_cb_t cb;
cb.pbwc_reg = &prg;
cb.pbwc_size = 0;
ndi_devi_enter(dip);
ddi_walk_devs(ddi_get_child(dip), pcitool_bar_walk_cb, &cb);
ndi_devi_exit(dip);
if (cb.pbwc_size == 0 || prg.offset >= cb.pbwc_size) {
prg.status = PCITOOL_INVALID_REGOFF;
rval = EIO;
goto copyout;
}
prg.phys_addr = base_addr + prg.offset;
if (io_space) {
rval = pcitool_io_access(&prg, write_flag);
} else {
size_t npages;
uint64_t va = pcitool_map(prg.phys_addr, size,
&npages);
if (va == 0) {
prg.status = PCITOOL_IO_ERROR;
rval = EIO;
goto copyout;
}
rval = pcitool_mem_access(&prg, va, write_flag);
pcitool_unmap(va, npages);
}
copyout:
prg.drvr_version = PCITOOL_VERSION;
if (ddi_copyout(&prg, arg, sizeof (pcitool_reg_t), mode) !=
DDI_SUCCESS) {
rval = EFAULT;
}
return (rval);
}