diff options
Diffstat (limited to 'OvmfPkg/VirtioRngDxe/VirtioRng.c')
-rw-r--r-- | OvmfPkg/VirtioRngDxe/VirtioRng.c | 653 |
1 files changed, 653 insertions, 0 deletions
diff --git a/OvmfPkg/VirtioRngDxe/VirtioRng.c b/OvmfPkg/VirtioRngDxe/VirtioRng.c new file mode 100644 index 0000000000..8eb1aa300b --- /dev/null +++ b/OvmfPkg/VirtioRngDxe/VirtioRng.c @@ -0,0 +1,653 @@ +/** @file
+
+ This driver produces EFI_RNG_PROTOCOL instances for virtio-rng devices.
+
+ The implementation is based on OvmfPkg/VirtioScsiDxe/VirtioScsi.c
+
+ Copyright (C) 2012, Red Hat, Inc.
+ Copyright (c) 2012 - 2014, Intel Corporation. All rights reserved.<BR>
+
+ This driver:
+
+ Copyright (C) 2016, Linaro Ltd.
+
+ This program and the accompanying materials are licensed and made available
+ under the terms and conditions of the BSD License which accompanies this
+ distribution. The full text of the license may be found at
+ http://opensource.org/licenses/bsd-license.php
+
+ THE PROGRAM IS DISTRIBUTED UNDER THE BSD LICENSE ON AN "AS IS" BASIS, WITHOUT
+ WARRANTIES OR REPRESENTATIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED.
+
+**/
+
+#include <Library/BaseMemoryLib.h>
+#include <Library/DebugLib.h>
+#include <Library/MemoryAllocationLib.h>
+#include <Library/UefiBootServicesTableLib.h>
+#include <Library/UefiLib.h>
+#include <Library/VirtioLib.h>
+
+#include "VirtioRng.h"
+
+/**
+ Returns information about the random number generation implementation.
+
+ @param[in] This A pointer to the EFI_RNG_PROTOCOL
+ instance.
+ @param[in,out] RNGAlgorithmListSize On input, the size in bytes of
+ RNGAlgorithmList.
+ On output with a return code of
+ EFI_SUCCESS, the size in bytes of the
+ data returned in RNGAlgorithmList. On
+ output with a return code of
+ EFI_BUFFER_TOO_SMALL, the size of
+ RNGAlgorithmList required to obtain the
+ list.
+ @param[out] RNGAlgorithmList A caller-allocated memory buffer filled
+ by the driver with one EFI_RNG_ALGORITHM
+ element for each supported RNG algorithm.
+ The list must not change across multiple
+ calls to the same driver. The first
+ algorithm in the list is the default
+ algorithm for the driver.
+
+ @retval EFI_SUCCESS The RNG algorithm list was returned
+ successfully.
+ @retval EFI_UNSUPPORTED The services is not supported by this
+ driver.
+ @retval EFI_DEVICE_ERROR The list of algorithms could not be
+ retrieved due to a hardware or firmware
+ error.
+ @retval EFI_INVALID_PARAMETER One or more of the parameters are
+ incorrect.
+ @retval EFI_BUFFER_TOO_SMALL The buffer RNGAlgorithmList is too small
+ to hold the result.
+
+**/
+STATIC
+EFI_STATUS
+EFIAPI
+VirtioRngGetInfo (
+ IN EFI_RNG_PROTOCOL *This,
+ IN OUT UINTN *RNGAlgorithmListSize,
+ OUT EFI_RNG_ALGORITHM *RNGAlgorithmList
+ )
+{
+ if (This == NULL || RNGAlgorithmListSize == NULL) {
+ return EFI_INVALID_PARAMETER;
+ }
+
+ if (*RNGAlgorithmListSize < sizeof (EFI_RNG_ALGORITHM)) {
+ *RNGAlgorithmListSize = sizeof (EFI_RNG_ALGORITHM);
+ return EFI_BUFFER_TOO_SMALL;
+ }
+
+ if (RNGAlgorithmList == NULL) {
+ return EFI_INVALID_PARAMETER;
+ }
+
+ *RNGAlgorithmListSize = sizeof (EFI_RNG_ALGORITHM);
+ CopyGuid (RNGAlgorithmList, &gEfiRngAlgorithmRaw);
+
+ return EFI_SUCCESS;
+}
+
+/**
+ Produces and returns an RNG value using either the default or specified RNG
+ algorithm.
+
+ @param[in] This A pointer to the EFI_RNG_PROTOCOL
+ instance.
+ @param[in] RNGAlgorithm A pointer to the EFI_RNG_ALGORITHM that
+ identifies the RNG algorithm to use. May
+ be NULL in which case the function will
+ use its default RNG algorithm.
+ @param[in] RNGValueLength The length in bytes of the memory buffer
+ pointed to by RNGValue. The driver shall
+ return exactly this numbers of bytes.
+ @param[out] RNGValue A caller-allocated memory buffer filled
+ by the driver with the resulting RNG
+ value.
+
+ @retval EFI_SUCCESS The RNG value was returned successfully.
+ @retval EFI_UNSUPPORTED The algorithm specified by RNGAlgorithm
+ is not supported by this driver.
+ @retval EFI_DEVICE_ERROR An RNG value could not be retrieved due
+ to a hardware or firmware error.
+ @retval EFI_NOT_READY There is not enough random data available
+ to satisfy the length requested by
+ RNGValueLength.
+ @retval EFI_INVALID_PARAMETER RNGValue is NULL or RNGValueLength is
+ zero.
+
+**/
+STATIC
+EFI_STATUS
+EFIAPI
+VirtioRngGetRNG (
+ IN EFI_RNG_PROTOCOL *This,
+ IN EFI_RNG_ALGORITHM *RNGAlgorithm, OPTIONAL
+ IN UINTN RNGValueLength,
+ OUT UINT8 *RNGValue
+ )
+{
+ VIRTIO_RNG_DEV *Dev;
+ DESC_INDICES Indices;
+ volatile UINT8 *Buffer;
+ UINTN Index;
+ UINT32 Len;
+ UINT32 BufferSize;
+ EFI_STATUS Status;
+
+ if (This == NULL || RNGValueLength == 0 || RNGValue == NULL) {
+ return EFI_INVALID_PARAMETER;
+ }
+
+ //
+ // We only support the raw algorithm, so reject requests for anything else
+ //
+ if (RNGAlgorithm != NULL &&
+ !CompareGuid (RNGAlgorithm, &gEfiRngAlgorithmRaw)) {
+ return EFI_UNSUPPORTED;
+ }
+
+ Buffer = (volatile UINT8 *)AllocatePool (RNGValueLength);
+ if (Buffer == NULL) {
+ return EFI_DEVICE_ERROR;
+ }
+
+ Dev = VIRTIO_ENTROPY_SOURCE_FROM_RNG (This);
+
+ //
+ // The Virtio RNG device may return less data than we asked it to, and can
+ // only return MAX_UINT32 bytes per invocation. So loop as long as needed to
+ // get all the entropy we were asked for.
+ //
+ for (Index = 0; Index < RNGValueLength; Index += Len) {
+ BufferSize = (UINT32)MIN (RNGValueLength - Index, (UINTN)MAX_UINT32);
+
+ VirtioPrepare (&Dev->Ring, &Indices);
+ VirtioAppendDesc (&Dev->Ring,
+ (UINTN)Buffer + Index,
+ BufferSize,
+ VRING_DESC_F_WRITE,
+ &Indices);
+
+ if (VirtioFlush (Dev->VirtIo, 0, &Dev->Ring, &Indices, &Len) !=
+ EFI_SUCCESS) {
+ Status = EFI_DEVICE_ERROR;
+ goto FreeBuffer;
+ }
+ ASSERT (Len > 0);
+ ASSERT (Len <= BufferSize);
+ }
+
+ for (Index = 0; Index < RNGValueLength; Index++) {
+ RNGValue[Index] = Buffer[Index];
+ }
+ Status = EFI_SUCCESS;
+
+FreeBuffer:
+ FreePool ((VOID *)Buffer);
+ return Status;
+}
+
+STATIC
+EFI_STATUS
+EFIAPI
+VirtioRngInit (
+ IN OUT VIRTIO_RNG_DEV *Dev
+ )
+{
+ UINT8 NextDevStat;
+ EFI_STATUS Status;
+ UINT16 QueueSize;
+ UINT32 Features;
+
+ //
+ // Execute virtio-0.9.5, 2.2.1 Device Initialization Sequence.
+ //
+ NextDevStat = 0; // step 1 -- reset device
+ Status = Dev->VirtIo->SetDeviceStatus (Dev->VirtIo, NextDevStat);
+ if (EFI_ERROR (Status)) {
+ goto Failed;
+ }
+
+ NextDevStat |= VSTAT_ACK; // step 2 -- acknowledge device presence
+ Status = Dev->VirtIo->SetDeviceStatus (Dev->VirtIo, NextDevStat);
+ if (EFI_ERROR (Status)) {
+ goto Failed;
+ }
+
+ NextDevStat |= VSTAT_DRIVER; // step 3 -- we know how to drive it
+ Status = Dev->VirtIo->SetDeviceStatus (Dev->VirtIo, NextDevStat);
+ if (EFI_ERROR (Status)) {
+ goto Failed;
+ }
+
+ //
+ // Set Page Size - MMIO VirtIo Specific
+ //
+ Status = Dev->VirtIo->SetPageSize (Dev->VirtIo, EFI_PAGE_SIZE);
+ if (EFI_ERROR (Status)) {
+ goto Failed;
+ }
+
+ //
+ // step 4a -- retrieve and validate features
+ //
+ Status = Dev->VirtIo->GetDeviceFeatures (Dev->VirtIo, &Features);
+ if (EFI_ERROR (Status)) {
+ goto Failed;
+ }
+
+ //
+ // step 4b -- allocate request virtqueue, just use #0
+ //
+ Status = Dev->VirtIo->SetQueueSel (Dev->VirtIo, 0);
+ if (EFI_ERROR (Status)) {
+ goto Failed;
+ }
+ Status = Dev->VirtIo->GetQueueNumMax (Dev->VirtIo, &QueueSize);
+ if (EFI_ERROR (Status)) {
+ goto Failed;
+ }
+
+ //
+ // VirtioRngGetRNG() uses one descriptor
+ //
+ if (QueueSize < 1) {
+ Status = EFI_UNSUPPORTED;
+ goto Failed;
+ }
+
+ Status = VirtioRingInit (QueueSize, &Dev->Ring);
+ if (EFI_ERROR (Status)) {
+ goto Failed;
+ }
+
+ //
+ // Additional steps for MMIO: align the queue appropriately, and set the
+ // size. If anything fails from here on, we must release the ring resources.
+ //
+ Status = Dev->VirtIo->SetQueueNum (Dev->VirtIo, QueueSize);
+ if (EFI_ERROR (Status)) {
+ goto ReleaseQueue;
+ }
+
+ Status = Dev->VirtIo->SetQueueAlign (Dev->VirtIo, EFI_PAGE_SIZE);
+ if (EFI_ERROR (Status)) {
+ goto ReleaseQueue;
+ }
+
+ //
+ // step 4c -- Report GPFN (guest-physical frame number) of queue.
+ //
+ Status = Dev->VirtIo->SetQueueAddress (Dev->VirtIo,
+ (UINT32) ((UINTN) Dev->Ring.Base >> EFI_PAGE_SHIFT));
+ if (EFI_ERROR (Status)) {
+ goto ReleaseQueue;
+ }
+
+ //
+ // step 5 -- Report understood features and guest-tuneables. None are
+ // currently defined for VirtioRng, and no generic features are needed by
+ // this driver.
+ //
+ Status = Dev->VirtIo->SetGuestFeatures (Dev->VirtIo, 0);
+ if (EFI_ERROR (Status)) {
+ goto ReleaseQueue;
+ }
+
+ //
+ // step 6 -- initialization complete
+ //
+ NextDevStat |= VSTAT_DRIVER_OK;
+ Status = Dev->VirtIo->SetDeviceStatus (Dev->VirtIo, NextDevStat);
+ if (EFI_ERROR (Status)) {
+ goto ReleaseQueue;
+ }
+
+ //
+ // populate the exported interface's attributes
+ //
+ Dev->Rng.GetInfo = VirtioRngGetInfo;
+ Dev->Rng.GetRNG = VirtioRngGetRNG;
+
+ return EFI_SUCCESS;
+
+ReleaseQueue:
+ VirtioRingUninit (&Dev->Ring);
+
+Failed:
+ //
+ // Notify the host about our failure to setup: virtio-0.9.5, 2.2.2.1 Device
+ // Status. VirtIo access failure here should not mask the original error.
+ //
+ NextDevStat |= VSTAT_FAILED;
+ Dev->VirtIo->SetDeviceStatus (Dev->VirtIo, NextDevStat);
+
+ return Status; // reached only via Failed above
+}
+
+
+STATIC
+VOID
+EFIAPI
+VirtioRngUninit (
+ IN OUT VIRTIO_RNG_DEV *Dev
+ )
+{
+ //
+ // Reset the virtual device -- see virtio-0.9.5, 2.2.2.1 Device Status. When
+ // VIRTIO_CFG_WRITE() returns, the host will have learned to stay away from
+ // the old comms area.
+ //
+ Dev->VirtIo->SetDeviceStatus (Dev->VirtIo, 0);
+ VirtioRingUninit (&Dev->Ring);
+}
+
+//
+// Event notification function enqueued by ExitBootServices().
+//
+
+STATIC
+VOID
+EFIAPI
+VirtioRngExitBoot (
+ IN EFI_EVENT Event,
+ IN VOID *Context
+ )
+{
+ VIRTIO_RNG_DEV *Dev;
+
+ //
+ // Reset the device. This causes the hypervisor to forget about the virtio
+ // ring.
+ //
+ // We allocated said ring in EfiBootServicesData type memory, and code
+ // executing after ExitBootServices() is permitted to overwrite it.
+ //
+ Dev = Context;
+ Dev->VirtIo->SetDeviceStatus (Dev->VirtIo, 0);
+}
+
+
+//
+// Probe, start and stop functions of this driver, called by the DXE core for
+// specific devices.
+//
+// The following specifications document these interfaces:
+// - Driver Writer's Guide for UEFI 2.3.1 v1.01, 9 Driver Binding Protocol
+// - UEFI Spec 2.3.1 + Errata C, 10.1 EFI Driver Binding Protocol
+//
+// The implementation follows:
+// - Driver Writer's Guide for UEFI 2.3.1 v1.01
+// - 5.1.3.4 OpenProtocol() and CloseProtocol()
+// - UEFI Spec 2.3.1 + Errata C
+// - 6.3 Protocol Handler Services
+//
+
+STATIC
+EFI_STATUS
+EFIAPI
+VirtioRngDriverBindingSupported (
+ IN EFI_DRIVER_BINDING_PROTOCOL *This,
+ IN EFI_HANDLE DeviceHandle,
+ IN EFI_DEVICE_PATH_PROTOCOL *RemainingDevicePath
+ )
+{
+ EFI_STATUS Status;
+ VIRTIO_DEVICE_PROTOCOL *VirtIo;
+
+ //
+ // Attempt to open the device with the VirtIo set of interfaces. On success,
+ // the protocol is "instantiated" for the VirtIo device. Covers duplicate
+ // open attempts (EFI_ALREADY_STARTED).
+ //
+ Status = gBS->OpenProtocol (
+ DeviceHandle, // candidate device
+ &gVirtioDeviceProtocolGuid, // for generic VirtIo access
+ (VOID **)&VirtIo, // handle to instantiate
+ This->DriverBindingHandle, // requestor driver identity
+ DeviceHandle, // ControllerHandle, according to
+ // the UEFI Driver Model
+ EFI_OPEN_PROTOCOL_BY_DRIVER // get exclusive VirtIo access to
+ // the device; to be released
+ );
+ if (EFI_ERROR (Status)) {
+ return Status;
+ }
+
+ if (VirtIo->SubSystemDeviceId != VIRTIO_SUBSYSTEM_ENTROPY_SOURCE) {
+ Status = EFI_UNSUPPORTED;
+ }
+
+ //
+ // We needed VirtIo access only transitorily, to see whether we support the
+ // device or not.
+ //
+ gBS->CloseProtocol (DeviceHandle, &gVirtioDeviceProtocolGuid,
+ This->DriverBindingHandle, DeviceHandle);
+ return Status;
+}
+
+STATIC
+EFI_STATUS
+EFIAPI
+VirtioRngDriverBindingStart (
+ IN EFI_DRIVER_BINDING_PROTOCOL *This,
+ IN EFI_HANDLE DeviceHandle,
+ IN EFI_DEVICE_PATH_PROTOCOL *RemainingDevicePath
+ )
+{
+ VIRTIO_RNG_DEV *Dev;
+ EFI_STATUS Status;
+
+ Dev = (VIRTIO_RNG_DEV *) AllocateZeroPool (sizeof *Dev);
+ if (Dev == NULL) {
+ return EFI_OUT_OF_RESOURCES;
+ }
+
+ Status = gBS->OpenProtocol (DeviceHandle, &gVirtioDeviceProtocolGuid,
+ (VOID **)&Dev->VirtIo, This->DriverBindingHandle,
+ DeviceHandle, EFI_OPEN_PROTOCOL_BY_DRIVER);
+ if (EFI_ERROR (Status)) {
+ goto FreeVirtioRng;
+ }
+
+ //
+ // VirtIo access granted, configure virtio-rng device.
+ //
+ Status = VirtioRngInit (Dev);
+ if (EFI_ERROR (Status)) {
+ goto CloseVirtIo;
+ }
+
+ Status = gBS->CreateEvent (EVT_SIGNAL_EXIT_BOOT_SERVICES, TPL_CALLBACK,
+ &VirtioRngExitBoot, Dev, &Dev->ExitBoot);
+ if (EFI_ERROR (Status)) {
+ goto UninitDev;
+ }
+
+ //
+ // Setup complete, attempt to export the driver instance's EFI_RNG_PROTOCOL
+ // interface.
+ //
+ Dev->Signature = VIRTIO_RNG_SIG;
+ Status = gBS->InstallProtocolInterface (&DeviceHandle,
+ &gEfiRngProtocolGuid, EFI_NATIVE_INTERFACE,
+ &Dev->Rng);
+ if (EFI_ERROR (Status)) {
+ goto CloseExitBoot;
+ }
+
+ return EFI_SUCCESS;
+
+CloseExitBoot:
+ gBS->CloseEvent (Dev->ExitBoot);
+
+UninitDev:
+ VirtioRngUninit (Dev);
+
+CloseVirtIo:
+ gBS->CloseProtocol (DeviceHandle, &gVirtioDeviceProtocolGuid,
+ This->DriverBindingHandle, DeviceHandle);
+
+FreeVirtioRng:
+ FreePool (Dev);
+
+ return Status;
+}
+
+
+STATIC
+EFI_STATUS
+EFIAPI
+VirtioRngDriverBindingStop (
+ IN EFI_DRIVER_BINDING_PROTOCOL *This,
+ IN EFI_HANDLE DeviceHandle,
+ IN UINTN NumberOfChildren,
+ IN EFI_HANDLE *ChildHandleBuffer
+ )
+{
+ EFI_STATUS Status;
+ EFI_RNG_PROTOCOL *Rng;
+ VIRTIO_RNG_DEV *Dev;
+
+ Status = gBS->OpenProtocol (
+ DeviceHandle, // candidate device
+ &gEfiRngProtocolGuid, // retrieve the RNG iface
+ (VOID **)&Rng, // target pointer
+ This->DriverBindingHandle, // requestor driver ident.
+ DeviceHandle, // lookup req. for dev.
+ EFI_OPEN_PROTOCOL_GET_PROTOCOL // lookup only, no new ref.
+ );
+ if (EFI_ERROR (Status)) {
+ return Status;
+ }
+
+ Dev = VIRTIO_ENTROPY_SOURCE_FROM_RNG (Rng);
+
+ //
+ // Handle Stop() requests for in-use driver instances gracefully.
+ //
+ Status = gBS->UninstallProtocolInterface (DeviceHandle,
+ &gEfiRngProtocolGuid, &Dev->Rng);
+ if (EFI_ERROR (Status)) {
+ return Status;
+ }
+
+ gBS->CloseEvent (Dev->ExitBoot);
+
+ VirtioRngUninit (Dev);
+
+ gBS->CloseProtocol (DeviceHandle, &gVirtioDeviceProtocolGuid,
+ This->DriverBindingHandle, DeviceHandle);
+
+ FreePool (Dev);
+
+ return EFI_SUCCESS;
+}
+
+
+//
+// The static object that groups the Supported() (ie. probe), Start() and
+// Stop() functions of the driver together. Refer to UEFI Spec 2.3.1 + Errata
+// C, 10.1 EFI Driver Binding Protocol.
+//
+STATIC EFI_DRIVER_BINDING_PROTOCOL gDriverBinding = {
+ &VirtioRngDriverBindingSupported,
+ &VirtioRngDriverBindingStart,
+ &VirtioRngDriverBindingStop,
+ 0x10, // Version, must be in [0x10 .. 0xFFFFFFEF] for IHV-developed drivers
+ NULL, // ImageHandle, to be overwritten by
+ // EfiLibInstallDriverBindingComponentName2() in VirtioRngEntryPoint()
+ NULL // DriverBindingHandle, ditto
+};
+
+
+//
+// The purpose of the following scaffolding (EFI_COMPONENT_NAME_PROTOCOL and
+// EFI_COMPONENT_NAME2_PROTOCOL implementation) is to format the driver's name
+// in English, for display on standard console devices. This is recommended for
+// UEFI drivers that follow the UEFI Driver Model. Refer to the Driver Writer's
+// Guide for UEFI 2.3.1 v1.01, 11 UEFI Driver and Controller Names.
+//
+
+STATIC
+EFI_UNICODE_STRING_TABLE mDriverNameTable[] = {
+ { "eng;en", L"Virtio Random Number Generator Driver" },
+ { NULL, NULL }
+};
+
+STATIC
+EFI_COMPONENT_NAME_PROTOCOL gComponentName;
+
+STATIC
+EFI_STATUS
+EFIAPI
+VirtioRngGetDriverName (
+ IN EFI_COMPONENT_NAME_PROTOCOL *This,
+ IN CHAR8 *Language,
+ OUT CHAR16 **DriverName
+ )
+{
+ return LookupUnicodeString2 (
+ Language,
+ This->SupportedLanguages,
+ mDriverNameTable,
+ DriverName,
+ (BOOLEAN)(This == &gComponentName) // Iso639Language
+ );
+}
+
+STATIC
+EFI_STATUS
+EFIAPI
+VirtioRngGetDeviceName (
+ IN EFI_COMPONENT_NAME_PROTOCOL *This,
+ IN EFI_HANDLE DeviceHandle,
+ IN EFI_HANDLE ChildHandle,
+ IN CHAR8 *Language,
+ OUT CHAR16 **ControllerName
+ )
+{
+ return EFI_UNSUPPORTED;
+}
+
+STATIC
+EFI_COMPONENT_NAME_PROTOCOL gComponentName = {
+ &VirtioRngGetDriverName,
+ &VirtioRngGetDeviceName,
+ "eng" // SupportedLanguages, ISO 639-2 language codes
+};
+
+STATIC
+EFI_COMPONENT_NAME2_PROTOCOL gComponentName2 = {
+ (EFI_COMPONENT_NAME2_GET_DRIVER_NAME) &VirtioRngGetDriverName,
+ (EFI_COMPONENT_NAME2_GET_CONTROLLER_NAME) &VirtioRngGetDeviceName,
+ "en" // SupportedLanguages, RFC 4646 language codes
+};
+
+
+//
+// Entry point of this driver.
+//
+EFI_STATUS
+EFIAPI
+VirtioRngEntryPoint (
+ IN EFI_HANDLE ImageHandle,
+ IN EFI_SYSTEM_TABLE *SystemTable
+ )
+{
+ return EfiLibInstallDriverBindingComponentName2 (
+ ImageHandle,
+ SystemTable,
+ &gDriverBinding,
+ ImageHandle,
+ &gComponentName,
+ &gComponentName2
+ );
+}
|