ApplePS2Controller.cpp [plain text]
#include <IOKit/assert.h>
#include <IOKit/IOService.h>
#include <IOKit/IOSyncer.h>
#include <IOKit/IOCommandQueue.h>
#include <IOKit/ps2/ApplePS2KeyboardDevice.h>
#include <IOKit/ps2/ApplePS2MouseDevice.h>
#include "ApplePS2Controller.h"
extern "C"
{
#include <architecture/i386/pio.h>
#include <machine/machine_routines.h>
}
static ApplePS2Controller * gApplePS2Controller = 0;
static void interruptHandlerMouse(OSObject *, void *, IOService *, int)
{
gApplePS2Controller->_interruptSourceMouse->interruptOccurred(0, 0, 0);
}
static void interruptHandlerKeyboard(OSObject *, void *, IOService *, int)
{
#if DEBUGGER_SUPPORT
UInt8 key;
UInt8 status;
gApplePS2Controller->lockController();
if ( ((status = inb(kCommandPort)) & kOutputReady) )
{
if ( (status & kMouseData) )
{
interruptHandlerMouse(0, 0, 0, 0);
}
else
{
key = inb(kDataPort);
if (gApplePS2Controller->doEscape(key) == false)
gApplePS2Controller->enqueueKeyboardData(key);
gApplePS2Controller->_interruptSourceKeyboard->interruptOccurred(0, 0, 0);
}
}
gApplePS2Controller->unlockController();
#else
gApplePS2Controller->_interruptSourceKeyboard->interruptOccurred(0, 0, 0);
#endif DEBUGGER_SUPPORT
}
#define super IOService
OSDefineMetaClassAndStructors(ApplePS2Controller, IOService);
bool ApplePS2Controller::init(OSDictionary * properties)
{
if (!super::init(properties)) return false;
_commandQueue = 0;
_workLoop = 0;
_interruptSourceKeyboard = 0;
_interruptSourceMouse = 0;
_interruptTargetKeyboard = 0;
_interruptTargetMouse = 0;
_interruptActionKeyboard = NULL;
_interruptActionMouse = NULL;
_interruptInstalledKeyboard = false;
_interruptInstalledMouse = false;
_mouseDevice = 0;
_keyboardDevice = 0;
#if DEBUGGER_SUPPORT
_extendedState = false;
_modifierState = 0x00;
_keyboardQueueAlloc = NULL;
queue_init(&_keyboardQueue);
queue_init(&_keyboardQueueUnused);
_controllerLockOldSpl = 0;
usimple_lock_init(&_controllerLock, ETAP_NO_TRACE);
#endif DEBUGGER_SUPPORT
return true;
}
bool ApplePS2Controller::start(IOService * provider)
{
if (!super::start(provider)) return false;
#if DEBUGGER_SUPPORT
_keyboardQueueAlloc = (KeyboardQueueElement *)
IOMalloc(kKeyboardQueueSize*sizeof(KeyboardQueueElement));
if (!_keyboardQueueAlloc) return false;
for (int index = 0; index < kKeyboardQueueSize; index++)
queue_enter(&_keyboardQueueUnused, &_keyboardQueueAlloc[index],
KeyboardQueueElement *, chain);
#endif DEBUGGER_SUPPORT
UInt8 commandByte;
writeCommandPort(kCP_GetCommandByte);
commandByte = readDataPort(kDT_Keyboard);
commandByte &= ~(kCB_EnableMouseIRQ | kCB_DisableMouseClock);
writeCommandPort(kCP_SetCommandByte);
writeDataPort(commandByte);
writeDataPort(kDP_SetDefaultsAndDisable);
readDataPort(kDT_Keyboard);
writeCommandPort(kCP_TransmitToMouse);
writeDataPort(kDP_SetDefaultsAndDisable);
readDataPort(kDT_Mouse);
while ( inb(kCommandPort) & kOutputReady )
{
inb(kDataPort);
IODelay(kDataDelay);
}
_workLoop = IOWorkLoop::workLoop();
_commandQueue = IOCommandQueue::commandQueue(
this, (IOCommandQueueAction) &ApplePS2Controller::processRequest);
_interruptSourceMouse = IOInterruptEventSource::interruptEventSource(
this, (IOInterruptEventAction) &ApplePS2Controller::interruptOccurred);
_interruptSourceKeyboard = IOInterruptEventSource::interruptEventSource(
this, (IOInterruptEventAction) &ApplePS2Controller::interruptOccurred);
if ( !_workLoop ||
!_commandQueue ||
!_interruptSourceMouse ||
!_interruptSourceKeyboard ) return false;
if ( _workLoop->addEventSource(_commandQueue) != kIOReturnSuccess )
return false;
_keyboardDevice = new ApplePS2KeyboardDevice;
if ( !_keyboardDevice ||
!_keyboardDevice->init() ||
!_keyboardDevice->attach(this) ) return false;
_mouseDevice = new ApplePS2MouseDevice;
if ( !_mouseDevice ||
!_mouseDevice->init() ||
!_mouseDevice->attach(this) ) return false;
gApplePS2Controller = this;
_keyboardDevice->registerService();
_mouseDevice->registerService();
return true; }
void ApplePS2Controller::stop(IOService * provider)
{
assert(_interruptInstalledKeyboard == false);
assert(_interruptInstalledMouse == false);
if (_keyboardDevice) _keyboardDevice->release();
if (_mouseDevice) _mouseDevice->release();
if (_workLoop) _workLoop->release();
if (_commandQueue) _commandQueue->release();
if (_interruptSourceKeyboard) _interruptSourceKeyboard->release();
if (_interruptSourceMouse) _interruptSourceMouse->release();
#if DEBUGGER_SUPPORT
if (_keyboardQueueAlloc)
IOFree(_keyboardQueueAlloc,kKeyboardQueueSize*sizeof(KeyboardQueueElement));
#endif DEBUGGER_SUPPORT
gApplePS2Controller = 0;
super::stop(provider);
}
IOWorkLoop * ApplePS2Controller::getWorkLoop() const
{
return _workLoop;
}
void ApplePS2Controller::installInterruptAction(PS2DeviceType deviceType,
OSObject * target,
PS2InterruptAction action)
{
if (deviceType == kDT_Keyboard && _interruptInstalledKeyboard == false)
{
target->retain();
_interruptTargetKeyboard = target;
_interruptActionKeyboard = action;
_workLoop->addEventSource(_interruptSourceKeyboard);
getProvider()->registerInterrupt(kIRQ_Keyboard,0, interruptHandlerKeyboard);
getProvider()->enableInterrupt(kIRQ_Keyboard);
_interruptInstalledKeyboard = true;
}
else if (deviceType == kDT_Mouse && _interruptInstalledMouse == false)
{
target->retain();
_interruptTargetMouse = target;
_interruptActionMouse = action;
_workLoop->addEventSource(_interruptSourceMouse);
getProvider()->registerInterrupt(kIRQ_Mouse, 0, interruptHandlerMouse);
getProvider()->enableInterrupt(kIRQ_Mouse);
_interruptInstalledMouse = true;
}
}
void ApplePS2Controller::uninstallInterruptAction(PS2DeviceType deviceType)
{
if (deviceType == kDT_Keyboard && _interruptInstalledKeyboard == true)
{
getProvider()->disableInterrupt(kIRQ_Keyboard);
getProvider()->unregisterInterrupt(kIRQ_Keyboard);
_workLoop->removeEventSource(_interruptSourceMouse);
_interruptInstalledKeyboard = false;
_interruptActionKeyboard = NULL;
_interruptTargetKeyboard->release();
_interruptTargetKeyboard = 0;
}
else if (deviceType == kDT_Mouse && _interruptInstalledMouse == true)
{
getProvider()->disableInterrupt(kIRQ_Mouse);
getProvider()->unregisterInterrupt(kIRQ_Mouse);
_workLoop->removeEventSource(_interruptSourceMouse);
_interruptInstalledMouse = false;
_interruptActionMouse = NULL;
_interruptTargetMouse->release();
_interruptTargetMouse = 0;
}
}
PS2Request * ApplePS2Controller::allocateRequest()
{
PS2Request * request = (PS2Request *) IOMalloc(sizeof(PS2Request));
bzero(request, sizeof(PS2Request));
return request;
}
void ApplePS2Controller::freeRequest(PS2Request * request)
{
IOFree(request, sizeof(PS2Request));
}
bool ApplePS2Controller::submitRequest(PS2Request * request)
{
return (_commandQueue->enqueueCommand(false, request) == KERN_SUCCESS);
}
void ApplePS2Controller::submitRequestAndBlock(PS2Request * request)
{
IOSyncer * completionSyncer = IOSyncer::create();
assert(completionSyncer);
request->completionTarget = this;
request->completionAction = submitRequestAndBlockCompletion;
request->completionParam = completionSyncer;
_commandQueue->enqueueCommand(true, request);
completionSyncer->wait(); }
void ApplePS2Controller::submitRequestAndBlockCompletion(void *, void * param)
{ IOSyncer * completionSyncer = (IOSyncer *) param;
completionSyncer->signal();
}
void ApplePS2Controller::interruptOccurred(IOInterruptEventSource *, int)
{
UInt8 status;
#if DEBUGGER_SUPPORT
lockController(); while (1)
{
if (dequeueKeyboardData(&status))
{
unlockController();
dispatchDriverInterrupt(kDT_Keyboard, status);
lockController();
}
else if ( (inb(kCommandPort) & (kOutputReady | kMouseData)) ==
(kOutputReady | kMouseData))
{
unlockController();
dispatchDriverInterrupt(kDT_Mouse, inb(kDataPort));
lockController();
}
else break; }
unlockController(); #else
while ( ((status = inb(kCommandPort)) & kOutputReady) )
{
dispatchDriverInterrupt((status&kMouseData)?kDT_Mouse:kDT_Keyboard,
inb(kDataPort));
}
#endif DEBUGGER_SUPPORT
}
void ApplePS2Controller::dispatchDriverInterrupt(PS2DeviceType deviceType,
UInt8 data)
{
if ( deviceType == kDT_Mouse )
{
if (_interruptInstalledMouse)
(*_interruptActionMouse)(_interruptTargetMouse, data);
}
else if ( deviceType == kDT_Keyboard )
{
if (_interruptInstalledKeyboard)
(*_interruptActionKeyboard)(_interruptTargetKeyboard, data);
}
}
void ApplePS2Controller::processRequest(PS2Request * request,
void * ,
void * ,
void * )
{
UInt8 byte;
PS2DeviceType deviceMode = kDT_Keyboard;
bool failed = false;
bool transmitToMouse = false;
unsigned index;
for (index = 0; index < request->commandsCount; index++)
{
switch (request->commands[index].command)
{
case kPS2C_ReadDataPort:
request->commands[index].inOrOut = readDataPort(deviceMode);
break;
case kPS2C_ReadDataPortAndCompare:
#if OUT_OF_ORDER_DATA_CORRECTION_FEATURE
byte = readDataPort(deviceMode, request->commands[index].inOrOut);
#else
byte = readDataPort(deviceMode);
#endif
failed = (byte != request->commands[index].inOrOut);
break;
case kPS2C_WriteDataPort:
writeDataPort(request->commands[index].inOrOut);
if (transmitToMouse) {
deviceMode = kDT_Mouse;
transmitToMouse = false;
}
else
{
deviceMode = kDT_Keyboard;
}
break;
case kPS2C_WriteCommandPort:
writeCommandPort(request->commands[index].inOrOut);
if (request->commands[index].inOrOut == kCP_TransmitToMouse)
transmitToMouse = true; break;
}
if (failed) break;
}
if (failed) request->commandsCount = index;
if (request->completionTarget && request->completionAction)
{
(*request->completionAction)(request->completionTarget,
request->completionParam);
}
}
UInt8 ApplePS2Controller::readDataPort(PS2DeviceType deviceType)
{
UInt8 readByte;
UInt8 status;
UInt32 timeoutCounter = 10000;
while (1)
{
#if DEBUGGER_SUPPORT
lockController(); if (deviceType == kDT_Keyboard && dequeueKeyboardData(&readByte))
{
unlockController();
return readByte;
}
#endif DEBUGGER_SUPPORT
while (timeoutCounter && !((status = inb(kCommandPort)) & kOutputReady))
{
timeoutCounter--;
IODelay(kDataDelay);
}
if (timeoutCounter == 0)
{
#if DEBUGGER_SUPPORT
unlockController(); #endif DEBUGGER_SUPPORT
IOLog("%s: Timed out on %s input stream.\n", getName(),
(deviceType == kDT_Keyboard) ? "keyboard" : "mouse");
return 0;
}
readByte = inb(kDataPort);
#if DEBUGGER_SUPPORT
unlockController(); #endif DEBUGGER_SUPPORT
if ( (status & kMouseData) )
{
if (deviceType == kDT_Mouse) return readByte;
}
else
{
if (deviceType == kDT_Keyboard) return readByte;
}
dispatchDriverInterrupt((deviceType==kDT_Keyboard)?kDT_Mouse:kDT_Keyboard,
readByte);
} }
#if OUT_OF_ORDER_DATA_CORRECTION_FEATURE
UInt8 ApplePS2Controller::readDataPort(PS2DeviceType deviceType,
UInt8 expectedByte)
{
UInt8 firstByte = 0;
bool firstByteHeld = false;
UInt8 readByte;
bool requestedStream;
UInt8 status;
UInt32 timeoutCounter = 10000;
while (1)
{
#if DEBUGGER_SUPPORT
lockController(); if (deviceType == kDT_Keyboard && dequeueKeyboardData(&readByte))
{
requestedStream = true;
goto skipForwardToY;
}
#endif DEBUGGER_SUPPORT
while (timeoutCounter && !((status = inb(kCommandPort)) & kOutputReady))
{
timeoutCounter--;
IODelay(kDataDelay);
}
if (timeoutCounter == 0)
{
#if DEBUGGER_SUPPORT
unlockController(); #endif DEBUGGER_SUPPORT
if (firstByteHeld) return firstByte;
IOLog("%s: Timed out on %s input stream.\n", getName(),
(deviceType == kDT_Keyboard) ? "keyboard" : "mouse");
return 0;
}
readByte = inb(kDataPort);
requestedStream = false;
if ( (status & kMouseData) )
{
if (deviceType == kDT_Mouse) requestedStream = true;
}
else
{
if (deviceType == kDT_Keyboard) requestedStream = true;
}
#if DEBUGGER_SUPPORT
skipForwardToY:
unlockController(); #endif DEBUGGER_SUPPORT
if (requestedStream)
{
if (readByte == expectedByte)
{
if (firstByteHeld == false)
{
return readByte;
}
else
{
dispatchDriverInterrupt(deviceType, firstByte);
return readByte;
}
}
else {
if (firstByteHeld == false)
{
firstByteHeld = true;
firstByte = readByte;
}
else if (readByte != expectedByte)
{
dispatchDriverInterrupt(deviceType, readByte);
return firstByte;
}
}
}
else
{
dispatchDriverInterrupt((deviceType==kDT_Keyboard)?kDT_Mouse:kDT_Keyboard,
readByte);
}
} }
#endif
void ApplePS2Controller::writeDataPort(UInt8 byte)
{
while (inb(kCommandPort) & kInputBusy) IODelay(kDataDelay);
outb(kDataPort, byte);
}
void ApplePS2Controller::writeCommandPort(UInt8 byte)
{
while (inb(kCommandPort) & kInputBusy) IODelay(kDataDelay);
outb(kCommandPort, byte);
}
#if DEBUGGER_SUPPORT
#define kModifierShiftLeft 0x01
#define kModifierShiftRight 0x02
#define kModifierCtrlLeft 0x04
#define kModifierCtrlRight 0x08
#define kModifierAltLeft 0x10
#define kModifierAltRight 0x20
#define kModifierWindowsLeft 0x40
#define kModifierWindowsRight 0x80
#define kModifierShiftMask (kModifierShiftLeft | kModifierShiftRight )
#define kModifierCtrlMask (kModifierCtrlLeft | kModifierCtrlRight )
#define kModifierAltMask (kModifierAltLeft | kModifierAltRight )
#define kModifierWindowsMask (kModifierWindowsLeft | kModifierWindowsRight)
bool ApplePS2Controller::doEscape(UInt8 scancode)
{
static struct
{
UInt8 scancode;
UInt8 extended;
UInt16 modifier;
} modifierTable[] = { { kSC_Alt, false, kModifierAltLeft },
{ kSC_Alt, true, kModifierAltRight },
{ kSC_Ctrl, false, kModifierCtrlLeft },
{ kSC_Ctrl, true, kModifierCtrlRight },
{ kSC_ShiftLeft, false, kModifierShiftLeft },
{ kSC_ShiftRight, false, kModifierShiftRight },
{ kSC_WindowsLeft, true, kModifierWindowsLeft },
{ kSC_WindowsRight, true, kModifierWindowsRight },
{ 0, 0, 0 } };
UInt32 index;
bool releaseModifiers = false;
bool upBit = (scancode & kSC_UpBit) ? true : false;
if (scancode == kSC_Extend)
{
_extendedState = true;
return false;
}
scancode &= ~kSC_UpBit;
for (index = 0; modifierTable[index].scancode; index++)
{
if ( modifierTable[index].scancode == scancode &&
modifierTable[index].extended == _extendedState )
{
if (upBit) _modifierState &= ~modifierTable[index].modifier;
else _modifierState |= modifierTable[index].modifier;
_extendedState = false;
return false;
}
}
if (scancode == kSC_Delete) {
if ( _modifierState == kModifierAltLeft ||
_modifierState == kModifierAltRight )
{
while (inb(kCommandPort) & kInputBusy) IODelay(kDataDelay);
outb(kCommandPort, kCP_DisableMouseClock);
Debugger("Programmer Key");
while (inb(kCommandPort) & kInputBusy) IODelay(kDataDelay);
outb(kCommandPort, kCP_EnableMouseClock);
releaseModifiers = true;
}
}
if (releaseModifiers)
{
for (index = 0; modifierTable[index].scancode; index++)
{
if ( _modifierState & modifierTable[index].modifier )
{
if (modifierTable[index].extended) enqueueKeyboardData(kSC_Extend);
enqueueKeyboardData(modifierTable[index].scancode | kSC_UpBit);
}
}
_modifierState = 0x00;
}
_extendedState = false;
return (releaseModifiers);
}
void ApplePS2Controller::enqueueKeyboardData(UInt8 key)
{
KeyboardQueueElement * element;
if (!queue_empty(&_keyboardQueueUnused))
{
queue_remove_first(&_keyboardQueueUnused,
element, KeyboardQueueElement *, chain);
element->data = key;
queue_enter(&_keyboardQueue, element, KeyboardQueueElement *, chain);
}
}
bool ApplePS2Controller::dequeueKeyboardData(UInt8 * key)
{
KeyboardQueueElement * element;
if (!queue_empty(&_keyboardQueue))
{
queue_remove_first(&_keyboardQueue, element, KeyboardQueueElement *, chain);
*key = element->data;
queue_enter(&_keyboardQueueUnused, element, KeyboardQueueElement *, chain);
return true;
}
return false;
}
void ApplePS2Controller::unlockController(void)
{
usimple_unlock(&_controllerLock);
ml_set_interrupts_enabled(_controllerLockOldSpl);
}
void ApplePS2Controller::lockController(void)
{
int oldSpl = ml_set_interrupts_enabled(FALSE);
usimple_lock(&_controllerLock);
_controllerLockOldSpl = oldSpl;
}
#endif DEBUGGER_SUPPORT