NetBSD/sys/arch/amiga/dev/kbd.c
jdolecek e0cc03a09b merge kqueue branch into -current
kqueue provides a stateful and efficient event notification framework
currently supported events include socket, file, directory, fifo,
pipe, tty and device changes, and monitoring of processes and signals

kqueue is supported by all writable filesystems in NetBSD tree
(with exception of Coda) and all device drivers supporting poll(2)

based on work done by Jonathan Lemon for FreeBSD
initial NetBSD port done by Luke Mewburn and Jason Thorpe
2002-10-23 09:10:23 +00:00

808 lines
17 KiB
C

/* $NetBSD: kbd.c,v 1.43 2002/10/23 09:10:33 jdolecek Exp $ */
/*
* Copyright (c) 1982, 1986, 1990 The Regents of the University of California.
* 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. All advertising materials mentioning features or use of this software
* must display the following acknowledgement:
* This product includes software developed by the University of
* California, Berkeley and its contributors.
* 4. Neither the name of the University nor the names of its contributors
* may 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.
*
* kbd.c
*/
#include <sys/cdefs.h>
__KERNEL_RCSID(0, "$NetBSD: kbd.c,v 1.43 2002/10/23 09:10:33 jdolecek Exp $");
#include <sys/param.h>
#include <sys/systm.h>
#include <sys/device.h>
#include <sys/ioctl.h>
#include <sys/tty.h>
#include <sys/proc.h>
#include <sys/file.h>
#include <sys/kernel.h>
#include <sys/syslog.h>
#include <sys/signalvar.h>
#include <sys/conf.h>
#include <dev/cons.h>
#include <machine/cpu.h>
#include <amiga/amiga/device.h>
#include <amiga/amiga/custom.h>
#ifdef DRACO
#include <m68k/asm_single.h>
#include <amiga/amiga/drcustom.h>
#endif
#include <amiga/amiga/cia.h>
#include <amiga/dev/itevar.h>
#include <amiga/dev/kbdreg.h>
#include <amiga/dev/kbdmap.h>
#include <amiga/dev/event_var.h>
#include <amiga/dev/vuid_event.h>
#include "kbd.h"
#include "ite.h"
/* WSKBD */
/*
* If NWSKBD>0 we try to attach an wskbd device to us. What follows
* is definitions of callback functions and structures that are passed
* to wscons when initializing.
*/
/*
* Now with wscons this driver exhibits some weird behaviour.
* It may act both as a driver of its own and the md part of the
* wskbd driver. Therefore it can be accessed through /dev/kbd
* and /dev/wskbd0 both.
*
* The data from they keyboard may end up in at least four different
* places:
* - If this driver has been opened (/dev/kbd) and the
* direct mode (TIOCDIRECT) has been set, data goes to
* the process who opened the device. Data will transmit itself
* as described by the firm_event structure.
* - If wskbd support is compiled in and a wskbd driver has been
* attached then the data is sent to it. Wskbd in turn may
* - Send the data in the wscons_event form to a process that
* has opened /dev/wskbd0
* - Feed the data to a virtual terminal.
* - If an ite is present the data may be fed to it.
*/
#include "wskbd.h"
#if NWSKBD>0
#include <dev/wscons/wsconsio.h>
#include <dev/wscons/wskbdvar.h>
#include <dev/wscons/wsksymdef.h>
#include <dev/wscons/wsksymvar.h>
#include <amiga/dev/wskbdmap_amiga.h>
/* accessops */
int kbd_enable(void *, int);
void kbd_set_leds(void *, int);
int kbd_ioctl(void *, u_long, caddr_t, int, struct proc *);
/* console ops */
void kbd_getc(void *, u_int *, int *);
void kbd_pollc(void *, int);
void kbd_bell(void *, u_int, u_int, u_int);
static struct wskbd_accessops kbd_accessops = {
kbd_enable,
kbd_set_leds,
kbd_ioctl
};
static struct wskbd_consops kbd_consops = {
kbd_getc,
kbd_pollc,
kbd_bell
};
/*
* Pointer to keymaps. They are defined in wskbdmap_amiga.c.
*/
static struct wskbd_mapdata kbd_mapdata = {
amigakbd_keydesctab,
KB_US
};
#endif /* WSKBD */
struct kbd_softc {
int k_event_mode; /* if true, collect events, else pass to ite */
struct evvar k_events; /* event queue state */
#ifdef DRACO
u_char k_rlprfx; /* MF-II rel. prefix has been seen */
u_char k_mf2;
#endif
#if NWSKBD>0
struct device *k_wskbddev; /* pointer to wskbd for sending strokes */
int k_pollingmode; /* polling mode on? whatever it isss... */
#endif
};
struct kbd_softc kbd_softc;
int kbdmatch(struct device *, struct cfdata *, void *);
void kbdattach(struct device *, struct device *, void *);
void kbdintr(int);
void kbdstuffchar(u_char);
int drkbdgetc(void);
int drkbdrputc(u_int8_t);
int drkbdputc(u_int8_t);
int drkbdputc2(u_int8_t, u_int8_t);
int drkbdwaitfor(int);
CFATTACH_DECL(kbd, sizeof(struct device),
kbdmatch, kbdattach, NULL, NULL);
dev_type_open(kbdopen);
dev_type_close(kbdclose);
dev_type_read(kbdread);
dev_type_ioctl(kbdioctl);
dev_type_poll(kbdpoll);
dev_type_kqfilter(kbdkqfilter);
const struct cdevsw kbd_cdevsw = {
kbdopen, kbdclose, kbdread, nowrite, kbdioctl,
nostop, notty, kbdpoll, nommap, kbdkqfilter,
};
/*ARGSUSED*/
int
kbdmatch(struct device *pdp, struct cfdata *cfp, void *auxp)
{
if (matchname((char *)auxp, "kbd"))
return(1);
return(0);
}
/*ARGSUSED*/
void
kbdattach(struct device *pdp, struct device *dp, void *auxp)
{
#ifdef DRACO
kbdenable();
if (kbd_softc.k_mf2)
printf(": QuickLogic type MF-II\n");
else
printf(": CIA A type Amiga\n");
#else
printf(": CIA A type Amiga\n");
#endif
#if NWSKBD>0
if (dp != NULL) {
/*
* Try to attach the wskbd.
*/
struct wskbddev_attach_args waa;
/* Maybe should be done before this?... */
wskbd_cnattach(&kbd_consops, NULL, &kbd_mapdata);
waa.console = 1;
waa.keymap = &kbd_mapdata;
waa.accessops = &kbd_accessops;
waa.accesscookie = NULL;
kbd_softc.k_wskbddev = config_found(dp, &waa, wskbddevprint);
kbd_softc.k_pollingmode = 0;
}
kbdenable();
#endif /* WSKBD */
}
/* definitions for amiga keyboard encoding. */
#define KEY_CODE(c) ((c) & 0x7f)
#define KEY_UP(c) ((c) & 0x80)
#define DATLO single_inst_bclr_b(draco_ioct->io_control, DRCNTRL_KBDDATOUT)
#define DATHI single_inst_bset_b(draco_ioct->io_control, DRCNTRL_KBDDATOUT)
#define CLKLO single_inst_bclr_b(draco_ioct->io_control, DRCNTRL_KBDCLKOUT)
#define CLKHI single_inst_bset_b(draco_ioct->io_control, DRCNTRL_KBDCLKOUT)
void
kbdenable(void)
{
static int kbd_inited = 0;
int s;
#ifdef DRACO
int id;
#endif
/*
* collides with external ints from SCSI, watch out for this when
* enabling/disabling interrupts there !!
*/
s = splhigh(); /* don't lower; might be called from early ddb */
if (kbd_inited) {
splx(s);
return;
}
kbd_inited = 1;
#ifdef DRACO
if (is_draco()) {
CLKLO;
delay(5000);
draco_ioct->io_kbdrst = 0;
if (drkbdputc(0xf2))
goto LnoMFII;
id = drkbdgetc() << 8;
id |= drkbdgetc();
if (id != 0xab83)
goto LnoMFII;
if (drkbdputc2(0xf0, 3)) /* mode 3 */
goto LnoMFII;
if (drkbdputc(0xf8)) /* make/break, no typematic */
goto LnoMFII;
if (drkbdputc(0xf4)) /* enable */
goto LnoMFII;
kbd_softc.k_mf2 = 1;
single_inst_bclr_b(draco_ioct->io_control, DRCNTRL_KBDINTENA);
ciaa.icr = CIA_ICR_SP; /* CIA SP interrupt disable */
ciaa.cra &= ~(1<<6); /* serial line == input */
splx(s);
return;
LnoMFII:
kbd_softc.k_mf2 = 0;
single_inst_bset_b(*draco_intena, DRIRQ_INT2);
ciaa.icr = CIA_ICR_IR_SC | CIA_ICR_SP;
/* SP interrupt enable */
ciaa.cra &= ~(1<<6); /* serial line == input */
splx(s);
return;
} else {
#endif
custom.intena = INTF_SETCLR | INTF_PORTS;
ciaa.icr = CIA_ICR_IR_SC | CIA_ICR_SP; /* SP interrupt enable */
ciaa.cra &= ~(1<<6); /* serial line == input */
#ifdef DRACO
}
#endif
kbd_softc.k_event_mode = 0;
kbd_softc.k_events.ev_io = 0;
splx(s);
}
#ifdef DRACO
/*
* call this with kbd interupt blocked
*/
int
drkbdgetc(void)
{
u_int8_t in;
while ((draco_ioct->io_status & DRSTAT_KBDRECV) == 0);
in = draco_ioct->io_kbddata;
draco_ioct->io_kbdrst = 0;
return in;
}
#define WAIT0 if (drkbdwaitfor(0)) goto Ltimeout
#define WAIT1 if (drkbdwaitfor(DRSTAT_KBDCLKIN)) goto Ltimeout
int
drkbdwaitfor(int bit)
{
int i;
i = 60000; /* about 50 ms max */
do {
if ((draco_ioct->io_status & DRSTAT_KBDCLKIN) == bit)
return 0;
} while (--i >= 0);
return 1;
}
/*
* Output a raw byte to the keyboard (+ parity and stop bit).
* return 0 on success, 1 on timeout.
*/
int
drkbdrputc(u_int8_t c)
{
u_int8_t parity;
int bitcnt;
DATLO; CLKHI; WAIT1;
parity = 0;
for (bitcnt=7; bitcnt >= 0; bitcnt--) {
WAIT0;
if (c & 1) {
DATHI;
} else {
++parity;
DATLO;
}
c >>= 1;
WAIT1;
}
WAIT0;
/* parity bit */
if (parity & 1) {
DATLO;
} else {
DATHI;
}
WAIT1;
/* stop bit */
WAIT0; DATHI; WAIT1;
WAIT0; /* XXX should check the ack bit here... */
WAIT1;
draco_ioct->io_kbdrst = 0;
return 0;
Ltimeout:
DATHI;
draco_ioct->io_kbdrst = 0;
return 1;
}
/*
* Output one cooked byte to the keyboard, with wait for ACK or RESEND,
* and retry if necessary. 0 == success, 1 == timeout
*/
int
drkbdputc(u_int8_t c)
{
int rc;
do {
if (drkbdrputc(c))
return(-1);
rc = drkbdgetc();
} while (rc == 0xfe);
return (!(rc == 0xfa));
}
/*
* same for twobyte sequence
*/
int
drkbdputc2(u_int8_t c1, u_int8_t c2)
{
int rc;
do {
do {
if (drkbdrputc(c1))
return(-1);
rc = drkbdgetc();
} while (rc == 0xfe);
if (rc != 0xfa)
return (-1);
if (drkbdrputc(c2))
return(-1);
rc = drkbdgetc();
} while (rc == 0xfe);
return (!(rc == 0xfa));
}
#endif
int
kbdopen(dev_t dev, int flags, int mode, struct proc *p)
{
kbdenable();
if (kbd_softc.k_events.ev_io)
return EBUSY;
kbd_softc.k_events.ev_io = p;
ev_init(&kbd_softc.k_events);
return (0);
}
int
kbdclose(dev_t dev, int flags, int mode, struct proc *p)
{
/* Turn off event mode, dump the queue */
kbd_softc.k_event_mode = 0;
ev_fini(&kbd_softc.k_events);
kbd_softc.k_events.ev_io = NULL;
return (0);
}
int
kbdread(dev_t dev, struct uio *uio, int flags)
{
return ev_read (&kbd_softc.k_events, uio, flags);
}
int
kbdioctl(dev_t dev, u_long cmd, register caddr_t data, int flag,
struct proc *p)
{
register struct kbd_softc *k = &kbd_softc;
switch (cmd) {
case KIOCTRANS:
if (*(int *)data == TR_UNTRANS_EVENT)
return 0;
break;
case KIOCGTRANS:
/* Get translation mode */
*(int *)data = TR_UNTRANS_EVENT;
return 0;
case KIOCSDIRECT:
k->k_event_mode = *(int *)data;
return 0;
case FIONBIO: /* we will remove this someday (soon???) */
return 0;
case FIOASYNC:
k->k_events.ev_async = *(int *)data != 0;
return 0;
case TIOCSPGRP:
if (*(int *)data != k->k_events.ev_io->p_pgid)
return EPERM;
return 0;
default:
return ENOTTY;
}
/* We identified the ioctl, but we do not handle it. */
return EOPNOTSUPP; /* misuse, but what the heck */
}
int
kbdpoll(dev_t dev, int events, struct proc *p)
{
return ev_poll (&kbd_softc.k_events, events, p);
}
int
kbdkqfilter(dev, kn)
dev_t dev;
struct knote *kn;
{
return (ev_kqfilter(&kbd_softc.k_events, kn));
}
void
kbdintr(int mask)
{
u_char c;
#ifdef KBDRESET
static int reset_warn;
#endif
/*
* now only invoked from generic CIA interrupt handler if there *is*
* a keyboard interrupt pending
*/
c = ~ciaa.sdr; /* keyboard data is inverted */
/* ack */
ciaa.cra |= (1 << 6); /* serial line output */
#ifdef KBDRESET
if (reset_warn && c == 0xf0) {
#ifdef DEBUG
printf ("kbdintr: !!!! Reset Warning !!!!\n");
#endif
bootsync();
reset_warn = 0;
DELAY(30000000);
}
#endif
/* wait 200 microseconds (for bloody Cherry keyboards..) */
DELAY(2000); /* fudge delay a bit for some keyboards */
ciaa.cra &= ~(1 << 6);
/* process the character */
c = (c >> 1) | (c << 7); /* rotate right once */
#ifdef KBDRESET
if (c == 0x78) {
#ifdef DEBUG
printf ("kbdintr: Reset Warning started\n");
#endif
++reset_warn;
return;
}
#endif
kbdstuffchar(c);
}
#ifdef DRACO
/* maps MF-II keycodes to Amiga keycodes */
const u_char drkbdtab[] = {
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x50,
0x45, 0xff, 0xff, 0xff, 0xff, 0x42, 0x00, 0x51,
0xff, 0x64, 0x60, 0x30, 0x63, 0x10, 0x01, 0x52,
0xff, 0x66, 0x31, 0x21, 0x20, 0x11, 0x02, 0x53,
0xff, 0x33, 0x32, 0x22, 0x12, 0x04, 0x03, 0x54,
0xff, 0x40, 0x34, 0x23, 0x14, 0x13, 0x05, 0x55,
0xff, 0x36, 0x35, 0x25, 0x24, 0x15, 0x06, 0x56,
0xff, 0x67, 0x37, 0x26, 0x16, 0x07, 0x08, 0x57,
/* --- */
0xff, 0x38, 0x27, 0x17, 0x18, 0x0a, 0x09, 0x58,
0xff, 0x39, 0x3a, 0x28, 0x29, 0x19, 0x0b, 0x59,
0xff, 0xff, 0x2a, 0x2b, 0x1a, 0x0c, 0x4b, 0xff,
0x65, 0x61, 0x44, 0x1b, 0xff, 0xff, 0x6f, 0xff,
0x4d, 0x4f, 0xff, 0x4c, 0x0d, 0xff, 0x41, 0x46,
0xff, 0x1d, 0x4e, 0x2d, 0x3d, 0x4a, 0x5f, 0x62,
0x0f, 0x3c, 0x1e, 0x2e, 0x2f, 0x3e, 0x5a, 0x5b,
0xff, 0x43, 0x1f, 0xff, 0x5e, 0x3f, 0x5c, 0xff,
/* --- */
0xff, 0xff, 0xff, 0xff, 0x5d
};
#endif
int
kbdgetcn(void)
{
int s;
u_char ints, mask, c, in;
#ifdef DRACO
if (is_draco() && kbd_softc.k_mf2) {
do {
c = 0;
s = spltty ();
while ((draco_ioct->io_status & DRSTAT_KBDRECV) == 0);
in = draco_ioct->io_kbddata;
draco_ioct->io_kbdrst = 0;
if (in == 0xF0) { /* release prefix */
c = 0x80;
while ((draco_ioct->io_status &
DRSTAT_KBDRECV) == 0);
in = draco_ioct->io_kbddata;
draco_ioct->io_kbdrst = 0;
}
splx(s);
#ifdef DRACORAWKEYDEBUG
printf("<%02x>", in);
#endif
c |= in>=sizeof(drkbdtab) ? 0xff : drkbdtab[in];
} while (c == 0xff);
return (c);
}
#endif
s = spltty();
for (ints = 0; ! ((mask = ciaa.icr) & CIA_ICR_SP);
ints |= mask) ;
in = ciaa.sdr;
c = ~in;
/* ack */
ciaa.cra |= (1 << 6); /* serial line output */
ciaa.sdr = 0xff; /* ack */
/* wait 200 microseconds */
DELAY(2000); /* XXXX only works as long as DELAY doesn't
* use a timer and waits.. */
ciaa.cra &= ~(1 << 6);
ciaa.sdr = in;
splx (s);
c = (c >> 1) | (c << 7);
/* take care that no CIA-interrupts are lost */
if (ints)
dispatch_cia_ints (0, ints);
return c;
}
void
kbdstuffchar(u_char c)
{
struct firm_event *fe;
struct kbd_softc *k = &kbd_softc;
int put;
#if NWSKBD>0
/*
* If we have attached a wskbd and not in polling mode and
* nobody has opened us directly, then send the keystroke
* to the wskbd.
*/
if (kbd_softc.k_pollingmode == 0
&& kbd_softc.k_wskbddev != NULL
&& k->k_event_mode == 0) {
wskbd_input(kbd_softc.k_wskbddev,
KEY_UP(c) ?
WSCONS_EVENT_KEY_UP :
WSCONS_EVENT_KEY_DOWN,
KEY_CODE(c));
return;
}
#endif /* NWSKBD */
/*
* If not in event mode, deliver straight to ite to process
* key stroke
*/
if (! k->k_event_mode) {
#if NITE>0
ite_filter (c, ITEFILT_TTY);
#endif
return;
}
/*
* Keyboard is generating events. Turn this keystroke into an
* event and put it in the queue. If the queue is full, the
* keystroke is lost (sorry!).
*/
put = k->k_events.ev_put;
fe = &k->k_events.ev_q[put];
put = (put + 1) % EV_QSIZE;
if (put == k->k_events.ev_get) {
log(LOG_WARNING, "keyboard event queue overflow\n");
/* ??? */
return;
}
fe->id = KEY_CODE(c);
fe->value = KEY_UP(c) ? VKEY_UP : VKEY_DOWN;
fe->time = time;
k->k_events.ev_put = put;
EV_WAKEUP(&k->k_events);
}
#ifdef DRACO
void
drkbdintr(void)
{
u_char in;
struct kbd_softc *k = &kbd_softc;
in = draco_ioct->io_kbddata;
draco_ioct->io_kbdrst = 0;
if (in == 0xF0)
k->k_rlprfx = 0x80;
else {
kbdstuffchar(in>=sizeof(drkbdtab) ? 0xff :
drkbdtab[in] | k->k_rlprfx);
k->k_rlprfx = 0;
}
}
#endif
#if NWSKBD>0
/*
* These are the callback functions that are passed to wscons.
* They really don't do anything worth noting, just call the
* other functions above.
*/
int
kbd_enable(void *c, int on)
{
/* Wonder what this is supposed to do... */
return (0);
}
void
kbd_set_leds(void *c, int leds)
{
}
int
kbd_ioctl(void *c, u_long cmd, caddr_t data, int flag, struct proc *p)
{
switch (cmd)
{
case WSKBDIO_COMPLEXBELL:
return 0;
case WSKBDIO_SETLEDS:
return 0;
case WSKBDIO_GETLEDS:
*(int*)data = 0;
return 0;
case WSKBDIO_GTYPE:
*(u_int*)data = WSKBD_TYPE_AMIGA;
return 0;
}
/*
* We are supposed to return EPASSTHROUGH to wscons if we didn't
* understand.
*/
return (EPASSTHROUGH);
}
void
kbd_getc(void *c, u_int *type, int *data)
{
int key;
key = kbdgetcn();
*data = KEY_CODE(key);
*type = KEY_UP(key) ? WSCONS_EVENT_KEY_UP : WSCONS_EVENT_KEY_DOWN;
}
void
kbd_pollc(void *c, int on)
{
kbd_softc.k_pollingmode = on;
}
void
kbd_bell(void *c, u_int x, u_int y, u_int z)
{
}
#endif /* WSKBD */