Add ps2 keyboard initialization.

Which also requires that we properly initialize the i8259 (ISA)
interrupt controller.
This commit is contained in:
Richard Henderson
2011-05-06 11:07:37 -07:00
parent 369d1d9a68
commit 2166044b71
7 changed files with 749 additions and 1 deletions

View File

@ -12,7 +12,8 @@ CPPFLAGS = -DSYSTEM_H='"sys-$(SYSTEM).h"'
CFLAGS += -mcpu=ev67
OBJS = pal.o sys-$(SYSTEM).o init.o crb.o uart.o console.o console-low.o memset.o printf.o
OBJS = pal.o sys-$(SYSTEM).o init.o crb.o uart.o console.o console-low.o \
memset.o printf.o util.o ps2port.o
all: palcode-$(SYSTEM)

27
init.c
View File

@ -22,6 +22,7 @@
#include <stddef.h>
#include "hwrpb.h"
#include "osf.h"
#include "ioport.h"
#include "uart.h"
#include "protos.h"
#include SYSTEM_H
@ -230,6 +231,30 @@ init_pcb (void)
pcb.flags = 1; /* FEN */
}
static void
init_i8259 (void)
{
/* Initialize the slave PIC. */
outb(0x11, PORT_PIC2_CMD); /* ICW1: edge trigger, cascade, ICW4 req */
outb(0x08, PORT_PIC2_DATA); /* ICW2: irq offset = 8 */
outb(0x02, PORT_PIC2_DATA); /* ICW3: slave ID 2 */
outb(0x01, PORT_PIC2_DATA); /* ICW4: not special nested, normal eoi */
/* Initialize the master PIC. */
outb(0x11, PORT_PIC1_CMD); /* ICW1 */
outb(0x00, PORT_PIC1_DATA); /* ICW2: irq offset = 0 */
outb(0x04, PORT_PIC1_DATA); /* ICW3: slave control INTC2 */
outb(0x01, PORT_PIC1_DATA); /* ICW4 */
/* Disable all interrupts. */
outb(0xff, PORT_PIC2_DATA);
outb(0xff, PORT_PIC1_DATA);
/* Non-specific EOI, clearing anything the might be pending. */
outb(0x20, PORT_PIC2_CMD);
outb(0x20, PORT_PIC1_CMD);
}
void
do_start(unsigned long memsize, void (*kernel_entry)(void), long cpus)
{
@ -238,7 +263,9 @@ do_start(unsigned long memsize, void (*kernel_entry)(void), long cpus)
init_page_table();
init_hwrpb(memsize);
init_pcb();
init_i8259();
uart_init();
ps2port_setup();
{
register int variant __asm__("$16") = 2; /* OSF/1 PALcode */

82
ioport.h Normal file
View File

@ -0,0 +1,82 @@
// Definitions for X86 IO port access.
//
// Copyright (C) 2008 Kevin O'Connor <kevin@koconnor.net>
//
// This file may be distributed under the terms of the GNU LGPLv3 license.
//
// This file copied (somewhat) intact from SeaBIOS.
#ifndef IOPORT_H
#define IOPORT_H
#define PORT_DMA_ADDR_2 0x0004
#define PORT_DMA_CNT_2 0x0005
#define PORT_DMA1_MASK_REG 0x000a
#define PORT_DMA1_MODE_REG 0x000b
#define PORT_DMA1_CLEAR_FF_REG 0x000c
#define PORT_DMA1_MASTER_CLEAR 0x000d
#define PORT_PIC1_CMD 0x0020
#define PORT_PIC1_DATA 0x0021
#define PORT_PIT_COUNTER0 0x0040
#define PORT_PIT_COUNTER1 0x0041
#define PORT_PIT_COUNTER2 0x0042
#define PORT_PIT_MODE 0x0043
#define PORT_PS2_DATA 0x0060
#define PORT_PS2_CTRLB 0x0061
#define PORT_PS2_STATUS 0x0064
#define PORT_CMOS_INDEX 0x0070
#define PORT_CMOS_DATA 0x0071
#define PORT_DIAG 0x0080
#define PORT_DMA_PAGE_2 0x0081
#define PORT_A20 0x0092
#define PORT_PIC2_CMD 0x00a0
#define PORT_PIC2_DATA 0x00a1
#define PORT_SMI_CMD 0x00b2
#define PORT_SMI_STATUS 0x00b3
#define PORT_DMA2_MASK_REG 0x00d4
#define PORT_DMA2_MODE_REG 0x00d6
#define PORT_DMA2_MASTER_CLEAR 0x00da
#define PORT_MATH_CLEAR 0x00f0
#define PORT_ATA2_CMD_BASE 0x0170
#define PORT_ATA1_CMD_BASE 0x01f0
#define PORT_LPT2 0x0278
#define PORT_SERIAL4 0x02e8
#define PORT_SERIAL2 0x02f8
#define PORT_ATA2_CTRL_BASE 0x0374
#define PORT_LPT1 0x0378
#define PORT_SERIAL3 0x03e8
#define PORT_ATA1_CTRL_BASE 0x03f4
#define PORT_FD_BASE 0x03f0
#define PORT_FD_DOR 0x03f2
#define PORT_FD_STATUS 0x03f4
#define PORT_FD_DATA 0x03f5
#define PORT_HD_DATA 0x03f6
#define PORT_FD_DIR 0x03f7
#define PORT_SERIAL1 0x03f8
#define PORT_PCI_CMD 0x0cf8
#define PORT_PCI_REBOOT 0x0cf9
#define PORT_PCI_DATA 0x0cfc
#define PORT_BIOS_DEBUG 0x0402
#define PORT_QEMU_CFG_CTL 0x0510
#define PORT_QEMU_CFG_DATA 0x0511
#define PORT_ACPI_PM_BASE 0xb000
#define PORT_SMB_BASE 0xb100
#define PORT_BIOS_APM 0x8900
// Serial port offsets
#define SEROFF_DATA 0
#define SEROFF_DLL 0
#define SEROFF_IER 1
#define SEROFF_DLH 1
#define SEROFF_IIR 2
#define SEROFF_LCR 3
#define SEROFF_LSR 5
#define SEROFF_MSR 6
// PORT_A20 bitdefs
#define A20_ENABLE_BIT 0x02
// PORT_CMOS_INDEX nmi disable bit
#define NMI_DISABLE_BIT 0x80
#endif // ioport.h

View File

@ -21,6 +21,11 @@
#ifndef PROTOS_H
#define PROTOS_H 1
#include <stdint.h>
#include <stdbool.h>
#include <stddef.h>
/*
* Call_Pal functions.
*/
@ -167,4 +172,20 @@ extern unsigned long crb_fixup(unsigned long vptptr, unsigned long hwrpb);
extern void do_console(void);
extern void entInt(void);
/*
* Utils
*/
extern void ndelay(unsigned long nsec);
static inline void udelay(unsigned long msec)
{
ndelay(msec * 1000);
}
/*
* Initialization
*/
extern void ps2port_setup(void);
#endif /* PROTOS_H */

507
ps2port.c Normal file
View File

@ -0,0 +1,507 @@
// Support for handling the PS/2 mouse/keyboard ports.
//
// Copyright (C) 2008 Kevin O'Connor <kevin@koconnor.net>
// Several ideas taken from code Copyright (c) 1999-2004 Vojtech Pavlik
//
// This file may be distributed under the terms of the GNU LGPLv3 license.
//
// This file is copied (mostly) intact from SeaBIOS.
#include "protos.h"
#include "ioport.h"
#include "ps2port.h"
typedef uint64_t u64;
#define dprintf(lvl, fmt, ...)
#define warn_timeout()
static inline u64
calc_future_tsc(int timeout)
{
return get_wall_time() + timeout;
}
static inline bool
check_tsc(u64 end)
{
return get_wall_time() > end;
}
static inline void
yield(void)
{
udelay(1);
}
static struct {
u8 ps2ctr;
} ebda;
#define GET_EBDA(VAR) ebda.VAR
#define SET_EBDA(VAR, VAL) (ebda.VAR = (VAL))
#define ASSERT32FLAT()
#define CONFIG_PS2PORT 1
#define enable_hwirq(level, func)
#define run_thread(func, val) func(val)
/****************************************************************
* Low level i8042 commands.
****************************************************************/
// Timeout value.
#define I8042_CTL_TIMEOUT 10000
#define I8042_BUFFER_SIZE 16
static int
i8042_wait_read(void)
{
dprintf(7, "i8042_wait_read\n");
int i;
for (i=0; i<I8042_CTL_TIMEOUT; i++) {
u8 status = inb(PORT_PS2_STATUS);
if (status & I8042_STR_OBF)
return 0;
udelay(50);
}
warn_timeout();
return -1;
}
static int
i8042_wait_write(void)
{
dprintf(7, "i8042_wait_write\n");
int i;
for (i=0; i<I8042_CTL_TIMEOUT; i++) {
u8 status = inb(PORT_PS2_STATUS);
if (! (status & I8042_STR_IBF))
return 0;
udelay(50);
}
warn_timeout();
return -1;
}
static int
i8042_flush(void)
{
dprintf(7, "i8042_flush\n");
int i;
for (i=0; i<I8042_BUFFER_SIZE; i++) {
u8 status = inb(PORT_PS2_STATUS);
if (! (status & I8042_STR_OBF))
return 0;
udelay(50);
inb(PORT_PS2_DATA);
}
warn_timeout();
return -1;
}
static int
__i8042_command(int command, u8 *param)
{
int receive = (command >> 8) & 0xf;
int send = (command >> 12) & 0xf;
// Send the command.
int ret = i8042_wait_write();
if (ret)
return ret;
outb(command, PORT_PS2_STATUS);
// Send parameters (if any).
int i;
for (i = 0; i < send; i++) {
ret = i8042_wait_write();
if (ret)
return ret;
outb(param[i], PORT_PS2_DATA);
}
// Receive parameters (if any).
for (i = 0; i < receive; i++) {
ret = i8042_wait_read();
if (ret)
return ret;
param[i] = inb(PORT_PS2_DATA);
dprintf(7, "i8042 param=%x\n", param[i]);
}
return 0;
}
static int
i8042_command(int command, u8 *param)
{
dprintf(7, "i8042_command cmd=%x\n", command);
int ret = __i8042_command(command, param);
if (ret)
dprintf(2, "i8042 command %x failed\n", command);
return ret;
}
static int
i8042_kbd_write(u8 c)
{
dprintf(7, "i8042_kbd_write c=%d\n", c);
int ret = i8042_wait_write();
if (! ret)
outb(c, PORT_PS2_DATA);
return ret;
}
static int
i8042_aux_write(u8 c)
{
return i8042_command(I8042_CMD_AUX_SEND, &c);
}
void
i8042_reboot(void)
{
int i;
for (i=0; i<10; i++) {
i8042_wait_write();
udelay(50);
outb(0xfe, PORT_PS2_STATUS); /* pulse reset low */
udelay(50);
}
}
/****************************************************************
* Device commands.
****************************************************************/
#define PS2_RET_ACK 0xfa
#define PS2_RET_NAK 0xfe
static int
ps2_recvbyte(int aux, int needack, int timeout)
{
u64 end = calc_future_tsc(timeout);
for (;;) {
u8 status = inb(PORT_PS2_STATUS);
if (status & I8042_STR_OBF) {
u8 data = inb(PORT_PS2_DATA);
dprintf(7, "ps2 read %x\n", data);
if (!!(status & I8042_STR_AUXDATA) == aux) {
if (!needack)
return data;
if (data == PS2_RET_ACK)
return data;
if (data == PS2_RET_NAK) {
dprintf(1, "Got ps2 nak (status=%x)\n", status);
return data;
}
}
// This data not part of command - just discard it.
dprintf(1, "Discarding ps2 data %02x (status=%02x)\n", data, status);
}
if (check_tsc(end)) {
// Don't warn on second byte of a reset
if (timeout > 100)
warn_timeout();
return -1;
}
yield();
}
}
static int
ps2_sendbyte(int aux, u8 command, int timeout)
{
dprintf(7, "ps2_sendbyte aux=%d cmd=%x\n", aux, command);
int ret;
if (aux)
ret = i8042_aux_write(command);
else
ret = i8042_kbd_write(command);
if (ret)
return ret;
// Read ack.
ret = ps2_recvbyte(aux, 1, timeout);
if (ret < 0)
return ret;
if (ret != PS2_RET_ACK)
return -1;
return 0;
}
static int
__ps2_command(int aux, int command, u8 *param)
{
int ret2;
int receive = (command >> 8) & 0xf;
int send = (command >> 12) & 0xf;
// Disable interrupts and keyboard/mouse.
u8 ps2ctr = GET_EBDA(ps2ctr);
u8 newctr = ((ps2ctr | I8042_CTR_AUXDIS | I8042_CTR_KBDDIS)
& ~(I8042_CTR_KBDINT|I8042_CTR_AUXINT));
dprintf(6, "i8042 ctr old=%x new=%x\n", ps2ctr, newctr);
int ret = i8042_command(I8042_CMD_CTL_WCTR, &newctr);
if (ret)
return ret;
// Flush any interrupts already pending.
yield();
// Enable port command is being sent to.
if (aux)
newctr &= ~I8042_CTR_AUXDIS;
else
newctr &= ~I8042_CTR_KBDDIS;
ret = i8042_command(I8042_CMD_CTL_WCTR, &newctr);
if (ret)
goto fail;
if (command == ATKBD_CMD_RESET_BAT) {
// Reset is special wrt timeouts and bytes received.
// Send command.
ret = ps2_sendbyte(aux, command, 1000);
if (ret)
goto fail;
// Receive parameters.
ret = ps2_recvbyte(aux, 0, 4000);
if (ret < 0)
goto fail;
param[0] = ret;
ret = ps2_recvbyte(aux, 0, 100);
if (ret < 0)
// Some devices only respond with one byte on reset.
ret = 0;
param[1] = ret;
} else if (command == ATKBD_CMD_GETID) {
// Getid is special wrt bytes received.
// Send command.
ret = ps2_sendbyte(aux, command, 200);
if (ret)
goto fail;
// Receive parameters.
ret = ps2_recvbyte(aux, 0, 500);
if (ret < 0)
goto fail;
param[0] = ret;
if (ret == 0xab || ret == 0xac || ret == 0x2b || ret == 0x5d
|| ret == 0x60 || ret == 0x47) {
// These ids (keyboards) return two bytes.
ret = ps2_recvbyte(aux, 0, 500);
if (ret < 0)
goto fail;
param[1] = ret;
} else {
param[1] = 0;
}
} else {
// Send command.
ret = ps2_sendbyte(aux, command, 200);
if (ret)
goto fail;
// Send parameters (if any).
int i;
for (i = 0; i < send; i++) {
ret = ps2_sendbyte(aux, param[i], 200);
if (ret)
goto fail;
}
// Receive parameters (if any).
for (i = 0; i < receive; i++) {
ret = ps2_recvbyte(aux, 0, 500);
if (ret < 0)
goto fail;
param[i] = ret;
}
}
ret = 0;
fail:
// Restore interrupts and keyboard/mouse.
ret2 = i8042_command(I8042_CMD_CTL_WCTR, &ps2ctr);
if (ret2)
return ret2;
return ret;
}
static int
ps2_command(int aux, int command, u8 *param)
{
dprintf(7, "ps2_command aux=%d cmd=%x\n", aux, command);
int ret = __ps2_command(aux, command, param);
if (ret)
dprintf(2, "ps2 command %x failed (aux=%d)\n", command, aux);
return ret;
}
int
ps2_kbd_command(int command, u8 *param)
{
return ps2_command(0, command, param);
}
int
ps2_mouse_command(int command, u8 *param)
{
return ps2_command(1, command, param);
}
/****************************************************************
* IRQ handlers
****************************************************************/
#if 0
// INT74h : PS/2 mouse hardware interrupt
void VISIBLE16
handle_74(void)
{
if (! CONFIG_PS2PORT)
return;
debug_isr(DEBUG_ISR_74);
u8 v = inb(PORT_PS2_STATUS);
if ((v & (I8042_STR_OBF|I8042_STR_AUXDATA))
!= (I8042_STR_OBF|I8042_STR_AUXDATA)) {
dprintf(1, "ps2 mouse irq but no mouse data.\n");
goto done;
}
v = inb(PORT_PS2_DATA);
if (!(GET_EBDA(ps2ctr) & I8042_CTR_AUXINT))
// Interrupts not enabled.
goto done;
process_mouse(v);
done:
eoi_pic2();
}
// INT09h : Keyboard Hardware Service Entry Point
void VISIBLE16
handle_09(void)
{
if (! CONFIG_PS2PORT)
return;
debug_isr(DEBUG_ISR_09);
// read key from keyboard controller
u8 v = inb(PORT_PS2_STATUS);
if (v & I8042_STR_AUXDATA) {
dprintf(1, "ps2 keyboard irq but found mouse data?!\n");
goto done;
}
v = inb(PORT_PS2_DATA);
if (!(GET_EBDA(ps2ctr) & I8042_CTR_KBDINT))
// Interrupts not enabled.
goto done;
process_key(v);
done:
eoi_pic1();
}
#endif
/****************************************************************
* Setup
****************************************************************/
static void
keyboard_init(void *data)
{
/* flush incoming keys */
int ret = i8042_flush();
if (ret)
return;
// Controller self-test.
u8 param[2];
ret = i8042_command(I8042_CMD_CTL_TEST, param);
if (ret)
return;
if (param[0] != 0x55) {
dprintf(1, "i8042 self test failed (got %x not 0x55)\n", param[0]);
return;
}
// Controller keyboard test.
ret = i8042_command(I8042_CMD_KBD_TEST, param);
if (ret)
return;
if (param[0] != 0x00) {
dprintf(1, "i8042 keyboard test failed (got %x not 0x00)\n", param[0]);
return;
}
// Disable keyboard and mouse events.
SET_EBDA(ps2ctr, I8042_CTR_KBDDIS | I8042_CTR_AUXDIS);
/* ------------------- keyboard side ------------------------*/
/* reset keyboard and self test (keyboard side) */
ret = ps2_kbd_command(ATKBD_CMD_RESET_BAT, param);
if (ret)
return;
if (param[0] != 0xaa) {
dprintf(1, "keyboard self test failed (got %x not 0xaa)\n", param[0]);
return;
}
/* Disable keyboard */
ret = ps2_kbd_command(ATKBD_CMD_RESET_DIS, NULL);
if (ret)
return;
// Set scancode command (mode 2)
param[0] = 0x02;
ret = ps2_kbd_command(ATKBD_CMD_SSCANSET, param);
if (ret)
return;
// Keyboard Mode: disable mouse, scan code convert, enable kbd IRQ
SET_EBDA(ps2ctr, I8042_CTR_AUXDIS | I8042_CTR_XLATE | I8042_CTR_KBDINT);
/* Enable keyboard */
ret = ps2_kbd_command(ATKBD_CMD_ENABLE, NULL);
if (ret)
return;
dprintf(1, "PS2 keyboard initialized\n");
}
void
ps2port_setup(void)
{
ASSERT32FLAT();
if (! CONFIG_PS2PORT)
return;
dprintf(3, "init ps2port\n");
enable_hwirq(1, FUNC16(entry_09));
enable_hwirq(12, FUNC16(entry_74));
run_thread(keyboard_init, NULL);
}

71
ps2port.h Normal file
View File

@ -0,0 +1,71 @@
/* Basic ps2 port (keyboard/mouse) command handling.
This file is copied (mostly) intact from SeaBIOS.
It is covered by the GNU Lesser General Public License, v3.
You should have received a copy of the GNU Lesser General Public License
along with this program; see the file COPYING. If not see
<http://www.gnu.org/licenses/>. */
#ifndef PS2PORT_H
#define PS2PORT_H
typedef uint8_t u8;
// Standard commands.
#define I8042_CMD_CTL_RCTR 0x0120
#define I8042_CMD_CTL_WCTR 0x1060
#define I8042_CMD_CTL_TEST 0x01aa
#define I8042_CMD_KBD_TEST 0x01ab
#define I8042_CMD_KBD_DISABLE 0x00ad
#define I8042_CMD_KBD_ENABLE 0x00ae
#define I8042_CMD_AUX_DISABLE 0x00a7
#define I8042_CMD_AUX_ENABLE 0x00a8
#define I8042_CMD_AUX_SEND 0x10d4
// Keyboard commands
#define ATKBD_CMD_SETLEDS 0x10ed
#define ATKBD_CMD_SSCANSET 0x10f0
#define ATKBD_CMD_GETID 0x02f2
#define ATKBD_CMD_ENABLE 0x00f4
#define ATKBD_CMD_RESET_DIS 0x00f5
#define ATKBD_CMD_RESET_BAT 0x02ff
// Mouse commands
#define PSMOUSE_CMD_SETSCALE11 0x00e6
#define PSMOUSE_CMD_SETSCALE21 0x00e7
#define PSMOUSE_CMD_SETRES 0x10e8
#define PSMOUSE_CMD_GETINFO 0x03e9
#define PSMOUSE_CMD_GETID 0x02f2
#define PSMOUSE_CMD_SETRATE 0x10f3
#define PSMOUSE_CMD_ENABLE 0x00f4
#define PSMOUSE_CMD_DISABLE 0x00f5
#define PSMOUSE_CMD_RESET_BAT 0x02ff
// Status register bits.
#define I8042_STR_PARITY 0x80
#define I8042_STR_TIMEOUT 0x40
#define I8042_STR_AUXDATA 0x20
#define I8042_STR_KEYLOCK 0x10
#define I8042_STR_CMDDAT 0x08
#define I8042_STR_MUXERR 0x04
#define I8042_STR_IBF 0x02
#define I8042_STR_OBF 0x01
// Control register bits.
#define I8042_CTR_KBDINT 0x01
#define I8042_CTR_AUXINT 0x02
#define I8042_CTR_IGNKEYLOCK 0x08
#define I8042_CTR_KBDDIS 0x10
#define I8042_CTR_AUXDIS 0x20
#define I8042_CTR_XLATE 0x40
// functions
void i8042_reboot(void);
int ps2_kbd_command(int command, u8 *param);
int ps2_mouse_command(int command, u8 *param);
void ps2port_setup(void);
#endif // ps2port.h

39
util.c Normal file
View File

@ -0,0 +1,39 @@
/* Utility functions for the QEMU PALcode.
Copyright (C) 2011 Richard Henderson
This file is part of QEMU PALcode.
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the text
of the GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; see the file COPYING. If not see
<http://www.gnu.org/licenses/>. */
#include "protos.h"
void
ndelay(unsigned long nsec)
{
unsigned long target, now;
now = get_wall_time();
target = now + nsec;
set_alarm_abs(nsec);
do
{
wtint(0);
now = get_wall_time();
}
while (now < target);
}