/*
 * 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 2008 Sun Microsystems, Inc.  All rights reserved.
 * Use is subject to license terms.
 */

#ifndef _SYS_CYCLIC_IMPL_H
#define	_SYS_CYCLIC_IMPL_H

#ifdef	__cplusplus
extern "C" {
#endif

#include <sys/cyclic.h>
#include <sys/rwlock.h>

/*
 *  Cyclic Subsystem Backend-supplied Interfaces
 *  --------------------------------------------
 *
 *  0  Background
 *
 *    The design, implementation and interfaces of the cyclic subsystem are
 *    covered in detail in block comments in the implementation.  This
 *    comment covers the interface from the cyclic subsystem into the cyclic
 *    backend.  The backend is specified by a structure of function pointers
 *    defined below.
 *
 *  1  Overview
 *
 *      cyb_configure()      <-- Configures the backend on the specified CPU
 *      cyb_unconfigure()    <-- Unconfigures the backend
 *      cyb_enable()         <-- Enables the CY_HIGH_LEVEL interrupt source
 *      cyb_disable()        <-- Disables the CY_HIGH_LEVEL interrupt source
 *      cyb_reprogram()      <-- Reprograms the CY_HIGH_LEVEL interrupt source
 *      cyb_softint()        <-- Generates a soft interrupt
 *      cyb_set_level()      <-- Sets the programmable interrupt level
 *      cyb_restore_level()  <-- Restores the programmable interrupt level
 *      cyb_xcall()          <-- Cross calls to the specified CPU
 *      cyb_suspend()        <-- Suspends the backend
 *      cyb_resume()         <-- Resumes the backend
 *
 *  2  cyb_arg_t cyb_configure(cpu_t *)
 *
 *  2.1  Overview
 *
 *    cyb_configure() should configure the specified CPU for cyclic operation.
 *
 *  2.2  Arguments and notes
 *
 *    cyb_configure() should initialize any backend-specific per-CPU
 *    structures for the specified CPU.  cyb_configure() will be called for
 *    each CPU (including the boot CPU) during boot.  If the platform
 *    supports dynamic reconfiguration, cyb_configure() will be called for
 *    new CPUs as they are configured into the system.
 *
 *  2.3  Return value
 *
 *    cyb_configure() is expected to return a cookie (a cyb_arg_t, which is
 *    of type void *) which will be used as the first argument for all future
 *    cyclic calls into the backend on the specified CPU.
 *
 *  2.4  Caller's context
 *
 *    cpu_lock will be held.  The caller's CPU is unspecified, and may or
 *    may not be the CPU specified to cyb_configure().
 *
 *  3  void cyb_unconfigure(cyb_arg_t arg)
 *
 *  3.1  Overview
 *
 *    cyb_unconfigure() should unconfigure the specified backend.
 *
 *  3.2  Arguments and notes
 *
 *    The only argument to cyb_unconfigure() is a cookie as returned from
 *    cyb_configure().
 *
 *    cyb_unconfigure() should free any backend-specific per-CPU structures
 *    for the specified backend.  cyb_unconfigure() will _only_ be called on
 *    platforms which support dynamic reconfiguration.  If the platform does
 *    not support dynamic reconfiguration, cyb_unconfigure() may panic.
 *
 *    After cyb_unconfigure() returns, the backend must not call cyclic_fire()
 *    on the corresponding CPU; doing so will result in a bad trap.
 *
 *  3.3  Return value
 *
 *    None.
 *
 *  3.4  Caller's context
 *
 *    cpu_lock will be held.  The caller's CPU is unspecified, and may or
 *    may not be the CPU specified to cyb_unconfigure().  The specified
 *    CPU is guaranteed to exist at the time cyb_unconfigure() is called.
 *    The cyclic subsystem is guaranteed to be suspended when cyb_unconfigure()
 *    is called, and interrupts are guaranteed to be disabled.
 *
 *  4  void cyb_enable(cyb_arg_t arg)
 *
 *  4.1  Overview
 *
 *    cyb_enable() should enable the CY_HIGH_LEVEL interrupt source on
 *    the specified backend.
 *
 *  4.2  Arguments and notes
 *
 *    The only argument to cyb_enable() is a backend cookie as returned from
 *    cyb_configure().
 *
 *    cyb_enable() will only be called if a) the specified backend has never
 *    been enabled or b) the specified backend has been explicitly disabled with
 *    cyb_disable().  In either case, cyb_enable() will only be called if
 *    the cyclic subsystem wishes to add a cyclic to the CPU corresponding
 *    to the specified backend.  cyb_enable() will be called before
 *    cyb_reprogram() for a given backend.
 *
 *    cyclic_fire() should not be called on a CPU which has not had its backend
 *    explicitly cyb_enable()'d, but to do so does not constitute fatal error.
 *
 *  4.3  Return value
 *
 *    None.
 *
 *  4.4  Caller's context
 *
 *    cyb_enable() will only be called from CY_HIGH_LEVEL context on the CPU
 *    corresponding to the specified backend.
 *
 *  5  void cyb_disable(cyb_arg_t arg)
 *
 *  5.1  Overview
 *
 *    cyb_disable() should disable the CY_HIGH_LEVEL interrupt source on
 *    the specified backend.
 *
 *  5.2  Arguments and notes
 *
 *    The only argument to cyb_disable() is a backend cookie as returned from
 *    cyb_configure().
 *
 *    cyb_disable() will only be called on backends which have been previously
 *    been cyb_enable()'d.  cyb_disable() will be called when all cyclics have
 *    been juggled away or removed from a cyb_enable()'d CPU.
 *
 *    cyclic_fire() should not be called on a CPU which has had its backend
 *    explicitly cyb_disable()'d, but to do so does not constitute fatal
 *    error.  cyb_disable() is thus not required to check for a pending
 *    CY_HIGH_LEVEL interrupt.
 *
 *  5.3  Return value
 *
 *    None.
 *
 *  5.4  Caller's context
 *
 *    cyb_disable() will only be called from CY_HIGH_LEVEL context on the CPU
 *    corresponding to the specified backend.
 *
 *  6  void cyb_reprogram(cyb_arg_t arg, hrtime_t time)
 *
 *  6.1  Overview
 *
 *    cyb_reprogram() should reprogram the CY_HIGH_LEVEL interrupt source
 *    to fire at the absolute time specified.
 *
 *  6.2  Arguments and notes
 *
 *    The first argument to cyb_reprogram() is a backend cookie as returned from
 *    cyb_configure().
 *
 *    The second argument is an absolute time at which the CY_HIGH_LEVEL
 *    interrupt should fire.  The specified time _may_ be in the past (albeit
 *    the very recent past).  If this is the case, the backend should generate
 *    a CY_HIGH_LEVEL interrupt as soon as possible.
 *
 *    The platform should not assume that cyb_reprogram() will be called with
 *    monotonically increasing values.
 *
 *    If the platform does not allow for interrupts at arbitrary times in the
 *    future, cyb_reprogram() may do nothing -- as long as cyclic_fire() is
 *    called periodically at CY_HIGH_LEVEL.  While this is clearly suboptimal
 *    (cyclic granularity will be bounded by the length of the period between
 *    cyclic_fire()'s), it allows the cyclic subsystem to be implemented on
 *    inferior hardware.
 *
 *  6.3  Return value
 *
 *     None.
 *
 *  6.4  Caller's context
 *
 *    cyb_reprogram() will only be called from CY_HIGH_LEVEL context on the CPU
 *    corresponding to the specified backend.
 *
 *  7  void cyb_softint(cyb_arg_t arg, cyc_level_t level)
 *
 *  7.1  Overview
 *
 *    cyb_softint() should generate a software interrupt on the specified
 *    backend at the specified level.
 *
 *  7.2  Arguments and notes
 *
 *    The first argument to cyb_softint() is a backend cookie as returned from
 *    cyb_configure().  The second argument is the interrupt level at which
 *    the software interrupt should be generated; it will be either
 *    CY_LOCK_LEVEL or CY_LOW_LEVEL.
 *
 *    The software interrupt _must_ be generated on the CPU corresponding
 *    to the specified backend; platforms are _required_ to have a per-CPU
 *    notion of a software interrupt.
 *
 *    Unless a software interrupt is already pending at the specified level,
 *    the software interrupt _must_ be generated.  Once cyclic_softint()
 *    has been called at a given level, the software interrupt at that level
 *    should no longer be considered pending; an intervening CY_HIGH_LEVEL
 *    interrupt and subsequent cyb_softint() must generate another software
 *    interrupt.
 *
 *  7.3  Return value
 *
 *    None.
 *
 *  7.4  Caller's context
 *
 *    cyb_softint() will only be called at a level higher than the one
 *    specified:  if CY_LOCK_LEVEL is specified, the caller will be at
 *    CY_HIGH_LEVEL; if CY_LOW_LEVEL is specified, the caller will be at
 *    either CY_HIGH_LEVEL or CY_LOCK_LEVEL.  cyb_softint() will only be
 *    called on the CPU corresponding to the specified backend.
 *
 *  8  cyb_set_level(cyb_arg_t arg, cyc_level_t level)
 *
 *  8.1  Overview
 *
 *    cyb_set_level() should set the programmable interrupt level to the
 *    level specified.
 *
 *  8.2  Arguments and notes
 *
 *    The first argument to cyb_set_level() is a backend cookie as returned
 *    from cyb_configure().  The second argument is the level to which
 *    the programmable interrupt level should be set; it will be one of
 *    CY_HIGH_LEVEL, CY_LOCK_LEVEL or CY_LOW_LEVEL.
 *
 *    After cyb_set_level() returns, the CPU associated with the specified
 *    backend should accept no interrupt at a level greater than or equal to
 *    the specified level.  This will generally be a wrapper around splx().
 *
 *    The cyclic subsystem will never call cyb_set_level() twice consecutively
 *    on the same backend; there will always be an intervening
 *    cyb_restore_level();
 *
 *  8.3  Return value
 *
 *    cyb_set_level() should return a cookie to be passed back to
 *    cyb_restore_level().  On most implementations, this cookie will be
 *    the spl at the time of cyb_set_level().
 *
 *  8.4  Caller's context
 *
 *    cyb_set_level() is unique in that it is the only backend-provided
 *    interface which may be called in cross call context (see cyb_xcall(),
 *    below).  cyb_set_level() may also be called from any of the cyclic
 *
 *  9  cyb_restore_level(cyb_arg_t arg, cyc_cookie_t cookie)
 *
 *  9.1  Overview
 *
 *    cyb_restore_level() should restore the programmable interrupt level
 *    based upon the specified cookie.
 *
 *  9.2  Arguments and notes
 *
 *    The first argument to cyb_restore_level() is a backend cookie as returned
 *    from cyb_configure().  The second argument is a cookie as returned from
 *    cyb_set_level().
 *
 *    cyb_restore_level() should restore the programmable interrupt level
 *    to its value when cyb_set_level() was called; the cookie is used
 *    to provide a hint to the backend.  cyb_restore_level() will not be
 *    called without a proceeding call to cyb_set_level(), and
 *    cyb_restore_level() will never be called twice consecutively on the
 *    same backend.
 *
 *  9.3  Return value
 *
 *    None.
 *
 *  9.4  Caller's context
 *
 *    The constraints outlined in 5.9.2 imply that cyb_restore_level() can
 *    only be called from CY_HIGH_LEVEL, CY_LOCK_LEVEL or CY_LOW_LEVEL context.
 *    cyb_restore_level() is always called on the CPU associated with the
 *    specified backend.
 *
 *  10  cyb_xcall(cyb_arg_t arg, cpu_t *, void(*func)(void *), void *farg)
 *
 *  10.1  Overview
 *
 *    cyb_xcall() should execute the specified function on the specified CPU.
 *
 *  10.2  Arguments and notes
 *
 *    The first argument to cyb_restore_level() is a backend cookie as returned
 *    from cyb_configure().  The second argument is a CPU on which the third
 *    argument, a function pointer, should be executed.  The fourth argument,
 *    a void *, should be passed as the argument to the specified function.
 *
 *    cyb_xcall() must provide exactly-once semantics.  If the specified
 *    function is called more than once, or not at all, the cyclic subsystem
 *    will become internally inconsistent.  The specified function must be
 *    be executed on the specified CPU, but may be executed in any context
 *    (any interrupt context or kernel context).
 *
 *    cyb_xcall() cannot block.  Any resources which cyb_xcall() needs to
 *    acquire must thus be protected by synchronization primitives which
 *    never require the caller to block.
 *
 *  10.3  Return value
 *
 *    None.
 *
 *  10.4  Caller's context
 *
 *    cpu_lock will be held and kernel preemption may be disabled.  The caller
 *    may be unable to block, giving rise to the constraint outlined in
 *    10.2, above.
 *
 *  11  cyb_suspend(cyb_arg_t arg)
 *
 *  11.1  Overview
 *
 *    cyb_suspend() should suspend the specified backend.
 *
 *  11.2  Arguments and notes
 *
 *    The only argument to cyb_suspend() is a backend cookie as returned from
 *    cyb_configure().
 *
 *    cyb_suspend() will never be called on enabled backends.  The backend
 *    should assume that the machine may be subsequently powered off; any
 *    volatile hardware state should be preserved and restored in cyb_resume().
 *    However, the backend should not _assume_ that the machine will be
 *    powered off; cyb_suspend() may also be called as part of dynamic
 *    reconfiguration.
 *
 *    cyb_suspend() will be called on the corresponding backend of each
 *    CPU in the system in succession, regardless of CPU state (P_ONLINE,
 *    P_OFFLINE, P_NOINTR).  The cyclic subsystem will not suspend only a
 *    fraction of the CPUs.
 *
 *  11.3  Return value
 *
 *    None.
 *
 *  11.4  Caller's context
 *
 *    cyb_suspend() will be called in cross call context on the CPU associated
 *    with the specified backend.
 *
 *  12  cyb_resume(cyb_arg_t arg)
 *
 *  12.1  Overview
 *
 *    cyb_resume() should resume the specified backend.
 *
 *  12.2  Arguments and notes
 *
 *    The only argument to cyb_resume() is a backend cookie as returned from
 *    cyb_resume().
 *
 *    Calls to cyb_resume() will always have been proceeded by corresponding
 *    calls to cyb_suspend().  The machine may have been powered off between
 *    cyb_suspend() and the call to cyb_resume().  cyb_resume() may decide
 *    to restore hardware to its state at the time cyb_suspend() was called.
 *
 *    The cyclic subsystem will make no calls into the backend between
 *    cyb_suspend() and cyb_resume().
 *
 *  12.3  Return value
 *
 *    None.
 *
 *  12.4  Caller's context
 *
 *    cyb_resume() will be called in cross call context on the CPU associated
 *    with the specified backend.
 */
typedef struct cyc_backend {
	cyb_arg_t (*cyb_configure)(cpu_t *);
	void (*cyb_unconfigure)(cyb_arg_t);
	void (*cyb_enable)(cyb_arg_t);
	void (*cyb_disable)(cyb_arg_t);
	void (*cyb_reprogram)(cyb_arg_t, hrtime_t);
	void (*cyb_softint)(cyb_arg_t, cyc_level_t);
	cyc_cookie_t (*cyb_set_level)(cyb_arg_t, cyc_level_t);
	void (*cyb_restore_level)(cyb_arg_t, cyc_cookie_t);
	void (*cyb_xcall)(cyb_arg_t, cpu_t *, cyc_func_t, void *);
	void (*cyb_suspend)(cyb_arg_t);
	void (*cyb_resume)(cyb_arg_t);
	cyb_arg_t cyb_arg;
} cyc_backend_t;

extern void cyclic_init(cyc_backend_t *be, hrtime_t resolution);
extern void cyclic_mp_init();

#ifdef DEBUG
#define	CYCLIC_TRACE
#endif

typedef enum {
	CYS_ONLINE,
	CYS_OFFLINE,
	CYS_EXPANDING,
	CYS_REMOVING,
	CYS_SUSPENDED
} cyc_state_t;

#define	CYF_FREE		0x0001
#define	CYF_CPU_BOUND		0x0002
#define	CYF_PART_BOUND		0x0004

typedef struct cyclic {
	hrtime_t cy_expire;
	hrtime_t cy_interval;
	void (*cy_handler)(void *);
	void *cy_arg;
	uint32_t cy_pend;
	uint16_t cy_flags;
	cyc_level_t cy_level;
} cyclic_t;

typedef struct cyc_pcbuffer {
	cyc_index_t *cypc_buf;
	int cypc_prodndx;
	int cypc_consndx;
	int cypc_sizemask;
} cyc_pcbuffer_t;

typedef struct cyc_softbuf {
	uchar_t cys_hard;		/* Can only be zero or one */
	uchar_t cys_soft;		/* Can only be zero or one */
	cyc_pcbuffer_t cys_buf[2];
} cyc_softbuf_t;

#define	CY_NTRACEREC		512

typedef struct cyc_tracerec {
	hrtime_t cyt_tstamp;
	char *cyt_why;
	uint64_t cyt_arg0;
	uint64_t cyt_arg1;
} cyc_tracerec_t;

typedef struct cyc_tracebuf {
	int cyt_ndx;
	cyc_tracerec_t cyt_buf[CY_NTRACEREC];
} cyc_tracebuf_t;

#define	CY_NCOVERAGE	127

typedef struct cyc_coverage {
	char *cyv_why;
	int cyv_passive_count;
	int cyv_count[CY_LEVELS];
	uint64_t cyv_arg0;
	uint64_t cyv_arg1;
} cyc_coverage_t;

typedef struct cyc_cpu {
	cpu_t *cyp_cpu;
	cyc_index_t *cyp_heap;
	cyclic_t *cyp_cyclics;
	cyc_index_t cyp_nelems;
	cyc_index_t cyp_size;
	cyc_state_t cyp_state;
	cyc_softbuf_t cyp_softbuf[CY_SOFT_LEVELS];
	cyc_backend_t *cyp_backend;
	ksema_t cyp_modify_wait;
	uint32_t cyp_modify_levels;
	uint32_t cyp_rpend;
#ifdef CYCLIC_TRACE
	cyc_tracebuf_t cyp_trace[CY_LEVELS];
#endif
} cyc_cpu_t;

typedef struct cyc_omni_cpu {
	cyc_cpu_t *cyo_cpu;
	cyc_index_t cyo_ndx;
	void *cyo_arg;
	struct cyc_omni_cpu *cyo_next;
} cyc_omni_cpu_t;

typedef struct cyc_id {
	krwlock_t cyi_lock;
	cyc_cpu_t *cyi_cpu;
	cyc_index_t cyi_ndx;
	struct cyc_id *cyi_prev;
	struct cyc_id *cyi_next;
	cyc_omni_handler_t cyi_omni_hdlr;
	cyc_omni_cpu_t *cyi_omni_list;
} cyc_id_t;

typedef struct cyc_xcallarg {
	cyc_cpu_t *cyx_cpu;
	cyc_handler_t *cyx_hdlr;
	cyc_time_t *cyx_when;
	cyc_index_t cyx_ndx;
	cyc_index_t *cyx_heap;
	cyclic_t *cyx_cyclics;
	cyc_index_t cyx_size;
	uint16_t cyx_flags;
	int cyx_wait;
} cyc_xcallarg_t;

#define	CY_DEFAULT_PERCPU	1
#define	CY_PASSIVE_LEVEL	-1

#define	CY_WAIT			0
#define	CY_NOWAIT		1

#define	CYC_HEAP_PARENT(ndx)		(((ndx) - 1) >> 1)
#define	CYC_HEAP_RIGHT(ndx)		(((ndx) + 1) << 1)
#define	CYC_HEAP_LEFT(ndx)		((((ndx) + 1) << 1) - 1)

#ifdef	__cplusplus
}
#endif

#endif /* _SYS_CYCLIC_IMPL_H */