public inbox for linux-kernel@vger.kernel.org
 help / color / mirror / Atom feed
From: Benjamin Herrenschmidt <benh@kernel.crashing.org>
To: Andrew Morton <akpm@osdl.org>
Cc: Russell King <rmk@arm.linux.org.uk>,
	Linux Kernel list <linux-kernel@vger.kernel.org>
Subject: [PATCH] 2/2 : 8250 serial: dynamic port table from arch
Date: Tue, 19 Oct 2004 16:12:29 +1000	[thread overview]
Message-ID: <1098166348.11449.43.camel@gaston> (raw)
In-Reply-To: <1098166305.15029.41.camel@gaston>

Hi !

This patch adds a ppc64 implementation of the routine providing
the list of default 8250 serial ports. It provides a empty list
by default unless the platform code fills it, and it provides
a generic function for user by Open Firmware based machines which
fills the list based on serial ports found in the OF device-tree.

It depends on the previous patch adding the generic support for
this to the 8250 driver.

Signed-off-by: Benjamin Herrenschmidt <benh@kernel.crashing.org>


Index: linux-maple/arch/ppc64/kernel/setup.c
===================================================================
--- linux-maple.orig/arch/ppc64/kernel/setup.c	2004-10-19 13:37:37.000000000 +1000
+++ linux-maple/arch/ppc64/kernel/setup.c	2004-10-19 16:13:04.264555048 +1000
@@ -30,6 +30,8 @@
 #include <linux/notifier.h>
 #include <linux/cpu.h>
 #include <linux/unistd.h>
+#include <linux/serial.h>
+#include <linux/8250.h>
 #include <asm/io.h>
 #include <asm/prom.h>
 #include <asm/processor.h>
@@ -51,6 +53,7 @@
 #include <asm/system.h>
 #include <asm/rtas.h>
 #include <asm/iommu.h>
+#include <asm/serial.h>
 
 #ifdef DEBUG
 #define DBG(fmt...) udbg_printf(fmt)
@@ -1106,6 +1109,177 @@
 __setup("decr_overclock_proc0=", set_decr_overclock_proc0 );
 __setup("decr_overclock=", set_decr_overclock );
 
+/*
+ * This function can be used by platforms to "find" legacy serial ports.
+ * It works for "serial" nodes under an "isa" node, and will try to
+ * respect the "ibm,aix-loc" property if any. It works with up to 8
+ * ports.
+ */
+
+#define MAX_LEGACY_SERIAL_PORTS	8
+static struct old_serial_port	old_serial_ports[MAX_LEGACY_SERIAL_PORTS];
+static unsigned int		old_serial_count;
+
+void __init generic_find_legacy_serial_ports(unsigned int *default_speed)
+{
+	struct device_node *np;
+	u32 *sizeprop;
+
+	struct isa_reg_property {
+		u32 space;
+		u32 address;
+		u32 size;
+	};
+	struct pci_reg_property {
+		struct pci_address addr;
+		u32 size_hi;
+		u32 size_lo;
+	};                                                                        
+
+	DBG(" -> generic_find_legacy_serial_port()\n");
+
+	naca->serialPortAddr = 0;
+	if (default_speed)
+		*default_speed = 0;
+
+	np = of_find_node_by_path("/");
+	if (!np)
+		return;
+
+	/* First fill our array */
+	for (np = NULL; (np = of_find_node_by_type(np, "serial"));) {
+		struct device_node *isa, *pci;
+		struct isa_reg_property *reg;
+		unsigned long phys_size, addr_size, io_base;
+		u32 *rangesp;
+		u32 *interrupts, *clk, *spd;
+		char *typep;
+		int index, rlen, rentsize;
+
+		/* Ok, first check if it's under an "isa" parent */
+		isa = of_get_parent(np);
+		if (!isa || strcmp(isa->name, "isa")) {
+			DBG("%s: no isa parent found\n", np->full_name);
+			continue;
+		}
+		
+		/* Now look for an "ibm,aix-loc" property that gives us ordering
+		 * if any...
+		 */
+	 	typep = (char *)get_property(np, "ibm,aix-loc", NULL);
+
+		/* Get the ISA port number */
+		reg = (struct isa_reg_property *)get_property(np, "reg", NULL);	
+		if (reg == NULL)
+			goto next_port;
+		/* We assume the interrupt number isn't translated ... */
+		interrupts = (u32 *)get_property(np, "interrupts", NULL);
+		/* get clock freq. if present */
+		clk = (u32 *)get_property(np, "clock-frequency", NULL);
+		/* get default speed if present */
+		spd = (u32 *)get_property(np, "current-speed", NULL);
+		/* Default to locate at end of array */
+		index = old_serial_count; /* end of the array by default */
+
+		/* If we have a location index, then use it */
+		if (typep && *typep == 'S') {
+			index = simple_strtol(typep+1, NULL, 0) - 1;
+			/* if index is out of range, use end of array instead */
+			if (index >= MAX_LEGACY_SERIAL_PORTS)
+				index = old_serial_count;
+			/* if our index is still out of range, that mean that
+			 * array is full, we could scan for a free slot but that
+			 * make little sense to bother, just skip the port
+			 */
+			if (index >= MAX_LEGACY_SERIAL_PORTS)
+				goto next_port;
+			if (index >= old_serial_count)
+				old_serial_count = index + 1;
+			/* Check if there is a port who already claimed our slot */
+			if (old_serial_ports[index].port != 0) {
+				/* if we still have some room, move it, else override */
+				if (old_serial_count < MAX_LEGACY_SERIAL_PORTS) {
+					DBG("Moved legacy port %d -> %d\n", index,
+					    old_serial_count);
+					old_serial_ports[old_serial_count++] =
+						old_serial_ports[index];
+				} else {
+					DBG("Replacing legacy port %d\n", index);
+				}
+			}
+		}
+		if (index >= MAX_LEGACY_SERIAL_PORTS)
+			goto next_port;
+		if (index >= old_serial_count)
+			old_serial_count = index + 1;
+
+		/* Now fill the entry */
+		memset(&old_serial_ports[index], 0, sizeof(struct old_serial_port));
+		old_serial_ports[index].uart = 0;
+		old_serial_ports[index].baud_base = clk ? (*clk / 16) : BASE_BAUD;
+		old_serial_ports[index].port = reg->address;
+		old_serial_ports[index].irq = interrupts ? interrupts[0] : 0;
+		old_serial_ports[index].flags = ASYNC_BOOT_AUTOCONF;
+
+		DBG("Added legacy port, index: %d, port: %x, irq: %d, clk: %d\n",
+		    index,
+		    old_serial_ports[index].port,
+		    old_serial_ports[index].irq,
+		    old_serial_ports[index].baud_base * 16);
+
+		/* Get phys address of IO reg for port 1 */
+		if (index != 0)
+			goto next_port;
+
+		pci = of_get_parent(isa);
+		if (!pci) {
+			DBG("%s: no pci parent found\n", np->full_name);
+			goto next_port;
+		}
+
+		rangesp = (u32 *)get_property(pci, "ranges", &rlen);
+		if (rangesp == NULL) {
+			of_node_put(pci);
+			goto next_port;
+		}
+		rlen /= 4;
+
+		/* we need the #size-cells of the PCI bridge node itself */
+		phys_size = 1;
+		sizeprop = (u32 *)get_property(pci, "#size-cells", NULL);
+		if (sizeprop != NULL)
+			phys_size = *sizeprop;
+		/* we need the parent #addr-cells */
+		addr_size = prom_n_addr_cells(pci);
+		rentsize = 3 + addr_size + phys_size;
+		io_base = 0;
+		for (;rlen >= rentsize; rlen -= rentsize,rangesp += rentsize) {
+			if (((rangesp[0] >> 24) & 0x3) != 1)
+				continue; /* not IO space */
+			io_base = rangesp[3];
+			if (addr_size == 2)
+				io_base = (io_base << 32) | rangesp[4];
+		}
+		if (io_base != 0) {
+			naca->serialPortAddr = io_base + reg->address;
+			if (default_speed && spd)
+				*default_speed = *spd;
+		}
+		of_node_put(pci);
+	next_port:
+		of_node_put(isa);
+	}
+
+	DBG(" <- generic_find_legacy_serial_port()\n");
+}
+
+struct old_serial_port *get_legacy_serial_ports(unsigned int *count)
+{
+	*count = old_serial_count;
+	return old_serial_ports;
+}
+EXPORT_SYMBOL(get_legacy_serial_ports);
+
 #ifdef CONFIG_XMON
 static int __init early_xmon(char *p)
 {
Index: linux-maple/arch/ppc64/kernel/udbg.c
===================================================================
--- linux-maple.orig/arch/ppc64/kernel/udbg.c	2004-10-19 13:37:19.000000000 +1000
+++ linux-maple/arch/ppc64/kernel/udbg.c	2004-10-19 16:13:04.274553528 +1000
@@ -49,15 +49,28 @@
 
 static volatile struct NS16550 *udbg_comport;
 
-void udbg_init_uart(void *comport)
+void udbg_init_uart(void *comport, unsigned int speed)
 {
+	u8 dll = 12;
+
+	switch(speed) {
+	case 115200:
+		dll = 1;
+		break;
+	case 57600:
+		dll = 2;
+		break;
+	case 38400:
+		dll = 3;
+		break;
+	}
 	if (comport) {
 		udbg_comport = (struct NS16550 *)comport;
 		udbg_comport->lcr = 0x00; eieio();
 		udbg_comport->ier = 0xFF; eieio();
 		udbg_comport->ier = 0x00; eieio();
 		udbg_comport->lcr = 0x80; eieio();	/* Access baud rate */
-		udbg_comport->dll = 12;   eieio();	/* 1 = 115200,  2 = 57600, 3 = 38400, 12 = 9600 baud */
+		udbg_comport->dll = dll;   eieio();	/* 1 = 115200,  2 = 57600, 3 = 38400, 12 = 9600 baud */
 		udbg_comport->dlm = 0;    eieio();	/* dll >> 8 which should be zero for fast rates; */
 		udbg_comport->lcr = 0x03; eieio();	/* 8 data, 1 stop, no parity */
 		udbg_comport->mcr = 0x03; eieio();	/* RTS/DTR */
Index: linux-maple/include/asm-ppc64/serial.h
===================================================================
--- linux-maple.orig/include/asm-ppc64/serial.h	2004-10-19 13:37:55.000000000 +1000
+++ linux-maple/include/asm-ppc64/serial.h	2004-10-19 16:13:04.283552160 +1000
@@ -18,113 +18,13 @@
  * as published by the Free Software Foundation; either version
  * 2 of the License, or (at your option) any later version.
  */
-#define BASE_BAUD ( 1843200 / 16 )
-
-#ifdef CONFIG_SERIAL_MANY_PORTS
-#define RS_TABLE_SIZE  64
-#else
-#define RS_TABLE_SIZE  4
-#endif
-
-/* Standard COM flags (except for COM4, because of the 8514 problem) */
-#ifdef CONFIG_SERIAL_DETECT_IRQ
-#define STD_COM_FLAGS (ASYNC_BOOT_AUTOCONF | ASYNC_AUTO_IRQ)
-#else
-#define STD_COM_FLAGS ASYNC_BOOT_AUTOCONF
-#endif
-
-#ifdef CONFIG_SERIAL_MANY_PORTS
-#define FOURPORT_FLAGS ASYNC_FOURPORT
-#define ACCENT_FLAGS 0
-#define BOCA_FLAGS 0
-#define HUB6_FLAGS 0
-#endif
-	
-/*
- * The following define the access methods for the HUB6 card. All
- * access is through two ports for all 24 possible chips. The card is
- * selected through the high 2 bits, the port on that card with the
- * "middle" 3 bits, and the register on that port with the bottom
- * 3 bits.
- *
- * While the access port and interrupt is configurable, the default
- * port locations are 0x302 for the port control register, and 0x303
- * for the data read/write register. Normally, the interrupt is at irq3
- * but can be anything from 3 to 7 inclusive. Note that using 3 will
- * require disabling com2.
- */
-
-#define C_P(card,port) (((card)<<6|(port)<<3) + 1)
 
-#define STD_SERIAL_PORT_DEFNS			\
-	/* UART CLK   PORT IRQ     FLAGS        */			\
-	{ 0, BASE_BAUD, 0x3F8, 4, STD_COM_FLAGS },	/* ttyS0 */	\
-	{ 0, BASE_BAUD, 0x2F8, 3, STD_COM_FLAGS },	/* ttyS1 */	\
-	{ 0, BASE_BAUD, 0x890, 0xf, STD_COM_FLAGS },	/* ttyS2 */	\
-	{ 0, BASE_BAUD, 0x898, 0xe, STD_COM_FLAGS },	/* ttyS3 */
-
-
-#ifdef CONFIG_SERIAL_MANY_PORTS
-#define EXTRA_SERIAL_PORT_DEFNS			\
-	{ 0, BASE_BAUD, 0x1A0, 9, FOURPORT_FLAGS }, 	/* ttyS4 */	\
-	{ 0, BASE_BAUD, 0x1A8, 9, FOURPORT_FLAGS },	/* ttyS5 */	\
-	{ 0, BASE_BAUD, 0x1B0, 9, FOURPORT_FLAGS },	/* ttyS6 */	\
-	{ 0, BASE_BAUD, 0x1B8, 9, FOURPORT_FLAGS },	/* ttyS7 */	\
-	{ 0, BASE_BAUD, 0x2A0, 5, FOURPORT_FLAGS },	/* ttyS8 */	\
-	{ 0, BASE_BAUD, 0x2A8, 5, FOURPORT_FLAGS },	/* ttyS9 */	\
-	{ 0, BASE_BAUD, 0x2B0, 5, FOURPORT_FLAGS },	/* ttyS10 */	\
-	{ 0, BASE_BAUD, 0x2B8, 5, FOURPORT_FLAGS },	/* ttyS11 */	\
-	{ 0, BASE_BAUD, 0x330, 4, ACCENT_FLAGS },	/* ttyS12 */	\
-	{ 0, BASE_BAUD, 0x338, 4, ACCENT_FLAGS },	/* ttyS13 */	\
-	{ 0, BASE_BAUD, 0x000, 0, 0 },	/* ttyS14 (spare) */		\
-	{ 0, BASE_BAUD, 0x000, 0, 0 },	/* ttyS15 (spare) */		\
-	{ 0, BASE_BAUD, 0x100, 12, BOCA_FLAGS },	/* ttyS16 */	\
-	{ 0, BASE_BAUD, 0x108, 12, BOCA_FLAGS },	/* ttyS17 */	\
-	{ 0, BASE_BAUD, 0x110, 12, BOCA_FLAGS },	/* ttyS18 */	\
-	{ 0, BASE_BAUD, 0x118, 12, BOCA_FLAGS },	/* ttyS19 */	\
-	{ 0, BASE_BAUD, 0x120, 12, BOCA_FLAGS },	/* ttyS20 */	\
-	{ 0, BASE_BAUD, 0x128, 12, BOCA_FLAGS },	/* ttyS21 */	\
-	{ 0, BASE_BAUD, 0x130, 12, BOCA_FLAGS },	/* ttyS22 */	\
-	{ 0, BASE_BAUD, 0x138, 12, BOCA_FLAGS },	/* ttyS23 */	\
-	{ 0, BASE_BAUD, 0x140, 12, BOCA_FLAGS },	/* ttyS24 */	\
-	{ 0, BASE_BAUD, 0x148, 12, BOCA_FLAGS },	/* ttyS25 */	\
-	{ 0, BASE_BAUD, 0x150, 12, BOCA_FLAGS },	/* ttyS26 */	\
-	{ 0, BASE_BAUD, 0x158, 12, BOCA_FLAGS },	/* ttyS27 */	\
-	{ 0, BASE_BAUD, 0x160, 12, BOCA_FLAGS },	/* ttyS28 */	\
-	{ 0, BASE_BAUD, 0x168, 12, BOCA_FLAGS },	/* ttyS29 */	\
-	{ 0, BASE_BAUD, 0x170, 12, BOCA_FLAGS },	/* ttyS30 */	\
-	{ 0, BASE_BAUD, 0x178, 12, BOCA_FLAGS },	/* ttyS31 */
-#else
-#define EXTRA_SERIAL_PORT_DEFNS
-#endif
-
-/* You can have up to four HUB6's in the system, but I've only
- * included two cards here for a total of twelve ports.
- */
-#if (defined(CONFIG_HUB6) && defined(CONFIG_SERIAL_MANY_PORTS))
-#define HUB6_SERIAL_PORT_DFNS		\
-	{ 0, BASE_BAUD, 0x302, 3, HUB6_FLAGS, C_P(0,0) },  /* ttyS32 */	\
-	{ 0, BASE_BAUD, 0x302, 3, HUB6_FLAGS, C_P(0,1) },  /* ttyS33 */	\
-	{ 0, BASE_BAUD, 0x302, 3, HUB6_FLAGS, C_P(0,2) },  /* ttyS34 */	\
-	{ 0, BASE_BAUD, 0x302, 3, HUB6_FLAGS, C_P(0,3) },  /* ttyS35 */	\
-	{ 0, BASE_BAUD, 0x302, 3, HUB6_FLAGS, C_P(0,4) },  /* ttyS36 */	\
-	{ 0, BASE_BAUD, 0x302, 3, HUB6_FLAGS, C_P(0,5) },  /* ttyS37 */	\
-	{ 0, BASE_BAUD, 0x302, 3, HUB6_FLAGS, C_P(1,0) },  /* ttyS38 */	\
-	{ 0, BASE_BAUD, 0x302, 3, HUB6_FLAGS, C_P(1,1) },  /* ttyS39 */	\
-	{ 0, BASE_BAUD, 0x302, 3, HUB6_FLAGS, C_P(1,2) },  /* ttyS40 */	\
-	{ 0, BASE_BAUD, 0x302, 3, HUB6_FLAGS, C_P(1,3) },  /* ttyS41 */	\
-	{ 0, BASE_BAUD, 0x302, 3, HUB6_FLAGS, C_P(1,4) },  /* ttyS42 */	\
-	{ 0, BASE_BAUD, 0x302, 3, HUB6_FLAGS, C_P(1,5) },  /* ttyS43 */
-#else
-#define HUB6_SERIAL_PORT_DFNS
-#endif
-
-#define MCA_SERIAL_PORT_DFNS
+/* Default baud base if not found in device-tree */
+#define BASE_BAUD ( 1843200 / 16 )
 
-#define SERIAL_PORT_DFNS		\
-	STD_SERIAL_PORT_DEFNS		\
-	EXTRA_SERIAL_PORT_DEFNS		\
-	HUB6_SERIAL_PORT_DFNS		\
-	MCA_SERIAL_PORT_DFNS
+#define ARCH_HAS_GET_LEGACY_SERIAL_PORTS
+struct old_serial_port;
+extern struct old_serial_port *get_legacy_serial_ports(unsigned int *count);
+#define UART_NR	(8 + CONFIG_SERIAL_8250_NR_UARTS)
 
 #endif /* _PPC64_SERIAL_H */
Index: linux-maple/arch/ppc64/kernel/pSeries_setup.c
===================================================================
--- linux-maple.orig/arch/ppc64/kernel/pSeries_setup.c	2004-10-19 13:37:21.000000000 +1000
+++ linux-maple/arch/ppc64/kernel/pSeries_setup.c	2004-10-19 16:13:04.304548968 +1000
@@ -82,6 +82,8 @@
 extern int  pSeries_set_rtc_time(struct rtc_time *rtc_time);
 extern void find_udbg_vterm(void);
 extern void SystemReset_FWNMI(void), MachineCheck_FWNMI(void);	/* from head.S */
+extern void generic_find_legacy_serial_ports(unsigned int *default_speed);
+
 int fwnmi_active;  /* TRUE if an FWNMI handler is present */
 
 unsigned long  virtPython0Facilities = 0;  // python0 facility area (memory mapped io) (64-bit format) VIRTUAL address.
@@ -189,75 +191,6 @@
 arch_initcall(pSeries_init_panel);
 
 
-
-void __init pSeries_find_serial_port(void)
-{
-	struct device_node *np;
-	unsigned long encode_phys_size = 32;
-	u32 *sizeprop;
-
-	struct isa_reg_property {
-		u32 space;
-		u32 address;
-		u32 size;
-	};
-	struct pci_reg_property {
-		struct pci_address addr;
-		u32 size_hi;
-		u32 size_lo;
-	};                                                                        
-
-	DBG(" -> pSeries_find_serial_port()\n");
-
-	naca->serialPortAddr = 0;
-
-	np = of_find_node_by_path("/");
-	if (!np)
-		return;
-	sizeprop = (u32 *)get_property(np, "#size-cells", NULL);
-	if (sizeprop != NULL)
-		encode_phys_size = (*sizeprop) << 5;
-	
-	for (np = NULL; (np = of_find_node_by_type(np, "serial"));) {
-		struct device_node *isa, *pci;
-		struct isa_reg_property *reg;
-		union pci_range *rangesp;
-		char *typep;
-
-	 	typep = (char *)get_property(np, "ibm,aix-loc", NULL);
-		if ((typep == NULL) || (typep && strcmp(typep, "S1")))
-			continue;
-
-		reg = (struct isa_reg_property *)get_property(np, "reg", NULL);	
-
-		isa = of_get_parent(np);
-		if (!isa) {
-			DBG("no isa parent found\n");
-			break;
-		}
-		pci = of_get_parent(isa);
-		if (!pci) {
-			DBG("no pci parent found\n");
-			break;
-		}
-
-		rangesp = (union pci_range *)get_property(pci, "ranges", NULL);
-
-		if ( encode_phys_size == 32 )
-			naca->serialPortAddr = rangesp->pci32.phys+reg->address;
-		else {
-			naca->serialPortAddr =
-				((((unsigned long)rangesp->pci64.phys_hi) << 32)
-				|
-			(rangesp->pci64.phys_lo)) + reg->address;
-		}
-		break;
-	}
-
-	DBG(" <- pSeries_find_serial_port()\n");
-}
-
-
 /* Build up the firmware_features bitmask field
  * using contents of device-tree/ibm,hypertas-functions.
  * Ultimately this functionality may be moved into prom.c prom_init().
@@ -337,6 +270,7 @@
 {
 	void *comport;
 	int iommu_off = 0;
+	unsigned int default_speed;
 
 	DBG(" -> pSeries_init_early()\n");
 
@@ -350,14 +284,14 @@
 			     get_property(of_chosen, "linux,iommu-off", NULL));
 	}
 
-	pSeries_find_serial_port();
+	generic_find_legacy_serial_ports(&default_speed);
 
 	if (systemcfg->platform & PLATFORM_LPAR)
 		find_udbg_vterm();
 	else if (naca->serialPortAddr) {
 		/* Map the uart for udbg. */
 		comport = (void *)__ioremap(naca->serialPortAddr, 16, _PAGE_NO_CACHE);
-		udbg_init_uart(comport);
+		udbg_init_uart(comport, default_speed);
 
 		ppc_md.udbg_putc = udbg_putc;
 		ppc_md.udbg_getc = udbg_getc;
Index: linux-maple/include/asm-ppc64/udbg.h
===================================================================
--- linux-maple.orig/include/asm-ppc64/udbg.h	2004-10-19 13:37:22.000000000 +1000
+++ linux-maple/include/asm-ppc64/udbg.h	2004-10-19 16:13:04.305548816 +1000
@@ -10,7 +10,7 @@
  * 2 of the License, or (at your option) any later version.
  */
 
-void udbg_init_uart(void *comport);
+void udbg_init_uart(void *comport, unsigned int speed);
 void udbg_putc(unsigned char c);
 unsigned char udbg_getc(void);
 int udbg_getc_poll(void);



      reply	other threads:[~2004-10-19  6:18 UTC|newest]

Thread overview: 2+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2004-10-19  6:11 [PATCH] 1/2 : 8250 serial: dynamic port table from arch Benjamin Herrenschmidt
2004-10-19  6:12 ` Benjamin Herrenschmidt [this message]

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=1098166348.11449.43.camel@gaston \
    --to=benh@kernel.crashing.org \
    --cc=akpm@osdl.org \
    --cc=linux-kernel@vger.kernel.org \
    --cc=rmk@arm.linux.org.uk \
    /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