From 505dad17d2216fab0342afb5794f300a949dd4c8 Mon Sep 17 00:00:00 2001 From: Konstantin Aladyshev Date: Mon, 12 Feb 2024 16:21:34 +0300 Subject: Add protocol installation callbacks lesson Signed-off-by: Konstantin Aladyshev --- .../Lesson_RegisterProtocolNotify/README.md | 470 +++++++++++++++++++++ 1 file changed, 470 insertions(+) create mode 100644 Lessons_uncategorized/Lesson_RegisterProtocolNotify/README.md (limited to 'Lessons_uncategorized/Lesson_RegisterProtocolNotify/README.md') diff --git a/Lessons_uncategorized/Lesson_RegisterProtocolNotify/README.md b/Lessons_uncategorized/Lesson_RegisterProtocolNotify/README.md new file mode 100644 index 0000000..72f4e65 --- /dev/null +++ b/Lessons_uncategorized/Lesson_RegisterProtocolNotify/README.md @@ -0,0 +1,470 @@ +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: +```cpp +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: +```cpp + 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` +```cpp +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: +```cpp +#include + +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: +```cpp +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. +```cpp +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: +```cpp +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](https://github.com/tianocore/edk2/blob/master/MdePkg/Include/Library/UefiLib.h) offers a `EfiCreateProtocolNotifyEvent` function: +```cpp +/** + 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: +```cpp +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 +``` -- cgit v1.2.3-18-g5258