All of lore.kernel.org
 help / color / mirror / Atom feed
From: Thomas Leonard <talex5@gmail.com>
To: xen-devel@lists.xenproject.org
Cc: Thomas Leonard <talex5@gmail.com>,
	Dave.Scott@eu.citrix.com, anil@recoil.org,
	stefano.stabellini@eu.citrix.com, samuel.thibault@ens-lyon.org
Subject: [PATCH ARM v7 05/13] mini-os: arm: boot code
Date: Fri,  8 Aug 2014 16:47:34 +0100	[thread overview]
Message-ID: <1407512862-9373-6-git-send-email-talex5@gmail.com> (raw)
In-Reply-To: <1407512862-9373-1-git-send-email-talex5@gmail.com>

Based on an initial patch by Karim Raslan.

Signed-off-by: Karim Allah Ahmed <karim.allah.ahmed@gmail.com>
Signed-off-by: Thomas Leonard <talex5@gmail.com>

---

Changes since v6:

- Fixed printk format types.

Addressed Ian Campbell's comments:
- ARMv7 A8.8.247 says udf should not be used conditionally, so use
  a branch instead.
- Enable caching earlier (required setting the cacheability for the
  translation table walks).
- Added some extra dsb instructions to protect against reordering.

Addressed Julien Grall's comments:
- Note that TTBCR is zero (using short-descriptors, TTBR0 only).
- Note about relying on 0x8000 load offset.
---
 extras/mini-os/arch/arm/arm32.S          | 211 +++++++++++++++++++++++++++++++
 extras/mini-os/arch/arm/hypercalls32.S   |  64 ++++++++++
 extras/mini-os/arch/arm/minios-arm32.lds |  83 ++++++++++++
 extras/mini-os/arch/arm/setup.c          | 119 +++++++++++++++++
 4 files changed, 477 insertions(+)
 create mode 100644 extras/mini-os/arch/arm/arm32.S
 create mode 100644 extras/mini-os/arch/arm/hypercalls32.S
 create mode 100755 extras/mini-os/arch/arm/minios-arm32.lds
 create mode 100644 extras/mini-os/arch/arm/setup.c

diff --git a/extras/mini-os/arch/arm/arm32.S b/extras/mini-os/arch/arm/arm32.S
new file mode 100644
index 0000000..d2e6932
--- /dev/null
+++ b/extras/mini-os/arch/arm/arm32.S
@@ -0,0 +1,211 @@
+@ Offset of the kernel within the RAM. This is a Linux/zImage convention which we
+@ rely on for now.
+#define ZIMAGE_KERNEL_OFFSET 0x8000
+
+.section .text
+
+.globl _start
+_start:
+	@ zImage header
+.rept   8
+        mov     r0, r0
+.endr
+        b       reset
+        .word   0x016f2818      @ Magic numbers to help the loader
+        .word   0		@ zImage start address (0 = relocatable)
+        .word   _edata - _start @ zImage end address (excludes bss section)
+	@ end of zImage header
+
+@ Called at boot time. Sets up MMU, exception vectors and stack, and then calls C arch_init() function.
+@ => r2 -> DTB
+@ <= never returns
+@ Note: this boot code needs to be within the first (1MB - ZIMAGE_KERNEL_OFFSET) of _start.
+reset:
+	@ Problem: the C code wants to be at a known address (_start), but Xen might
+	@ load us anywhere. We initialise the MMU (mapping virtual to physical @ addresses)
+	@ so everything ends up where the code expects it to be.
+	@
+	@ We calculate the offet between where the linker thought _start would be and where
+	@ it actually is and initialise the page tables to have that offset for every page.
+	@
+	@ When we turn on the MMU, we're still executing at the old address. We don't want
+	@ the code to disappear from under us. So we have to do the mapping in stages:
+	@
+	@ 1. set up a mapping to our current page from both its current and desired addresses
+	@ 2. enable the MMU
+	@ 3. jump to the new address
+	@ 4. remap all the other pages with the calculated offset
+
+	adr	r1, _start		@ r1 = physical address of _start
+	ldr	r3, =_start		@ r3 = (desired) virtual address of _start
+	sub 	r9, r1, r3		@ r9 = (physical - virtual) offset
+
+	ldr	r7, =_page_dir		@ r7 = (desired) virtual addr of translation table
+	add	r1, r7, r9		@ r1 = physical addr of translation table
+
+	@ Tell the system where our page table is located.
+	@ This is the 16 KB top-level translation table, in which
+	@ each word maps one 1MB virtual section to a physical section.
+	@ Note: We leave TTBCR as 0, meaning that only TTBR0 is used and
+	@ we use the short-descriptor format (32-bit physical addresses).
+	orr	r0, r1, #0b0001011	@ Sharable, Inner/Outer Write-Back Write-Allocate Cacheable
+	mcr	p15, 0, r0, c2, c0, 0	@ set TTBR0
+
+	@ Set access permission for domains.
+	@ Domains are deprecated, but we have to configure them anyway.
+	@ We mark every page as being domain 0 and set domain 0 to "client mode"
+	@ (client mode = use access flags in page table).
+	mov	r0, #1			@ 1 = client
+	mcr	p15, 0, r0, c3, c0, 0	@ DACR
+
+	@ Template (flags) for a 1 MB page-table entry.
+	@ TEX[2:0] C B = 001 1 1 (outer and inner write-back, write-allocate)
+	ldr	r8, =(0x2 +  		/* Section entry */ \
+		      0xc +  		/* C B */ \
+		      (3 << 10) + 	/* Read/write */ \
+		      (1 << 12) +	/* TEX */ \
+		      (1 << 16) +	/* Sharable */ \
+		      (1<<19))		/* Non-secure */
+	@ r8 = template page table entry
+
+	@ Add an entry for the current physical section, at the old and new
+	@ addresses. It's OK if they're the same.
+	mov	r0, pc, lsr#20
+	mov	r0, r0, lsl#20		@ r0 = physical address of this code's section start
+	orr	r3, r0, r8		@ r3 = table entry for this section
+	ldr	r4, =_start		@ r4 = desired virtual address of this section
+	str	r3, [r1, r4, lsr#18] 	@ map desired virtual section to this code
+	str	r3, [r1, r0, lsr#18]	@ map current section to this code too
+
+	@ Invalidate TLB
+	dsb				@ Caching is off, but must still prevent reordering
+	mcr	p15, 0, r1, c8, c7, 0	@ TLBIALL
+
+	@ Enable MMU / SCTLR
+	mrc	p15, 0, r1, c1, c0, 0	@ SCTLR
+	orr	r1, r1, #3 << 11	@ enable icache, branch prediction
+	orr	r1, r1, #4 + 1		@ enable dcache, MMU
+	mcr	p15, 0, r1, c1, c0, 0	@ SCTLR
+	isb
+
+	ldr	r1, =stage2		@ Virtual address of stage2
+	bx	r1
+
+@ Called once the MMU is enabled. The boot code and the page table are mapped,
+@ but nothing else is yet.
+@
+@ => r2 -> dtb (physical)
+@    r7 = virtual address of page table
+@    r8 = section entry template (flags)
+@    r9 = desired physical - virtual offset
+@    pc -> somewhere in newly-mapped virtual code section
+stage2:
+	@ Invalidate TLB
+	mcr	p15, 0, r1, c8, c7, 0	@ TLBIALL
+	isb
+
+	@ The new mapping has now taken effect:
+	@ r7 -> page_dir
+
+	@ Fill in the whole top-level translation table (at page_dir).
+	@ Populate the whole pagedir with 1MB section descriptors.
+
+	mov	r1, r7			@ r1 -> first section entry
+	add	r3, r1, #4*4*1024	@ limit (4 GB address space, 4 byte entries)
+	orr	r0, r8, r9		@ r0 = entry mapping section zero to start of physical RAM
+1:
+	str	r0, [r1],#4		@ write the section entry
+	add	r0, r0, #1 << 20 	@ next physical page (wraps)
+	cmp	r1, r3
+	bne	1b
+
+	@ Invalidate TLB
+	dsb
+	mcr	p15, 0, r1, c8, c7, 0	@ TLBIALL
+	isb
+
+	@ Set VBAR -> exception_vector_table
+	@ SCTLR.V = 0
+	adr	r0, exception_vector_table
+	mcr	p15, 0, r0, c12, c0, 0
+
+	@ Enable hardware floating point:
+	@ 1. Access to CP10 and CP11 must be enabled in the Coprocessor Access
+	@    Control Register (CP15.CACR):
+	mrc	p15, 0, r1, c1, c0, 2		@ CACR
+	orr	r1, r1, #(3 << 20) + (3 << 22)	@ full access for CP10 & CP11
+	mcr	p15, 0, r1, c1, c0, 2
+	@ 2. The EN bit in the FPEXC register must be set:
+	vmrs	r0, FPEXC
+	orr	r0, r0, #1<<30		@ EN (enable)
+	vmsr	FPEXC, r0
+
+	@ Initialise 16 KB stack
+	ldr	sp, =_boot_stack_end
+
+	sub	r0, r2, r9		@ r0 -> device tree (virtual address)
+	mov	r1, r9			@ r1 = physical_address_offset
+
+	b	arch_init
+
+.pushsection .bss
+@ Note: calling arch_init zeroes out this region.
+.align 12
+.globl shared_info_page
+shared_info_page:
+	.fill (1024), 4, 0x0
+
+.align 3
+.globl irqstack
+.globl irqstack_end
+irqstack:
+	.fill (1024), 4, 0x0
+irqstack_end:
+
+.popsection
+
+@ exception base address
+.align 5
+.globl exception_vector_table
+@ Note: remember to call CLREX if returning from an exception:
+@ "The architecture enables the local monitor to treat any exclusive store as
+@  matching a previous LDREX address. For this reason, use of the CLREX
+@  instruction to clear an existing tag is required on context switches."
+@ -- ARM Cortex-A Series Programmer’s Guide (Version: 4.0)
+exception_vector_table:
+	b	. @ reset
+	b	. @ undefined instruction
+	b	. @ supervisor call
+	b	. @ prefetch call
+	b	. @ prefetch abort
+	b	. @ data abort
+	b	irq_handler @ irq
+	.word 0xe7f000f0    @ abort on FIQ
+
+@ Call fault_undefined_instruction in "Undefined mode"
+bug:
+	.word	0xe7f000f0    	@ und/udf - a "Permanently Undefined" instruction
+
+irq_handler:
+	ldr	sp, =irqstack_end
+	push	{r0 - r12, r14}
+
+	ldr	r0, IRQ_handler
+	cmp	r0, #0
+	beq	bug
+	blx	r0		@ call handler
+
+	@ Return from IRQ
+	pop	{r0 - r12, r14}
+	clrex
+	subs	pc, lr, #4
+
+.globl IRQ_handler
+IRQ_handler:
+	.long	0x0
+
+@ This is called if you try to divide by zero. For now, we make a supervisor call,
+@ which will make us halt.
+.globl raise
+raise:
+	svc	0
diff --git a/extras/mini-os/arch/arm/hypercalls32.S b/extras/mini-os/arch/arm/hypercalls32.S
new file mode 100644
index 0000000..af8e175
--- /dev/null
+++ b/extras/mini-os/arch/arm/hypercalls32.S
@@ -0,0 +1,64 @@
+/******************************************************************************
+ * hypercall.S
+ *
+ * Xen hypercall wrappers
+ *
+ * Stefano Stabellini <stefano.stabellini@eu.citrix.com>, Citrix, 2012
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License version 2
+ * as published by the Free Software Foundation; or, when distributed
+ * separately from the Linux kernel or incorporated into other
+ * software packages, subject to the following license:
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this source file (the "Software"), to deal in the Software without
+ * restriction, including without limitation the rights to use, copy, modify,
+ * merge, publish, distribute, sublicense, and/or sell copies of the Software,
+ * and to permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ * IN THE SOFTWARE.
+ */
+
+#include <xen/xen.h>
+
+#define __HVC(imm16) .long ((0xE1400070 | (((imm16) & 0xFFF0) << 4) | ((imm16) & 0x000F)) & 0xFFFFFFFF)
+
+#define XEN_IMM 0xEA1
+
+#define HYPERCALL_SIMPLE(hypercall)		\
+.globl HYPERVISOR_##hypercall;			\
+.align 4,0x90;					\
+HYPERVISOR_##hypercall:				\
+        mov r12, #__HYPERVISOR_##hypercall;	\
+        __HVC(XEN_IMM);				\
+        mov pc, lr;
+
+#define _hypercall0 HYPERCALL_SIMPLE
+#define _hypercall1 HYPERCALL_SIMPLE
+#define _hypercall2 HYPERCALL_SIMPLE
+#define _hypercall3 HYPERCALL_SIMPLE
+#define _hypercall4 HYPERCALL_SIMPLE
+
+_hypercall2(sched_op);
+_hypercall2(memory_op);
+_hypercall2(event_channel_op);
+_hypercall2(xen_version);
+_hypercall3(console_io);
+_hypercall1(physdev_op);
+_hypercall3(grant_table_op);
+_hypercall3(vcpu_op);
+_hypercall1(sysctl);
+_hypercall1(domctl);
+_hypercall2(hvm_op);
+_hypercall1(xsm_op);
diff --git a/extras/mini-os/arch/arm/minios-arm32.lds b/extras/mini-os/arch/arm/minios-arm32.lds
new file mode 100755
index 0000000..9627162
--- /dev/null
+++ b/extras/mini-os/arch/arm/minios-arm32.lds
@@ -0,0 +1,83 @@
+OUTPUT_ARCH(arm)
+ENTRY(_start)
+SECTIONS
+{
+  /* Note: we currently assume that Xen will load the kernel image
+   * at start-of-RAM + 0x8000. We use this initial 32 KB for the stack
+   * and translation tables.
+   */
+  _boot_stack 	 = 0x400000;	/* 16 KB boot stack */
+  _boot_stack_end = 0x404000;
+  _page_dir      = 0x404000;	/* 16 KB translation table */
+  .		 = 0x408000;
+  _text = .;			/* Text and read-only data */
+  .text : {
+	*(.text)
+	*(.gnu.warning)
+	} = 0x9090
+
+  _etext = .;			/* End of text section */
+
+  .rodata : { *(.rodata) *(.rodata.*) }
+  . = ALIGN(4096);
+  _erodata = .;
+
+  /* newlib initialization functions */
+  . = ALIGN(32 / 8);
+  PROVIDE (__preinit_array_start = .);
+  .preinit_array     : { *(.preinit_array) }
+  PROVIDE (__preinit_array_end = .);
+  PROVIDE (__init_array_start = .);
+  .init_array     : { *(.init_array) }
+  PROVIDE (__init_array_end = .);
+  PROVIDE (__fini_array_start = .);
+  .fini_array     : { *(.fini_array) }
+  PROVIDE (__fini_array_end = .);
+
+  .ctors : {
+        __CTOR_LIST__ = .;
+        *(.ctors)
+	CONSTRUCTORS
+        LONG(0)
+        __CTOR_END__ = .;
+        }
+
+  .dtors : {
+        __DTOR_LIST__ = .;
+        *(.dtors)
+        LONG(0)
+        __DTOR_END__ = .;
+        }
+
+  .data : {			/* Data */
+	*(.data)
+	}
+
+  /* Note: linker will insert any extra sections here, just before .bss */
+
+  .bss : {
+	_edata = .;			/* End of data included in image */
+	/* Nothing after here is included in the zImage's size */
+
+	__bss_start = .;
+	*(.bss)
+        *(.app.bss)
+	}
+  _end = . ;
+
+  /* Sections to be discarded */
+  /DISCARD/ : {
+	*(.text.exit)
+	*(.data.exit)
+	*(.exitcall.exit)
+	}
+
+  /* Stabs debugging sections.  */
+  .stab 0 : { *(.stab) }
+  .stabstr 0 : { *(.stabstr) }
+  .stab.excl 0 : { *(.stab.excl) }
+  .stab.exclstr 0 : { *(.stab.exclstr) }
+  .stab.index 0 : { *(.stab.index) }
+  .stab.indexstr 0 : { *(.stab.indexstr) }
+  .comment 0 : { *(.comment) }
+}
diff --git a/extras/mini-os/arch/arm/setup.c b/extras/mini-os/arch/arm/setup.c
new file mode 100644
index 0000000..06afe46
--- /dev/null
+++ b/extras/mini-os/arch/arm/setup.c
@@ -0,0 +1,119 @@
+#include <mini-os/os.h>
+#include <mini-os/kernel.h>
+#include <mini-os/gic.h>
+#include <mini-os/console.h>
+#include <xen/xen.h>
+#include <xen/memory.h>
+#include <xen/hvm/params.h>
+#include <arch_mm.h>
+#include <libfdt.h>
+
+/*
+ * This structure contains start-of-day info, such as pagetable base pointer,
+ * address of the shared_info structure, and things like that.
+ * On x86, the hypervisor passes it to us. On ARM, we fill it in ourselves.
+ */
+union start_info_union start_info_union;
+
+/*
+ * Shared page for communicating with the hypervisor.
+ * Events flags go here, for example.
+ */
+shared_info_t *HYPERVISOR_shared_info;
+
+extern char shared_info_page[PAGE_SIZE];
+
+void *device_tree;
+
+static int hvm_get_parameter(int idx, uint64_t *value)
+{
+    struct xen_hvm_param xhv;
+    int ret;
+
+    xhv.domid = DOMID_SELF;
+    xhv.index = idx;
+    ret = HYPERVISOR_hvm_op(HVMOP_get_param, &xhv);
+    if (ret < 0) {
+        BUG();
+    }
+    *value = xhv.value;
+    return ret;
+}
+
+static void get_console(void)
+{
+    uint64_t v = -1;
+
+    hvm_get_parameter(HVM_PARAM_CONSOLE_EVTCHN, &v);
+    start_info.console.domU.evtchn = v;
+
+    hvm_get_parameter(HVM_PARAM_CONSOLE_PFN, &v);
+    start_info.console.domU.mfn = v;
+
+    printk("Console is on port %d\n", start_info.console.domU.evtchn);
+    printk("Console ring is at mfn %lx\n", (unsigned long) start_info.console.domU.mfn);
+}
+
+void get_xenbus(void)
+{
+    uint64_t value;
+
+    if (hvm_get_parameter(HVM_PARAM_STORE_EVTCHN, &value))
+        BUG();
+
+    start_info.store_evtchn = (int)value;
+
+    if(hvm_get_parameter(HVM_PARAM_STORE_PFN, &value))
+        BUG();
+    start_info.store_mfn = (unsigned long)value;
+}
+
+/*
+ * INITIAL C ENTRY POINT.
+ */
+void arch_init(void *dtb_pointer, uint32_t physical_offset)
+{
+    struct xen_add_to_physmap xatp;
+    int r;
+
+    memset(&__bss_start, 0, &_end - &__bss_start);
+
+    physical_address_offset = physical_offset;
+
+    xprintk("Virtual -> physical offset = %x\n", physical_address_offset);
+
+    xprintk("Checking DTB at %p...\n", dtb_pointer);
+
+    if ((r = fdt_check_header(dtb_pointer))) {
+        xprintk("Invalid DTB from Xen: %s\n", fdt_strerror(r));
+        BUG();
+    }
+    device_tree = dtb_pointer;
+
+    /* Map shared_info page */
+    xatp.domid = DOMID_SELF;
+    xatp.idx = 0;
+    xatp.space = XENMAPSPACE_shared_info;
+    xatp.gpfn = virt_to_pfn(shared_info_page);
+    if (HYPERVISOR_memory_op(XENMEM_add_to_physmap, &xatp) != 0)
+        BUG();
+    HYPERVISOR_shared_info = (struct shared_info *)shared_info_page;
+
+    /* Fill in start_info */
+    get_console();
+    get_xenbus();
+
+    gic_init();
+
+    start_kernel();
+}
+
+void
+arch_fini(void)
+{
+}
+
+void
+arch_do_exit(void)
+{
+}
-- 
2.0.3


_______________________________________________
Xen-devel mailing list
Xen-devel@lists.xen.org
http://lists.xen.org/xen-devel

  parent reply	other threads:[~2014-08-08 15:47 UTC|newest]

Thread overview: 31+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2014-08-08 15:47 [PATCH ARM v7 00/13] mini-os: initial ARM support Thomas Leonard
2014-08-08 15:47 ` [PATCH ARM v7 01/13] mini-os: don't include lib.h from mm.h Thomas Leonard
2014-08-08 15:47 ` [PATCH ARM v7 02/13] mini-os: added HYPERVISOR_xsm_op Thomas Leonard
2014-08-08 15:47 ` [PATCH ARM v7 03/13] mini-os: move poor rand function to test.c Thomas Leonard
2014-08-08 16:37   ` Samuel Thibault
2014-08-11  7:59     ` Thomas Leonard
2014-08-11  9:29       ` Samuel Thibault
2014-08-28 15:07         ` Thomas Leonard
2014-08-08 15:47 ` [PATCH ARM v7 04/13] mini-os: arm: add header files Thomas Leonard
2014-08-08 15:47 ` Thomas Leonard [this message]
2014-08-08 15:47 ` [PATCH ARM v7 06/13] mini-os: arm: memory management Thomas Leonard
2014-08-08 15:47 ` [PATCH ARM v7 07/13] mini-os: arm: scheduling Thomas Leonard
2014-08-08 15:47 ` [PATCH ARM v7 08/13] mini-os: arm: events Thomas Leonard
2014-09-08 11:52   ` Ian Campbell
2014-08-08 15:47 ` [PATCH ARM v7 09/13] mini-os: arm: time Thomas Leonard
2014-09-08 10:59   ` Ian Campbell
2014-09-08 16:08     ` Thomas Leonard
2014-09-22 10:35       ` Dave Scott
2014-09-23 16:53         ` Stefano Stabellini
2014-09-26 10:01         ` Thomas Leonard
2014-10-01 11:10         ` Thomas Leonard
2014-10-01 11:10         ` Thomas Leonard
2014-10-01 11:31           ` Ian Campbell
2014-08-08 15:47 ` [PATCH ARM v7 10/13] mini-os: arm: interrupt controller Thomas Leonard
2014-09-08 11:01   ` Ian Campbell
2014-08-08 15:47 ` [PATCH ARM v7 11/13] mini-os: arm: build system Thomas Leonard
2014-09-08 11:05   ` Ian Campbell
2014-08-08 15:47 ` [PATCH ARM v7 12/13] mini-os: arm: show registers, stack and exception vector on fault Thomas Leonard
2014-09-08 11:06   ` Ian Campbell
2014-08-08 15:47 ` [PATCH ARM v7 13/13] mini-os: fixed compiling with debug=n Thomas Leonard
2014-09-08 11:08   ` Ian Campbell

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-all 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=1407512862-9373-6-git-send-email-talex5@gmail.com \
    --to=talex5@gmail.com \
    --cc=Dave.Scott@eu.citrix.com \
    --cc=anil@recoil.org \
    --cc=samuel.thibault@ens-lyon.org \
    --cc=stefano.stabellini@eu.citrix.com \
    --cc=xen-devel@lists.xenproject.org \
    /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 an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.