From: "Chao Li" <lichao@loongson.cn>
To: devel@edk2.groups.io
Cc: Ray Ni <ray.ni@intel.com>, Rahul Kumar <rahul1.kumar@intel.com>,
Gerd Hoffmann <kraxel@redhat.com>,
Baoqi Zhang <zhangbaoqi@loongson.cn>,
Dongyan Qian <qiandongyan@loongson.cn>,
Xianglai Li <lixianglai@loongson.cn>,
Bibo Mao <maobibo@loongson.cn>
Subject: [edk2-devel] [PATCH v2 11/13] UefiCpuPkg: Add CpuMmuInitLib to UefiCpuPkg
Date: Wed, 20 Mar 2024 16:43:45 +0800 [thread overview]
Message-ID: <20240320084345.282542-1-lichao@loongson.cn> (raw)
In-Reply-To: <20240320084152.268323-1-lichao@loongson.cn>
Add a new base library named CpuMmuInitLib and add a LoongArch64
instance with in the library.
It is the consumer of the CpuMmuLib.
BZ: https://bugzilla.tianocore.org/show_bug.cgi?id=4734
Cc: Ray Ni <ray.ni@intel.com>
Cc: Rahul Kumar <rahul1.kumar@intel.com>
Cc: Gerd Hoffmann <kraxel@redhat.com>
Signed-off-by: Chao Li <lichao@loongson.cn>
Co-authored-by: Baoqi Zhang <zhangbaoqi@loongson.cn>
Co-authored-by: Dongyan Qian <qiandongyan@loongson.cn>
Co-authored-by: Xianglai Li <lixianglai@loongson.cn>
Co-authored-by: Bibo Mao <maobibo@loongson.cn>
---
.../Library/CpuMmuInitLib/CpuMmuInitLib.inf | 41 ++++
.../Library/CpuMmuInitLib/CpuMmuInitLib.uni | 14 ++
.../CpuMmuInitLib/LoongArch64/CpuMmuInit.c | 232 ++++++++++++++++++
.../LoongArch64/TlbExceptionHandle.S | 51 ++++
.../LoongArch64/TlbExceptionHandle.h | 36 +++
UefiCpuPkg/UefiCpuPkg.dsc | 1 +
6 files changed, 375 insertions(+)
create mode 100644 UefiCpuPkg/Library/CpuMmuInitLib/CpuMmuInitLib.inf
create mode 100644 UefiCpuPkg/Library/CpuMmuInitLib/CpuMmuInitLib.uni
create mode 100644 UefiCpuPkg/Library/CpuMmuInitLib/LoongArch64/CpuMmuInit.c
create mode 100644 UefiCpuPkg/Library/CpuMmuInitLib/LoongArch64/TlbExceptionHandle.S
create mode 100644 UefiCpuPkg/Library/CpuMmuInitLib/LoongArch64/TlbExceptionHandle.h
diff --git a/UefiCpuPkg/Library/CpuMmuInitLib/CpuMmuInitLib.inf b/UefiCpuPkg/Library/CpuMmuInitLib/CpuMmuInitLib.inf
new file mode 100644
index 0000000000..673d2a8eab
--- /dev/null
+++ b/UefiCpuPkg/Library/CpuMmuInitLib/CpuMmuInitLib.inf
@@ -0,0 +1,41 @@
+## @file
+# CPU Memory Map Unit Initialization library instance.
+#
+# Copyright (c) 2024 Loongson Technology Corporation Limited. All rights reserved.<BR>
+#
+# SPDX-License-Identifier: BSD-2-Clause-Patent
+#
+##
+
+[Defines]
+ INF_VERSION = 1.29
+ BASE_NAME = CpuMmuInitLib
+ MODULE_UNI_FILE = CpuMmuInitLib.uni
+ FILE_GUID = F67EB983-AC2A-7550-AB69-3BC51A1C895B
+ MODULE_TYPE = BASE
+ VERSION_STRING = 1.0
+ LIBRARY_CLASS = CpuMmuInitLib
+
+#
+# VALID_ARCHITECTURES = LOONGARCH64
+#
+
+[Sources.LoongArch64]
+ LoongArch64/TlbExceptionHandle.S | GCC
+ LoongArch64/CpuMmuInit.c
+ LoongArch64/TlbExceptionHandle.h
+
+[Packages]
+ MdePkg/MdePkg.dec
+ MdeModulePkg/MdeModulePkg.dec
+ UefiCpuPkg/UefiCpuPkg.dec
+
+[Pcd]
+ gUefiCpuPkgTokenSpaceGuid.PcdCpuExceptionVectorBaseAddress
+
+[LibraryClasses]
+ CacheMaintenanceLib
+ CpuMmuLib
+ DebugLib
+ MemoryAllocationLib
+ PcdLib
diff --git a/UefiCpuPkg/Library/CpuMmuInitLib/CpuMmuInitLib.uni b/UefiCpuPkg/Library/CpuMmuInitLib/CpuMmuInitLib.uni
new file mode 100644
index 0000000000..907f024302
--- /dev/null
+++ b/UefiCpuPkg/Library/CpuMmuInitLib/CpuMmuInitLib.uni
@@ -0,0 +1,14 @@
+// /** @file
+// CPU Memory Map Unit Initialization library instance.
+//
+// CPU Memory Map Unit Initialization library instance.
+//
+// Copyright (c) 2024, Loongson Technology Corporation Limited. All rights reserved.<BR>
+//
+// SPDX-License-Identifier: BSD-2-Clause-Patent
+//
+// **/
+
+#string STR_MODULE_ABSTRACT #language en-US "CPU Memory Manager Unit library instance for PEI modules."
+
+#string STR_MODULE_DESCRIPTION #language en-US "CPU Memory Manager Unit library instance for PEI modules."
diff --git a/UefiCpuPkg/Library/CpuMmuInitLib/LoongArch64/CpuMmuInit.c b/UefiCpuPkg/Library/CpuMmuInitLib/LoongArch64/CpuMmuInit.c
new file mode 100644
index 0000000000..5c74bdc264
--- /dev/null
+++ b/UefiCpuPkg/Library/CpuMmuInitLib/LoongArch64/CpuMmuInit.c
@@ -0,0 +1,232 @@
+/** @file
+ CPU Memory Map Unit Initialization library instance.
+
+ Copyright (c) 2024 Loongson Technology Corporation Limited. All rights reserved.<BR>
+
+ SPDX-License-Identifier: BSD-2-Clause-Patent
+
+**/
+
+#include <Uefi.h>
+#include <Library/BaseLib.h>
+#include <Library/BaseMemoryLib.h>
+#include <Library/CacheMaintenanceLib.h>
+#include <Library/CpuMmuLib.h>
+#include <Library/CpuMmuInitLib.h>
+#include <Library/DebugLib.h>
+#include <Library/MemoryAllocationLib.h>
+#include <Library/PcdLib.h>
+#include <Protocol/DebugSupport.h>
+#include <Register/LoongArch64/Csr.h>
+#include <Register/LoongArch64/Cpucfg.h>
+#include "TlbExceptionHandle.h"
+
+//
+// For coding convenience, define the maximum valid
+// LoongArch exception.
+// Since UEFI V2.11, it will be present in DebugSupport.h.
+//
+#define MAX_LOONGARCH_EXCEPTION 64
+
+//
+// Because the page size in edk2 is 4KB, the lowest level
+// page table is align to 12 bits, and the page table width
+// of other levels is set to 9 bits by default, which will
+// be 3 or 4 or 5 level page tables, and continuous.
+//
+// Correspondence between max virtual memory address width
+// and page table level:
+// 39 bit >= VA > 31 bit, 3 level page tables
+// 48 bit >= VA > 40 bit, 4 level page tables
+// 57 bit >= VA > 49 bit, 5 level page tables
+//
+#define BIT_WIDTH_PER_LEVEL 9
+#define MAX_SIZE_OF_PGD ((1 << BIT_WIDTH_PER_LEVEL) * 8) // 512 items, 8 Byte each.
+
+/**
+ Create a page table and initialize the memory management unit(MMU).
+
+ @param[in] MemoryTable A pointer to a memory ragion table.
+ @param[out] TranslationTableBase A pointer to a translation table base address.
+ @param[out] TranslationTableSize A pointer to a translation table base size.
+
+ @retval EFI_SUCCESS Configure MMU successfully.
+ EFI_INVALID_PARAMETER MemoryTable is NULL.
+ EFI_UNSUPPORTED Out of memory space or size not aligned.
+**/
+EFI_STATUS
+EFIAPI
+ConfigureMemoryManagementUnit (
+ IN EFI_MEMORY_DESCRIPTOR *MemoryTable,
+ OUT VOID **TranslationTableBase OPTIONAL,
+ OUT UINTN *TranslationTableSize OPTIONAL
+ )
+{
+ VOID *TranslationTable;
+ UINTN Length;
+ UINTN TlbReEntry;
+ UINTN TlbReEntryOffset;
+ UINTN Remaining;
+ EFI_STATUS Status;
+ CPUCFG_REG1_INFO_DATA CpucfgReg1Data;
+ UINT8 CpuVirtMemAddressWidth;
+ UINT8 PageTableLevelNum;
+ UINT8 CurrentPageTableLevel;
+ UINT32 Pwcl0Value;
+ UINT32 Pwcl1Value;
+
+ if (MemoryTable == NULL) {
+ ASSERT (MemoryTable != NULL);
+ return EFI_INVALID_PARAMETER;
+ }
+
+ //
+ // Get the the CPU virtual memory address width.
+ //
+ AsmCpucfg (CPUCFG_REG1_INFO, &CpucfgReg1Data.Uint32);
+
+ CpuVirtMemAddressWidth = (UINT8)(CpucfgReg1Data.Bits.VALEN + 1);
+
+ //
+ // Statisitics the maximum page table level
+ //
+ PageTableLevelNum = 0x0;
+ if (((CpuVirtMemAddressWidth - EFI_PAGE_SHIFT) % BIT_WIDTH_PER_LEVEL) > 0) {
+ PageTableLevelNum++;
+ }
+
+ PageTableLevelNum += (CpuVirtMemAddressWidth - EFI_PAGE_SHIFT) / BIT_WIDTH_PER_LEVEL;
+
+ //
+ // Set page table level
+ //
+ Pwcl0Value = 0x0;
+ Pwcl1Value = 0x0;
+ for (CurrentPageTableLevel = 0x0; CurrentPageTableLevel < PageTableLevelNum; CurrentPageTableLevel++) {
+ if (CurrentPageTableLevel < 0x3) {
+ // Less then or equal to level 3
+ Pwcl0Value |= ((BIT_WIDTH_PER_LEVEL * CurrentPageTableLevel + EFI_PAGE_SHIFT) << 10 * CurrentPageTableLevel) |
+ BIT_WIDTH_PER_LEVEL << (10 * CurrentPageTableLevel + 5);
+ } else {
+ // Lager then level 3
+ Pwcl1Value |= ((BIT_WIDTH_PER_LEVEL * CurrentPageTableLevel + EFI_PAGE_SHIFT) << 12 * (CurrentPageTableLevel - 3)) |
+ BIT_WIDTH_PER_LEVEL << (12 * (CurrentPageTableLevel - 3) + 6);
+ }
+
+ DEBUG ((
+ DEBUG_INFO,
+ "%a %d Level %d DIR shift %d.\n",
+ __func__,
+ __LINE__,
+ (CurrentPageTableLevel + 1),
+ (BIT_WIDTH_PER_LEVEL * CurrentPageTableLevel + EFI_PAGE_SHIFT)
+ ));
+ }
+
+ CsrWrite (LOONGARCH_CSR_PWCTL0, Pwcl0Value);
+ if (Pwcl1Value != 0x0) {
+ CsrWrite (LOONGARCH_CSR_PWCTL1, Pwcl1Value);
+ }
+
+ //
+ // Set page size
+ //
+ CsrXChg (LOONGARCH_CSR_TLBIDX, (DEFAULT_PAGE_SIZE << CSR_TLBIDX_SIZE), CSR_TLBIDX_SIZE_MASK);
+ CsrWrite (LOONGARCH_CSR_STLBPGSIZE, DEFAULT_PAGE_SIZE);
+ CsrXChg (LOONGARCH_CSR_TLBREHI, (DEFAULT_PAGE_SIZE << CSR_TLBREHI_PS_SHIFT), CSR_TLBREHI_PS);
+
+ //
+ // Create PGD and set the PGD address to PGDL
+ //
+ TranslationTable = AllocatePages (EFI_SIZE_TO_PAGES (MAX_SIZE_OF_PGD));
+ ZeroMem (TranslationTable, MAX_SIZE_OF_PGD);
+
+ if (TranslationTable == NULL) {
+ goto FreeTranslationTable;
+ }
+
+ CsrWrite (LOONGARCH_CSR_PGDL, (UINTN)TranslationTable);
+
+ //
+ // Fill page tables
+ //
+ while (MemoryTable->NumberOfPages != 0) {
+ DEBUG ((
+ DEBUG_INFO,
+ "%a %d VirtualBase %p VirtualEnd %p Attributes %p .\n",
+ __func__,
+ __LINE__,
+ MemoryTable->VirtualStart,
+ (EFI_PAGES_TO_SIZE (MemoryTable->NumberOfPages) + MemoryTable->VirtualStart),
+ MemoryTable->Attribute
+ ));
+
+ Status = SetMemoryRegionAttributes (
+ MemoryTable->VirtualStart,
+ EFI_PAGES_TO_SIZE (MemoryTable->NumberOfPages),
+ MemoryTable->Attribute,
+ 0x0
+ );
+
+ if (EFI_ERROR (Status)) {
+ goto FreeTranslationTable;
+ }
+
+ MemoryTable++;
+ }
+
+ //
+ // Set TLB exception handler
+ //
+ ///
+ /// TLB Re-entry address at the end of exception vector, a vector is up to 512 bytes,
+ /// so the starting address is: total exception vector size + total interrupt vector size + base.
+ /// The total size of TLB handler and exception vector size and interrupt vector size should not
+ /// be lager than 64KB.
+ ///
+ Length = (UINTN)HandleTlbRefillEnd - (UINTN)HandleTlbRefillStart;
+ TlbReEntryOffset = (MAX_LOONGARCH_EXCEPTION + MAX_LOONGARCH_INTERRUPT) * 512;
+ Remaining = TlbReEntryOffset % SIZE_4KB;
+ if (Remaining != 0x0) {
+ TlbReEntryOffset += (SIZE_4KB - Remaining);
+ }
+
+ TlbReEntry = PcdGet64 (PcdCpuExceptionVectorBaseAddress) + TlbReEntryOffset;
+ if ((TlbReEntryOffset + Length) > SIZE_64KB) {
+ goto FreeTranslationTable;
+ }
+
+ //
+ // Ensure that TLB refill exception base address alignment is equals to 4KB and is valid.
+ //
+ if (TlbReEntry & (SIZE_4KB - 1)) {
+ goto FreeTranslationTable;
+ }
+
+ CopyMem ((VOID *)TlbReEntry, HandleTlbRefillStart, Length);
+ InvalidateInstructionCacheRange ((VOID *)(UINTN)HandleTlbRefillStart, Length);
+
+ //
+ // Set the address of TLB refill exception handler
+ //
+ SetTlbRebaseAddress ((UINTN)TlbReEntry);
+
+ //
+ // Enable MMU
+ //
+ CsrXChg (LOONGARCH_CSR_CRMD, BIT4, BIT4|BIT3);
+
+ DEBUG ((DEBUG_INFO, "%a %d Enable MMU Start PageBassAddress %p.\n", __func__, __LINE__, TranslationTable));
+
+ //
+ // Set MMU enable flag.
+ //
+ return EFI_SUCCESS;
+
+FreeTranslationTable:
+ if (TranslationTable != NULL) {
+ FreePages (TranslationTable, EFI_SIZE_TO_PAGES (MAX_SIZE_OF_PGD));
+ }
+
+ return EFI_UNSUPPORTED;
+}
diff --git a/UefiCpuPkg/Library/CpuMmuInitLib/LoongArch64/TlbExceptionHandle.S b/UefiCpuPkg/Library/CpuMmuInitLib/LoongArch64/TlbExceptionHandle.S
new file mode 100644
index 0000000000..4395b574f5
--- /dev/null
+++ b/UefiCpuPkg/Library/CpuMmuInitLib/LoongArch64/TlbExceptionHandle.S
@@ -0,0 +1,51 @@
+#------------------------------------------------------------------------------
+#
+# TLB refill exception handler
+#
+# Copyright (c) 2024 Loongson Technology Corporation Limited. All rights reserved.<BR>
+#
+# SPDX-License-Identifier: BSD-2-Clause-Patent
+#
+#-----------------------------------------------------------------------------
+
+#include <Register/LoongArch64/Csr.h>
+
+ASM_GLOBAL ASM_PFX(HandleTlbRefillStart)
+ASM_GLOBAL ASM_PFX(HandleTlbRefillEnd)
+
+#
+# Refill the page table.
+# @param VOID
+# @retval VOID
+#
+ASM_PFX(HandleTlbRefillStart):
+ csrwr $t0, LOONGARCH_CSR_TLBRSAVE
+ csrrd $t0, LOONGARCH_CSR_PWCTL1
+ srli.d $t0, $t0, 18
+ andi $t0, $t0, 0x3F
+ bnez $t0, Level5
+ csrrd $t0, LOONGARCH_CSR_PWCTL1
+ srli.d $t0, $t0, 6
+ andi $t0, $t0, 0x3F
+ bnez $t0, Level4
+ csrrd $t0, LOONGARCH_CSR_PGD
+ b Level3
+Level5:
+ csrrd $t0, LOONGARCH_CSR_PGD
+ lddir $t0, $t0, 4 #Put pud BaseAddress into T0
+ lddir $t0, $t0, 3 #Put pud BaseAddress into T0
+ b Level3
+Level4:
+ csrrd $t0, LOONGARCH_CSR_PGD
+ lddir $t0, $t0, 3 #Put pud BaseAddress into T0
+Level3:
+ lddir $t0, $t0, 2 #Put pmd BaseAddress into T0
+ lddir $t0, $t0, 1 #Put pte BaseAddress into T0
+ ldpte $t0, 0
+ ldpte $t0, 1
+ tlbfill // refill hi, lo0, lo1
+ csrrd $t0, LOONGARCH_CSR_TLBRSAVE
+ ertn
+ASM_PFX(HandleTlbRefillEnd):
+
+ .end
diff --git a/UefiCpuPkg/Library/CpuMmuInitLib/LoongArch64/TlbExceptionHandle.h b/UefiCpuPkg/Library/CpuMmuInitLib/LoongArch64/TlbExceptionHandle.h
new file mode 100644
index 0000000000..c164db567d
--- /dev/null
+++ b/UefiCpuPkg/Library/CpuMmuInitLib/LoongArch64/TlbExceptionHandle.h
@@ -0,0 +1,36 @@
+/** @file
+
+ Copyright (c) 2024 Loongson Technology Corporation Limited. All rights reserved.<BR>
+
+ SPDX-License-Identifier: BSD-2-Clause-Patent
+
+**/
+
+#ifndef TLB_EXCEPTION_HANDLE_H_
+#define TLB_EXCEPTION_HANDLE_H_
+
+/**
+ TLB refill handler start.
+
+ @param none
+
+ @retval none
+**/
+VOID
+HandleTlbRefillStart (
+ VOID
+ );
+
+/**
+ TLB refill handler end.
+
+ @param none
+
+ @retval none
+**/
+VOID
+HandleTlbRefillEnd (
+ VOID
+ );
+
+#endif // TLB_EXCEPTION_HANDLE_H_
diff --git a/UefiCpuPkg/UefiCpuPkg.dsc b/UefiCpuPkg/UefiCpuPkg.dsc
index e92ceb6466..a9787f9d70 100644
--- a/UefiCpuPkg/UefiCpuPkg.dsc
+++ b/UefiCpuPkg/UefiCpuPkg.dsc
@@ -213,6 +213,7 @@ [Components.RISCV64]
[Components.LOONGARCH64]
UefiCpuPkg/Library/CpuMmuLib/CpuMmuLib.inf
+ UefiCpuPkg/Library/CpuMmuInitLib/CpuMmuInitLib.inf
[BuildOptions]
*_*_*_CC_FLAGS = -D DISABLE_NEW_DEPRECATED_INTERFACES
--
2.27.0
-=-=-=-=-=-=-=-=-=-=-=-
Groups.io Links: You receive all messages sent to this group.
View/Reply Online (#116927): https://edk2.groups.io/g/devel/message/116927
Mute This Topic: https://groups.io/mt/105041101/7686176
Group Owner: devel+owner@edk2.groups.io
Unsubscribe: https://edk2.groups.io/g/devel/unsub [rebecca@openfw.io]
-=-=-=-=-=-=-=-=-=-=-=-
next prev parent reply other threads:[~2024-03-20 8:43 UTC|newest]
Thread overview: 30+ messages / expand[flat|nested] mbox.gz Atom feed top
2024-03-20 8:41 [edk2-devel] [PATCH v2 00/13] Part 2 patch set to add LoongArch support into UefiCpuPkg Chao Li
2024-03-20 8:42 ` [edk2-devel] [PATCH v2 01/13] UefiCpuPkg/CpuTimerLib: Reorder the INF file alphabetically Chao Li
2024-03-22 1:15 ` Ni, Ray
2024-03-20 8:42 ` [edk2-devel] [PATCH v2 02/13] UefiCpuPkg/CpuExceptionHandlerLib: Reorder the INF files alphabetically Chao Li
2024-03-22 1:21 ` Ni, Ray
2024-03-20 8:42 ` [edk2-devel] [PATCH v2 03/13] UefiCpuPkg/MpInitLib: " Chao Li
2024-03-20 8:42 ` [edk2-devel] [PATCH v2 04/13] UefiCpuPkg/CpuDxe: Reorder the INF file alphabetically Chao Li
2024-03-20 8:43 ` [edk2-devel] [PATCH v2 05/13] UefiCpuPkg: Add LoongArch64 CPU Timer instance Chao Li
2024-03-20 8:43 ` [edk2-devel] [PATCH v2 06/13] UefiCpuPkg: Add CPU exception library for LoongArch Chao Li
2024-03-20 8:43 ` [edk2-devel] [PATCH v2 07/13] UefiCpuPkg: Add CpuMmuLib.h to UefiCpuPkg Chao Li
2024-03-25 2:29 ` Ni, Ray
2024-03-25 2:31 ` Ni, Ray
2024-03-25 2:52 ` Chao Li
2024-03-20 8:43 ` [edk2-devel] [PATCH v2 08/13] UefiCpuPkg: Added a new PCD named PcdCpuExceptionVectorBaseAddress Chao Li
2024-03-20 8:43 ` [edk2-devel] [PATCH v2 09/13] UefiCpuPkg: Add CpuMmuLib to UefiCpuPkg Chao Li
2024-03-20 8:43 ` [edk2-devel] [PATCH v2 10/13] UefiCpuPkg: Add CpuMmuInitLib.h " Chao Li
2024-03-20 8:43 ` Chao Li [this message]
2024-03-20 8:43 ` [edk2-devel] [PATCH v2 12/13] UefiCpuPkg: Add multiprocessor library for LoongArch64 Chao Li
2024-03-20 8:43 ` [edk2-devel] [PATCH v2 13/13] UefiCpuPkg: Add CpuDxe driver " Chao Li
2024-03-25 2:35 ` Ni, Ray
2024-03-22 12:39 ` [edk2-devel] [PATCH v2 00/13] Part 2 patch set to add LoongArch support into UefiCpuPkg Gerd Hoffmann
2024-03-25 2:46 ` Ni, Ray
2024-03-25 3:10 ` Chao Li
[not found] ` <17BFE34457098413.21233@groups.io>
2024-03-26 1:32 ` Chao Li
[not found] ` <17C02C7EE39BF604.20354@groups.io>
2024-03-29 1:28 ` Chao Li
2024-04-09 2:06 ` Ni, Ray
2024-04-09 4:29 ` Chao Li
2024-04-09 5:27 ` Ni, Ray
2024-04-09 6:21 ` Chao Li
[not found] ` <17C1180424B48FA9.19344@groups.io>
2024-04-03 1:21 ` Chao Li
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=20240320084345.282542-1-lichao@loongson.cn \
--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