cnex.c revision 0de66c941512d3ab0d15397815d8c9c552bcbf8d
/*
* 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 2008 Sun Microsystems, Inc. All rights reserved.
* Use is subject to license terms.
*/
/*
* 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_intr_map cnex_class_to_intr[] = {
{LDC_DEV_GENERIC, PIL_3, 0},
{LDC_DEV_SERIAL, PIL_6, 0}
};
#define CNEX_MAX_DEVS (sizeof (cnex_class_to_intr) / \
sizeof (cnex_class_to_intr[0]))
#define CNEX_TX_INTR_WEIGHT 0
static int cnex_wait_retries = 3;
static void *cnex_state;
/*
* Channel Interrupt Distribution
*
* In order to balance interrupts among available CPUs, we use
* the intr_dist_cpuid_{add,remove}_device_weight() interface to
* assign weights to channel interrupts. These weights, which are
* defined in the cnex_intr_map structure, influence which CPU
* is returned by intr_dist_cpuid() when called via the cnex
* interrupt redistribution callback cnex_intr_redist().
* Interrupts for VIO devclass channels are given more weight than
* other interrupts because they are expected to occur more
* frequently and have a larger impact on overall performance.
* Transmit interrupts are given a zero weight because they are
* not used.
*
* The interrupt weights influence the target CPU selection when
* interrupts are redistributed and when they are added. However,
* removal of interrupts can unbalance the distribution even if
* they are removed in converse order--compared to the order they
* are added. This can occur when interrupts are removed after
* redistribution occurs.
*
* Channel interrupt weights affect interrupt-CPU distribution
* relative to other weighted interrupts on the system. For VIO
* devclass channels, values are chosen to match those used by
* the PCI express nexus driver for net and storage devices.
*/
/*
* 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 */
ddi_quiesce_not_needed, /* quiesce */
};
/*
* Module linkage information for the kernel.
*/
"sun4v channel-devices nexus",
&cnex_ops,
};
static struct modlinkage modlinkage = {
};
int
_init(void)
{
int err;
/*
* Check HV intr group api versioning.
* Note that cnex assumes interrupt cookies is
* in version 1.0 of the intr group api.
*/
"group versioning errno=%d", err);
return (err);
return (ENOTSUP);
}
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.
*
* Note: any interrupts with weight greater than or equal to
* weight_max must be redistributed when this callback is
* invoked with (weight == weight_max) which will be once per
* redistribution.
*/
/*ARGSUSED*/
static void
{
}
}
/* next channel */
}
}
/*
* Internal function to replace the CPU used by an interrupt
* during interrupt redistribution.
*/
static int
{
int intr_state;
int rv;
/* Determine if the interrupt is enabled */
if (rv) {
DWARN("cnex_intr_new_cpu: rx ino=0x%llx, can't get valid\n",
return (rv);
}
/* If it is enabled, disable it */
if (intr_state == HV_INTR_VALID) {
if (rv) {
return (rv);
}
}
/* Target the interrupt at a new CPU. */
/* Re-enable the interrupt if it was enabled */
if (intr_state == HV_INTR_VALID) {
}
return (0);
}
/*
* Internal function to disable an interrupt and wait
* for any pending interrupts to finish.
*/
static int
{
/* disable interrupts */
if (rv) {
DWARN("cnex_intr_dis_wait: ino=0x%llx, can't set valid\n",
return (ENXIO);
}
/*
* Make a best effort to wait for pending interrupts
* to finish. There is not much we can do if we timeout.
*/
retries = 0;
do {
if (rv) {
DWARN("cnex_intr_dis_wait: ino=0x%llx, can't get "
return (ENXIO);
}
if (intr_state != HV_INTR_DELIVERED_STATE)
break;
return (0);
}
/*
* Returns the interrupt weight to use for the specified devclass.
*/
static int32_t
{
int idx;
}
}
/*
* If this code is reached, the specified devclass is
* invalid. New devclasses should be added to
* cnex_class_to_intr.
*/
ASSERT(0);
return (0);
}
/*
* 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);
/*
* check to see if we looped through the list of channel IDs without
* matching one (i.e. an 'ino' has not been initialised).
*/
return (ENOENT);
}
/* Allocate a new channel structure */
/* Initialize the channel */
/*
* Add channel to nexus channel list.
* Check again to see if channel is already registered since
* clist_lock was dropped above.
*/
while (cldcp) {
return (EINVAL);
}
}
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 */
/* save data for DTrace probes used by intrstat(1m) */
/*
* Verify that the ino does not generate a cookie which
* is outside the (MINVINTR_COOKIE, MAXIVNUM) range of the
* system interrupt table.
*/
DWARN("cnex_add_intr: invalid cookie %x ino %x\n",
return (EINVAL);
}
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 */
DWARN("cnex_add_intr: add_ivintr fail cookie %x ino %x\n",
return (EINVAL);
}
/* set the cookie in the HV */
/* pick next CPU in the domain for this channel */
/* 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);
}
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;
/*
* The 'interrupt__start' and 'interrupt__complete' probes
* are provided to support 'intrstat' command. These probes
* help monitor the interrupts on a per device basis only.
* In order to provide the ability to monitor the
* activity on a per channel basis, two additional
* probes('channelintr__start','channelintr__complete')
* are provided here.
*/
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:
/*
*/
}
}
/*
* cnex_find_chan_dip -- Find the dip of a device that is corresponding
* to the specific channel. Below are the details on how the dip
* is derived.
*
* - In the MD, the cfg-handle is expected to be unique for
* virtual-device nodes that have the same 'name' property value.
* This value is expected to be the same as that of "reg" property
* of the corresponding OBP device node.
*
* - The value of the 'name' property of a virtual-device node
* in the MD is expected to be the same for the corresponding
* OBP device node.
*
* - Find the virtual-device node corresponding to a channel-endpoint
* by walking backwards. Then obtain the values for the 'name' and
* 'cfg-handle' properties.
*
* - Walk all the children of the cnex, find a matching dip which
* has the same 'name' and 'reg' property values.
*
* - The channels that have no corresponding device driver are
* treated as if they correspond to the cnex driver,
* that is, return cnex dip for them. This means, the
* cnex acts as an umbrella device driver. Note, this is
* for 'intrstat' statistics purposes only. As a result of this,
* the 'intrstat' shows cnex as the device that is servicing the
* interrupts corresponding to these channels.
*
* For now, only one such case is known, that is, the channels that
* are used by the "domain-services".
*/
static dev_info_t *
{
int listsz;
int num_nodes;
int num_devs;
char *md_name;
if (num_devs <= 0) {
DWARN("cnex_find_chan_dip:channel(0x%llx): "
"No virtual-device found\n", chan_id);
goto fdip_exit;
}
DWARN("cnex_find_chan_dip:channel(0x%llx): "
"name property not found\n", chan_id);
goto fdip_exit;
}
D1("cnex_find_chan_dip: channel(0x%llx): virtual-device "
DWARN("cnex_find_chan_dip:channel(0x%llx): virtual-device's "
"cfg-handle property not found\n", chan_id);
goto fdip_exit;
}
D1("cnex_find_chan_dip:channel(0x%llx): virtual-device cfg-handle "
int *cnex_regspec;
char *dev_name;
DDI_PROP_DONTPASS, "name",
&dev_name) != DDI_PROP_SUCCESS) {
DWARN("cnex_find_chan_dip: name property not"
" found for dip(0x%p)\n", cdip);
continue;
}
continue;
}
DDI_PROP_DONTPASS, "reg",
DWARN("cnex_find_chan_dip: reg property not"
" found for dip(0x%p)\n", cdip);
continue;
}
if (*cnex_regspec == cfghdl) {
D1("cnex_find_chan_dip:channel(0x%llx): found "
break;
}
}
/*
* If a virtual-device node exists but no dip found,
* then for now print a DEBUG error message only.
*/
if (num_devs > 0) {
DERR("cnex_find_chan_dip:channel(0x%llx): "
"No device found\n", chan_id);
}
/* If no dip was found, return cnex device's dip. */
}
D1("cnex_find_chan_dip:channel(0x%llx): returning dip=0x%p\n",
return (cdip);
}
/* -------------------------------------------------------------------------- */