diff options
| author | Adam Shapiro <[email protected]> | 2015-11-17 12:11:14 -0800 |
|---|---|---|
| committer | Adam Shapiro <[email protected]> | 2015-11-17 12:11:14 -0800 |
| commit | 5b815f85ef86b2c2522dbd79e9b1b900f8fb77e4 (patch) | |
| tree | 37a66ba8dadc5ad5d04e49d815cd19907ef8805e /general/HalExtensionSample/HalExtSampleTimers2/HalExtSampleTimers2.c | |
| parent | 2c9b5b696dc2c396e6ffc9ec721d8085f279a114 (diff) | |
Add New Sample To Git Hub
Adding the new HAL Extension sample to the online repository in the
"General" Category
Diffstat (limited to 'general/HalExtensionSample/HalExtSampleTimers2/HalExtSampleTimers2.c')
| -rw-r--r-- | general/HalExtensionSample/HalExtSampleTimers2/HalExtSampleTimers2.c | 825 |
1 files changed, 825 insertions, 0 deletions
diff --git a/general/HalExtensionSample/HalExtSampleTimers2/HalExtSampleTimers2.c b/general/HalExtensionSample/HalExtSampleTimers2/HalExtSampleTimers2.c new file mode 100644 index 00000000..9a580fea --- /dev/null +++ b/general/HalExtensionSample/HalExtSampleTimers2/HalExtSampleTimers2.c @@ -0,0 +1,825 @@ +/*++ + +Copyright (c) 2011 Microsoft Corporation + +Module Name: + + HalExtSampleTimers2.c + +Abstract: + + This file implements a HAL Extension Module for the fictitious EG2Timer. + +Author: + + Evan Green (evgreen) 8-Jan-2011 + +--*/ + +// +// ------------------------------------------------------------------- Includes +// + +#include <nthalext.h> + +// +// -------------------------------------------------------------- Specification +// + +// +// The EG2Timer is a fast-access low latency deadline-based timer designed +// for high-performance timekeeping operations. It consists of one counter +// counter and three match registers against that counter. Each match register +// consists of a 32-bit value that is compared on each clock cycle to the +// current count value. If the values match and the match register is enabled +// for generating an interrupt, an edge triggered interrupt will be fired. The +// register can also be configured with an "interval" value such that the +// match register is automatically moved forward by the given interval to +// generate periodic interrupts. The main counter is 32 bits and runs at 15MHz. +// The register layout of the timer block appears below. +// +// Offset Register Size +// 0x00 GlobalControl 4 +// 0x04 CounterValue 4 +// 0x08 Timer0_Match 4 +// 0x0C Timer0_Interval 4 +// 0x10 Timer0_Control 4 +// 0x14 Timer1_Match 4 +// 0x18 Timer1_Interval 4 +// 0x1C Timer1_Control 4 +// 0x20 Timer2_Match 4 +// 0x24 Timer2_Interval 4 +// 0x28 Timer2_Control 4 +// +// Register Descriptions: +// +// GlobalControl - Controls global state relating to the timer block. On reset, +// this register's value is 0. This register is Read/Write. +// +// Bit Function +// 31-1 Reserved. Read as 0, always write 0 to maintain future +// compatibility. +// +// 0 Enabled. When set to 1, the main counter is enabled and will +// count. When set to 0, the main counter is disabled and will +// not run. +// +// CounterValue - A Read/Write register containing the current value of the +// counter. Writes to this register must be done with caution as they do +// not alter or adjust the contents of the match registers. On reset, the +// value of this register is 0. +// +// TimerN_Match - A Read/Write register that contains the match value that this +// interrupt is primed against. When the global counter equals the match +// value, an interrupt will be generated. If the interrupt is set for +// periodic mode, the Interval value will be automatically added to the +// match value when the interrupt occurs. On reset, the value of this +// register is 0. +// +// TimerN_Interval - A Read/Write register that contains the periodic interval +// to add to the match register if the interrupt is armed for periodic +// mode. On reset, the value of this register is 0. +// +// TimerN_Control - A Read/Write register containing a bitfield that controls +// the behavior of the match register and associated interrupt. On reset, +// the value of this register is 0. +// +// Bit Function +// 31-2 Reserved. Read as 0, always write 0 to maintain future +// compatibility. +// +// 1 Periodic. When set to 1, the value in the Interval register will +// automatically be added to the value in the Match register and +// written back to the Match register when a match occurs. When +// set to 0, the Match register will not change when a match +// occurs. +// +// 0 InterruptEnable. When set to 1, an interrupt will be generated +// when a match occurs. When set to 0, no interrupt will be +// generated when a match occurs. The periodic bit is still live +// however, the Match register will continue to get accumulated +// with the Interval register on matches. +// + +// +// ---------------------------------------------------------------- Definitions +// + +// +// Define the total size of the register block, which is 1 page. +// + +#define EG2TIMER_BLOCK_SIZE 0x1000 + +// +// Define the size of one timer's match register block, in ULONGs. +// + +#define EG2MATCH_SIZE 3 + +// +// Define the total number of match registers. +// + +#define EG2MATCH_COUNT 3 + +// +// Define the timer's bit width. +// + +#define EG2TIMER_BIT_WIDTH 32 + +// +// Define the counter's frequency, in Hertz. +// + +#define EG2TIMER_FREQUENCY 15000000 + +// +// Define the global control bits. +// + +#define EG2TIMER_GLOBAL_CONTROL_ENABLE 0x00000001 + +// +// Define bits for the control register. +// + +#define EG2TIMER_MATCH_INTERRUPT_ENABLE 0x00000001 +#define EG2TIMER_MATCH_PERIODIC 0x00000002 + +// +// Define the special offset used to indicate this timer is the counter +// itself. +// + +#define EG2TIMER_COUNTER_OFFSET 0xFFFFFFFF + +// +// ------------------------------------------------------ Data Type Definitions +// + + +// +// Define the registers and their offsets, in ULONGs. +// + +typedef enum _EG2TIMER_REGISTER { + Eg2TimerGlobalControl = 0, + Eg2TimerCurrentCount = 1, + Eg2TimerMatch = 2, + Eg2TimerInterval = 3, + Eg2TimerControl = 4 +} EG2TIMER_REGISTER, *PEG2TIMER_REGISTER; + +// +// Define the format of the private data structure. The offset member stores the +// offset to the match register, in ULONGs. The value 0 is reserved for the +// global counter. +// + +typedef struct _EG2TIMER_DATA { + ULONG Offset; + TIMER_MODE Mode; + ULONG Period; +} EG2TIMER_DATA, *PEG2TIMER_DATA; + +// +// --------------------------------------------------------------------- Macros +// + +// +// The following macros are used to read from and write to the timer. The first +// parameter is the offset in ULONGs to apply to the requested register. The +// second parameter is the register to read or write. For write functions, the +// third parameter is the value to write. +// +// READ_REGISTER_ULONG and WRITE_REGISTER_ULONG should always be used to +// ensure that the proper barriers and flushes are in place for doing direct +// hardware accesses. +// + +#define READ_EG2TIMER(_TimerOffset, _Register) \ + READ_REGISTER_ULONG((PULONG)Eg2TimerBase + (_TimerOffset) + (_Register)) + +#define WRITE_EG2TIMER(_TimerOffset, _Register, _Value) \ + WRITE_REGISTER_ULONG((PULONG)Eg2TimerBase + (_TimerOffset) + (_Register), \ + (_Value)) + +// +// ----------------------------------------------- Internal Function Prototypes +// + +NTSTATUS +Eg2TimerRegister ( + __in ULONG Handle, + __in PCSRT_RESOURCE_GROUP_HEADER ResourceGroup + ); + +_Function_class_(TIMER_INITIALIZE) +NTSTATUS +Eg2TimerInitialize ( + __in PVOID TimerData + ); + +_Function_class_(TIMER_QUERY_COUNTER) +ULONGLONG +Eg2TimerQueryCounter ( + __in PVOID TimerData + ); + +_Function_class_(TIMER_ACKNOWLEDGE_INTERRUPT) +VOID +Eg2TimerAcknowledgeInterrupt ( + __in PVOID TimerData + ); + +_Function_class_(TIMER_ARM_TIMER) +NTSTATUS +Eg2TimerArm ( + __in PVOID TimerData, + __in TIMER_MODE Mode, + __in ULONGLONG TickCount + ); + +_Function_class_(TIMER_STOP) +VOID +Eg2TimerStop ( + __in PVOID TimerData + ); + +// +// -------------------------------------------------------------------- Globals +// + +// +// Define the physical address of the timer block. This information can either +// be hardcoded like it is here or fetched out of the CSRT resource passed to +// the extension. +// + +ULONGLONG Eg2TimerPhysicalAddress = 0xBEEF7AC0; + +// +// Define the GSIVs for each timer's interrupt. This is also a candidate for +// information to be retrieved out of the CSRT. +// + +ULONG Eg2TimerGsi[EG2MATCH_COUNT] = { + 40, + 41, + 42, +}; + +// +// Define the mapped virtual address of the timer block. +// + +PVOID Eg2TimerBase = NULL; + +// +// ------------------------------------------------------------------ Functions +// + +NTSTATUS +AddResourceGroup ( + __in ULONG Handle, + __in PCSRT_RESOURCE_GROUP_HEADER ResourceGroup + ) + +/*++ + +Routine Description: + + This routine identifies and registers all of the Resource Descriptors + in the specified Resource Group. + +Arguments: + + Handle - Supplies the HAL Extension handle which must be passed to other + HAL Extension APIs. + + ResourceGroup - Supplies a pointer to the Resource Group which the + HAL Extension has been installed on. + +Return Value: + + NTSTATUS code. + +--*/ + +{ + + NTSTATUS Status; + + // + // Register the main timer block. + // + + Status = Eg2TimerRegister(Handle, ResourceGroup); + if (!NT_SUCCESS(Status)) { + goto AddResourceGroupEnd; + } + + Status = STATUS_SUCCESS; + +AddResourceGroupEnd: + return Status; +} + +// +// --------------------------------------------------------- Internal Functions +// + +NTSTATUS +Eg2TimerRegister ( + __in ULONG Handle, + __in PCSRT_RESOURCE_GROUP_HEADER ResourceGroup + ) + +/*++ + +Routine Description: + + This routine registers the EG2 Timer hardware. + +Arguments: + + Handle - Supplies the HAL Extension handle which must be passed to other + HAL Extension APIs. + + ResourceGroup - Supplies a pointer to the Resource Group which the + HAL Extension has been installed on. + +Return Value: + + NT status code. + +--*/ + +{ + + EG2TIMER_DATA InternalData; + TIMER_INITIALIZATION_BLOCK NewTimer; + PHYSICAL_ADDRESS PhysicalAddress; + CSRT_RESOURCE_DESCRIPTOR_HEADER ResourceDescriptorHeader; + NTSTATUS Status; + ULONG TimerIndex; + + // + // DEV HACK: Makeup a resource type until we get correct CSRT parsing. + // + + ResourceDescriptorHeader.Type = CSRT_RD_TYPE_TIMER; + ResourceDescriptorHeader.Subtype = CSRT_RD_SUBTYPE_TIMER; + ResourceDescriptorHeader.Length = sizeof(CSRT_RESOURCE_DESCRIPTOR_HEADER); + + // + // Register the entire timer block's address usage with the HAL. This + // address space should be shown to the HAL as reserved even if the timer + // is not going to be registered or used so that the system knows that + // region of *physical* address space is occupied. + // + + PhysicalAddress.QuadPart = Eg2TimerPhysicalAddress; + Status = HalRegisterPermanentAddressUsage(PhysicalAddress, + EG2TIMER_BLOCK_SIZE); + + if (!NT_SUCCESS(Status)) { + goto RegisterEnd; + } + + // + // Register the main counter as a non-interrupt generating timer, as it can + // be used completely independently of the match registers as long as it + // is never written to. + // + + RtlZeroMemory(&NewTimer, sizeof(TIMER_INITIALIZATION_BLOCK)); + RtlZeroMemory(&InternalData, sizeof(EG2TIMER_DATA)); + INITIALIZE_TIMER_HEADER(&NewTimer); + NewTimer.CounterBitWidth = EG2TIMER_BIT_WIDTH; + NewTimer.CounterFrequency = EG2TIMER_FREQUENCY; + + // + // Set the pointer to the internal data and its size. The pointer can + // be the same for each timer (and a local variable) because a *copy* + // of this data will be made for each timer registered. This is the + // extensions only chance to dynamically allocate memory. + // + + NewTimer.InternalData = &InternalData; + NewTimer.InternalDataSize = sizeof(EG2TIMER_DATA); + NewTimer.Interrupt.Mode = Latched; + NewTimer.Interrupt.Polarity = InterruptActiveHigh; + + // + // This must be set to indicate that this is a custom third-party timer. + // The HAL will fail the registration if this is not set correctly. + // + + NewTimer.KnownType = TimerUnknown; + + // + // The timer does not support a divisor. The GSI data can be hardcoded + // like it is here or pulled out of the resource from the CSRT table. + // Filling in extra functions doesn't hurt as the HAL will never call + // anything but Initialize and QueryCounter on timers that don't + // generate interrupts. + // + + NewTimer.MaxDivisor = 1; + NewTimer.FunctionTable.Initialize = Eg2TimerInitialize; + NewTimer.FunctionTable.QueryCounter = Eg2TimerQueryCounter; + NewTimer.FunctionTable.AcknowledgeInterrupt = Eg2TimerAcknowledgeInterrupt; + NewTimer.FunctionTable.ArmTimer = Eg2TimerArm; + NewTimer.FunctionTable.Stop = Eg2TimerStop; + NewTimer.Capabilities = TIMER_COUNTER_READABLE; + InternalData.Offset = EG2TIMER_COUNTER_OFFSET; + ResourceDescriptorHeader.Uid = EG2TIMER_COUNTER_OFFSET; + Status = RegisterResourceDescriptor(Handle, + ResourceGroup, + &ResourceDescriptorHeader, + &NewTimer); + + if (!NT_SUCCESS(Status)) { + goto RegisterEnd; + } + + + // + // Register each match register as a separate non-readable timer with the + // HAL. Since this timer is deadline-based, it can do pseudo-periodic + // mode and lossless rate transitions. + // + + NewTimer.Capabilities = TIMER_ONE_SHOT_CAPABLE | + TIMER_PERIODIC_CAPABLE | + TIMER_PSEUDO_PERIODIC_CAPABLE | + TIMER_GENERATES_LINE_BASED_INTERRUPTS; + + for (TimerIndex = 0; TimerIndex < EG2MATCH_COUNT; TimerIndex += 1) { + NewTimer.Interrupt.Gsi = Eg2TimerGsi[TimerIndex]; + InternalData.Offset = EG2MATCH_SIZE * TimerIndex; + ResourceDescriptorHeader.Uid = EG2MATCH_SIZE * TimerIndex; + Status = RegisterResourceDescriptor(Handle, + ResourceGroup, + &ResourceDescriptorHeader, + &NewTimer); + + if (!NT_SUCCESS(Status)) { + goto RegisterEnd; + } + } + + Status = STATUS_SUCCESS; + +RegisterEnd: + return Status; +} + +_Function_class_(TIMER_INITIALIZE) +NTSTATUS +Eg2TimerInitialize ( + __in PVOID TimerData + ) + +/*++ + +Routine Description: + + This routine is responsible for initializing the timer hardware. This is + guaranteed to be the first timer routine called by the HAL. It must prepare + the timer for use by beginning the timer's counter ticking if the counter + is readable, setting the intial input clock divisor to 1 if applicable, + and masking all interrupts. If the timer's stop routine is called, this + routine will be called before the timer is queried or armed again. It will + not be called between every rearming of the timer. + + This routine will not be called concurrently with any other calls to + this HAL Timer extension. For per-processor timers, this routine will be + called once on each processor. A failure on any processor blocks the timer's + use on all processors. + +Arguments: + + TimerData - Supplies a pointer to the timer's private context. The contents + of this pointer were specified when the timer was initially registered, + and may be modified inside this routine. The HAL does not interpret any + data deferenced from this pointer. + +Return Value: + + Returns an NT status code indicating success or failure. If a successful + status code is returned then the HAL may subsequently call further routines + in this HAL extension to query or arm the timer. If a failure code is + returned, this HAL extension will not attempt to use this timer unless the + Initialize routine is called again and succeeds. + +--*/ + +{ + + PHYSICAL_ADDRESS PhysicalAddress; + NTSTATUS Status; + PEG2TIMER_DATA Timer; + + Timer = (PEG2TIMER_DATA)TimerData; + + // + // Map the timer if no one has done that yet. + // + + if (Eg2TimerBase == NULL) { + PhysicalAddress.QuadPart = Eg2TimerPhysicalAddress; + Eg2TimerBase = HalMapIoSpace(PhysicalAddress, + EG2TIMER_BLOCK_SIZE, + MmNonCached); + + if (Eg2TimerBase == NULL) { + Status = STATUS_INSUFFICIENT_RESOURCES; + goto InitializeEnd; + } + } + + // + // Start the counter ticking in free running mode, and mask all interrupts. + // The counter must *not* be reset here, otherwise an Initialize call on the + // counter would affect the match register timers, which as far as the HAL + // is concerned are completely independent from one another. + // + + WRITE_EG2TIMER(0, Eg2TimerGlobalControl, EG2TIMER_GLOBAL_CONTROL_ENABLE); + if (Timer->Offset != EG2TIMER_COUNTER_OFFSET) { + WRITE_EG2TIMER(Timer->Offset, Eg2TimerControl, 0); + } + + Status = STATUS_SUCCESS; + +InitializeEnd: + return Status; +} + +_Function_class_(TIMER_QUERY_COUNTER) +ULONGLONG +Eg2TimerQueryCounter ( + __in PVOID TimerData + ) + +/*++ + +Routine Description: + + This routine queries the timer hardware and retrieves the current counter + value. + + Timers are assumed to always count *up*. If the actual timer hardware counts + down, then this routine should subtract the current count from the maximum + counter value so that values appear to count up. This routine may be called + concurrently on multiple processors and must be reentrant. This routine is + extremely performance sensitive, as it may be used to back the system + performance counter. + +Arguments: + + TimerData - Supplies a pointer to the timer's private context, whose + initial content was supplied when the timer was registered. + +Return Value: + + Returns the hardware's current count. + +--*/ + +{ + + PEG2TIMER_DATA Timer; + + Timer = (PEG2TIMER_DATA)TimerData; + + NT_ASSERT(Timer->Offset == EG2TIMER_COUNTER_OFFSET); + + return READ_EG2TIMER(Timer->Offset, Eg2TimerCurrentCount); +} + +_Function_class_(TIMER_ACKNOWLEDGE_INTERRUPT) +VOID +Eg2TimerAcknowledgeInterrupt ( + __in PVOID TimerData + ) + +/*++ + +Routine Description: + + This routine performs any actions necessary to acknowledge and quiesce a + timer interrupt. For per-processor timers, this routine may be called + concurrently on multiple processors. This routine will be called on every + timer interrupt at the hardware priority level of that interrupt, so this + routine is extremely performance sensitive. For timers running in + pseudo-periodic mode, this routine must rearm the timer for the same + interval as it was armed with without introducing delay into the interrupt + interval. Only deadline-based timers support pseudo-periodic mode. + +Arguments: + + TimerData - Supplies a pointer to the timer's private context, whose + initial content was supplied when the timer was registered. + +Return Value: + + None. + +--*/ + +{ + + ULONG MatchValue; + PEG2TIMER_DATA Timer; + + // + // No action is necessary here as far as acknowledging the interrupt. + // If the current mode is pseudo-periodic, the next interrupt must be + // armed now. + // + + Timer = (PEG2TIMER_DATA)TimerData; + + NT_ASSERT(Timer->Offset != EG2TIMER_COUNTER_OFFSET); + NT_ASSERT(Timer->Mode != TimerModeInvalid); + + if (Timer->Mode == TimerModePseudoPeriodic) { + + NT_ASSERT(Timer->Period != 0); + + // + // Read the deadline that just passed, add the period, and then write + // the new deadline. + // + + MatchValue = READ_EG2TIMER(Timer->Offset, Eg2TimerMatch); + MatchValue += Timer->Period; + WRITE_EG2TIMER(Timer->Offset, Eg2TimerMatch, MatchValue); + } + + return; +} + +_Function_class_(TIMER_ARM_TIMER) +NTSTATUS +Eg2TimerArm ( + __in PVOID TimerData, + __in TIMER_MODE Mode, + __in ULONGLONG TickCount + ) + +/*++ + +Routine Description: + + This routine arms a timer to fire an interrupt after the given number of + timer ticks. For timers that only interrupt on rollovers, this simply + enables the interrupt, the tick count parameter is ignored. If the timer + is currently armed for a different mode or tick count, this call is + expected to replace that programming. This routine will not get called + concurrently with other timer calls, except on per-processor timers, where + it may get called concurrently on different processors. + +Arguments: + + TimerData - Supplies a pointer to the timer's private context, whose + initial content was supplied when the timer was registered. + + Mode - Supplies the mode to arm the timer in, which will be one of the + modes the HAL extension advertised support for when registering the + timer. The modes are as follows: + + OneShot - Arms the timer to fire the given number of ticks from now. + Only one interrupt is expected to come in. The HAL does not make + assumptions on whether or not the expiration of this interrupt + causes the counter to stop. The Query Counter routine will not be + called while a timer is armed to fire an interrupt. + + Periodic - Arms the timer to fire periodically with an interval of the + given number of ticks. The first interrupt should happen + approximately the given number of ticks from when the arm timer + function was invoked. + + PseudoPeriodic - Arms the timer with the same functional behavior as + periodic mode, with the knowledge that the timer will have to rearm + itself during the acknowledge interrupt routine. This mode is + expected to have slightly worse performance than pure periodic mode, + but is expected to generate periodic interrupts at the exact rate + specified. + + TickCount - Supplies the number of ticks from now that the timer should + assert its interrupt in. For timers that are only capable of + interrupting on rollovers from their maximum value to 0, this parameter + is ignored. + +Return Value: + + Returns and NTSTATUS code indicating success or failure. If the timer + returns success, then the interrupt is expected to come in the specified + number of ticks from when the function was called, with a tolerance of + however long the function took to execute. If the routine fails, then no + timer routines will be called again until the timer Initialize routine is + called again and succeeds. In most cases, returning a failure code results + in a system bugcheck. + +--*/ + +{ + + ULONG ControlRegister; + ULONG MatchValue; + PEG2TIMER_DATA Timer; + + Timer = (PEG2TIMER_DATA)TimerData; + + NT_ASSERT(TickCount != 0); + NT_ASSERT(TickCount <= 0xFFFFFFFF); + NT_ASSERT(Timer->Offset != EG2TIMER_COUNTER_OFFSET); + + // + // This will never occur. + // + + if ((TickCount > 0xFFFFFFFF) || (TickCount == 0)) { + return STATUS_INVALID_PARAMETER; + } + + // + // Disable the timer while it's being programmed to avoid spurious + // interrupts. + // + + WRITE_EG2TIMER(Timer->Offset, Eg2TimerControl, 0); + ControlRegister = EG2TIMER_MATCH_INTERRUPT_ENABLE; + + // + // For periodic mode, set the periodic interval register. + // + + if (Mode == TimerModePeriodic) { + ControlRegister |= EG2TIMER_MATCH_PERIODIC; + WRITE_EG2TIMER(Timer->Offset, Eg2TimerInterval, (ULONG)TickCount); + } + + Timer->Mode = Mode; + Timer->Period = (ULONG)TickCount; + + // + // Calculate and write in the first match value. + // + + MatchValue = READ_EG2TIMER(Timer->Offset, Eg2TimerCurrentCount); + MatchValue += (ULONG)TickCount; + WRITE_EG2TIMER(Timer->Offset, Eg2TimerMatch, MatchValue); + + // + // Enable the interrupt. + // + + WRITE_EG2TIMER(Timer->Offset, Eg2TimerControl, ControlRegister); + return STATUS_SUCCESS; +} + +_Function_class_(TIMER_STOP) +VOID +Eg2TimerStop ( + __in PVOID TimerData + ) + +/*++ + +Routine Description: + + This routine stops a timer from ticking. After this function returns, the + timer should not generate any more interrupts, and reads to its counter + might return the same value every time. + +Arguments: + + TimerData - Supplies a pointer to the timer's private context, whose + initial content was supplied when the timer was registered. + +Return Value: + + None, this function must succeed. + +--*/ + +{ + + PEG2TIMER_DATA Timer; + + Timer = (PEG2TIMER_DATA)TimerData; + + // + // Clear the interrupt enable bit. + // + + WRITE_EG2TIMER(Timer->Offset, Eg2TimerControl, 0); + return; +} |
