269 lines
6.3 KiB
C++
269 lines
6.3 KiB
C++
/*
|
|
* \brief PS/2 mouse protocol handler
|
|
* \author Norman Feske
|
|
* \date 2007-10-09
|
|
*/
|
|
|
|
/*
|
|
* Copyright (C) 2007-2013 Genode Labs GmbH
|
|
*
|
|
* This file is part of the Genode OS framework, which is distributed
|
|
* under the terms of the GNU General Public License version 2.
|
|
*/
|
|
|
|
#ifndef _PS2_MOUSE_H_
|
|
#define _PS2_MOUSE_H_
|
|
|
|
#include <base/printf.h>
|
|
#include <input/event_queue.h>
|
|
#include <input/keycodes.h>
|
|
|
|
#include "input_driver.h"
|
|
|
|
class Ps2_mouse : public Input_driver
|
|
{
|
|
enum Command
|
|
{
|
|
CMD_GET_ID = 0xf2,
|
|
CMD_SET_RATE = 0xf3,
|
|
CMD_ENABLE_STREAM = 0xf4,
|
|
CMD_DISABLE_STREAM = 0xf5,
|
|
CMD_SET_DEFAULTS = 0xf6,
|
|
};
|
|
|
|
enum Return
|
|
{
|
|
RET_ACK = 0xfa,
|
|
RET_NAK = 0xfe,
|
|
RET_ERROR = 0xff,
|
|
};
|
|
|
|
enum Flag
|
|
{
|
|
FLAG_BTN_LEFT = 0x01,
|
|
FLAG_BTN_RIGHT = 0x02,
|
|
FLAG_BTN_MIDDLE = 0x04,
|
|
FLAG_X_SIGN = 0x10,
|
|
FLAG_Y_SIGN = 0x20,
|
|
FLAG_X_OVER = 0x40,
|
|
FLAG_Y_OVER = 0x80,
|
|
};
|
|
|
|
enum {
|
|
LEFT = 0,
|
|
RIGHT = 1,
|
|
MIDDLE = 2,
|
|
SIDE = 3,
|
|
EXTRA = 4,
|
|
NUM_BUTTONS,
|
|
};
|
|
|
|
enum {
|
|
PS2_PACKET_LEN = 3,
|
|
IMPS2_PACKET_LEN = 4,
|
|
EXPS2_PACKET_LEN = 4,
|
|
MAX_PACKET_LEN = 4,
|
|
};
|
|
|
|
enum Type { PS2, IMPS2, EXPS2 };
|
|
|
|
private:
|
|
|
|
static const bool verbose = false;
|
|
|
|
Serial_interface &_aux;
|
|
Input::Event_queue &_ev_queue;
|
|
|
|
Type _type;
|
|
|
|
bool _button_state[NUM_BUTTONS];
|
|
|
|
unsigned char _packet[MAX_PACKET_LEN];
|
|
int _packet_len;
|
|
int _packet_idx;
|
|
|
|
/**
|
|
* Generate mouse button event on state changes
|
|
*
|
|
* Depending on the old and new state, this function
|
|
* posts press or release events for the mouse buttons
|
|
* to the event queue. Note that the old state value
|
|
* gets set to the new state.
|
|
*/
|
|
void _button_event(bool *old_state, bool new_state, int key_code)
|
|
{
|
|
if (*old_state == new_state) return;
|
|
|
|
if (verbose)
|
|
Genode::printf("post %s, key_code = %d\n", new_state ? "PRESS" : "RELEASE", key_code);
|
|
|
|
_ev_queue.add(Input::Event(new_state ? Input::Event::PRESS
|
|
: Input::Event::RELEASE,
|
|
key_code, 0, 0, 0, 0));
|
|
*old_state = new_state;
|
|
}
|
|
|
|
/**
|
|
* Probe for extended ImPS/2 mouse (IntelliMouse)
|
|
*/
|
|
bool _probe_imps2()
|
|
{
|
|
/* send magic rate setting combination */
|
|
const unsigned char magic[] = { CMD_SET_RATE, 200, CMD_SET_RATE, 100, CMD_SET_RATE, 80 };
|
|
|
|
for (unsigned i = 0; i < sizeof(magic); ++i) {
|
|
_aux.write(magic[i]);
|
|
if (_aux.read() != RET_ACK) return false;
|
|
}
|
|
|
|
/* check device ID */
|
|
unsigned char id;
|
|
_aux.write(CMD_GET_ID);
|
|
if (_aux.read() != RET_ACK) return false;
|
|
id = _aux.read();
|
|
|
|
return id == 3;
|
|
}
|
|
|
|
/**
|
|
* Probe for extended ExPS/2 mouse (IntelliMouse Explorer)
|
|
*/
|
|
bool _probe_exps2()
|
|
{
|
|
/* send magic rate setting combination */
|
|
const unsigned char magic[] = { CMD_SET_RATE, 200, CMD_SET_RATE, 200, CMD_SET_RATE, 80 };
|
|
|
|
for (unsigned i = 0; i < sizeof(magic); ++i) {
|
|
_aux.write(magic[i]);
|
|
if (_aux.read() != RET_ACK) return false;
|
|
}
|
|
|
|
/* check device ID */
|
|
unsigned char id;
|
|
_aux.write(CMD_GET_ID);
|
|
if (_aux.read() != RET_ACK) return false;
|
|
id = _aux.read();
|
|
|
|
return id == 4;
|
|
}
|
|
|
|
public:
|
|
|
|
Ps2_mouse(Serial_interface &aux, Input::Event_queue &ev_queue)
|
|
:
|
|
_aux(aux),
|
|
_ev_queue(ev_queue), _type(PS2),
|
|
_packet_len(PS2_PACKET_LEN), _packet_idx(0)
|
|
{
|
|
for (unsigned i = 0; i < NUM_BUTTONS; ++i)
|
|
_button_state[i] = false;
|
|
reset();
|
|
}
|
|
|
|
void reset()
|
|
{
|
|
_aux.write(CMD_SET_DEFAULTS);
|
|
if (_aux.read() != RET_ACK)
|
|
PWRN("Could not set defaults");
|
|
|
|
_aux.write(CMD_ENABLE_STREAM);
|
|
if (_aux.read() != RET_ACK)
|
|
PWRN("Could not enable stream");
|
|
|
|
/* probe for protocol extensions */
|
|
if (_probe_exps2()) {
|
|
_type = EXPS2;
|
|
_packet_len = EXPS2_PACKET_LEN;
|
|
Genode::printf("Detected ExPS/2 mouse - activating scroll-wheel and 5-button support.\n");
|
|
} else if (_probe_imps2()) {
|
|
_type = IMPS2;
|
|
_packet_len = IMPS2_PACKET_LEN;
|
|
Genode::printf("Detected ImPS/2 mouse - activating scroll-wheel support.\n");
|
|
}
|
|
|
|
/* set sane sample rate */
|
|
_aux.write(CMD_SET_RATE);
|
|
if (_aux.read() == RET_ACK) {
|
|
_aux.write(100);
|
|
_aux.read();
|
|
}
|
|
}
|
|
|
|
|
|
/****************************
|
|
** Input driver interface **
|
|
****************************/
|
|
|
|
void handle_event()
|
|
{
|
|
_packet[_packet_idx++] = _aux.read();
|
|
if (_packet_idx < _packet_len)
|
|
return;
|
|
|
|
/* decode packet and feed event queue */
|
|
int rel_x = _packet[1], rel_y = _packet[2];
|
|
|
|
/* shortcut for packet header */
|
|
int ph = _packet[0];
|
|
|
|
/* sign extend motion values */
|
|
rel_x |= (ph & FLAG_X_SIGN) ? ~0xff : 0;
|
|
rel_y |= (ph & FLAG_Y_SIGN) ? ~0xff : 0;
|
|
|
|
/* discard motion values on overflow */
|
|
rel_x &= (ph & FLAG_X_OVER) ? 0 : ~0;
|
|
rel_y &= (ph & FLAG_Y_OVER) ? 0 : ~0;
|
|
|
|
/* generate motion event */
|
|
if (rel_x || rel_y) {
|
|
|
|
/* mirror y axis to make the movement correspond to screen coordinates */
|
|
rel_y = -rel_y;
|
|
|
|
if (verbose)
|
|
Genode::printf("post MOTION, rel_x = %d, rel_y = %d\n", rel_x, rel_y);
|
|
|
|
_ev_queue.add(Input::Event(Input::Event::MOTION,
|
|
0, 0, 0, rel_x, rel_y));
|
|
}
|
|
|
|
/* generate wheel event */
|
|
int rel_z = 0;
|
|
if (_type == IMPS2)
|
|
rel_z = (signed char) _packet[3];
|
|
if (_type == EXPS2) {
|
|
rel_z = _packet[3] & 0xf;
|
|
/* sign extend value */
|
|
rel_z |= (rel_z & 0x8) ? ~0x0f : 0;
|
|
}
|
|
if (rel_z) {
|
|
/* mirror y axis to make "scroll up" generate positive values */
|
|
rel_z = -rel_z;
|
|
|
|
if (verbose)
|
|
Genode::printf("post WHEEL, rel_z = %d\n", rel_z);
|
|
|
|
_ev_queue.add(Input::Event(Input::Event::WHEEL,
|
|
0, 0, 0, 0, rel_z));
|
|
}
|
|
|
|
/* detect changes of mouse-button state and post corresponding events */
|
|
_button_event(&_button_state[LEFT], ph & FLAG_BTN_LEFT, Input::BTN_LEFT);
|
|
_button_event(&_button_state[RIGHT], ph & FLAG_BTN_RIGHT, Input::BTN_RIGHT);
|
|
_button_event(&_button_state[MIDDLE], ph & FLAG_BTN_MIDDLE, Input::BTN_MIDDLE);
|
|
|
|
/* post extra button events */
|
|
if (_type == EXPS2) {
|
|
_button_event(&_button_state[SIDE], _packet[3] & 0x10, Input::BTN_SIDE);
|
|
_button_event(&_button_state[EXTRA], _packet[3] & 0x20, Input::BTN_EXTRA);
|
|
}
|
|
|
|
/* start new packet */
|
|
_packet_idx = 0;
|
|
}
|
|
|
|
bool event_pending() { return _aux.data_read_ready(); }
|
|
};
|
|
|
|
#endif /* _PS2_MOUSE_H_ */
|