From: Jeremy Linton <lintonrjeremy@gmail.com>
Signed-off-by: Pete Batard <pete@akeo.ie>
---
Silicon/Broadcom/Bcm27xx/Library/Bcm2711PciHostBridgeLib/Bcm2711PciHostBridgeLib.c | 193 ++++++++++++++++
Silicon/Broadcom/Bcm27xx/Library/Bcm2711PciHostBridgeLib/Bcm2711PciHostBridgeLib.inf | 51 +++++
Silicon/Broadcom/Bcm27xx/Library/Bcm2711PciHostBridgeLib/Bcm2711PciHostBridgeLibConstructor.c | 235 ++++++++++++++++++++
3 files changed, 479 insertions(+)
diff --git a/Silicon/Broadcom/Bcm27xx/Library/Bcm2711PciHostBridgeLib/Bcm2711PciHostBridgeLib.c b/Silicon/Broadcom/Bcm27xx/Library/Bcm2711PciHostBridgeLib/Bcm2711PciHostBridgeLib.c
new file mode 100644
index 000000000000..e642bf4e246a
--- /dev/null
+++ b/Silicon/Broadcom/Bcm27xx/Library/Bcm2711PciHostBridgeLib/Bcm2711PciHostBridgeLib.c
@@ -0,0 +1,193 @@
+/** @file
+ *
+ * PCI Host Bridge Library instance for Bcm2711 ARM SOC
+ *
+ * Copyright (c) 2019, Jeremy Linton
+ * Copyright (c) 2017, Linaro Ltd. All rights reserved.<BR>
+ *
+ * SPDX-License-Identifier: BSD-2-Clause-Patent
+ *
+ **/
+
+#include <IndustryStandard/Bcm2711.h>
+#include <IndustryStandard/Pci22.h>
+#include <Library/DebugLib.h>
+#include <Library/DevicePathLib.h>
+#include <Library/MemoryAllocationLib.h>
+#include <Library/PcdLib.h>
+#include <Library/PciHostBridgeLib.h>
+#include <PiDxe.h>
+#include <Protocol/PciRootBridgeIo.h>
+#include <Protocol/PciHostBridgeResourceAllocation.h>
+
+#pragma pack(1)
+
+typedef PACKED struct {
+ ACPI_HID_DEVICE_PATH AcpiDevicePath;
+ EFI_DEVICE_PATH_PROTOCOL EndDevicePath;
+} EFI_PCI_ROOT_BRIDGE_DEVICE_PATH;
+
+#pragma pack ()
+
+STATIC CONST EFI_PCI_ROOT_BRIDGE_DEVICE_PATH mEfiPciRootBridgeDevicePath[] = {
+ {
+ {
+ {
+ ACPI_DEVICE_PATH,
+ ACPI_DP,
+ {
+ (UINT8)(sizeof (ACPI_HID_DEVICE_PATH)),
+ (UINT8)(sizeof (ACPI_HID_DEVICE_PATH) >> 8)
+ }
+ },
+ EISA_PNP_ID (0x0A08), // PCI Express
+ 0
+ },
+
+ {
+ END_DEVICE_PATH_TYPE,
+ END_ENTIRE_DEVICE_PATH_SUBTYPE,
+ {
+ END_DEVICE_PATH_LENGTH,
+ 0
+ }
+ }
+ },
+};
+
+GLOBAL_REMOVE_IF_UNREFERENCED
+CHAR16 *mPciHostBridgeLibAcpiAddressSpaceTypeStr[] = {
+ L"Mem", L"I/O", L"Bus"
+};
+
+// these should come from the pcd...
+#define BCM2711_PCI_SEG0_BUSNUM_MIN 0x00
+#define BCM2711_PCI_SEG0_BUSNUM_MAX 0xFF
+#define BCM2711_PCI_SEG0_PORTIO_MIN 0x01
+#define BCM2711_PCI_SEG0_PORTIO_MAX 0x00 //MIN>MAX disables PIO
+#define BCM2711_PCI_SEG0_PORTIO_OFFSET 0x00
+// the bridge thinks its MMIO is here (which means it can't access this area in phy ram)
+#define BCM2711_PCI_SEG0_MMIO32_MIN PCIE_TOP_OF_MEM_WIN
+#define BCM2711_PCI_SEG0_MMIO32_MAX (PCIE_TOP_OF_MEM_WIN + PCIE_BRIDGE_MMIO_LEN)
+// the CPU views it via a window here..
+#define BCM2711_PCI_SEG0_MMIO32_XLATE (PCIE_CPU_MMIO_WINDOW - PCIE_TOP_OF_MEM_WIN)
+
+// we might be able to size another region?
+#define BCM2711_PCI_SEG0_MMIO64_MIN 0x00
+#define BCM2711_PCI_SEG0_MMIO64_MAX 0x00
+
+//
+// See description in MdeModulePkg/Include/Library/PciHostBridgeLib.h
+//
+PCI_ROOT_BRIDGE mPciRootBridges[] = {
+ {
+ 0, // Segment
+ 0, // Supports
+ 0, // Attributes
+ FALSE, // DmaAbove4G
+ FALSE, // NoExtendedConfigSpace (true=256 byte config, false=4k)
+ FALSE, // ResourceAssigned
+ EFI_PCI_HOST_BRIDGE_COMBINE_MEM_PMEM, // AllocationAttributes
+ { BCM2711_PCI_SEG0_BUSNUM_MIN,
+ BCM2711_PCI_SEG0_BUSNUM_MAX }, // Bus
+ { BCM2711_PCI_SEG0_PORTIO_MIN,
+ BCM2711_PCI_SEG0_PORTIO_MAX,
+ MAX_UINT64 - BCM2711_PCI_SEG0_PORTIO_OFFSET + 1 }, // Io
+ { BCM2711_PCI_SEG0_MMIO32_MIN,
+ BCM2711_PCI_SEG0_MMIO32_MAX,
+ MAX_UINT64 - BCM2711_PCI_SEG0_MMIO32_XLATE + 1 }, // Mem
+ { MAX_UINT64, 0x0 }, // MemAbove4G
+ { MAX_UINT64, 0x0 }, // Pefetchable Mem
+ { MAX_UINT64, 0x0 }, // Pefetchable MemAbove4G
+ (EFI_DEVICE_PATH_PROTOCOL *)&mEfiPciRootBridgeDevicePath[0]
+ }
+};
+
+/**
+ Return all the root bridge instances in an array.
+
+ @param Count Return the count of root bridge instances.
+
+ @return All the root bridge instances in an array.
+ The array should be passed into PciHostBridgeFreeRootBridges()
+ when it's not used.
+**/
+PCI_ROOT_BRIDGE *
+EFIAPI
+PciHostBridgeGetRootBridges (
+ OUT UINTN *Count
+ )
+{
+ *Count = ARRAY_SIZE (mPciRootBridges);
+ return mPciRootBridges;
+}
+
+/**
+ Free the root bridge instances array returned from PciHostBridgeGetRootBridges().
+
+ @param Bridges The root bridge instances array.
+ @param Count The count of the array.
+**/
+VOID
+EFIAPI
+PciHostBridgeFreeRootBridges (
+ PCI_ROOT_BRIDGE *Bridges,
+ UINTN Count
+ )
+{
+}
+
+/**
+ Inform the platform that the resource conflict happens.
+
+ @param HostBridgeHandle Handle of the Host Bridge.
+ @param Configuration Pointer to PCI I/O and PCI memory resource
+ descriptors. The Configuration contains the resources
+ for all the root bridges. The resource for each root
+ bridge is terminated with END descriptor and an
+ additional END is appended indicating the end of the
+ entire resources. The resource descriptor field
+ values follow the description in
+ EFI_PCI_HOST_BRIDGE_RESOURCE_ALLOCATION_PROTOCOL
+ .SubmitResources().
+**/
+VOID
+EFIAPI
+PciHostBridgeResourceConflict (
+ EFI_HANDLE HostBridgeHandle,
+ VOID *Configuration
+ )
+{
+ EFI_ACPI_ADDRESS_SPACE_DESCRIPTOR *Descriptor;
+ UINTN RootBridgeIndex;
+ DEBUG ((DEBUG_ERROR, "PciHostBridge: Resource conflict happens!\n"));
+
+ RootBridgeIndex = 0;
+ Descriptor = (EFI_ACPI_ADDRESS_SPACE_DESCRIPTOR *) Configuration;
+ while (Descriptor->Desc == ACPI_ADDRESS_SPACE_DESCRIPTOR) {
+ DEBUG ((DEBUG_ERROR, "RootBridge[%d]:\n", RootBridgeIndex++));
+ for (; Descriptor->Desc == ACPI_ADDRESS_SPACE_DESCRIPTOR; Descriptor++) {
+ ASSERT (Descriptor->ResType <
+ ARRAY_SIZE (mPciHostBridgeLibAcpiAddressSpaceTypeStr));
+ DEBUG ((DEBUG_ERROR, " %s: Length/Alignment = 0x%lx / 0x%lx\n",
+ mPciHostBridgeLibAcpiAddressSpaceTypeStr[Descriptor->ResType],
+ Descriptor->AddrLen, Descriptor->AddrRangeMax
+ ));
+ if (Descriptor->ResType == ACPI_ADDRESS_SPACE_TYPE_MEM) {
+ DEBUG ((DEBUG_ERROR, " Granularity/SpecificFlag = %ld / %02x%s\n",
+ Descriptor->AddrSpaceGranularity, Descriptor->SpecificFlag,
+ ((Descriptor->SpecificFlag &
+ EFI_ACPI_MEMORY_RESOURCE_SPECIFIC_FLAG_CACHEABLE_PREFETCHABLE
+ ) != 0) ? L" (Prefetchable)" : L""
+ ));
+ }
+ }
+ //
+ // Skip the END descriptor for root bridge
+ //
+ ASSERT (Descriptor->Desc == ACPI_END_TAG_DESCRIPTOR);
+ Descriptor = (EFI_ACPI_ADDRESS_SPACE_DESCRIPTOR *)(
+ (EFI_ACPI_END_TAG_DESCRIPTOR *)Descriptor + 1
+ );
+ }
+}
diff --git a/Silicon/Broadcom/Bcm27xx/Library/Bcm2711PciHostBridgeLib/Bcm2711PciHostBridgeLib.inf b/Silicon/Broadcom/Bcm27xx/Library/Bcm2711PciHostBridgeLib/Bcm2711PciHostBridgeLib.inf
new file mode 100644
index 000000000000..fc23b9486057
--- /dev/null
+++ b/Silicon/Broadcom/Bcm27xx/Library/Bcm2711PciHostBridgeLib/Bcm2711PciHostBridgeLib.inf
@@ -0,0 +1,51 @@
+## @file
+#
+# PCI Host Bridge Library instance for Bcm2711 ARM SOC
+# Liberally borrowed from the SynQuacer
+#
+# Copyright (c) 2019, Jeremy Linton
+# Copyright (c) 2017, Linaro Ltd. All rights reserved.
+#
+# SPDX-License-Identifier: BSD-2-Clause-Patent
+#
+##
+
+[Defines]
+ INF_VERSION = 0x00010019
+ BASE_NAME = Bcm2711PciHostBridgeLib
+ FILE_GUID = 8d52c038-0040-11ea-83fb-5254005675a0
+ MODULE_TYPE = DXE_DRIVER
+ VERSION_STRING = 1.0
+ LIBRARY_CLASS = PciHostBridgeLib|DXE_DRIVER
+ CONSTRUCTOR = Bcm2711PciHostBridgeLibConstructor
+
+#
+# The following information is for reference only and not required by the build
+# tools.
+#
+# VALID_ARCHITECTURES = ARM AARCH64
+#
+
+[Sources]
+ Bcm2711PciHostBridgeLib.c
+ Bcm2711PciHostBridgeLibConstructor.c
+
+[Packages]
+ ArmPkg/ArmPkg.dec
+ MdePkg/MdePkg.dec
+ MdeModulePkg/MdeModulePkg.dec
+ Silicon/Broadcom/Bcm27xx/Bcm27xx.dec
+
+[LibraryClasses]
+ ArmLib
+ DebugLib
+ DevicePathLib
+ MemoryAllocationLib
+ PcdLib
+ UefiBootServicesTableLib
+
+[FixedPcd]
+ gBcm27xxTokenSpaceGuid.PcdBcm27xxPciRegBase
+ gBcm27xxTokenSpaceGuid.PcdBcm27xxPciBusMmioAdr
+ gBcm27xxTokenSpaceGuid.PcdBcm27xxPciBusMmioLen
+ gBcm27xxTokenSpaceGuid.PcdBcm27xxPciCpuMmioAdr
diff --git a/Silicon/Broadcom/Bcm27xx/Library/Bcm2711PciHostBridgeLib/Bcm2711PciHostBridgeLibConstructor.c b/Silicon/Broadcom/Bcm27xx/Library/Bcm2711PciHostBridgeLib/Bcm2711PciHostBridgeLibConstructor.c
new file mode 100644
index 000000000000..90bbc3bf66c4
--- /dev/null
+++ b/Silicon/Broadcom/Bcm27xx/Library/Bcm2711PciHostBridgeLib/Bcm2711PciHostBridgeLibConstructor.c
@@ -0,0 +1,235 @@
+/** @file
+ *
+ * PCI Host Bridge Library instance for Bcm2711 ARM SOC
+ *
+ * Copyright (c) 2019, Jeremy Linton
+ * Copyright (c) 2017, Linaro Ltd. All rights reserved.
+ *
+ * SPDX-License-Identifier: BSD-2-Clause-Patent
+ *
+ * This module initializes the Pci as close to a standard
+ * PCI root complex as possible. The general information
+ * for this driver was sourced from.
+ *
+ * See https://github.com/raspberrypi/linux/blob/rpi-5.3.y/drivers/pci/controller/pcie-brcmstb.c
+ * and https://github.com/raspberrypi/linux/blob/rpi-5.3.y/arch/arm/boot/dts/bcm2838.dtsi
+ *
+ **/
+
+#include <IndustryStandard/Bcm2711.h>
+#include <IndustryStandard/Pci22.h>
+#include <Library/ArmLib.h>
+#include <Library/DebugLib.h>
+#include <Library/IoLib.h>
+#include <Library/PcdLib.h>
+#include <Library/PciHostBridgeLib.h>
+#include <Library/UefiBootServicesTableLib.h>
+#include <PiDxe.h>
+#include <Protocol/PciHostBridgeResourceAllocation.h>
+
+STATIC
+UINT32
+RdRegister (
+ UINT32 Offset
+ )
+{
+ EFI_PHYSICAL_ADDRESS Base = PCIE_REG_BASE;
+
+ ArmDataMemoryBarrier ();
+
+ return MmioRead32 (Base + Offset);
+}
+
+
+STATIC
+VOID
+RMWRegister (
+ UINT32 Offset,
+ UINT32 Mask,
+ UINT32 In
+ )
+{
+ EFI_PHYSICAL_ADDRESS Addr = PCIE_REG_BASE;
+ UINT32 Data = 0;
+ UINT32 Shift;
+
+ Addr += Offset;
+ Shift = 1;
+ if (In) {
+ while (!(Mask & Shift))
+ Shift <<= 1;
+ Data = (MmioRead32 (Addr) & ~Mask) | ((In * Shift) & Mask);
+ } else {
+ Data = MmioRead32 (Addr) & ~Mask;
+ }
+
+ MmioWrite32 (Addr, Data);
+
+ ArmDataMemoryBarrier ();
+}
+
+
+STATIC
+VOID
+WdRegister (
+ UINT32 Offset,
+ UINT32 In
+ )
+{
+ EFI_PHYSICAL_ADDRESS Base = PCIE_REG_BASE;
+
+ MmioWrite32 (Base + Offset, In);
+
+ ArmDataMemoryBarrier ();
+}
+
+
+EFI_STATUS
+EFIAPI
+Bcm2711PciHostBridgeLibConstructor (
+ IN EFI_HANDLE ImageHandle,
+ IN EFI_SYSTEM_TABLE *SystemTable
+ )
+{
+ INTN Timeout = 100;
+ UINT32 Data;
+ EFI_PHYSICAL_ADDRESS TopOfPciMap;
+
+ DEBUG ((DEBUG_VERBOSE, "PCIe RootBridge constructor\n"));
+
+ // Reset controller
+ RMWRegister (PCIE_RGR1_SW_INIT_1, PCIE_RGR1_SW_INIT_1_INIT_MASK, 1);
+ // PERST
+ RMWRegister (PCIE_RGR1_SW_INIT_1, PCIE_RGR1_SW_INIT_1_PERST_MASK, 1);
+
+ gBS->Stall (1000);
+ // take the bridge out of reset
+ RMWRegister (PCIE_RGR1_SW_INIT_1, PCIE_RGR1_SW_INIT_1_INIT_MASK, 0);
+
+
+ RMWRegister (PCIE_MISC_HARD_PCIE_HARD_DEBUG,
+ PCIE_MISC_HARD_PCIE_HARD_DEBUG_SERDES_IDDQ_MASK, 0);
+ RdRegister (PCIE_MISC_HARD_PCIE_HARD_DEBUG);
+ // Wait for SerDes to be stable
+ gBS->Stall (1000);
+
+ // Read revision
+ Data = RdRegister (PCIE_MISC_REVISION);
+ DEBUG ((DEBUG_INFO, "RootBridge: Revision %x\n", Data & PCIE_MISC_REVISION_MAJMIN_MASK));
+
+ RMWRegister (PCIE_MISC_MISC_CTRL, PCIE_MISC_MISC_CTRL_SCB_ACCESS_EN_MASK, 1);
+ RMWRegister (PCIE_MISC_MISC_CTRL, PCIE_MISC_MISC_CTRL_CFG_READ_UR_MODE_MASK, 1);
+ RMWRegister (PCIE_MISC_MISC_CTRL, PCIE_MISC_MISC_CTRL_MAX_BURST_SIZE_MASK, BURST_SIZE_128);
+
+ //
+ // "RC_BAR2" is the inbound TLP window.
+ // Having non RAM regions in the window is ok (and encouraged? for PtP?)
+ // so lets just map the entire address space.
+ //
+ // For regions > 64K then the pci->mem window size = log2(size)-15
+ // which is dumped into the low bits of the offset and written to
+ // the "LO" register with the high bits of the offset written into
+ // the "HI" part. The Linux driver makes the point that the offset
+ // must be aligned to its size aka a 1G region must start on a 1G
+ // boundary. The size parms are 1GB=0xf=log2(size)-15), or 4G=0x11
+ //
+
+ DEBUG ((DEBUG_VERBOSE, "RootBridge: Program bottom 4G of ram\n"));
+
+ // lets assume a start addr of 0, size 4G
+ WdRegister (PCIE_MISC_RC_BAR2_CONFIG_LO, 0x11); /* Size = 4G */
+ WdRegister (PCIE_MISC_RC_BAR2_CONFIG_HI, 0); /* Start at addr0 */
+ RMWRegister (PCIE_MISC_MISC_CTRL, PCIE_MISC_MISC_CTRL_SCB0_SIZE_MASK, 0x11);
+
+ // RC_BAR1 pcie->gisb disable
+ WdRegister (PCIE_MISC_RC_BAR1_CONFIG_LO, 0);
+ // RC_BAR3 pcie->scb disable
+ WdRegister (PCIE_MISC_RC_BAR3_CONFIG_LO, 0);
+
+ TopOfPciMap = PCIE_TOP_OF_MEM_WIN;
+
+ DEBUG ((DEBUG_VERBOSE, "RootBridge: MMIO PCIe addr %llx\n", TopOfPciMap));
+
+ //
+ // Setup the PCI side of the MMIO window.
+ //
+ // All the _WIN0_ values make one think there can be more than one
+ // mapping, which might mean it's possible to program a prefetchable
+ // window, or a PIO window...
+ //
+ WdRegister (PCIE_MISC_CPU_2_PCIE_MEM_WIN0_LO, TopOfPciMap);
+ WdRegister (PCIE_MISC_CPU_2_PCIE_MEM_WIN0_HI, TopOfPciMap >> 32);
+
+ //
+ // Set up the CPU MMIO addresses. The BASE_LIMIT register holds the
+ // bottom part of the start and end addresses in a 16-bit field (64k)
+ // aligned on a 1M boundary (aka only 12 bit active) the top 32-bits
+ // are then in their own registers. Further these address ranges are
+ // setup to match the Linux driver and seem less than ideal on the RPi
+ //
+ // The mapping should be 1:1 if possible
+ //
+ EFI_PHYSICAL_ADDRESS CpuAddrStart = PCIE_CPU_MMIO_WINDOW;
+ EFI_PHYSICAL_ADDRESS CpuAddrEnd = CpuAddrStart + PCIE_BRIDGE_MMIO_LEN;
+
+ DEBUG ((DEBUG_VERBOSE, "RootBridge: MMIO CPU addr %llx\n", CpuAddrStart));
+
+ RMWRegister (PCIE_MISC_CPU_2_PCIE_MEM_WIN0_BASE_LIMIT,
+ PCIE_MISC_CPU_2_PCIE_MEM_WIN0_BASE_LIMIT_BASE_MASK, CpuAddrStart >> 20);
+ RMWRegister (PCIE_MISC_CPU_2_PCIE_MEM_WIN0_BASE_LIMIT,
+ PCIE_MISC_CPU_2_PCIE_MEM_WIN0_BASE_LIMIT_LIMIT_MASK, CpuAddrEnd >> 20);
+ RMWRegister (PCIE_MISC_CPU_2_PCIE_MEM_WIN0_BASE_HI,
+ PCIE_MISC_CPU_2_PCIE_MEM_WIN0_BASE_HI_BASE_MASK, CpuAddrStart >> 32);
+ RMWRegister (PCIE_MISC_CPU_2_PCIE_MEM_WIN0_LIMIT_HI,
+ PCIE_MISC_CPU_2_PCIE_MEM_WIN0_LIMIT_HI_LIMIT_MASK, CpuAddrEnd >> 32);
+
+ //
+ // Consider MSI setup here, not that it matters much its likely the legacy intX
+ // is as fast or faster...
+ //
+
+ // Clear and mask interrupts.
+ WdRegister (PCIE_INTR2_CPU_MASK_CLR, 0xffffffff);
+ WdRegister (PCIE_INTR2_CPU_MASK_SET, 0xffffffff);
+
+ // Set link cap & link ctl?
+ //RMWRegister (BRCM_PCIE_CAP_REGS+PCI_LNKCAP, LNKCAP, pen);
+ //RMWRegister (BRCM_PCIE_CTL_REGS+PCI_LNKCAP, LNKCAP, pen);
+
+ // De-assert PERST
+ RMWRegister (PCIE_RGR1_SW_INIT_1, PCIE_RGR1_SW_INIT_1_PERST_MASK, 0);
+ DEBUG ((DEBUG_VERBOSE, "RootBridge: Reset done\n"));
+
+ // Wait for linkup
+ do {
+ Data = RdRegister (PCIE_MISC_PCIE_STATUS);
+ gBS->Stall (1000);
+ Timeout --;
+ } while (((Data & 0x30) != 0x030) && (Timeout));
+ DEBUG ((DEBUG_VERBOSE, "PCIe link ready (status=%x) Timeout=%d\n", Data, Timeout));
+
+ if ((Data & 0x30) != 0x30) {
+ DEBUG ((DEBUG_ERROR, "PCIe link not ready (status=%x)\n", Data));
+ return EFI_DEVICE_ERROR;
+ }
+
+ if ((Data & 0x80) != 0x80) {
+ DEBUG ((DEBUG_ERROR, "PCIe link not in RC mode (status=%x)\n", Data));
+ return EFI_UNSUPPORTED;
+ }
+
+ // Change class code of the root port
+ RMWRegister(BRCM_PCIE_CLASS, PCIE_RC_CFG_PRIV1_ID_VAL3_CLASS_CODE_MASK, 0x60400);
+
+ //
+ // PCIe->SCB endian mode for BAR
+ // field ENDIAN_MODE_BAR2 = little endian = 0
+ //
+ RMWRegister (PCIE_RC_CFG_VENDOR_VENDOR_SPECIFIC_REG1,
+ PCIE_RC_CFG_VENDOR_VENDOR_SPECIFIC_REG1_ENDIAN_MODE_BAR2_MASK, 0);
+
+ RMWRegister (PCIE_MISC_HARD_PCIE_HARD_DEBUG,
+ PCIE_MISC_HARD_PCIE_HARD_DEBUG_CLKREQ_DEBUG_ENABLE_MASK, 1);
+
+ return EFI_SUCCESS;
+}
--
2.21.0.windows.1
-=-=-=-=-=-=-=-=-=-=-=-
Groups.io Links: You receive all messages sent to this group.
View/Reply Online (#52163): https://edk2.groups.io/g/devel/message/52163
Mute This Topic: https://groups.io/mt/68271366/1787277
Group Owner: devel+owner@edk2.groups.io
Unsubscribe: https://edk2.groups.io/g/devel/unsub [importer@patchew.org]
-=-=-=-=-=-=-=-=-=-=-=-
© 2016 - 2024 Red Hat, Inc.