diff mbox series

[RFC,v2,20/44] i386/tdx: Parse tdx metadata and store the result into TdxGuestState

Message ID acaf651389c3f407a9d6d0a2e943daf0a85bb5fc.1625704981.git.isaku.yamahata@intel.com (mailing list archive)
State New, archived
Headers show
Series TDX support | expand

Commit Message

Isaku Yamahata July 8, 2021, 12:54 a.m. UTC
From: Isaku Yamahata <isaku.yamahata@intel.com>

Add support for loading TDX's Trusted Domain Virtual Firmware (TDVF) via
the generic loader.  Prioritize the TDVF above plain hex to avoid false
positives with hex (TDVF has explicit metadata to confirm it's a TDVF).

Enumerate TempMem as added, private memory, i.e. E820_RESERVED,
otherwise TDVF will interpret the whole shebang as MMIO and complain
that the aperture overlaps other MMIO regions.

Signed-off-by: Isaku Yamahata <isaku.yamahata@intel.com>
Co-developed-by: Sean Christopherson <sean.j.christopherson@intel.com>
Signed-off-by: Sean Christopherson <sean.j.christopherson@intel.com>
Reported-by: Min M. Xu <min.m.xu@intel.com>
---
 hw/core/generic-loader.c |   5 +
 hw/core/meson.build      |   3 +
 hw/core/tdvf-stub.c      |   6 +
 hw/i386/meson.build      |   1 +
 hw/i386/tdvf.c           | 312 +++++++++++++++++++++++++++++++++++++++
 include/sysemu/tdvf.h    |   6 +
 target/i386/kvm/tdx.h    |  26 ++++
 7 files changed, 359 insertions(+)
 create mode 100644 hw/core/tdvf-stub.c
 create mode 100644 hw/i386/tdvf.c
 create mode 100644 include/sysemu/tdvf.h

Comments

Gerd Hoffmann Aug. 26, 2021, 11:18 a.m. UTC | #1
Hi,

> +        /*
> +         * If TDVF temp memory describe in TDVF metadata lays in RAM, reserve
> +         * the region property.
> +         */
> +        if (entry->address >= 4 * GiB + x86ms->above_4g_mem_size ||
> +            entry->address + entry->size >= 4 * GiB + x86ms->above_4g_mem_size) {
> +            error_report("TDVF type %u address 0x%" PRIx64 " size 0x%" PRIx64
> +                         " above high memory",
> +                         entry->type, entry->address, entry->size);
> +            exit(1);
> +        }

I think you can simply use dma_memory_map() API, then just work with
guest physical addresses and drop the messy and error-prone memory
region offset calculations.

> +    entry->mem_ptr = memory_region_get_ram_ptr(entry->mr);
> +    if (entry->data_len) {
> +        /*
> +         * The memory_region api doesn't allow partial file mapping, create
> +         * ram and copy the contents
> +         */
> +        if (lseek(fd, entry->data_offset, SEEK_SET) != entry->data_offset) {
> +            error_report("can't seek to 0x%x %s", entry->data_offset, filename);
> +            exit(1);
> +        }
> +        if (read(fd, entry->mem_ptr, entry->data_len) != entry->data_len) {
> +            error_report("can't read 0x%x %s", entry->data_len, filename);
> +            exit(1);
> +        }
> +    }

Wouldn't a simple rom_add_blob work here?

> +int load_tdvf(const char *filename)
> +{

> +    for_each_fw_entry(fw, entry) {
> +        if (entry->address < x86ms->below_4g_mem_size ||
> +            entry->address > 4 * GiB) {
> +            tdvf_init_ram_memory(ms, entry);
> +        } else {
> +            tdvf_init_bios_memory(fd, filename, entry);
> +        }
> +    }

Why there are two different ways to load the firmware?

Also: why is all this firmware volume parsing needed?  The normal ovmf
firmware can simply be mapped just below 4G, why can't tdvf work the
same way?

thanks,
  Gerd
Xiaoyao Li Jan. 4, 2022, 1:08 p.m. UTC | #2
On 8/26/2021 7:18 PM, Gerd Hoffmann wrote:
>> +int load_tdvf(const char *filename)
>> +{
> 
>> +    for_each_fw_entry(fw, entry) {
>> +        if (entry->address < x86ms->below_4g_mem_size ||
>> +            entry->address > 4 * GiB) {
>> +            tdvf_init_ram_memory(ms, entry);
>> +        } else {
>> +            tdvf_init_bios_memory(fd, filename, entry);
>> +        }
>> +    }
> 
> Why there are two different ways to load the firmware?

because there are two different parts in TDVF:
  a) one is firmware volume (BFV and CFV, i.e., OVMF_CODE.fd and 
OVMF_VAR.fd). Those are ROMs;

  b) the other is some RAM regions, e.g., temp memory for BFV early 
running and TD HOB to pass info to TDVF; Those are RAMs which is already 
added to TDX VM;

> Also: why is all this firmware volume parsing needed?  The normal ovmf
> firmware can simply be mapped just below 4G, why can't tdvf work the
> same way?

Ideally, the firmware (part a above) can be mapped just below 4G like 
what we do for OVMF.

But it needs additional when map part a) to parse the metadata and get 
location of part b) and initialize the RAM of part b). Yes, the 
additional work can be added in existing OVMF laoding flow as pflash.


+ Laszlo,

Regarding laoding TDVF as pflash, I have some questions:

- pflash requires KVM to support readonly mmeory. However, for TDX, it 
doesn't support readonly memory. Is it a must? or we can make an 
exception for TDX?

- I saw from 
https://lists.gnu.org/archive/html/qemu-discuss/2018-04/msg00045.html, 
you said when load OVMF as pflash, it's MMIO. But for TDVF, it's treated 
as private memory. I'm not sure whether it will cause some potential 
problem if loading TDVF with pflash.

Anyway I tried changing the existing pflash approach to load TDVF. It 
can boot a TDX VM and no issue.

> thanks,
>    Gerd
>
Laszlo Ersek Jan. 6, 2022, 4:06 p.m. UTC | #3
On 01/04/22 14:08, Xiaoyao Li wrote:

> + Laszlo,
> 
> Regarding laoding TDVF as pflash, I have some questions:
> 
> - pflash requires KVM to support readonly mmeory. However, for TDX, it
> doesn't support readonly memory. Is it a must? or we can make an
> exception for TDX?
> 
> - I saw from
> https://lists.gnu.org/archive/html/qemu-discuss/2018-04/msg00045.html,
> you said when load OVMF as pflash, it's MMIO. But for TDVF, it's treated
> as private memory. I'm not sure whether it will cause some potential
> problem if loading TDVF with pflash.
> 
> Anyway I tried changing the existing pflash approach to load TDVF. It
> can boot a TDX VM and no issue.

I have no comments on whether TDX should or should not use pflash.

If you go without pflash, then you likely will not have a
standards-conformant UEFI variable store. (Unless you reimplement the
variable arch protocols in edk2 on top of something else than the Fault
Tolerant Write and Firmware Volume Block protocols.) Whether a
conformant UEFI varstore matters to you (or to TDX in general) is
something I can't comment on.

(I've generally stopped commenting on confidential computing topics, but
this message allows for comments on just pflash, and how it impacts OVMF.)

Regarding pflash itself, the read-only KVM memslot is required for it.
Otherwise pflash cannot work as a "ROMD device" (= you can't flip it
back and forth between ROM mode and programming (MMIO) mode).

Thanks
Laszlo
Xiaoyao Li Jan. 7, 2022, 7:05 a.m. UTC | #4
On 1/7/2022 12:06 AM, Laszlo Ersek wrote:
> On 01/04/22 14:08, Xiaoyao Li wrote:
> 
>> + Laszlo,
>>
>> Regarding laoding TDVF as pflash, I have some questions:
>>
>> - pflash requires KVM to support readonly mmeory. However, for TDX, it
>> doesn't support readonly memory. Is it a must? or we can make an
>> exception for TDX?
>>
>> - I saw from
>> https://lists.gnu.org/archive/html/qemu-discuss/2018-04/msg00045.html,
>> you said when load OVMF as pflash, it's MMIO. But for TDVF, it's treated
>> as private memory. I'm not sure whether it will cause some potential
>> problem if loading TDVF with pflash.
>>
>> Anyway I tried changing the existing pflash approach to load TDVF. It
>> can boot a TDX VM and no issue.
> 
> I have no comments on whether TDX should or should not use pflash.
> 
> If you go without pflash, then you likely will not have a
> standards-conformant UEFI variable store. (Unless you reimplement the
> variable arch protocols in edk2 on top of something else than the Fault
> Tolerant Write and Firmware Volume Block protocols.) Whether a
> conformant UEFI varstore matters to you (or to TDX in general) is
> something I can't comment on.

Thanks for your reply! Laszlo

regarding "standards-conformant UEFI variable store", I guess you mean 
the change to UEFI non-volatile variables needs to be synced back to the 
OVMF_VARS.fd file. right?

If so, I need to sync with internal folks who are upstreaming TDVF 
support into OVMF.

> (I've generally stopped commenting on confidential computing topics, but
> this message allows for comments on just pflash, and how it impacts OVMF.)
> 
> Regarding pflash itself, the read-only KVM memslot is required for it.
> Otherwise pflash cannot work as a "ROMD device" (= you can't flip it
> back and forth between ROM mode and programming (MMIO) mode).

We don't need Read-only mode for TDVF so far. If for this purpose, is it 
acceptable that allowing a pflash without KVM readonly memslot support 
if read-only is not required for the specific pflash device?

We are trying to follow the existing usage of OVMF for TDX, since TDVF 
support will be landed in OVMF instead of a new separate binary.

> Thanks
> Laszlo
>
Gerd Hoffmann Jan. 10, 2022, 11:01 a.m. UTC | #5
> > If you go without pflash, then you likely will not have a
> > standards-conformant UEFI variable store. (Unless you reimplement the
> > variable arch protocols in edk2 on top of something else than the Fault
> > Tolerant Write and Firmware Volume Block protocols.) Whether a
> > conformant UEFI varstore matters to you (or to TDX in general) is
> > something I can't comment on.
> 
> Thanks for your reply! Laszlo
> 
> regarding "standards-conformant UEFI variable store", I guess you mean the
> change to UEFI non-volatile variables needs to be synced back to the
> OVMF_VARS.fd file. right?

Yes.  UEFI variables are expected to be persistent, and syncing to
OVMF_VARS.fd handles that.

Not fully sure whenever that expectation holds up in the CC world.  At
least the AmdSev variant has just OVMF.fd, i.e. no CODE/VARS split.

> > Regarding pflash itself, the read-only KVM memslot is required for it.
> > Otherwise pflash cannot work as a "ROMD device" (= you can't flip it
> > back and forth between ROM mode and programming (MMIO) mode).
> 
> We don't need Read-only mode for TDVF so far. If for this purpose, is it
> acceptable that allowing a pflash without KVM readonly memslot support if
> read-only is not required for the specific pflash device?

In case you don't want/need persistent VARS (which strictly speaking is
a UEFI spec violation) you should be able to go for a simple "-bios
OVMF.fd".

take care,
  Gerd
Xiaoyao Li Jan. 10, 2022, 12:09 p.m. UTC | #6
On 1/10/2022 7:01 PM, Gerd Hoffmann wrote:
>>> If you go without pflash, then you likely will not have a
>>> standards-conformant UEFI variable store. (Unless you reimplement the
>>> variable arch protocols in edk2 on top of something else than the Fault
>>> Tolerant Write and Firmware Volume Block protocols.) Whether a
>>> conformant UEFI varstore matters to you (or to TDX in general) is
>>> something I can't comment on.
>>
>> Thanks for your reply! Laszlo
>>
>> regarding "standards-conformant UEFI variable store", I guess you mean the
>> change to UEFI non-volatile variables needs to be synced back to the
>> OVMF_VARS.fd file. right?
> 
> Yes.  UEFI variables are expected to be persistent, and syncing to
> OVMF_VARS.fd handles that.

Further question.

Is it achieved via read-only memslot that when UEFI variable gets 
changed, it exits to QEMU with KVM_EXIT_MMIO due to read-only memslot so 
QEMU can sync the change to OVMF_VAR.fd?

> Not fully sure whenever that expectation holds up in the CC world.  At
> least the AmdSev variant has just OVMF.fd, i.e. no CODE/VARS split.
> 
>>> Regarding pflash itself, the read-only KVM memslot is required for it.
>>> Otherwise pflash cannot work as a "ROMD device" (= you can't flip it
>>> back and forth between ROM mode and programming (MMIO) mode).
>>
>> We don't need Read-only mode for TDVF so far. If for this purpose, is it
>> acceptable that allowing a pflash without KVM readonly memslot support if
>> read-only is not required for the specific pflash device?
> 
> In case you don't want/need persistent VARS (which strictly speaking is
> a UEFI spec violation) you should be able to go for a simple "-bios
> OVMF.fd".
> 
> take care,
>    Gerd
>
Laszlo Ersek Jan. 11, 2022, 8:19 a.m. UTC | #7
On 01/10/22 13:09, Xiaoyao Li wrote:
> On 1/10/2022 7:01 PM, Gerd Hoffmann wrote:
>>>> If you go without pflash, then you likely will not have a
>>>> standards-conformant UEFI variable store. (Unless you reimplement
>>>> the variable arch protocols in edk2 on top of something else than
>>>> the Fault Tolerant Write and Firmware Volume Block protocols.)
>>>> Whether a conformant UEFI varstore matters to you (or to TDX in
>>>> general) is something I can't comment on.
>>>
>>> Thanks for your reply! Laszlo
>>>
>>> regarding "standards-conformant UEFI variable store", I guess you
>>> mean the
>>> change to UEFI non-volatile variables needs to be synced back to the
>>> OVMF_VARS.fd file. right?
>>
>> Yes.  UEFI variables are expected to be persistent, and syncing to
>> OVMF_VARS.fd handles that.
>
> Further question.
>
> Is it achieved via read-only memslot that when UEFI variable gets
> changed, it exits to QEMU with KVM_EXIT_MMIO due to read-only memslot
> so QEMU can sync the change to OVMF_VAR.fd?

Yes.

When the flash device is in "romd_mode", that's when a readonly KVM
memslot is used. In this case, the guest can read and execute from the
memory region in question, only writes trap to QEMU. Such a write
(WRITE_BYTE_CMD) is what the guest's flash driver uses to flip the flash
device out of "romd_mode".

When the flash device is not in "romd_mode", then no KVM memslot is used
at all, and both reads and writes trap to QEMU. Once the flash
programming is done, the guest's flash driver issues a particular write
command (READ_ARRAY_CMD) that flips the device back to "romd_mode" (and
then the readonly KVM memslot is re-established).

Here's a rough call tree (for the non-SMM case, updating a
non-authenticated non-volatile variable):

  VariableServiceSetVariable()                             [MdeModulePkg/Universal/Variable/RuntimeDxe/Variable.c]
    UpdateVariable()                                       [MdeModulePkg/Universal/Variable/RuntimeDxe/Variable.c]
      UpdateVariableStore()                                [MdeModulePkg/Universal/Variable/RuntimeDxe/Variable.c]
        FvbProtocolWrite()                                 [OvmfPkg/QemuFlashFvbServicesRuntimeDxe/FwBlockService.c]
          QemuFlashWrite()                                 [OvmfPkg/QemuFlashFvbServicesRuntimeDxe/QemuFlash.c]

            QemuFlashPtrWrite (WRITE_BYTE_CMD /* 0x10 */)
               QEMU:
                pflash_write()                             [hw/block/pflash_cfi01.c]
                  (wcycle == 0)
                  memory_region_rom_device_set_romd(false) [softmmu/memory.c]
                    ...
                      kvm_region_del()                     [accel/kvm/kvm-all.c]
                        kvm_set_phys_mem(false)            [accel/kvm/kvm-all.c]
                          /* unregister the slot */

                  /* Single Byte Program */
                  wcycle++

            QemuFlashPtrWrite (Buffer[Loop])
              QEMU:
                pflash_write()                             [hw/block/pflash_cfi01.c]
                  (wcycle == 1)
                  /* Single Byte Program */
                  pflash_data_write()                      [hw/block/pflash_cfi01.c]
                  pflash_update()                          [hw/block/pflash_cfi01.c]
                    blk_pwrite()                           [block/block-backend.c]
                  wcycle = 0

            QemuFlashPtrWrite (READ_ARRAY_CMD /* 0xff */)
              QEMU:
                pflash_write()                             [hw/block/pflash_cfi01.c]
                  (wcycle == 0)
                  memory_region_rom_device_set_romd(false) [softmmu/memory.c]
                    /* no actual change */
                  /* Read Array */
                  memory_region_rom_device_set_romd(true)  [softmmu/memory.c]
                    kvm_region_add()                       [accel/kvm/kvm-all.c]
                      kvm_set_phys_mem(true)               [accel/kvm/kvm-all.c]
                        /* register the new slot */
                        kvm_mem_flags()                    [accel/kvm/kvm-all.c]
                          ... memory_region_is_romd() ...  [include/exec/memory.h]
                          flags |= KVM_MEM_READONLY

Thanks
Laszlo
Laszlo Ersek Jan. 11, 2022, 8:48 a.m. UTC | #8
On 01/11/22 09:19, Laszlo Ersek wrote:

> Here's a rough call tree (for the non-SMM case, updating a
> non-authenticated non-volatile variable):
>
>   VariableServiceSetVariable()                             [MdeModulePkg/Universal/Variable/RuntimeDxe/Variable.c]
>     UpdateVariable()                                       [MdeModulePkg/Universal/Variable/RuntimeDxe/Variable.c]
>       UpdateVariableStore()                                [MdeModulePkg/Universal/Variable/RuntimeDxe/Variable.c]
>         FvbProtocolWrite()                                 [OvmfPkg/QemuFlashFvbServicesRuntimeDxe/FwBlockService.c]
>           QemuFlashWrite()                                 [OvmfPkg/QemuFlashFvbServicesRuntimeDxe/QemuFlash.c]
>
>             QemuFlashPtrWrite (WRITE_BYTE_CMD /* 0x10 */)
>                QEMU:
>                 pflash_write()                             [hw/block/pflash_cfi01.c]
>                   (wcycle == 0)
>                   memory_region_rom_device_set_romd(false) [softmmu/memory.c]
>                     ...
>                       kvm_region_del()                     [accel/kvm/kvm-all.c]
>                         kvm_set_phys_mem(false)            [accel/kvm/kvm-all.c]
>                           /* unregister the slot */
>
>                   /* Single Byte Program */
>                   wcycle++
>
>             QemuFlashPtrWrite (Buffer[Loop])
>               QEMU:
>                 pflash_write()                             [hw/block/pflash_cfi01.c]
>                   (wcycle == 1)
>                   /* Single Byte Program */
>                   pflash_data_write()                      [hw/block/pflash_cfi01.c]
>                   pflash_update()                          [hw/block/pflash_cfi01.c]
>                     blk_pwrite()                           [block/block-backend.c]
>                   wcycle = 0
>
>             QemuFlashPtrWrite (READ_ARRAY_CMD /* 0xff */)
>               QEMU:
>                 pflash_write()                             [hw/block/pflash_cfi01.c]
>                   (wcycle == 0)
>                   memory_region_rom_device_set_romd(false) [softmmu/memory.c]
>                     /* no actual change */
>                   /* Read Array */
>                   memory_region_rom_device_set_romd(true)  [softmmu/memory.c]
>                     kvm_region_add()                       [accel/kvm/kvm-all.c]
>                       kvm_set_phys_mem(true)               [accel/kvm/kvm-all.c]
>                         /* register the new slot */
>                         kvm_mem_flags()                    [accel/kvm/kvm-all.c]
>                           ... memory_region_is_romd() ...  [include/exec/memory.h]
>                           flags |= KVM_MEM_READONLY

In that call tree, I ignored Reclaim()
[MdeModulePkg/Universal/Variable/RuntimeDxe/Variable.c]; Reclaim() is
called from more places than just from UpdateVariable().

In Reclaim(), we (roughly) have

  Reclaim()              [MdeModulePkg/Universal/Variable/RuntimeDxe/Variable.c]
    FtwVariableSpace()   [MdeModulePkg/Universal/Variable/RuntimeDxe/Reclaim.c]
      FtwWrite()         [MdeModulePkg/Universal/FaultTolerantWriteDxe/FaultTolerantWrite.c]
        QemuFlashWrite() [OvmfPkg/QemuFlashFvbServicesRuntimeDxe/QemuFlash.c]

For a bit more info on the internals of FtwWrite(), see the attached
message (I'd provide a URL, but Intel had killed the edk2-devel archives
on lists.01.org, and the other archives don't go back to 2014...)

Thanks
Laszlo
On 04/18/14 21:32, Kirkendall, Garrett wrote:
> Is there any good documentation for how the Fault Tolerant Write is
> supposed to work?
> 
> I understand that NV storage, FTW working space and FTW spare space are
> supposed to be in the same Firmware Volume.
> 
> I’m having trouble deciphering how big the FTW working and spare areas
> should be in relation to the NV storage space.
> 
> Also, in a bunch of places, it looks like the code was written such that
> the working space must fit within one block size.  What happens if need
> space spanning multiple blocks?  Below are parts from two functions that
> end in an ASSERT because only one block gets read and returns an error
> when the requested FVB->Read input size is larger than one block of data.

If it's any help, here's a diagram I derived last December, while I was
hunting down <https://github.com/tianocore/edk2/commit/06f1982a>:

On 12/17/13 07:16, Laszlo Ersek wrote:
> During reclaim, the following data movements take place (I'm skipping
> the erasures and the in-memory buffer manipulations):
>
>       +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+   L: event log
> LIVE  |    varstore               |L|W|   W: working block
>       +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
>
>       +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
> SPARE |                               |
>       +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
>
> (1) copy LIVE to MyBuffer
> (2) copy SPARE to SpareBuffer
> (3) copy MyBuffer to SPARE
> (4) copy SPARE to Buffer
> (5) copy Buffer to LIVE
> (6) copy SpareBuffer to SPARE

(MyBuffer, SpareBuffer, and Buffer are temporary memory buffers.)

In OVMF, the block size is 4K. The varstore is 14 blocks (56K), plus we
got one block (4K) for the event log and one block (4K) for the working
block. In total, 64K in the live half. The spare half is the same size,
giving 128K total for the firmware volume.

I'm also attaching the debug patch I wrote at that time for the FTW and
auth variable services, plus its output (which I annotated during
analysis) that helped me understand what was happening. Maybe you can
reuse something from them.

Laszlo
Reclaim: enter, VariableBase=0xFFE00048, IsVolatile=0, NewVariableSize=0x0, ReclaimPubKeyStore=0
  FtwVariableSpace:/home/lacos/src/upstream/edk2-git-svn/SecurityPkg/VariableAuthenticated/RuntimeDxe/Reclaim.c: enter
    FvbProtocolGetAttributes: enter
      FvbGetVolumeAttributes: enter, Instance=0x0, Global=9F7DAF18, Virtual=0
        GetFvbInstance: enter, Instance=0x0, Global=9F7DAF18, Virtual=0
        GetFvbInstance: exit @ 229, Status=Success
      FvbGetVolumeAttributes: exit @ 320, Status=Success, Attributes=0x4FEFF
    FvbProtocolGetAttributes: exit @ 704, Status=Success

    FvbProtocolGetPhysicalAddress: enter
      FvbGetPhysicalAddress: enter, Instance=0x0, Global=9F7DAF18, Virtual=0
        GetFvbInstance: enter, Instance=0x0, Global=9F7DAF18, Virtual=0
        GetFvbInstance: exit @ 229, Status=Success
      FvbGetPhysicalAddress: exit @ 275, Status=Success, Address=0xFFE00000
    FvbProtocolGetPhysicalAddress: exit @ 620, Status=Success

    FvbProtocolGetAttributes: enter
      FvbGetVolumeAttributes: enter, Instance=0x0, Global=9F7DAF18, Virtual=0
        GetFvbInstance: enter, Instance=0x0, Global=9F7DAF18, Virtual=0
        GetFvbInstance: exit @ 229, Status=Success
      FvbGetVolumeAttributes: exit @ 320, Status=Success, Attributes=0x4FEFF
    FvbProtocolGetAttributes: exit @ 704, Status=Success

    FvbProtocolGetPhysicalAddress: enter
      FvbGetPhysicalAddress: enter, Instance=0x0, Global=9F7DAF18, Virtual=0
        GetFvbInstance: enter, Instance=0x0, Global=9F7DAF18, Virtual=0
        GetFvbInstance: exit @ 229, Status=Success
      FvbGetPhysicalAddress: exit @ 275, Status=Success, Address=0xFFE00000
    FvbProtocolGetPhysicalAddress: exit @ 620, Status=Success

    FvbProtocolGetPhysicalAddress: enter
      FvbGetPhysicalAddress: enter, Instance=0x0, Global=9F7DAF18, Virtual=0
        GetFvbInstance: enter, Instance=0x0, Global=9F7DAF18, Virtual=0
        GetFvbInstance: exit @ 229, Status=Success
      FvbGetPhysicalAddress: exit @ 275, Status=Success, Address=0xFFE00000
    FvbProtocolGetPhysicalAddress: exit @ 620, Status=Success

    FtwWrite: enter, Lba=0x0 Offset=0x48 Length=0xDFB8 PrivateData=0 FvBlockHandle=9F58E798 Buffer=9F7BE060
      WorkSpaceRefresh: enter
        FvbProtocolRead: enter, Lba=0xF Offset=0x0 NumBytes=0x1000 Buffer=9EBEF0E0
          QemuFlashRead: enter, Lba=0xF Offset=0x0 NumBytes=0x1000 Buffer=9EBEF0E0
            QemuFlashPtr: enter, Lba=0xF Offset=0x0
            QemuFlashPtr: exit, Ret=FFE0F000
          QemuFlashRead: exit 2
        FvbProtocolRead: exit @ 944, Status=Success, NumBytes=0x1000
        FtwGetLastWriteHeader: enter, FtwWorkSpaceSize=0x1000
        FtwGetLastWriteHeader: exit @ 881, Status=Success
        Ftw: Remaining work space size - 590
        FtwGetLastWriteRecord: enter
        FtwGetLastWriteRecord: exit @ 924, Status=Success
      WorkSpaceRefresh: exit 6

      IsErasedFlashBuffer: enter, Buffer=9EBEFB50 BufferSize=0x28
      IsErasedFlashBuffer: exit, IsEmpty=1

      FtwAllocate: enter, CallerId=FE5CEA76-4F72-49E8-986F-2CD899DFFE5D, PrivateDataSize=0x0, NumberOfWrites=0x1
        WorkSpaceRefresh: enter
          FvbProtocolRead: enter, Lba=0xF Offset=0x0 NumBytes=0x1000 Buffer=9EBEF0E0
            QemuFlashRead: enter, Lba=0xF Offset=0x0 NumBytes=0x1000 Buffer=9EBEF0E0
              QemuFlashPtr: enter, Lba=0xF Offset=0x0
              QemuFlashPtr: exit, Ret=FFE0F000
            QemuFlashRead: exit 2
          FvbProtocolRead: exit @ 944, Status=Success, NumBytes=0x1000
          FtwGetLastWriteHeader: enter, FtwWorkSpaceSize=0x1000
          FtwGetLastWriteHeader: exit @ 881, Status=Success
          Ftw: Remaining work space size - 590
          FtwGetLastWriteRecord: enter
          FtwGetLastWriteRecord: exit @ 924, Status=Success
        WorkSpaceRefresh: exit 6
        FvbProtocolWrite: enter, Lba=0xF Offset=0xA70 NumBytes=0x28 Buffer=9EBEFB50
          QemuFlashWrite: enter, Lba=0xF Offset=0xA70 NumBytes=0x28 Buffer=9EBEFB50
            QemuFlashPtr: enter, Lba=0xF Offset=0xA70
            QemuFlashPtr: exit, Ret=FFE0FA70
          QemuFlashWrite: exit 2
        FvbProtocolWrite: exit @ 891, Status=Success, NumBytes=0x28
        FtwUpdateFvState: enter, Lba=0xF Offset=0xA70 NewBit=2
          FvbProtocolRead: enter, Lba=0xF Offset=0xA70 NumBytes=0x1 Buffer=9F852647
            QemuFlashRead: enter, Lba=0xF Offset=0xA70 NumBytes=0x1 Buffer=9F852647
              QemuFlashPtr: enter, Lba=0xF Offset=0xA70
              QemuFlashPtr: exit, Ret=FFE0FA70
            QemuFlashRead: exit 2
          FvbProtocolRead: exit @ 944, Status=Success, NumBytes=0x1
          FvbProtocolWrite: enter, Lba=0xF Offset=0xA70 NumBytes=0x1 Buffer=9F852647
            QemuFlashWrite: enter, Lba=0xF Offset=0xA70 NumBytes=0x1 Buffer=9F852647
              QemuFlashPtr: enter, Lba=0xF Offset=0xA70
              QemuFlashPtr: exit, Ret=FFE0FA70
            QemuFlashWrite: exit 2
          FvbProtocolWrite: exit @ 891, Status=Success, NumBytes=0x1
        FtwUpdateFvState: exit @ 824, Status=Success
        Ftw: Allocate() success, Caller:FE5CEA76-4F72-49E8-986F-2CD899DFFE5D, # 1
      FtwAllocate: exit 7

      FtwGetFvbByHandle: enter
      FtwGetFvbByHandle: exit: Success

      FvbProtocolGetPhysicalAddress: enter
        FvbGetPhysicalAddress: enter, Instance=0x0, Global=9F7DAF18, Virtual=0
          GetFvbInstance: enter, Instance=0x0, Global=9F7DAF18, Virtual=0
          GetFvbInstance: exit @ 229, Status=Success
        FvbGetPhysicalAddress: exit @ 275, Status=Success, Address=0xFFE00000
      FvbProtocolGetPhysicalAddress: exit @ 620, Status=Success

      IsBootBlock: enter, Lba=0x0
        FtwGetSarProtocol: enter
        FtwGetSarProtocol: exit: Not Found
      IsBootBlock: exit 2: Not Found

      FvbProtocolWrite: enter, Lba=0xF Offset=0xA98 NumBytes=0x28 Buffer=9EBEFB78
        QemuFlashWrite: enter, Lba=0xF Offset=0xA98 NumBytes=0x28 Buffer=9EBEFB78
          QemuFlashPtr: enter, Lba=0xF Offset=0xA98
          QemuFlashPtr: exit, Ret=FFE0FA98
        QemuFlashWrite: exit 2
      FvbProtocolWrite: exit @ 891, Status=Success, NumBytes=0x28

      //
      // Read all original data from target block to memory buffer
      //
      FvbProtocolRead: enter, Lba=0x0 Offset=0x0 NumBytes=0x1000 Buffer=9C806018
        QemuFlashRead: enter, Lba=0x0 Offset=0x0 NumBytes=0x1000 Buffer=9C806018
          QemuFlashPtr: enter, Lba=0x0 Offset=0x0
          QemuFlashPtr: exit, Ret=FFE00000
        QemuFlashRead: exit 2
      FvbProtocolRead: exit @ 944, Status=Success, NumBytes=0x1000

      FvbProtocolRead: enter, Lba=0x1 Offset=0x0 NumBytes=0x1000 Buffer=9C807018
      QemuFlashRead: enter, Lba=0x1 Offset=0x0 NumBytes=0x1000 Buffer=9C807018
      QemuFlashPtr: enter, Lba=0x1 Offset=0x0
      QemuFlashPtr: exit, Ret=FFE01000
      QemuFlashRead: exit 2
      FvbProtocolRead: exit @ 944, Status=Success, NumBytes=0x1000

      FvbProtocolRead: enter, Lba=0x2 Offset=0x0 NumBytes=0x1000 Buffer=9C808018
      QemuFlashRead: enter, Lba=0x2 Offset=0x0 NumBytes=0x1000 Buffer=9C808018
      QemuFlashPtr: enter, Lba=0x2 Offset=0x0
      QemuFlashPtr: exit, Ret=FFE02000
      QemuFlashRead: exit 2
      FvbProtocolRead: exit @ 944, Status=Success, NumBytes=0x1000

      FvbProtocolRead: enter, Lba=0x3 Offset=0x0 NumBytes=0x1000 Buffer=9C809018
      QemuFlashRead: enter, Lba=0x3 Offset=0x0 NumBytes=0x1000 Buffer=9C809018
      QemuFlashPtr: enter, Lba=0x3 Offset=0x0
      QemuFlashPtr: exit, Ret=FFE03000
      QemuFlashRead: exit 2
      FvbProtocolRead: exit @ 944, Status=Success, NumBytes=0x1000

      FvbProtocolRead: enter, Lba=0x4 Offset=0x0 NumBytes=0x1000 Buffer=9C80A018
      QemuFlashRead: enter, Lba=0x4 Offset=0x0 NumBytes=0x1000 Buffer=9C80A018
      QemuFlashPtr: enter, Lba=0x4 Offset=0x0
      QemuFlashPtr: exit, Ret=FFE04000
      QemuFlashRead: exit 2
      FvbProtocolRead: exit @ 944, Status=Success, NumBytes=0x1000

      FvbProtocolRead: enter, Lba=0x5 Offset=0x0 NumBytes=0x1000 Buffer=9C80B018
      QemuFlashRead: enter, Lba=0x5 Offset=0x0 NumBytes=0x1000 Buffer=9C80B018
      QemuFlashPtr: enter, Lba=0x5 Offset=0x0
      QemuFlashPtr: exit, Ret=FFE05000
      QemuFlashRead: exit 2
      FvbProtocolRead: exit @ 944, Status=Success, NumBytes=0x1000

      FvbProtocolRead: enter, Lba=0x6 Offset=0x0 NumBytes=0x1000 Buffer=9C80C018
      QemuFlashRead: enter, Lba=0x6 Offset=0x0 NumBytes=0x1000 Buffer=9C80C018
      QemuFlashPtr: enter, Lba=0x6 Offset=0x0
      QemuFlashPtr: exit, Ret=FFE06000
      QemuFlashRead: exit 2
      FvbProtocolRead: exit @ 944, Status=Success, NumBytes=0x1000

      FvbProtocolRead: enter, Lba=0x7 Offset=0x0 NumBytes=0x1000 Buffer=9C80D018
      QemuFlashRead: enter, Lba=0x7 Offset=0x0 NumBytes=0x1000 Buffer=9C80D018
      QemuFlashPtr: enter, Lba=0x7 Offset=0x0
      QemuFlashPtr: exit, Ret=FFE07000
      QemuFlashRead: exit 2
      FvbProtocolRead: exit @ 944, Status=Success, NumBytes=0x1000

      FvbProtocolRead: enter, Lba=0x8 Offset=0x0 NumBytes=0x1000 Buffer=9C80E018
      QemuFlashRead: enter, Lba=0x8 Offset=0x0 NumBytes=0x1000 Buffer=9C80E018
      QemuFlashPtr: enter, Lba=0x8 Offset=0x0
      QemuFlashPtr: exit, Ret=FFE08000
      QemuFlashRead: exit 2
      FvbProtocolRead: exit @ 944, Status=Success, NumBytes=0x1000

      FvbProtocolRead: enter, Lba=0x9 Offset=0x0 NumBytes=0x1000 Buffer=9C80F018
      QemuFlashRead: enter, Lba=0x9 Offset=0x0 NumBytes=0x1000 Buffer=9C80F018
      QemuFlashPtr: enter, Lba=0x9 Offset=0x0
      QemuFlashPtr: exit, Ret=FFE09000
      QemuFlashRead: exit 2
      FvbProtocolRead: exit @ 944, Status=Success, NumBytes=0x1000

      FvbProtocolRead: enter, Lba=0xA Offset=0x0 NumBytes=0x1000 Buffer=9C810018
      QemuFlashRead: enter, Lba=0xA Offset=0x0 NumBytes=0x1000 Buffer=9C810018
      QemuFlashPtr: enter, Lba=0xA Offset=0x0
      QemuFlashPtr: exit, Ret=FFE0A000
      QemuFlashRead: exit 2
      FvbProtocolRead: exit @ 944, Status=Success, NumBytes=0x1000

      FvbProtocolRead: enter, Lba=0xB Offset=0x0 NumBytes=0x1000 Buffer=9C811018
      QemuFlashRead: enter, Lba=0xB Offset=0x0 NumBytes=0x1000 Buffer=9C811018
      QemuFlashPtr: enter, Lba=0xB Offset=0x0
      QemuFlashPtr: exit, Ret=FFE0B000
      QemuFlashRead: exit 2
      FvbProtocolRead: exit @ 944, Status=Success, NumBytes=0x1000

      FvbProtocolRead: enter, Lba=0xC Offset=0x0 NumBytes=0x1000 Buffer=9C812018
      QemuFlashRead: enter, Lba=0xC Offset=0x0 NumBytes=0x1000 Buffer=9C812018
      QemuFlashPtr: enter, Lba=0xC Offset=0x0
      QemuFlashPtr: exit, Ret=FFE0C000
      QemuFlashRead: exit 2
      FvbProtocolRead: exit @ 944, Status=Success, NumBytes=0x1000

      FvbProtocolRead: enter, Lba=0xD Offset=0x0 NumBytes=0x1000 Buffer=9C813018
      QemuFlashRead: enter, Lba=0xD Offset=0x0 NumBytes=0x1000 Buffer=9C813018
      QemuFlashPtr: enter, Lba=0xD Offset=0x0
      QemuFlashPtr: exit, Ret=FFE0D000
      QemuFlashRead: exit 2
      FvbProtocolRead: exit @ 944, Status=Success, NumBytes=0x1000

      FvbProtocolRead: enter, Lba=0xE Offset=0x0 NumBytes=0x1000 Buffer=9C814018
      QemuFlashRead: enter, Lba=0xE Offset=0x0 NumBytes=0x1000 Buffer=9C814018
      QemuFlashPtr: enter, Lba=0xE Offset=0x0
      QemuFlashPtr: exit, Ret=FFE0E000
      QemuFlashRead: exit 2
      FvbProtocolRead: exit @ 944, Status=Success, NumBytes=0x1000

      FvbProtocolRead: enter, Lba=0xF Offset=0x0 NumBytes=0x1000 Buffer=9C815018
      QemuFlashRead: enter, Lba=0xF Offset=0x0 NumBytes=0x1000 Buffer=9C815018
      QemuFlashPtr: enter, Lba=0xF Offset=0x0
      QemuFlashPtr: exit, Ret=FFE0F000
      QemuFlashRead: exit 2
      FvbProtocolRead: exit @ 944, Status=Success, NumBytes=0x1000


      //
      // Try to keep the content of spare block
      // Save spare block into a spare backup memory buffer (Sparebuffer)
      //
      FvbProtocolRead: enter, Lba=0x10 Offset=0x0 NumBytes=0x1000 Buffer=9C7F5018
      QemuFlashRead: enter, Lba=0x10 Offset=0x0 NumBytes=0x1000 Buffer=9C7F5018
      QemuFlashPtr: enter, Lba=0x10 Offset=0x0
      QemuFlashPtr: exit, Ret=FFE10000
      QemuFlashRead: exit 2
      FvbProtocolRead: exit @ 944, Status=Success, NumBytes=0x1000

      FvbProtocolRead: enter, Lba=0x11 Offset=0x0 NumBytes=0x1000 Buffer=9C7F6018
      QemuFlashRead: enter, Lba=0x11 Offset=0x0 NumBytes=0x1000 Buffer=9C7F6018
      QemuFlashPtr: enter, Lba=0x11 Offset=0x0
      QemuFlashPtr: exit, Ret=FFE11000
      QemuFlashRead: exit 2
      FvbProtocolRead: exit @ 944, Status=Success, NumBytes=0x1000

      FvbProtocolRead: enter, Lba=0x12 Offset=0x0 NumBytes=0x1000 Buffer=9C7F7018
      QemuFlashRead: enter, Lba=0x12 Offset=0x0 NumBytes=0x1000 Buffer=9C7F7018
      QemuFlashPtr: enter, Lba=0x12 Offset=0x0
      QemuFlashPtr: exit, Ret=FFE12000
      QemuFlashRead: exit 2
      FvbProtocolRead: exit @ 944, Status=Success, NumBytes=0x1000

      FvbProtocolRead: enter, Lba=0x13 Offset=0x0 NumBytes=0x1000 Buffer=9C7F8018
      QemuFlashRead: enter, Lba=0x13 Offset=0x0 NumBytes=0x1000 Buffer=9C7F8018
      QemuFlashPtr: enter, Lba=0x13 Offset=0x0
      QemuFlashPtr: exit, Ret=FFE13000
      QemuFlashRead: exit 2
      FvbProtocolRead: exit @ 944, Status=Success, NumBytes=0x1000

      FvbProtocolRead: enter, Lba=0x14 Offset=0x0 NumBytes=0x1000 Buffer=9C7F9018
      QemuFlashRead: enter, Lba=0x14 Offset=0x0 NumBytes=0x1000 Buffer=9C7F9018
      QemuFlashPtr: enter, Lba=0x14 Offset=0x0
      QemuFlashPtr: exit, Ret=FFE14000
      QemuFlashRead: exit 2
      FvbProtocolRead: exit @ 944, Status=Success, NumBytes=0x1000

      FvbProtocolRead: enter, Lba=0x15 Offset=0x0 NumBytes=0x1000 Buffer=9C7FA018
      QemuFlashRead: enter, Lba=0x15 Offset=0x0 NumBytes=0x1000 Buffer=9C7FA018
      QemuFlashPtr: enter, Lba=0x15 Offset=0x0
      QemuFlashPtr: exit, Ret=FFE15000
      QemuFlashRead: exit 2
      FvbProtocolRead: exit @ 944, Status=Success, NumBytes=0x1000

      FvbProtocolRead: enter, Lba=0x16 Offset=0x0 NumBytes=0x1000 Buffer=9C7FB018
      QemuFlashRead: enter, Lba=0x16 Offset=0x0 NumBytes=0x1000 Buffer=9C7FB018
      QemuFlashPtr: enter, Lba=0x16 Offset=0x0
      QemuFlashPtr: exit, Ret=FFE16000
      QemuFlashRead: exit 2
      FvbProtocolRead: exit @ 944, Status=Success, NumBytes=0x1000

      FvbProtocolRead: enter, Lba=0x17 Offset=0x0 NumBytes=0x1000 Buffer=9C7FC018
      QemuFlashRead: enter, Lba=0x17 Offset=0x0 NumBytes=0x1000 Buffer=9C7FC018
      QemuFlashPtr: enter, Lba=0x17 Offset=0x0
      QemuFlashPtr: exit, Ret=FFE17000
      QemuFlashRead: exit 2
      FvbProtocolRead: exit @ 944, Status=Success, NumBytes=0x1000

      FvbProtocolRead: enter, Lba=0x18 Offset=0x0 NumBytes=0x1000 Buffer=9C7FD018
      QemuFlashRead: enter, Lba=0x18 Offset=0x0 NumBytes=0x1000 Buffer=9C7FD018
      QemuFlashPtr: enter, Lba=0x18 Offset=0x0
      QemuFlashPtr: exit, Ret=FFE18000
      QemuFlashRead: exit 2
      FvbProtocolRead: exit @ 944, Status=Success, NumBytes=0x1000

      FvbProtocolRead: enter, Lba=0x19 Offset=0x0 NumBytes=0x1000 Buffer=9C7FE018
      QemuFlashRead: enter, Lba=0x19 Offset=0x0 NumBytes=0x1000 Buffer=9C7FE018
      QemuFlashPtr: enter, Lba=0x19 Offset=0x0
      QemuFlashPtr: exit, Ret=FFE19000
      QemuFlashRead: exit 2
      FvbProtocolRead: exit @ 944, Status=Success, NumBytes=0x1000

      FvbProtocolRead: enter, Lba=0x1A Offset=0x0 NumBytes=0x1000 Buffer=9C7FF018
      QemuFlashRead: enter, Lba=0x1A Offset=0x0 NumBytes=0x1000 Buffer=9C7FF018
      QemuFlashPtr: enter, Lba=0x1A Offset=0x0
      QemuFlashPtr: exit, Ret=FFE1A000
      QemuFlashRead: exit 2
      FvbProtocolRead: exit @ 944, Status=Success, NumBytes=0x1000

      FvbProtocolRead: enter, Lba=0x1B Offset=0x0 NumBytes=0x1000 Buffer=9C800018
      QemuFlashRead: enter, Lba=0x1B Offset=0x0 NumBytes=0x1000 Buffer=9C800018
      QemuFlashPtr: enter, Lba=0x1B Offset=0x0
      QemuFlashPtr: exit, Ret=FFE1B000
      QemuFlashRead: exit 2
      FvbProtocolRead: exit @ 944, Status=Success, NumBytes=0x1000

      FvbProtocolRead: enter, Lba=0x1C Offset=0x0 NumBytes=0x1000 Buffer=9C801018
      QemuFlashRead: enter, Lba=0x1C Offset=0x0 NumBytes=0x1000 Buffer=9C801018
      QemuFlashPtr: enter, Lba=0x1C Offset=0x0
      QemuFlashPtr: exit, Ret=FFE1C000
      QemuFlashRead: exit 2
      FvbProtocolRead: exit @ 944, Status=Success, NumBytes=0x1000

      FvbProtocolRead: enter, Lba=0x1D Offset=0x0 NumBytes=0x1000 Buffer=9C802018
      QemuFlashRead: enter, Lba=0x1D Offset=0x0 NumBytes=0x1000 Buffer=9C802018
      QemuFlashPtr: enter, Lba=0x1D Offset=0x0
      QemuFlashPtr: exit, Ret=FFE1D000
      QemuFlashRead: exit 2
      FvbProtocolRead: exit @ 944, Status=Success, NumBytes=0x1000

      FvbProtocolRead: enter, Lba=0x1E Offset=0x0 NumBytes=0x1000 Buffer=9C803018
      QemuFlashRead: enter, Lba=0x1E Offset=0x0 NumBytes=0x1000 Buffer=9C803018
      QemuFlashPtr: enter, Lba=0x1E Offset=0x0
      QemuFlashPtr: exit, Ret=FFE1E000
      QemuFlashRead: exit 2
      FvbProtocolRead: exit @ 944, Status=Success, NumBytes=0x1000

      FvbProtocolRead: enter, Lba=0x1F Offset=0x0 NumBytes=0x1000 Buffer=9C804018
      QemuFlashRead: enter, Lba=0x1F Offset=0x0 NumBytes=0x1000 Buffer=9C804018
      QemuFlashPtr: enter, Lba=0x1F Offset=0x0
      QemuFlashPtr: exit, Ret=FFE1F000
      QemuFlashRead: exit 2
      FvbProtocolRead: exit @ 944, Status=Success, NumBytes=0x1000

      //
      // Write the memory buffer to spare block
      //
      FtwEraseSpareBlock: enter
        FvbProtocolEraseBlocks: enter
          GetFvbInstance: enter, Instance=0x0, Global=9F7DAF18, Virtual=0
          GetFvbInstance: exit @ 229, Status=Success
          QemuFlashEraseBlock: enter, Lba=0x10
          QemuFlashPtr: enter, Lba=0x10 Offset=0x0
          QemuFlashPtr: exit, Ret=FFE10000
          QemuFlashEraseBlock: exit 2
          QemuFlashEraseBlock: enter, Lba=0x11
          QemuFlashPtr: enter, Lba=0x11 Offset=0x0
          QemuFlashPtr: exit, Ret=FFE11000
          QemuFlashEraseBlock: exit 2
          QemuFlashEraseBlock: enter, Lba=0x12
          QemuFlashPtr: enter, Lba=0x12 Offset=0x0
          QemuFlashPtr: exit, Ret=FFE12000
          QemuFlashEraseBlock: exit 2
          QemuFlashEraseBlock: enter, Lba=0x13
          QemuFlashPtr: enter, Lba=0x13 Offset=0x0
          QemuFlashPtr: exit, Ret=FFE13000
          QemuFlashEraseBlock: exit 2
          QemuFlashEraseBlock: enter, Lba=0x14
          QemuFlashPtr: enter, Lba=0x14 Offset=0x0
          QemuFlashPtr: exit, Ret=FFE14000
          QemuFlashEraseBlock: exit 2
          QemuFlashEraseBlock: enter, Lba=0x15
          QemuFlashPtr: enter, Lba=0x15 Offset=0x0
          QemuFlashPtr: exit, Ret=FFE15000
          QemuFlashEraseBlock: exit 2
          QemuFlashEraseBlock: enter, Lba=0x16
          QemuFlashPtr: enter, Lba=0x16 Offset=0x0
          QemuFlashPtr: exit, Ret=FFE16000
          QemuFlashEraseBlock: exit 2
          QemuFlashEraseBlock: enter, Lba=0x17
          QemuFlashPtr: enter, Lba=0x17 Offset=0x0
          QemuFlashPtr: exit, Ret=FFE17000
          QemuFlashEraseBlock: exit 2
          QemuFlashEraseBlock: enter, Lba=0x18
          QemuFlashPtr: enter, Lba=0x18 Offset=0x0
          QemuFlashPtr: exit, Ret=FFE18000
          QemuFlashEraseBlock: exit 2
          QemuFlashEraseBlock: enter, Lba=0x19
          QemuFlashPtr: enter, Lba=0x19 Offset=0x0
          QemuFlashPtr: exit, Ret=FFE19000
          QemuFlashEraseBlock: exit 2
          QemuFlashEraseBlock: enter, Lba=0x1A
          QemuFlashPtr: enter, Lba=0x1A Offset=0x0
          QemuFlashPtr: exit, Ret=FFE1A000
          QemuFlashEraseBlock: exit 2
          QemuFlashEraseBlock: enter, Lba=0x1B
          QemuFlashPtr: enter, Lba=0x1B Offset=0x0
          QemuFlashPtr: exit, Ret=FFE1B000
          QemuFlashEraseBlock: exit 2
          QemuFlashEraseBlock: enter, Lba=0x1C
          QemuFlashPtr: enter, Lba=0x1C Offset=0x0
          QemuFlashPtr: exit, Ret=FFE1C000
          QemuFlashEraseBlock: exit 2
          QemuFlashEraseBlock: enter, Lba=0x1D
          QemuFlashPtr: enter, Lba=0x1D Offset=0x0
          QemuFlashPtr: exit, Ret=FFE1D000
          QemuFlashEraseBlock: exit 2
          QemuFlashEraseBlock: enter, Lba=0x1E
          QemuFlashPtr: enter, Lba=0x1E Offset=0x0
          QemuFlashPtr: exit, Ret=FFE1E000
          QemuFlashEraseBlock: exit 2
          QemuFlashEraseBlock: enter, Lba=0x1F
          QemuFlashPtr: enter, Lba=0x1F Offset=0x0
          QemuFlashPtr: exit, Ret=FFE1F000
          QemuFlashEraseBlock: exit 2
        FvbProtocolEraseBlocks: exit @ 839, Status=Success
      FtwEraseSpareBlock: exit: Success

      FvbProtocolWrite: enter, Lba=0x10 Offset=0x0 NumBytes=0x1000 Buffer=9C806018
      QemuFlashWrite: enter, Lba=0x10 Offset=0x0 NumBytes=0x1000 Buffer=9C806018
      QemuFlashPtr: enter, Lba=0x10 Offset=0x0
      QemuFlashPtr: exit, Ret=FFE10000
      QemuFlashWrite: exit 2
      FvbProtocolWrite: exit @ 891, Status=Success, NumBytes=0x1000
      FvbProtocolWrite: enter, Lba=0x11 Offset=0x0 NumBytes=0x1000 Buffer=9C807018
      QemuFlashWrite: enter, Lba=0x11 Offset=0x0 NumBytes=0x1000 Buffer=9C807018
      QemuFlashPtr: enter, Lba=0x11 Offset=0x0
      QemuFlashPtr: exit, Ret=FFE11000
      QemuFlashWrite: exit 2
      FvbProtocolWrite: exit @ 891, Status=Success, NumBytes=0x1000
      FvbProtocolWrite: enter, Lba=0x12 Offset=0x0 NumBytes=0x1000 Buffer=9C808018
      QemuFlashWrite: enter, Lba=0x12 Offset=0x0 NumBytes=0x1000 Buffer=9C808018
      QemuFlashPtr: enter, Lba=0x12 Offset=0x0
      QemuFlashPtr: exit, Ret=FFE12000
      QemuFlashWrite: exit 2
      FvbProtocolWrite: exit @ 891, Status=Success, NumBytes=0x1000
      FvbProtocolWrite: enter, Lba=0x13 Offset=0x0 NumBytes=0x1000 Buffer=9C809018
      QemuFlashWrite: enter, Lba=0x13 Offset=0x0 NumBytes=0x1000 Buffer=9C809018
      QemuFlashPtr: enter, Lba=0x13 Offset=0x0
      QemuFlashPtr: exit, Ret=FFE13000
      QemuFlashWrite: exit 2
      FvbProtocolWrite: exit @ 891, Status=Success, NumBytes=0x1000
      FvbProtocolWrite: enter, Lba=0x14 Offset=0x0 NumBytes=0x1000 Buffer=9C80A018
      QemuFlashWrite: enter, Lba=0x14 Offset=0x0 NumBytes=0x1000 Buffer=9C80A018
      QemuFlashPtr: enter, Lba=0x14 Offset=0x0
      QemuFlashPtr: exit, Ret=FFE14000
      QemuFlashWrite: exit 2
      FvbProtocolWrite: exit @ 891, Status=Success, NumBytes=0x1000
      FvbProtocolWrite: enter, Lba=0x15 Offset=0x0 NumBytes=0x1000 Buffer=9C80B018
      QemuFlashWrite: enter, Lba=0x15 Offset=0x0 NumBytes=0x1000 Buffer=9C80B018
      QemuFlashPtr: enter, Lba=0x15 Offset=0x0
      QemuFlashPtr: exit, Ret=FFE15000
      QemuFlashWrite: exit 2
      FvbProtocolWrite: exit @ 891, Status=Success, NumBytes=0x1000
      FvbProtocolWrite: enter, Lba=0x16 Offset=0x0 NumBytes=0x1000 Buffer=9C80C018
      QemuFlashWrite: enter, Lba=0x16 Offset=0x0 NumBytes=0x1000 Buffer=9C80C018
      QemuFlashPtr: enter, Lba=0x16 Offset=0x0
      QemuFlashPtr: exit, Ret=FFE16000
      QemuFlashWrite: exit 2
      FvbProtocolWrite: exit @ 891, Status=Success, NumBytes=0x1000
      FvbProtocolWrite: enter, Lba=0x17 Offset=0x0 NumBytes=0x1000 Buffer=9C80D018
      QemuFlashWrite: enter, Lba=0x17 Offset=0x0 NumBytes=0x1000 Buffer=9C80D018
      QemuFlashPtr: enter, Lba=0x17 Offset=0x0
      QemuFlashPtr: exit, Ret=FFE17000
      QemuFlashWrite: exit 2
      FvbProtocolWrite: exit @ 891, Status=Success, NumBytes=0x1000
      FvbProtocolWrite: enter, Lba=0x18 Offset=0x0 NumBytes=0x1000 Buffer=9C80E018
      QemuFlashWrite: enter, Lba=0x18 Offset=0x0 NumBytes=0x1000 Buffer=9C80E018
      QemuFlashPtr: enter, Lba=0x18 Offset=0x0
      QemuFlashPtr: exit, Ret=FFE18000
      QemuFlashWrite: exit 2
      FvbProtocolWrite: exit @ 891, Status=Success, NumBytes=0x1000
      FvbProtocolWrite: enter, Lba=0x19 Offset=0x0 NumBytes=0x1000 Buffer=9C80F018
      QemuFlashWrite: enter, Lba=0x19 Offset=0x0 NumBytes=0x1000 Buffer=9C80F018
      QemuFlashPtr: enter, Lba=0x19 Offset=0x0
      QemuFlashPtr: exit, Ret=FFE19000
      QemuFlashWrite: exit 2
      FvbProtocolWrite: exit @ 891, Status=Success, NumBytes=0x1000
      FvbProtocolWrite: enter, Lba=0x1A Offset=0x0 NumBytes=0x1000 Buffer=9C810018
      QemuFlashWrite: enter, Lba=0x1A Offset=0x0 NumBytes=0x1000 Buffer=9C810018
      QemuFlashPtr: enter, Lba=0x1A Offset=0x0
      QemuFlashPtr: exit, Ret=FFE1A000
      QemuFlashWrite: exit 2
      FvbProtocolWrite: exit @ 891, Status=Success, NumBytes=0x1000
      FvbProtocolWrite: enter, Lba=0x1B Offset=0x0 NumBytes=0x1000 Buffer=9C811018
      QemuFlashWrite: enter, Lba=0x1B Offset=0x0 NumBytes=0x1000 Buffer=9C811018
      QemuFlashPtr: enter, Lba=0x1B Offset=0x0
      QemuFlashPtr: exit, Ret=FFE1B000
      QemuFlashWrite: exit 2
      FvbProtocolWrite: exit @ 891, Status=Success, NumBytes=0x1000
      FvbProtocolWrite: enter, Lba=0x1C Offset=0x0 NumBytes=0x1000 Buffer=9C812018
      QemuFlashWrite: enter, Lba=0x1C Offset=0x0 NumBytes=0x1000 Buffer=9C812018
      QemuFlashPtr: enter, Lba=0x1C Offset=0x0
      QemuFlashPtr: exit, Ret=FFE1C000
      QemuFlashWrite: exit 2
      FvbProtocolWrite: exit @ 891, Status=Success, NumBytes=0x1000
      FvbProtocolWrite: enter, Lba=0x1D Offset=0x0 NumBytes=0x1000 Buffer=9C813018
      QemuFlashWrite: enter, Lba=0x1D Offset=0x0 NumBytes=0x1000 Buffer=9C813018
      QemuFlashPtr: enter, Lba=0x1D Offset=0x0
      QemuFlashPtr: exit, Ret=FFE1D000
      QemuFlashWrite: exit 2
      FvbProtocolWrite: exit @ 891, Status=Success, NumBytes=0x1000
      FvbProtocolWrite: enter, Lba=0x1E Offset=0x0 NumBytes=0x1000 Buffer=9C814018
      QemuFlashWrite: enter, Lba=0x1E Offset=0x0 NumBytes=0x1000 Buffer=9C814018
      QemuFlashPtr: enter, Lba=0x1E Offset=0x0
      QemuFlashPtr: exit, Ret=FFE1E000
      QemuFlashWrite: exit 2
      FvbProtocolWrite: exit @ 891, Status=Success, NumBytes=0x1000

      !!! out-of-varstore write !!!
      FvbProtocolWrite: enter, Lba=0x1F Offset=0x0 NumBytes=0x1000 Buffer=9C815018
      QemuFlashWrite: enter, Lba=0x1F Offset=0x0 NumBytes=0x1000 Buffer=9C815018
      QemuFlashPtr: enter, Lba=0x1F Offset=0x0
      QemuFlashPtr: exit, Ret=FFE1F000
      QemuFlashWrite: exit 2
      FvbProtocolWrite: exit @ 891, Status=Success, NumBytes=0x1000

      //
      // Set the SpareComplete in the FTW record,
      //
      FtwUpdateFvState: enter, Lba=0xF Offset=0xA98 NewBit=2
        FvbProtocolRead: enter, Lba=0xF Offset=0xA98 NumBytes=0x1 Buffer=9F8526C7
          QemuFlashRead: enter, Lba=0xF Offset=0xA98 NumBytes=0x1 Buffer=9F8526C7
            QemuFlashPtr: enter, Lba=0xF Offset=0xA98
            QemuFlashPtr: exit, Ret=FFE0FA98
          QemuFlashRead: exit 2
        FvbProtocolRead: exit @ 944, Status=Success, NumBytes=0x1
        FvbProtocolWrite: enter, Lba=0xF Offset=0xA98 NumBytes=0x1 Buffer=9F8526C7
          QemuFlashWrite: enter, Lba=0xF Offset=0xA98 NumBytes=0x1 Buffer=9F8526C7
            QemuFlashPtr: enter, Lba=0xF Offset=0xA98
            QemuFlashPtr: exit, Ret=FFE0FA98
          QemuFlashWrite: exit 2
        FvbProtocolWrite: exit @ 891, Status=Success, NumBytes=0x1
      FtwUpdateFvState: exit @ 824, Status=Success

      //
      //  Since the content has already backuped in spare block, the write is
      //  guaranteed to be completed with fault tolerant manner.
      //
      FtwWriteRecord: enter
        //
        // IF target block is working block, THEN Flush Spare Block To Working Block;
        // ELSE flush spare block to target block, which may be boot block after all.
        //
        IsWorkingBlock: enter, Lba=0x0
        IsWorkingBlock: exit: 1
        FtwUpdateFvState: enter, Lba=0x1F Offset=0xA98 NewBit=2
          FvbProtocolRead: enter, Lba=0x1F Offset=0xA98 NumBytes=0x1 Buffer=9F852657
            QemuFlashRead: enter, Lba=0x1F Offset=0xA98 NumBytes=0x1 Buffer=9F852657
              QemuFlashPtr: enter, Lba=0x1F Offset=0xA98
              QemuFlashPtr: exit, Ret=FFE1FA98
            QemuFlashRead: exit 2
          FvbProtocolRead: exit @ 944, Status=Success, NumBytes=0x1
          FvbProtocolWrite: enter, Lba=0x1F Offset=0xA98 NumBytes=0x1 Buffer=9F852657
            QemuFlashWrite: enter, Lba=0x1F Offset=0xA98 NumBytes=0x1 Buffer=9F852657
              QemuFlashPtr: enter, Lba=0x1F Offset=0xA98
              QemuFlashPtr: exit, Ret=FFE1FA98
            QemuFlashWrite: exit 2
          FvbProtocolWrite: exit @ 891, Status=Success, NumBytes=0x1
        FtwUpdateFvState: exit @ 824, Status=Success

        FlushSpareBlockToWorkingBlock: enter
          FtwUpdateFvState: enter, Lba=0x1F Offset=0x14 NewBit=1
            FvbProtocolRead: enter, Lba=0x1F Offset=0x14 NumBytes=0x1 Buffer=9F8525D7
              QemuFlashRead: enter, Lba=0x1F Offset=0x14 NumBytes=0x1 Buffer=9F8525D7
                QemuFlashPtr: enter, Lba=0x1F Offset=0x14
                QemuFlashPtr: exit, Ret=FFE1F014
              QemuFlashRead: exit 2
            FvbProtocolRead: exit @ 944, Status=Success, NumBytes=0x1
            FvbProtocolWrite: enter, Lba=0x1F Offset=0x14 NumBytes=0x1 Buffer=9F8525D7
              QemuFlashWrite: enter, Lba=0x1F Offset=0x14 NumBytes=0x1 Buffer=9F8525D7
                QemuFlashPtr: enter, Lba=0x1F Offset=0x14
                QemuFlashPtr: exit, Ret=FFE1F014
              QemuFlashWrite: exit 2
            FvbProtocolWrite: exit @ 891, Status=Success, NumBytes=0x1
          FtwUpdateFvState: exit @ 824, Status=Success

          //
          // Read from spare block to memory buffer
          //
          FvbProtocolRead: enter, Lba=0x10 Offset=0x0 NumBytes=0x1000 Buffer=9C806018
          QemuFlashRead: enter, Lba=0x10 Offset=0x0 NumBytes=0x1000 Buffer=9C806018
          QemuFlashPtr: enter, Lba=0x10 Offset=0x0
          QemuFlashPtr: exit, Ret=FFE10000
          QemuFlashRead: exit 2
          FvbProtocolRead: exit @ 944, Status=Success, NumBytes=0x1000
          FvbProtocolRead: enter, Lba=0x11 Offset=0x0 NumBytes=0x1000 Buffer=9C807018
          QemuFlashRead: enter, Lba=0x11 Offset=0x0 NumBytes=0x1000 Buffer=9C807018
          QemuFlashPtr: enter, Lba=0x11 Offset=0x0
          QemuFlashPtr: exit, Ret=FFE11000
          QemuFlashRead: exit 2
          FvbProtocolRead: exit @ 944, Status=Success, NumBytes=0x1000
          FvbProtocolRead: enter, Lba=0x12 Offset=0x0 NumBytes=0x1000 Buffer=9C808018
          QemuFlashRead: enter, Lba=0x12 Offset=0x0 NumBytes=0x1000 Buffer=9C808018
          QemuFlashPtr: enter, Lba=0x12 Offset=0x0
          QemuFlashPtr: exit, Ret=FFE12000
          QemuFlashRead: exit 2
          FvbProtocolRead: exit @ 944, Status=Success, NumBytes=0x1000
          FvbProtocolRead: enter, Lba=0x13 Offset=0x0 NumBytes=0x1000 Buffer=9C809018
          QemuFlashRead: enter, Lba=0x13 Offset=0x0 NumBytes=0x1000 Buffer=9C809018
          QemuFlashPtr: enter, Lba=0x13 Offset=0x0
          QemuFlashPtr: exit, Ret=FFE13000
          QemuFlashRead: exit 2
          FvbProtocolRead: exit @ 944, Status=Success, NumBytes=0x1000
          FvbProtocolRead: enter, Lba=0x14 Offset=0x0 NumBytes=0x1000 Buffer=9C80A018
          QemuFlashRead: enter, Lba=0x14 Offset=0x0 NumBytes=0x1000 Buffer=9C80A018
          QemuFlashPtr: enter, Lba=0x14 Offset=0x0
          QemuFlashPtr: exit, Ret=FFE14000
          QemuFlashRead: exit 2
          FvbProtocolRead: exit @ 944, Status=Success, NumBytes=0x1000
          FvbProtocolRead: enter, Lba=0x15 Offset=0x0 NumBytes=0x1000 Buffer=9C80B018
          QemuFlashRead: enter, Lba=0x15 Offset=0x0 NumBytes=0x1000 Buffer=9C80B018
          QemuFlashPtr: enter, Lba=0x15 Offset=0x0
          QemuFlashPtr: exit, Ret=FFE15000
          QemuFlashRead: exit 2
          FvbProtocolRead: exit @ 944, Status=Success, NumBytes=0x1000
          FvbProtocolRead: enter, Lba=0x16 Offset=0x0 NumBytes=0x1000 Buffer=9C80C018
          QemuFlashRead: enter, Lba=0x16 Offset=0x0 NumBytes=0x1000 Buffer=9C80C018
          QemuFlashPtr: enter, Lba=0x16 Offset=0x0
          QemuFlashPtr: exit, Ret=FFE16000
          QemuFlashRead: exit 2
          FvbProtocolRead: exit @ 944, Status=Success, NumBytes=0x1000
          FvbProtocolRead: enter, Lba=0x17 Offset=0x0 NumBytes=0x1000 Buffer=9C80D018
          QemuFlashRead: enter, Lba=0x17 Offset=0x0 NumBytes=0x1000 Buffer=9C80D018
          QemuFlashPtr: enter, Lba=0x17 Offset=0x0
          QemuFlashPtr: exit, Ret=FFE17000
          QemuFlashRead: exit 2
          FvbProtocolRead: exit @ 944, Status=Success, NumBytes=0x1000
          FvbProtocolRead: enter, Lba=0x18 Offset=0x0 NumBytes=0x1000 Buffer=9C80E018
          QemuFlashRead: enter, Lba=0x18 Offset=0x0 NumBytes=0x1000 Buffer=9C80E018
          QemuFlashPtr: enter, Lba=0x18 Offset=0x0
          QemuFlashPtr: exit, Ret=FFE18000
          QemuFlashRead: exit 2
          FvbProtocolRead: exit @ 944, Status=Success, NumBytes=0x1000
          FvbProtocolRead: enter, Lba=0x19 Offset=0x0 NumBytes=0x1000 Buffer=9C80F018
          QemuFlashRead: enter, Lba=0x19 Offset=0x0 NumBytes=0x1000 Buffer=9C80F018
          QemuFlashPtr: enter, Lba=0x19 Offset=0x0
          QemuFlashPtr: exit, Ret=FFE19000
          QemuFlashRead: exit 2
          FvbProtocolRead: exit @ 944, Status=Success, NumBytes=0x1000
          FvbProtocolRead: enter, Lba=0x1A Offset=0x0 NumBytes=0x1000 Buffer=9C810018
          QemuFlashRead: enter, Lba=0x1A Offset=0x0 NumBytes=0x1000 Buffer=9C810018
          QemuFlashPtr: enter, Lba=0x1A Offset=0x0
          QemuFlashPtr: exit, Ret=FFE1A000
          QemuFlashRead: exit 2
          FvbProtocolRead: exit @ 944, Status=Success, NumBytes=0x1000
          FvbProtocolRead: enter, Lba=0x1B Offset=0x0 NumBytes=0x1000 Buffer=9C811018
          QemuFlashRead: enter, Lba=0x1B Offset=0x0 NumBytes=0x1000 Buffer=9C811018
          QemuFlashPtr: enter, Lba=0x1B Offset=0x0
          QemuFlashPtr: exit, Ret=FFE1B000
          QemuFlashRead: exit 2
          FvbProtocolRead: exit @ 944, Status=Success, NumBytes=0x1000
          FvbProtocolRead: enter, Lba=0x1C Offset=0x0 NumBytes=0x1000 Buffer=9C812018
          QemuFlashRead: enter, Lba=0x1C Offset=0x0 NumBytes=0x1000 Buffer=9C812018
          QemuFlashPtr: enter, Lba=0x1C Offset=0x0
          QemuFlashPtr: exit, Ret=FFE1C000
          QemuFlashRead: exit 2
          FvbProtocolRead: exit @ 944, Status=Success, NumBytes=0x1000
          FvbProtocolRead: enter, Lba=0x1D Offset=0x0 NumBytes=0x1000 Buffer=9C813018
          QemuFlashRead: enter, Lba=0x1D Offset=0x0 NumBytes=0x1000 Buffer=9C813018
          QemuFlashPtr: enter, Lba=0x1D Offset=0x0
          QemuFlashPtr: exit, Ret=FFE1D000
          QemuFlashRead: exit 2
          FvbProtocolRead: exit @ 944, Status=Success, NumBytes=0x1000
          FvbProtocolRead: enter, Lba=0x1E Offset=0x0 NumBytes=0x1000 Buffer=9C814018
          QemuFlashRead: enter, Lba=0x1E Offset=0x0 NumBytes=0x1000 Buffer=9C814018
          QemuFlashPtr: enter, Lba=0x1E Offset=0x0
          QemuFlashPtr: exit, Ret=FFE1E000
          QemuFlashRead: exit 2
          FvbProtocolRead: exit @ 944, Status=Success, NumBytes=0x1000
          FvbProtocolRead: enter, Lba=0x1F Offset=0x0 NumBytes=0x1000 Buffer=9C815018
          QemuFlashRead: enter, Lba=0x1F Offset=0x0 NumBytes=0x1000 Buffer=9C815018
          QemuFlashPtr: enter, Lba=0x1F Offset=0x0
          QemuFlashPtr: exit, Ret=FFE1F000
          QemuFlashRead: exit 2
          FvbProtocolRead: exit @ 944, Status=Success, NumBytes=0x1000

          //
          // Clear the CRC and STATE, copy data from spare to working block.
          //
          InitWorkSpaceHeader: enter
          InitWorkSpaceHeader: exit 2

          //
          // target block is working block, then
          //   Set WorkingBlockInvalid in EFI_FAULT_TOLERANT_WORKING_BLOCK_HEADER
          //   before erase the working block.
          //
          FtwUpdateFvState: enter, Lba=0xF Offset=0x14 NewBit=2
            FvbProtocolRead: enter, Lba=0xF Offset=0x14 NumBytes=0x1 Buffer=9F8525D7
              QemuFlashRead: enter, Lba=0xF Offset=0x14 NumBytes=0x1 Buffer=9F8525D7
                QemuFlashPtr: enter, Lba=0xF Offset=0x14
                QemuFlashPtr: exit, Ret=FFE0F014
              QemuFlashRead: exit 2
            FvbProtocolRead: exit @ 944, Status=Success, NumBytes=0x1
            FvbProtocolWrite: enter, Lba=0xF Offset=0x14 NumBytes=0x1 Buffer=9F8525D7
              QemuFlashWrite: enter, Lba=0xF Offset=0x14 NumBytes=0x1 Buffer=9F8525D7
                QemuFlashPtr: enter, Lba=0xF Offset=0x14
                QemuFlashPtr: exit, Ret=FFE0F014
              QemuFlashWrite: exit 2
            FvbProtocolWrite: exit @ 891, Status=Success, NumBytes=0x1
          FtwUpdateFvState: exit @ 824, Status=Success

          //
          // Erase the working block
          //
          FtwEraseBlock: enter, Lba=0x0
            FvbProtocolEraseBlocks: enter
              GetFvbInstance: enter, Instance=0x0, Global=9F7DAF18, Virtual=0
              GetFvbInstance: exit @ 229, Status=Success
              QemuFlashEraseBlock: enter, Lba=0x0
              QemuFlashPtr: enter, Lba=0x0 Offset=0x0
              QemuFlashPtr: exit, Ret=FFE00000
              QemuFlashEraseBlock: exit 2
              QemuFlashEraseBlock: enter, Lba=0x1
              QemuFlashPtr: enter, Lba=0x1 Offset=0x0
              QemuFlashPtr: exit, Ret=FFE01000
              QemuFlashEraseBlock: exit 2
              QemuFlashEraseBlock: enter, Lba=0x2
              QemuFlashPtr: enter, Lba=0x2 Offset=0x0
              QemuFlashPtr: exit, Ret=FFE02000
              QemuFlashEraseBlock: exit 2
              QemuFlashEraseBlock: enter, Lba=0x3
              QemuFlashPtr: enter, Lba=0x3 Offset=0x0
              QemuFlashPtr: exit, Ret=FFE03000
              QemuFlashEraseBlock: exit 2
              QemuFlashEraseBlock: enter, Lba=0x4
              QemuFlashPtr: enter, Lba=0x4 Offset=0x0
              QemuFlashPtr: exit, Ret=FFE04000
              QemuFlashEraseBlock: exit 2
              QemuFlashEraseBlock: enter, Lba=0x5
              QemuFlashPtr: enter, Lba=0x5 Offset=0x0
              QemuFlashPtr: exit, Ret=FFE05000
              QemuFlashEraseBlock: exit 2
              QemuFlashEraseBlock: enter, Lba=0x6
              QemuFlashPtr: enter, Lba=0x6 Offset=0x0
              QemuFlashPtr: exit, Ret=FFE06000
              QemuFlashEraseBlock: exit 2
              QemuFlashEraseBlock: enter, Lba=0x7
              QemuFlashPtr: enter, Lba=0x7 Offset=0x0
              QemuFlashPtr: exit, Ret=FFE07000
              QemuFlashEraseBlock: exit 2
              QemuFlashEraseBlock: enter, Lba=0x8
              QemuFlashPtr: enter, Lba=0x8 Offset=0x0
              QemuFlashPtr: exit, Ret=FFE08000
              QemuFlashEraseBlock: exit 2
              QemuFlashEraseBlock: enter, Lba=0x9
              QemuFlashPtr: enter, Lba=0x9 Offset=0x0
              QemuFlashPtr: exit, Ret=FFE09000
              QemuFlashEraseBlock: exit 2
              QemuFlashEraseBlock: enter, Lba=0xA
              QemuFlashPtr: enter, Lba=0xA Offset=0x0
              QemuFlashPtr: exit, Ret=FFE0A000
              QemuFlashEraseBlock: exit 2
              QemuFlashEraseBlock: enter, Lba=0xB
              QemuFlashPtr: enter, Lba=0xB Offset=0x0
              QemuFlashPtr: exit, Ret=FFE0B000
              QemuFlashEraseBlock: exit 2
              QemuFlashEraseBlock: enter, Lba=0xC
              QemuFlashPtr: enter, Lba=0xC Offset=0x0
              QemuFlashPtr: exit, Ret=FFE0C000
              QemuFlashEraseBlock: exit 2
              QemuFlashEraseBlock: enter, Lba=0xD
              QemuFlashPtr: enter, Lba=0xD Offset=0x0
              QemuFlashPtr: exit, Ret=FFE0D000
              QemuFlashEraseBlock: exit 2
              QemuFlashEraseBlock: enter, Lba=0xE
              QemuFlashPtr: enter, Lba=0xE Offset=0x0
              QemuFlashPtr: exit, Ret=FFE0E000
              QemuFlashEraseBlock: exit 2
              QemuFlashEraseBlock: enter, Lba=0xF
              QemuFlashPtr: enter, Lba=0xF Offset=0x0
              QemuFlashPtr: exit, Ret=FFE0F000
              QemuFlashEraseBlock: exit 2
            FvbProtocolEraseBlocks: exit @ 839, Status=Success
          FtwEraseBlock: exit: Success

          //
          // Write memory buffer to working block, using the FvbBlock protocol interface
          //
          FvbProtocolWrite: enter, Lba=0x0 Offset=0x0 NumBytes=0x1000 Buffer=9C806018
          QemuFlashWrite: enter, Lba=0x0 Offset=0x0 NumBytes=0x1000 Buffer=9C806018
          QemuFlashPtr: enter, Lba=0x0 Offset=0x0
          QemuFlashPtr: exit, Ret=FFE00000
          QemuFlashWrite: exit 2
          FvbProtocolWrite: exit @ 891, Status=Success, NumBytes=0x1000
          FvbProtocolWrite: enter, Lba=0x1 Offset=0x0 NumBytes=0x1000 Buffer=9C807018
          QemuFlashWrite: enter, Lba=0x1 Offset=0x0 NumBytes=0x1000 Buffer=9C807018
          QemuFlashPtr: enter, Lba=0x1 Offset=0x0
          QemuFlashPtr: exit, Ret=FFE01000
          QemuFlashWrite: exit 2
          FvbProtocolWrite: exit @ 891, Status=Success, NumBytes=0x1000
          FvbProtocolWrite: enter, Lba=0x2 Offset=0x0 NumBytes=0x1000 Buffer=9C808018
          QemuFlashWrite: enter, Lba=0x2 Offset=0x0 NumBytes=0x1000 Buffer=9C808018
          QemuFlashPtr: enter, Lba=0x2 Offset=0x0
          QemuFlashPtr: exit, Ret=FFE02000
          QemuFlashWrite: exit 2
          FvbProtocolWrite: exit @ 891, Status=Success, NumBytes=0x1000
          FvbProtocolWrite: enter, Lba=0x3 Offset=0x0 NumBytes=0x1000 Buffer=9C809018
          QemuFlashWrite: enter, Lba=0x3 Offset=0x0 NumBytes=0x1000 Buffer=9C809018
          QemuFlashPtr: enter, Lba=0x3 Offset=0x0
          QemuFlashPtr: exit, Ret=FFE03000
          QemuFlashWrite: exit 2
          FvbProtocolWrite: exit @ 891, Status=Success, NumBytes=0x1000
          FvbProtocolWrite: enter, Lba=0x4 Offset=0x0 NumBytes=0x1000 Buffer=9C80A018
          QemuFlashWrite: enter, Lba=0x4 Offset=0x0 NumBytes=0x1000 Buffer=9C80A018
          QemuFlashPtr: enter, Lba=0x4 Offset=0x0
          QemuFlashPtr: exit, Ret=FFE04000
          QemuFlashWrite: exit 2
          FvbProtocolWrite: exit @ 891, Status=Success, NumBytes=0x1000
          FvbProtocolWrite: enter, Lba=0x5 Offset=0x0 NumBytes=0x1000 Buffer=9C80B018
          QemuFlashWrite: enter, Lba=0x5 Offset=0x0 NumBytes=0x1000 Buffer=9C80B018
          QemuFlashPtr: enter, Lba=0x5 Offset=0x0
          QemuFlashPtr: exit, Ret=FFE05000
          QemuFlashWrite: exit 2
          FvbProtocolWrite: exit @ 891, Status=Success, NumBytes=0x1000
          FvbProtocolWrite: enter, Lba=0x6 Offset=0x0 NumBytes=0x1000 Buffer=9C80C018
          QemuFlashWrite: enter, Lba=0x6 Offset=0x0 NumBytes=0x1000 Buffer=9C80C018
          QemuFlashPtr: enter, Lba=0x6 Offset=0x0
          QemuFlashPtr: exit, Ret=FFE06000
          QemuFlashWrite: exit 2
          FvbProtocolWrite: exit @ 891, Status=Success, NumBytes=0x1000
          FvbProtocolWrite: enter, Lba=0x7 Offset=0x0 NumBytes=0x1000 Buffer=9C80D018
          QemuFlashWrite: enter, Lba=0x7 Offset=0x0 NumBytes=0x1000 Buffer=9C80D018
          QemuFlashPtr: enter, Lba=0x7 Offset=0x0
          QemuFlashPtr: exit, Ret=FFE07000
          QemuFlashWrite: exit 2
          FvbProtocolWrite: exit @ 891, Status=Success, NumBytes=0x1000
          FvbProtocolWrite: enter, Lba=0x8 Offset=0x0 NumBytes=0x1000 Buffer=9C80E018
          QemuFlashWrite: enter, Lba=0x8 Offset=0x0 NumBytes=0x1000 Buffer=9C80E018
          QemuFlashPtr: enter, Lba=0x8 Offset=0x0
          QemuFlashPtr: exit, Ret=FFE08000
          QemuFlashWrite: exit 2
          FvbProtocolWrite: exit @ 891, Status=Success, NumBytes=0x1000
          FvbProtocolWrite: enter, Lba=0x9 Offset=0x0 NumBytes=0x1000 Buffer=9C80F018
          QemuFlashWrite: enter, Lba=0x9 Offset=0x0 NumBytes=0x1000 Buffer=9C80F018
          QemuFlashPtr: enter, Lba=0x9 Offset=0x0
          QemuFlashPtr: exit, Ret=FFE09000
          QemuFlashWrite: exit 2
          FvbProtocolWrite: exit @ 891, Status=Success, NumBytes=0x1000
          FvbProtocolWrite: enter, Lba=0xA Offset=0x0 NumBytes=0x1000 Buffer=9C810018
          QemuFlashWrite: enter, Lba=0xA Offset=0x0 NumBytes=0x1000 Buffer=9C810018
          QemuFlashPtr: enter, Lba=0xA Offset=0x0
          QemuFlashPtr: exit, Ret=FFE0A000
          QemuFlashWrite: exit 2
          FvbProtocolWrite: exit @ 891, Status=Success, NumBytes=0x1000
          FvbProtocolWrite: enter, Lba=0xB Offset=0x0 NumBytes=0x1000 Buffer=9C811018
          QemuFlashWrite: enter, Lba=0xB Offset=0x0 NumBytes=0x1000 Buffer=9C811018
          QemuFlashPtr: enter, Lba=0xB Offset=0x0
          QemuFlashPtr: exit, Ret=FFE0B000
          QemuFlashWrite: exit 2
          FvbProtocolWrite: exit @ 891, Status=Success, NumBytes=0x1000
          FvbProtocolWrite: enter, Lba=0xC Offset=0x0 NumBytes=0x1000 Buffer=9C812018
          QemuFlashWrite: enter, Lba=0xC Offset=0x0 NumBytes=0x1000 Buffer=9C812018
          QemuFlashPtr: enter, Lba=0xC Offset=0x0
          QemuFlashPtr: exit, Ret=FFE0C000
          QemuFlashWrite: exit 2
          FvbProtocolWrite: exit @ 891, Status=Success, NumBytes=0x1000
          FvbProtocolWrite: enter, Lba=0xD Offset=0x0 NumBytes=0x1000 Buffer=9C813018
          QemuFlashWrite: enter, Lba=0xD Offset=0x0 NumBytes=0x1000 Buffer=9C813018
          QemuFlashPtr: enter, Lba=0xD Offset=0x0
          QemuFlashPtr: exit, Ret=FFE0D000
          QemuFlashWrite: exit 2
          FvbProtocolWrite: exit @ 891, Status=Success, NumBytes=0x1000
          FvbProtocolWrite: enter, Lba=0xE Offset=0x0 NumBytes=0x1000 Buffer=9C814018
          QemuFlashWrite: enter, Lba=0xE Offset=0x0 NumBytes=0x1000 Buffer=9C814018
          QemuFlashPtr: enter, Lba=0xE Offset=0x0
          QemuFlashPtr: exit, Ret=FFE0E000
          QemuFlashWrite: exit 2
          FvbProtocolWrite: exit @ 891, Status=Success, NumBytes=0x1000
          FvbProtocolWrite: enter, Lba=0xF Offset=0x0 NumBytes=0x1000 Buffer=9C815018
          QemuFlashWrite: enter, Lba=0xF Offset=0x0 NumBytes=0x1000 Buffer=9C815018
          QemuFlashPtr: enter, Lba=0xF Offset=0x0
          QemuFlashPtr: exit, Ret=FFE0F000
          QemuFlashWrite: exit 2
          FvbProtocolWrite: exit @ 891, Status=Success, NumBytes=0x1000

          //
          // Update the VALID of the working block
          //
          FtwUpdateFvState: enter, Lba=0xF Offset=0x14 NewBit=1
            FvbProtocolRead: enter, Lba=0xF Offset=0x14 NumBytes=0x1 Buffer=9F8525D7
              QemuFlashRead: enter, Lba=0xF Offset=0x14 NumBytes=0x1 Buffer=9F8525D7
                QemuFlashPtr: enter, Lba=0xF Offset=0x14
                QemuFlashPtr: exit, Ret=FFE0F014
              QemuFlashRead: exit 2
            FvbProtocolRead: exit @ 944, Status=Success, NumBytes=0x1
            FvbProtocolWrite: enter, Lba=0xF Offset=0x14 NumBytes=0x1 Buffer=9F8525D7
              QemuFlashWrite: enter, Lba=0xF Offset=0x14 NumBytes=0x1 Buffer=9F8525D7
                QemuFlashPtr: enter, Lba=0xF Offset=0x14
                QemuFlashPtr: exit, Ret=FFE0F014
              QemuFlashWrite: exit 2
            FvbProtocolWrite: exit @ 891, Status=Success, NumBytes=0x1
          FtwUpdateFvState: exit @ 824, Status=Success
        FlushSpareBlockToWorkingBlock: exit @ 765, Status=Success

        //
        // Record the DestionationComplete in record
        //
        FtwUpdateFvState: enter, Lba=0xF Offset=0xA98 NewBit=4
          FvbProtocolRead: enter, Lba=0xF Offset=0xA98 NumBytes=0x1 Buffer=9F852657
            QemuFlashRead: enter, Lba=0xF Offset=0xA98 NumBytes=0x1 Buffer=9F852657
              QemuFlashPtr: enter, Lba=0xF Offset=0xA98
              QemuFlashPtr: exit, Ret=FFE0FA98
            QemuFlashRead: exit 2
          FvbProtocolRead: exit @ 944, Status=Success, NumBytes=0x1
          FvbProtocolWrite: enter, Lba=0xF Offset=0xA98 NumBytes=0x1 Buffer=9F852657
            QemuFlashWrite: enter, Lba=0xF Offset=0xA98 NumBytes=0x1 Buffer=9F852657
              QemuFlashPtr: enter, Lba=0xF Offset=0xA98
              QemuFlashPtr: exit, Ret=FFE0FA98
            QemuFlashWrite: exit 2
          FvbProtocolWrite: exit @ 891, Status=Success, NumBytes=0x1
        FtwUpdateFvState: exit @ 824, Status=Success

        //
        // If this is the last Write in these write sequence,
        // set the complete flag of write header.
        //
        IsLastRecordOfWrites: enter
        IsLastRecordOfWrites: exit: 1
        FtwUpdateFvState: enter, Lba=0xF Offset=0xA70 NewBit=4
          FvbProtocolRead: enter, Lba=0xF Offset=0xA70 NumBytes=0x1 Buffer=9F852657
            QemuFlashRead: enter, Lba=0xF Offset=0xA70 NumBytes=0x1 Buffer=9F852657
              QemuFlashPtr: enter, Lba=0xF Offset=0xA70
              QemuFlashPtr: exit, Ret=FFE0FA70
            QemuFlashRead: exit 2
          FvbProtocolRead: exit @ 944, Status=Success, NumBytes=0x1
          FvbProtocolWrite: enter, Lba=0xF Offset=0xA70 NumBytes=0x1 Buffer=9F852657
            QemuFlashWrite: enter, Lba=0xF Offset=0xA70 NumBytes=0x1 Buffer=9F852657
              QemuFlashPtr: enter, Lba=0xF Offset=0xA70
              QemuFlashPtr: exit, Ret=FFE0FA70
            QemuFlashWrite: exit 2
          FvbProtocolWrite: exit @ 891, Status=Success, NumBytes=0x1
        FtwUpdateFvState: exit @ 824, Status=Success
      FtwWriteRecord: exit 5

      //
      // Restore spare backup buffer into spare block , if no failure happened during FtwWrite.
      //
      FtwEraseSpareBlock: enter
        FvbProtocolEraseBlocks: enter
          GetFvbInstance: enter, Instance=0x0, Global=9F7DAF18, Virtual=0
          GetFvbInstance: exit @ 229, Status=Success
          QemuFlashEraseBlock: enter, Lba=0x10
          QemuFlashPtr: enter, Lba=0x10 Offset=0x0
          QemuFlashPtr: exit, Ret=FFE10000
          QemuFlashEraseBlock: exit 2
          QemuFlashEraseBlock: enter, Lba=0x11
          QemuFlashPtr: enter, Lba=0x11 Offset=0x0
          QemuFlashPtr: exit, Ret=FFE11000
          QemuFlashEraseBlock: exit 2
          QemuFlashEraseBlock: enter, Lba=0x12
          QemuFlashPtr: enter, Lba=0x12 Offset=0x0
          QemuFlashPtr: exit, Ret=FFE12000
          QemuFlashEraseBlock: exit 2
          QemuFlashEraseBlock: enter, Lba=0x13
          QemuFlashPtr: enter, Lba=0x13 Offset=0x0
          QemuFlashPtr: exit, Ret=FFE13000
          QemuFlashEraseBlock: exit 2
          QemuFlashEraseBlock: enter, Lba=0x14
          QemuFlashPtr: enter, Lba=0x14 Offset=0x0
          QemuFlashPtr: exit, Ret=FFE14000
          QemuFlashEraseBlock: exit 2
          QemuFlashEraseBlock: enter, Lba=0x15
          QemuFlashPtr: enter, Lba=0x15 Offset=0x0
          QemuFlashPtr: exit, Ret=FFE15000
          QemuFlashEraseBlock: exit 2
          QemuFlashEraseBlock: enter, Lba=0x16
          QemuFlashPtr: enter, Lba=0x16 Offset=0x0
          QemuFlashPtr: exit, Ret=FFE16000
          QemuFlashEraseBlock: exit 2
          QemuFlashEraseBlock: enter, Lba=0x17
          QemuFlashPtr: enter, Lba=0x17 Offset=0x0
          QemuFlashPtr: exit, Ret=FFE17000
          QemuFlashEraseBlock: exit 2
          QemuFlashEraseBlock: enter, Lba=0x18
          QemuFlashPtr: enter, Lba=0x18 Offset=0x0
          QemuFlashPtr: exit, Ret=FFE18000
          QemuFlashEraseBlock: exit 2
          QemuFlashEraseBlock: enter, Lba=0x19
          QemuFlashPtr: enter, Lba=0x19 Offset=0x0
          QemuFlashPtr: exit, Ret=FFE19000
          QemuFlashEraseBlock: exit 2
          QemuFlashEraseBlock: enter, Lba=0x1A
          QemuFlashPtr: enter, Lba=0x1A Offset=0x0
          QemuFlashPtr: exit, Ret=FFE1A000
          QemuFlashEraseBlock: exit 2
          QemuFlashEraseBlock: enter, Lba=0x1B
          QemuFlashPtr: enter, Lba=0x1B Offset=0x0
          QemuFlashPtr: exit, Ret=FFE1B000
          QemuFlashEraseBlock: exit 2
          QemuFlashEraseBlock: enter, Lba=0x1C
          QemuFlashPtr: enter, Lba=0x1C Offset=0x0
          QemuFlashPtr: exit, Ret=FFE1C000
          QemuFlashEraseBlock: exit 2
          QemuFlashEraseBlock: enter, Lba=0x1D
          QemuFlashPtr: enter, Lba=0x1D Offset=0x0
          QemuFlashPtr: exit, Ret=FFE1D000
          QemuFlashEraseBlock: exit 2
          QemuFlashEraseBlock: enter, Lba=0x1E
          QemuFlashPtr: enter, Lba=0x1E Offset=0x0
          QemuFlashPtr: exit, Ret=FFE1E000
          QemuFlashEraseBlock: exit 2
          QemuFlashEraseBlock: enter, Lba=0x1F
          QemuFlashPtr: enter, Lba=0x1F Offset=0x0
          QemuFlashPtr: exit, Ret=FFE1F000
          QemuFlashEraseBlock: exit 2
        FvbProtocolEraseBlocks: exit @ 839, Status=Success
      FtwEraseSpareBlock: exit: Success

      FvbProtocolWrite: enter, Lba=0x10 Offset=0x0 NumBytes=0x1000 Buffer=9C7F5018
      QemuFlashWrite: enter, Lba=0x10 Offset=0x0 NumBytes=0x1000 Buffer=9C7F5018
      QemuFlashPtr: enter, Lba=0x10 Offset=0x0
      QemuFlashPtr: exit, Ret=FFE10000
      QemuFlashWrite: exit 2
      FvbProtocolWrite: exit @ 891, Status=Success, NumBytes=0x1000
      FvbProtocolWrite: enter, Lba=0x11 Offset=0x0 NumBytes=0x1000 Buffer=9C7F6018
      QemuFlashWrite: enter, Lba=0x11 Offset=0x0 NumBytes=0x1000 Buffer=9C7F6018
      QemuFlashPtr: enter, Lba=0x11 Offset=0x0
      QemuFlashPtr: exit, Ret=FFE11000
      QemuFlashWrite: exit 2
      FvbProtocolWrite: exit @ 891, Status=Success, NumBytes=0x1000
      FvbProtocolWrite: enter, Lba=0x12 Offset=0x0 NumBytes=0x1000 Buffer=9C7F7018
      QemuFlashWrite: enter, Lba=0x12 Offset=0x0 NumBytes=0x1000 Buffer=9C7F7018
      QemuFlashPtr: enter, Lba=0x12 Offset=0x0
      QemuFlashPtr: exit, Ret=FFE12000
      QemuFlashWrite: exit 2
      FvbProtocolWrite: exit @ 891, Status=Success, NumBytes=0x1000
      FvbProtocolWrite: enter, Lba=0x13 Offset=0x0 NumBytes=0x1000 Buffer=9C7F8018
      QemuFlashWrite: enter, Lba=0x13 Offset=0x0 NumBytes=0x1000 Buffer=9C7F8018
      QemuFlashPtr: enter, Lba=0x13 Offset=0x0
      QemuFlashPtr: exit, Ret=FFE13000
      QemuFlashWrite: exit 2
      FvbProtocolWrite: exit @ 891, Status=Success, NumBytes=0x1000
      FvbProtocolWrite: enter, Lba=0x14 Offset=0x0 NumBytes=0x1000 Buffer=9C7F9018
      QemuFlashWrite: enter, Lba=0x14 Offset=0x0 NumBytes=0x1000 Buffer=9C7F9018
      QemuFlashPtr: enter, Lba=0x14 Offset=0x0
      QemuFlashPtr: exit, Ret=FFE14000
      QemuFlashWrite: exit 2
      FvbProtocolWrite: exit @ 891, Status=Success, NumBytes=0x1000
      FvbProtocolWrite: enter, Lba=0x15 Offset=0x0 NumBytes=0x1000 Buffer=9C7FA018
      QemuFlashWrite: enter, Lba=0x15 Offset=0x0 NumBytes=0x1000 Buffer=9C7FA018
      QemuFlashPtr: enter, Lba=0x15 Offset=0x0
      QemuFlashPtr: exit, Ret=FFE15000
      QemuFlashWrite: exit 2
      FvbProtocolWrite: exit @ 891, Status=Success, NumBytes=0x1000
      FvbProtocolWrite: enter, Lba=0x16 Offset=0x0 NumBytes=0x1000 Buffer=9C7FB018
      QemuFlashWrite: enter, Lba=0x16 Offset=0x0 NumBytes=0x1000 Buffer=9C7FB018
      QemuFlashPtr: enter, Lba=0x16 Offset=0x0
      QemuFlashPtr: exit, Ret=FFE16000
      QemuFlashWrite: exit 2
      FvbProtocolWrite: exit @ 891, Status=Success, NumBytes=0x1000
      FvbProtocolWrite: enter, Lba=0x17 Offset=0x0 NumBytes=0x1000 Buffer=9C7FC018
      QemuFlashWrite: enter, Lba=0x17 Offset=0x0 NumBytes=0x1000 Buffer=9C7FC018
      QemuFlashPtr: enter, Lba=0x17 Offset=0x0
      QemuFlashPtr: exit, Ret=FFE17000
      QemuFlashWrite: exit 2
      FvbProtocolWrite: exit @ 891, Status=Success, NumBytes=0x1000
      FvbProtocolWrite: enter, Lba=0x18 Offset=0x0 NumBytes=0x1000 Buffer=9C7FD018
      QemuFlashWrite: enter, Lba=0x18 Offset=0x0 NumBytes=0x1000 Buffer=9C7FD018
      QemuFlashPtr: enter, Lba=0x18 Offset=0x0
      QemuFlashPtr: exit, Ret=FFE18000
      QemuFlashWrite: exit 2
      FvbProtocolWrite: exit @ 891, Status=Success, NumBytes=0x1000
      FvbProtocolWrite: enter, Lba=0x19 Offset=0x0 NumBytes=0x1000 Buffer=9C7FE018
      QemuFlashWrite: enter, Lba=0x19 Offset=0x0 NumBytes=0x1000 Buffer=9C7FE018
      QemuFlashPtr: enter, Lba=0x19 Offset=0x0
      QemuFlashPtr: exit, Ret=FFE19000
      QemuFlashWrite: exit 2
      FvbProtocolWrite: exit @ 891, Status=Success, NumBytes=0x1000
      FvbProtocolWrite: enter, Lba=0x1A Offset=0x0 NumBytes=0x1000 Buffer=9C7FF018
      QemuFlashWrite: enter, Lba=0x1A Offset=0x0 NumBytes=0x1000 Buffer=9C7FF018
      QemuFlashPtr: enter, Lba=0x1A Offset=0x0
      QemuFlashPtr: exit, Ret=FFE1A000
      QemuFlashWrite: exit 2
      FvbProtocolWrite: exit @ 891, Status=Success, NumBytes=0x1000
      FvbProtocolWrite: enter, Lba=0x1B Offset=0x0 NumBytes=0x1000 Buffer=9C800018
      QemuFlashWrite: enter, Lba=0x1B Offset=0x0 NumBytes=0x1000 Buffer=9C800018
      QemuFlashPtr: enter, Lba=0x1B Offset=0x0
      QemuFlashPtr: exit, Ret=FFE1B000
      QemuFlashWrite: exit 2
      FvbProtocolWrite: exit @ 891, Status=Success, NumBytes=0x1000
      FvbProtocolWrite: enter, Lba=0x1C Offset=0x0 NumBytes=0x1000 Buffer=9C801018
      QemuFlashWrite: enter, Lba=0x1C Offset=0x0 NumBytes=0x1000 Buffer=9C801018
      QemuFlashPtr: enter, Lba=0x1C Offset=0x0
      QemuFlashPtr: exit, Ret=FFE1C000
      QemuFlashWrite: exit 2
      FvbProtocolWrite: exit @ 891, Status=Success, NumBytes=0x1000
      FvbProtocolWrite: enter, Lba=0x1D Offset=0x0 NumBytes=0x1000 Buffer=9C802018
      QemuFlashWrite: enter, Lba=0x1D Offset=0x0 NumBytes=0x1000 Buffer=9C802018
      QemuFlashPtr: enter, Lba=0x1D Offset=0x0
      QemuFlashPtr: exit, Ret=FFE1D000
      QemuFlashWrite: exit 2
      FvbProtocolWrite: exit @ 891, Status=Success, NumBytes=0x1000
      FvbProtocolWrite: enter, Lba=0x1E Offset=0x0 NumBytes=0x1000 Buffer=9C803018
      QemuFlashWrite: enter, Lba=0x1E Offset=0x0 NumBytes=0x1000 Buffer=9C803018
      QemuFlashPtr: enter, Lba=0x1E Offset=0x0
      QemuFlashPtr: exit, Ret=FFE1E000
      QemuFlashWrite: exit 2
      FvbProtocolWrite: exit @ 891, Status=Success, NumBytes=0x1000

      !!! out-of-varstore write !!!
      FvbProtocolWrite: enter, Lba=0x1F Offset=0x0 NumBytes=0x1000 Buffer=9C804018
      QemuFlashWrite: enter, Lba=0x1F Offset=0x0 NumBytes=0x1000 Buffer=9C804018
      QemuFlashPtr: enter, Lba=0x1F Offset=0x0
      QemuFlashPtr: exit, Ret=FFE1F000
      QemuFlashWrite: exit 2
      FvbProtocolWrite: exit @ 891, Status=Success, NumBytes=0x1000

      //
      // All success.
      //
      Ftw: Write() success, (Lba:Offset)=(0:0x48), Length: 0xDFB8
    FtwWrite: exit 20
  FtwVariableSpace: exit @ 167, Status=Success
Xiaoyao Li Jan. 24, 2022, 6:22 a.m. UTC | #9
On 1/10/2022 7:01 PM, Gerd Hoffmann wrote:
>>> Regarding pflash itself, the read-only KVM memslot is required for it.
>>> Otherwise pflash cannot work as a "ROMD device" (= you can't flip it
>>> back and forth between ROM mode and programming (MMIO) mode).
>>
>> We don't need Read-only mode for TDVF so far. If for this purpose, is it
>> acceptable that allowing a pflash without KVM readonly memslot support if
>> read-only is not required for the specific pflash device?
> 
> In case you don't want/need persistent VARS (which strictly speaking is
> a UEFI spec violation) you should be able to go for a simple "-bios
> OVMF.fd".
> 

Gerd and Laszlo,

First, thank you for your patient explanation of how pflash works in 
QEMU and the clarification of usage of pflash with OVMF.

Below I want to share current situation of loading TDVF.

Restrictions from TDX architecture:
- Current TDX doesn't support read-only memory, i.e., cannot trap write.

- Current TDVF spec states that "In order to simplify the design, TDVF 
does not support non-volatile variables" in chapter 8.3.3


Regarding what interface should be used to load TDVF, there are three 
options:

   1) pflash: the same as how we load OVMF.

   Suppose TDVF support will finally get into OVMF, using this
   interface, it's full compatible with normal VMs. No change required
   to QEMU command line and TDVF binary is mapped to the GPA address
   right below 4G, but they are actually mapped as RAM.

   Of course, we need several hacks (special handling) in QEMU.

   Of course, they don't work as flash, the change made to it doesn't
   persist.

   2) -bios

   exploit "-bios" parameter to load TDVF. But again, read-only is not
   supported. TDVF is mapped as RAM.

   3) generic loader

   Just like what this series does. Implement specific parser in generic
   loader to load and parse TDVF as private RAM.

I'm nor sure if 1) is acceptable from your side. If not, we will go with 
option 3, since 2) doesn't make too much sense.
Gerd Hoffmann Jan. 25, 2022, 7:42 a.m. UTC | #10
> Regarding what interface should be used to load TDVF, there are three
> options:
> 
>   1) pflash: the same as how we load OVMF.
> 
>   Suppose TDVF support will finally get into OVMF, using this
>   interface, it's full compatible with normal VMs. No change required
>   to QEMU command line and TDVF binary is mapped to the GPA address
>   right below 4G, but they are actually mapped as RAM.
> 
>   Of course, we need several hacks (special handling) in QEMU.

What kind if "hack"?

>   Of course, they don't work as flash, the change made to it doesn't
>   persist.
> 
>   2) -bios
> 
>   exploit "-bios" parameter to load TDVF. But again, read-only is not
>   supported. TDVF is mapped as RAM.
> 
>   3) generic loader
> 
>   Just like what this series does. Implement specific parser in generic
>   loader to load and parse TDVF as private RAM.
> 
> I'm nor sure if 1) is acceptable from your side. If not, we will go with
> option 3, since 2) doesn't make too much sense.

Yep, Daniel (Cc'ed) tried (2) recently for SEV.  Didn't work due to
differences in -bios and -pflash reset handling.  So that option is
out I guess.

SEV uses (1), and there is some support code which you should be able to
reuse (walker for the list of guid-sections with meta-data in the ovmf
reset vector for example).

So TDX going for (1) too is probably the best option.

take care,
  Gerd
Xiaoyao Li Jan. 25, 2022, 8:22 a.m. UTC | #11
On 1/25/2022 3:42 PM, Gerd Hoffmann wrote:
>> Regarding what interface should be used to load TDVF, there are three
>> options:
>>
>>    1) pflash: the same as how we load OVMF.
>>
>>    Suppose TDVF support will finally get into OVMF, using this
>>    interface, it's full compatible with normal VMs. No change required
>>    to QEMU command line and TDVF binary is mapped to the GPA address
>>    right below 4G, but they are actually mapped as RAM.
>>
>>    Of course, we need several hacks (special handling) in QEMU.
> 
> What kind if "hack"?

For example, several pflash codes require the support of read-only 
memslot from KVM. We need to absolve it for TDX guest.

And the pflash won't work as a flash device that no write induced 
KVM_EXIT_MMIO will go to its callback.

>>    Of course, they don't work as flash, the change made to it doesn't
>>    persist.
>>
>>    2) -bios
>>
>>    exploit "-bios" parameter to load TDVF. But again, read-only is not
>>    supported. TDVF is mapped as RAM.
>>
>>    3) generic loader
>>
>>    Just like what this series does. Implement specific parser in generic
>>    loader to load and parse TDVF as private RAM.
>>
>> I'm nor sure if 1) is acceptable from your side. If not, we will go with
>> option 3, since 2) doesn't make too much sense.
> 
> Yep, Daniel (Cc'ed) tried (2) recently for SEV.  Didn't work due to
> differences in -bios and -pflash reset handling.  So that option is
> out I guess.
> 
> SEV uses (1), and there is some support code which you should be able to
> reuse (walker for the list of guid-sections with meta-data in the ovmf
> reset vector for example).
> 
> So TDX going for (1) too is probably the best option.

Yes. With option 1), it's friendly to user that no command change.
And also compatible for future TDX/TDVF when non-volatile variable is 
supported.

Next version of this series will go with option 1). Let's wait and see 
if the real implementation is acceptable or not.

> take care,
>    Gerd
>
diff mbox series

Patch

diff --git a/hw/core/generic-loader.c b/hw/core/generic-loader.c
index d14f932eea..ee2f49b47a 100644
--- a/hw/core/generic-loader.c
+++ b/hw/core/generic-loader.c
@@ -34,6 +34,7 @@ 
 #include "hw/core/cpu.h"
 #include "sysemu/dma.h"
 #include "sysemu/reset.h"
+#include "sysemu/tdvf.h"
 #include "hw/boards.h"
 #include "hw/loader.h"
 #include "hw/qdev-properties.h"
@@ -147,6 +148,10 @@  static void generic_loader_realize(DeviceState *dev, Error **errp)
                                       as);
             }
 
+            if (size < 0) {
+                size = load_tdvf(s->file);
+            }
+
             if (size < 0) {
                 size = load_targphys_hex_as(s->file, &entry, as);
             }
diff --git a/hw/core/meson.build b/hw/core/meson.build
index 18f44fb7c2..ec943debf1 100644
--- a/hw/core/meson.build
+++ b/hw/core/meson.build
@@ -24,6 +24,9 @@  common_ss.add(when: 'CONFIG_REGISTER', if_true: files('register.c'))
 common_ss.add(when: 'CONFIG_SPLIT_IRQ', if_true: files('split-irq.c'))
 common_ss.add(when: 'CONFIG_XILINX_AXI', if_true: files('stream.c'))
 
+common_ss.add(when: 'CONFIG_TDX', if_false: files('tdvf-stub.c'))
+common_ss.add(when: 'CONFIG_ALL', if_true: files('tdvf-stub.c'))
+
 softmmu_ss.add(files(
   'cpu-sysemu.c',
   'fw-path-provider.c',
diff --git a/hw/core/tdvf-stub.c b/hw/core/tdvf-stub.c
new file mode 100644
index 0000000000..5f2586dd70
--- /dev/null
+++ b/hw/core/tdvf-stub.c
@@ -0,0 +1,6 @@ 
+#include "sysemu/tdvf.h"
+
+int load_tdvf(const char *filename)
+{
+    return -1;
+}
diff --git a/hw/i386/meson.build b/hw/i386/meson.build
index e5d109f5c6..945e805525 100644
--- a/hw/i386/meson.build
+++ b/hw/i386/meson.build
@@ -24,6 +24,7 @@  i386_ss.add(when: 'CONFIG_PC', if_true: files(
   'pc_sysfw.c',
   'acpi-build.c',
   'port92.c'))
+i386_ss.add(when: 'CONFIG_TDX', if_true: files('tdvf.c'))
 
 subdir('kvm')
 subdir('xen')
diff --git a/hw/i386/tdvf.c b/hw/i386/tdvf.c
new file mode 100644
index 0000000000..9b0065d656
--- /dev/null
+++ b/hw/i386/tdvf.c
@@ -0,0 +1,312 @@ 
+/*
+ * SPDX-License-Identifier: GPL-2.0-or-later
+
+ * Copyright (c) 2020 Intel Corporation
+ * Author: Isaku Yamahata <isaku.yamahata at gmail.com>
+ *                        <isaku.yamahata at intel.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "qemu/osdep.h"
+#include "qapi/error.h"
+#include "qemu/error-report.h"
+#include "qemu/units.h"
+#include "cpu.h"
+#include "exec/hwaddr.h"
+#include "hw/boards.h"
+#include "hw/i386/e820_memory_layout.h"
+#include "hw/i386/tdvf.h"
+#include "hw/i386/x86.h"
+#include "hw/loader.h"
+#include "sysemu/tdx.h"
+#include "sysemu/tdvf.h"
+#include "target/i386/kvm/tdx.h"
+
+static void tdvf_init_ram_memory(MachineState *ms, TdxFirmwareEntry *entry)
+{
+    void *ram_ptr = memory_region_get_ram_ptr(ms->ram);
+    X86MachineState *x86ms = X86_MACHINE(ms);
+
+    if (entry->type == TDVF_SECTION_TYPE_BFV ||
+        entry->type == TDVF_SECTION_TYPE_CFV) {
+            error_report("TDVF type %u addr 0x%" PRIx64 " in RAM (disallowed)",
+                         entry->type, entry->address);
+            exit(1);
+    }
+
+    if (entry->address < 4 * GiB) {
+        entry->mem_ptr = ram_ptr + entry->address;
+    } else {
+        /*
+         * If TDVF temp memory describe in TDVF metadata lays in RAM, reserve
+         * the region property.
+         */
+        if (entry->address >= 4 * GiB + x86ms->above_4g_mem_size ||
+            entry->address + entry->size >= 4 * GiB + x86ms->above_4g_mem_size) {
+            error_report("TDVF type %u address 0x%" PRIx64 " size 0x%" PRIx64
+                         " above high memory",
+                         entry->type, entry->address, entry->size);
+            exit(1);
+        }
+        entry->mem_ptr = ram_ptr + x86ms->below_4g_mem_size +
+                         entry->address - 4 * GiB;
+    }
+    e820_change_type(entry->address, entry->size, E820_RESERVED);
+}
+
+static void tdvf_init_bios_memory(int fd, const char *filename,
+                                  TdxFirmwareEntry *entry)
+{
+    static unsigned int nr_cfv;
+    static unsigned int nr_tmp;
+
+    MemoryRegion *system_memory = get_system_memory();
+    Error *err = NULL;
+    const char *name;
+
+    /* Error out if the section might overlap other structures. */
+    if (entry->address < 4 * GiB - 16 * MiB) {
+        error_report("TDVF type %u address 0x%" PRIx64 " in PCI hole",
+                        entry->type, entry->address);
+        exit(1);
+    }
+
+    if (entry->type == TDVF_SECTION_TYPE_BFV) {
+        name = g_strdup("tdvf.bfv");
+    } else if (entry->type == TDVF_SECTION_TYPE_CFV) {
+        name = g_strdup_printf("tdvf.cfv%u", nr_cfv++);
+    } else if (entry->type == TDVF_SECTION_TYPE_TD_HOB) {
+        name = g_strdup("tdvf.hob");
+    } else if (entry->type == TDVF_SECTION_TYPE_TEMP_MEM) {
+        name = g_strdup_printf("tdvf.tmp%u", nr_tmp++);
+    } else {
+        error_report("TDVF type %u unknown/unsupported", entry->type);
+        exit(1);
+    }
+    entry->mr = g_malloc(sizeof(*entry->mr));
+
+    memory_region_init_ram(entry->mr, NULL, name, entry->size, &err);
+    if (err) {
+        error_report_err(err);
+        exit(1);
+    }
+
+    entry->mem_ptr = memory_region_get_ram_ptr(entry->mr);
+    if (entry->data_len) {
+        /*
+         * The memory_region api doesn't allow partial file mapping, create
+         * ram and copy the contents
+         */
+        if (lseek(fd, entry->data_offset, SEEK_SET) != entry->data_offset) {
+            error_report("can't seek to 0x%x %s", entry->data_offset, filename);
+            exit(1);
+        }
+        if (read(fd, entry->mem_ptr, entry->data_len) != entry->data_len) {
+            error_report("can't read 0x%x %s", entry->data_len, filename);
+            exit(1);
+        }
+    }
+
+    memory_region_add_subregion(system_memory, entry->address, entry->mr);
+
+    if (entry->type == TDVF_SECTION_TYPE_TEMP_MEM) {
+        e820_add_entry(entry->address, entry->size, E820_RESERVED);
+    }
+}
+
+static void tdvf_parse_section_entry(TdxFirmwareEntry *entry,
+                                     const TdvfSectionEntry *src,
+                                     uint64_t file_size)
+{
+    entry->data_offset = le32_to_cpu(src->DataOffset);
+    entry->data_len = le32_to_cpu(src->RawDataSize);
+    entry->address = le64_to_cpu(src->MemoryAddress);
+    entry->size = le64_to_cpu(src->MemoryDataSize);
+    entry->type = le32_to_cpu(src->Type);
+    entry->attributes = le32_to_cpu(src->Attributes);
+
+    /* sanity check */
+    if (entry->data_offset + entry->data_len > file_size) {
+        error_report("too large section: DataOffset 0x%x RawDataSize 0x%x",
+                     entry->data_offset, entry->data_len);
+        exit(1);
+    }
+    if (entry->size < entry->data_len) {
+        error_report("broken metadata RawDataSize 0x%x MemoryDataSize 0x%lx",
+                     entry->data_len, entry->size);
+        exit(1);
+    }
+    if (!QEMU_IS_ALIGNED(entry->address, TARGET_PAGE_SIZE)) {
+        error_report("MemoryAddress 0x%lx not page aligned", entry->address);
+        exit(1);
+    }
+    if (!QEMU_IS_ALIGNED(entry->size, TARGET_PAGE_SIZE)) {
+        error_report("MemoryDataSize 0x%lx not page aligned", entry->size);
+        exit(1);
+    }
+    if (entry->type == TDVF_SECTION_TYPE_TD_HOB ||
+        entry->type == TDVF_SECTION_TYPE_TEMP_MEM) {
+        if (entry->data_len > 0) {
+            error_report("%d section with RawDataSize 0x%x > 0",
+                         entry->type, entry->data_len);
+            exit(1);
+        }
+    }
+}
+
+static void tdvf_parse_metadata_entries(int fd, TdxFirmware *fw,
+                                        TdvfMetadata *metadata)
+{
+
+    TdvfSectionEntry *sections;
+    ssize_t entries_size;
+    uint32_t len, i;
+
+    fw->nr_entries = le32_to_cpu(metadata->NumberOfSectionEntries);
+    if (fw->nr_entries < 2) {
+        error_report("Invalid number of entries (%u) in TDVF", fw->nr_entries);
+        exit(1);
+    }
+
+    len = le32_to_cpu(metadata->Length);
+    entries_size = fw->nr_entries * sizeof(TdvfSectionEntry);
+    if (len != sizeof(*metadata) + entries_size) {
+        error_report("TDVF metadata len (0x%x) mismatch, expected (0x%x)",
+                     len, (uint32_t)(sizeof(*metadata) + entries_size));
+        exit(1);
+    }
+
+    fw->entries = g_new(TdxFirmwareEntry, fw->nr_entries);
+    sections = g_new(TdvfSectionEntry, fw->nr_entries);
+
+    if (read(fd, sections, entries_size) != entries_size)  {
+        error_report("Failed to read TDVF section entries");
+        exit(1);
+    }
+
+    for (i = 0; i < fw->nr_entries; i++) {
+        tdvf_parse_section_entry(&fw->entries[i], &sections[i], fw->file_size);
+    }
+    g_free(sections);
+}
+
+static int tdvf_parse_metadata_header(int fd, TdvfMetadata *metadata)
+{
+    uint32_t offset;
+    int64_t size;
+
+    size = lseek(fd, 0, SEEK_END);
+    if (size < TDVF_METDATA_OFFSET_FROM_END || (uint32_t)size != size) {
+        return -1;
+    }
+
+    /* Chase the metadata pointer to get to the actual metadata. */
+    offset = size - TDVF_METDATA_OFFSET_FROM_END;
+    if (lseek(fd, offset, SEEK_SET) != offset) {
+        return -1;
+    }
+    if (read(fd, &offset, sizeof(offset)) != sizeof(offset)) {
+        return -1;
+    }
+
+    offset = le32_to_cpu(offset);
+    if (offset > size - sizeof(*metadata)) {
+        return -1;
+    }
+
+    /* Pointer to the metadata has been resolved, read the actual metadata. */
+    if (lseek(fd, offset, SEEK_SET) != offset) {
+        return -1;
+    }
+    if (read(fd, metadata, sizeof(*metadata)) != sizeof(*metadata)) {
+        return -1;
+    }
+
+    /* Finally, verify the signature to determine if this is a TDVF image. */
+    if (metadata->Signature[0] != 'T' || metadata->Signature[1] != 'D' ||
+        metadata->Signature[2] != 'V' || metadata->Signature[3] != 'F') {
+        return -1;
+    }
+
+    /* Sanity check that the TDVF doesn't overlap its own metadata. */
+    metadata->Length = le32_to_cpu(metadata->Length);
+    if (metadata->Length > size - offset) {
+        return -1;
+    }
+
+    /* Only version 1 is supported/defined. */
+    metadata->Version = le32_to_cpu(metadata->Version);
+    if (metadata->Version != 1) {
+        return -1;
+    }
+
+    return size;
+}
+
+int load_tdvf(const char *filename)
+{
+    MachineState *ms = MACHINE(qdev_get_machine());
+    X86MachineState *x86ms = X86_MACHINE(ms);
+    TdxFirmwareEntry *entry;
+    TdvfMetadata metadata;
+    TdxGuest *tdx;
+    TdxFirmware *fw;
+    int64_t size;
+    int fd;
+
+    if (!kvm_enabled()) {
+        return -1;
+    }
+
+    tdx = (void *)object_dynamic_cast(OBJECT(ms->cgs), TYPE_TDX_GUEST);
+    if (!tdx) {
+        return -1;
+    }
+
+    fd = open(filename, O_RDONLY | O_BINARY);
+    if (fd < 0) {
+        return -1;
+    }
+
+    size = tdvf_parse_metadata_header(fd, &metadata);
+    if (size < 0) {
+        close(fd);
+        return -1;
+    }
+
+    /* Error out if the user is attempting to load multiple TDVFs. */
+    fw = &tdx->fw;
+    if (fw->file_name) {
+        error_report("tdvf can only be specified once.");
+        exit(1);
+    }
+
+    fw->file_size = size;
+    fw->file_name = g_strdup(filename);
+
+    tdvf_parse_metadata_entries(fd, fw, &metadata);
+
+    for_each_fw_entry(fw, entry) {
+        if (entry->address < x86ms->below_4g_mem_size ||
+            entry->address > 4 * GiB) {
+            tdvf_init_ram_memory(ms, entry);
+        } else {
+            tdvf_init_bios_memory(fd, filename, entry);
+        }
+    }
+
+    close(fd);
+    return 0;
+}
diff --git a/include/sysemu/tdvf.h b/include/sysemu/tdvf.h
new file mode 100644
index 0000000000..0cf085e3ae
--- /dev/null
+++ b/include/sysemu/tdvf.h
@@ -0,0 +1,6 @@ 
+#ifndef QEMU_TDVF_H
+#define QEMU_TDVF_H
+
+int load_tdvf(const char *filename);
+
+#endif
diff --git a/target/i386/kvm/tdx.h b/target/i386/kvm/tdx.h
index 844d24aade..2fed27b3fb 100644
--- a/target/i386/kvm/tdx.h
+++ b/target/i386/kvm/tdx.h
@@ -5,6 +5,30 @@ 
 #include "qapi/error.h"
 #include "exec/confidential-guest-support.h"
 
+typedef struct TdxFirmwareEntry {
+    uint32_t data_offset;
+    uint32_t data_len;
+    uint64_t address;
+    uint64_t size;
+    uint32_t type;
+    uint32_t attributes;
+
+    MemoryRegion *mr;
+    void *mem_ptr;
+} TdxFirmwareEntry;
+
+typedef struct TdxFirmware {
+    const char *file_name;
+    uint64_t file_size;
+
+    /* metadata */
+    uint32_t nr_entries;
+    TdxFirmwareEntry *entries;
+} TdxFirmware;
+
+#define for_each_fw_entry(fw, e)                                        \
+    for (e = (fw)->entries; e != (fw)->entries + (fw)->nr_entries; e++)
+
 #define TYPE_TDX_GUEST "tdx-guest"
 #define TDX_GUEST(obj)     \
     OBJECT_CHECK(TdxGuest, (obj), TYPE_TDX_GUEST)
@@ -20,6 +44,8 @@  typedef struct TdxGuest {
 
     bool initialized;
     bool debug;
+
+    TdxFirmware fw;
 } TdxGuest;
 
 int tdx_kvm_init(ConfidentialGuestSupport *cgs, Error **errp);