public inbox for devel@edk2.groups.io
 help / color / mirror / Atom feed
From: "Laszlo Ersek" <lersek@redhat.com>
To: edk2-devel-groups-io <devel@edk2.groups.io>
Cc: "Ard Biesheuvel" <ard.biesheuvel@linaro.org>,
	"Igor Mammedov" <imammedo@redhat.com>,
	"Jiewen Yao" <jiewen.yao@intel.com>,
	"Jordan Justen" <jordan.l.justen@intel.com>,
	"Michael Kinney" <michael.d.kinney@intel.com>,
	"Philippe Mathieu-Daudé" <philmd@redhat.com>
Subject: [PATCH v2 16/16] OvmfPkg/CpuS3DataDxe: enable S3 resume after CPU hotplug
Date: Wed, 26 Feb 2020 23:11:56 +0100	[thread overview]
Message-ID: <20200226221156.29589-17-lersek@redhat.com> (raw)
In-Reply-To: <20200226221156.29589-1-lersek@redhat.com>

During normal boot, CpuS3DataDxe allocates

- an empty CPU_REGISTER_TABLE entry in the
  "ACPI_CPU_DATA.PreSmmInitRegisterTable" array, and

- an empty CPU_REGISTER_TABLE entry in the "ACPI_CPU_DATA.RegisterTable"
  array,

for every CPU whose APIC ID CpuS3DataDxe can learn.

Currently EFI_MP_SERVICES_PROTOCOL is used for both determining the number
of CPUs -- the protocol reports the present-at-boot CPU count --, and for
retrieving the APIC IDs of those CPUs.

Consequently, if a CPU is hot-plugged at OS runtime, then S3 resume
breaks. That's because PiSmmCpuDxeSmm will not find the hot-added CPU's
APIC ID associated with any CPU_REGISTER_TABLE object, in the SMRAM copies
of either of the "RegisterTable" and "PreSmmInitRegisterTable" arrays. The
failure to match the hot-added CPU's APIC ID trips the ASSERT() in
SetRegister() [UefiCpuPkg/PiSmmCpuDxeSmm/CpuS3.c].

If "PcdQ35SmramAtDefaultSmbase" is TRUE, then:

- prepare CPU_REGISTER_TABLE objects for all possible CPUs, not just the
  present-at-boot CPUs (PlatformPei stored the possible CPU count to
  "PcdCpuMaxLogicalProcessorNumber");

- use QEMU_CPUHP_CMD_GET_ARCH_ID for filling in the "InitialApicId" fields
  of the CPU_REGISTER_TABLE objects.

This provides full APIC ID coverage for PiSmmCpuDxeSmm during S3 resume,
accommodating CPUs hot-added at OS runtime.

This patch is best reviewed with

$ git show -b

Cc: Ard Biesheuvel <ard.biesheuvel@linaro.org>
Cc: Igor Mammedov <imammedo@redhat.com>
Cc: Jiewen Yao <jiewen.yao@intel.com>
Cc: Jordan Justen <jordan.l.justen@intel.com>
Cc: Michael Kinney <michael.d.kinney@intel.com>
Cc: Philippe Mathieu-Daudé <philmd@redhat.com>
Ref: https://bugzilla.tianocore.org/show_bug.cgi?id=1512
Signed-off-by: Laszlo Ersek <lersek@redhat.com>
Acked-by: Ard Biesheuvel <ard.biesheuvel@linaro.org>
---

Notes:
    v2:
    
    - Pick up Ard's Acked-by, which is conditional on approval from Intel
      reviewers on Cc. (I'd like to save Ard the churn of re-acking
      unmodified patches.)

 OvmfPkg/CpuS3DataDxe/CpuS3DataDxe.inf |  4 +
 OvmfPkg/CpuS3DataDxe/CpuS3Data.c      | 91 ++++++++++++++------
 2 files changed, 70 insertions(+), 25 deletions(-)

diff --git a/OvmfPkg/CpuS3DataDxe/CpuS3DataDxe.inf b/OvmfPkg/CpuS3DataDxe/CpuS3DataDxe.inf
index f9679e0c33b3..ceae1d4078c7 100644
--- a/OvmfPkg/CpuS3DataDxe/CpuS3DataDxe.inf
+++ b/OvmfPkg/CpuS3DataDxe/CpuS3DataDxe.inf
@@ -16,46 +16,50 @@
 #
 ##
 
 [Defines]
   INF_VERSION                    = 1.29
   BASE_NAME                      = CpuS3DataDxe
   FILE_GUID                      = 229B7EFD-DA02-46B9-93F4-E20C009F94E9
   MODULE_TYPE                    = DXE_DRIVER
   VERSION_STRING                 = 1.0
   ENTRY_POINT                    = CpuS3DataInitialize
 
 # The following information is for reference only and not required by the build
 # tools.
 #
 #  VALID_ARCHITECTURES           = IA32 X64
 
 [Sources]
   CpuS3Data.c
 
 [Packages]
   MdeModulePkg/MdeModulePkg.dec
   MdePkg/MdePkg.dec
+  OvmfPkg/OvmfPkg.dec
   UefiCpuPkg/UefiCpuPkg.dec
 
 [LibraryClasses]
   BaseLib
   BaseMemoryLib
   DebugLib
+  IoLib
   MemoryAllocationLib
   MtrrLib
   UefiBootServicesTableLib
   UefiDriverEntryPoint
 
 [Guids]
   gEfiEndOfDxeEventGroupGuid         ## CONSUMES   ## Event
 
 [Protocols]
   gEfiMpServiceProtocolGuid          ## CONSUMES
 
 [Pcd]
   gEfiMdeModulePkgTokenSpaceGuid.PcdAcpiS3Enable                    ## CONSUMES
   gUefiCpuPkgTokenSpaceGuid.PcdCpuApStackSize                       ## CONSUMES
+  gUefiCpuPkgTokenSpaceGuid.PcdCpuMaxLogicalProcessorNumber         ## CONSUMES
   gUefiCpuPkgTokenSpaceGuid.PcdCpuS3DataAddress                     ## PRODUCES
+  gUefiOvmfPkgTokenSpaceGuid.PcdQ35SmramAtDefaultSmbase             ## CONSUMES
 
 [Depex]
   gEfiMpServiceProtocolGuid
diff --git a/OvmfPkg/CpuS3DataDxe/CpuS3Data.c b/OvmfPkg/CpuS3DataDxe/CpuS3Data.c
index 8bb9807cd501..bac7285aa2f3 100644
--- a/OvmfPkg/CpuS3DataDxe/CpuS3Data.c
+++ b/OvmfPkg/CpuS3DataDxe/CpuS3Data.c
@@ -4,51 +4,55 @@ ACPI CPU Data initialization module
 This module initializes the ACPI_CPU_DATA structure and registers the address
 of this structure in the PcdCpuS3DataAddress PCD.  This is a generic/simple
 version of this module.  It does not provide a machine check handler or CPU
 register initialization tables for ACPI S3 resume.  It also only supports the
 number of CPUs reported by the MP Services Protocol, so this module does not
 support hot plug CPUs.  This module can be copied into a CPU specific package
 and customized if these additional features are required.
 
 Copyright (c) 2013 - 2017, Intel Corporation. All rights reserved.<BR>
 Copyright (c) 2015 - 2020, Red Hat, Inc.
 
 SPDX-License-Identifier: BSD-2-Clause-Patent
 
 **/
 
 #include <PiDxe.h>
 
 #include <AcpiCpuData.h>
 
 #include <Library/BaseLib.h>
 #include <Library/BaseMemoryLib.h>
 #include <Library/DebugLib.h>
+#include <Library/IoLib.h>
 #include <Library/MemoryAllocationLib.h>
 #include <Library/MtrrLib.h>
 #include <Library/UefiBootServicesTableLib.h>
 
 #include <Protocol/MpService.h>
 #include <Guid/EventGroup.h>
 
+#include <IndustryStandard/Q35MchIch9.h>
+#include <IndustryStandard/QemuCpuHotplug.h>
+
 //
 // Data structure used to allocate ACPI_CPU_DATA and its supporting structures
 //
 typedef struct {
   ACPI_CPU_DATA             AcpiCpuData;
   MTRR_SETTINGS             MtrrTable;
   IA32_DESCRIPTOR           GdtrProfile;
   IA32_DESCRIPTOR           IdtrProfile;
 } ACPI_CPU_DATA_EX;
 
 /**
   Allocate EfiACPIMemoryNVS memory.
 
   @param[in] Size   Size of memory to allocate.
 
   @return       Allocated address for output.
 
 **/
 VOID *
 AllocateAcpiNvsMemory (
   IN UINTN  Size
   )
@@ -144,89 +148,101 @@ CpuS3DataOnEndOfDxe (
    to the address that ACPI_CPU_DATA is allocated at.
 
    @param[in] ImageHandle  The firmware allocated handle for the EFI image.
    @param[in] SystemTable  A pointer to the EFI System Table.
 
    @retval EFI_SUCCESS     The entry point is executed successfully.
    @retval EFI_UNSUPPORTED Do not support ACPI S3.
    @retval other           Some error occurs when executing this entry point.
 
 **/
 EFI_STATUS
 EFIAPI
 CpuS3DataInitialize (
   IN EFI_HANDLE        ImageHandle,
   IN EFI_SYSTEM_TABLE  *SystemTable
   )
 {
   EFI_STATUS                 Status;
   ACPI_CPU_DATA_EX           *AcpiCpuDataEx;
   ACPI_CPU_DATA              *AcpiCpuData;
   EFI_MP_SERVICES_PROTOCOL   *MpServices;
   UINTN                      NumberOfCpus;
-  UINTN                      NumberOfEnabledProcessors;
   VOID                       *Stack;
   UINTN                      TableSize;
   CPU_REGISTER_TABLE         *RegisterTable;
   UINTN                      Index;
   EFI_PROCESSOR_INFORMATION  ProcessorInfoBuffer;
   UINTN                      GdtSize;
   UINTN                      IdtSize;
   VOID                       *Gdt;
   VOID                       *Idt;
   EFI_EVENT                  Event;
   ACPI_CPU_DATA              *OldAcpiCpuData;
+  BOOLEAN                    FetchPossibleApicIds;
 
   if (!PcdGetBool (PcdAcpiS3Enable)) {
     return EFI_UNSUPPORTED;
   }
 
   //
   // Set PcdCpuS3DataAddress to the base address of the ACPI_CPU_DATA structure
   //
   OldAcpiCpuData = (ACPI_CPU_DATA *) (UINTN) PcdGet64 (PcdCpuS3DataAddress);
 
   AcpiCpuDataEx = AllocateZeroPages (sizeof (ACPI_CPU_DATA_EX));
   ASSERT (AcpiCpuDataEx != NULL);
   AcpiCpuData = &AcpiCpuDataEx->AcpiCpuData;
 
   //
-  // Get MP Services Protocol
+  // The "SMRAM at default SMBASE" feature guarantees that
+  // QEMU_CPUHP_CMD_GET_ARCH_ID too is available.
   //
-  Status = gBS->LocateProtocol (
-                  &gEfiMpServiceProtocolGuid,
-                  NULL,
-                  (VOID **)&MpServices
-                  );
-  ASSERT_EFI_ERROR (Status);
+  FetchPossibleApicIds = PcdGetBool (PcdQ35SmramAtDefaultSmbase);
 
-  //
-  // Get the number of CPUs
-  //
-  Status = MpServices->GetNumberOfProcessors (
-                         MpServices,
-                         &NumberOfCpus,
-                         &NumberOfEnabledProcessors
-                         );
-  ASSERT_EFI_ERROR (Status);
+  if (FetchPossibleApicIds) {
+    NumberOfCpus = PcdGet32 (PcdCpuMaxLogicalProcessorNumber);
+  } else {
+    UINTN NumberOfEnabledProcessors;
+
+    //
+    // Get MP Services Protocol
+    //
+    Status = gBS->LocateProtocol (
+                    &gEfiMpServiceProtocolGuid,
+                    NULL,
+                    (VOID **)&MpServices
+                    );
+    ASSERT_EFI_ERROR (Status);
+
+    //
+    // Get the number of CPUs
+    //
+    Status = MpServices->GetNumberOfProcessors (
+                           MpServices,
+                           &NumberOfCpus,
+                           &NumberOfEnabledProcessors
+                           );
+    ASSERT_EFI_ERROR (Status);
+  }
   AcpiCpuData->NumberOfCpus = (UINT32)NumberOfCpus;
 
   //
   // Initialize ACPI_CPU_DATA fields
   //
   AcpiCpuData->StackSize                 = PcdGet32 (PcdCpuApStackSize);
   AcpiCpuData->ApMachineCheckHandlerBase = 0;
   AcpiCpuData->ApMachineCheckHandlerSize = 0;
   AcpiCpuData->GdtrProfile  = (EFI_PHYSICAL_ADDRESS)(UINTN)&AcpiCpuDataEx->GdtrProfile;
   AcpiCpuData->IdtrProfile  = (EFI_PHYSICAL_ADDRESS)(UINTN)&AcpiCpuDataEx->IdtrProfile;
   AcpiCpuData->MtrrTable    = (EFI_PHYSICAL_ADDRESS)(UINTN)&AcpiCpuDataEx->MtrrTable;
 
   //
   // Allocate stack space for all CPUs.
   // Use ACPI NVS memory type because this data will be directly used by APs
   // in S3 resume phase in long mode. Also during S3 resume, the stack buffer
   // will only be used as scratch space. i.e. we won't read anything from it
   // before we write to it, in PiSmmCpuDxeSmm.
   //
   Stack = AllocateAcpiNvsMemory (NumberOfCpus * AcpiCpuData->StackSize);
   ASSERT (Stack != NULL);
   AcpiCpuData->StackAddress = (EFI_PHYSICAL_ADDRESS)(UINTN)Stack;
@@ -244,58 +260,83 @@ CpuS3DataInitialize (
   IdtSize = AcpiCpuDataEx->IdtrProfile.Limit + 1;
   Gdt = AllocateZeroPages (GdtSize + IdtSize);
   ASSERT (Gdt != NULL);
   Idt = (VOID *)((UINTN)Gdt + GdtSize);
   CopyMem (Gdt, (VOID *)AcpiCpuDataEx->GdtrProfile.Base, GdtSize);
   CopyMem (Idt, (VOID *)AcpiCpuDataEx->IdtrProfile.Base, IdtSize);
   AcpiCpuDataEx->GdtrProfile.Base = (UINTN)Gdt;
   AcpiCpuDataEx->IdtrProfile.Base = (UINTN)Idt;
 
   if (OldAcpiCpuData != NULL) {
     AcpiCpuData->RegisterTable           = OldAcpiCpuData->RegisterTable;
     AcpiCpuData->PreSmmInitRegisterTable = OldAcpiCpuData->PreSmmInitRegisterTable;
     AcpiCpuData->ApLocation              = OldAcpiCpuData->ApLocation;
     CopyMem (&AcpiCpuData->CpuStatus, &OldAcpiCpuData->CpuStatus, sizeof (CPU_STATUS_INFORMATION));
   } else {
     //
     // Allocate buffer for empty RegisterTable and PreSmmInitRegisterTable for all CPUs
     //
     TableSize = 2 * NumberOfCpus * sizeof (CPU_REGISTER_TABLE);
     RegisterTable = (CPU_REGISTER_TABLE *)AllocateZeroPages (TableSize);
     ASSERT (RegisterTable != NULL);
 
+    if (FetchPossibleApicIds) {
+      //
+      // Write a valid selector so that other hotplug registers can be
+      // accessed.
+      //
+      IoWrite32 (ICH9_CPU_HOTPLUG_BASE + QEMU_CPUHP_W_CPU_SEL, 0);
+      //
+      // We'll be fetching the APIC IDs.
+      //
+      IoWrite8 (ICH9_CPU_HOTPLUG_BASE + QEMU_CPUHP_W_CMD,
+        QEMU_CPUHP_CMD_GET_ARCH_ID);
+    }
     for (Index = 0; Index < NumberOfCpus; Index++) {
-      Status = MpServices->GetProcessorInfo (
-                           MpServices,
-                           Index,
-                           &ProcessorInfoBuffer
-                           );
-      ASSERT_EFI_ERROR (Status);
+      UINT32 InitialApicId;
 
-      RegisterTable[Index].InitialApicId      = (UINT32)ProcessorInfoBuffer.ProcessorId;
+      if (FetchPossibleApicIds) {
+        IoWrite32 (ICH9_CPU_HOTPLUG_BASE + QEMU_CPUHP_W_CPU_SEL,
+          (UINT32)Index);
+        InitialApicId = IoRead32 (
+                          ICH9_CPU_HOTPLUG_BASE + QEMU_CPUHP_RW_CMD_DATA);
+      } else {
+        Status = MpServices->GetProcessorInfo (
+                             MpServices,
+                             Index,
+                             &ProcessorInfoBuffer
+                             );
+        ASSERT_EFI_ERROR (Status);
+        InitialApicId = (UINT32)ProcessorInfoBuffer.ProcessorId;
+      }
+
+      DEBUG ((DEBUG_VERBOSE, "%a: Index=%05Lu ApicId=0x%08x\n", __FUNCTION__,
+        (UINT64)Index, InitialApicId));
+
+      RegisterTable[Index].InitialApicId      = InitialApicId;
       RegisterTable[Index].TableLength        = 0;
       RegisterTable[Index].AllocatedSize      = 0;
       RegisterTable[Index].RegisterTableEntry = 0;
 
-      RegisterTable[NumberOfCpus + Index].InitialApicId      = (UINT32)ProcessorInfoBuffer.ProcessorId;
+      RegisterTable[NumberOfCpus + Index].InitialApicId      = InitialApicId;
       RegisterTable[NumberOfCpus + Index].TableLength        = 0;
       RegisterTable[NumberOfCpus + Index].AllocatedSize      = 0;
       RegisterTable[NumberOfCpus + Index].RegisterTableEntry = 0;
     }
     AcpiCpuData->RegisterTable           = (EFI_PHYSICAL_ADDRESS)(UINTN)RegisterTable;
     AcpiCpuData->PreSmmInitRegisterTable = (EFI_PHYSICAL_ADDRESS)(UINTN)(RegisterTable + NumberOfCpus);
   }
 
   //
   // Set PcdCpuS3DataAddress to the base address of the ACPI_CPU_DATA structure
   //
   Status = PcdSet64S (PcdCpuS3DataAddress, (UINT64)(UINTN)AcpiCpuData);
   ASSERT_EFI_ERROR (Status);
 
   //
   // Register EFI_END_OF_DXE_EVENT_GROUP_GUID event.
   // The notification function allocates StartupVector and saves MTRRs for ACPI_CPU_DATA
   //
   Status = gBS->CreateEventEx (
                   EVT_NOTIFY_SIGNAL,
                   TPL_CALLBACK,
                   CpuS3DataOnEndOfDxe,
-- 
2.19.1.3.g30247aa5d201


  parent reply	other threads:[~2020-02-26 22:12 UTC|newest]

Thread overview: 53+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2020-02-26 22:11 [PATCH v2 00/16] OvmfPkg: support VCPU hotplug with -D SMM_REQUIRE Laszlo Ersek
2020-02-26 22:11 ` [PATCH v2 01/16] MdeModulePkg/PiSmmCore: log SMM image start failure Laszlo Ersek
2020-03-02 12:47   ` [edk2-devel] " Laszlo Ersek
2020-03-02 12:55     ` Liming Gao
2020-03-02 13:46   ` Philippe Mathieu-Daudé
2020-03-03  0:46   ` Dong, Eric
2020-02-26 22:11 ` [PATCH v2 02/16] UefiCpuPkg/PiSmmCpuDxeSmm: fix S3 Resume for CPU hotplug Laszlo Ersek
2020-02-28  3:05   ` [edk2-devel] " Dong, Eric
2020-02-28 10:50     ` Laszlo Ersek
2020-03-04 12:23     ` Laszlo Ersek
2020-03-04 14:36       ` Dong, Eric
2020-02-26 22:11 ` [PATCH v2 03/16] OvmfPkg: clone SmmCpuPlatformHookLib from UefiCpuPkg Laszlo Ersek
2020-03-02 13:27   ` Ard Biesheuvel
2020-03-02 13:49   ` Philippe Mathieu-Daudé
2020-02-26 22:11 ` [PATCH v2 04/16] OvmfPkg: enable SMM Monarch Election in PiSmmCpuDxeSmm Laszlo Ersek
2020-03-02 13:32   ` Ard Biesheuvel
2020-02-26 22:11 ` [PATCH v2 05/16] OvmfPkg: enable CPU hotplug support " Laszlo Ersek
2020-03-02 13:33   ` Ard Biesheuvel
2020-02-26 22:11 ` [PATCH v2 06/16] OvmfPkg/CpuHotplugSmm: introduce skeleton for CPU Hotplug SMM driver Laszlo Ersek
2020-03-02 13:44   ` Ard Biesheuvel
2020-02-26 22:11 ` [PATCH v2 07/16] OvmfPkg/CpuHotplugSmm: add hotplug register block helper functions Laszlo Ersek
2020-03-02 13:24   ` Philippe Mathieu-Daudé
2020-03-02 13:45   ` [edk2-devel] " Ard Biesheuvel
2020-02-26 22:11 ` [PATCH v2 08/16] OvmfPkg/CpuHotplugSmm: define the QEMU_CPUHP_CMD_GET_ARCH_ID macro Laszlo Ersek
2020-03-02 13:22   ` Philippe Mathieu-Daudé
2020-03-02 13:45   ` Ard Biesheuvel
2020-02-26 22:11 ` [PATCH v2 09/16] OvmfPkg/CpuHotplugSmm: add function for collecting CPUs with events Laszlo Ersek
2020-03-02 13:49   ` Ard Biesheuvel
2020-03-02 20:34   ` Philippe Mathieu-Daudé
2020-03-03 10:31     ` Laszlo Ersek
2020-02-26 22:11 ` [PATCH v2 10/16] OvmfPkg/CpuHotplugSmm: collect " Laszlo Ersek
2020-03-02 13:58   ` Ard Biesheuvel
2020-02-26 22:11 ` [PATCH v2 11/16] OvmfPkg/CpuHotplugSmm: introduce Post-SMM Pen for hot-added CPUs Laszlo Ersek
2020-03-02 14:02   ` [edk2-devel] " Ard Biesheuvel
2020-02-26 22:11 ` [PATCH v2 12/16] OvmfPkg/CpuHotplugSmm: introduce First SMI Handler " Laszlo Ersek
2020-03-02 14:03   ` Ard Biesheuvel
2020-02-26 22:11 ` [PATCH v2 13/16] OvmfPkg/CpuHotplugSmm: complete root MMI handler for CPU hotplug Laszlo Ersek
2020-03-02 14:05   ` Ard Biesheuvel
2020-02-26 22:11 ` [PATCH v2 14/16] OvmfPkg: clone CpuS3DataDxe from UefiCpuPkg Laszlo Ersek
2020-03-02 13:44   ` Philippe Mathieu-Daudé
2020-03-02 14:06   ` Ard Biesheuvel
2020-02-26 22:11 ` [PATCH v2 15/16] OvmfPkg/CpuS3DataDxe: superficial cleanups Laszlo Ersek
2020-03-02 13:25   ` Philippe Mathieu-Daudé
2020-03-02 14:06   ` Ard Biesheuvel
2020-02-26 22:11 ` Laszlo Ersek [this message]
2020-03-02 14:16   ` [PATCH v2 16/16] OvmfPkg/CpuS3DataDxe: enable S3 resume after CPU hotplug Ard Biesheuvel
2020-03-02 15:46 ` [edk2-devel] [PATCH v2 00/16] OvmfPkg: support VCPU hotplug with -D SMM_REQUIRE Boris Ostrovsky
2020-03-02 19:22   ` Laszlo Ersek
2020-03-02 19:59 ` Laszlo Ersek
2020-03-04 13:29   ` Philippe Mathieu-Daudé
2020-03-04 18:09     ` Laszlo Ersek
2020-03-04 12:29 ` Laszlo Ersek
2020-03-05  8:32   ` Laszlo Ersek

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-list from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20200226221156.29589-17-lersek@redhat.com \
    --to=devel@edk2.groups.io \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox