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