/* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License, Version 1.0 only * (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 * or http://www.opensolaris.org/os/licensing. * 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 2005 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. * * mps_table.h -- MP Specification table definitions */ #ifndef _MPS_TABLE_H #define _MPS_TABLE_H #ifdef __cplusplus extern "C" { #endif struct mps_fps_hdr { /* MP Floating Pointer Structure */ uint32_t fps_sig; /* _MP_ (0x5F4D505F) */ uint32_t fps_mpct_paddr; /* paddr of MP Configuration Table */ uchar_t fps_len; /* in paragraph (16-bytes units) */ uchar_t fps_spec_rev; /* MP Spec. version no. */ uchar_t fps_cksum; /* checksum of complete structure */ uchar_t fps_featinfo1; /* mp feature info byte 1 */ uchar_t fps_featinfo2; /* mp feature info byte 2 */ uchar_t fps_featinfo3; /* mp feature info byte 3 */ uchar_t fps_featinfo4; /* mp feature info byte 4 */ uchar_t fps_featinfo5; /* mp feature info byte 5 */ }; struct mps_ct_hdr { /* MP Configuration Table Header */ uint32_t ct_sig; /* "PCMP" */ uint16_t ct_len; /* base configuration in bytes */ uchar_t ct_spec_rev; /* MP Spec. version no. */ uchar_t ct_cksum; /* base configuration table checksum */ char ct_oem_id[8]; /* string identifies the manufacturer */ char ct_prod_id[12]; /* string identifies the product */ uint32_t ct_oem_ptr; /* paddr to an OEM-defined table */ uint16_t ct_oem_tbl_len; /* size of base OEM table in bytes */ uint16_t ct_entry_cnt; /* no. of entries in the base table */ uint32_t ct_local_apic; /* paddr of local APIC */ uint16_t ct_ext_tbl_len; /* extended table in bytes */ uchar_t ct_ext_cksum; /* checksum for the extended table */ }; /* Base MP Configuration Table entry type definitions */ #define CPU_TYPE 0 #define BUS_TYPE 1 #define IO_APIC_TYPE 2 #define IO_INTR_TYPE 3 #define LOCAL_INTR_TYPE 4 /* Base MP Configuration Table entry size definitions */ #define CPU_SIZE 20 #define BUS_SIZE 8 #define IO_APIC_SIZE 8 #define IO_INTR_SIZE 8 #define LOCAL_INTR_SIZE 8 /* Extended MP Configuration Table entry type definitions */ #define SYS_AS_MAPPING 128 #define BUS_HIERARCHY_DESC 129 #define COMP_BUS_AS_MODIFIER 130 /* Extended MP Configuration Table entry size definitions */ #define SYS_AS_MAPPING_SIZE 20 #define BUS_HIERARCHY_DESC_SIZE 8 #define COMP_BUS_AS_MODIFIER_SIZE 8 struct sasm { /* System Address Space Mapping Entry */ uchar_t sasm_type; /* type 128 */ uchar_t sasm_len; /* entry length in bytes (20) */ uchar_t sasm_bus_id; /* bus id where this is mapped */ uchar_t sasm_as_type; /* system address type */ /* system address type definitions */ #define IO_TYPE 0 #define MEM_TYPE 1 #define PREFETCH_TYPE 2 #define BUSRANGE_TYPE 3 uint32_t sasm_as_base; /* starting address */ uint32_t sasm_as_base_hi; uint32_t sasm_as_len; /* no. of addresses visiblie to the bus */ uint32_t sasm_as_len_hi; }; struct bhd { /* Bus Hierarchy Descriptor Entry */ uchar_t bhd_type; /* type 129 */ uchar_t bhd_len; /* entry length in bytes (8) */ uchar_t bhd_bus_id; /* bus id of this bus */ uchar_t bhd_bus_info; /* bus information */ /* Bus Information bit definition */ #define BHD_BUS_INFO_SD 1 /* Subtractive Decode Bus */ uchar_t bhd_parent; }; struct cbasm { /* Compatibility Bus Address Space Modifier Entry */ uchar_t cbasm_type; /* type 130 */ uchar_t cbasm_len; /* entry length in bytes (8) */ uchar_t cbasm_bus_id; /* bus to be modified */ uchar_t cbasm_addr_mod; /* address modifier */ /* Address Modifier bit definiton */ #define CBASM_ADDR_MOD_PR 1 /* 1 = subtracted, 0 = added */ uint32_t cbasm_pr_list; /* identify list of predefined address ranges */ }; #ifdef __cplusplus } #endif #endif /* _MPS_TABLE_H */ /* * 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 * or http://www.opensolaris.org/os/licensing. * 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 2010 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ /* * Copyright 2012 Garrett D'Amore . All rights reserved. * Copyright 2016 Joyent, Inc. */ /* * Host to PCI local bus driver */ #include #include #include #include #include #include #include #include #include #include #include #include /* Save minimal state. */ void *pci_statep; /* * Bus Operation functions */ static int pci_bus_map(dev_info_t *, dev_info_t *, ddi_map_req_t *, off_t, off_t, caddr_t *); static int pci_ctlops(dev_info_t *, dev_info_t *, ddi_ctl_enum_t, void *, void *); static int pci_intr_ops(dev_info_t *, dev_info_t *, ddi_intr_op_t, ddi_intr_handle_impl_t *, void *); static int pci_fm_init(dev_info_t *, dev_info_t *, int, ddi_iblock_cookie_t *); static int pci_fm_callback(dev_info_t *, ddi_fm_error_t *, const void *); struct bus_ops pci_bus_ops = { BUSO_REV, pci_bus_map, NULL, NULL, NULL, i_ddi_map_fault, NULL, ddi_dma_allochdl, ddi_dma_freehdl, ddi_dma_bindhdl, ddi_dma_unbindhdl, ddi_dma_flush, ddi_dma_win, ddi_dma_mctl, pci_ctlops, ddi_bus_prop_op, 0, /* (*bus_get_eventcookie)(); */ 0, /* (*bus_add_eventcall)(); */ 0, /* (*bus_remove_eventcall)(); */ 0, /* (*bus_post_event)(); */ 0, /* (*bus_intr_ctl)(); */ 0, /* (*bus_config)(); */ 0, /* (*bus_unconfig)(); */ pci_fm_init, /* (*bus_fm_init)(); */ NULL, /* (*bus_fm_fini)(); */ NULL, /* (*bus_fm_access_enter)(); */ NULL, /* (*bus_fm_access_exit)(); */ NULL, /* (*bus_power)(); */ pci_intr_ops /* (*bus_intr_op)(); */ }; /* * One goal here is to leverage off of the pcihp.c source without making * changes to it. Call into it's cb_ops directly if needed, piggybacking * anything else needed by the pci_tools.c module. Only pci_tools and pcihp * will be opening PCI nexus driver file descriptors. */ static int pci_open(dev_t *, int, int, cred_t *); static int pci_close(dev_t, int, int, cred_t *); static int pci_ioctl(dev_t, int, intptr_t, int, cred_t *, int *); static int pci_prop_op(dev_t, dev_info_t *, ddi_prop_op_t, int, char *, caddr_t, int *); static int pci_info(dev_info_t *, ddi_info_cmd_t, void *, void **); static void pci_peekpoke_cb(dev_info_t *, ddi_fm_error_t *); struct cb_ops pci_cb_ops = { pci_open, /* open */ pci_close, /* close */ nodev, /* strategy */ nodev, /* print */ nodev, /* dump */ nodev, /* read */ nodev, /* write */ pci_ioctl, /* ioctl */ nodev, /* devmap */ nodev, /* mmap */ nodev, /* segmap */ nochpoll, /* poll */ pci_prop_op, /* cb_prop_op */ NULL, /* streamtab */ D_NEW | D_MP | D_HOTPLUG, /* Driver compatibility flag */ CB_REV, /* rev */ nodev, /* int (*cb_aread)() */ nodev /* int (*cb_awrite)() */ }; /* * Device Node Operation functions */ static int pci_attach(dev_info_t *devi, ddi_attach_cmd_t cmd); static int pci_detach(dev_info_t *devi, ddi_detach_cmd_t cmd); struct dev_ops pci_ops = { DEVO_REV, /* devo_rev */ 0, /* refcnt */ pci_info, /* info */ nulldev, /* identify */ nulldev, /* probe */ pci_attach, /* attach */ pci_detach, /* detach */ nulldev, /* reset */ &pci_cb_ops, /* driver operations */ &pci_bus_ops, /* bus operations */ NULL, /* power */ ddi_quiesce_not_needed /* quiesce */ }; /* * This variable controls the default setting of the command register * for pci devices. See pci_initchild() for details. */ static ushort_t pci_command_default = PCI_COMM_ME | PCI_COMM_MAE | PCI_COMM_IO; /* * Internal routines in support of particular pci_ctlops. */ static int pci_removechild(dev_info_t *child); static int pci_initchild(dev_info_t *child); /* * Module linkage information for the kernel. */ static struct modldrv modldrv = { &mod_driverops, /* Type of module */ "x86 Host to PCI nexus driver", /* Name of module */ &pci_ops, /* driver ops */ }; static struct modlinkage modlinkage = { MODREV_1, (void *)&modldrv, NULL }; int _init(void) { int e; /* * Initialize per-pci bus soft state pointer. */ e = ddi_soft_state_init(&pci_statep, sizeof (pci_state_t), 1); if (e != 0) return (e); if ((e = mod_install(&modlinkage)) != 0) ddi_soft_state_fini(&pci_statep); return (e); } int _fini(void) { int rc; rc = mod_remove(&modlinkage); if (rc != 0) return (rc); ddi_soft_state_fini(&pci_statep); return (rc); } int _info(struct modinfo *modinfop) { return (mod_info(&modlinkage, modinfop)); } /*ARGSUSED*/ static int pci_attach(dev_info_t *devi, ddi_attach_cmd_t cmd) { /* * Use the minor number as constructed by pcihp, as the index value to * ddi_soft_state_zalloc. */ int instance = ddi_get_instance(devi); pci_state_t *pcip = NULL; switch (cmd) { case DDI_ATTACH: break; case DDI_RESUME: return (DDI_SUCCESS); default: return (DDI_FAILURE); } if (ddi_prop_update_string(DDI_DEV_T_NONE, devi, "device_type", "pci") != DDI_PROP_SUCCESS) { cmn_err(CE_WARN, "pci: 'device_type' prop create failed"); } if (ddi_soft_state_zalloc(pci_statep, instance) == DDI_SUCCESS) { pcip = ddi_get_soft_state(pci_statep, instance); } if (pcip == NULL) { goto bad_soft_state; } pcip->pci_dip = devi; pcip->pci_soft_state = PCI_SOFT_STATE_CLOSED; /* * Initialize hotplug support on this bus. At minimum * (for non hotplug bus) this would create ":devctl" minor * node to support DEVCTL_DEVICE_* and DEVCTL_BUS_* ioctls * to this bus. */ if (pcihp_init(devi) != DDI_SUCCESS) { cmn_err(CE_WARN, "pci: Failed to setup hotplug framework"); goto bad_pcihp_init; } /* Second arg: initialize for pci, not pci_express */ if (pcitool_init(devi, B_FALSE) != DDI_SUCCESS) { goto bad_pcitool_init; } pcip->pci_fmcap = DDI_FM_ERRCB_CAPABLE | DDI_FM_ACCCHK_CAPABLE | DDI_FM_DMACHK_CAPABLE; ddi_fm_init(devi, &pcip->pci_fmcap, &pcip->pci_fm_ibc); mutex_init(&pcip->pci_mutex, NULL, MUTEX_DRIVER, NULL); mutex_init(&pcip->pci_err_mutex, NULL, MUTEX_DRIVER, (void *)pcip->pci_fm_ibc); mutex_init(&pcip->pci_peek_poke_mutex, NULL, MUTEX_DRIVER, (void *)pcip->pci_fm_ibc); if (pcip->pci_fmcap & DDI_FM_ERRCB_CAPABLE) { pci_ereport_setup(devi); ddi_fm_handler_register(devi, pci_fm_callback, NULL); } ddi_report_dev(devi); return (DDI_SUCCESS); bad_pcitool_init: (void) pcihp_uninit(devi); bad_pcihp_init: ddi_soft_state_free(pci_statep, instance); bad_soft_state: return (DDI_FAILURE); } /*ARGSUSED*/ static int pci_detach(dev_info_t *devi, ddi_detach_cmd_t cmd) { int instance = ddi_get_instance(devi); pci_state_t *pcip; pcip = ddi_get_soft_state(pci_statep, ddi_get_instance(devi)); switch (cmd) { case DDI_DETACH: if (pcip->pci_fmcap & DDI_FM_ERRCB_CAPABLE) { ddi_fm_handler_unregister(devi); pci_ereport_teardown(devi); } mutex_destroy(&pcip->pci_peek_poke_mutex); mutex_destroy(&pcip->pci_err_mutex); mutex_destroy(&pcip->pci_mutex); ddi_fm_fini(devi); /* Uninitialize pcitool support. */ pcitool_uninit(devi); /* Uninitialize hotplug support on this bus. */ (void) pcihp_uninit(devi); ddi_soft_state_free(pci_statep, instance); return (DDI_SUCCESS); case DDI_SUSPEND: return (DDI_SUCCESS); default: return (DDI_FAILURE); } } static int pci_bus_map(dev_info_t *dip, dev_info_t *rdip, ddi_map_req_t *mp, off_t offset, off_t len, caddr_t *vaddrp) { struct regspec64 reg; ddi_map_req_t mr; ddi_acc_hdl_t *hp; ddi_acc_impl_t *hdlp; pci_regspec_t pci_reg; pci_regspec_t *pci_rp; int rnumber; uint64_t pci_rlength; uint_t nelems; pci_acc_cfblk_t *cfp; int space; pci_state_t *pcip; mr = *mp; /* Get private copy of request */ mp = &mr; if (mp->map_handlep != NULL) { pcip = ddi_get_soft_state(pci_statep, ddi_get_instance(dip)); hdlp = (ddi_acc_impl_t *)(mp->map_handlep)->ah_platform_private; hdlp->ahi_err_mutexp = &pcip->pci_err_mutex; hdlp->ahi_peekpoke_mutexp = &pcip->pci_peek_poke_mutex; hdlp->ahi_scan_dip = dip; hdlp->ahi_scan = pci_peekpoke_cb; } /* * check for register number */ switch (mp->map_type) { case DDI_MT_REGSPEC: pci_reg = *(pci_regspec_t *)(mp->map_obj.rp); pci_rp = &pci_reg; if (pci_common_get_reg_prop(rdip, pci_rp) != DDI_SUCCESS) return (DDI_FAILURE); break; case DDI_MT_RNUMBER: rnumber = mp->map_obj.rnumber; /* * get ALL "reg" properties for dip, select the one of * of interest. In x86, "assigned-addresses" property * is identical to the "reg" property, so there is no * need to cross check the two to determine the physical * address of the registers. * This routine still performs some validity checks to * make sure that everything is okay. */ if (ddi_prop_lookup_int_array(DDI_DEV_T_ANY, rdip, DDI_PROP_DONTPASS, "reg", (int **)&pci_rp, &nelems) != DDI_PROP_SUCCESS) return (DDI_FAILURE); /* * validate the register number. */ nelems /= (sizeof (pci_regspec_t) / sizeof (int)); if (rnumber >= nelems) { ddi_prop_free(pci_rp); return (DDI_FAILURE); } /* * copy the required entry. */ pci_reg = pci_rp[rnumber]; /* * free the memory allocated by ddi_prop_lookup_int_array */ ddi_prop_free(pci_rp); pci_rp = &pci_reg; if (pci_common_get_reg_prop(rdip, pci_rp) != DDI_SUCCESS) return (DDI_FAILURE); mp->map_type = DDI_MT_REGSPEC; break; default: return (DDI_ME_INVAL); } space = pci_rp->pci_phys_hi & PCI_REG_ADDR_M; /* * check for unmap and unlock of address space */ if ((mp->map_op == DDI_MO_UNMAP) || (mp->map_op == DDI_MO_UNLOCK)) { switch (space) { case PCI_ADDR_CONFIG: /* No work required on unmap of Config space */ return (DDI_SUCCESS); case PCI_ADDR_IO: reg.regspec_bustype = 1; break; case PCI_ADDR_MEM64: case PCI_ADDR_MEM32: reg.regspec_bustype = 0; break; default: return (DDI_FAILURE); } reg.regspec_addr = (uint64_t)pci_rp->pci_phys_mid << 32 | (uint64_t)pci_rp->pci_phys_low; reg.regspec_size = (uint64_t)pci_rp->pci_size_hi << 32 | (uint64_t)pci_rp->pci_size_low; /* * Adjust offset and length * A non-zero length means override the one in the regspec. */ if (reg.regspec_addr + offset < MAX(reg.regspec_addr, offset)) return (DDI_FAILURE); reg.regspec_addr += offset; if (len != 0) reg.regspec_size = len; mp->map_obj.rp = (struct regspec *)® mp->map_flags |= DDI_MF_EXT_REGSPEC; return (ddi_map(dip, mp, (off_t)0, (off_t)0, vaddrp)); } /* check for user mapping request - not legal for Config */ if (mp->map_op == DDI_MO_MAP_HANDLE && space == PCI_ADDR_CONFIG) { return (DDI_FAILURE); } /* * check for config space * On x86, CONFIG is not mapped via MMU and there is * no endian-ness issues. Set the attr field in the handle to * indicate that the common routines to call the nexus driver. */ if (space == PCI_ADDR_CONFIG) { /* Can't map config space without a handle */ hp = (ddi_acc_hdl_t *)mp->map_handlep; if (hp == NULL) return (DDI_FAILURE); /* record the device address for future reference */ cfp = (pci_acc_cfblk_t *)&hp->ah_bus_private; cfp->c_busnum = PCI_REG_BUS_G(pci_rp->pci_phys_hi); cfp->c_devnum = PCI_REG_DEV_G(pci_rp->pci_phys_hi); cfp->c_funcnum = PCI_REG_FUNC_G(pci_rp->pci_phys_hi); *vaddrp = (caddr_t)offset; return (pci_fm_acc_setup(hp, offset, len)); } /* * range check */ pci_rlength = (uint64_t)pci_rp->pci_size_low | (uint64_t)pci_rp->pci_size_hi << 32; if ((offset >= pci_rlength) || (len > pci_rlength) || (offset + len > pci_rlength) || (offset + len < MAX(offset, len))) { return (DDI_FAILURE); } /* * convert the pci regsec into the generic regspec used by the * parent root nexus driver. */ switch (space) { case PCI_ADDR_IO: reg.regspec_bustype = 1; break; case PCI_ADDR_MEM64: case PCI_ADDR_MEM32: reg.regspec_bustype = 0; break; default: return (DDI_FAILURE); } reg.regspec_addr = (uint64_t)pci_rp->pci_phys_mid << 32 | (uint64_t)pci_rp->pci_phys_low; reg.regspec_size = pci_rlength; /* * Adjust offset and length * A non-zero length means override the one in the regspec. */ if (reg.regspec_addr + offset < MAX(reg.regspec_addr, offset)) return (DDI_FAILURE); reg.regspec_addr += offset; if (len != 0) reg.regspec_size = len; mp->map_obj.rp = (struct regspec *)® mp->map_flags |= DDI_MF_EXT_REGSPEC; return (ddi_map(dip, mp, (off_t)0, (off_t)0, vaddrp)); } /*ARGSUSED*/ static int pci_ctlops(dev_info_t *dip, dev_info_t *rdip, ddi_ctl_enum_t ctlop, void *arg, void *result) { pci_regspec_t *drv_regp; uint_t reglen; int totreg; pci_state_t *pcip; struct attachspec *asp; struct detachspec *dsp; switch (ctlop) { case DDI_CTLOPS_REPORTDEV: if (rdip == (dev_info_t *)0) return (DDI_FAILURE); cmn_err(CE_CONT, "?PCI-device: %s@%s, %s%d\n", ddi_node_name(rdip), ddi_get_name_addr(rdip), ddi_driver_name(rdip), ddi_get_instance(rdip)); return (DDI_SUCCESS); case DDI_CTLOPS_INITCHILD: return (pci_initchild((dev_info_t *)arg)); case DDI_CTLOPS_UNINITCHILD: return (pci_removechild((dev_info_t *)arg)); case DDI_CTLOPS_SIDDEV: return (DDI_SUCCESS); case DDI_CTLOPS_REGSIZE: case DDI_CTLOPS_NREGS: if (rdip == (dev_info_t *)0) return (DDI_FAILURE); *(int *)result = 0; if (ddi_prop_lookup_int_array(DDI_DEV_T_ANY, rdip, DDI_PROP_DONTPASS, "reg", (int **)&drv_regp, ®len) != DDI_PROP_SUCCESS) { return (DDI_FAILURE); } totreg = (reglen * sizeof (int)) / sizeof (pci_regspec_t); if (ctlop == DDI_CTLOPS_NREGS) *(int *)result = totreg; else if (ctlop == DDI_CTLOPS_REGSIZE) { uint64_t val; int rn; rn = *(int *)arg; if (rn >= totreg) { ddi_prop_free(drv_regp); return (DDI_FAILURE); } val = drv_regp[rn].pci_size_low | (uint64_t)drv_regp[rn].pci_size_hi << 32; if (val > OFF_MAX) { int ce = CE_NOTE; #ifdef DEBUG ce = CE_WARN; #endif dev_err(rdip, ce, "failed to get register " "size, value larger than OFF_MAX: 0x%" PRIx64 "\n", val); return (DDI_FAILURE); } *(off_t *)result = (off_t)val; } ddi_prop_free(drv_regp); return (DDI_SUCCESS); case DDI_CTLOPS_POWER: { power_req_t *reqp = (power_req_t *)arg; /* * We currently understand reporting of PCI_PM_IDLESPEED * capability. Everything else is passed up. */ if ((reqp->request_type == PMR_REPORT_PMCAP) && (reqp->req.report_pmcap_req.cap == PCI_PM_IDLESPEED)) { return (DDI_SUCCESS); } return (ddi_ctlops(dip, rdip, ctlop, arg, result)); } case DDI_CTLOPS_PEEK: case DDI_CTLOPS_POKE: pcip = ddi_get_soft_state(pci_statep, ddi_get_instance(dip)); return (pci_peekpoke_check(dip, rdip, ctlop, arg, result, pci_common_peekpoke, &pcip->pci_err_mutex, &pcip->pci_peek_poke_mutex, pci_peekpoke_cb)); /* for now only X86 systems support PME wakeup from suspended state */ case DDI_CTLOPS_ATTACH: asp = (struct attachspec *)arg; if (asp->cmd == DDI_RESUME && asp->when == DDI_PRE) if (pci_pre_resume(rdip) != DDI_SUCCESS) return (DDI_FAILURE); return (ddi_ctlops(dip, rdip, ctlop, arg, result)); case DDI_CTLOPS_DETACH: dsp = (struct detachspec *)arg; if (dsp->cmd == DDI_SUSPEND && dsp->when == DDI_POST) if (pci_post_suspend(rdip) != DDI_SUCCESS) return (DDI_FAILURE); return (ddi_ctlops(dip, rdip, ctlop, arg, result)); default: return (ddi_ctlops(dip, rdip, ctlop, arg, result)); } /* NOTREACHED */ } /* * pci_intr_ops */ static int pci_intr_ops(dev_info_t *pdip, dev_info_t *rdip, ddi_intr_op_t intr_op, ddi_intr_handle_impl_t *hdlp, void *result) { return (pci_common_intr_ops(pdip, rdip, intr_op, hdlp, result)); } static int pci_initchild(dev_info_t *child) { char name[80]; ddi_acc_handle_t config_handle; ushort_t command_preserve, command; if (pci_common_name_child(child, name, 80) != DDI_SUCCESS) { return (DDI_FAILURE); } ddi_set_name_addr(child, name); /* * Pseudo nodes indicate a prototype node with per-instance * properties to be merged into the real h/w device node. * The interpretation of the unit-address is DD[,F] * where DD is the device id and F is the function. */ if (ndi_dev_is_persistent_node(child) == 0) { extern int pci_allow_pseudo_children; ddi_set_parent_data(child, NULL); /* * Try to merge the properties from this prototype * node into real h/w nodes. */ if (ndi_merge_node(child, pci_common_name_child) == DDI_SUCCESS) { /* * Merged ok - return failure to remove the node. */ ddi_set_name_addr(child, NULL); return (DDI_FAILURE); } /* workaround for ddivs to run under PCI */ if (pci_allow_pseudo_children) { /* * If the "interrupts" property doesn't exist, * this must be the ddivs no-intr case, and it returns * DDI_SUCCESS instead of DDI_FAILURE. */ if (ddi_prop_get_int(DDI_DEV_T_ANY, child, DDI_PROP_DONTPASS, "interrupts", -1) == -1) return (DDI_SUCCESS); /* * Create the ddi_parent_private_data for a pseudo * child. */ pci_common_set_parent_private_data(child); return (DDI_SUCCESS); } /* * The child was not merged into a h/w node, * but there's not much we can do with it other * than return failure to cause the node to be removed. */ cmn_err(CE_WARN, "!%s@%s: %s.conf properties not merged", ddi_get_name(child), ddi_get_name_addr(child), ddi_get_name(child)); ddi_set_name_addr(child, NULL); return (DDI_NOT_WELL_FORMED); } if (ddi_prop_get_int(DDI_DEV_T_ANY, child, DDI_PROP_DONTPASS, "interrupts", -1) != -1) pci_common_set_parent_private_data(child); else ddi_set_parent_data(child, NULL); /* * initialize command register */ if (pci_config_setup(child, &config_handle) != DDI_SUCCESS) return (DDI_FAILURE); /* * Support for the "command-preserve" property. */ command_preserve = ddi_prop_get_int(DDI_DEV_T_ANY, child, DDI_PROP_DONTPASS, "command-preserve", 0); command = pci_config_get16(config_handle, PCI_CONF_COMM); command &= (command_preserve | PCI_COMM_BACK2BACK_ENAB); command |= (pci_command_default & ~command_preserve); pci_config_put16(config_handle, PCI_CONF_COMM, command); pci_config_teardown(&config_handle); return (DDI_SUCCESS); } static int pci_removechild(dev_info_t *dip) { struct ddi_parent_private_data *pdptr; if ((pdptr = ddi_get_parent_data(dip)) != NULL) { kmem_free(pdptr, (sizeof (*pdptr) + sizeof (struct intrspec))); ddi_set_parent_data(dip, NULL); } ddi_set_name_addr(dip, NULL); /* * Strip the node to properly convert it back to prototype form */ ddi_remove_minor_node(dip, NULL); impl_rem_dev_props(dip); return (DDI_SUCCESS); } /* * When retrofitting this module for pci_tools, functions such as open, close, * and ioctl are now pulled into this module. Before this, the functions in * the pcihp module were referenced directly. Now they are called or * referenced through the pcihp cb_ops structure from functions in this module. */ static int pci_open(dev_t *devp, int flags, int otyp, cred_t *credp) { return ((pcihp_get_cb_ops())->cb_open(devp, flags, otyp, credp)); } static int pci_close(dev_t dev, int flags, int otyp, cred_t *credp) { return ((pcihp_get_cb_ops())->cb_close(dev, flags, otyp, credp)); } static int pci_ioctl(dev_t dev, int cmd, intptr_t arg, int mode, cred_t *credp, int *rvalp) { minor_t minor = getminor(dev); int instance = PCI_MINOR_NUM_TO_INSTANCE(minor); pci_state_t *pci_p = ddi_get_soft_state(pci_statep, instance); int ret = ENOTTY; if (pci_p == NULL) return (ENXIO); switch (PCI_MINOR_NUM_TO_PCI_DEVNUM(minor)) { case PCI_TOOL_REG_MINOR_NUM: case PCI_TOOL_INTR_MINOR_NUM: /* To handle pcitool related ioctls */ ret = pci_common_ioctl(pci_p->pci_dip, dev, cmd, arg, mode, credp, rvalp); break; default: /* To handle devctl and hotplug related ioctls */ ret = (pcihp_get_cb_ops())->cb_ioctl(dev, cmd, arg, mode, credp, rvalp); break; } return (ret); } static int pci_prop_op(dev_t dev, dev_info_t *dip, ddi_prop_op_t prop_op, int flags, char *name, caddr_t valuep, int *lengthp) { return ((pcihp_get_cb_ops())->cb_prop_op(dev, dip, prop_op, flags, name, valuep, lengthp)); } static int pci_info(dev_info_t *dip, ddi_info_cmd_t cmd, void *arg, void **result) { return (pcihp_info(dip, cmd, arg, result)); } void pci_peekpoke_cb(dev_info_t *dip, ddi_fm_error_t *derr) { (void) pci_ereport_post(dip, derr, NULL); } /*ARGSUSED*/ static int pci_fm_init(dev_info_t *dip, dev_info_t *tdip, int cap, ddi_iblock_cookie_t *ibc) { pci_state_t *pcip = ddi_get_soft_state(pci_statep, ddi_get_instance(dip)); ASSERT(ibc != NULL); *ibc = pcip->pci_fm_ibc; return (pcip->pci_fmcap); } /*ARGSUSED*/ static int pci_fm_callback(dev_info_t *dip, ddi_fm_error_t *derr, const void *no_used) { pci_state_t *pcip = ddi_get_soft_state(pci_statep, ddi_get_instance(dip)); mutex_enter(&pcip->pci_err_mutex); pci_ereport_post(dip, derr, NULL); mutex_exit(&pcip->pci_err_mutex); return (derr->fme_status); } /* * 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 * or http://www.opensolaris.org/os/licensing. * 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 (c) 2005, 2010, Oracle and/or its affiliates. All rights reserved. * Copyright 2022 Oxide Computer Company */ /* * File that has code which is common between pci(4D) and npe(4D) * It shares the following: * - interrupt code * - pci_tools ioctl code * - name_child code * - set_parent_private_data code */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include /* * Function prototypes */ static int pci_get_priority(dev_info_t *, ddi_intr_handle_impl_t *, int *); static int pci_enable_intr(dev_info_t *, dev_info_t *, ddi_intr_handle_impl_t *, uint32_t); static void pci_disable_intr(dev_info_t *, dev_info_t *, ddi_intr_handle_impl_t *, uint32_t); static int pci_alloc_intr_fixed(dev_info_t *, dev_info_t *, ddi_intr_handle_impl_t *, void *); static int pci_free_intr_fixed(dev_info_t *, dev_info_t *, ddi_intr_handle_impl_t *); /* Extern declarations for PSM module */ extern int (*psm_intr_ops)(dev_info_t *, ddi_intr_handle_impl_t *, psm_intr_op_t, int *); extern ddi_irm_pool_t *apix_irm_pool_p; /* * pci_name_child: * * Assign the address portion of the node name */ int pci_common_name_child(dev_info_t *child, char *name, int namelen) { int dev, func, length; char **unit_addr; uint_t n; pci_regspec_t *pci_rp; if (ndi_dev_is_persistent_node(child) == 0) { /* * For .conf node, use "unit-address" property */ if (ddi_prop_lookup_string_array(DDI_DEV_T_ANY, child, DDI_PROP_DONTPASS, "unit-address", &unit_addr, &n) != DDI_PROP_SUCCESS) { cmn_err(CE_WARN, "cannot find unit-address in %s.conf", ddi_get_name(child)); return (DDI_FAILURE); } if (n != 1 || *unit_addr == NULL || **unit_addr == 0) { cmn_err(CE_WARN, "unit-address property in %s.conf" " not well-formed", ddi_get_name(child)); ddi_prop_free(unit_addr); return (DDI_FAILURE); } (void) snprintf(name, namelen, "%s", *unit_addr); ddi_prop_free(unit_addr); return (DDI_SUCCESS); } if (ddi_prop_lookup_int_array(DDI_DEV_T_ANY, child, DDI_PROP_DONTPASS, "reg", (int **)&pci_rp, (uint_t *)&length) != DDI_PROP_SUCCESS) { cmn_err(CE_WARN, "cannot find reg property in %s", ddi_get_name(child)); return (DDI_FAILURE); } /* copy the device identifications */ dev = PCI_REG_DEV_G(pci_rp->pci_phys_hi); func = PCI_REG_FUNC_G(pci_rp->pci_phys_hi); /* * free the memory allocated by ddi_prop_lookup_int_array */ ddi_prop_free(pci_rp); if (func != 0) { (void) snprintf(name, namelen, "%x,%x", dev, func); } else { (void) snprintf(name, namelen, "%x", dev); } return (DDI_SUCCESS); } /* * Interrupt related code: * * The following busop is common to npe and pci drivers * bus_introp */ /* * Create the ddi_parent_private_data for a pseudo child. */ void pci_common_set_parent_private_data(dev_info_t *dip) { struct ddi_parent_private_data *pdptr; pdptr = (struct ddi_parent_private_data *)kmem_zalloc( (sizeof (struct ddi_parent_private_data) + sizeof (struct intrspec)), KM_SLEEP); pdptr->par_intr = (struct intrspec *)(pdptr + 1); pdptr->par_nintr = 1; ddi_set_parent_data(dip, pdptr); } /* * pci_get_priority: * Figure out the priority of the device */ static int pci_get_priority(dev_info_t *dip, ddi_intr_handle_impl_t *hdlp, int *pri) { struct intrspec *ispec; DDI_INTR_NEXDBG((CE_CONT, "pci_get_priority: dip = 0x%p, hdlp = %p\n", (void *)dip, (void *)hdlp)); if ((ispec = (struct intrspec *)pci_intx_get_ispec(dip, dip, hdlp->ih_inum)) == NULL) { if (DDI_INTR_IS_MSI_OR_MSIX(hdlp->ih_type)) { *pri = pci_class_to_pil(dip); pci_common_set_parent_private_data(hdlp->ih_dip); ispec = (struct intrspec *)pci_intx_get_ispec(dip, dip, hdlp->ih_inum); return (DDI_SUCCESS); } return (DDI_FAILURE); } *pri = ispec->intrspec_pri; return (DDI_SUCCESS); } static int pcieb_intr_pri_counter = 0; /* * pci_common_intr_ops: bus_intr_op() function for interrupt support */ int pci_common_intr_ops(dev_info_t *pdip, dev_info_t *rdip, ddi_intr_op_t intr_op, ddi_intr_handle_impl_t *hdlp, void *result) { int priority = 0; int psm_status = 0; int pci_status = 0; int pci_rval, psm_rval = PSM_FAILURE; int types = 0; int pciepci = 0; int i, j, count; int rv; int behavior; int cap_ptr; uint16_t msi_cap_base, msix_cap_base, cap_ctrl; char *prop; ddi_intrspec_t isp; struct intrspec *ispec; ddi_intr_handle_impl_t tmp_hdl; ddi_intr_msix_t *msix_p; ihdl_plat_t *ihdl_plat_datap; ddi_intr_handle_t *h_array; ddi_acc_handle_t handle; apic_get_intr_t intrinfo; DDI_INTR_NEXDBG((CE_CONT, "pci_common_intr_ops: pdip 0x%p, rdip 0x%p, op %x handle 0x%p\n", (void *)pdip, (void *)rdip, intr_op, (void *)hdlp)); /* Process the request */ switch (intr_op) { case DDI_INTROP_SUPPORTED_TYPES: /* * First we determine the interrupt types supported by the * device itself, then we filter them through what the OS * and system supports. We determine system-level * interrupt type support for anything other than fixed intrs * through the psm_intr_ops vector */ rv = DDI_FAILURE; /* Fixed supported by default */ types = DDI_INTR_TYPE_FIXED; if (psm_intr_ops == NULL) { *(int *)result = types; return (DDI_SUCCESS); } if (pci_config_setup(rdip, &handle) != DDI_SUCCESS) return (DDI_FAILURE); /* Sanity test cap control values if found */ if (PCI_CAP_LOCATE(handle, PCI_CAP_ID_MSI, &msi_cap_base) == DDI_SUCCESS) { cap_ctrl = PCI_CAP_GET16(handle, 0, msi_cap_base, PCI_MSI_CTRL); if (cap_ctrl == PCI_CAP_EINVAL16) goto SUPPORTED_TYPES_OUT; types |= DDI_INTR_TYPE_MSI; } if (PCI_CAP_LOCATE(handle, PCI_CAP_ID_MSI_X, &msix_cap_base) == DDI_SUCCESS) { cap_ctrl = PCI_CAP_GET16(handle, 0, msix_cap_base, PCI_MSIX_CTRL); if (cap_ctrl == PCI_CAP_EINVAL16) goto SUPPORTED_TYPES_OUT; types |= DDI_INTR_TYPE_MSIX; } /* * Filter device-level types through system-level support */ tmp_hdl.ih_type = types; if ((*psm_intr_ops)(rdip, &tmp_hdl, PSM_INTR_OP_CHECK_MSI, &types) != PSM_SUCCESS) goto SUPPORTED_TYPES_OUT; DDI_INTR_NEXDBG((CE_CONT, "pci_common_intr_ops: " "rdip: 0x%p supported types: 0x%x\n", (void *)rdip, types)); /* * Export any MSI/MSI-X cap locations via properties */ if (types & DDI_INTR_TYPE_MSI) { if (ndi_prop_update_int(DDI_DEV_T_NONE, rdip, "pci-msi-capid-pointer", (int)msi_cap_base) != DDI_PROP_SUCCESS) goto SUPPORTED_TYPES_OUT; } if (types & DDI_INTR_TYPE_MSIX) { if (ndi_prop_update_int(DDI_DEV_T_NONE, rdip, "pci-msix-capid-pointer", (int)msix_cap_base) != DDI_PROP_SUCCESS) goto SUPPORTED_TYPES_OUT; } rv = DDI_SUCCESS; SUPPORTED_TYPES_OUT: *(int *)result = types; pci_config_teardown(&handle); return (rv); case DDI_INTROP_NAVAIL: case DDI_INTROP_NINTRS: if (DDI_INTR_IS_MSI_OR_MSIX(hdlp->ih_type)) { if (pci_msi_get_nintrs(hdlp->ih_dip, hdlp->ih_type, result) != DDI_SUCCESS) return (DDI_FAILURE); } else { *(int *)result = i_ddi_get_intx_nintrs(hdlp->ih_dip); if (*(int *)result == 0) return (DDI_FAILURE); } break; case DDI_INTROP_ALLOC: /* * FIXED type */ if (hdlp->ih_type == DDI_INTR_TYPE_FIXED) return (pci_alloc_intr_fixed(pdip, rdip, hdlp, result)); /* * MSI or MSIX (figure out number of vectors available) */ if (DDI_INTR_IS_MSI_OR_MSIX(hdlp->ih_type) && (psm_intr_ops != NULL) && (pci_get_priority(rdip, hdlp, &priority) == DDI_SUCCESS)) { /* * Following check is a special case for 'pcieb'. * This makes sure vectors with the right priority * are allocated for pcieb during ALLOC time. */ if (strcmp(ddi_driver_name(rdip), "pcieb") == 0) { hdlp->ih_pri = (pcieb_intr_pri_counter % 2) ? 4 : 7; pciepci = 1; } else hdlp->ih_pri = priority; behavior = (int)(uintptr_t)hdlp->ih_scratch2; /* * Cache in the config handle and cap_ptr */ if (i_ddi_get_pci_config_handle(rdip) == NULL) { if (pci_config_setup(rdip, &handle) != DDI_SUCCESS) return (DDI_FAILURE); i_ddi_set_pci_config_handle(rdip, handle); } prop = NULL; cap_ptr = 0; if (hdlp->ih_type == DDI_INTR_TYPE_MSI) prop = "pci-msi-capid-pointer"; else if (hdlp->ih_type == DDI_INTR_TYPE_MSIX) prop = "pci-msix-capid-pointer"; /* * Enforce the calling of DDI_INTROP_SUPPORTED_TYPES * for MSI(X) before allocation */ if (prop != NULL) { cap_ptr = ddi_prop_get_int(DDI_DEV_T_ANY, rdip, DDI_PROP_DONTPASS, prop, 0); if (cap_ptr == 0) { DDI_INTR_NEXDBG((CE_CONT, "pci_common_intr_ops: rdip: 0x%p " "attempted MSI(X) alloc without " "cap property\n", (void *)rdip)); return (DDI_FAILURE); } } i_ddi_set_msi_msix_cap_ptr(rdip, cap_ptr); /* * Allocate interrupt vectors */ (void) (*psm_intr_ops)(rdip, hdlp, PSM_INTR_OP_ALLOC_VECTORS, result); if (*(int *)result == 0) return (DDI_INTR_NOTFOUND); /* verify behavior flag and take appropriate action */ if ((behavior == DDI_INTR_ALLOC_STRICT) && (*(int *)result < hdlp->ih_scratch1)) { DDI_INTR_NEXDBG((CE_CONT, "pci_common_intr_ops: behavior %x, " "couldn't get enough intrs\n", behavior)); hdlp->ih_scratch1 = *(int *)result; (void) (*psm_intr_ops)(rdip, hdlp, PSM_INTR_OP_FREE_VECTORS, NULL); return (DDI_EAGAIN); } if (hdlp->ih_type == DDI_INTR_TYPE_MSIX) { if (!(msix_p = i_ddi_get_msix(hdlp->ih_dip))) { msix_p = pci_msix_init(hdlp->ih_dip); if (msix_p) { i_ddi_set_msix(hdlp->ih_dip, msix_p); } else { DDI_INTR_NEXDBG((CE_CONT, "pci_common_intr_ops: MSI-X" "table initilization failed" ", rdip 0x%p inum 0x%x\n", (void *)rdip, hdlp->ih_inum)); (void) (*psm_intr_ops)(rdip, hdlp, PSM_INTR_OP_FREE_VECTORS, NULL); return (DDI_FAILURE); } } } if (pciepci) { /* update priority in ispec */ isp = pci_intx_get_ispec(pdip, rdip, (int)hdlp->ih_inum); ispec = (struct intrspec *)isp; if (ispec) ispec->intrspec_pri = hdlp->ih_pri; ++pcieb_intr_pri_counter; } } else return (DDI_FAILURE); break; case DDI_INTROP_FREE: if (DDI_INTR_IS_MSI_OR_MSIX(hdlp->ih_type) && (psm_intr_ops != NULL)) { if (i_ddi_intr_get_current_nintrs(hdlp->ih_dip) - 1 == 0) { if (handle = i_ddi_get_pci_config_handle( rdip)) { (void) pci_config_teardown(&handle); i_ddi_set_pci_config_handle(rdip, NULL); } if (cap_ptr = i_ddi_get_msi_msix_cap_ptr(rdip)) i_ddi_set_msi_msix_cap_ptr(rdip, 0); } (void) (*psm_intr_ops)(rdip, hdlp, PSM_INTR_OP_FREE_VECTORS, NULL); if (hdlp->ih_type == DDI_INTR_TYPE_MSIX) { msix_p = i_ddi_get_msix(hdlp->ih_dip); if (msix_p && (i_ddi_intr_get_current_nintrs( hdlp->ih_dip) - 1) == 0) { pci_msix_fini(msix_p); i_ddi_set_msix(hdlp->ih_dip, NULL); } } } else if (hdlp->ih_type == DDI_INTR_TYPE_FIXED) { return (pci_free_intr_fixed(pdip, rdip, hdlp)); } else return (DDI_FAILURE); break; case DDI_INTROP_GETPRI: /* Get the priority */ if (pci_get_priority(rdip, hdlp, &priority) != DDI_SUCCESS) return (DDI_FAILURE); DDI_INTR_NEXDBG((CE_CONT, "pci_common_intr_ops: " "priority = 0x%x\n", priority)); *(int *)result = priority; break; case DDI_INTROP_SETPRI: /* Validate the interrupt priority passed */ if (*(int *)result > LOCK_LEVEL) return (DDI_FAILURE); /* Ensure that PSM is all initialized */ if (psm_intr_ops == NULL) return (DDI_FAILURE); isp = pci_intx_get_ispec(pdip, rdip, (int)hdlp->ih_inum); ispec = (struct intrspec *)isp; if (ispec == NULL) return (DDI_FAILURE); /* For fixed interrupts */ if (hdlp->ih_type == DDI_INTR_TYPE_FIXED) { /* if interrupt is shared, return failure */ ((ihdl_plat_t *)hdlp->ih_private)->ip_ispecp = ispec; psm_rval = (*psm_intr_ops)(rdip, hdlp, PSM_INTR_OP_GET_SHARED, &psm_status); /* * For fixed interrupts, the irq may not have been * allocated when SET_PRI is called, and the above * GET_SHARED op may return PSM_FAILURE. This is not * a real error and is ignored below. */ if ((psm_rval != PSM_FAILURE) && (psm_status == 1)) { DDI_INTR_NEXDBG((CE_CONT, "pci_common_intr_ops: " "dip 0x%p cannot setpri, psm_rval=%d," "psm_status=%d\n", (void *)rdip, psm_rval, psm_status)); return (DDI_FAILURE); } } /* Change the priority */ if ((*psm_intr_ops)(rdip, hdlp, PSM_INTR_OP_SET_PRI, result) == PSM_FAILURE) return (DDI_FAILURE); /* update ispec */ ispec->intrspec_pri = *(int *)result; break; case DDI_INTROP_ADDISR: /* update ispec */ isp = pci_intx_get_ispec(pdip, rdip, (int)hdlp->ih_inum); ispec = (struct intrspec *)isp; if (ispec) { ispec->intrspec_func = hdlp->ih_cb_func; ihdl_plat_datap = (ihdl_plat_t *)hdlp->ih_private; pci_kstat_create(&ihdl_plat_datap->ip_ksp, pdip, hdlp); } break; case DDI_INTROP_REMISR: /* Get the interrupt structure pointer */ isp = pci_intx_get_ispec(pdip, rdip, (int)hdlp->ih_inum); ispec = (struct intrspec *)isp; if (ispec) { ispec->intrspec_func = (uint_t (*)()) 0; ihdl_plat_datap = (ihdl_plat_t *)hdlp->ih_private; if (ihdl_plat_datap->ip_ksp != NULL) pci_kstat_delete(ihdl_plat_datap->ip_ksp); } break; case DDI_INTROP_GETCAP: /* * First check the config space and/or * MSI capability register(s) */ pci_rval = DDI_FAILURE; if (DDI_INTR_IS_MSI_OR_MSIX(hdlp->ih_type)) pci_rval = pci_msi_get_cap(rdip, hdlp->ih_type, &pci_status); else if (hdlp->ih_type == DDI_INTR_TYPE_FIXED) pci_rval = pci_intx_get_cap(rdip, &pci_status); /* next check with PSM module */ if (psm_intr_ops != NULL) psm_rval = (*psm_intr_ops)(rdip, hdlp, PSM_INTR_OP_GET_CAP, &psm_status); DDI_INTR_NEXDBG((CE_CONT, "pci: GETCAP returned psm_rval = %x, " "psm_status = %x, pci_rval = %x, pci_status = %x\n", psm_rval, psm_status, pci_rval, pci_status)); if (psm_rval == PSM_FAILURE && pci_rval == DDI_FAILURE) { *(int *)result = 0; return (DDI_FAILURE); } if (psm_rval == PSM_SUCCESS) *(int *)result = psm_status; if (pci_rval == DDI_SUCCESS) *(int *)result |= pci_status; DDI_INTR_NEXDBG((CE_CONT, "pci: GETCAP returned = %x\n", *(int *)result)); break; case DDI_INTROP_SETCAP: DDI_INTR_NEXDBG((CE_CONT, "pci_common_intr_ops: " "SETCAP cap=0x%x\n", *(int *)result)); if (psm_intr_ops == NULL) return (DDI_FAILURE); if ((*psm_intr_ops)(rdip, hdlp, PSM_INTR_OP_SET_CAP, result)) { DDI_INTR_NEXDBG((CE_CONT, "GETCAP: psm_intr_ops" " returned failure\n")); return (DDI_FAILURE); } break; case DDI_INTROP_ENABLE: DDI_INTR_NEXDBG((CE_CONT, "pci_common_intr_ops: ENABLE\n")); if (psm_intr_ops == NULL) return (DDI_FAILURE); if (pci_enable_intr(pdip, rdip, hdlp, hdlp->ih_inum) != DDI_SUCCESS) return (DDI_FAILURE); DDI_INTR_NEXDBG((CE_CONT, "pci_common_intr_ops: ENABLE " "vector=0x%x\n", hdlp->ih_vector)); break; case DDI_INTROP_DISABLE: DDI_INTR_NEXDBG((CE_CONT, "pci_common_intr_ops: DISABLE\n")); if (psm_intr_ops == NULL) return (DDI_FAILURE); pci_disable_intr(pdip, rdip, hdlp, hdlp->ih_inum); DDI_INTR_NEXDBG((CE_CONT, "pci_common_intr_ops: DISABLE " "vector = %x\n", hdlp->ih_vector)); break; case DDI_INTROP_BLOCKENABLE: DDI_INTR_NEXDBG((CE_CONT, "pci_common_intr_ops: " "BLOCKENABLE\n")); if (hdlp->ih_type != DDI_INTR_TYPE_MSI) { DDI_INTR_NEXDBG((CE_CONT, "BLOCKENABLE: not MSI\n")); return (DDI_FAILURE); } /* Check if psm_intr_ops is NULL? */ if (psm_intr_ops == NULL) return (DDI_FAILURE); count = hdlp->ih_scratch1; h_array = (ddi_intr_handle_t *)hdlp->ih_scratch2; for (i = 0; i < count; i++) { hdlp = (ddi_intr_handle_impl_t *)h_array[i]; if (pci_enable_intr(pdip, rdip, hdlp, hdlp->ih_inum) != DDI_SUCCESS) { DDI_INTR_NEXDBG((CE_CONT, "BLOCKENABLE: " "pci_enable_intr failed for %d\n", i)); for (j = 0; j < i; j++) { hdlp = (ddi_intr_handle_impl_t *) h_array[j]; pci_disable_intr(pdip, rdip, hdlp, hdlp->ih_inum); } return (DDI_FAILURE); } DDI_INTR_NEXDBG((CE_CONT, "pci_common_intr_ops: " "BLOCKENABLE inum %x done\n", hdlp->ih_inum)); } break; case DDI_INTROP_BLOCKDISABLE: DDI_INTR_NEXDBG((CE_CONT, "pci_common_intr_ops: " "BLOCKDISABLE\n")); if (hdlp->ih_type != DDI_INTR_TYPE_MSI) { DDI_INTR_NEXDBG((CE_CONT, "BLOCKDISABLE: not MSI\n")); return (DDI_FAILURE); } /* Check if psm_intr_ops is present */ if (psm_intr_ops == NULL) return (DDI_FAILURE); count = hdlp->ih_scratch1; h_array = (ddi_intr_handle_t *)hdlp->ih_scratch2; for (i = 0; i < count; i++) { hdlp = (ddi_intr_handle_impl_t *)h_array[i]; pci_disable_intr(pdip, rdip, hdlp, hdlp->ih_inum); DDI_INTR_NEXDBG((CE_CONT, "pci_common_intr_ops: " "BLOCKDISABLE inum %x done\n", hdlp->ih_inum)); } break; case DDI_INTROP_SETMASK: case DDI_INTROP_CLRMASK: /* * First handle in the config space */ if (intr_op == DDI_INTROP_SETMASK) { if (DDI_INTR_IS_MSI_OR_MSIX(hdlp->ih_type)) pci_status = pci_msi_set_mask(rdip, hdlp->ih_type, hdlp->ih_inum); else if (hdlp->ih_type == DDI_INTR_TYPE_FIXED) pci_status = pci_intx_set_mask(rdip); } else { if (DDI_INTR_IS_MSI_OR_MSIX(hdlp->ih_type)) pci_status = pci_msi_clr_mask(rdip, hdlp->ih_type, hdlp->ih_inum); else if (hdlp->ih_type == DDI_INTR_TYPE_FIXED) pci_status = pci_intx_clr_mask(rdip); } /* For MSI/X; no need to check with PSM module */ if (hdlp->ih_type != DDI_INTR_TYPE_FIXED) return (pci_status); /* For fixed interrupts only: handle config space first */ if (hdlp->ih_type == DDI_INTR_TYPE_FIXED && pci_status == DDI_SUCCESS) break; /* For fixed interrupts only: confer with PSM module next */ if (psm_intr_ops != NULL) { /* If interrupt is shared; do nothing */ psm_rval = (*psm_intr_ops)(rdip, hdlp, PSM_INTR_OP_GET_SHARED, &psm_status); if (psm_rval == PSM_FAILURE || psm_status == 1) return (pci_status); /* Now, PSM module should try to set/clear the mask */ if (intr_op == DDI_INTROP_SETMASK) psm_rval = (*psm_intr_ops)(rdip, hdlp, PSM_INTR_OP_SET_MASK, NULL); else psm_rval = (*psm_intr_ops)(rdip, hdlp, PSM_INTR_OP_CLEAR_MASK, NULL); } return ((psm_rval == PSM_FAILURE) ? DDI_FAILURE : DDI_SUCCESS); case DDI_INTROP_GETPENDING: /* * First check the config space and/or * MSI capability register(s) */ pci_rval = DDI_FAILURE; if (DDI_INTR_IS_MSI_OR_MSIX(hdlp->ih_type)) pci_rval = pci_msi_get_pending(rdip, hdlp->ih_type, hdlp->ih_inum, &pci_status); else if (hdlp->ih_type == DDI_INTR_TYPE_FIXED) pci_rval = pci_intx_get_pending(rdip, &pci_status); /* On failure; next try with PSM module */ if (pci_rval != DDI_SUCCESS && psm_intr_ops != NULL) psm_rval = (*psm_intr_ops)(rdip, hdlp, PSM_INTR_OP_GET_PENDING, &psm_status); DDI_INTR_NEXDBG((CE_CONT, "pci: GETPENDING returned " "psm_rval = %x, psm_status = %x, pci_rval = %x, " "pci_status = %x\n", psm_rval, psm_status, pci_rval, pci_status)); if (psm_rval == PSM_FAILURE && pci_rval == DDI_FAILURE) { *(int *)result = 0; return (DDI_FAILURE); } if (psm_rval != PSM_FAILURE) *(int *)result = psm_status; else if (pci_rval != DDI_FAILURE) *(int *)result = pci_status; DDI_INTR_NEXDBG((CE_CONT, "pci: GETPENDING returned = %x\n", *(int *)result)); break; case DDI_INTROP_GETTARGET: DDI_INTR_NEXDBG((CE_CONT, "pci_common_intr_ops: GETTARGET\n")); bcopy(hdlp, &tmp_hdl, sizeof (ddi_intr_handle_impl_t)); tmp_hdl.ih_private = (void *)&intrinfo; intrinfo.avgi_req_flags = PSMGI_INTRBY_DEFAULT; intrinfo.avgi_req_flags |= PSMGI_REQ_CPUID; if ((*psm_intr_ops)(rdip, &tmp_hdl, PSM_INTR_OP_GET_INTR, NULL) == PSM_FAILURE) return (DDI_FAILURE); *(int *)result = intrinfo.avgi_cpu_id; DDI_INTR_NEXDBG((CE_CONT, "pci_common_intr_ops: GETTARGET " "vector = 0x%x, cpu = 0x%x\n", hdlp->ih_vector, *(int *)result)); break; case DDI_INTROP_SETTARGET: DDI_INTR_NEXDBG((CE_CONT, "pci_common_intr_ops: SETTARGET\n")); bcopy(hdlp, &tmp_hdl, sizeof (ddi_intr_handle_impl_t)); tmp_hdl.ih_private = (void *)(uintptr_t)*(int *)result; tmp_hdl.ih_flags = PSMGI_INTRBY_DEFAULT; if ((*psm_intr_ops)(rdip, &tmp_hdl, PSM_INTR_OP_SET_CPU, &psm_status) == PSM_FAILURE) return (DDI_FAILURE); hdlp->ih_vector = tmp_hdl.ih_vector; DDI_INTR_NEXDBG((CE_CONT, "pci_common_intr_ops: SETTARGET " "vector = 0x%x\n", hdlp->ih_vector)); break; case DDI_INTROP_GETPOOL: /* * For MSI/X interrupts use global IRM pool if available. */ if (apix_irm_pool_p && DDI_INTR_IS_MSI_OR_MSIX(hdlp->ih_type)) { *(ddi_irm_pool_t **)result = apix_irm_pool_p; return (DDI_SUCCESS); } return (DDI_ENOTSUP); default: return (i_ddi_intr_ops(pdip, rdip, intr_op, hdlp, result)); } return (DDI_SUCCESS); } /* * Allocate a vector for FIXED type interrupt. */ int pci_alloc_intr_fixed(dev_info_t *pdip, dev_info_t *rdip, ddi_intr_handle_impl_t *hdlp, void *result) { struct intrspec *ispec; ddi_intr_handle_impl_t info_hdl; int ret; int free_phdl = 0; int pci_rval; int pci_status = 0; apic_get_type_t type_info; if (psm_intr_ops == NULL) return (DDI_FAILURE); /* Figure out if this device supports MASKING */ pci_rval = pci_intx_get_cap(rdip, &pci_status); if (pci_rval == DDI_SUCCESS && pci_status) hdlp->ih_cap |= pci_status; /* * If the PSM module is "APIX" then pass the request for * allocating the vector now. */ bzero(&info_hdl, sizeof (ddi_intr_handle_impl_t)); info_hdl.ih_private = &type_info; if ((*psm_intr_ops)(NULL, &info_hdl, PSM_INTR_OP_APIC_TYPE, NULL) == PSM_SUCCESS && strcmp(type_info.avgi_type, APIC_APIX_NAME) == 0) { ispec = (struct intrspec *)pci_intx_get_ispec(pdip, rdip, (int)hdlp->ih_inum); if (ispec == NULL) return (DDI_FAILURE); if (hdlp->ih_private == NULL) { /* allocate phdl structure */ free_phdl = 1; i_ddi_alloc_intr_phdl(hdlp); } ((ihdl_plat_t *)hdlp->ih_private)->ip_ispecp = ispec; ret = (*psm_intr_ops)(rdip, hdlp, PSM_INTR_OP_ALLOC_VECTORS, result); if (free_phdl) { /* free up the phdl structure */ free_phdl = 0; i_ddi_free_intr_phdl(hdlp); hdlp->ih_private = NULL; } } else { /* * No APIX module; fall back to the old scheme where the * interrupt vector is allocated during ddi_intr_enable() call. */ *(int *)result = 1; ret = DDI_SUCCESS; } return (ret); } /* * Free up the vector for FIXED (legacy) type interrupt. */ static int pci_free_intr_fixed(dev_info_t *pdip, dev_info_t *rdip, ddi_intr_handle_impl_t *hdlp) { struct intrspec *ispec; ddi_intr_handle_impl_t info_hdl; int ret; apic_get_type_t type_info; if (psm_intr_ops == NULL) return (DDI_FAILURE); /* * If the PSM module is "APIX" then pass the request to it * to free up the vector now. */ bzero(&info_hdl, sizeof (ddi_intr_handle_impl_t)); info_hdl.ih_private = &type_info; if ((*psm_intr_ops)(NULL, &info_hdl, PSM_INTR_OP_APIC_TYPE, NULL) == PSM_SUCCESS && strcmp(type_info.avgi_type, APIC_APIX_NAME) == 0) { ispec = (struct intrspec *)pci_intx_get_ispec(pdip, rdip, (int)hdlp->ih_inum); if (ispec == NULL) return (DDI_FAILURE); ((ihdl_plat_t *)hdlp->ih_private)->ip_ispecp = ispec; ret = (*psm_intr_ops)(rdip, hdlp, PSM_INTR_OP_FREE_VECTORS, NULL); } else { /* * No APIX module; fall back to the old scheme where * the interrupt vector was already freed during * ddi_intr_disable() call. */ ret = DDI_SUCCESS; } return (ret); } int pci_get_intr_from_vecirq(apic_get_intr_t *intrinfo_p, int vecirq, boolean_t is_irq) { ddi_intr_handle_impl_t get_info_ii_hdl; if (is_irq) intrinfo_p->avgi_req_flags |= PSMGI_INTRBY_IRQ; /* * 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. */ get_info_ii_hdl.ih_private = intrinfo_p; get_info_ii_hdl.ih_vector = vecirq; if ((*psm_intr_ops)(NULL, &get_info_ii_hdl, PSM_INTR_OP_GET_INTR, NULL) == PSM_FAILURE) return (DDI_FAILURE); return (DDI_SUCCESS); } int pci_get_cpu_from_vecirq(int vecirq, boolean_t is_irq) { int rval; apic_get_intr_t intrinfo; intrinfo.avgi_req_flags = PSMGI_REQ_CPUID; rval = pci_get_intr_from_vecirq(&intrinfo, vecirq, is_irq); if (rval == DDI_SUCCESS) return (intrinfo.avgi_cpu_id); else return (-1); } static int pci_enable_intr(dev_info_t *pdip, dev_info_t *rdip, ddi_intr_handle_impl_t *hdlp, uint32_t inum) { struct intrspec *ispec; int irq; ihdl_plat_t *ihdl_plat_datap = (ihdl_plat_t *)hdlp->ih_private; DDI_INTR_NEXDBG((CE_CONT, "pci_enable_intr: hdlp %p inum %x\n", (void *)hdlp, inum)); /* Translate the interrupt if needed */ ispec = (struct intrspec *)pci_intx_get_ispec(pdip, rdip, (int)inum); if (ispec == NULL) return (DDI_FAILURE); if (DDI_INTR_IS_MSI_OR_MSIX(hdlp->ih_type)) { ispec->intrspec_vec = inum; ispec->intrspec_pri = hdlp->ih_pri; } ihdl_plat_datap->ip_ispecp = ispec; /* translate the interrupt if needed */ if ((*psm_intr_ops)(rdip, hdlp, PSM_INTR_OP_XLATE_VECTOR, &irq) == PSM_FAILURE) return (DDI_FAILURE); DDI_INTR_NEXDBG((CE_CONT, "pci_enable_intr: priority=%x irq=%x\n", hdlp->ih_pri, irq)); /* Add the interrupt handler */ if (!add_avintr((void *)hdlp, hdlp->ih_pri, hdlp->ih_cb_func, DEVI(rdip)->devi_name, irq, hdlp->ih_cb_arg1, hdlp->ih_cb_arg2, &ihdl_plat_datap->ip_ticks, rdip)) return (DDI_FAILURE); hdlp->ih_vector = irq; return (DDI_SUCCESS); } static void pci_disable_intr(dev_info_t *pdip, dev_info_t *rdip, ddi_intr_handle_impl_t *hdlp, uint32_t inum) { int irq; struct intrspec *ispec; ihdl_plat_t *ihdl_plat_datap = (ihdl_plat_t *)hdlp->ih_private; DDI_INTR_NEXDBG((CE_CONT, "pci_disable_intr: \n")); ispec = (struct intrspec *)pci_intx_get_ispec(pdip, rdip, (int)inum); if (ispec == NULL) return; if (DDI_INTR_IS_MSI_OR_MSIX(hdlp->ih_type)) { ispec->intrspec_vec = inum; ispec->intrspec_pri = hdlp->ih_pri; } ihdl_plat_datap->ip_ispecp = ispec; /* translate the interrupt if needed */ (void) (*psm_intr_ops)(rdip, hdlp, PSM_INTR_OP_XLATE_VECTOR, &irq); /* Disable the interrupt handler */ rem_avintr((void *)hdlp, hdlp->ih_pri, hdlp->ih_cb_func, irq); ihdl_plat_datap->ip_ispecp = NULL; } /* * Miscellaneous library function */ int pci_common_get_reg_prop(dev_info_t *dip, pci_regspec_t *pci_rp) { int i; int number; int assigned_addr_len; uint_t phys_hi = pci_rp->pci_phys_hi; pci_regspec_t *assigned_addr; if (((phys_hi & PCI_REG_ADDR_M) == PCI_ADDR_CONFIG) || (phys_hi & PCI_RELOCAT_B)) return (DDI_SUCCESS); /* * the "reg" property specifies relocatable, get and interpret the * "assigned-addresses" property. */ if (ddi_prop_lookup_int_array(DDI_DEV_T_ANY, dip, DDI_PROP_DONTPASS, "assigned-addresses", (int **)&assigned_addr, (uint_t *)&assigned_addr_len) != DDI_PROP_SUCCESS) return (DDI_FAILURE); /* * Scan the "assigned-addresses" for one that matches the specified * "reg" property entry. */ phys_hi &= PCI_CONF_ADDR_MASK; number = assigned_addr_len / (sizeof (pci_regspec_t) / sizeof (int)); for (i = 0; i < number; i++) { if ((assigned_addr[i].pci_phys_hi & PCI_CONF_ADDR_MASK) == phys_hi) { /* * When the system does not manage to allocate PCI * resources for a device, then the value that is stored * in assigned addresses ends up being the hardware * default reset value of '0'. On currently supported * platforms, physical address zero is associated with * memory; however, on other platforms this may be the * exception vector table (ARM), etc. and so we opt to * generally keep the idea in PCI that the reset value * will not be used for actual MMIO allocations. If such * a platform comes around where it is worth using that * bit of MMIO for PCI then we should make this check * platform-specific. * * Note, the +1 in the print statement is because a * given regs[0] describes B/D/F information for the * device. */ if (assigned_addr[i].pci_phys_mid == 0 && assigned_addr[i].pci_phys_low == 0) { dev_err(dip, CE_WARN, "regs[%u] does not have " "a valid MMIO address", i + 1); goto err; } pci_rp->pci_phys_mid = assigned_addr[i].pci_phys_mid; pci_rp->pci_phys_low = assigned_addr[i].pci_phys_low; ddi_prop_free(assigned_addr); return (DDI_SUCCESS); } } err: ddi_prop_free(assigned_addr); return (DDI_FAILURE); } /* * To handle PCI tool ioctls */ /*ARGSUSED*/ int pci_common_ioctl(dev_info_t *dip, dev_t dev, int cmd, intptr_t arg, int mode, cred_t *credp, int *rvalp) { minor_t minor = getminor(dev); int rv = ENOTTY; switch (PCI_MINOR_NUM_TO_PCI_DEVNUM(minor)) { case PCI_TOOL_REG_MINOR_NUM: switch (cmd) { case PCITOOL_DEVICE_SET_REG: case PCITOOL_DEVICE_GET_REG: /* Require full privileges. */ if (secpolicy_kmdb(credp)) rv = EPERM; else rv = pcitool_dev_reg_ops(dip, (void *)arg, cmd, mode); break; case PCITOOL_NEXUS_SET_REG: case PCITOOL_NEXUS_GET_REG: /* Require full privileges. */ if (secpolicy_kmdb(credp)) rv = EPERM; else rv = pcitool_bus_reg_ops(dip, (void *)arg, cmd, mode); break; } break; case PCI_TOOL_INTR_MINOR_NUM: switch (cmd) { case PCITOOL_DEVICE_SET_INTR: /* Require PRIV_SYS_RES_CONFIG, same as psradm */ if (secpolicy_ponline(credp)) { rv = EPERM; break; } /*FALLTHRU*/ /* These require no special privileges. */ case PCITOOL_DEVICE_GET_INTR: case PCITOOL_SYSTEM_INTR_INFO: rv = pcitool_intr_admn(dip, (void *)arg, cmd, mode); break; } break; default: break; } return (rv); } int pci_common_ctlops_poke(peekpoke_ctlops_t *in_args) { size_t size = in_args->size; uintptr_t dev_addr = in_args->dev_addr; uintptr_t host_addr = in_args->host_addr; ddi_acc_impl_t *hp = (ddi_acc_impl_t *)in_args->handle; ddi_acc_hdl_t *hdlp = (ddi_acc_hdl_t *)in_args->handle; size_t repcount = in_args->repcount; uint_t flags = in_args->flags; int err = DDI_SUCCESS; /* * if no handle then this is a poke. We have to return failure here * as we have no way of knowing whether this is a MEM or IO space access */ if (in_args->handle == NULL) return (DDI_FAILURE); /* * rest of this function is actually for cautious puts */ for (; repcount; repcount--) { if (hp->ahi_acc_attr == DDI_ACCATTR_CONFIG_SPACE) { switch (size) { case sizeof (uint8_t): pci_config_wr8(hp, (uint8_t *)dev_addr, *(uint8_t *)host_addr); break; case sizeof (uint16_t): pci_config_wr16(hp, (uint16_t *)dev_addr, *(uint16_t *)host_addr); break; case sizeof (uint32_t): pci_config_wr32(hp, (uint32_t *)dev_addr, *(uint32_t *)host_addr); break; case sizeof (uint64_t): pci_config_wr64(hp, (uint64_t *)dev_addr, *(uint64_t *)host_addr); break; default: err = DDI_FAILURE; break; } } else if (hp->ahi_acc_attr & DDI_ACCATTR_IO_SPACE) { if (hdlp->ah_acc.devacc_attr_endian_flags == DDI_STRUCTURE_BE_ACC) { switch (size) { case sizeof (uint8_t): i_ddi_io_put8(hp, (uint8_t *)dev_addr, *(uint8_t *)host_addr); break; case sizeof (uint16_t): i_ddi_io_swap_put16(hp, (uint16_t *)dev_addr, *(uint16_t *)host_addr); break; case sizeof (uint32_t): i_ddi_io_swap_put32(hp, (uint32_t *)dev_addr, *(uint32_t *)host_addr); break; /* * note the 64-bit case is a dummy * function - so no need to swap */ case sizeof (uint64_t): i_ddi_io_put64(hp, (uint64_t *)dev_addr, *(uint64_t *)host_addr); break; default: err = DDI_FAILURE; break; } } else { switch (size) { case sizeof (uint8_t): i_ddi_io_put8(hp, (uint8_t *)dev_addr, *(uint8_t *)host_addr); break; case sizeof (uint16_t): i_ddi_io_put16(hp, (uint16_t *)dev_addr, *(uint16_t *)host_addr); break; case sizeof (uint32_t): i_ddi_io_put32(hp, (uint32_t *)dev_addr, *(uint32_t *)host_addr); break; case sizeof (uint64_t): i_ddi_io_put64(hp, (uint64_t *)dev_addr, *(uint64_t *)host_addr); break; default: err = DDI_FAILURE; break; } } } else { if (hdlp->ah_acc.devacc_attr_endian_flags == DDI_STRUCTURE_BE_ACC) { switch (size) { case sizeof (uint8_t): *(uint8_t *)dev_addr = *(uint8_t *)host_addr; break; case sizeof (uint16_t): *(uint16_t *)dev_addr = ddi_swap16(*(uint16_t *)host_addr); break; case sizeof (uint32_t): *(uint32_t *)dev_addr = ddi_swap32(*(uint32_t *)host_addr); break; case sizeof (uint64_t): *(uint64_t *)dev_addr = ddi_swap64(*(uint64_t *)host_addr); break; default: err = DDI_FAILURE; break; } } else { switch (size) { case sizeof (uint8_t): *(uint8_t *)dev_addr = *(uint8_t *)host_addr; break; case sizeof (uint16_t): *(uint16_t *)dev_addr = *(uint16_t *)host_addr; break; case sizeof (uint32_t): *(uint32_t *)dev_addr = *(uint32_t *)host_addr; break; case sizeof (uint64_t): *(uint64_t *)dev_addr = *(uint64_t *)host_addr; break; default: err = DDI_FAILURE; break; } } } host_addr += size; if (flags == DDI_DEV_AUTOINCR) dev_addr += size; } return (err); } int pci_fm_acc_setup(ddi_acc_hdl_t *hp, off_t offset, off_t len) { ddi_acc_impl_t *ap = (ddi_acc_impl_t *)hp->ah_platform_private; /* endian-ness check */ if (hp->ah_acc.devacc_attr_endian_flags == DDI_STRUCTURE_BE_ACC) return (DDI_FAILURE); /* * range check */ if ((offset >= PCI_CONF_HDR_SIZE) || (len > PCI_CONF_HDR_SIZE) || (offset + len > PCI_CONF_HDR_SIZE)) return (DDI_FAILURE); ap->ahi_acc_attr |= DDI_ACCATTR_CONFIG_SPACE; /* * always use cautious mechanism for config space gets */ ap->ahi_get8 = i_ddi_caut_get8; ap->ahi_get16 = i_ddi_caut_get16; ap->ahi_get32 = i_ddi_caut_get32; ap->ahi_get64 = i_ddi_caut_get64; ap->ahi_rep_get8 = i_ddi_caut_rep_get8; ap->ahi_rep_get16 = i_ddi_caut_rep_get16; ap->ahi_rep_get32 = i_ddi_caut_rep_get32; ap->ahi_rep_get64 = i_ddi_caut_rep_get64; if (hp->ah_acc.devacc_attr_access == DDI_CAUTIOUS_ACC) { ap->ahi_put8 = i_ddi_caut_put8; ap->ahi_put16 = i_ddi_caut_put16; ap->ahi_put32 = i_ddi_caut_put32; ap->ahi_put64 = i_ddi_caut_put64; ap->ahi_rep_put8 = i_ddi_caut_rep_put8; ap->ahi_rep_put16 = i_ddi_caut_rep_put16; ap->ahi_rep_put32 = i_ddi_caut_rep_put32; ap->ahi_rep_put64 = i_ddi_caut_rep_put64; } else { ap->ahi_put8 = pci_config_wr8; ap->ahi_put16 = pci_config_wr16; ap->ahi_put32 = pci_config_wr32; ap->ahi_put64 = pci_config_wr64; ap->ahi_rep_put8 = pci_config_rep_wr8; ap->ahi_rep_put16 = pci_config_rep_wr16; ap->ahi_rep_put32 = pci_config_rep_wr32; ap->ahi_rep_put64 = pci_config_rep_wr64; } /* Initialize to default check/notify functions */ ap->ahi_fault_check = i_ddi_acc_fault_check; ap->ahi_fault_notify = i_ddi_acc_fault_notify; ap->ahi_fault = 0; impl_acc_err_init(hp); return (DDI_SUCCESS); } int pci_common_ctlops_peek(peekpoke_ctlops_t *in_args) { size_t size = in_args->size; uintptr_t dev_addr = in_args->dev_addr; uintptr_t host_addr = in_args->host_addr; ddi_acc_impl_t *hp = (ddi_acc_impl_t *)in_args->handle; ddi_acc_hdl_t *hdlp = (ddi_acc_hdl_t *)in_args->handle; size_t repcount = in_args->repcount; uint_t flags = in_args->flags; int err = DDI_SUCCESS; /* * if no handle then this is a peek. We have to return failure here * as we have no way of knowing whether this is a MEM or IO space access */ if (in_args->handle == NULL) return (DDI_FAILURE); for (; repcount; repcount--) { if (hp->ahi_acc_attr == DDI_ACCATTR_CONFIG_SPACE) { switch (size) { case sizeof (uint8_t): *(uint8_t *)host_addr = pci_config_rd8(hp, (uint8_t *)dev_addr); break; case sizeof (uint16_t): *(uint16_t *)host_addr = pci_config_rd16(hp, (uint16_t *)dev_addr); break; case sizeof (uint32_t): *(uint32_t *)host_addr = pci_config_rd32(hp, (uint32_t *)dev_addr); break; case sizeof (uint64_t): *(uint64_t *)host_addr = pci_config_rd64(hp, (uint64_t *)dev_addr); break; default: err = DDI_FAILURE; break; } } else if (hp->ahi_acc_attr & DDI_ACCATTR_IO_SPACE) { if (hdlp->ah_acc.devacc_attr_endian_flags == DDI_STRUCTURE_BE_ACC) { switch (size) { case sizeof (uint8_t): *(uint8_t *)host_addr = i_ddi_io_get8(hp, (uint8_t *)dev_addr); break; case sizeof (uint16_t): *(uint16_t *)host_addr = i_ddi_io_swap_get16(hp, (uint16_t *)dev_addr); break; case sizeof (uint32_t): *(uint32_t *)host_addr = i_ddi_io_swap_get32(hp, (uint32_t *)dev_addr); break; /* * note the 64-bit case is a dummy * function - so no need to swap */ case sizeof (uint64_t): *(uint64_t *)host_addr = i_ddi_io_get64(hp, (uint64_t *)dev_addr); break; default: err = DDI_FAILURE; break; } } else { switch (size) { case sizeof (uint8_t): *(uint8_t *)host_addr = i_ddi_io_get8(hp, (uint8_t *)dev_addr); break; case sizeof (uint16_t): *(uint16_t *)host_addr = i_ddi_io_get16(hp, (uint16_t *)dev_addr); break; case sizeof (uint32_t): *(uint32_t *)host_addr = i_ddi_io_get32(hp, (uint32_t *)dev_addr); break; case sizeof (uint64_t): *(uint64_t *)host_addr = i_ddi_io_get64(hp, (uint64_t *)dev_addr); break; default: err = DDI_FAILURE; break; } } } else { if (hdlp->ah_acc.devacc_attr_endian_flags == DDI_STRUCTURE_BE_ACC) { switch (in_args->size) { case sizeof (uint8_t): *(uint8_t *)host_addr = *(uint8_t *)dev_addr; break; case sizeof (uint16_t): *(uint16_t *)host_addr = ddi_swap16(*(uint16_t *)dev_addr); break; case sizeof (uint32_t): *(uint32_t *)host_addr = ddi_swap32(*(uint32_t *)dev_addr); break; case sizeof (uint64_t): *(uint64_t *)host_addr = ddi_swap64(*(uint64_t *)dev_addr); break; default: err = DDI_FAILURE; break; } } else { switch (in_args->size) { case sizeof (uint8_t): *(uint8_t *)host_addr = *(uint8_t *)dev_addr; break; case sizeof (uint16_t): *(uint16_t *)host_addr = *(uint16_t *)dev_addr; break; case sizeof (uint32_t): *(uint32_t *)host_addr = *(uint32_t *)dev_addr; break; case sizeof (uint64_t): *(uint64_t *)host_addr = *(uint64_t *)dev_addr; break; default: err = DDI_FAILURE; break; } } } host_addr += size; if (flags == DDI_DEV_AUTOINCR) dev_addr += size; } return (err); } /*ARGSUSED*/ int pci_common_peekpoke(dev_info_t *dip, dev_info_t *rdip, ddi_ctl_enum_t ctlop, void *arg, void *result) { if (ctlop == DDI_CTLOPS_PEEK) return (pci_common_ctlops_peek((peekpoke_ctlops_t *)arg)); else return (pci_common_ctlops_poke((peekpoke_ctlops_t *)arg)); } /* * These are the get and put functions to be shared with drivers. The * mutex locking is done inside the functions referenced, rather than * here, and is thus shared across PCI child drivers and any other * consumers of PCI config space (such as the ACPI subsystem). * * The configuration space addresses come in as pointers. This is fine on * a 32-bit system, where the VM space and configuration space are the same * size. It's not such a good idea on a 64-bit system, where memory * addresses are twice as large as configuration space addresses. At some * point in the call tree we need to take a stand and say "you are 32-bit * from this time forth", and this seems like a nice self-contained place. */ uint8_t pci_config_rd8(ddi_acc_impl_t *hdlp, uint8_t *addr) { pci_acc_cfblk_t *cfp; uint8_t rval; int reg; ASSERT64(((uintptr_t)addr >> 32) == 0); reg = (int)(uintptr_t)addr; cfp = (pci_acc_cfblk_t *)&hdlp->ahi_common.ah_bus_private; rval = (*pci_getb_func)(cfp->c_busnum, cfp->c_devnum, cfp->c_funcnum, reg); return (rval); } void pci_config_rep_rd8(ddi_acc_impl_t *hdlp, uint8_t *host_addr, uint8_t *dev_addr, size_t repcount, uint_t flags) { uint8_t *h, *d; h = host_addr; d = dev_addr; if (flags == DDI_DEV_AUTOINCR) for (; repcount; repcount--) *h++ = pci_config_rd8(hdlp, d++); else for (; repcount; repcount--) *h++ = pci_config_rd8(hdlp, d); } uint16_t pci_config_rd16(ddi_acc_impl_t *hdlp, uint16_t *addr) { pci_acc_cfblk_t *cfp; uint16_t rval; int reg; ASSERT64(((uintptr_t)addr >> 32) == 0); reg = (int)(uintptr_t)addr; cfp = (pci_acc_cfblk_t *)&hdlp->ahi_common.ah_bus_private; rval = (*pci_getw_func)(cfp->c_busnum, cfp->c_devnum, cfp->c_funcnum, reg); return (rval); } void pci_config_rep_rd16(ddi_acc_impl_t *hdlp, uint16_t *host_addr, uint16_t *dev_addr, size_t repcount, uint_t flags) { uint16_t *h, *d; h = host_addr; d = dev_addr; if (flags == DDI_DEV_AUTOINCR) for (; repcount; repcount--) *h++ = pci_config_rd16(hdlp, d++); else for (; repcount; repcount--) *h++ = pci_config_rd16(hdlp, d); } uint32_t pci_config_rd32(ddi_acc_impl_t *hdlp, uint32_t *addr) { pci_acc_cfblk_t *cfp; uint32_t rval; int reg; ASSERT64(((uintptr_t)addr >> 32) == 0); reg = (int)(uintptr_t)addr; cfp = (pci_acc_cfblk_t *)&hdlp->ahi_common.ah_bus_private; rval = (*pci_getl_func)(cfp->c_busnum, cfp->c_devnum, cfp->c_funcnum, reg); return (rval); } void pci_config_rep_rd32(ddi_acc_impl_t *hdlp, uint32_t *host_addr, uint32_t *dev_addr, size_t repcount, uint_t flags) { uint32_t *h, *d; h = host_addr; d = dev_addr; if (flags == DDI_DEV_AUTOINCR) for (; repcount; repcount--) *h++ = pci_config_rd32(hdlp, d++); else for (; repcount; repcount--) *h++ = pci_config_rd32(hdlp, d); } void pci_config_wr8(ddi_acc_impl_t *hdlp, uint8_t *addr, uint8_t value) { pci_acc_cfblk_t *cfp; int reg; ASSERT64(((uintptr_t)addr >> 32) == 0); reg = (int)(uintptr_t)addr; cfp = (pci_acc_cfblk_t *)&hdlp->ahi_common.ah_bus_private; (*pci_putb_func)(cfp->c_busnum, cfp->c_devnum, cfp->c_funcnum, reg, value); } void pci_config_rep_wr8(ddi_acc_impl_t *hdlp, uint8_t *host_addr, uint8_t *dev_addr, size_t repcount, uint_t flags) { uint8_t *h, *d; h = host_addr; d = dev_addr; if (flags == DDI_DEV_AUTOINCR) for (; repcount; repcount--) pci_config_wr8(hdlp, d++, *h++); else for (; repcount; repcount--) pci_config_wr8(hdlp, d, *h++); } void pci_config_wr16(ddi_acc_impl_t *hdlp, uint16_t *addr, uint16_t value) { pci_acc_cfblk_t *cfp; int reg; ASSERT64(((uintptr_t)addr >> 32) == 0); reg = (int)(uintptr_t)addr; cfp = (pci_acc_cfblk_t *)&hdlp->ahi_common.ah_bus_private; (*pci_putw_func)(cfp->c_busnum, cfp->c_devnum, cfp->c_funcnum, reg, value); } void pci_config_rep_wr16(ddi_acc_impl_t *hdlp, uint16_t *host_addr, uint16_t *dev_addr, size_t repcount, uint_t flags) { uint16_t *h, *d; h = host_addr; d = dev_addr; if (flags == DDI_DEV_AUTOINCR) for (; repcount; repcount--) pci_config_wr16(hdlp, d++, *h++); else for (; repcount; repcount--) pci_config_wr16(hdlp, d, *h++); } void pci_config_wr32(ddi_acc_impl_t *hdlp, uint32_t *addr, uint32_t value) { pci_acc_cfblk_t *cfp; int reg; ASSERT64(((uintptr_t)addr >> 32) == 0); reg = (int)(uintptr_t)addr; cfp = (pci_acc_cfblk_t *)&hdlp->ahi_common.ah_bus_private; (*pci_putl_func)(cfp->c_busnum, cfp->c_devnum, cfp->c_funcnum, reg, value); } void pci_config_rep_wr32(ddi_acc_impl_t *hdlp, uint32_t *host_addr, uint32_t *dev_addr, size_t repcount, uint_t flags) { uint32_t *h, *d; h = host_addr; d = dev_addr; if (flags == DDI_DEV_AUTOINCR) for (; repcount; repcount--) pci_config_wr32(hdlp, d++, *h++); else for (; repcount; repcount--) pci_config_wr32(hdlp, d, *h++); } uint64_t pci_config_rd64(ddi_acc_impl_t *hdlp, uint64_t *addr) { uint32_t lw_val; uint32_t hi_val; uint32_t *dp; uint64_t val; dp = (uint32_t *)addr; lw_val = pci_config_rd32(hdlp, dp); dp++; hi_val = pci_config_rd32(hdlp, dp); val = ((uint64_t)hi_val << 32) | lw_val; return (val); } void pci_config_wr64(ddi_acc_impl_t *hdlp, uint64_t *addr, uint64_t value) { uint32_t lw_val; uint32_t hi_val; uint32_t *dp; dp = (uint32_t *)addr; lw_val = (uint32_t)(value & 0xffffffff); hi_val = (uint32_t)(value >> 32); pci_config_wr32(hdlp, dp, lw_val); dp++; pci_config_wr32(hdlp, dp, hi_val); } void pci_config_rep_rd64(ddi_acc_impl_t *hdlp, uint64_t *host_addr, uint64_t *dev_addr, size_t repcount, uint_t flags) { if (flags == DDI_DEV_AUTOINCR) { for (; repcount; repcount--) *host_addr++ = pci_config_rd64(hdlp, dev_addr++); } else { for (; repcount; repcount--) *host_addr++ = pci_config_rd64(hdlp, dev_addr); } } void pci_config_rep_wr64(ddi_acc_impl_t *hdlp, uint64_t *host_addr, uint64_t *dev_addr, size_t repcount, uint_t flags) { if (flags == DDI_DEV_AUTOINCR) { for (; repcount; repcount--) pci_config_wr64(hdlp, host_addr++, *dev_addr++); } else { for (; repcount; repcount--) pci_config_wr64(hdlp, host_addr++, *dev_addr); } } /* * 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 * or http://www.opensolaris.org/os/licensing. * 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. * * Copyright 2019 Joyent, Inc. */ #ifndef _PCI_PCI_COMMON_H #define _PCI_PCI_COMMON_H #ifdef __cplusplus extern "C" { #endif /* * Common header file with definitions shared between * pci(4D) and npe(4D) */ /* State structure. */ typedef struct pci_state { dev_info_t *pci_dip; int pci_fmcap; uint_t pci_soft_state; ddi_iblock_cookie_t pci_fm_ibc; kmutex_t pci_mutex; kmutex_t pci_peek_poke_mutex; kmutex_t pci_err_mutex; /* * The following members are only used by npe(4D). * See uts/i86pc/io/pciex/npe.c for more information. */ ndi_event_hdl_t pci_ndi_event_hdl; } pci_state_t; /* * These are the access routines. * The pci_bus_map sets the handle to point to these in pci(4D). * The npe_bus_map sets the handle to point to these in npe(4D). */ uint8_t pci_config_rd8(ddi_acc_impl_t *hdlp, uint8_t *addr); uint16_t pci_config_rd16(ddi_acc_impl_t *hdlp, uint16_t *addr); uint32_t pci_config_rd32(ddi_acc_impl_t *hdlp, uint32_t *addr); uint64_t pci_config_rd64(ddi_acc_impl_t *hdlp, uint64_t *addr); void pci_config_wr8(ddi_acc_impl_t *hdlp, uint8_t *addr, uint8_t value); void pci_config_wr16(ddi_acc_impl_t *hdlp, uint16_t *addr, uint16_t value); void pci_config_wr32(ddi_acc_impl_t *hdlp, uint32_t *addr, uint32_t value); void pci_config_wr64(ddi_acc_impl_t *hdlp, uint64_t *addr, uint64_t value); void pci_config_rep_rd8(ddi_acc_impl_t *hdlp, uint8_t *host_addr, uint8_t *dev_addr, size_t repcount, uint_t flags); void pci_config_rep_rd16(ddi_acc_impl_t *hdlp, uint16_t *host_addr, uint16_t *dev_addr, size_t repcount, uint_t flags); void pci_config_rep_rd32(ddi_acc_impl_t *hdlp, uint32_t *host_addr, uint32_t *dev_addr, size_t repcount, uint_t flags); void pci_config_rep_rd64(ddi_acc_impl_t *hdlp, uint64_t *host_addr, uint64_t *dev_addr, size_t repcount, uint_t flags); void pci_config_rep_wr8(ddi_acc_impl_t *hdlp, uint8_t *host_addr, uint8_t *dev_addr, size_t repcount, uint_t flags); void pci_config_rep_wr16(ddi_acc_impl_t *hdlp, uint16_t *host_addr, uint16_t *dev_addr, size_t repcount, uint_t flags); void pci_config_rep_wr32(ddi_acc_impl_t *hdlp, uint32_t *host_addr, uint32_t *dev_addr, size_t repcount, uint_t flags); void pci_config_rep_wr64(ddi_acc_impl_t *hdlp, uint64_t *host_addr, uint64_t *dev_addr, size_t repcount, uint_t flags); /* * PCI tool related declarations */ int pci_common_ioctl(dev_info_t *dip, dev_t dev, int cmd, intptr_t arg, int mode, cred_t *credp, int *rvalp); /* * Interrupt related declaration */ int pci_common_intr_ops(dev_info_t *, dev_info_t *, ddi_intr_op_t, ddi_intr_handle_impl_t *, void *); void pci_common_set_parent_private_data(dev_info_t *); /* * Miscellaneous library functions */ int pci_common_get_reg_prop(dev_info_t *dip, pci_regspec_t *pci_rp); int pci_common_name_child(dev_info_t *child, char *name, int namelen); int pci_common_peekpoke(dev_info_t *dip, dev_info_t *rdip, ddi_ctl_enum_t ctlop, void *arg, void *result); int pci_fm_acc_setup(ddi_acc_hdl_t *hp, off_t offset, off_t len); #ifdef __cplusplus } #endif #endif /* _PCI_PCI_COMMON_H */ /* * 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 * or http://www.opensolaris.org/os/licensing. * 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 (c) 2005, 2010, Oracle and/or its affiliates. All rights reserved. */ /* * Kstat support for X86 PCI driver */ #include #include #include #include #include #include typedef struct pci_kstat_private { ddi_intr_handle_impl_t *hdlp; dev_info_t *rootnex_dip; } pci_kstat_private_t; static struct { kstat_named_t ihks_name; kstat_named_t ihks_type; kstat_named_t ihks_cpu; kstat_named_t ihks_pil; kstat_named_t ihks_time; kstat_named_t ihks_ino; kstat_named_t ihks_cookie; kstat_named_t ihks_devpath; kstat_named_t ihks_buspath; } pci_ks_template = { { "name", KSTAT_DATA_CHAR }, { "type", KSTAT_DATA_CHAR }, { "cpu", KSTAT_DATA_UINT64 }, { "pil", KSTAT_DATA_UINT64 }, { "time", KSTAT_DATA_UINT64 }, { "ino", KSTAT_DATA_UINT64 }, { "cookie", KSTAT_DATA_UINT64 }, { "devpath", KSTAT_DATA_STRING }, { "buspath", KSTAT_DATA_STRING }, }; static char ih_devpath[MAXPATHLEN]; static char ih_buspath[MAXPATHLEN]; static uint32_t pci_ks_inst; static kmutex_t pci_ks_template_lock; extern int (*psm_intr_ops)(dev_info_t *, ddi_intr_handle_impl_t *, psm_intr_op_t, int *); /*ARGSUSED*/ static int pci_ih_ks_update(kstat_t *ksp, int rw) { pci_kstat_private_t *private_data = (pci_kstat_private_t *)ksp->ks_private; dev_info_t *rootnex_dip = private_data->rootnex_dip; ddi_intr_handle_impl_t tmp_hdl, *ih_p = private_data->hdlp; dev_info_t *dip = ih_p->ih_dip; int maxlen = sizeof (pci_ks_template.ihks_name.value.c); apic_get_intr_t intrinfo; (void) snprintf(pci_ks_template.ihks_name.value.c, maxlen, "%s%d", ddi_driver_name(dip), ddi_get_instance(dip)); (void) ddi_pathname(dip, ih_devpath); (void) ddi_pathname(rootnex_dip, ih_buspath); kstat_named_setstr(&pci_ks_template.ihks_devpath, ih_devpath); kstat_named_setstr(&pci_ks_template.ihks_buspath, ih_buspath); /* * Note that although possibly multiple vectors can map to an IRQ, the * vector returned below will always be the same for a given IRQ * specified, and so all kstats for a given IRQ will report the same * value for the ino field. * * --- * * Check for the enabled state, since kstats are set up when the ISR is * added, not enabled. There may be a period where interrupts are not * enabled even though they may have been added. * * It is also possible that the vector is for a dummy interrupt. * pci_get_intr_from_vecirq will return failure in this case. */ bcopy(ih_p, &tmp_hdl, sizeof (ddi_intr_handle_impl_t)); tmp_hdl.ih_private = (void *)&intrinfo; intrinfo.avgi_cpu_id = 0; /* In case psm_intr_ops fails */ intrinfo.avgi_req_flags = PSMGI_REQ_CPUID | PSMGI_REQ_VECTOR; intrinfo.avgi_req_flags |= PSMGI_INTRBY_DEFAULT; if ((ih_p->ih_state != DDI_IHDL_STATE_ENABLE) || ((*psm_intr_ops)(NULL, &tmp_hdl, PSM_INTR_OP_GET_INTR, NULL) != DDI_SUCCESS) || (intrinfo.avgi_cpu_id & PSMGI_CPU_FLAGS)) { (void) strcpy(pci_ks_template.ihks_type.value.c, "disabled"); pci_ks_template.ihks_pil.value.ui64 = 0; pci_ks_template.ihks_time.value.ui64 = 0; pci_ks_template.ihks_cookie.value.ui64 = 0; pci_ks_template.ihks_cpu.value.ui64 = 0; pci_ks_template.ihks_ino.value.ui64 = 0; /* Interrupt is user-bound. Remove kstat. */ if (intrinfo.avgi_cpu_id & PSMGI_CPU_FLAGS) (void) taskq_dispatch(system_taskq, (void (*)(void *))pci_kstat_delete, ksp, TQ_SLEEP); return (0); } /* * Interrupt is valid (not a dummy), not user-bound to a specific cpu, * and enabled. Update kstat fields. */ switch (ih_p->ih_type) { case DDI_INTR_TYPE_MSI: (void) strcpy(pci_ks_template.ihks_type.value.c, "msi"); break; case DDI_INTR_TYPE_MSIX: (void) strcpy(pci_ks_template.ihks_type.value.c, "msix"); break; default: (void) strcpy(pci_ks_template.ihks_type.value.c, "fixed"); break; } pci_ks_template.ihks_pil.value.ui64 = ih_p->ih_pri; pci_ks_template.ihks_time.value.ui64 = ((ihdl_plat_t *)ih_p->ih_private)->ip_ticks; scalehrtime((hrtime_t *)&pci_ks_template.ihks_time.value.ui64); pci_ks_template.ihks_cookie.value.ui64 = ih_p->ih_vector; /* CPU won't be user bound at this point. */ pci_ks_template.ihks_cpu.value.ui64 = intrinfo.avgi_cpu_id; pci_ks_template.ihks_ino.value.ui64 = intrinfo.avgi_vector; return (0); } void pci_kstat_create(kstat_t **kspp, dev_info_t *rootnex_dip, ddi_intr_handle_impl_t *hdlp) { pci_kstat_private_t *private_data; *kspp = kstat_create("pci_intrs", atomic_inc_32_nv(&pci_ks_inst), _MODULE_NAME, "interrupts", KSTAT_TYPE_NAMED, sizeof (pci_ks_template) / sizeof (kstat_named_t), KSTAT_FLAG_VIRTUAL); if (*kspp != NULL) { private_data = kmem_zalloc(sizeof (pci_kstat_private_t), KM_SLEEP); private_data->hdlp = hdlp; private_data->rootnex_dip = rootnex_dip; (*kspp)->ks_private = private_data; (*kspp)->ks_data_size += MAXPATHLEN * 2; (*kspp)->ks_lock = &pci_ks_template_lock; (*kspp)->ks_data = &pci_ks_template; (*kspp)->ks_update = pci_ih_ks_update; kstat_install(*kspp); } } /* * This function is invoked in two ways: * - Thru taskq thread via pci_ih_ks_update to remove kstats for user-bound * interrupts. * - From the REMISR introp when an interrupt is being removed. */ void pci_kstat_delete(kstat_t *ksp) { pci_kstat_private_t *kstat_private; ddi_intr_handle_impl_t *hdlp; if (ksp) { kstat_private = ksp->ks_private; hdlp = kstat_private->hdlp; ((ihdl_plat_t *)hdlp->ih_private)->ip_ksp = NULL; /* * Delete the kstat before removing the private pointer, to * prevent a kstat update from coming after private is freed. */ kstat_delete(ksp); kmem_free(kstat_private, sizeof (pci_kstat_private_t)); } } /* * 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 * or http://www.opensolaris.org/os/licensing. * 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 (c) 2005, 2010, Oracle and/or its affiliates. All rights reserved. * Copyright 2016 Joyent, Inc. * Copyright 2019 Western Digital Corporation * Copyright 2020 OmniOS Community Edition (OmniOSce) Association. * Copyright 2024 Oxide Computer Company */ /* * This file contains the x86 PCI platform resource discovery backend. This uses * data from a combination of sources, preferring ACPI, if present, and if not, * falling back to either the PCI hot-plug resource table or the mps tables. * * Today, to get information from ACPI we need to start from a dev_info_t. This * is partly why the PRD interface has a callback for getting information about * a dev_info_t. It also means we cannot initialize the tables with information * until all devices have been initially scanned. */ #include #include #include #include #include #include #include #include #include #include #include #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); /* * -1 = attempt ACPI resource discovery * 0 = don't attempt ACPI resource discovery * 1 = ACPI resource discovery successful */ 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]; /* * This indicates whether or not we have a traditional x86 BIOS present or not. */ static boolean_t pci_prd_have_bios = B_TRUE; /* * This value is set up as part of PCI configuration space initialization. */ 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(); } } /* * Trim overlapping bus ranges in acpi_bus_res[] * Some BIOSes report root-bridges with bus ranges that * overlap, for example:"0..255" and "8..255". Lower-numbered * ranges are trimmed by upper-numbered ranges (so "0..255" would * be trimmed to "0..7", in the example). */ static void acpi_trim_bus_ranges(void) { struct memlist *ranges, *current; int bus; ranges = NULL; /* * Assumptions: * - there exists at most 1 bus range entry for each bus number * - there are no (broken) ranges that start at the same bus number */ for (bus = 0; bus < PCI_MAX_BUS_NUM; bus++) { struct memlist *prev, *orig, *new; /* skip buses with no range entry */ if ((orig = acpi_bus_res[bus]) == NULL) continue; /* * create copy of existing range and overload * 'prev' pointer to link existing to new copy */ new = pci_memlist_alloc(); new->ml_address = orig->ml_address; new->ml_size = orig->ml_size; new->ml_prev = orig; /* sorted insertion of 'new' into ranges list */ for (current = ranges, prev = NULL; current != NULL; prev = current, current = current->ml_next) if (new->ml_address < current->ml_address) break; if (prev == NULL) { /* place at beginning of (possibly) empty list */ new->ml_next = ranges; ranges = new; } else { /* place in list (possibly at end) */ new->ml_next = current; prev->ml_next = new; } } /* scan the list, perform trimming */ current = ranges; while (current != NULL) { struct memlist *next = current->ml_next; /* done when no range above current */ if (next == NULL) break; /* * trim size in original range element * (current->ml_prev points to the original range) */ if ((current->ml_address + current->ml_size) > next->ml_address) current->ml_prev->ml_size = next->ml_address - current->ml_address; current = next; } /* discard the list */ pci_memlist_free_all(&ranges); /* OK if ranges == NULL */ } 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; } /* pci_memlist_count() treats NULL head as zero-length */ 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; /* ignore consumed resources */ if (rp->Data.Address.ProducerConsumer == 1) return (AE_OK); switch (rp->Type) { case ACPI_RESOURCE_TYPE_IRQ: /* never expect to see a PCI bus produce an Interrupt */ dprintf("%s\n", "IRQ"); break; case ACPI_RESOURCE_TYPE_DMA: /* never expect to see a PCI bus produce 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: /* only expect to see this as a consumer */ 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: /* only expect to see this as a consumer */ dprintf("%s\n", "MEMORY24"); break; case ACPI_RESOURCE_TYPE_MEMORY32: /* only expect to see this as a consumer */ dprintf("%s\n", "MEMORY32"); break; case ACPI_RESOURCE_TYPE_FIXED_MEMORY32: /* only expect to see this as a consumer */ 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: /* never expect to see a PCI bus produce an Interrupt */ dprintf("%s\n", "EXTENDED_IRQ"); break; case ACPI_RESOURCE_TYPE_GENERIC_REGISTER: /* never expect to see a PCI bus produce an GAS */ 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) { /* check "PCMP" signature */ 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) { /* not MPSpec rev 1.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; /* Search for the "_MP_" or "$HRT" signature */ 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; /* * Use AcpiGetObjectInfo() to find the device _HID * If not a PCI root-bus, ignore this device and continue * the walk */ 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); /* * acpica_get_busno() will check the presence of _BBN and * fail if not present. It will then use the _CRS method to * retrieve the actual bus number assigned, it will fall back * to _BBN should the _CRS method fail. */ if (ACPI_SUCCESS(acpica_get_busno(hdl, &busnum))) { /* * Ignore invalid _BBN return values here (rather * than panic) and emit a warning; something else * may suffer failure as a result of the broken BIOS. */ 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); } /* PCI and no _BBN, continue walk */ 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; /* * First scan ACPI devices for anything that might be here. After that, * go through and check the old BIOS IRQ routing table for additional * buses. Note, slot naming from the IRQ table comes later. */ (void) AcpiGetDevices(NULL, pci_process_acpi_device, &cb, &rv); pci_bios_bus_iter(func, arg); } /* * If there is actually a PCI IRQ routing table present, then we want to use * this to go back and update the slot name. In particular, if we have no PCI * IRQ routing table, then we use the existing slot names that were already set * up for us in picex_slot_names_prop() from the capability register. Otherwise, * we actually delete all slot-names properties from buses and instead use * something from the IRQ routing table if it exists. * * Note, the property is always deleted regardless of whether or not it exists * in the IRQ routing table. Finally, we have traditionally kept "pcie0" names * as special as apparently that can't be represented in the IRQ routing table. */ 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); } /* * These compatibility flags generally exist for i86pc. We need to still * enumerate ISA bridges and the naming of device nodes and aliases must be kept * consistent lest we break boot. See uts/common/io/pciex/pci_props.c theory * statement for more information. */ 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)); } /* * 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 * or http://www.opensolaris.org/os/licensing. * 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 (c) 2005, 2010, Oracle and/or its affiliates. All rights reserved. */ /* * Copyright 2026 Oxide Computer Company */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef __xpv #include #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; /* Max offset allowed into config space for a particular device. */ 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 declarations */ 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); /* Create pcitool nodes for register access and interrupt routing. */ 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); } /*ARGSUSED*/ 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; /* Version 1 of pcitool_intr_set_t doesn't have flags. */ 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; /* * 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 (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: /* Error making the change */ rval = EIO; iset.status = PCITOOL_IO_ERROR; break; case ENXIO: /* Couldn't convert vector to irq */ rval = EINVAL; iset.status = PCITOOL_INVALID_INO; break; case EINVAL: /* CPU out of range */ rval = EINVAL; iset.status = PCITOOL_INVALID_CPUID; break; case ENOTSUP: /* Requested PSM intr ops missing */ rval = ENOTSUP; iset.status = PCITOOL_IO_ERROR; break; } } /* Return original CPU. */ iset.cpu_id = old_cpu; /* Return new vector */ 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); } /* It is assumed that dip != NULL */ 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) { /* Array part isn't used here, but oh well... */ 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; /* Read in just the header part, no array section. */ 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; } } /* Validate argument. */ 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; /* * 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. */ 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; } /* Caller wants device information returned. */ if (num_devs_ret > 0) { intr_info.avgi_req_flags |= PSMGI_REQ_GET_DEVS; /* * Allocate room. * If num_devs_ret == 0 iget remains pointing to partial_iget. */ iget_kmem_alloc_size = PCITOOL_IGET_SIZE(num_devs_ret); iget = kmem_alloc(iget_kmem_alloc_size, KM_SLEEP); /* Read in whole structure to verify there's room. */ if (ddi_copyin(arg, iget, iget_kmem_alloc_size, mode) != SUCCESS) { /* Be consistent and just return EFAULT here. */ kmem_free(iget, iget_kmem_alloc_size); return (EFAULT); } } bzero(iget, PCITOOL_IGET_SIZE(num_devs_ret)); iget->ino = info_hdl.ih_vector; /* * Lock device tree branch from the pci root nexus on down if info will * be extracted from dips returned from the tree. */ if (intr_info.avgi_req_flags & PSMGI_REQ_GET_DEVS) { ndi_devi_enter(dip); } /* Call psm_intr_ops(PSM_INTR_OP_GET_INTR) to get information. */ 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; } /* * Fill in the pcitool_intr_get_t to be returned, * with the CPU, num_devs_ret and num_devs. */ 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; /* Number of devices returned by apic. */ iget->num_devs = intr_info.avgi_num_devs; /* Device info was returned. */ if (intr_info.avgi_req_flags & PSMGI_REQ_GET_DEVS) { /* * num devs returned is num devs ret by apic, * space permitting. */ iget->num_devs_ret = min(num_devs_ret, intr_info.avgi_num_devs); /* * 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++) pcitool_get_intr_dev_info(intr_info.avgi_dip_list[i], &iget->dev[i]); /* Free kmem_alloc'ed memory of the apic_get_intr_t */ 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); } /*ARGSUSED*/ 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 we need user_version, and to ret same user version as passed in */ 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; /* For UPPC systems, psm_intr_ops has no entry for APIC_TYPE. */ 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); } /* * Main function for handling interrupt CPU binding requests and queries. * Need to implement later */ int pcitool_intr_admn(dev_info_t *dip, void *arg, int cmd, int mode) { int rval; switch (cmd) { /* Associate a new CPU with a given vector */ 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); } /* * Perform register accesses on the nexus device itself. * No explicit PCI nexus device for X86, so not applicable. */ /*ARGSUSED*/ int pcitool_bus_reg_ops(dev_info_t *dip, void *arg, int cmd, int mode) { return (ENOTSUP); } /* Swap endianness. */ 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); } /* * 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 for I/O and mem * space accessing (not for pci config space), in case future X86 platforms * require it. */ /* Access device. prg is modified. */ 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); } /* * 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 -1 on error. */ 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; } } /* * Check if legacy I/O config access is used, in which case the valid * range varies with the I/O space mechanism used. */ if (req.ioacc && (prg->offset + size - 1 > pci_iocfg_max_offset)) { prg->status = PCITOOL_INVALID_ADDRESS; return (ENOTSUP); } /* Set phys_addr only if MMIO is used */ 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; /* * 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. */ 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; /* * 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. */ 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); } /* * 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 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; /* Desired mapping would span more than two pages. */ 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)++; } /* Get page(s) of virtual space. */ 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 /* * 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. */ ASSERT(DOMAIN_IS_INITDOMAIN(xen_info)); pfn = xen_assign_pfn(mmu_btop(page_base)); #else pfn = btop(page_base); #endif /* Now map the allocated virtual space to the physical address. */ 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); } /* Unmap the mapped page(s). */ 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; /* * Skip BARs which return an invalid read. Historical versions * of this code also did the same when the BAR was 0; however, * that ignored the fact that you could have the prefetch bit or * similar things set so we defer that until we have read the * entire data. */ if (raw[idx] == PCI_EINVAL32) continue; /* * If we encounter a 64-bit BAR that ends up counting for two * different entries. Determine if that's the case and we need * to make sure the next loop accounts for this before we check * if this matches. */ 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); } } /* * A value of zero is the hardware reset value. It is also * always an invalid BAR value on x86 as on all platforms this * never refers to MMIO space. This is generally true in * practice for I/O ports, but less of a guarantee. If we find * I/O port assignments starting at 0 then we should come back * to this and revisit it. */ 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; /* * Our job is to evaluate if this dip is the one that we're looking for based * upon its PCI b/d/f. If it is, then we need to look at its * assigned-addresses[] and bind the size of the corresponding bar. We need to * be careful we don't recurse into non-PCI children. If this isn't an instance * of pcieb or pci_pci, then we will not have additional PCI children and * therefore must prune. */ 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); } /* * Perform register accesses on PCI leaf devices and intermediate bridges. */ 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); } /* * Initially see if this is a known argument. We'll deal with checking * the actual upper bound based on the device type later on. */ if (prg.barnum > PCITOOL_ROM) { prg.status = PCITOOL_OUT_OF_RANGE; rval = EINVAL; goto copyout; } /* Validate address arguments of bus / dev / func */ 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 we have access to extended configuration space then we're not * going to be using I/O ports. */ if (max_cfg_size == PCIE_CONF_HDR_SIZE) cfgspace_io = B_FALSE; /* * First see if this is configuration space. If so, this is the simplest * case that we have. */ 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; } /* * This isn't configuration space. We must check to see what the device * type is. The device type will determine the number of BARs that are * valid and whether or not we can even begin to assume that a ROM is * present. To do this we must read from configuration space. We do this * by setting up a duplicate register access method and treating this * like a configuration space read. */ 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; } /* * Bridges only have access to two BARs. This isn't the normal >= due to * the fact that bridge numbers are offset by one. The ioctl treats the * value zero as configuration space. */ if (bridge && prg.barnum > PCI_BCNF_BASE_NUM) { prg.status = PCITOOL_OUT_OF_RANGE; rval = EINVAL; goto copyout; } /* * We need to see if this BAR number corresponds to something that * exists. To do this we must walk all of the BARs in order as we may * have a 64-bit BAR which requires two entries. If this is an expansion * ROM, while it will get treated like a 32-bit BAR, we need to check a * few additional things there such as whether it's enabled at all. In * addition, if we have been asked to write it, we must outright fail. */ 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; } /* * The only place that has the size information currently is the * dev_info_t through its reg[] or assigned-addresses[] property. This * is a bit unfortunate as we don't have the dev_info_t for the target * that we want to read. However, we can rely upon an assumption. If we * want to read a BAR and the kernel does not have a dev_info_t meaning * it has not assigned this address, then it's going to be quite odd. * The target device will be downstream of this nexus, meaning that we * can walk the tree here and try to find it. If we can find it, then we * can find the corresponding assigned-addresses[] entry and confirm the * size. */ 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); } /* * 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 * or http://www.opensolaris.org/os/licensing. * 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. */ #ifndef _PCI_TOOLS_EXT_H #define _PCI_TOOLS_EXT_H #ifdef __cplusplus extern "C" { #endif /* This file contains pcitool defs exported to other modules of a PCI driver. */ /* * Functions exported from the pci_tools.c module. */ extern int pcitool_dev_reg_ops(dev_info_t *dip, void *arg, int cmd, int mode); extern int pcitool_bus_reg_ops(dev_info_t *dip, void *arg, int cmd, int mode); extern int pcitool_intr_admn(dev_info_t *dip, void *arg, int cmd, int mode); extern int pcitool_init(dev_info_t *dip, boolean_t is_pciex); extern void pcitool_uninit(dev_info_t *dip); #ifdef __cplusplus } #endif #endif /* _PCI_TOOLS_EXT_H */ /* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License, Version 1.0 only * (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 * or http://www.opensolaris.org/os/licensing. * 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 2005 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. * * PCItool interfaces internal to the i86pc PCI nexus driver. */ #ifndef _PCI_VAR_H #define _PCI_VAR_H #ifdef __cplusplus extern "C" { #endif /* Functions exported from pci_common.c */ #define IS_IRQ B_TRUE #define IS_VEC B_FALSE extern int pci_get_intr_from_vecirq(apic_get_intr_t *intrinfo_p, int vecirq, boolean_t is_irq); extern int pci_get_cpu_from_vecirq(int vecirq, boolean_t is_irq); /* Functions exported from pci_kstats.c */ extern void pci_kstat_create(kstat_t **kspp, dev_info_t *nexus_dip, ddi_intr_handle_impl_t *hdlp); extern void pci_kstat_delete(kstat_t *kspp); #ifdef __cplusplus } #endif #endif /* _PCI_VAR_H */ /* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License, Version 1.0 only * (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 * or http://www.opensolaris.org/os/licensing. * 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 2005 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ /* * pcihrt.h -- PCI Hot-Plug Resource Table */ #ifndef _PCIHRT_H #define _PCIHRT_H #ifdef __cplusplus extern "C" { #endif struct hrt_hdr { /* PCI Hot-Plug Configuration Resource Table header */ uint32_t hrt_sig; /* $HRT */ uint16_t hrt_avail_imap; /* Bitmap of unused IRQs */ uint16_t hrt_used_imap; /* Bitmap of IRQs used by PCI */ uchar_t hrt_entry_cnt; /* no. of PCI hot-plug slot entries */ uchar_t hrt_ver; /* version no. = 1 */ uchar_t hrt_resv0; /* reserved */ uchar_t hrt_resv1; /* reserved */ uchar_t hrt_resv2; /* reserved */ uchar_t hrt_resv3; /* reserved */ uchar_t hrt_resv4; /* reserved */ uchar_t hrt_resv5; /* reserved */ }; struct php_entry { /* PCI hot-plug slot entry */ uchar_t php_devno; /* PCI dev/func no. of the slot */ uchar_t php_pri_bus; /* Primary bus of this slot */ uchar_t php_sec_bus; /* Secondary bus of this slot */ uchar_t php_subord_bus; /* Max Subordinate bus of this slot */ uint16_t php_io_start; /* allocated I/O space starting addr */ uint16_t php_io_size; /* allocated I/O space size in bytes */ uint16_t php_mem_start; /* allocated Memory space start addr */ uint16_t php_mem_size; /* allocated Memory space size in 64k */ uint16_t php_pfmem_start; /* allocated Prefetchable Memory start */ uint16_t php_pfmem_size; /* allocated Prefetchable size in 64k */ }; #ifdef __cplusplus } #endif #endif /* _PCIHRT_H */