/*
* CDDL HEADER START
*
* The contents of this file are subject to the terms of the
* Common Development and Distribution License (the "License").
* You may not use this file except in compliance with the License.
*
* You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
* or http://www.opensolaris.org/os/licensing.
* See the License for the specific language governing permissions
* and limitations under the License.
*
* When distributing Covered Code, include this CDDL HEADER in each
* file and include the License file at usr/src/OPENSOLARIS.LICENSE.
* If applicable, add the following below this CDDL HEADER, with the
* fields enclosed by brackets "[]" replaced with your own identifying
* information: Portions Copyright [yyyy] [name of copyright owner]
*
* CDDL HEADER END
*/
/*
* Copyright 2007 Sun Microsystems, Inc. All rights reserved.
* Use is subject to license terms.
*/
#pragma ident "%Z%%M% %I% %E% SMI"
#include <sys/types.h>
#include <sys/systm.h>
#include <sys/sysmacros.h>
#include <sys/kobj.h>
#include <sys/membar.h>
#include <sys/dmv.h>
#include <sys/prom_debug.h>
#include <sys/machsystm.h>
#include <vm/vm_dep.h>
/*
* Implementation of databearing mondo vector handler registration routines.
* See PSARC 1998/222 for more details.
*/
/*
* The dmv_interface_*_version variables are provided to protect a
* driver against changes in the databearing mondo interfaces.
*
* The major version is incremented when an incompatible change
* is made to an interface; for instance, a routine which used to take
* 3 parameters now takes 4, or a routine which used have the semantics
* "do X" now has the semantics "do Y". Before calling any of the
* databearing mondo routines, a driver must check the major version
* it was compiled with (i.e., the constant DMV_INTERFACE_MAJOR_VERSION)
* against the contents of dmv_interface_major_version. If the two
* are different, the driver must refuse to operate.
*
* The minor version is incremented when an upward-compatible change
* is made to an interface; for instance, a routine now supports a new
* flag bit (in an existing flags argument). A client can use the
* minor version to see whether a feature it depends on is available
* in its environment; in order to enable this, the documentation
* for new features should note which major and minor version the
* feature first appears in.
*/
int dmv_interface_major_version = DMV_INTERFACE_MAJOR_VERSION;
int dmv_interface_minor_version = DMV_INTERFACE_MINOR_VERSION;
/*
* These are where the number of hardware and software DMV inums are kept.
* If they're zero, we use the platform's default values. (These are not
* patchable in /etc/system, since the dispatch table is allocated before
* /etc/system is loaded; however, you could patch them by adb'ing unix.)
*/
uint_t dmv_hwint = 0;
uint_t dmv_swint = 0;
uint_t dmv_totalints = 0;
struct dmv_disp *dmv_dispatch_table = (struct dmv_disp *)0;
/*
* dmv_disp_lock protects the dispatch table from being modified by two
* threads concurrently. It is not used to protect the table from being
* modified while being used by the actual interrupt dispatch code; see
* comments at the end of dmv.h for the rationale.
*/
kmutex_t dmv_disp_lock;
/*
* dmv_add_intr is called to add a databearing mondo interrupt handler
* for a real device to the system. Only one handler may be registered
* for a dmv_inum at any one time.
*
* Note that if a processor receives a databearing mondo interrupt
* for which a handler has not been registered, the behavior is
* undefined. (Current practice for normal mondos which are unhandled
* depends on whether DEBUG is on; a DEBUG kernel prints an error
* and breaks to OBP, while a non-DEBUG kernel simply panics. This
* model will likely be followed for databearing mondos.)
*
* Parameters:
* dmv_inum interrupt number for the device.
*
* routine pointer to the device's vectored interrupt
* handler. This routine is subject to the
* constraints outlined below in "Handler
* Characteristics and Environment".
*
* arg argument which will be passed to the device's
* handler.
*
* Return value: 0 if the handler was added successfully, -1 if
* handler was already registered for the given
* dmv_inum.
*
* Handler Characteristics and Environment
*
* Handler Entry:
*
* On entry to the handler, the %g registers are set as follows:
*
* %g1 The argument (arg) passed to dmv_add_intr().
* %g2 Word 0 of the incoming mondo vector.
*
*
* Handler Constraints:
*
* While executing, the handler must obey the following rules:
*
* 1. The handler is limited to the use of registers %g1 through
* %g7.
*
* 2. The handler may not modify %cwp (i.e., may not execute a
* SAVE or RESTORE instruction).
*
* 3. The handler may not read or write the stack.
*
* 4. The handler may not call any other DDI or kernel routines.
*
* 5. The handler may not call any other routines inside the
* handler's own driver, since this would modify %o7; however,
* it is permissible to jump to a routine within the handler's
* driver.
*
* 6. The handler may read the Incoming Interrupt Vector Data
* registers, and the Interrupt Vector Receive register, but
* must not modify these registers. (Note: symbols for the
* ASIs and addresses of these registers are in <sys/spitasi.h>
* and <sys/intreg.h>.)
*
* 7. The handler may read or write driver-private data
* structures; in order to protect against simultaneous
* modification by other driver routines, nonblocking data
* sharing algorithms must be used. (For instance,
* compare-and-swap could be used to update counters or add
* entries to linked lists; producer-consumer queues are
* another possibility.)
*
* 8. The handler should neither read nor write any other
* processor register nor kernel data item which is not
* explicitly mentioned in this list. [Yes, this is rather
* strict; the intent here is that as handler implementations
* are done, and more experience is gained, additional items
* may be permitted.]
*
*
* Handler Exit:
*
* When the handler's processing is complete, the handler must
* exit by jumping to the label dmv_finish_intr. At this time,
* the handler may optionally request the execution of a soft
* interrupt routine in order to do further processing at normal
* interrupt level. It is strongly advised that drivers do
* minimal processing in their databearing mondo handlers;
* whenever possible, tasks should be postponed to a later
* soft interrupt routine. (This is analogous to the DDI
* "high-level interrupt" concept, although a databearing mondo
* handler's environment is even more restrictive than that of
* a high-level interrupt routine.)
*
* Soft interrupt routines should be registered by calling
* add_softintr(), which will return an interrupt number. This
* interrupt number should be saved in a driver-private data
* structure for later use.
*
* The contents of %g1 on entry to dmv_finish_intr determine
* whether a soft interrupt routine will be called, as follows:
*
* If %g1 is less than zero, no interrupt will be queued.
*
* Otherwise, %g1 is assumed to be an interrupt number
* obtained from add_softintr. This interrupt routine
* will be executed in the normal way at the requested
* priority. (Note that this routine may or may not
* execute on the same CPU as the current handler.)
*/
int
dmv_add_intr(int dmv_inum, void (*routine)(), void *arg)
{
if (dmv_inum < 0 || dmv_inum >= dmv_hwint)
return (-1);
mutex_enter(&dmv_disp_lock);
if (dmv_dispatch_table[dmv_inum].dmv_func != 0) {
mutex_exit(&dmv_disp_lock);
return (-1);
}
dmv_dispatch_table[dmv_inum].dmv_arg = arg;
membar_sync();
dmv_dispatch_table[dmv_inum].dmv_func = routine;
mutex_exit(&dmv_disp_lock);
return (0);
}
/*
* dmv_add_softintr is called to add a databearing mondo interrupt
* handler for a pseudo-device to the system.
*
* Parameters:
* routine pointer to the device's vectored interrupt
* handler. This routine is subject to the
* constraints outlined above in "Handler
* Characteristics and Environment".
*
* arg argument which will be passed to the device's
* handler.
*
* Return value: dmv_inum allocated if one was available, -1 if
* all soft dmv_inums are already allocated
*/
int
dmv_add_softintr(void (*routine)(void), void *arg)
{
int i;
mutex_enter(&dmv_disp_lock);
for (i = dmv_hwint; i < dmv_totalints; i++) {
if (dmv_dispatch_table[i].dmv_func == 0) {
dmv_dispatch_table[i].dmv_arg = arg;
membar_sync();
dmv_dispatch_table[i].dmv_func = routine;
mutex_exit(&dmv_disp_lock);
return (i);
}
}
mutex_exit(&dmv_disp_lock);
return (-1);
}
/*
* dmv_rem_intr is called to remove a databearing interrupt handler
* from the system.
*
* Parameters:
* dmv_inum interrupt number for the device.
*
* Return value: 0 if the handler was removed successfully, -1
* if no handler was registered for the given
* dmv_inum.
*/
int
dmv_rem_intr(int dmv_inum)
{
if (dmv_inum < 0 || dmv_inum >= (dmv_totalints))
return (-1);
mutex_enter(&dmv_disp_lock);
if (dmv_dispatch_table[dmv_inum].dmv_func == 0) {
mutex_exit(&dmv_disp_lock);
return (-1);
}
dmv_dispatch_table[dmv_inum].dmv_func = 0;
mutex_exit(&dmv_disp_lock);
return (0);
}
/*
* Allocate the dmv dispatch table from nucleus data memory.
*/
int
ndata_alloc_dmv(struct memlist *ndata)
{
size_t alloc_sz;
uint_t plat_hwint = 0;
uint_t plat_swint = 0;
void (*plat_dmv_params)(uint_t *, uint_t *);
/*
* Get platform default values, if they exist
*/
plat_dmv_params = (void (*)(uint_t *, uint_t *))
kobj_getsymvalue("plat_dmv_params", 0);
if (plat_dmv_params)
(*plat_dmv_params)(&plat_hwint, &plat_swint);
/*
* Set sizes to platform defaults if user hasn't set them
*/
if (dmv_hwint == 0)
dmv_hwint = plat_hwint;
if (dmv_swint == 0)
dmv_swint = plat_swint;
/*
* Allocate table if we need it
*/
dmv_totalints = dmv_hwint + dmv_swint;
if (dmv_totalints != 0) {
alloc_sz = sizeof (struct dmv_disp) * (dmv_totalints + 1);
dmv_dispatch_table = ndata_alloc(ndata, alloc_sz,
sizeof (struct dmv_disp));
if (dmv_dispatch_table == NULL)
return (-1);
bzero(dmv_dispatch_table, alloc_sz);
/* use uintptr_t to suppress the gcc warning */
PRM_DEBUG((uintptr_t)dmv_dispatch_table);
}
return (0);
}