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