plan9fox/sys/src/9/sgi/devkbd.c
2015-04-02 18:35:43 +02:00

340 lines
5.5 KiB
C

/*
* keyboard input
*/
#include "u.h"
#include "../port/lib.h"
#include "mem.h"
#include "dat.h"
#include "fns.h"
#include "io.h"
#include "../port/error.h"
enum {
Data= 0x40+3, /* data port */
Cmd= 0x44+3, /* command port (write) */
Status= 0x44+3, /* status port (read) */
Inready= 0x01, /* input character ready */
Outbusy= 0x02, /* output busy */
Sysflag= 0x04, /* system flag */
Cmddata= 0x08, /* cmd==0, data==1 */
Inhibit= 0x10, /* keyboard/mouse inhibited */
Minready= 0x20, /* mouse character ready */
Rtimeout= 0x40, /* general timeout */
Parity= 0x80,
};
enum
{
/* controller command byte */
Cscs1= (1<<6), /* scan code set 1 */
Cauxdis= (1<<5), /* mouse disable */
Ckbddis= (1<<4), /* kbd disable */
Csf= (1<<2), /* system flag */
Cauxint= (1<<1), /* mouse interrupt enable */
Ckbdint= (1<<0), /* kbd interrupt enable */
};
enum {
Qdir,
Qscancode,
Qleds,
};
static Dirtab kbdtab[] = {
".", {Qdir, 0, QTDIR}, 0, 0555,
"scancode", {Qscancode, 0}, 0, 0440,
"leds", {Qleds, 0}, 0, 0220,
};
static Lock i8042lock;
static uchar ccc, dummy;
static struct {
Ref ref;
Queue *q;
uchar *io;
} kbd;
#define inb(r) (dummy=kbd.io[r])
#define outb(r,b) (kbd.io[r]=b)
/*
* wait for output no longer busy
*/
static int
outready(void)
{
int tries;
for(tries = 0; (inb(Status) & Outbusy); tries++){
if(tries > 500)
return -1;
delay(2);
}
return 0;
}
/*
* wait for input
*/
static int
inready(void)
{
int tries;
for(tries = 0; !(inb(Status) & Inready); tries++){
if(tries > 500)
return -1;
delay(2);
}
return 0;
}
/*
* set keyboard's leds for lock states (scroll, numeric, caps).
*
* at least one keyboard (from Qtronics) also sets its numeric-lock
* behaviour to match the led state, though it has no numeric keypad,
* and some BIOSes bring the system up with numeric-lock set and no
* setting to change that. this combination steals the keys for these
* characters and makes it impossible to generate them: uiolkjm&*().
* thus we'd like to be able to force the numeric-lock led (and behaviour) off.
*/
static void
setleds(int leds)
{
static int old = -1;
if(!conf.keyboard || leds == old)
return;
leds &= 7;
ilock(&i8042lock);
for(;;){
if(outready() < 0)
break;
outb(Data, 0xed); /* `reset keyboard lock states' */
if(outready() < 0)
break;
outb(Data, leds);
if(outready() < 0)
break;
old = leds;
break;
}
iunlock(&i8042lock);
}
/*
* keyboard interrupt
*/
static void
i8042intr(Ureg*, void*)
{
extern void sgimouseputc(int);
int s, c;
uchar b;
/*
* get status
*/
ilock(&i8042lock);
s = inb(Status);
if(!(s&Inready)){
iunlock(&i8042lock);
return;
}
/*
* get the character
*/
c = inb(Data);
iunlock(&i8042lock);
b = c & 0xff;
/*
* if it's the aux port...
*/
if(s & Minready){
sgimouseputc(b);
return;
}
qproduce(kbd.q, &b, 1);
}
static void
pollintr(void)
{
i8042intr(nil, nil);
}
static Chan *
kbdattach(char *spec)
{
return devattach(L'b', spec);
}
static Walkqid*
kbdwalk(Chan *c, Chan *nc, char **name, int nname)
{
return devwalk(c, nc, name, nname, kbdtab, nelem(kbdtab), devgen);
}
static int
kbdstat(Chan *c, uchar *dp, int n)
{
return devstat(c, dp, n, kbdtab, nelem(kbdtab), devgen);
}
static Chan*
kbdopen(Chan *c, int omode)
{
if(!iseve())
error(Eperm);
if(c->qid.path == Qscancode){
if(waserror()){
decref(&kbd.ref);
nexterror();
}
if(incref(&kbd.ref) != 1)
error(Einuse);
c = devopen(c, omode, kbdtab, nelem(kbdtab), devgen);
poperror();
return c;
}
return devopen(c, omode, kbdtab, nelem(kbdtab), devgen);
}
static void
kbdclose(Chan *c)
{
if((c->flag & COPEN) && c->qid.path == Qscancode)
decref(&kbd.ref);
}
static Block*
kbdbread(Chan *c, long n, ulong off)
{
if(c->qid.path == Qscancode)
return qbread(kbd.q, n);
else
return devbread(c, n, off);
}
static long
kbdread(Chan *c, void *a, long n, vlong)
{
if(c->qid.path == Qscancode)
return qread(kbd.q, a, n);
if(c->qid.path == Qdir)
return devdirread(c, a, n, kbdtab, nelem(kbdtab), devgen);
error(Egreg);
return 0;
}
static long
kbdwrite(Chan *c, void *a, long n, vlong)
{
char tmp[8+1], *p;
if(c->qid.path != Qleds)
error(Egreg);
p = tmp + n;
if(n >= sizeof(tmp))
p = tmp + sizeof(tmp)-1;
memmove(tmp, a, p - tmp);
*p = 0;
setleds(atoi(tmp));
return n;
}
static char *initfailed = "i8042: kbdinit failed\n";
static int
outbyte(int port, int c)
{
outb(port, c);
if(outready() < 0) {
print(initfailed);
return -1;
}
return 0;
}
static void
kbdinit(void)
{
int c, try;
kbd.io = IO(uchar, HPC3_KBDMS);
kbd.q = qopen(1024, Qcoalesce, 0, 0);
if(kbd.q == nil)
panic("kbdinit: qopen");
qnoblock(kbd.q, 1);
/* wait for a quiescent controller */
try = 1000;
while(try-- > 0 && (c = inb(Status)) & (Outbusy | Inready)) {
if(c & Inready)
inb(Data);
delay(1);
}
if (try <= 0) {
print(initfailed);
return;
}
/* get current controller command byte */
outb(Cmd, 0x20);
if(inready() < 0){
print("i8042: kbdinit can't read ccc\n");
ccc = 0;
} else
ccc = inb(Data);
/* enable kbd xfers and interrupts */
ccc &= ~Ckbddis;
ccc |= Csf | Ckbdint | Cscs1;
if(outready() < 0) {
print(initfailed);
return;
}
/* disable mouse */
if (outbyte(Cmd, 0x60) < 0 || outbyte(Data, ccc) < 0){
print("i8042: kbdinit mouse disable failed\n");
return;
}
conf.keyboard = 1;
addclock0link(pollintr, 5);
}
Dev kbddevtab = {
L'b',
"kbd",
devreset,
kbdinit,
devshutdown,
kbdattach,
kbdwalk,
kbdstat,
kbdopen,
devcreate,
kbdclose,
kbdread,
kbdbread,
kbdwrite,
devbwrite,
devremove,
devwstat,
};