/*
* This file and its contents are supplied under the terms of the
* Common Development and Distribution License ("CDDL"), version 1.0.
* You may only use this file in accordance with the terms of version
* 1.0 of the CDDL.
*
* A full copy of the text of the CDDL should have accompanied this
* source. A copy of the CDDL is also available via the Internet at
*/
/*
* Copyright (c) 2013, Joyent, Inc. All rights reserved.
*/
#include <sys/ddi_periodic.h>
#include <sys/id_space.h>
#include <sys/sysmacros.h>
#include <sys/taskq_impl.h>
extern void sir_on(int);
/*
* The ddi_periodic_add(9F) Implementation
*
* This file contains the implementation of the ddi_periodic_add(9F) interface.
* It is a thin wrapper around the cyclic subsystem (see documentation in
* (and unregistering) callbacks for periodic invocation at arbitrary
* interrupt levels, or in kernel context.
*
* Each call to ddi_periodic_add will result in a new opaque handle, as
* allocated from an id_space, a new "periodic" object (ddi_periodic_impl_t)
* and a registered cyclic.
*
* Operation
*
* Whenever the cyclic fires, our cyclic handler checks that the particular
* periodic is not dispatched already (we do not support overlapping execution
* of the consumer's handler function), and not yet cancelled. If both of
* these conditions hold, we mark the periodic as DPF_DISPATCHED and enqueue it
* to either the taskq (for DDI_IPL_0) or to one of the soft interrupt queues
* (DDI_IPL_1 to DDI_IPL_10).
*
* While the taskq (or soft interrupt handler) is handling a particular
* periodic, we mark it as DPF_EXECUTING. When complete, we reset both
* DPF_DISPATCHED and DPF_EXECUTING.
*
* Cancellation
*
* ddi_periodic_delete(9F) historically had spectacularly loose semantics with
* respect to cancellation concurrent with handler execution. These semantics
* are now tighter:
*
* 1. At most one invocation of ddi_periodic_delete(9F) will actually
* perform the deletion, all others will return immediately.
* 2. The invocation that performs the deletion will _block_ until
* the handler is no longer running, and all resources have been
* released.
*
* We affect this model by removing the cancelling periodic from the
* global list and marking it DPF_CANCELLED. This will prevent further
* execution of the handler. We then wait on a CV until the DPF_EXECUTING
* and DPF_DISPATCHED flags are clear, which means the periodic is removed
* from all request queues, is no longer executing, and may be freed. At this
* point we return the opaque ID to the id_space and free the memory.
*
* NOTE:
* The ddi_periodic_add(9F) interface is presently limited to a minimum period
* of 10ms between firings.
*/
/*
* Tuneables:
*/
/*
* Globals:
*/
/*
* periodics_lock protects the list of all periodics (periodics), and
* each of the soft interrupt request queues (periodic_softint_queue).
*
* Do not hold an individual periodic's lock while obtaining periodics_lock.
* While in the periodic_softint_queue list, the periodic will be marked
* DPF_DISPATCHED, and thus safe from frees. Only the invocation of
* i_untimeout() that removes the periodic from the global list is allowed
* to free it.
*/
typedef enum periodic_ipl {
PERI_IPL_0 = 0,
static char *
{
}
/*
* This function may be called either from a soft interrupt handler
* (ddi_periodic_softintr), or as a taskq worker function.
*/
static void
{
/*
* We must be DISPATCHED, but not yet EXECUTING:
*/
/*
* If we have not yet been cancelled, then
* mark us executing:
*/
/*
* Execute the handler, without holding locks:
*/
dpr->dpr_fire_count++;
}
/*
* We're done with this periodic for now, so release it and
* wake anybody that was waiting for us to be finished:
*/
}
void
{
/*
* Pull the first scheduled periodic off the queue for this priority
* level:
*/
NULL) {
/*
* And execute it:
*/
}
}
void
ddi_periodic_init(void)
{
int i;
/*
* Create a kmem_cache for request tracking objects, and a list
* to store them in so we can later delete based on opaque handles:
*/
/*
* Initialise the identifier space for ddi_periodic_add(9F):
*/
/*
* Initialise the request queue for each soft interrupt level:
*/
for (i = PERI_IPL_1; i <= PERI_IPL_10; i++) {
}
/*
* Create the taskq for running PERI_IPL_0 handlers. This taskq will
* _only_ be used with taskq_dispatch_ent(), and a taskq_ent_t
* pre-allocated with the ddi_periodic_impl_t.
*/
ddi_periodic_taskq_threadcount, maxclsyspri, 0, 0, 0);
/*
* Initialize the mutex lock used for the soft interrupt request
* queues.
*/
}
void
ddi_periodic_fini(void)
{
int i;
/*
* Find all periodics that have not yet been unregistered and,
* on DEBUG bits, print a warning about this resource leak.
*/
#ifdef DEBUG
printf("DDI periodic handler not deleted (id=%lx, hdlr=%s)\n",
#endif
/*
* Delete the periodic ourselves:
*/
}
/*
* At this point there are no remaining cyclics, so clean up the
* remaining resources:
*/
for (i = PERI_IPL_1; i <= PERI_IPL_10; i++)
}
static void
{
/*
* If we've been cancelled, or we're already dispatched, then exit
* immediately:
*/
return;
}
/*
* This periodic is not presently dispatched, so dispatch it now:
*/
/*
* DDI_IPL_0 periodics are dispatched onto the taskq:
*/
} else {
/*
* Higher priority periodics are handled by a soft interrupt
* handler. Enqueue us for processing by the handler:
*/
dpr);
/*
* Request the execution of the soft interrupt handler for this
* periodic's priority level.
*/
}
}
static void
{
return;
/*
* By now, we should have a periodic that is not busy, and has been
* cancelled:
*/
}
static ddi_periodic_impl_t *
periodic_create(void)
{
return (dpr);
}
/*
* This function provides the implementation for the ddi_periodic_add(9F)
* interface. It registers a periodic handler and returns an opaque identifier
* that can be unregistered via ddi_periodic_delete(9F)/i_untimeout().
*
* It may be called in user or kernel context, provided cpu_lock is not held.
*/
{
/*
* Allocate object to track this periodic:
*/
dpr = periodic_create();
/*
* The minimum supported interval between firings of the periodic
* handler is 10ms; see ddi_periodic_add(9F) for more details. If a
* shorter interval is requested, round up.
*/
if (ddi_periodic_resolution > interval) {
"The periodic timeout (handler=%s, interval=%lld) "
"requests a finer interval than the supported resolution. "
}
/*
* Ensure that the interval is an even multiple of the base resolution
* that is at least as long as the requested interval.
*/
/*
* Create the underlying cyclic:
*/
/*
* Make the id visible to ddi_periodic_delete(9F) before we
* return it:
*/
}
/*
* This function provides the implementation for the ddi_periodic_delete(9F)
* interface. It cancels a periodic handler previously registered through
* ddi_periodic_add(9F)/i_timeout().
*
* It may be called in user or kernel context, provided cpu_lock is not held.
* It may NOT be called from within a periodic handler.
*/
void
{
/*
* Find the periodic in the list of all periodics and remove it.
* If we find in (and remove it from) the global list, we have
* license to free it once it is no longer busy.
*/
break;
}
}
/*
* We could not find a periodic for this id, so bail out:
*/
return;
/*
* We should be the only one trying to cancel this periodic:
*/
/*
* Removing a periodic from within its own handler function will
* cause a deadlock, so panic explicitly.
*/
panic("ddi_periodic_delete(%lx) called from its own handler\n",
}
/*
* Mark the periodic as cancelled:
*/
/*
* Cancel our cyclic. cyclic_remove() guarantees that the cyclic
* handler will not run again after it returns. Note that the cyclic
* handler merely _dispatches_ the periodic, so this does _not_ mean
* the periodic handler is also finished running.
*/
/*
* Wait until the periodic handler is no longer running:
*/
}
}