//#define COSMOSDEBUG using System; using Cosmos.Common.Extensions; using Cosmos.Debug.Kernel; namespace Cosmos.HAL { public class PS2Controller : Device { private enum Command : byte { GetConfigurationByte = 0x20, SetConfigurationByte = 0x60, DisableSecondPS2Port = 0xA7, EnableSecondPS2Port = 0xA8, TestSecondPS2Port = 0xA9, TestPS2Controller = 0xAA, TestFirstPS2Port = 0xAB, DisableFirstPS2Port = 0xAD, EnableFirstPS2Port = 0xAE, WriteNextByteToSecondPS2PortInputBuffer = 0xD4, PulseOutputLineBase = 0xF0 } private enum DeviceCommand : byte { IdentifyDevice = 0xF2, DisableScanning = 0xF5 } [Flags] private enum OutputLines { None = 0x00, First = 0x01, Second = 0x02, Third = 0x04, Fourth = 0x08, All = First | Second | Third | Fourth } public const byte Ack = 0xFA; public const uint WAIT_TIMEOUT = 100000; public bool IsDualChannel; public bool SelfTestPassed; public bool FirstPortTestPassed; public bool SecondPortTestPassed; public Device FirstDevice; public Device SecondDevice; private Core.IOGroup.PS2Controller IO = Core.Global.BaseIOGroups.PS2Controller; private Debugger mDebugger = new Debugger("HAL", "PS2Controller"); /// /// Initializes the PS/2 controller. /// public void Initialize() { // http://wiki.osdev.org/%228042%22_PS/2_Controller#Initialising_the_PS.2F2_Controller // Disable Devices SendCommand(Command.DisableFirstPS2Port); SendCommand(Command.DisableSecondPS2Port); // Flush The Output Buffer while (WaitToReadData()) { ReadData(); } // Set the Controller Configuration Byte SendCommand(Command.GetConfigurationByte); var xConfigurationByte = ReadData(); // check if the controller is dual channel IsDualChannel = (xConfigurationByte & (1 << 5)) != 0; // clear bits 0 and 1 // TODO: when we support the scan code set 2, clear bit 6 too, to disable translation xConfigurationByte = (byte)(xConfigurationByte & ~(0b0000_0011)); SendCommand(Command.SetConfigurationByte, xConfigurationByte); // Perform Controller Self Test SendCommand(Command.TestPS2Controller); SelfTestPassed = ReadData() == 0x55; // Determine If There Are 2 Channels // note: at this point, IsDualChannel may be true and the controller may not be dual channel, // but false means that it's surely not dual channel if (IsDualChannel) { SendCommand(Command.EnableSecondPS2Port); SendCommand(Command.GetConfigurationByte); xConfigurationByte = ReadData(); IsDualChannel = (xConfigurationByte & (1 << 5)) == 0; if (IsDualChannel) { SendCommand(Command.DisableSecondPS2Port); } } // Perform Interface Tests FirstPortTestPassed = TestPort(1); if (IsDualChannel) { SecondPortTestPassed = TestPort(2); } // Enable Devices if (FirstPortTestPassed) { SendCommand(Command.EnableFirstPS2Port); // enable interrupt xConfigurationByte |= 0b01; } if (SecondPortTestPassed) { SendCommand(Command.EnableSecondPS2Port); // enable interrupt xConfigurationByte |= 0b10; } SendCommand(Command.SetConfigurationByte, xConfigurationByte); if (FirstPortTestPassed) { IdentifyDevice(1, out FirstDevice); } if (SecondPortTestPassed) { IdentifyDevice(2, out SecondDevice); } } /// /// Identifies a PS/2 device. /// /// The port of the PS/2 device to identify. /// An instance of the identified device. private void IdentifyDevice(byte aPort, out Device aDevice) { aDevice = null; if (aPort == 1 || aPort == 2) { var xSecondPort = aPort == 2; WaitToWrite(); SendDeviceCommand(DeviceCommand.DisableScanning, xSecondPort); WaitToWrite(); SendDeviceCommand(DeviceCommand.IdentifyDevice, xSecondPort); byte xFirstByte = 0; byte xSecondByte = 0; if (ReadByteAfterAckWithTimeout(ref xFirstByte)) { /* * |--------|---------------------------| * | Byte | Device Type | * |--------|---------------------------| * | 0x00 | Standard PS/2 mouse | * |--------|---------------------------| * | 0x03 | Mouse with scroll wheel | * |--------|---------------------------| * | 0x04 | 5-button mouse | * |--------|---------------------------| * | 0x50 | Laptop Touchpad | * |--------|---------------------------| */ if (xFirstByte == 0x00 || xFirstByte == 0x03 || xFirstByte == 0x04 || xFirstByte == 0x50) { var xDevice = new PS2Mouse(aPort, xFirstByte); xDevice.Initialize(); aDevice = xDevice; } /* * |-----------------|----------------------------------------------------------------| * | Bytes | Device Type | * |-----------------|----------------------------------------------------------------| * | 0xAB, 0x41 | MF2 keyboard with translation enabled in the PS/2 Controller | * | or 0xAB, 0xC1 | (not possible for the second PS/2 port) | * |-----------------|----------------------------------------------------------------| * | 0xAB, 0x83 | MF2 keyboard | * |-----------------|----------------------------------------------------------------| */ else if (xFirstByte == 0xAB && ReadDataWithTimeout(ref xSecondByte)) { // TODO: replace xTest with (xSecondByte == 0x41 || xSecondByte == 0xC1) // when the stack corruption detection works better for complex conditions var xTest = (xSecondByte == 0x41 || xSecondByte == 0xC1); if (xTest && aPort == 1) { var xDevice = new PS2Keyboard(aPort); xDevice.Initialize(); aDevice = xDevice; } else if (xSecondByte == 0x83) { var xDevice = new PS2Keyboard(aPort); xDevice.Initialize(); aDevice = xDevice; } } } /* * |--------|---------------------------------------------------------------------| * | Byte | Device Type | * |--------|---------------------------------------------------------------------| * | None | Ancient AT keyboard with translation enabled in the PS/Controller | * | | (not possible for the second PS/2 port) | * |--------|---------------------------------------------------------------------| */ else if (aPort == 1) { var xDevice = new PS2Keyboard(aPort); xDevice.Initialize(); aDevice = xDevice; } if (aDevice == null) { mDebugger.SendInternal("(PS/2 Controller) Device detection failed:"); mDebugger.SendInternal("First Byte: " + xFirstByte); mDebugger.SendInternal("Second Byte: " + xSecondByte); throw new Exception("(PS/2 Controller) PS/2 device not supported"); } } else { throw new Exception("(PS/2 Controller) Port " + aPort + " doesn't exist"); } } /// /// Tests a PS/2 device port. /// /// The PS/2 device port to test. /// private bool TestPort(byte aPort) { if (aPort == 1 || aPort == 2) { if (aPort == 1) { SendCommand(Command.TestFirstPS2Port); } else { SendCommand(Command.TestSecondPS2Port); } var xTestByte = ReadData(); if (xTestByte == 0x00) { return true; } else { string xErrorMessage; /* * |--------|-------------------------| * | Byte | Error | * |--------|-------------------------| * | 0x01 | clock line stuck low | * |--------|-------------------------| * | 0x02 | clock line stuck high | * |--------|-------------------------| * | 0x03 | data line stuck low | * |--------|-------------------------| * | 0x04 | data line stuck high | * |--------|-------------------------| */ switch (xTestByte) { case 0x01: xErrorMessage = "Clock Line Stuck Low"; break; case 0x02: xErrorMessage = "Clock Line Stuck High"; break; case 0x03: xErrorMessage = "Data Line Stuck Low"; break; case 0x04: xErrorMessage = "Data Line Stuck High"; break; default: throw new Exception("(PS/2 Controller) (Port " + aPort + ") Device test status unknown: " + xTestByte.ToHex()); } mDebugger.Send("(PS/2 Controller) (Port " + aPort + ") Device test error: '" + xErrorMessage + "'"); return false; } } else { throw new Exception("(PS/2 Controller) Port " + aPort + " doesn't exist"); } } /// /// Pulses the PS/2 controller's output line. /// /// The reset line. /// The second line. /// The third line. /// The fourth line. private void PulseOutputLine(OutputLines aOutputLines) { byte xMask = (byte)aOutputLines; mDebugger.SendInternal("(PS/2 Controller) Pulsing output line:"); mDebugger.SendInternal("Mask:"); mDebugger.SendInternal(xMask); WaitToWrite(); IO.Command.Byte = (byte)((byte)Command.PulseOutputLineBase | xMask); mDebugger.SendInternal("Output line pulsed."); } /// /// Waits for the acknowledgement byte (0xFA). Returns false if the timeout expires, true otherwise. /// /// Returns false if the timeout expires, true otherwise. public bool WaitForAck() { int i = 0; while (IO.Data.Byte != Ack) { i++; if (i >= WAIT_TIMEOUT) { mDebugger.SendInternal("(PS/2 Controller) Timeout expired in PS2Controller.WaitForAck()"); return false; } } return true; } /// /// Reads the byte after acknowledgement. /// /// The byte read after acknowledgement. public byte ReadByteAfterAck() { byte xByte; int i = 0; do { xByte = IO.Data.Byte; i++; if (i >= WAIT_TIMEOUT) { mDebugger.Send("(PS/2 Controller) Timeout expired in PS2Controller.ReadByteAfterAck"); break; } } while (xByte == Ack); return xByte; } /// /// Reads the byte after acknowledgement. /// /// The byte read after acknowledgement. /// Returns false if the timeout expired, true otherwise. public bool ReadByteAfterAckWithTimeout(ref byte aByte) { int i = 0; do { aByte = IO.Data.Byte; i++; if (i >= WAIT_TIMEOUT) { return false; } } while (aByte == Ack); return true; } /// /// Prepares the controller to write a command in the second PS/2 device port. /// public void PrepareSecondPortWrite() { SendCommand(Command.WriteNextByteToSecondPS2PortInputBuffer); } /// /// Waits for the PS/2 device reset. /// /// Returns true if the device resets successfully, false otherwise. public bool WaitForDeviceReset() { mDebugger.SendInternal("(PS/2 Controller) Waiting for device reset:"); //if (!WaitForAck()) //{ // mDebugger.SendInternal("(PS/2 Controller) No Acknowledgement"); //} WaitToReadData(); byte xByte = IO.Data.Byte; mDebugger.SendInternal("(PS/2 Controller) Device reset reponse byte: " + xByte); if (xByte == 0xAA) { mDebugger.SendInternal("(PS/2 Controller) Device reset successful"); return true; } else { mDebugger.SendInternal("(PS/2 Controller) Device reset failed"); return false; } } /// /// Waits to read data. /// /// Returns false if the timeout expired, true otherwise. public bool WaitToReadData() { int i = 0; while ((IO.Status.Byte & 1) == 0) { i++; if (i >= WAIT_TIMEOUT) { mDebugger.SendInternal("Timeout expired in PS2Controller.WaitToReadData(), IO.Status.Byte: " + IO.Status.Byte); return false; } } return true; } /// /// Waits to write data. /// /// Returns false if the timeout expired, true otherwise. public bool WaitToWrite() { int i = 0; while ((IO.Status.Byte & (1 << 1)) != 0) { i++; if (i >= WAIT_TIMEOUT) { mDebugger.SendInternal("Timeout expired in PS2Controller.WaitToWrite()"); return false; } } return true; } #region IO private byte ReadData() { WaitToReadData(); var xByte = IO.Data.Byte; mDebugger.SendInternal("(PS/2 Controller) Reading data:"); mDebugger.SendInternal("(PS/2 Controller) Returned byte:"); mDebugger.SendInternal(xByte); return xByte; } private bool ReadDataWithTimeout(ref byte aByte) { mDebugger.SendInternal("(PS/2 Controller) Reading data with timeout:"); if (WaitToReadData()) { aByte = IO.Data.Byte; mDebugger.SendInternal("(PS/2 Controller) Returned byte:"); mDebugger.SendInternal(aByte); return true; } mDebugger.SendInternal("(PS/2 Controller) Timeout expired"); return false; } private void WriteData(byte aByte) { WaitToWrite(); IO.Data.Byte = aByte; } private void SendCommand(Command aCommand, byte? aByte = null) { mDebugger.SendInternal("(PS/2 Controller) Sending command:"); mDebugger.SendInternal("Command:"); mDebugger.SendInternal((byte)aCommand); WaitToWrite(); IO.Command.Byte = (byte)aCommand; mDebugger.SendInternal("Command sent."); if (aByte.HasValue) { mDebugger.SendInternal("(PS/2 Controller) Sending byte after command:"); mDebugger.SendInternal("Byte value:"); mDebugger.SendInternal(aByte.Value); WaitToWrite(); IO.Data.Byte = aByte.Value; } } private void SendDeviceCommand(DeviceCommand aDeviceCommand, bool aSecondPS2Port, byte? aByte = null) { mDebugger.SendInternal("(PS/2 Controller) Sending device command:"); mDebugger.SendInternal("Device command:"); mDebugger.SendInternal((byte)aDeviceCommand); if (aSecondPS2Port) { SendCommand(Command.WriteNextByteToSecondPS2PortInputBuffer); } WaitToWrite(); IO.Data.Byte = (byte)aDeviceCommand; WaitForAck(); mDebugger.SendInternal("Device command sent."); if (aByte.HasValue) { mDebugger.SendInternal("(PS/2 Controller) Sending byte after device command:"); mDebugger.SendInternal("Byte value:"); mDebugger.SendInternal(aByte.Value); if (aSecondPS2Port) { SendCommand(Command.WriteNextByteToSecondPS2PortInputBuffer); } WaitToWrite(); IO.Data.Byte = aByte.Value; WaitForAck(); } } #endregion } }