1 /** @file
2   RISC-V Timer Architectural Protocol for U5 series platform.
3 
4   Copyright (c) 2019, Hewlett Packard Enterprise Development LP. All rights reserved.<BR>
5 
6   SPDX-License-Identifier: BSD-2-Clause-Patent
7 
8 **/
9 
10 #include "Timer.h"
11 #include <Library/RiscVEdk2SbiLib.h>
12 #include <sbi/riscv_asm.h>
13 #include <sbi/riscv_encoding.h>
14 #include <sbi/riscv_io.h>
15 #include <sbi/riscv_atomic.h>
16 #include <U5Clint.h>
17 
18 STATIC volatile VOID * const p_mtime = (VOID *)CLINT_REG_MTIME;
19 #define MTIME          (*p_mtime)
20 #define MTIMECMP(i)    (p_mtimecmp[i])
21 BOOLEAN TimerHandlerReentry = FALSE;
22 
23 //
24 // The handle onto which the Timer Architectural Protocol will be installed
25 //
26 STATIC EFI_HANDLE mTimerHandle = NULL;
27 
28 //
29 // The Timer Architectural Protocol that this driver produces
30 //
31 EFI_TIMER_ARCH_PROTOCOL   mTimer = {
32   TimerDriverRegisterHandler,
33   TimerDriverSetTimerPeriod,
34   TimerDriverGetTimerPeriod,
35   TimerDriverGenerateSoftInterrupt
36 };
37 
38 //
39 // Pointer to the CPU Architectural Protocol instance
40 //
41 EFI_CPU_ARCH_PROTOCOL *mCpu;
42 
43 //
44 // The notification function to call on every timer interrupt.
45 // A bug in the compiler prevents us from initializing this here.
46 //
47 STATIC EFI_TIMER_NOTIFY mTimerNotifyFunction;
48 
49 //
50 // The current period of the timer interrupt
51 //
52 STATIC UINT64 mTimerPeriod = 0;
53 
54 /**
55   U5 Series Timer Interrupt Handler.
56 
57   @param InterruptType    The type of interrupt that occured
58   @param SystemContext    A pointer to the system context when the interrupt occured
59 **/
60 
61 VOID
62 EFIAPI
TimerInterruptHandler(IN EFI_EXCEPTION_TYPE InterruptType,IN EFI_SYSTEM_CONTEXT SystemContext)63 TimerInterruptHandler (
64   IN EFI_EXCEPTION_TYPE   InterruptType,
65   IN EFI_SYSTEM_CONTEXT   SystemContext
66   )
67 {
68   EFI_TPL OriginalTPL;
69   UINT64 RiscvTimer;
70 
71   if (TimerHandlerReentry) {
72     //
73     // MMode timer occurred when processing
74     // SMode timer handler.
75     //
76     RiscvTimer = readq_relaxed(p_mtime);
77     SbiSetTimer (RiscvTimer += mTimerPeriod);
78     csr_clear(CSR_SIP, MIP_STIP);
79     return;
80   }
81   TimerHandlerReentry = TRUE;
82 
83   OriginalTPL = gBS->RaiseTPL (TPL_HIGH_LEVEL);
84   csr_clear(CSR_SIE, MIP_STIP); // Disable SMode timer int
85   csr_clear(CSR_SIP, MIP_STIP);
86   if (mTimerPeriod == 0) {
87     gBS->RestoreTPL (OriginalTPL);
88     csr_clear(CSR_SIE, MIP_STIP); // Disable SMode timer int
89     return;
90   }
91   if (mTimerNotifyFunction != NULL) {
92       mTimerNotifyFunction (mTimerPeriod);
93   }
94   RiscvTimer = readq_relaxed(p_mtime);
95   SbiSetTimer (RiscvTimer += mTimerPeriod);
96   gBS->RestoreTPL (OriginalTPL);
97   csr_set(CSR_SIE, MIP_STIP); // enable SMode timer int
98   TimerHandlerReentry = FALSE;
99 }
100 
101 /**
102 
103   This function registers the handler NotifyFunction so it is called every time
104   the timer interrupt fires.  It also passes the amount of time since the last
105   handler call to the NotifyFunction.  If NotifyFunction is NULL, then the
106   handler is unregistered.  If the handler is registered, then EFI_SUCCESS is
107   returned.  If the CPU does not support registering a timer interrupt handler,
108   then EFI_UNSUPPORTED is returned.  If an attempt is made to register a handler
109   when a handler is already registered, then EFI_ALREADY_STARTED is returned.
110   If an attempt is made to unregister a handler when a handler is not registered,
111   then EFI_INVALID_PARAMETER is returned.  If an error occurs attempting to
112   register the NotifyFunction with the timer interrupt, then EFI_DEVICE_ERROR
113   is returned.
114 
115   @param This             The EFI_TIMER_ARCH_PROTOCOL instance.
116   @param NotifyFunction   The function to call when a timer interrupt fires.  This
117                           function executes at TPL_HIGH_LEVEL.  The DXE Core will
118                           register a handler for the timer interrupt, so it can know
119                           how much time has passed.  This information is used to
120                           signal timer based events.  NULL will unregister the handler.
121 
122   @retval        EFI_SUCCESS            The timer handler was registered.
123   @retval        EFI_UNSUPPORTED        The platform does not support timer interrupts.
124   @retval        EFI_ALREADY_STARTED    NotifyFunction is not NULL, and a handler is already
125                                         registered.
126   @retval        EFI_INVALID_PARAMETER  NotifyFunction is NULL, and a handler was not
127                                         previously registered.
128   @retval        EFI_DEVICE_ERROR       The timer handler could not be registered.
129 
130 **/
131 EFI_STATUS
132 EFIAPI
TimerDriverRegisterHandler(IN EFI_TIMER_ARCH_PROTOCOL * This,IN EFI_TIMER_NOTIFY NotifyFunction)133 TimerDriverRegisterHandler (
134   IN EFI_TIMER_ARCH_PROTOCOL  *This,
135   IN EFI_TIMER_NOTIFY         NotifyFunction
136   )
137 {
138   DEBUG ((DEBUG_INFO, "TimerDriverRegisterHandler(0x%lx) called\n", NotifyFunction));
139   mTimerNotifyFunction = NotifyFunction;
140   return EFI_SUCCESS;
141 }
142 
143 /**
144 
145   This function adjusts the period of timer interrupts to the value specified
146   by TimerPeriod.  If the timer period is updated, then the selected timer
147   period is stored in EFI_TIMER.TimerPeriod, and EFI_SUCCESS is returned.  If
148   the timer hardware is not programmable, then EFI_UNSUPPORTED is returned.
149   If an error occurs while attempting to update the timer period, then the
150   timer hardware will be put back in its state prior to this call, and
151   EFI_DEVICE_ERROR is returned.  If TimerPeriod is 0, then the timer interrupt
152   is disabled.  This is not the same as disabling the CPU's interrupts.
153   Instead, it must either turn off the timer hardware, or it must adjust the
154   interrupt controller so that a CPU interrupt is not generated when the timer
155   interrupt fires.
156 
157 
158   @param This            The EFI_TIMER_ARCH_PROTOCOL instance.
159   @param TimerPeriod     The rate to program the timer interrupt in 100 nS units.  If
160                          the timer hardware is not programmable, then EFI_UNSUPPORTED is
161                          returned.  If the timer is programmable, then the timer period
162                          will be rounded up to the nearest timer period that is supported
163                          by the timer hardware.  If TimerPeriod is set to 0, then the
164                          timer interrupts will be disabled.
165 
166   @retval        EFI_SUCCESS       The timer period was changed.
167   @retval        EFI_UNSUPPORTED   The platform cannot change the period of the timer interrupt.
168   @retval        EFI_DEVICE_ERROR  The timer period could not be changed due to a device error.
169 
170 **/
171 EFI_STATUS
172 EFIAPI
TimerDriverSetTimerPeriod(IN EFI_TIMER_ARCH_PROTOCOL * This,IN UINT64 TimerPeriod)173 TimerDriverSetTimerPeriod (
174   IN EFI_TIMER_ARCH_PROTOCOL  *This,
175   IN UINT64                   TimerPeriod
176   )
177 {
178   UINT64 RiscvTimer;
179 
180   DEBUG ((DEBUG_INFO, "TimerDriverSetTimerPeriod(0x%lx)\n", TimerPeriod));
181 
182   if (TimerPeriod == 0) {
183     mTimerPeriod = 0;
184     csr_clear(CSR_SIE, MIP_STIP); // disable timer int
185     return EFI_SUCCESS;
186   }
187 
188   mTimerPeriod = TimerPeriod / 10; // convert unit from 100ns to 1us
189 
190   RiscvTimer = readq_relaxed(p_mtime);
191   SbiSetTimer(RiscvTimer + mTimerPeriod);
192 
193   mCpu->EnableInterrupt(mCpu);
194   csr_set(CSR_SIE, MIP_STIP); // enable timer int
195   return EFI_SUCCESS;
196 }
197 
198 /**
199 
200   This function retrieves the period of timer interrupts in 100 ns units,
201   returns that value in TimerPeriod, and returns EFI_SUCCESS.  If TimerPeriod
202   is NULL, then EFI_INVALID_PARAMETER is returned.  If a TimerPeriod of 0 is
203   returned, then the timer is currently disabled.
204 
205 
206   @param This            The EFI_TIMER_ARCH_PROTOCOL instance.
207   @param TimerPeriod     A pointer to the timer period to retrieve in 100 ns units.  If
208                          0 is returned, then the timer is currently disabled.
209 
210   @retval EFI_SUCCESS            The timer period was returned in TimerPeriod.
211   @retval EFI_INVALID_PARAMETER  TimerPeriod is NULL.
212 
213 **/
214 EFI_STATUS
215 EFIAPI
TimerDriverGetTimerPeriod(IN EFI_TIMER_ARCH_PROTOCOL * This,OUT UINT64 * TimerPeriod)216 TimerDriverGetTimerPeriod (
217   IN EFI_TIMER_ARCH_PROTOCOL   *This,
218   OUT UINT64                   *TimerPeriod
219   )
220 {
221   *TimerPeriod = mTimerPeriod;
222   return EFI_SUCCESS;
223 }
224 
225 /**
226 
227   This function generates a soft timer interrupt. If the platform does not support soft
228   timer interrupts, then EFI_UNSUPPORTED is returned. Otherwise, EFI_SUCCESS is returned.
229   If a handler has been registered through the EFI_TIMER_ARCH_PROTOCOL.RegisterHandler()
230   service, then a soft timer interrupt will be generated. If the timer interrupt is
231   enabled when this service is called, then the registered handler will be invoked. The
232   registered handler should not be able to distinguish a hardware-generated timer
233   interrupt from a software-generated timer interrupt.
234 
235 
236   @param This              The EFI_TIMER_ARCH_PROTOCOL instance.
237 
238   @retval EFI_SUCCESS       The soft timer interrupt was generated.
239   @retval EFI_UNSUPPORTEDT  The platform does not support the generation of soft timer interrupts.
240 
241 **/
242 EFI_STATUS
243 EFIAPI
TimerDriverGenerateSoftInterrupt(IN EFI_TIMER_ARCH_PROTOCOL * This)244 TimerDriverGenerateSoftInterrupt (
245   IN EFI_TIMER_ARCH_PROTOCOL  *This
246   )
247 {
248   return EFI_SUCCESS;
249 }
250 
251 /**
252   Initialize the Timer Architectural Protocol driver
253 
254   @param ImageHandle     ImageHandle of the loaded driver
255   @param SystemTable     Pointer to the System Table
256 
257   @retval EFI_SUCCESS            Timer Architectural Protocol created
258   @retval EFI_OUT_OF_RESOURCES   Not enough resources available to initialize driver.
259   @retval EFI_DEVICE_ERROR       A device error occured attempting to initialize the driver.
260 
261 **/
262 EFI_STATUS
263 EFIAPI
TimerDriverInitialize(IN EFI_HANDLE ImageHandle,IN EFI_SYSTEM_TABLE * SystemTable)264 TimerDriverInitialize (
265   IN EFI_HANDLE        ImageHandle,
266   IN EFI_SYSTEM_TABLE  *SystemTable
267   )
268 {
269   EFI_STATUS  Status;
270 
271   //
272   // Initialize the pointer to our notify function.
273   //
274   mTimerNotifyFunction = NULL;
275 
276   //
277   // Make sure the Timer Architectural Protocol is not already installed in the system
278   //
279   ASSERT_PROTOCOL_ALREADY_INSTALLED (NULL, &gEfiTimerArchProtocolGuid);
280 
281   //
282   // Find the CPU architectural protocol.
283   //
284   Status = gBS->LocateProtocol (&gEfiCpuArchProtocolGuid, NULL, (VOID **) &mCpu);
285   ASSERT_EFI_ERROR (Status);
286 
287   //
288   // Force the timer to be disabled
289   //
290   Status = TimerDriverSetTimerPeriod (&mTimer, 0);
291   ASSERT_EFI_ERROR (Status);
292 
293   //
294   // Install interrupt handler for RISC-V Timer.
295   //
296   Status = mCpu->RegisterInterruptHandler (mCpu, EXCEPT_RISCV_TIMER_INT, TimerInterruptHandler);
297   ASSERT_EFI_ERROR (Status);
298 
299   //
300   // Force the timer to be enabled at its default period
301   //
302   Status = TimerDriverSetTimerPeriod (&mTimer, DEFAULT_TIMER_TICK_DURATION);
303   ASSERT_EFI_ERROR (Status);
304 
305   //
306   // Install the Timer Architectural Protocol onto a new handle
307   //
308   Status = gBS->InstallMultipleProtocolInterfaces (
309                   &mTimerHandle,
310                   &gEfiTimerArchProtocolGuid, &mTimer,
311                   NULL
312                   );
313   ASSERT_EFI_ERROR (Status);
314   return Status;
315 }
316