#if defined(DEBUG)
#define BUSRA_DEBUG
#endif
#include <sys/types.h>
#include <sys/systm.h>
#include <sys/ddi.h>
#include <sys/sunddi.h>
#include <sys/sunndi.h>
#include <sys/ddi_impldefs.h>
#include <sys/ndi_impldefs.h>
#include <sys/kmem.h>
#include <sys/pctypes.h>
#include <sys/modctl.h>
#include <sys/debug.h>
#include <sys/spl.h>
#include <sys/pci.h>
#include <sys/autoconf.h>
#if defined(BUSRA_DEBUG)
int busra_debug = 0;
#define DEBUGPRT \
if (busra_debug) cmn_err
#else
#define DEBUGPRT \
if (0) cmn_err
#endif
kmutex_t ra_lock;
struct ra_resource {
struct ra_resource *ra_next;
uint64_t ra_base;
uint64_t ra_len;
};
struct ra_dip_type {
struct ra_dip_type *ra_next;
struct ra_resource *ra_rangeset;
dev_info_t *ra_dip;
};
struct ra_type_map {
struct ra_type_map *ra_next;
struct ra_dip_type *ra_dip_list;
char *type;
};
static struct ra_type_map *ra_map_list_head = NULL;
extern struct mod_ops mod_miscops;
#ifdef BUSRA_DEBUG
void ra_dump_all(char *, dev_info_t *);
#endif
static struct ra_dip_type *find_dip_map_resources(dev_info_t *dip, char *type,
struct ra_dip_type ***backdip, struct ra_type_map ***backtype,
uint32_t flag);
static int isnot_pow2(uint64_t value);
static int claim_pci_busnum(dev_info_t *dip, void *arg);
static int ra_map_exist(dev_info_t *dip, char *type);
static int pci_get_available_prop(dev_info_t *dip, uint64_t base,
uint64_t len, char *busra_type);
static int pci_put_available_prop(dev_info_t *dip, uint64_t base,
uint64_t len, char *busra_type);
static uint32_t pci_type_ra2pci(char *type);
static boolean_t is_pcie_fabric(dev_info_t *dip);
#define PCI_ADDR_TYPE_MASK (PCI_REG_ADDR_M | PCI_REG_PF_M)
#define PCI_ADDR_TYPE_INVAL 0xffffffff
#define RA_INSERT(prev, el) \
el->ra_next = *prev; \
*prev = el;
#define RA_REMOVE(prev, el) \
*prev = el->ra_next;
static struct modlmisc modlmisc = {
&mod_miscops,
"Bus Resource Allocator (BUSRA)",
};
static struct modlinkage modlinkage = {
MODREV_1, (void *)&modlmisc, NULL
};
int
_init()
{
int ret;
mutex_init(&ra_lock, NULL, MUTEX_DRIVER,
(void *)(intptr_t)__ipltospl(SPL7 - 1));
if ((ret = mod_install(&modlinkage)) != 0) {
mutex_destroy(&ra_lock);
}
return (ret);
}
int
_fini()
{
int ret;
mutex_enter(&ra_lock);
if (ra_map_list_head != NULL) {
mutex_exit(&ra_lock);
return (EBUSY);
}
ret = mod_remove(&modlinkage);
mutex_exit(&ra_lock);
if (ret == 0)
mutex_destroy(&ra_lock);
return (ret);
}
int
_info(struct modinfo *modinfop)
{
return (mod_info(&modlinkage, modinfop));
}
int
ndi_ra_map_setup(dev_info_t *dip, char *type)
{
struct ra_type_map *typemapp;
struct ra_dip_type *dipmap;
struct ra_dip_type **backdip;
struct ra_type_map **backtype;
mutex_enter(&ra_lock);
dipmap = find_dip_map_resources(dip, type, &backdip, &backtype, 0);
if (dipmap == NULL) {
if (backtype == NULL) {
typemapp = (struct ra_type_map *)
kmem_zalloc(sizeof (*typemapp), KM_SLEEP);
typemapp->type = (char *)kmem_zalloc(strlen(type) + 1,
KM_SLEEP);
(void) strcpy(typemapp->type, type);
RA_INSERT(&ra_map_list_head, typemapp);
} else {
typemapp = *backtype;
}
if (backdip == NULL) {
dipmap = (struct ra_dip_type *)
kmem_zalloc(sizeof (*dipmap), KM_SLEEP);
dipmap->ra_dip = dip;
RA_INSERT(&typemapp->ra_dip_list, dipmap);
}
}
mutex_exit(&ra_lock);
return (NDI_SUCCESS);
}
int
ndi_ra_map_destroy(dev_info_t *dip, char *type)
{
struct ra_dip_type *dipmap;
struct ra_dip_type **backdip;
struct ra_type_map **backtype, *typemap;
struct ra_resource *range;
mutex_enter(&ra_lock);
dipmap = find_dip_map_resources(dip, type, &backdip, &backtype, 0);
if (dipmap == NULL) {
mutex_exit(&ra_lock);
return (NDI_FAILURE);
}
ASSERT((backdip != NULL) && (backtype != NULL));
while (dipmap->ra_rangeset != NULL) {
range = dipmap->ra_rangeset;
RA_REMOVE(&dipmap->ra_rangeset, range);
kmem_free((caddr_t)range, sizeof (*range));
}
RA_REMOVE(backdip, dipmap);
kmem_free((caddr_t)dipmap, sizeof (*dipmap));
if ((*backtype)->ra_dip_list == NULL) {
typemap = *backtype;
RA_REMOVE(backtype, (*backtype));
kmem_free((caddr_t)typemap->type, strlen(typemap->type) + 1);
kmem_free((caddr_t)typemap, sizeof (*typemap));
}
mutex_exit(&ra_lock);
return (NDI_SUCCESS);
}
static int
ra_map_exist(dev_info_t *dip, char *type)
{
struct ra_dip_type **backdip;
struct ra_type_map **backtype;
mutex_enter(&ra_lock);
if (find_dip_map_resources(dip, type, &backdip, &backtype, 0) == NULL) {
mutex_exit(&ra_lock);
return (NDI_FAILURE);
}
mutex_exit(&ra_lock);
return (NDI_SUCCESS);
}
static struct ra_dip_type *
find_dip_map_resources(dev_info_t *dip, char *type,
struct ra_dip_type ***backdip, struct ra_type_map ***backtype,
uint32_t flag)
{
struct ra_type_map **prevmap;
struct ra_dip_type *dipmap, **prevdip;
ASSERT(mutex_owned(&ra_lock));
prevdip = NULL;
dipmap = NULL;
prevmap = &ra_map_list_head;
while (*prevmap) {
if (strcmp((*prevmap)->type, type) == 0)
break;
prevmap = &(*prevmap)->ra_next;
}
if (*prevmap) {
for (; dip != NULL; dip = ddi_get_parent(dip)) {
prevdip = &(*prevmap)->ra_dip_list;
dipmap = *prevdip;
while (dipmap) {
if (dipmap->ra_dip == dip)
break;
prevdip = &dipmap->ra_next;
dipmap = dipmap->ra_next;
}
if (dipmap != NULL) {
break;
}
if (!(flag & NDI_RA_PASS)) {
break;
}
}
}
*backtype = (*prevmap == NULL) ? NULL: prevmap;
*backdip = (dipmap == NULL) ? NULL: prevdip;
return (dipmap);
}
int
ndi_ra_free(dev_info_t *dip, uint64_t base, uint64_t len, char *type,
uint32_t flag)
{
struct ra_dip_type *dipmap;
struct ra_resource *newmap, *overlapmap, *oldmap = NULL;
struct ra_resource *mapp, **backp;
uint64_t newend, mapend;
struct ra_dip_type **backdip;
struct ra_type_map **backtype;
if (len == 0) {
return (NDI_SUCCESS);
}
mutex_enter(&ra_lock);
if ((dipmap = find_dip_map_resources(dip, type, &backdip, &backtype,
flag)) == NULL) {
mutex_exit(&ra_lock);
return (NDI_FAILURE);
}
mapp = dipmap->ra_rangeset;
backp = &dipmap->ra_rangeset;
newend = base + len;
for (; mapp != NULL; backp = &(mapp->ra_next), mapp = mapp->ra_next) {
mapend = mapp->ra_base + mapp->ra_len;
if ((base <= mapp->ra_base && newend > mapp->ra_base) ||
(base > mapp->ra_base && base < mapend)) {
overlapmap = mapp;
goto overlap;
} else if ((base == mapend && mapp->ra_next) &&
(newend > mapp->ra_next->ra_base)) {
overlapmap = mapp->ra_next;
goto overlap;
}
if (newend == mapp->ra_base) {
mapp->ra_base = base;
mapp->ra_len += len;
break;
} else if (base == mapend) {
mapp->ra_len += len;
if (mapp->ra_next &&
(newend == mapp->ra_next->ra_base)) {
oldmap = mapp->ra_next;
mapp->ra_len += oldmap->ra_len;
RA_REMOVE(&mapp->ra_next, oldmap);
kmem_free((caddr_t)oldmap, sizeof (*oldmap));
}
break;
} else if (base < mapp->ra_base) {
newmap = (struct ra_resource *)
kmem_zalloc(sizeof (*newmap), KM_SLEEP);
newmap->ra_base = base;
newmap->ra_len = len;
RA_INSERT(backp, newmap);
break;
}
}
if (mapp == NULL) {
newmap = (struct ra_resource *)
kmem_zalloc(sizeof (*newmap), KM_SLEEP);
newmap->ra_base = base;
newmap->ra_len = len;
RA_INSERT(backp, newmap);
}
mutex_exit(&ra_lock);
(void) pci_put_available_prop(dipmap->ra_dip, base, len, type);
return (NDI_SUCCESS);
overlap:
cmn_err(CE_NOTE, "!ndi_ra_free: bad free, dip %p, resource type %s \n",
(void *)dip, type);
cmn_err(CE_NOTE, "!ndi_ra_free: freeing base 0x%" PRIx64 ", len 0x%"
PRIX64 " overlaps with existing resource base 0x%" PRIx64
", len 0x%" PRIx64 "\n", base, len, overlapmap->ra_base,
overlapmap->ra_len);
mutex_exit(&ra_lock);
return (NDI_FAILURE);
}
static int
isnot_pow2(uint64_t value)
{
uint32_t low;
uint32_t hi;
low = value & 0xffffffff;
hi = value >> 32;
if ((ddi_ffs(low) == ddi_fls(low)) &&
(ddi_ffs(hi) == ddi_fls(hi)))
return (0);
return (1);
}
static void
adjust_link(struct ra_resource **backp, struct ra_resource *mapp,
uint64_t base, uint64_t len)
{
struct ra_resource *newmap;
uint64_t newlen;
if (base != mapp->ra_base) {
newlen = base - mapp->ra_base;
if ((mapp->ra_len - newlen) == len) {
mapp->ra_len = newlen;
} else {
newmap = (struct ra_resource *)
kmem_zalloc(sizeof (*newmap), KM_SLEEP);
newmap->ra_base = base + len;
newmap->ra_len = mapp->ra_len - (len + newlen);
mapp->ra_len = newlen;
RA_INSERT(&(mapp->ra_next), newmap);
}
} else {
mapp->ra_base += len;
mapp->ra_len -= len;
if (mapp->ra_len == 0) {
RA_REMOVE(backp, mapp);
kmem_free((caddr_t)mapp, sizeof (*mapp));
}
}
}
int
ndi_ra_alloc(dev_info_t *dip, ndi_ra_request_t *req, uint64_t *retbasep,
uint64_t *retlenp, char *type, uint32_t flag)
{
struct ra_dip_type *dipmap;
struct ra_resource *mapp, **backp, **backlargestp;
uint64_t mask = 0;
uint64_t len, remlen, largestbase, largestlen;
uint64_t base, oldbase, lower, upper;
struct ra_dip_type **backdip;
struct ra_type_map **backtype;
int rval = NDI_FAILURE;
len = req->ra_len;
if (req->ra_flags & NDI_RA_ALIGN_SIZE) {
if (isnot_pow2(req->ra_len)) {
DEBUGPRT(CE_WARN, "ndi_ra_alloc: bad length(pow2) 0x%"
PRIx64, req->ra_len);
*retbasep = 0;
*retlenp = 0;
return (NDI_FAILURE);
}
}
mask = (req->ra_flags & NDI_RA_ALIGN_SIZE) ? (len - 1) :
req->ra_align_mask;
mutex_enter(&ra_lock);
dipmap = find_dip_map_resources(dip, type, &backdip, &backtype, flag);
if ((dipmap == NULL) || ((mapp = dipmap->ra_rangeset) == NULL)) {
mutex_exit(&ra_lock);
DEBUGPRT(CE_CONT, "ndi_ra_alloc no map found for this type\n");
return (NDI_FAILURE);
}
DEBUGPRT(CE_CONT, "ndi_ra_alloc: mapp = %p len=%" PRIx64 ", mask=%"
PRIx64 "\n", (void *)mapp, len, mask);
backp = &(dipmap->ra_rangeset);
backlargestp = NULL;
largestbase = 0;
largestlen = 0;
lower = 0;
upper = ~(uint64_t)0;
if (req->ra_flags & NDI_RA_ALLOC_BOUNDED) {
lower = req->ra_boundbase;
upper = req->ra_boundlen + lower;
if ((upper == 0) || (upper < req->ra_boundlen))
upper = ~(uint64_t)0;
DEBUGPRT(CE_CONT, "ndi_ra_alloc: ra_len = %" PRIx64 ", len = %"
PRIx64 " ra_base=%" PRIx64 ", mask=%" PRIx64
"\n", mapp->ra_len, len, mapp->ra_base, mask);
for (; mapp != NULL && (mapp->ra_base + mapp->ra_len) < lower;
backp = &(mapp->ra_next), mapp = mapp->ra_next) {
if (((mapp->ra_len + mapp->ra_base) == 0) ||
((mapp->ra_len + mapp->ra_base) < mapp->ra_len))
break;
DEBUGPRT(CE_CONT, " ra_len = %" PRIx64 ", ra_base=%"
PRIx64 "\n", mapp->ra_len, mapp->ra_base);
}
}
if (!(req->ra_flags & NDI_RA_ALLOC_SPECIFIED)) {
DEBUGPRT(CE_CONT, "ndi_ra_alloc(unspecified request)"
"lower=%" PRIx64 ", upper=%" PRIx64 "\n", lower, upper);
for (; mapp != NULL && mapp->ra_base <= upper;
backp = &(mapp->ra_next), mapp = mapp->ra_next) {
DEBUGPRT(CE_CONT, "ndi_ra_alloc: ra_len = %" PRIx64
", len = %" PRIx64 "", mapp->ra_len, len);
base = mapp->ra_base;
if (base < lower) {
base = lower;
DEBUGPRT(CE_CONT, "\tbase=%" PRIx64
", ra_base=%" PRIx64 ", mask=%" PRIx64,
base, mapp->ra_base, mask);
}
if ((base & mask) != 0) {
oldbase = base;
base = base & ~mask;
base += (mask + 1);
DEBUGPRT(CE_CONT, "\tnew base=%" PRIx64 "\n",
base);
if (base >= (oldbase + mapp->ra_len + 1)) {
continue;
}
}
if (req->ra_flags & NDI_RA_ALLOC_PARTIAL_OK) {
if ((upper - mapp->ra_base) < mapp->ra_len)
remlen = upper - base;
else
remlen = mapp->ra_len -
(base - mapp->ra_base);
if ((backlargestp == NULL) ||
(largestlen < remlen)) {
backlargestp = backp;
largestbase = base;
largestlen = remlen;
}
}
if (mapp->ra_len >= len) {
if ((len > (mapp->ra_len -
(base - mapp->ra_base))) ||
((len - 1 + base) > upper)) {
continue;
}
DEBUGPRT(CE_CONT, "\thave a fit\n");
adjust_link(backp, mapp, base, len);
rval = NDI_SUCCESS;
break;
}
}
} else {
base = req->ra_addr;
len = req->ra_len;
for (; mapp != NULL && mapp->ra_base <= upper;
backp = &(mapp->ra_next), mapp = mapp->ra_next) {
if (base >= mapp->ra_base &&
((base - mapp->ra_base) < mapp->ra_len)) {
if ((len > mapp->ra_len) ||
(base - mapp->ra_base >
mapp->ra_len - len)) {
if (req->ra_flags &
NDI_RA_ALLOC_PARTIAL_OK) {
if ((upper - mapp->ra_base)
< mapp->ra_len)
remlen = upper - base;
else
remlen =
mapp->ra_len -
(base -
mapp->ra_base);
}
backlargestp = backp;
largestbase = base;
largestlen = remlen;
base = 0;
} else {
adjust_link(backp, mapp, base, len);
rval = NDI_SUCCESS;
}
break;
}
}
}
if ((rval != NDI_SUCCESS) &&
(req->ra_flags & NDI_RA_ALLOC_PARTIAL_OK) &&
(backlargestp != NULL)) {
adjust_link(backlargestp, *backlargestp, largestbase,
largestlen);
base = largestbase;
len = largestlen;
rval = NDI_RA_PARTIAL_REQ;
}
mutex_exit(&ra_lock);
if (rval == NDI_FAILURE) {
*retbasep = 0;
*retlenp = 0;
} else {
*retbasep = base;
*retlenp = len;
}
if ((rval == NDI_SUCCESS) || (rval == NDI_RA_PARTIAL_REQ))
(void) pci_get_available_prop(dipmap->ra_dip,
*retbasep, *retlenp, type);
return (rval);
}
int
isa_resource_setup()
{
dev_info_t *used, *usedpdip;
struct iorange {
uint32_t base;
uint32_t len;
} *iorange;
struct memrange {
uint32_t base;
uint32_t len;
} *memrange;
uint32_t *irq;
int proplen;
int i, len;
int maxrange;
ndi_ra_request_t req;
uint64_t retbase;
uint64_t retlen;
used = ddi_find_devinfo("used-resources", -1, 0);
if (used == NULL) {
DEBUGPRT(CE_CONT,
"isa_resource_setup: used-resources not found");
return (NDI_FAILURE);
}
usedpdip = ddi_root_node();
DEBUGPRT(CE_CONT, "isa_resource_setup: used = %p usedpdip = %p\n",
(void *)used, (void *)usedpdip);
if (ndi_ra_map_setup(usedpdip, NDI_RA_TYPE_IO) == NDI_FAILURE) {
return (NDI_FAILURE);
}
(void) ndi_ra_free(usedpdip, 0, 0xffff + 1, NDI_RA_TYPE_IO, 0);
if (ddi_getlongprop(DDI_DEV_T_ANY, used, DDI_PROP_DONTPASS,
"io-space", (caddr_t)&iorange, &proplen) == DDI_SUCCESS) {
maxrange = proplen / sizeof (struct iorange);
for (i = 0; i < maxrange; i++) {
bzero((caddr_t)&req, sizeof (req));
req.ra_addr = (uint64_t)iorange[i].base;
req.ra_len = (uint64_t)iorange[i].len;
req.ra_flags = NDI_RA_ALLOC_SPECIFIED;
(void) ndi_ra_alloc(usedpdip, &req, &retbase, &retlen,
NDI_RA_TYPE_IO, 0);
}
kmem_free((caddr_t)iorange, proplen);
}
if (ndi_ra_map_setup(usedpdip, NDI_RA_TYPE_MEM) == NDI_FAILURE) {
return (NDI_FAILURE);
}
(void) ndi_ra_free(usedpdip, 0, ((uint64_t)((uint32_t)~0)) + 1,
NDI_RA_TYPE_MEM, 0);
if (ddi_getlongprop(DDI_DEV_T_ANY, used, DDI_PROP_DONTPASS,
"device-memory", (caddr_t)&memrange, &proplen) == DDI_SUCCESS) {
maxrange = proplen / sizeof (struct memrange);
for (i = 0; i < maxrange; i++) {
bzero((caddr_t)&req, sizeof (req));
req.ra_addr = (uint64_t)memrange[i].base;
req.ra_len = (uint64_t)memrange[i].len;
req.ra_flags = NDI_RA_ALLOC_SPECIFIED;
(void) ndi_ra_alloc(usedpdip, &req, &retbase, &retlen,
NDI_RA_TYPE_MEM, 0);
}
kmem_free((caddr_t)memrange, proplen);
}
if (ndi_ra_map_setup(usedpdip, NDI_RA_TYPE_INTR) == NDI_FAILURE) {
return (NDI_FAILURE);
}
(void) ndi_ra_free(usedpdip, 0, 16, NDI_RA_TYPE_INTR, 0);
#if defined(__x86)
bzero(&req, sizeof (req));
req.ra_addr = 2;
req.ra_len = 1;
req.ra_flags = NDI_RA_ALLOC_SPECIFIED;
(void) ndi_ra_alloc(usedpdip, &req, &retbase, &retlen,
NDI_RA_TYPE_INTR, 0);
#endif
if (ddi_getlongprop(DDI_DEV_T_ANY, used, DDI_PROP_DONTPASS,
"interrupts", (caddr_t)&irq, &proplen) == DDI_SUCCESS) {
len = (proplen / sizeof (uint32_t));
for (i = 0; i < len; i++) {
bzero((caddr_t)&req, sizeof (req));
req.ra_addr = (uint64_t)irq[i];
req.ra_len = 1;
req.ra_flags = NDI_RA_ALLOC_SPECIFIED;
(void) ndi_ra_alloc(usedpdip, &req, &retbase, &retlen,
NDI_RA_TYPE_INTR, 0);
}
kmem_free((caddr_t)irq, proplen);
}
#ifdef BUSRA_DEBUG
if (busra_debug) {
(void) ra_dump_all(NULL, usedpdip);
}
#endif
return (NDI_SUCCESS);
}
#ifdef BUSRA_DEBUG
void
ra_dump_all(char *type, dev_info_t *dip)
{
struct ra_type_map *typemap;
struct ra_dip_type *dipmap;
struct ra_resource *res;
typemap = (struct ra_type_map *)ra_map_list_head;
for (; typemap != NULL; typemap = typemap->ra_next) {
if (type != NULL) {
if (strcmp(typemap->type, type) != 0)
continue;
}
cmn_err(CE_CONT, "type is %s\n", typemap->type);
for (dipmap = typemap->ra_dip_list; dipmap != NULL;
dipmap = dipmap->ra_next) {
if (dip != NULL) {
if ((dipmap->ra_dip) != dip)
continue;
}
cmn_err(CE_CONT, " dip is %p\n",
(void *)dipmap->ra_dip);
for (res = dipmap->ra_rangeset; res != NULL;
res = res->ra_next) {
cmn_err(CE_CONT, "\t range is %" PRIx64
" %" PRIx64 "\n", res->ra_base,
res->ra_len);
}
if (dip != NULL)
break;
}
if (type != NULL)
break;
}
}
#endif
struct bus_range {
uint32_t lo;
uint32_t hi;
} pci_bus_range;
struct busnum_ctrl {
int rv;
dev_info_t *dip;
struct bus_range *range;
};
int
pci_resource_setup(dev_info_t *dip)
{
pci_regspec_t *regs;
int rlen, rcount, i;
char bus_type[16] = "(unknown)";
int len;
struct busnum_ctrl ctrl;
int rval = NDI_SUCCESS;
len = sizeof (bus_type);
if (ddi_prop_op(DDI_DEV_T_ANY, dip, PROP_LEN_AND_VAL_BUF,
DDI_PROP_CANSLEEP | DDI_PROP_DONTPASS, "device_type",
(caddr_t)&bus_type, &len) != DDI_SUCCESS)
return (NDI_FAILURE);
if ((strcmp(bus_type, "pci") != 0) && (strcmp(bus_type, "pciex") != 0))
return (NDI_FAILURE);
{
if (ra_map_exist(dip, NDI_RA_TYPE_MEM) == NDI_SUCCESS) {
return (NDI_FAILURE);
}
}
if (ndi_ra_map_setup(dip, NDI_RA_TYPE_MEM) == NDI_FAILURE) {
return (NDI_FAILURE);
}
if (ndi_ra_map_setup(dip, NDI_RA_TYPE_IO) == NDI_FAILURE) {
return (NDI_FAILURE);
}
if (ndi_ra_map_setup(dip, NDI_RA_TYPE_PCI_BUSNUM) == NDI_FAILURE) {
return (NDI_FAILURE);
}
if (ndi_ra_map_setup(dip, NDI_RA_TYPE_PCI_PREFETCH_MEM) ==
NDI_FAILURE) {
return (NDI_FAILURE);
}
if (ddi_getlongprop(DDI_DEV_T_ANY, dip, DDI_PROP_DONTPASS,
"available", (caddr_t)®s, &rlen) == DDI_SUCCESS) {
(void) ndi_prop_remove(DDI_DEV_T_NONE, dip, "available");
rcount = rlen / sizeof (pci_regspec_t);
for (i = 0; i < rcount; i++) {
switch (PCI_REG_ADDR_G(regs[i].pci_phys_hi)) {
case PCI_REG_ADDR_G(PCI_ADDR_MEM32):
(void) ndi_ra_free(dip,
(uint64_t)regs[i].pci_phys_low,
(uint64_t)regs[i].pci_size_low,
(regs[i].pci_phys_hi & PCI_REG_PF_M) ?
NDI_RA_TYPE_PCI_PREFETCH_MEM :
NDI_RA_TYPE_MEM,
0);
break;
case PCI_REG_ADDR_G(PCI_ADDR_MEM64):
(void) ndi_ra_free(dip,
((uint64_t)(regs[i].pci_phys_mid) << 32) |
((uint64_t)(regs[i].pci_phys_low)),
((uint64_t)(regs[i].pci_size_hi) << 32) |
((uint64_t)(regs[i].pci_size_low)),
(regs[i].pci_phys_hi & PCI_REG_PF_M) ?
NDI_RA_TYPE_PCI_PREFETCH_MEM :
NDI_RA_TYPE_MEM,
0);
break;
case PCI_REG_ADDR_G(PCI_ADDR_IO):
(void) ndi_ra_free(dip,
(uint64_t)regs[i].pci_phys_low,
(uint64_t)regs[i].pci_size_low,
NDI_RA_TYPE_IO,
0);
break;
case PCI_REG_ADDR_G(PCI_ADDR_CONFIG):
break;
default:
cmn_err(CE_WARN,
"pci_resource_setup: bad addr type: %x\n",
PCI_REG_ADDR_G(regs[i].pci_phys_hi));
break;
}
}
kmem_free(regs, rlen);
}
len = sizeof (struct bus_range);
if (ddi_getlongprop_buf(DDI_DEV_T_ANY, dip, DDI_PROP_DONTPASS,
"available-bus-range", (caddr_t)&pci_bus_range, &len) ==
DDI_SUCCESS) {
(void) ndi_ra_free(dip, (uint64_t)pci_bus_range.lo,
(uint64_t)pci_bus_range.hi - (uint64_t)pci_bus_range.lo +
1, NDI_RA_TYPE_PCI_BUSNUM, 0);
} else {
len = sizeof (struct bus_range);
if (ddi_getlongprop_buf(DDI_DEV_T_ANY, dip,
DDI_PROP_DONTPASS, "bus-range", (caddr_t)&pci_bus_range,
&len) == DDI_SUCCESS) {
if (pci_bus_range.lo != pci_bus_range.hi) {
(void) ndi_ra_free(dip,
(uint64_t)pci_bus_range.lo + 1,
(uint64_t)pci_bus_range.hi -
(uint64_t)pci_bus_range.lo,
NDI_RA_TYPE_PCI_BUSNUM, 0);
ctrl.rv = DDI_SUCCESS;
ctrl.dip = dip;
ctrl.range = &pci_bus_range;
ndi_devi_enter(dip);
ddi_walk_devs(ddi_get_child(dip),
claim_pci_busnum, (void *)&ctrl);
ndi_devi_exit(dip);
if (ctrl.rv != DDI_SUCCESS) {
(void) ndi_ra_map_destroy(dip,
NDI_RA_TYPE_PCI_BUSNUM);
rval = NDI_FAILURE;
}
}
}
}
#ifdef BUSRA_DEBUG
if (busra_debug) {
(void) ra_dump_all(NULL, dip);
}
#endif
return (rval);
}
static int
claim_pci_busnum(dev_info_t *dip, void *arg)
{
struct bus_range pci_bus_range;
struct busnum_ctrl *ctrl;
ndi_ra_request_t req;
char bus_type[16] = "(unknown)";
int len;
uint64_t base;
uint64_t retlen;
ctrl = (struct busnum_ctrl *)arg;
len = sizeof (bus_type);
if (ddi_prop_op(DDI_DEV_T_ANY, dip, PROP_LEN_AND_VAL_BUF,
DDI_PROP_CANSLEEP | DDI_PROP_DONTPASS, "device_type",
(caddr_t)&bus_type, &len) != DDI_SUCCESS)
return (DDI_WALK_PRUNECHILD);
if ((strcmp(bus_type, "pci") != 0) && (strcmp(bus_type, "pciex") != 0))
return (DDI_WALK_PRUNECHILD);
len = sizeof (struct bus_range);
if (ddi_getlongprop_buf(DDI_DEV_T_ANY, dip, DDI_PROP_DONTPASS,
"bus-range", (caddr_t)&pci_bus_range, &len) == DDI_SUCCESS) {
if ((pci_bus_range.lo >= ctrl->range->lo) &&
(pci_bus_range.hi <= ctrl->range->hi)) {
bzero((caddr_t)&req, sizeof (req));
req.ra_addr = (uint64_t)pci_bus_range.lo;
req.ra_flags |= NDI_RA_ALLOC_SPECIFIED;
req.ra_len = (uint64_t)pci_bus_range.hi -
(uint64_t)pci_bus_range.lo + 1;
if (ndi_ra_alloc(ctrl->dip, &req, &base, &retlen,
NDI_RA_TYPE_PCI_BUSNUM, 0) == NDI_SUCCESS)
return (DDI_WALK_PRUNECHILD);
}
}
ctrl->rv = DDI_FAILURE;
return (DDI_WALK_TERMINATE);
}
void
pci_resource_destroy(dev_info_t *dip)
{
(void) ndi_ra_map_destroy(dip, NDI_RA_TYPE_IO);
(void) ndi_ra_map_destroy(dip, NDI_RA_TYPE_MEM);
(void) ndi_ra_map_destroy(dip, NDI_RA_TYPE_PCI_BUSNUM);
(void) ndi_ra_map_destroy(dip, NDI_RA_TYPE_PCI_PREFETCH_MEM);
}
int
pci_resource_setup_avail(dev_info_t *dip, pci_regspec_t *avail_p, int entries)
{
int i;
if (ndi_ra_map_setup(dip, NDI_RA_TYPE_MEM) == NDI_FAILURE)
return (NDI_FAILURE);
if (ndi_ra_map_setup(dip, NDI_RA_TYPE_IO) == NDI_FAILURE)
return (NDI_FAILURE);
if (ndi_ra_map_setup(dip, NDI_RA_TYPE_PCI_PREFETCH_MEM) == NDI_FAILURE)
return (NDI_FAILURE);
for (i = 0; i < entries; i++, avail_p++) {
if (avail_p->pci_phys_hi == -1u)
goto err;
switch (PCI_REG_ADDR_G(avail_p->pci_phys_hi)) {
case PCI_REG_ADDR_G(PCI_ADDR_MEM32): {
(void) ndi_ra_free(dip, (uint64_t)avail_p->pci_phys_low,
(uint64_t)avail_p->pci_size_low,
(avail_p->pci_phys_hi & PCI_REG_PF_M) ?
NDI_RA_TYPE_PCI_PREFETCH_MEM : NDI_RA_TYPE_MEM,
0);
}
break;
case PCI_REG_ADDR_G(PCI_ADDR_IO):
(void) ndi_ra_free(dip, (uint64_t)avail_p->pci_phys_low,
(uint64_t)avail_p->pci_size_low, NDI_RA_TYPE_IO, 0);
break;
default:
goto err;
}
}
#ifdef BUSRA_DEBUG
if (busra_debug) {
(void) ra_dump_all(NULL, dip);
}
#endif
return (NDI_SUCCESS);
err:
cmn_err(CE_WARN, "pci_resource_setup_avail: bad entry[%d]=%x\n",
i, avail_p->pci_phys_hi);
return (NDI_FAILURE);
}
static boolean_t
is_pcie_fabric(dev_info_t *dip)
{
dev_info_t *root = ddi_root_node();
dev_info_t *pdip;
boolean_t found = B_FALSE;
char *bus;
if (ddi_prop_lookup_string(DDI_DEV_T_ANY, dip,
DDI_PROP_DONTPASS, "device_type", &bus) !=
DDI_PROP_SUCCESS) {
DEBUGPRT(CE_WARN, "is_pcie_fabric: cannot find "
"\"device_type\" property for dip %p\n", (void *)dip);
return (B_FALSE);
}
if (strcmp(bus, "pciex") == 0) {
ddi_prop_free(bus);
return (B_TRUE);
} else if (strcmp(bus, "pci") == 0) {
ddi_prop_free(bus);
} else {
ddi_prop_free(bus);
return (B_FALSE);
}
for (pdip = ddi_get_parent(dip); pdip && (pdip != root) &&
!found; pdip = ddi_get_parent(pdip)) {
if (ddi_prop_lookup_string(DDI_DEV_T_ANY, pdip,
DDI_PROP_DONTPASS, "device_type", &bus) !=
DDI_PROP_SUCCESS)
break;
if (strcmp(bus, "pciex") == 0)
found = B_TRUE;
ddi_prop_free(bus);
}
return (found);
}
static int
pci_get_available_prop(dev_info_t *dip, uint64_t base, uint64_t len,
char *busra_type)
{
pci_regspec_t *regs, *newregs;
uint_t status;
int rlen, rcount;
int i, j, k;
uint64_t dlen;
boolean_t found = B_FALSE;
uint32_t type;
if ((type = pci_type_ra2pci(busra_type)) == PCI_ADDR_TYPE_INVAL)
return (DDI_SUCCESS);
if (!is_pcie_fabric(dip))
return (DDI_SUCCESS);
status = ddi_getlongprop(DDI_DEV_T_ANY, dip,
DDI_PROP_DONTPASS | DDI_PROP_NOTPROM,
"available", (caddr_t)®s, &rlen);
ASSERT(status == DDI_SUCCESS);
if (status != DDI_SUCCESS)
return (status);
newregs = kmem_alloc(rlen + sizeof (pci_regspec_t), KM_SLEEP);
rcount = rlen / sizeof (pci_regspec_t);
for (i = 0, j = 0; i < rcount; i++) {
if (type == (regs[i].pci_phys_hi & PCI_ADDR_TYPE_MASK)) {
uint64_t range_base, range_len;
range_base = ((uint64_t)(regs[i].pci_phys_mid) << 32) |
((uint64_t)(regs[i].pci_phys_low));
range_len = ((uint64_t)(regs[i].pci_size_hi) << 32) |
((uint64_t)(regs[i].pci_size_low));
if ((base < range_base) ||
(base + len > range_base + range_len)) {
goto copy_entry;
}
found = B_TRUE;
dlen = base - range_base;
if (dlen != 0) {
newregs[j].pci_phys_hi = regs[i].pci_phys_hi;
newregs[j].pci_phys_mid =
(uint32_t)(range_base >> 32);
newregs[j].pci_phys_low =
(uint32_t)(range_base);
newregs[j].pci_size_hi = (uint32_t)(dlen >> 32);
newregs[j].pci_size_low = (uint32_t)dlen;
j++;
}
dlen = (range_base + range_len) - (base + len);
if (dlen != 0) {
newregs[j].pci_phys_hi = regs[i].pci_phys_hi;
newregs[j].pci_phys_mid =
(uint32_t)((base + len)>> 32);
newregs[j].pci_phys_low =
(uint32_t)(base + len);
newregs[j].pci_size_hi = (uint32_t)(dlen >> 32);
newregs[j].pci_size_low = (uint32_t)dlen;
j++;
}
for (k = i + 1; k < rcount; k++) {
newregs[j] = regs[k];
j++;
}
goto done;
} else {
copy_entry:
newregs[j] = regs[i];
j++;
}
}
done:
ASSERT(found == B_TRUE);
if (!found) {
cmn_err(CE_WARN, "pci_get_available_prop: failed to remove "
"resource from dip %p : base 0x%" PRIx64 ", len 0x%" PRIX64
", type 0x%x\n", (void *)dip, base, len, type);
kmem_free(newregs, rlen + sizeof (pci_regspec_t));
kmem_free(regs, rlen);
return (DDI_FAILURE);
}
if (j == 0) {
(void) ndi_prop_remove(DDI_DEV_T_NONE, dip, "available");
} else {
(void) ndi_prop_update_int_array(DDI_DEV_T_NONE, dip,
"available", (int *)newregs,
(j * sizeof (pci_regspec_t)) / sizeof (int));
}
kmem_free(newregs, rlen + sizeof (pci_regspec_t));
kmem_free(regs, rlen);
return (DDI_SUCCESS);
}
static int
pci_put_available_prop(dev_info_t *dip, uint64_t base, uint64_t len,
char *busra_type)
{
pci_regspec_t *regs, *newregs;
uint_t status;
int rlen, rcount;
int i, j, k;
int matched = 0;
uint64_t orig_base = base;
uint64_t orig_len = len;
uint32_t type;
if ((type = pci_type_ra2pci(busra_type)) == PCI_ADDR_TYPE_INVAL)
return (DDI_SUCCESS);
if (!is_pcie_fabric(dip))
return (DDI_SUCCESS);
status = ddi_getlongprop(DDI_DEV_T_ANY, dip,
DDI_PROP_DONTPASS | DDI_PROP_NOTPROM,
"available", (caddr_t)®s, &rlen);
switch (status) {
case DDI_PROP_NOT_FOUND:
goto not_found;
case DDI_PROP_SUCCESS:
break;
default:
return (status);
}
newregs = kmem_alloc(rlen + sizeof (pci_regspec_t), KM_SLEEP);
rcount = rlen / sizeof (pci_regspec_t);
for (i = 0, j = 0; i < rcount; i++) {
if (type == (regs[i].pci_phys_hi & PCI_ADDR_TYPE_MASK)) {
uint64_t range_base, range_len;
range_base = ((uint64_t)(regs[i].pci_phys_mid) << 32) |
((uint64_t)(regs[i].pci_phys_low));
range_len = ((uint64_t)(regs[i].pci_size_hi) << 32) |
((uint64_t)(regs[i].pci_size_low));
if ((base + len < range_base) ||
(base > range_base + range_len)) {
goto copy_entry;
}
#if 0
ASSERT((base + len == range_base) ||
(base == range_base + range_len));
#endif
if ((base + len != range_base) &&
(base != range_base + range_len)) {
cmn_err(CE_WARN, "pci_put_available_prop: "
"failed to add resource to dip %p : "
"base 0x%" PRIx64 ", len 0x%" PRIx64 " "
"overlaps with existing resource "
"base 0x%" PRIx64 ", len 0x%" PRIx64 "\n",
(void *)dip, orig_base, orig_len,
range_base, range_len);
goto failure;
}
ASSERT(matched < 2);
if (!(matched < 2)) {
cmn_err(CE_WARN, "pci_put_available_prop: "
"failed to add resource to dip %p : "
"base 0x%" PRIx64 ", len 0x%" PRIx64 " "
"found overlaps in existing resources\n",
(void *)dip, orig_base, orig_len);
goto failure;
}
len += range_len;
if (base == range_base + range_len)
base = range_base;
if (matched == 0) {
newregs[j].pci_phys_hi = regs[i].pci_phys_hi;
newregs[j].pci_phys_mid =
(uint32_t)(base >> 32);
newregs[j].pci_phys_low = (uint32_t)(base);
newregs[j].pci_size_hi = (uint32_t)(len >> 32);
newregs[j].pci_size_low = (uint32_t)len;
matched = 1;
k = j;
j++;
} else {
newregs[k].pci_phys_hi = regs[i].pci_phys_hi;
newregs[k].pci_phys_mid =
(uint32_t)(base >> 32);
newregs[k].pci_phys_low = (uint32_t)(base);
newregs[k].pci_size_hi = (uint32_t)(len >> 32);
newregs[k].pci_size_low = (uint32_t)len;
matched = 2;
}
} else {
copy_entry:
newregs[j] = regs[i];
j++;
}
}
if (matched == 0) {
ASSERT(j == rcount);
newregs[j].pci_phys_hi = type;
newregs[j].pci_phys_hi |= PCI_REG_REL_M;
newregs[j].pci_phys_mid = (uint32_t)(base >> 32);
newregs[j].pci_phys_low = (uint32_t)base;
newregs[j].pci_size_hi = (uint32_t)(len >> 32);
newregs[j].pci_size_low = (uint32_t)len;
j++;
}
(void) ndi_prop_update_int_array(DDI_DEV_T_NONE, dip,
"available", (int *)newregs,
(j * sizeof (pci_regspec_t)) / sizeof (int));
kmem_free(newregs, rlen + sizeof (pci_regspec_t));
kmem_free(regs, rlen);
return (DDI_SUCCESS);
not_found:
newregs = kmem_alloc(sizeof (pci_regspec_t), KM_SLEEP);
newregs[0].pci_phys_hi = type;
newregs[0].pci_phys_hi |= PCI_REG_REL_M;
newregs[0].pci_phys_mid = (uint32_t)(base >> 32);
newregs[0].pci_phys_low = (uint32_t)base;
newregs[0].pci_size_hi = (uint32_t)(len >> 32);
newregs[0].pci_size_low = (uint32_t)len;
(void) ndi_prop_update_int_array(DDI_DEV_T_NONE, dip,
"available", (int *)newregs,
sizeof (pci_regspec_t) / sizeof (int));
kmem_free(newregs, sizeof (pci_regspec_t));
return (DDI_SUCCESS);
failure:
kmem_free(newregs, rlen + sizeof (pci_regspec_t));
kmem_free(regs, rlen);
return (DDI_FAILURE);
}
static uint32_t
pci_type_ra2pci(char *type)
{
uint32_t pci_type = PCI_ADDR_TYPE_INVAL;
if (strcmp(type, NDI_RA_TYPE_IO) == 0) {
pci_type = PCI_ADDR_IO;
} else if (strcmp(type, NDI_RA_TYPE_MEM) == 0) {
pci_type = PCI_ADDR_MEM32;
} else if (strcmp(type, NDI_RA_TYPE_PCI_PREFETCH_MEM) == 0) {
pci_type = PCI_ADDR_MEM32;
pci_type |= PCI_REG_PF_M;
}
return (pci_type);
}