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