250 lines
6.2 KiB
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");
|
|
}
|