From: "Nhi Pham" <nhi@os.amperecomputing.com>
To: devel@edk2.groups.io
Cc: Nhi Pham <nhi@os.amperecomputing.com>
Subject: [edk2-platforms][PATCH 06/34] Platform/Ampere: Add AcpiPccLib to support ACPI PCCT Table
Date: Wed, 9 Dec 2020 16:25:03 +0700 [thread overview]
Message-ID: <20201209092531.30867-7-nhi@os.amperecomputing.com> (raw)
In-Reply-To: <20201209092531.30867-1-nhi@os.amperecomputing.com>
The AcpiPccLib provides functions to allocate and get the physical
address of PCC shared memory.
Signed-off-by: Nhi Pham <nhi@os.amperecomputing.com>
---
Silicon/Ampere/AmperePkg.dec | 4 +
Platform/Ampere/Library/AcpiPccLib/AcpiPccLib.inf | 45 +++
Silicon/Ampere/Include/Library/AcpiPccLib.h | 91 ++++++
Platform/Ampere/Library/AcpiPccLib/AcpiPccLib.c | 322 ++++++++++++++++++++
4 files changed, 462 insertions(+)
diff --git a/Silicon/Ampere/AmperePkg.dec b/Silicon/Ampere/AmperePkg.dec
index 47b29f508185..fa5b7074f749 100755
--- a/Silicon/Ampere/AmperePkg.dec
+++ b/Silicon/Ampere/AmperePkg.dec
@@ -22,8 +22,12 @@ [Defines]
#
################################################################################
[Includes.common]
+ Include # Root include for the package
[LibraryClasses]
+ ## @libraryclass Provides functions to create the ACPI PCCT Table which which advertises PCC mailbox channel information.
+ AcpiPccLib|Silicon/Ampere/Include/Library/AcpiPccLib.h
+
[Guids]
gAmpereTokenSpaceGuid = { 0xdbd4436e, 0x89cb, 0x44dc, { 0xb5, 0xc0, 0x49, 0xc3, 0x91, 0x35, 0xbf, 0xdf } }
diff --git a/Platform/Ampere/Library/AcpiPccLib/AcpiPccLib.inf b/Platform/Ampere/Library/AcpiPccLib/AcpiPccLib.inf
new file mode 100755
index 000000000000..d4b48e0fa248
--- /dev/null
+++ b/Platform/Ampere/Library/AcpiPccLib/AcpiPccLib.inf
@@ -0,0 +1,45 @@
+## @file
+#
+# Copyright (c) 2020, Ampere Computing LLC. All rights reserved.<BR>
+#
+# SPDX-License-Identifier: BSD-2-Clause-Patent
+#
+##
+
+[Defines]
+ INF_VERSION = 0x0001001B
+ BASE_NAME = AcpiPccLib
+ FILE_GUID = 790519F0-F344-11E3-AC10-0800200C9A66
+ MODULE_TYPE = BASE
+ VERSION_STRING = 1.0
+ LIBRARY_CLASS = AcpiPccLib
+
+[Sources.common]
+ AcpiPccLib.c
+
+[FeaturePcd]
+
+[Pcd]
+
+[FixedPcd]
+
+[Packages]
+ ArmPkg/ArmPkg.dec
+ MdePkg/MdePkg.dec
+ ArmPlatformPkg/ArmPlatformPkg.dec
+ Silicon/Ampere/AmperePkg.dec
+ Silicon/Ampere/AmpereAltraPkg/Ac01Pkg.dec
+
+[LibraryClasses]
+ ArmLib
+ BaseLib
+ DebugLib
+ IoLib
+ PrintLib
+ TimerLib
+ BaseMemoryLib
+ UefiBootServicesTableLib
+
+[Pcd]
+ gAmpereTokenSpaceGuid.PcdPmproDbBaseReg
+ gAmpereTokenSpaceGuid.PcdSmproDbBaseReg
diff --git a/Silicon/Ampere/Include/Library/AcpiPccLib.h b/Silicon/Ampere/Include/Library/AcpiPccLib.h
new file mode 100755
index 000000000000..7ec761adaec1
--- /dev/null
+++ b/Silicon/Ampere/Include/Library/AcpiPccLib.h
@@ -0,0 +1,91 @@
+/** @file
+
+ Copyright (c) 2020, Ampere Computing LLC. All rights reserved.<BR>
+
+ SPDX-License-Identifier: BSD-2-Clause-Patent
+
+**/
+
+#ifndef _ACPI_PCC_LIB_H_
+#define _ACPI_PCC_LIB_H_
+
+struct ACPI_PCCT_SHARED_MEMORY {
+ UINT32 Signature;
+ union {
+ UINT16 Command;
+ struct PCC_COMMAND {
+ UINT16 CommandCode:8;
+ UINT16 Reserved:7;
+ UINT16 Interrupt:1;
+ } __attribute__ ((packed)) CmdT;
+ } CmdData;
+ union {
+ UINT16 Status;
+ struct PCC_STATUS {
+ UINT16 CommandComplete:1;
+ UINT16 SciDb:1;
+ UINT16 Error:1;
+ UINT16 PlatformNotification:1;
+ UINT16 Reserved:12;
+ } __attribute__ ((packed)) StatusT;
+ } StatusData;
+} __attribute__((packed));
+
+EFI_STATUS
+EFIAPI
+AcpiPccSendMsg (
+ IN UINT32 Socket,
+ IN UINT32 Subspace,
+ IN VOID *MsgBuf,
+ IN UINT32 Length
+ );
+
+EFI_STATUS
+EFIAPI
+AcpiPccUnmaskInt (
+ IN UINT32 Socket,
+ IN UINT32 Subspace
+ );
+
+EFI_STATUS
+EFIAPI
+AcpiPccSyncSharedMemAddr (
+ IN UINT32 Socket,
+ IN UINT32 Subspace
+ );
+
+EFI_STATUS
+EFIAPI
+AcpiPccSharedMemInit (
+ IN UINT32 Socket,
+ IN UINT32 Subspace
+ );
+
+EFI_STATUS
+EFIAPI
+AcpiPccSharedMemInitV2 (
+ IN UINT32 Socket,
+ IN UINT32 Subspace
+ );
+
+EFI_STATUS
+EFIAPI
+AcpiIppPccIsSupported (
+ VOID
+ );
+
+EFI_STATUS
+EFIAPI
+AcpiPccAllocSharedMemory (
+ OUT UINT64 *PccSharedMemPointer,
+ IN UINT32 SubspaceNum
+ );
+
+VOID
+EFIAPI
+AcpiPccFreeSharedMemory (
+ OUT UINT64 *PccSharedMemPointer,
+ IN UINT32 SubspaceNum
+ );
+
+#endif /* _ACPI_PCC_LIB_H_*/
diff --git a/Platform/Ampere/Library/AcpiPccLib/AcpiPccLib.c b/Platform/Ampere/Library/AcpiPccLib/AcpiPccLib.c
new file mode 100755
index 000000000000..94e55ce34505
--- /dev/null
+++ b/Platform/Ampere/Library/AcpiPccLib/AcpiPccLib.c
@@ -0,0 +1,322 @@
+/** @file
+
+ Copyright (c) 2020, Ampere Computing LLC. All rights reserved.<BR>
+
+ SPDX-License-Identifier: BSD-2-Clause-Patent
+
+**/
+
+#include <Base.h>
+#include <Library/ArmLib.h>
+#include <Library/BaseMemoryLib.h>
+#include <Library/DebugLib.h>
+#include <Library/IoLib.h>
+#include <Library/PrintLib.h>
+#include <Library/TimerLib.h>
+#include <Uefi/UefiSpec.h>
+#include <Library/UefiBootServicesTableLib.h>
+#include <Platform/Ac01.h>
+#include <Library/AcpiPccLib.h>
+#include <Uefi/UefiBaseType.h>
+
+#define PCC_NULL_MSG 0x0F000000
+
+STATIC UINT64 PccSharedMemAddr = 0;
+
+STATIC EFI_STATUS
+AcpiPccGetSharedMemAddr (
+ IN UINT32 Socket,
+ IN UINT32 Subspace,
+ OUT VOID **AcpiPcct
+ )
+{
+ if ((Subspace >= PCC_MAX_SUBSPACES_PER_SOCKET)
+ || (Socket >= PLATFORM_CPU_MAX_SOCKET)) {
+ return EFI_INVALID_PARAMETER;
+ }
+
+ if (PccSharedMemAddr == 0) {
+ return EFI_NOT_READY;
+ }
+
+ *AcpiPcct = (VOID *) (PccSharedMemAddr + PCC_SUBSPACE_SHARED_MEM_SIZE *
+ (Subspace + PCC_MAX_SUBSPACES_PER_SOCKET * Socket));
+
+ return EFI_SUCCESS;
+}
+
+EFI_STATUS
+EFIAPI
+AcpiPccSendMsg (
+ IN UINT32 Socket,
+ IN UINT32 Subspace,
+ IN VOID *MsgBuf,
+ IN UINT32 Length
+ )
+{
+ INTN TimeoutCnt = PCC_NOMINAL_LATENCY / PCC_CMD_POLL_UDELAY;
+ VOID *CommunicationSpacePtr;
+ struct ACPI_PCCT_SHARED_MEMORY *AcpiPcct;
+ EFI_STATUS Status;
+ UINT32 PccMsg;
+
+ if ((Subspace >= PCC_MAX_SUBSPACES_PER_SOCKET)
+ || (Socket >= PLATFORM_CPU_MAX_SOCKET)) {
+ return EFI_INVALID_PARAMETER;
+ }
+
+ Status = AcpiPccGetSharedMemAddr (Socket, Subspace, (VOID **) &AcpiPcct);
+ if (EFI_ERROR (Status)) {
+ return Status;
+ }
+
+ CommunicationSpacePtr = AcpiPcct + 1;
+
+ /* Write Data into Communication Space Region */
+ CopyMem (CommunicationSpacePtr, MsgBuf, Length);
+ /* Flip CMD_COMPLETE bit */
+ AcpiPcct->StatusData.StatusT.CommandComplete = 0;
+ /* PCC signature */
+ AcpiPcct->Signature = PCC_SIGNATURE_MASK | Subspace;
+ /* Ring the Doorbell */
+ PccMsg = PCC_MSG;
+ /* Store the upper address (Bit 40-43) of PCC shared memory */
+ PccMsg |= ((UINT64) AcpiPcct >> 40) & PCP_MSG_UPPER_ADDR_MASK;
+ if (Subspace < PMPRO_MAX_DB) {
+ MmioWrite32 (PMPRO_DBx_REG (Socket, Subspace, DB_OUT), PccMsg);
+ } else {
+ MmioWrite32 (SMPRO_DBx_REG (Socket, Subspace - PMPRO_MAX_DB, DB_OUT),
+ PccMsg);
+ }
+
+ /* Polling CMD_COMPLETE bit */
+ while (AcpiPcct->StatusData.StatusT.CommandComplete != 1) {
+ if (--TimeoutCnt <= 0) {
+ return EFI_TIMEOUT;
+ }
+ MicroSecondDelay (PCC_CMD_POLL_UDELAY);
+ };
+
+ return EFI_SUCCESS;
+}
+
+EFI_STATUS
+EFIAPI
+AcpiPccUnmaskInt (
+ IN UINT32 Socket,
+ IN UINT32 Subspace
+ )
+{
+ if ((Subspace >= PCC_MAX_SUBSPACES_PER_SOCKET)
+ || (Socket >= PLATFORM_CPU_MAX_SOCKET)) {
+ return EFI_INVALID_PARAMETER;
+ }
+
+ /* Unmask Interrupt */
+ if (Subspace < PMPRO_MAX_DB) {
+ MmioWrite32 (
+ PMPRO_DBx_REG (Socket, Subspace, DB_STATUSMASK),
+ ~DB_AVAIL_MASK
+ );
+ } else {
+ MmioWrite32 (
+ SMPRO_DBx_REG (Socket, Subspace - PMPRO_MAX_DB, DB_STATUSMASK),
+ ~DB_AVAIL_MASK
+ );
+ }
+
+ return EFI_SUCCESS;
+}
+
+EFI_STATUS
+EFIAPI
+AcpiPccSyncSharedMemAddr (
+ IN UINT32 Socket,
+ IN UINT32 Subspace
+ )
+{
+ UINT32 PccData;
+
+ if ((Subspace >= PCC_MAX_SUBSPACES_PER_SOCKET)
+ || (Socket >= PLATFORM_CPU_MAX_SOCKET)) {
+ return EFI_INVALID_PARAMETER;
+ }
+
+ /*
+ * Advertise shared memory address to Platform (SMPro/PMPro)
+ * by ring the doorbell with dummy PCC message
+ */
+ PccData = PCC_NULL_MSG;
+
+ return AcpiPccSendMsg (Socket, Subspace, &PccData, 4);
+}
+
+EFI_STATUS
+EFIAPI
+AcpiPccSharedMemInit (
+ IN UINT32 Socket,
+ IN UINT32 Subspace
+ )
+{
+ struct ACPI_PCCT_SHARED_MEMORY *AcpiPcct;
+ EFI_STATUS Status;
+
+ if ((Subspace >= PCC_MAX_SUBSPACES_PER_SOCKET)
+ || (Socket >= PLATFORM_CPU_MAX_SOCKET)) {
+ return EFI_INVALID_PARAMETER;
+ }
+
+ Status = AcpiPccGetSharedMemAddr (Socket, Subspace, (VOID **) &AcpiPcct);
+ if (EFI_ERROR (Status)) {
+ return Status;
+ }
+
+ /* Set Shared Memory address into DB OUT register */
+ if (Subspace < PMPRO_MAX_DB) {
+ MmioWrite32 (
+ PMPRO_DBx_REG (Socket, Subspace, DB_OUT0),
+ (UINT32) ((UINT64) AcpiPcct >> 8)
+ );
+ } else {
+ MmioWrite32 (
+ SMPRO_DBx_REG(Socket, Subspace - PMPRO_MAX_DB, DB_OUT0),
+ (UINT32) ((UINT64) AcpiPcct >> 8)
+ );
+ }
+
+ /* Init shared memory for each PCC subspaces */
+ SetMem (
+ (VOID *) AcpiPcct,
+ sizeof (struct ACPI_PCCT_SHARED_MEMORY) + PCC_MSG_SIZE,
+ 0x0
+ );
+ AcpiPcct->StatusData.StatusT.CommandComplete = 1;
+
+ return EFI_SUCCESS;
+}
+
+EFI_STATUS
+EFIAPI
+AcpiPccSharedMemInitV2 (
+ IN UINT32 Socket,
+ IN UINT32 Subspace
+ )
+{
+ struct ACPI_PCCT_SHARED_MEMORY *AcpiPcct;
+ EFI_STATUS Status;
+ UINT32 AlignBit;
+
+ if ((Subspace >= PCC_MAX_SUBSPACES_PER_SOCKET)
+ || (Socket >= PLATFORM_CPU_MAX_SOCKET)) {
+ return EFI_INVALID_PARAMETER;
+ }
+
+ Status = AcpiPccGetSharedMemAddr (Socket, Subspace, (VOID **) &AcpiPcct);
+ if (EFI_ERROR (Status)) {
+ return Status;
+ }
+
+ if ((PCC_MSG & PCC_256_ALIGN_ADDR) != 0) {
+ AlignBit = 8;
+ }
+
+ /* Set Shared Memory address into DB OUT register */
+ if (Subspace < PMPRO_MAX_DB) {
+ MmioWrite32 (
+ PMPRO_DBx_REG (Socket, Subspace, DB_OUT0),
+ (UINT32) ((UINT64) AcpiPcct >> AlignBit)
+ );
+
+ MmioWrite32 (
+ (PMPRO_DBx_REG (Socket, Subspace, DB_OUT1)),
+ (UINT32) ((UINT64) AcpiPcct >> (32 + AlignBit))
+ );
+ } else {
+ MmioWrite32 (
+ SMPRO_DBx_REG (Socket, Subspace - PMPRO_MAX_DB, DB_OUT0),
+ (UINT32) ((UINT64) AcpiPcct >> AlignBit)
+ );
+
+ MmioWrite32 (
+ SMPRO_DBx_REG (Socket, Subspace - PMPRO_MAX_DB, DB_OUT1),
+ (UINT32) ((UINT64) AcpiPcct >> (32 + AlignBit))
+ );
+ }
+
+ /* Init shared memory for each PCC subspaces */
+ SetMem (
+ (VOID *) AcpiPcct,
+ sizeof (struct ACPI_PCCT_SHARED_MEMORY) + PCC_MSG_SIZE,
+ 0x0
+ );
+ AcpiPcct->StatusData.StatusT.CommandComplete = 1;
+
+ return EFI_SUCCESS;
+}
+
+EFI_STATUS
+EFIAPI
+AcpiIppPccIsSupported (
+ VOID
+ )
+{
+ EFI_STATUS Status;
+
+ /* Send a PCC NULL command to check if IPP supports PCC request */
+ AcpiPccSharedMemInit (0, 0);
+
+ Status = AcpiPccSyncSharedMemAddr (0, 0);
+ if (EFI_ERROR (Status)) {
+ return EFI_UNSUPPORTED;
+ }
+
+ return EFI_SUCCESS;
+}
+
+EFI_STATUS
+EFIAPI
+AcpiPccAllocSharedMemory (
+ OUT UINT64 *PccSharedMemPointer,
+ IN UINT32 SubspaceNum
+ )
+{
+ EFI_STATUS Status;
+
+ if (SubspaceNum > PCC_MAX_SUBSPACES) {
+ return EFI_INVALID_PARAMETER;
+ }
+
+ Status = gBS->AllocatePages (
+ AllocateAnyPages,
+ EfiRuntimeServicesData,
+ EFI_SIZE_TO_PAGES (PCC_SUBSPACE_SHARED_MEM_SIZE * SubspaceNum),
+ &PccSharedMemAddr
+ );
+ if (EFI_ERROR (Status)) {
+ DEBUG ((DEBUG_ERROR, "Failed to allocate PCC shared memory\n"));
+ return Status;
+ }
+
+ *PccSharedMemPointer = PccSharedMemAddr;
+
+ return EFI_SUCCESS;
+}
+
+VOID
+EFIAPI
+AcpiPccFreeSharedMemory (
+ OUT UINT64 *PccSharedMemPointer,
+ IN UINT32 SubspaceNum
+ )
+{
+ if (SubspaceNum > PCC_MAX_SUBSPACES) {
+ return;
+ }
+
+ gBS->FreePages (
+ *PccSharedMemPointer,
+ EFI_SIZE_TO_PAGES (PCC_SUBSPACE_SHARED_MEM_SIZE * SubspaceNum)
+ );
+
+ PccSharedMemAddr = 0;
+}
--
2.17.1
next prev parent reply other threads:[~2020-12-09 9:24 UTC|newest]
Thread overview: 40+ messages / expand[flat|nested] mbox.gz Atom feed top
2020-12-09 9:24 [edk2-platforms][PATCH 00/34] Add new Ampere Mt. Jade platform Nhi Pham
2020-12-09 9:24 ` [edk2-platforms][PATCH 01/34] Initial support for Ampere Altra and " Nhi Pham
2021-01-07 23:57 ` [edk2-devel] " Leif Lindholm
2021-01-15 4:59 ` Vu Nguyen
2020-12-09 9:24 ` [edk2-platforms][PATCH 02/34] Platform/Ampere: Implement FailSafe library Nhi Pham
2020-12-09 9:25 ` [edk2-platforms][PATCH 03/34] Platform/Ampere: Add FailSafe and WDT support Nhi Pham
2020-12-09 9:25 ` [edk2-platforms][PATCH 04/34] AmpereAltraPkg: Implement GpioLib and I2cLib modules Nhi Pham
2020-12-09 9:25 ` [edk2-platforms][PATCH 05/34] JadePkg: Implement RealTimeClockLib for PCF85063 Nhi Pham
2020-12-09 9:25 ` Nhi Pham [this message]
2020-12-09 9:25 ` [edk2-platforms][PATCH 07/34] Platform/Ampere: Add AcpiHelperLib to update ACPI DSDT table Nhi Pham
2020-12-09 9:25 ` [edk2-platforms][PATCH 08/34] JadePkg: Initial support for static ACPI tables Nhi Pham
2020-12-09 9:25 ` [edk2-platforms][PATCH 09/34] JadePkg: Install some ACPI tables at runtime Nhi Pham
2020-12-09 9:25 ` [edk2-platforms][PATCH 10/34] Silicon/Ampere: Support Non Volatile storage for Variable service Nhi Pham
2020-12-09 9:25 ` [edk2-platforms][PATCH 11/34] Silicon/Ampere: Support PlatformManagerUiLib Nhi Pham
2020-12-09 9:25 ` [edk2-platforms][PATCH 12/34] AmpereAltraPkg: Add PcieCore Library Nhi Pham
2020-12-09 9:25 ` [edk2-platforms][PATCH 13/34] AmpereAltraPkg: Add PciHostBridge driver Nhi Pham
2020-12-09 9:25 ` [edk2-platforms][PATCH 14/34] JadePkg: Add implementation for PcieBoardLib Nhi Pham
2020-12-09 9:25 ` [edk2-platforms][PATCH 15/34] JadePkg: Enable PCIe support Nhi Pham
2020-12-09 9:25 ` [edk2-platforms][PATCH 16/34] JadePkg: Add ASpeed GOP driver Nhi Pham
2020-12-09 9:25 ` [edk2-platforms][PATCH 17/34] Silicon/Ampere: Add Random Number Generator Support Nhi Pham
2020-12-09 9:25 ` [edk2-platforms][PATCH 18/34] Silicon/Ampere: Fixup runtime memory attribute Nhi Pham
2020-12-09 9:25 ` [edk2-platforms][PATCH 19/34] JadePkg: Add SMBIOS tables support Nhi Pham
2020-12-09 9:25 ` [edk2-platforms][PATCH 20/34] AmpereAltraPkg: Add DebugInfoPei module Nhi Pham
2020-12-09 9:25 ` [edk2-platforms][PATCH 21/34] Silicon/Ampere: Add platform info screen Nhi Pham
2020-12-09 9:25 ` [edk2-platforms][PATCH 22/34] Silicon/Ampere: Add Memory " Nhi Pham
2020-12-09 9:25 ` [edk2-platforms][PATCH 23/34] AmpereAltraPkg: Add CPU Configuration for SubNUMA Nhi Pham
2020-12-09 9:25 ` [edk2-platforms][PATCH 24/34] AmpereAltraPkg: Add ACPI configuration screen Nhi Pham
2020-12-09 9:25 ` [edk2-platforms][PATCH 25/34] AmpereAltraPkg: Implement PlatformFlashAccessLib instance Nhi Pham
2020-12-09 9:25 ` [edk2-platforms][PATCH 26/34] JadePkg: Add implementation for UEFI Capsule Update Nhi Pham
2020-12-09 9:25 ` [edk2-platforms][PATCH 27/34] JadePkg: Add Capsule Update support Nhi Pham
2020-12-09 9:25 ` [edk2-platforms][PATCH 28/34] Silicon/Ampere: Implement PlatformBootManagerLib for LinuxBoot Nhi Pham
2020-12-09 9:25 ` [edk2-platforms][PATCH 29/34] Platform/Ampere: Add LinuxBoot image Nhi Pham
2020-12-10 12:40 ` [edk2-devel] " Leif Lindholm
2020-12-11 2:38 ` Nhi Pham
2020-12-11 11:15 ` Leif Lindholm
2020-12-09 9:25 ` [edk2-platforms][PATCH 30/34] JadePkg: Support LinuxBoot DSC/FDF build for Jade platform Nhi Pham
2020-12-09 9:25 ` [edk2-platforms][PATCH 31/34] AmpereAltraPkg: Add BootProgress support Nhi Pham
2020-12-09 9:25 ` [edk2-platforms][PATCH 32/34] JadePkg: Add ACPI/APEI tables Nhi Pham
2020-12-09 9:25 ` [edk2-platforms][PATCH 33/34] Platform/Ampere: Add AcpiApeiLib Nhi Pham
2020-12-09 9:25 ` [edk2-platforms][PATCH 34/34] AmpereAltraPkg, JadePkg: Add RAS setting screen Nhi Pham
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=20201209092531.30867-7-nhi@os.amperecomputing.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