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