cnex.c revision b0fc0e77220f1fa4c933fd58a4e1dedcd650b0f1
/*
* 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 2006 Sun Microsystems, Inc. All rights reserved.
* Use is subject to license terms.
*/
#pragma ident "%Z%%M% %I% %E% SMI"
/*
* Logical domain channel devices are devices implemented entirely
* in software; cnex is the nexus for channel-devices. They use
* the HV channel interfaces via the LDC transport module to send
* and receive data and to register callbacks.
*/
#include <sys/ddi_impldefs.h>
#include <sys/instance.h>
#include <sys/machsystm.h>
#include <sys/ddi_intr_impl.h>
#include <sys/hypervisor_api.h>
#include <sys/mach_descrip.h>
/*
* Internal functions/information
*/
static struct cnex_pil_map cnex_class_to_pil[] = {
{LDC_DEV_GENERIC, PIL_3},
{LDC_DEV_BLK, PIL_4},
{LDC_DEV_BLK_SVC, PIL_3},
{LDC_DEV_NT, PIL_6},
{LDC_DEV_NT_SVC, PIL_4},
};
#define CNEX_MAX_DEVS (sizeof (cnex_class_to_pil) / \
sizeof (cnex_class_to_pil[0]))
static void *cnex_state;
static void cnex_intr_redist(void *arg);
/*
* Debug info
*/
#ifdef DEBUG
/*
* Print debug messages
*
* set cnexdbg to 0xf for enabling all msgs
* 0x8 - Errors
* 0x4 - Warnings
* 0x2 - All debug messages
* 0x1 - Minimal debug messages
*/
int cnexdbg = 0x8;
static void
{
char buf[512];
}
#define D1 \
if (cnexdbg & 0x01) \
#define D2 \
if (cnexdbg & 0x02) \
#define DWARN \
if (cnexdbg & 0x04) \
#define DERR \
if (cnexdbg & 0x08) \
#else
#define D1
#define D2
#define DWARN
#define DERR
#endif
/*
* Config information
*/
void *);
static struct bus_ops cnex_bus_ops = {
nullbusmap, /* bus_map */
NULL, /* bus_get_intrspec */
NULL, /* bus_add_intrspec */
NULL, /* bus_remove_intrspec */
i_ddi_map_fault, /* bus_map_fault */
ddi_no_dma_map, /* bus_dma_map */
ddi_no_dma_allochdl, /* bus_dma_allochdl */
NULL, /* bus_dma_freehdl */
NULL, /* bus_dma_bindhdl */
NULL, /* bus_dma_unbindhdl */
NULL, /* bus_dma_flush */
NULL, /* bus_dma_win */
NULL, /* bus_dma_ctl */
cnex_ctl, /* bus_ctl */
ddi_bus_prop_op, /* bus_prop_op */
0, /* bus_get_eventcookie */
0, /* bus_add_eventcall */
0, /* bus_remove_eventcall */
0, /* bus_post_event */
NULL, /* bus_intr_ctl */
NULL, /* bus_config */
NULL, /* bus_unconfig */
NULL, /* bus_fm_init */
NULL, /* bus_fm_fini */
NULL, /* bus_fm_access_enter */
NULL, /* bus_fm_access_exit */
NULL, /* bus_power */
NULL /* bus_intr_op */
};
static struct cb_ops cnex_cb_ops = {
cnex_open, /* open */
cnex_close, /* close */
nodev, /* strategy */
nodev, /* print */
nodev, /* dump */
nodev, /* read */
nodev, /* write */
cnex_ioctl, /* ioctl */
nodev, /* devmap */
nodev, /* mmap */
nodev, /* segmap */
nochpoll, /* poll */
ddi_prop_op, /* cb_prop_op */
0, /* streamtab */
};
DEVO_REV, /* devo_rev, */
0, /* refcnt */
ddi_getinfo_1to1, /* info */
nulldev, /* identify */
nulldev, /* probe */
cnex_attach, /* attach */
cnex_detach, /* detach */
nodev, /* reset */
&cnex_cb_ops, /* driver operations */
&cnex_bus_ops, /* bus operations */
nulldev /* power */
};
/*
* Module linkage information for the kernel.
*/
"sun4v channel-devices nexus driver v%I%",
&cnex_ops,
};
static struct modlinkage modlinkage = {
};
int
_init(void)
{
int err;
sizeof (cnex_soft_state_t), 0)) != 0) {
return (err);
}
return (err);
}
return (0);
}
int
_fini(void)
{
int err;
return (err);
return (0);
}
int
{
}
/*
* Callback function invoked by the interrupt redistribution
* framework. This will redirect interrupts at CPUs that are
* currently available in the system.
*/
static void
cnex_intr_redist(void *arg)
{
int intr_state;
int rv;
/*
* Don't do anything for disabled interrupts.
*/
if (rv) {
DWARN("cnex_intr_redist: tx ino=0x%llx, "
return;
}
if (intr_state == HV_INTR_NOTVALID) {
continue;
}
cpuid = intr_dist_cpuid();
/* disable interrupts */
if (rv) {
DWARN("cnex_intr_redist: tx ino=0x%llx, "
return;
}
/*
* Make a best effort to wait for pending interrupts
* to finish. There is not much we can do if we timeout.
*/
do {
if (rv) {
DWARN("cnex_intr_redist: tx ino=0x%llx,"
return;
}
break;
else
} while (!panicstr &&
}
/*
* Don't do anything for disabled interrupts.
*/
if (rv) {
DWARN("cnex_intr_redist: rx ino=0x%llx, "
return;
}
if (intr_state == HV_INTR_NOTVALID) {
continue;
}
cpuid = intr_dist_cpuid();
/* disable interrupts */
if (rv) {
DWARN("cnex_intr_redist: rx ino=0x%llx, "
return;
}
/*
* Make a best effort to wait for pending interrupts
* to finish. There is not much we can do if we timeout.
*/
do {
if (rv) {
DWARN("cnex_intr_redist: rx ino=0x%llx,"
return;
}
break;
else
} while (!panicstr &&
}
/* next channel */
}
}
/*
* Exported interface to register a LDC endpoint with
* the channel nexus
*/
static int
{
int idx;
/* Get device instance and structure */
/* Check to see if channel is already registered */
while (cldcp) {
return (EINVAL);
}
}
DWARN("cnex_reg_chan: cannot init MD\n");
return (ENXIO);
}
/* search for all channel_endpoint nodes */
if (num_channels <= 0) {
DWARN("cnex_reg_chan: invalid channel id\n");
(void) md_fini_handle(mdp);
return (EINVAL);
}
/* Get the channel ID */
if (status) {
DWARN("cnex_reg_chan: cannot read LDC ID\n");
(void) md_fini_handle(mdp);
return (ENXIO);
}
continue;
/* Get the Tx and Rx ino */
if (status) {
DWARN("cnex_reg_chan: cannot read Tx ino\n");
(void) md_fini_handle(mdp);
return (ENXIO);
}
if (status) {
DWARN("cnex_reg_chan: cannot read Rx ino\n");
(void) md_fini_handle(mdp);
return (ENXIO);
}
}
(void) md_fini_handle(mdp);
/* Allocate a new channel structure */
/* Initialize the channel */
/* add channel to nexus channel list */
return (0);
}
/*
*/
static int
{
int instance;
/* Get device instance and structure */
/* get channel info */
while (cldcp) {
break;
}
return (EINVAL);
}
/* get channel lock */
/* get interrupt type */
if (itype == CNEX_TX_INTR) {
} else if (itype == CNEX_RX_INTR) {
} else {
return (EINVAL);
}
/* check if a handler is already added */
DWARN("cnex_add_intr: interrupt handler exists\n");
return (EINVAL);
}
/* save interrupt handler info */
/*
* FIXME - generate the interrupt cookie
* using the interrupt registry
*/
D1("cnex_add_intr: add hdlr, cfghdl=0x%llx, ino=0x%llx, "
/* Pick a PIL on the basis of the channel's devclass */
break;
}
}
/* add interrupt to solaris ivec table */
/* set the cookie in the HV */
/* pick next CPU in the domain for this channel */
cpuid = intr_dist_cpuid();
/* set the target CPU and then enable interrupts */
if (rv) {
DWARN("cnex_add_intr: ino=0x%llx, cannot set target cpu\n",
goto hv_error;
}
if (rv) {
DWARN("cnex_add_intr: ino=0x%llx, cannot set state\n",
goto hv_error;
}
if (rv) {
DWARN("cnex_add_intr: ino=0x%llx, cannot set valid\n",
goto hv_error;
}
return (0);
return (ENXIO);
}
/*
* Exported interface to unregister a LDC endpoint with
* the channel nexus
*/
static int
{
int instance;
/* Get device instance and structure */
/* find and remove channel from list */
prev_cldcp = NULL;
while (cldcp) {
break;
prev_cldcp = cldcp;
}
if (cldcp == 0) {
return (EINVAL);
}
DWARN("cnex_unreg_chan: handlers still exist\n");
return (ENXIO);
}
if (prev_cldcp)
else
/* destroy mutex */
/* free channel */
return (0);
}
/*
*/
static int
{
/* Get device instance and structure */
/* get channel info */
while (cldcp) {
break;
}
return (EINVAL);
}
/* get rid of the channel intr handler */
/* get interrupt type */
if (itype == CNEX_TX_INTR) {
} else if (itype == CNEX_RX_INTR) {
} else {
DWARN("cnex_rem_intr: invalid interrupt type\n");
return (EINVAL);
}
/* check if a handler is already added */
DWARN("cnex_rem_intr: interrupt handler does not exist\n");
return (EINVAL);
}
if (rv) {
return (ENXIO);
}
/*
* Check if there are pending interrupts. If interrupts are
* pending return EAGAIN.
*/
if (rv) {
DWARN("cnex_rem_intr: ino=0x%llx, cannot get state\n",
return (ENXIO);
}
/* if interrupts are still pending print warning */
if (istate != HV_INTR_IDLE_STATE) {
DWARN("cnex_rem_intr: cannot remove intr busy ino=%x\n",
return (EAGAIN);
}
/* Pick a PIL on the basis of the channel's devclass */
break;
}
}
/* remove interrupt */
/* clear interrupt info */
return (0);
}
/*
*/
static int
{
int rv;
int instance;
/* Get device instance and structure */
/* get channel info */
while (cldcp) {
break;
}
return (EINVAL);
}
/* get interrupt type */
if (itype == CNEX_TX_INTR) {
} else if (itype == CNEX_RX_INTR) {
} else {
DWARN("cnex_clr_intr: invalid interrupt type\n");
return (EINVAL);
}
/* check if a handler is already added */
DWARN("cnex_clr_intr: interrupt handler does not exist\n");
return (EINVAL);
}
if (rv) {
DWARN("cnex_clr_intr: cannot clear interrupt state\n");
return (ENXIO);
}
return (0);
}
/*
* Channel nexus interrupt handler wrapper
*/
static uint_t
{
int res;
return (res);
}
/*ARGSUSED*/
static int
{
switch (cmd) {
case DDI_ATTACH:
break;
case DDI_RESUME:
return (DDI_SUCCESS);
default:
return (DDI_FAILURE);
}
/*
* Get the instance specific soft state structure.
* Save the devi for this instance in the soft_state data.
*/
return (DDI_FAILURE);
return (DDI_FAILURE);
}
/* get the sun4v config handle for this device */
/* init channel list mutex */
/* Register with LDC module */
/*
* LDC register will fail if an nexus instance had already
* registered with the LDC framework
*/
if (rv) {
DWARN("cnex_attach: unable to register with LDC\n");
return (DDI_FAILURE);
}
DDI_NT_NEXUS, 0) != DDI_SUCCESS) {
return (DDI_FAILURE);
}
/* Add interrupt redistribution callback. */
return (DDI_SUCCESS);
}
/*ARGSUSED*/
static int
{
int instance;
switch (cmd) {
case DDI_DETACH:
break;
case DDI_SUSPEND:
return (DDI_SUCCESS);
default:
return (DDI_FAILURE);
}
/* check if there are any channels still registered */
return (DDI_FAILURE);
}
/* Unregister with LDC module */
(void) ldc_unregister(&cinfo);
/* Remove interrupt redistribution callback. */
/* destroy mutex */
/* free soft state structure */
return (DDI_SUCCESS);
}
/*ARGSUSED*/
static int
{
int instance;
return (EINVAL);
return (ENXIO);
return (0);
}
/*ARGSUSED*/
static int
{
int instance;
return (EINVAL);
return (ENXIO);
return (0);
}
/*ARGSUSED*/
static int
{
int instance;
return (ENXIO);
}
static int
{
char name[MAXNAMELEN];
int *cnex_regspec;
switch (ctlop) {
case DDI_CTLOPS_REPORTDEV:
return (DDI_FAILURE);
return (DDI_SUCCESS);
case DDI_CTLOPS_INITCHILD:
{
DDI_PROP_DONTPASS, "reg",
return (DDI_FAILURE);
}
return (DDI_SUCCESS);
}
case DDI_CTLOPS_UNINITCHILD:
{
"DDI_CTLOPS_UNINITCHILD(%s, instance=%d)",
return (DDI_SUCCESS);
}
case DDI_CTLOPS_DMAPMAPC:
case DDI_CTLOPS_REPORTINT:
case DDI_CTLOPS_REGSIZE:
case DDI_CTLOPS_NREGS:
case DDI_CTLOPS_SIDDEV:
case DDI_CTLOPS_SLAVEONLY:
case DDI_CTLOPS_AFFINITY:
case DDI_CTLOPS_POKE:
case DDI_CTLOPS_PEEK:
/*
* These ops correspond to functions that "shouldn't" be called
* by a channel-device driver. So we whine when we're called.
*/
return (DDI_FAILURE);
case DDI_CTLOPS_ATTACH:
case DDI_CTLOPS_BTOP:
case DDI_CTLOPS_BTOPR:
case DDI_CTLOPS_DETACH:
case DDI_CTLOPS_DVMAPAGESIZE:
case DDI_CTLOPS_IOMIN:
case DDI_CTLOPS_POWER:
case DDI_CTLOPS_PTOB:
default:
/*
*/
}
}
/* -------------------------------------------------------------------------- */