On 04/09/18 10:49, Daniel P. Berrangé wrote:
On Sat, Apr 07, 2018 at 02:01:17AM +0200, Laszlo Ersek wrote:
> Add a schema that describes the properties of virtual machine firmware.
>
> Each firmware executable installed on a host system should come with a
> JSON file that conforms to this schema, and informs the management
> applications about the firmware's properties.
>
> In addition, a configuration directory with symlinks to the JSON files
> should exist, with the symlinks carefully named to reflect a priority
> order. Management applications can then search this directory in priority
> order for the first firmware executable that satisfies their search
> criteria. The found JSON file provides the management layer with domain
> configuration bits that are required to run the firmware binary.
>
> Cc: "Daniel P. Berrange" <berrange(a)redhat.com>
> Cc: Alexander Graf <agraf(a)suse.de>
> Cc: Ard Biesheuvel <ard.biesheuvel(a)linaro.org>
> Cc: David Gibson <dgibson(a)redhat.com>
> Cc: Eric Blake <eblake(a)redhat.com>
> Cc: Gary Ching-Pang Lin <glin(a)suse.com>
> Cc: Gerd Hoffmann <kraxel(a)redhat.com>
> Cc: Kashyap Chamarthy <kchamart(a)redhat.com>
> Cc: Markus Armbruster <armbru(a)redhat.com>
> Cc: Michael Roth <mdroth(a)linux.vnet.ibm.com>
> Cc: Michal Privoznik <mprivozn(a)redhat.com>
> Cc: Peter Krempa <pkrempa(a)redhat.com>
> Cc: Peter Maydell <peter.maydell(a)linaro.org>
> Cc: Thomas Huth <thuth(a)redhat.com>
> Signed-off-by: Laszlo Ersek <lersek(a)redhat.com>
> ---
>
> Notes:
> Folks on the CC list, please try to see if the suggested schema is
> flexible enough to describe the virtual firmware(s) that you are
> familiar with. Thanks!
>
> Makefile | 9 ++
> Makefile.objs | 4 +
> qapi/firmware.json | 343 ++++++++++++++++++++++++++++++++++++++++++++++++++
> qapi/qapi-schema.json | 1 +
> qmp.c | 5 +
> .gitignore | 4 +
> 6 files changed, 366 insertions(+)
> create mode 100644 qapi/firmware.json
>
> diff --git a/qapi/firmware.json b/qapi/firmware.json
> new file mode 100644
> index 000000000000..f267240f44dd
> --- /dev/null
> +++ b/qapi/firmware.json
> @@ -0,0 +1,343 @@
> +# -*- Mode: Python -*-
> +
> +##
> +# = Firmware
> +##
> +
> +##
> +# @FirmwareDevice:
> +#
> +# Defines the device types that a firmware file can be mapped into.
> +#
> +# @memory: The firmware file is to be mapped into memory.
> +#
> +# @kernel: The firmware file is to be loaded like a Linux kernel. This is
> +# similar to @memory but may imply additional processing that is
> +# specific to the target architecture.
> +#
> +# @flash: The firmware file is to be mapped into a pflash chip.
> +#
> +# Since: 2.13
> +##
> +{ 'enum' : 'FirmwareDevice',
> + 'data' : [ 'memory', 'kernel', 'flash' ] }
> +
> +##
> +# @FirmwareAccess:
> +#
> +# Defines the possible permissions for a given access mode to a device that
> +# maps a firmware file.
> +#
> +# @denied: The access is denied.
> +#
> +# @permitted: The access is permitted.
> +#
> +# @restricted-to-secure-context: The access is permitted for guest code that
> +# runs in a secure context; otherwise the access
> +# is denied. The definition of "secure
context"
> +# is specific to the target architecture.
> +#
> +# Since: 2.13
> +##
> +{ 'enum' : 'FirmwareAccess',
> + 'data' : [ 'denied', 'permitted',
'restricted-to-secure-context' ] }
I'm not really understanding the purpose of this - what does it map to
on the command line ?
That's difficult to answer generally, because -bios and -kernel have
different meanings per board type. So I didn't aim at command line
switches here; instead I tried to capture where and how the firmware
wants to "end up" in the virtual hardware. How that maps to a particular
board is a separate question.
For example, OVMF can be loaded in a multitude of ways:
(1) "OVMF.fd" (a unified image that contains an executable and a live
variable store too) can be loaded with "-bios". This will place the full
image into ROM (that is FirmwareDevice=memory, read and exec: permitted,
write: denied). This will not provide a spec-compatible UEFI variable
service to the guest, but many people use OVMF like this. The libvirt
domain XML can accommodate this case:
<loader type='rom'>OVMF.fd</loader>
(2) "OVMF.fd" can be loaded into a single pflash chip (single pflash
drive, read/write). The command line switch is "-drive
if=pflash,format=raw,file=OVMF.fd,unit=0,readonly=off". This gives the
guest a spec-compliant UEFI variable service; however, the variable
store is inseparable from the firmware binary, and upgrading the latter
without losing the former is not possible, from a packaging perspective.
This maps to FirmwareDevice=flash, with all of
read/write/exec=permitted. Libvirt can describe this too in the domain XML:
<loader type='pflash' readonly='no'>OVMF.fd</loader>
(3) Now we're coming to the split image, namely OVMF_CODE.fd, and a
*copy of* the OVMF_VARS.fd template. This is mapped with
-drive if=pflash,format=raw,file=OVMF_CODE.fd,unit=0,readonly=on \
-drive if=pflash,format=raw,file=varstore.fd,unit=1,readonly=off
To the guest, it looks like (2), but on the host side, it allows for
centralized firmware binary upgrades, while preserving the domains'
variable stores. This maps to (a) the system firwmare with
FirmwareDevice=flash, read/exec: permitted, and write: denied; and (b)
one nvramslot with FirmwareDevice=flash, and read/exec/write: permitted.
The domain XML fragment is
<loader type='pflash'
readonly='yes'>OVMF_CODE.fd</loader>
<nvram>varstore.fd</nvram>
(4) An extension of (3) is when write accesses to the variable store are
restricted to code that runs in SMM. The QEMU options are:
-drive if=pflash,format=raw,file=OVMF_CODE.fd,unit=0,readonly=on \
-drive if=pflash,format=raw,file=varstore.fd,unit=1,readonly=off \
-machine <pc-q35-2.4+>,smm=on \
-global driver=cfi.pflash01,property=secure,value=on \
This maps to (a) the system firwmare with FirmwareDevice=flash,
read/exec: permitted, and write: denied; and (b) one nvramslot with
FirmwareDevice=flash, read/exec: permitted, write:
restricted-to-secure-context.
The domain XML fragment is
<os>
<type arch='x86_64' machine='pc-q35-2.4+'>hvm</type>
<loader type='pflash' readonly='yes'
secure='yes'>OVMF_CODE.fd</loader>
<nvram>varstore.fd</nvram>
</os>
<features>
<smm state='on'/>
</features>
On other target arches, "restricted-to-secure-context" may be defined
differently from "x86 SMM"; for example on aarch64, TrustZone / "secure
world" exist, as far as I know. (The edk2 project doesn't have any
upstream core code for supporting that though.) The command line
switches could be quite different.
So, the schema intends to describe the mapping that the firmware expects
from the board. How that is implemented on the QEMU command line is left
as an exercise to ... libvirtd. :)
> +
> +##
> +# @FirmwareMapping:
> +#
> +# Collects the mapping device type and the access permissions to that device
> +# for system firmware and for NVRAM slots.
> +#
> +# @device: The system firmware or the NVRAM slot must reside in a device of
> +# this type.
> +#
> +# @read: Permission for the guest to read the device that maps the firmware
> +# file. If the field is missing, @permitted is assumed.
> +#
> +# @write: Permission for the guest to write the device that maps the firmware
> +# file. If the field is missing, @permitted is assumed.
> +#
> +# @execute: Permission for the guest to execute code from the device that maps
> +# the firmware file. If the field is missing, @permitted is assumed.
> +#
> +# Since: 2.13
> +##
> +{ 'struct' : 'FirmwareMapping',
> + 'data' : { 'device' : 'FirmwareDevice',
> + '*read' : 'FirmwareAccess',
> + '*write' : 'FirmwareAccess',
> + '*execute' : 'FirmwareAccess' } }
Again, what this this map to on the command line ?
> +##
> +# @FirmwareFile:
> +#
> +# Gathers the common traits of system firmware executables and NVRAM templates.
> +#
> +# @pathname: absolute pathname of the firmware file on the host filesystem
> +#
> +# @description: human-readable description of the firmware file
> +#
> +# @tags: a list of machine-readable strings providing additional information
This makes it look like this information is something applications should be
using when setting up firmwares, which is definitely not what we want. Lets
rename this
"@build_options: arbitrary list of firmware specific build options, for
informative purposes only. Applications should not attempt
to interpret / assign meaning to these options"
Hmmm, I agree with you half-way here. I'm not saying that applications
*should* consult the tags, but they might want let the user express a
search condition for the tags. Near the end of the RFC, there's an
example JSON where the sole nvramslot advertizes two variable store
templates (both of which are compatible with the firmware and the
nvramslot from a technical POV). However, one varstore template is
logically empty, and the other varstore template has the MS certificates
pre-enrolled and Secure Boot enabled. If the new domain is created with
an OS installer that is not signed at all, the choice of varstore
template can make a big difference. And, the way I could distinguish
these two templates from each other (in a machine readable format) is
the "tags" list -- pls. search the RFC for the string
'"mscerts"'.
I figured either the apps or the user might want to decide about the
varstore template.
> +# @format: If the @FirmwareDevice that this @FirmwareFile is mapped into is
> +# @flash, then @format describes the block format of the drive that
> +# backs the device. Otherwise, this field should be 'raw' or
absent.
> +# If the field is missing, 'raw' is assumed.
Why does the format needed ?
If the firmware is to be loaded via a block device driver, then it should
not matter what format is used. If the mgmt app wants to take the firmware
file and convert it to qcow2 format that's fine - the code loading this
in QEMU will still see raw content.
The pflash device model has a backing drive, and the block format for
that drive can be qcow2 or raw (or other things), in theory anyway
(libvirt only supports raw now). Block format probing is discouraged, so
if the firmware package (on the host) provides the firmware binary
and/or the variable store template in qcow2 block format, then the tools
should know about that without probing.
Indeed it does not matter to the guest (the block format is a backend
property), but it's a detail that the firmware package should expose to
the host toolstack.
> +#
> +# Since: 2.13
> +##
> +{ 'struct' : 'FirmwareFile',
> + 'data' : { 'pathname' : 'str',
> + '*description' : 'str',
> + '*tags' : [ 'str' ],
> + '*format' : 'str' } }
> +
> +##
> +# @NVRAMSlot:
> +#
> +# Defines the mapping properties of an NVRAM slot, and associates compatible
> +# NVRAM templates with the NVRAM slot.
> +#
> +# @slot-id: The numeric identifier of the NVRAM slot. The interpretation of
> +# @slot-id is specific to the target architecture and the chosen
> +# system firmware.
What does this correspond to at the CLI level ? Is this the -drive unit=NN
option when loading via a block device ?
Yes, for i440fx and q35 with pflash, such a mapping makes sense. For
other target arches and board types, the mapping could be different (in
Thomas's example a uint64_t slot-id isn't even expressive enough).
Following your earlier advice, I tried to accommodate arches and boards
that I know nothing about :) , so I added the field mainly as a
placeholder, and to tell apart nvramslots. (In fact, nvram-slots,
*plural*, is already speculative generality, because I know of no board
that takes more than one nvram / pflash chip. But, turning that field
into a list wasn't too hard, and it can save us some headache down the
road. In turn I thought it prudent to add a slot-id, just to ensure some
kind of unicity.)
How slot-id is mapped to an actual board is left to libvirtd C code :)
> +#
> +# @nvram-map: the mapping requirements of this NVRAM slot
> +#
> +# @templates: A non-empty list of @FirmwareFile elements. Any @FirmwareFile
> +# identified by this list as an NVRAM template can be copied to
> +# create an actual NVRAM file, and the NVRAM file can be mapped
> +# into the NVRAM slot identified by @slot-id, subject to the
> +# mapping requirements in @nvram-map.
> +#
> +# Since: 2.13
> +##
> +{ 'struct' : 'NVRAMSlot',
> + 'data' : { 'slot-id' : 'uint64',
> + 'nvram-map' : 'FirmwareMapping',
> + 'templates' : [ 'FirmwareFile' ] } }
> +
> +##
> +# @SystemFirmwareType:
> +#
> +# Lists system firmware types commonly used with QEMU virtual machines.
> +#
> +# @bios: The system firmware was built from the SeaBIOS project.
> +#
> +# @slof: The system firmware was built from the Slimline Open Firmware project.
> +#
> +# @uboot: The system firmware was built from the U-Boot project.
> +#
> +# @uefi: The system firmware was built from the edk2 (EFI Development Kit II)
> +# project.
> +#
> +# Since: 2.13
> +##
> +{ 'enum' : 'SystemFirmwareType',
> + 'data' : [ 'bios', 'slof', 'uboot', 'uefi'
] }
> +
> +##
> +# @SystemFirmware:
> +#
> +# Describes a system firmware binary and any NVRAM slots that it requires.
> +#
> +# @executable: Identifies the platform firmware executable.
> +#
> +# @type: The type by which the system firmware is commonly known. This is the
> +# main search key by which management software looks up a system
> +# firmware image for a new domain.
> +#
> +# @targets: a non-empty list of target architectures that are capable of
> +# executing the system firmware
> +#
> +# @sysfw-map: the mapping requirements of the system firmware binary
> +#
> +# @nvram-slots: A list of NVRAM slots that are required by the system firmware.
> +# The @slot-id field must be unique across the list. Importantly,
> +# if any @FirmwareAccess is @restricted-to-secure-context in
> +# @sysfw-map or in any @nvram-map in @nvram-slots, then (a) the
> +# virtual machine configuration is required to emulate the secure
> +# code execution context (as defined for @targets), and (b) the
> +# virtual machine configuration is required to actually restrict
> +# the access in question to the secure execution context.
> +#
> +# @supports-uefi-secure-boot: Whether the system firmware implements the
> +# software interfaces for UEFI Secure Boot, as
> +# defined in the UEFI specification. If the field
> +# is missing, its assumed value is 'false'.
> +#
> +# @supports-amd-sev: Whether the system firmware supports running under AMD
> +# Secure Encrypted Virtualization, as specified in the AMD64
> +# Architecture Programmer's Manual. If the field is missing,
> +# its assumed value is 'false'.
> +#
> +# @supports-acpi-s3: Whether the system firmware supports S3 sleep (suspend to
> +# RAM), as defined in the ACPI specification. If the field
> +# is missing, its assumed value is 'false'.
> +#
> +# @supports-acpi-s4: Whether the system firmware supports S4 hibernation
> +# (suspend to disk), as defined in the ACPI specification.
> +# If the field is missing, its assumed value is 'false'.
I think these should just be an enum again
"FirmwareFeatures": [ "secure-boot", "amd-sev",
"acpi-s3", "acpi-s4" ]
You mean a list of such enum constants? That makes sense. (I figured
individual fields would be easier to filter for, but I agree that a list
of enum constants is more frugal in the schema.)
> +#
> +# Since: 2.13
> +#
> +# Examples:
> +#
> +# {
> +# "executable": {
> +# "pathname": "/usr/share/seabios/bios-256k.bin",
> +# "description": "SeaBIOS",
> +# "tags": [
> +# "CONFIG_ROM_SIZE=256"
> +# ]
> +# },
> +# "type": "bios",
> +# "targets": [
> +# "i386",
> +# "x86_64"
> +# ],
> +# "sysfw-map": {
> +# "device": "memory",
> +# "write": "denied"
> +# },
> +# "supports-acpi-s3": true,
> +# "supports-acpi-s4": true
> +# }
> +#
> +# {
> +# "executable": {
> +# "pathname": "/usr/share/OVMF/OVMF_CODE.secboot.fd",
> +# "description": "OVMF with Secure Boot and SMM-protected
varstore",
> +# "tags": [
> +# "FD_SIZE_4MB",
> +# "IA32X64",
> +# "SECURE_BOOT_ENABLE",
> +# "SMM_REQUIRE"
> +# ]
> +# },
> +# "type": "uefi",
> +# "targets": [
> +# "x86_64"
> +# ],
> +# "sysfw-map": {
> +# "device": "flash",
> +# "write": "denied"
> +# },
> +# "nvram-slots": [
> +# {
> +# "slot-id": 1,
> +# "nvram-map" : {
> +# "device": "flash",
> +# "write": "restricted-to-secure-context"
> +# },
> +# "templates": [
> +# {
> +# "pathname":
"/usr/share/OVMF/OVMF_VARS.fd",
> +# "description": "empty varstore template"
> +# },
> +# {
> +# "pathname":
"/usr/share/OVMF/OVMF_VARS.secboot.fd",
> +# "description": "varstore template with the
Microsoft certificates enrolled for Secure Boot",
> +# "tags": [
> +# "mscerts"
> +# ]
The tags field is arbitrary firmware specific strings with no pre-declared
meaning. So we need to provide a explicit way to represent features against
this, such that libvirt can determine the secure boot vars file.
Right. So I guess I should keep the "tags" fields (with free-form
strings), but also introduce at least two enum types, one with "system
firmware features" and another with "nvramslot template features". And
then use lists of those.
Any advice on how to separate these enum types? For example, can we
assume that two enum types in total will suffice, because we can lump
the features for all templates of all nvram slots of all boards of all
architectures into a single NVRAMSlotTemplateFeature enum type?
> +# }
> +# ]
> +# }
> +# ],
> +# "supports-uefi-secure-boot": true,
> +# "supports-amd-sev": true,
> +# "supports-acpi-s3": true
> +# }
> +#
> +# {
> +# "executable": {
> +# "pathname": "/usr/share/AAVMF/AAVMF_CODE.fd",
> +# "description": "ARM64 UEFI firmware",
> +# "tags": [
> +# "AARCH64"
> +# ]
> +# },
> +# "type": "uefi",
> +# "targets": [
> +# "aarch64"
> +# ],
> +# "sysfw-map": {
> +# "device": "flash",
> +# "write": "denied"
> +# },
> +# "nvram-slots": [
> +# {
> +# "slot-id": 1,
> +# "nvram-map" : {
> +# "device": "flash"
> +# },
> +# "templates": [
> +# {
> +# "pathname":
"/usr/share/AAVMF/AAVMF_VARS.fd",
> +# "description": "empty varstore template"
> +# }
> +# ]
> +# }
> +# ]
> +# }
> +#
> +# {
> +# "executable": {
> +# "pathname":
"/usr/share/edk2.git/ovmf-ia32/OVMF_CODE-pure-efi.fd",
> +# "description": "32-bit OVMF with unprotected varstore and
no Secure Boot",
> +# "tags": [
> +# "FD_SIZE_2MB",
> +# "IA32"
> +# ]
> +# },
> +# "type": "uefi",
> +# "targets": [
> +# "i386",
> +# "x86_64"
> +# ],
> +# "sysfw-map": {
> +# "device": "flash",
> +# "write": "denied"
> +# },
> +# "nvram-slots": [
> +# {
> +# "slot-id": 1,
> +# "nvram-map" : {
> +# "device": "flash"
> +# },
> +# "templates": [
> +# {
> +# "pathname":
"/usr/share/edk2.git/ovmf-ia32/OVMF_VARS-pure-efi.fd",
> +# "description": "empty varstore template"
> +# }
> +# ]
> +# }
> +# ],
> +# "supports-acpi-s3": true
> +# }
> +##
> +{ 'struct' : 'SystemFirmware',
> + 'data' : { 'executable' :
'FirmwareFile',
> + 'type' : 'SystemFirmwareType',
> + 'targets' : [ 'str' ],
> + 'sysfw-map' : 'FirmwareMapping',
> + '*nvram-slots' : [ 'NVRAMSlot' ],
> + '*supports-uefi-secure-boot' : 'bool',
> + '*supports-amd-sev' : 'bool',
> + '*supports-acpi-s3' : 'bool',
> + '*supports-acpi-s4' : 'bool' } }
> +
> +##
> +# @x-check-firmware:
> +#
> +# Accept a @SystemFirmware object and do nothing, successfully. This command
> +# can be used in the QMP shell to validate @SystemFirmware JSON against the
> +# schema, and to pretty print it.
> +#
> +# @sysfw: ignored
> +#
> +# Since: 2.13
> +##
> +{ 'command' : 'x-check-firmware',
> + 'data' : { 'sysfw' : 'SystemFirmware' } }
The "json_reformat" command line tool can be used for reformatting
Awesome, thanks for the tip!
I wonder if we could usefully provide a command line tool for qemu
to
validate against qapi schemas ? eg something like
qemu-qapi-validate --type=SystemFirmware /path/to/schema /path/to/document
That would be a useful tool; I'd use it if it existed :)
If I must, I can tack it to my todo list. It wouldn't be my preference
right now.
Thanks
Laszlo