Skip to content

Latest commit

 

History

History

Lesson_RegisterProtocolNotify

Folders and files

NameName
Last commit message
Last commit date

parent directory

..
 
 

In this lesson we would investigate the EFI_BOOT_SERVICES.RegisterProtocolNotify() function. This API can be usefull to create callbacks that are executed when certain protocols are installed in the system.

For our investigation let's create a simple driver ProtocolEventDriver:

./createNewDriver.sh ProtocolEventDriver

Add the following code to create a callback function NotifyFunc executed when the local EFI_EVENT Event is signaled:

EFI_EVENT Event;
UINTN NotifyData = 0;

VOID EFIAPI NotifyFunc(EFI_EVENT Event, VOID* Context)
{
  ...
}


EFI_STATUS
EFIAPI
ProtocolEventDriverUnload (
  EFI_HANDLE ImageHandle
  )
{
  gBS->CloseEvent(Event);

  return EFI_SUCCESS;
}

EFI_STATUS
EFIAPI
ProtocolEventDriverEntryPoint (
  IN EFI_HANDLE        ImageHandle,
  IN EFI_SYSTEM_TABLE  *SystemTable
  )
{
  EFI_STATUS Status;
  Status = gBS->CreateEvent(EVT_NOTIFY_SIGNAL,
                            TPL_NOTIFY,
                            &NotifyFunc,
                            &NotifyData,
                            &Event);
  if (EFI_ERROR(Status)) {
    Print(L"Error! CreateEvent returned: %r\n", Status);
    return Status;
  }

  return EFI_SUCCESS;
}

Here we pass UINTN NotifyData to the callback function. Let's increment it inside the callback code, so we can understand how many times the callback was called:

  if (Context == NULL)
    return;

  Print(L"\nEvent is signaled! Context = %d\n", *(UINTN*)Context);
  *(UINTN*)Context += 1;

Right now no one can signal our event, therefore the callback code would never be executed. Let's register the protocol install notifier. Here we would monitor installation of our own SIMPLE_CLASS_PROTOCOL which we've created in the earlier lesson.

In case you don't remember check out the protocol API in the UefiLessonsPkg/Include/Protocol/SimpleClass.h

typedef
EFI_STATUS
(EFIAPI* SIMPLE_CLASS_GET_NUMBER)(
  UINTN* Number
  );


typedef
EFI_STATUS
(EFIAPI* SIMPLE_CLASS_SET_NUMBER)(
  UINTN Number
  );


struct _SIMPLE_CLASS_PROTOCOL {
  SIMPLE_CLASS_GET_NUMBER GetNumber;
  SIMPLE_CLASS_SET_NUMBER SetNumber;
};

In the nutshell this protocol is a simple abstraction to the UINTN Number field.

Now to the RegisterProtocolNotify(). Here is its description from the UEFI specification:

EFI_BOOT_SERVICES.RegisterProtocolNotify()

Summary:
Creates an event that is to be signaled whenever an interface is installed for a specified protocol.

Prototype:
typedef
EFI_STATUS
(EFIAPI *EFI_REGISTER_PROTOCOL_NOTIFY) (
  IN EFI_GUID *Protocol,
  IN EFI_EVENT Event,
  OUT VOID **Registration
);

Parameters:
Protocol     - The numeric ID of the protocol for which the event is to be registered
Event        - Event that is to be signaled whenever a protocol interface is registered for Protocol.
               The same EFI_EVENT may be used for multiple protocol notify registrations
Registration - A pointer to a memory location to receive the registration value. This value must be saved and used by the notification function
               of Event to retrieve the list of handles that have added a protocol interface of type Protocol

Description:
The RegisterProtocolNotify() function creates an event that is to be signaled whenever a protocol interface is installed for Protocol by InstallProtocolInterface() or EFI_BOOT_SERVICES.ReinstallProtocolInterface().

Once Event has been signaled, the EFI_BOOT_SERVICES.LocateHandle() function can be called to identify the newly installed, or reinstalled, handles that support Protocol. The Registration parameter in EFI_BOOT_SERVICES.RegisterProtocolNotify() corresponds to the SearchKey parameter in LocateHandle().

And this is how we would use it:

#include <Protocol/SimpleClass.h>

STATIC VOID  *mRegistrationTracker;

EFI_STATUS
EFIAPI
ProtocolEventDriverEntryPoint (
  IN EFI_HANDLE        ImageHandle,
  IN EFI_SYSTEM_TABLE  *SystemTable
  )
{
  ...

  Status = gBS->RegisterProtocolNotify(&gSimpleClassProtocolGuid,
                                       Event,
                                       &mRegistrationTracker);
  if (EFI_ERROR(Status)) {
    Print(L"Error! RegisterProtocolNotify returned: %r\n", Status);
    return Status;
  }

  return EFI_SUCCESS;
}

Don't mind the Registration parameter for now, we'll investigate it later.

Also don't forget to add the necessary code to the INF file to be able to use our SIMPLE_CLASS_PROTOCOL in the driver code:

[Packages]
  ...
  UefiLessonsPkg/UefiLessonsPkg.dec

[Protocols]
  gSimpleClassProtocolGuid

Now when we load SimpleClassProtocol.efi driver (created in the earlier lesson) that installs the SIMPLE_CLASS_PROTOCOL protocol we would see that our callback code is executed:

FS0:\> load ProtocolEventDriver.efi
Image 'FS0:\ProtocolEventDriver.efi' loaded at 6415000 - Success
FS0:\> load SimpleClassProtocol.efi
Hello from SimpleClassProtocol driver
Event is signaled! Context = 0
, handle=640FB98
Image 'FS0:\SimpleClassProtocol.efi' loaded at 640C000 - Success

The strings

Hello from SimpleClassProtocol driver
, handle=640FB98

Are printed from the SimpleClassProtocol driver. Here is its code:

EFI_STATUS
EFIAPI
SimpleClassProtocolDriverEntryPoint (
  IN EFI_HANDLE        ImageHandle,
  IN EFI_SYSTEM_TABLE  *SystemTable
  )
{
  Print(L"Hello from SimpleClassProtocol driver");

  EFI_STATUS Status = gBS->InstallMultipleProtocolInterfaces(
                             &mSimpleClassHandle,
                             &gSimpleClassProtocolGuid,
                             &mSimpleClass,
                             NULL
                             );
  if (!EFI_ERROR(Status))
    Print(L", handle=%p\n", mSimpleClassHandle);
  else
    Print(L"\n", mSimpleClassHandle);

  return Status;
}

You can see how the \nEvent is signaled! Context = 0\n from the ProtocolEventDriver was printed in between the prints of SimpleClassProtocol driver. That means that as soon as the SIMPLE_CLASS_PROTOCOL was installed, our callback was executed interrupting the SimpleClassProtocol driver code.

You can execute the load SimpleClassProtocol.efi again and see that our callback would be called on each protocol install:

FS0:\> load ProtocolEventDriver.efi
Image 'FS0:\ProtocolEventDriver.efi' loaded at 6415000 - Success
FS0:\> load SimpleClassProtocol.efi
Hello from SimpleClassProtocol driver
Event is signaled! Context = 0
, handle=640FB98
Image 'FS0:\SimpleClassProtocol.efi' loaded at 640C000 - Success
FS0:\> load SimpleClassProtocol.efi
Hello from SimpleClassProtocol driver
Event is signaled! Context = 1
, handle=641AB18
Image 'FS0:\SimpleClassProtocol.efi' loaded at 6408000 - Success
FS0:\> load SimpleClassProtocol.efi
Hello from SimpleClassProtocol driver
Event is signaled! Context = 2
, handle=6419918
Image 'FS0:\SimpleClassProtocol.efi' loaded at 63E3000 - Success

You can also see that as we've coded earlier, the UINTN NotifyData (aka Context) is incremented on each callback execution.

Now let's check the dh output:

FS0:\> dh
...
A4: ImageDevicePath(..C1)/\ProtocolEventDriver.efi) LoadedImage(\ProtocolEventDriver.efi)
A5: ImageDevicePath(..C1)/\SimpleClassProtocol.efi) LoadedImage(\SimpleClassProtocol.efi)
A6: B5510EEA-6F11-4E4B-AD0F-35CE17BD7A67
A7: ImageDevicePath(..C1)/\SimpleClassProtocol.efi) LoadedImage(\SimpleClassProtocol.efi)
A8: B5510EEA-6F11-4E4B-AD0F-35CE17BD7A67
A9: ImageDevicePath(..C1)/\SimpleClassProtocol.efi) LoadedImage(\SimpleClassProtocol.efi)
AA: B5510EEA-6F11-4E4B-AD0F-35CE17BD7A67

You can see that each load SimpleClassProtocol.efi command installs a separate SIMPLE_CLASS_PROTOCOL protocol to the system.

Now let's try to actually use the SIMPLE_CLASS_PROTOCOL inside the callback function. We would use the gBS->LocateProtocol to find the protocol and perform +5 operation on its internal number via the protocol SIMPLE_CLASS_GET_NUMBER/SIMPLE_CLASS_SET_NUMBER functions.

VOID EFIAPI NotifyFunc(EFI_EVENT Event, VOID* Context)
{
  ...

  EFI_STATUS Status;
  SIMPLE_CLASS_PROTOCOL* SimpleClass;
  Status = gBS->LocateProtocol(&gSimpleClassProtocolGuid,
                               NULL,
                               (VOID**)&SimpleClass);
  if (EFI_ERROR(Status)) {
    Print(L"Error! LocateProtocol returned: %r\n", Status);
    return;
  }

  UINTN Number;
  Status = SimpleClass->GetNumber(&Number);
  if (!EFI_ERROR(Status)) {
    Print(L"Current number = %d\n", Number);
  } else {
    Print(L"Error! Can't get number: %r\n", Status);
    return;
  }

  Status = SimpleClass->SetNumber(Number+5);
  if (EFI_ERROR(Status)) {
    Print(L"Error! Can't set number: %r\n", Status);
    return;
  }
}

This is what we would get from this code:

FS0:\> load ProtocolEventDriver.efi
Image 'FS0:\ProtocolEventDriver.efi' loaded at 6415000 - Success
FS0:\> load SimpleClassProtocol.efi
Hello from SimpleClassProtocol driver
Event is signaled! Context = 0
Current number = 0
, handle=640FB98
Image 'FS0:\SimpleClassProtocol.efi' loaded at 640C000 - Success
FS0:\> load SimpleClassProtocol.efi
Hello from SimpleClassProtocol driver
Event is signaled! Context = 1
Current number = 5
, handle=646A818
Image 'FS0:\SimpleClassProtocol.efi' loaded at 6408000 - Success
FS0:\> load SimpleClassProtocol.efi
Hello from SimpleClassProtocol driver
Event is signaled! Context = 2
Current number = 10
, handle=6419918
Image 'FS0:\SimpleClassProtocol.efi' loaded at 63E3000 - Success

You can see the problem here. Each time we perform load SimpleClassProtocol.efi we install additional protocol to the system. We saw that in the dh command output. But gBS->LocateProtocol always finds the first installed protocol. So how can we call the callback code on the newly installed protocol that caused the callback in the first place?

To fix that we can utilize Registration parameter of the RegisterProtocolNotify/LocateProtocol functions.

With it the LocateProtocol function would return the next handle that is new for the registration.

Here is a description of the EFI_BOOT_SERVICES.LocateProtocol() function from the specification. We've already saw it, and used it many times, but now pay attention to the Registration parameter:

EFI_BOOT_SERVICES.LocateProtocol()

Summary:
Returns the first protocol instance that matches the given protocol.

Prototype:
typedef
EFI_STATUS
(EFIAPI *EFI_LOCATE_PROTOCOL) (
  IN EFI_GUID *Protocol,
  IN VOID *Registration OPTIONAL,
  OUT VOID **Interface
);

Parameters:
Protocol      - Provides the protocol to search for
Registration  - Optional registration key returned from EFI_BOOT_SERVICES.RegisterProtocolNotify(). If Registration is NULL, then it is ignored
Interface     - On return, a pointer to the first interface that matches Protocol and Registration

Description:
The LocateProtocol() function finds the first device handle that support Protocol, and returns a pointer to the protocol interface from that handle in Interface. If no protocol instances are found, then Interface is set to NULL.

If Registration is not NULL, and there are no new handles for Registration, then EFI_NOT_FOUND is returned.

All we need to do now to fix the problem is to use mRegistrationTracker instead of NULL in the gBS->LocateProtocol call. In the nutshell the registration variable is used in 3 places:

STATIC VOID  *mRegistrationTracker;                                     // <-----

VOID EFIAPI NotifyFunc(EFI_EVENT Event, VOID* Context)
{
  ...
  Status = gBS->LocateProtocol(&gSimpleClassProtocolGuid,
                               mRegistrationTracker,                    // <-----
                               (VOID**)&SimpleClass);
  ...
}

...

EFI_STATUS
EFIAPI
ProtocolEventDriverEntryPoint (
  IN EFI_HANDLE        ImageHandle,
  IN EFI_SYSTEM_TABLE  *SystemTable
  )
{
  ...
  Status = gBS->RegisterProtocolNotify(&gSimpleClassProtocolGuid,
                                       Event,
                                       &mRegistrationTracker);          // <-----
  ...
}

Now on each callback we would work with the newly installed protocol:

FS0:\> load ProtocolEventDriver.efi
Image 'FS0:\ProtocolEventDriver.efi' loaded at 6415000 - Success
FS0:\> load SimpleClassProtocol.efi
Hello from SimpleClassProtocol driver
Event is signaled! Context = 0
Current number = 0
, handle=640FB98
Image 'FS0:\SimpleClassProtocol.efi' loaded at 640C000 - Success
FS0:\> load SimpleClassProtocol.efi
Hello from SimpleClassProtocol driver
Event is signaled! Context = 1
Current number = 0
, handle=646C218
Image 'FS0:\SimpleClassProtocol.efi' loaded at 6408000 - Success
FS0:\> load SimpleClassProtocol.efi
Hello from SimpleClassProtocol driver
Event is signaled! Context = 2
Current number = 0
, handle=6419918
Image 'FS0:\SimpleClassProtocol.efi' loaded at 63E3000 - Success

EfiCreateProtocolNotifyEvent

To ease creation process of the protocol notification callbacks the UefiLib offers a EfiCreateProtocolNotifyEvent function:

/**
  Creates and returns a notification event and registers that event with all the protocol
  instances specified by ProtocolGuid.

  This function causes the notification function to be executed for every protocol of type
  ProtocolGuid instance that exists in the system when this function is invoked. If there are
  no instances of ProtocolGuid in the handle database at the time this function is invoked,
  then the notification function is still executed one time. In addition, every time a protocol
  of type ProtocolGuid instance is installed or reinstalled, the notification function is also
  executed. This function returns the notification event that was created.
  If ProtocolGuid is NULL, then ASSERT().
  If NotifyTpl is not a legal TPL value, then ASSERT().
  If NotifyFunction is NULL, then ASSERT().
  If Registration is NULL, then ASSERT().


  @param  ProtocolGuid    Supplies GUID of the protocol upon whose installation the event is fired.
  @param  NotifyTpl       Supplies the task priority level of the event notifications.
  @param  NotifyFunction  Supplies the function to notify when the event is signaled.
  @param  NotifyContext   The context parameter to pass to NotifyFunction.
  @param  Registration    A pointer to a memory location to receive the registration value.
                          This value is passed to LocateHandle() to obtain new handles that
                          have been added that support the ProtocolGuid-specified protocol.

  @return The notification event that was created.

**/
EFI_EVENT
EFIAPI
EfiCreateProtocolNotifyEvent (
  IN  EFI_GUID          *ProtocolGuid,
  IN  EFI_TPL           NotifyTpl,
  IN  EFI_EVENT_NOTIFY  NotifyFunction,
  IN  VOID              *NotifyContext   OPTIONAL,
  OUT VOID              **Registration
  )

This API abstracts the calling of CreateEvent/RegisterProtocolNotify functions.

Keep in mind that EfiCreateProtocolNotifyEvent also immediately signals the callback function NotifyFunction manually right after the RegisterProtocolNotify call. So your notification function can be executed even if there are no target protocols in the system.

You can see how the EfiCreateProtocolNotifyEvent can simplify our main code:

EFI_STATUS
EFIAPI
ProtocolEventDriverEntryPoint (
  IN EFI_HANDLE        ImageHandle,
  IN EFI_SYSTEM_TABLE  *SystemTable
  )
{
  Event = EfiCreateProtocolNotifyEvent(&gSimpleClassProtocolGuid,
                                       TPL_NOTIFY,
                                       &NotifyFunc,
                                       &NotifyData,
                                       &mRegistrationTracker);
  return EFI_SUCCESS;
}

If you'll test this application you'll see that opposed to our own code before, here the created event is signaled one time even when there are no SIMPLE_CLASS_PROTOCOL protocols in the system.

FS0:\> load ProtocolEventDriver.efi

Event is signaled! Context = 0                                      <-------
Error! LocateProtocol returned: Not Found                           <-------
Image 'FS0:\ProtocolEventDriver.efi' loaded at 6415000 - Success
FS0:\> load SimpleClassProtocol.efi
Hello from SimpleClassProtocol driver
Event is signaled! Context = 1
Current number = 0
, handle=640FB98
Image 'FS0:\SimpleClassProtocol.efi' loaded at 640C000 - Success
FS0:\> load SimpleClassProtocol.efi
Hello from SimpleClassProtocol driver
Event is signaled! Context = 2
Current number = 0
, handle=641A918
Image 'FS0:\SimpleClassProtocol.efi' loaded at 6408000 - Success
FS0:\> load SimpleClassProtocol.efi
Hello from SimpleClassProtocol driver
Event is signaled! Context = 3
Current number = 0
, handle=6419918
Image 'FS0:\SimpleClassProtocol.efi' loaded at 63E3000 - Success