/*++ Copyright (c) 2013 Minoca Corp. All Rights Reserved Module Name: pl050.c Abstract: This module implements the driver for the ARM PrimeCell PL050 keyboard and mouse controller. Author: Evan Green 22-Sep-2013 Environment: Kernel --*/ // // ------------------------------------------------------------------- Includes // #include #include "../i8042.h" // // --------------------------------------------------------------------- Macros // // // These macros read and write byte registers in the PL050. The first register // is a pointer to the controller structure, the second is the register number. // #define PL050_READ(_Controller, _Register) \ HlReadRegister8(((PUCHAR)(_Controller)->RegisterBase) + (_Register)) #define PL050_WRITE(_Controller, _Register, _Value) \ HlWriteRegister8(((PUCHAR)(_Controller)->RegisterBase) + (_Register), \ (_Value)) // // This macro spins waiting for the last keyboard command to finish. // #define WAIT_FOR_INPUT_BUFFER(_Device) \ while ((PL050_READ(_Device, Pl050RegisterStatus) & \ PL050_STATUS_TRANSMIT_EMPTY) == 0) { \ \ NOTHING; \ } // // This macro determines if data is available to be received from the device. // #define IS_DATA_AVAILABLE(_Device) \ ((PL050_READ(_Device, Pl050RegisterStatus) & \ PL050_STATUS_RECEIVE_FULL) != 0) // // ---------------------------------------------------------------- Definitions // // // Define the size of the buffer of bytes stored directly by the ISR. // #define PL050_BUFFER_SIZE 256 #define PL050_ALLOCATION_TAG 0x30506C50 // '05lP' // // Define the number of microseconds to wait for a command to complete. // #define PL050_COMMAND_TIMEOUT (50 * MICROSECONDS_PER_MILLISECOND) // // Define control register bits. // #define PL050_CONTROL_ENABLE 0x04 #define PL050_CONTROL_TRANSMIT_INTERRUPT_ENABLE 0x08 #define PL050_CONTROL_RECEIVE_INTERRUPT_ENABLE 0x10 // // Define status register bits. // #define PL050_STATUS_RECEIVE_BUSY 0x08 #define PL050_STATUS_RECEIVE_FULL 0x10 #define PL050_STATUS_TRANSMIT_BUSY 0x20 #define PL050_STATUS_TRANSMIT_EMPTY 0x40 // // ------------------------------------------------------ Data Type Definitions // typedef enum _PL050_REGISTER { Pl050RegisterControl = 0x00, Pl050RegisterStatus = 0x04, Pl050RegisterData = 0x08, Pl050RegisterClockDivisor = 0x0C, Pl050RegisterInterruptStatus = 0x10 } PL050_REGISTER, *PPL050_REGISTER; /*++ Structure Description: This structure stores context about a device driven by the Pl050 driver. Members: IsMouse - Stores a boolean indicating whether the device is a mouse (TRUE) or a keyboard (FALSE). PhysicalAddress - Stores the physical address of the registers. RegisterBase - Stores the virtual address of the registers. InterruptVector - Stores the interrupt vector that this interrupt comes in on. InterruptLine - Stores the interrupt line that the interrupt comes in on. InterruptResourcesFound - Stores a boolean indicating whether or not the interrupt vector and interrupt line fields are valid. InterruptHandle - Stores the handle for the connected interrupt. UserInputDeviceHandle - Stores the handle returned by the User Input library. InterruptLock - Stores a spinlock synchronizing access to the device with the interrupt service routine. ReadLock - Stores a pointer to a queued lock that serializes read access to the data buffer. ReadIndex - Stores the index of the next byte to read out of the data buffer. WriteIndex - Stores the index of the next byte to write to the data buffer. DataBuffer - Stores the buffer of keys coming out of the controller. --*/ typedef struct _PL050_DEVICE { BOOL IsMouse; ULONGLONG PhysicalAddress; PVOID RegisterBase; ULONGLONG InterruptVector; ULONGLONG InterruptLine; BOOL InterruptResourcesFound; HANDLE InterruptHandle; HANDLE UserInputDeviceHandle; KSPIN_LOCK InterruptLock; PQUEUED_LOCK ReadLock; volatile ULONG ReadIndex; volatile ULONG WriteIndex; volatile UCHAR DataBuffer[PL050_BUFFER_SIZE]; } PL050_DEVICE, *PPL050_DEVICE; // // ----------------------------------------------- Internal Function Prototypes // KSTATUS Pl050AddDevice ( PVOID Driver, PSTR DeviceId, PSTR ClassId, PSTR CompatibleIds, PVOID DeviceToken ); VOID Pl050DispatchStateChange ( PIRP Irp, PVOID DeviceContext, PVOID IrpContext ); VOID Pl050DispatchOpen ( PIRP Irp, PVOID DeviceContext, PVOID IrpContext ); VOID Pl050DispatchClose ( PIRP Irp, PVOID DeviceContext, PVOID IrpContext ); VOID Pl050DispatchIo ( PIRP Irp, PVOID DeviceContext, PVOID IrpContext ); VOID Pl050DispatchSystemControl ( PIRP Irp, PVOID DeviceContext, PVOID IrpContext ); INTERRUPT_STATUS Pl050InterruptService ( PVOID Context ); INTERRUPT_STATUS Pl050InterruptServiceWorker ( PVOID Parameter ); KSTATUS Pl050pProcessResourceRequirements ( PIRP Irp, PPL050_DEVICE Device ); KSTATUS Pl050pStartDevice ( PIRP Irp, PPL050_DEVICE Device ); KSTATUS Pl050pEnableDevice ( PVOID OsDevice, PPL050_DEVICE Device ); KSTATUS Pl050pDisableDevice ( PPL050_DEVICE Device ); KSTATUS Pl050pSetLedState ( PVOID Device, PVOID DeviceContext, ULONG LedState ); KSTATUS Pl050pSendKeyboardCommand ( PPL050_DEVICE Device, UCHAR Command, UCHAR Parameter ); KSTATUS Pl050pSetScanSet ( PPL050_DEVICE Device, UCHAR ScanSet ); KSTATUS Pl050pIdentifyDevice ( PPL050_DEVICE Device, PBOOL IsMouse ); // // -------------------------------------------------------------------- Globals // PDRIVER Pl050Driver = NULL; // // ------------------------------------------------------------------ Functions // KSTATUS DriverEntry ( PDRIVER Driver ) /*++ Routine Description: This routine is the entry point for the Pl050 driver. It registers its other dispatch functions, and performs driver-wide initialization. Arguments: Driver - Supplies a pointer to the driver object. Return Value: STATUS_SUCCESS on success. Failure code on error. --*/ { DRIVER_FUNCTION_TABLE FunctionTable; KSTATUS Status; Pl050Driver = Driver; RtlZeroMemory(&FunctionTable, sizeof(DRIVER_FUNCTION_TABLE)); FunctionTable.Version = DRIVER_FUNCTION_TABLE_VERSION; FunctionTable.AddDevice = Pl050AddDevice; FunctionTable.DispatchStateChange = Pl050DispatchStateChange; FunctionTable.DispatchOpen = Pl050DispatchOpen; FunctionTable.DispatchClose = Pl050DispatchClose; FunctionTable.DispatchIo = Pl050DispatchIo; FunctionTable.DispatchSystemControl = Pl050DispatchSystemControl; Status = IoRegisterDriverFunctions(Driver, &FunctionTable); return Status; } // // --------------------------------------------------------- Internal Functions // KSTATUS Pl050AddDevice ( PVOID Driver, PSTR DeviceId, PSTR ClassId, PSTR CompatibleIds, PVOID DeviceToken ) /*++ Routine Description: This routine is called when a device is detected for which the Pl050 driver acts as the function driver. The driver will attach itself to the stack. Arguments: Driver - Supplies a pointer to the driver being called. DeviceId - Supplies a pointer to a string with the device ID. ClassId - Supplies a pointer to a string containing the device's class ID. CompatibleIds - Supplies a pointer to a string containing device IDs that would be compatible with this device. DeviceToken - Supplies an opaque token that the driver can use to identify the device in the system. This token should be used when attaching to the stack. Return Value: STATUS_SUCCESS on success. Failure code if the driver was unsuccessful in attaching itself. --*/ { PPL050_DEVICE NewDevice; KSTATUS Status; // // there is a match, create the device context and attach to the device. // NewDevice = MmAllocateNonPagedPool(sizeof(PL050_DEVICE), PL050_ALLOCATION_TAG); if (NewDevice == NULL) { return STATUS_INSUFFICIENT_RESOURCES; } RtlZeroMemory(NewDevice, sizeof(PL050_DEVICE)); KeInitializeSpinLock(&(NewDevice->InterruptLock)); NewDevice->InterruptHandle = INVALID_HANDLE; NewDevice->UserInputDeviceHandle = INVALID_HANDLE; NewDevice->ReadLock = KeCreateQueuedLock(); if (NewDevice->ReadLock == NULL) { Status = STATUS_INSUFFICIENT_RESOURCES; goto AddDeviceEnd; } Status = IoAttachDriverToDevice(Driver, DeviceToken, NewDevice); if (!KSUCCESS(Status)) { goto AddDeviceEnd; } AddDeviceEnd: if (!KSUCCESS(Status)) { if (NewDevice != NULL) { if (NewDevice->UserInputDeviceHandle != INVALID_HANDLE) { InDestroyInputDevice(NewDevice->UserInputDeviceHandle); } if (NewDevice->ReadLock != NULL) { KeDestroyQueuedLock(NewDevice->ReadLock); } MmFreeNonPagedPool(NewDevice); } } return Status; } VOID Pl050DispatchStateChange ( PIRP Irp, PVOID DeviceContext, PVOID IrpContext ) /*++ Routine Description: This routine handles State Change IRPs. Arguments: Irp - Supplies a pointer to the I/O request packet. DeviceContext - Supplies the context pointer supplied by the driver when it attached itself to the driver stack. Presumably this pointer contains driver-specific device context. IrpContext - Supplies the context pointer supplied by the driver when the IRP was created. Return Value: None. --*/ { PPL050_DEVICE Device; KSTATUS Status; ASSERT(Irp->MajorCode == IrpMajorStateChange); Device = (PPL050_DEVICE)DeviceContext; switch (Irp->MinorCode) { case IrpMinorQueryResources: // // On the way up, filter the resource requirements to add interrupt // vectors to any lines. // if (Irp->Direction == IrpUp) { Status = Pl050pProcessResourceRequirements(Irp, Device); if (!KSUCCESS(Status)) { IoCompleteIrp(Pl050Driver, Irp, Status); } } break; case IrpMinorStartDevice: // // Attempt to fire the thing up if the bus has already started it. // if (Irp->Direction == IrpUp) { Status = Pl050pStartDevice(Irp, Device); if (!KSUCCESS(Status)) { IoCompleteIrp(Pl050Driver, Irp, Status); } } break; // // For all other IRPs, do nothing. // default: break; } return; } VOID Pl050DispatchOpen ( PIRP Irp, PVOID DeviceContext, PVOID IrpContext ) /*++ Routine Description: This routine handles Open IRPs. Arguments: Irp - Supplies a pointer to the I/O request packet. DeviceContext - Supplies the context pointer supplied by the driver when it attached itself to the driver stack. Presumably this pointer contains driver-specific device context. IrpContext - Supplies the context pointer supplied by the driver when the IRP was created. Return Value: None. --*/ { return; } VOID Pl050DispatchClose ( PIRP Irp, PVOID DeviceContext, PVOID IrpContext ) /*++ Routine Description: This routine handles Close IRPs. Arguments: Irp - Supplies a pointer to the I/O request packet. DeviceContext - Supplies the context pointer supplied by the driver when it attached itself to the driver stack. Presumably this pointer contains driver-specific device context. IrpContext - Supplies the context pointer supplied by the driver when the IRP was created. Return Value: None. --*/ { return; } VOID Pl050DispatchIo ( PIRP Irp, PVOID DeviceContext, PVOID IrpContext ) /*++ Routine Description: This routine handles I/O IRPs. Arguments: Irp - Supplies a pointer to the I/O request packet. DeviceContext - Supplies the context pointer supplied by the driver when it attached itself to the driver stack. Presumably this pointer contains driver-specific device context. IrpContext - Supplies the context pointer supplied by the driver when the IRP was created. Return Value: None. --*/ { return; } VOID Pl050DispatchSystemControl ( PIRP Irp, PVOID DeviceContext, PVOID IrpContext ) /*++ Routine Description: This routine handles System Control IRPs. Arguments: Irp - Supplies a pointer to the I/O request packet. DeviceContext - Supplies the context pointer supplied by the driver when it attached itself to the driver stack. Presumably this pointer contains driver-specific device context. IrpContext - Supplies the context pointer supplied by the driver when the IRP was created. Return Value: None. --*/ { ASSERT(Irp->MajorCode == IrpMajorSystemControl); // // Do no processing on any IRPs. Let them flow. // return; } INTERRUPT_STATUS Pl050InterruptService ( PVOID Context ) /*++ Routine Description: This routine implements the PL-050 keyboard controller interrupt service routine. Arguments: Context - Supplies the context pointer given to the system when the interrupt was connected. In this case, this points to the device context. Return Value: Interrupt status. --*/ { UCHAR Byte; PPL050_DEVICE Device; INTERRUPT_STATUS InterruptStatus; ULONG WriteIndex; Device = (PPL050_DEVICE)Context; InterruptStatus = InterruptStatusNotClaimed; // // Check to see if there is data waiting. // if ((PL050_READ(Device, Pl050RegisterStatus) & PL050_STATUS_RECEIVE_FULL) != 0) { // // There was data here, so most likely it was this device interrupting. // InterruptStatus = InterruptStatusClaimed; // // Read the bytes out of the controller. // KeAcquireSpinLock(&(Device->InterruptLock)); WriteIndex = Device->WriteIndex; while ((PL050_READ(Device, Pl050RegisterStatus) & PL050_STATUS_RECEIVE_FULL) != 0) { Byte = PL050_READ(Device, Pl050RegisterData); if (((WriteIndex + 1) % PL050_BUFFER_SIZE) != Device->ReadIndex) { Device->DataBuffer[WriteIndex] = Byte; // // Advance the write index. // if ((WriteIndex + 1) == PL050_BUFFER_SIZE) { WriteIndex = 0; } else { WriteIndex += 1; } } else { RtlDebugPrint("Pl050: Device 0x%08x, buffer overflow, losing " "byte %02X\n", Device, Byte); } } // // Save the new write index now that everything's out. // Device->WriteIndex = WriteIndex; KeReleaseSpinLock(&(Device->InterruptLock)); } return InterruptStatus; } INTERRUPT_STATUS Pl050InterruptServiceWorker ( PVOID Parameter ) /*++ Routine Description: This routine processes interrupts for the Pl050 controller at low level. Arguments: Parameter - Supplies an optional parameter passed in by the creator of the work item. Return Value: Interrupt status. --*/ { UCHAR Byte; UCHAR Code1; UCHAR Code2; UCHAR Code3; PPL050_DEVICE Device; USER_INPUT_EVENT Event; BOOL KeyUp; ULONG ReadIndex; Code1 = 0; Code2 = 0; Code3 = 0; Device = (PPL050_DEVICE)Parameter; ASSERT(KeGetRunLevel() == RunLevelLow); RtlZeroMemory(&Event, sizeof(USER_INPUT_EVENT)); // // Pull as much data out of the buffer as there is. // KeAcquireQueuedLock(Device->ReadLock); ReadIndex = Device->ReadIndex; while (ReadIndex != Device->WriteIndex) { Byte = Device->DataBuffer[ReadIndex]; ReadIndex += 1; if (ReadIndex == PL050_BUFFER_SIZE) { ReadIndex = 0; } if (Device->IsMouse != FALSE) { // // Eventually process the mouse events. // } else { // // If the first byte read was the extended 2 code, then another two // bytes should be coming in. Get those bytes. // if (Code1 == SCAN_CODE_1_EXTENDED_2_CODE) { if (Code2 == 0) { Code2 = Byte; continue; } Code3 = Byte; // // If the first byte read was the extended (1) code, then another // byte should be coming in. Get that byte. // } else if (Code1 == SCAN_CODE_1_EXTENDED_CODE) { Code2 = Byte; } else { Code1 = Byte; if ((Code1 == SCAN_CODE_1_EXTENDED_CODE) || (Code1 == SCAN_CODE_1_EXTENDED_2_CODE)) { continue; } } // // Get the specifics of the event. // Event.U.Key = I8042ConvertScanCodeToKey(Code1, Code2, Code3, &KeyUp); if (Event.U.Key != KeyboardKeyInvalid) { if (KeyUp != FALSE) { Event.EventType = UserInputEventKeyUp; } else { Event.EventType = UserInputEventKeyDown; } // // Log the event. // InReportInputEvent(Device->UserInputDeviceHandle, &Event); } // // A full key combination was read, move the read index forward. // Device->ReadIndex = ReadIndex; Code1 = 0; Code2 = 0; } } KeReleaseQueuedLock(Device->ReadLock); return InterruptStatusClaimed; } KSTATUS Pl050pProcessResourceRequirements ( PIRP Irp, PPL050_DEVICE Device ) /*++ Routine Description: This routine filters through the resource requirements presented by the bus. It adds an interrupt vector requirement for any interrupt line requested. Arguments: Irp - Supplies a pointer to the I/O request packet. Device - Supplies a pointer to this controller device. Return Value: Status code. --*/ { PRESOURCE_CONFIGURATION_LIST Requirements; KSTATUS Status; RESOURCE_REQUIREMENT VectorRequirement; ASSERT((Irp->MajorCode == IrpMajorStateChange) && (Irp->MinorCode == IrpMinorQueryResources)); // // Initialize a nice interrupt vector requirement in preparation. // RtlZeroMemory(&VectorRequirement, sizeof(RESOURCE_REQUIREMENT)); VectorRequirement.Type = ResourceTypeInterruptVector; VectorRequirement.Minimum = 0; VectorRequirement.Maximum = -1; VectorRequirement.Length = 1; // // Loop through all configuration lists, creating a vector for each line. // Requirements = Irp->U.QueryResources.ResourceRequirements; Status = IoCreateAndAddInterruptVectorsForLines(Requirements, &VectorRequirement); if (!KSUCCESS(Status)) { goto ProcessResourceRequirementsEnd; } ProcessResourceRequirementsEnd: return Status; } KSTATUS Pl050pStartDevice ( PIRP Irp, PPL050_DEVICE Device ) /*++ Routine Description: This routine starts up the PL-050 controller. Arguments: Irp - Supplies a pointer to the I/O request packet. Device - Supplies a pointer to this controller device. Return Value: Status code. --*/ { PRESOURCE_ALLOCATION Allocation; PRESOURCE_ALLOCATION_LIST AllocationList; IO_CONNECT_INTERRUPT_PARAMETERS Connect; PRESOURCE_ALLOCATION LineAllocation; BOOL RegistersFound; KSTATUS Status; RegistersFound = FALSE; // // If there are no resources, then return success but don't start anything. // AllocationList = Irp->U.StartDevice.ProcessorLocalResources; if (AllocationList == NULL) { Status = STATUS_SUCCESS; goto StartDeviceEnd; } // // Loop through the allocated resources to get the control and data ports, // and the interrupt. // Allocation = IoGetNextResourceAllocation(AllocationList, NULL); while (Allocation != NULL) { if (Allocation->Type == ResourceTypePhysicalAddressSpace) { if (Device->PhysicalAddress != Allocation->Allocation) { if (Device->RegisterBase != NULL) { MmUnmapAddress(Device->RegisterBase, Allocation->Length); Device->RegisterBase = NULL; } Device->PhysicalAddress = Allocation->Allocation; } RegistersFound = TRUE; if (Device->RegisterBase == NULL) { Device->RegisterBase = MmMapPhysicalAddress( Device->PhysicalAddress, Allocation->Length, TRUE, FALSE, TRUE); if (Device->RegisterBase == NULL) { Status = STATUS_INSUFFICIENT_RESOURCES; goto StartDeviceEnd; } } // // If the resource is an interrupt vector, then it should have an // owning interrupt line allocation. // } else if (Allocation->Type == ResourceTypeInterruptVector) { // // Currently only one interrupt resource is expected. // ASSERT(Device->InterruptResourcesFound == FALSE); ASSERT(Allocation->OwningAllocation != NULL); // // Save the line and vector number. // LineAllocation = Allocation->OwningAllocation; Device->InterruptLine = LineAllocation->Allocation; Device->InterruptVector = Allocation->Allocation; Device->InterruptResourcesFound = TRUE; } // // Get the next allocation in the list. // Allocation = IoGetNextResourceAllocation(AllocationList, Allocation); } // // Fail if the controller base wasn't found. // if (RegistersFound == FALSE) { Status = STATUS_INVALID_CONFIGURATION; goto StartDeviceEnd; } // // Make sure the device and its interrupts are disabled before connecting // the interrupt. There may be leftover state from the last reboot. // Status = Pl050pDisableDevice(Device); if (!KSUCCESS(Status)) { goto StartDeviceEnd; } // // Attempt to connect the interrupt. // ASSERT(Device->InterruptHandle == INVALID_HANDLE); RtlZeroMemory(&Connect, sizeof(IO_CONNECT_INTERRUPT_PARAMETERS)); Connect.Version = IO_CONNECT_INTERRUPT_PARAMETERS_VERSION; Connect.Device = Irp->Device; Connect.LineNumber = Device->InterruptLine; Connect.Vector = Device->InterruptVector; Connect.InterruptServiceRoutine = Pl050InterruptService; Connect.LowLevelServiceRoutine = Pl050InterruptServiceWorker; Connect.Context = Device; Connect.Interrupt = &(Device->InterruptHandle); Status = IoConnectInterrupt(&Connect); if (!KSUCCESS(Status)) { goto StartDeviceEnd; } // // Fire up the device. // Status = Pl050pEnableDevice(Irp->Device, Device); if (!KSUCCESS(Status)) { goto StartDeviceEnd; } StartDeviceEnd: if (!KSUCCESS(Status)) { if (Device->InterruptHandle != INVALID_HANDLE) { IoDisconnectInterrupt(Device->InterruptHandle); Device->InterruptHandle = INVALID_HANDLE; } if (Device->UserInputDeviceHandle != INVALID_HANDLE) { InDestroyInputDevice(Device->UserInputDeviceHandle); Device->UserInputDeviceHandle = INVALID_HANDLE; } } return Status; } KSTATUS Pl050pEnableDevice ( PVOID OsDevice, PPL050_DEVICE Device ) /*++ Routine Description: This routine enables the given PL-050 device. Arguments: OsDevice - Supplies a pointer to the OS device token. Device - Supplies a pointer to this controller device. Return Value: Status code. --*/ { UCHAR ControlByte; USER_INPUT_DEVICE_DESCRIPTION Description; KSTATUS Status; ControlByte = PL050_CONTROL_ENABLE; PL050_WRITE(Device, Pl050RegisterControl, ControlByte); // // Figure out if this is a keyboard or a mouse. // Status = Pl050pIdentifyDevice(Device, &(Device->IsMouse)); if (!KSUCCESS(Status)) { goto EnableDeviceEnd; } if (Device->IsMouse != FALSE) { // // Mice are not currently supported. // return STATUS_NOT_IMPLEMENTED; } else { // // Set the scan set for the keyboard. // Status = Pl050pSetScanSet(Device, 1); if (!KSUCCESS(Status)) { goto EnableDeviceEnd; } // // Set the typematic rate/delay on the keyboard. Start by sending the // command. This command overlaps with the mouse sample rate. // Status = Pl050pSendKeyboardCommand(Device, KEYBOARD_COMMAND_SET_TYPEMATIC, DEFAULT_TYPEMATIC_VALUE); if (!KSUCCESS(Status)) { goto EnableDeviceEnd; } // // Enable the keyboard. This overlaps with the mouse enable command. // Status = Pl050pSendKeyboardCommand(Device, KEYBOARD_COMMAND_ENABLE, KEYBOARD_COMMAND_NO_PARAMETER); if (!KSUCCESS(Status)) { goto EnableDeviceEnd; } // // Create the user input handle if not already done. // if (Device->UserInputDeviceHandle == INVALID_HANDLE) { Description.Device = OsDevice; Description.DeviceContext = Device; Description.Type = UserInputDeviceKeyboard; Description.InterfaceVersion = USER_INPUT_KEYBOARD_DEVICE_INTERFACE_VERSION; Description.U.KeyboardInterface.SetLedState = Pl050pSetLedState; Device->UserInputDeviceHandle = InRegisterInputDevice(&Description); if (Device->UserInputDeviceHandle == INVALID_HANDLE) { Status = STATUS_UNSUCCESSFUL; goto EnableDeviceEnd; } } // // Enable the keyboard interrupt. // ControlByte |= PL050_CONTROL_RECEIVE_INTERRUPT_ENABLE; PL050_WRITE(Device, Pl050RegisterControl, ControlByte); } EnableDeviceEnd: return Status; } KSTATUS Pl050pDisableDevice ( PPL050_DEVICE Device ) /*++ Routine Description: This routine disables a PL050 mouse or keyboard. Arguments: Device - Supplies a pointer to this controller device. Return Value: Status code. --*/ { UCHAR KeyboardResult; UCHAR ReadStatus; KSTATUS ReturnStatus; // // Send the disable command and wait for one of the expected status codes. // The keyboard command overlaps with the mouse disable command. // WAIT_FOR_INPUT_BUFFER(Device); PL050_WRITE(Device, Pl050RegisterData, KEYBOARD_COMMAND_RESET_AND_DISABLE); while (TRUE) { // // Loop waiting for the command to be received. // while (TRUE) { ReadStatus = PL050_READ(Device, Pl050RegisterStatus); if ((ReadStatus & PL050_STATUS_RECEIVE_FULL) != 0) { break; } } // // Read the result. If it is not a keyboard status, just eat it and try // again. It's likely that there is something in the keyboard buffer. // KeyboardResult = PL050_READ(Device, Pl050RegisterData); if (KeyboardResult == KEYBOARD_STATUS_ACKNOWLEDGE) { ReturnStatus = STATUS_SUCCESS; break; } if (KeyboardResult == KEYBOARD_STATUS_RESEND) { ReturnStatus = STATUS_NOT_READY; break; } if (KeyboardResult == KEYBOARD_STATUS_OVERRUN) { ReturnStatus = STATUS_BUFFER_OVERRUN; break; } if (KeyboardResult == KEYBOARD_STATUS_INVALID) { ReturnStatus = STATUS_DEVICE_IO_ERROR; break; } } // // The control register is supposed to be cleared to zero on reset, but // just make sure in case of faulty hardware. This will disable interrupts. // PL050_WRITE(Device, Pl050RegisterControl, 0); return ReturnStatus; } KSTATUS Pl050pSetLedState ( PVOID Device, PVOID DeviceContext, ULONG LedState ) /*++ Routine Description: This routine sets a keyboard's LED state (e.g. Number lock, Caps lock and scroll lock). The state is absolute; the desired state for each LED must be supplied. Arguments: Device - Supplies a pointer to the OS device representing the user input device. DeviceContext - Supplies the opaque device context supplied in the device description upon registration with the user input library. LedState - Supplies a bitmask of flags describing the desired LED state. See USER_INPUT_KEYBOARD_LED_* for definition. Return Value: Status code. --*/ { UCHAR KeyboardLedState; PPL050_DEVICE Pl050Device; KSTATUS Status; Pl050Device = (PPL050_DEVICE)DeviceContext; // // Convert the LED state to the proper format. // KeyboardLedState = 0; if ((LedState & USER_INPUT_KEYBOARD_LED_SCROLL_LOCK) != 0) { KeyboardLedState |= KEYBOARD_LED_SCROLL_LOCK; } if ((LedState & USER_INPUT_KEYBOARD_LED_NUM_LOCK) != 0) { KeyboardLedState |= KEYBOARD_LED_NUM_LOCK; } if ((LedState & USER_INPUT_KEYBOARD_LED_CAPS_LOCK) != 0) { KeyboardLedState |= KEYBOARD_LED_CAPS_LOCK; } Status = Pl050pSendKeyboardCommand(Pl050Device, KEYBOARD_COMMAND_SET_LEDS, KeyboardLedState); return Status; } KSTATUS Pl050pSendKeyboardCommand ( PPL050_DEVICE Device, UCHAR Command, UCHAR Parameter ) /*++ Routine Description: This routine sends a command byte to the keyboard itself (not the keyboard controller) and checks the return status byte. Arguments: Device - Supplies a pointer to this controller device. Command - Supplies the command to write to the keyboard. Parameter - Supplies an additional byte to send. Set this to 0xFF to skip sending this byte. Return Value: Status code indicating whether the keyboard succeeded or failed the command. --*/ { ULONGLONG EndTime; UCHAR KeyboardResult; UCHAR Status; WAIT_FOR_INPUT_BUFFER(Device); PL050_WRITE(Device, Pl050RegisterData, Command); if (Parameter != KEYBOARD_COMMAND_NO_PARAMETER) { WAIT_FOR_INPUT_BUFFER(Device); PL050_WRITE(Device, Pl050RegisterData, Parameter); } // // Wait for the command to complete. // EndTime = HlQueryTimeCounter() + KeConvertMicrosecondsToTimeTicks(PL050_COMMAND_TIMEOUT); while (TRUE) { Status = PL050_READ(Device, Pl050RegisterStatus); if ((Status & PL050_STATUS_RECEIVE_FULL) != 0) { break; } if (HlQueryTimeCounter() >= EndTime) { return STATUS_TIMEOUT; } } // // Read the result. // KeyboardResult = PL050_READ(Device, Pl050RegisterData); if (KeyboardResult == KEYBOARD_STATUS_ACKNOWLEDGE) { return STATUS_SUCCESS; } if (KeyboardResult == KEYBOARD_STATUS_RESEND) { return STATUS_NOT_READY; } if (KeyboardResult == KEYBOARD_STATUS_OVERRUN) { return STATUS_BUFFER_OVERRUN; } return STATUS_DEVICE_IO_ERROR; } KSTATUS Pl050pSetScanSet ( PPL050_DEVICE Device, UCHAR ScanSet ) /*++ Routine Description: This routine sets the scan set for the keyboard. Arguments: Device - Supplies a pointer to this controller device. ScanSet - Supplies the scan set to get. Valid values are 1, 2, and 3. Return Value: Status code indicating whether the keyboard succeeded or failed the command. --*/ { KSTATUS Status; Status = Pl050pSendKeyboardCommand(Device, KEYBOARD_COMMAND_GET_SET_SCAN_SET, ScanSet); return Status; } KSTATUS Pl050pIdentifyDevice ( PPL050_DEVICE Device, PBOOL IsMouse ) /*++ Routine Description: This routine determines if the given device is a mouse or a keyboard. Arguments: Device - Supplies a pointer to this controller device. IsMouse - Supplies a boolean indicating if the device is a mouse. Return Value: Status code indicating whether the determination was successful or not. --*/ { ULONGLONG EndTime; UCHAR Result[3]; ULONG ResultCount; ULONG ResultIndex; KSTATUS Status; // // Disable the device to prevent keystrokes from getting in the way during // the determination. // Status = Pl050pDisableDevice(Device); if (!KSUCCESS(Status)) { goto IdentifyDeviceEnd; } // // Get the keyboard identity. This overlaps with the mouse read ID command. // WAIT_FOR_INPUT_BUFFER(Device); PL050_WRITE(Device, Pl050RegisterData, KEYBOARD_COMMAND_IDENTIFY); EndTime = HlQueryTimeCounter() + KeConvertMicrosecondsToTimeTicks(PL050_COMMAND_TIMEOUT); ResultCount = 0; while (ResultCount < sizeof(Result)) { if (IS_DATA_AVAILABLE(Device)) { Result[ResultCount] = PL050_READ(Device, Pl050RegisterData); ResultCount += 1; continue; } if (HlQueryTimeCounter() >= EndTime) { Status = STATUS_TIMEOUT; break; } } if (ResultCount == 0) { goto IdentifyDeviceEnd; } ResultIndex = 0; if (Result[ResultIndex] == KEYBOARD_STATUS_ACKNOWLEDGE) { ResultIndex += 1; } *IsMouse = FALSE; if ((ResultCount > ResultIndex) && ((Result[ResultIndex] == PS2_STANDARD_MOUSE) || (Result[ResultIndex] == PS2_MOUSE_WITH_SCROLL_WHEEL) || (Result[ResultIndex] == PS2_FIVE_BUTTON_MOUSE))) { *IsMouse = TRUE; } // // Re-enable scanning. This overlaps with the mouse enable command. // Status = Pl050pSendKeyboardCommand(Device, KEYBOARD_COMMAND_ENABLE, KEYBOARD_COMMAND_NO_PARAMETER); IdentifyDeviceEnd: return Status; }