root/src/add-ons/kernel/bus_managers/ps2/ps2_standard_mouse.cpp
/*
 * Copyright 2001-2014 Haiku, Inc. All rights reserved.
 * Distributed under the terms of the MIT License.
 *
 * Authors (in chronological order):
 *              Elad Lahav, elad@eldarshany.com
 *              Stefano Ceccherini, burton666@libero.it
 *              Axel Dörfler, axeld@pinc-software.de
 *              Marcus Overhagen, marcus@overhagen.de
 *              Clemens Zeidler, czeidler@gmx.de
 *              John Scipione, jscipione@gmail.com
 */


/*!     PS/2 mouse device driver

        A PS/2 mouse is connected to the IBM 8042 controller, and gets its
        name from the IBM PS/2 personal computer, which was the first to
        use this device. All resources are shared between the keyboard, and
        the mouse, referred to as the "Auxiliary Device".

        I/O:
        ~~~
        The controller has 3 I/O registers:
        1. Status (input), mapped to port 64h
        2. Control (output), mapped to port 64h
        3. Data (input/output), mapped to port 60h

        Data:
        ~~~~
        A packet read from the mouse data port is composed of
        three bytes:
                byte 0: status byte, where
                        - bit 7: Y overflow (1 = true)
                        - bit 6: X overflow (1 = true)
                        - bit 5: MSB of Y offset
                        - bit 4: MSB of X offset
                        - bit 3: Syncronization bit (always 1)
                        - bit 2: Middle button (1 = down)
                        - bit 1: Right button (1 = down)
                        - bit 0: Left button (1 = down)
                byte 1: X position change, since last probed (-127 to +127)
                byte 2: Y position change, since last probed (-127 to +127)

        Intellimouse mice send a four byte packet, where the first three
        bytes are the same as standard mice, and the last one reports the
        Z position, which is, usually, the wheel movement.

        Interrupts:
        ~~~~~~~~~~
        The PS/2 mouse device is connected to interrupt 12.
        The controller uses 3 consecutive interrupts to inform the computer
        that it has new data. On the first the data register holds the status
        byte, on the second the X offset, and on the 3rd the Y offset.
*/


#include <stdlib.h>
#include <string.h>

#include <keyboard_mouse_driver.h>

#include "ps2_service.h"
#include "ps2_standard_mouse.h"


//#define TRACE_PS2_MOUSE
#ifdef TRACE_PS2_MOUSE
#       define TRACE(x...) dprintf(x)
#else
#       define TRACE(x...)
#endif


const char* kStandardMousePath[4] = {
        "input/mouse/ps2/standard_0",
        "input/mouse/ps2/standard_1",
        "input/mouse/ps2/standard_2",
        "input/mouse/ps2/standard_3"
};

const char* kIntelliMousePath[4] = {
        "input/mouse/ps2/intelli_0",
        "input/mouse/ps2/intelli_1",
        "input/mouse/ps2/intelli_2",
        "input/mouse/ps2/intelli_3"
};


//!     Set sampling rate of the ps2 port.
static inline status_t
ps2_set_sample_rate(ps2_dev* dev, uint8 rate)
{
        return ps2_dev_command(dev, PS2_CMD_SET_SAMPLE_RATE, &rate, 1, NULL, 0);
}


//!     Converts a packet received by the mouse to a "movement".
static void
ps2_packet_to_movement(standard_mouse_cookie* cookie, uint8 packet[],
        mouse_movement* pos)
{
        int buttons = packet[0] & 7;
        int xDelta = ((packet[0] & 0x10) ? ~0xff : 0) | packet[1];
        int yDelta = ((packet[0] & 0x20) ? ~0xff : 0) | packet[2];
        int xDeltaWheel = 0;
        int yDeltaWheel = 0;
        bigtime_t currentTime = system_time();

        if (buttons != 0 && cookie->buttons_state == 0) {
                if (cookie->click_last_time + cookie->click_speed > currentTime)
                        cookie->click_count++;
                else
                        cookie->click_count = 1;

                cookie->click_last_time = currentTime;
        }

        cookie->buttons_state = buttons;

        if (cookie->flags & F_MOUSE_TYPE_INTELLIMOUSE) {
                yDeltaWheel = packet[3] & 0x07;
                if (packet[3] & 0x08)
                        yDeltaWheel |= ~0x07;
        }
#if 0
        if (cookie->flags & F_MOUSE_TYPE_2WHEELS) {
                switch (packet[3] & 0x0F) {
                        case 0x01: yDeltaWheel = +1; break; // wheel 1 down
                        case 0x0F: yDeltaWheel = -1; break; // wheel 1 up
                        case 0x02: xDeltaWheel = +1; break; // wheel 2 down
                        case 0x0E: xDeltaWheel = -1; break; // wheel 2 up
                }
        }
#endif

#if 0
        TRACE("packet: %02x %02x %02x %02x: xd %d, yd %d, 0x%x (%d), w-xd %d, "
                "w-yd %d\n", packet[0], packet[1], packet[2], packet[3],
                xDelta, yDelta, buttons, cookie->click_count, xDeltaWheel,
                yDeltaWheel);
#endif

        if (pos != NULL) {
                pos->xdelta = xDelta;
                pos->ydelta = yDelta;
                pos->buttons = buttons;
                pos->clicks = cookie->click_count;
                pos->modifiers = 0;
                pos->timestamp = currentTime;
                pos->wheel_ydelta = yDeltaWheel;
                pos->wheel_xdelta = xDeltaWheel;

                TRACE("ps2: ps2_packet_to_movement xdelta: %d, ydelta: %d, buttons %x, "
                        "clicks: %d, timestamp %" B_PRIdBIGTIME "\n",
                        xDelta, yDelta, buttons, cookie->click_count, currentTime);
        }
}


//!     Read a mouse event from the mouse events chain buffer.
static status_t
standard_mouse_read_event(standard_mouse_cookie* cookie,
        mouse_movement* movement)
{
        uint8 packet[PS2_MAX_PACKET_SIZE];
        status_t status;

        TRACE("ps2: standard_mouse_read_event\n");

        status = acquire_sem_etc(cookie->standard_mouse_sem, 1, B_CAN_INTERRUPT, 0);
        TRACE("ps2: standard_mouse_read_event acquired\n");
        if (status < B_OK)
                return status;

        if (!cookie->dev->active) {
                TRACE("ps2: standard_mouse_read_event: Error device no longer "
                        "active\n");
                return B_ERROR;
        }

        if (packet_buffer_read(cookie->standard_mouse_buffer, packet,
                cookie->dev->packet_size) != cookie->dev->packet_size) {
                TRACE("ps2: error copying buffer\n");
                return B_ERROR;
        }

        if (!(packet[0] & 8))
                panic("ps2: got broken data from packet_buffer_read\n");

        ps2_packet_to_movement(cookie, packet, movement);
        return B_OK;
}


//      #pragma mark - Interrupt handler functions


void
standard_mouse_disconnect(ps2_dev* dev)
{
        // the mouse device might not be opened at this point
        INFO("ps2: ps2_standard_mouse_disconnect %s\n", dev->name);
        if (dev->flags & PS2_FLAG_OPEN)
                release_sem(((standard_mouse_cookie*)dev->cookie)->standard_mouse_sem);
}


/*!     Interrupt handler for the mouse device. Called whenever the I/O
        controller generates an interrupt for the PS/2 mouse. Reads mouse
        information from the data port, and stores it, so it can be accessed
        by read() operations. The full data is obtained using 3 consecutive
        calls to the handler, each holds a different byte on the data port.
*/
int32
standard_mouse_handle_int(ps2_dev* dev)
{
        standard_mouse_cookie* cookie = (standard_mouse_cookie*)dev->cookie;
        const uint8 data = dev->history[0].data;

        TRACE("ps2: standard mouse: %1x\t%1x\t%1x\t%1x\t%1x\t%1x\t%1x\t%1x\n",
                data >> 7 & 1, data >> 6 & 1, data >> 5 & 1,
                data >> 4 & 1, data >> 3 & 1, data >> 2 & 1,
                data >> 1 & 1, data >> 0 & 1);

        if (cookie->packet_index == 0 && (data & 8) == 0) {
                INFO("ps2: bad mouse data, trying resync\n");
                return B_HANDLED_INTERRUPT;
        }

        // Workarounds for active multiplexing keyboard controllers
        // that lose data or send them to the wrong port.
        if (cookie->packet_index == 0 && (data & 0xc0) != 0) {
                INFO("ps2: strange mouse data, x/y overflow, trying resync\n");
                return B_HANDLED_INTERRUPT;
        }

        cookie->buffer[cookie->packet_index++] = data;

        if (cookie->packet_index != dev->packet_size) {
                // packet not yet complete
                return B_HANDLED_INTERRUPT;
        }

        // complete packet is assembled

        cookie->packet_index = 0;
        if (packet_buffer_write(cookie->standard_mouse_buffer,
                cookie->buffer, dev->packet_size) != dev->packet_size) {
                // buffer is full, drop new data
                return B_HANDLED_INTERRUPT;
        }

        release_sem_etc(cookie->standard_mouse_sem, 1, B_DO_NOT_RESCHEDULE);

        return B_INVOKE_SCHEDULER;
}


//      #pragma mark - probe_standard_mouse()


status_t
probe_standard_mouse(ps2_dev* dev)
{
        status_t status;
        uint8 deviceId = 0;

        // get device id
        status = ps2_dev_command(dev, PS2_CMD_GET_DEVICE_ID, NULL, 0,
                &deviceId, 1);
        if (status != B_OK) {
                INFO("ps2: probe_mouse get device id failed\n");
                return B_ERROR;
        }

        TRACE("ps2: probe_mouse device id: %2x\n", deviceId);

        // check for MS Intellimouse
        if (deviceId == 0) {
                uint8 alternate_device_id;
                status  = ps2_set_sample_rate(dev, 200);
                status |= ps2_set_sample_rate(dev, 100);
                status |= ps2_set_sample_rate(dev, 80);
                status |= ps2_dev_command(dev, PS2_CMD_GET_DEVICE_ID, NULL, 0,
                        &alternate_device_id, 1);
                if (status == 0) {
                        TRACE("ps2: probe_mouse alternate device id: %2x\n",
                                alternate_device_id);
                        deviceId = alternate_device_id;
                }
        }

        if (deviceId == PS2_DEV_ID_STANDARD
                || deviceId == PS2_DEV_ID_TOUCHPAD_RICATECH) {
                INFO("ps2: probe_mouse Standard PS/2 mouse found\n");
                dev->name = kStandardMousePath[dev->idx];
                dev->packet_size = PS2_PACKET_STANDARD;
        } else if (deviceId == PS2_DEV_ID_INTELLIMOUSE) {
                dev->name = kIntelliMousePath[dev->idx];
                dev->packet_size = PS2_PACKET_INTELLIMOUSE;
                INFO("ps2: probe_mouse Extended PS/2 mouse found\n");
        } else {
                INFO("ps2: probe_mouse Error unknown device id.\n");
                return B_ERROR;
        }

        return B_OK;
}


//      #pragma mark - Device functions


status_t
standard_mouse_open(const char* name, uint32 flags, void** _cookie)
{
        standard_mouse_cookie* cookie;
        status_t status;
        ps2_dev* dev = NULL;
        int i;

        TRACE("ps2: standard_mouse_open %s\n", name);

        for (dev = NULL, i = 0; i < PS2_DEVICE_COUNT; i++) {
                if (0 == strcmp(ps2_device[i].name, name)) {
                        dev = &ps2_device[i];
                        break;
                }
#if 0
                if (0 == strcmp(g_passthrough_dev.name, name)) {
                        dev = &g_passthrough_dev;
                        isSynapticsPTDevice = true;
                        break;
                }
#endif
        }

        if (dev == NULL) {
                TRACE("ps2: dev = NULL\n");
                return B_ERROR;
        }

        if (atomic_or(&dev->flags, PS2_FLAG_OPEN) & PS2_FLAG_OPEN)
                return B_BUSY;

        cookie = (standard_mouse_cookie*)malloc(sizeof(standard_mouse_cookie));
        if (cookie == NULL)
                goto err1;

        *_cookie = cookie;
        memset(cookie, 0, sizeof(*cookie));

        cookie->dev = dev;
        dev->cookie = cookie;
        dev->disconnect = &standard_mouse_disconnect;
        dev->handle_int = &standard_mouse_handle_int;

        if (strstr(dev->name, "standard") != NULL)
                cookie->flags = F_MOUSE_TYPE_STANDARD;

        if (strstr(dev->name, "intelli") != NULL)
                cookie->flags = F_MOUSE_TYPE_INTELLIMOUSE;

        cookie->standard_mouse_buffer
                = create_packet_buffer(MOUSE_HISTORY_SIZE * dev->packet_size);
        if (cookie->standard_mouse_buffer == NULL) {
                TRACE("ps2: can't allocate mouse actions buffer\n");
                goto err2;
        }

        // create the mouse semaphore, used for synchronization between
        // the interrupt handler and the read operation
        cookie->standard_mouse_sem = create_sem(0, "ps2_standard_mouse_sem");
        if (cookie->standard_mouse_sem < 0) {
                TRACE("ps2: failed creating PS/2 mouse semaphore!\n");
                goto err3;
        }

        status = ps2_dev_command(dev, PS2_CMD_ENABLE, NULL, 0, NULL, 0);
        if (status < B_OK) {
                INFO("ps2: cannot enable mouse %s\n", name);
                goto err4;
        }

        atomic_or(&dev->flags, PS2_FLAG_ENABLED);


        TRACE("ps2: standard_mouse_open %s success\n", name);
        return B_OK;

err4:
        delete_sem(cookie->standard_mouse_sem);
err3:
        delete_packet_buffer(cookie->standard_mouse_buffer);
err2:
        free(cookie);
err1:
        atomic_and(&dev->flags, ~PS2_FLAG_OPEN);

        TRACE("ps2: standard_mouse_open %s failed\n", name);
        return B_ERROR;
}


status_t
standard_mouse_close(void* _cookie)
{
        standard_mouse_cookie* cookie = (standard_mouse_cookie*)_cookie;

        TRACE("ps2: standard_mouse_close %s enter\n", cookie->dev->name);

        ps2_dev_command_timeout(cookie->dev, PS2_CMD_DISABLE, NULL, 0, NULL, 0,
                150000);

        delete_packet_buffer(cookie->standard_mouse_buffer);
        delete_sem(cookie->standard_mouse_sem);

        atomic_and(&cookie->dev->flags, ~PS2_FLAG_OPEN);
        atomic_and(&cookie->dev->flags, ~PS2_FLAG_ENABLED);

        TRACE("ps2: standard_mouse_close %s done\n", cookie->dev->name);
        return B_OK;
}


status_t
standard_mouse_freecookie(void* _cookie)
{
        standard_mouse_cookie* cookie = (standard_mouse_cookie*)_cookie;
        free(cookie);
        return B_OK;
}


static status_t
standard_mouse_read(void* cookie, off_t pos, void* buffer, size_t* _length)
{
        *_length = 0;
        return B_NOT_ALLOWED;
}


static status_t
standard_mouse_write(void* cookie, off_t pos, const void* buffer,
        size_t* _length)
{
        *_length = 0;
        return B_NOT_ALLOWED;
}


status_t
standard_mouse_ioctl(void* _cookie, uint32 op, void* buffer, size_t length)
{
        standard_mouse_cookie* cookie = (standard_mouse_cookie*)_cookie;

        switch (op) {
                case MS_NUM_EVENTS:
                {
                        int32 count;
                        TRACE("ps2: ioctl MS_NUM_EVENTS\n");
                        get_sem_count(cookie->standard_mouse_sem, &count);
                        return count;
                }

                case MS_READ:
                {
                        mouse_movement movement;
                        status_t status;
                        TRACE("ps2: ioctl MS_READ\n");
                        if ((status = standard_mouse_read_event(cookie, &movement)) < B_OK)
                                return status;
//                      TRACE("%s %d %d %d %d\n", cookie->dev->name,
//                              movement.xdelta, movement.ydelta, movement.buttons,
//                              movement.clicks);
                        return user_memcpy(buffer, &movement, sizeof(movement));
                }

                case MS_SET_TYPE:
                        TRACE("ps2: ioctl MS_SET_TYPE not implemented\n");
                        return B_BAD_VALUE;

                case MS_SET_MAP:
                        TRACE("ps2: ioctl MS_SET_MAP (set mouse mapping) not "
                                "implemented\n");
                        return B_BAD_VALUE;

                case MS_GET_ACCEL:
                        TRACE("ps2: ioctl MS_GET_ACCEL (get mouse acceleration) not "
                                "implemented\n");
                        return B_BAD_VALUE;

                case MS_SET_ACCEL:
                        TRACE("ps2: ioctl MS_SET_ACCEL (set mouse acceleration) not "
                                "implemented\n");
                        return B_BAD_VALUE;

                case MS_SET_CLICKSPEED:
                        TRACE("ps2: ioctl MS_SETCLICK (set click speed)\n");
                        return user_memcpy(&cookie->click_speed, buffer, sizeof(bigtime_t));

                default:
                        TRACE("ps2: ioctl unknown mouse opcode: %" B_PRIu32 "\n", op);
                        return B_DEV_INVALID_IOCTL;
        }
}


device_hooks gStandardMouseDeviceHooks = {
        standard_mouse_open,
        standard_mouse_close,
        standard_mouse_freecookie,
        standard_mouse_ioctl,
        standard_mouse_read,
        standard_mouse_write,
};