#include <sys/types.h>
#include <sys/memlist.h>
#include <sys/pci.h>
#include <sys/pci_impl.h>
#include <sys/pci_cfgspace_impl.h>
#include <sys/sunndi.h>
#include <sys/systm.h>
#include <sys/cmn_err.h>
#include <sys/acpi/acpi.h>
#include <sys/acpica.h>
#include <sys/plat/pci_prd.h>
#include "mps_table.h"
#include "pcihrt.h"
extern int pci_bios_maxbus;
int pci_prd_debug = 0;
#define dprintf if (pci_prd_debug) printf
#define dcmn_err if (pci_prd_debug != 0) cmn_err
static int tbl_init = 0;
static uchar_t *mps_extp = NULL;
static uchar_t *mps_ext_endp = NULL;
static struct php_entry *hrt_hpep;
static uint_t hrt_entry_cnt = 0;
static int acpi_cb_cnt = 0;
static pci_prd_upcalls_t *prd_upcalls;
static void mps_probe(void);
static void acpi_pci_probe(void);
static int mps_find_bus_res(uint32_t, pci_prd_rsrc_t, struct memlist **);
static void hrt_probe(void);
static int hrt_find_bus_res(uint32_t, pci_prd_rsrc_t, struct memlist **);
static int acpi_find_bus_res(uint32_t, pci_prd_rsrc_t, struct memlist **);
static uchar_t *find_sig(uchar_t *cp, int len, char *sig);
static int checksum(unsigned char *cp, int len);
static ACPI_STATUS acpi_wr_cb(ACPI_RESOURCE *rp, void *context);
static void acpi_trim_bus_ranges(void);
volatile int acpi_resource_discovery = -1;
struct memlist *acpi_io_res[PCI_MAX_BUS_NUM];
struct memlist *acpi_mem_res[PCI_MAX_BUS_NUM];
struct memlist *acpi_pmem_res[PCI_MAX_BUS_NUM];
struct memlist *acpi_bus_res[PCI_MAX_BUS_NUM];
static boolean_t pci_prd_have_bios = B_TRUE;
extern int pci_bios_maxbus;
static void
acpi_pci_probe(void)
{
ACPI_HANDLE ah;
int bus;
if (acpi_resource_discovery == 0)
return;
for (bus = 0; bus <= pci_bios_maxbus; bus++) {
dev_info_t *dip;
dip = prd_upcalls->pru_bus2dip_f(bus);
if (dip == NULL ||
(ACPI_FAILURE(acpica_get_handle(dip, &ah))))
continue;
(void) AcpiWalkResources(ah, "_CRS", acpi_wr_cb,
(void *)(uintptr_t)bus);
}
if (acpi_cb_cnt > 0) {
acpi_resource_discovery = 1;
acpi_trim_bus_ranges();
}
}
static void
acpi_trim_bus_ranges(void)
{
struct memlist *ranges, *current;
int bus;
ranges = NULL;
for (bus = 0; bus < PCI_MAX_BUS_NUM; bus++) {
struct memlist *prev, *orig, *new;
if ((orig = acpi_bus_res[bus]) == NULL)
continue;
new = pci_memlist_alloc();
new->ml_address = orig->ml_address;
new->ml_size = orig->ml_size;
new->ml_prev = orig;
for (current = ranges, prev = NULL; current != NULL;
prev = current, current = current->ml_next)
if (new->ml_address < current->ml_address)
break;
if (prev == NULL) {
new->ml_next = ranges;
ranges = new;
} else {
new->ml_next = current;
prev->ml_next = new;
}
}
current = ranges;
while (current != NULL) {
struct memlist *next = current->ml_next;
if (next == NULL)
break;
if ((current->ml_address + current->ml_size) > next->ml_address)
current->ml_prev->ml_size =
next->ml_address - current->ml_address;
current = next;
}
pci_memlist_free_all(&ranges);
}
static int
acpi_find_bus_res(uint32_t bus, pci_prd_rsrc_t type, struct memlist **res)
{
ASSERT3U(bus, <, PCI_MAX_BUS_NUM);
switch (type) {
case PCI_PRD_R_IO:
*res = acpi_io_res[bus];
break;
case PCI_PRD_R_MMIO:
*res = acpi_mem_res[bus];
break;
case PCI_PRD_R_PREFETCH:
*res = acpi_pmem_res[bus];
break;
case PCI_PRD_R_BUS:
*res = acpi_bus_res[bus];
break;
default:
*res = NULL;
break;
}
return (pci_memlist_count(*res));
}
static struct memlist **
rlistpp(UINT8 t, UINT8 caching, int bus)
{
switch (t) {
case ACPI_MEMORY_RANGE:
if (caching == ACPI_PREFETCHABLE_MEMORY)
return (&acpi_pmem_res[bus]);
else
return (&acpi_mem_res[bus]);
break;
case ACPI_IO_RANGE:
return (&acpi_io_res[bus]);
break;
case ACPI_BUS_NUMBER_RANGE:
return (&acpi_bus_res[bus]);
break;
}
return (NULL);
}
static void
acpi_dbg(uint_t bus, uint64_t addr, uint64_t len, uint8_t caching, uint8_t type,
char *tag)
{
char *s;
switch (type) {
case ACPI_MEMORY_RANGE:
s = "MEM";
break;
case ACPI_IO_RANGE:
s = "IO";
break;
case ACPI_BUS_NUMBER_RANGE:
s = "BUS";
break;
default:
s = "???";
break;
}
dprintf("ACPI: bus %x %s/%s %lx/%lx (Caching: %x)\n", bus,
tag, s, addr, len, caching);
}
static ACPI_STATUS
acpi_wr_cb(ACPI_RESOURCE *rp, void *context)
{
int bus = (intptr_t)context;
if (rp->Data.Address.ProducerConsumer == 1)
return (AE_OK);
switch (rp->Type) {
case ACPI_RESOURCE_TYPE_IRQ:
dprintf("%s\n", "IRQ");
break;
case ACPI_RESOURCE_TYPE_DMA:
dprintf("%s\n", "DMA");
break;
case ACPI_RESOURCE_TYPE_START_DEPENDENT:
dprintf("%s\n", "START_DEPENDENT");
break;
case ACPI_RESOURCE_TYPE_END_DEPENDENT:
dprintf("%s\n", "END_DEPENDENT");
break;
case ACPI_RESOURCE_TYPE_IO:
if (rp->Data.Io.AddressLength == 0)
break;
acpi_cb_cnt++;
pci_memlist_insert(&acpi_io_res[bus], rp->Data.Io.Minimum,
rp->Data.Io.AddressLength);
if (pci_prd_debug != 0) {
acpi_dbg(bus, rp->Data.Io.Minimum,
rp->Data.Io.AddressLength, 0, ACPI_IO_RANGE, "IO");
}
break;
case ACPI_RESOURCE_TYPE_FIXED_IO:
dprintf("%s\n", "FIXED_IO");
break;
case ACPI_RESOURCE_TYPE_VENDOR:
dprintf("%s\n", "VENDOR");
break;
case ACPI_RESOURCE_TYPE_END_TAG:
dprintf("%s\n", "END_TAG");
break;
case ACPI_RESOURCE_TYPE_MEMORY24:
dprintf("%s\n", "MEMORY24");
break;
case ACPI_RESOURCE_TYPE_MEMORY32:
dprintf("%s\n", "MEMORY32");
break;
case ACPI_RESOURCE_TYPE_FIXED_MEMORY32:
dprintf("%s\n", "FIXED_MEMORY32");
break;
case ACPI_RESOURCE_TYPE_ADDRESS16:
if (rp->Data.Address16.Address.AddressLength == 0)
break;
acpi_cb_cnt++;
pci_memlist_insert(rlistpp(rp->Data.Address16.ResourceType,
rp->Data.Address.Info.Mem.Caching, bus),
rp->Data.Address16.Address.Minimum,
rp->Data.Address16.Address.AddressLength);
if (pci_prd_debug != 0) {
acpi_dbg(bus,
rp->Data.Address16.Address.Minimum,
rp->Data.Address16.Address.AddressLength,
rp->Data.Address.Info.Mem.Caching,
rp->Data.Address16.ResourceType, "ADDRESS16");
}
break;
case ACPI_RESOURCE_TYPE_ADDRESS32:
if (rp->Data.Address32.Address.AddressLength == 0)
break;
acpi_cb_cnt++;
pci_memlist_insert(rlistpp(rp->Data.Address32.ResourceType,
rp->Data.Address.Info.Mem.Caching, bus),
rp->Data.Address32.Address.Minimum,
rp->Data.Address32.Address.AddressLength);
if (pci_prd_debug != 0) {
acpi_dbg(bus,
rp->Data.Address32.Address.Minimum,
rp->Data.Address32.Address.AddressLength,
rp->Data.Address.Info.Mem.Caching,
rp->Data.Address32.ResourceType, "ADDRESS32");
}
break;
case ACPI_RESOURCE_TYPE_ADDRESS64:
if (rp->Data.Address64.Address.AddressLength == 0)
break;
acpi_cb_cnt++;
pci_memlist_insert(rlistpp(rp->Data.Address64.ResourceType,
rp->Data.Address.Info.Mem.Caching, bus),
rp->Data.Address64.Address.Minimum,
rp->Data.Address64.Address.AddressLength);
if (pci_prd_debug != 0) {
acpi_dbg(bus,
rp->Data.Address64.Address.Minimum,
rp->Data.Address64.Address.AddressLength,
rp->Data.Address.Info.Mem.Caching,
rp->Data.Address64.ResourceType, "ADDRESS64");
}
break;
case ACPI_RESOURCE_TYPE_EXTENDED_ADDRESS64:
if (rp->Data.ExtAddress64.Address.AddressLength == 0)
break;
acpi_cb_cnt++;
pci_memlist_insert(rlistpp(rp->Data.ExtAddress64.ResourceType,
rp->Data.Address.Info.Mem.Caching, bus),
rp->Data.ExtAddress64.Address.Minimum,
rp->Data.ExtAddress64.Address.AddressLength);
if (pci_prd_debug != 0) {
acpi_dbg(bus,
rp->Data.ExtAddress64.Address.Minimum,
rp->Data.ExtAddress64.Address.AddressLength,
rp->Data.Address.Info.Mem.Caching,
rp->Data.ExtAddress64.ResourceType, "EXTADDRESS64");
}
break;
case ACPI_RESOURCE_TYPE_EXTENDED_IRQ:
dprintf("%s\n", "EXTENDED_IRQ");
break;
case ACPI_RESOURCE_TYPE_GENERIC_REGISTER:
dprintf("%s\n", "GENERIC_REGISTER");
break;
}
return (AE_OK);
}
static void
mps_probe(void)
{
uchar_t *extp;
struct mps_fps_hdr *fpp = NULL;
struct mps_ct_hdr *ctp;
uintptr_t ebda_start, base_end;
ushort_t ebda_seg, base_size, ext_len, base_len, base_end_seg;
base_size = *((ushort_t *)(0x413));
ebda_seg = *((ushort_t *)(0x40e));
ebda_start = ((uint32_t)ebda_seg) << 4;
if (ebda_seg != 0) {
fpp = (struct mps_fps_hdr *)find_sig(
(uchar_t *)ebda_start, 1024, "_MP_");
}
if (fpp == NULL) {
base_end_seg = (base_size > 512) ? 0x9FC0 : 0x7FC0;
if (base_end_seg != ebda_seg) {
base_end = ((uintptr_t)base_end_seg) << 4;
fpp = (struct mps_fps_hdr *)find_sig(
(uchar_t *)base_end, 1024, "_MP_");
}
}
if (fpp == NULL) {
fpp = (struct mps_fps_hdr *)find_sig(
(uchar_t *)0xF0000, 0x10000, "_MP_");
}
if (fpp == NULL) {
dprintf("MP Spec table doesn't exist");
return;
} else {
dprintf("Found MP Floating Pointer Structure at %p\n",
(void *)fpp);
}
if (checksum((uchar_t *)fpp, fpp->fps_len * 16) != 0) {
dprintf("MP Floating Pointer Structure checksum error");
return;
}
ctp = (struct mps_ct_hdr *)(uintptr_t)fpp->fps_mpct_paddr;
if (ctp->ct_sig != 0x504d4350) {
dprintf("MP Configuration Table signature is wrong");
return;
}
base_len = ctp->ct_len;
if (checksum((uchar_t *)ctp, base_len) != 0) {
dprintf("MP Configuration Table checksum error");
return;
}
if (ctp->ct_spec_rev != 4) {
dprintf("MP Spec 1.1 found - extended table doesn't exist");
return;
}
if ((ext_len = ctp->ct_ext_tbl_len) == 0) {
dprintf("MP Spec 1.4 found - extended table doesn't exist");
return;
}
extp = (uchar_t *)ctp + base_len;
if (((checksum(extp, ext_len) + ctp->ct_ext_cksum) & 0xFF) != 0) {
dprintf("MP Extended Table checksum error");
return;
}
mps_extp = extp;
mps_ext_endp = mps_extp + ext_len;
}
static int
mps_find_bus_res(uint32_t bus, pci_prd_rsrc_t rsrc, struct memlist **res)
{
struct sasm *sasmp;
uchar_t *extp;
int res_cnt, type;
ASSERT3U(bus, <, PCI_MAX_BUS_NUM);
if (mps_extp == NULL)
return (0);
switch (rsrc) {
case PCI_PRD_R_IO:
type = IO_TYPE;
break;
case PCI_PRD_R_MMIO:
type = MEM_TYPE;
break;
case PCI_PRD_R_PREFETCH:
type = PREFETCH_TYPE;
break;
case PCI_PRD_R_BUS:
type = BUSRANGE_TYPE;
break;
default:
*res = NULL;
return (0);
}
extp = mps_extp;
res_cnt = 0;
while (extp < mps_ext_endp) {
switch (*extp) {
case SYS_AS_MAPPING:
sasmp = (struct sasm *)extp;
if (sasmp->sasm_as_type == type &&
sasmp->sasm_bus_id == bus) {
uint64_t base, len;
base = (uint64_t)sasmp->sasm_as_base |
(uint64_t)sasmp->sasm_as_base_hi << 32;
len = (uint64_t)sasmp->sasm_as_len |
(uint64_t)sasmp->sasm_as_len_hi << 32;
pci_memlist_insert(res, base, len);
res_cnt++;
}
extp += SYS_AS_MAPPING_SIZE;
break;
case BUS_HIERARCHY_DESC:
extp += BUS_HIERARCHY_DESC_SIZE;
break;
case COMP_BUS_AS_MODIFIER:
extp += COMP_BUS_AS_MODIFIER_SIZE;
break;
default:
cmn_err(CE_WARN, "Unknown descriptor type %d"
" in BIOS Multiprocessor Spec table.",
*extp);
pci_memlist_free_all(res);
return (0);
}
}
return (res_cnt);
}
static void
hrt_probe(void)
{
struct hrt_hdr *hrtp;
dprintf("search PCI Hot-Plug Resource Table starting at 0xF0000\n");
if ((hrtp = (struct hrt_hdr *)find_sig((uchar_t *)0xF0000,
0x10000, "$HRT")) == NULL) {
dprintf("NO PCI Hot-Plug Resource Table");
return;
}
dprintf("Found PCI Hot-Plug Resource Table at %p\n", (void *)hrtp);
if (hrtp->hrt_ver != 1) {
dprintf("PCI Hot-Plug Resource Table version no. <> 1\n");
return;
}
hrt_entry_cnt = (uint_t)hrtp->hrt_entry_cnt;
dprintf("No. of PCI hot-plug slot entries = 0x%x\n", hrt_entry_cnt);
hrt_hpep = (struct php_entry *)(hrtp + 1);
}
static int
hrt_find_bus_res(uint32_t bus, pci_prd_rsrc_t type, struct memlist **res)
{
int res_cnt;
struct php_entry *hpep;
ASSERT3U(bus, <, PCI_MAX_BUS_NUM);
if (hrt_hpep == NULL || hrt_entry_cnt == 0)
return (0);
hpep = hrt_hpep;
res_cnt = 0;
for (uint_t i = 0; i < hrt_entry_cnt; i++, hpep++) {
if (hpep->php_pri_bus != bus)
continue;
if (type == PCI_PRD_R_IO) {
if (hpep->php_io_start == 0 || hpep->php_io_size == 0)
continue;
pci_memlist_insert(res, (uint64_t)hpep->php_io_start,
(uint64_t)hpep->php_io_size);
res_cnt++;
} else if (type == PCI_PRD_R_MMIO) {
if (hpep->php_mem_start == 0 || hpep->php_mem_size == 0)
continue;
pci_memlist_insert(res,
((uint64_t)hpep->php_mem_start) << 16,
((uint64_t)hpep->php_mem_size) << 16);
res_cnt++;
} else if (type == PCI_PRD_R_PREFETCH) {
if (hpep->php_pfmem_start == 0 ||
hpep->php_pfmem_size == 0)
continue;
pci_memlist_insert(res,
((uint64_t)hpep->php_pfmem_start) << 16,
((uint64_t)hpep->php_pfmem_size) << 16);
res_cnt++;
}
}
return (res_cnt);
}
static uchar_t *
find_sig(uchar_t *cp, int len, char *sig)
{
long i;
for (i = 0; i < len; i += 16) {
if (cp[0] == sig[0] && cp[1] == sig[1] &&
cp[2] == sig[2] && cp[3] == sig[3])
return (cp);
cp += 16;
}
return (NULL);
}
static int
checksum(unsigned char *cp, int len)
{
int i;
unsigned int cksum;
for (i = cksum = 0; i < len; i++)
cksum += (unsigned int) *cp++;
return ((int)(cksum & 0xFF));
}
uint32_t
pci_prd_max_bus(void)
{
return ((uint32_t)pci_bios_maxbus);
}
struct memlist *
pci_prd_find_resource(uint32_t bus, pci_prd_rsrc_t rsrc)
{
struct memlist *res = NULL;
if (bus > pci_bios_maxbus)
return (NULL);
if (tbl_init == 0) {
tbl_init = 1;
acpi_pci_probe();
if (pci_prd_have_bios) {
hrt_probe();
mps_probe();
}
}
if (acpi_find_bus_res(bus, rsrc, &res) > 0)
return (res);
if (pci_prd_have_bios && hrt_find_bus_res(bus, rsrc, &res) > 0)
return (res);
if (pci_prd_have_bios)
(void) mps_find_bus_res(bus, rsrc, &res);
return (res);
}
typedef struct {
pci_prd_root_complex_f ppac_func;
void *ppac_arg;
} pci_prd_acpi_cb_t;
static ACPI_STATUS
pci_process_acpi_device(ACPI_HANDLE hdl, UINT32 level, void *ctx, void **rv)
{
ACPI_DEVICE_INFO *adi;
int busnum;
pci_prd_acpi_cb_t *cb = ctx;
if (ACPI_FAILURE(AcpiGetObjectInfo(hdl, &adi)))
return (AE_OK);
if (!(adi->Valid & ACPI_VALID_HID)) {
AcpiOsFree(adi);
return (AE_OK);
}
if (strncmp(adi->HardwareId.String, PCI_ROOT_HID_STRING,
sizeof (PCI_ROOT_HID_STRING)) &&
strncmp(adi->HardwareId.String, PCI_EXPRESS_ROOT_HID_STRING,
sizeof (PCI_EXPRESS_ROOT_HID_STRING))) {
AcpiOsFree(adi);
return (AE_OK);
}
AcpiOsFree(adi);
if (ACPI_SUCCESS(acpica_get_busno(hdl, &busnum))) {
if (busnum < 0) {
dcmn_err(CE_NOTE,
"pci_process_acpi_device: invalid _BBN 0x%x",
busnum);
return (AE_CTRL_DEPTH);
}
if (cb->ppac_func((uint32_t)busnum, cb->ppac_arg))
return (AE_CTRL_DEPTH);
return (AE_CTRL_TERMINATE);
}
return (AE_OK);
}
void
pci_prd_root_complex_iter(pci_prd_root_complex_f func, void *arg)
{
void *rv;
pci_prd_acpi_cb_t cb;
cb.ppac_func = func;
cb.ppac_arg = arg;
(void) AcpiGetDevices(NULL, pci_process_acpi_device, &cb, &rv);
pci_bios_bus_iter(func, arg);
}
void
pci_prd_slot_name(uint32_t bus, dev_info_t *dip)
{
char slotprop[256];
int len;
char *slotcap_name;
if (pci_irq_nroutes == 0)
return;
if (dip != NULL) {
if (ddi_prop_lookup_string(DDI_DEV_T_ANY, pci_bus_res[bus].dip,
DDI_PROP_DONTPASS, "slot-names", &slotcap_name) !=
DDI_SUCCESS || strcmp(slotcap_name, "pcie0") != 0) {
(void) ndi_prop_remove(DDI_DEV_T_NONE,
pci_bus_res[bus].dip, "slot-names");
}
}
len = pci_slot_names_prop(bus, slotprop, sizeof (slotprop));
if (len > 0) {
if (dip != NULL) {
ASSERT((len % sizeof (int)) == 0);
(void) ndi_prop_update_int_array(DDI_DEV_T_NONE,
pci_bus_res[bus].dip, "slot-names",
(int *)slotprop, len / sizeof (int));
} else {
cmn_err(CE_NOTE, "!BIOS BUG: Invalid bus number in PCI "
"IRQ routing table; Not adding slot-names "
"property for incorrect bus %d", bus);
}
}
}
boolean_t
pci_prd_multi_root_ok(void)
{
return (acpi_resource_discovery > 0);
}
pci_prd_compat_flags_t
pci_prd_compat_flags(void)
{
return (PCI_PRD_COMPAT_ISA | PCI_PRD_COMPAT_PCI_NODE_NAME |
PCI_PRD_COMPAT_SUBSYS);
}
int
pci_prd_init(pci_prd_upcalls_t *upcalls)
{
if (ddi_prop_exists(DDI_DEV_T_ANY, ddi_root_node(), DDI_PROP_DONTPASS,
"efi-systab")) {
pci_prd_have_bios = B_FALSE;
}
prd_upcalls = upcalls;
return (0);
}
void
pci_prd_fini(void)
{
int bus;
for (bus = 0; bus <= pci_bios_maxbus; bus++) {
pci_memlist_free_all(&acpi_io_res[bus]);
pci_memlist_free_all(&acpi_mem_res[bus]);
pci_memlist_free_all(&acpi_pmem_res[bus]);
pci_memlist_free_all(&acpi_bus_res[bus]);
}
}
static struct modlmisc pci_prd_modlmisc_i86pc = {
.misc_modops = &mod_miscops,
.misc_linkinfo = "i86pc PCI Resource Discovery"
};
static struct modlinkage pci_prd_modlinkage_i86pc = {
.ml_rev = MODREV_1,
.ml_linkage = { &pci_prd_modlmisc_i86pc, NULL }
};
int
_init(void)
{
return (mod_install(&pci_prd_modlinkage_i86pc));
}
int
_info(struct modinfo *modinfop)
{
return (mod_info(&pci_prd_modlinkage_i86pc, modinfop));
}
int
_fini(void)
{
return (mod_remove(&pci_prd_modlinkage_i86pc));
}