445605a6e0
The PS/2 mouse device responds to a reset command with a sequence of ACK(fa), RESULT(aa) and ID(00). Most PS/2 mice immediately returns ACK, but spend sometime before sending RESULT. The Armada takes time before ACK; extra delay is necessary before the call to read ACK. The problem was reported in comp.unix.bsd.freebsd.misc and the patch was tested by the reporter. No PR was filed, by the way.
1050 lines
26 KiB
C
1050 lines
26 KiB
C
/*-
|
|
* Copyright (c) 1996 Kazutaka YOKOTA (yokota@zodiac.mech.utsunomiya-u.ac.jp)
|
|
* 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.
|
|
* 3. The name of the author may not be used to endorse or promote
|
|
* products derived from this software without specific prior written
|
|
* permission.
|
|
*
|
|
* THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``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 REGENTS OR CONTRIBUTORS 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.
|
|
*
|
|
* $FreeBSD$
|
|
*/
|
|
|
|
#include "sc.h"
|
|
#include "psm.h"
|
|
#include "opt_kbdio.h"
|
|
|
|
#include <sys/param.h>
|
|
#include <sys/systm.h>
|
|
#include <sys/syslog.h>
|
|
#include <machine/clock.h>
|
|
#ifdef PC98
|
|
#include <pc98/pc98/pc98.h>
|
|
#else
|
|
#include <i386/isa/isa.h>
|
|
#endif
|
|
#include <i386/isa/isa_device.h>
|
|
#include <i386/isa/kbdio.h>
|
|
|
|
/*
|
|
* driver specific options: the following options may be set by
|
|
* `options' statements in the kernel configuration file.
|
|
*/
|
|
|
|
/* retry count */
|
|
#ifndef KBD_MAXRETRY
|
|
#define KBD_MAXRETRY 3
|
|
#endif
|
|
|
|
/* timing parameters */
|
|
#ifndef KBD_RESETDELAY
|
|
#define KBD_RESETDELAY 200 /* wait 200msec after kbd/mouse reset */
|
|
#endif
|
|
#ifndef KBD_MAXWAIT
|
|
#define KBD_MAXWAIT 5 /* wait 5 times at most after reset */
|
|
#endif
|
|
|
|
/* I/O recovery time */
|
|
#ifdef PC98
|
|
#define KBDC_DELAYTIME 37
|
|
#define KBDD_DELAYTIME 37
|
|
#else
|
|
#define KBDC_DELAYTIME 20
|
|
#define KBDD_DELAYTIME 7
|
|
#endif
|
|
|
|
/* debug option */
|
|
#ifndef KBDIO_DEBUG
|
|
#define KBDIO_DEBUG 0
|
|
#endif
|
|
|
|
/* end of driver specific options */
|
|
|
|
/* constants */
|
|
|
|
#define NKBDC max(NSC, NPSM)
|
|
#define KBDQ_BUFSIZE 32
|
|
|
|
/* macros */
|
|
|
|
#ifndef max
|
|
#define max(x,y) ((x) > (y) ? (x) : (y))
|
|
#endif
|
|
#ifndef min
|
|
#define min(x,y) ((x) < (y) ? (x) : (y))
|
|
#endif
|
|
|
|
#define kbdcp(p) ((struct kbdc_softc *)(p))
|
|
#define nextq(i) (((i) + 1) % KBDQ_BUFSIZE)
|
|
#define availq(q) ((q)->head != (q)->tail)
|
|
#if KBDIO_DEBUG >= 2
|
|
#define emptyq(q) ((q)->tail = (q)->head = (q)->qcount = 0)
|
|
#else
|
|
#define emptyq(q) ((q)->tail = (q)->head = 0)
|
|
#endif
|
|
|
|
/* local variables */
|
|
|
|
typedef struct _kqueue {
|
|
int head;
|
|
int tail;
|
|
unsigned char q[KBDQ_BUFSIZE];
|
|
#if KBDIO_DEBUG >= 2
|
|
int call_count;
|
|
int qcount;
|
|
int max_qcount;
|
|
#endif
|
|
} kqueue;
|
|
|
|
struct kbdc_softc {
|
|
int port; /* base port address */
|
|
int command_byte; /* current command byte value */
|
|
int command_mask; /* command byte mask bits for kbd/aux devices */
|
|
int lock; /* FIXME: XXX not quite a semaphore... */
|
|
kqueue kbd; /* keyboard data queue */
|
|
kqueue aux; /* auxiliary data queue */
|
|
};
|
|
|
|
static struct kbdc_softc kbdc_softc[NKBDC] = { { 0 }, };
|
|
|
|
static int verbose = KBDIO_DEBUG;
|
|
|
|
/* function prototypes */
|
|
|
|
#ifndef PC98
|
|
static int addq(kqueue *q, int c);
|
|
static int removeq(kqueue *q);
|
|
static int wait_while_controller_busy(struct kbdc_softc *kbdc);
|
|
static int wait_for_data(struct kbdc_softc *kbdc);
|
|
#endif
|
|
static int wait_for_kbd_data(struct kbdc_softc *kbdc);
|
|
#ifndef PC98
|
|
static int wait_for_kbd_ack(struct kbdc_softc *kbdc);
|
|
static int wait_for_aux_data(struct kbdc_softc *kbdc);
|
|
static int wait_for_aux_ack(struct kbdc_softc *kbdc);
|
|
#endif
|
|
|
|
/* associate a port number with a KBDC */
|
|
|
|
KBDC
|
|
kbdc_open(int port)
|
|
{
|
|
#ifdef PC98
|
|
if (NKBDC) {
|
|
/* PC-98 has only one keyboard I/F */
|
|
kbdc_softc[0].port = port;
|
|
kbdc_softc[0].lock = FALSE;
|
|
return (KBDC)&kbdc_softc[0];
|
|
}
|
|
return NULL; /* You didn't include sc driver in your config file */
|
|
#else
|
|
int s;
|
|
int i;
|
|
|
|
s = spltty();
|
|
for (i = 0; i < NKBDC; ++i) {
|
|
if (kbdc_softc[i].port == port) {
|
|
splx(s);
|
|
return (KBDC) &kbdc_softc[i];
|
|
}
|
|
if (kbdc_softc[i].port <= 0) {
|
|
kbdc_softc[i].port = port;
|
|
kbdc_softc[i].command_byte = -1;
|
|
kbdc_softc[i].command_mask = 0;
|
|
kbdc_softc[i].lock = FALSE;
|
|
kbdc_softc[i].kbd.head = kbdc_softc[i].kbd.tail = 0;
|
|
kbdc_softc[i].aux.head = kbdc_softc[i].aux.tail = 0;
|
|
#if KBDIO_DEBUG >= 2
|
|
kbdc_softc[i].kbd.call_count = 0;
|
|
kbdc_softc[i].kbd.qcount = kbdc_softc[i].kbd.max_qcount = 0;
|
|
kbdc_softc[i].aux.call_count = 0;
|
|
kbdc_softc[i].aux.qcount = kbdc_softc[i].aux.max_qcount = 0;
|
|
#endif
|
|
splx(s);
|
|
return (KBDC) &kbdc_softc[i];
|
|
}
|
|
}
|
|
splx(s);
|
|
return NULL;
|
|
#endif
|
|
}
|
|
|
|
/*
|
|
* I/O access arbitration in `kbdio'
|
|
*
|
|
* The `kbdio' module uses a simplistic convention to arbitrate
|
|
* I/O access to the controller/keyboard/mouse. The convention requires
|
|
* close cooperation of the calling device driver.
|
|
*
|
|
* The device driver which utilizes the `kbdio' module are assumed to
|
|
* have the following set of routines.
|
|
* a. An interrupt handler (the bottom half of the driver).
|
|
* b. Timeout routines which may briefly polls the keyboard controller.
|
|
* c. Routines outside interrupt context (the top half of the driver).
|
|
* They should follow the rules below:
|
|
* 1. The interrupt handler may assume that it always has full access
|
|
* to the controller/keyboard/mouse.
|
|
* 2. The other routines must issue `spltty()' if they wish to
|
|
* prevent the interrupt handler from accessing
|
|
* the controller/keyboard/mouse.
|
|
* 3. The timeout routines and the top half routines of the device driver
|
|
* arbitrate I/O access by observing the lock flag in `kbdio'.
|
|
* The flag is manipulated via `kbdc_lock()'; when one wants to
|
|
* perform I/O, call `kbdc_lock(kbdc, TRUE)' and proceed only if
|
|
* the call returns with TRUE. Otherwise the caller must back off.
|
|
* Call `kbdc_lock(kbdc, FALSE)' when necessary I/O operaion
|
|
* is finished. This mechanism does not prevent the interrupt
|
|
* handler from being invoked at any time and carrying out I/O.
|
|
* Therefore, `spltty()' must be strategically placed in the device
|
|
* driver code. Also note that the timeout routine may interrupt
|
|
* `kbdc_lock()' called by the top half of the driver, but this
|
|
* interruption is OK so long as the timeout routine observes the
|
|
* the rule 4 below.
|
|
* 4. The interrupt and timeout routines should not extend I/O operation
|
|
* across more than one interrupt or timeout; they must complete
|
|
* necessary I/O operation within one invokation of the routine.
|
|
* This measns that if the timeout routine acquires the lock flag,
|
|
* it must reset the flag to FALSE before it returns.
|
|
*/
|
|
|
|
/* set/reset polling lock */
|
|
int
|
|
kbdc_lock(KBDC p, int lock)
|
|
{
|
|
int prevlock;
|
|
|
|
prevlock = kbdcp(p)->lock;
|
|
kbdcp(p)->lock = lock;
|
|
|
|
return (prevlock != lock);
|
|
}
|
|
|
|
/* check if any data is waiting to be processed */
|
|
int
|
|
kbdc_data_ready(KBDC p)
|
|
{
|
|
#ifdef PC98
|
|
return (inb(kbdcp(p)->port + KBD_STATUS_PORT) & KBDS_ANY_BUFFER_FULL);
|
|
#else
|
|
return (availq(&kbdcp(p)->kbd) || availq(&kbdcp(p)->aux)
|
|
|| (inb(kbdcp(p)->port + KBD_STATUS_PORT) & KBDS_ANY_BUFFER_FULL));
|
|
#endif
|
|
}
|
|
|
|
#ifndef PC98
|
|
/* queuing functions */
|
|
|
|
static int
|
|
addq(kqueue *q, int c)
|
|
{
|
|
if (nextq(q->tail) != q->head) {
|
|
q->q[q->tail] = c;
|
|
q->tail = nextq(q->tail);
|
|
#if KBDIO_DEBUG >= 2
|
|
++q->call_count;
|
|
++q->qcount;
|
|
if (q->qcount > q->max_qcount)
|
|
q->max_qcount = q->qcount;
|
|
#endif
|
|
return TRUE;
|
|
}
|
|
return FALSE;
|
|
}
|
|
|
|
static int
|
|
removeq(kqueue *q)
|
|
{
|
|
int c;
|
|
|
|
if (q->tail != q->head) {
|
|
c = q->q[q->head];
|
|
q->head = nextq(q->head);
|
|
#if KBDIO_DEBUG >= 2
|
|
--q->qcount;
|
|
#endif
|
|
return c;
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
/*
|
|
* device I/O routines
|
|
*/
|
|
static int
|
|
wait_while_controller_busy(struct kbdc_softc *kbdc)
|
|
{
|
|
/* CPU will stay inside the loop for 100msec at most */
|
|
int retry = 5000;
|
|
int port = kbdc->port;
|
|
int f;
|
|
|
|
while ((f = inb(port + KBD_STATUS_PORT)) & KBDS_INPUT_BUFFER_FULL) {
|
|
if ((f & KBDS_BUFFER_FULL) == KBDS_KBD_BUFFER_FULL) {
|
|
DELAY(KBDD_DELAYTIME);
|
|
addq(&kbdc->kbd, inb(port + KBD_DATA_PORT));
|
|
} else if ((f & KBDS_BUFFER_FULL) == KBDS_AUX_BUFFER_FULL) {
|
|
DELAY(KBDD_DELAYTIME);
|
|
addq(&kbdc->aux, inb(port + KBD_DATA_PORT));
|
|
}
|
|
DELAY(KBDC_DELAYTIME);
|
|
if (--retry < 0)
|
|
return FALSE;
|
|
}
|
|
return TRUE;
|
|
}
|
|
|
|
/*
|
|
* wait for any data; whether it's from the controller,
|
|
* the keyboard, or the aux device.
|
|
*/
|
|
static int
|
|
wait_for_data(struct kbdc_softc *kbdc)
|
|
{
|
|
/* CPU will stay inside the loop for 200msec at most */
|
|
int retry = 10000;
|
|
int port = kbdc->port;
|
|
int f;
|
|
|
|
while ((f = inb(port + KBD_STATUS_PORT) & KBDS_ANY_BUFFER_FULL) == 0) {
|
|
DELAY(KBDC_DELAYTIME);
|
|
if (--retry < 0)
|
|
return 0;
|
|
}
|
|
DELAY(KBDD_DELAYTIME);
|
|
return f;
|
|
}
|
|
#endif /* !PC98 */
|
|
|
|
/* wait for data from the keyboard */
|
|
static int
|
|
wait_for_kbd_data(struct kbdc_softc *kbdc)
|
|
{
|
|
/* CPU will stay inside the loop for 200msec at most */
|
|
int retry = 10000;
|
|
int port = kbdc->port;
|
|
int f;
|
|
|
|
while ((f = inb(port + KBD_STATUS_PORT) & KBDS_BUFFER_FULL)
|
|
!= KBDS_KBD_BUFFER_FULL) {
|
|
#ifdef PC98
|
|
DELAY(KBDD_DELAYTIME);
|
|
#else
|
|
if (f == KBDS_AUX_BUFFER_FULL) {
|
|
DELAY(KBDD_DELAYTIME);
|
|
addq(&kbdc->aux, inb(port + KBD_DATA_PORT));
|
|
}
|
|
#endif
|
|
DELAY(KBDC_DELAYTIME);
|
|
if (--retry < 0)
|
|
return 0;
|
|
}
|
|
DELAY(KBDD_DELAYTIME);
|
|
return f;
|
|
}
|
|
|
|
#ifndef PC98
|
|
/*
|
|
* wait for an ACK(FAh), RESEND(FEh), or RESET_FAIL(FCh) from the keyboard.
|
|
* queue anything else.
|
|
*/
|
|
static int
|
|
wait_for_kbd_ack(struct kbdc_softc *kbdc)
|
|
{
|
|
/* CPU will stay inside the loop for 200msec at most */
|
|
int retry = 10000;
|
|
int port = kbdc->port;
|
|
int f;
|
|
int b;
|
|
|
|
while (retry-- > 0) {
|
|
if ((f = inb(port + KBD_STATUS_PORT)) & KBDS_ANY_BUFFER_FULL) {
|
|
DELAY(KBDD_DELAYTIME);
|
|
b = inb(port + KBD_DATA_PORT);
|
|
if ((f & KBDS_BUFFER_FULL) == KBDS_KBD_BUFFER_FULL) {
|
|
if ((b == KBD_ACK) || (b == KBD_RESEND)
|
|
|| (b == KBD_RESET_FAIL))
|
|
return b;
|
|
addq(&kbdc->kbd, b);
|
|
} else if ((f & KBDS_BUFFER_FULL) == KBDS_AUX_BUFFER_FULL) {
|
|
addq(&kbdc->aux, b);
|
|
}
|
|
}
|
|
DELAY(KBDC_DELAYTIME);
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
/* wait for data from the aux device */
|
|
static int
|
|
wait_for_aux_data(struct kbdc_softc *kbdc)
|
|
{
|
|
/* CPU will stay inside the loop for 200msec at most */
|
|
int retry = 10000;
|
|
int port = kbdc->port;
|
|
int f;
|
|
|
|
while ((f = inb(port + KBD_STATUS_PORT) & KBDS_BUFFER_FULL)
|
|
!= KBDS_AUX_BUFFER_FULL) {
|
|
if (f == KBDS_KBD_BUFFER_FULL) {
|
|
DELAY(KBDD_DELAYTIME);
|
|
addq(&kbdc->kbd, inb(port + KBD_DATA_PORT));
|
|
}
|
|
DELAY(KBDC_DELAYTIME);
|
|
if (--retry < 0)
|
|
return 0;
|
|
}
|
|
DELAY(KBDD_DELAYTIME);
|
|
return f;
|
|
}
|
|
|
|
/*
|
|
* wait for an ACK(FAh), RESEND(FEh), or RESET_FAIL(FCh) from the aux device.
|
|
* queue anything else.
|
|
*/
|
|
static int
|
|
wait_for_aux_ack(struct kbdc_softc *kbdc)
|
|
{
|
|
/* CPU will stay inside the loop for 200msec at most */
|
|
int retry = 10000;
|
|
int port = kbdc->port;
|
|
int f;
|
|
int b;
|
|
|
|
while (retry-- > 0) {
|
|
if ((f = inb(port + KBD_STATUS_PORT)) & KBDS_ANY_BUFFER_FULL) {
|
|
DELAY(KBDD_DELAYTIME);
|
|
b = inb(port + KBD_DATA_PORT);
|
|
if ((f & KBDS_BUFFER_FULL) == KBDS_AUX_BUFFER_FULL) {
|
|
if ((b == PSM_ACK) || (b == PSM_RESEND)
|
|
|| (b == PSM_RESET_FAIL))
|
|
return b;
|
|
addq(&kbdc->aux, b);
|
|
} else if ((f & KBDS_BUFFER_FULL) == KBDS_KBD_BUFFER_FULL) {
|
|
addq(&kbdc->kbd, b);
|
|
}
|
|
}
|
|
DELAY(KBDC_DELAYTIME);
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
/* write a one byte command to the controller */
|
|
int
|
|
write_controller_command(KBDC p, int c)
|
|
{
|
|
if (!wait_while_controller_busy(kbdcp(p)))
|
|
return FALSE;
|
|
outb(kbdcp(p)->port + KBD_COMMAND_PORT, c);
|
|
return TRUE;
|
|
}
|
|
|
|
/* write a one byte data to the controller */
|
|
int
|
|
write_controller_data(KBDC p, int c)
|
|
{
|
|
if (!wait_while_controller_busy(kbdcp(p)))
|
|
return FALSE;
|
|
outb(kbdcp(p)->port + KBD_DATA_PORT, c);
|
|
return TRUE;
|
|
}
|
|
|
|
/* write a one byte keyboard command */
|
|
int
|
|
write_kbd_command(KBDC p, int c)
|
|
{
|
|
if (!wait_while_controller_busy(kbdcp(p)))
|
|
return FALSE;
|
|
outb(kbdcp(p)->port + KBD_DATA_PORT, c);
|
|
return TRUE;
|
|
}
|
|
|
|
/* write a one byte auxiliary device command */
|
|
int
|
|
write_aux_command(KBDC p, int c)
|
|
{
|
|
if (!write_controller_command(p, KBDC_WRITE_TO_AUX))
|
|
return FALSE;
|
|
return write_controller_data(p, c);
|
|
}
|
|
|
|
/* send a command to the keyboard and wait for ACK */
|
|
int
|
|
send_kbd_command(KBDC p, int c)
|
|
{
|
|
int retry = KBD_MAXRETRY;
|
|
int res = -1;
|
|
|
|
while (retry-- > 0) {
|
|
if (!write_kbd_command(p, c))
|
|
continue;
|
|
res = wait_for_kbd_ack(kbdcp(p));
|
|
if (res == KBD_ACK)
|
|
break;
|
|
}
|
|
return res;
|
|
}
|
|
|
|
/* send a command to the auxiliary device and wait for ACK */
|
|
int
|
|
send_aux_command(KBDC p, int c)
|
|
{
|
|
int retry = KBD_MAXRETRY;
|
|
int res = -1;
|
|
|
|
while (retry-- > 0) {
|
|
if (!write_aux_command(p, c))
|
|
continue;
|
|
/*
|
|
* FIXME: XXX
|
|
* The aux device may have already sent one or two bytes of
|
|
* status data, when a command is received. It will immediately
|
|
* stop data transmission, thus, leaving an incomplete data
|
|
* packet in our buffer. We have to discard any unprocessed
|
|
* data in order to remove such packets. Well, we may remove
|
|
* unprocessed, but necessary data byte as well...
|
|
*/
|
|
emptyq(&kbdcp(p)->aux);
|
|
res = wait_for_aux_ack(kbdcp(p));
|
|
if (res == PSM_ACK)
|
|
break;
|
|
}
|
|
return res;
|
|
}
|
|
|
|
/* send a command and a data to the keyboard, wait for ACKs */
|
|
int
|
|
send_kbd_command_and_data(KBDC p, int c, int d)
|
|
{
|
|
int retry;
|
|
int res = -1;
|
|
|
|
for (retry = KBD_MAXRETRY; retry > 0; --retry) {
|
|
if (!write_kbd_command(p, c))
|
|
continue;
|
|
res = wait_for_kbd_ack(kbdcp(p));
|
|
if (res == KBD_ACK)
|
|
break;
|
|
else if (res != KBD_RESEND)
|
|
return res;
|
|
}
|
|
if (retry <= 0)
|
|
return res;
|
|
|
|
for (retry = KBD_MAXRETRY, res = -1; retry > 0; --retry) {
|
|
if (!write_kbd_command(p, d))
|
|
continue;
|
|
res = wait_for_kbd_ack(kbdcp(p));
|
|
if (res != KBD_RESEND)
|
|
break;
|
|
}
|
|
return res;
|
|
}
|
|
|
|
/* send a command and a data to the auxiliary device, wait for ACKs */
|
|
int
|
|
send_aux_command_and_data(KBDC p, int c, int d)
|
|
{
|
|
int retry;
|
|
int res = -1;
|
|
|
|
for (retry = KBD_MAXRETRY; retry > 0; --retry) {
|
|
if (!write_aux_command(p, c))
|
|
continue;
|
|
emptyq(&kbdcp(p)->aux);
|
|
res = wait_for_aux_ack(kbdcp(p));
|
|
if (res == PSM_ACK)
|
|
break;
|
|
else if (res != PSM_RESEND)
|
|
return res;
|
|
}
|
|
if (retry <= 0)
|
|
return res;
|
|
|
|
for (retry = KBD_MAXRETRY, res = -1; retry > 0; --retry) {
|
|
if (!write_aux_command(p, d))
|
|
continue;
|
|
res = wait_for_aux_ack(kbdcp(p));
|
|
if (res != PSM_RESEND)
|
|
break;
|
|
}
|
|
return res;
|
|
}
|
|
|
|
/*
|
|
* read one byte from any source; whether from the controller,
|
|
* the keyboard, or the aux device
|
|
*/
|
|
int
|
|
read_controller_data(KBDC p)
|
|
{
|
|
if (availq(&kbdcp(p)->kbd))
|
|
return removeq(&kbdcp(p)->kbd);
|
|
if (availq(&kbdcp(p)->aux))
|
|
return removeq(&kbdcp(p)->aux);
|
|
if (!wait_for_data(kbdcp(p)))
|
|
return -1; /* timeout */
|
|
return inb(kbdcp(p)->port + KBD_DATA_PORT);
|
|
}
|
|
#endif /* !PC98 */
|
|
|
|
#if KBDIO_DEBUG >= 2
|
|
static int call = 0;
|
|
#endif
|
|
|
|
/* read one byte from the keyboard */
|
|
int
|
|
read_kbd_data(KBDC p)
|
|
{
|
|
#ifndef PC98
|
|
#if KBDIO_DEBUG >= 2
|
|
if (++call > 2000) {
|
|
call = 0;
|
|
log(LOG_DEBUG, "KBDIO: kbd q: %d calls, max %d chars, "
|
|
"aux q: %d calls, max %d chars\n",
|
|
kbdcp(p)->kbd.call_count, kbdcp(p)->kbd.max_qcount,
|
|
kbdcp(p)->aux.call_count, kbdcp(p)->aux.max_qcount);
|
|
}
|
|
#endif
|
|
|
|
if (availq(&kbdcp(p)->kbd))
|
|
return removeq(&kbdcp(p)->kbd);
|
|
#endif /* !PC98 */
|
|
if (!wait_for_kbd_data(kbdcp(p)))
|
|
return -1; /* timeout */
|
|
#ifdef PC98
|
|
DELAY(KBDC_DELAYTIME);
|
|
#endif
|
|
return inb(kbdcp(p)->port + KBD_DATA_PORT);
|
|
}
|
|
|
|
/* read one byte from the keyboard, but return immediately if
|
|
* no data is waiting
|
|
*/
|
|
int
|
|
read_kbd_data_no_wait(KBDC p)
|
|
{
|
|
int f;
|
|
|
|
#ifdef PC98
|
|
f = inb(kbdcp(p)->port + KBD_STATUS_PORT) & KBDS_BUFFER_FULL;
|
|
#else
|
|
#if KBDIO_DEBUG >= 2
|
|
if (++call > 2000) {
|
|
call = 0;
|
|
log(LOG_DEBUG, "KBDIO: kbd q: %d calls, max %d chars, "
|
|
"aux q: %d calls, max %d chars\n",
|
|
kbdcp(p)->kbd.call_count, kbdcp(p)->kbd.max_qcount,
|
|
kbdcp(p)->aux.call_count, kbdcp(p)->aux.max_qcount);
|
|
}
|
|
#endif
|
|
|
|
if (availq(&kbdcp(p)->kbd))
|
|
return removeq(&kbdcp(p)->kbd);
|
|
f = inb(kbdcp(p)->port + KBD_STATUS_PORT) & KBDS_BUFFER_FULL;
|
|
if (f == KBDS_AUX_BUFFER_FULL) {
|
|
DELAY(KBDD_DELAYTIME);
|
|
addq(&kbdcp(p)->aux, inb(kbdcp(p)->port + KBD_DATA_PORT));
|
|
f = inb(kbdcp(p)->port + KBD_STATUS_PORT) & KBDS_BUFFER_FULL;
|
|
}
|
|
#endif /* PC98 */
|
|
if (f == KBDS_KBD_BUFFER_FULL) {
|
|
DELAY(KBDD_DELAYTIME);
|
|
return inb(kbdcp(p)->port + KBD_DATA_PORT);
|
|
}
|
|
return -1; /* no data */
|
|
}
|
|
|
|
#ifndef PC98
|
|
/* read one byte from the aux device */
|
|
int
|
|
read_aux_data(KBDC p)
|
|
{
|
|
if (availq(&kbdcp(p)->aux))
|
|
return removeq(&kbdcp(p)->aux);
|
|
if (!wait_for_aux_data(kbdcp(p)))
|
|
return -1; /* timeout */
|
|
return inb(kbdcp(p)->port + KBD_DATA_PORT);
|
|
}
|
|
|
|
/* read one byte from the aux device, but return immediately if
|
|
* no data is waiting
|
|
*/
|
|
int
|
|
read_aux_data_no_wait(KBDC p)
|
|
{
|
|
int f;
|
|
|
|
if (availq(&kbdcp(p)->aux))
|
|
return removeq(&kbdcp(p)->aux);
|
|
f = inb(kbdcp(p)->port + KBD_STATUS_PORT) & KBDS_BUFFER_FULL;
|
|
if (f == KBDS_KBD_BUFFER_FULL) {
|
|
DELAY(KBDD_DELAYTIME);
|
|
addq(&kbdcp(p)->kbd, inb(kbdcp(p)->port + KBD_DATA_PORT));
|
|
f = inb(kbdcp(p)->port + KBD_STATUS_PORT) & KBDS_BUFFER_FULL;
|
|
}
|
|
if (f == KBDS_AUX_BUFFER_FULL) {
|
|
DELAY(KBDD_DELAYTIME);
|
|
return inb(kbdcp(p)->port + KBD_DATA_PORT);
|
|
}
|
|
return -1; /* no data */
|
|
}
|
|
|
|
/* discard data from the keyboard */
|
|
void
|
|
empty_kbd_buffer(KBDC p, int wait)
|
|
{
|
|
int t;
|
|
int b;
|
|
int f;
|
|
#if KBDIO_DEBUG >= 2
|
|
int c1 = 0;
|
|
int c2 = 0;
|
|
#endif
|
|
int delta = 2;
|
|
|
|
for (t = wait; t > 0; ) {
|
|
if ((f = inb(kbdcp(p)->port + KBD_STATUS_PORT)) & KBDS_ANY_BUFFER_FULL) {
|
|
DELAY(KBDD_DELAYTIME);
|
|
b = inb(kbdcp(p)->port + KBD_DATA_PORT);
|
|
if ((f & KBDS_BUFFER_FULL) == KBDS_AUX_BUFFER_FULL) {
|
|
addq(&kbdcp(p)->aux, b);
|
|
#if KBDIO_DEBUG >= 2
|
|
++c2;
|
|
} else {
|
|
++c1;
|
|
#endif
|
|
}
|
|
t = wait;
|
|
} else {
|
|
t -= delta;
|
|
}
|
|
DELAY(delta*1000);
|
|
}
|
|
#if KBDIO_DEBUG >= 2
|
|
if ((c1 > 0) || (c2 > 0))
|
|
log(LOG_DEBUG, "kbdio: %d:%d char read (empty_kbd_buffer)\n", c1, c2);
|
|
#endif
|
|
|
|
emptyq(&kbdcp(p)->kbd);
|
|
}
|
|
|
|
/* discard data from the aux device */
|
|
void
|
|
empty_aux_buffer(KBDC p, int wait)
|
|
{
|
|
int t;
|
|
int b;
|
|
int f;
|
|
#if KBDIO_DEBUG >= 2
|
|
int c1 = 0;
|
|
int c2 = 0;
|
|
#endif
|
|
int delta = 2;
|
|
|
|
for (t = wait; t > 0; ) {
|
|
if ((f = inb(kbdcp(p)->port + KBD_STATUS_PORT)) & KBDS_ANY_BUFFER_FULL) {
|
|
DELAY(KBDD_DELAYTIME);
|
|
b = inb(kbdcp(p)->port + KBD_DATA_PORT);
|
|
if ((f & KBDS_BUFFER_FULL) == KBDS_KBD_BUFFER_FULL) {
|
|
addq(&kbdcp(p)->kbd, b);
|
|
#if KBDIO_DEBUG >= 2
|
|
++c1;
|
|
} else {
|
|
++c2;
|
|
#endif
|
|
}
|
|
t = wait;
|
|
} else {
|
|
t -= delta;
|
|
}
|
|
DELAY(delta*1000);
|
|
}
|
|
#if KBDIO_DEBUG >= 2
|
|
if ((c1 > 0) || (c2 > 0))
|
|
log(LOG_DEBUG, "kbdio: %d:%d char read (empty_aux_buffer)\n", c1, c2);
|
|
#endif
|
|
|
|
emptyq(&kbdcp(p)->aux);
|
|
}
|
|
|
|
/* discard any data from the keyboard or the aux device */
|
|
void
|
|
empty_both_buffers(KBDC p, int wait)
|
|
{
|
|
int t;
|
|
int f;
|
|
#if KBDIO_DEBUG >= 2
|
|
int c1 = 0;
|
|
int c2 = 0;
|
|
#endif
|
|
int delta = 2;
|
|
|
|
for (t = wait; t > 0; ) {
|
|
if ((f = inb(kbdcp(p)->port + KBD_STATUS_PORT)) & KBDS_ANY_BUFFER_FULL) {
|
|
DELAY(KBDD_DELAYTIME);
|
|
(void)inb(kbdcp(p)->port + KBD_DATA_PORT);
|
|
#if KBDIO_DEBUG >= 2
|
|
if ((f & KBDS_BUFFER_FULL) == KBDS_KBD_BUFFER_FULL)
|
|
++c1;
|
|
else
|
|
++c2;
|
|
#endif
|
|
t = wait;
|
|
} else {
|
|
t -= delta;
|
|
}
|
|
DELAY(delta*1000);
|
|
}
|
|
#if KBDIO_DEBUG >= 2
|
|
if ((c1 > 0) || (c2 > 0))
|
|
log(LOG_DEBUG, "kbdio: %d:%d char read (empty_both_buffers)\n", c1, c2);
|
|
#endif
|
|
|
|
emptyq(&kbdcp(p)->kbd);
|
|
emptyq(&kbdcp(p)->aux);
|
|
}
|
|
|
|
/* keyboard and mouse device control */
|
|
|
|
/* NOTE: enable the keyboard port but disable the keyboard
|
|
* interrupt before calling "reset_kbd()".
|
|
*/
|
|
int
|
|
reset_kbd(KBDC p)
|
|
{
|
|
int retry = KBD_MAXRETRY;
|
|
int again = KBD_MAXWAIT;
|
|
int c = KBD_RESEND; /* keep the compiler happy */
|
|
|
|
while (retry-- > 0) {
|
|
empty_both_buffers(p, 10);
|
|
if (!write_kbd_command(p, KBDC_RESET_KBD))
|
|
continue;
|
|
emptyq(&kbdcp(p)->kbd);
|
|
c = read_controller_data(p);
|
|
if (verbose || bootverbose)
|
|
log(LOG_DEBUG, "kbdio: RESET_KBD return code:%04x\n", c);
|
|
if (c == KBD_ACK) /* keyboard has agreed to reset itself... */
|
|
break;
|
|
}
|
|
if (retry < 0)
|
|
return FALSE;
|
|
|
|
while (again-- > 0) {
|
|
/* wait awhile, well, in fact we must wait quite loooooooooooong */
|
|
DELAY(KBD_RESETDELAY*1000);
|
|
c = read_controller_data(p); /* RESET_DONE/RESET_FAIL */
|
|
if (c != -1) /* wait again if the controller is not ready */
|
|
break;
|
|
}
|
|
if (verbose || bootverbose)
|
|
log(LOG_DEBUG, "kbdio: RESET_KBD status:%04x\n", c);
|
|
if (c != KBD_RESET_DONE)
|
|
return FALSE;
|
|
return TRUE;
|
|
}
|
|
|
|
/* NOTE: enable the aux port but disable the aux interrupt
|
|
* before calling `reset_aux_dev()'.
|
|
*/
|
|
int
|
|
reset_aux_dev(KBDC p)
|
|
{
|
|
int retry = KBD_MAXRETRY;
|
|
int again = KBD_MAXWAIT;
|
|
int c = PSM_RESEND; /* keep the compiler happy */
|
|
|
|
while (retry-- > 0) {
|
|
empty_both_buffers(p, 10);
|
|
if (!write_aux_command(p, PSMC_RESET_DEV))
|
|
continue;
|
|
emptyq(&kbdcp(p)->aux);
|
|
/* NOTE: Compaq Armada laptops require extra delay here. XXX */
|
|
for (again = KBD_MAXWAIT; again > 0; --again) {
|
|
DELAY(KBD_RESETDELAY*1000);
|
|
c = read_aux_data_no_wait(p);
|
|
if (c != -1)
|
|
break;
|
|
}
|
|
if (verbose || bootverbose)
|
|
log(LOG_DEBUG, "kbdio: RESET_AUX return code:%04x\n", c);
|
|
if (c == PSM_ACK) /* aux dev is about to reset... */
|
|
break;
|
|
}
|
|
if (retry < 0)
|
|
return FALSE;
|
|
|
|
for (again = KBD_MAXWAIT; again > 0; --again) {
|
|
/* wait awhile, well, quite looooooooooooong */
|
|
DELAY(KBD_RESETDELAY*1000);
|
|
c = read_aux_data_no_wait(p); /* RESET_DONE/RESET_FAIL */
|
|
if (c != -1) /* wait again if the controller is not ready */
|
|
break;
|
|
}
|
|
if (verbose || bootverbose)
|
|
log(LOG_DEBUG, "kbdio: RESET_AUX status:%04x\n", c);
|
|
if (c != PSM_RESET_DONE) /* reset status */
|
|
return FALSE;
|
|
|
|
c = read_aux_data(p); /* device ID */
|
|
if (verbose || bootverbose)
|
|
log(LOG_DEBUG, "kbdio: RESET_AUX ID:%04x\n", c);
|
|
/* NOTE: we could check the device ID now, but leave it later... */
|
|
return TRUE;
|
|
}
|
|
|
|
/* controller diagnostics and setup */
|
|
|
|
int
|
|
test_controller(KBDC p)
|
|
{
|
|
int retry = KBD_MAXRETRY;
|
|
int again = KBD_MAXWAIT;
|
|
int c = KBD_DIAG_FAIL;
|
|
|
|
while (retry-- > 0) {
|
|
empty_both_buffers(p, 10);
|
|
if (write_controller_command(p, KBDC_DIAGNOSE))
|
|
break;
|
|
}
|
|
if (retry < 0)
|
|
return FALSE;
|
|
|
|
emptyq(&kbdcp(p)->kbd);
|
|
while (again-- > 0) {
|
|
/* wait awhile */
|
|
DELAY(KBD_RESETDELAY*1000);
|
|
c = read_controller_data(p); /* DIAG_DONE/DIAG_FAIL */
|
|
if (c != -1) /* wait again if the controller is not ready */
|
|
break;
|
|
}
|
|
if (verbose || bootverbose)
|
|
log(LOG_DEBUG, "kbdio: DIAGNOSE status:%04x\n", c);
|
|
return (c == KBD_DIAG_DONE);
|
|
}
|
|
|
|
int
|
|
test_kbd_port(KBDC p)
|
|
{
|
|
int retry = KBD_MAXRETRY;
|
|
int again = KBD_MAXWAIT;
|
|
int c = -1;
|
|
|
|
while (retry-- > 0) {
|
|
empty_both_buffers(p, 10);
|
|
if (write_controller_command(p, KBDC_TEST_KBD_PORT))
|
|
break;
|
|
}
|
|
if (retry < 0)
|
|
return FALSE;
|
|
|
|
emptyq(&kbdcp(p)->kbd);
|
|
while (again-- > 0) {
|
|
c = read_controller_data(p);
|
|
if (c != -1) /* try again if the controller is not ready */
|
|
break;
|
|
}
|
|
if (verbose || bootverbose)
|
|
log(LOG_DEBUG, "kbdio: TEST_KBD_PORT status:%04x\n", c);
|
|
return c;
|
|
}
|
|
|
|
int
|
|
test_aux_port(KBDC p)
|
|
{
|
|
int retry = KBD_MAXRETRY;
|
|
int again = KBD_MAXWAIT;
|
|
int c = -1;
|
|
|
|
while (retry-- > 0) {
|
|
empty_both_buffers(p, 10);
|
|
if (write_controller_command(p, KBDC_TEST_AUX_PORT))
|
|
break;
|
|
}
|
|
if (retry < 0)
|
|
return FALSE;
|
|
|
|
emptyq(&kbdcp(p)->kbd);
|
|
while (again-- > 0) {
|
|
c = read_controller_data(p);
|
|
if (c != -1) /* try again if the controller is not ready */
|
|
break;
|
|
}
|
|
if (verbose || bootverbose)
|
|
log(LOG_DEBUG, "kbdio: TEST_AUX_PORT status:%04x\n", c);
|
|
return c;
|
|
}
|
|
|
|
int
|
|
kbdc_get_device_mask(KBDC p)
|
|
{
|
|
return kbdcp(p)->command_mask;
|
|
}
|
|
|
|
void
|
|
kbdc_set_device_mask(KBDC p, int mask)
|
|
{
|
|
kbdcp(p)->command_mask =
|
|
mask & (KBD_KBD_CONTROL_BITS | KBD_AUX_CONTROL_BITS);
|
|
}
|
|
|
|
int
|
|
get_controller_command_byte(KBDC p)
|
|
{
|
|
if (kbdcp(p)->command_byte != -1)
|
|
return kbdcp(p)->command_byte;
|
|
if (!write_controller_command(p, KBDC_GET_COMMAND_BYTE))
|
|
return -1;
|
|
emptyq(&kbdcp(p)->kbd);
|
|
kbdcp(p)->command_byte = read_controller_data(p);
|
|
return kbdcp(p)->command_byte;
|
|
}
|
|
|
|
int
|
|
set_controller_command_byte(KBDC p, int mask, int command)
|
|
{
|
|
if (get_controller_command_byte(p) == -1)
|
|
return FALSE;
|
|
|
|
command = (kbdcp(p)->command_byte & ~mask) | (command & mask);
|
|
#if 0
|
|
if (command == kbdcp(p)->command_byte)
|
|
return TRUE;
|
|
#endif
|
|
if (command & KBD_DISABLE_KBD_PORT) {
|
|
if (!write_controller_command(p, KBDC_DISABLE_KBD_PORT))
|
|
return FALSE;
|
|
}
|
|
if (!write_controller_command(p, KBDC_SET_COMMAND_BYTE))
|
|
return FALSE;
|
|
if (!write_controller_data(p, command))
|
|
return FALSE;
|
|
kbdcp(p)->command_byte = command;
|
|
|
|
if (verbose || bootverbose)
|
|
log(LOG_DEBUG, "kbdio: new command byte:%04x (set_controller...)\n",
|
|
command);
|
|
|
|
return TRUE;
|
|
}
|
|
#endif /* !PC98 */
|