UpdateCapsule() implementation

Description

This page is intended to contain development information of UpdateCapsule() functionality implementation both in Linux kernel and UEFI (edk2).

JIRA CARD: https://cards.linaro.org/browse/CARD-1241

Scope:

This task doesn't involve implementation of the whole firmware update procedure (or any other use cases). So next stuff should be done:

  1. Add code in kernel to make it possible to use UpdateCapsule() and other related functions

  2. Add code in UEFI (edk2) to make it possible to use UpdateCapsule() and other related functions

Issues

1. UEFI Runtime Services version incorrectly passed to the kernel. In this code (drivers/firmware/efi/runtime.c):

static efi_status_t virt_efi_update_capsule(efi_capsule_header_t **capsules,
                                            unsigned long count,
                                            unsigned long sg_list)
{
        if (efi.runtime_version < EFI_2_00_SYSTEM_TABLE_REVISION)
                return EFI_UNSUPPORTED;

        return efi_call_virt(update_capsule, capsules, count, sg_list);
}

Condition is false, because efi.runtime_version = 0.

From the other side, looking to edk2 code, one can see (in MdePkg/Include/Uefi/UefiSpec.h):

#define EFI_2_40_SYSTEM_TABLE_REVISION  ((2 << 16) | (40))
#define EFI_SYSTEM_TABLE_REVISION       EFI_2_40_SYSTEM_TABLE_REVISION
#define EFI_SPECIFICATION_VERSION       EFI_SYSTEM_TABLE_REVISION
#define EFI_RUNTIME_SERVICES_REVISION   EFI_SPECIFICATION_VERSION

i.e. EFI_RUNTIME_SERVICES_REVISION = ((2 << 16) | (40)).

So obviously efi.runtime_version is not being passed from UEFI to kernel somehow.

Complementary 1:

If we look into efi_entry() (drivers/firmware/efi/arm-stub.c), there is "sys_table->hdr.revision" variable which is equal to "0x20028", corresponding to UEFI version 2.40. So this value somehow is not being passed to "efi" global data structure later on.

Complementary 2:

Patch fixes this issue was sent to upstream: http://lists.infradead.org/pipermail/linux-arm-kernel/2014-August/279477.html

Patch upstreamed: https://git.kernel.org/cgit/linux/kernel/git/mfleming/efi.git/commit/?h=urgent&id=6a7519e81321343165f89abb8b616df186d3e57a

Automated testing notes

Kernel

Here is instructions how to patch and build kernel.

Obtain kernel sources:

$ git clone https://git.linaro.org/people/semen.protsenko/leg-kernel.git
$ cd leg-kernel

Build kernel:

$ make ARCH=arm64 defconfig
$ make Image -j4
$ make fvp-base-gicv2-psci.dtb

Output files are (kernel image and built device tree file):

 - arch/arm64/boot/Image
 - arch/arm64/boot/dts/fvp-base-gicv2-psci.dtb

MMC image

Now that we have kernel image and built device tree, we can create MMC image. I'm using MMC image with next structure of first partition:

├── dtb
│   └── fvp.dtb
├── EFI
│   └── BOOT
│       └── Image
└── startup.nsh

where "startup.nsh" file has next content:

Image dtb=dtb/fvp.dtb console=ttyAMA0,38400n8 debug user_debug=31 loglevel=9 uefi_debug root=/dev/vda2 rootfstype=ext4 rootwait rw

If you need some instructions how to create such an image, please check this link: https://wiki.linaro.org/LEG/Engineering/Kernel/ACPI/AcpiOnArmV8FvpUefi#Creating_MMC_image

UEFI

Here is instructions how to patch and build UEFI.

Obtain UEFI source code with debug patches:

$ git clone https://git.linaro.org/people/semen.protsenko/linaro-edk2.git
$ cd linaro-edk2

Build UEFI:

$ export CROSS_COMPILE=aarch64-linux-gnu-
$ uefi-build.sh fvp

Output files can be found this way:

$ find Build/ -name '*.fd'

Starting FVP model

One can use script like that to run FVP model:

fvp_ver=5311
fvp_model=$models_dir/FVP_Base_AEMv8A-AEMv8A_${fvp_ver}/models/Linux64_GCC-4.1/FVP_Base_AEMv8A-AEMv8A
fvp_mmc=$images_dir/update_capsule.img
fvp_uefi=$repos_dir/linaro-edk2/Build/ArmVExpress-FVP-AArch64/RELEASE_GCC48/FV/FVP_AARCH64_EFI.fd
fvp_uefi_sec=bl1.bin

$fvp_model                                                              \
        -V                                                              \
        -C bp.flashloader0.fname=$fvp_uefi                              \
        -C pctl.startup=0.0.0.0                                         \
        -C bp.secure_memory=0                                           \
        -C cluster0.NUM_CORES=4                                         \
        -C cluster1.NUM_CORES=4                                         \
        -C bp.pl011_uart0.untimed_fifos=1                               \
        -C bp.mmc.p_mmc_file=$fvp_mmc                                   \
        -C bp.virtioblockdevice.image_path=$fvp_mmc                     \
        -C bp.secureflashloader.fname=$fvp_uefi_sec                     \
        -C bp.hostbridge.userNetworking=true                            \
        -C bp.smsc_91c111.enabled=true                                  \
        -C bp.hostbridge.userNetPorts="8022=22"                         \
        -C cache_state_modelled=0

Actual testing

To run UpdateCapsule() runtime service one can do:

# cat /sys/test_uefi_rs/tuc

Ouput must be exactly like this:

### CapsuleCount: 2
### Content of capsule #0
### Guid:
111
222
333
10
11
12
13
14
15
16
17

### Content of capsule #1
### Guid:
111
222
333
20
21
22
23
24
25
26
27

This should be automated in LAVA test. LAVA test should compare output of this command with output listed above. Test passed if comparison successful. Otherwise test failed.

Possible test written in bash might look like:

do_test() {
        cat /sys/test_uefi_rs/tuc >test_out.txt
        out=$(diff test_ok.txt test_out.txt)
        if [ -n "$out" ]; then
                echo "[EE] Test failed!" >&2
                cat test_out.txt
                rm -f test_out.txt
                exit 1
        fi

        echo "[II] Test successful!"
        rm -f test_out.txt
}

while :; do
        do_test
done

where "test_out.txt" file should contain correct output of "/sys/test_uefi_rs/tuc" file (listed above).

UEFI debug

In order to debug UEFI code, one can use my patches for printing to UART:

[1] https://review.linaro.org/#/c/3079/

[2] https://review.linaro.org/#/c/3080/

[3] https://review.linaro.org/#/c/3081/

[4] https://review.linaro.org/#/c/3082/

UpdateCapsule test with Matt's patches

Kernel

First, apply next patches to leg-kernel:

Then build this kernel as usual and repack your MMC image with new kernel image.

UEFI

Clone and build "linaro-edk" repo using "linaro-topic-capsule" branch.

Testing

Run FVP model with UEFI and kernel you've built. Now execute next command:

# cat /sys/test_uefi_rs/tuc

It should print out next text to your console:

### CapsuleCount: 1
### Content of capsule #0
### Guid:
111
222
333
10
11
12
13
14
15
16
17

How it works

1. Kernel patches you've applied earlier add kernel module allows you to execute UpdateCapsule() runtime service (using Matt's API) from userspace (using sysfs capabilities). This module sends to UEFI (using UpdateCapsule() RS) certain params.

2. UEFI topic branch you was using contains patches that print obtained UpdateCapsule() parameters to memory shared between UEFI and kernel.

3. Once UpdateCapsule() RS finished, kernel prints out shared memory content to the screen.

Why not printing params to serial console via UART directly from UEFI? Well, this way we wouldn't be able to use that output in kernel terminal (like redirecting this output to some file).

[1] UEFI Specification (see chapter 7.5.3)

[2] EFI Capsule Specification

[3] FVP FastModel 5311 manual

  • (can be found at "FVP_Base_AEMv8A-AEMv8A_5311/doc/DUI0575E_fast_model_ve_rtsm_rm.pdf")
    full name: "Real-Time System Models Version 1.4: VE and MPS RTSM Reference Guide"

[4] PL011 UART TRM

  • full name: "PrimeCell UART (PL011): Technical Reference Manual"

LEG/ServerArchitecture/UEFI/uefi_update_capsule (last modified 2017-08-17 12:13:12)