pc, pc64: move common irq handling code out of trap.c
Move the common irq handling code out of trap.c into pc/irq.c so that it can be shared between 386 and amd64 ports.
This commit is contained in:
parent
97008caa41
commit
a041c90431
5 changed files with 349 additions and 588 deletions
313
sys/src/9/pc/irq.c
Normal file
313
sys/src/9/pc/irq.c
Normal file
|
@ -0,0 +1,313 @@
|
||||||
|
#include "u.h"
|
||||||
|
#include "tos.h"
|
||||||
|
#include "../port/lib.h"
|
||||||
|
#include "mem.h"
|
||||||
|
#include "dat.h"
|
||||||
|
#include "fns.h"
|
||||||
|
#include "io.h"
|
||||||
|
#include "ureg.h"
|
||||||
|
#include "../port/error.h"
|
||||||
|
|
||||||
|
static Lock vctllock;
|
||||||
|
static Vctl *vctl[256];
|
||||||
|
|
||||||
|
enum
|
||||||
|
{
|
||||||
|
Ntimevec = 20 /* number of time buckets for each intr */
|
||||||
|
};
|
||||||
|
ulong intrtimes[256][Ntimevec];
|
||||||
|
|
||||||
|
/*
|
||||||
|
* keep histogram of interrupt service times
|
||||||
|
*/
|
||||||
|
static void
|
||||||
|
intrtime(Mach*, int vno)
|
||||||
|
{
|
||||||
|
ulong diff;
|
||||||
|
ulong x;
|
||||||
|
|
||||||
|
x = perfticks();
|
||||||
|
diff = x - m->perf.intrts;
|
||||||
|
m->perf.intrts = x;
|
||||||
|
|
||||||
|
m->perf.inintr += diff;
|
||||||
|
if(up == nil && m->perf.inidle > diff)
|
||||||
|
m->perf.inidle -= diff;
|
||||||
|
|
||||||
|
diff /= m->cpumhz*100; /* quantum = 100µsec */
|
||||||
|
if(diff >= Ntimevec)
|
||||||
|
diff = Ntimevec-1;
|
||||||
|
intrtimes[vno][diff]++;
|
||||||
|
}
|
||||||
|
|
||||||
|
int
|
||||||
|
irqhandled(Ureg *ureg, int vno)
|
||||||
|
{
|
||||||
|
Vctl *ctl, *v;
|
||||||
|
int i;
|
||||||
|
|
||||||
|
if(ctl = vctl[vno]){
|
||||||
|
if(ctl->isintr){
|
||||||
|
m->perf.intrts = perfticks();
|
||||||
|
m->intr++;
|
||||||
|
if(vno >= VectorPIC)
|
||||||
|
m->lastintr = ctl->irq;
|
||||||
|
}
|
||||||
|
if(ctl->isr)
|
||||||
|
ctl->isr(vno);
|
||||||
|
for(v = ctl; v != nil; v = v->next){
|
||||||
|
if(v->f)
|
||||||
|
v->f(ureg, v->a);
|
||||||
|
}
|
||||||
|
if(ctl->eoi)
|
||||||
|
ctl->eoi(vno);
|
||||||
|
|
||||||
|
if(ctl->isintr){
|
||||||
|
intrtime(m, vno);
|
||||||
|
|
||||||
|
if(up){
|
||||||
|
if(ctl->irq == IrqCLOCK || ctl->irq == IrqTIMER){
|
||||||
|
/* delaysched set because we held a lock or because our quantum ended */
|
||||||
|
if(up->delaysched)
|
||||||
|
sched();
|
||||||
|
} else {
|
||||||
|
preempted();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(vno < VectorPIC)
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* An unknown interrupt.
|
||||||
|
* Check for a default IRQ7. This can happen when
|
||||||
|
* the IRQ input goes away before the acknowledge.
|
||||||
|
* In this case, a 'default IRQ7' is generated, but
|
||||||
|
* the corresponding bit in the ISR isn't set.
|
||||||
|
* In fact, just ignore all such interrupts.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/* call all interrupt routines, just in case */
|
||||||
|
for(i = VectorPIC; i <= MaxIrqLAPIC; i++){
|
||||||
|
ctl = vctl[i];
|
||||||
|
if(ctl == nil)
|
||||||
|
continue;
|
||||||
|
if(!ctl->isintr)
|
||||||
|
continue;
|
||||||
|
for(v = ctl; v != nil; v = v->next){
|
||||||
|
if(v->f)
|
||||||
|
v->f(ureg, v->a);
|
||||||
|
}
|
||||||
|
/* should we do this? */
|
||||||
|
if(ctl->eoi)
|
||||||
|
ctl->eoi(i);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* clear the interrupt */
|
||||||
|
i8259isr(vno);
|
||||||
|
|
||||||
|
if(0)print("cpu%d: spurious interrupt %d, last %d\n",
|
||||||
|
m->machno, vno, m->lastintr);
|
||||||
|
if(0)if(conf.nmach > 1){
|
||||||
|
for(i = 0; i < MAXMACH; i++){
|
||||||
|
Mach *mach;
|
||||||
|
|
||||||
|
if(active.machs[i] == 0)
|
||||||
|
continue;
|
||||||
|
mach = MACHP(i);
|
||||||
|
if(m->machno == mach->machno)
|
||||||
|
continue;
|
||||||
|
print(" cpu%d: last %d",
|
||||||
|
mach->machno, mach->lastintr);
|
||||||
|
}
|
||||||
|
print("\n");
|
||||||
|
}
|
||||||
|
m->spuriousintr++;
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
trapenable(int vno, void (*f)(Ureg*, void*), void* a, char *name)
|
||||||
|
{
|
||||||
|
Vctl *v;
|
||||||
|
|
||||||
|
if(vno < 0 || vno >= VectorPIC)
|
||||||
|
panic("trapenable: vno %d", vno);
|
||||||
|
if((v = xalloc(sizeof(Vctl))) == nil)
|
||||||
|
panic("trapenable: out of memory");
|
||||||
|
v->tbdf = BUSUNKNOWN;
|
||||||
|
v->f = f;
|
||||||
|
v->a = a;
|
||||||
|
strncpy(v->name, name, KNAMELEN-1);
|
||||||
|
v->name[KNAMELEN-1] = 0;
|
||||||
|
|
||||||
|
ilock(&vctllock);
|
||||||
|
if(vctl[vno])
|
||||||
|
v->next = vctl[vno]->next;
|
||||||
|
vctl[vno] = v;
|
||||||
|
iunlock(&vctllock);
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
intrenable(int irq, void (*f)(Ureg*, void*), void* a, int tbdf, char *name)
|
||||||
|
{
|
||||||
|
int vno;
|
||||||
|
Vctl *v;
|
||||||
|
|
||||||
|
if(f == nil){
|
||||||
|
print("intrenable: nil handler for %d, tbdf 0x%uX for %s\n",
|
||||||
|
irq, tbdf, name);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(tbdf != BUSUNKNOWN && (irq == 0xff || irq == 0))
|
||||||
|
irq = -1;
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* IRQ2 doesn't really exist, it's used to gang the interrupt
|
||||||
|
* controllers together. A device set to IRQ2 will appear on
|
||||||
|
* the second interrupt controller as IRQ9.
|
||||||
|
*/
|
||||||
|
if(irq == 2)
|
||||||
|
irq = 9;
|
||||||
|
|
||||||
|
if((v = xalloc(sizeof(Vctl))) == nil)
|
||||||
|
panic("intrenable: out of memory");
|
||||||
|
v->isintr = 1;
|
||||||
|
v->irq = irq;
|
||||||
|
v->tbdf = tbdf;
|
||||||
|
v->f = f;
|
||||||
|
v->a = a;
|
||||||
|
strncpy(v->name, name, KNAMELEN-1);
|
||||||
|
v->name[KNAMELEN-1] = 0;
|
||||||
|
|
||||||
|
ilock(&vctllock);
|
||||||
|
vno = arch->intrenable(v);
|
||||||
|
if(vno == -1){
|
||||||
|
iunlock(&vctllock);
|
||||||
|
print("intrenable: couldn't enable irq %d, tbdf 0x%uX for %s\n",
|
||||||
|
irq, tbdf, v->name);
|
||||||
|
xfree(v);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if(vctl[vno]){
|
||||||
|
if(vctl[vno]->isr != v->isr || vctl[vno]->eoi != v->eoi)
|
||||||
|
panic("intrenable: handler: %s %s %#p %#p %#p %#p",
|
||||||
|
vctl[vno]->name, v->name,
|
||||||
|
vctl[vno]->isr, v->isr, vctl[vno]->eoi, v->eoi);
|
||||||
|
v->next = vctl[vno];
|
||||||
|
}
|
||||||
|
vctl[vno] = v;
|
||||||
|
iunlock(&vctllock);
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
intrdisable(int irq, void (*f)(Ureg *, void *), void *a, int tbdf, char *name)
|
||||||
|
{
|
||||||
|
Vctl **pv, *v;
|
||||||
|
int vno;
|
||||||
|
|
||||||
|
if(irq == 2)
|
||||||
|
irq = 9;
|
||||||
|
if(arch->intrvecno == nil || (tbdf != BUSUNKNOWN && (irq == 0xff || irq == 0))){
|
||||||
|
/*
|
||||||
|
* on APIC machine, irq is pretty meaningless
|
||||||
|
* and disabling a the vector is not implemented.
|
||||||
|
* however, we still want to remove the matching
|
||||||
|
* Vctl entry to prevent calling Vctl.f() with a
|
||||||
|
* stale Vctl.a pointer.
|
||||||
|
*/
|
||||||
|
irq = -1;
|
||||||
|
vno = VectorPIC;
|
||||||
|
} else {
|
||||||
|
vno = arch->intrvecno(irq);
|
||||||
|
}
|
||||||
|
ilock(&vctllock);
|
||||||
|
do {
|
||||||
|
for(pv = &vctl[vno]; (v = *pv) != nil; pv = &v->next){
|
||||||
|
if(v->isintr && (v->irq == irq || irq == -1)
|
||||||
|
&& v->tbdf == tbdf && v->f == f && v->a == a
|
||||||
|
&& strcmp(v->name, name) == 0)
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if(v != nil){
|
||||||
|
if(v->disable != nil)
|
||||||
|
(*v->disable)(v);
|
||||||
|
*pv = v->next;
|
||||||
|
xfree(v);
|
||||||
|
|
||||||
|
if(irq != -1 && vctl[vno] == nil && arch->intrdisable != nil)
|
||||||
|
arch->intrdisable(irq);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
} while(irq == -1 && ++vno <= MaxVectorAPIC);
|
||||||
|
iunlock(&vctllock);
|
||||||
|
}
|
||||||
|
|
||||||
|
static long
|
||||||
|
irqallocread(Chan*, void *a, long n, vlong offset)
|
||||||
|
{
|
||||||
|
char buf[2*(11+1)+KNAMELEN+1+1];
|
||||||
|
int vno, m;
|
||||||
|
Vctl *v;
|
||||||
|
|
||||||
|
if(n < 0 || offset < 0)
|
||||||
|
error(Ebadarg);
|
||||||
|
|
||||||
|
for(vno=0; vno<nelem(vctl); vno++){
|
||||||
|
for(v=vctl[vno]; v; v=v->next){
|
||||||
|
m = snprint(buf, sizeof(buf), "%11d %11d %.*s\n", vno, v->irq, KNAMELEN, v->name);
|
||||||
|
offset -= m;
|
||||||
|
if(offset >= 0)
|
||||||
|
continue;
|
||||||
|
if(n > -offset)
|
||||||
|
n = -offset;
|
||||||
|
offset += m;
|
||||||
|
memmove(a, buf+offset, n);
|
||||||
|
return n;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
nmienable(void)
|
||||||
|
{
|
||||||
|
int x;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Hack: should be locked with NVRAM access.
|
||||||
|
*/
|
||||||
|
outb(0x70, 0x80); /* NMI latch clear */
|
||||||
|
outb(0x70, 0);
|
||||||
|
|
||||||
|
x = inb(0x61) & 0x07; /* Enable NMI */
|
||||||
|
outb(0x61, 0x0C|x);
|
||||||
|
outb(0x61, x);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
nmihandler(Ureg *ureg, void*)
|
||||||
|
{
|
||||||
|
/*
|
||||||
|
* Don't re-enable, it confuses the crash dumps.
|
||||||
|
nmienable();
|
||||||
|
*/
|
||||||
|
iprint("cpu%d: nmi PC %#p, status %ux\n",
|
||||||
|
m->machno, ureg->pc, inb(0x61));
|
||||||
|
while(m->machno != 0)
|
||||||
|
;
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
irqinit(void)
|
||||||
|
{
|
||||||
|
addarchfile("irqalloc", 0444, irqallocread, nil);
|
||||||
|
|
||||||
|
trapenable(VectorNMI, nmihandler, nil, "nmi");
|
||||||
|
nmienable();
|
||||||
|
}
|
|
@ -54,6 +54,7 @@ OBJ=\
|
||||||
main.$O\
|
main.$O\
|
||||||
memory.$O\
|
memory.$O\
|
||||||
mmu.$O\
|
mmu.$O\
|
||||||
|
irq.$O\
|
||||||
trap.$O\
|
trap.$O\
|
||||||
bootargs.$O\
|
bootargs.$O\
|
||||||
$CONF.root.$O\
|
$CONF.root.$O\
|
||||||
|
|
|
@ -9,6 +9,9 @@
|
||||||
#include "../port/error.h"
|
#include "../port/error.h"
|
||||||
#include <trace.h>
|
#include <trace.h>
|
||||||
|
|
||||||
|
extern int irqhandled(Ureg*, int);
|
||||||
|
extern void irqinit(void);
|
||||||
|
|
||||||
void noted(Ureg*, ulong);
|
void noted(Ureg*, ulong);
|
||||||
|
|
||||||
static void debugexc(Ureg*, void*);
|
static void debugexc(Ureg*, void*);
|
||||||
|
@ -18,173 +21,6 @@ static void doublefault(Ureg*, void*);
|
||||||
static void unexpected(Ureg*, void*);
|
static void unexpected(Ureg*, void*);
|
||||||
static void _dumpstack(Ureg*);
|
static void _dumpstack(Ureg*);
|
||||||
|
|
||||||
static Lock vctllock;
|
|
||||||
static Vctl *vctl[256];
|
|
||||||
|
|
||||||
enum
|
|
||||||
{
|
|
||||||
Ntimevec = 20 /* number of time buckets for each intr */
|
|
||||||
};
|
|
||||||
ulong intrtimes[256][Ntimevec];
|
|
||||||
|
|
||||||
void
|
|
||||||
intrenable(int irq, void (*f)(Ureg*, void*), void* a, int tbdf, char *name)
|
|
||||||
{
|
|
||||||
int vno;
|
|
||||||
Vctl *v;
|
|
||||||
|
|
||||||
if(f == nil){
|
|
||||||
print("intrenable: nil handler for %d, tbdf 0x%uX for %s\n",
|
|
||||||
irq, tbdf, name);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if(tbdf != BUSUNKNOWN && (irq == 0xff || irq == 0))
|
|
||||||
irq = -1;
|
|
||||||
|
|
||||||
/*
|
|
||||||
* IRQ2 doesn't really exist, it's used to gang the interrupt
|
|
||||||
* controllers together. A device set to IRQ2 will appear on
|
|
||||||
* the second interrupt controller as IRQ9.
|
|
||||||
*/
|
|
||||||
if(irq == 2)
|
|
||||||
irq = 9;
|
|
||||||
|
|
||||||
if((v = xalloc(sizeof(Vctl))) == nil)
|
|
||||||
panic("intrenable: out of memory");
|
|
||||||
v->isintr = 1;
|
|
||||||
v->irq = irq;
|
|
||||||
v->tbdf = tbdf;
|
|
||||||
v->f = f;
|
|
||||||
v->a = a;
|
|
||||||
strncpy(v->name, name, KNAMELEN-1);
|
|
||||||
v->name[KNAMELEN-1] = 0;
|
|
||||||
|
|
||||||
ilock(&vctllock);
|
|
||||||
vno = arch->intrenable(v);
|
|
||||||
if(vno == -1){
|
|
||||||
iunlock(&vctllock);
|
|
||||||
print("intrenable: couldn't enable irq %d, tbdf 0x%uX for %s\n",
|
|
||||||
irq, tbdf, v->name);
|
|
||||||
xfree(v);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if(vctl[vno]){
|
|
||||||
if(vctl[vno]->isr != v->isr || vctl[vno]->eoi != v->eoi)
|
|
||||||
panic("intrenable: handler: %s %s %#p %#p %#p %#p",
|
|
||||||
vctl[vno]->name, v->name,
|
|
||||||
vctl[vno]->isr, v->isr, vctl[vno]->eoi, v->eoi);
|
|
||||||
v->next = vctl[vno];
|
|
||||||
}
|
|
||||||
vctl[vno] = v;
|
|
||||||
iunlock(&vctllock);
|
|
||||||
}
|
|
||||||
|
|
||||||
void
|
|
||||||
intrdisable(int irq, void (*f)(Ureg *, void *), void *a, int tbdf, char *name)
|
|
||||||
{
|
|
||||||
Vctl **pv, *v;
|
|
||||||
int vno;
|
|
||||||
|
|
||||||
if(irq == 2)
|
|
||||||
irq = 9;
|
|
||||||
if(arch->intrvecno == nil || (tbdf != BUSUNKNOWN && (irq == 0xff || irq == 0))){
|
|
||||||
/*
|
|
||||||
* on APIC machine, irq is pretty meaningless.
|
|
||||||
* however, we still want to remove the matching
|
|
||||||
* Vctl entry to prevent calling Vctl.f() with a
|
|
||||||
* stale Vctl.a pointer.
|
|
||||||
*/
|
|
||||||
irq = -1;
|
|
||||||
vno = VectorPIC;
|
|
||||||
} else {
|
|
||||||
vno = arch->intrvecno(irq);
|
|
||||||
}
|
|
||||||
ilock(&vctllock);
|
|
||||||
do {
|
|
||||||
for(pv = &vctl[vno]; (v = *pv) != nil; pv = &v->next){
|
|
||||||
if(v->isintr && (v->irq == irq || irq == -1)
|
|
||||||
&& v->tbdf == tbdf && v->f == f && v->a == a
|
|
||||||
&& strcmp(v->name, name) == 0)
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
if(v != nil){
|
|
||||||
if(v->disable != nil)
|
|
||||||
(*v->disable)(v);
|
|
||||||
*pv = v->next;
|
|
||||||
xfree(v);
|
|
||||||
|
|
||||||
if(irq != -1 && vctl[vno] == nil && arch->intrdisable != nil)
|
|
||||||
arch->intrdisable(irq);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
} while(irq == -1 && ++vno <= MaxVectorAPIC);
|
|
||||||
iunlock(&vctllock);
|
|
||||||
}
|
|
||||||
|
|
||||||
static long
|
|
||||||
irqallocread(Chan*, void *a, long n, vlong offset)
|
|
||||||
{
|
|
||||||
char buf[2*(11+1)+KNAMELEN+1+1];
|
|
||||||
int vno, m;
|
|
||||||
Vctl *v;
|
|
||||||
|
|
||||||
if(n < 0 || offset < 0)
|
|
||||||
error(Ebadarg);
|
|
||||||
|
|
||||||
for(vno=0; vno<nelem(vctl); vno++){
|
|
||||||
for(v=vctl[vno]; v; v=v->next){
|
|
||||||
m = snprint(buf, sizeof(buf), "%11d %11d %.*s\n", vno, v->irq, KNAMELEN, v->name);
|
|
||||||
offset -= m;
|
|
||||||
if(offset >= 0)
|
|
||||||
continue;
|
|
||||||
if(n > -offset)
|
|
||||||
n = -offset;
|
|
||||||
offset += m;
|
|
||||||
memmove(a, buf+offset, n);
|
|
||||||
return n;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
void
|
|
||||||
trapenable(int vno, void (*f)(Ureg*, void*), void* a, char *name)
|
|
||||||
{
|
|
||||||
Vctl *v;
|
|
||||||
|
|
||||||
if(vno < 0 || vno >= VectorPIC)
|
|
||||||
panic("trapenable: vno %d", vno);
|
|
||||||
if((v = xalloc(sizeof(Vctl))) == nil)
|
|
||||||
panic("trapenable: out of memory");
|
|
||||||
v->tbdf = BUSUNKNOWN;
|
|
||||||
v->f = f;
|
|
||||||
v->a = a;
|
|
||||||
strncpy(v->name, name, KNAMELEN-1);
|
|
||||||
v->name[KNAMELEN-1] = 0;
|
|
||||||
|
|
||||||
ilock(&vctllock);
|
|
||||||
if(vctl[vno])
|
|
||||||
v->next = vctl[vno]->next;
|
|
||||||
vctl[vno] = v;
|
|
||||||
iunlock(&vctllock);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void
|
|
||||||
nmienable(void)
|
|
||||||
{
|
|
||||||
int x;
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Hack: should be locked with NVRAM access.
|
|
||||||
*/
|
|
||||||
outb(0x70, 0x80); /* NMI latch clear */
|
|
||||||
outb(0x70, 0);
|
|
||||||
|
|
||||||
x = inb(0x61) & 0x07; /* Enable NMI */
|
|
||||||
outb(0x61, 0x0C|x);
|
|
||||||
outb(0x61, x);
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Minimal trap setup. Just enough so that we can panic
|
* Minimal trap setup. Just enough so that we can panic
|
||||||
* on traps (bugs) during kernel initialization.
|
* on traps (bugs) during kernel initialization.
|
||||||
|
@ -228,6 +64,8 @@ trapinit0(void)
|
||||||
void
|
void
|
||||||
trapinit(void)
|
trapinit(void)
|
||||||
{
|
{
|
||||||
|
irqinit();
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Special traps.
|
* Special traps.
|
||||||
* Syscall() is called directly without going through trap().
|
* Syscall() is called directly without going through trap().
|
||||||
|
@ -237,9 +75,6 @@ trapinit(void)
|
||||||
trapenable(VectorPF, fault386, 0, "fault386");
|
trapenable(VectorPF, fault386, 0, "fault386");
|
||||||
trapenable(Vector2F, doublefault, 0, "doublefault");
|
trapenable(Vector2F, doublefault, 0, "doublefault");
|
||||||
trapenable(Vector15, unexpected, 0, "unexpected");
|
trapenable(Vector15, unexpected, 0, "unexpected");
|
||||||
nmienable();
|
|
||||||
|
|
||||||
addarchfile("irqalloc", 0444, irqallocread, nil);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static char* excname[32] = {
|
static char* excname[32] = {
|
||||||
|
@ -277,27 +112,18 @@ static char* excname[32] = {
|
||||||
"31 (reserved)",
|
"31 (reserved)",
|
||||||
};
|
};
|
||||||
|
|
||||||
/*
|
static int
|
||||||
* keep histogram of interrupt service times
|
usertrap(int vno)
|
||||||
*/
|
|
||||||
void
|
|
||||||
intrtime(Mach*, int vno)
|
|
||||||
{
|
{
|
||||||
ulong diff;
|
char buf[ERRMAX];
|
||||||
ulong x;
|
|
||||||
|
|
||||||
x = perfticks();
|
if(vno < nelem(excname)){
|
||||||
diff = x - m->perf.intrts;
|
spllo();
|
||||||
m->perf.intrts = x;
|
sprint(buf, "sys: trap: %s", excname[vno]);
|
||||||
|
postnote(up, 1, buf, NDebug);
|
||||||
m->perf.inintr += diff;
|
return 1;
|
||||||
if(up == nil && m->perf.inidle > diff)
|
}
|
||||||
m->perf.inidle -= diff;
|
return 0;
|
||||||
|
|
||||||
diff /= m->cpumhz*100; /* quantum = 100µsec */
|
|
||||||
if(diff >= Ntimevec)
|
|
||||||
diff = Ntimevec-1;
|
|
||||||
intrtimes[vno][diff]++;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* go to user space */
|
/* go to user space */
|
||||||
|
@ -325,112 +151,16 @@ kexit(Ureg*)
|
||||||
void
|
void
|
||||||
trap(Ureg* ureg)
|
trap(Ureg* ureg)
|
||||||
{
|
{
|
||||||
int clockintr, i, vno, user;
|
int vno, user;
|
||||||
char buf[ERRMAX];
|
|
||||||
Vctl *ctl, *v;
|
|
||||||
Mach *mach;
|
|
||||||
|
|
||||||
m->perf.intrts = perfticks();
|
|
||||||
user = userureg(ureg);
|
user = userureg(ureg);
|
||||||
if(user){
|
if(user){
|
||||||
up->dbgreg = ureg;
|
up->dbgreg = ureg;
|
||||||
cycles(&up->kentry);
|
cycles(&up->kentry);
|
||||||
}
|
}
|
||||||
|
|
||||||
clockintr = 0;
|
|
||||||
|
|
||||||
vno = ureg->trap;
|
vno = ureg->trap;
|
||||||
if(ctl = vctl[vno]){
|
if(!irqhandled(ureg, vno) && (!user || !usertrap(vno))){
|
||||||
if(ctl->isintr){
|
|
||||||
m->intr++;
|
|
||||||
if(vno >= VectorPIC && vno != VectorSYSCALL)
|
|
||||||
m->lastintr = ctl->irq;
|
|
||||||
}
|
|
||||||
|
|
||||||
if(ctl->isr)
|
|
||||||
ctl->isr(vno);
|
|
||||||
for(v = ctl; v != nil; v = v->next){
|
|
||||||
if(v->f)
|
|
||||||
v->f(ureg, v->a);
|
|
||||||
}
|
|
||||||
if(ctl->eoi)
|
|
||||||
ctl->eoi(vno);
|
|
||||||
|
|
||||||
if(ctl->isintr){
|
|
||||||
intrtime(m, vno);
|
|
||||||
|
|
||||||
if(ctl->irq == IrqCLOCK || ctl->irq == IrqTIMER)
|
|
||||||
clockintr = 1;
|
|
||||||
|
|
||||||
if(up && !clockintr)
|
|
||||||
preempted();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if(vno < nelem(excname) && user){
|
|
||||||
spllo();
|
|
||||||
sprint(buf, "sys: trap: %s", excname[vno]);
|
|
||||||
postnote(up, 1, buf, NDebug);
|
|
||||||
}
|
|
||||||
else if(vno >= VectorPIC && vno != VectorSYSCALL){
|
|
||||||
/*
|
|
||||||
* An unknown interrupt.
|
|
||||||
* Check for a default IRQ7. This can happen when
|
|
||||||
* the IRQ input goes away before the acknowledge.
|
|
||||||
* In this case, a 'default IRQ7' is generated, but
|
|
||||||
* the corresponding bit in the ISR isn't set.
|
|
||||||
* In fact, just ignore all such interrupts.
|
|
||||||
*/
|
|
||||||
|
|
||||||
/* call all interrupt routines, just in case */
|
|
||||||
for(i = VectorPIC; i <= MaxIrqLAPIC; i++){
|
|
||||||
ctl = vctl[i];
|
|
||||||
if(ctl == nil)
|
|
||||||
continue;
|
|
||||||
if(!ctl->isintr)
|
|
||||||
continue;
|
|
||||||
for(v = ctl; v != nil; v = v->next){
|
|
||||||
if(v->f)
|
|
||||||
v->f(ureg, v->a);
|
|
||||||
}
|
|
||||||
/* should we do this? */
|
|
||||||
if(ctl->eoi)
|
|
||||||
ctl->eoi(i);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* clear the interrupt */
|
|
||||||
i8259isr(vno);
|
|
||||||
|
|
||||||
if(0)print("cpu%d: spurious interrupt %d, last %d\n",
|
|
||||||
m->machno, vno, m->lastintr);
|
|
||||||
if(0)if(conf.nmach > 1){
|
|
||||||
for(i = 0; i < MAXMACH; i++){
|
|
||||||
if(active.machs[i] == 0)
|
|
||||||
continue;
|
|
||||||
mach = MACHP(i);
|
|
||||||
if(m->machno == mach->machno)
|
|
||||||
continue;
|
|
||||||
print(" cpu%d: last %d",
|
|
||||||
mach->machno, mach->lastintr);
|
|
||||||
}
|
|
||||||
print("\n");
|
|
||||||
}
|
|
||||||
m->spuriousintr++;
|
|
||||||
if(user)
|
|
||||||
kexit(ureg);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
else{
|
|
||||||
if(vno == VectorNMI){
|
|
||||||
/*
|
|
||||||
* Don't re-enable, it confuses the crash dumps.
|
|
||||||
nmienable();
|
|
||||||
*/
|
|
||||||
iprint("cpu%d: nmi PC %#8.8lux, status %ux\n",
|
|
||||||
m->machno, ureg->pc, inb(0x61));
|
|
||||||
while(m->machno != 0)
|
|
||||||
;
|
|
||||||
}
|
|
||||||
|
|
||||||
if(!user){
|
if(!user){
|
||||||
void (*pc)(void);
|
void (*pc)(void);
|
||||||
ulong *sp;
|
ulong *sp;
|
||||||
|
@ -494,12 +224,6 @@ trap(Ureg* ureg)
|
||||||
}
|
}
|
||||||
splhi();
|
splhi();
|
||||||
|
|
||||||
/* delaysched set because we held a lock or because our quantum ended */
|
|
||||||
if(up && up->delaysched && clockintr){
|
|
||||||
sched();
|
|
||||||
splhi();
|
|
||||||
}
|
|
||||||
|
|
||||||
if(user){
|
if(user){
|
||||||
if(up->procctl || up->nnote)
|
if(up->procctl || up->nnote)
|
||||||
notify(ureg);
|
notify(ureg);
|
||||||
|
|
|
@ -52,6 +52,7 @@ OBJ=\
|
||||||
main.$O\
|
main.$O\
|
||||||
memory.$O\
|
memory.$O\
|
||||||
mmu.$O\
|
mmu.$O\
|
||||||
|
irq.$O\
|
||||||
trap.$O\
|
trap.$O\
|
||||||
bootargs.$O\
|
bootargs.$O\
|
||||||
$CONF.root.$O\
|
$CONF.root.$O\
|
||||||
|
|
|
@ -9,6 +9,9 @@
|
||||||
#include "../port/error.h"
|
#include "../port/error.h"
|
||||||
#include <trace.h>
|
#include <trace.h>
|
||||||
|
|
||||||
|
extern int irqhandled(Ureg*, int);
|
||||||
|
extern void irqinit(void);
|
||||||
|
|
||||||
void noted(Ureg*, ulong);
|
void noted(Ureg*, ulong);
|
||||||
|
|
||||||
static void debugexc(Ureg*, void*);
|
static void debugexc(Ureg*, void*);
|
||||||
|
@ -18,176 +21,6 @@ static void doublefault(Ureg*, void*);
|
||||||
static void unexpected(Ureg*, void*);
|
static void unexpected(Ureg*, void*);
|
||||||
static void _dumpstack(Ureg*);
|
static void _dumpstack(Ureg*);
|
||||||
|
|
||||||
static Lock vctllock;
|
|
||||||
static Vctl *vctl[256];
|
|
||||||
|
|
||||||
enum
|
|
||||||
{
|
|
||||||
Ntimevec = 20 /* number of time buckets for each intr */
|
|
||||||
};
|
|
||||||
ulong intrtimes[256][Ntimevec];
|
|
||||||
|
|
||||||
void
|
|
||||||
intrenable(int irq, void (*f)(Ureg*, void*), void* a, int tbdf, char *name)
|
|
||||||
{
|
|
||||||
int vno;
|
|
||||||
Vctl *v;
|
|
||||||
|
|
||||||
if(f == nil){
|
|
||||||
print("intrenable: nil handler for %d, tbdf 0x%uX for %s\n",
|
|
||||||
irq, tbdf, name);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if(tbdf != BUSUNKNOWN && (irq == 0xff || irq == 0))
|
|
||||||
irq = -1;
|
|
||||||
|
|
||||||
|
|
||||||
/*
|
|
||||||
* IRQ2 doesn't really exist, it's used to gang the interrupt
|
|
||||||
* controllers together. A device set to IRQ2 will appear on
|
|
||||||
* the second interrupt controller as IRQ9.
|
|
||||||
*/
|
|
||||||
if(irq == 2)
|
|
||||||
irq = 9;
|
|
||||||
|
|
||||||
if((v = xalloc(sizeof(Vctl))) == nil)
|
|
||||||
panic("intrenable: out of memory");
|
|
||||||
v->isintr = 1;
|
|
||||||
v->irq = irq;
|
|
||||||
v->tbdf = tbdf;
|
|
||||||
v->f = f;
|
|
||||||
v->a = a;
|
|
||||||
strncpy(v->name, name, KNAMELEN-1);
|
|
||||||
v->name[KNAMELEN-1] = 0;
|
|
||||||
|
|
||||||
ilock(&vctllock);
|
|
||||||
vno = arch->intrenable(v);
|
|
||||||
if(vno == -1){
|
|
||||||
iunlock(&vctllock);
|
|
||||||
print("intrenable: couldn't enable irq %d, tbdf 0x%uX for %s\n",
|
|
||||||
irq, tbdf, v->name);
|
|
||||||
xfree(v);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if(vctl[vno]){
|
|
||||||
if(vctl[vno]->isr != v->isr || vctl[vno]->eoi != v->eoi)
|
|
||||||
panic("intrenable: handler: %s %s %#p %#p %#p %#p",
|
|
||||||
vctl[vno]->name, v->name,
|
|
||||||
vctl[vno]->isr, v->isr, vctl[vno]->eoi, v->eoi);
|
|
||||||
v->next = vctl[vno];
|
|
||||||
}
|
|
||||||
vctl[vno] = v;
|
|
||||||
iunlock(&vctllock);
|
|
||||||
}
|
|
||||||
|
|
||||||
void
|
|
||||||
intrdisable(int irq, void (*f)(Ureg *, void *), void *a, int tbdf, char *name)
|
|
||||||
{
|
|
||||||
Vctl **pv, *v;
|
|
||||||
int vno;
|
|
||||||
|
|
||||||
if(irq == 2)
|
|
||||||
irq = 9;
|
|
||||||
if(arch->intrvecno == nil || (tbdf != BUSUNKNOWN && (irq == 0xff || irq == 0))){
|
|
||||||
/*
|
|
||||||
* on APIC machine, irq is pretty meaningless
|
|
||||||
* and disabling a the vector is not implemented.
|
|
||||||
* however, we still want to remove the matching
|
|
||||||
* Vctl entry to prevent calling Vctl.f() with a
|
|
||||||
* stale Vctl.a pointer.
|
|
||||||
*/
|
|
||||||
irq = -1;
|
|
||||||
vno = VectorPIC;
|
|
||||||
} else {
|
|
||||||
vno = arch->intrvecno(irq);
|
|
||||||
}
|
|
||||||
ilock(&vctllock);
|
|
||||||
do {
|
|
||||||
for(pv = &vctl[vno]; (v = *pv) != nil; pv = &v->next){
|
|
||||||
if(v->isintr && (v->irq == irq || irq == -1)
|
|
||||||
&& v->tbdf == tbdf && v->f == f && v->a == a
|
|
||||||
&& strcmp(v->name, name) == 0)
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
if(v != nil){
|
|
||||||
if(v->disable != nil)
|
|
||||||
(*v->disable)(v);
|
|
||||||
*pv = v->next;
|
|
||||||
xfree(v);
|
|
||||||
|
|
||||||
if(irq != -1 && vctl[vno] == nil && arch->intrdisable != nil)
|
|
||||||
arch->intrdisable(irq);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
} while(irq == -1 && ++vno <= MaxVectorAPIC);
|
|
||||||
iunlock(&vctllock);
|
|
||||||
}
|
|
||||||
|
|
||||||
static long
|
|
||||||
irqallocread(Chan*, void *a, long n, vlong offset)
|
|
||||||
{
|
|
||||||
char buf[2*(11+1)+KNAMELEN+1+1];
|
|
||||||
int vno, m;
|
|
||||||
Vctl *v;
|
|
||||||
|
|
||||||
if(n < 0 || offset < 0)
|
|
||||||
error(Ebadarg);
|
|
||||||
|
|
||||||
for(vno=0; vno<nelem(vctl); vno++){
|
|
||||||
for(v=vctl[vno]; v; v=v->next){
|
|
||||||
m = snprint(buf, sizeof(buf), "%11d %11d %.*s\n", vno, v->irq, KNAMELEN, v->name);
|
|
||||||
offset -= m;
|
|
||||||
if(offset >= 0)
|
|
||||||
continue;
|
|
||||||
if(n > -offset)
|
|
||||||
n = -offset;
|
|
||||||
offset += m;
|
|
||||||
memmove(a, buf+offset, n);
|
|
||||||
return n;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
void
|
|
||||||
trapenable(int vno, void (*f)(Ureg*, void*), void* a, char *name)
|
|
||||||
{
|
|
||||||
Vctl *v;
|
|
||||||
|
|
||||||
if(vno < 0 || vno >= VectorPIC)
|
|
||||||
panic("trapenable: vno %d", vno);
|
|
||||||
if((v = xalloc(sizeof(Vctl))) == nil)
|
|
||||||
panic("trapenable: out of memory");
|
|
||||||
v->tbdf = BUSUNKNOWN;
|
|
||||||
v->f = f;
|
|
||||||
v->a = a;
|
|
||||||
strncpy(v->name, name, KNAMELEN-1);
|
|
||||||
v->name[KNAMELEN-1] = 0;
|
|
||||||
|
|
||||||
ilock(&vctllock);
|
|
||||||
if(vctl[vno])
|
|
||||||
v->next = vctl[vno]->next;
|
|
||||||
vctl[vno] = v;
|
|
||||||
iunlock(&vctllock);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void
|
|
||||||
nmienable(void)
|
|
||||||
{
|
|
||||||
int x;
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Hack: should be locked with NVRAM access.
|
|
||||||
*/
|
|
||||||
outb(0x70, 0x80); /* NMI latch clear */
|
|
||||||
outb(0x70, 0);
|
|
||||||
|
|
||||||
x = inb(0x61) & 0x07; /* Enable NMI */
|
|
||||||
outb(0x61, 0x0C|x);
|
|
||||||
outb(0x61, x);
|
|
||||||
}
|
|
||||||
|
|
||||||
void
|
void
|
||||||
trapinit0(void)
|
trapinit0(void)
|
||||||
{
|
{
|
||||||
|
@ -232,6 +65,8 @@ trapinit0(void)
|
||||||
void
|
void
|
||||||
trapinit(void)
|
trapinit(void)
|
||||||
{
|
{
|
||||||
|
irqinit();
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Special traps.
|
* Special traps.
|
||||||
* Syscall() is called directly without going through trap().
|
* Syscall() is called directly without going through trap().
|
||||||
|
@ -241,8 +76,6 @@ trapinit(void)
|
||||||
trapenable(VectorPF, faultamd64, 0, "faultamd64");
|
trapenable(VectorPF, faultamd64, 0, "faultamd64");
|
||||||
trapenable(Vector2F, doublefault, 0, "doublefault");
|
trapenable(Vector2F, doublefault, 0, "doublefault");
|
||||||
trapenable(Vector15, unexpected, 0, "unexpected");
|
trapenable(Vector15, unexpected, 0, "unexpected");
|
||||||
nmienable();
|
|
||||||
addarchfile("irqalloc", 0444, irqallocread, nil);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static char* excname[32] = {
|
static char* excname[32] = {
|
||||||
|
@ -280,27 +113,18 @@ static char* excname[32] = {
|
||||||
"31 (reserved)",
|
"31 (reserved)",
|
||||||
};
|
};
|
||||||
|
|
||||||
/*
|
static int
|
||||||
* keep histogram of interrupt service times
|
usertrap(int vno)
|
||||||
*/
|
|
||||||
void
|
|
||||||
intrtime(Mach*, int vno)
|
|
||||||
{
|
{
|
||||||
ulong diff;
|
char buf[ERRMAX];
|
||||||
ulong x;
|
|
||||||
|
|
||||||
x = perfticks();
|
if(vno < nelem(excname)){
|
||||||
diff = x - m->perf.intrts;
|
spllo();
|
||||||
m->perf.intrts = x;
|
sprint(buf, "sys: trap: %s", excname[vno]);
|
||||||
|
postnote(up, 1, buf, NDebug);
|
||||||
m->perf.inintr += diff;
|
return 1;
|
||||||
if(up == nil && m->perf.inidle > diff)
|
}
|
||||||
m->perf.inidle -= diff;
|
return 0;
|
||||||
|
|
||||||
diff /= m->cpumhz*100; /* quantum = 100µsec */
|
|
||||||
if(diff >= Ntimevec)
|
|
||||||
diff = Ntimevec-1;
|
|
||||||
intrtimes[vno][diff]++;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* go to user space */
|
/* go to user space */
|
||||||
|
@ -321,112 +145,16 @@ kexit(Ureg*)
|
||||||
void
|
void
|
||||||
trap(Ureg *ureg)
|
trap(Ureg *ureg)
|
||||||
{
|
{
|
||||||
int clockintr, i, vno, user;
|
int vno, user;
|
||||||
char buf[ERRMAX];
|
|
||||||
Vctl *ctl, *v;
|
|
||||||
Mach *mach;
|
|
||||||
|
|
||||||
m->perf.intrts = perfticks();
|
|
||||||
user = userureg(ureg);
|
user = userureg(ureg);
|
||||||
if(user){
|
if(user){
|
||||||
up->dbgreg = ureg;
|
up->dbgreg = ureg;
|
||||||
cycles(&up->kentry);
|
cycles(&up->kentry);
|
||||||
}
|
}
|
||||||
|
|
||||||
clockintr = 0;
|
|
||||||
|
|
||||||
vno = ureg->type;
|
vno = ureg->type;
|
||||||
|
if(!irqhandled(ureg, vno) && (!user || !usertrap(vno))){
|
||||||
if(ctl = vctl[vno]){
|
|
||||||
if(ctl->isintr){
|
|
||||||
m->intr++;
|
|
||||||
if(vno >= VectorPIC)
|
|
||||||
m->lastintr = ctl->irq;
|
|
||||||
}
|
|
||||||
if(ctl->isr)
|
|
||||||
ctl->isr(vno);
|
|
||||||
for(v = ctl; v != nil; v = v->next){
|
|
||||||
if(v->f)
|
|
||||||
v->f(ureg, v->a);
|
|
||||||
}
|
|
||||||
if(ctl->eoi)
|
|
||||||
ctl->eoi(vno);
|
|
||||||
|
|
||||||
if(ctl->isintr){
|
|
||||||
intrtime(m, vno);
|
|
||||||
|
|
||||||
if(ctl->irq == IrqCLOCK || ctl->irq == IrqTIMER)
|
|
||||||
clockintr = 1;
|
|
||||||
|
|
||||||
if(up && !clockintr)
|
|
||||||
preempted();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if(vno < nelem(excname) && user){
|
|
||||||
spllo();
|
|
||||||
sprint(buf, "sys: trap: %s", excname[vno]);
|
|
||||||
postnote(up, 1, buf, NDebug);
|
|
||||||
}
|
|
||||||
else if(vno >= VectorPIC){
|
|
||||||
/*
|
|
||||||
* An unknown interrupt.
|
|
||||||
* Check for a default IRQ7. This can happen when
|
|
||||||
* the IRQ input goes away before the acknowledge.
|
|
||||||
* In this case, a 'default IRQ7' is generated, but
|
|
||||||
* the corresponding bit in the ISR isn't set.
|
|
||||||
* In fact, just ignore all such interrupts.
|
|
||||||
*/
|
|
||||||
|
|
||||||
/* call all interrupt routines, just in case */
|
|
||||||
for(i = VectorPIC; i <= MaxIrqLAPIC; i++){
|
|
||||||
ctl = vctl[i];
|
|
||||||
if(ctl == nil)
|
|
||||||
continue;
|
|
||||||
if(!ctl->isintr)
|
|
||||||
continue;
|
|
||||||
for(v = ctl; v != nil; v = v->next){
|
|
||||||
if(v->f)
|
|
||||||
v->f(ureg, v->a);
|
|
||||||
}
|
|
||||||
/* should we do this? */
|
|
||||||
if(ctl->eoi)
|
|
||||||
ctl->eoi(i);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* clear the interrupt */
|
|
||||||
i8259isr(vno);
|
|
||||||
|
|
||||||
if(0)print("cpu%d: spurious interrupt %d, last %d\n",
|
|
||||||
m->machno, vno, m->lastintr);
|
|
||||||
if(0)if(conf.nmach > 1){
|
|
||||||
for(i = 0; i < MAXMACH; i++){
|
|
||||||
if(active.machs[i] == 0)
|
|
||||||
continue;
|
|
||||||
mach = MACHP(i);
|
|
||||||
if(m->machno == mach->machno)
|
|
||||||
continue;
|
|
||||||
print(" cpu%d: last %d",
|
|
||||||
mach->machno, mach->lastintr);
|
|
||||||
}
|
|
||||||
print("\n");
|
|
||||||
}
|
|
||||||
m->spuriousintr++;
|
|
||||||
if(user)
|
|
||||||
kexit(ureg);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
else{
|
|
||||||
if(vno == VectorNMI){
|
|
||||||
/*
|
|
||||||
* Don't re-enable, it confuses the crash dumps.
|
|
||||||
nmienable();
|
|
||||||
*/
|
|
||||||
iprint("cpu%d: nmi PC %#p, status %ux\n",
|
|
||||||
m->machno, ureg->pc, inb(0x61));
|
|
||||||
while(m->machno != 0)
|
|
||||||
;
|
|
||||||
}
|
|
||||||
|
|
||||||
if(!user){
|
if(!user){
|
||||||
void (*pc)(void);
|
void (*pc)(void);
|
||||||
|
|
||||||
|
@ -464,12 +192,6 @@ trap(Ureg *ureg)
|
||||||
}
|
}
|
||||||
splhi();
|
splhi();
|
||||||
|
|
||||||
/* delaysched set because we held a lock or because our quantum ended */
|
|
||||||
if(up && up->delaysched && clockintr){
|
|
||||||
sched();
|
|
||||||
splhi();
|
|
||||||
}
|
|
||||||
|
|
||||||
if(user){
|
if(user){
|
||||||
if(up->procctl || up->nnote)
|
if(up->procctl || up->nnote)
|
||||||
notify(ureg);
|
notify(ureg);
|
||||||
|
|
Loading…
Reference in a new issue