qemu-devel
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

[Qemu-devel] implementing EFI_SMM_CONTROL2_PROTOCOL.Trigger() (was: [PAT


From: Laszlo Ersek
Subject: [Qemu-devel] implementing EFI_SMM_CONTROL2_PROTOCOL.Trigger() (was: [PATCH 6/6] [wip] tseg, part2, not (yet) tested)
Date: Tue, 21 Apr 2015 22:31:36 +0200
User-agent: Mozilla/5.0 (X11; Linux x86_64; rv:31.0) Gecko/20100101 Thunderbird/31.6.0

adding edk2-devel

On 04/21/15 17:21, Paolo Bonzini wrote:
> 
> 
> On 21/04/2015 17:05, Laszlo Ersek wrote:
>>
>> Yet another question -- as far as I understand, I should have enough
>> info (with my pending questions of course) for EFI_SMM_ACCESS2_PROTOCOL.
>> I've now reviewed EFI_SMM_CONTROL2_PROTOCOL too, and AFAICS the only
>> thing I need to know for it is "how to raise an SMI, synchronously".
>> What are the plans for that? An ioport write perhaps? (I skimmed the
>> ICH9 spec, but whatever I found seemed to be quite inappropriate.)
> 
> You can write to ioport 0xb2 in order to raise the SMI, or you can also
> write to the APIC ICR register with LOCAL_APIC_DELIVERY_MODE_SMI.  I am
> not sure if the latter is synchronous.
> 
> If you use the former, it probably should be protected by some kind of
> spinlock (I don't know the details of UEFI multi tasking) and you also
> have to set the APMC_EN and GBL_SMI_EN bits of the SMI_EN register.

This raises a number of questions. I'm hoping to get help for the below
from edk2-devel subscribers. :)

(1) About locking. "MdePkg/Library/UefiLib/UefiLib.c" in edk2 has a pair
of functions called EfiAcquireLock() and EfiReleaseLock() -- and some
variation too.

I'm not really sure why these functions (and the underlying data
structure, EFI_LOCK) exist.

------------------------------
typedef enum {
  EfiLockUninitialized = 0,
  EfiLockReleased      = 1,
  EfiLockAcquired      = 2
} EFI_LOCK_STATE;

typedef struct {
  EFI_TPL         Tpl;
  EFI_TPL         OwnerTpl;
  EFI_LOCK_STATE  Lock;
} EFI_LOCK;

VOID
EFIAPI
EfiAcquireLock (
  IN EFI_LOCK  *Lock
  )
{
  ASSERT (Lock != NULL);
  ASSERT (Lock->Lock == EfiLockReleased);

  Lock->OwnerTpl = gBS->RaiseTPL (Lock->Tpl);
  Lock->Lock     = EfiLockAcquired;
}

VOID
EFIAPI
EfiReleaseLock (
  IN EFI_LOCK  *Lock
  )
{
  EFI_TPL Tpl;

  ASSERT (Lock != NULL);
  ASSERT (Lock->Lock == EfiLockAcquired);

  Tpl = Lock->OwnerTpl;

  Lock->Lock = EfiLockReleased;

  gBS->RestoreTPL (Tpl);
}
------------------------------

I understand the stashing of the old TPL (task priority level) for
restoration. I understand that the lock object itself carries new (ie.
masked) TPL as well. The lock state is apparently only used for
enforcing symmetry between locking and unlocking.

But the prototypes of these functions are very misleading. They imply
that you can take separate locks, and that locking one will not prevent
locking another. This is false. There's only one task priority level,
and raising the TPL ensures that various registered callbacks
(TPL_CALLBACK) and asynch notifications (TPL_NOTIFY: in practice always
running in the context of the timer interrupt handler) are masked.

So, if you have two EFI_LOCK objects, Lock1 and Lock2, and Lock1.Tpl ==
TPL_CALLBACK, and Lock2.Tpl == TPL_NOTIFY, and you take Lock2 first,
then an attempt to lock the seemingly independent Lock1 (whose TPL is
TPL_CALLBACK, which is lower than the current TPL, TPL_NOTIFY) will lead
to indeterminate system state. See the description of the RaiseTPL()
boot service:

    If NewTpl is below the current TPL level, then the system behavior
    is indeterminate.

So, I really don't understand why these UefiLib functions exist at all;
to me they seem more confusing than useful.

(2) Again, about locking. Assuming someone explains the above functions
to me, or I just use naked gBS->RaiseTPL() inside
EFI_SMM_CONTROL2_PROTOCOL.Trigger(), I wonder if that's a good idea.

Namely, the hardware should assert the SMI while inside Trigger(),
edk2's SMI handler / dispatcher will run, and call into the appropriate
SMM DXE driver. That driver might do any kind of stuff, like update the
varstore in the pflash, which could take long.

I observe two things:
(a) "taking long" may not be allowed on whatever task priority level we
are at the moment. For example, if we raised the TPL to TPL_NOTIFY in
the "locking", then:

    Blocking is not allowed at this level. Code executes to completion
    and returns. If code requires more processing, it needs to signal
    an event to wait to obtain control again at whatever level it
    requires. This level is typically used to process low level IO to
    or from a device.

I guess it's akin to the Linux kernel's "hardirq" (top half) context. So
I'm not sure all SMM DXE drivers satisfy this, ie. if Trigger() can just
bump the TPL to TPL_NOTIFY indiscriminately.

(b) This is actually the opposite argument. Since SMM is security
sensitive, *and* UEFI is single-processor / single-threaded (well you
can use multiple processors, you just can't run any UEFI code on
anything but the BP), the timer interrupt should be blocked / masked
*anyway* while in SMM, no? Otherwise edk2 could start running a plain
timer event callback in the middle of an SMM handler. Which makes me
think that this locking / interrupt masking must already be in place,
and it's not the responsibility of the individual
EFI_SMM_CONTROL2_PROTOCOL.Trigger() implementation. (PI 1.3 vol4 doesn't
mention this responsibility in any case.)

I've learned about the "EDK II SMM Call Topology" document from a piwg
message:

http://sourceforge.net/projects/edk2/files/General%20Documentation/EDK%20II%20SMM%20call%20topology.pdf/download

It doesn't speak about masking the timer.

Does SMI mask other interrupts "architecturally" perhaps?

...

(3) Oh, this is sad. Well, I am sad. Turns out there's a third UEFI
protocol that OVMF needs to implement: EFI_SMM_CONFIGURATION_PROTOCOL.
Its only interesting member is RegisterSmmEntry(), and that function is
supposed to bind the central entry point (which is already available in
the edk2 tree) to the processor-level SMI handler.

(I'm kind of confused, because last time I experimented with faking
SMRAM / SMM in OVMF, my fake Trigger() function just returned success,
and there was no EFI_SMM_CONFIGURATION_PROTOCOL at all, and things
seemed to work. In retrospect I can't imagine how control was
transferred at all, without actual SMM / SMI support in both QEMU and
OVMF. Hm... looking at occurrences of "SmmEntryPointRegistered", this
may have been intentional in edk2.)

EFI_SMM_CONFIGURATION_PROTOCOL discussed in the "EDK II SMM Call
Topology" document, on the "SmmDriverDispatcher" and especially the
"SMBASE Relocation" pages. It takes a separate CPU driver, and
(obviously) assembly code.

The "SMBASE Relocation" page references "IA32FamilyCpuPkg", which is not
open source. Nothing in edk2 produces gEfiSmmConfigurationProtocol, and
no source file contains the RSM instruction.

The Vlv2TbltDevicePkg package (ValleyView2 Tablet?) makes several
references to IA32FamilyCpuPkg, but those are only references.

I guess IA32FamilyCpuPkg is exactly the kind of chipset code that Intel
has not opened up.

Our "SMM in OVMF" effort just got set back by months. :(

Laszlo



reply via email to

[Prev in Thread] Current Thread [Next in Thread]