Source to iokit/Drivers/hidsystem/drvApplePS2Mouse/ApplePS2Mouse.cpp
/*
* 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/assert.h>
#include <IOKit/IOLib.h>
#include "ApplePS2Mouse.h"
// =============================================================================
// ApplePS2Mouse Class Implementation
//
#define super IOHIPointing
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 3; };
IOFixed ApplePS2Mouse::resolution() { return _resolution; };
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
bool ApplePS2Mouse::init(OSDictionary * properties)
{
//
// Initialize this object's minimal state. This is invoked right after this
// object is instantiated.
//
if (!super::init(properties)) return false;
_device = 0;
_interruptHandlerInstalled = false;
_packetByteCount = 0;
_packetLength = kPacketLengthStandard;
_resolution = (150) << 16; // (default is 150 dpi; 6 counts/mm)
_type = kMouseTypeStandard;
return true;
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
ApplePS2Mouse * ApplePS2Mouse::probe(IOService * provider, SInt32 * score)
{
//
// 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).
//
ApplePS2MouseDevice * device = (ApplePS2MouseDevice *)provider;
PS2Request * request = device->allocateRequest();
bool success;
if (!super::probe(provider, score)) return 0;
//
// Check to see if acknowledges are being received for commands to the mouse.
//
// (get information command)
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;
device->submitRequestAndBlock(request);
// (free the request)
success = (request->commandsCount == 6);
device->freeRequest(request);
return (success) ? this : 0;
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
bool ApplePS2Mouse::start(IOService * provider)
{
//
// 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();
//
// Install our driver's interrupt handler, for asynchronous data delivery.
//
_device->installInterruptAction(this,
(PS2InterruptAction)&ApplePS2Mouse::interruptOccurred);
_interruptHandlerInstalled = true;
//
// Obtain our mouse's resolution and sampling rate.
//
switch (getMouseInformation() & 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
}
//
// Enable the Intellimouse mode, should this be an Intellimouse.
//
if ( setIntellimouseMode() == true )
{
_packetLength = kPacketLengthIntellimouse;
_type = kMouseTypeIntellimouse;
}
//
// Enable the mouse clock (should already be so) and the mouse IRQ line.
//
setCommandByte(kCB_EnableMouseIRQ, kCB_DisableMouseClock);
//
// Finally, we enable the mouse itself, so that it may start reporting
// mouse events.
//
setMouseEnable(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);
//
// Disable the mouse clock and the mouse IRQ line.
//
setCommandByte(kCB_DisableMouseClock, kCB_EnableMouseIRQ);
//
// Uninstall the interrupt handler.
//
if ( _interruptHandlerInstalled ) _device->uninstallInterruptAction();
_interruptHandlerInstalled = false;
//
// Release the pointer to the provider object.
//
_device->release();
_device = 0;
super::stop(provider);
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void ApplePS2Mouse::interruptOccurred(UInt8 data) // PS2InterruptAction
{
//
// This will be invoked automatically from our device when asynchronous mouse
// needs to be delivered. Process the mouse data. Do NOT send any BLOCKING
// commands to our device in this context.
//
// 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 data from PS/2 controller.\n", getName());
return;
}
//
// Add this byte to the packet buffer. If the packet is complete, that is,
// we have the three bytes, dispatch this packet for processing.
//
_packetBuffer[_packetByteCount++] = data;
if (_packetByteCount == _packetLength)
{
dispatchRelativePointerEventWithPacket(_packetBuffer);
_packetByteCount = 0;
}
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void ApplePS2Mouse::dispatchRelativePointerEventWithPacket(UInt8 * packet)
{
//
// 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
//
UInt32 buttons = 0;
SInt32 dx;
SInt32 dy;
SInt32 dz;
AbsoluteTime now;
if ( !(packet[0] & 0x1) ) buttons |= 0x1; // left button (bit 0 in packet)
if ( !(packet[0] & 0x2) ) buttons |= 0x2; // right button (bit 1 in packet)
if ( !(packet[0] & 0x4) ) buttons |= 0x4; // middle button (bit 2 in packet)
dx = ((packet[0] & 0x10) ? 0xffffff00 : 0 ) | packet[1];
dy = -(((packet[0] & 0x20) ? 0xffffff00 : 0 ) | packet[2]);
dz = (SInt32)((SInt8)packet[3]);
clock_get_uptime(&now);
dispatchRelativePointerEvent(dx, dy, buttons, now);
return;
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
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.
//
PS2Request * request = _device->allocateRequest();
// (mouse enable/disable command)
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;
_device->submitRequest(request); // asynchronous, auto-free'd
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void ApplePS2Mouse::setMouseSampleRate(UInt8 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.
//
PS2Request * request = _device->allocateRequest();
// (set mouse sample rate command)
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;
_device->submitRequest(request); // asynchronous, auto-free'd
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
bool 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.
//
UInt32 mouseInfo;
bool isIntellimouse;
//
// Obtain the current sample rate, in order that we may restore it after
// the Intellimouse command sequence completes.
//
mouseInfo = getMouseInformation();
if (mouseInfo == (UInt32)(-1)) return false;
//
// 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 );
//
// Determine whether we have an Intellimouse by asking for the mouse's ID.
//
isIntellimouse = ( getMouseID() == kMouseTypeIntellimouse );
//
// Restore the original sampling rate, before we obliterated it.
//
setMouseSampleRate(mouseInfo & 0x0000FF);
return isIntellimouse;
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
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.
//
PS2Request * request = _device->allocateRequest();
UInt32 returnValue = (UInt32)(-1);
// (get information command)
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;
_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);
}
_device->freeRequest(request);
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.
//
PS2Request * request = _device->allocateRequest();
UInt8 returnValue = (UInt8)(-1);
// (get information command)
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;
_device->submitRequestAndBlock(request);
if (request->commandsCount == 4) // success?
returnValue = request->commands[3].inOrOut;
_device->freeRequest(request);
return returnValue;
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void ApplePS2Mouse::setCommandByte(UInt8 setBits, UInt8 clearBits)
{
//
// Sets the bits setBits and clears the bits clearBits "atomically" in the
// controller's Command Byte. Since the controller does not provide such
// a read-modify-write primitive, we resort to a test-and-set try loop.
//
// Do NOT issue this request from the interrupt/completion context.
//
UInt8 commandByte;
UInt8 commandByteNew;
PS2Request * request = _device->allocateRequest();
do
{
// (read command byte)
request->commands[0].command = kPS2C_WriteCommandPort;
request->commands[0].inOrOut = kCP_GetCommandByte;
request->commands[1].command = kPS2C_ReadDataPort;
request->commands[1].inOrOut = 0;
request->commandsCount = 2;
_device->submitRequestAndBlock(request);
//
// Modify the command byte as requested by caller.
//
commandByte = request->commands[1].inOrOut;
commandByteNew = (commandByte | setBits) & (~clearBits);
// ("test-and-set" command byte)
request->commands[0].command = kPS2C_WriteCommandPort;
request->commands[0].inOrOut = kCP_GetCommandByte;
request->commands[1].command = kPS2C_ReadDataPortAndCompare;
request->commands[1].inOrOut = commandByte;
request->commands[2].command = kPS2C_WriteCommandPort;
request->commands[2].inOrOut = kCP_SetCommandByte;
request->commands[3].command = kPS2C_WriteDataPort;
request->commands[3].inOrOut = commandByteNew;
request->commandsCount = 4;
_device->submitRequestAndBlock(request);
//
// Repeat this loop if last command failed, that is, if the old command byte
// was modified since we first read it.
//
} while (request->commandsCount != 4);
_device->freeRequest(request);
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void ApplePS2Mouse::accelerationTable(IOHIAccelerationPoint ** table,
IOItemCount * numEntries)
{
static IOHIAccelerationPoint defaultTable[] =
{
{ 0x0000000, 0x000000 },
{ 0x000713b, 0x006000 },
{ 0x0010000, 0x010000 },
{ 0x0044ec5, 0x108000 },
{ 0x00c0000, 0x5f0000 },
{ 0x016ec4f, 0x8b0000 },
{ 0x01d3b14, 0x948000 },
{ 0x0227627, 0x960000 },
{ 0x7ffffff, 0x960000 }
};
*table = defaultTable;
*numEntries = sizeof(defaultTable) / sizeof(defaultTable[0]);
}