[net.unix-wizards] DELAY

D0430@PUCC.BITNET (Paul Lansky) (10/01/86)

In a driver we have for some D-A converters (under Ultrix 1.1)
I put a DELAY(bignumber) (about 1/2 second) in at clean-up time to
give the DACs a chance to reset themselves properly.  This has
the effect, however, of suspending all system activity for that
amount of time.  From the looks of the code (in machdep.c) I don't
see why this would happen since there seems to be no relevant spl().
I ain't no wizard so I may be missing something fundamental.
Can someone suggest an alternate approach?
 
Thanks
 
Paul Lansky
Music Department
Princeton University
            {seismo,ucbvax} .. princeton!winnie!paul
or d0430@pucc (on bitnet)

mike@BRL.ARPA (Mike Muuss) (10/07/86)

DELAY(num) causes the kernel to loop hard, at the current ipl.
If entered at spl0(), interupts will still be processed, but
the kernel will just keep looping...  Recall that the kernel
will not context switch unless swtch() is called -- and you DON'T
want to do that in this case.

More likely, what you want to do is to have your driver_close()
routine shut down the DACs, and then execute a bit of code
something like this:

driver_close()
{
	/* Shut down the DACs here */
	timeout( &driver_close, 2*HZ );
	s = spl6();	/* protect against races */
	dac_wait = 1;
	while( dac_wait )
		sleep( &dac_wait, PZERO );
	splx(s);
	/* Handle final cleanups */
}

close_timeout()
{
	dac_wait = 0;
	wakeup( &dac_wait );
}

(Check the arguments to sleep() and timeout() carefully for the version
of UNIX that you are using -- the above code is just to give you the
"flavor" of this operation).

The timeout() routine arranges for the UNIX clock() routine to call your
time-handler after the specified number of clock ticks (2 seconds worth
in this example) have passed.  In the interim, execution proceeds normally.

The sleep() routine causes this process to be de-selected, and a context
switch to another process to occur.  This will allow other users to
continue executing while the timeout runs.  IMPORTANT NOTE:  The
loop around the sleep() is generally very important -- there are
no guarantees that the sleep() will not return from some other cause
(event) than the one you are expecting, so be certain to wait for
the intended condition to happen.  In this case, the transition
from 1 to 0 of the dac_wait flag.  (If you know *exactly* what you
are doing, and know *exactly* the semantics of sleep/wakeup on your
system, this can be avoided, but my advice to you is:  "don't").

Note that when sleep() context switches, it drops the high spl.
When the sleeping context is resumed by wakeup(), the high spl
is restored for you at the right point in the context switch.

Note that the above code is impervious to multiple timeouts
(which *shouldn't* ordinarily occur), because any additional timeouts
will just clear the dac_wait flag.

Note that if you were EXPECTING competing timeout or interupt events
during this close operation, it would be best to protect the
set..while..sleep loop with spl6 [for timeouts] or spl4,5 [for device
interupts], to prevent race conditions on the dac_wait flag.

Hope this helps all you folks out there write better drivers.
Best,
 -Mike Muuss

(301)-278-6678
  AV  298-6678
  FTS 939-6678

ArpaNet:  <Mike @ BRL.ARPA>
UUCP:     decvax!brl-bmd!mike
Postal:
  Mike Muuss
  Leader, Advanced Computer Systems Team
  Computer Science and Mathematics Branch
  Systems Engineering and Concepts Analysis Division
  U.S. Army Ballistic Research Laboratory
  Attn: SLCBR-SECAD (Muuss)
  APG, MD  21005-5066

D0430@PUCC.BITNET (Paul Lansky) (10/07/86)

howdy,
 
I'd like to express many thanks for the dozen or so answers I've gotten
to my DELAY query.  They all suggest using timeout/sleep in some combination
or subterfuge.  I've implemented this and it seems to work beautifully.
The most economical and straightforward suggestion came from Jim Lowe
who suggested the following (similar to what happens in the uda.c and
tmscp.c drivers in Ultrix)
 
    (void)timeout(wakeup, (caddr_t)&tp->t_dev, hz>>1) ;
    (void)sleep((caddr_t)&tp->t_dev, PZERO);
 
 
You all are quite a resource!
 
Paul Lansky
Music Department
Princeton University     {seismo, ucbvax} princeton!winnie!paul
                         bitnet == d0430@pucc

chris@umcp-cs.UUCP (Chris Torek) (10/08/86)

I never saw the original article, but I can guess at the problem
being solved.  Mike's advice is accurrate, but, I think, overcautious:

In article <4393@brl-smoke.ARPA> mike@BRL.ARPA (Mike Muuss) writes:
>driver_close()
>{
>	/* Shut down the DACs here */
>	timeout( &driver_close, 2*HZ );

(That should be `close_timeout', not `&driver_close', and the routine
must be declared above.)

>	s = spl6();	/* protect against races */
>	dac_wait = 1;
>	while( dac_wait )
>		sleep( &dac_wait, PZERO );
>	splx(s);
>	/* Handle final cleanups */
>}
>
>close_timeout()
>{
>	dac_wait = 0;
>	wakeup( &dac_wait );
>}

>(Check the arguments to sleep() and timeout() carefully for the version
>of UNIX that you are using -- the above code is just to give you the
>"flavor" of this operation).

I might go so far as to say that every Unix will do them the same
way, but no doubt someone out there has made some incompatible
changes to its semantics *without* changing the name. . . .

>The sleep() routine causes this process to be de-selected, and a context
>switch to another process to occur. ... The loop around the sleep() is
>generally very important -- there are no guarantees that the sleep()
>will not return from some other cause (event) than the one you are
>expecting, so be certain to wait for the intended condition to happen.

There *are* some guarantees.  The sleep() will return only when
someone does a wakeup() on the exact same value as the first argument
to sleep(), OR when a signal occurs, provided the second argument
was greater than PZERO.  As Mike cautioned, though, your kernel
vendor just might have broken this.

>Note that when sleep() context switches, it drops the high spl.
>When the sleeping context is resumed by wakeup(), the high spl
>is restored for you at the right point in the context switch.

(`Drops the high ipl' sounds better.  IPL stands for Interrupt
Priority Level; spl stands for `set priority level'.  At least,
that is my best guess as to what it stands for . . . .)

Anyway, I would use---in fact, I have already used, in similar
cases---the following:

	/* DAC driver */

	struct dac_softc {
		int	sc_flags;	/* e.g., exclusive use opens */
		...
	} dac_softc[NDAC];

		...

	#define	DACPRI	(PZERO - 1)		/* adjust as appropriate */
	extern	int hz;
	int	wakeup();

	#define	dacunit(dev)	minor(dev)
		...

	/*
	 * Close a DAC.
	 */
	/*ARGSUSED*/
	dacclose(dev, flag)
		dev_t dev;
	{
		register struct dacdevice *dac;
		register struct dac_softc *sc;

		dac = (struct dacdevice *) dacinfo[dacunit(dac)]->ui_addr;
		sc = &dac_softc[dacunit(dac)];
		sc->sc_flags |= SC_SHUTDOWN;	/* mark shutdown in progress */
		dac->dac_foo = DAC_SHUTDOWN;	/* shut down DAC */
		timeout(wakeup, (caddr_t) sc, hz * 2);
		sleep((caddr_t) sc, DACPRI);	/* wait two seconds */
		dac->dac_foo = 0;		/* then release it */
		sc->sc_flags = 0;		/* mark closed */
	}
-- 
In-Real-Life: Chris Torek, Univ of MD Comp Sci Dept (+1 301 454 1516)
UUCP:	seismo!umcp-cs!chris
CSNet:	chris@umcp-cs		ARPA:	chris@mimsy.umd.edu