]> git.lizzy.rs Git - plan9front.git/blob - sys/src/9/pc/devkbd.c
merge
[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
168         c = 0;
169         tries = 0;
170
171         ilock(&i8042lock);
172         do{
173                 if(tries++ > 2)
174                         break;
175                 if(outready() < 0)
176                         break;
177                 outb(Cmd, 0xD4);
178                 if(outready() < 0)
179                         break;
180                 outb(Data, cmd);
181                 if(outready() < 0)
182                         break;
183                 if(inready() < 0)
184                         break;
185                 c = inb(Data);
186         } while(c == 0xFE || c == 0);
187         iunlock(&i8042lock);
188
189         if(c != 0xFA){
190                 print("i8042: %2.2ux returned to the %2.2ux command (pc=%#p)\n", c, cmd, getcallerpc(&cmd));
191                 return -1;
192         }
193         return 0;
194 }
195
196 int
197 i8042auxcmds(uchar *cmd, int ncmd)
198 {
199         int i;
200
201         ilock(&i8042lock);
202         for(i=0; i<ncmd; i++){
203                 if(outready() < 0)
204                         break;
205                 outb(Cmd, 0xD4);
206                 if(outready() < 0)
207                         break;
208                 outb(Data, cmd[i]);
209         }
210         iunlock(&i8042lock);
211         return i;
212 }
213
214 /*
215  * set keyboard's leds for lock states (scroll, numeric, caps).
216  *
217  * at least one keyboard (from Qtronics) also sets its numeric-lock
218  * behaviour to match the led state, though it has no numeric keypad,
219  * and some BIOSes bring the system up with numeric-lock set and no
220  * setting to change that.  this combination steals the keys for these
221  * characters and makes it impossible to generate them: uiolkjm&*().
222  * thus we'd like to be able to force the numeric-lock led (and behaviour) off.
223  */
224 static void
225 setleds(int leds)
226 {
227         static int old = -1;
228
229         if(nokbd || leds == old)
230                 return;
231         leds &= 7;
232         ilock(&i8042lock);
233         for(;;){
234                 if(outready() < 0)
235                         break;
236                 outb(Data, 0xed);               /* `reset keyboard lock states' */
237                 if(outready() < 0)
238                         break;
239                 outb(Data, leds);
240                 if(outready() < 0)
241                         break;
242                 old = leds;
243                 break;
244         }
245         iunlock(&i8042lock);
246 }
247
248 /*
249  *  keyboard interrupt
250  */
251 static void
252 i8042intr(Ureg*, void*)
253 {
254         int s, c;
255         uchar b;
256
257         /*
258          *  get status
259          */
260         ilock(&i8042lock);
261         s = inb(Status);
262         if(!(s&Inready)){
263                 iunlock(&i8042lock);
264                 return;
265         }
266
267         /*
268          *  get the character
269          */
270         c = inb(Data);
271         iunlock(&i8042lock);
272
273         /*
274          *  if it's the aux port...
275          */
276         if(s & Minready){
277                 if(auxputc != nil)
278                         auxputc(c, 0);
279                 return;
280         }
281
282         b = c & 0xff;
283         qproduce(kbd.q, &b, 1);
284 }
285
286 void
287 i8042auxenable(void (*putc)(int, int))
288 {
289         char *err = "i8042: aux init failed\n";
290
291         /* enable kbd/aux xfers and interrupts */
292         ccc &= ~Cauxdis;
293         ccc |= Cauxint;
294
295         ilock(&i8042lock);
296         if(outready() < 0)
297                 print(err);
298         outb(Cmd, 0x60);                        /* write control register */
299         if(outready() < 0)
300                 print(err);
301         outb(Data, ccc);
302         if(outready() < 0)
303                 print(err);
304         outb(Cmd, 0xA8);                        /* auxiliary device enable */
305         if(outready() < 0){
306                 print(err);
307                 iunlock(&i8042lock);
308                 return;
309         }
310         auxputc = putc;
311         intrenable(IrqAUX, i8042intr, 0, BUSUNKNOWN, "kbdaux");
312         iunlock(&i8042lock);
313 }
314
315 static Chan *
316 kbdattach(char *spec)
317 {
318         return devattach(L'b', spec);
319 }
320
321 static Walkqid*
322 kbdwalk(Chan *c, Chan *nc, char **name, int nname)
323 {
324         return devwalk(c, nc, name, nname, kbdtab, nelem(kbdtab), devgen);
325 }
326
327 static int
328 kbdstat(Chan *c, uchar *dp, int n)
329 {
330         return devstat(c, dp, n, kbdtab, nelem(kbdtab), devgen);
331 }
332
333 static Chan*
334 kbdopen(Chan *c, int omode)
335 {
336         if(!iseve())
337                 error(Eperm);
338         if(c->qid.path == Qscancode){
339                 if(waserror()){
340                         decref(&kbd.ref);
341                         nexterror();
342                 }
343                 if(incref(&kbd.ref) != 1)
344                         error(Einuse);
345                 c = devopen(c, omode, kbdtab, nelem(kbdtab), devgen);
346                 poperror();
347                 return c;
348         }
349         return devopen(c, omode, kbdtab, nelem(kbdtab), devgen);
350 }
351
352 static void
353 kbdclose(Chan *c)
354 {
355         if((c->flag & COPEN) && c->qid.path == Qscancode)
356                 decref(&kbd.ref);
357 }
358
359 static Block*
360 kbdbread(Chan *c, long n, ulong off)
361 {
362         if(c->qid.path == Qscancode)
363                 return qbread(kbd.q, n);
364         else
365                 return devbread(c, n, off);
366 }
367
368 static long
369 kbdread(Chan *c, void *a, long n, vlong)
370 {
371         if(c->qid.path == Qscancode)
372                 return qread(kbd.q, a, n);
373         if(c->qid.path == Qdir)
374                 return devdirread(c, a, n, kbdtab, nelem(kbdtab), devgen);
375
376         error(Egreg);
377         return 0;
378 }
379
380 static long
381 kbdwrite(Chan *c, void *a, long n, vlong)
382 {
383         char tmp[8+1], *p;
384
385         if(c->qid.path != Qleds)
386                 error(Egreg);
387
388         p = tmp + n;
389         if(n >= sizeof(tmp))
390                 p = tmp + sizeof(tmp)-1;
391         memmove(tmp, a, p - tmp);
392         *p = 0;
393
394         setleds(atoi(tmp));
395
396         return n;
397 }
398
399 Dev kbddevtab = {
400         L'b',
401         "kbd",
402
403         devreset,
404         devinit,
405         devshutdown,
406         kbdattach,
407         kbdwalk,
408         kbdstat,
409         kbdopen,
410         devcreate,
411         kbdclose,
412         kbdread,
413         kbdbread,
414         kbdwrite,
415         devbwrite,
416         devremove,
417         devwstat,
418 };
419
420
421 static char *initfailed = "i8042: kbdinit failed\n";
422
423 static int
424 outbyte(int port, int c)
425 {
426         outb(port, c);
427         if(outready() < 0) {
428                 print(initfailed);
429                 return -1;
430         }
431         return 0;
432 }
433
434 void
435 kbdenable(void)
436 {
437         kbd.q = qopen(1024, Qcoalesce, 0, 0);
438         if(kbd.q == nil)
439                 panic("kbdenable");
440         qnoblock(kbd.q, 1);
441
442         ioalloc(Data, 1, 0, "kbd");
443         ioalloc(Cmd, 1, 0, "kbd");
444
445         intrenable(IrqKBD, i8042intr, 0, BUSUNKNOWN, "kbd");
446 }
447
448 void
449 kbdinit(void)
450 {
451         int c, try;
452
453         /* wait for a quiescent controller */
454         try = 1000;
455         while(try-- > 0 && (c = inb(Status)) & (Outbusy | Inready)) {
456                 if(c & Inready)
457                         inb(Data);
458                 delay(1);
459         }
460         if (try <= 0) {
461                 print(initfailed);
462                 return;
463         }
464
465         /* get current controller command byte */
466         outb(Cmd, 0x20);
467         if(inready() < 0){
468                 print("i8042: kbdinit can't read ccc\n");
469                 ccc = 0;
470         } else
471                 ccc = inb(Data);
472
473         /* enable kbd xfers and interrupts */
474         ccc &= ~Ckbddis;
475         ccc |= Csf | Ckbdint | Cscs1;
476         if(outready() < 0) {
477                 print(initfailed);
478                 return;
479         }
480
481         nokbd = 0;
482
483         /* disable mouse */
484         if (outbyte(Cmd, 0x60) < 0 || outbyte(Data, ccc) < 0)
485                 print("i8042: kbdinit mouse disable failed\n");
486 }