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