/* * 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) 2006, 2010, Oracle and/or its affiliates. All rights reserved. */ /* * Niagara2 Network Interface Unit (NIU) Nexus Driver */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "niumx_var.h" static int niumx_fm_init_child(dev_info_t *, dev_info_t *, int, ddi_iblock_cookie_t *); static int niumx_intr_ops(dev_info_t *dip, dev_info_t *rdip, ddi_intr_op_t intr_op, ddi_intr_handle_impl_t *hdlp, void *result); static int niumx_attach(dev_info_t *devi, ddi_attach_cmd_t cmd); static int niumx_detach(dev_info_t *devi, ddi_detach_cmd_t cmd); static int niumx_set_intr(dev_info_t *dip, dev_info_t *rdip, ddi_intr_handle_impl_t *hdlp, int valid); static int niumx_add_intr(dev_info_t *dip, dev_info_t *rdip, ddi_intr_handle_impl_t *hdlp); static int niumx_rem_intr(dev_info_t *dip, dev_info_t *rdip, ddi_intr_handle_impl_t *hdlp); static uint_t niumx_intr_hdlr(void *arg); static int niumx_map(dev_info_t *dip, dev_info_t *rdip, ddi_map_req_t *mp, off_t offset, off_t len, caddr_t *addrp); static int niumx_dma_allochdl(dev_info_t *dip, dev_info_t *rdip, ddi_dma_attr_t *attrp, int (*waitfp)(caddr_t), caddr_t arg, ddi_dma_handle_t *handlep); static int niumx_dma_freehdl(dev_info_t *dip, dev_info_t *rdip, ddi_dma_handle_t handlep); static int niumx_dma_bindhdl(dev_info_t *dip, dev_info_t *rdip, ddi_dma_handle_t handle, ddi_dma_req_t *dmareq, ddi_dma_cookie_t *cookiep, uint_t *ccountp); static int niumx_dma_unbindhdl(dev_info_t *dip, dev_info_t *rdip, ddi_dma_handle_t handle); static int niumx_ctlops(dev_info_t *dip, dev_info_t *rdip, ddi_ctl_enum_t op, void *arg, void *result); int niumxtool_init(dev_info_t *dip); void niumxtool_uninit(dev_info_t *dip); int niumx_get_intr_target(niumx_devstate_t *niumxds_p, niudevino_t ino, niucpuid_t *cpu_id); int niumx_set_intr_target(niumx_devstate_t *niumxds_p, niudevino_t ino, niucpuid_t cpu_id); static struct bus_ops niumx_bus_ops = { BUSO_REV, niumx_map, 0, 0, 0, i_ddi_map_fault, 0, niumx_dma_allochdl, niumx_dma_freehdl, niumx_dma_bindhdl, niumx_dma_unbindhdl, 0, 0, 0, niumx_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)(); */ niumx_fm_init_child, /* (*bus_fm_init)(); */ 0, /* (*bus_fm_fini)(); */ 0, /* (*bus_enter)() */ 0, /* (*bus_exit)() */ 0, /* (*bus_power)() */ niumx_intr_ops /* (*bus_intr_op)(); */ }; extern struct cb_ops niumx_cb_ops; static struct dev_ops niumx_ops = { DEVO_REV, /* devo_rev */ 0, /* refcnt */ ddi_no_info, /* info */ nulldev, /* identify */ 0, /* probe */ niumx_attach, /* attach */ niumx_detach, /* detach */ nulldev, /* reset */ &niumx_cb_ops, /* driver operations */ &niumx_bus_ops, /* bus operations */ 0, /* power */ ddi_quiesce_not_supported, /* devo_quiesce */ }; /* Module linkage information for the kernel. */ static struct modldrv modldrv = { &mod_driverops, /* Type of module */ "NIU Nexus Driver", &niumx_ops, /* driver ops */ }; static struct modlinkage modlinkage = { MODREV_1, (void *)&modldrv, NULL }; void *niumx_state; /* * forward function declarations: */ static void niumx_removechild(dev_info_t *); static int niumx_initchild(dev_info_t *child); int _init(void) { int e; uint64_t mjrnum; uint64_t mnrnum; /* * Check HV intr group api versioning. * This driver uses the old interrupt routines which are supported * in old firmware in the CORE API group and in newer firmware in * the INTR API group. Support for these calls will be dropped * once the INTR API group major goes to 2. */ if ((hsvc_version(HSVC_GROUP_INTR, &mjrnum, &mnrnum) == 0) && (mjrnum > NIUMX_INTR_MAJOR_VER)) { cmn_err(CE_WARN, "niumx: unsupported intr api group: " "maj:0x%lx, min:0x%lx", mjrnum, mnrnum); return (ENOTSUP); } if ((e = ddi_soft_state_init(&niumx_state, sizeof (niumx_devstate_t), 1)) == 0 && (e = mod_install(&modlinkage)) != 0) ddi_soft_state_fini(&niumx_state); return (e); } int _fini(void) { int e; if ((e = mod_remove(&modlinkage)) == 0) ddi_soft_state_fini(&niumx_state); return (e); } int _info(struct modinfo *modinfop) { return (mod_info(&modlinkage, modinfop)); } hrtime_t niumx_intr_timeout = 2ull * NANOSEC; /* 2 seconds in nanoseconds */ void niumx_intr_dist(void *arg) { niumx_devstate_t *niumxds_p = (niumx_devstate_t *)arg; kmutex_t *lock_p = &niumxds_p->niumx_mutex; int i; niumx_ih_t *ih_p = niumxds_p->niumx_ihtable; DBG(NIUMX_DBG_A_INTX, NULL, "niumx_intr_dist entered\n"); mutex_enter(lock_p); for (i = 0; i < NIUMX_MAX_INTRS; i++, ih_p++) { niusysino_t sysino = ih_p->ih_sysino; niucpuid_t cpuid; int state; hrtime_t start; dev_info_t *dip = ih_p->ih_dip; if (!sysino || (cpuid = intr_dist_cpuid()) == ih_p->ih_cpuid) continue; (void) hvio_intr_setvalid(sysino, HV_INTR_NOTVALID); /* check for pending interrupts, busy wait if so */ for (start = gethrtime(); !panicstr && (hvio_intr_getstate(sysino, &state) == H_EOK) && (state == HV_INTR_DELIVERED_STATE); /* */) { if (gethrtime() - start > niumx_intr_timeout) { cmn_err(CE_WARN, "%s%d: niumx_intr_dist: " "pending interrupt (%x,%lx) timedout\n", ddi_driver_name(dip), ddi_get_instance(dip), ih_p->ih_inum, sysino); (void) hvio_intr_setstate(sysino, HV_INTR_IDLE_STATE); break; } } (void) hvio_intr_settarget(sysino, cpuid); if (ih_p->ih_state == HV_INTR_VALID) (void) hvio_intr_setvalid(sysino, HV_INTR_VALID); else (void) hvio_intr_setvalid(sysino, HV_INTR_NOTVALID); ih_p->ih_cpuid = cpuid; } mutex_exit(lock_p); } static int niumx_attach(dev_info_t *dip, ddi_attach_cmd_t cmd) { int instance = ddi_get_instance(dip); niumx_devstate_t *niumxds_p; /* devstate pointer */ niu_regspec_t *reg_p; niumx_ih_t *ih_p; uint_t reglen; int i, ret = DDI_SUCCESS; switch (cmd) { case DDI_ATTACH: if (ddi_prop_lookup_int_array(DDI_DEV_T_ANY, dip, DDI_PROP_DONTPASS, "reg", (int **)®_p, ®len) != DDI_PROP_SUCCESS) { DBG(NIUMX_DBG_ATTACH, dip, "reg lookup failed\n"); ret = DDI_FAILURE; goto done; } /* * Allocate and get soft state structure. */ if (ddi_soft_state_zalloc(niumx_state, instance) != DDI_SUCCESS) { ret = DDI_FAILURE; goto prop_free; } niumxds_p = (niumx_devstate_t *)ddi_get_soft_state(niumx_state, instance); niumxds_p->dip = dip; niumxds_p->niumx_open_count = 0; mutex_init(&niumxds_p->niumx_mutex, NULL, MUTEX_DRIVER, NULL); DBG(NIUMX_DBG_ATTACH, dip, "soft state alloc'd instance = %d, " "niumxds_p = %p\n", instance, niumxds_p); /* hv devhdl: low 28-bit of 1st "reg" entry's addr.hi */ niumxds_p->niumx_dev_hdl = (niudevhandle_t)(reg_p->addr_high & NIUMX_DEVHDLE_MASK); ih_p = niumxds_p->niumx_ihtable; for (i = 0; i < NIUMX_MAX_INTRS; i++, ih_p++) { ih_p->ih_sysino = 0; ih_p->ih_state = HV_INTR_NOTVALID; } /* add interrupt redistribution callback */ intr_dist_add(niumx_intr_dist, niumxds_p); niumxds_p->niumx_fm_cap = DDI_FM_EREPORT_CAPABLE; ddi_fm_init(niumxds_p->dip, &niumxds_p->niumx_fm_cap, &niumxds_p->niumx_fm_ibc); if (niumxtool_init(dip) != DDI_SUCCESS) { ret = DDI_FAILURE; goto cleanup; } ret = DDI_SUCCESS; goto prop_free; cleanup: mutex_destroy(&niumxds_p->niumx_mutex); ddi_soft_state_free(niumx_state, ddi_get_instance(dip)); prop_free: ddi_prop_free(reg_p); done: return (ret); case DDI_RESUME: default: break; } return (ret); } static int niumx_detach(dev_info_t *dip, ddi_detach_cmd_t cmd) { niumx_devstate_t *niumxds_p; switch (cmd) { case DDI_DETACH: niumxds_p = (niumx_devstate_t *) ddi_get_soft_state(niumx_state, ddi_get_instance(dip)); intr_dist_rem(niumx_intr_dist, niumxds_p); ddi_fm_fini(dip); niumxtool_uninit(dip); mutex_destroy(&niumxds_p->niumx_mutex); ddi_soft_state_free(niumx_state, ddi_get_instance(dip)); return (DDI_SUCCESS); case DDI_SUSPEND: default: break; } return (DDI_FAILURE); } /* * Function used to initialize FMA for our children nodes. Called * through pci busops when child node calls ddi_fm_init. */ /*ARGSUSED*/ int niumx_fm_init_child(dev_info_t *dip, dev_info_t *cdip, int cap, ddi_iblock_cookie_t *ibc_p) { niumx_devstate_t *niumxds_p = NIUMX_DIP_TO_STATE(dip); ASSERT(ibc_p != NULL); *ibc_p = niumxds_p->niumx_fm_ibc; return (niumxds_p->niumx_fm_cap); } /*ARGSUSED*/ int niumx_map(dev_info_t *dip, dev_info_t *rdip, ddi_map_req_t *mp, off_t offset, off_t len, caddr_t *vaddrp) { struct regspec p_regspec; ddi_map_req_t p_mapreq; niu_regspec_t *reg_p; int i, rn = mp->map_obj.rnumber, reglen, rnglen, rngnum, ret; niumx_ranges_t *rng_p; uint32_t reg_begin, rng_begin; DBG(NIUMX_DBG_MAP, dip, "%s%d: mapping %s%d reg %d\n", NIUMX_NAMEINST(dip), NIUMX_NAMEINST(rdip), rn); if (ddi_getlongprop(DDI_DEV_T_ANY, rdip, DDI_PROP_DONTPASS, "reg", (caddr_t)®_p, ®len) != DDI_SUCCESS) return (DDI_FAILURE); if (rn < 0 || (rn >= reglen / sizeof (niu_regspec_t))) { DBG(NIUMX_DBG_MAP, dip, "rnumber out of range: %d\n", rn); kmem_free(reg_p, reglen); return (DDI_ME_RNUMBER_RANGE); } /* build regspec up for parent */ p_mapreq = *mp; /* dup the whole structure */ p_mapreq.map_type = DDI_MT_REGSPEC; p_mapreq.map_obj.rp = &p_regspec; if (ddi_getlongprop(DDI_DEV_T_ANY, dip, DDI_PROP_DONTPASS, "ranges", (caddr_t)&rng_p, &rnglen) != DDI_SUCCESS) { DBG(NIUMX_DBG_MAP, dip, "%s%d: no ranges property\n", ddi_driver_name(dip), ddi_get_instance(dip)); kmem_free(reg_p, reglen); return (DDI_FAILURE); } /* locate matching ranges record */ rngnum = rnglen / sizeof (niumx_ranges_t); for (i = 0, reg_p += rn; i < rngnum; rng_p++, i++) { if (reg_p->addr_high == rng_p->child_hi) break; } if (i >= rngnum) { DBG(NIUMX_DBG_MAP, dip, "ranges record for reg[%d] " "not found.\n", rn); ret = DDI_ME_REGSPEC_RANGE; goto err; } /* * validate request has matching bus type and within 4G * limit by comparing addr.hi of "ranges" and child "reg". */ ASSERT(reg_p->size_high == 0); rng_begin = rng_p->child_lo; reg_begin = reg_p->addr_low; /* check to verify reg bounds are within rng bounds */ if (reg_begin < rng_begin || (reg_begin + (reg_p->size_low - 1)) > (rng_begin + (rng_p->size_lo - 1))) { DBG(NIUMX_DBG_MAP, dip, "size out of range for reg[%d].\n", rn); ret = DDI_ME_REGSPEC_RANGE; goto err; } p_regspec.regspec_bustype = rng_p->parent_hi; p_regspec.regspec_addr = reg_begin - rng_begin + rng_p->parent_lo; p_regspec.regspec_size = reg_p->size_low; DBG(NIUMX_DBG_MAP, dip, "regspec:bus,addr,size = (%x,%x,%x)\n", p_regspec.regspec_bustype, p_regspec.regspec_addr, p_regspec.regspec_size); ret = ddi_map(dip, &p_mapreq, 0, 0, vaddrp); DBG(NIUMX_DBG_MAP, dip, "niumx_map: ret %d.\n", ret); err: kmem_free(rng_p - i, rnglen); kmem_free(reg_p - rn, reglen); return (ret); } /* * niumx_ctlops */ int niumx_ctlops(dev_info_t *dip, dev_info_t *rdip, ddi_ctl_enum_t ctlop, void *arg, void *result) { niu_regspec_t *reg_p; int reglen, totreg; DBG(NIUMX_DBG_CTLOPS, dip, "niumx_ctlops ctlop=%d.\n", ctlop); if (rdip == (dev_info_t *)0) return (DDI_FAILURE); switch (ctlop) { case DDI_CTLOPS_REPORTDEV: cmn_err(CE_NOTE, "device: %s@%s, %s%d\n", ddi_node_name(rdip), ddi_get_name_addr(rdip), NIUMX_NAMEINST(rdip)); return (DDI_SUCCESS); case DDI_CTLOPS_INITCHILD: return (niumx_initchild((dev_info_t *)arg)); case DDI_CTLOPS_UNINITCHILD: niumx_removechild((dev_info_t *)arg); return (DDI_SUCCESS); case DDI_CTLOPS_REGSIZE: case DDI_CTLOPS_NREGS: /* fall through */ break; default: DBG(NIUMX_DBG_CTLOPS, dip, "just pass to ddi_cltops.\n"); return (ddi_ctlops(dip, rdip, ctlop, arg, result)); } /* REGSIZE/NREGS */ *(int *)result = 0; if (ddi_getlongprop(DDI_DEV_T_NONE, rdip, DDI_PROP_DONTPASS | DDI_PROP_CANSLEEP, "reg", (caddr_t)®_p, ®len) != DDI_SUCCESS) return (DDI_FAILURE); totreg = reglen / sizeof (niu_regspec_t); if (ctlop == DDI_CTLOPS_NREGS) { DBG(NIUMX_DBG_CTLOPS, (dev_info_t *)dip, "niumx_ctlops NREGS=%d.\n", totreg); *(int *)result = totreg; } else if (ctlop == DDI_CTLOPS_REGSIZE) { int rn; rn = *(int *)arg; if (rn >= totreg) { kmem_free(reg_p, reglen); return (DDI_FAILURE); } *(off_t *)result = (reg_p + rn)->size_low; DBG(NIUMX_DBG_CTLOPS, (dev_info_t *)dip, "rn = %d, REGSIZE=%x.\n", rn, *(off_t *)result); } kmem_free(reg_p, reglen); return (DDI_SUCCESS); } /* * niumx_name_child * * This function is called from init_child to name a node. It is * also passed as a callback for node merging functions. * * return value: DDI_SUCCESS, DDI_FAILURE */ static int niumx_name_child(dev_info_t *child, char *name, int namelen) { niu_regspec_t *r; uint_t n; DBG(NIUMX_DBG_CHK_MOD, (dev_info_t *)child, "==> niumx_name_child\n"); if (ndi_dev_is_persistent_node(child) == 0) { char **unit_addr; /* name .conf nodes by "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 name node from %s.conf", ddi_driver_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_driver_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); } /* name hardware nodes by "reg" property */ if (ddi_prop_lookup_int_array(DDI_DEV_T_ANY, child, DDI_PROP_DONTPASS, "reg", (int **)&r, &n) != DDI_SUCCESS) { cmn_err(CE_WARN, "reg property not well-formed"); return (DDI_FAILURE); } (void) snprintf(name, namelen, "%x", (r[0].addr_high)); ddi_prop_free(r); return (DDI_SUCCESS); } static int niumx_initchild(dev_info_t *child) { char name[MAXNAMELEN]; DBG(NIUMX_DBG_CHK_MOD, (dev_info_t *)child, "==> niumx_initchild\n"); /* * Non-peristent nodes indicate a prototype node with per-instance * properties to be merged into the real h/w device node. */ if (ndi_dev_is_persistent_node(child) == 0) { niu_regspec_t *r; uint_t n; if (ddi_prop_lookup_int_array(DDI_DEV_T_ANY, child, DDI_PROP_DONTPASS, "reg", (int **)&r, &n) == DDI_SUCCESS) { cmn_err(CE_WARN, "cannot merge prototype from %s.conf", ddi_driver_name(child)); ddi_prop_free(r); return (DDI_NOT_WELL_FORMED); } if (niumx_name_child(child, name, MAXNAMELEN) != DDI_SUCCESS) return (DDI_NOT_WELL_FORMED); ddi_set_name_addr(child, name); 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, niumx_name_child) == DDI_SUCCESS) { /* * Merged ok - return failure to remove the node. */ ddi_set_name_addr(child, NULL); return (DDI_FAILURE); } /* * 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_driver_name(child), ddi_get_name_addr(child), ddi_driver_name(child)); ddi_set_name_addr(child, NULL); return (DDI_NOT_WELL_FORMED); } /* * Initialize real h/w nodes */ if (niumx_name_child(child, name, MAXNAMELEN) != DDI_SUCCESS) return (DDI_FAILURE); ddi_set_name_addr(child, name); return (DDI_SUCCESS); } static void niumx_removechild(dev_info_t *dip) { ddi_set_name_addr(dip, NULL); ddi_remove_minor_node(dip, NULL); impl_rem_dev_props(dip); } /* * bus dma alloc handle entry point: */ /*ARGSUSED*/ int niumx_dma_allochdl(dev_info_t *dip, dev_info_t *rdip, ddi_dma_attr_t *attrp, int (*waitfp)(caddr_t), caddr_t arg, ddi_dma_handle_t *handlep) { ddi_dma_impl_t *mp; int sleep = (waitfp == DDI_DMA_SLEEP) ? KM_SLEEP : KM_NOSLEEP; DBG(NIUMX_DBG_DMA_ALLOCH, dip, "rdip=%s%d\n", NIUMX_NAMEINST(rdip)); if (attrp->dma_attr_version != DMA_ATTR_V0) { DBG(NIUMX_DBG_DMA_ALLOCH, (dev_info_t *)dip, "DDI_DMA_BADATTR\n"); return (DDI_DMA_BADATTR); } /* Caution: we don't use zalloc to enhance performance! */ if ((mp = kmem_alloc(sizeof (ddi_dma_impl_t), sleep)) == 0) { DBG(NIUMX_DBG_DMA_ALLOCH, dip, "can't alloc ddi_dma_impl_t\n"); return (DDI_FAILURE); } mp->dmai_rdip = rdip; mp->dmai_pfnlst = NULL; mp->dmai_cookie = NULL; mp->dmai_ncookies = 0; mp->dmai_curcookie = 0; mp->dmai_fault = 0; mp->dmai_fault_check = NULL; mp->dmai_fault_notify = NULL; mp->dmai_attr = *attrp; /* set requestors attr info */ DBG(NIUMX_DBG_DMA_ALLOCH, dip, "mp=%p\n", mp); *handlep = (ddi_dma_handle_t)mp; return (DDI_SUCCESS); } /* * bus dma free handle entry point: */ /*ARGSUSED*/ int niumx_dma_freehdl(dev_info_t *dip, dev_info_t *rdip, ddi_dma_handle_t handle) { ddi_dma_impl_t *mp = (ddi_dma_impl_t *)handle; if (mp->dmai_cookie) kmem_free(mp->dmai_cookie, sizeof (ddi_dma_cookie_t)); kmem_free(mp, sizeof (ddi_dma_impl_t)); return (DDI_SUCCESS); } /* * bus dma bind handle entry point: * * check/enforce DMA type, setup pfn0 and some other key pieces * of this dma request. * Note: this only works with DMA_OTYP_VADDR, and makes use of the known * fact that only contiguous memory blocks will be passed in. * Therefore only one cookie will ever be returned. * * return values: * DDI_DMA_NOMAPPING - can't get valid pfn0, or bad dma type * DDI_DMA_NORESOURCES * DDI_SUCCESS * * dma handle members affected (set on exit): * mp->dmai_object - dmareq->dmar_object * mp->dmai_rflags - dmareq->dmar_flags * mp->dmai_pfn0 - 1st page pfn (if va/size pair and not shadow) * mp->dmai_roffset - initialized to starting page offset * mp->dmai_size - # of total pages of entire object * mp->dmai_cookie - new cookie alloc'd */ /*ARGSUSED*/ int niumx_dma_bindhdl(dev_info_t *dip, dev_info_t *rdip, ddi_dma_handle_t handle, ddi_dma_req_t *dmareq, ddi_dma_cookie_t *cookiep, uint_t *ccountp) { int (*waitfp)(caddr_t) = dmareq->dmar_fp; ddi_dma_impl_t *mp = (ddi_dma_impl_t *)handle; ddi_dma_obj_t *dobj_p = &dmareq->dmar_object; uint32_t offset; pfn_t pfn0; int ret; DBG(NIUMX_DBG_DMA_BINDH, dip, "rdip=%s%d mp=%p dmareq=%p\n", NIUMX_NAMEINST(rdip), mp, dmareq); /* first check dma type */ mp->dmai_rflags = dmareq->dmar_flags & DMP_DDIFLAGS | DMP_NOSYNC; switch (dobj_p->dmao_type) { case DMA_OTYP_VADDR: { caddr_t vaddr = dobj_p->dmao_obj.virt_obj.v_addr; struct as *as_p = dobj_p->dmao_obj.virt_obj.v_as; struct hat *hat_p = as_p ? as_p->a_hat : kas.a_hat; offset = (ulong_t)vaddr & NIUMX_PAGE_OFFSET; pfn0 = hat_getpfnum(hat_p, vaddr); } break; case DMA_OTYP_BUFVADDR: case DMA_OTYP_PAGES: case DMA_OTYP_PADDR: default: cmn_err(CE_WARN, "%s%d requested unsupported dma type %x", NIUMX_NAMEINST(mp->dmai_rdip), dobj_p->dmao_type); ret = DDI_DMA_NOMAPPING; goto err; } if (pfn0 == PFN_INVALID) { cmn_err(CE_WARN, "%s%d: invalid pfn0 for DMA object %p", NIUMX_NAMEINST(dip), (void *)dobj_p); ret = DDI_DMA_NOMAPPING; goto err; } mp->dmai_object = *dobj_p; /* whole object */ mp->dmai_pfn0 = (void *)pfn0; /* cache pfn0 */ mp->dmai_roffset = offset; /* pg0 offset */ mp->dmai_mapping = mp->dmai_roffset | NIUMX_PTOB(pfn0); mp->dmai_size = mp->dmai_object.dmao_size; DBG(NIUMX_DBG_DMA_BINDH, dip, "check pfn: mp=%p pfn0=%x\n", mp, mp->dmai_pfn0); if (!(mp->dmai_cookie = kmem_zalloc(sizeof (ddi_dma_cookie_t), waitfp == DDI_DMA_SLEEP ? KM_SLEEP : KM_NOSLEEP))) { ret = DDI_DMA_NORESOURCES; goto err; } mp->dmai_cookie->dmac_laddress = mp->dmai_mapping; mp->dmai_cookie->dmac_size = mp->dmai_size; mp->dmai_ncookies = 1; mp->dmai_curcookie = 0; *ccountp = 1; *cookiep = *mp->dmai_cookie; DBG(NIUMX_DBG_DMA_BINDH, dip, "cookie %" PRIx64 "+%x, count=%d\n", cookiep->dmac_address, cookiep->dmac_size, *ccountp); return (DDI_DMA_MAPPED); err: DBG(NIUMX_DBG_DMA_BINDH, (dev_info_t *)dip, "niumx_dma_bindhdl error ret=%d\n", ret); return (ret); } /* * bus dma unbind handle entry point: */ /*ARGSUSED*/ int niumx_dma_unbindhdl(dev_info_t *dip, dev_info_t *rdip, ddi_dma_handle_t handle) { ddi_dma_impl_t *mp = (ddi_dma_impl_t *)handle; DBG(NIUMX_DBG_DMA_UNBINDH, dip, "rdip=%s%d, mp=%p\n", ddi_driver_name(rdip), ddi_get_instance(rdip), handle); if (mp->dmai_cookie) { kmem_free(mp->dmai_cookie, sizeof (ddi_dma_cookie_t)); mp->dmai_cookie = NULL; mp->dmai_ncookies = mp->dmai_curcookie = 0; } return (DDI_SUCCESS); } /*ARGSUSED*/ int niumx_intr_ops(dev_info_t *dip, dev_info_t *rdip, ddi_intr_op_t intr_op, ddi_intr_handle_impl_t *hdlp, void *result) { int ret = DDI_SUCCESS; DBG(NIUMX_DBG_INTROPS, dip, "niumx_intr_ops: dip=%p rdip=%p intr_op=%x " "handle=%p\n", dip, rdip, intr_op, hdlp); switch (intr_op) { case DDI_INTROP_SUPPORTED_TYPES: *(int *)result = DDI_INTR_TYPE_FIXED; break; case DDI_INTROP_GETCAP: *(int *)result = DDI_INTR_FLAG_LEVEL; break; case DDI_INTROP_SETCAP: ret = DDI_ENOTSUP; break; case DDI_INTROP_ALLOC: /* scratch1 = count, # of intrs from DDI framework */ *(int *)result = hdlp->ih_scratch1; break; case DDI_INTROP_FREE: /* Do we need to do anything here? */ break; case DDI_INTROP_GETPRI: *(int *)result = NIUMX_DEFAULT_PIL; break; case DDI_INTROP_SETPRI: ret = DDI_ENOTSUP; break; case DDI_INTROP_ADDISR: ret = niumx_add_intr(dip, rdip, hdlp); break; case DDI_INTROP_REMISR: ret = niumx_rem_intr(dip, rdip, hdlp); break; case DDI_INTROP_ENABLE: ret = niumx_set_intr(dip, rdip, hdlp, HV_INTR_VALID); break; case DDI_INTROP_DISABLE: ret = niumx_set_intr(dip, rdip, hdlp, HV_INTR_NOTVALID); break; case DDI_INTROP_SETMASK: ret = DDI_ENOTSUP; break; case DDI_INTROP_CLRMASK: ret = DDI_ENOTSUP; break; case DDI_INTROP_GETPENDING: ret = DDI_ENOTSUP; break; case DDI_INTROP_NINTRS: case DDI_INTROP_NAVAIL: { niudevino_t *inos_p; int inoslen; if (ddi_getlongprop(DDI_DEV_T_ANY, rdip, DDI_PROP_DONTPASS, "interrupts", (caddr_t)&inos_p, &inoslen) != DDI_SUCCESS) { ret = DDI_FAILURE; break; } *(int *)result = inoslen / sizeof (uint32_t); kmem_free(inos_p, inoslen); } break; case DDI_INTROP_GETTARGET: { niumx_devstate_t *niumxds_p; niumxds_p = (niumx_devstate_t *)ddi_get_soft_state(niumx_state, ddi_get_instance(dip)); ret = niumx_get_intr_target(niumxds_p, hdlp->ih_vector, (niucpuid_t *)result); } break; case DDI_INTROP_SETTARGET: { niumx_devstate_t *niumxds_p; niumxds_p = (niumx_devstate_t *)ddi_get_soft_state(niumx_state, ddi_get_instance(dip)); ret = niumx_set_intr_target(niumxds_p, hdlp->ih_vector, *(niucpuid_t *)result); } break; default: ret = DDI_ENOTSUP; break; } DBG(NIUMX_DBG_INTROPS, dip, "niumx_intr_ops: ret=%d\n", ret); return (ret); } int niumx_set_intr(dev_info_t *dip, dev_info_t *rdip, ddi_intr_handle_impl_t *hdlp, int valid) { niumx_ih_t *ih_p; int ret = DDI_SUCCESS; uint64_t hvret; niumx_devstate_t *niumxds_p; /* devstate pointer */ int instance = ddi_get_instance(dip); niumxds_p = (niumx_devstate_t *)ddi_get_soft_state(niumx_state, instance); ASSERT(hdlp->ih_inum < NIUMX_MAX_INTRS); ih_p = niumxds_p->niumx_ihtable + hdlp->ih_vector; DBG(NIUMX_DBG_A_INTX, dip, "niumx_set_intr: rdip=%s%d, valid=%d %s (%x,%x)\n", NIUMX_NAMEINST(rdip), valid, valid ? "enabling" : "disabling", ih_p->ih_inum, ih_p->ih_sysino); if (valid == HV_INTR_VALID) (void) hvio_intr_setstate(ih_p->ih_sysino, HV_INTR_IDLE_STATE); if ((hvret = hvio_intr_setvalid(ih_p->ih_sysino, valid)) != H_EOK) { DBG(NIUMX_DBG_A_INTX, dip, "hvio_intr_setvalid failed, ret 0x%x\n", hvret); ret = DDI_FAILURE; } else ih_p->ih_state = valid; return (ret); } int niumx_get_intr_target(niumx_devstate_t *niumxds_p, niudevino_t ino, niucpuid_t *cpu_id) { niumx_ih_t *ih_p; niusysino_t sysino; int rval = DDI_SUCCESS; ih_p = niumxds_p->niumx_ihtable + ino; sysino = ih_p->ih_sysino; if (sysino == 0) { rval = EINVAL; goto done; } if (hvio_intr_gettarget(sysino, cpu_id) != H_EOK) { rval = EINVAL; goto done; } if (ih_p->ih_cpuid != *cpu_id) rval = EIO; done: return (rval); } int niumx_set_intr_target(niumx_devstate_t *niumxds_p, niudevino_t ino, niucpuid_t cpu_id) { dev_info_t *dip = niumxds_p->dip; niumx_ih_t *ih_p; niucpuid_t old_cpu_id; niusysino_t sysino; int ret = DDI_SUCCESS; int state; hrtime_t start; extern const int _ncpu; extern cpu_t *cpu[]; mutex_enter(&cpu_lock); ih_p = niumxds_p->niumx_ihtable + ino; sysino = ih_p->ih_sysino; if (sysino == 0) { ret = EINVAL; goto done; } if (hvio_intr_gettarget(sysino, &old_cpu_id) != H_EOK) { ret = EINVAL; goto done; } if ((cpu_id < _ncpu) && (cpu[cpu_id] && cpu_is_online(cpu[cpu_id]))) { if (cpu_id == old_cpu_id) goto done; /* check for pending interrupts, busy wait if so */ for (start = gethrtime(); !panicstr && (hvio_intr_getstate(sysino, &state) == H_EOK) && (state == HV_INTR_DELIVERED_STATE); /* */) { if (gethrtime() - start > niumx_intr_timeout) { cmn_err(CE_WARN, "%s%d: niumx_intr_dist: " "pending interrupt (%x,%lx) timedout\n", ddi_driver_name(dip), ddi_get_instance(dip), ih_p->ih_inum, sysino); (void) hvio_intr_setstate(sysino, HV_INTR_IDLE_STATE); break; } } (void) hvio_intr_settarget(sysino, cpu_id); if (ih_p->ih_state == HV_INTR_VALID) (void) hvio_intr_setvalid(sysino, HV_INTR_VALID); else (void) hvio_intr_setvalid(sysino, HV_INTR_NOTVALID); ih_p->ih_cpuid = cpu_id; } else { ret = DDI_EINVAL; } done: mutex_exit(&cpu_lock); return (ret); } /* * niumx_add_intr: * * This function is called to register interrupts. */ int niumx_add_intr(dev_info_t *dip, dev_info_t *rdip, ddi_intr_handle_impl_t *hdlp) { niumx_ih_t *ih_p; int ret = DDI_SUCCESS; uint64_t hvret; niusysino_t sysino; niumx_devstate_t *niumxds_p; /* devstate pointer */ int instance = ddi_get_instance(dip); niumxds_p = (niumx_devstate_t *)ddi_get_soft_state(niumx_state, instance); /* get new ino */ if (hdlp->ih_inum >= NIUMX_MAX_INTRS) { DBG(NIUMX_DBG_INTR, dip, "error: inum %d out of range\n", hdlp->ih_inum); ret = DDI_FAILURE; goto done; } ih_p = niumxds_p->niumx_ihtable + hdlp->ih_vector; if ((hvret = hvio_intr_devino_to_sysino(NIUMX_DIP_TO_HANDLE(dip), hdlp->ih_vector, &sysino)) != H_EOK) { DBG(NIUMX_DBG_INTR, dip, "hvio_intr_devino_to_sysino failed, " "ret 0x%x\n", hvret); ret = DDI_FAILURE; goto done; } ih_p->ih_sysino = sysino; ih_p->ih_dip = rdip; ih_p->ih_inum = hdlp->ih_inum; ih_p->ih_hdlr = hdlp->ih_cb_func; ih_p->ih_arg1 = hdlp->ih_cb_arg1; ih_p->ih_arg2 = hdlp->ih_cb_arg2; DBG(NIUMX_DBG_A_INTX, dip, "niumx_add_intr: rdip=%s%d inum=0x%x " "handler=%p arg1=%p arg2=%p, new ih_p = %p\n", NIUMX_NAMEINST(rdip), hdlp->ih_inum, hdlp->ih_cb_func, hdlp->ih_cb_arg1, hdlp->ih_cb_arg2, ih_p); if (hdlp->ih_pri == 0) hdlp->ih_pri = NIUMX_DEFAULT_PIL; ih_p->ih_pri = hdlp->ih_pri; DBG(NIUMX_DBG_A_INTX, dip, "for ino %x adding (%x,%x)\n", hdlp->ih_vector, ih_p->ih_inum, ih_p->ih_sysino); /* Save sysino value in hdlp */ hdlp->ih_vector = ih_p->ih_sysino; /* swap in our handler & arg */ DDI_INTR_ASSIGN_HDLR_N_ARGS(hdlp, (ddi_intr_handler_t *)niumx_intr_hdlr, (void *)ih_p, NULL); ret = i_ddi_add_ivintr(hdlp); /* Restore orig. interrupt handler & args in handle. */ DDI_INTR_ASSIGN_HDLR_N_ARGS(hdlp, ih_p->ih_hdlr, ih_p->ih_arg1, ih_p->ih_arg2); if (ret != DDI_SUCCESS) { DBG(NIUMX_DBG_A_INTX, dip, "i_ddi_add_ivintr error ret=%x\n", ret); goto done; } /* select cpu, saving it for removal */ ih_p->ih_cpuid = intr_dist_cpuid(); if ((hvret = hvio_intr_settarget(ih_p->ih_sysino, ih_p->ih_cpuid)) != H_EOK) { DBG(NIUMX_DBG_A_INTX, dip, "hvio_intr_settarget failed, ret 0x%x\n", hvret); ret = DDI_FAILURE; } done: DBG(NIUMX_DBG_A_INTX, dip, "done, ret = %d, ih_p 0x%p, hdlp 0x%p\n", ih_p, hdlp, ret); return (ret); } /* * niumx_rem_intr: * * This function is called to unregister interrupts. */ /*ARGSUSED*/ int niumx_rem_intr(dev_info_t *dip, dev_info_t *rdip, ddi_intr_handle_impl_t *hdlp) { niumx_ih_t *ih_p; int ret = DDI_SUCCESS, state; hrtime_t start; niusysino_t sysino; niumx_devstate_t *niumxds_p; /* devstate pointer */ int instance = ddi_get_instance(dip); niumxds_p = (niumx_devstate_t *)ddi_get_soft_state(niumx_state, instance); ASSERT(hdlp->ih_inum < NIUMX_MAX_INTRS); ih_p = niumxds_p->niumx_ihtable + hdlp->ih_vector; sysino = ih_p->ih_sysino; DBG(NIUMX_DBG_R_INTX, dip, "removing (%x,%x)\n", ih_p->ih_inum, sysino); (void) hvio_intr_setvalid(sysino, HV_INTR_NOTVALID); /* check for pending interrupts, busy wait if so */ for (start = gethrtime(); !panicstr && (hvio_intr_getstate(sysino, &state) == H_EOK) && (state == HV_INTR_DELIVERED_STATE); /* */) { if (gethrtime() - start > niumx_intr_timeout) { cmn_err(CE_WARN, "%s%d: niumx_intr_dist: " "pending interrupt (%x,%lx) timedout\n", ddi_driver_name(dip), ddi_get_instance(dip), ih_p->ih_inum, sysino); ret = DDI_FAILURE; goto fail; } } ih_p->ih_sysino = 0; hdlp->ih_vector = (uint32_t)sysino; if (hdlp->ih_vector != 0) i_ddi_rem_ivintr(hdlp); fail: return (ret); } /* * niumx_intr_hdlr (our interrupt handler) */ uint_t niumx_intr_hdlr(void *arg) { niumx_ih_t *ih_p = (niumx_ih_t *)arg; uint_t r; DTRACE_PROBE4(interrupt__start, dev_info_t, ih_p->ih_dip, void *, ih_p->ih_hdlr, caddr_t, ih_p->ih_arg1, caddr_t, ih_p->ih_arg2); r = (*ih_p->ih_hdlr)(ih_p->ih_arg1, ih_p->ih_arg2); DTRACE_PROBE4(interrupt__complete, dev_info_t, ih_p->ih_dip, void *, ih_p->ih_hdlr, caddr_t, ih_p->ih_arg1, int, r); (void) hvio_intr_setstate(ih_p->ih_sysino, HV_INTR_IDLE_STATE); return (r); } #ifdef DEBUG uint64_t niumx_debug_flags = 0; static char *niumx_debug_sym [] = { /* same sequence as niumx_debug_bit */ /* 0 */ "attach", /* 1 */ "map", /* 2 */ "nex-ctlops", /* 3 */ "introps", /* 4 */ "intr-add", /* 5 */ "intr-rem", /* 6 */ "intr", /* 7 */ "dma-alloc", /* 8 */ "dma-bind", /* 9 */ "dma-unbind", /* 10 */ "chk-dma-mode" }; /*ARGSUSED*/ void niumx_dbg(niumx_debug_bit_t bit, dev_info_t *dip, char *fmt, ...) { va_list ap; char msgbuf[1024]; if (!(1ull << bit & niumx_debug_flags)) return; va_start(ap, fmt); (void) vsprintf(msgbuf, fmt, ap); va_end(ap); cmn_err(CE_NOTE, "%s: %s", niumx_debug_sym[bit], msgbuf); } #endif /* DEBUG */