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