root/usr/src/uts/sun4/os/dmv.c
/*
 * 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);
}