rootnex.c revision 86a9c507121ec77a92601d8844a5ca82373cd4aa
/*
* 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
* 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.
*/
/*
* x86 root nexus driver
*/
#include <sys/sysmacros.h>
#include <sys/autoconf.h>
#include <sys/sysmacros.h>
#include <sys/ddidmareq.h>
#include <vm/seg_kmem.h>
#include <sys/ddi_impldefs.h>
#include <sys/mach_intr.h>
#ifdef __xpv
#include <sys/bootinfo.h>
#include <sys/hypervisor.h>
#include <sys/bootconf.h>
#include <vm/kboot_mmu.h>
#else
#include <sys/intel_iommu.h>
#endif
/*
* drivers.
*/
#ifdef DEBUG
int rootnex_alloc_check_parms = 1;
int rootnex_bind_check_parms = 1;
int rootnex_bind_check_inuse = 1;
int rootnex_unbind_verify_buffer = 0;
int rootnex_sync_check_parms = 1;
#else
int rootnex_alloc_check_parms = 0;
int rootnex_bind_check_parms = 0;
int rootnex_bind_check_inuse = 0;
int rootnex_unbind_verify_buffer = 0;
int rootnex_sync_check_parms = 0;
#endif
/* Master Abort and Target Abort panic flag */
int rootnex_fm_ma_ta_panic_flag = 0;
/* Semi-temporary patchables to phase in bug fixes, test drivers, etc. */
int rootnex_bind_fail = 1;
int rootnex_bind_warn = 1;
/* bitmasks for rootnex_warn_list. Up to 8 different warnings with uint8_t */
#define ROOTNEX_BIND_WARNING (0x1 << 0)
/*
* revert back to old broken behavior of always sync'ing entire copy buffer.
* This is useful if be have a buggy driver which doesn't correctly pass in
* the offset and size into ddi_dma_sync().
*/
int rootnex_sync_ignore_params = 0;
/*
* For the 64-bit kernel, pre-alloc enough cookies for a 256K buffer plus 1
* page for alignment. For the 32-bit kernel, pre-alloc enough cookies for a
* 64K buffer plus 1 page for alignment (we have less kernel space in a 32-bit
* kernel). Allocate enough windows to handle a 256K buffer w/ at least 65
* sgllen DMA engine, and enough copybuf buffer state pages to handle 2 pages
* (< 8K). We will still need to allocate the copy buffer during bind though
* attach.
*/
#if defined(__amd64)
int rootnex_prealloc_cookies = 65;
int rootnex_prealloc_windows = 4;
int rootnex_prealloc_copybuf = 2;
#else
int rootnex_prealloc_cookies = 33;
int rootnex_prealloc_windows = 4;
int rootnex_prealloc_copybuf = 2;
#endif
/* driver global state */
static rootnex_state_t *rootnex_state;
/* shortcut to rootnex counters */
static uint64_t *rootnex_cnt;
/*
* XXX - does x86 even need these or are they left over from the SPARC days?
*/
static rootnex_intprop_t rootnex_intprp[] = {
{ "PAGESIZE", PAGESIZE },
{ "MMU_PAGESIZE", MMU_PAGESIZE },
{ "MMU_PAGEOFFSET", MMU_PAGEOFFSET },
{ DDI_RELATIVE_ADDRESSING, 1 },
};
#ifdef __xpv
typedef maddr_t rootnex_addr_t;
#else
typedef paddr_t rootnex_addr_t;
#endif
#if !defined(__xpv)
#endif
static struct cb_ops rootnex_cb_ops = {
nodev, /* open */
nodev, /* close */
nodev, /* strategy */
nodev, /* print */
nodev, /* dump */
nodev, /* read */
nodev, /* write */
nodev, /* ioctl */
nodev, /* devmap */
nodev, /* mmap */
nodev, /* segmap */
nochpoll, /* chpoll */
ddi_prop_op, /* cb_prop_op */
NULL, /* struct streamtab */
CB_REV, /* Rev */
nodev, /* cb_aread */
nodev /* cb_awrite */
};
#if !defined(__xpv)
#endif
static struct bus_ops rootnex_bus_ops = {
NULL,
NULL,
NULL,
0, /* bus_intr_ctl */
0, /* bus_config */
0, /* bus_unconfig */
rootnex_fm_init, /* bus_fm_init */
NULL, /* bus_fm_fini */
NULL, /* bus_fm_access_enter */
NULL, /* bus_fm_access_exit */
NULL, /* bus_powr */
rootnex_intr_ops /* bus_intr_op */
};
static struct dev_ops rootnex_ops = {
0,
NULL,
ddi_quiesce_not_needed, /* quiesce */
};
static struct modldrv rootnex_modldrv = {
"i86pc root nexus",
};
static struct modlinkage rootnex_modlinkage = {
(void *)&rootnex_modldrv,
};
#if !defined(__xpv)
static iommulib_nexops_t iommulib_nexops = {
"Rootnex IOMMU ops Vers 1.1",
NULL,
};
#endif
/*
* extern hacks
*/
extern struct seg_ops segdev_ops;
extern int ignore_hardware_nodes; /* force flag from ddi_impl.c */
#ifdef DDI_MAP_DEBUG
extern int ddi_map_debug_flag;
#endif
psm_intr_op_t, int *);
/*
* Use device arena to use for device control register mappings.
* Various kernel memory walkers (debugger, dtrace) need to know
* to avoid this address range to prevent undesired device activity.
*/
/*
* Internal functions
*/
static int rootnex_dma_init();
static void rootnex_add_props(dev_info_t *);
/*
* _init()
*
*/
int
_init(void)
{
return (mod_install(&rootnex_modlinkage));
}
/*
* _info()
*
*/
int
{
}
/*
* _fini()
*
*/
int
_fini(void)
{
return (EBUSY);
}
/*
* rootnex_attach()
*
*/
static int
{
int fmcap;
int e;
switch (cmd) {
case DDI_ATTACH:
break;
case DDI_RESUME:
return (DDI_SUCCESS);
default:
return (DDI_FAILURE);
}
/*
* We should only have one instance of rootnex. Save it away since we
* don't have an easy way to get it back later.
*/
/*
* Set minimum fm capability level for i86pc platforms and then
* initialize error handling. Since we're the rootnex, we don't
* care what's returned in the fmcap field.
*/
/* initialize DMA related state */
e = rootnex_dma_init();
if (e != DDI_SUCCESS) {
return (DDI_FAILURE);
}
/* Add static root node properties */
/* since we can't call ddi_report_dev() */
/* Initialize rootnex event handle */
#if !defined(__xpv)
#if defined(__amd64)
/* probe intel iommu */
/* attach the iommu nodes */
if (intel_iommu_support) {
if (intel_iommu_attach_dmar_nodes() == DDI_SUCCESS) {
} else {
}
}
#endif
ASSERT(e == DDI_SUCCESS);
#endif
return (DDI_SUCCESS);
}
/*
* rootnex_detach()
*
*/
/*ARGSUSED*/
static int
{
switch (cmd) {
case DDI_SUSPEND:
break;
default:
return (DDI_FAILURE);
}
return (DDI_SUCCESS);
}
/*
* rootnex_dma_init()
*
*/
/*ARGSUSED*/
static int
{
/*
* pre-alloc in dma_alloc_handle
*/
(rootnex_prealloc_windows * sizeof (rootnex_window_t)) +
(rootnex_prealloc_copybuf * sizeof (rootnex_pgmap_t));
/*
* setup DDI DMA handle kmem cache, align each handle on 64 bytes,
* allocate 16 extra bytes for struct pointer alignment
* (p->dmai_private & dma->dp_prealloc_buffer)
*/
return (DDI_FAILURE);
}
/*
* allocate array to track which major numbers we have printed warnings
* for.
*/
KM_SLEEP);
return (DDI_SUCCESS);
}
/*
* rootnex_add_props()
*
*/
static void
{
int i;
for (i = 0; i < NROOT_INTPROPS; i++) {
}
}
/*
* *************************
* ctlops related routines
* *************************
*/
/*
* rootnex_ctlops()
*
*/
/*ARGSUSED*/
static int
{
int n, *ptr;
struct ddi_parent_private_data *pdp;
switch (ctlop) {
case DDI_CTLOPS_DMAPMAPC:
/*
* Return 'partial' to indicate that dma mapping
* has to be done in the main MMU.
*/
return (DDI_DMA_PARTIAL);
case DDI_CTLOPS_BTOP:
/*
* Convert byte count input to physical page units.
* (byte counts that are not a page-size multiple
* are rounded down)
*/
return (DDI_SUCCESS);
case DDI_CTLOPS_PTOB:
/*
* Convert size in physical pages to bytes
*/
return (DDI_SUCCESS);
case DDI_CTLOPS_BTOPR:
/*
* Convert byte count input to physical page units
* (byte counts that are not a page-size multiple
* are rounded up)
*/
return (DDI_SUCCESS);
case DDI_CTLOPS_INITCHILD:
return (impl_ddi_sunbus_initchild(arg));
case DDI_CTLOPS_UNINITCHILD:
return (DDI_SUCCESS);
case DDI_CTLOPS_REPORTDEV:
return (rootnex_ctl_reportdev(rdip));
case DDI_CTLOPS_IOMIN:
/*
* Nothing to do here but reflect back..
*/
return (DDI_SUCCESS);
case DDI_CTLOPS_REGSIZE:
case DDI_CTLOPS_NREGS:
break;
case DDI_CTLOPS_SIDDEV:
if (ndi_dev_is_prom_node(rdip))
return (DDI_SUCCESS);
return (DDI_SUCCESS);
return (DDI_FAILURE);
case DDI_CTLOPS_POWER:
case DDI_CTLOPS_RESERVED0: /* Was DDI_CTLOPS_NINTRS, obsolete */
case DDI_CTLOPS_RESERVED1: /* Was DDI_CTLOPS_POKE_INIT, obsolete */
case DDI_CTLOPS_RESERVED2: /* Was DDI_CTLOPS_POKE_FLUSH, obsolete */
case DDI_CTLOPS_RESERVED3: /* Was DDI_CTLOPS_POKE_FINI, obsolete */
case DDI_CTLOPS_RESERVED4: /* Was DDI_CTLOPS_INTR_HILEVEL, obsolete */
case DDI_CTLOPS_RESERVED5: /* Was DDI_CTLOPS_XLATE_INTRS, obsolete */
if (!rootnex_state->r_reserved_msg_printed) {
}
return (DDI_FAILURE);
default:
return (DDI_FAILURE);
}
/*
* The rest are for "hardware" properties
*/
return (DDI_FAILURE);
if (ctlop == DDI_CTLOPS_NREGS) {
} else {
n = *ptr;
return (DDI_FAILURE);
}
}
return (DDI_SUCCESS);
}
/*
* rootnex_ctl_reportdev()
*
*/
static int
{
char *buf;
for (i = 0; i < sparc_pd_getnreg(dev); i++) {
if (i == 0)
": ");
else
" and ");
switch (rp->regspec_bustype) {
case BTEISA:
break;
case BTISA:
break;
default:
"space %x offset %x",
break;
}
}
for (i = 0, n = sparc_pd_getnintr(dev); i < n; i++) {
int pri;
if (i != 0) {
",");
}
" sparc ipl %d", pri);
}
#ifdef DEBUG
"printed length 1024, real length %d", f_len);
}
#endif /* DEBUG */
return (DDI_SUCCESS);
}
/*
* ******************
* map related code
* ******************
*/
/*
* rootnex_map()
*
*/
static int
{
int error;
case DDI_MO_MAP_LOCKED:
case DDI_MO_UNMAP:
case DDI_MO_MAP_HANDLE:
break;
default:
#ifdef DDI_MAP_DEBUG
#endif /* DDI_MAP_DEBUG */
return (DDI_ME_UNIMPLEMENTED);
}
#ifdef DDI_MAP_DEBUG
#endif /* DDI_MAP_DEBUG */
return (DDI_ME_UNIMPLEMENTED);
}
/*
* First, if given an rnumber, convert it to a regspec...
* (Presumably, this is on behalf of a child of the root node?)
*/
#ifdef DDI_MAP_DEBUG
static char *out_of_range =
"rootnex_map: Out of range rnumber <%d>, device <%s>";
#endif /* DDI_MAP_DEBUG */
#ifdef DDI_MAP_DEBUG
ddi_get_name(rdip));
#endif /* DDI_MAP_DEBUG */
return (DDI_ME_RNUMBER_RANGE);
}
/*
* Convert the given ddi_map_req_t from rnumber to regspec...
*/
}
/*
* Adjust offset and length correspnding to called values...
* XXX: A non-zero length means override the one in the regspec
* XXX: (regardless of what's in the parent's range?)
*/
#ifdef DDI_MAP_DEBUG
#endif /* DDI_MAP_DEBUG */
/*
* I/O or memory mapping:
*
* <bustype=0, addr=x, len=x>: memory
* <bustype=1, addr=x, len=x>: i/o
* <bustype>1, addr=0, len=x>: x86-compatibility i/o
*/
return (DDI_ME_INVAL);
}
/*
* compatibility i/o mapping
*/
} else {
/*
* Normal memory or i/o mapping
*/
}
if (len != 0)
#ifdef DDI_MAP_DEBUG
#endif /* DDI_MAP_DEBUG */
/*
* Apply any parent ranges at this level, if applicable.
* (This is where nexus specific regspec translation takes place.
* Use of this function is implicit agreement that translation is
* provided via ddi_apply_range.)
*/
#ifdef DDI_MAP_DEBUG
ddi_map_debug("applying range of parent <%s> to child <%s>...\n",
#endif /* DDI_MAP_DEBUG */
return (error);
case DDI_MO_MAP_LOCKED:
/*
* Set up the locked down kernel mapping to the regspec...
*/
case DDI_MO_UNMAP:
/*
* Release mapping...
*/
case DDI_MO_MAP_HANDLE:
return (rootnex_map_handle(mp));
default:
return (DDI_ME_UNIMPLEMENTED);
}
}
/*
* rootnex_map_fault()
*
* fault in mappings for requestors
*/
/*ARGSUSED*/
static int
{
#ifdef DDI_MAP_DEBUG
ddi_map_debug(" Seg <%s>\n",
#endif /* DDI_MAP_DEBUG */
/*
* This is all terribly broken, but it is a start
*
* XXX Note that this test means that segdev_ops
* must be exported from seg_dev.c.
* XXX What about devices with their own segment drivers?
*/
/*
* This is one plausible interpretation of
* a null hat i.e. use the first hat on the
* address space hat list which by convention is
* the hat of the system MMU. At alternative
* would be to panic .. this might well be better ..
*/
}
} else
return (DDI_FAILURE);
return (DDI_SUCCESS);
}
/*
* rootnex_map_regspec()
* we don't support mapping of I/O cards above 4Gb
*/
static int
{
void *cvaddr;
#ifdef DDI_MAP_DEBUG
"rootnex_map_regspec: <0x%x 0x%x 0x%x> handle 0x%x\n",
#endif /* DDI_MAP_DEBUG */
/*
* I/O or memory mapping
*
* <bustype=0, addr=x, len=x>: memory
* <bustype=1, addr=x, len=x>: i/o
* <bustype>1, addr=0, len=x>: x86-compatibility i/o
*/
return (DDI_FAILURE);
}
if (rp->regspec_bustype != 0) {
/*
* I/O space - needs a handle.
*/
return (DDI_FAILURE);
}
#ifdef DDI_MAP_DEBUG
ddi_map_debug("rootnex_map_regspec: mmap() "
"to I/O space is not supported.\n");
#endif /* DDI_MAP_DEBUG */
return (DDI_ME_INVAL);
} else {
/*
* 1275-compliant vs. compatibility i/o mapping
*/
*vaddrp =
#ifdef __xpv
if (DOMAIN_IS_INITDOMAIN(xen_info)) {
MMU_PAGEMASK));
} else {
}
#else
#endif
}
#ifdef DDI_MAP_DEBUG
"rootnex_map_regspec: \"Mapping\" %d bytes I/O space at 0x%x\n",
#endif /* DDI_MAP_DEBUG */
return (DDI_SUCCESS);
}
/*
* Memory space
*/
/*
* hat layer ignores
* hp->ah_acc.devacc_attr_endian_flags.
*/
case DDI_STRICTORDER_ACC:
break;
case DDI_UNORDERED_OK_ACC:
break;
case DDI_MERGING_OK_ACC:
break;
case DDI_LOADCACHING_OK_ACC:
break;
case DDI_STORECACHING_OK_ACC:
break;
}
} else {
}
#ifdef __xpv
/*
* If we're dom0, we're using a real device so we need to translate
* the MA to a PA.
*/
if (DOMAIN_IS_INITDOMAIN(xen_info)) {
} else {
}
#else
#endif
if (rp->regspec_size == 0) {
#ifdef DDI_MAP_DEBUG
ddi_map_debug("rootnex_map_regspec: zero regspec_size\n");
#endif /* DDI_MAP_DEBUG */
return (DDI_ME_INVAL);
}
/* extra cast to make gcc happy */
} else {
#ifdef DDI_MAP_DEBUG
ddi_map_debug("rootnex_map_regspec: Mapping %d pages "
#endif /* DDI_MAP_DEBUG */
return (DDI_ME_NORESOURCES);
/*
* Now map in the pages we've allocated...
*/
/* save away pfn and npages for FMA */
if (hp) {
}
}
#ifdef DDI_MAP_DEBUG
#endif /* DDI_MAP_DEBUG */
return (DDI_SUCCESS);
}
/*
* rootnex_unmap_regspec()
*
*/
static int
{
return (0);
if (rp->regspec_size == 0) {
#ifdef DDI_MAP_DEBUG
ddi_map_debug("rootnex_unmap_regspec: zero regspec_size\n");
#endif /* DDI_MAP_DEBUG */
return (DDI_ME_INVAL);
}
/*
* I/O or memory mapping:
*
* <bustype=0, addr=x, len=x>: memory
* <bustype=1, addr=x, len=x>: i/o
* <bustype>1, addr=0, len=x>: x86-compatibility i/o
*/
if (rp->regspec_bustype != 0) {
/*
* This is I/O space, which requires no particular
* processing on unmap since it isn't mapped in the
* first place.
*/
return (DDI_SUCCESS);
}
/*
* Memory space
*/
/*
* Destroy the pointer - the mapping has logically gone
*/
return (DDI_SUCCESS);
}
/*
* rootnex_map_handle()
*
*/
static int
{
#ifdef DDI_MAP_DEBUG
"rootnex_map_handle: <0x%x 0x%x 0x%x> handle 0x%x\n",
#endif /* DDI_MAP_DEBUG */
/*
* I/O or memory mapping:
*
* <bustype=0, addr=x, len=x>: memory
* <bustype=1, addr=x, len=x>: i/o
* <bustype>1, addr=0, len=x>: x86-compatibility i/o
*/
if (rp->regspec_bustype != 0) {
/*
* This refers to I/O space, and we don't support "mapping"
* I/O space to a user.
*/
return (DDI_FAILURE);
}
/*
* Set up the hat_flags for the mapping.
*/
case DDI_NEVERSWAP_ACC:
break;
case DDI_STRUCTURE_LE_ACC:
break;
case DDI_STRUCTURE_BE_ACC:
return (DDI_FAILURE);
default:
return (DDI_REGS_ACC_CONFLICT);
}
case DDI_STRICTORDER_ACC:
break;
case DDI_UNORDERED_OK_ACC:
break;
case DDI_MERGING_OK_ACC:
break;
case DDI_LOADCACHING_OK_ACC:
break;
case DDI_STORECACHING_OK_ACC:
break;
default:
return (DDI_FAILURE);
}
if (rp->regspec_size == 0)
return (DDI_ME_INVAL);
#ifdef __xpv
/*
* If we're dom0, we're using a real device so we need to translate
* the MA to a PA.
*/
if (DOMAIN_IS_INITDOMAIN(xen_info)) {
(rbase & MMU_PAGEOFFSET);
} else {
}
#else
#endif
return (DDI_SUCCESS);
}
/*
* ************************
* interrupt related code
* ************************
*/
/*
* rootnex_intr_ops()
* bus_intr_op() function for interrupt support
*/
/* ARGSUSED */
static int
{
struct ddi_parent_private_data *pdp;
"rootnex_intr_ops: pdip = %p, rdip = %p, intr_op = %x, hdlp = %p\n",
/* Process the interrupt operation */
switch (intr_op) {
case DDI_INTROP_GETCAP:
/* First check with pcplusmp */
if (psm_intr_ops == NULL)
return (DDI_FAILURE);
*(int *)result = 0;
return (DDI_FAILURE);
}
break;
case DDI_INTROP_SETCAP:
if (psm_intr_ops == NULL)
return (DDI_FAILURE);
return (DDI_FAILURE);
break;
case DDI_INTROP_ALLOC:
return (DDI_FAILURE);
break;
case DDI_INTROP_FREE:
/*
* Special case for 'pcic' driver' only.
* If an intrspec was created for it, clean it up here
* See detailed comments on this in the function
* rootnex_get_ispec().
*/
/*
* Set it to zero; so that
* DDI framework doesn't free it again
*/
}
break;
case DDI_INTROP_GETPRI:
return (DDI_FAILURE);
break;
case DDI_INTROP_SETPRI:
/* Validate the interrupt priority passed to us */
if (*(int *)result > LOCK_LEVEL)
return (DDI_FAILURE);
/* Ensure that PSM is all initialized and ispec is ok */
if ((psm_intr_ops == NULL) ||
return (DDI_FAILURE);
/* Change the priority */
return (DDI_FAILURE);
/* update the ispec with the new priority */
break;
case DDI_INTROP_ADDISR:
return (DDI_FAILURE);
break;
case DDI_INTROP_REMISR:
return (DDI_FAILURE);
break;
case DDI_INTROP_ENABLE:
return (DDI_FAILURE);
/* Call psmi to translate irq with the dip */
if (psm_intr_ops == NULL)
return (DDI_FAILURE);
return (DDI_FAILURE);
/* Add the interrupt handler */
return (DDI_FAILURE);
break;
case DDI_INTROP_DISABLE:
return (DDI_FAILURE);
/* Call psm_ops() to translate irq with the dip */
if (psm_intr_ops == NULL)
return (DDI_FAILURE);
/* Remove the interrupt handler */
break;
case DDI_INTROP_SETMASK:
if (psm_intr_ops == NULL)
return (DDI_FAILURE);
return (DDI_FAILURE);
break;
case DDI_INTROP_CLRMASK:
if (psm_intr_ops == NULL)
return (DDI_FAILURE);
return (DDI_FAILURE);
break;
case DDI_INTROP_GETPENDING:
if (psm_intr_ops == NULL)
return (DDI_FAILURE);
result)) {
*(int *)result = 0;
return (DDI_FAILURE);
}
break;
case DDI_INTROP_NAVAIL:
case DDI_INTROP_NINTRS:
if (*(int *)result == 0) {
/*
* Special case for 'pcic' driver' only. This driver
* driver is a child of 'isa' and 'rootnex' drivers.
*
* See detailed comments on this in the function
* rootnex_get_ispec().
*
* Children of 'pcic' send 'NINITR' request all the
* way to rootnex driver. But, the 'pdp->par_nintr'
* field may not initialized. So, we fake it here
* to return 1 (a la what PCMCIA nexus does).
*/
*(int *)result = 1;
else
return (DDI_FAILURE);
}
break;
break;
default:
return (DDI_FAILURE);
}
return (DDI_SUCCESS);
}
/*
* rootnex_get_ispec()
* convert an interrupt number to an interrupt specification.
* The interrupt number determines which interrupt spec will be
* returned if more than one exists.
*
* Look into the parent private data area of the 'rdip' to find out
* the interrupt specification. First check to make sure there is
* one that matchs "inumber" and then return a pointer to it.
*
* Return NULL if one could not be found.
*
* NOTE: This is needed for rootnex_intr_ops()
*/
static struct intrspec *
{
/*
* Special case handling for drivers that provide their own
* intrspec structures instead of relying on the DDI framework.
*
* A broken hardware driver in ON could potentially provide its
* own intrspec structure, instead of relying on the hardware.
* If these drivers are children of 'rootnex' then we need to
* continue to provide backward compatibility to them here.
*
* Following check is a special case for 'pcic' driver which
* was found to have broken hardwre andby provides its own intrspec.
*
* Verbatim comments from this driver are shown here:
* "Don't use the ddi_add_intr since we don't have a
* default intrspec in all cases."
*
* Since an 'ispec' may not be always created for it,
* check for that and create one if so.
*
* NOTE: Currently 'pcic' is the only driver found to do this.
*/
}
/* Validate the interrupt number */
return (NULL);
/* Get the interrupt structure pointer and return that */
}
/*
* ******************
* dma related code
* ******************
*/
/*ARGSUSED*/
static int
{
int kmflag;
int e;
/* convert our sleep flags */
if (waitfp == DDI_DMA_SLEEP) {
} else {
kmflag = KM_NOSLEEP;
}
/*
* We try to do only one memory allocation here. We'll do a little
* pointer manipulation later. If the bind ends up taking more than
* our prealloc's space, we'll have to allocate more memory in the
* bind operation. Not great, but much better than before and the
* best we can do with the current bind interfaces.
*/
if (waitfp != DDI_DMA_DONTWAIT) {
}
return (DDI_DMA_NORESOURCES);
}
/* Do our pointer manipulation now, align the structures */
/* setup the handle */
/* we don't need to worry about the SPL since we do a tryenter */
/*
* Figure out our maximum segment size. If the segment size is greater
* than 4G, we will limit it to (4G - 1) since the max size of a dma
* object (ddi_dma_obj_t.dmao_size) is 32 bits. dma_attr_seg and
* dma_attr_count_max are size-1 type values.
*
* Maximum segment size is the largest physically contiguous chunk of
* memory that we can return from a bind (i.e. the maximum size of a
* single cookie).
*/
/* handle the rollover cases */
}
}
/*
* granularity may or may not be a power of two. If it isn't, we can't
* use a simple mask.
*/
} else {
}
/*
* maxxfer should be a whole multiple of granularity. If we're going to
* break up a window because we're greater than maxxfer, we might as
* well make sure it's maxxfer is a whole multiple so we don't have to
* worry about triming the window later on for this case.
*/
if (dma->dp_granularity_power_2) {
(attr->dma_attr_maxxfer &
} else {
}
} else {
}
maxsegmentsize = 0xFFFFFFFF;
} else {
}
/* check the ddi_dma_attr arg to make sure it makes a little sense */
if (rootnex_alloc_check_parms) {
if (e != DDI_SUCCESS) {
return (e);
}
}
return (DDI_SUCCESS);
}
/*
* rootnex_dma_allochdl()
* called from ddi_dma_alloc_handle().
*/
static int
{
#if !defined(__xpv)
int retval;
/* No IOMMU */
handlep));
} else if (retval != DDI_SUCCESS) {
return (DDI_FAILURE);
}
/* has an IOMMU */
#else
handlep));
#endif
}
/*ARGSUSED*/
static int
{
/* unbind should have been called first */
return (DDI_SUCCESS);
}
/*
* rootnex_dma_freehdl()
* called from ddi_dma_free_handle().
*/
static int
{
#if !defined(__xpv)
if (IOMMU_USED(rdip)) {
}
#endif
}
/*ARGSUSED*/
static int
{
int kmflag;
int e;
} else {
}
/*
* This is useful for debugging a driver. Not as useful in a production
* system. The only time this will fail is if you have a driver bug.
*/
if (rootnex_bind_check_inuse) {
/*
* No one else should ever have this lock unless someone else
* is trying to use this handle. So contention on the lock
* is the same as inuse being set.
*/
if (e == 0) {
return (DDI_DMA_INUSE);
}
return (DDI_DMA_INUSE);
}
}
/* check the ddi_dma_attr arg to make sure it makes a little sense */
if (rootnex_bind_check_parms) {
if (e != DDI_SUCCESS) {
return (e);
}
}
/* save away the original bind info */
#if !defined(__xpv)
if (rootnex_state->r_intel_iommu_enabled) {
switch (e) {
case IOMMU_SGL_SUCCESS:
goto rootnex_sgl_end;
case IOMMU_SGL_DISABLE:
goto rootnex_sgl_start;
case IOMMU_SGL_NORESOURCES:
return (DDI_DMA_NORESOURCES);
default:
"undefined value returned from"
" intel_iommu_map_sgl: %d",
e);
return (DDI_DMA_NORESOURCES);
}
}
#endif
/*
* Figure out a rough estimate of what maximum number of pages this
* buffer could use (a high estimate of course).
*/
/*
* We'll use the pre-allocated cookies for any bind that will *always*
* fit (more important to be consistent, we don't want to create
* additional degenerate cases).
*/
/*
* For anything larger than that, we'll go ahead and allocate the
* maximum number of pages we expect to see. Hopefuly, we won't be
* seeing this path in the fast path for high performance devices very
* frequently.
*
* a ddi bind interface that allowed the driver to provide storage to
* the bind interface would speed this case up.
*/
} else {
/* convert the sleep flags */
} else {
kmflag = KM_NOSLEEP;
}
/*
* Save away how much memory we allocated. If we're doing a
* nosleep, the alloc could fail...
*/
sizeof (ddi_dma_cookie_t);
return (DDI_DMA_NORESOURCES);
}
}
/*
* Get the real sgl. rootnex_get_sgl will fill in cookie array while
* looking at the contraints in the dma structure. It will then put some
* additional state about the sgl in the dma struct (i.e. is the sgl
* clean, or do we need to do some munging; how many pages need to be
* copied, etc.)
*/
&dma->dp_sglinfo);
/* if we don't need a copy buffer, we don't need to sync */
if (sinfo->si_copybuf_req == 0) {
}
/*
* if we don't need the copybuf and we don't need to do a partial, we
* hit the fast path. All the high performance devices should be trying
* to hit this path. To hit this path, a device should be able to reach
* all of memory, shouldn't try to bind more than it can transfer, and
* handle [sgllen]).
*/
if ((sinfo->si_copybuf_req == 0) &&
/*
* If the driver supports FMA, insert the handle in the FMA DMA
* handle cache.
*/
}
/*
* copy out the first cookie and ccountp, set the cookie
* pointer to the second cookie. The first cookie is passed
* back on the stack. Additional cookies are accessed via
* ddi_dma_nextcookie()
*/
hp->dmai_cookie++;
return (DDI_DMA_MAPPED);
}
/*
* go to the slow path, we may need to alloc more memory, create
* multiple windows, and munge up a sgl to make the device happy.
*/
if ((e != DDI_DMA_MAPPED) && (e != DDI_DMA_PARTIAL_MAP)) {
if (dma->dp_need_to_free_cookie) {
}
return (e);
}
/*
* If the driver supports FMA, insert the handle in the FMA DMA handle
* cache.
*/
}
/* if the first window uses the copy buffer, sync it for the device */
}
/*
* copy out the first cookie and ccountp, set the cookie pointer to the
* If we have a partial map (i.e. multiple windows), the number of
* cookies we return is the number of cookies in the first window.
*/
if (e == DDI_DMA_MAPPED) {
} else {
}
hp->dmai_cookie++;
return (e);
}
/*
* rootnex_dma_bindhdl()
* called from ddi_dma_addr_bind_handle() and ddi_dma_buf_bind_handle().
*/
static int
{
#if !defined(__xpv)
if (IOMMU_USED(rdip)) {
}
#endif
}
/*ARGSUSED*/
static int
{
int e;
/* make sure the buffer wasn't free'd before calling unbind */
if (rootnex_unbind_verify_buffer) {
e = rootnex_verify_buffer(dma);
if (e != DDI_SUCCESS) {
ASSERT(0);
return (DDI_FAILURE);
}
}
/* sync the current window before unbinding the buffer */
}
/*
* If the driver supports FMA, remove the handle in the FMA DMA handle
* cache.
*/
}
}
/*
* cleanup and copy buffer or window state. if we didn't use the copy
* buffer or windows, there won't be much to do :-)
*/
#if !defined(__xpv)
/*
* If intel iommu enabled, clean up the page tables and free the dvma
*/
if (rootnex_state->r_intel_iommu_enabled) {
}
#endif
/*
* If we had to allocate space to for the worse case sgl (it didn't
* fit into our pre-allocate buffer), free that up now
*/
if (dma->dp_need_to_free_cookie) {
}
/*
* clean up the handle so it's ready for the next bind (i.e. if the
* handle is reused).
*/
return (DDI_SUCCESS);
}
/*
* rootnex_dma_unbindhdl()
* called from ddi_dma_unbind_handle()
*/
/*ARGSUSED*/
static int
{
#if !defined(__xpv)
if (IOMMU_USED(rdip)) {
}
#endif
}
#if !defined(__xpv)
static int
{
return (dma->dp_sleep_flags);
}
/*ARGSUSED*/
static void
{
} else {
}
hp->dmai_cookie++;
}
/*ARGSUSED*/
static int
{
int i;
int km_flags;
} else {
}
return (DDI_DMA_NORESOURCES);
}
for (i = 0; i < *ccountp; i++) {
}
return (DDI_SUCCESS);
}
/*ARGSUSED*/
static int
{
} else {
+ dma->dp_cookies;
}
return (DDI_SUCCESS);
}
/*ARGSUSED*/
static int
{
/* check if cookies have not been switched */
return (DDI_SUCCESS);
} else {
+ dma->dp_cookies;
}
return (DDI_SUCCESS);
}
#endif
/*
* rootnex_verify_buffer()
* verify buffer wasn't free'd
*/
static int
{
char b;
int i;
/* Figure out how many pages this buffer occupies */
} else {
}
case DMA_OTYP_PAGES:
/*
* for a linked list of pp's walk through them to make sure
* they're locked and not free.
*/
for (i = 0; i < pcnt; i++) {
return (DDI_FAILURE);
}
}
break;
case DMA_OTYP_VADDR:
case DMA_OTYP_BUFVADDR:
/*
* for an array of pp's walk through them to make sure they're
* not free. It's possible that they may not be locked.
*/
if (pplist) {
for (i = 0; i < pcnt; i++) {
return (DDI_FAILURE);
}
}
/* For a virtual address, try to peek at each page */
} else {
for (i = 0; i < pcnt; i++) {
return (DDI_FAILURE);
vaddr += MMU_PAGESIZE;
}
}
}
break;
default:
ASSERT(0);
break;
}
return (DDI_SUCCESS);
}
/*
* rootnex_clean_dmahdl()
* Clean the dma handle. This should be called on a handle alloc and an
* unbind handle. Set the handle state to the default settings.
*/
static void
{
dma->dp_current_cookie = 0;
dma->dp_copybuf_size = 0;
#if !defined(__amd64)
#endif
/* FMA related initialization */
hp->dmai_fault = 0;
}
/*
* rootnex_valid_alloc_parms()
* Called in ddi_dma_alloc_handle path to validate its parameters.
*/
static int
{
return (DDI_DMA_BADATTR);
}
return (DDI_DMA_BADATTR);
}
attr->dma_attr_sgllen <= 0) {
return (DDI_DMA_BADATTR);
}
/* We should be able to DMA into every byte offset in a page */
if (maxsegmentsize < MMU_PAGESIZE) {
return (DDI_DMA_BADATTR);
}
return (DDI_SUCCESS);
}
/*
* rootnex_valid_bind_parms()
* Called in ddi_dma_*_bind_handle path to validate its parameters.
*/
/* ARGSUSED */
static int
{
#if !defined(__amd64)
/*
* we only support up to a 2G-1 transfer size on 32-bit kernels so
* we can track the offset for the obsoleted interfaces.
*/
return (DDI_DMA_TOOBIG);
}
#endif
return (DDI_SUCCESS);
}
/*
* rootnex_get_sgl()
* Called in bind fastpath to get the sgl. Most of this will be replaced
* with a call to the vm layer when vm2.0 comes around...
*/
static void
{
/* shortcuts */
pcnt = 0;
cnt = 0;
/*
* if we were passed down a linked list of pages, i.e. pointer to
* page_t, use this to get our physical address and buf offset.
*/
if (buftype == DMA_OTYP_PAGES) {
/*
* We weren't passed down a linked list of pages, but if we were passed
* down an array of pages, use this to get our physical address and buf
* offset.
*/
(buftype == DMA_OTYP_BUFVADDR));
}
pcnt++;
/*
* All we have is a virtual address, we'll need to call into the VM
* to get the physical address.
*/
} else {
(buftype == DMA_OTYP_BUFVADDR));
}
}
#ifdef __xpv
/*
* If we're dom0, we're using a real device so we need to load
* the cookies with MFNs instead of PFNs.
*/
#else
#endif
/*
* Setup the first cookie with the physical address of the page and the
* size of the page (which takes into account the initial offset into
* the page.
*/
/*
* Save away the buffer offset into the page. We'll need this later in
* the copy buffer code to help figure out the page index within the
* buffer and the offset into the current page.
*/
/*
* If the DMA engine can't reach the physical address, increase how
* much copy buffer we need. We always increase by pagesize so we don't
* have to worry about converting offsets. Set a flag in the cookies
* dmac_type to indicate that it uses the copy buffer. If this isn't the
* last cookie, go to the next cookie (since we separate each page which
* uses the copy buffer in case the copy buffer is not physically
* contiguous.
*/
cnt++;
}
}
/*
* save this page's physical address so we can figure out if the next
* page is physically contiguous. Keep decrementing size until we are
* done with the buffer.
*/
while (size > 0) {
/* Get the size for this page (i.e. partial or full page) */
if (buftype == DMA_OTYP_PAGES) {
/* get the paddr from the page_t */
/* index into the array of page_t's to get the paddr */
pcnt++;
} else {
/* call into the VM to get the paddr */
vaddr));
}
#ifdef __xpv
/*
* If we're dom0, we're using a real device so we need to load
* the cookies with MFNs instead of PFNs.
*/
#else
#endif
/* check to see if this page needs the copy buffer */
/*
* if there is something in the current cookie, go to
* the next one. We only want one page in a cookie which
* uses the copybuf since the copybuf doesn't have to
* be physically contiguous.
*/
cnt++;
}
#if defined(__amd64)
#else
/*
* save the buf offset for 32-bit kernel. used in the
* obsoleted interfaces.
*/
#endif
/* if this isn't the last cookie, go to the next one */
cnt++;
}
/*
* this page didn't need the copy buffer, if it's not physically
* contiguous, or it would put us over a segment boundary, or it
* puts us over the max cookie size, or the current sgl doesn't
* have anything in it.
*/
/*
* if we're not already in a new cookie, go to the next
* cookie.
*/
cnt++;
}
/* save the cookie information */
#if defined(__amd64)
#else
/*
* save the buf offset for 32-bit kernel. used in the
* obsoleted interfaces.
*/
#endif
/*
* this page didn't need the copy buffer, it is physically
* contiguous with the last page, and it's <= the max cookie
* size.
*/
} else {
/*
* if this exactly == the maximum cookie size, and
* it isn't the last cookie, go to the next cookie.
*/
cnt++;
}
}
/*
* save this page's physical address so we can figure out if the
* next page is physically contiguous. Keep decrementing size
* until we are done with the buffer.
*/
}
/* we're done, save away how many cookies the sgl has */
} else {
}
}
/*
* rootnex_bind_slowpath()
* Call in the bind path if the calling driver can't use the sgl without
* with a partial bind.
*/
static int
{
int e;
int i;
copybuf_used = 0;
/*
* If we're using the copybuf, set the copybuf state in dma struct.
* Needs to be first since it sets the copy buffer size.
*/
if (sinfo->si_copybuf_req != 0) {
if (e != DDI_SUCCESS) {
return (e);
}
} else {
dma->dp_copybuf_size = 0;
}
/*
* Figure out if we need to do a partial mapping. If so, figure out
* if we need to trim the buffers when we munge the sgl.
*/
}
} else {
}
/* If we need to do a partial bind, make sure the driver supports it */
if (dma->dp_partial_required &&
/*
* patchable which allows us to print one warning per major
* number.
*/
if ((rootnex_bind_warn) &&
"driver is using ddi_dma_attr(9S) incorrectly. "
"There is a small risk of data corruption in "
"replaced with a corrected version for proper "
"system operation. To disable this warning, add "
"'set rootnex:rootnex_bind_warn=0' to "
}
return (DDI_DMA_TOOBIG);
}
/*
* we might need multiple windows, setup state to handle them. In this
* code path, we will have at least one window.
*/
if (e != DDI_SUCCESS) {
return (e);
}
cur_offset = 0;
}
/* loop though all the cookies we got back from get_sgl() */
for (i = 0; i < sinfo->si_sgl_size; i++) {
/*
* If we're using the copy buffer, check this cookie and setup
* its associated copy buffer state. If this cookie uses the
* copy buffer, make sure we sync this window during dma_sync.
*/
if (dma->dp_copybuf_size > 0) {
}
}
/*
* save away the cookie size, since it could be modified in
* the windowing code.
*/
/* if we went over max copybuf size */
if (dma->dp_copybuf_size &&
if (e != DDI_SUCCESS) {
return (e);
}
/*
* if the coookie uses the copy buffer, make sure the
* new window we just moved to is set to sync.
*/
}
/* if the cookie cnt == max sgllen, move to the next window */
if (e != DDI_SUCCESS) {
return (e);
}
/*
* if the coookie uses the copy buffer, make sure the
* new window we just moved to is set to sync.
*/
}
/* else if we will be over maxxfer */
dma->dp_maxxfer) {
cookie);
if (e != DDI_SUCCESS) {
return (e);
}
/*
* if the coookie uses the copy buffer, make sure the
* new window we just moved to is set to sync.
*/
}
/* else this cookie fits in the current window */
} else {
window->wd_cookie_cnt++;
}
/* track our offset into the buffer, go to the next cookie */
cur_offset += dmac_size;
cookie++;
}
/* if we ended up with a zero sized window in the end, clean it up */
window--;
}
if (!partial) {
return (DDI_DMA_MAPPED);
}
return (DDI_DMA_PARTIAL_MAP);
}
/*
* rootnex_setup_copybuf()
* Called in bind slowpath. Figures out if we're going to use the copy
* buffer, and if we do, sets up the basic state to handle it.
*/
static int
{
int cansleep;
int e;
#if !defined(__amd64)
int vmflag;
#endif
/* read this first so it's consistent through the routine */
/* We need to call into the rootnex on ddi_dma_sync() */
/* make sure the copybuf size <= the max size */
#if !defined(__amd64)
/*
* now. We only do this for the 32-bit kernel. We use seg kpm space for
* the 64-bit kernel.
*/
/* convert the sleep flags */
} else {
vmflag = VM_NOSLEEP;
}
vmflag);
return (DDI_DMA_NORESOURCES);
}
}
#endif
/* convert the sleep flags */
cansleep = 1;
} else {
cansleep = 0;
}
/*
* Allocate the actual copy buffer. This needs to fit within the DMA
* engine limits, so we can't use kmem_alloc... We don't need
* contiguous memory (sgllen) since we will be forcing windows on
* sgllen anyway.
*/
/*
* this should be < 0 to indicate no limit, but due to a bug in
* the rootnex, we'll set it to the maximum positive int.
*/
if (e != DDI_SUCCESS) {
#if !defined(__amd64)
}
#endif
return (DDI_DMA_NORESOURCES);
}
return (DDI_SUCCESS);
}
/*
* rootnex_setup_windows()
* Called in bind slowpath to setup the window state. We always have windows
* in the slowpath. Even if the window count = 1.
*/
static int
{
dma->dp_current_win = 0;
/* If we don't need to do a partial, we only have one window */
if (!dma->dp_partial_required) {
/*
* we need multiple windows, need to figure out the worse case number
* of windows.
*/
} else {
/*
* if we need windows because we need more copy buffer that
* we allow, the worse case number of windows we could need
* here would be (copybuf space required / copybuf space that
* we have) plus one for remainder, and plus 2 to handle the
* extra pages on the trim for the first and last pages of the
* buffer (a page is the minimum window size so under the right
* attr settings, you could have a window for each page).
* The last page will only be hit here if the size is not a
* multiple of the granularity (which theoretically shouldn't
* be the case but never has been enforced, so we could have
* broken things without it).
*/
} else {
copybuf_win = 0;
}
/*
* if we need windows because we have more cookies than the H/W
* can handle, the number of windows we would need here would
* be (cookie count / cookies count H/W supports) plus one for
* remainder, and plus 2 to handle the extra pages on the trim
* (see above comment about trim)
*/
+ 1) + 2;
} else {
sglwin = 0;
}
/*
* if we need windows because we're binding more memory than the
* H/W can transfer at once, the number of windows we would need
* here would be (xfer count / max xfer H/W supports) plus one
* for remainder, and plus 2 to handle the extra pages on the
* trim (see above comment about trim)
*/
} else {
maxxfer_win = 0;
}
}
/*
* Get space for window and potential copy buffer state. Before we
* go and allocate memory, see if we can get away with using what's
* left in the pre-allocted state or the dynamically allocated sgl.
*/
sizeof (ddi_dma_cookie_t));
/* if we dynamically allocated space for the cookies */
if (dma->dp_need_to_free_cookie) {
/* if we have more space in the pre-allocted buffer, use it */
/*
* else, we have more free space in the dynamically allocated
* buffer, i.e. the buffer wasn't worse case fragmented so we
* didn't need a lot of cookies.
*/
} else {
windowp = (rootnex_window_t *)
}
/* we used the pre-alloced buffer */
} else {
windowp = (rootnex_window_t *)
}
/*
* figure out how much state we need to track the copy buffer. Add an
* addition 8 bytes for pointer alignemnt later.
*/
if (dma->dp_copybuf_size > 0) {
sizeof (rootnex_pgmap_t);
} else {
copy_state_size = 0;
}
/* add an additional 8 bytes for pointer alignment */
/* if we have enough space already, use it */
if (state_available >= space_needed) {
/* not enough space, need to allocate more. */
} else {
return (DDI_DMA_NORESOURCES);
}
}
/*
* we allocate copy buffer state and window state at the same time.
* setup our copy buffer state pointers. Make sure it's aligned.
*/
if (dma->dp_copybuf_size > 0) {
#if !defined(__amd64)
/*
* make sure all pm_mapped, pm_vaddr, and pm_pp are set to
*/
#endif
} else {
}
return (DDI_SUCCESS);
}
/*
* rootnex_teardown_copybuf()
* cleans up after rootnex_setup_copybuf()
*/
static void
{
#if !defined(__amd64)
int i;
/*
* if we allocated kernel heap VMEM space, go through all the pages and
* map out any of the ones that we're mapped into the kernel heap VMEM
* arena. Then free the VMEM space.
*/
}
}
}
#endif
/* if we allocated a copy buffer, free it */
}
}
/*
* rootnex_teardown_windows()
* cleans up after rootnex_setup_windows()
*/
static void
{
/*
* if we had to allocate window state on the last bind (because we
* didn't have enough pre-allocated space in the handle), free it.
*/
if (dma->dp_need_to_free_window) {
}
}
/*
* rootnex_init_win()
* Called in bind slow path during creation of a new window. Initializes
* window state to default values.
*/
/*ARGSUSED*/
static void
{
window->wd_cookie_cnt = 0;
#if !defined(__amd64)
#endif
}
/*
* rootnex_setup_cookie()
* Called in the bind slow path when the sgl uses the copy buffer. If any of
* the sgl uses the copy buffer, we need to go through each cookie, figure
* out if it uses the copy buffer, and if it does, save away everything we'll
* need during sync.
*/
static void
{
#if defined(__amd64)
#else
#endif
/*
* Calculate the page index relative to the start of the buffer. The
* index to the current page for our buffer is the offset into the
* first page of the buffer plus our current offset into the buffer
* itself, shifted of course...
*/
/* if this cookie uses the copy buffer */
/*
* NOTE: we know that since this cookie uses the copy buffer, it
* is <= MMU_PAGESIZE.
*/
/*
* get the offset into the page. For the 64-bit kernel, get the
* pfn which we'll use with seg kpm.
*/
#if defined(__amd64)
/* mfn_to_pfn() is a NOP on i86pc */
#endif /* __amd64 */
/* figure out if the copybuf size is a power of 2 */
} else {
}
/* This page uses the copy buffer */
/*
* save the copy buffer KVA that we'll use with this page.
* if we still fit within the copybuf, it's a simple add.
* otherwise, we need to wrap over using & or % accordingly.
*/
} else {
if (copybuf_sz_power_2) {
(*copybuf_used &
} else {
}
}
/*
* over write the cookie physical address with the address of
* the physical address of the copy buffer page that we will
* use.
*/
#ifdef __xpv
/*
* If we're dom0, we're using a real device so we need to load
* the cookies with MAs instead of PAs.
*/
#else
#endif
/* if we have a kernel VA, it's easy, just save that address */
/*
* save away the page aligned virtual address of the
* driver buffer. Offsets are handled in the sync code.
*/
& MMU_PAGEMASK);
#if !defined(__amd64)
/*
* we didn't need to, and will never need to map this
* page.
*/
#endif
/* we don't have a kernel VA. We need one for the bcopy. */
} else {
#if defined(__amd64)
/*
* for the 64-bit kernel, it's easy. We use seg kpm to
* get a Kernel VA for the corresponding pfn.
*/
#else
/*
* for the 32-bit kernel, this is a pain. First we'll
* save away the page_t or user VA for this page. This
* is needed in rootnex_dma_win() when we switch to a
* new window which requires us to re-map the copy
* buffer.
*/
} else {
(((uintptr_t)
cur_offset) & MMU_PAGEMASK);
}
/*
* save away the page aligned virtual address which was
* allocated from the kernel heap arena (taking into
* account if we need more copy buffer than we alloced
* and use multiple windows to handle this, i.e. &,%).
* NOTE: there isn't and physical memory backing up this
* virtual address space currently.
*/
if ((*copybuf_used + MMU_PAGESIZE) <=
dma->dp_copybuf_size) {
} else {
if (copybuf_sz_power_2) {
(*copybuf_used &
} else {
(*copybuf_used %
dma->dp_copybuf_size)) &
}
}
/*
* if we haven't used up the available copy buffer yet,
* map the kva to the physical page.
*/
} else {
}
/*
* we've used up the available copy buffer, this page
* will have to be mapped during rootnex_dma_win() when
* we switch to a new window which requires a re-map
* the copy buffer. (32-bit kernel only)
*/
} else {
}
#endif
/* go to the next page_t */
}
}
/* add to the copy buffer count */
*copybuf_used += MMU_PAGESIZE;
/*
* This cookie doesn't use the copy buffer. Walk through the pages this
* cookie occupies to reflect this.
*/
} else {
/*
* figure out how many pages the cookie occupies. We need to
* use the original page offset of the buffer and the cookies
* offset in the buffer to do this.
*/
while (pcnt > 0) {
#if !defined(__amd64)
/*
* the 32-bit kernel doesn't have seg kpm, so we need
* to map in the driver buffer (if it didn't come down
* with a kernel VA) on the fly. Since this page doesn't
* use the copy buffer, it's not, or will it ever, have
* to be mapped in.
*/
#endif
/*
* we need to update pidx and cur_pp or we'll loose
* track of where we are.
*/
}
pidx++;
pcnt--;
}
}
}
/*
* rootnex_sgllen_window_boundary()
* Called in the bind slow path when the next cookie causes us to exceed (in
* this case == since we start at 0 and sgllen starts at 1) the maximum sgl
* length supported by the DMA H/W.
*/
static int
{
/*
* if we know we'll never have to trim, it's pretty easy. Just move to
* the next window and init it. We're done.
*/
if (!dma->dp_trim_required) {
(*windowp)++;
(*windowp)->wd_cookie_cnt++;
return (DDI_SUCCESS);
}
/* figure out how much we need to trim from the window */
if (dma->dp_granularity_power_2) {
} else {
}
/* The window's a whole multiple of granularity. We're done */
if (trim_sz == 0) {
(*windowp)++;
(*windowp)->wd_cookie_cnt++;
return (DDI_SUCCESS);
}
/*
* The window's not a whole multiple of granularity, since we know this
* is due to the sgllen, we need to go back to the last cookie and trim
* that one, add the left over part of the old cookie into the new
* window, and then add in the new cookie into the new window.
*/
/*
* make sure the driver isn't making us do something bad... Trimming and
* sgllen == 1 don't go together.
*/
return (DDI_DMA_NOMAPPING);
}
/*
* first, setup the current window to account for the trim. Need to go
* back to the last cookie for this.
*/
cookie--;
/* save the buffer offsets for the next window */
/*
* set this now in case this is the first window. all other cases are
* set in dma_win()
*/
/*
* initialize the next window using what's left over in the previous
* cookie.
*/
(*windowp)++;
(*windowp)->wd_cookie_cnt++;
}
/*
* now go back to the current cookie and add it to the new window. set
* the new window size to the what was left over from the previous
* cookie and what's in the current cookie.
*/
cookie++;
(*windowp)->wd_cookie_cnt++;
/*
* trim plus the next cookie could put us over maxxfer (a cookie can be
* a max size of maxxfer). Handle that case.
*/
/*
* maxxfer is already a whole multiple of granularity, and this
* trim will be <= the previous trim (since a cookie can't be
* larger than maxxfer). Make things simple here.
*/
/* save the buffer offsets for the next window */
/* setup the next window */
(*windowp)++;
(*windowp)->wd_cookie_cnt++;
}
return (DDI_SUCCESS);
}
/*
* rootnex_copybuf_window_boundary()
* Called in bind slowpath when we get to a window boundary because we used
* up all the copy buffer that we have.
*/
static int
{
/*
* the copy buffer should be a whole multiple of page size. We know that
* this cookie is <= MMU_PAGESIZE.
*/
/*
* from now on, all new windows in this bind need to be re-mapped during
* ddi_dma_getwin() (32-bit kernel only). i.e. we ran out out copybuf
* space...
*/
#if !defined(__amd64)
#endif
/* reset copybuf used */
*copybuf_used = 0;
/*
* if we don't have to trim (since granularity is set to 1), go to the
* next window and add the current cookie to it. We know the current
* cookie uses the copy buffer since we're in this code path.
*/
if (!dma->dp_trim_required) {
(*windowp)++;
/* Add this cookie to the new window */
(*windowp)->wd_cookie_cnt++;
*copybuf_used += MMU_PAGESIZE;
return (DDI_SUCCESS);
}
/*
* *** may need to trim, figure it out.
*/
/* figure out how much we need to trim from the window */
if (dma->dp_granularity_power_2) {
} else {
}
/*
* if the window's a whole multiple of granularity, go to the next
* window, init it, then add in the current cookie. We know the current
* cookie uses the copy buffer since we're in this code path.
*/
if (trim_sz == 0) {
(*windowp)++;
/* Add this cookie to the new window */
(*windowp)->wd_cookie_cnt++;
*copybuf_used += MMU_PAGESIZE;
return (DDI_SUCCESS);
}
/*
* *** We figured it out, we definitly need to trim
*/
/*
* make sure the driver isn't making us do something bad...
* Trimming and sgllen == 1 don't go together.
*/
return (DDI_DMA_NOMAPPING);
}
/*
* first, setup the current window to account for the trim. Need to go
* back to the last cookie for this. Some of the last cookie will be in
* the current window, and some of the last cookie will be in the new
* window. All of the current cookie will be in the new window.
*/
cookie--;
/*
* we're trimming the last cookie (not the current cookie). So that
* last cookie may have or may not have been using the copy buffer (
* we know the cookie passed in uses the copy buffer since we're in
* this code path).
*
* If the last cookie doesn't use the copy buffer, nothing special to
* do. However, if it does uses the copy buffer, it will be both the
* last page in the current window and the first page in the next
* window. Since we are reusing the copy buffer (and KVA space on the
* 32-bit kernel), this page will use the end of the copy buffer in the
* current window, and the start of the copy buffer in the next window.
* Track that info... The cookie physical address was already set to
* the copy buffer physical address in setup_cookie..
*/
#if !defined(__amd64)
#endif
}
/* save the buffer offsets for the next window */
/*
* set this now in case this is the first window. all other cases are
* set in dma_win()
*/
/*
* initialize the next window using what's left over in the previous
* cookie.
*/
(*windowp)++;
(*windowp)->wd_cookie_cnt++;
/*
* again, we're tracking if the last cookie uses the copy buffer.
* read the comment above for more info on why we need to track
* additional state.
*
* For the first cookie in the new window, we need reset the physical
* address to DMA into to the start of the copy buffer plus any
* initial page offset which may be present.
*/
poff;
#ifdef __xpv
/*
* If we're dom0, we're using a real device so we need to load
* the cookies with MAs instead of PAs.
*/
#else
#endif
#if !defined(__amd64)
#endif
/* account for the cookie copybuf usage in the new window */
*copybuf_used += MMU_PAGESIZE;
/*
* every piece of code has to have a hack, and here is this
* ones :-)
*
* There is a complex interaction between setup_cookie and the
* copybuf window boundary. The complexity had to be in either
* the maxxfer window, or the copybuf window, and I chose the
* copybuf code.
*
* So in this code path, we have taken the last cookie,
* virtually broken it in half due to the trim, and it happens
* to use the copybuf which further complicates life. At the
* same time, we have already setup the current cookie, which
* is now wrong. More background info: the current cookie uses
* the copybuf, so it is only a page long max. So we need to
* fix the current cookies copy buffer address, physical
* address, and kva for the 32-bit kernel. We due this by
* bumping them by page size (of course, we can't due this on
* the physical address since the copy buffer may not be
* physically contiguous).
*/
cookie++;
#ifdef __xpv
/*
* If we're dom0, we're using a real device so we need to load
* the cookies with MAs instead of PAs.
*/
#else
#endif
#if !defined(__amd64)
#endif
} else {
/* go back to the current cookie */
cookie++;
}
/*
* add the current cookie to the new window. set the new window size to
* the what was left over from the previous cookie and what's in the
* current cookie.
*/
(*windowp)->wd_cookie_cnt++;
/*
* we know that the cookie passed in always uses the copy buffer. We
* wouldn't be here if it didn't.
*/
*copybuf_used += MMU_PAGESIZE;
return (DDI_SUCCESS);
}
/*
* rootnex_maxxfer_window_boundary()
* Called in bind slowpath when we get to a window boundary because we will
* go over maxxfer.
*/
static int
{
/*
* calculate how much we have to trim off of the current cookie to equal
* maxxfer. We don't have to account for granularity here since our
* maxxfer already takes that into account.
*/
/* save cookie size since we need it later and we might change it */
/*
* if we're not trimming the entire cookie, setup the current window to
* account for the trim.
*/
(*windowp)->wd_cookie_cnt++;
/*
* set the adjusted cookie size now in case this is the first
* window. All other windows are taken care of in get win
*/
}
/*
* coffset is the current offset within the cookie, new_offset is the
* current offset with the entire buffer.
*/
/* initialize the next window */
(*windowp)++;
(*windowp)->wd_cookie_cnt++;
}
return (DDI_SUCCESS);
}
/*ARGSUSED*/
static int
{
int e;
/*
* if we don't have any windows, we don't need to sync. A copybuf
* will cause us to have at least one window.
*/
return (DDI_SUCCESS);
}
/* This window may not need to be sync'd */
return (DDI_SUCCESS);
}
/* handle off and len special cases */
if ((off == 0) || (rootnex_sync_ignore_params)) {
} else {
}
if ((len == 0) || (rootnex_sync_ignore_params)) {
} else {
}
/* check the sync args to make sure they make a little sense */
if (rootnex_sync_check_parms) {
if (e != DDI_SUCCESS) {
return (DDI_FAILURE);
}
}
/*
* special case the first page to handle the offset into the page. The
* offset to the current page for our buffer is the offset into the
* first page of the buffer plus our current offset into the buffer
* itself, masked of course.
*/
/* go through all the pages that we want to sync */
while (size > 0) {
/*
* Calculate the page index relative to the start of the buffer.
* The index to the current page for our buffer is the offset
* into the first page of the buffer plus our current offset
* into the buffer itself, shifted of course...
*/
/*
* if this page uses the copy buffer, we need to sync it,
* otherwise, go on to the next page.
*/
if (cbpage->pm_uses_copybuf) {
/* cbaddr and kaddr should be page aligned */
MMU_PAGEOFFSET) == 0);
MMU_PAGEOFFSET) == 0);
/*
* if we're copying for the device, we are going to
* copy from the drivers buffer and to the rootnex
* allocated copy buffer.
*/
if (cache_flags == DDI_DMA_SYNC_FORDEV) {
/*
* copy from the rootnex allocated copy buffer to the
* drivers buffer.
*/
} else {
}
}
/*
* decrement size until we're done, update our offset into the
* buffer, and get the next page size.
*/
/* page offset is zero for the rest of this loop */
poff = 0;
}
return (DDI_SUCCESS);
}
/*
* rootnex_dma_sync()
* called from ddi_dma_sync() if DMP_NOSYNC is not set in hp->dmai_rflags.
* We set DMP_NOSYNC if we're not using the copy buffer. If DMP_NOSYNC
* is set, ddi_dma_sync() returns immediately passing back success.
*/
/*ARGSUSED*/
static int
{
#if !defined(__xpv)
if (IOMMU_USED(rdip)) {
cache_flags));
}
#endif
cache_flags));
}
/*
* rootnex_valid_sync_parms()
* checks the parameters passed to sync to verify they are correct.
*/
static int
{
/*
* the first part of the test to make sure the offset passed in is
* within the window.
*/
return (DDI_FAILURE);
}
/*
* second and last part of the test to make sure the offset and length
* passed in is within the window.
*/
return (DDI_FAILURE);
}
/*
* if we are sync'ing for the device, the DDI_DMA_WRITE flag should
* be set too.
*/
if ((cache_flags == DDI_DMA_SYNC_FORDEV) &&
return (DDI_SUCCESS);
}
/*
* at this point, either DDI_DMA_SYNC_FORCPU or DDI_DMA_SYNC_FORKERNEL
* should be set. Also DDI_DMA_READ should be set in the flags.
*/
if (((cache_flags == DDI_DMA_SYNC_FORCPU) ||
(cache_flags == DDI_DMA_SYNC_FORKERNEL)) &&
return (DDI_SUCCESS);
}
return (DDI_FAILURE);
}
/*ARGSUSED*/
static int
{
#if !defined(__amd64)
int i;
#endif
#if !defined(__amd64)
#endif
/* If we try and get a window which doesn't exist, return failure */
return (DDI_FAILURE);
}
/*
* if we don't have any windows, and they're asking for the first
* window, setup the cookie pointer to the first cookie in the bind.
* setup our return values, then increment the cookie since we return
* the first cookie on the stack.
*/
if (win != 0) {
return (DDI_FAILURE);
}
*offp = 0;
hp->dmai_cookie++;
return (DDI_SUCCESS);
}
/* sync the old window before moving on to the new one */
}
#if !defined(__amd64)
/*
* before we move to the next window, if we need to re-map, unmap all
* the pages in this window.
*/
if (dma->dp_cb_remaping) {
/*
* If we switch to this window again, we'll need to map in
* on the fly next time.
*/
/*
* calculate the page index into the buffer where this window
* starts, and the number of pages this window takes up.
*/
/* unmap pages which are currently mapped in this window */
for (i = 0; i < pcnt; i++) {
}
pidx++;
}
}
#endif
/*
* Move to the new window.
* NOTE: current_win must be set for sync to work right
*/
if (trim->tr_trim_first) {
#if !defined(__amd64)
#endif
if (trim->tr_first_copybuf_win) {
#if !defined(__amd64)
#endif
}
}
if (trim->tr_trim_last) {
if (trim->tr_last_copybuf_win) {
#if !defined(__amd64)
#endif
}
}
/*
* setup the cookie pointer to the first cookie in the window. setup
* our return values, then increment the cookie since we return the
* first cookie on the stack.
*/
hp->dmai_cookie++;
#if !defined(__amd64)
/* re-map copybuf if required for this window */
if (dma->dp_cb_remaping) {
/*
* calculate the page index into the buffer where this
* window starts.
*/
/*
* the first page can get unmapped if it's shared with the
* previous window. Even if the rest of this window is already
* mapped in, we need to still check this one.
*/
}
}
pidx++;
/* map in the rest of the pages if required */
if (window->wd_remap_copybuf) {
/* figure out many pages this window takes up */
/* map pages which require it */
for (i = 1; i < pcnt; i++) {
if (pmap->pm_uses_copybuf) {
}
}
pidx++;
}
}
}
#endif
/* if the new window uses the copy buffer, sync it for the device */
}
return (DDI_SUCCESS);
}
/*
* rootnex_dma_win()
* called from ddi_dma_getwin()
*/
/*ARGSUSED*/
static int
{
#if !defined(__xpv)
if (IOMMU_USED(rdip)) {
}
#endif
}
/*
* ************************
* obsoleted dma routines
* ************************
*/
/*
* rootnex_dma_map()
* called from ddi_dma_setup()
* NO IOMMU in 32 bit mode. The below routines doesn't work in 64 bit mode.
*/
/* ARGSUSED */
static int
{
#if defined(__amd64)
/*
* this interface is not supported in 64-bit x86 kernel. See comment in
* rootnex_dma_mctl()
*/
return (DDI_DMA_NORESOURCES);
#else /* 32-bit x86 kernel */
int e;
/*
* if the driver is just testing to see if it's possible to do the bind,
* we'll use local state. Otherwise, use the handle pointer passed in.
*/
} else {
}
/* convert the limit structure to a dma_attr one */
dma_attr.dma_attr_flags = 0;
if (e != DDI_SUCCESS) {
return (e);
}
if ((e != DDI_DMA_MAPPED) && (e != DDI_DMA_PARTIAL_MAP)) {
return (e);
}
/*
* if the driver is just testing to see if it's possible to do the bind,
* free up the local state and return the result.
*/
if (e == DDI_DMA_MAPPED) {
return (DDI_DMA_MAPOK);
} else {
return (DDI_DMA_NOMAPPING);
}
}
return (e);
#endif /* defined(__amd64) */
}
/*
* rootnex_dma_mctl()
*
* No IOMMU in 32 bit mode. The below routine doesn't work in 64 bit mode.
*/
/* ARGSUSED */
static int
{
#if defined(__amd64)
/*
* DDI_DMA_SMEM_ALLOC & DDI_DMA_IOPB_ALLOC we're changed to have a
* common implementation in genunix, so they no longer have x86
* specific functionality which called into dma_ctl.
*
* The rest of the obsoleted interfaces were never supported in the
* 64-bit x86 kernel. For s10, the obsoleted DDI_DMA_SEGTOC interface
* was not ported to the x86 64-bit kernel do to serious x86 rootnex
* implementation issues.
*
* If you can't use DDI_DMA_SEGTOC; DDI_DMA_NEXTSEG, DDI_DMA_FREE, and
* DDI_DMA_NEXTWIN are useless since you can get to the cookie, so we
* reflect that now too...
*
* Even though we fixed the pointer problem in DDI_DMA_SEGTOC, we are
* not going to put this functionality into the 64-bit x86 kernel now.
* It wasn't ported to the 64-bit kernel for s10, no reason to change
* that in a future release.
*/
return (DDI_FAILURE);
#else /* 32-bit x86 kernel */
int e;
/*
* DDI_DMA_SEGTOC, DDI_DMA_NEXTSEG, and DDI_DMA_NEXTWIN are a little
* hacky since were optimizing for the current interfaces and so we can
* cleanup the mess in genunix. Hopefully we will remove the this
* obsoleted routines someday soon.
*/
switch (request) {
case DDI_DMA_SEGTOC: /* ddi_dma_segtocookie() */
/*
* convert segment to cookie. We don't distinguish between the
* two :-)
*/
return (DDI_SUCCESS);
case DDI_DMA_NEXTSEG: /* ddi_dma_nextseg() */
return (DDI_DMA_STALE);
}
/* handle the case where we don't have any windows */
/*
* if seg == NULL, and we don't have any windows,
* return the first cookie in the sgl.
*/
dma->dp_current_cookie = 0;
return (DDI_SUCCESS);
/* if we have more cookies, go to the next cookie */
} else {
return (DDI_DMA_DONE);
}
dma->dp_current_cookie++;
hp->dmai_cookie++;
return (DDI_SUCCESS);
}
}
/* We have one or more windows */
/*
* if seg == NULL, return the first cookie in the current
* window
*/
dma->dp_current_cookie = 0;
/*
* go to the next cookie in the window then see if we done with
* this window.
*/
} else {
window->wd_cookie_cnt) {
return (DDI_DMA_DONE);
}
dma->dp_current_cookie++;
hp->dmai_cookie++;
}
return (DDI_SUCCESS);
case DDI_DMA_NEXTWIN: /* ddi_dma_nextwin() */
return (DDI_DMA_STALE);
}
/* if win == NULL, return the first window in the bind */
nwin = 0;
/*
* else, go to the next window then see if we're done with all
* the windows.
*/
} else {
return (DDI_DMA_DONE);
}
}
/* switch to the next window */
ASSERT(e == DDI_SUCCESS);
if (e != DDI_SUCCESS) {
return (DDI_DMA_STALE);
}
/* reset the cookie back to the first cookie in the window */
} else {
}
return (DDI_SUCCESS);
case DDI_DMA_FREE: /* ddi_dma_free() */
if (rootnex_state->r_dvma_call_list_id) {
}
return (DDI_SUCCESS);
case DDI_DMA_IOPB_ALLOC: /* get contiguous DMA-able memory */
case DDI_DMA_SMEM_ALLOC: /* get contiguous DMA-able memory */
/* should never get here, handled in genunix */
ASSERT(0);
return (DDI_FAILURE);
case DDI_DMA_KVADDR:
case DDI_DMA_GETERR:
case DDI_DMA_COFF:
return (DDI_FAILURE);
}
return (DDI_FAILURE);
#endif /* defined(__amd64) */
}
/*
* *********
* FMA Code
* *********
*/
/*
* rootnex_fm_init()
* FMA init busop
*/
/* ARGSUSED */
static int
{
return (ddi_system_fmcap);
}
/*
* rootnex_dma_check()
* Function called after a dma fault occurred to find out whether the
* fault address is associated with a driver that is able to handle faults
* and recover from faults.
*/
/* ARGSUSED */
static int
const void *not_used)
{
int i;
int j;
/* The driver has to set DDI_DMA_FLAGERR to recover from dma faults */
/* Get the address that we need to search for */
/*
* if we don't have any windows, we can just walk through all the
* cookies.
*/
/* for each cookie */
/*
* if the faulted address is within the physical address
* range of the cookie, return DDI_FM_NONFATAL.
*/
return (DDI_FM_NONFATAL);
}
}
/* fault_addr not within this DMA handle */
return (DDI_FM_UNKNOWN);
}
/* we have mutiple windows, walk through each window */
/* Go through all the cookies in the window */
for (j = 0; j < window->wd_cookie_cnt; j++) {
/*
* if we are trimming the first cookie in the window,
* and this is the first cookie, adjust the start
* address and size of the cookie to account for the
* trim.
*/
}
/*
* if we are trimming the last cookie in the window,
* and this is the last cookie, adjust the start
* address and size of the cookie to account for the
* trim.
*/
}
/*
* if the faulted address is within the physical address
* range of the cookie, return DDI_FM_NONFATAL.
*/
if ((fault_addr >= start_addr) &&
(fault_addr <= end_addr)) {
return (DDI_FM_NONFATAL);
}
}
}
/* fault_addr not within this DMA handle */
return (DDI_FM_UNKNOWN);
}