basekernel/kernel/mouse.c

250 lines
6.2 KiB
C

/*
Copyright (C) 2016-2019 The University of Notre Dame
This software is distributed under the GNU General Public License.
See the file LICENSE for details.
*/
#include "mouse.h"
#include "console.h"
#include "ioports.h"
#include "interrupt.h"
#include "kernel/ascii.h"
#include "process.h"
#include "kernelcore.h"
#include "event_queue.h"
/*
The PS2 interface uses a data port and a command port.
Reading the command port yields a status byte,
while writing to the command port executes commands.
*/
#define PS2_DATA_PORT 0x60
#define PS2_COMMAND_PORT 0x64
/* Some commands that may be sent to the command port. */
#define PS2_COMMAND_READ_CONFIG 0x20
#define PS2_COMMAND_WRITE_CONFIG 0x60
#define PS2_COMMAND_DISABLE_MOUSE 0xA7
#define PS2_COMMAND_ENABLE_MOUSE 0xA8
#define PS2_COMMAND_DISABLE_KEYBOARD 0xAD
#define PS2_COMMAND_ENABLE_KEYBOARD 0xAE
#define PS2_COMMAND_MOUSE_PREFIX 0xD4
/* The status byte read from the command port has these fields. */
#define PS2_STATUS_OBF 0x01 // true: may not write to data port
#define PS2_STATUS_IBF 0x02 // true: may read from data port
#define PS2_STATUS_SYS 0x04 // true when port is initialized
#define PS2_STATUS_A2 0x08 // true if command port was last written to
#define PS2_STATUS_INH 0x10 // true if keyboard inhibited
#define PS2_STATUS_MOBF 0x20 // true if mouse output available
#define PS2_STATUS_TOUT 0x40 // true if timeout during I/O
#define PS2_STATUS_PERR 0x80 // true indicates parity error
/*
In addition, a configuration byte may be read/written
via PS2_COMMAND_READ/WRITECONFIG. The configuration byte
has these bitfields.
*/
#define PS2_CONFIG_PORTA_IRQ 0x01
#define PS2_CONFIG_PORTB_IRQ 0x02
#define PS2_CONFIG_SYSTEM 0x04
#define PS2_CONFIG_RESERVED1 0x08
#define PS2_CONFIG_PORTA_CLOCK 0x10
#define PS2_CONFIG_PORTB_CLOCK 0x20
#define PS2_CONFIG_PORTA_TRANSLATE 0x40
#define PS2_CONFIG_RESERVED2 0x80
/*
The mouse has several specialized commands that may
be sent by first sending PS2_COMMAND_MOUSE_PREFIX,
then one of the following:
*/
#define PS2_MOUSE_COMMAND_ENABLE_STREAMING 0xea
#define PS2_MOUSE_COMMAND_ENABLE_DEVICE 0xf4
#define PS2_MOUSE_COMMAND_RESET 0xff
/*
To read/write data to/from the PS2 port, we must first check
for the input/output buffer full (IBF/OBF) bits are set appropriately
in the status register.
*/
uint8_t ps2_read_data()
{
uint8_t status;
do {
status = inb(PS2_COMMAND_PORT);
} while(!(status & PS2_STATUS_OBF));
return inb(PS2_DATA_PORT);
}
void ps2_write_data(uint8_t data)
{
uint8_t status;
do {
status = inb(PS2_COMMAND_PORT);
} while(status & PS2_STATUS_IBF);
return outb(data, PS2_DATA_PORT);
}
/*
In a similar way, to write a command to the status port,
we must also check that the IBF field is cleared.
*/
void ps2_write_command(uint8_t data)
{
uint8_t status;
do {
status = inb(PS2_COMMAND_PORT);
} while(status & PS2_STATUS_IBF);
return outb(data, PS2_COMMAND_PORT);
}
/*
Clear the buffer of all data by reading until OBF and IBF
are both clear. Useful when resetting the device to achieve
a known state.
*/
void ps2_clear_buffer()
{
uint8_t status;
do {
status = inb(PS2_COMMAND_PORT);
if(status & PS2_STATUS_OBF) {
inb(PS2_DATA_PORT);
continue;
}
} while(status & (PS2_STATUS_OBF | PS2_STATUS_IBF));
}
/*
Send a mouse-specific command by sending the mouse prefix,
then the mouse command as data, then reading back an
acknowledgement.
*/
void ps2_mouse_command(uint8_t command)
{
ps2_write_command(PS2_COMMAND_MOUSE_PREFIX);
ps2_write_data(command);
ps2_read_data();
}
/*
Read and write the PS2 configuration byte, which
does not involve an acknowledgement.
*/
uint8_t ps2_config_get()
{
ps2_write_command(PS2_COMMAND_READ_CONFIG);
return ps2_read_data();
}
void ps2_config_set(uint8_t config)
{
ps2_write_command(PS2_COMMAND_WRITE_CONFIG);
ps2_write_data(config);
}
static struct mouse_state state = {0,0,0};
static struct mouse_state last_state = {0,0,0};
/*
On each interrupt, read three bytes from the PS 2 port, which
gives buttons and status, X and Y position. The ninth (sign) bit
of the X and Y position is given as a single bit in the status
word, so we must assemble a twos-complement integer if needed.
Finally, take those values and update the current mouse state.
*/
static void mouse_interrupt(int i, int code)
{
uint8_t m1 = inb(PS2_DATA_PORT);
uint8_t m2 = inb(PS2_DATA_PORT);
uint8_t m3 = inb(PS2_DATA_PORT);
last_state = state;
state.buttons = m1 & 0x03;
state.x += m1 & 0x10 ? 0xffffff00 | m2 : m2;
state.y -= m1 & 0x20 ? 0xffffff00 | m3 : m3;
if(state.x < 0)
state.x = 0;
if(state.y < 0)
state.y = 0;
if(state.x >= video_xres)
state.x = video_xres - 1;
if(state.y >= video_yres)
state.y = video_yres - 1;
// XXX skip mouse events for now!
return;
if(state.buttons!=last_state.buttons) {
int i;
for(i=0;i<8;i++) {
uint8_t mask = (1<<i);
if( (state.buttons&mask) && !(last_state.buttons&mask) ) {
event_queue_post_root(EVENT_BUTTON_DOWN,i,state.x,state.y);
} else if( !(state.buttons&mask) && (last_state.buttons&mask) ) {
event_queue_post_root(EVENT_BUTTON_UP,i,state.x,state.y);
}
}
}
if(state.x!=last_state.x || state.y!=last_state.y) {
event_queue_post_root(EVENT_MOUSE_MOVE,0,state.x,state.y);
}
}
/*
Do a non-blocking read of the current mouse state.
Block interrupts while reading, to avoid inconsistent state.
*/
void mouse_read(struct mouse_state *e)
{
interrupt_disable(44);
*e = state;
interrupt_enable(44);
}
/*
Unlike the keyboard, the mouse is not automatically enabled
at bootup. We must first obtain tbe ps2 configuration register,
enable both port A (keyboard) and port B (mouse), then send
a series of commands to reset the mouse and enable "streaming",
which causes an interrupt for every move of the mouse.
*/
void mouse_init()
{
ps2_clear_buffer();
uint8_t config = ps2_config_get();
config |= PS2_CONFIG_PORTA_IRQ;
config |= PS2_CONFIG_PORTB_IRQ;
config &= ~PS2_CONFIG_PORTA_CLOCK;
config &= ~PS2_CONFIG_PORTB_CLOCK;
config |= PS2_CONFIG_PORTA_TRANSLATE;
ps2_config_set(config);
ps2_mouse_command(PS2_MOUSE_COMMAND_RESET);
ps2_mouse_command(PS2_MOUSE_COMMAND_ENABLE_DEVICE);
ps2_mouse_command(PS2_MOUSE_COMMAND_ENABLE_STREAMING);
interrupt_register(44, mouse_interrupt);
interrupt_enable(44);
printf("mouse: ready\n");
}