/** @file This driver produces Virtio Device Protocol instances for Virtio PCI devices. Copyright (C) 2012, Red Hat, Inc. Copyright (c) 2012 - 2016, Intel Corporation. All rights reserved.
Copyright (C) 2013, ARM Ltd. Copyright (C) 2017, AMD Inc, All rights reserved.
SPDX-License-Identifier: BSD-2-Clause-Patent **/ #include #include #include #include #include #include #include "VirtioPciDevice.h" STATIC VIRTIO_DEVICE_PROTOCOL mDeviceProtocolTemplate = { 0, // Revision 0, // SubSystemDeviceId VirtioPciGetDeviceFeatures, // GetDeviceFeatures VirtioPciSetGuestFeatures, // SetGuestFeatures VirtioPciSetQueueAddress, // SetQueueAddress VirtioPciSetQueueSel, // SetQueueSel VirtioPciSetQueueNotify, // SetQueueNotify VirtioPciSetQueueAlignment, // SetQueueAlignment VirtioPciSetPageSize, // SetPageSize VirtioPciGetQueueSize, // GetQueueNumMax VirtioPciSetQueueSize, // SetQueueNum VirtioPciGetDeviceStatus, // GetDeviceStatus VirtioPciSetDeviceStatus, // SetDeviceStatus VirtioPciDeviceWrite, // WriteDevice VirtioPciDeviceRead, // ReadDevice VirtioPciAllocateSharedPages, // AllocateSharedPages VirtioPciFreeSharedPages, // FreeSharedPages VirtioPciMapSharedBuffer, // MapSharedBuffer VirtioPciUnmapSharedBuffer, // UnmapSharedBuffer }; /** Read a word from Region 0 of the device specified by PciIo. Region 0 must be an iomem region. This is an internal function for the PCI implementation of the protocol. @param[in] Dev Virtio PCI device. @param[in] FieldOffset Source offset. @param[in] FieldSize Source field size, must be in { 1, 2, 4, 8 }. @param[in] BufferSize Number of bytes available in the target buffer. Must equal FieldSize. @param[out] Buffer Target buffer. @return Status code returned by PciIo->Io.Read(). **/ EFI_STATUS EFIAPI VirtioPciIoRead ( IN VIRTIO_PCI_DEVICE *Dev, IN UINTN FieldOffset, IN UINTN FieldSize, IN UINTN BufferSize, OUT VOID *Buffer ) { UINTN Count; EFI_PCI_IO_PROTOCOL_WIDTH Width; EFI_PCI_IO_PROTOCOL *PciIo; ASSERT (FieldSize == BufferSize); PciIo = Dev->PciIo; Count = 1; switch (FieldSize) { case 1: Width = EfiPciIoWidthUint8; break; case 2: Width = EfiPciIoWidthUint16; break; case 8: // // The 64bit PCI I/O is broken down into two 32bit reads to prevent // any alignment or width issues. // The UEFI spec says under EFI_PCI_IO_PROTOCOL.Io.Write(): // // The I/O operations are carried out exactly as requested. The caller // is responsible for any alignment and I/O width issues which the // bus, device, platform, or type of I/O might require. For example on // some platforms, width requests of EfiPciIoWidthUint64 do not work. // Count = 2; // // fall through // case 4: Width = EfiPciIoWidthUint32; break; default: ASSERT (FALSE); return EFI_INVALID_PARAMETER; } return PciIo->Io.Read ( PciIo, Width, PCI_BAR_IDX0, FieldOffset, Count, Buffer ); } /** Write a word into Region 0 of the device specified by PciIo. Region 0 must be an iomem region. This is an internal function for the PCI implementation of the protocol. @param[in] Dev Virtio PCI device. @param[in] FieldOffset Destination offset. @param[in] FieldSize Destination field size, must be in { 1, 2, 4, 8 }. @param[in] Value Little endian value to write, converted to UINT64. The least significant FieldSize bytes will be used. @return Status code returned by PciIo->Io.Write(). **/ EFI_STATUS EFIAPI VirtioPciIoWrite ( IN VIRTIO_PCI_DEVICE *Dev, IN UINTN FieldOffset, IN UINTN FieldSize, IN UINT64 Value ) { UINTN Count; EFI_PCI_IO_PROTOCOL_WIDTH Width; EFI_PCI_IO_PROTOCOL *PciIo; PciIo = Dev->PciIo; Count = 1; switch (FieldSize) { case 1: Width = EfiPciIoWidthUint8; break; case 2: Width = EfiPciIoWidthUint16; break; case 8: // // The 64bit PCI I/O is broken down into two 32bit writes to prevent // any alignment or width issues. // The UEFI spec says under EFI_PCI_IO_PROTOCOL.Io.Write(): // // The I/O operations are carried out exactly as requested. The caller // is responsible for any alignment and I/O width issues which the // bus, device, platform, or type of I/O might require. For example on // some platforms, width requests of EfiPciIoWidthUint64 do not work // Count = Count * 2; // // fall through // case 4: Width = EfiPciIoWidthUint32; break; default: ASSERT (FALSE); return EFI_INVALID_PARAMETER; } return PciIo->Io.Write ( PciIo, Width, PCI_BAR_IDX0, FieldOffset, Count, &Value ); } /** Device probe function for this driver. The DXE core calls this function for any given device in order to see if the driver can drive the device. @param[in] This The EFI_DRIVER_BINDING_PROTOCOL object incorporating this driver (independently of any device). @param[in] DeviceHandle The device to probe. @param[in] RemainingDevicePath Relevant only for bus drivers, ignored. @retval EFI_SUCCESS The driver supports the device being probed. @retval EFI_UNSUPPORTED Based on virtio-pci discovery, we do not support the device. @return Error codes from the OpenProtocol() boot service or the PciIo protocol. **/ STATIC EFI_STATUS EFIAPI VirtioPciDeviceBindingSupported ( IN EFI_DRIVER_BINDING_PROTOCOL *This, IN EFI_HANDLE DeviceHandle, IN EFI_DEVICE_PATH_PROTOCOL *RemainingDevicePath ) { EFI_STATUS Status; EFI_PCI_IO_PROTOCOL *PciIo; PCI_TYPE00 Pci; // // Attempt to open the device with the PciIo set of interfaces. On success, // the protocol is "instantiated" for the PCI device. Covers duplicate open // attempts (EFI_ALREADY_STARTED). // Status = gBS->OpenProtocol ( DeviceHandle, // candidate device &gEfiPciIoProtocolGuid, // for generic PCI access (VOID **)&PciIo, // handle to instantiate This->DriverBindingHandle, // requestor driver identity DeviceHandle, // ControllerHandle, according to // the UEFI Driver Model EFI_OPEN_PROTOCOL_BY_DRIVER // get exclusive PciIo access to // the device; to be released ); if (EFI_ERROR (Status)) { return Status; } // // Read entire PCI configuration header for more extensive check ahead. // Status = PciIo->Pci.Read ( PciIo, // (protocol, device) // handle EfiPciIoWidthUint32, // access width & copy // mode 0, // Offset sizeof Pci / sizeof (UINT32), // Count &Pci // target buffer ); if (Status == EFI_SUCCESS) { // // virtio-0.9.5, 2.1 PCI Discovery // if ((Pci.Hdr.VendorId == VIRTIO_VENDOR_ID) && (Pci.Hdr.DeviceId >= 0x1000) && (Pci.Hdr.DeviceId <= 0x103F) && (Pci.Hdr.RevisionID == 0x00)) { Status = EFI_SUCCESS; } else { Status = EFI_UNSUPPORTED; } } // // We needed PCI IO access only transitorily, to see whether we support the // device or not. // gBS->CloseProtocol (DeviceHandle, &gEfiPciIoProtocolGuid, This->DriverBindingHandle, DeviceHandle); return Status; } /** Initialize the VirtIo PCI Device @param[in, out] Dev The driver instance to configure. The caller is responsible for Device->PciIo's validity (ie. working IO access to the underlying virtio-pci device). @retval EFI_SUCCESS Setup complete. @retval EFI_UNSUPPORTED The underlying IO device doesn't support the provided address offset and read size. @return Error codes from PciIo->Pci.Read(). **/ STATIC EFI_STATUS EFIAPI VirtioPciInit ( IN OUT VIRTIO_PCI_DEVICE *Device ) { EFI_STATUS Status; EFI_PCI_IO_PROTOCOL *PciIo; PCI_TYPE00 Pci; ASSERT (Device != NULL); PciIo = Device->PciIo; ASSERT (PciIo != NULL); ASSERT (PciIo->Pci.Read != NULL); Status = PciIo->Pci.Read ( PciIo, // (protocol, device) // handle EfiPciIoWidthUint32, // access width & copy // mode 0, // Offset sizeof (Pci) / sizeof (UINT32), // Count &Pci // target buffer ); if (EFI_ERROR (Status)) { return Status; } // // Copy protocol template // CopyMem (&Device->VirtioDevice, &mDeviceProtocolTemplate, sizeof (VIRTIO_DEVICE_PROTOCOL)); // // Initialize the protocol interface attributes // Device->VirtioDevice.Revision = VIRTIO_SPEC_REVISION (0, 9, 5); Device->VirtioDevice.SubSystemDeviceId = Pci.Device.SubsystemID; // // Note: We don't support the MSI-X capability. If we did, // the offset would become 24 after enabling MSI-X. // Device->DeviceSpecificConfigurationOffset = VIRTIO_DEVICE_SPECIFIC_CONFIGURATION_OFFSET_PCI; return EFI_SUCCESS; } /** Uninitialize the internals of a virtio-pci device that has been successfully set up with VirtioPciInit(). @param[in, out] Dev The device to clean up. **/ STATIC VOID EFIAPI VirtioPciUninit ( IN OUT VIRTIO_PCI_DEVICE *Device ) { // Note: This function mirrors VirtioPciInit() that does not allocate any // resources - there's nothing to free here. } /** After we've pronounced support for a specific device in DriverBindingSupported(), we start managing said device (passed in by the Driver Execution Environment) with the following service. See DriverBindingSupported() for specification references. @param[in] This The EFI_DRIVER_BINDING_PROTOCOL object incorporating this driver (independently of any device). @param[in] DeviceHandle The supported device to drive. @param[in] RemainingDevicePath Relevant only for bus drivers, ignored. @retval EFI_SUCCESS Driver instance has been created and initialized for the virtio-pci device, it is now accessible via VIRTIO_DEVICE_PROTOCOL. @retval EFI_OUT_OF_RESOURCES Memory allocation failed. @return Error codes from the OpenProtocol() boot service, the PciIo protocol, VirtioPciInit(), or the InstallProtocolInterface() boot service. **/ STATIC EFI_STATUS EFIAPI VirtioPciDeviceBindingStart ( IN EFI_DRIVER_BINDING_PROTOCOL *This, IN EFI_HANDLE DeviceHandle, IN EFI_DEVICE_PATH_PROTOCOL *RemainingDevicePath ) { VIRTIO_PCI_DEVICE *Device; EFI_STATUS Status; Device = (VIRTIO_PCI_DEVICE *) AllocateZeroPool (sizeof *Device); if (Device == NULL) { return EFI_OUT_OF_RESOURCES; } Status = gBS->OpenProtocol (DeviceHandle, &gEfiPciIoProtocolGuid, (VOID **)&Device->PciIo, This->DriverBindingHandle, DeviceHandle, EFI_OPEN_PROTOCOL_BY_DRIVER); if (EFI_ERROR (Status)) { goto FreeVirtioPci; } // // We must retain and ultimately restore the original PCI attributes of the // device. See Driver Writer's Guide for UEFI 2.3.1 v1.01, 18.3 PCI drivers / // 18.3.2 Start() and Stop(). // // The third parameter ("Attributes", input) is ignored by the Get operation. // The fourth parameter ("Result", output) is ignored by the Enable and Set // operations. // // For virtio-pci we only need IO space access. // Status = Device->PciIo->Attributes (Device->PciIo, EfiPciIoAttributeOperationGet, 0, &Device->OriginalPciAttributes); if (EFI_ERROR (Status)) { goto ClosePciIo; } Status = Device->PciIo->Attributes ( Device->PciIo, EfiPciIoAttributeOperationEnable, (EFI_PCI_IO_ATTRIBUTE_IO | EFI_PCI_IO_ATTRIBUTE_BUS_MASTER), NULL ); if (EFI_ERROR (Status)) { goto ClosePciIo; } // // PCI IO access granted, configure protocol instance // Status = VirtioPciInit (Device); if (EFI_ERROR (Status)) { goto RestorePciAttributes; } // // Setup complete, attempt to export the driver instance's VirtioDevice // interface. // Device->Signature = VIRTIO_PCI_DEVICE_SIGNATURE; Status = gBS->InstallProtocolInterface (&DeviceHandle, &gVirtioDeviceProtocolGuid, EFI_NATIVE_INTERFACE, &Device->VirtioDevice); if (EFI_ERROR (Status)) { goto UninitDev; } return EFI_SUCCESS; UninitDev: VirtioPciUninit (Device); RestorePciAttributes: Device->PciIo->Attributes (Device->PciIo, EfiPciIoAttributeOperationSet, Device->OriginalPciAttributes, NULL); ClosePciIo: gBS->CloseProtocol (DeviceHandle, &gEfiPciIoProtocolGuid, This->DriverBindingHandle, DeviceHandle); FreeVirtioPci: FreePool (Device); return Status; } /** Stop driving the Virtio PCI device @param[in] This The EFI_DRIVER_BINDING_PROTOCOL object incorporating this driver (independently of any device). @param[in] DeviceHandle Stop driving this device. @param[in] NumberOfChildren Since this function belongs to a device driver only (as opposed to a bus driver), the caller environment sets NumberOfChildren to zero, and we ignore it. @param[in] ChildHandleBuffer Ignored (corresponding to NumberOfChildren). @retval EFI_SUCCESS Driver instance has been stopped and the PCI configuration attributes have been restored. @return Error codes from the OpenProtocol() or CloseProtocol(), UninstallProtocolInterface() boot services. **/ STATIC EFI_STATUS EFIAPI VirtioPciDeviceBindingStop ( IN EFI_DRIVER_BINDING_PROTOCOL *This, IN EFI_HANDLE DeviceHandle, IN UINTN NumberOfChildren, IN EFI_HANDLE *ChildHandleBuffer ) { EFI_STATUS Status; VIRTIO_DEVICE_PROTOCOL *VirtioDevice; VIRTIO_PCI_DEVICE *Device; Status = gBS->OpenProtocol ( DeviceHandle, // candidate device &gVirtioDeviceProtocolGuid, // retrieve the VirtIo iface (VOID **)&VirtioDevice, // target pointer This->DriverBindingHandle, // requestor driver identity DeviceHandle, // requesting lookup for dev. EFI_OPEN_PROTOCOL_GET_PROTOCOL // lookup only, no ref. added ); if (EFI_ERROR (Status)) { return Status; } Device = VIRTIO_PCI_DEVICE_FROM_VIRTIO_DEVICE (VirtioDevice); // // Handle Stop() requests for in-use driver instances gracefully. // Status = gBS->UninstallProtocolInterface (DeviceHandle, &gVirtioDeviceProtocolGuid, &Device->VirtioDevice); if (EFI_ERROR (Status)) { return Status; } VirtioPciUninit (Device); Device->PciIo->Attributes (Device->PciIo, EfiPciIoAttributeOperationSet, Device->OriginalPciAttributes, NULL); Status = gBS->CloseProtocol (DeviceHandle, &gEfiPciIoProtocolGuid, This->DriverBindingHandle, DeviceHandle); FreePool (Device); return Status; } // // 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 = { &VirtioPciDeviceBindingSupported, &VirtioPciDeviceBindingStart, &VirtioPciDeviceBindingStop, 0x10, // Version, must be in [0x10 .. 0xFFFFFFEF] for IHV-developed drivers NULL, // ImageHandle, to be overwritten by // EfiLibInstallDriverBindingComponentName2() in VirtioPciEntryPoint() 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 PCI Driver" }, { NULL, NULL } }; STATIC EFI_COMPONENT_NAME_PROTOCOL gComponentName; EFI_STATUS EFIAPI VirtioPciGetDriverName ( IN EFI_COMPONENT_NAME_PROTOCOL *This, IN CHAR8 *Language, OUT CHAR16 **DriverName ) { return LookupUnicodeString2 ( Language, This->SupportedLanguages, mDriverNameTable, DriverName, (BOOLEAN)(This == &gComponentName) // Iso639Language ); } EFI_STATUS EFIAPI VirtioPciGetDeviceName ( 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 = { &VirtioPciGetDriverName, &VirtioPciGetDeviceName, "eng" // SupportedLanguages, ISO 639-2 language codes }; STATIC EFI_COMPONENT_NAME2_PROTOCOL gComponentName2 = { (EFI_COMPONENT_NAME2_GET_DRIVER_NAME) &VirtioPciGetDriverName, (EFI_COMPONENT_NAME2_GET_CONTROLLER_NAME) &VirtioPciGetDeviceName, "en" // SupportedLanguages, RFC 4646 language codes }; // // Entry point of this driver. // EFI_STATUS EFIAPI VirtioPciDeviceEntryPoint ( IN EFI_HANDLE ImageHandle, IN EFI_SYSTEM_TABLE *SystemTable ) { return EfiLibInstallDriverBindingComponentName2 ( ImageHandle, SystemTable, &gDriverBinding, ImageHandle, &gComponentName, &gComponentName2 ); }