Re: splx()

Linus Torvalds (Linus.Torvalds@cs.Helsinki.FI)
Tue, 18 Jul 1995 08:54:05 +0300


Greg Wolodkin: "splx()" (Jul 17, 12:44):
> Hiya. I'm working on a linux port of a BSD-based MIDI driver, and
> I'm looking for a function splx() so that I can block interrupts and
> then later restore them to whatever the state was before I blocked.

_please_ don't use splx(): it's not a very good interface for any
reason. I despise the stupid interface, all the more because it has
resulted in bad things for hardware as well..

[ Preacher mode on: this isn't specifically to Greg, but is just
something I feel very strongly about: ]

I say this every once in a while: spl-levels are BAD. There is _no_
good reason to use prioritized interrupts. Only broken hardware uses
them, and the right way to handle interrupts is with a bit-mask, not
with a priority. Look at the linux low-level interrupt handling
routines to see how this is done (<asm-i386/irq.h>).

For things that want to be atomic, it's easier to just disable all
interrupts, and do whatever you want to do quickly. If you're doing
something that is so slow that you don't want to disable all interrupts,
you're doing something wrong, and spl-levels wouldn't be the solution to
this problem anyway.

(slow things that need lots of locking are more appropriately handled by
software interrupts, the linux "bottom half handlers". They can be
blocked individually without blocking any hardware interrupts at all,
and they have much nicer atomicity guarantees anyway. This is how the
slow and race-prone tty and network processing is done).

> int splx (int new_level) {
> register int old_level, tmp;
>
> save_flags(tmp);
> old_level = (tmp & 0x200) ? 7 : 0;
> if (new_level)
> sti();
> else
> cli();
> return old_level;
> }
>
> The file it was in was apparently splx.c.

This is very much i386-specific and won't work on anything else. Please
don't use this.

> At any rate I couldn't find this function in the new 1.2.x, 1.3.x kernels.
> For now I've just added it to the driver module and it seems to work fine,
> but I thought I'd ask to see why it had disappeared, and also to see if
> there isn't a better/cleaner solution already existing in the 1.2.x kernel.

What's wrong with:

unsigned long flags;

save_flags(flags); /* old "spl-level" for BSD people */
cli(); /* disable all interrupts */
...
restore_flags(flags); /* restore old "spl-level" */

which does the same thing, except

- you aren't fooled into thinking that "spl's are ok - we're just
blocking part of the interrupts".
- it's more flexible (you can save the flags just once, and then do any
amount of cli()/restore_flags(): see "serial.c" for an example of this)
- it's the way everything else in the kernel does it.

If you need to have the same source-tree for both BSD and linux, I'd
suggest something like

#ifdef __linux__
#define disable_irq() ({ unsigned long flags; save_flags(flags); cli(); flags; })
#define restore_irq(x) restore_flags(x)
#else
#define disable_irq() splx(7)
#define restore_irq(x) splx(x)
#endif

and then you use

old = disable_irq();
...
restore_irq(old)

which is actually more readable than either format, but loses (very
little - it's seldom used) of the flexibility of the linux format.

Linus