RFC: https://bugzilla.tianocore.org/show_bug.cgi?id=3429
The IOMMU protocol driver provides capabilities to set a DMA access
attribute and methods to allocate, free, map and unmap the DMA memory
for the PCI Bus devices.
The current IoMmuDxe driver supports DMA operations inside SEV guest.
To support DMA operation in TDX guest, mIoMmuType is added to determine
if it is Legac guest, SEV guest or TDX guest.
Due to security reasons all DMA operations inside the SEV/TDX guest must
be performed on shared pages. The IOMMU protocol driver for the SEV/TDX
guest uses a bounce buffer to map guest DMA buffer to shared pages in
order to provide the support for DMA operations inside SEV/TDX guest.
The call of SEV or TDX specific function to set/clear EncMask/SharedBit
is determined by mIoMmuType.
Cc: Ard Biesheuvel <ardb+tianocore@kernel.org>
Cc: Jordan Justen <jordan.l.justen@intel.com>
Cc: Brijesh Singh <brijesh.singh@amd.com>
Cc: Erdem Aktas <erdemaktas@google.com>
Cc: James Bottomley <jejb@linux.ibm.com>
Cc: Jiewen Yao <jiewen.yao@intel.com>
Cc: Tom Lendacky <thomas.lendacky@amd.com>
Cc: Gerd Hoffmann <kraxel@redhat.com>
Signed-off-by: Min Xu <min.m.xu@intel.com>
---
OvmfPkg/IoMmuDxe/AmdSevIoMmu.c | 104 +++++++++++++++++++++------------
OvmfPkg/IoMmuDxe/AmdSevIoMmu.h | 6 +-
OvmfPkg/IoMmuDxe/IoMmuDxe.c | 6 +-
OvmfPkg/IoMmuDxe/IoMmuDxe.inf | 5 ++
OvmfPkg/OvmfPkgX64.dsc | 2 +
5 files changed, 81 insertions(+), 42 deletions(-)
diff --git a/OvmfPkg/IoMmuDxe/AmdSevIoMmu.c b/OvmfPkg/IoMmuDxe/AmdSevIoMmu.c
index b30628078f73..f6ccb4b2c675 100644
--- a/OvmfPkg/IoMmuDxe/AmdSevIoMmu.c
+++ b/OvmfPkg/IoMmuDxe/AmdSevIoMmu.c
@@ -1,9 +1,9 @@
/** @file
The protocol provides support to allocate, free, map and umap a DMA buffer
- for bus master (e.g PciHostBridge). When SEV is enabled, the DMA operations
- must be performed on unencrypted buffer hence we use a bounce buffer to map
- the guest buffer into an unencrypted DMA buffer.
+ for bus master (e.g PciHostBridge). When SEV or TDX is enabled, the DMA
+ operations must be performed on unencrypted buffer hence we use a bounce
+ buffer to map the guest buffer into an unencrypted DMA buffer.
Copyright (c) 2017, AMD Inc. All rights reserved.<BR>
Copyright (c) 2017, Intel Corporation. All rights reserved.<BR>
@@ -12,6 +12,8 @@
**/
+#include <Library/PcdLib.h>
+#include <ConfidentialComputingGuestAttr.h>
#include "AmdSevIoMmu.h"
#define MAP_INFO_SIG SIGNATURE_64 ('M', 'A', 'P', '_', 'I', 'N', 'F', 'O')
@@ -74,7 +76,7 @@ typedef struct {
/**
Provides the controller-specific addresses required to access system memory
- from a DMA bus master. On SEV guest, the DMA operations must be performed on
+ from a DMA bus master. On SEV/TDX guest, the DMA operations must be performed on
shared buffer hence we allocate a bounce buffer to map the HostAddress to a
DeviceAddress. The Encryption attribute is removed from the DeviceAddress
buffer.
@@ -246,14 +248,29 @@ IoMmuMap (
goto FreeMapInfo;
}
- //
- // Clear the memory encryption mask on the plaintext buffer.
- //
- Status = MemEncryptSevClearPageEncMask (
- 0,
- MapInfo->PlainTextAddress,
- MapInfo->NumberOfPages
- );
+ if (CC_GUEST_IS_SEV (PcdGet64 (PcdConfidentialComputingGuestAttr))) {
+ //
+ // Clear the memory encryption mask on the plaintext buffer.
+ //
+ Status = MemEncryptSevClearPageEncMask (
+ 0,
+ MapInfo->PlainTextAddress,
+ MapInfo->NumberOfPages
+ );
+ } else if (CC_GUEST_IS_TDX (PcdGet64 (PcdConfidentialComputingGuestAttr))) {
+ //
+ // Set the memory shared bit.
+ //
+ Status = MemEncryptTdxSetPageSharedBit (
+ 0,
+ MapInfo->PlainTextAddress,
+ MapInfo->NumberOfPages
+ );
+
+ } else {
+ ASSERT (FALSE);
+ }
+
ASSERT_EFI_ERROR (Status);
if (EFI_ERROR (Status)) {
CpuDeadLoop ();
@@ -353,7 +370,7 @@ IoMmuUnmapWorker (
}
MapInfo = (MAP_INFO *)Mapping;
-
+ Status = EFI_SUCCESS;
//
// set CommonBufferHeader to suppress incorrect compiler/analyzer warnings
//
@@ -399,15 +416,30 @@ IoMmuUnmapWorker (
break;
}
- //
- // Restore the memory encryption mask on the area we used to hold the
- // plaintext.
- //
- Status = MemEncryptSevSetPageEncMask (
- 0,
- MapInfo->PlainTextAddress,
- MapInfo->NumberOfPages
- );
+ if (CC_GUEST_IS_SEV (PcdGet64 (PcdConfidentialComputingGuestAttr))) {
+ //
+ // Restore the memory encryption mask on the area we used to hold the
+ // plaintext.
+ //
+ Status = MemEncryptSevSetPageEncMask (
+ 0,
+ MapInfo->PlainTextAddress,
+ MapInfo->NumberOfPages
+ );
+ } else if (CC_GUEST_IS_TDX (PcdGet64 (PcdConfidentialComputingGuestAttr))) {
+ //
+ // Restore the memory shared bit mask on the area we used to hold the
+ // plaintext.
+ //
+ Status = MemEncryptTdxClearPageSharedBit (
+ 0,
+ MapInfo->PlainTextAddress,
+ MapInfo->NumberOfPages
+ );
+ } else {
+ ASSERT (FALSE);
+ }
+
ASSERT_EFI_ERROR (Status);
if (EFI_ERROR (Status)) {
CpuDeadLoop ();
@@ -731,7 +763,7 @@ IoMmuSetAttribute (
return EFI_UNSUPPORTED;
}
-EDKII_IOMMU_PROTOCOL mAmdSev = {
+EDKII_IOMMU_PROTOCOL mIoMmu = {
EDKII_IOMMU_PROTOCOL_REVISION,
IoMmuSetAttribute,
IoMmuMap,
@@ -763,7 +795,7 @@ EDKII_IOMMU_PROTOCOL mAmdSev = {
STATIC
VOID
EFIAPI
-AmdSevExitBoot (
+IoMmuExitBoot (
IN EFI_EVENT Event,
IN VOID *EventToSignal
)
@@ -771,11 +803,11 @@ AmdSevExitBoot (
//
// (1) The NotifyFunctions of all the events in
// EFI_EVENT_GROUP_EXIT_BOOT_SERVICES will have been queued before
- // AmdSevExitBoot() is entered.
+ // IoMmuExitBoot() is entered.
//
- // (2) AmdSevExitBoot() is executing minimally at TPL_CALLBACK.
+ // (2) IoMmuExitBoot() is executing minimally at TPL_CALLBACK.
//
- // (3) AmdSevExitBoot() has been queued in unspecified order relative to the
+ // (3) IoMmuExitBoot() has been queued in unspecified order relative to the
// NotifyFunctions of all the other events in
// EFI_EVENT_GROUP_EXIT_BOOT_SERVICES whose NotifyTpl is the same as
// Event's.
@@ -783,13 +815,13 @@ AmdSevExitBoot (
// Consequences:
//
// - If Event's NotifyTpl is TPL_CALLBACK, then some other NotifyFunctions
- // queued at TPL_CALLBACK may be invoked after AmdSevExitBoot() returns.
+ // queued at TPL_CALLBACK may be invoked after IoMmuExitBoot() returns.
//
// - If Event's NotifyTpl is TPL_NOTIFY, then some other NotifyFunctions
- // queued at TPL_NOTIFY may be invoked after AmdSevExitBoot() returns; plus
+ // queued at TPL_NOTIFY may be invoked after IoMmuExitBoot() returns; plus
// *all* NotifyFunctions queued at TPL_CALLBACK will be invoked strictly
// after all NotifyFunctions queued at TPL_NOTIFY, including
- // AmdSevExitBoot(), have been invoked.
+ // IoMmuExitBoot(), have been invoked.
//
// - By signaling EventToSignal here, whose NotifyTpl is TPL_CALLBACK, we
// queue EventToSignal's NotifyFunction after the NotifyFunctions of *all*
@@ -815,7 +847,7 @@ AmdSevExitBoot (
STATIC
VOID
EFIAPI
-AmdSevUnmapAllMappings (
+IoMmuUnmapAllMappings (
IN EFI_EVENT Event,
IN VOID *Context
)
@@ -834,7 +866,7 @@ AmdSevUnmapAllMappings (
NextNode = GetNextNode (&mMapInfos, Node);
MapInfo = CR (Node, MAP_INFO, Link, MAP_INFO_SIG);
IoMmuUnmapWorker (
- &mAmdSev, // This
+ &mIoMmu, // This
MapInfo, // Mapping
TRUE // MemoryMapLocked
);
@@ -847,7 +879,7 @@ AmdSevUnmapAllMappings (
**/
EFI_STATUS
EFIAPI
-AmdSevInstallIoMmuProtocol (
+InstallIoMmuProtocol (
VOID
)
{
@@ -863,7 +895,7 @@ AmdSevInstallIoMmuProtocol (
Status = gBS->CreateEvent (
EVT_NOTIFY_SIGNAL, // Type
TPL_CALLBACK, // NotifyTpl
- AmdSevUnmapAllMappings, // NotifyFunction
+ IoMmuUnmapAllMappings, // NotifyFunction
NULL, // NotifyContext
&UnmapAllMappingsEvent // Event
);
@@ -878,7 +910,7 @@ AmdSevInstallIoMmuProtocol (
Status = gBS->CreateEvent (
EVT_SIGNAL_EXIT_BOOT_SERVICES, // Type
TPL_CALLBACK, // NotifyTpl
- AmdSevExitBoot, // NotifyFunction
+ IoMmuExitBoot, // NotifyFunction
UnmapAllMappingsEvent, // NotifyContext
&ExitBootEvent // Event
);
@@ -889,7 +921,7 @@ AmdSevInstallIoMmuProtocol (
Handle = NULL;
Status = gBS->InstallMultipleProtocolInterfaces (
&Handle,
- &gEdkiiIoMmuProtocolGuid, &mAmdSev,
+ &gEdkiiIoMmuProtocolGuid, &mIoMmu,
NULL
);
if (EFI_ERROR (Status)) {
diff --git a/OvmfPkg/IoMmuDxe/AmdSevIoMmu.h b/OvmfPkg/IoMmuDxe/AmdSevIoMmu.h
index 8244f28b57fd..8fdfa9968593 100644
--- a/OvmfPkg/IoMmuDxe/AmdSevIoMmu.h
+++ b/OvmfPkg/IoMmuDxe/AmdSevIoMmu.h
@@ -21,17 +21,17 @@
#include <Library/BaseMemoryLib.h>
#include <Library/DebugLib.h>
#include <Library/MemEncryptSevLib.h>
+#include <Library/MemEncryptTdxLib.h>
#include <Library/MemoryAllocationLib.h>
#include <Library/UefiBootServicesTableLib.h>
/**
- Install IOMMU protocol to provide the DMA support for PciHostBridge and
- MemEncryptSevLib.
+ Install IOMMU protocol to provide the DMA support for PciHostBridge.
**/
EFI_STATUS
EFIAPI
-AmdSevInstallIoMmuProtocol (
+InstallIoMmuProtocol (
VOID
);
diff --git a/OvmfPkg/IoMmuDxe/IoMmuDxe.c b/OvmfPkg/IoMmuDxe/IoMmuDxe.c
index 13df8ba874c5..89ebe254601d 100644
--- a/OvmfPkg/IoMmuDxe/IoMmuDxe.c
+++ b/OvmfPkg/IoMmuDxe/IoMmuDxe.c
@@ -22,11 +22,11 @@ IoMmuDxeEntryPoint (
EFI_HANDLE Handle;
//
- // When SEV is enabled, install IoMmu protocol otherwise install the
+ // When SEV or TDX is enabled, install IoMmu protocol otherwise install the
// placeholder protocol so that other dependent module can run.
//
- if (MemEncryptSevIsEnabled ()) {
- Status = AmdSevInstallIoMmuProtocol ();
+ if (MemEncryptSevIsEnabled () || MemEncryptTdxIsEnabled ()) {
+ Status = InstallIoMmuProtocol ();
} else {
Handle = NULL;
diff --git a/OvmfPkg/IoMmuDxe/IoMmuDxe.inf b/OvmfPkg/IoMmuDxe/IoMmuDxe.inf
index 2ebd74e5558c..e10be1dcff49 100644
--- a/OvmfPkg/IoMmuDxe/IoMmuDxe.inf
+++ b/OvmfPkg/IoMmuDxe/IoMmuDxe.inf
@@ -26,16 +26,21 @@
MdePkg/MdePkg.dec
MdeModulePkg/MdeModulePkg.dec
OvmfPkg/OvmfPkg.dec
+# UefiCpuPkg/UefiCpuPkg.dec
[LibraryClasses]
BaseLib
BaseMemoryLib
DebugLib
MemEncryptSevLib
+ MemEncryptTdxLib
MemoryAllocationLib
UefiBootServicesTableLib
UefiDriverEntryPoint
+[Pcd]
+ gEfiMdePkgTokenSpaceGuid.PcdConfidentialComputingGuestAttr
+
[Protocols]
gEdkiiIoMmuProtocolGuid ## SOMETIME_PRODUCES
gIoMmuAbsentProtocolGuid ## SOMETIME_PRODUCES
diff --git a/OvmfPkg/OvmfPkgX64.dsc b/OvmfPkg/OvmfPkgX64.dsc
index c8ab0dc7060e..11e3f00e8c14 100644
--- a/OvmfPkg/OvmfPkgX64.dsc
+++ b/OvmfPkg/OvmfPkgX64.dsc
@@ -180,6 +180,8 @@
VirtioLib|OvmfPkg/Library/VirtioLib/VirtioLib.inf
LoadLinuxLib|OvmfPkg/Library/LoadLinuxLib/LoadLinuxLib.inf
MemEncryptSevLib|OvmfPkg/Library/BaseMemEncryptSevLib/DxeMemEncryptSevLib.inf
+ MemEncryptTdxLib|OvmfPkg/Library/BaseMemEncryptTdxLib/BaseMemEncryptTdxLib.inf
+
!if $(SMM_REQUIRE) == FALSE
LockBoxLib|OvmfPkg/Library/LockBoxLib/LockBoxBaseLib.inf
!endif
--
2.29.2.windows.2
-=-=-=-=-=-=-=-=-=-=-=-
Groups.io Links: You receive all messages sent to this group.
View/Reply Online (#82984): https://edk2.groups.io/g/devel/message/82984
Mute This Topic: https://groups.io/mt/86739898/1787277
Group Owner: devel+owner@edk2.groups.io
Unsubscribe: https://edk2.groups.io/g/devel/unsub [importer@patchew.org]
-=-=-=-=-=-=-=-=-=-=-=-
Hi,
> + if (CC_GUEST_IS_SEV (PcdGet64 (PcdConfidentialComputingGuestAttr))) {
> + //
> + // Clear the memory encryption mask on the plaintext buffer.
> + //
> + Status = MemEncryptSevClearPageEncMask (
> + 0,
> + MapInfo->PlainTextAddress,
> + MapInfo->NumberOfPages
> + );
> + } else if (CC_GUEST_IS_TDX (PcdGet64 (PcdConfidentialComputingGuestAttr))) {
> + //
> + // Set the memory shared bit.
> + //
> + Status = MemEncryptTdxSetPageSharedBit (
> + 0,
> + MapInfo->PlainTextAddress,
> + MapInfo->NumberOfPages
> + );
Again, this looks very simliar and like a great opportunity to share
code.
take care,
Gerd
-=-=-=-=-=-=-=-=-=-=-=-
Groups.io Links: You receive all messages sent to this group.
View/Reply Online (#83229): https://edk2.groups.io/g/devel/message/83229
Mute This Topic: https://groups.io/mt/86739898/1787277
Group Owner: devel+owner@edk2.groups.io
Unsubscribe: https://edk2.groups.io/g/devel/unsub [importer@patchew.org]
-=-=-=-=-=-=-=-=-=-=-=-
Hi
>
> > + if (CC_GUEST_IS_SEV (PcdGet64 (PcdConfidentialComputingGuestAttr))) {
> > + //
> > + // Clear the memory encryption mask on the plaintext buffer.
> > + //
> > + Status = MemEncryptSevClearPageEncMask (
> > + 0,
> > + MapInfo->PlainTextAddress,
> > + MapInfo->NumberOfPages
> > + );
> > + } else if (CC_GUEST_IS_TDX (PcdGet64
> (PcdConfidentialComputingGuestAttr))) {
> > + //
> > + // Set the memory shared bit.
> > + //
> > + Status = MemEncryptTdxSetPageSharedBit (
> > + 0,
> > + MapInfo->PlainTextAddress,
> > + MapInfo->NumberOfPages
> > + );
>
> Again, this looks very simliar and like a great opportunity to share code.
>
MemEncryptSevClearPageEncMask () is implemented in MemEncryptSevLib.
MemEncryptTdxSetPageSharedBit () is implemented in MemEncryptTdxlib.
Yes, we have considered to merge these 2 EncryptLib into one lib (for example: MemoryEncryptCcLib). But after investigation and some PoC, we find it will make the code complicated and hard to maintain. (many if-else checking in the code)
1. From the naming perspective (in SEV/TDX documentation), SEV's bit is Enc bit, but TDX's bit is shared bit.
2. In SEV's SetMemoryEncDec () it handles differently for the different version of SEV (for example, Sev-Snp). I am not sure if there will be more specific process will be added in the future.
3. In TDX's SetMemorySharedOrPrivate, currently it is simple and clean. But there maybe some new features added in the future.
I am thinking if it is a better choice that every vendor takes their responsibility to maintain their own lib/code?
In the current EDK2 CI there is no test case for SEV or TDX, I am a little nervous if some changes will impact the existing feature.
Thanks
Min
-=-=-=-=-=-=-=-=-=-=-=-
Groups.io Links: You receive all messages sent to this group.
View/Reply Online (#84670): https://edk2.groups.io/g/devel/message/84670
Mute This Topic: https://groups.io/mt/86739898/1787277
Group Owner: devel+owner@edk2.groups.io
Unsubscribe: https://edk2.groups.io/g/devel/unsub [importer@patchew.org]
-=-=-=-=-=-=-=-=-=-=-=-
On Mon, Dec 13, 2021 at 02:39:53AM +0000, Xu, Min M wrote:
> Hi
> >
> > > + if (CC_GUEST_IS_SEV (PcdGet64 (PcdConfidentialComputingGuestAttr))) {
> > > + //
> > > + // Clear the memory encryption mask on the plaintext buffer.
> > > + //
> > > + Status = MemEncryptSevClearPageEncMask (
> > > + 0,
> > > + MapInfo->PlainTextAddress,
> > > + MapInfo->NumberOfPages
> > > + );
> > > + } else if (CC_GUEST_IS_TDX (PcdGet64
> > (PcdConfidentialComputingGuestAttr))) {
> > > + //
> > > + // Set the memory shared bit.
> > > + //
> > > + Status = MemEncryptTdxSetPageSharedBit (
> > > + 0,
> > > + MapInfo->PlainTextAddress,
> > > + MapInfo->NumberOfPages
> > > + );
> >
> > Again, this looks very simliar and like a great opportunity to share code.
> >
> MemEncryptSevClearPageEncMask () is implemented in MemEncryptSevLib.
> MemEncryptTdxSetPageSharedBit () is implemented in MemEncryptTdxlib.
>
> Yes, we have considered to merge these 2 EncryptLib into one lib (for
> example: MemoryEncryptCcLib). But after investigation and some PoC, we
> find it will make the code complicated and hard to maintain. (many
> if-else checking in the code)
> 1. From the naming perspective (in SEV/TDX documentation), SEV's bit is Enc bit, but TDX's bit is shared bit.
> 2. In SEV's SetMemoryEncDec () it handles differently for the different version of SEV (for example, Sev-Snp). I am not sure if there will be more specific process will be added in the future.
> 3. In TDX's SetMemorySharedOrPrivate, currently it is simple and clean. But there maybe some new features added in the future.
> I am thinking if it is a better choice that every vendor takes their responsibility to maintain their own lib/code?
Well, I still think there is opportunity to share code, specifically the
page table handling. Have a generic page table walker which is able to
set and clear bits for a given memory range. Then the sev/tdx specific
code can just call that instead of both having their own, duplicated
page table walking logic.
Maybe the page table walking should even be a MdeModulePkg Library, i.e.
move the code for page table walking (and huge page splitting) in
MdeModulePkg/Core/DxeIplPeim/X64/VirtualMemory.c to a library so it can
be reused elsewhere without duplicating the code,
take care,
Gerd
-=-=-=-=-=-=-=-=-=-=-=-
Groups.io Links: You receive all messages sent to this group.
View/Reply Online (#84671): https://edk2.groups.io/g/devel/message/84671
Mute This Topic: https://groups.io/mt/86739898/1787277
Group Owner: devel+owner@edk2.groups.io
Unsubscribe: https://edk2.groups.io/g/devel/unsub [importer@patchew.org]
-=-=-=-=-=-=-=-=-=-=-=-
Hi,
> > > > + if (CC_GUEST_IS_SEV (PcdGet64 (PcdConfidentialComputingGuestAttr)))
> {
> > > > + //
> > > > + // Clear the memory encryption mask on the plaintext buffer.
> > > > + //
> > > > + Status = MemEncryptSevClearPageEncMask (
> > > > + 0,
> > > > + MapInfo->PlainTextAddress,
> > > > + MapInfo->NumberOfPages
> > > > + );
> > > > + } else if (CC_GUEST_IS_TDX (PcdGet64
> > > (PcdConfidentialComputingGuestAttr))) {
> > > > + //
> > > > + // Set the memory shared bit.
> > > > + //
> > > > + Status = MemEncryptTdxSetPageSharedBit (
> > > > + 0,
> > > > + MapInfo->PlainTextAddress,
> > > > + MapInfo->NumberOfPages
> > > > + );
> > >
> > > Again, this looks very simliar and like a great opportunity to share code.
> > >
> > MemEncryptSevClearPageEncMask () is implemented in MemEncryptSevLib.
> > MemEncryptTdxSetPageSharedBit () is implemented in MemEncryptTdxlib.
> >
> > Yes, we have considered to merge these 2 EncryptLib into one lib (for
> > example: MemoryEncryptCcLib). But after investigation and some PoC, we
> > find it will make the code complicated and hard to maintain. (many
> > if-else checking in the code)
>
> > 1. From the naming perspective (in SEV/TDX documentation), SEV's bit is Enc
> bit, but TDX's bit is shared bit.
> > 2. In SEV's SetMemoryEncDec () it handles differently for the different version
> of SEV (for example, Sev-Snp). I am not sure if there will be more specific
> process will be added in the future.
> > 3. In TDX's SetMemorySharedOrPrivate, currently it is simple and clean. But
> there maybe some new features added in the future.
>
> > I am thinking if it is a better choice that every vendor takes their responsibility
> to maintain their own lib/code?
>
> Well, I still think there is opportunity to share code, specifically the page table
> handling. Have a generic page table walker which is able to set and clear bits for
> a given memory range. Then the sev/tdx specific code can just call that instead
> of both having their own, duplicated page table walking logic.
>
> Maybe the page table walking should even be a MdeModulePkg Library, i.e.
> move the code for page table walking (and huge page splitting) in
> MdeModulePkg/Core/DxeIplPeim/X64/VirtualMemory.c to a library so it can be
> reused elsewhere without duplicating the code,
Thanks for the suggestion. I will carefully think about it and figure out if it is feasible. (A Poc as the first step )
Thanks
Min
-=-=-=-=-=-=-=-=-=-=-=-
Groups.io Links: You receive all messages sent to this group.
View/Reply Online (#84675): https://edk2.groups.io/g/devel/message/84675
Mute This Topic: https://groups.io/mt/86739898/1787277
Group Owner: devel+owner@edk2.groups.io
Unsubscribe: https://edk2.groups.io/g/devel/unsub [importer@patchew.org]
-=-=-=-=-=-=-=-=-=-=-=-
© 2016 - 2026 Red Hat, Inc.