/* * \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 #include #include #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_ */