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