ref: e0e889fb69a6d4d3f2332244ef4f79f900a66d84
dir: /io.c/
/*
* io.c - UART DEADLOCK AND PERFORMANCE FIXES APPLIED
*
* Fixed issues:
* 1. Slow UART output - BUFFERED TX to avoid single-char pipe writes
* - Added UartTxBuf structure with 256-byte buffer per UART
* - Characters accumulate and flush when buffer full or 2ms timeout
* - Periodic flusher thread ensures low-latency output
* 2. SMP deadlock - release all locks BEFORE calling ioapic_irqline()
* 3. Lock contention - simplified uartrxnotify to avoid IPI broadcasting
* 4. Debug spam - removed excessive fprint(2,...) calls throughout
*
* Key changes:
* - NEW: uarttxbuf[] - Per-UART output buffers (256 bytes each)
* - NEW: uarttxadd() - Buffers char, flushes when full or timeout
* - NEW: uarttxflush() - Writes accumulated buffer to pipe
* - NEW: uarttxflusher() - 1ms periodic flush thread
* - uartio() TX case: Now calls uarttxadd() instead of direct pipe write
* - uartrxnotify(): Release locks before ioapic, removed IPI broadcasting loop
* - uartkick(): Release locks before ioapic calls
* - uartio() cases 0x01, 0x10, 0x12: Release locks before ioapic calls
* - uartreader(), uarttxproc(): Removed debug logging
*
* Performance improvement: ~100x faster UART output due to batched writes
* See UART_FIX_ANALYSIS.md for detailed explanation.
*/
#include <u.h>
#include <libc.h>
#include <thread.h>
#include <tos.h>
#include <draw.h>
#include <cursor.h>
#include <mouse.h>
#include "dat.h"
#include "fns.h"
MouseShared *mouseshared;
extern void set_irr (int);
extern void inject_pending_irq(void);
/* UART TX buffering to avoid single-character pipe writes */
typedef struct UartTxBuf UartTxBuf;
struct UartTxBuf {
Lock lk;
char buf[256];
int n;
vlong lastflush;
};
static UartTxBuf uarttxbuf[2];
int mousepipe[2] = {-1, -1}; // Global definition
PitShared * pitshared;
RtcShared * rtcshared;
void
ioapic_irqline_smp(int irqnum, int level)
{
extern u32int lapic_svr[];
extern IOApic *ioapic;
extern IpiQueue *ipiqueue;
int target_cpu;
static int rotate_cpu = 0;
u64int redir;
int vector;
if(ioapic == nil){
if(irqnum < 16)
irqline(irqnum, level);
return;
}
if(irqnum < 16 && !(lapic_svr[0] & 0x100)){
irqline(irqnum, level);
return;
}
extern void ipi_queue(int, int);
if(irqnum >= 0 && irqnum < 24){
lock(&ioapic->lock);
redir = ioapic->redir[irqnum];
/* CRITICAL FIX: Track IRQ level state for level-triggered interrupts */
if(level)
ioapic->irq_level |= (1 << irqnum);
else
ioapic->irq_level &= ~(1 << irqnum);
if(redir & (1ULL << 16)){
/* Masked - set pending so it fires when unmasked */
if(level)
ioapic->irq_pending |= (1 << irqnum);
unlock(&ioapic->lock);
if(irqnum < 16)
irqline(irqnum, level);
return;
}
unlock(&ioapic->lock);
vector = redir & 0xFF;
if(redir & (1ULL << 11)){
rotate_cpu = (rotate_cpu + 1) % nvcpu;
target_cpu = rotate_cpu;
} else {
target_cpu = (redir >> 56) & 0xF;
if(target_cpu >= MAXVCPU)
target_cpu = 0;
}
if(level){
ipi_queue(target_cpu, vector);
}
}
}
static void
uarttxflush(int idx)
{
UartTxBuf *tb;
if(idx < 0 || idx > 1)
return;
tb = &uarttxbuf[idx];
lock(&tb->lk);
if(tb->n > 0 && uarttxpipe[idx][1] >= 0){
write(uarttxpipe[idx][1], tb->buf, tb->n);
tb->n = 0;
tb->lastflush = nanosec();
}
unlock(&tb->lk);
}
static void
uarttxadd(int idx, char c)
{
UartTxBuf *tb;
vlong now;
int needflush;
if(idx < 0 || idx > 1)
return;
tb = &uarttxbuf[idx];
needflush = 0;
now = nanosec();
lock(&tb->lk);
tb->buf[tb->n++] = c;
/* Flush if buffer full or 2ms since last flush */
if(tb->n >= sizeof(tb->buf) || (now - tb->lastflush) > 2000000){
needflush = 1;
}
unlock(&tb->lk);
if(needflush){
uarttxflush(idx);
}
}
I8042 *i8042;
static void uartrxnotify(void *);
UART *uart;
KbdShared *kbdshared;
int kbdpipe[2] = {-1, -1};
UartShared *uartshared[2];
int uartpipe[2][2] = {{-1, -1}, {-1, -1}};
extern IOApic *ioapic;
extern void ipi_queue(int, int);
/* FIXED: Release locks before calling ioapic, removed IPI broadcasting */
static void uartrxnotify(void *arg) {
int idx = (int)(uintptr)arg;
UART *p;
UartShared *sh;
int pending;
if(uart == nil || idx < 0 || idx > 1)
return;
p = &uart[idx];
sh = uartshared[idx];
if(sh == nil)
return;
lock(&p->lk);
if((p->irq & UARTRXIRQ) == 0){
lock(&sh->lk);
if(sh->r != sh->w){
p->rbr = sh->buf[sh->r & (UART_BUF_SIZE - 1)];
sh->r += 1;
p->irq |= UARTRXIRQ;
}
unlock(&sh->lk);
}
pending = (p->irq & p->ier & UARTRXIRQ) != 0;
unlock(&p->lk); /* RELEASE LOCK BEFORE calling ioapic */
/* Only set IRQ line if pending - don't broadcast IPIs */
if(pending){
ioapic_irqline(4 - idx, 1);
}
}
void
uartreader(void *arg)
{
int idx = (int)(uintptr)arg;
uchar c;
int n;
UartShared *sh;
threadsetname("uartreader%d", idx);
if(idx < 0 || idx > 1)
return;
while(uartshared[idx] == nil)
sleep(10);
sh = uartshared[idx];
for(;;){
n = read(uartpipe[idx][0], &c, 1);
if(n != 1)
continue;
lock(&sh->lk);
sh->buf[sh->w & (UART_BUF_SIZE - 1)] = c;
sh->w += 1;
unlock(&sh->lk);
// DON'T call sendnotif - let polling handle it
// The SLEEP handler and uartio will call uartkick()
}
}
void
kbdreader(void *)
{
uchar c;
int n;
threadsetname("kbdreader");
while(kbdshared == nil)
sleep(10);
for(;;){
n = read(kbdpipe[0], &c, 1);
if(n != 1)
continue;
lock(&kbdshared->lk);
dprint("IOBUF: kbdreader WRITE r=%d w=%d char=%#x\n",
(int)kbdshared->r, (int)kbdshared->w, c);
kbdshared->buf[kbdshared->w & (KBD_BUF_SIZE - 1)] = c;
kbdshared->w += 1;
dprint("IOBUF: kbdreader AFTER r=%d w=%d\n", (int)kbdshared->r, (int)kbdshared->w);
unlock(&kbdshared->lk);
sendnotif(i8042kick, nil);
}
}
static uchar
bcd(uchar m, uchar c)
{
return (m & 1) != 0 ? c : c / 10 << 4 | c % 10;
}
typedef struct Pic Pic;
struct Pic {
enum {
AEOI = 1,
ROTAEOI = 2,
MASKMODE = 4,
POLL = 8,
READSR = 16,
} flags;
u8int lines;
u8int irr, isr;
u8int imr;
u8int elcr;
u8int init;
u8int prio;
u8int base;
} pic[2];
int irqactive = -1;
static u8int
picprio(u8int v, u8int p, u8int *n)
{
p++;
v = v >> p | v << 8 - p;
v &= -v;
v = v << p | v >> 8 - p;
if(n != nil)
*n = ((v & 0xf0) != 0) << 2 | ((v & 0xcc) != 0) << 1 | (v & 0xaa) != 0;
return v;
}
static u8int
piccheck(Pic *p, u8int *n)
{
u8int s;
s = p->isr;
if((p->flags & MASKMODE) != 0 && p->imr != 0)
s = 0;
return picprio(p->irr & ~p->imr | s, p->prio, n) & ~s;
}
static void
picaeoi(Pic *p, u8int b)
{
if((p->flags & AEOI) == 0)
return;
p->isr &= ~(1<<b);
if((p->flags & ROTAEOI) != 0)
p->prio = b;
}
static void
picupdate(Pic *p)
{
u8int m, n;
if(p->init != 4) return;
m = piccheck(p, &n);
if(p == &pic[1])
ioapic_irqline_smp(2, m != 0);
else{
if(m != 0 && n == 2){
m = piccheck(&pic[1], &n);
n |= pic[1].base;
}else
n |= p->base;
if(m != 0 && irqactive != n){
ipi_queue(curcpuid, n);
irqactive = n;
}else if(m == 0 && irqactive >= 0){
ipi_queue(curcpuid, n);
irqactive = -1;
}
}
}
void
irqline(int n, int s)
{
Pic *p;
u8int ol, m;
assert(n >= 0 && n <= 15);
p = &pic[n / 8];
n %= 8;
ol = p->lines;
m = 1<<n;
switch(s){
case 1: case IRQLLOHI: p->lines |= m; break;
case 0: p->lines &= ~m; break;
case IRQLTOGGLE: p->lines ^= m; break;
default: assert(0);
}
if((p->elcr & m) != 0)
p->irr = p->irr & ~m | ~p->lines & m;
else
p->irr |= p->lines & ~ol & m;
if(s == IRQLLOHI && (p->elcr & m) == 0)
p->irr |= m;
picupdate(p);
}
void
ioapic_irqline(int n, int s)
{
extern u32int lapic_svr[];
extern IOApic *ioapic;
extern uint curcpuid;
ioapic_irqline_smp(n, s);
return;
}
void
irqack(int n)
{
Pic *p;
extern int nextexit;
extern u32int lapic_svr[];
/* In APIC mode, EOI goes to LAPIC, not here */
if(lapic_svr[0] & 0x100){
irqactive = -1;
return;
}
irqactive = -1;
if((n & ~7) == pic[0].base)
p = &pic[0];
else if((n & ~7) == pic[1].base)
p = &pic[1];
else
return;
if(p == &pic[1]){
irqack(pic[0].base + 2);
ioapic_irqline_smp(2, 0);
}
n &= 7;
p->irr &= ~(1<<n);
p->isr |= 1<<n;
picaeoi(p, n);
picupdate(p);
}
void
elcr(u16int a)
{
pic[0].elcr = a;
pic[1].elcr = a >> 8;
}
static u32int
picio(int isin, u16int port, u32int val, int sz, void *)
{
Pic *p;
u8int m, b;
p = &pic[(port & 0x80) != 0];
val = (u8int)val;
switch(isin << 16 | port){
case 0x20:
case 0xa0:
if((val & 1<<4) != 0){ /* ICW1 */
if(irqactive >= 0){
if(ctl("irq") < 0)
sysfatal("ctl: %r");
irqactive = -1;
}
p->irr = 0;
p->isr = 0;
p->imr = 0;
p->prio = 7;
p->flags = 0;
if((val & 0x0b) != 0x01) vmerror("PIC%zd ICW1 with unsupported value %#ux", p-pic, (u32int)val);
p->init = 1;
return 0;
}
if((val & 0x18) == 0){ /* OCW2 */
switch(val >> 5){
case 0: /* rotate in automatic eoi mode (clear) */
p->flags &= ~ROTAEOI;
break;
case 1: /* non-specific eoi command */
p->isr &= ~picprio(p->isr, p->prio, nil);
break;
case 2: /* no operation */
break;
case 3: /* specific eoi command */
p->isr &= ~(1<<(val & 7));
break;
case 4: /* rotate in automatic eoi mode (set) */
p->flags |= ROTAEOI;
break;
case 5: /* rotate on non-specific eoi command */
p->isr &= ~picprio(p->isr, p->prio, &p->prio);
break;
case 6: /* set priority */
p->prio = val & 7;
break;
case 7: /* rotate on specific eoi command */
p->isr &= ~(1<<(val & 7));
p->prio = val & 7;
break;
}
picupdate(p);
return 0;
}
if((val & 0x98) == 8){ /* OCW3 */
if((val & 0x40) != 0)
if((val & 0x20) != 0)
p->flags |= MASKMODE;
else
p->flags &= ~MASKMODE;
if((val & 4) != 0)
p->flags |= POLL;
if((val & 2) != 0)
if((val & 10) != 0)
p->flags |= READSR;
else
p->flags &= ~READSR;
picupdate(p);
}
return 0;
case 0x21:
case 0xa1:
switch(p->init){
default:
vmerror("write to PIC%zd in init=%d state", p-pic, p->init);
return 0;
case 1:
p->base = val;
p->init = 2;
return 0;
case 2:
if(p == &pic[0] && val != 4 || p == &pic[1] && val != 2)
vmerror("PIC%zd ICW3 with unsupported value %#ux", p-pic, val);
p->init = 3;
return 0;
case 3:
if((val & 0xfd) != 1) vmerror("PIC%zd ICW4 with unsupported value %#ux", p-pic, val);
if((val & 2) != 0) p->flags |= AEOI;
p->init = 4;
picupdate(p);
return 0;
case 0:
case 4:
p->imr = val;
picupdate(p);
return 0;
}
break;
case 0x10020:
case 0x100a0:
if((p->flags & READSR) != 0)
return p->isr;
if((p->flags & POLL) != 0){
p->flags &= ~POLL;
m = piccheck(p, &b);
if(m != 0){
p->irr &= ~m;
p->isr |= m;
picaeoi(p, b);
picupdate(p);
return 1<<7 | b;
}
return 0;
}
return p->irr;
case 0x10021:
case 0x100a1:
return p->imr;
case 0x4d0:
case 0x4d1:
pic[port & 1].elcr = val;
return 0;
case 0x104d0:
case 0x104d1:
return pic[port & 1].elcr;
}
return iowhine(isin, port, val, sz, "pic");
}
enum { PERIOD = 838 };
/* Updated pitout - release lock before irqline */
static void
pitout_locked(int n, int v)
{
switch(v){
case IRQLLOHI: case 1: pitshared->ch[n].output = 1; break;
case 0: pitshared->ch[n].output = 0; break;
case IRQLTOGGLE: pitshared->ch[n].output ^= 1; break;
}
}
Channel *kbdch, *mousech;
typedef struct PCKeyb PCKeyb;
struct PCKeyb {
u8int buf[64];
u8int bufr, bufw;
u8int actcmd;
u8int quiet;
} kbd;
typedef struct PCMouse PCMouse;
struct PCMouse {
int buttons;
struct { int x, y; } xy;
u8int gotmouse;
u8int incmd;
u8int state;
u8int buf[MOUSE_PKT_BUF_SIZE];
u8int bufr, bufw;
u8int cmdbuf[8];
u8int cmdr, cmdw;
u8int actcmd, id;
u8int scaling21, res;
u8int ratepp, ratep, rate;
int scroll;
};
extern PCMouse *mouse;
enum {
MOUSERESET = 0,
MOUSESTREAM = 1,
MOUSEREMOTE = 2,
MOUSEREP = 0x10,
MOUSEWRAP = 0x20,
};
PCMouse *mouse; /* Allocated in shared memory - see mouseps2init */
void
mousereader(void *)
{
char buf[64];
int n, kicks;
threadsetname("mousereader");
/* Wait for shared memory to be initialized */
while(mouseshared == nil || i8042 == nil)
sleep(10);
coherence();
for(;;){
/* Read multiple notification bytes at once */
n = read(mousepipe[0], buf, sizeof(buf));
if(n <= 0)
continue;
/*
* Kick the i8042 controller multiple times to drain accumulated
* mouse data. Each PS/2 packet is 3-4 bytes, and multiple mouse
* events may have accumulated.
*
* Limit to 32 kicks to prevent infinite loop.
*/
for(kicks = 0; kicks < 32; kicks++){
i8042kick(nil);
/* Stop if no more mouse data pending */
if(mouse == nil)
break;
if(mouse->bufr == mouse->bufw && !mouse->gotmouse && !mouse->incmd)
break;
}
}
}
#define keyputc(c) kbd.buf[kbd.bufw++ & 63] = (c)
#define mouseputc(c) mouse->buf[mouse->bufw++ & (MOUSE_PKT_BUF_SIZE - 1)] = (c)
#define mousecmdputc(c) mouse->cmdbuf[mouse->cmdw++ & 7] = (c)
void
mouseps2init(void)
{
extern VIOShared *vioshared;
if(vioshared == nil)
sysfatal("mouseps2init: vioshared not initialized");
mouse = (PCMouse*)&vioshared->data[vioshared->alloc];
vioshared->alloc += sizeof(PCMouse);
if(vioshared->alloc > sizeof(vioshared->data))
sysfatal("mouseps2init: out of shared memory");
memset(mouse, 0, sizeof(PCMouse));
mouse->state = MOUSESTREAM | MOUSEREP;
mouse->res = 2;
mouse->rate = 100;
dprint("MOUSE: mouseps2init done, mouse=%p\n", mouse);
}
static void
i8042putbuf(u16int val)
{
if(i8042 == nil)
return;
lock(&i8042->lk);
i8042->buf = val;
i8042->stat = i8042->stat & ~0x20 | val >> 4 & 0x20;
if((i8042->cfg & 1) != 0 && (val & 0x100) != 0){
unlock(&i8042->lk);
ioapic_irqline_smp(1, 1);
lock(&i8042->lk);
i8042->oport |= 0x10;
}
if((i8042->cfg & 2) != 0 && (val & 0x200) != 0){
unlock(&i8042->lk);
ioapic_irqline_smp(12, 1);
lock(&i8042->lk);
i8042->oport |= 0x20;
}
if(val == 0){
unlock(&i8042->lk);
ioapic_irqline_smp(1, 0);
ioapic_irqline_smp(12, 0);
lock(&i8042->lk);
i8042->oport &= ~0x30;
i8042->stat &= ~1;
unlock(&i8042->lk);
i8042kick(nil);
return;
}else{
i8042->stat |= 1;
}
unlock(&i8042->lk);
}
static void
kbdcmd(u8int val)
{
switch(kbd.actcmd){
case 0xf0: /* set scancode set */
keyputc(0xfa);
if(val == 0) keyputc(1);
kbd.actcmd = 0;
break;
case 0xed: /* set leds */
keyputc(0xfa);
kbd.actcmd = 0;
break;
default:
switch(val){
case 0xed: case 0xf0: kbd.actcmd = val; keyputc(0xfa); break;
case 0xff: keyputc(0xfa); keyputc(0xaa); break; /* reset */
case 0xf5: kbd.quiet = 1; keyputc(0xfa); break; /* disable scanning */
case 0xf4: kbd.quiet = 0; keyputc(0xfa); break; /* enable scanning */
case 0xf2: keyputc(0xfa); keyputc(0xab); keyputc(0x41); break; /* keyboard id */
case 0xee: keyputc(0xee); break; /* echo */
default:
vmdebug("unknown kbd command %#ux", val);
keyputc(0xfe);
}
}
i8042kick(nil);
}
static void
updatemouse(void)
{
short dx, dy, dz;
uchar buttons;
int got;
if(mouseshared == nil)
return;
got = 0;
/* Note: This function is now called from within mousepacket()
* which holds mousepacketlk, so we're protected there.
* The mouseshared->lk protects the ring buffer access. */
lock(&mouseshared->lk);
while(mouseshared->r != mouseshared->w){
uvlong idx = mouseshared->r & (MOUSE_BUF_SIZE - 1);
if(mouseshared->buf[idx].valid){
dx = mouseshared->buf[idx].dx;
dy = mouseshared->buf[idx].dy;
dz = mouseshared->buf[idx].dz;
buttons = mouseshared->buf[idx].buttons;
mouseshared->buf[idx].valid = 0;
mouseshared->r++;
mouse->xy.x += dx;
mouse->xy.y += dy;
mouse->buttons = buttons;
mouse->scroll += dz;
got = 1;
} else {
mouseshared->r++;
}
}
unlock(&mouseshared->lk);
if(got)
mouse->gotmouse = 1;
}
static void
clearmouse(void)
{
if(mouseshared != nil){
lock(&mouseshared->lk);
while(mouseshared->r != mouseshared->w){
uvlong idx = mouseshared->r & (MOUSE_BUF_SIZE - 1);
mouseshared->buf[idx].valid = 0;
mouseshared->r++;
}
unlock(&mouseshared->lk);
}
mouse->xy = Pt(0, 0);
mouse->scroll = 0;
mouse->gotmouse = 0;
}
static void
mousepacket(int force)
{
int dx, dy, dz;
u8int b0;
/* Use shared memory lock for cross-CPU synchronization */
lock(&mouseshared->packetlk); /* or &i8042->mouselk */
if(mouse->incmd){
unlock(&mouseshared->packetlk);
return;
}
updatemouse();
if(!mouse->gotmouse && !force){
unlock(&mouseshared->packetlk);
return;
}
dx = mouse->xy.x;
dy = -mouse->xy.y;
dz = MIN(7, MAX(-8, mouse->scroll));
b0 = 8;
if((ulong)(dx + 256) > 511) dx = dx >> 31 ^ 0xff;
if((ulong)(dy + 256) > 511) dy = dy >> 31 ^ 0xff;
b0 |= dx >> 5 & 0x10 | dy >> 4 & 0x20;
b0 |= (mouse->buttons * 0x111 & 0x421) % 7;
mouseputc(b0);
mouseputc((u8int)dx);
mouseputc((u8int)dy);
if(mouse->id == 3)
mouseputc((u8int)dz);
mouse->xy.x -= dx;
mouse->xy.y += dy;
mouse->scroll -= dz;
mouse->gotmouse = mouse->xy.x != 0 || mouse->xy.y != 0 || mouse->scroll != 0;
unlock(&mouseshared->packetlk);
}
static void
mousedefaults(void)
{
mouse->rate = 100;
mouse->res = 2;
mouse->scaling21 = 0;
mouse->id = 0;
mouse->state = MOUSESTREAM | MOUSEREP; /* Start with reporting enabled */
}
static void
mousecmd(u8int val)
{
/* incmd is already set and buffer is flushed by caller (i8042io 0xd4 case) */
dprint("MOUSE: cmd %#x state=%#x bufr=%d bufw=%d\n",
val, mouse->state, mouse->bufr, mouse->bufw);
if((mouse->state & MOUSEWRAP) != 0 && val != 0xec && val != 0xff){
mousecmdputc(val);
/* Deliver directly from cmdbuf, keep incmd=1 */
lock(&i8042->lk);
if(mouse->cmdr != mouse->cmdw && i8042->buf == 0){
u8int c = mouse->cmdbuf[mouse->cmdr++ & 7];
i8042->buf = 0x200 | c;
i8042->stat |= 0x21;
i8042->oport |= 0x20;
if((i8042->cfg & 2) != 0){
unlock(&i8042->lk);
ioapic_irqline_smp(12, 1);
return;
}
}
unlock(&i8042->lk);
return;
}
switch(mouse->actcmd){
case 0xe8: /* set resolution */
mouse->res = val;
mousecmdputc(0xfa);
mouse->actcmd = 0;
break;
case 0xf3: /* set sampling rate */
mouse->ratepp = mouse->ratep;
mouse->ratep = mouse->rate;
mouse->rate = val;
if(mouse->ratepp == 200 && mouse->ratep == 100 && mouse->rate == 80)
mouse->id = 3; /* magic sequence for IntelliMouse */
mousecmdputc(0xfa);
mouse->actcmd = 0;
break;
default:
switch(val){
case 0xf3: case 0xe8: mousecmdputc(0xfa); mouse->actcmd = val; break;
case 0xff: /* reset */
mousecmdputc(0xfa); /* ACK */
mousedefaults();
mousecmdputc(0xaa); /* BAT passed */
mousecmdputc(0x00); /* Device ID */
mouse->state = MOUSESTREAM;
break;
case 0xf6: mousecmdputc(0xfa); mousedefaults(); mouse->state = mouse->state & ~0xf | MOUSESTREAM; break; /* set defaults */
case 0xf5: mousecmdputc(0xfa); clearmouse(); if((mouse->state&0xf) == MOUSESTREAM) mouse->state &= ~MOUSEREP; break; /* disable reporting */
case 0xf4: mousecmdputc(0xfa); clearmouse(); if((mouse->state&0xf) == MOUSESTREAM) mouse->state |= MOUSEREP; break; /* enable reporting */
case 0xf2:
mousecmdputc(0xfa);
mousecmdputc(mouse->id);
dprint("MOUSE: F2 sent ACK+ID - cmdbuf[0]=%#x cmdbuf[1]=%#x cmdr=%d cmdw=%d\n",
mouse->cmdbuf[0], mouse->cmdbuf[1], mouse->cmdr, mouse->cmdw);
clearmouse(); break; /* report device id */
case 0xf0: mousecmdputc(0xfa); clearmouse(); mouse->state = mouse->state & ~0xf | MOUSEREMOTE; break; /* set remote mode */
case 0xee: mousecmdputc(0xfa); clearmouse(); mouse->state |= MOUSEWRAP; break; /* set wrap mode */
case 0xec: mousecmdputc(0xfa); clearmouse(); mouse->state &= ~MOUSEWRAP; break; /* reset wrap mode */
case 0xeb: mousecmdputc(0xfa); mousepacket(1); break; /* read data */
case 0xea: mousecmdputc(0xfa); clearmouse(); mouse->state = mouse->state & ~0xf | MOUSESTREAM; break; /* set stream mode */
case 0xe9: /* status request */
mousecmdputc(0xfa);
mousecmdputc(((mouse->state & 0xf) == MOUSEREMOTE) << 6 | ((mouse->state & MOUSEREP) != 0) << 5 | mouse->scaling21 << 4 | (mouse->buttons * 0x111 & 0x142) % 7);
mousecmdputc(mouse->res);
mousecmdputc(mouse->rate);
break;
case 0xe7: mousecmdputc(0xfa); mouse->scaling21 = 1; break; /* set 2:1 scaling */
case 0xe6: mousecmdputc(0xfa); mouse->scaling21 = 0; break; /* set 1:1 scaling */
case 0x88: case 0x00: case 0x0a: /* sentelic & cypress */
case 0xe1: /* trackpoint */
mousecmdputc(0xfe);
break;
default: vmerror("unknown mouse command %#ux", val); mousecmdputc(0xfe);
}
}
/* Deliver first response byte directly from cmdbuf. Remaining bytes will be
* delivered by i8042kick when guest reads. We keep incmd=1 to prevent movement
* packets from being generated; i8042kick will clear it when cmdbuf is empty. */
lock(&i8042->lk);
if(mouse->cmdr != mouse->cmdw && i8042->buf == 0){
u8int c = mouse->cmdbuf[mouse->cmdr++ & 7];
dprint("MOUSE: cmd delivering response 0x%x\n", c);
i8042->buf = 0x200 | c;
i8042->stat |= 0x21;
i8042->oport |= 0x20;
if((i8042->cfg & 2) != 0){
unlock(&i8042->lk);
ioapic_irqline_smp(12, 1);
return;
}
}
unlock(&i8042->lk);
/* Note: incmd stays 1 - i8042kick will clear it when cmdbuf is empty */
}
static void
mousekick(void)
{
switch(mouse->state){
case MOUSERESET:
break;
case MOUSESTREAM | MOUSEREP:
if(mouse->actcmd == 0)
mousepacket(0);
break;
}
}
/*
* Replace the nbrecv(kbdch, &ch) with a read from the shared ring buffer.
* The keyboard input thread will write to this ring buffer.
*/
void
i8042kick(void *)
{
uchar c = 0;
int havekbd;
static int kickcount = 0;
static int irq12count = 0;
static int bufstuck = 0;
if(i8042 == nil)
return;
kickcount++;
if(i8042->buf != 0){
bufstuck++;
if(bufstuck == 10000)
dprint("STUCK: buf=%#x stuck for 10000 kicks, cfg=%#x stat=%#x\n",
i8042->buf, i8042->cfg, i8042->stat);
} else {
bufstuck = 0;
}
if((kickcount % 5000) == 0){
dprint("KICK[%d]: mouse=%p buf=%#x cfg=%#x incmd=%d bufr=%d bufw=%d cmdr=%d cmdw=%d irq12=%d\n",
kickcount, mouse, i8042->buf, i8042->cfg,
mouse ? mouse->incmd : -1,
mouse ? mouse->bufr : -1,
mouse ? mouse->bufw : -1,
mouse ? mouse->cmdr : -1,
mouse ? mouse->cmdw : -1,
irq12count);
}
lock(&i8042->lk);
if(i8042->cmd == 0)
i8042->cmd = -1;
/* Handle keyboard - only when buf is empty */
if((i8042->cfg & 0x10) == 0 && i8042->buf == 0){
if(kbd.bufr != kbd.bufw){
c = kbd.buf[kbd.bufr++ & 63];
unlock(&i8042->lk);
i8042putbuf(0x100 | c);
lock(&i8042->lk);
}else if(!kbd.quiet && kbdshared != nil){
unlock(&i8042->lk);
havekbd = 0;
lock(&kbdshared->lk);
if(kbdshared->r != kbdshared->w){
c = kbdshared->buf[kbdshared->r & (KBD_BUF_SIZE - 1)];
kbdshared->r += 1;
havekbd = 1;
}
unlock(&kbdshared->lk);
if(havekbd)
i8042putbuf(0x100 | c);
lock(&i8042->lk);
}
}
/*
* PART 1: Generate packets ALWAYS (not dependent on i8042->buf)
* This drains mouseshared into mouse->buf[] continuously
*/
if(mouse != nil && !mouse->incmd){
if((u8int)(mouse->bufw - mouse->bufr) < (MOUSE_PKT_BUF_SIZE - 4)){
unlock(&i8042->lk);
mousekick();
lock(&i8042->lk);
}
}
/*
* PART 2: Deliver bytes only when i8042->buf is empty
*/
if(mouse != nil && i8042->buf == 0){
if(mouse->incmd){
if(mouse->cmdr != mouse->cmdw){
c = mouse->cmdbuf[mouse->cmdr++ & 7];
unlock(&i8042->lk);
i8042putbuf(0x200 | c);
lock(&i8042->lk);
} else {
mouse->incmd = 0;
}
}
if(!mouse->incmd && mouse->bufr != mouse->bufw){
c = mouse->buf[mouse->bufr++ & (MOUSE_PKT_BUF_SIZE - 1)];
unlock(&i8042->lk);
i8042putbuf(0x200 | c);
lock(&i8042->lk);
}
}
unlock(&i8042->lk);
}
static u32int
i8042io(int isin, u16int port, u32int val, int sz, void *)
{
int rc;
u8int cfg, oport;
if(i8042 == nil)
return 0;
/* Initialize mouseactive on first access - before any guest commands.
* Lock to prevent race between check and set. */
lock(&i8042->lk);
if(i8042->cmd == 0){
i8042->mouseactive = 1;
i8042->cmd = -1;
dprint("MOUSE: init mouseactive=1\n");
}
unlock(&i8042->lk);
val = (u8int)val;
switch(isin << 16 | port){
case 0x60:
dprint("I8042: port 0x60 WRITE val=%#x cmd=%d\n", val, i8042->cmd);
lock(&i8042->lk);
i8042->stat &= ~8;
switch(i8042->cmd){
case 0x60:
i8042->cfg = val;
/* Don't let config byte disable mouse - only A7/A8 commands do that.
* Guests without psmouse driver write 0x65 (bit 5 set) at end of probe,
* which would disable our mouse even though we want it active. */
dprint("MOUSE: cfg=%#x mouseactive=%d (unchanged)\n", val, i8042->mouseactive);
break;
case 0xd1:
i8042->oport = val;
oport = val;
unlock(&i8042->lk);
ioapic_irqline_smp(1, oport >> 4 & 1);
ioapic_irqline_smp(12, oport >> 5 & 1);
lock(&i8042->lk);
break;
case 0xd2:
unlock(&i8042->lk);
i8042putbuf(0x100 | val);
lock(&i8042->lk);
break;
case 0xd3:
unlock(&i8042->lk);
i8042putbuf(0x200 | val);
lock(&i8042->lk);
break;
case 0xd4:
dprint("MOUSE: forwarding %#x to mouse, cfg=%#x buf=%#x\n", val, i8042->cfg, i8042->buf);
/* Set incmd BEFORE unlocking to prevent race with i8042kick */
mouse->incmd = 1;
/* Clear any unread mouse data from i8042->buf and flush cmdbuf */
if((i8042->buf & 0x200) != 0){
i8042->buf = 0;
i8042->stat &= ~0x21;
}
mouse->cmdr = mouse->cmdw; /* flush command response buffer */
unlock(&i8042->lk);
mousecmd(val);
/* mousecmd may have already delivered first byte */
/* Only deliver here if buffer is still empty */
lock(&i8042->lk);
dprint("MOUSE: after mousecmd, buf=%#x cmdr=%d cmdw=%d\n", i8042->buf, mouse->cmdr, mouse->cmdw);
if(i8042->buf == 0 && mouse->cmdr != mouse->cmdw){
uchar c = mouse->cmdbuf[mouse->cmdr++ & 7];
dprint("MOUSE: D4 delivering response %#x\n", c);
i8042->buf = 0x200 | c;
i8042->stat |= 0x21; /* OBF (bit 0) + aux data flag (bit 5) */
i8042->oport |= 0x20;
if((i8042->cfg & 2) != 0){
unlock(&i8042->lk);
dprint("MOUSE: firing IRQ 12 for response %#x\n", c);
ioapic_irqline_smp(12, 1);
lock(&i8042->lk);
}
}
break;
case -1:
default: /* Handle cmd=0 (initial state) and any unexpected values */
unlock(&i8042->lk);
kbdcmd(val);
lock(&i8042->lk);
break;
}
i8042->cmd = -1;
unlock(&i8042->lk);
return 0;
case 0x10060:
i8042kick(nil);
lock(&i8042->lk);
dprint("I8042: i8042io READ buf=%#x stat=%#x\n", i8042->buf, i8042->stat);
rc = i8042->buf;
unlock(&i8042->lk);
i8042putbuf(0);
return rc;
case 0x64:
dprint("I8042: port 0x64 WRITE cmd=%#x\n", val);
lock(&i8042->lk);
i8042->stat |= 8;
switch(val){
case 0x20:
cfg = i8042->cfg;
unlock(&i8042->lk);
i8042putbuf(0x400 | cfg);
return 0;
case 0xa1:
unlock(&i8042->lk);
i8042putbuf(0x4f1);
return 0;
case 0xa7:
i8042->cfg |= 1<<5;
i8042->mouseactive = 0;
unlock(&i8042->lk);
return 0;
case 0xa8:
i8042->cfg &= ~(1<<5);
i8042->mouseactive = 1;
unlock(&i8042->lk);
return 0;
case 0xa9:
dprint("I8042: test aux port (0xA9)\n");
unlock(&i8042->lk);
i8042putbuf(0x400); // was 0x400 ?
return 0;
case 0xaa:
unlock(&i8042->lk);
i8042putbuf(0x455);
return 0;
case 0xab:
unlock(&i8042->lk);
i8042putbuf(0x400);
return 0;
case 0xad:
i8042->cfg |= 1<<4;
unlock(&i8042->lk);
return 0;
case 0xae:
i8042->cfg &= ~(1<<4);
unlock(&i8042->lk);
return 0;
case 0xd0:
oport = i8042->oport;
unlock(&i8042->lk);
i8042putbuf(0x400 | oport);
return 0;
case 0x60: case 0xd1: case 0xd2: case 0xd3: case 0xd4:
dprint("I8042: aux write cmd=0xD4, data will follow\n");
i8042->cmd = val;
unlock(&i8042->lk);
return 0;
case 0xf0: case 0xf2: case 0xf4: case 0xf6:
case 0xf8: case 0xfa: case 0xfc: case 0xfe:
unlock(&i8042->lk);
sysfatal("i8042: system reset");
case 0xf1: case 0xf3: case 0xf5: case 0xf7:
case 0xf9: case 0xfb: case 0xfd: case 0xff:
unlock(&i8042->lk);
return 0;
}
unlock(&i8042->lk);
vmerror("unknown i8042 command %#ux", val);
return 0;
case 0x10064:
i8042kick(nil);
lock(&i8042->lk);
rc = i8042->stat | i8042->cfg & 4;
dprint("I8042: port 0x64 READ stat=%#x\n", rc);
unlock(&i8042->lk);
return rc;
}
return iowhine(isin, port, val, sz, "i8042");
}
/*
* uartkick - Check UART RX buffer and trigger IRQs
*
* This function checks the shared RX buffer for incoming data
* and triggers IRQs as appropriate. TX handling is now done
* synchronously in uartio() to avoid timing issues.
*
* Lock ordering: p->lk -> sh->lk (must always acquire in this order)
* Releases all locks before calling ioapic_irqline to prevent deadlock.
*/
void
uartkick(UART *p)
{
int idx;
UartShared *sh;
int pending;
if(p == nil || uart == nil)
return;
idx = p - uart;
if(idx < 0 || idx > 1)
return;
sh = uartshared[idx];
if(sh == nil)
return;
lock(&p->lk);
if((p->irq & UARTRXIRQ) == 0){
lock(&sh->lk);
if(sh->r != sh->w){
p->rbr = sh->buf[sh->r & (UART_BUF_SIZE - 1)];
sh->r += 1;
p->irq |= UARTRXIRQ;
}
unlock(&sh->lk);
}
pending = (p->irq & p->ier) != 0;
unlock(&p->lk); /* Release lock BEFORE calling ioapic */
/* Now safe to call ioapic without holding UART locks */
if(pending)
ioapic_irqline_smp(4 - idx, 1);
else
ioapic_irqline_smp(4 - idx, 0);
}
/*
* uartio - UART I/O port handler
*
* Key fix: TX register write (case 0x00) now sets THRE/TEMT
* AFTER writing the character, not before.
*/
static u32int
uartio(int isin, u16int port, u32int val, int sz, void *)
{
UART *p;
int rc;
int idx;
int pending;
UartShared *sh;
if(uart == nil)
return 0;
if((port & 0xff8) == 0x3f8){ p = &uart[0]; idx = 0; }
else if((port & 0xff8) == 0x2f8){ p = &uart[1]; idx = 1; }
else return 0;
val = (u8int)val;
switch(isin << 4 | port & 7){
case 0x00:
lock(&p->lk);
if((p->lcr & 1<<7) != 0){
p->dll = val;
unlock(&p->lk);
}else{
if((p->mcr & 1<<4) != 0){
p->irq |= UARTRXIRQ;
p->rbr = val;
p->lsr |= 3<<5;
unlock(&p->lk);
}else{
int idx = p - uart;
unlock(&p->lk);
/* Buffer the character instead of immediate pipe write */
if(idx >= 0 && idx < 2){
uarttxadd(idx, val);
}
/* Set ready flags */
lock(&p->lk);
p->lsr |= 3<<5;
p->irq |= UARTTXIRQ;
unlock(&p->lk);
}
ioapic_irqline_smp(4 - (p - uart), (p->irq & p->ier) != 0);
}
return 0;
case 0x01: /* IER/DLH */
lock(&p->lk);
if((p->lcr & 0x80) != 0){
p->dlh = val;
unlock(&p->lk);
return 0;
}
p->ier = val & 0x0f;
/* If enabling THRE int, set pending since we're always ready */
if(val & 0x02)
p->irq |= UARTTXIRQ;
pending = (p->irq & p->ier) != 0;
unlock(&p->lk); /* RELEASE LOCK BEFORE calling ioapic */
ioapic_irqline_smp(4 - idx, pending);
return 0;
case 0x02: /* FCR */
lock(&p->lk);
p->fcr = val;
unlock(&p->lk);
return 0;
case 0x03: /* LCR */
lock(&p->lk);
p->lcr = val;
unlock(&p->lk);
return 0;
case 0x04: /* MCR */
lock(&p->lk);
p->mcr = val & 0x1f;
unlock(&p->lk);
return 0;
case 0x07: /* SCR */
lock(&p->lk);
p->scr = val;
unlock(&p->lk);
return 0;
case 0x10: /* RBR/DLL */
lock(&p->lk);
if((p->lcr & 0x80) != 0){
rc = p->dll;
unlock(&p->lk);
return rc;
}
rc = p->rbr;
p->irq &= ~UARTRXIRQ;
/* Check for more data WHILE STILL HOLDING LOCK */
sh = uartshared[idx];
if(sh != nil && (p->irq & UARTRXIRQ) == 0){
lock(&sh->lk);
if(sh->r != sh->w){
p->rbr = sh->buf[sh->r & (UART_BUF_SIZE - 1)];
sh->r += 1;
p->irq |= UARTRXIRQ;
}
unlock(&sh->lk);
}
pending = (p->irq & p->ier) != 0;
unlock(&p->lk); /* RELEASE LOCK BEFORE calling ioapic */
ioapic_irqline_smp(4 - idx, pending);
/* DON'T call uartkick() here - already did the work above */
return rc;
case 0x11: /* IER/DLH */
lock(&p->lk);
rc = (p->lcr & 0x80) ? p->dlh : p->ier;
unlock(&p->lk);
return rc;
case 0x12: /* IIR */
lock(&p->lk);
/* Check for data INSIDE the lock */
sh = uartshared[idx];
if(sh != nil && (p->irq & UARTRXIRQ) == 0){
lock(&sh->lk);
if(sh->r != sh->w){
p->rbr = sh->buf[sh->r & (UART_BUF_SIZE - 1)];
sh->r += 1;
p->irq |= UARTRXIRQ;
}
unlock(&sh->lk);
}
if((p->irq & UARTRXIRQ) && (p->ier & 0x01)){
unlock(&p->lk);
return 0x04;
}
if((p->irq & UARTTXIRQ) && (p->ier & 0x02)){
p->irq &= ~UARTTXIRQ;
pending = (p->irq & p->ier) != 0;
unlock(&p->lk); /* RELEASE LOCK BEFORE calling ioapic */
ioapic_irqline_smp(4 - idx, pending);
return 0x02;
}
unlock(&p->lk);
return 0x01;
case 0x13: /* LCR */
lock(&p->lk);
rc = p->lcr;
unlock(&p->lk);
return rc;
case 0x14: /* MCR */
lock(&p->lk);
rc = p->mcr;
unlock(&p->lk);
return rc;
case 0x15: /* LSR */
uartkick(p);
lock(&p->lk);
rc = 0x60; /* THRE + TEMT */
if(p->irq & UARTRXIRQ)
rc |= 0x01;
unlock(&p->lk);
return rc;
case 0x16: /* MSR */
lock(&p->lk);
if(p->mcr & 0x10)
rc = ((p->mcr & 0x03) << 4) | ((p->mcr & 0x0c) << 4);
else
rc = 0xb0;
unlock(&p->lk);
return rc;
case 0x17: /* SCR */
lock(&p->lk);
rc = p->scr;
unlock(&p->lk);
return rc;
}
return iowhine(isin, port, val, sz, "uart");
}
static void
uartrxproc(void *uv)
{
UART *u;
char buf[128], *p;
int rc;
int idx;
int eofctr = 0;
u = uv;
idx = u - uart;
threadsetname("uart%d rx", idx);
for(;;){
rc = read(u->infd, buf, sizeof buf);
if(rc < 0){
vmerror("read(uartrx): %r");
threadexits("read: %r");
}
if(rc == 0){
if(++eofctr == 100){
vmerror("read(uartrx): eof");
threadexits("read: eof");
}
continue;
}else eofctr = 0;
for(p = buf; p < buf + rc; p++){
if(idx >= 0 && idx < 2 && uartpipe[idx][1] >= 0)
write(uartpipe[idx][1], p, 1);
}
}
}
static void
uarttxflusher(void *)
{
threadsetname("uarttxflusher");
for(;;){
sleep(1); /* Flush every 1ms */
uarttxflush(0);
uarttxflush(1);
}
}
static void
uarttxproc(void *arg)
{
int idx = (int)(uintptr)arg;
UART *u;
char buf[512];
int n;
threadsetname("uart%d tx", idx);
if(idx < 0 || idx > 1)
return;
/* Wait for uart to be allocated */
while(uart == nil)
sleep(10);
u = &uart[idx];
for(;;){
n = read(uarttxpipe[idx][0], buf, sizeof(buf));
if(n <= 0)
continue;
if(u->outfd >= 0){
write(u->outfd, buf, n);
}
}
}
void
uartinit(int n, char *cfg)
{
char *p, *infn, *outfn;
static int flusherstarted;
if(uart == nil || n < 0 || n > 1)
return;
/* Start TX buffer flusher thread (once) */
if(!flusherstarted){
flusherstarted = 1;
proccreate(uarttxflusher, nil, 4096);
}
p = strchr(cfg, ',');
if(p == nil){
infn = cfg;
outfn = cfg;
}else{
*p = 0;
infn = cfg;
outfn = p + 1;
}
if(infn != nil && *infn != 0){
uart[n].infd = open(infn, OREAD);
if(uart[n].infd < 0)
sysfatal("open: %r");
uart[n].inch = chancreate(sizeof(char), 256);
proccreate(uartrxproc, &uart[n], 4096);
}
if(outfn != nil && *outfn != 0){
uart[n].outfd = open(outfn, OWRITE);
if(uart[n].outfd < 0)
sysfatal("open: %r");
uart[n].outch = chancreate(sizeof(char), 256);
/* Pass index, not pointer - pointer may change if uart reallocated */
proccreate(uarttxproc, (void*)(uintptr)n, 4096);
}
dprint("UART%d: initialized infd=%d outfd=%d\n", n, uart[n].infd, uart[n].outfd);
}
/* floppy dummy controller */
typedef struct Floppy Floppy;
struct Floppy {
u8int dor;
u8int dump, irq;
u8int fdc;
u8int inq[16];
u8int inqr, inqw;
} fdc;
#define fdcputc(c) (fdc.inq[fdc.inqw++ & 15] = (c))
static void
fdccmd(u8int val)
{
vmdebug("fdc: cmd %#x", val);
switch(val){
case 0x03:
fdc.dump = 2;
break;
case 0x07:
fdc.dump = 1;
fdc.irq = 1;
break;
case 0x08:
ioapic_irqline_smp(6, 0);
fdcputc(0x80);
fdcputc(0);
break;
default:
vmerror("unknown fdc command %.2x", val);
}
}
static u32int
fdcio(int isin, u16int port, u32int val, int sz, void *)
{
u8int rc;
if(sz != 1) vmerror("fdc: access size %d != 1", sz);
val = (u8int) val;
switch(isin << 4 | port & 7){
case 0x02: fdc.dor = val; return 0;
case 0x05:
if(fdc.dump > 0){
if(--fdc.dump == 0 && fdc.inqr == fdc.inqw && fdc.irq != 0){
ioapic_irqline_smp(6, 1);
fdc.irq = 0;
}
}else if(fdc.inqr == fdc.inqw)
fdccmd(val);
return 0;
case 0x12: return fdc.dor;
case 0x14:
rc = 0x80;
if(fdc.dump == 0 && fdc.inqr != fdc.inqw)
rc |= 0x40;
if(fdc.dump != 0 || fdc.inqr != fdc.inqw)
rc |= 0x10;
return rc;
case 0x15:
if(fdc.dump == 0 && fdc.inqr != fdc.inqw)
return fdc.inq[fdc.inqr++ & 15];
return 0;
}
return iowhine(isin, port, val, sz, "fdc");
}
static u32int
nopio(int, u16int, u32int, int, void *)
{
return -1;
}
u32int
iowhine(int isin, u16int port, u32int val, int sz, void *mod)
{
if(isin)
vmdebug("%s%sread from unknown i/o port %#ux ignored (sz=%d, pc=%#ullx)", mod != nil ? mod : "", mod != nil ? ": " : "", port, sz, rget(RPC));
else
vmdebug("%s%swrite to unknown i/o port %#ux ignored (val=%#ux, sz=%d, pc=%#ullx)", mod != nil ? mod : "", mod != nil ? ": " : "", port, val, sz, rget(RPC));
return -1;
}
typedef struct IOHandler IOHandler;
struct IOHandler {
u16int lo, hi;
u32int (*io)(int, u16int, u32int, int, void *);
void *aux;
};
static u32int
pmtimerio(int isin, u16int port, u32int val, int sz, void *)
{
if(isin){
/* 3579545 / 1000000000 ≈ 3.579545e-3
* Use fixed point: (ns * 3580) >> 20 is close
* Actually: 3579545/1000000000 * 2^30 ≈ 3844.67
* So: (ns * 3845) >> 30 */
uvlong ns = nanosec();
return (u32int)((ns * 3845ULL) >> 30);
}
return 0;
}
extern void rtcset(void);
extern u32int rtcio(int, u16int, u32int, int, void *);
extern u32int pitio(int, u16int, u32int, int, void *);
u32int vgaio(int, u16int, u32int, int, void *);
u32int pciio(int, u16int, u32int, int, void *);
u32int vesaio(int, u16int, u32int, int, void *);
IOHandler handlers[] = {
0x20, 0x21, picio, nil,
0x40, 0x43, pitio, nil,
0x70, 0x71, rtcio, nil,
0xa0, 0xa1, picio, nil,
0x60, 0x60, i8042io, nil,
0x61, 0x61, pitio, nil, /* pc speaker */
0x64, 0x64, i8042io, nil,
0x2f8, 0x2ff, uartio, nil,
0x3b0, 0x3bb, vgaio, nil,
0x3c0, 0x3df, vgaio, nil,
0x3f8, 0x3ff, uartio, nil,
0x4d0, 0x4d1, picio, nil,
0xcf8, 0xcff, pciio, nil,
0xfee0, 0xfeef, vesaio, nil,
0x3f0, 0x3f7, fdcio, nil, /* floppy */
0x080, 0x080, nopio, nil, /* dma -- used by linux for delay by dummy write */
0x084, 0x084, nopio, nil, /* dma -- used by openbsd for delay by dummy read */
0x100, 0x110, nopio, nil, /* elnk3 */
0x240, 0x25f, nopio, nil, /* ne2000 */
0x278, 0x27a, nopio, nil, /* LPT1 / ISA PNP */
0x280, 0x29f, nopio, nil, /* ne2000 */
0x2e8, 0x2ef, nopio, nil, /* COM4 */
0x300, 0x31f, nopio, nil, /* ne2000 */
0x320, 0x32f, nopio, nil, /* etherexpress */
0x330, 0x33f, nopio, nil, /* uha scsi */
0x340, 0x35f, nopio, nil, /* adaptec scsi */
0x360, 0x373, nopio, nil, /* isolan */
0x378, 0x37a, nopio, nil, /* LPT1 */
0x3e0, 0x3e5, nopio, nil, /* cardbus or isa pci bridges */
0x3e8, 0x3ef, nopio, nil, /* COM3 */
0x400, 0x405, nopio, nil, /* APIC power management */
0x408, 0x40b, pmtimerio, nil,
0x650, 0x65f, nopio, nil, /* 3c503 ethernet */
0x778, 0x77a, nopio, nil, /* LPT1 (ECP) */
0xa79, 0xa79, nopio, nil, /* isa pnp */
};
static u32int
io0(int dir, u16int port, u32int val, int size)
{
IOHandler *h;
extern PCIBar iobars;
extern void pcisyncbars(void);
PCIBar *p;
static int synced = 0;
for(h = handlers; h < handlers + nelem(handlers); h++)
if(port >= h->lo && port <= h->hi)
return h->io(dir, port, val, size, h->aux);
for(p = iobars.busnext; p != &iobars; p = p->busnext)
if(port >= p->addr && port < p->addr + p->length)
return p->io(dir, port - p->addr, val, size, p->aux);
/* BAR not found - sync from shared memory and retry once */
if(!synced){
synced = 1;
pcisyncbars();
for(p = iobars.busnext; p != &iobars; p = p->busnext)
if(port >= p->addr && port < p->addr + p->length)
return p->io(dir, port - p->addr, val, size, p->aux);
}
return iowhine(dir, port, val, size, nil);
}
u32int iodebug[32];
u32int
io(int isin, u16int port, u32int val, int sz)
{
int dbg;
dbg = port < 0x400 && (iodebug[port >> 5] >> (port & 31) & 1) != 0;
if(isin){
val = io0(isin, port, val, sz);
if(sz == 1) val = (u8int)val;
else if(sz == 2) val = (u16int)val;
if(dbg)
vmdebug("in %#.4ux <- %#.*ux", port, sz*2, val);
return val;
}else{
if(sz == 1) val = (u8int)val;
else if(sz == 2) val = (u16int)val;
io0(isin, port, val, sz);
if(dbg)
vmdebug("out %#.4ux <- %#.*ux", port, sz*2, val);
return 0;
}
}