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");
 | |
| }
 |