]> git.lizzy.rs Git - plan9front.git/blob - sys/src/9/pc/devkbd.c
kernel: cleanup the software mouse cursor mess
[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",
156                         c, cmd, getcallerpc(&cmd));
157                 return -1;
158         }
159         return 0;
160 }
161
162 /*
163  * set keyboard's leds for lock states (scroll, numeric, caps).
164  *
165  * at least one keyboard (from Qtronics) also sets its numeric-lock
166  * behaviour to match the led state, though it has no numeric keypad,
167  * and some BIOSes bring the system up with numeric-lock set and no
168  * setting to change that.  this combination steals the keys for these
169  * characters and makes it impossible to generate them: uiolkjm&*().
170  * thus we'd like to be able to force the numeric-lock led (and behaviour) off.
171  */
172 static void
173 setleds(int leds)
174 {
175         static int old = -1;
176
177         if(nokbd || leds == old)
178                 return;
179         leds &= 7;
180         ilock(&i8042lock);
181         for(;;){
182                 if(outready() < 0)
183                         break;
184                 outb(Data, 0xed);               /* `reset keyboard lock states' */
185                 if(outready() < 0)
186                         break;
187                 outb(Data, leds);
188                 if(outready() < 0)
189                         break;
190                 old = leds;
191                 break;
192         }
193         iunlock(&i8042lock);
194 }
195
196 /*
197  *  keyboard interrupt
198  */
199 static void
200 i8042intr(Ureg*, void*)
201 {
202         int s, c;
203         uchar b;
204
205         /*
206          *  get status
207          */
208         ilock(&i8042lock);
209         s = inb(Status);
210         if(!(s&Inready)){
211                 iunlock(&i8042lock);
212                 return;
213         }
214
215         /*
216          *  get the character
217          */
218         c = inb(Data);
219         iunlock(&i8042lock);
220
221         /*
222          *  if it's the aux port...
223          */
224         if(s & Minready){
225                 if(auxputc != nil)
226                         auxputc(c, 0);
227                 return;
228         }
229
230         b = c & 0xff;
231         qproduce(kbd.q, &b, 1);
232 }
233
234 void
235 i8042auxenable(void (*putc)(int, int))
236 {
237         static char err[] = "i8042: aux init failed\n";
238
239         ilock(&i8042lock);
240
241         /* enable kbd/aux xfers and interrupts */
242         ccc &= ~Cauxdis;
243         ccc |= Cauxint;
244
245         if(outready() < 0)
246                 print(err);
247         outb(Cmd, 0x60);                        /* write control register */
248         if(outready() < 0)
249                 print(err);
250         outb(Data, ccc);
251         if(outready() < 0)
252                 print(err);
253         outb(Cmd, 0xA8);                        /* auxiliary device enable */
254         if(outready() < 0){
255                 print(err);
256                 iunlock(&i8042lock);
257                 return;
258         }
259         auxputc = putc;
260         intrenable(IrqAUX, i8042intr, 0, BUSUNKNOWN, "kbdaux");
261
262         iunlock(&i8042lock);
263 }
264
265 static void
266 kbdpoll(void)
267 {
268         if(nokbd || qlen(kbd.q) > 0)
269                 return;
270         i8042intr(0, 0);
271 }
272
273 static void
274 kbdshutdown(void)
275 {
276         if(nokbd)
277                 return;
278         /* disable kbd and aux xfers and interrupts */
279         ccc &= ~(Ckbdint|Cauxint);
280         ccc |= (Cauxdis|Ckbddis);
281         outready();
282         outb(Cmd, 0x60);
283         outready();
284         outb(Data, ccc);
285         outready();
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 static void
375 kbdreset(void)
376 {
377         static char initfailed[] = "i8042: kbd init failed\n";
378         int c, try;
379
380         kbd.q = qopen(1024, Qcoalesce, 0, 0);
381         if(kbd.q == nil)
382                 panic("kbdreset");
383         qnoblock(kbd.q, 1);
384
385         /* wait for a quiescent controller */
386         try = 1000;
387         while(try-- > 0 && (c = inb(Status)) & (Outbusy | Inready)) {
388                 if(c & Inready)
389                         inb(Data);
390                 delay(1);
391         }
392         if (try <= 0) {
393                 print(initfailed);
394                 return;
395         }
396
397         /* get current controller command byte */
398         outb(Cmd, 0x20);
399         if(inready() < 0){
400                 print("i8042: can't read ccc\n");
401                 ccc = 0;
402         } else
403                 ccc = inb(Data);
404
405         /* enable kbd xfers and interrupts */
406         ccc &= ~Ckbddis;
407         ccc |= Csf | Ckbdint | Cscs1;
408
409         /* disable ps2 mouse */
410         ccc &= ~Cauxint;
411         ccc |= Cauxdis;
412
413         if(outready() < 0) {
414                 print(initfailed);
415                 return;
416         }
417         outb(Cmd, 0x60);
418         outready();
419         outb(Data, ccc);
420         outready();
421
422         nokbd = 0;
423         ioalloc(Cmd, 1, 0, "i8042.cs");
424         ioalloc(Data, 1, 0, "i8042.data");
425         intrenable(IrqKBD, i8042intr, 0, BUSUNKNOWN, "kbd");
426 }
427
428 Dev kbddevtab = {
429         L'b',
430         "kbd",
431
432         kbdreset,
433         devinit,
434         kbdshutdown,
435         kbdattach,
436         kbdwalk,
437         kbdstat,
438         kbdopen,
439         devcreate,
440         kbdclose,
441         kbdread,
442         kbdbread,
443         kbdwrite,
444         devbwrite,
445         devremove,
446         devwstat,
447 };
448