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