
From richard: A couple of patches applied yesterday should make debugging on ARM a bit more reliable. Using db or acid on ARM, you may have noticed that a program being debugged would sometimes execute through a breakpoint without stopping, or run away while being single stepped. It turns out, as often happens, that one symptom had two separate causes. For details: /n/sources/patch/applied/5db-condcode/readme /n/sources/patch/applied/arm-bkpt-cond/readme To take advantage of the patches, rebuild libmach.a, then acid and db. On machines with a kw kernel (sheevaplug et al), you'll also want to rebuild /arm/9plug; otherwise breakpoints will stop working at all. The new 9plug will, however, still work with the old libmach; and the bcm and teg2 kernels are already compatible with the new libmach.
680 lines
13 KiB
C
680 lines
13 KiB
C
/*
|
|
* sheevaplug traps, exceptions, interrupts, system calls.
|
|
*/
|
|
#include "u.h"
|
|
#include "../port/lib.h"
|
|
#include "mem.h"
|
|
#include "dat.h"
|
|
#include "fns.h"
|
|
#include "io.h"
|
|
#include "ureg.h"
|
|
#include "../port/error.h"
|
|
|
|
#include "arm.h"
|
|
|
|
enum {
|
|
Ntimevec = 20, /* # of time buckets for each intr */
|
|
Nvecs = 256,
|
|
};
|
|
|
|
extern int notify(Ureg*);
|
|
|
|
extern int ldrexvalid;
|
|
|
|
typedef struct Vctl Vctl;
|
|
typedef struct Vctl {
|
|
Vctl* next; /* handlers on this vector */
|
|
char *name; /* of driver, xallocated */
|
|
void (*f)(Ureg*, void*); /* handler to call */
|
|
void* a; /* argument to call it with */
|
|
} Vctl;
|
|
|
|
static Lock vctllock;
|
|
static Vctl* vctl[32];
|
|
|
|
uvlong ninterrupt;
|
|
uvlong ninterruptticks;
|
|
ulong intrtimes[Nvecs][Ntimevec];
|
|
|
|
typedef struct Handler Handler;
|
|
struct Handler {
|
|
void (*r)(Ureg*, void*);
|
|
void *a;
|
|
char name[KNAMELEN];
|
|
};
|
|
|
|
static Handler irqlo[32];
|
|
static Handler irqhi[32];
|
|
static Handler irqbridge[32];
|
|
static Lock irqlock;
|
|
static int probing, trapped;
|
|
|
|
typedef struct Irq Irq;
|
|
struct Irq {
|
|
ulong *irq;
|
|
ulong *irqmask;
|
|
Handler *irqvec;
|
|
int nirqvec;
|
|
char *name;
|
|
};
|
|
/* irq and irqmask are filled in by trapinit */
|
|
static Irq irqs[] = {
|
|
[Irqlo] {nil, nil, irqlo, nelem(irqlo), "lo"},
|
|
[Irqhi] {nil, nil, irqhi, nelem(irqhi), "hi"},
|
|
[Irqbridge] {nil, nil, irqbridge, nelem(irqbridge), "bridge"},
|
|
};
|
|
|
|
/*
|
|
* keep histogram of interrupt service times
|
|
*/
|
|
void
|
|
intrtime(Mach*, int vno)
|
|
{
|
|
ulong diff, x;
|
|
|
|
if (m == nil)
|
|
return;
|
|
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;
|
|
|
|
if (m->cpuhz == 0) /* not set yet? */
|
|
return;
|
|
diff /= (m->cpuhz/1000000)*100; /* quantum = 100µsec */
|
|
if(diff >= Ntimevec)
|
|
diff = Ntimevec-1;
|
|
assert(vno >= 0 && vno < Nvecs);
|
|
intrtimes[vno][diff]++;
|
|
}
|
|
|
|
void
|
|
intrfmtcounts(char *s, char *se)
|
|
{
|
|
USED(s, se);
|
|
}
|
|
|
|
static void
|
|
dumpcounts(void)
|
|
{
|
|
}
|
|
|
|
void
|
|
intrclear(int sort, int v)
|
|
{
|
|
*irqs[sort].irq = ~(1 << v);
|
|
}
|
|
|
|
void
|
|
intrmask(int sort, int v)
|
|
{
|
|
*irqs[sort].irqmask &= ~(1 << v);
|
|
}
|
|
|
|
void
|
|
intrunmask(int sort, int v)
|
|
{
|
|
*irqs[sort].irqmask |= 1 << v;
|
|
}
|
|
|
|
static void
|
|
maskallints(void)
|
|
{
|
|
CpucsReg *cpu = (CpucsReg *)soc.cpu;
|
|
IntrReg *intr;
|
|
|
|
/* no fiq or ep in use */
|
|
intr = (IntrReg *)soc.intr;
|
|
intr->lo.irqmask = 0;
|
|
intr->hi.irqmask = 0;
|
|
cpu->irqmask = 0;
|
|
coherence();
|
|
}
|
|
|
|
void
|
|
intrset(Handler *h, void (*f)(Ureg*, void*), void *a, char *name)
|
|
{
|
|
if(h->r != nil) {
|
|
// iprint("duplicate irq: %s (%#p)\n", h->name, h->r);
|
|
return;
|
|
}
|
|
h->r = f;
|
|
h->a = a;
|
|
strncpy(h->name, name, KNAMELEN-1);
|
|
h->name[KNAMELEN-1] = 0;
|
|
}
|
|
|
|
void
|
|
intrunset(Handler *h)
|
|
{
|
|
h->r = nil;
|
|
h->a = nil;
|
|
h->name[0] = 0;
|
|
}
|
|
|
|
void
|
|
intrdel(Handler *h, void (*f)(Ureg*, void*), void *a, char *name)
|
|
{
|
|
if(h->r != f || h->a != a || strcmp(h->name, name) != 0)
|
|
return;
|
|
intrunset(h);
|
|
}
|
|
|
|
void
|
|
intrenable(int sort, int v, void (*f)(Ureg*, void*), void *a, char *name)
|
|
{
|
|
//iprint("enabling intr %d vec %d for %s\n", sort, v, name);
|
|
ilock(&irqlock);
|
|
intrset(&irqs[sort].irqvec[v], f, a, name);
|
|
intrunmask(sort, v);
|
|
iunlock(&irqlock);
|
|
}
|
|
|
|
void
|
|
intrdisable(int sort, int v, void (*f)(Ureg*, void*), void* a, char *name)
|
|
{
|
|
ilock(&irqlock);
|
|
intrdel(&irqs[sort].irqvec[v], f, a, name);
|
|
intrmask(sort, v);
|
|
iunlock(&irqlock);
|
|
}
|
|
|
|
/*
|
|
* called by trap to handle interrupts
|
|
*/
|
|
static void
|
|
intrs(Ureg *ur, int sort)
|
|
{
|
|
int i, s;
|
|
ulong ibits;
|
|
Handler *h;
|
|
Irq irq;
|
|
|
|
assert(sort >= 0 && sort < nelem(irqs));
|
|
irq = irqs[sort];
|
|
ibits = *irq.irq;
|
|
ibits &= *irq.irqmask;
|
|
|
|
for(i = 0; i < irq.nirqvec && ibits; i++)
|
|
if(ibits & (1<<i)){
|
|
h = &irq.irqvec[i];
|
|
if(h->r != nil){
|
|
h->r(ur, h->a);
|
|
splhi();
|
|
intrtime(m, sort*32 + i);
|
|
if (sort == Irqbridge && i == IRQcputimer0)
|
|
m->inclockintr = 1;
|
|
ibits &= ~(1<<i);
|
|
}
|
|
}
|
|
if(ibits != 0) {
|
|
iprint("spurious irq%s interrupt: %8.8lux\n", irq.name, ibits);
|
|
s = splfhi();
|
|
*irq.irq &= ibits;
|
|
*irq.irqmask &= ~ibits;
|
|
splx(s);
|
|
}
|
|
}
|
|
|
|
void
|
|
intrhi(Ureg *ureg, void*)
|
|
{
|
|
intrs(ureg, Irqhi);
|
|
}
|
|
|
|
void
|
|
intrbridge(Ureg *ureg, void*)
|
|
{
|
|
intrs(ureg, Irqbridge);
|
|
intrclear(Irqlo, IRQ0bridge);
|
|
}
|
|
|
|
void
|
|
trapinit(void)
|
|
{
|
|
int i;
|
|
CpucsReg *cpu;
|
|
IntrReg *intr;
|
|
Vectorpage *page0 = (Vectorpage*)HVECTORS;
|
|
|
|
intr = (IntrReg *)soc.intr;
|
|
cpu = (CpucsReg *)soc.cpu;
|
|
irqs[Irqlo].irq = &intr->lo.irq;
|
|
irqs[Irqlo].irqmask = &intr->lo.irqmask;
|
|
irqs[Irqhi].irq = &intr->hi.irq;
|
|
irqs[Irqhi].irqmask = &intr->hi.irqmask;
|
|
irqs[Irqbridge].irq = &cpu->irq;
|
|
irqs[Irqbridge].irqmask = &cpu->irqmask;
|
|
coherence();
|
|
|
|
setr13(PsrMfiq, m->fiqstack + nelem(m->fiqstack));
|
|
setr13(PsrMirq, m->irqstack + nelem(m->irqstack));
|
|
setr13(PsrMabt, m->abtstack + nelem(m->abtstack));
|
|
setr13(PsrMund, m->undstack + nelem(m->undstack));
|
|
|
|
memmove(page0->vectors, vectors, sizeof page0->vectors);
|
|
memmove(page0->vtable, vtable, sizeof page0->vtable);
|
|
cacheuwbinv();
|
|
l2cacheuwbinv();
|
|
|
|
cpu->cpucfg &= ~Cfgvecinithi;
|
|
|
|
for(i = 0; i < nelem(irqlo); i++)
|
|
intrunset(&irqlo[i]);
|
|
for(i = 0; i < nelem(irqhi); i++)
|
|
intrunset(&irqhi[i]);
|
|
for(i = 0; i < nelem(irqbridge); i++)
|
|
intrunset(&irqbridge[i]);
|
|
|
|
/* disable all interrupts */
|
|
intr->lo.fiqmask = intr->hi.fiqmask = 0;
|
|
intr->lo.irqmask = intr->hi.irqmask = 0;
|
|
intr->lo.epmask = intr->hi.epmask = 0;
|
|
cpu->irqmask = 0;
|
|
coherence();
|
|
|
|
/* clear interrupts */
|
|
intr->lo.irq = intr->hi.irq = ~0;
|
|
cpu->irq = ~0;
|
|
coherence();
|
|
|
|
intrenable(Irqlo, IRQ0hisum, intrhi, nil, "hi");
|
|
intrenable(Irqlo, IRQ0bridge, intrbridge, nil, "bridge");
|
|
|
|
/* enable watchdog & access-error interrupts */
|
|
cpu->irqmask |= 1 << IRQcputimerwd | 1 << IRQaccesserr;
|
|
coherence();
|
|
}
|
|
|
|
static char *trapnames[PsrMask+1] = {
|
|
[ PsrMusr ] "user mode",
|
|
[ PsrMfiq ] "fiq interrupt",
|
|
[ PsrMirq ] "irq interrupt",
|
|
[ PsrMsvc ] "svc/swi exception",
|
|
[ PsrMabt ] "prefetch abort/data abort",
|
|
[ PsrMabt+1 ] "data abort",
|
|
[ PsrMund ] "undefined instruction",
|
|
[ PsrMsys ] "sys trap",
|
|
};
|
|
|
|
static char *
|
|
trapname(int psr)
|
|
{
|
|
char *s;
|
|
|
|
s = trapnames[psr & PsrMask];
|
|
if(s == nil)
|
|
s = "unknown trap number in psr";
|
|
return s;
|
|
}
|
|
|
|
/*
|
|
* called by trap to handle access faults
|
|
*/
|
|
static void
|
|
faultarm(Ureg *ureg, uintptr va, int user, int read)
|
|
{
|
|
int n, insyscall;
|
|
char buf[ERRMAX];
|
|
static int cnt, lastpid;
|
|
static ulong lastva;
|
|
|
|
if(up == nil) {
|
|
dumpregs(ureg);
|
|
panic("fault: nil up in faultarm, accessing %#p", va);
|
|
}
|
|
insyscall = up->insyscall;
|
|
up->insyscall = 1;
|
|
|
|
/* this is quite helpful during mmu and cache debugging */
|
|
if(va == lastva && up->pid == lastpid) {
|
|
++cnt;
|
|
if (cnt >= 2)
|
|
/* fault() isn't fixing the underlying cause */
|
|
panic("fault: %d consecutive faults for va %#lux",
|
|
cnt+1, va);
|
|
} else {
|
|
cnt = 0;
|
|
lastva = va;
|
|
lastpid = up->pid;
|
|
}
|
|
|
|
n = fault(va, read);
|
|
if(n < 0){
|
|
if(!user){
|
|
dumpregs(ureg);
|
|
panic("fault: kernel accessing %#p", va);
|
|
}
|
|
/* don't dump registers; programs suicide all the time */
|
|
snprint(buf, sizeof buf, "sys: trap: fault %s va=%#p",
|
|
read? "read": "write", va);
|
|
postnote(up, 1, buf, NDebug);
|
|
}
|
|
up->insyscall = insyscall;
|
|
}
|
|
|
|
/*
|
|
* returns 1 if the instruction writes memory, 0 otherwise
|
|
*/
|
|
int
|
|
writetomem(ulong inst)
|
|
{
|
|
/* swap always write memory */
|
|
if((inst & 0x0FC00000) == 0x01000000)
|
|
return 1;
|
|
|
|
/* loads and stores are distinguished by bit 20 */
|
|
if(inst & (1<<20))
|
|
return 0;
|
|
|
|
return 1;
|
|
}
|
|
|
|
void
|
|
trap(Ureg *ureg)
|
|
{
|
|
int user, x, rv, rem;
|
|
ulong inst;
|
|
u32int fsr;
|
|
uintptr va;
|
|
char buf[ERRMAX];
|
|
|
|
if(up != nil)
|
|
rem = (char*)ureg - up->kstack;
|
|
else
|
|
rem = (char*)ureg - ((char*)m + sizeof(Mach));
|
|
if(rem < 256) {
|
|
dumpstack();
|
|
panic("trap %d bytes remaining, up %#p ureg %#p at pc %#lux",
|
|
rem, up, ureg, ureg->pc);
|
|
}
|
|
|
|
user = (ureg->psr & PsrMask) == PsrMusr;
|
|
if(user){
|
|
up->dbgreg = ureg;
|
|
cycles(&up->kentry);
|
|
}
|
|
|
|
if(ureg->type == PsrMabt+1)
|
|
ureg->pc -= 8;
|
|
else
|
|
ureg->pc -= 4;
|
|
|
|
m->inclockintr = 0;
|
|
switch(ureg->type) {
|
|
default:
|
|
panic("unknown trap %ld", ureg->type);
|
|
break;
|
|
case PsrMirq:
|
|
ldrexvalid = 0;
|
|
// splflo(); /* allow fast interrupts */
|
|
intrs(ureg, Irqlo);
|
|
m->intr++;
|
|
break;
|
|
case PsrMabt: /* prefetch fault */
|
|
ldrexvalid = 0;
|
|
faultarm(ureg, ureg->pc, user, 1);
|
|
if(up->nnote == 0 &&
|
|
(*(u32int*)ureg->pc & ~(0xF<<28)) == 0x01200070)
|
|
postnote(up, 1, "sys: breakpoint", NDebug);
|
|
break;
|
|
case PsrMabt+1: /* data fault */
|
|
ldrexvalid = 0;
|
|
va = farget();
|
|
inst = *(ulong*)(ureg->pc);
|
|
fsr = fsrget() & 0xf;
|
|
if (probing && !user) {
|
|
if (trapped++ > 0)
|
|
panic("trap: recursive probe %#lux", va);
|
|
ureg->pc += 4; /* continue at next instruction */
|
|
break;
|
|
}
|
|
switch(fsr){
|
|
case 0x0:
|
|
panic("vector exception at %#lux", ureg->pc);
|
|
break;
|
|
case 0x1:
|
|
case 0x3:
|
|
if(user){
|
|
snprint(buf, sizeof buf,
|
|
"sys: alignment: pc %#lux va %#p\n",
|
|
ureg->pc, va);
|
|
postnote(up, 1, buf, NDebug);
|
|
} else
|
|
panic("kernel alignment: pc %#lux va %#p", ureg->pc, va);
|
|
break;
|
|
case 0x2:
|
|
panic("terminal exception at %#lux", ureg->pc);
|
|
break;
|
|
case 0x4:
|
|
case 0x6:
|
|
case 0x8:
|
|
case 0xa:
|
|
case 0xc:
|
|
case 0xe:
|
|
panic("external abort %#ux pc %#lux addr %#px",
|
|
fsr, ureg->pc, va);
|
|
break;
|
|
case 0x5: /* translation fault, no section entry */
|
|
case 0x7: /* translation fault, no page entry */
|
|
faultarm(ureg, va, user, !writetomem(inst));
|
|
break;
|
|
case 0x9:
|
|
case 0xb:
|
|
/* domain fault, accessing something we shouldn't */
|
|
if(user){
|
|
snprint(buf, sizeof buf,
|
|
"sys: access violation: pc %#lux va %#p\n",
|
|
ureg->pc, va);
|
|
postnote(up, 1, buf, NDebug);
|
|
} else
|
|
panic("kernel access violation: pc %#lux va %#p",
|
|
ureg->pc, va);
|
|
break;
|
|
case 0xd:
|
|
case 0xf:
|
|
/* permission error, copy on write or real permission error */
|
|
faultarm(ureg, va, user, !writetomem(inst));
|
|
break;
|
|
}
|
|
break;
|
|
case PsrMund: /* undefined instruction */
|
|
if(user){
|
|
if(seg(up, ureg->pc, 0) != nil &&
|
|
(*(u32int*)ureg->pc & ~(0xF<<28)) == 0x01200070)
|
|
postnote(up, 1, "sys: breakpoint", NDebug);
|
|
else{
|
|
/* look for floating point instructions to interpret */
|
|
x = spllo();
|
|
rv = fpiarm(ureg);
|
|
splx(x);
|
|
if(rv == 0){
|
|
ldrexvalid = 0;
|
|
snprint(buf, sizeof buf,
|
|
"undefined instruction: pc %#lux",
|
|
ureg->pc);
|
|
postnote(up, 1, buf, NDebug);
|
|
}
|
|
}
|
|
}else{
|
|
iprint("undefined instruction: pc %#lux inst %#ux\n",
|
|
ureg->pc, ((u32int*)ureg->pc)[-2]);
|
|
panic("undefined instruction");
|
|
}
|
|
break;
|
|
}
|
|
splhi();
|
|
|
|
/* delaysched set because we held a lock or because our quantum ended */
|
|
if(up && up->delaysched && m->inclockintr){
|
|
ldrexvalid = 0;
|
|
sched();
|
|
splhi();
|
|
}
|
|
|
|
if(user){
|
|
if(up->procctl || up->nnote)
|
|
notify(ureg);
|
|
kexit(ureg);
|
|
}
|
|
}
|
|
|
|
int
|
|
isvalidaddr(void *v)
|
|
{
|
|
return (uintptr)v >= KZERO;
|
|
}
|
|
|
|
void
|
|
dumplongs(char *msg, ulong *v, int n)
|
|
{
|
|
int i, l;
|
|
|
|
l = 0;
|
|
iprint("%s at %.8p: ", msg, v);
|
|
for(i=0; i<n; i++){
|
|
if(l >= 4){
|
|
iprint("\n %.8p: ", v);
|
|
l = 0;
|
|
}
|
|
if(isvalidaddr(v)){
|
|
iprint(" %.8lux", *v++);
|
|
l++;
|
|
}else{
|
|
iprint(" invalid");
|
|
break;
|
|
}
|
|
}
|
|
iprint("\n");
|
|
}
|
|
|
|
static void
|
|
dumpstackwithureg(Ureg *ureg)
|
|
{
|
|
uintptr l, i, v, estack;
|
|
u32int *p;
|
|
|
|
iprint("ktrace /kernel/path %#.8lux %#.8lux %#.8lux # pc, sp, link\n",
|
|
ureg->pc, ureg->sp, ureg->r14);
|
|
delay(2000);
|
|
i = 0;
|
|
if(up != nil && (uintptr)&l <= (uintptr)up->kstack+KSTACK)
|
|
estack = (uintptr)up->kstack+KSTACK;
|
|
else if((uintptr)&l >= (uintptr)m->stack
|
|
&& (uintptr)&l <= (uintptr)m+MACHSIZE)
|
|
estack = (uintptr)m+MACHSIZE;
|
|
else{
|
|
if(up != nil)
|
|
iprint("&up->kstack %#p &l %#p\n", up->kstack, &l);
|
|
else
|
|
iprint("&m %#p &l %#p\n", m, &l);
|
|
return;
|
|
}
|
|
for(l = (uintptr)&l; l < estack; l += sizeof(uintptr)){
|
|
v = *(uintptr*)l;
|
|
if(KTZERO < v && v < (uintptr)etext && !(v & 3)){
|
|
v -= sizeof(u32int); /* back up an instr */
|
|
p = (u32int*)v;
|
|
if((*p & 0x0f000000) == 0x0b000000){ /* BL instr? */
|
|
iprint("%#8.8lux=%#8.8lux ", l, v);
|
|
i++;
|
|
}
|
|
}
|
|
if(i == 4){
|
|
i = 0;
|
|
iprint("\n");
|
|
}
|
|
}
|
|
if(i)
|
|
iprint("\n");
|
|
}
|
|
|
|
/*
|
|
* Fill in enough of Ureg to get a stack trace, and call a function.
|
|
* Used by debugging interface rdb.
|
|
*/
|
|
void
|
|
callwithureg(void (*fn)(Ureg*))
|
|
{
|
|
Ureg ureg;
|
|
|
|
ureg.pc = getcallerpc(&fn);
|
|
ureg.sp = PTR2UINT(&fn);
|
|
fn(&ureg);
|
|
}
|
|
|
|
void
|
|
dumpstack(void)
|
|
{
|
|
callwithureg(dumpstackwithureg);
|
|
}
|
|
|
|
void
|
|
dumpregs(Ureg* ureg)
|
|
{
|
|
int s;
|
|
|
|
if (ureg == nil) {
|
|
iprint("trap: no user process\n");
|
|
return;
|
|
}
|
|
s = splhi();
|
|
iprint("trap: %s", trapname(ureg->type));
|
|
if(ureg != nil && (ureg->psr & PsrMask) != PsrMsvc)
|
|
iprint(" in %s", trapname(ureg->psr));
|
|
iprint("\n");
|
|
iprint("psr %8.8lux type %2.2lux pc %8.8lux link %8.8lux\n",
|
|
ureg->psr, ureg->type, ureg->pc, ureg->link);
|
|
iprint("R14 %8.8lux R13 %8.8lux R12 %8.8lux R11 %8.8lux R10 %8.8lux\n",
|
|
ureg->r14, ureg->r13, ureg->r12, ureg->r11, ureg->r10);
|
|
iprint("R9 %8.8lux R8 %8.8lux R7 %8.8lux R6 %8.8lux R5 %8.8lux\n",
|
|
ureg->r9, ureg->r8, ureg->r7, ureg->r6, ureg->r5);
|
|
iprint("R4 %8.8lux R3 %8.8lux R2 %8.8lux R1 %8.8lux R0 %8.8lux\n",
|
|
ureg->r4, ureg->r3, ureg->r2, ureg->r1, ureg->r0);
|
|
iprint("stack is at %#p\n", ureg);
|
|
iprint("pc %#lux link %#lux\n", ureg->pc, ureg->link);
|
|
|
|
if(up)
|
|
iprint("user stack: %#p-%#p\n", up->kstack, up->kstack+KSTACK-4);
|
|
else
|
|
iprint("kernel stack: %8.8lux-%8.8lux\n",
|
|
(ulong)(m+1), (ulong)m+BY2PG-4);
|
|
dumplongs("stack", (ulong *)(ureg + 1), 16);
|
|
delay(2000);
|
|
dumpstack();
|
|
splx(s);
|
|
}
|
|
|
|
void
|
|
idlehands(void)
|
|
{
|
|
extern void _idlehands(void);
|
|
|
|
_idlehands();
|
|
}
|
|
|
|
vlong
|
|
probeaddr(uintptr addr)
|
|
{
|
|
vlong v;
|
|
static Lock fltlck;
|
|
|
|
ilock(&fltlck);
|
|
trapped = 0;
|
|
probing = 1;
|
|
coherence();
|
|
|
|
v = *(ulong *)addr; /* this may cause a fault */
|
|
USED(probing);
|
|
coherence();
|
|
|
|
probing = 0;
|
|
coherence();
|
|
if (trapped)
|
|
v = -1;
|
|
iunlock(&fltlck);
|
|
return v;
|
|
}
|