/*
* Copyright (c) 2004 Ben Harris
* Copyright (c) 1998
* Matthias Drochner. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
* IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
* THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
/* descriptor for one device command */
struct pckbport_devcmd {
TAILQ_ENTRY(pckbport_devcmd) next;
int flags;
#define KBC_CMDFLAG_SYNC 1 /* give descriptor back to caller */
#define KBC_CMDFLAG_SLOW 2
u_char cmd[4];
int cmdlen, cmdidx, retries;
u_char response[4];
int status, responselen, responseidx;
};
/* data per slave device */
struct pckbport_slotdata {
int polling; /* don't process data in interrupt handler */
TAILQ_HEAD(, pckbport_devcmd) cmdqueue; /* active commands */
TAILQ_HEAD(, pckbport_devcmd) freequeue; /* free commands */
#define NCMD 5
struct pckbport_devcmd cmds[NCMD];
};
int
pckbport_poll_data(pckbport_tag_t t, pckbport_slot_t slot)
{
struct pckbport_slotdata *q = t->t_slotdata[slot];
int c;
c = pckbport_poll_data1(t, slot);
if (c != -1 && q && CMD_IN_QUEUE(q))
/*
* we jumped into a running command - try to deliver
* the response
*/
if (pckbport_cmdresponse(t, slot, c))
return -1;
return c;
}
/*
* switch scancode translation on / off
* return nonzero on success
*/
int
pckbport_xt_translation(pckbport_tag_t t, pckbport_slot_t slot, int on)
{
/*
* Pass command to device, poll for ACK and data.
* to be called at spltty()
*/
static void
pckbport_poll_cmd1(struct pckbport_tag *t, pckbport_slot_t slot,
struct pckbport_devcmd *cmd)
{
int i, c = 0;
while (cmd->cmdidx < cmd->cmdlen) {
if (!pckbport_send_devcmd(t, slot, cmd->cmd[cmd->cmdidx])) {
printf("pckbport_cmd: send error\n");
cmd->status = EIO;
return;
}
for (i = 10; i; i--) { /* 1s ??? */
c = pckbport_poll_data1(t, slot);
if (c != -1)
break;
}
switch (c) {
case KBR_ACK:
cmd->cmdidx++;
continue;
case KBR_BAT_DONE:
case KBR_BAT_FAIL:
case KBR_RESEND:
DPRINTF(("%s: %s\n", __func__, c == KBR_RESEND ?
"RESEND" : (c == KBR_BAT_DONE ? "BAT_DONE" :
"BAT_FAIL")));
if (cmd->retries++ < 5)
continue;
else {
DPRINTF(("%s: cmd failed\n", __func__));
cmd->status = EIO;
return;
}
case -1:
DPRINTF(("%s: timeout\n", __func__));
cmd->status = EIO;
return;
}
DPRINTF(("%s: lost 0x%x\n", __func__, c));
}
while (cmd->responseidx < cmd->responselen) {
if (cmd->flags & KBC_CMDFLAG_SLOW)
i = 100; /* 10s ??? */
else
i = 10; /* 1s ??? */
while (i--) {
c = pckbport_poll_data1(t, slot);
if (c != -1)
break;
}
if (c == -1) {
DPRINTF(("%s: no data\n", __func__));
cmd->status = ETIMEDOUT;
return;
} else
cmd->response[cmd->responseidx++] = c;
}
}
/* for use in autoconfiguration */
int
pckbport_poll_cmd(pckbport_tag_t t, pckbport_slot_t slot, const u_char *cmd,
int len, int responselen, u_char *respbuf, int slow)
{
struct pckbport_devcmd nc;
if ((len > 4) || (responselen > 4))
return (EINVAL);
if (nc.status == 0 && respbuf)
memcpy(respbuf, nc.response, responselen);
return nc.status;
}
/*
* Clean up a command queue, throw away everything.
*/
void
pckbport_cleanqueue(struct pckbport_slotdata *q)
{
struct pckbport_devcmd *cmd;
while ((cmd = TAILQ_FIRST(&q->cmdqueue))) {
TAILQ_REMOVE(&q->cmdqueue, cmd, next);
#ifdef PCKBPORTDEBUG
printf("%s: removing", __func__);
for (int i = 0; i < cmd->cmdlen; i++)
printf(" %02x", cmd->cmd[i]);
printf("\n");
#endif
TAILQ_INSERT_TAIL(&q->freequeue, cmd, next);
}
}
/*
* Timeout error handler: clean queues and data port.
* XXX could be less invasive.
*/
void
pckbport_cleanup(void *self)
{
struct pckbport_tag *t = self;
int s;
u_char cmd[1], resp[2];
printf("pckbport: command timeout\n");
s = spltty();
if (t->t_slotdata[PCKBPORT_KBD_SLOT])
pckbport_cleanqueue(t->t_slotdata[PCKBPORT_KBD_SLOT]);
if (t->t_slotdata[PCKBPORT_AUX_SLOT])
pckbport_cleanqueue(t->t_slotdata[PCKBPORT_AUX_SLOT]);
/*
* Pass command to device during normal operation.
* to be called at spltty()
*/
void
pckbport_start(struct pckbport_tag *t, pckbport_slot_t slot)
{
struct pckbport_slotdata *q = t->t_slotdata[slot];
struct pckbport_devcmd *cmd = TAILQ_FIRST(&q->cmdqueue);
KASSERT(cmd != NULL);
if (q->polling) {
do {
pckbport_poll_cmd1(t, slot, cmd);
if (cmd->status)
printf("pckbport_start: command error\n");
/*
* Put command into the device's command queue, return zero or errno.
*/
int
pckbport_enqueue_cmd(pckbport_tag_t t, pckbport_slot_t slot, const u_char *cmd,
int len, int responselen, int sync, u_char *respbuf)
{
struct pckbport_slotdata *q = t->t_slotdata[slot];
struct pckbport_devcmd *nc;
int s, isactive, res = 0;
if ((len > 4) || (responselen > 4))
return EINVAL;
s = spltty();
nc = TAILQ_FIRST(&q->freequeue);
if (nc)
TAILQ_REMOVE(&q->freequeue, nc, next);
splx(s);
if (!nc)
return ENOMEM;
if (q->polling && sync)
/*
* XXX We should poll until the queue is empty.
* But we don't come here normally, so make
* it simple and throw away everything.
*/
pckbport_cleanqueue(q);
isactive = CMD_IN_QUEUE(q);
TAILQ_INSERT_TAIL(&q->cmdqueue, nc, next);
if (!isactive)
pckbport_start(t, slot);
if (q->polling)
res = (sync ? nc->status : 0);
else if (sync) {
if ((res = tsleep(nc, 0, "kbccmd", 1*hz))) {
TAILQ_REMOVE(&q->cmdqueue, nc, next);
pckbport_cleanup(t);
} else
res = nc->status;
} else
callout_reset(&t->t_cleanup, hz, pckbport_cleanup, t);
if (sync) {
if (respbuf)
memcpy(respbuf, nc->response, responselen);
TAILQ_INSERT_TAIL(&q->freequeue, nc, next);
}