pckbport.c revision 1.2 1 /* $NetBSD: pckbport.c,v 1.2 2004/03/18 21:05:19 bjh21 Exp $ */
2
3 /*
4 * Copyright (c) 2004 Ben Harris
5 * Copyright (c) 1998
6 * Matthias Drochner. All rights reserved.
7 *
8 * Redistribution and use in source and binary forms, with or without
9 * modification, are permitted provided that the following conditions
10 * are met:
11 * 1. Redistributions of source code must retain the above copyright
12 * notice, this list of conditions and the following disclaimer.
13 * 2. Redistributions in binary form must reproduce the above copyright
14 * notice, this list of conditions and the following disclaimer in the
15 * documentation and/or other materials provided with the distribution.
16 * 3. All advertising materials mentioning features or use of this software
17 * must display the following acknowledgement:
18 * This product includes software developed for the NetBSD Project
19 * by Matthias Drochner.
20 * 4. The name of the author may not be used to endorse or promote products
21 * derived from this software without specific prior written permission.
22 *
23 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
24 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
25 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
26 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
27 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
28 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
29 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
30 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
31 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
32 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
33 */
34
35 #include <sys/cdefs.h>
36 __KERNEL_RCSID(0, "$NetBSD: pckbport.c,v 1.2 2004/03/18 21:05:19 bjh21 Exp $");
37
38 #include <sys/param.h>
39 #include <sys/systm.h>
40 #include <sys/callout.h>
41 #include <sys/kernel.h>
42 #include <sys/proc.h>
43 #include <sys/device.h>
44 #include <sys/malloc.h>
45 #include <sys/errno.h>
46 #include <sys/queue.h>
47 #include <sys/lock.h>
48
49 #include <dev/pckbport/pckbportvar.h>
50
51 #include "locators.h"
52
53 #ifdef __HAVE_NWSCONS /* XXX: this port uses sys/dev/pckbport */
54 #include "pckbd.h"
55 #else /* ie: only md drivers attach to pckbport */
56 #define NPCKBD 0
57 #endif
58 #if (NPCKBD > 0)
59 #include <dev/pckbport/pckbdvar.h>
60 #endif
61
62 /* descriptor for one device command */
63 struct pckbport_devcmd {
64 TAILQ_ENTRY(pckbport_devcmd) next;
65 int flags;
66 #define KBC_CMDFLAG_SYNC 1 /* give descriptor back to caller */
67 #define KBC_CMDFLAG_SLOW 2
68 u_char cmd[4];
69 int cmdlen, cmdidx, retries;
70 u_char response[4];
71 int status, responselen, responseidx;
72 };
73
74 /* data per slave device */
75 struct pckbport_slotdata {
76 int polling; /* don't process data in interrupt handler */
77 TAILQ_HEAD(, pckbport_devcmd) cmdqueue; /* active commands */
78 TAILQ_HEAD(, pckbport_devcmd) freequeue; /* free commands */
79 #define NCMD 5
80 struct pckbport_devcmd cmds[NCMD];
81 };
82
83 #define CMD_IN_QUEUE(q) (TAILQ_FIRST(&(q)->cmdqueue) != NULL)
84
85 static void pckbport_init_slotdata(struct pckbport_slotdata *);
86 static int pckbport_submatch(struct device *, struct cfdata *, void *);
87 static int pckbportprint(void *, const char *);
88
89 static struct pckbport_slotdata pckbport_cons_slotdata;
90
91 static int pckbport_poll_data1(pckbport_tag_t, pckbport_slot_t);
92 static int pckbport_send_devcmd(struct pckbport_tag *, pckbport_slot_t,
93 u_char);
94 static void pckbport_poll_cmd1(struct pckbport_tag *, pckbport_slot_t,
95 struct pckbport_devcmd *);
96
97 static void pckbport_cleanqueue(struct pckbport_slotdata *);
98 static void pckbport_cleanup(void *);
99 static int pckbport_cmdresponse(struct pckbport_tag *, pckbport_slot_t,
100 u_char);
101 static void pckbport_start(struct pckbport_tag *, pckbport_slot_t);
102
103 static const char * const pckbport_slot_names[] = { "kbd", "aux" };
104
105 static struct pckbport_tag pckbport_cntag;
106
107 #define KBC_DEVCMD_ACK 0xfa
108 #define KBC_DEVCMD_RESEND 0xfe
109
110 #define KBD_DELAY DELAY(8)
111
112 static int
113 pckbport_poll_data1(pckbport_tag_t t, pckbport_slot_t slot)
114 {
115
116 return t->t_ops->t_poll_data1(t->t_cookie, slot);
117 }
118
119 static int
120 pckbport_send_devcmd(struct pckbport_tag *t, pckbport_slot_t slot, u_char val)
121 {
122
123 return t->t_ops->t_send_devcmd(t->t_cookie, slot, val);
124 }
125
126 static int
127 pckbport_submatch(struct device *parent, struct cfdata *cf, void *aux)
128 {
129 struct pckbport_attach_args *pa = aux;
130
131 if (cf->cf_loc[PCKBPORTCF_SLOT] != PCKBPORTCF_SLOT_DEFAULT &&
132 cf->cf_loc[PCKBPORTCF_SLOT] != pa->pa_slot)
133 return 0;
134 return config_match(parent, cf, aux);
135 }
136
137 pckbport_tag_t
138 pckbport_attach(void *cookie, struct pckbport_accessops const *ops)
139 {
140 pckbport_tag_t t;
141
142 if (cookie == pckbport_cntag.t_cookie &&
143 ops == pckbport_cntag.t_ops)
144 return &pckbport_cntag;
145 t = malloc(sizeof(struct pckbport_tag), M_DEVBUF, M_NOWAIT | M_ZERO);
146 if (t == NULL) return NULL;
147 t->t_cookie = cookie;
148 t->t_ops = ops;
149 return t;
150 }
151
152 struct device *
153 pckbport_attach_slot(struct device *dev, pckbport_tag_t t,
154 pckbport_slot_t slot)
155 {
156 struct pckbport_attach_args pa;
157 void *sdata;
158 struct device *found;
159 int alloced = 0;
160
161 pa.pa_tag = t;
162 pa.pa_slot = slot;
163
164 if (t->t_slotdata[slot] == NULL) {
165 sdata = malloc(sizeof(struct pckbport_slotdata),
166 M_DEVBUF, M_NOWAIT);
167 if (sdata == NULL) {
168 printf("%s: no memory\n", dev->dv_xname);
169 return 0;
170 }
171 t->t_slotdata[slot] = sdata;
172 pckbport_init_slotdata(t->t_slotdata[slot]);
173 alloced++;
174 }
175
176 found = config_found_sm(dev, &pa, pckbportprint, pckbport_submatch);
177
178 if (found == NULL && alloced) {
179 free(t->t_slotdata[slot], M_DEVBUF);
180 t->t_slotdata[slot] = NULL;
181 }
182
183 return found;
184 }
185
186 int
187 pckbportprint(void *aux, const char *pnp)
188 {
189 struct pckbport_attach_args *pa = aux;
190
191 if (!pnp)
192 aprint_normal(" (%s slot)", pckbport_slot_names[pa->pa_slot]);
193 return QUIET;
194 }
195
196 void
197 pckbport_init_slotdata(struct pckbport_slotdata *q)
198 {
199 int i;
200
201 TAILQ_INIT(&q->cmdqueue);
202 TAILQ_INIT(&q->freequeue);
203
204 for (i = 0; i < NCMD; i++)
205 TAILQ_INSERT_TAIL(&q->freequeue, &(q->cmds[i]), next);
206
207 q->polling = 0;
208 }
209
210 void
211 pckbport_flush(pckbport_tag_t t, pckbport_slot_t slot)
212 {
213
214 (void)pckbport_poll_data1(t, slot);
215 }
216
217 int
218 pckbport_poll_data(pckbport_tag_t t, pckbport_slot_t slot)
219 {
220 struct pckbport_slotdata *q = t->t_slotdata[slot];
221 int c;
222
223 c = pckbport_poll_data1(t, slot);
224 if (c != -1 && q && CMD_IN_QUEUE(q))
225 /*
226 * we jumped into a running command - try to deliver
227 * the response
228 */
229 if (pckbport_cmdresponse(t, slot, c))
230 return -1;
231 return c;
232 }
233
234 /*
235 * switch scancode translation on / off
236 * return nonzero on success
237 */
238 int
239 pckbport_xt_translation(pckbport_tag_t t, pckbport_slot_t slot, int on)
240 {
241
242 return t->t_ops->t_xt_translation(t->t_cookie, slot, on);
243 }
244
245 void
246 pckbport_slot_enable(pckbport_tag_t t, pckbport_slot_t slot, int on)
247 {
248
249 t->t_ops->t_slot_enable(t->t_cookie, slot, on);
250 }
251
252 void
253 pckbport_set_poll(pckbport_tag_t t, pckbport_slot_t slot, int on)
254 {
255
256 t->t_slotdata[slot]->polling = on;
257 t->t_ops->t_set_poll(t->t_cookie, slot, on);
258 }
259
260 /*
261 * Pass command to device, poll for ACK and data.
262 * to be called at spltty()
263 */
264 static void
265 pckbport_poll_cmd1(struct pckbport_tag *t, pckbport_slot_t slot,
266 struct pckbport_devcmd *cmd)
267 {
268 int i, c = 0;
269
270 while (cmd->cmdidx < cmd->cmdlen) {
271 if (!pckbport_send_devcmd(t, slot, cmd->cmd[cmd->cmdidx])) {
272 printf("pckbport_cmd: send error\n");
273 cmd->status = EIO;
274 return;
275 }
276 for (i = 10; i; i--) { /* 1s ??? */
277 c = pckbport_poll_data1(t, slot);
278 if (c != -1)
279 break;
280 }
281
282 if (c == KBC_DEVCMD_ACK) {
283 cmd->cmdidx++;
284 continue;
285 }
286 if (c == KBC_DEVCMD_RESEND) {
287 #ifdef PCKBPORTDEBUG
288 printf("pckbport_cmd: RESEND\n");
289 #endif
290 if (cmd->retries++ < 5)
291 continue;
292 else {
293 #ifdef PCKBPORTDEBUG
294 printf("pckbport: cmd failed\n");
295 #endif
296 cmd->status = EIO;
297 return;
298 }
299 }
300 if (c == -1) {
301 #ifdef PCKBPORTDEBUG
302 printf("pckbport_cmd: timeout\n");
303 #endif
304 cmd->status = EIO;
305 return;
306 }
307 #ifdef PCKBPORTDEBUG
308 printf("pckbport_cmd: lost 0x%x\n", c);
309 #endif
310 }
311
312 while (cmd->responseidx < cmd->responselen) {
313 if (cmd->flags & KBC_CMDFLAG_SLOW)
314 i = 100; /* 10s ??? */
315 else
316 i = 10; /* 1s ??? */
317 while (i--) {
318 c = pckbport_poll_data1(t, slot);
319 if (c != -1)
320 break;
321 }
322 if (c == -1) {
323 #ifdef PCKBPORTDEBUG
324 printf("pckbport_cmd: no data\n");
325 #endif
326 cmd->status = ETIMEDOUT;
327 return;
328 } else
329 cmd->response[cmd->responseidx++] = c;
330 }
331 }
332
333 /* for use in autoconfiguration */
334 int
335 pckbport_poll_cmd(pckbport_tag_t t, pckbport_slot_t slot, u_char *cmd, int len,
336 int responselen, u_char *respbuf, int slow)
337 {
338 struct pckbport_devcmd nc;
339
340 if ((len > 4) || (responselen > 4))
341 return (EINVAL);
342
343 memset(&nc, 0, sizeof(nc));
344 memcpy(nc.cmd, cmd, len);
345 nc.cmdlen = len;
346 nc.responselen = responselen;
347 nc.flags = (slow ? KBC_CMDFLAG_SLOW : 0);
348
349 pckbport_poll_cmd1(t, slot, &nc);
350
351 if (nc.status == 0 && respbuf)
352 memcpy(respbuf, nc.response, responselen);
353
354 return nc.status;
355 }
356
357 /*
358 * Clean up a command queue, throw away everything.
359 */
360 void
361 pckbport_cleanqueue(struct pckbport_slotdata *q)
362 {
363 struct pckbport_devcmd *cmd;
364 #ifdef PCKBPORTDEBUG
365 int i;
366 #endif
367
368 while ((cmd = TAILQ_FIRST(&q->cmdqueue))) {
369 TAILQ_REMOVE(&q->cmdqueue, cmd, next);
370 #ifdef PCKBPORTDEBUG
371 printf("pckbport_cleanqueue: removing");
372 for (i = 0; i < cmd->cmdlen; i++)
373 printf(" %02x", cmd->cmd[i]);
374 printf("\n");
375 #endif
376 TAILQ_INSERT_TAIL(&q->freequeue, cmd, next);
377 }
378 }
379
380 /*
381 * Timeout error handler: clean queues and data port.
382 * XXX could be less invasive.
383 */
384 void
385 pckbport_cleanup(void *self)
386 {
387 struct pckbport_tag *t = self;
388 int s;
389
390 printf("pckbport: command timeout\n");
391
392 s = spltty();
393
394 if (t->t_slotdata[PCKBPORT_KBD_SLOT])
395 pckbport_cleanqueue(t->t_slotdata[PCKBPORT_KBD_SLOT]);
396 if (t->t_slotdata[PCKBPORT_AUX_SLOT])
397 pckbport_cleanqueue(t->t_slotdata[PCKBPORT_AUX_SLOT]);
398
399 #if 0 /* XXXBJH Move to controller driver? */
400 while (bus_space_read_1(t->t_iot, t->t_ioh_c, 0) & KBS_DIB) {
401 KBD_DELAY;
402 (void) bus_space_read_1(t->t_iot, t->t_ioh_d, 0);
403 }
404 #endif
405
406 /* reset KBC? */
407
408 splx(s);
409 }
410
411 /*
412 * Pass command to device during normal operation.
413 * to be called at spltty()
414 */
415 void
416 pckbport_start(struct pckbport_tag *t, pckbport_slot_t slot)
417 {
418 struct pckbport_slotdata *q = t->t_slotdata[slot];
419 struct pckbport_devcmd *cmd = TAILQ_FIRST(&q->cmdqueue);
420
421 if (q->polling) {
422 do {
423 pckbport_poll_cmd1(t, slot, cmd);
424 if (cmd->status)
425 printf("pckbport_start: command error\n");
426
427 TAILQ_REMOVE(&q->cmdqueue, cmd, next);
428 if (cmd->flags & KBC_CMDFLAG_SYNC)
429 wakeup(cmd);
430 else {
431 callout_stop(&t->t_cleanup);
432 TAILQ_INSERT_TAIL(&q->freequeue, cmd, next);
433 }
434 cmd = TAILQ_FIRST(&q->cmdqueue);
435 } while (cmd);
436 return;
437 }
438
439 if (!pckbport_send_devcmd(t, slot, cmd->cmd[cmd->cmdidx])) {
440 printf("pckbport_start: send error\n");
441 /* XXX what now? */
442 return;
443 }
444 }
445
446 /*
447 * Handle command responses coming in asynchronously,
448 * return nonzero if valid response.
449 * to be called at spltty()
450 */
451 int
452 pckbport_cmdresponse(struct pckbport_tag *t, pckbport_slot_t slot, u_char data)
453 {
454 struct pckbport_slotdata *q = t->t_slotdata[slot];
455 struct pckbport_devcmd *cmd = TAILQ_FIRST(&q->cmdqueue);
456
457 #ifdef DIAGNOSTIC
458 if (!cmd)
459 panic("pckbport_cmdresponse: no active command");
460 #endif
461 if (cmd->cmdidx < cmd->cmdlen) {
462 if (data != KBC_DEVCMD_ACK && data != KBC_DEVCMD_RESEND)
463 return 0;
464
465 if (data == KBC_DEVCMD_RESEND) {
466 if (cmd->retries++ < 5)
467 /* try again last command */
468 goto restart;
469 else {
470 #ifdef PCKBPORTDEBUG
471 printf("pckbport: cmd failed\n");
472 #endif
473 cmd->status = EIO;
474 /* dequeue */
475 }
476 } else {
477 if (++cmd->cmdidx < cmd->cmdlen)
478 goto restart;
479 if (cmd->responselen)
480 return 1;
481 /* else dequeue */
482 }
483 } else if (cmd->responseidx < cmd->responselen) {
484 cmd->response[cmd->responseidx++] = data;
485 if (cmd->responseidx < cmd->responselen)
486 return 1;
487 /* else dequeue */
488 } else
489 return 0;
490
491 /* dequeue: */
492 TAILQ_REMOVE(&q->cmdqueue, cmd, next);
493 if (cmd->flags & KBC_CMDFLAG_SYNC)
494 wakeup(cmd);
495 else {
496 callout_stop(&t->t_cleanup);
497 TAILQ_INSERT_TAIL(&q->freequeue, cmd, next);
498 }
499 if (!CMD_IN_QUEUE(q))
500 return 1;
501 restart:
502 pckbport_start(t, slot);
503 return 1;
504 }
505
506 /*
507 * Put command into the device's command queue, return zero or errno.
508 */
509 int
510 pckbport_enqueue_cmd(pckbport_tag_t t, pckbport_slot_t slot, u_char *cmd,
511 int len, int responselen, int sync, u_char *respbuf)
512 {
513 struct pckbport_slotdata *q = t->t_slotdata[slot];
514 struct pckbport_devcmd *nc;
515 int s, isactive, res = 0;
516
517 if ((len > 4) || (responselen > 4))
518 return EINVAL;
519 s = spltty();
520 nc = TAILQ_FIRST(&q->freequeue);
521 if (nc)
522 TAILQ_REMOVE(&q->freequeue, nc, next);
523 splx(s);
524 if (!nc)
525 return ENOMEM;
526
527 memset(nc, 0, sizeof(*nc));
528 memcpy(nc->cmd, cmd, len);
529 nc->cmdlen = len;
530 nc->responselen = responselen;
531 nc->flags = (sync ? KBC_CMDFLAG_SYNC : 0);
532
533 s = spltty();
534
535 if (q->polling && sync)
536 /*
537 * XXX We should poll until the queue is empty.
538 * But we don't come here normally, so make
539 * it simple and throw away everything.
540 */
541 pckbport_cleanqueue(q);
542
543 isactive = CMD_IN_QUEUE(q);
544 TAILQ_INSERT_TAIL(&q->cmdqueue, nc, next);
545 if (!isactive)
546 pckbport_start(t, slot);
547
548 if (q->polling)
549 res = (sync ? nc->status : 0);
550 else if (sync) {
551 if ((res = tsleep(nc, 0, "kbccmd", 1*hz))) {
552 TAILQ_REMOVE(&q->cmdqueue, nc, next);
553 pckbport_cleanup(t);
554 } else
555 res = nc->status;
556 } else
557 callout_reset(&t->t_cleanup, hz, pckbport_cleanup, t);
558
559 if (sync) {
560 if (respbuf)
561 memcpy(respbuf, nc->response, responselen);
562 TAILQ_INSERT_TAIL(&q->freequeue, nc, next);
563 }
564
565 splx(s);
566
567 return res;
568 }
569
570 void
571 pckbport_set_inputhandler(pckbport_tag_t t, pckbport_slot_t slot,
572 pckbport_inputfcn func, void *arg, char *name)
573 {
574
575 if (slot >= PCKBPORT_NSLOTS)
576 panic("pckbport_set_inputhandler: bad slot %d", slot);
577
578 t->t_ops->t_intr_establish(t->t_cookie, slot);
579
580 t->t_inputhandler[slot] = func;
581 t->t_inputarg[slot] = arg;
582 t->t_subname[slot] = name;
583 }
584
585 void
586 pckbportintr(pckbport_tag_t t, pckbport_slot_t slot, int data)
587 {
588 struct pckbport_slotdata *q;
589
590 q = t->t_slotdata[slot];
591
592 if (!q) {
593 /* XXX do something for live insertion? */
594 printf("pckbportintr: no dev for slot %d\n", slot);
595 return;
596 }
597
598 if (CMD_IN_QUEUE(q) && pckbport_cmdresponse(t, slot, data))
599 return;
600
601 if (t->t_inputhandler[slot])
602 (*t->t_inputhandler[slot])(t->t_inputarg[slot], data);
603 #ifdef PCKBPORTDEBUG
604 else
605 printf("pckbportintr: slot %d lost %d\n", slot, data);
606 #endif
607 }
608
609 int
610 pckbport_cnattach(void *cookie, struct pckbport_accessops const *ops,
611 pckbport_slot_t slot)
612 {
613 int res = 0;
614 pckbport_tag_t t = &pckbport_cntag;
615
616 t->t_cookie = cookie;
617 t->t_ops = ops;
618
619 /* flush */
620 pckbport_flush(t, slot);
621
622 #if (NPCKBD > 0)
623 res = pckbd_cnattach(t, slot);
624 #elif (NPCKBPORT_MACHDEP_CNATTACH > 0)
625 res = pckbport_machdep_cnattach(t, slot);
626 #else
627 res = ENXIO;
628 #endif /* NPCKBPORT_MACHDEP_CNATTACH > 0 */
629
630 if (res == 0) {
631 t->t_slotdata[slot] = &pckbport_cons_slotdata;
632 pckbport_init_slotdata(&pckbport_cons_slotdata);
633 }
634
635 return res;
636 }
637