netdev.vger.kernel.org archive mirror
 help / color / mirror / Atom feed
From: Weifeng Voon <weifeng.voon@intel.com>
To: "David S. Miller" <davem@davemloft.net>
Cc: netdev@vger.kernel.org, linux-kernel@vger.kernel.org,
	Ong Boon Leong <boon.leong.ong@intel.com>,
	Kweh Hock Leong <hock.leong.kweh@intel.com>,
	Weifeng Voon <weifeng.voon@intel.com>
Subject: [PATCH 4/7] net: stmmac: introducing support for DWC xPCS logics
Date: Thu, 25 Apr 2019 01:17:18 +0800	[thread overview]
Message-ID: <1556126241-2774-5-git-send-email-weifeng.voon@intel.com> (raw)
In-Reply-To: <1556126241-2774-1-git-send-email-weifeng.voon@intel.com>

From: Ong Boon Leong <boon.leong.ong@intel.com>

xPCS is DWC Ethernet Physical Coding Sublayer that may be integrated
into a GbE controller that uses DWC EQoS MAC controller. An example of
HW configuration is shown below:-

  <-----------------GBE Controller---------->|<--External PHY chip-->

  +----------+         +----+    +---+               +--------------+
  |   EQoS   | <-GMII->|xPCS|<-->|L1 | <-- SGMII --> | External GbE |
  |   MAC    |         |    |    |PHY|               | PHY Chip     |
  +----------+         +----+    +---+               +--------------+
         ^               ^                                  ^
         |               |                                  |
         +---------------------MDIO-------------------------+

xPCS is a Clause-45 MDIO Manageable Device (MMD) and we need a way to
differentiate it from external PHY chip that is discovered over MDIO.
Therefore, xpcs_phy_addr is introduced in stmmac platform data
(plat_stmmacenet_data) for differentiating xPCS from 'phy_addr' that
belongs to external PHY.

Basic functionalities for initializing xPCS and configuring auto
negotiation (AN), loopback, link status, AN advertisement and Link
Partner ability are implemented.

xPCS interrupt handling for C37 AN complete is also implemented.

Tested-by: Kweh Hock Leong <hock.leong.kweh@intel.com>
Reviewed-by: Chuah Kim Tatt <kim.tatt.chuah@intel.com>
Reviewed-by: Voon Weifeng <weifeng.voon@intel.com>
Reviewed-by: Kweh Hock Leong <hock.leong.kweh@intel.com>
Reviewed-by: Baoli Zhang <baoli.zhang@intel.com>
Signed-off-by: Ong Boon Leong <boon.leong.ong@intel.com>
---
 drivers/net/ethernet/stmicro/stmmac/dw_xpcs.h | 288 ++++++++++++++++++++++++++
 drivers/net/ethernet/stmicro/stmmac/hwif.h    |  17 ++
 include/linux/stmmac.h                        |   1 +
 3 files changed, 306 insertions(+)
 create mode 100644 drivers/net/ethernet/stmicro/stmmac/dw_xpcs.h

diff --git a/drivers/net/ethernet/stmicro/stmmac/dw_xpcs.h b/drivers/net/ethernet/stmicro/stmmac/dw_xpcs.h
new file mode 100644
index 0000000..446b714
--- /dev/null
+++ b/drivers/net/ethernet/stmicro/stmmac/dw_xpcs.h
@@ -0,0 +1,288 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/* dw_xpcs.h: DWC Ethernet Physical Coding Sublayer Header
+ *
+ * Copyright (c) 2019, Intel Corporation.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU General Public License,
+ * version 2, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
+ * more details.
+ */
+#ifndef __DW_XPCS_H__
+#define __DW_XPCS_H__
+
+#include <linux/mdio.h>
+#include <linux/bitops.h>
+#include "stmmac.h"
+
+/* XPCS Control & MII MMD Device Addresses */
+#define XPCS_MDIO_CONTROL_MMD	MDIO_MMD_VEND1
+#define XPCS_MDIO_MII_MMD	MDIO_MMD_VEND2
+
+/* Control MMD register offsets */
+#define MDIO_CONTROL_MMD_CTRL		0x9	/* Control */
+
+/* Control MMD Control defines */
+#define MDIO_CONTROL_MMD_CTRL_MII_MMD_EN	1 /* MII MMD Enable */
+
+/* MII MMD registers offsets */
+#define MDIO_MII_MMD_CTRL		0x0	/* Control */
+#define MDIO_MII_MMD_ADV		0x4	/* AN Advertisement */
+#define MDIO_MII_MMD_LPA		0x5	/* Link Partner Ability */
+#define MDIO_MII_MMD_DIGITAL_CTRL_1	0x8000	/* Digital Control 1 */
+#define MDIO_MII_MMD_AN_CTRL		0x8001	/* AN Control */
+#define MDIO_MII_MMD_AN_STAT		0x8002	/* AN Status */
+
+/* MII MMD Control defines */
+#define MDIO_MII_MMD_CTRL_ANE		BIT(12)	/* AN Enable */
+#define MDIO_MII_MMD_CTRL_LBE		BIT(14)	/* Loopback Enable */
+#define MDIO_MII_MMD_CTRL_RANE		BIT(9)	/* Restart AN */
+
+/* MII MMD AN Advertisement & Link Partner Ability */
+#define MDIO_MII_MMD_HD			BIT(6)	/* Half duplex */
+#define MDIO_MII_MMD_FD			BIT(5)	/* Full duplex */
+#define MDIO_MII_MMD_PSE_SHIFT		7	/* Pause Ability shift */
+#define MDIO_MII_MMD_PSE	GENMASK(8, 7)	/* Pause Ability */
+
+/* MII MMD Digital Control 1 defines */
+#define MDIO_MII_MMD_DIGI_CTRL_1_SGMII_PHY_MD	BIT(0) /* SGMII PHY mode */
+
+/* MII MMD AN Control defines */
+#define MDIO_MII_MMD_AN_CTRL_TX_CONFIG_SHIFT	3 /* TX Config shift */
+#define AN_CTRL_TX_CONF_PHY_SIDE_SGMII		0x1 /* PHY side SGMII mode */
+#define AN_CTRL_TX_CONF_MAC_SIDE_SGMII		0x0 /* MAC side SGMII mode */
+#define MDIO_MII_MMD_AN_CTRL_PCS_MD_SHIFT	1  /* PCS Mode shift */
+#define MDIO_MII_MMD_AN_CTRL_PCS_MD	GENMASK(2, 1) /* PCS Mode */
+#define AN_CTRL_PCS_MD_C37_1000BASEX	0x0	/* C37 AN for 1000BASE-X */
+#define AN_CTRL_PCS_MD_C37_SGMII	0x2	/* C37 AN for SGMII */
+#define MDIO_MII_MMD_AN_CTRL_AN_INTR_EN	BIT(0)	/* AN Complete Intr Enable */
+
+/* MII MMD AN Status defines for C37 AN SGMII Status */
+#define AN_STAT_C37_AN_CMPLT		BIT(0)	/* AN Complete Intr */
+#define AN_STAT_C37_AN_FD		BIT(1)	/* Full Duplex */
+#define AN_STAT_C37_AN_SPEED_SHIFT	2	/* AN Speed shift */
+#define AN_STAT_C37_AN_SPEED		GENMASK(3, 2)	/* AN Speed */
+#define AN_STAT_C37_AN_10MBPS		0x0	/* 10 Mbps */
+#define AN_STAT_C37_AN_100MBPS		0x1	/* 100 Mbps */
+#define AN_STAT_C37_AN_1000MBPS		0x2	/* 1000 Mbps */
+#define AN_STAT_C37_AN_LNKSTS		BIT(4)	/* Link Status */
+
+/**
+ * dw_xpcs_init - To initialize xPCS
+ * @ndev: network device pointer
+ * @mode: PCS mode
+ * Description: this is to initialize xPCS
+ */
+static inline void dw_xpcs_init(struct net_device *ndev, int pcs_mode)
+{
+	struct stmmac_priv *priv = netdev_priv(ndev);
+	int xpcs_phy_addr = priv->plat->xpcs_phy_addr;
+
+	/* Set SGMII PHY mode control */
+	u16 phydata = (u16)mdiobus_read(priv->mii, xpcs_phy_addr,
+				    (MII_ADDR_C45 |
+				     (XPCS_MDIO_MII_MMD <<
+				      MII_DEVADDR_C45_SHIFT) |
+				     MDIO_MII_MMD_DIGITAL_CTRL_1));
+
+	phydata |= MDIO_MII_MMD_DIGI_CTRL_1_SGMII_PHY_MD;
+
+	mdiobus_write(priv->mii, xpcs_phy_addr,
+		      (MII_ADDR_C45 | (XPCS_MDIO_MII_MMD <<
+		       MII_DEVADDR_C45_SHIFT) | MDIO_MII_MMD_DIGITAL_CTRL_1),
+		      (int)phydata);
+
+	/* Set PHY side SGMII, PCS Mode & Enable C37 AN complete interrupt */
+	phydata = (u16)mdiobus_read(priv->mii, xpcs_phy_addr,
+				    (MII_ADDR_C45 |
+				     (XPCS_MDIO_MII_MMD <<
+				      MII_DEVADDR_C45_SHIFT) |
+				     MDIO_MII_MMD_AN_CTRL));
+
+	phydata &= ~MDIO_MII_MMD_AN_CTRL_PCS_MD;
+	phydata |= (((pcs_mode << MDIO_MII_MMD_AN_CTRL_PCS_MD_SHIFT) &
+		    MDIO_MII_MMD_AN_CTRL_PCS_MD) |
+		    (AN_CTRL_TX_CONF_PHY_SIDE_SGMII <<
+		    MDIO_MII_MMD_AN_CTRL_TX_CONFIG_SHIFT) |
+		    MDIO_MII_MMD_AN_CTRL_AN_INTR_EN);
+
+	mdiobus_write(priv->mii, xpcs_phy_addr,
+		      (MII_ADDR_C45 | (XPCS_MDIO_MII_MMD <<
+		       MII_DEVADDR_C45_SHIFT) | MDIO_MII_MMD_AN_CTRL),
+		      (int)phydata);
+}
+
+/**
+ * dw_xpcs_rane - To restart Auto Negotiation (AN)
+ * @ndev: network device pointer
+ * @restart: to restart AN
+ * Description: this is to restart AN.
+ */
+static inline void dw_xpcs_rane(struct net_device *ndev, bool restart)
+{
+	struct stmmac_priv *priv = netdev_priv(ndev);
+	int xpcs_phy_addr = priv->plat->xpcs_phy_addr;
+
+	u16 phydata = (u16)mdiobus_read(priv->mii, xpcs_phy_addr,
+				(MII_ADDR_C45 | (XPCS_MDIO_MII_MMD <<
+				 MII_DEVADDR_C45_SHIFT) |
+				 MDIO_MII_MMD_CTRL));
+
+	if (restart)
+		phydata |= MDIO_MII_MMD_CTRL_RANE;
+
+	mdiobus_write(priv->mii, xpcs_phy_addr,
+		      (MII_ADDR_C45 | (XPCS_MDIO_MII_MMD <<
+		       MII_DEVADDR_C45_SHIFT) | MDIO_MII_MMD_CTRL),
+		      (int)phydata);
+}
+
+/**
+ * dw_xpcs_ctrl_ane - To program the MII MMD Control Register.
+ * @ndev: network device pointer
+ * @ane: to enable the Auto Negotiation
+ * @loopback: to cause the PHY to loopback Tx data into Rx path.
+ * Description: this is the main function to configure the MII MMD
+ * control register and init the AN Enable and select loopback.
+ */
+static inline void dw_xpcs_ctrl_ane(struct net_device *ndev, bool ane,
+				    bool loopback)
+{
+	struct stmmac_priv *priv = netdev_priv(ndev);
+	int xpcs_phy_addr = priv->plat->xpcs_phy_addr;
+
+	u16 phydata = (u16)mdiobus_read(priv->mii, xpcs_phy_addr,
+				(MII_ADDR_C45 | (XPCS_MDIO_MII_MMD <<
+				 MII_DEVADDR_C45_SHIFT) |
+				 MDIO_MII_MMD_CTRL));
+
+	if (ane)
+		phydata |= (MDIO_MII_MMD_CTRL_ANE | MDIO_MII_MMD_CTRL_RANE);
+
+	if (loopback)
+		phydata |= MDIO_MII_MMD_CTRL_LBE;
+
+	mdiobus_write(priv->mii, xpcs_phy_addr,
+		      (MII_ADDR_C45 | (XPCS_MDIO_MII_MMD <<
+		       MII_DEVADDR_C45_SHIFT) | MDIO_MII_MMD_CTRL),
+		      (int)phydata);
+}
+
+/**
+ * dw_xpcs_get_adv_lp - Get AN Advertisement and Link Partner Ability
+ * @ndev: network device pointer
+ * @adv_lp: structure to store the adv, lp status
+ * Description: this is to expose the Auto Negotiation Advertisement and
+ * Link partner ability status to ethtool support.
+ */
+static inline void dw_xpcs_get_adv_lp(struct net_device *ndev,
+				      struct rgmii_adv *adv_lp)
+{
+	struct stmmac_priv *priv = netdev_priv(ndev);
+	int xpcs_phy_addr = priv->plat->xpcs_phy_addr;
+
+	/* AN Advertisement Ability */
+	u16 value = (u16)mdiobus_read(priv->mii, xpcs_phy_addr,
+				(MII_ADDR_C45 | (XPCS_MDIO_MII_MMD <<
+				 MII_DEVADDR_C45_SHIFT) |
+				 MDIO_MII_MMD_ADV));
+
+	if (value & MDIO_MII_MMD_FD)
+		adv_lp->duplex = DUPLEX_FULL;
+	if (value & MDIO_MII_MMD_HD)
+		adv_lp->duplex = DUPLEX_HALF;
+	adv_lp->pause = (u32)((value & MDIO_MII_MMD_PSE) >>
+			      MDIO_MII_MMD_PSE_SHIFT);
+
+	/* Link Partner Ability */
+	value = (u16)mdiobus_read(priv->mii, xpcs_phy_addr,
+				  (MII_ADDR_C45 | (XPCS_MDIO_MII_MMD <<
+				   MII_DEVADDR_C45_SHIFT) |
+				   MDIO_MII_MMD_LPA));
+
+	if (value & MDIO_MII_MMD_FD)
+		adv_lp->lp_duplex = DUPLEX_FULL;
+	if (value & MDIO_MII_MMD_HD)
+		adv_lp->lp_duplex = DUPLEX_HALF;
+	adv_lp->lp_pause = (u32)((value & MDIO_MII_MMD_PSE) >>
+				 MDIO_MII_MMD_PSE_SHIFT);
+}
+
+/**
+ * dw_xpcs_get_linkstatus - Get Link Status
+ * @an_stat: C37 AN status value
+ * @x: stmmac extra status
+ * Description: this is to read the link extra status from <C37 AN SGMII
+ * status> field of MII MMD AN Status register.
+ */
+static inline void dw_xpcs_get_linkstatus(u16 an_stat,
+					  struct stmmac_extra_stats *x)
+{
+	/* Check the link status */
+	if (an_stat & AN_STAT_C37_AN_LNKSTS) {
+		int speed_value;
+
+		x->pcs_link = 1;
+
+		speed_value = ((an_stat & AN_STAT_C37_AN_SPEED) >>
+				AN_STAT_C37_AN_SPEED_SHIFT);
+		if (speed_value == AN_STAT_C37_AN_1000MBPS)
+			x->pcs_speed = SPEED_1000;
+		else if (speed_value == AN_STAT_C37_AN_100MBPS)
+			x->pcs_speed = SPEED_100;
+		else
+			x->pcs_speed = SPEED_10;
+
+		if (an_stat & AN_STAT_C37_AN_FD)
+			x->pcs_duplex = 1;
+		else
+			x->pcs_duplex = 0;
+
+		pr_info("Link is Up - %d/%s\n", (int)x->pcs_speed,
+			x->pcs_duplex ? "Full" : "Half");
+	} else {
+		x->pcs_link = 0;
+		pr_info("Link is Down\n");
+	}
+}
+
+/**
+ * dw_xpcs_irq_status - Get xPCS IRQ Status
+ * @ndev: network device pointer
+ * @x: stmmac extra status
+ * Description: this is to read the xPCS IRQ status.
+ */
+static inline int dw_xpcs_irq_status(struct net_device *ndev,
+				     struct stmmac_extra_stats *x)
+{
+	struct stmmac_priv *priv = netdev_priv(ndev);
+	int xpcs_phy_addr = priv->plat->xpcs_phy_addr;
+	int ret = IRQ_NONE;
+
+	/* C37 AN status */
+	u16 an_stat = (u16)mdiobus_read(priv->mii, xpcs_phy_addr,
+					(MII_ADDR_C45 |
+					 (XPCS_MDIO_MII_MMD <<
+					  MII_DEVADDR_C45_SHIFT) |
+					 MDIO_MII_MMD_AN_STAT));
+
+	if (an_stat & AN_STAT_C37_AN_CMPLT) {
+		x->irq_pcs_ane_n++;
+		dw_xpcs_get_linkstatus(an_stat, x);
+
+		/* Clear C37 AN complete status by writing zero */
+		mdiobus_write(priv->mii, xpcs_phy_addr,
+			      (MII_ADDR_C45 | (XPCS_MDIO_MII_MMD <<
+			       MII_DEVADDR_C45_SHIFT) |
+			       MDIO_MII_MMD_AN_STAT),
+			      0);
+		ret = IRQ_HANDLED;
+	}
+
+	return ret;
+}
+#endif /* __DW_XPCS_H__ */
diff --git a/drivers/net/ethernet/stmicro/stmmac/hwif.h b/drivers/net/ethernet/stmicro/stmmac/hwif.h
index 5bb0023..cb7eb48 100644
--- a/drivers/net/ethernet/stmicro/stmmac/hwif.h
+++ b/drivers/net/ethernet/stmicro/stmmac/hwif.h
@@ -310,6 +310,13 @@ struct stmmac_ops {
 			     bool loopback);
 	void (*pcs_rane)(void __iomem *ioaddr, bool restart);
 	void (*pcs_get_adv_lp)(void __iomem *ioaddr, struct rgmii_adv *adv);
+	/* xPCS calls */
+	void (*xpcs_init)(struct net_device *ndev, int pcs_mode);
+	void (*xpcs_ctrl_ane)(struct net_device *ndev, bool ane, bool loopback);
+	void (*xpcs_rane)(struct net_device *ndev, bool restart);
+	void (*xpcs_get_adv_lp)(struct net_device *ndev, struct rgmii_adv *adv);
+	int (*xpcs_irq_status)(struct net_device *ndev,
+			       struct stmmac_extra_stats *x);
 	/* Safety Features */
 	int (*safety_feat_config)(void __iomem *ioaddr, unsigned int asp);
 	int (*safety_feat_irq_status)(struct net_device *ndev,
@@ -382,6 +389,16 @@ struct stmmac_ops {
 	stmmac_do_void_callback(__priv, mac, pcs_rane, __args)
 #define stmmac_pcs_get_adv_lp(__priv, __args...) \
 	stmmac_do_void_callback(__priv, mac, pcs_get_adv_lp, __args)
+#define stmmac_xpcs_init(__priv, __args...) \
+	stmmac_do_void_callback(__priv, mac, xpcs_init, __args)
+#define stmmac_xpcs_ctrl_ane(__priv, __args...) \
+	stmmac_do_void_callback(__priv, mac, xpcs_ctrl_ane, __args)
+#define stmmac_xpcs_rane(__priv, __args...) \
+	stmmac_do_void_callback(__priv, mac, xpcs_rane, __args)
+#define stmmac_xpcs_get_adv_lp(__priv, __args...) \
+	stmmac_do_void_callback(__priv, mac, xpcs_get_adv_lp, __args)
+#define stmmac_xpcs_irq_status(__priv, __args...) \
+	stmmac_do_callback(__priv, mac, xpcs_irq_status, __args)
 #define stmmac_safety_feat_config(__priv, __args...) \
 	stmmac_do_callback(__priv, mac, safety_feat_config, __args)
 #define stmmac_safety_feat_irq_status(__priv, __args...) \
diff --git a/include/linux/stmmac.h b/include/linux/stmmac.h
index 4335bd7..b00e795 100644
--- a/include/linux/stmmac.h
+++ b/include/linux/stmmac.h
@@ -148,6 +148,7 @@ struct stmmac_txq_cfg {
 struct plat_stmmacenet_data {
 	int bus_id;
 	int phy_addr;
+	int xpcs_phy_addr;
 	int interface;
 	struct stmmac_mdio_bus_data *mdio_bus_data;
 	struct device_node *phy_node;
-- 
1.9.1


  parent reply	other threads:[~2019-04-24  9:17 UTC|newest]

Thread overview: 36+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2019-04-24 17:17 [PATCH 0/7] net: stmmac: enable EHL SGMII Weifeng Voon
2019-04-24  9:26 ` Jose Abreu
2019-04-24 13:48 ` Andrew Lunn
2019-04-25  7:27   ` Voon, Weifeng
2019-04-25 12:38     ` Andrew Lunn
2019-04-25 14:39       ` Ong, Boon Leong
2019-04-25 15:23         ` Andrew Lunn
2019-04-29  5:37           ` Ong, Boon Leong
2019-04-29 13:10             ` Andrew Lunn
2019-04-29 13:44               ` Jose Abreu
2019-04-24 17:17 ` [PATCH 1/7] net: stmmac: add EHL SGMII 1Gbps platform data and PCI ID Weifeng Voon
2019-04-25  7:04   ` Voon, Weifeng
2019-04-25 12:24     ` Andrew Lunn
2019-04-26  2:10       ` Voon, Weifeng
2019-04-24 17:17 ` [PATCH 2/7] net: stmmac: enable clause 45 mdio support Weifeng Voon
2019-04-24 13:18   ` Andrew Lunn
2019-04-25  7:12     ` Voon, Weifeng
2019-04-24 17:17 ` [PATCH 3/7] net: stmmac: dma channel control register need to be init first Weifeng Voon
2019-04-25  7:06   ` Voon, Weifeng
2019-04-29 10:25     ` Jose Abreu
2019-04-30  2:49       ` Voon, Weifeng
2019-04-24 17:17 ` Weifeng Voon [this message]
2019-04-24 13:41   ` [PATCH 4/7] net: stmmac: introducing support for DWC xPCS logics Andrew Lunn
2019-04-25  1:45     ` Ong, Boon Leong
2019-04-25  3:21       ` Andrew Lunn
2019-04-25  6:51         ` Ong, Boon Leong
2019-04-25 12:22           ` Andrew Lunn
2019-04-25  7:06   ` Voon, Weifeng
2019-04-29 13:23     ` Jose Abreu
2019-04-24 17:17 ` [PATCH 5/7] net: stmmac: add xpcs function hooks into main driver and ethtool Weifeng Voon
2019-04-25  7:07   ` Voon, Weifeng
2019-04-24 17:17 ` [PATCH 6/7] net: stmmac: add xPCS platform data for EHL Weifeng Voon
2019-04-25  7:08   ` Voon, Weifeng
2019-04-24 17:17 ` [PATCH 7/7] net: stmmac: add xPCS functions for device with DWMACv5.1 Weifeng Voon
2019-04-25  7:09   ` Voon, Weifeng
2019-04-25  7:00 ` [PATCH 0/7] net: stmmac: enable EHL SGMII Voon, Weifeng

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=1556126241-2774-5-git-send-email-weifeng.voon@intel.com \
    --to=weifeng.voon@intel.com \
    --cc=boon.leong.ong@intel.com \
    --cc=davem@davemloft.net \
    --cc=hock.leong.kweh@intel.com \
    --cc=linux-kernel@vger.kernel.org \
    --cc=netdev@vger.kernel.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 a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).