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 */ 63struct 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 */ 75struct 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 85static void pckbport_init_slotdata(struct pckbport_slotdata *); 86static int pckbport_submatch(struct device *, struct cfdata *, void *); 87static int pckbportprint(void *, const char *); 88 89static struct pckbport_slotdata pckbport_cons_slotdata; 90 91static int pckbport_poll_data1(pckbport_tag_t, pckbport_slot_t); 92static int pckbport_send_devcmd(struct pckbport_tag *, pckbport_slot_t, 93 u_char); 94static void pckbport_poll_cmd1(struct pckbport_tag *, pckbport_slot_t, 95 struct pckbport_devcmd *); 96 97static void pckbport_cleanqueue(struct pckbport_slotdata *); 98static void pckbport_cleanup(void *); 99static int pckbport_cmdresponse(struct pckbport_tag *, pckbport_slot_t, 100 u_char); 101static void pckbport_start(struct pckbport_tag *, pckbport_slot_t); 102 103static const char * const pckbport_slot_names[] = { "kbd", "aux" }; 104 105static 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 112static int 113pckbport_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 119static int 120pckbport_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 126static int 127pckbport_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 137pckbport_tag_t 138pckbport_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 152struct device * 153pckbport_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 186int 187pckbportprint(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 196void 197pckbport_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 210void 211pckbport_flush(pckbport_tag_t t, pckbport_slot_t slot) 212{ 213 214 (void)pckbport_poll_data1(t, slot); 215} 216 217int 218pckbport_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 */ 238int 239pckbport_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 245void 246pckbport_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 252void 253pckbport_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 */ 264static void 265pckbport_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 */ 334int 335pckbport_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 */ 360void 361pckbport_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 */ 384void 385pckbport_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 */ 415void 416pckbport_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 */ 451int 452pckbport_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; 501restart: 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 */ 509int 510pckbport_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 570void 571pckbport_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 585void 586pckbportintr(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 609int 610pckbport_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