UEFI News and Commentary

Showing posts with label Timer. Show all posts
Showing posts with label Timer. Show all posts

Sunday, September 08, 2013

UEFI 2.4 Review, Part 11: Timestamp Protocol

This article is the eleventh in a series looking at the changes introduced in the UEFI 2.4 specification. This time we take a look at the time stamp protocol (EFI_TIMESTAMP_PROTOCOL) in chapter 34.

Timestamp counters are common on various CPU architectures. So common, in fact, that many features have come to rely on them, such as ACPI's Firmware Performance Data Table (FPDT, chapter 5.2.23). On the x86 processors, the RDTSC instruction returns the current value. But, in various incarnations, this value has to be manipulated based on the number of cores, power-state, etc. So how to get a reliable number? Or, for that matter, how does this map on to ARM's generic timer?

I recently ran into this while trying to build the UEFI shell. There were recent changes to add the DP command, which displays the FPDT contents, to the UEFI shell. But this command had a dependency on the TimerLib, which was fixed at build time for a specific hardware implementation. That means that if the shell was run on another system, with a slightly different timer implementation, the DP command would not work or, worse, return the wrong value.

Whenever there is a hardware dependency like this, UEFI likes to abstract the hardware capability behind a protocol. In this case the EFI_TIMESTAMP_PROTOCOL was modeled after the TimerLib. It has two functions: GetTimeStamp() and GetProperties().

GetTimeStamp() simply returns the current time stamp value. There are a few restrictions though: the frequency of the time stamp must remain constant, regardless of power management state and the time stamp value may roll over. This latter is very important, especially for time stamp counters with fewer bits, since they can roll over in a few seconds.

GetProperties() returns the frequency (in Hz) of the time stamp counter, and the maximum value. This allows the caller to convert differences between time stamp counter values into actual time. It also allows for it to check for overflows. This is importance, since some implementations use the ACPI timer instead of an integrated CPU time stamp counter, and some implementations of the ACPI timer are only 24- or 32-bits in size.

With this new protocol, UEFI 2.4 allows utilities to track performance of a system in a way which spans the wide variety of system and CPU architectures supported.

Thursday, September 20, 2012

HOW TO: Move an Image Using a Timer

Now that I've been able to get images to move using keyboard input, my next project has been to get an image to move around on its own and bounce off the edges of the screen.  This article will describe the process to do that, and it assumes that you have followed the directions for previous articles detailing how to move an image using keyboard input.

The first thing I realized about this is that getting an image to move on its own will require a timer.  SetTimer() is a function in Boot Services that will signal an event every so often. I can then use WaitForEvent() to wait until the timer signals an event and then move the image according to that event, rather than user input.

So let's walk through the code that we use to get this image bouncing around.

Firstly, we have our global variables.  We still need the instance of EFI_GRAPHICS_OUTPUT_PROTOCOL, the dimensions of the image, and the black pixel to clear the image, so we can keep those.  Only one more is needed, and that's a pointer to an EFI_EVENT.  This event  will be a simple, empty event that the timer will signal after every given time interval.

EFI_GRAPHICS_OUTPUT_PROTOCOL *mGraphicsOutput;
UINTN mHeight;
UINTN mWidth;

EFI_GRAPHICS_OUTPUT_BLT_PIXEL mBlack = {0, 0, 1};

EFI_EVENT mEvent;

Now we can get into the meat of the actual function that bounces this image around the screen.  We start off by initializing the EFI_EVENT using CreateEvent().  Like I mentioned earlier, it's an empty timer event so we set the first parameter (the event type) to EVT_TIMER, the last one as a pointer to mEvent, and the others are 0 and NULL.

  gBS->CreateEvent (
    EVT_TIMER,
    0,
    NULL,
    NULL,
    &mEvent
    );

After that, we call SetTimer() to create our timer that'll be the signal when to move the image.  The first parameter is mEvent, the event that is signaled whenever the timer goes off.  Second, we have to specify what sort of a timer this is, so we put TimerPeriodic, which means the timer will go off every interval, rather than only once.  The final parameter is the time interval that determines how often we want the timer to signal.  It's measured in increments of 100 nanoseconds, so I put a large number in.

  gBS->SetTimer (
    mEvent,
    TimerPeriodic,
    1000000  
    );

Next, we need to initialize all our variables to their starting values.  We give the ScanCode an intial value of NULL just to start.  DeltaX and DeltaY are the values that help determine which direction the image will be moving.  I set them both to a non-zero number so that the image won't simply bounce up and down.  After that, we have to initialize the array of events that WaitForEvent() is going to wait for.  First is one that reads if the user pressed something on the keyboard so that we can have an exit condition.  Second is mEvent, which is what the timer will signal every time interval. And finally, we initialize the current coordinates of the image.

  Key.ScanCode = SCAN_NULL;

  DeltaX = 5;
  DeltaY = 5;
  events[0] = gST->ConIn->WaitForKey;
  events[1] = mEvent;
  CurrentX = (mGraphicsOutput->Mode->Info->HorizontalResolution / 2) - (mWidth / 2);
  CurrentY = (mGraphicsOutput->Mode->Info->VerticalResolution / 2) - (mHeight / 2);

Finally, we get to our loop.  The condition for this loop is that the [End] key is not pressed.  If it is, the application will quit.  We start off by waiting for an event.  It will be waiting either for a timer signal or for a keyboard key to be pressed.  So once an event is signaled, we have to check to see which it is.  If it was a key press, we read which key was pressed and check the loop condition.  If [End] was pressed, we exit the loop.

do {
    gBS->WaitForEvent (2, events, &index);

    if (index == 0)  {
      gST->ConIn->ReadKeyStroke(gST->ConIn, &Key);
      continue;

So if it wasn't a keyboard event that was signaled, it must have been a timer event, so we have to move the image.  The first step is to set the new values of the X and Y coordinates.  We do that by adding the values of DeltaX and DeltaY to the current coordinates.  We then have to check to see if the image would go beyond the borders of the window.  If they do, set the new coordinates to the value so the image is right on the edge of the screen, and then change DeltaX or DeltaY so that the image will "bounce" off the wall.  If it hits the left or right borders, change the sign of DeltaX.  If it hits the top or bottom, change the sign of DeltaY.

} else {
      
      NewX = CurrentX + DeltaX;
      NewY = CurrentY + DeltaY;

      if (NewX + mWidth > mGraphicsOutput->Mode->Info->HorizontalResolution) {
        NewX = mGraphicsOutput->Mode->Info->HorizontalResolution - mWidth;
        DeltaX = -DeltaX;
      } else if (NewX < 0) {
        NewX = 0;
        DeltaX = -DeltaX;
      }
      if (NewY + mHeight > mGraphicsOutput->Mode->Info->VerticalResolution) {
        NewY = mGraphicsOutput->Mode->Info->VerticalResolution - mHeight;
        DeltaY = -DeltaY;
      } else if (NewY < 0) {
        NewY = 0;
        DeltaY = -DeltaY;
      }

The final parts of the loop are mainly concerned with displaying the image.  We use the EfiBltVideoFill operation of Blt() to clear the image and then call our own function, DisplayImageAt() to display the image, using the new coordinates.  Lastly, we set the current coordinates to the coordinates of the newly displayed image.

      mGraphicsOutput->Blt(
                      mGraphicsOutput,
                      &mBlack,
                      EfiBltVideoFill,
                      0,
                      0,
                      CurrentX,
                      CurrentY,
                      mWidth,
                      mHeight,
                      0
                      );
      DisplayImageAt (NewX, NewY);
    
      CurrentX = NewX;
      CurrentY = NewY;
    }
   
  } while (Key.ScanCode != SCAN_END);

The last two lines of the function lie outside the loop, and they simply clear the screen and return EFI_SUCCESS.  

  gST->ConOut->Reset (gST->ConOut, FALSE);
  return EFI_SUCCESS;

And that's it!  The image will move around the screen on its own and "bounce" off the edges of the window, and the application will quit when the [End] key is pressed.

If you are interested in seeing the entirety of the code, let me know and I'll get it sent to you.