Re: SmmSwDispatch2Protocol - Registering an SMI - Can't find protocol with OVMF


Laszlo Ersek
 

Hi,

(cross-posting to edk2-devel, comments below)

On 09/29/20 14:32, Simon McMillan wrote:
Trying to register a SMI in my custom SMM module I'm running into
issues.

Before registering the SMI, the code looks for the specific protocol
this way:
Status = gSmst->SmmLocateProtocol(
&gEfiSmmSwDispatch2ProtocolGuid,
NULL,
(VOID**)&SwDispatch
);
DEBUG ( (EFI_D_INFO, "SmmSwDispatch2Protocol Status: 0x%x\n", Status) );
ASSERT_EFI_ERROR(Status);

Unfortunately, while starting OVMF, boot hangs and the following
message appears:
SmmSwDispatch2Protocol Status: 0xE
ASSERT_EFI_ERROR (Status = Not Found)

Adding gEfiSmmSwDispatch2ProtocolGuid to the Depex section of my INF
file doesn't solve the issue, in fact it prevents the custom code from
being loaded at all.

For info in OVMF, I simply added a reference to the inf file in the
DSC file, so that my custom code is built along OVMF:

custom_code/custom_code.inf

OVMF is built with the following options:
build -DSMM_REQUIRE

Then the SMM module is converted to the right format and injected
manually with UEFITool.

Would anyone be of help with this situation? Or could give me an
advice of what would be wrong or missing?
OVMF does not contain an implementation of the
EFI_MM_SW_DISPATCH_PROTOCOL.

While OVMF does handle a particular software MMI, that handling occurs
via a root MMI handler (in "OvmfPkg/CpuHotplugSmm") that watches out for
a particular command port value.

When I reviewed using a root MMI handler vs. EFI_MM_SW_DISPATCH_PROTOCOL
in last October, the latter seemed like a needless complication to me.
Mike, Jiewen and others provided many helpful explanations, and I
decided that EFI_MM_SW_DISPATCH_PROTOCOL was unjustified for OVMF.
Below, I'll try to rehash and extend parts of those discussions.

Considering EFI_MM_SW_DISPATCH_PROTOCOL, OVMF would have had to produce
that protocol from a chipset (aka "silicon") driver. And for actually
handling the CPU hotplug MMI, OVMF would have to consume
EFI_MM_SW_DISPATCH_PROTOCOL in a different -- not silicon, but
"platform" -- driver, and register an MMI handler through
EFI_MM_SW_DISPATCH_PROTOCOL.

However, for OVMF and QEMU, there is no practical distinction between
"chipset" (= "silicon") and "platform". The EFI_MM_SW_DISPATCH_PROTOCOL
implementation would be a thick wrapper on top of IO port 0xB2,
providing a more or less useless software abstraction.

- The only small advantage would be that EFI_MM_SW_DISPATCH_PROTOCOL
would permit the caller to ask for a new (previously not agreed-upon)
"SwMmiInputValue" (via ((UINTN)-1)), eliminating the need for
coordination wrt. the 0xB2 IO port values.

This was not a compelling feature, as upstream OVMF was about to get
its first own MMI handler. Additionally, the command port value was
going to have to be agreed upon between the OS (QEMU would generate
the ACPI payload for the OS, for triggering the MMI) and the firmware.
For such "public" MMIs, the generation of "SwMmiInputValue" by
EFI_MM_SW_DISPATCH_PROTOCOL.Register() is not useful anyway.

- Furthermore, there was a *big* disadvantage -- the registered
"DispatchFunction" would have to be called back with
EFI_MM_SW_CONTEXT. And filling in EFI_MM_SW_CONTEXT.SwMmiCpuIndex --
"the 0-based index of the CPU which generated the software MMI" -- is
both quite useless [*], and very challenging on OVMF / QEMU [**].

[*] It might tell us what VCPU was executing the ACPI GPE handler in
the OS's AML interpreter (ultimately raising the VCPU hotplug
MMI), but what is that knowledge good for?

[**] To refer forward to the QncSmmDispatcher module as an example: in
that driver, "SwSmiCpuIndex" is laboriously determined in
SwGetBuffer(), by checking which CPU's save state map indicates
that *that* CPU was interrupted in the particular IO port write
instruction that triggered the SMI.

But on QEMU/KVM, this kind of information is not even available.
The SmmCpuFeaturesReadSaveStateRegister() function in
"OvmfPkg/Library/SmmCpuFeaturesLib/SmmCpuFeaturesLib.c" returns
constant EFI_NOT_FOUND, for EFI_SMM_SAVE_STATE_REGISTER_IO.
QEMU/KVM does not describe the interrupted OUT instruction in the
save state map, and so the method seen in Quark's SwGetBuffer()
couldn't work.

So, quite a few complications, for little use.

In the edk2-platforms tree, the directory

Silicon/Intel/QuarkSocPkg/QuarkNorthCluster/Smm/DxeSmm/QncSmmDispatcher

contains a driver that produces EFI_MM_SW_DISPATCH_PROTOCOL (still using
the older / original name EFI_SMM_SW_DISPATCH2_PROTOCOL). When I
reviewed this driver, I formed the opinion that it wasn't really
"silicon-specific" -- I felt that it should be turned into a "universal"
IA32/X64 driver. The responsibilities of the driver (in relation to
EFI_MM_SW_DISPATCH_PROTOCOL) seem to be:

(1) It has to install a root MMI handler with
gMmst->MmiHandlerRegister().

(2) It must fetch the Command and Data port values from the *same*
chipset registers that the EFI_MM_CONTROL_PROTOCOL.Trigger()
function writes. The location and nature of this register block is
about the only "silicon-specific" bit, IMO.

(3) In the root MMI handler, the driver must look up the fetched Command
value against the dictionary that is managed by Register() /
UnRegister().

(4) Upon a match, the driver must call the callback associated with
Command, and also pass Data to it (coming from
EFI_MM_CONTROL_PROTOCOL.Trigger() via IO port 0xB3).

(5) EFI_MM_SW_DISPATCH_PROTOCOL.Register() must never hand out such a
Command value (a.k.a. "SwMmiInputValue"), for ((UINTN)-1), that
equals the value that EFI_MM_CONTROL_PROTOCOL.Trigger() stores for
(CommandPort==NULL).

Otherwise, a Communicate request could be mistaken for *that* SW
MMI, when SmmEntryPoint() [MdeModulePkg/Core/PiSmmCore/PiSmmCore.c]
invokes the root MMI handler registered in (1), after processing the
Communicate request.

This (ie., the agreement between EFI_MM_CONTROL_PROTOCOL and
EFI_MM_SW_DISPATCH_PROTOCOL) is maybe the 2nd silicon-specific point
that the EFI_MM_SW_DISPATCH_PROTOCOL driver has to take into
account.

If such a generic driver existed, then we could perhaps include it
verbatim in the OVMF platform DSC / FDF files. If someone really needed
EFI_MM_SW_DISPATCH_PROTOCOL specifically.

Until then, I suggest registering another root MMI handler with
gMmst->MmiHandlerRegister(), like "OvmfPkg/CpuHotplugSmm" does. In the
handler, filter for a command port value that is strictly greater than
ICH9_APM_CNT_CPU_HOTPLUG (4).

See commit 17efae27acaf ("OvmfPkg/CpuHotplugSmm: introduce skeleton for
CPU Hotplug SMM driver", 2020-03-04).

Manual arbitration between the various values for command port 0xB2 is
necessary, accordingly. In practice this should not be a problem, as
OVMF is open source; just be prepared that new Command values upstreamed
to OVMF in the future might require you to pick a different Command
value for your out-of-tree driver.

Picking a value greater than ICH9_APM_CNT_CPU_HOTPLUG (4) will also
ensure that your root MMI handler filter out the "default" command port
value (namely zero) that OvmfPkg/SmmControl2Dxe uses in the Trigger()
implementation, when SmmCommunicationCommunicate()
[MdeModulePkg/Core/PiSmmCore/PiSmmIpl.c] invokes Trigger() with
(Command==NULL), for submitting a Communicate request.

Thanks
Laszlo

Join discuss@edk2.groups.io to automatically receive all group messages.