1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
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 <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:
```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
```
|