All of lore.kernel.org
 help / color / mirror / Atom feed
From: Rajesh Yadav <ryadav-sgV2jX0FEOL9JmXXK+q4OQ@public.gmane.org>
To: dri-devel-PD4FTy7X32lNgt0PjOBp9y5qC8QIuHrW@public.gmane.org,
	freedreno-PD4FTy7X32lNgt0PjOBp9y5qC8QIuHrW@public.gmane.org,
	linux-arm-msm-u79uwXL29TY76Z2rM5mHXA@public.gmane.org
Cc: robdclark-Re5JQEeQqe8AvxtiuMwx3w@public.gmane.org,
	seanpaul-F7+t8E8rja9g9hUCZPvPmw@public.gmane.org,
	Rajesh Yadav <ryadav-sgV2jX0FEOL9JmXXK+q4OQ@public.gmane.org>,
	hoegsberg-F7+t8E8rja9g9hUCZPvPmw@public.gmane.org
Subject: [DPU PATCH v3 04/12] drm/msm/dpu: create new platform driver for dpu device
Date: Mon, 14 May 2018 20:56:51 +0530	[thread overview]
Message-ID: <1526311619-3309-5-git-send-email-ryadav@codeaurora.org> (raw)
In-Reply-To: <1526311619-3309-1-git-send-email-ryadav-sgV2jX0FEOL9JmXXK+q4OQ@public.gmane.org>

Current MSM display controller HW matches a tree like
hierarchy where MDSS top level wrapper is parent device
and mdp5/dpu, dsi, dp are child devices.

Each child device like mdp5, dsi etc. have a separate driver,
but currently dpu handling is tied to a single driver which
was managing both mdss and dpu resources.

Inorder to have the cleaner one to one device and driver
association, this change adds a new platform_driver for dpu
child device node which implements the kms functionality.

The dpu driver implements runtime_pm support for managing clocks
and bus bandwidth etc.

Changes in v3:
	- none

Changes in v2:
	- remove redundant param check from _dpu_kms_hw_destroy (Sean Paul)
	- remove explicit calls to devm_kfree (Sean Paul)
	- merge dpu_init into dpu_bind (Sean Paul)
	- merge dpu_destroy into dpu_unbind (Sean Paul)
	- use %pK for kernel pointer printing (Jordan Crouse)
	- remove explicit devm allocation failure message (Jordan Crouse)

Signed-off-by: Rajesh Yadav <ryadav@codeaurora.org>
Reviewed-by: Jordan Crouse <jcrouse@codeaurora.org>
Reviewed-by: Sean Paul <seanpaul@chromium.org>
---
 drivers/gpu/drm/msm/disp/dpu1/dpu_kms.c | 238 +++++++++++++++++++++++++-------
 drivers/gpu/drm/msm/disp/dpu1/dpu_kms.h |   4 +
 drivers/gpu/drm/msm/msm_drv.c           |   2 +
 drivers/gpu/drm/msm/msm_drv.h           |   3 +
 4 files changed, 196 insertions(+), 51 deletions(-)

diff --git a/drivers/gpu/drm/msm/disp/dpu1/dpu_kms.c b/drivers/gpu/drm/msm/disp/dpu1/dpu_kms.c
index e4ab753..85f3dbc 100644
--- a/drivers/gpu/drm/msm/disp/dpu1/dpu_kms.c
+++ b/drivers/gpu/drm/msm/disp/dpu1/dpu_kms.c
@@ -1030,16 +1030,12 @@ static long dpu_kms_round_pixclk(struct msm_kms *kms, unsigned long rate,
 	return rate;
 }
 
-static void _dpu_kms_hw_destroy(struct dpu_kms *dpu_kms,
-		struct platform_device *pdev)
+static void _dpu_kms_hw_destroy(struct dpu_kms *dpu_kms)
 {
 	struct drm_device *dev;
 	struct msm_drm_private *priv;
 	int i;
 
-	if (!dpu_kms || !pdev)
-		return;
-
 	dev = dpu_kms->dev;
 	if (!dev)
 		return;
@@ -1091,15 +1087,15 @@ static void _dpu_kms_hw_destroy(struct dpu_kms *dpu_kms,
 	dpu_kms->core_client = NULL;
 
 	if (dpu_kms->vbif[VBIF_NRT])
-		msm_iounmap(pdev, dpu_kms->vbif[VBIF_NRT]);
+		msm_iounmap(dpu_kms->pdev, dpu_kms->vbif[VBIF_NRT]);
 	dpu_kms->vbif[VBIF_NRT] = NULL;
 
 	if (dpu_kms->vbif[VBIF_RT])
-		msm_iounmap(pdev, dpu_kms->vbif[VBIF_RT]);
+		msm_iounmap(dpu_kms->pdev, dpu_kms->vbif[VBIF_RT]);
 	dpu_kms->vbif[VBIF_RT] = NULL;
 
 	if (dpu_kms->mmio)
-		msm_iounmap(pdev, dpu_kms->mmio);
+		msm_iounmap(dpu_kms->pdev, dpu_kms->mmio);
 	dpu_kms->mmio = NULL;
 
 	dpu_reg_dma_deinit();
@@ -1172,8 +1168,6 @@ int dpu_kms_mmu_attach(struct dpu_kms *dpu_kms, bool secure_only)
 static void dpu_kms_destroy(struct msm_kms *kms)
 {
 	struct dpu_kms *dpu_kms;
-	struct drm_device *dev;
-	struct platform_device *platformdev;
 
 	if (!kms) {
 		DPU_ERROR("invalid kms\n");
@@ -1181,20 +1175,7 @@ static void dpu_kms_destroy(struct msm_kms *kms)
 	}
 
 	dpu_kms = to_dpu_kms(kms);
-	dev = dpu_kms->dev;
-	if (!dev) {
-		DPU_ERROR("invalid device\n");
-		return;
-	}
-
-	platformdev = to_platform_device(dev->dev);
-	if (!platformdev) {
-		DPU_ERROR("invalid platform device\n");
-		return;
-	}
-
-	_dpu_kms_hw_destroy(dpu_kms, platformdev);
-	kfree(dpu_kms);
+	_dpu_kms_hw_destroy(dpu_kms);
 }
 
 static void dpu_kms_preclose(struct msm_kms *kms, struct drm_file *file)
@@ -1550,7 +1531,6 @@ static int dpu_kms_hw_init(struct msm_kms *kms)
 	struct dpu_kms *dpu_kms;
 	struct drm_device *dev;
 	struct msm_drm_private *priv;
-	struct platform_device *platformdev;
 	int i, rc = -EINVAL;
 
 	if (!kms) {
@@ -1565,34 +1545,28 @@ static int dpu_kms_hw_init(struct msm_kms *kms)
 		goto end;
 	}
 
-	platformdev = to_platform_device(dev->dev);
-	if (!platformdev) {
-		DPU_ERROR("invalid platform device\n");
-		goto end;
-		}
-
 	priv = dev->dev_private;
 	if (!priv) {
 		DPU_ERROR("invalid private data\n");
 		goto end;
 	}
 
-	dpu_kms->mmio = msm_ioremap(platformdev, "mdp_phys", "mdp_phys");
+	dpu_kms->mmio = msm_ioremap(dpu_kms->pdev, "mdp_phys", "mdp_phys");
 	if (IS_ERR(dpu_kms->mmio)) {
 		rc = PTR_ERR(dpu_kms->mmio);
 		DPU_ERROR("mdp register memory map failed: %d\n", rc);
 		dpu_kms->mmio = NULL;
 		goto error;
 	}
-	DRM_INFO("mapped mdp address space @%p\n", dpu_kms->mmio);
-	dpu_kms->mmio_len = msm_iomap_size(platformdev, "mdp_phys");
+	DRM_DEBUG("mapped dpu address space @%pK\n", dpu_kms->mmio);
+	dpu_kms->mmio_len = msm_iomap_size(dpu_kms->pdev, "mdp_phys");
 
 	rc = dpu_dbg_reg_register_base(DPU_DBG_NAME, dpu_kms->mmio,
 			dpu_kms->mmio_len);
 	if (rc)
 		DPU_ERROR("dbg base register kms failed: %d\n", rc);
 
-	dpu_kms->vbif[VBIF_RT] = msm_ioremap(platformdev, "vbif_phys",
+	dpu_kms->vbif[VBIF_RT] = msm_ioremap(dpu_kms->pdev, "vbif_phys",
 								"vbif_phys");
 	if (IS_ERR(dpu_kms->vbif[VBIF_RT])) {
 		rc = PTR_ERR(dpu_kms->vbif[VBIF_RT]);
@@ -1600,20 +1574,20 @@ static int dpu_kms_hw_init(struct msm_kms *kms)
 		dpu_kms->vbif[VBIF_RT] = NULL;
 		goto error;
 	}
-	dpu_kms->vbif_len[VBIF_RT] = msm_iomap_size(platformdev,
+	dpu_kms->vbif_len[VBIF_RT] = msm_iomap_size(dpu_kms->pdev,
 								"vbif_phys");
 	rc = dpu_dbg_reg_register_base("vbif_rt", dpu_kms->vbif[VBIF_RT],
 				dpu_kms->vbif_len[VBIF_RT]);
 	if (rc)
 		DPU_ERROR("dbg base register vbif_rt failed: %d\n", rc);
 
-	dpu_kms->vbif[VBIF_NRT] = msm_ioremap(platformdev, "vbif_nrt_phys",
+	dpu_kms->vbif[VBIF_NRT] = msm_ioremap(dpu_kms->pdev, "vbif_nrt_phys",
 								"vbif_nrt_phys");
 	if (IS_ERR(dpu_kms->vbif[VBIF_NRT])) {
 		dpu_kms->vbif[VBIF_NRT] = NULL;
 		DPU_DEBUG("VBIF NRT is not defined");
 	} else {
-		dpu_kms->vbif_len[VBIF_NRT] = msm_iomap_size(platformdev,
+		dpu_kms->vbif_len[VBIF_NRT] = msm_iomap_size(dpu_kms->pdev,
 							"vbif_nrt_phys");
 		rc = dpu_dbg_reg_register_base("vbif_nrt",
 				dpu_kms->vbif[VBIF_NRT],
@@ -1624,13 +1598,13 @@ static int dpu_kms_hw_init(struct msm_kms *kms)
 	}
 
 #ifdef CONFIG_CHROME_REGDMA
-	dpu_kms->reg_dma = msm_ioremap(platformdev, "regdma_phys",
+	dpu_kms->reg_dma = msm_ioremap(dpu_kms->pdev, "regdma_phys",
 								"regdma_phys");
 	if (IS_ERR(dpu_kms->reg_dma)) {
 		dpu_kms->reg_dma = NULL;
 		DPU_DEBUG("REG_DMA is not defined");
 	} else {
-		dpu_kms->reg_dma_len = msm_iomap_size(platformdev,
+		dpu_kms->reg_dma_len = msm_iomap_size(dpu_kms->pdev,
 								"regdma_phys");
 		rc =  dpu_dbg_reg_register_base("reg_dma",
 				dpu_kms->reg_dma,
@@ -1804,14 +1778,13 @@ static int dpu_kms_hw_init(struct msm_kms *kms)
 	dpu_power_resource_enable(&priv->phandle, dpu_kms->core_client, false);
 	pm_runtime_put_sync(dev->dev);
 error:
-	_dpu_kms_hw_destroy(dpu_kms, platformdev);
+	_dpu_kms_hw_destroy(dpu_kms);
 end:
 	return rc;
 }
 
 struct msm_kms *dpu_kms_init(struct drm_device *dev)
 {
-	struct platform_device *pdev = to_platform_device(dev->dev);
 	struct msm_drm_private *priv;
 	struct dpu_kms *dpu_kms;
 	int irq;
@@ -1821,24 +1794,187 @@ struct msm_kms *dpu_kms_init(struct drm_device *dev)
 		return ERR_PTR(-EINVAL);
 	}
 
-	irq = platform_get_irq(pdev, 0);
+	priv = dev->dev_private;
+	dpu_kms = to_dpu_kms(priv->kms);
+
+	irq = irq_of_parse_and_map(dpu_kms->pdev->dev.of_node, 0);
 	if (irq < 0) {
 		DPU_ERROR("failed to get irq: %d\n", irq);
 		return ERR_PTR(irq);
 	}
+	dpu_kms->base.irq = irq;
 
-	priv = dev->dev_private;
+	return &dpu_kms->base;
+}
 
-	dpu_kms = kzalloc(sizeof(*dpu_kms), GFP_KERNEL);
-	if (!dpu_kms) {
-		DPU_ERROR("failed to allocate dpu kms\n");
-		return ERR_PTR(-ENOMEM);
+static int dpu_bind(struct device *dev, struct device *master, void *data)
+{
+	struct drm_device *ddev = dev_get_drvdata(master);
+	struct platform_device *pdev = to_platform_device(dev);
+	struct msm_drm_private *priv = ddev->dev_private;
+	struct dpu_kms *dpu_kms;
+	struct dss_module_power *mp;
+	int ret = 0;
+
+	dpu_kms = devm_kzalloc(&pdev->dev, sizeof(*dpu_kms), GFP_KERNEL);
+	if (!dpu_kms)
+		return -ENOMEM;
+
+	mp = &dpu_kms->mp;
+	ret = msm_dss_parse_clock(pdev, mp);
+	if (ret) {
+		DPU_ERROR("failed to parse clocks, ret=%d\n", ret);
+		return ret;
+	}
+
+	ret = msm_dss_get_clk(&pdev->dev, mp->clk_config, mp->num_clk);
+	if (ret) {
+		pr_err("failed to get clocks, ret=%d\n", ret);
+		goto clk_get_error;
+	}
+
+	ret = msm_dss_clk_set_rate(mp->clk_config, mp->num_clk);
+	if (ret) {
+		pr_err("failed to set clock rate, ret=%d\n", ret);
+		goto clk_rate_error;
 	}
 
+	platform_set_drvdata(pdev, dpu_kms);
+
 	msm_kms_init(&dpu_kms->base, &kms_funcs);
-	dpu_kms->dev = dev;
-	dpu_kms->base.irq = irq;
+	dpu_kms->dev = ddev;
+	dpu_kms->pdev = pdev;
 
-	return &dpu_kms->base;
+	pm_runtime_enable(&pdev->dev);
+	dpu_kms->rpm_enabled = true;
+
+	priv->kms = &dpu_kms->base;
+	return ret;
+
+clk_rate_error:
+	msm_dss_put_clk(mp->clk_config, mp->num_clk);
+clk_get_error:
+	devm_kfree(&pdev->dev, mp->clk_config);
+	mp->num_clk = 0;
+	return ret;
 }
 
+static void dpu_unbind(struct device *dev, struct device *master, void *data)
+{
+	struct platform_device *pdev = to_platform_device(dev);
+	struct dpu_kms *dpu_kms = platform_get_drvdata(pdev);
+	struct dss_module_power *mp = &dpu_kms->mp;
+
+	msm_dss_put_clk(mp->clk_config, mp->num_clk);
+	devm_kfree(&pdev->dev, mp->clk_config);
+	mp->num_clk = 0;
+
+	if (dpu_kms->rpm_enabled)
+		pm_runtime_disable(&pdev->dev);
+}
+
+static const struct component_ops dpu_ops = {
+	.bind   = dpu_bind,
+	.unbind = dpu_unbind,
+};
+
+static int dpu_dev_probe(struct platform_device *pdev)
+{
+	return component_add(&pdev->dev, &dpu_ops);
+}
+
+static int dpu_dev_remove(struct platform_device *pdev)
+{
+	component_del(&pdev->dev, &dpu_ops);
+	return 0;
+}
+
+static int dpu_runtime_suspend(struct device *dev)
+{
+	int rc = -1;
+	struct platform_device *pdev = to_platform_device(dev);
+	struct dpu_kms *dpu_kms = platform_get_drvdata(pdev);
+	struct drm_device *ddev;
+	struct msm_drm_private *priv;
+	struct dss_module_power *mp = &dpu_kms->mp;
+
+	ddev = dpu_kms->dev;
+	if (!ddev) {
+		DPU_ERROR("invalid drm_device\n");
+		goto exit;
+	}
+	priv = ddev->dev_private;
+
+	rc = dpu_power_resource_enable(&priv->phandle,
+		dpu_kms->core_client, false);
+	if (rc)
+		DPU_ERROR("resource disable failed: %d\n", rc);
+
+	rc = msm_dss_enable_clk(mp->clk_config, mp->num_clk, false);
+	if (rc)
+		DPU_ERROR("clock disable failed rc:%d\n", rc);
+
+exit:
+	return rc;
+}
+
+static int dpu_runtime_resume(struct device *dev)
+{
+	int rc = -1;
+	struct platform_device *pdev = to_platform_device(dev);
+	struct dpu_kms *dpu_kms = platform_get_drvdata(pdev);
+	struct drm_device *ddev;
+	struct msm_drm_private *priv;
+	struct dss_module_power *mp = &dpu_kms->mp;
+
+	ddev = dpu_kms->dev;
+	if (!ddev) {
+		DPU_ERROR("invalid drm_device\n");
+		goto exit;
+	}
+	priv = ddev->dev_private;
+
+	rc = msm_dss_enable_clk(mp->clk_config, mp->num_clk, true);
+	if (rc) {
+		DPU_ERROR("clock enable failed rc:%d\n", rc);
+		goto exit;
+	}
+
+	rc = dpu_power_resource_enable(&priv->phandle, dpu_kms->core_client,
+		true);
+	if (rc)
+		DPU_ERROR("resource enable failed: %d\n", rc);
+
+exit:
+	return rc;
+}
+
+static const struct dev_pm_ops dpu_pm_ops = {
+	SET_RUNTIME_PM_OPS(dpu_runtime_suspend, dpu_runtime_resume, NULL)
+};
+
+static const struct of_device_id dpu_dt_match[] = {
+	{ .compatible = "qcom,dpu", },
+	{}
+};
+MODULE_DEVICE_TABLE(of, dpu_dt_match);
+
+static struct platform_driver dpu_driver = {
+	.probe = dpu_dev_probe,
+	.remove = dpu_dev_remove,
+	.driver = {
+		.name = "msm_dpu",
+		.of_match_table = dpu_dt_match,
+		.pm = &dpu_pm_ops,
+	},
+};
+
+void __init msm_dpu_register(void)
+{
+	platform_driver_register(&dpu_driver);
+}
+
+void __exit msm_dpu_unregister(void)
+{
+	platform_driver_unregister(&dpu_driver);
+}
diff --git a/drivers/gpu/drm/msm/disp/dpu1/dpu_kms.h b/drivers/gpu/drm/msm/disp/dpu1/dpu_kms.h
index a1c0910..3c69921 100644
--- a/drivers/gpu/drm/msm/disp/dpu1/dpu_kms.h
+++ b/drivers/gpu/drm/msm/disp/dpu1/dpu_kms.h
@@ -200,6 +200,10 @@ struct dpu_kms {
 	struct dpu_hw_mdp *hw_mdp;
 
 	bool has_danger_ctrl;
+
+	struct platform_device *pdev;
+	bool rpm_enabled;
+	struct dss_module_power mp;
 };
 
 struct vsync_info {
diff --git a/drivers/gpu/drm/msm/msm_drv.c b/drivers/gpu/drm/msm/msm_drv.c
index a0e73ea..5470529 100644
--- a/drivers/gpu/drm/msm/msm_drv.c
+++ b/drivers/gpu/drm/msm/msm_drv.c
@@ -1731,6 +1731,7 @@ static int __init msm_drm_register(void)
 
 	DBG("init");
 	msm_mdp_register();
+	msm_dpu_register();
 	msm_dsi_register();
 	msm_edp_register();
 	msm_hdmi_register();
@@ -1747,6 +1748,7 @@ static void __exit msm_drm_unregister(void)
 	msm_edp_unregister();
 	msm_dsi_unregister();
 	msm_mdp_unregister();
+	msm_dpu_unregister();
 }
 
 module_init(msm_drm_register);
diff --git a/drivers/gpu/drm/msm/msm_drv.h b/drivers/gpu/drm/msm/msm_drv.h
index e8e5e73..22a3096 100644
--- a/drivers/gpu/drm/msm/msm_drv.h
+++ b/drivers/gpu/drm/msm/msm_drv.h
@@ -682,6 +682,9 @@ static inline int msm_dsi_modeset_init(struct msm_dsi *msm_dsi,
 void __init msm_mdp_register(void);
 void __exit msm_mdp_unregister(void);
 
+void __init msm_dpu_register(void);
+void __exit msm_dpu_unregister(void);
+
 #ifdef CONFIG_DEBUG_FS
 void msm_gem_describe(struct drm_gem_object *obj, struct seq_file *m);
 void msm_gem_describe_objects(struct list_head *list, struct seq_file *m);
-- 
The Qualcomm Innovation Center, Inc. is a member of the Code Aurora Forum,
a Linux Foundation Collaborative Project

_______________________________________________
Freedreno mailing list
Freedreno@lists.freedesktop.org
https://lists.freedesktop.org/mailman/listinfo/freedreno

  parent reply	other threads:[~2018-05-14 15:26 UTC|newest]

Thread overview: 14+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2018-05-14 15:26 [DPU PATCH v3 00/12] Refactor DPU device/driver hierarchy and add runtime_pm support Rajesh Yadav
2018-05-14 15:26 ` [DPU PATCH v3 01/12] drm/msm: remove redundant pm_runtime_enable call from msm_drv Rajesh Yadav
     [not found] ` <1526311619-3309-1-git-send-email-ryadav-sgV2jX0FEOL9JmXXK+q4OQ@public.gmane.org>
2018-05-14 15:26   ` [DPU PATCH v3 02/12] drm/msm/mdp5: subclass msm_mdss for mdp5 Rajesh Yadav
2018-05-14 15:26   ` [DPU PATCH v3 03/12] drm/msm/dpu: add MDSS top level driver for dpu Rajesh Yadav
2018-05-14 15:26   ` Rajesh Yadav [this message]
2018-05-14 15:26   ` [DPU PATCH v3 05/12] drm/msm/dpu: update dpu sub-block offsets wrt dpu base address Rajesh Yadav
     [not found]     ` <1526311619-3309-6-git-send-email-ryadav-sgV2jX0FEOL9JmXXK+q4OQ@public.gmane.org>
2018-06-19  8:59       ` ryadav-sgV2jX0FEOL9JmXXK+q4OQ
2018-05-14 15:26   ` [DPU PATCH v3 06/12] drm/msm/dpu: use runtime_pm calls on dpu device Rajesh Yadav
2018-05-14 15:26   ` [DPU PATCH v3 07/12] drm/msm/dpu: remove clock management code from dpu_power_handle Rajesh Yadav
2018-05-14 15:26   ` [DPU PATCH v3 08/12] drm/msm/dpu: remove power " Rajesh Yadav
2018-05-14 15:26   ` [DPU PATCH v3 09/12] drm/msm/dp: remove dpu_power_handle calls from dp driver Rajesh Yadav
2018-05-14 15:26   ` [DPU PATCH v3 10/12] drm/msm/dpu: use runtime_pm calls in dpu_dbg Rajesh Yadav
2018-05-14 15:26   ` [DPU PATCH v3 11/12] drm/msm/dpu: move dpu_power_handle to dpu folder Rajesh Yadav
2018-05-14 15:26   ` [DPU PATCH v3 12/12] drm/msm/dpu: add error handling in dpu_core_perf_crtc_update Rajesh Yadav

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=1526311619-3309-5-git-send-email-ryadav@codeaurora.org \
    --to=ryadav-sgv2jx0feol9jmxxk+q4oq@public.gmane.org \
    --cc=dri-devel-PD4FTy7X32lNgt0PjOBp9y5qC8QIuHrW@public.gmane.org \
    --cc=freedreno-PD4FTy7X32lNgt0PjOBp9y5qC8QIuHrW@public.gmane.org \
    --cc=hoegsberg-F7+t8E8rja9g9hUCZPvPmw@public.gmane.org \
    --cc=linux-arm-msm-u79uwXL29TY76Z2rM5mHXA@public.gmane.org \
    --cc=robdclark-Re5JQEeQqe8AvxtiuMwx3w@public.gmane.org \
    --cc=seanpaul-F7+t8E8rja9g9hUCZPvPmw@public.gmane.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.