Files
OS-X-Voodoo-PS2-Controller/VoodooPS2Mouse/VoodooPS2Mouse.cpp
2013-12-05 06:08:58 -08:00

1404 lines
46 KiB
C++

/*
* Copyright (c) 1998-2000 Apple Computer, Inc. All rights reserved.
*
* @APPLE_LICENSE_HEADER_START@
*
* The contents of this file constitute Original Code as defined in and
* are subject to the Apple Public Source License Version 1.1 (the
* "License"). You may not use this file except in compliance with the
* License. Please obtain a copy of the License at
* http://www.apple.com/publicsource and read it before using this file.
*
* This Original Code and all software distributed under the License are
* distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, EITHER
* EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
* INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE OR NON-INFRINGEMENT. Please see the
* License for the specific language governing rights and limitations
* under the License.
*
* @APPLE_LICENSE_HEADER_END@
*/
#include <IOKit/IOLib.h>
#include <IOKit/hidsystem/IOHIDParameter.h>
#include "VoodooPS2Controller.h"
#include "VoodooPS2Mouse.h"
// enable for mouse debugging
#ifdef DEBUG_MSG
#define DEBUG_VERBOSE
#endif
// =============================================================================
// ApplePS2Mouse Class Implementation
//
OSDefineMetaClassAndStructors(ApplePS2Mouse, IOHIPointing);
UInt32 ApplePS2Mouse::deviceType() { return NX_EVS_DEVICE_TYPE_MOUSE; };
UInt32 ApplePS2Mouse::interfaceID() { return NX_EVS_DEVICE_INTERFACE_BUS_ACE; };
IOItemCount ApplePS2Mouse::buttonCount() { return _buttonCount; };
IOFixed ApplePS2Mouse::resolution() { return _resolution; };
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
bool ApplePS2Mouse::init(OSDictionary * dict)
{
//
// Initialize this object's minimal state. This is invoked right after this
// object is instantiated.
//
if (!super::init(dict))
return false;
// find config specific to Platform Profile
OSDictionary* list = OSDynamicCast(OSDictionary, dict->getObject(kPlatformProfile));
OSDictionary* config = ApplePS2Controller::makeConfigurationNode(list);
if (config)
{
// if DisableDevice is Yes, then do not load at all...
OSBoolean* disable = OSDynamicCast(OSBoolean, config->getObject(kDisableDevice));
if (disable && disable->isTrue())
{
config->release();
return false;
}
#ifdef DEBUG
// save configuration for later/diagnostics...
setProperty(kMergedConfiguration, config);
#endif
}
// initialize state...
_device = 0;
_interruptHandlerInstalled = false;
_packetByteCount = 0;
_lastdata = 0;
_packetLength = kPacketLengthStandard;
defres = 150 << 16; // (default is 150 dpi; 6 counts/mm)
forceres = false;
mouseyinverter = 1; // 1 for normal, -1 for inverting
scrollyinverter = 1; // 1 for normal, -1 for inverting
_type = kMouseTypeStandard;
_buttonCount = 3;
_mouseInfoBytes = (UInt32)-1;
ignoreall = false;
ledpresent = false;
resmode = -1;
forcesetres = false;
scrollres = 10;
actliketrackpad = false;
keytime = 0;
maxaftertyping = 500000000;
buttonmask = ~0;
scroll = true;
noled = false;
wakedelay = 1000;
usb_mouse_stops_trackpad = true;
mousecount = 0;
_cmdGate = 0;
// state for middle button
_buttonTimer = 0;
_mbuttonstate = STATE_NOBUTTONS;
_pendingbuttons = 0;
_buttontime = 0;
_maxmiddleclicktime = 100000000;
// load settings
setParamPropertiesGated(config);
OSSafeRelease(config);
// remove some properties so system doesn't think it is a trackpad
// this should cause "Product" = "Mouse" in ioreg.
if (!actliketrackpad)
{
removeProperty("VendorID");
removeProperty("ProductID");
removeProperty("HIDPointerAccelerationType");
removeProperty("HIDScrollAccelerationType");
removeProperty("TrackpadScroll");
}
IOLog("VoodooPS2Mouse Version 1.8.9 loaded...\n");
return true;
}
void ApplePS2Mouse::setParamPropertiesGated(OSDictionary * config)
{
if (NULL == config)
return;
const struct {const char *name; int *var;} int32vars[]={
{"DefaultResolution", &defres},
{"ResolutionMode", &resmode},
{"ScrollResolution", &scrollres},
{"MouseYInverter", &mouseyinverter},
{"ScrollYInverter", &scrollyinverter},
{"WakeDelay", &wakedelay},
{"MouseCount", &mousecount},
{"ButtonCount", &_buttonCount},
};
const struct {const char *name; int *var;} boolvars[]={
{"ForceDefaultResolution", &forceres},
{"ForceSetResolution", &forcesetres},
{"ActLikeTrackpad", &actliketrackpad},
{"DisableLEDUpdating", &noled},
{"FakeMiddleButton", &_fakemiddlebutton},
};
const struct {const char* name; bool* var;} lowbitvars[]={
{"TrackpadScroll", &scroll},
{"OutsidezoneNoAction When Typing", &outzone_wt},
{"PalmNoAction Permanent", &palm},
{"PalmNoAction When Typing", &palm_wt},
{"USBMouseStopsTrackpad", &usb_mouse_stops_trackpad},
};
const struct {const char* name; uint64_t* var; } int64vars[]={
{"MiddleClickTime", &_maxmiddleclicktime},
};
OSNumber *num;
OSBoolean *bl;
int oldmousecount = mousecount;
bool old_usb_mouse_stops_trackpad = usb_mouse_stops_trackpad;
// 64-bit config items
for (int i = 0; i < countof(int64vars); i++)
if ((num=OSDynamicCast(OSNumber, config->getObject(int64vars[i].name))))
{
*int64vars[i].var = num->unsigned64BitValue();
setProperty(int64vars[i].name, *int64vars[i].var, 64);
}
// boolean config items
for (int i = 0; i < countof(boolvars); i++)
if ((bl=OSDynamicCast (OSBoolean,config->getObject (boolvars[i].name))))
{
*boolvars[i].var = bl->isTrue();
setProperty(boolvars[i].name, *boolvars[i].var ? kOSBooleanTrue : kOSBooleanFalse);
}
// lowbit config items
for (int i = 0; i < countof(lowbitvars); i++)
if ((num=OSDynamicCast (OSNumber,config->getObject(lowbitvars[i].name))))
{
*lowbitvars[i].var = (num->unsigned32BitValue()&0x1)?true:false;
setProperty(lowbitvars[i].name, *lowbitvars[i].var ? 1 : 0, 32);
}
// 32-bit config items
for (int i = 0; i < countof(int32vars);i++)
if ((num=OSDynamicCast (OSNumber,config->getObject (int32vars[i].name))))
{
*int32vars[i].var = num->unsigned32BitValue();
setProperty(int32vars[i].name, *int32vars[i].var, 32);
}
// check for special terminating sequence from PS2Daemon
if (-1 == mousecount)
{
// when system is shutting down/restarting we want to force LED off
if (ledpresent && !noled)
setTouchpadLED(0x10);
mousecount = oldmousecount;
}
// disable trackpad when USB mouse is plugged in
// check for mouse count changing...
if ((oldmousecount != 0) != (mousecount != 0) || old_usb_mouse_stops_trackpad != usb_mouse_stops_trackpad)
{
// either last mouse removed or first mouse added
ignoreall = (mousecount != 0) && usb_mouse_stops_trackpad;
updateTouchpadLED();
}
// convert to IOFixed format...
defres <<= 16;
}
IOReturn ApplePS2Mouse::setParamProperties(OSDictionary* dict)
{
if (_cmdGate)
{
// syncronize through workloop...
_cmdGate->runAction(OSMemberFunctionCast(IOCommandGate::Action, this, &ApplePS2Mouse::setParamPropertiesGated), dict);
}
return super::setParamProperties(dict);
}
IOReturn ApplePS2Mouse::setProperties(OSObject *props)
{
OSDictionary *dict = OSDynamicCast(OSDictionary, props);
if (dict && _cmdGate)
{
// syncronize through workloop...
_cmdGate->runAction(OSMemberFunctionCast(IOCommandGate::Action, this, &ApplePS2Mouse::setParamPropertiesGated), dict);
}
return super::setProperties(props);
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
ApplePS2Mouse* ApplePS2Mouse::probe(IOService * provider, SInt32 * score)
{
DEBUG_LOG("ApplePS2Mouse::probe entered...\n");
//
// The driver has been instructed to verify the presence of the actual
// hardware we represent. We are guaranteed by the controller that the
// mouse clock is enabled and the mouse itself is disabled (thus it
// won't send any asynchronous mouse data that may mess up the
// responses expected by the commands we send it).
//
if (!super::probe(provider, score))
return 0;
ApplePS2MouseDevice* device = (ApplePS2MouseDevice*)provider;
//
// Check to see if acknowledges are being received for commands to the mouse.
//
// (get information command)
TPS2Request<6> request;
request.commands[0].command = kPS2C_WriteCommandPort;
request.commands[0].inOrOut = kCP_TransmitToMouse;
request.commands[1].command = kPS2C_WriteDataPort;
request.commands[1].inOrOut = kDP_GetMouseInformation;
request.commands[2].command = kPS2C_ReadDataPortAndCompare;
request.commands[2].inOrOut = kSC_Acknowledge;
request.commands[3].command = kPS2C_ReadDataPort;
request.commands[3].inOrOut = 0;
request.commands[4].command = kPS2C_ReadDataPort;
request.commands[4].inOrOut = 0;
request.commands[5].command = kPS2C_ReadDataPort;
request.commands[5].inOrOut = 0;
request.commandsCount = 6;
assert(request.commandsCount <= countof(request.commands));
device->submitRequestAndBlock(&request);
DEBUG_LOG("ApplePS2Mouse::probe leaving.\n");
return 6 == request.commandsCount ? this : 0;
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
bool ApplePS2Mouse::start(IOService * provider)
{
DEBUG_LOG("%s::start called\n", getName());
//
// The driver has been instructed to start. This is called after a
// successful probe and match.
//
if (!super::start(provider))
return false;
//
// Maintain a pointer to and retain the provider object.
//
_device = (ApplePS2MouseDevice *)provider;
_device->retain();
//
// Setup workloop with command gate for thread syncronization...
//
IOWorkLoop* pWorkLoop = getWorkLoop();
_cmdGate = IOCommandGate::commandGate(this);
if (!pWorkLoop || !_cmdGate)
{
_device->release();
return false;
}
pWorkLoop->addEventSource(_cmdGate);
//
// Setup button timer event source
//
_buttonTimer = IOTimerEventSource::timerEventSource(this, OSMemberFunctionCast(IOTimerEventSource::Action, this, &ApplePS2Mouse::onButtonTimer));
if (_buttonTimer)
pWorkLoop->addEventSource(_buttonTimer);
//
// Lock the controller during initialization
//
_device->lock();
//
// Reset and enable the mouse.
//
resetMouse();
//
// Install our driver's interrupt handler, for asynchronous data delivery.
//
_device->installInterruptAction(this,
OSMemberFunctionCast(PS2InterruptAction, this, &ApplePS2Mouse::interruptOccurred),
OSMemberFunctionCast(PS2PacketAction, this, &ApplePS2Mouse::packetReady));
_interruptHandlerInstalled = true;
// now safe to allow other threads
_device->unlock();
//
// Install our power control handler.
//
_device->installPowerControlAction( this,OSMemberFunctionCast
(PS2PowerControlAction,this, &ApplePS2Mouse::setDevicePowerState) );
_powerControlHandlerInstalled = true;
//
// Install message hook for keyboard to trackpad communication
//
if (actliketrackpad)
{
_device->installMessageAction( this,
OSMemberFunctionCast(PS2MessageAction, this, &ApplePS2Mouse::receiveMessage));
_messageHandlerInstalled = true;
}
return true;
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void ApplePS2Mouse::stop(IOService * provider)
{
//
// The driver has been instructed to stop. Note that we must break all
// connections to other service objects now (ie. no registered actions,
// no pointers and retains to objects, etc), if any.
//
assert(_device == provider);
//
// Disable the mouse itself, so that it may stop reporting mouse events.
//
setMouseEnable(false);
// free up the command gate
IOWorkLoop* pWorkLoop = getWorkLoop();
if (pWorkLoop)
{
if (_cmdGate)
{
pWorkLoop->removeEventSource(_cmdGate);
_cmdGate->release();
_cmdGate = 0;
}
if (_buttonTimer)
{
pWorkLoop->removeEventSource(_buttonTimer);
_buttonTimer->release();
_buttonTimer = 0;
}
}
//
// Uninstall the interrupt handler.
//
if ( _interruptHandlerInstalled ) _device->uninstallInterruptAction();
_interruptHandlerInstalled = false;
//
// Uninstall the power control handler.
//
if ( _powerControlHandlerInstalled ) _device->uninstallPowerControlAction();
_powerControlHandlerInstalled = false;
//
// Uinstall message handler.
//
if (_messageHandlerInstalled)
{
_device->uninstallMessageAction();
_messageHandlerInstalled = false;
}
//
// Release the pointer to the provider object.
//
OSSafeReleaseNULL(_device);;
super::stop(provider);
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void ApplePS2Mouse::resetMouse()
{
DEBUG_LOG("%s::resetMouse called\n", getName());
//
// Reset the mouse to its default state.
//
// Contrary to what you might think kDP_SetDefaultsAndDisable does not set
// relative mode (for trackpads), as kDP_SetDefaults does. This is probably an
// oversight/error in the naming of these constants.
//
// For the kDP_SetDefaults, there is no point in trying to read the ACK
// ... it is just going to time out... and then later show up in the
// input stream unexpectedly.
TPS2Request<8> request;
request.commands[0].command = kPS2C_WriteCommandPort;
request.commands[0].inOrOut = kCP_TransmitToMouse;
request.commands[1].command = kPS2C_WriteDataPort;
request.commands[1].inOrOut = kDP_SetDefaults;
request.commands[2].command = kPS2C_WriteCommandPort;
request.commands[2].inOrOut = kCP_TransmitToMouse;
request.commands[3].command = kPS2C_WriteDataPort;
request.commands[3].inOrOut = kDP_GetMouseInformation;
request.commands[4].command = kPS2C_ReadDataPortAndCompare;
request.commands[4].inOrOut = kSC_Acknowledge;
request.commands[5].command = kPS2C_ReadDataPort;
request.commands[5].inOrOut = 0;
request.commands[6].command = kPS2C_ReadDataPort;
request.commands[6].inOrOut = 0;
request.commands[7].command = kPS2C_ReadDataPort;
request.commands[7].inOrOut = 0;
request.commandsCount = 8;
assert(request.commandsCount <= countof(request.commands));
_device->submitRequestAndBlock(&request);
if (8 != request.commandsCount)
DEBUG_LOG("%s: reset mouse sequence failed: %d\n", getName(), request.commandsCount);
// Now deal with Synaptics specifics (ActLikeTrackpad trick)...
ledpresent = false;
do if (actliketrackpad && !noled)
{
// do Synaptics specific, but only if it is Synaptics device
UInt8 buf3[3];
if (!getTouchPadData(0x0, buf3) || (0x46 != buf3[1] && 0x47 != buf3[1]))
break;
// it is Synaptics, now test for LED capability...
if (!getTouchPadData(0x2, buf3) || !(buf3[0] & 0x80))
break;
int nExtendedQueries = (buf3[0] & 0x70) >> 4;
// check LED capability if query is supported
if (nExtendedQueries >= 1 && getTouchPadData(0x9, buf3))
{
ledpresent = (buf3[0] >> 6) & 1;
DEBUG_LOG("%s: ledpresent=%d\n", getName(), ledpresent);
}
} while (false);
//
// Obtain our mouse's resolution and sampling rate.
//
if (_mouseInfoBytes == (UInt32)-1)
{
// attempt switch to high resolution
if (forcesetres && resmode != -1)
setMouseResolution(resmode);
_mouseInfoBytes = getMouseInformation();
if (forceres)
_resolution = defres;
else
switch (_mouseInfoBytes & 0x00FF00)
{
case 0x0000: _resolution = (25) << 16; break; // 25 dpi
case 0x0100: _resolution = (50) << 16; break; // 50 dpi
case 0x0200: _resolution = (100) << 16; break; // 100 dpi
case 0x0300: _resolution = (200) << 16; break; // 200 dpi
default: _resolution = (150) << 16; break; // 150 dpi
}
DEBUG_LOG("%s: _resolution=0x%x\n", getName(), _resolution);
}
else
{
setMouseResolution((_mouseInfoBytes >> 8) & 3);
}
//
// Enable the Intellimouse mode, should this be an Intellimouse.
//
_type = setIntellimouseMode();
if (kMouseTypeStandard != _type)
{
_packetLength = kPacketLengthIntellimouse;
if (kMouseTypeIntellimouseExplorer == _type)
_buttonCount = 5;
else
_buttonCount = 3;
//
// Report the resolution of the scroll wheel. This property must
// be present to enable acceleration for Z-axis movement.
//
setProperty(kIOHIDScrollResolutionKey, (scrollres << 16), 32);
if (!actliketrackpad)
setProperty(kIOHIDScrollAccelerationTypeKey, kIOHIDMouseAccelerationType);
else
setProperty(kIOHIDScrollAccelerationTypeKey, kIOHIDTrackpadAccelerationType);
}
else
{
_packetLength = kPacketLengthStandard;
removeProperty(kIOHIDScrollResolutionKey);
removeProperty(kIOHIDScrollAccelerationTypeKey);
}
if (!actliketrackpad)
setProperty(kIOHIDPointerAccelerationTypeKey, kIOHIDMouseAccelerationType);
else
setProperty(kIOHIDPointerAccelerationTypeKey, kIOHIDTrackpadAccelerationType);
// simulate three buttons with only two buttons if enabled
if (2 == _buttonCount && _fakemiddlebutton && _buttonTimer)
_buttonCount = 3;
// initialize packet buffer
_packetByteCount = 0;
_ringBuffer.reset();
//
// Finally, we enable the mouse itself, so that it may start reporting
// mouse events.
//
setMouseEnable(true);
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void ApplePS2Mouse::initMouse()
{
DEBUG_LOG("%s::initMouse called\n", getName());
//
// Request the mouse to stop. A 0xF5 command is issued.
//
setMouseEnable(false);
//
// Reset the mouse (synchronous).
//
resetMouse();
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
PS2InterruptResult ApplePS2Mouse::interruptOccurred(UInt8 data) // PS2InterruptAction
{
//
// This will be invoked automatically from our device when asynchronous mouse
// needs to be delivered. Process the mouse data.
//
UInt8* packet = _ringBuffer.head();
// special case for $AA $00, spontaneous reset (usually due to static electricity)
if (kSC_Reset == _lastdata && 0x00 == data)
{
IOLog("%s: Unexpected reset (%02x %02x) request from PS/2 controller\n", getName(), _lastdata, data);
// spontaneous reset, device has announced with $AA $00, schedule a reset
packet[0] = 0x00;
packet[1] = kSC_Reset;
_ringBuffer.advanceHead(kPacketLengthMax);
_packetByteCount = 0;
return kPS2IR_packetReady;
}
_lastdata = data;
// We ignore all bytes until we see the start of a packet, otherwise the mouse
// packets may get out of sequence and things will get very confusing.
//
if (_packetByteCount == 0 && ((data == kSC_Acknowledge) || !(data & 0x08)))
{
IOLog("%s: Unexpected byte0 data (%02x) from PS/2 controller\n", getName(), data);
//
// Reset the mouse when packet synchronization is lost. Limit the number
// of consecutive resets to guard against flaky hardware.
//
if (_mouseResetCount < 5)
{
_mouseResetCount++;
packet[0] = 0x00;
packet[1] = kSC_Acknowledge;
_ringBuffer.advanceHead(kPacketLengthMax);
return kPS2IR_packetReady;
}
return kPS2IR_packetBuffering;
}
//
// Add this byte to the packet buffer. If the packet is complete, that is,
// we have the three (or four) bytes, dispatch this packet for processing.
//
packet[_packetByteCount++] = data;
if (_packetByteCount == _packetLength)
{
_mouseResetCount = 0;
_ringBuffer.advanceHead(kPacketLengthMax);
_packetByteCount = 0;
return kPS2IR_packetReady;
}
return kPS2IR_packetBuffering;
}
void ApplePS2Mouse::packetReady()
{
// empty the ring buffer, dispatching each packet...
// all packets are kPacketLengthMax even if _packetLength is smaller, as they
// are padded at interrupt time.
while (_ringBuffer.count() >= kPacketLengthMax)
{
UInt8* packet = _ringBuffer.tail();
if (0x00 != packet[0])
{
// normal packet with deltas
dispatchRelativePointerEventWithPacket(_ringBuffer.tail(), _packetLength);
}
else
{
////initMouse();
}
_ringBuffer.advanceTail(kPacketLengthMax);
}
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void ApplePS2Mouse::onButtonTimer(void)
{
uint64_t now_abs;
clock_get_uptime(&now_abs);
middleButton(lastbuttons, now_abs, fromTimer);
}
UInt32 ApplePS2Mouse::middleButton(UInt32 buttons, uint64_t now_abs, MBComingFrom from)
{
if (!_fakemiddlebutton || _buttonCount <= 2 || (ignoreall && fromMouse == from))
return buttons;
// cancel timer if we see input before timeout has fired, but after expired
bool timeout = false;
uint64_t now_ns;
absolutetime_to_nanoseconds(now_abs, &now_ns);
if (fromTimer == from || now_ns - _buttontime > _maxmiddleclicktime)
timeout = true;
//
// A state machine to simulate middle buttons with two buttons pressed
// together.
//
switch (_mbuttonstate)
{
// no buttons down, waiting for something to happen
case STATE_NOBUTTONS:
if (buttons & 0x4)
_mbuttonstate = STATE_NOOP;
else if (0x3 == buttons)
_mbuttonstate = STATE_MIDDLE;
else if (0x0 != buttons)
{
// only single button, so delay this for a bit
_pendingbuttons = buttons;
_buttontime = now_ns;
setTimerTimeout(_buttonTimer, _maxmiddleclicktime);
_mbuttonstate = STATE_WAIT4TWO;
}
break;
// waiting for second button to come down or timeout
case STATE_WAIT4TWO:
if (!timeout && 0x3 == buttons)
{
_pendingbuttons = 0;
cancelTimer(_buttonTimer);
_mbuttonstate = STATE_MIDDLE;
}
else if (timeout || buttons != _pendingbuttons)
{
if (fromTimer == from || !(buttons & _pendingbuttons))
dispatchRelativePointerEventX(0, 0, buttons|_pendingbuttons, now_abs);
_pendingbuttons = 0;
cancelTimer(_buttonTimer);
if (0x0 == buttons)
_mbuttonstate = STATE_NOBUTTONS;
else
_mbuttonstate = STATE_NOOP;
}
break;
// both buttons down and delivering middle button
case STATE_MIDDLE:
if (0x0 == buttons)
_mbuttonstate = STATE_NOBUTTONS;
else if (0x3 != (buttons & 0x3))
{
// only single button, so delay to see if we get to none
_pendingbuttons = buttons;
_buttontime = now_ns;
setTimerTimeout(_buttonTimer, _maxmiddleclicktime);
_mbuttonstate = STATE_WAIT4NONE;
}
break;
// was middle button, but one button now up, waiting for second to go up
case STATE_WAIT4NONE:
if (!timeout && 0x0 == buttons)
{
_pendingbuttons = 0;
cancelTimer(_buttonTimer);
_mbuttonstate = STATE_NOBUTTONS;
}
else if (timeout || buttons != _pendingbuttons)
{
if (fromTimer == from)
dispatchRelativePointerEventX(0, 0, buttons|_pendingbuttons, now_abs);
_pendingbuttons = 0;
cancelTimer(_buttonTimer);
if (0x0 == buttons)
_mbuttonstate = STATE_NOBUTTONS;
else
_mbuttonstate = STATE_NOOP;
}
break;
case STATE_NOOP:
if (0x0 == buttons)
_mbuttonstate = STATE_NOBUTTONS;
break;
}
// modify buttons after new state set
switch (_mbuttonstate)
{
case STATE_MIDDLE:
buttons = 0x4;
break;
case STATE_WAIT4NONE:
case STATE_WAIT4TWO:
buttons &= ~0x3;
break;
case STATE_NOBUTTONS:
case STATE_NOOP:
break;
}
// return modified buttons
return buttons;
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void ApplePS2Mouse::dispatchRelativePointerEventWithPacket(UInt8 * packet,
UInt32 packetSize)
{
//
// Process the three byte mouse packet that was retreived from the mouse.
// The format of the bytes is as follows:
//
// 7 6 5 4 3 2 1 0
// YO XO YS XS 1 M R L
// X7 X6 X5 X4 X3 X3 X1 X0
// Y7 Y6 Y5 Y4 Y3 Y2 Y1 Y0
// Z7 Z6 Z5 Z4 Z3 Z2 Z1 Z0 <- fourth byte returned only for Intellimouse type
//
// 0 0 B5 B4 Z3 Z2 Z1 Z0 <- fourth byte for 5-button wheel mouse mode
//
UInt32 buttons = packet[0] & 0x7;
SInt32 dx = ((packet[0] & 0x10) ? 0xffffff00 : 0 ) | packet[1];
SInt32 dy = -(((packet[0] & 0x20) ? 0xffffff00 : 0 ) | packet[2]);
SInt16 dz = 0;
uint64_t now_abs;
clock_get_uptime(&now_abs);
uint64_t now_ns;
absolutetime_to_nanoseconds(now_abs, &now_ns);
if ( packetSize > 3 )
{
// Pull out fourth and fifth buttons.
if (_type == kMouseTypeIntellimouseExplorer)
{
buttons |= (packet[3] & 0x30) >> 1;
//if (packet[3] & 0x10) buttons |= 0x8; // fourth button (bit 4 in packet)
//if (packet[3] & 0x20) buttons |= 0x10; // fifth button (bit 5 in packet)
}
//
// We treat the 4th byte in the packet as a 8-bit signed Z value.
// There are mice that can report only 4-bits for the Z data, and
// use two of the remaining 4 bits for buttons 4 and 5. To enable
// the 5-button mouse mode, the command sequence should be:
//
// setMouseSampleRate(200);
// setMouseSampleRate(200);
// setMouseSampleRate(80);
//
// Those devices will return 0x04 for the device ID.
//
// %%KCD - As it turns out, the valid range for scroll data from
// PS2 mice is -8 to +7, thus the upper four bits are just a sign
// bit. If we just sign extend the lower four bits, the scroll
// calculation works for normal scrollwheel mice and five button mice.
if (!actliketrackpad || scroll)
dz = (SInt16)(((SInt8)(packet[3] << 4)) >> 4);
}
buttons = middleButton(buttons, now_abs, fromMouse);
lastbuttons = buttons;
// ignore button 1 and 2 (could be simulated by trackpad) if just after typing
if (palm_wt || outzone_wt)
{
if (now_ns-keytime <= maxaftertyping)
buttonmask = ~(buttons & 0x3);
else
buttonmask = ~0;
buttons &= buttonmask;
}
if (!ignoreall)
dispatchRelativePointerEventX(dx, mouseyinverter*dy, buttons, now_abs);
if ( dz && (!(palm_wt || outzone_wt) || now_ns-keytime > maxaftertyping))
{
//
// The Z counter is negative on an upwards scroll (away from the user),
// and positive when scrolling downwards. Invert this before passing to
// HID/CG.
//
if (!ignoreall)
dispatchScrollWheelEventX(-scrollyinverter*dz, 0, 0, now_abs);
}
#ifdef DEBUG_VERBOSE
IOLog("ps2m: dx=%d, dy=%d, dz=%d, buttons=%d\n", dx, dy, dz, buttons);
#endif
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void ApplePS2Mouse::setMouseEnable(bool enable)
{
//
// Instructs the mouse to start or stop the reporting of mouse events.
// Be aware that while the mouse is enabled, asynchronous mouse events
// may arrive in the middle of command sequences sent to the controller,
// and may get confused for expected command responses.
//
// It is safe to issue this request from the interrupt/completion context.
//
// (mouse enable/disable command)
TPS2Request<3> request;
request.commands[0].command = kPS2C_WriteCommandPort;
request.commands[0].inOrOut = kCP_TransmitToMouse;
request.commands[1].command = kPS2C_WriteDataPort;
request.commands[1].inOrOut = enable ? kDP_Enable : kDP_SetDefaultsAndDisable;
request.commands[2].command = kPS2C_ReadDataPortAndCompare;
request.commands[2].inOrOut = kSC_Acknowledge;
request.commandsCount = 3;
assert(request.commandsCount <= countof(request.commands));
_device->submitRequestAndBlock(&request);
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void ApplePS2Mouse::setMouseSampleRate(UInt8 sampleRate)
{
DEBUG_LOG("%s::setMouseSampleRate(0x%x)\n", getName(), sampleRate);
//
// Instructs the mouse to change its sampling rate to the given value, in
// reports per second.
//
// It is safe to issue this request from the interrupt/completion context.
//
// (set mouse sample rate command)
TPS2Request<6> request;
request.commands[0].command = kPS2C_WriteCommandPort;
request.commands[0].inOrOut = kCP_TransmitToMouse;
request.commands[1].command = kPS2C_WriteDataPort;
request.commands[1].inOrOut = kDP_SetMouseSampleRate;
request.commands[2].command = kPS2C_ReadDataPortAndCompare;
request.commands[2].inOrOut = kSC_Acknowledge;
request.commands[3].command = kPS2C_WriteCommandPort;
request.commands[3].inOrOut = kCP_TransmitToMouse;
request.commands[4].command = kPS2C_WriteDataPort;
request.commands[4].inOrOut = sampleRate;
request.commands[5].command = kPS2C_ReadDataPortAndCompare;
request.commands[5].inOrOut = kSC_Acknowledge;
request.commandsCount = 6;
assert(request.commandsCount <= countof(request.commands));
_device->submitRequestAndBlock(&request);
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void ApplePS2Mouse::setMouseResolution(UInt8 resolution)
{
//
// Instructs the mouse to change its resolution given the following
// resolution codes:
//
// 0 = 25 dpi
// 1 = 50 dpi
// 2 = 100 dpi
// 3 = 200 dpi
//
// It is safe to issue this request from the interrupt/completion context.
//
DEBUG_LOG("%s::setMouseResolution(0x%x)\n", getName(), resolution);
// (set mouse resolution command)
TPS2Request<6> request;
request.commands[0].command = kPS2C_WriteCommandPort;
request.commands[0].inOrOut = kCP_TransmitToMouse;
request.commands[1].command = kPS2C_WriteDataPort;
request.commands[1].inOrOut = kDP_SetMouseResolution;
request.commands[2].command = kPS2C_ReadDataPortAndCompare;
request.commands[2].inOrOut = kSC_Acknowledge;
request.commands[3].command = kPS2C_WriteCommandPort;
request.commands[3].inOrOut = kCP_TransmitToMouse;
request.commands[4].command = kPS2C_WriteDataPort;
request.commands[4].inOrOut = resolution;
request.commands[5].command = kPS2C_ReadDataPortAndCompare;
request.commands[5].inOrOut = kSC_Acknowledge;
request.commandsCount = 6;
assert(request.commandsCount <= countof(request.commands));
_device->submitRequestAndBlock(&request);
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
PS2MouseId ApplePS2Mouse::setIntellimouseMode()
{
//
// Determines whether this mouse is a Microsoft Intellimouse, and if it is,
// it enables it (the mouse will send 4 byte packets for mouse events from
// then on). Returns true if the Intellimouse mode was succesfully enabled.
//
// Do NOT issue this request from the interrupt/completion context.
//
UInt8 mouseIDByte;
PS2MouseId mouseID;
//
// Generate the special command sequence to enable the 'Intellimouse' mode.
// The sequence is to set the sampling rate to 200, 100, then 80, at which
// point the mouse will start sending 4 byte packets for mouse events and
// return a mouse ID of 3.
//
setMouseSampleRate(200);
setMouseSampleRate(100);
setMouseSampleRate(80 );
//
// Pause before sending the next command after switching mouse mode.
//
IOSleep(50);
//
// Determine whether we have an Intellimouse by asking for the mouse's ID.
//
mouseIDByte = getMouseID();
switch(mouseIDByte)
{
// %%KCD - I have a device that (incorrectly?) responds
// with the Intellimouse Explorer mouse ID in response to the standard
// Intellimouse device query. It also seems to then go into
// five button mode in that case, so look for that here.
case kMouseTypeIntellimouseExplorer:
mouseID = kMouseTypeIntellimouseExplorer;
break;
case kMouseTypeIntellimouse:
mouseID = kMouseTypeIntellimouse;
break;
default:
mouseID = kMouseTypeStandard;
break;
}
if (mouseID == kMouseTypeIntellimouse)
{
// Try to enter 5 button mode if we're not there already.
// Same code as above, more or less.
setMouseSampleRate(200);
setMouseSampleRate(200);
setMouseSampleRate(80 );
IOSleep(50);
mouseIDByte = getMouseID();
switch (mouseIDByte)
{
case kMouseTypeIntellimouseExplorer:
mouseID = kMouseTypeIntellimouseExplorer;
break;
default:
mouseID = kMouseTypeIntellimouse;
break;
}
}
//
// Restore the original sampling rate, before we obliterated it.
//
setMouseSampleRate(_mouseInfoBytes & 0x0000FF);
DEBUG_LOG("%s::setIntellimouseMode() returns 0x%x\n", getName(), mouseID);
return mouseID;
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
UInt32 ApplePS2Mouse::getMouseInformation()
{
//
// Asks the mouse to transmit its three information bytes. Should the
// mouse not respond, a value of (UInt32)(-1) is returned.
//
// Do NOT issue this request from the interrupt/completion context.
//
UInt32 returnValue = (UInt32)(-1);
// (get information command)
TPS2Request<6> request;
request.commands[0].command = kPS2C_WriteCommandPort;
request.commands[0].inOrOut = kCP_TransmitToMouse;
request.commands[1].command = kPS2C_WriteDataPort;
request.commands[1].inOrOut = kDP_GetMouseInformation;
request.commands[2].command = kPS2C_ReadDataPortAndCompare;
request.commands[2].inOrOut = kSC_Acknowledge;
request.commands[3].command = kPS2C_ReadDataPort;
request.commands[3].inOrOut = 0;
request.commands[4].command = kPS2C_ReadDataPort;
request.commands[4].inOrOut = 0;
request.commands[5].command = kPS2C_ReadDataPort;
request.commands[5].inOrOut = 0;
request.commandsCount = 6;
assert(request.commandsCount <= countof(request.commands));
_device->submitRequestAndBlock(&request);
if (request.commandsCount == 6) // success?
{
returnValue = ((UInt32)request.commands[3].inOrOut << 16) |
((UInt32)request.commands[4].inOrOut << 8 ) |
((UInt32)request.commands[5].inOrOut);
}
DEBUG_LOG("%s::getMouseInformation() returns 0x%x\n", getName(), returnValue);
return returnValue;
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
UInt8 ApplePS2Mouse::getMouseID()
{
//
// Asks the mouse to transmit its identification byte. Should the mouse
// not respond, a value of (UInt8)(-1) is returned.
//
// Note that some documentation on PS/2 mice implies that two identification
// bytes are returned and not one. This was proven to be false in my tests.
//
// Do NOT issue this request from the interrupt/completion context.
//
UInt8 returnValue = (UInt8)(-1);
// (get information command)
TPS2Request<4> request;
request.commands[0].command = kPS2C_WriteCommandPort;
request.commands[0].inOrOut = kCP_TransmitToMouse;
request.commands[1].command = kPS2C_WriteDataPort;
request.commands[1].inOrOut = kDP_GetId;
request.commands[2].command = kPS2C_ReadDataPortAndCompare;
request.commands[2].inOrOut = kSC_Acknowledge;
request.commands[3].command = kPS2C_ReadDataPort;
request.commands[3].inOrOut = 0;
request.commandsCount = 4;
assert(request.commandsCount <= countof(request.commands));
_device->submitRequestAndBlock(&request);
if (request.commandsCount == 4) // success?
returnValue = request.commands[3].inOrOut;
DEBUG_LOG("%s::getMouseID returns 0x%x\n", getName(), returnValue);
return returnValue;
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void ApplePS2Mouse::setDevicePowerState( UInt32 whatToDo )
{
DEBUG_LOG("%s::setDevicePowerState(0x%x)\n", getName(), whatToDo);
switch ( whatToDo )
{
case kPS2C_DisableDevice:
// Disable mouse (synchronous).
setMouseEnable( false );
break;
case kPS2C_EnableDevice:
// Allow time for device to initialize
IOSleep(wakedelay);
// Enable mouse and restore state.
resetMouse();
// update touchpad LED after sleep
updateTouchpadLED();
break;
}
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void ApplePS2Mouse::receiveMessage(int message, void* data)
{
//
// Here is where we receive messages from the keyboard driver
//
// This allows for the keyboard driver to enable/disable the trackpad
// when a certain keycode is pressed.
//
// It also allows the trackpad driver to learn the last time a key
// has been pressed, so it can implement various "ignore trackpad
// input while typing" options.
//
switch (message)
{
case kPS2M_getDisableTouchpad:
{
bool* pResult = (bool*)data;
*pResult = !ignoreall;
break;
}
case kPS2M_setDisableTouchpad:
{
bool enable = *((bool*)data);
// ignoreall is true when trackpad has been disabled
if (enable == ignoreall)
{
// save state, and update LED
ignoreall = !enable;
updateTouchpadLED();
}
break;
}
case kPS2M_notifyKeyPressed:
{
// just remember last time key pressed... this can be used in
// interrupt handler to detect unintended input while typing
PS2KeyInfo* pInfo = (PS2KeyInfo*)data;
switch (pInfo->adbKeyCode)
{
// don't store key time for modifier keys going down
case 0x38: // left shift
case 0x3c: // right shift
case 0x3b: // left control
case 0x3e: // right control
case 0x3a: // left alt (command)
case 0x3d: // right alt
case 0x37: // left windows (option)
case 0x36: // right windows
if (pInfo->goingDown)
break;
default:
keytime = pInfo->time;
}
break;
}
}
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
//
// This code is specific to Synaptics Touchpads that have an LED indicator, but
// it does no harm to Synaptics units that don't have an LED.
//
// Generally it is used to indicate that the touchpad has been made inactive.
//
// In the case of this package, we can disable the touchpad with both keyboard
// and the touchpad itself.
//
// Linux sources were very useful in figuring this out...
// This patch to support HP Probook Synaptics LED in Linux was where found the
// information:
// https://github.com/mmonaco/PKGBUILDs/blob/master/synaptics-led/synled.patch
//
// To quote from the email:
//
// From: Takashi Iwai <tiwai@suse.de>
// Date: Sun, 16 Sep 2012 14:19:41 -0600
// Subject: [PATCH] input: Add LED support to Synaptics device
//
// The new Synaptics devices have an LED on the top-left corner.
// This patch adds a new LED class device to control it. It's created
// dynamically upon synaptics device probing.
//
// The LED is controlled via the command 0x0a with parameters 0x88 or 0x10.
// This seems only on/off control although other value might be accepted.
//
// The detection of the LED isn't clear yet. It should have been the new
// capability bits that indicate the presence, but on real machines, it
// doesn't fit. So, for the time being, the driver checks the product id
// in the ext capability bits and assumes that LED exists on the known
// devices.
//
// Signed-off-by: Takashi Iwai <tiwai@suse.de>
//
//REVIEW: this code copied from VoodooPS2SynapticsTouchPad.cpp
// would be nice to figure out how to share this code between the two kexts
void ApplePS2Mouse::updateTouchpadLED()
{
if (ledpresent && !noled)
setTouchpadLED(ignoreall ? 0x88 : 0x10);
}
bool ApplePS2Mouse::setTouchpadLED(UInt8 touchLED)
{
TPS2Request<12> request;
// send NOP before special command sequence
request.commands[0].command = kPS2C_SendMouseCommandAndCompareAck;
request.commands[0].inOrOut = kDP_SetMouseScaling1To1;
// 4 set resolution commands, each encode 2 data bits of LED level
request.commands[1].command = kPS2C_SendMouseCommandAndCompareAck;
request.commands[1].inOrOut = kDP_SetMouseResolution;
request.commands[2].command = kPS2C_SendMouseCommandAndCompareAck;
request.commands[2].inOrOut = (touchLED >> 6) & 0x3;
request.commands[3].command = kPS2C_SendMouseCommandAndCompareAck;
request.commands[3].inOrOut = kDP_SetMouseResolution;
request.commands[4].command = kPS2C_SendMouseCommandAndCompareAck;
request.commands[4].inOrOut = (touchLED >> 4) & 0x3;
request.commands[5].command = kPS2C_SendMouseCommandAndCompareAck;
request.commands[5].inOrOut = kDP_SetMouseResolution;
request.commands[6].command = kPS2C_SendMouseCommandAndCompareAck;
request.commands[6].inOrOut = (touchLED >> 2) & 0x3;
request.commands[7].command = kPS2C_SendMouseCommandAndCompareAck;
request.commands[7].inOrOut = kDP_SetMouseResolution;
request.commands[8].command = kPS2C_SendMouseCommandAndCompareAck;
request.commands[8].inOrOut = (touchLED >> 0) & 0x3;
// Set sample rate 10 (10 is command for setting LED)
request.commands[9].command = kPS2C_SendMouseCommandAndCompareAck;
request.commands[9].inOrOut = kDP_SetMouseSampleRate;
request.commands[10].command = kPS2C_SendMouseCommandAndCompareAck;
request.commands[10].inOrOut = 10; // 0x0A command for setting LED
// finally send NOP command to end the special sequence
request.commands[11].command = kPS2C_SendMouseCommandAndCompareAck;
request.commands[11].inOrOut = kDP_SetMouseScaling1To1;
request.commandsCount = 12;
assert(request.commandsCount <= countof(request.commands));
_device->submitRequestAndBlock(&request);
return 12 == request.commandsCount;
}
bool ApplePS2Mouse::getTouchPadData(UInt8 dataSelector, UInt8 buf3[])
{
TPS2Request<14> request;
// Disable stream mode before the command sequence.
request.commands[0].command = kPS2C_SendMouseCommandAndCompareAck;
request.commands[0].inOrOut = kDP_SetDefaultsAndDisable;
// 4 set resolution commands, each encode 2 data bits.
request.commands[1].command = kPS2C_SendMouseCommandAndCompareAck;
request.commands[1].inOrOut = kDP_SetMouseResolution;
request.commands[2].command = kPS2C_SendMouseCommandAndCompareAck;
request.commands[2].inOrOut = (dataSelector >> 6) & 0x3;
request.commands[3].command = kPS2C_SendMouseCommandAndCompareAck;
request.commands[3].inOrOut = kDP_SetMouseResolution;
request.commands[4].command = kPS2C_SendMouseCommandAndCompareAck;
request.commands[4].inOrOut = (dataSelector >> 4) & 0x3;
request.commands[5].command = kPS2C_SendMouseCommandAndCompareAck;
request.commands[5].inOrOut = kDP_SetMouseResolution;
request.commands[6].command = kPS2C_SendMouseCommandAndCompareAck;
request.commands[6].inOrOut = (dataSelector >> 2) & 0x3;
request.commands[7].command = kPS2C_SendMouseCommandAndCompareAck;
request.commands[7].inOrOut = kDP_SetMouseResolution;
request.commands[8].command = kPS2C_SendMouseCommandAndCompareAck;
request.commands[8].inOrOut = (dataSelector >> 0) & 0x3;
// Read response bytes.
request.commands[9].command = kPS2C_SendMouseCommandAndCompareAck;
request.commands[9].inOrOut = kDP_GetMouseInformation;
request.commands[10].command = kPS2C_ReadDataPort;
request.commands[10].inOrOut = 0;
request.commands[11].command = kPS2C_ReadDataPort;
request.commands[11].inOrOut = 0;
request.commands[12].command = kPS2C_ReadDataPort;
request.commands[12].inOrOut = 0;
request.commands[13].command = kPS2C_SendMouseCommandAndCompareAck;
request.commands[13].inOrOut = kDP_SetDefaultsAndDisable;
request.commandsCount = 14;
assert(request.commandsCount <= countof(request.commands));
_device->submitRequestAndBlock(&request);
if (14 != request.commandsCount)
return false;
// store results
buf3[0] = request.commands[10].inOrOut;
buf3[1] = request.commands[11].inOrOut;
buf3[2] = request.commands[12].inOrOut;
return true;
}