]> git.lizzy.rs Git - plan9front.git/blob - sys/src/9/pc/devkbd.c
etheriwl: implement 4965 firmware bootstrap (untested)
[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 off)
359 {
360         if(c->qid.path == Qscancode)
361                 return qbread(kbd.q, n);
362         else
363                 return devbread(c, n, off);
364 }
365
366 static long
367 kbdread(Chan *c, void *a, long n, vlong)
368 {
369         if(c->qid.path == Qscancode)
370                 return qread(kbd.q, a, n);
371         if(c->qid.path == Qdir)
372                 return devdirread(c, a, n, kbdtab, nelem(kbdtab), devgen);
373
374         error(Egreg);
375         return 0;
376 }
377
378 static long
379 kbdwrite(Chan *c, void *a, long n, vlong)
380 {
381         char tmp[8+1], *p;
382
383         if(c->qid.path != Qleds)
384                 error(Egreg);
385
386         p = tmp + n;
387         if(n >= sizeof(tmp))
388                 p = tmp + sizeof(tmp)-1;
389         memmove(tmp, a, p - tmp);
390         *p = 0;
391
392         setleds(atoi(tmp));
393
394         return n;
395 }
396
397 Dev kbddevtab = {
398         L'b',
399         "kbd",
400
401         devreset,
402         devinit,
403         devshutdown,
404         kbdattach,
405         kbdwalk,
406         kbdstat,
407         kbdopen,
408         devcreate,
409         kbdclose,
410         kbdread,
411         kbdbread,
412         kbdwrite,
413         devbwrite,
414         devremove,
415         devwstat,
416 };
417
418
419 static char *initfailed = "i8042: kbdinit failed\n";
420
421 static int
422 outbyte(int port, int c)
423 {
424         outb(port, c);
425         if(outready() < 0) {
426                 print(initfailed);
427                 return -1;
428         }
429         return 0;
430 }
431
432 void
433 kbdenable(void)
434 {
435         kbd.q = qopen(1024, Qcoalesce, 0, 0);
436         if(kbd.q == nil)
437                 panic("kbdenable");
438         qnoblock(kbd.q, 1);
439
440         ioalloc(Data, 1, 0, "kbd");
441         ioalloc(Cmd, 1, 0, "kbd");
442
443         intrenable(IrqKBD, i8042intr, 0, BUSUNKNOWN, "kbd");
444 }
445
446 void
447 kbdinit(void)
448 {
449         int c, try;
450
451         /* wait for a quiescent controller */
452         try = 1000;
453         while(try-- > 0 && (c = inb(Status)) & (Outbusy | Inready)) {
454                 if(c & Inready)
455                         inb(Data);
456                 delay(1);
457         }
458         if (try <= 0) {
459                 print(initfailed);
460                 return;
461         }
462
463         /* get current controller command byte */
464         outb(Cmd, 0x20);
465         if(inready() < 0){
466                 print("i8042: kbdinit can't read ccc\n");
467                 ccc = 0;
468         } else
469                 ccc = inb(Data);
470
471         /* enable kbd xfers and interrupts */
472         ccc &= ~Ckbddis;
473         ccc |= Csf | Ckbdint | Cscs1;
474         if(outready() < 0) {
475                 print(initfailed);
476                 return;
477         }
478
479         nokbd = 0;
480
481         /* disable mouse */
482         if (outbyte(Cmd, 0x60) < 0 || outbyte(Data, ccc) < 0)
483                 print("i8042: kbdinit mouse disable failed\n");
484 }