* Re: NotifyPpi description contradiction
2016-11-06 0:43 NotifyPpi description contradiction valerij zaporogeci
@ 2016-11-07 15:47 ` Laszlo Ersek
2016-11-07 21:04 ` valerij zaporogeci
0 siblings, 1 reply; 3+ messages in thread
From: Laszlo Ersek @ 2016-11-07 15:47 UTC (permalink / raw)
To: valerij zaporogeci; +Cc: edk2-devel
On 11/06/16 01:43, valerij zaporogeci wrote:
> Hi.
> I have two quotes from Pei spec (Vol1) related to the notification
> (callback) invocation logic. Tell me, which one tells the truth. They
> are obviously contradictive.
> Quote (page 26, description of NotifyPpi service):
> Description
> This service enables PEIMs to register a given service to be invoked
> when another service is installed or reinstalled. This service will
> fire notifications on PPIs installed prior to this service
> invocation. This is different behavior than the
> RegisterProtocolNotify of UEFI2.0, for example
> EFI_PEI_NOTIFY_DESCRIPTOR is defined in “PEIM Descriptors” on page
> 113.
> Quote (page 115-116, EFI_PEI_NOTIFY_DESCRIPTOR description):
> Description
> EFI_PEI_NOTIFY_DESCRIPTOR is a data structure that is used by a PEIM
> that needs to be called back when a PPI is installed or reinstalled.
> The notification is similar to the RegisterProtocolNotify() function
> in the UEFI 2.0 Specification.
> ...
> Following is an example of the notification use model for
> EFI_PEI_PERMANENT_MEMORY_INSTALLED_PPI. In this example, a PEIM
> called SamplePeim executes early in the PEI phase before main memory
> is available. However, SamplePeim also needs to create some large
> data structure later in the PEI phase. As such, SamplePeim has a NULL
> depex, but after its entry point is processed, it needs to call
> NotifyPpi() with a EFI_PEI_NOTIFY_DESCRIPTOR, where the notification
> descriptor includes the following:
> • A reference to EFI_PEI_PERMANENT_MEMORY_INSTALLED_PPI
> • A reference to a function within this same PEIM called SampleCallback
> When the PEI Foundation finally migrates the system from temporary
> to permanent memory and installs the
> EFI_PEI_PERMANENT_MEMORY_INSTALLED_PPI, the PEI Foundation assesses
> if there are any pending notifications on this PPI. After the PEI
> Foundation discovers the descriptor from SamplePeim, the PEI
> Foundation invokes SampleCallback.
>
> So, when SamplePeim invokes NotifyPpi on
> EFI_PEI_PERMANENT_MEMORY_INSTALLED_PPI, its callback (SampleCallback)
> will be called if EFI_PEI_PERMANENT_MEMORY_INSTALLED_PPI is ALREADY
> installed at this time, or if EFI_PEI_PERMANENT_MEMORY_INSTALLED_PPI
> will be installed later, AFTER this notification registration for it
> by NotifyPpi?
Both.
> It's obvious, that the answer is the second variant - when the PPI in
> question will be installed AFTER the notification registration on it.
> As it is seen on the quoted example. But then, the first quote,
> NotifyPpi description, says absolutely opposite and very misleading
> thing.
No, it doesn't say the opposite.
> And yet, in light of the above, NotifyPpi description also lacks
> explanation how it would behave if a PEIM tries to register a
> notification for the PPI already installed. What should happen in this
> case? Should Peim's callback still be called (when if so)? Or should
> NotifyPpi return an error, or success without any action? I believe,
> it should register it and call only for the other later installed
> instances with the same GUID. Is it a right guess?
RegisterProtocolNotify() is tied to an event.
- Events are UEFI-only, in PEI there is no corresponding concept.
- The event that is passed to RegisterProtocolNotify() is signaled when
the protocol in question is installed or reinstalled.
- The event is not signaled right when RegisterProtocolNotify() returns.
- When the event is signaled (manually, or by the system in response to
protocol install / reinstall), the notify function associated with
the event is enqueued at the TPL that was specified at event creation
time.
- When the notification function executes, it calls LocateProtocol() in
a loop, to find all new (or reinstalled) instances of the protocol in
question. Each call of LocateProtocol() returns one as yet unseen
instance (if any), until the loop terminates upon seeing
EFI_NOT_FOUND.
(Similarly: LocateHandle() with SearchKey==ByRegisterNotify.)
The state object that tracks the already processed / seen
protocol instances (or handles) is pointed-to by the Registration
pointer (or the SearchKey pointer). It is an opaque system-internal
object that was allocated and returned by RegisterProtocolNotify().
This internal object carries state not just between adjacent
iterations of the loop in question, but also between separate calls
of the event notification function. This way the callback only has to
process as yet unseen (preexistent, freshly installed, or
reinstalled) protocol instances / handles.
- To see why a loop is necessary per callback invocation, consider the
TPL. An agent that installs multiple instances of the same protocol
(on separate handles of course) might do that running at a task prio
level that is higher than or equal to the TPL of the notification
function. (Protocol Handler Services are permitted to be called at
levels up to and including TPL_NOTIFY.) In this case the event is
signaled several times, but only the first signal makes a real
difference; the event enters the signaled state and its notification
function is enqueued. When the TPL is finally lowered sufficiently,
the notification function is called only once -- and then it has to
process all the new / reinstalled protocol instances (or handles).
- Because at the time of calling RegisterProtocolNotify() the event is
not automatically signaled, and protocol instances from earlier could
exist, the programmer is responsible for calling SignalEvent()
manually, right after RegisterProtocolNotify(). This will result in
one call to the event notification function (assuming an appropriate
TPL -- otherwise, it'll happen when the TPL is lowered), and then the
function will have to process *all* preexistent protocol interfaces
in said loop.
- When you close the event, the protocol notify that signals it goes
away automatically, along with the Registration tracker.
Now let's consider the spec quotes.
* NotifyPpi() is similar to RegisterProtocolNotify() in that the former
allows the programmer to register callbacks for PPI installations and
reinstallations.
This is why the quote on pages 115-116 is correct about
RegisterProtocolNotify() and NotifyPpi() being similar.
* NotifyPpi() is dissimilar to RegisterProtocolNotify() in two facets:
- there are no events in PEI -- from which the difference follows that
NotifyPpi() fires immediately for preexistent PPIs, and
- there is no Registration object for tracking processed instances of a
given PPI GUID. Instead, LocatePpi() takes a parameter called Instance
(of type UINTN):
PEI manages the interface set by maintaining a partial order on the
interfaces such that the Instance of the interface, among others,
can be traversed. [...] The Instance value designates when a PPI
was installed. For an implementation that must reference all
possible manifestations of a given GUID-named PPI, the code should
invoke LocatePpi() with a monotonically increasing Instance number
until EFI_NOT_FOUND is returned.
So you would maintain an UINTN variable (STATIC or dynamically
allocated) to carry this information from one invocation of the callback
to the next.
These two reasons are why the quote on page 26 is correct about
RegisterProtocolNotify() and NotifyPpi() being different.
> And also, regarding the fact there might be multiple instances of the
> particular PPI (with the same Guid). Did I understood properly, that
> with this model, since Peim registers for notification by the GUID,
> this means that on every instance installation and reinstallation of
> the PPI with the same GUID, Peim's callback will and should be called?
> Thank you for possible answer.
For preexistent instances of a PPI, NotifyPpi() will fire immediately,
once in total, for all preexistent instances cumulatively.
For instances of a PPI installed (or reinstalled) with the notification
already in place, it depends on EFI_PEI_PPI_DESCRIPTOR_NOTIFY_CALLBACK
vs. EFI_PEI_PPI_DESCRIPTOR_NOTIFY_DISPATCH. With the former, you get a
callback once per install / reinstall, on the stack of the InstallPpi()
call. With the latter, you can get a single callback for a batch of PPI
instances, when the PEIM that called InstallPpi() -- possibly several
times -- exits its entry point function and returns to the PEI core.
However, you can (and should) write the callback function such that this
distinction doesn't matter. Again, write a loop with LocatePpi() in the
loop body, and use a static (or dynamically allocated) UINTN Instance
counter, for carrying the "seen vs. unseen" boundary from one iteration
to the next, possibly across separate invocations of the function.
(Corrections are welcome, obviously!)
Thanks
Laszlo
^ permalink raw reply [flat|nested] 3+ messages in thread