Subject: [PATCH 2/2] services: libvirt: Add UEFI firmware support.

From: Maxim Cournoyer <[email-masked]>
Date: Wed, 19 Mar 2025 21:45:13 +0900

This makes libvirt able to boot images that require a UEFI bootloader, with
the available firmwares exposed to libvirt made configurable via a new
configuration field.  For more background on the problem this fixes, see the
same issue that was reported in NixOS (see:
https://github.com/NixOS/nixpkgs/issues/115996).

* gnu/services/virtualization.scm: (list-of-file-likes?): New predicate.
(libvirt-configuration): [firmwares]: New field.
(/etc/qemu/firmware): New procedure.
(libvirt-service-type): Extend the etc-service-type with it.
(generate-libvirt-documentation): Delete obsolete procedure.
* doc/guix.texi: Re-generate doc.
* gnu/tests/virtualization.scm (run-libvirt-test): Augment memory from 256 to
512 MiB.  Test it.

Change-Id: I40694964405f13681520bf1e28b7365b0200d8f7
---

 doc/guix.texi                   | 506 ++++++++------------------------
 gnu/services/virtualization.scm |  76 +++--
 gnu/tests/virtualization.scm    |  33 ++-
 3 files changed, 211 insertions(+), 404 deletions(-)

diff --git a/doc/guix.texi b/doc/guix.texi
index 0488559332..e36fbad19f 100644
--- a/doc/guix.texi
+++ b/doc/guix.texi
@@ -37895,406 +37895,220 @@ Virtualization Services
 @end lisp
 @end defvar
 
-@c Auto-generated with (generate-libvirt-documentation)
+@c Auto-generated with (configuration->documentation 'libvirt-configuration)
+@c %start of fragment
+@deftp {Data Type} libvirt-configuration
 Available @code{libvirt-configuration} fields are:
 
-@deftypevr {@code{libvirt-configuration} parameter} package libvirt
+@table @asis
+@item @code{libvirt} (default: @code{libvirt}) (type: file-like)
 Libvirt package.
 
-@end deftypevr
-
-@deftypevr {@code{libvirt-configuration} parameter} boolean listen-tls?
-Flag listening for secure TLS connections on the public TCP/IP port.
-You must set @code{listen} for this to have any effect.
-
-It is necessary to setup a CA and issue server certificates before using
-this capability.
-
-Defaults to @samp{#t}.
-
-@end deftypevr
-
-@deftypevr {@code{libvirt-configuration} parameter} boolean listen-tcp?
-Listen for unencrypted TCP connections on the public TCP/IP port.  You must
-set @code{listen} for this to have any effect.
-
-Using the TCP socket requires SASL authentication by default.  Only SASL
-mechanisms which support data encryption are allowed.  This is
-DIGEST_MD5 and GSSAPI (Kerberos5).
-
-Defaults to @samp{#f}.
-
-@end deftypevr
-
-@deftypevr {@code{libvirt-configuration} parameter} string tls-port
-Port for accepting secure TLS connections.   This can be a port number,
-or service name.
+@item @code{qemu} (default: @code{qemu}) (type: file-like)
+Qemu package.
 
-Defaults to @samp{"16514"}.
+@item @code{firmwares} (default: @code{(ovmf-x86-64)}) (type: list-of-file-likes)
+List of UEFI/BIOS firmware packages to make available.  Each firmware
+package should contain a @file{share/qemu/firmware/@var{NAME}.json} QEMU
+firmware metadata file.
 
-@end deftypevr
+@item @code{listen-tls?} (default: @code{#t}) (type: boolean)
+Flag listening for secure TLS connections on the public TCP/IP port.
+must set @code{listen} for this to have any effect.  It is necessary to
+setup a CA and issue server certificates before using this capability.
 
-@deftypevr {@code{libvirt-configuration} parameter} string tcp-port
-Port for accepting insecure TCP connections.  This can be a port number,
-or service name.
+@item @code{listen-tcp?} (default: @code{#f}) (type: boolean)
+Listen for unencrypted TCP connections on the public TCP/IP port.  must
+set @code{listen} for this to have any effect.  Using the TCP socket
+requires SASL authentication by default.  Only SASL mechanisms which
+support data encryption are allowed.  This is DIGEST_MD5 and GSSAPI
+(Kerberos5)
 
-Defaults to @samp{"16509"}.
+@item @code{tls-port} (default: @code{"16514"}) (type: string)
+Port for accepting secure TLS connections This can be a port number, or
+service name
 
-@end deftypevr
+@item @code{tcp-port} (default: @code{"16509"}) (type: string)
+Port for accepting insecure TCP connections This can be a port number,
+or service name
 
-@deftypevr {@code{libvirt-configuration} parameter} string listen-addr
+@item @code{listen-addr} (default: @code{"0.0.0.0"}) (type: string)
 IP address or hostname used for client connections.
 
-Defaults to @samp{"0.0.0.0"}.
-
-@end deftypevr
-
-@deftypevr {@code{libvirt-configuration} parameter} boolean mdns-adv?
-Flag toggling mDNS advertisement of the libvirt service.
-
-Alternatively can disable for all services on a host by stopping the
-Avahi daemon.
+@item @code{mdns-adv?} (default: @code{#f}) (type: boolean)
+Flag toggling mDNS advertisement of the libvirt service.  Alternatively
+can disable for all services on a host by stopping the Avahi daemon.
 
-Defaults to @samp{#f}.
-
-@end deftypevr
-
-@deftypevr {@code{libvirt-configuration} parameter} string mdns-name
+@item @code{mdns-name} (default: @code{"Virtualization Host terra"}) (type: string)
 Default mDNS advertisement name.  This must be unique on the immediate
 broadcast network.
 
-Defaults to @samp{"Virtualization Host <hostname>"}.
-
-@end deftypevr
-
-@deftypevr {@code{libvirt-configuration} parameter} string unix-sock-group
+@item @code{unix-sock-group} (default: @code{"libvirt"}) (type: string)
 UNIX domain socket group ownership.  This can be used to allow a
 'trusted' set of users access to management capabilities without
 becoming root.
 
-Defaults to @samp{"libvirt"}.
-
-@end deftypevr
-
-@deftypevr {@code{libvirt-configuration} parameter} string unix-sock-ro-perms
+@item @code{unix-sock-ro-perms} (default: @code{"0777"}) (type: string)
 UNIX socket permissions for the R/O socket.  This is used for monitoring
 VM status only.
 
-Defaults to @samp{"0777"}.
-
-@end deftypevr
-
-@deftypevr {@code{libvirt-configuration} parameter} string unix-sock-rw-perms
+@item @code{unix-sock-rw-perms} (default: @code{"0770"}) (type: string)
 UNIX socket permissions for the R/W socket.  Default allows only root.
 If PolicyKit is enabled on the socket, the default will change to allow
 everyone (eg, 0777)
 
-Defaults to @samp{"0770"}.
-
-@end deftypevr
-
-@deftypevr {@code{libvirt-configuration} parameter} string unix-sock-admin-perms
+@item @code{unix-sock-admin-perms} (default: @code{"0777"}) (type: string)
 UNIX socket permissions for the admin socket.  Default allows only owner
 (root), do not change it unless you are sure to whom you are exposing
 the access to.
 
-Defaults to @samp{"0777"}.
-
-@end deftypevr
-
-@deftypevr {@code{libvirt-configuration} parameter} string unix-sock-dir
+@item @code{unix-sock-dir} (default: @code{"/var/run/libvirt"}) (type: string)
 The directory in which sockets will be found/created.
 
-Defaults to @samp{"/var/run/libvirt"}.
-
-@end deftypevr
-
-@deftypevr {@code{libvirt-configuration} parameter} string auth-unix-ro
+@item @code{auth-unix-ro} (default: @code{"polkit"}) (type: string)
 Authentication scheme for UNIX read-only sockets.  By default socket
 permissions allow anyone to connect
 
-Defaults to @samp{"polkit"}.
-
-@end deftypevr
-
-@deftypevr {@code{libvirt-configuration} parameter} string auth-unix-rw
+@item @code{auth-unix-rw} (default: @code{"polkit"}) (type: string)
 Authentication scheme for UNIX read-write sockets.  By default socket
 permissions only allow root.  If PolicyKit support was compiled into
 libvirt, the default will be to use 'polkit' auth.
 
-Defaults to @samp{"polkit"}.
-
-@end deftypevr
-
-@deftypevr {@code{libvirt-configuration} parameter} string auth-tcp
+@item @code{auth-tcp} (default: @code{"sasl"}) (type: string)
 Authentication scheme for TCP sockets.  If you don't enable SASL, then
 all TCP traffic is cleartext.  Don't do this outside of a dev/test
 scenario.
 
-Defaults to @samp{"sasl"}.
-
-@end deftypevr
-
-@deftypevr {@code{libvirt-configuration} parameter} string auth-tls
+@item @code{auth-tls} (default: @code{"none"}) (type: string)
 Authentication scheme for TLS sockets.  TLS sockets already have
 encryption provided by the TLS layer, and limited authentication is done
-by certificates.
-
-It is possible to make use of any SASL authentication mechanism as well,
-by using 'sasl' for this option
+by certificates.  It is possible to make use of any SASL authentication
+mechanism as well, by using 'sasl' for this option
 
-Defaults to @samp{"none"}.
-
-@end deftypevr
-
-@deftypevr {@code{libvirt-configuration} parameter} optional-list access-drivers
-API access control scheme.
-
-By default an authenticated user is allowed access to all APIs.  Access
-drivers can place restrictions on this.
-
-Defaults to @samp{'()}.
-
-@end deftypevr
+@item @code{access-drivers} (default: @code{()}) (type: optional-list)
+API access control scheme.  By default an authenticated user is allowed
+access to all APIs.  Access drivers can place restrictions on this.
 
-@deftypevr {@code{libvirt-configuration} parameter} string key-file
+@item @code{key-file} (default: @code{""}) (type: string)
 Server key file path.  If set to an empty string, then no private key is
 loaded.
 
-Defaults to @samp{""}.
-
-@end deftypevr
-
-@deftypevr {@code{libvirt-configuration} parameter} string cert-file
+@item @code{cert-file} (default: @code{""}) (type: string)
 Server key file path.  If set to an empty string, then no certificate is
 loaded.
 
-Defaults to @samp{""}.
-
-@end deftypevr
-
-@deftypevr {@code{libvirt-configuration} parameter} string ca-file
+@item @code{ca-file} (default: @code{""}) (type: string)
 Server key file path.  If set to an empty string, then no CA certificate
 is loaded.
 
-Defaults to @samp{""}.
-
-@end deftypevr
-
-@deftypevr {@code{libvirt-configuration} parameter} string crl-file
+@item @code{crl-file} (default: @code{""}) (type: string)
 Certificate revocation list path.  If set to an empty string, then no
 CRL is loaded.
 
-Defaults to @samp{""}.
-
-@end deftypevr
-
-@deftypevr {@code{libvirt-configuration} parameter} boolean tls-no-sanity-cert
-Disable verification of our own server certificates.
+@item @code{tls-no-sanity-cert} (default: @code{#f}) (type: boolean)
+Disable verification of our own server certificates.  When libvirtd
+starts it performs some sanity checks against its own certificates.
 
-When libvirtd starts it performs some sanity checks against its own
-certificates.
+@item @code{tls-no-verify-cert} (default: @code{#f}) (type: boolean)
+Disable verification of client certificates.  Client certificate
+verification is the primary authentication mechanism.  Any client which
+does not present a certificate signed by the CA will be rejected.
 
-Defaults to @samp{#f}.
-
-@end deftypevr
-
-@deftypevr {@code{libvirt-configuration} parameter} boolean tls-no-verify-cert
-Disable verification of client certificates.
-
-Client certificate verification is the primary authentication mechanism.
-Any client which does not present a certificate signed by the CA will be
-rejected.
-
-Defaults to @samp{#f}.
-
-@end deftypevr
-
-@deftypevr {@code{libvirt-configuration} parameter} optional-list tls-allowed-dn-list
+@item @code{tls-allowed-dn-list} (default: @code{()}) (type: optional-list)
 Whitelist of allowed x509 Distinguished Name.
 
-Defaults to @samp{'()}.
-
-@end deftypevr
-
-@deftypevr {@code{libvirt-configuration} parameter} optional-list sasl-allowed-usernames
+@item @code{sasl-allowed-usernames} (default: @code{()}) (type: optional-list)
 Whitelist of allowed SASL usernames.  The format for username depends on
 the SASL authentication mechanism.
 
-Defaults to @samp{'()}.
-
-@end deftypevr
-
-@deftypevr {@code{libvirt-configuration} parameter} string tls-priority
+@item @code{tls-priority} (default: @code{"NORMAL"}) (type: string)
 Override the compile time default TLS priority string.  The default is
-usually @samp{"NORMAL"} unless overridden at build time.  Only set this is it
+usually "NORMAL" unless overridden at build time.  Only set this is it
 is desired for libvirt to deviate from the global default settings.
 
-Defaults to @samp{"NORMAL"}.
-
-@end deftypevr
-
-@deftypevr {@code{libvirt-configuration} parameter} integer max-clients
+@item @code{max-clients} (default: @code{5000}) (type: integer)
 Maximum number of concurrent client connections to allow over all
 sockets combined.
 
-Defaults to @samp{5000}.
-
-@end deftypevr
-
-@deftypevr {@code{libvirt-configuration} parameter} integer max-queued-clients
+@item @code{max-queued-clients} (default: @code{1000}) (type: integer)
 Maximum length of queue of connections waiting to be accepted by the
 daemon.  Note, that some protocols supporting retransmission may obey
 this so that a later reattempt at connection succeeds.
 
-Defaults to @samp{1000}.
-
-@end deftypevr
-
-@deftypevr {@code{libvirt-configuration} parameter} integer max-anonymous-clients
+@item @code{max-anonymous-clients} (default: @code{20}) (type: integer)
 Maximum length of queue of accepted but not yet authenticated clients.
 Set this to zero to turn this feature off
 
-Defaults to @samp{20}.
-
-@end deftypevr
-
-@deftypevr {@code{libvirt-configuration} parameter} integer min-workers
+@item @code{min-workers} (default: @code{5}) (type: integer)
 Number of workers to start up initially.
 
-Defaults to @samp{5}.
-
-@end deftypevr
-
-@deftypevr {@code{libvirt-configuration} parameter} integer max-workers
-Maximum number of worker threads.
-
-If the number of active clients exceeds @code{min-workers}, then more
-threads are spawned, up to max_workers limit.  Typically you'd want
-max_workers to equal maximum number of clients allowed.
-
-Defaults to @samp{20}.
-
-@end deftypevr
+@item @code{max-workers} (default: @code{20}) (type: integer)
+Maximum number of worker threads.  If the number of active clients
+exceeds @code{min-workers}, then more threads are spawned, up to
+max_workers limit.  Typically you'd want max_workers to equal maximum
+number of clients allowed.
 
-@deftypevr {@code{libvirt-configuration} parameter} integer prio-workers
+@item @code{prio-workers} (default: @code{5}) (type: integer)
 Number of priority workers.  If all workers from above pool are stuck,
 some calls marked as high priority (notably domainDestroy) can be
 executed in this pool.
 
-Defaults to @samp{5}.
-
-@end deftypevr
-
-@deftypevr {@code{libvirt-configuration} parameter} integer max-requests
+@item @code{max-requests} (default: @code{20}) (type: integer)
 Total global limit on concurrent RPC calls.
 
-Defaults to @samp{20}.
-
-@end deftypevr
-
-@deftypevr {@code{libvirt-configuration} parameter} integer max-client-requests
+@item @code{max-client-requests} (default: @code{5}) (type: integer)
 Limit on concurrent requests from a single client connection.  To avoid
 one client monopolizing the server this should be a small fraction of
 the global max_requests and max_workers parameter.
 
-Defaults to @samp{5}.
-
-@end deftypevr
-
-@deftypevr {@code{libvirt-configuration} parameter} integer admin-min-workers
+@item @code{admin-min-workers} (default: @code{1}) (type: integer)
 Same as @code{min-workers} but for the admin interface.
 
-Defaults to @samp{1}.
-
-@end deftypevr
-
-@deftypevr {@code{libvirt-configuration} parameter} integer admin-max-workers
+@item @code{admin-max-workers} (default: @code{5}) (type: integer)
 Same as @code{max-workers} but for the admin interface.
 
-Defaults to @samp{5}.
-
-@end deftypevr
-
-@deftypevr {@code{libvirt-configuration} parameter} integer admin-max-clients
+@item @code{admin-max-clients} (default: @code{5}) (type: integer)
 Same as @code{max-clients} but for the admin interface.
 
-Defaults to @samp{5}.
-
-@end deftypevr
-
-@deftypevr {@code{libvirt-configuration} parameter} integer admin-max-queued-clients
+@item @code{admin-max-queued-clients} (default: @code{5}) (type: integer)
 Same as @code{max-queued-clients} but for the admin interface.
 
-Defaults to @samp{5}.
-
-@end deftypevr
-
-@deftypevr {@code{libvirt-configuration} parameter} integer admin-max-client-requests
+@item @code{admin-max-client-requests} (default: @code{5}) (type: integer)
 Same as @code{max-client-requests} but for the admin interface.
 
-Defaults to @samp{5}.
-
-@end deftypevr
-
-@deftypevr {@code{libvirt-configuration} parameter} integer log-level
+@item @code{log-level} (default: @code{3}) (type: integer)
 Logging level.  4 errors, 3 warnings, 2 information, 1 debug.
 
-Defaults to @samp{3}.
-
-@end deftypevr
-
-@deftypevr {@code{libvirt-configuration} parameter} string log-filters
-Logging filters.
-
-A filter allows to select a different logging level for a given category
-of logs.  The format for a filter is one of:
+@item @code{log-filters} (default: @code{"3:remote 4:event"}) (type: string)
+Logging filters.  A filter allows selecting a different logging level
+for a given category of logs The format for a filter is one of:
 
 @itemize @bullet
-@item
-x:name
-
-@item
-x:+name
-
+@item x:name
+@item x:+name
 @end itemize
 
 where @code{name} is a string which is matched against the category
 given in the @code{VIR_LOG_INIT()} at the top of each libvirt source
-file, e.g., @samp{"remote"}, @samp{"qemu"}, or @samp{"util.json"} (the
-name in the filter can be a substring of the full category name, in
-order to match multiple similar categories), the optional @samp{"+"}
-prefix tells libvirt to log stack trace for each message matching name,
-and @code{x} is the minimal level where matching messages should be
-logged:
+file, e.g., "remote", "qemu", or "util.json" (the name in the filter can
+be a substring of the full category name, in order to match multiple
+similar categories), the optional "+" prefix tells libvirt to log stack
+trace for each message matching name, and @code{x} is the minimal level
+where matching messages should be logged:
 
 @itemize @bullet
-@item
-1: DEBUG
-
-@item
-2: INFO
-
-@item
-3: WARNING
-
-@item
-4: ERROR
-
+@item 1: DEBUG
+@item 2: INFO
+@item 3: WARNING
+@item 4: ERROR
 @end itemize
 
 Multiple filters can be defined in a single filters statement, they just
 need to be separated by spaces.
 
-Defaults to @samp{"3:remote 4:event"}.
-
-@end deftypevr
-
-@deftypevr {@code{libvirt-configuration} parameter} string log-outputs
-Logging outputs.
-
-An output is one of the places to save logging information.  The format
-for an output can be:
+@item @code{log-outputs} (default: @code{"3:syslog:libvirtd"}) (type: string)
+Logging outputs.  An output is one of the places to save logging
+information The format for an output can be:
 
 @table @code
 @item x:stderr
@@ -38308,137 +38122,77 @@ Virtualization Services
 
 @item x:journald
 output to journald logging system
-
 @end table
 
-In all case the x prefix is the minimal level, acting as a filter
+In all case the x prefix is the minimal level, acting as a
+filter
 
 @itemize @bullet
-@item
-1: DEBUG
-
-@item
-2: INFO
-
-@item
-3: WARNING
-
-@item
-4: ERROR
-
+@item 1: DEBUG
+@item 2: INFO
+@item 3: WARNING
+@item 4: ERROR
 @end itemize
 
 Multiple outputs can be defined, they just need to be separated by
 spaces.
 
-Defaults to @samp{"3:stderr"}.
-
-@end deftypevr
-
-@deftypevr {@code{libvirt-configuration} parameter} integer audit-level
+@item @code{audit-level} (default: @code{1}) (type: integer)
 Allows usage of the auditing subsystem to be altered
 
 @itemize @bullet
-@item
-0: disable all auditing
-
-@item
-1: enable auditing, only if enabled on host
-
-@item
-2: enable auditing, and exit if disabled on host.
-
+@item 0: disable all auditing
+@item 1: enable auditing, only if enabled on host
+@item 2: enable auditing, and exit if disabled on host.
 @end itemize
 
-Defaults to @samp{1}.
-
-@end deftypevr
-
-@deftypevr {@code{libvirt-configuration} parameter} boolean audit-logging
+@item @code{audit-logging} (default: @code{#f}) (type: boolean)
 Send audit messages via libvirt logging infrastructure.
 
-Defaults to @samp{#f}.
-
-@end deftypevr
-
-@deftypevr {@code{libvirt-configuration} parameter} optional-string host-uuid
-Host UUID@.  UUID must not have all digits be the same.
-
-Defaults to @samp{""}.
-
-@end deftypevr
+@item @code{host-uuid} (default: @code{""}) (type: optional-string)
+Host UUID.  UUID must not have all digits be the same.
 
-@deftypevr {@code{libvirt-configuration} parameter} string host-uuid-source
+@item @code{host-uuid-source} (default: @code{"smbios"}) (type: string)
 Source to read host UUID.
 
 @itemize @bullet
-@item
-@code{smbios}: fetch the UUID from @code{dmidecode -s system-uuid}
-
-@item
-@code{machine-id}: fetch the UUID from @code{/etc/machine-id}
-
+@item @code{smbios}: fetch the UUID from @code{dmidecode -s system-uuid}
+@item @code{machine-id}: fetch the UUID from @code{/etc/machine-id}
 @end itemize
 
 If @code{dmidecode} does not provide a valid UUID a temporary UUID will
 be generated.
 
-Defaults to @samp{"smbios"}.
-
-@end deftypevr
-
-@deftypevr {@code{libvirt-configuration} parameter} integer keepalive-interval
+@item @code{keepalive-interval} (default: @code{5}) (type: integer)
 A keepalive message is sent to a client after @code{keepalive_interval}
 seconds of inactivity to check if the client is still responding.  If
 set to -1, libvirtd will never send keepalive requests; however clients
 can still send them and the daemon will send responses.
 
-Defaults to @samp{5}.
-
-@end deftypevr
-
-@deftypevr {@code{libvirt-configuration} parameter} integer keepalive-count
+@item @code{keepalive-count} (default: @code{5}) (type: integer)
 Maximum number of keepalive messages that are allowed to be sent to the
 client without getting any response before the connection is considered
-broken.
-
-In other words, the connection is automatically closed approximately
-after @code{keepalive_interval * (keepalive_count + 1)} seconds since
-the last message received from the client.  When @code{keepalive-count}
-is set to 0, connections will be automatically closed after
-@code{keepalive-interval} seconds of inactivity without sending any
-keepalive messages.
-
-Defaults to @samp{5}.
-
-@end deftypevr
-
-@deftypevr {@code{libvirt-configuration} parameter} integer admin-keepalive-interval
+broken.  In other words, the connection is automatically closed
+approximately after @code{keepalive_interval * (keepalive_count + 1)}
+seconds since the last message received from the client.  When
+@code{keepalive-count} is set to 0, connections will be automatically
+closed after @code{keepalive-interval} seconds of inactivity without
+sending any keepalive messages.
+
+@item @code{admin-keepalive-interval} (default: @code{5}) (type: integer)
 Same as above but for admin interface.
 
-Defaults to @samp{5}.
-
-@end deftypevr
-
-@deftypevr {@code{libvirt-configuration} parameter} integer admin-keepalive-count
+@item @code{admin-keepalive-count} (default: @code{5}) (type: integer)
 Same as above but for admin interface.
 
-Defaults to @samp{5}.
-
-@end deftypevr
-
-@deftypevr {@code{libvirt-configuration} parameter} integer ovs-timeout
-Timeout for Open vSwitch calls.
+@item @code{ovs-timeout} (default: @code{5}) (type: integer)
+Timeout for Open vSwitch calls.  The @code{ovs-vsctl} utility is used
+for the configuration and its timeout option is set by default to 5
+seconds to avoid potential infinite waits blocking libvirt.
 
-The @code{ovs-vsctl} utility is used for the configuration and its
-timeout option is set by default to 5 seconds to avoid potential
-infinite waits blocking libvirt.
-
-Defaults to @samp{5}.
-
-@end deftypevr
-
-@c %end of autogenerated docs
+@end table
+@end deftp
+@c %end of fragment
 
 @subsubheading Virtlog daemon
 The virtlogd service is a server side daemon component of libvirt that is
diff --git a/gnu/services/virtualization.scm b/gnu/services/virtualization.scm
index 555c0be55e..40dad7dc1e 100644
--- a/gnu/services/virtualization.scm
+++ b/gnu/services/virtualization.scm
@@ -7,6 +7,7 @@
 ;;; Copyright © 2022 Leo Nikkilä <[email-masked]>
 ;;; Copyright © 2023 Efraim Flashner <[email-masked]>
 ;;; Copyright © 2024 Raven Hallsby <[email-masked]>
+;;; Copyright © 2025 Maxim Cournoyer <[email-masked]>
 ;;;
 ;;; This file is part of GNU Guix.
 ;;;
@@ -29,6 +30,7 @@ (define-module (gnu services virtualization)
   #:use-module (gnu image)
   #:use-module (gnu packages admin)
   #:use-module (gnu packages bash)
+  #:use-module (gnu packages firmware)
   #:use-module (gnu packages gdb)
   #:autoload   (gnu packages gnupg) (guile-gcrypt)
   #:use-module (gnu packages package-management)
@@ -107,6 +109,7 @@ (define-module (gnu services virtualization)
             libvirt-configuration-ca-file
             libvirt-configuration-cert-file
             libvirt-configuration-crl-file
+            libvirt-configuration-firmwares
             libvirt-configuration-host-uuid
             libvirt-configuration-host-uuid-source
             libvirt-configuration-keepalive-count
@@ -205,6 +208,9 @@ (define (serialize-optional-string field-name val)
       (format #t "# ~a = \"\"\n" (uglify-field-name field-name))
       (serialize-string field-name val)))
 
+(define list-of-file-likes?
+  (list-of file-like?))
+
 (define-configuration libvirt-configuration
   (libvirt
    (file-like libvirt)
@@ -212,7 +218,12 @@ (define-configuration libvirt-configuration
   (qemu
    (file-like qemu)
    "Qemu package.")
-
+  (firmwares
+   (list-of-file-likes (list ovmf-x86-64))
+   "List of UEFI/BIOS firmware packages to make available.  Each firmware
+package should contain a @file{share/qemu/firmware/@var{NAME}.json} QEMU
+firmware metadata file."
+   (serializer empty-serializer))
   (listen-tls?
    (boolean #t)
    "Flag listening for secure TLS connections on the public TCP/IP port.
@@ -540,7 +551,6 @@ (define (%libvirt-activation config)
         (use-modules (guix build utils))
         (mkdir-p #$sock-dir))))
 
-
 (define (libvirt-shepherd-service config)
   (let* ((config-file (libvirt-conf-file config))
          (libvirt (libvirt-configuration-libvirt config))
@@ -553,7 +563,8 @@ (define (libvirt-shepherd-service config)
                      (list (string-append #$libvirt "/sbin/libvirtd")
                            "-f" #$config-file
                            #$@(if listen-tcp? '("--listen") '()))
-                     ;; For finding qemu, ip binaries and kernel modules.
+                     ;; For finding qemu, firmwares, the 'ip' command and
+                     ;; kernel modules.
                      #:environment-variables
                      (list
                       (string-append
@@ -564,29 +575,45 @@ (define (libvirt-shepherd-service config)
                        "/run/booted-system/kernel/lib/modules"))))
            (stop #~(make-kill-destructor))))))
 
+(define (/etc/qemu/firmware config)
+  (let ((firmwares (libvirt-configuration-firmwares config)))
+    `(("qemu"
+       ,(computed-file
+         "etc-qemu-firmware"
+         (with-imported-modules '((guix build union))
+           #~(begin
+               (use-modules (guix build union) (srfi srfi-26))
+               (mkdir #$output)
+               (union-build (string-append #$output "/firmware")
+                            (map (cut string-append <> "/share/qemu/firmware")
+                                 (list #$@firmwares))))))))))
+
 (define libvirt-service-type
-  (service-type (name 'libvirt)
-		(extensions
-                 (list
-                  (service-extension polkit-service-type
-                                     (compose list libvirt-configuration-libvirt))
-                  (service-extension profile-service-type
-                                     (lambda (config)
-                                       (list
-                                        (libvirt-configuration-libvirt config)
-                                        (libvirt-configuration-qemu config))))
-                  (service-extension activation-service-type
-                                     %libvirt-activation)
-                  (service-extension shepherd-root-service-type
-                                     libvirt-shepherd-service)
-                  (service-extension account-service-type
-                                     (const %libvirt-accounts))))
-                (default-value (libvirt-configuration))
-                (description "Run @command{libvirtd}, a daemon of the libvirt
+  (service-type
+   (name 'libvirt)
+   (extensions
+    (list
+     (service-extension polkit-service-type
+                        (compose list libvirt-configuration-libvirt))
+     (service-extension profile-service-type
+                        (lambda (config)
+                          (list (libvirt-configuration-libvirt config)
+                                (libvirt-configuration-qemu config))))
+     ;; Libvirt only considers the $libvirt/share/qemu/firmware and
+     ;; /etc/qemu/firmware directories to locate the QEMU firmware metadata
+     ;; specifications.
+     (service-extension etc-service-type /etc/qemu/firmware)
+     (service-extension activation-service-type
+                        %libvirt-activation)
+     (service-extension shepherd-root-service-type
+                        libvirt-shepherd-service)
+     (service-extension account-service-type
+                        (const %libvirt-accounts))))
+   (default-value (libvirt-configuration))
+   (description "Run @command{libvirtd}, a daemon of the libvirt
 virtualization management system.  This daemon runs on host servers and
 performs required management tasks for virtualized guests.")))
 
-
 (define-record-type* <virtlog-configuration>
   virtlog-configuration make-virtlog-configuration
   virtlog-configuration?
@@ -638,11 +665,6 @@ (define virtlog-service-type
                 (description "Run @command{virtlogd}, a daemon libvirt that is
 used to manage logs from @acronym{VM, virtual machine} consoles.")))
 
-(define (generate-libvirt-documentation)
-  (generate-documentation
-   `((libvirt-configuration ,libvirt-configuration-fields))
-   'libvirt-configuration))
-
 
 ;;;
 ;;; Transparent QEMU emulation via binfmt_misc.
diff --git a/gnu/tests/virtualization.scm b/gnu/tests/virtualization.scm
index a3c9c4014b..e08f66eb28 100644
--- a/gnu/tests/virtualization.scm
+++ b/gnu/tests/virtualization.scm
@@ -4,6 +4,7 @@
 ;;; Copyright © 2020 Jan (janneke) Nieuwenhuizen <[email-masked]>
 ;;; Copyright © 2021 Pierre Langlois <[email-masked]>
 ;;; Copyright © 2022 Marius Bakke <[email-masked]>
+;;; Copyright © 2025 Maxim Cournoyer <[email-masked]>
 ;;;
 ;;; This file is part of GNU Guix.
 ;;;
@@ -69,7 +70,8 @@ (define (run-libvirt-test)
   (define vm
     (virtual-machine
      (operating-system os)
-     (port-forwardings '())))
+     (port-forwardings '())
+     (memory-size 512)))
 
   (define test
     (with-imported-modules '((gnu build marionette))
@@ -135,6 +137,35 @@ (define (run-libvirt-test)
                          "-c" "qemu:///system" "net-start" "default"))
              marionette))
 
+          (test-assert "configured firmwares are available to libvirt"
+            (marionette-eval
+             '(begin
+                (use-modules (ice-9 popen)
+                             (ice-9 textual-ports)
+                             (srfi srfi-1)
+                             (srfi srfi-26))
+                (let* ((conf-firmwares (list #$@(libvirt-configuration-firmwares
+                                                 (libvirt-configuration))))
+                       (virsh #$(file-append libvirt "/bin/virsh"))
+                       (input-pipe (open-pipe*
+                                    OPEN_READ
+                                    virsh "-c" "qemu:///system"
+                                    "domcapabilities" "--xpath"
+                                    "/domainCapabilities/os/loader/value/text()"))
+                       (output (get-string-all input-pipe))
+                       (found-firmwares (string-split (string-trim-both output)
+                                                      #\newline)))
+                  (close-pipe input-pipe)
+                  ;; Check that every configured firmware package is covered
+                  ;; by at least by one firmware file available to libvirt.
+                  (every (lambda (conf-firmware)
+                           ;; The firmwares listed by virsh contains their
+                           ;; full file names, not just their package output.
+                           (any (cut string-prefix? conf-firmware <>)
+                                found-firmwares))
+                         conf-firmwares)))
+             marionette))
+
           (test-end))))
 
   (gexp->derivation "libvirt-test" test))
-- 
2.48.1