2021-10-11 22:19:34 -03:00

473 lines
12 KiB
C
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

static char sccsid[] = "@(#)75 1.17 src/bos/kernel/lib/libsys/timeout.c, libsys, bos41J, 9515B_all 4/7/95 11:23:35";
/*
* COMPONENT_NAME: SYSPROC
*
* FUNCTIONS: timeout
* timeout_end
* timeoutcf
* untimeout
*
* ORIGINS: 3,27,83
*
* This module contains IBM CONFIDENTIAL code. -- (IBM
* Confidential Restricted when combined with the aggregated
* modules for this product)
* SOURCE MATERIALS
*
* (C) COPYRIGHT International Business Machines Corp. 1989,1993
* All Rights Reserved
* US Government Users Restricted Rights - Use, duplication or
* disclosure restricted by GSA ADP Schedule Contract with IBM Corp.
*/
#include <sys/types.h> /* always gotta have this one */
#include <sys/param.h> /* to define the number of clock ticks/sec (HZ) */
#include <sys/syspest.h>/* to define the assert and ASSERT macros */
#include <sys/timer.h> /* For the timer related defines */
#include <sys/intr.h> /* for the serialization defines */
#include <sys/malloc.h> /* for the parameters to xmalloc() */
#include <sys/lock_def.h>
/*
* IMPORTANT MP PORTING NOTE :
*
* These functions have not been properly architected to be used in
* MP environments. Whenever possible one should use the base timer
* kernel primitives : talloc, tstart, tstop, and tfree. The kernel
* also provides the following watchdog routines : w_init, w_stop,
* w_clear, and w_start.
*
* The design flaw is that the caller may be holding a lock that is
* needed by the callout handler when calling these routines. If the
* timer pops while one of these routines is in progress a deadlock
* may occur, because we don't know the address of the callers lock
* to release and reacquire. We will just loop in one of the
* following routines.
*
* This possibility did not exist in the original uni-processor code,
* because interrupt locks were not used and it is likely to be
* introduced when porting to MP, because of the general guideline of
* translating i_disable to disable_lock calls. Anybody that falls
* into this trap should rewrite their code to use the kernel
* primitives listed above.
*
* IBM code should not use these primitives.
*
* In retrospect we should have changed untimeout to return an error
* much like we did with tstop.
*/
/*
* Instrumentation is not possible, because there is no initialization
* routine that can be called.
*/
Simple_lock callout_lock = { SIMPLE_LOCK_AVAIL };
struct tos {
struct tos *toprev; /* previous tos in callout table*/
struct tos *tonext; /* next tos in callout table */
struct trb *trb; /* this timer request block */
};
struct callo {
int ncallo; /* number of callout table elements */
struct tos *head; /* callout table head element */
};
struct callo callo = {0, NULL};/* callout table anchor */
static void timeout_end(struct trb *); /* timeout()'s timeout function */
extern int tstop(struct trb *);
extern void tstart(struct trb *);
extern void i_enable(int);
/*
* NAME: timeout
*
* FUNCTION: Arrange that the specified function is called with the
* specified argument in the specified number of ticks.
*
* EXECUTION ENVIRONMENT:
*
* This routine can be called under either a process or an interrupt level.
*
* It does not page fault except when called on a pageable stack.
*
* NOTES: Callers of this routine are expected to have called timeopen()
* to create that caller's callout table.
*
* RECOVERY OPERATION:
*
* RETURNS: timeout() has no returned value.
*
* EXTERNAL PROCEDURES CALLED: disable_lock
* unlock_enable
*/
void
timeout(
register void (*func)(), /* function to call at timeout*/
register caddr_t arg, /* It's argument. */
register int ticks) /* when to set timeout for */
{
register int ipri; /* caller's interrupt priority */
register struct tos *tos; /* tos to use for timeout */
register struct trb *trb; /* trb in the tos being used */
struct itimerstruc_t tv; /* timeout interval */
tv.it_value.tv_sec = ticks / HZ;
tv.it_value.tv_nsec = (ticks % HZ) * (NS_PER_SEC / HZ);
assert(callo.ncallo != 0);
timeout_retry:
ipri = disable_lock(INTMAX, &callout_lock);
/*
* Run the callout table chain to see if there is already a pending
* timeout for the specified function. If so, that timeout will
* be cancelled and the tos re-used.
*/
for(tos = callo.head; tos != NULL; tos = tos->tonext) {
if((tos->trb->tof == func) && (tos->trb->func_data == (int)arg))
break;
}
/*
* If a pending timeout for the specified function was NOT found,
* then the callout table chain will have to be run to find an
* unused tos.
*/
if(tos == NULL) {
for(tos = callo.head; tos != NULL; tos = tos->tonext) {
if(tos->trb->tof == NULL) {
break;
}
}
/*
* If there isn't an available tos, then there is no error
* recovery. This means that either the caller has not
* correctly registered the number of callout table entries
* that would be needed or is incorrectly using the ones that
* were registered. Either way, panic is the only recourse.
*/
assert(tos != NULL);
}
/*
* A pending timeout for the specified function WAS found.
* If the request is still active, stop it.
*/
while (tstop(tos->trb)) {
unlock_enable(ipri, &callout_lock);
goto timeout_retry;
}
tos->trb->knext = NULL;
tos->trb->kprev = NULL;
tos->trb->flags = 0;
tos->trb->timeout = tv;
tos->trb->tof = func;
tos->trb->func = (void (*)()) timeout_end;
tos->trb->func_data = (ulong) arg;
tos->trb->ipri = INTTIMER;
tos->trb->id = -1;
tstart(tos->trb);
unlock_enable(ipri, &callout_lock);
}
/*
* NAME: untimeout()
*
* FUNCTION: Simulate the old UNIX-style untimeout() kernel routine.
*
* EXECUTION ENVIRONMENT:
*
* This routine can be called under either a process or an interrupt level.
*
* It does not page fault except when called on a pageable stack.
*
* NOTES:
*
* RECOVERY OPERATION:
*
* RETURNS: untimeout() has no returned value.
*
* EXTERNAL PROCEDURES CALLED: disable_lock
* unlock_enable
*/
void
untimeout(
register void (*func)(),
register ulong arg)
{
register int ipri; /* caller's interrupt priority */
register struct tos *tos; /* tos to walk callout table */
register struct trb *trb; /* trb for this tos */
untimeout_retry:
ipri = disable_lock(INTMAX, &callout_lock);
/* Run the callout table chain looking for the timeout. */
for(tos = callo.head; tos != NULL; tos = tos->tonext) {
if(tos->trb->tof == func && tos->trb->func_data == arg) {
break;
}
}
if(tos) {
/*
* Found it on the timeout list - stop the pending timeout
* if it is active.
*/
while(tstop(tos->trb)) {
unlock_enable(ipri, &callout_lock);
goto untimeout_retry;
}
/* Mark this callout table entry as free. */
tos->trb->knext = NULL;
tos->trb->kprev = NULL;
tos->trb->tof = NULL;
}
unlock_enable(ipri, &callout_lock);
}
/*
* NAME: timeout_end
*
* FUNCTION: Call the function associated with the current timeout.
*
* EXECUTION ENVIRONMENT:
*
* This routine is called on the clock interrupt level.
* It is static.
*
* It does not page fault.
*
* RECOVERY OPERATION:
*
* RETURNS: timeout_end() has no returned value.
*
* EXTERNAL PROCEDURES CALLED:
*/
static void
timeout_end(
struct trb *trb) /* trb of the current timeout */
{
register void (*func)(); /* function to call at timeout */
int ipri;
func = trb->tof;
ipri = disable_lock(INTMAX, &callout_lock);
trb->func = NULL;
trb->tof = NULL; /* Zero out pointer to user function */
unlock_enable(ipri, &callout_lock);
(* func)(trb->func_data);
/* for compatibility with untimeout() */
}
/*
* NAME: timeoutcf
*
* FUNCTION: Configure (increase or decrease) the caller's callout table
* for use with timeout().
*
* EXECUTION ENVIRONMENT:
*
* This routine can only be called under a process.
*
* It may page fault.
*
* NOTES: If the caller calls timeoutcf(-x), then there must be at least
* x callout table entries WHICH ARE NOT CURRENTLY SET TO TIMEOUT.
* Otherwise, panic will result.
*
* RECOVERY OPERATION:
*
* RETURNS: 0 upon successful allocation/deallocation of the callout
* table element, -1 otherwise.
*
* EXTERNAL PROCEDURES CALLED: talloc
* tfree
*/
int
timeoutcf(
register int cocnt) /* # entries to change callout table by */
{
register int ipri; /* caller's interrupt priority */
register int rv; /* return value to the caller */
register struct tos *tos; /* tos to add to/remove from table */
register struct trb *trb; /* trb in the tos to be added/removed */
rv = 0;
if(cocnt > 0) {
/*
* Callout table is being enlarged - keep working until the
* right number of elements have been added.
*/
while(cocnt > 0) {
/* Allocate a timer request block. */
trb = (struct trb *) talloc();
/*
* If the low-level timer service could not provide
* a trb, the callout table can't be expanded any
* more so get out.
*/
if(trb == NULL) {
rv = -1;
break;
}
/* Allocate memory for the callout table structure. */
tos = (struct tos *)
xmalloc((uint)sizeof(struct tos), (uint)0, pinned_heap);
/*
* If memory couldn't be allocated for the tos, the
* callout table can't be expanded any more so get out.
*/
if(tos == NULL) {
rv = -1;
break;
}
else {
bzero(tos, sizeof(struct tos));
}
/* The trb and the tos were both allocated. */
tos->trb = trb;
#ifdef DEBUG
/*
* Debug code to ensure that the low-level timer
* service talloc() clears out the pointers.
*/
ASSERT(trb->knext == NULL);
ASSERT(trb->kprev == NULL);
#endif /* DEBUG */
ipri = disable_lock(INTMAX, &callout_lock);
if(callo.head == NULL) {
/*
* The callout table is currently empty. This
* is the easy case, just set the head of the
* callout chain to this tos.
*/
callo.head = tos;
}
else {
/*
* The callout table is not empty. Chain this
* trb to the head of the callout chain.
*/
tos->tonext = callo.head;
callo.head->toprev = tos;
callo.head = tos;
}
/* Just finished adding a trb to the callout table. */
callo.ncallo++;
cocnt--;
unlock_enable(ipri, &callout_lock);
}
}
else {
/*
* Callout table is being shrunk - keep working until the
* right number of elements have been removed being careful
* only to remove elements which do not belong to timeout
* requests that are currently active.
*/
if(cocnt < 0) {
/*
* There had better be at least as many tos's in
* the callout table as the size by which the caller
* wants to decrease the size of the table.
*/
assert(callo.ncallo >= -cocnt);
while(cocnt < 0) {
timeoutcf_retry:
/*
* Start from the head of the callout chain,
* making sure that there is a tos at the
* head (i.e. that there is a callout chain).
*/
ipri = disable_lock(INTMAX, &callout_lock);
tos = callo.head;
assert(tos != NULL);
/*
* Keep walking down the callout chain until
* a tos is found which is not currently
* active.
*/
while((tos != NULL) &&
(tos->trb->tof != NULL)) {
tos = tos->tonext;
}
/*
* If trb is not NULL, then there was not a
* callout table entry that wasn't set to
* timeout. Panic.
*/
assert(tos != NULL);
/*
* Stop the pending timeout if it is active.
*/
while (tstop(tos->trb)) {
unlock_enable(ipri, &callout_lock);
goto timeoutcf_retry;
}
/*
* Found a free callout table element, free
* it and remove it from the callout table.
*/
tfree(tos->trb);
if(callo.head == tos) {
callo.head = tos->tonext;
if(callo.head != NULL) {
callo.head->toprev = NULL;
}
}
else {
assert(tos->toprev != NULL);
tos->toprev->tonext = tos->tonext;
if(tos->tonext != NULL) {
tos->tonext->toprev =
tos->toprev;
}
}
/*
* Just finished removing a trb from the
* callout table.
*/
callo.ncallo--;
cocnt++;
unlock_enable(ipri, &callout_lock);
xmfree((void *)tos, pinned_heap);
}
}
}
return(rv);
}