]> git.lizzy.rs Git - plan9front.git/blob - sys/src/9/pc/devkbd.c
kbdfs
[plan9front.git] / sys / src / 9 / pc / devkbd.c
1 /*
2  * keyboard input
3  */
4 #include        "u.h"
5 #include        "../port/lib.h"
6 #include        "mem.h"
7 #include        "dat.h"
8 #include        "fns.h"
9 #include        "io.h"
10 #include        "../port/error.h"
11
12 enum {
13         Data=           0x60,           /* data port */
14
15         Status=         0x64,           /* status port */
16          Inready=       0x01,           /*  input character ready */
17          Outbusy=       0x02,           /*  output busy */
18          Sysflag=       0x04,           /*  system flag */
19          Cmddata=       0x08,           /*  cmd==0, data==1 */
20          Inhibit=       0x10,           /*  keyboard/mouse inhibited */
21          Minready=      0x20,           /*  mouse character ready */
22          Rtimeout=      0x40,           /*  general timeout */
23          Parity=        0x80,
24
25         Cmd=            0x64,           /* command port (write only) */
26
27         Spec=           0xF800,         /* Unicode private space */
28         PF=             Spec|0x20,      /* num pad function key */
29         View=           Spec|0x00,      /* view (shift window up) */
30         KF=             0xF000,         /* function key (begin Unicode private space) */
31         Shift=          Spec|0x60,
32         Break=          Spec|0x61,
33         Ctrl=           Spec|0x62,
34         Latin=          Spec|0x63,
35         Caps=           Spec|0x64,
36         Num=            Spec|0x65,
37         Middle=         Spec|0x66,
38         Altgr=          Spec|0x67,
39         Kmouse=         Spec|0x100,
40         No=             0x00,           /* peter */
41
42         Home=           KF|13,
43         Up=             KF|14,
44         Pgup=           KF|15,
45         Print=          KF|16,
46         Left=           KF|17,
47         Right=          KF|18,
48         End=            KF|24,
49         Down=           View,
50         Pgdown=         KF|19,
51         Ins=            KF|20,
52         Del=            0x7F,
53         Scroll=         KF|21,
54
55         Nscan=  128,
56
57         Int=    0,                      /* kbscans indices */
58         Ext,
59         Nscans,
60 };
61
62 enum
63 {
64         /* controller command byte */
65         Cscs1=          (1<<6),         /* scan code set 1 */
66         Cauxdis=        (1<<5),         /* mouse disable */
67         Ckbddis=        (1<<4),         /* kbd disable */
68         Csf=            (1<<2),         /* system flag */
69         Cauxint=        (1<<1),         /* mouse interrupt enable */
70         Ckbdint=        (1<<0),         /* kbd interrupt enable */
71 };
72
73 enum {
74         Qdir,
75         Qscancode,
76         Qleds,
77 };
78
79 static Dirtab kbdtab[] = {
80         ".",            {Qdir, 0, QTDIR},       0,      0555,
81         "scancode",     {Qscancode, 0},         0,      0440,
82         "leds",         {Qleds, 0},             0,      0220,
83 };
84
85 static Lock i8042lock;
86 static uchar ccc;
87 static void kbdputc(int);
88 static void (*auxputc)(int, int);
89 static int nokbd = 1;                   /* flag: no PS/2 keyboard */
90
91 static struct {
92         Ref ref;
93         Queue *q;
94 } kbd;
95
96 /*
97  *  wait for output no longer busy
98  */
99 static int
100 outready(void)
101 {
102         int tries;
103
104         for(tries = 0; (inb(Status) & Outbusy); tries++){
105                 if(tries > 500)
106                         return -1;
107                 delay(2);
108         }
109         return 0;
110 }
111
112 /*
113  *  wait for input
114  */
115 static int
116 inready(void)
117 {
118         int tries;
119
120         for(tries = 0; !(inb(Status) & Inready); tries++){
121                 if(tries > 500)
122                         return -1;
123                 delay(2);
124         }
125         return 0;
126 }
127
128 /*
129  *  ask 8042 to reset the machine
130  */
131 void
132 i8042reset(void)
133 {
134         int i, x;
135
136         if(nokbd)
137                 return;
138
139         *((ushort*)KADDR(0x472)) = 0x1234;      /* BIOS warm-boot flag */
140
141         /*
142          *  newer reset the machine command
143          */
144         outready();
145         outb(Cmd, 0xFE);
146         outready();
147
148         /*
149          *  Pulse it by hand (old somewhat reliable)
150          */
151         x = 0xDF;
152         for(i = 0; i < 5; i++){
153                 x ^= 1;
154                 outready();
155                 outb(Cmd, 0xD1);
156                 outready();
157                 outb(Data, x);  /* toggle reset */
158                 delay(100);
159         }
160 }
161
162 int
163 i8042auxcmd(int cmd)
164 {
165         unsigned int c;
166         int tries;
167         static int badkbd;
168
169         if(badkbd)
170                 return -1;
171         c = 0;
172         tries = 0;
173
174         ilock(&i8042lock);
175         do{
176                 if(tries++ > 2)
177                         break;
178                 if(outready() < 0)
179                         break;
180                 outb(Cmd, 0xD4);
181                 if(outready() < 0)
182                         break;
183                 outb(Data, cmd);
184                 if(outready() < 0)
185                         break;
186                 if(inready() < 0)
187                         break;
188                 c = inb(Data);
189         } while(c == 0xFE || c == 0);
190         iunlock(&i8042lock);
191
192         if(c != 0xFA){
193                 print("i8042: %2.2ux returned to the %2.2ux command\n", c, cmd);
194                 badkbd = 1;     /* don't keep trying; there might not be one */
195                 return -1;
196         }
197         return 0;
198 }
199
200 int
201 i8042auxcmds(uchar *cmd, int ncmd)
202 {
203         int i;
204
205         ilock(&i8042lock);
206         for(i=0; i<ncmd; i++){
207                 if(outready() < 0)
208                         break;
209                 outb(Cmd, 0xD4);
210                 if(outready() < 0)
211                         break;
212                 outb(Data, cmd[i]);
213         }
214         iunlock(&i8042lock);
215         return i;
216 }
217
218 /*
219  * set keyboard's leds for lock states (scroll, numeric, caps).
220  *
221  * at least one keyboard (from Qtronics) also sets its numeric-lock
222  * behaviour to match the led state, though it has no numeric keypad,
223  * and some BIOSes bring the system up with numeric-lock set and no
224  * setting to change that.  this combination steals the keys for these
225  * characters and makes it impossible to generate them: uiolkjm&*().
226  * thus we'd like to be able to force the numeric-lock led (and behaviour) off.
227  */
228 static void
229 setleds(int leds)
230 {
231         static int old = -1;
232
233         if(nokbd || leds == old)
234                 return;
235         leds &= 7;
236         ilock(&i8042lock);
237         for(;;){
238                 if(outready() < 0)
239                         break;
240                 outb(Data, 0xed);               /* `reset keyboard lock states' */
241                 if(outready() < 0)
242                         break;
243                 outb(Data, leds);
244                 if(outready() < 0)
245                         break;
246                 old = leds;
247                 break;
248         }
249         iunlock(&i8042lock);
250 }
251
252 /*
253  *  keyboard interrupt
254  */
255 static void
256 i8042intr(Ureg*, void*)
257 {
258         int s, c;
259         uchar b;
260
261         /*
262          *  get status
263          */
264         ilock(&i8042lock);
265         s = inb(Status);
266         if(!(s&Inready)){
267                 iunlock(&i8042lock);
268                 return;
269         }
270
271         /*
272          *  get the character
273          */
274         c = inb(Data);
275         iunlock(&i8042lock);
276
277         /*
278          *  if it's the aux port...
279          */
280         if(s & Minready){
281                 if(auxputc != nil)
282                         auxputc(c, 0);
283                 return;
284         }
285
286         b = c & 0xff;
287         qproduce(kbd.q, &b, 1);
288 }
289
290 void
291 i8042auxenable(void (*putc)(int, int))
292 {
293         char *err = "i8042: aux init failed\n";
294
295         /* enable kbd/aux xfers and interrupts */
296         ccc &= ~Cauxdis;
297         ccc |= Cauxint;
298
299         ilock(&i8042lock);
300         if(outready() < 0)
301                 print(err);
302         outb(Cmd, 0x60);                        /* write control register */
303         if(outready() < 0)
304                 print(err);
305         outb(Data, ccc);
306         if(outready() < 0)
307                 print(err);
308         outb(Cmd, 0xA8);                        /* auxiliary device enable */
309         if(outready() < 0){
310                 iunlock(&i8042lock);
311                 return;
312         }
313         auxputc = putc;
314         intrenable(IrqAUX, i8042intr, 0, BUSUNKNOWN, "kbdaux");
315         iunlock(&i8042lock);
316 }
317
318 static Chan *
319 kbdattach(char *spec)
320 {
321         return devattach(L'b', spec);
322 }
323
324 static Walkqid*
325 kbdwalk(Chan *c, Chan *nc, char **name, int nname)
326 {
327         return devwalk(c, nc, name, nname, kbdtab, nelem(kbdtab), devgen);
328 }
329
330 static int
331 kbdstat(Chan *c, uchar *dp, int n)
332 {
333         return devstat(c, dp, n, kbdtab, nelem(kbdtab), devgen);
334 }
335
336 static Chan*
337 kbdopen(Chan *c, int omode)
338 {
339         if(!iseve())
340                 error(Eperm);
341         if(c->qid.path == Qscancode){
342                 if(incref(&kbd.ref) != 1){
343                         decref(&kbd.ref);
344                         error(Einuse);
345                 }
346         }
347         return devopen(c, omode, kbdtab, nelem(kbdtab), devgen);
348 }
349
350 static void
351 kbdclose(Chan *c)
352 {
353         if(c->qid.path == Qscancode)
354                 decref(&kbd.ref);
355 }
356
357 static Block*
358 kbdbread(Chan *c, long n, ulong)
359 {
360         if(c->qid.path != Qscancode)
361                 error(Egreg);
362
363         return qbread(kbd.q, n);
364 }
365
366 static long
367 kbdread(Chan *c, void *a, long n, vlong)
368 {
369         if(c->qid.path != Qscancode)
370                 error(Egreg);
371
372         return qread(kbd.q, a, n);
373 }
374
375 static long
376 kbdwrite(Chan *c, void *a, long n, vlong)
377 {
378         char tmp[8+1], *p;
379
380         if(c->qid.path != Qleds)
381                 error(Egreg);
382
383         p = tmp + n;
384         if(n >= sizeof(tmp))
385                 p = tmp + sizeof(tmp)-1;
386         memmove(tmp, a, p - tmp);
387         *p = 0;
388
389         setleds(atoi(tmp));
390
391         return n;
392 }
393
394 Dev kbddevtab = {
395         L'b',
396         "kbd",
397
398         devreset,
399         devinit,
400         devshutdown,
401         kbdattach,
402         kbdwalk,
403         kbdstat,
404         kbdopen,
405         devcreate,
406         kbdclose,
407         kbdread,
408         kbdbread,
409         kbdwrite,
410         devbwrite,
411         devremove,
412         devwstat,
413 };
414
415
416 static char *initfailed = "i8042: kbdinit failed\n";
417
418 static int
419 outbyte(int port, int c)
420 {
421         outb(port, c);
422         if(outready() < 0) {
423                 print(initfailed);
424                 return -1;
425         }
426         return 0;
427 }
428
429 void
430 kbdenable(void)
431 {
432         kbd.q = qopen(4*1024, 0, 0, 0);
433         if(kbd.q == nil)
434                 panic("kbdenable");
435         qnoblock(kbd.q, 1);
436
437         ioalloc(Data, 1, 0, "kbd");
438         ioalloc(Cmd, 1, 0, "kbd");
439
440         intrenable(IrqKBD, i8042intr, 0, BUSUNKNOWN, "kbd");
441 }
442
443 void
444 kbdinit(void)
445 {
446         int c, try;
447
448         /* wait for a quiescent controller */
449         try = 1000;
450         while(try-- > 0 && (c = inb(Status)) & (Outbusy | Inready)) {
451                 if(c & Inready)
452                         inb(Data);
453                 delay(1);
454         }
455         if (try <= 0) {
456                 print(initfailed);
457                 return;
458         }
459
460         /* get current controller command byte */
461         outb(Cmd, 0x20);
462         if(inready() < 0){
463                 print("i8042: kbdinit can't read ccc\n");
464                 ccc = 0;
465         } else
466                 ccc = inb(Data);
467
468         /* enable kbd xfers and interrupts */
469         ccc &= ~Ckbddis;
470         ccc |= Csf | Ckbdint | Cscs1;
471         if(outready() < 0) {
472                 print(initfailed);
473                 return;
474         }
475
476         nokbd = 0;
477
478         /* disable mouse */
479         if (outbyte(Cmd, 0x60) < 0 || outbyte(Data, ccc) < 0)
480                 print("i8042: kbdinit mouse disable failed\n");
481 }