From: Douglas Anderson <dianders@chromium.org> To: Thierry Reding <thierry.reding@gmail.com>, Rob Herring <robh+dt@kernel.org> Cc: Maarten Lankhorst <maarten.lankhorst@linux.intel.com>, Steev Klimaszewski <steev@kali.org>, dri-devel@lists.freedesktop.org, David Airlie <airlied@linux.ie>, devicetree@vger.kernel.org, Daniel Vetter <daniel@ffwll.ch>, Maxime Ripard <mripard@kernel.org>, Sam Ravnborg <sam@ravnborg.org>, Thomas Zimmermann <tzimmermann@suse.de>, linux-arm-msm@vger.kernel.org, Linus W <linus.walleij@linaro.org>, Bjorn Andersson <bjorn.andersson@linaro.org>, Douglas Anderson <dianders@chromium.org>, linux-kernel@vger.kernel.org Subject: [RFC PATCH 5/8] drm/panel-simple: Copy "desc" into driver data; don't store a pointer Date: Thu, 22 Jul 2021 17:21:43 -0700 [thread overview] Message-ID: <20210722172104.RFC.5.I89640bdefeafa3b4a856f16fb7f150dc2c4b14d5@changeid> (raw) In-Reply-To: <20210723002146.1962910-1-dianders@chromium.org> Up until now "desc" has usually pointed to one of the "static const" objects defined in the panel-simple driver. Just storing a pointer to one of these data elements made sense. In a future patch to support probable eDP panels, however, it's convenient to be able to modify the delays that the driver uses as it starts up. One nice way to handle this is to just copy the "desc" into our driver data instead of storing a pointer. This has a nice side effect of simplifying the "DPI" case since we no longer need an extra alloc there. Signed-off-by: Douglas Anderson <dianders@chromium.org> --- drivers/gpu/drm/panel/panel-simple.c | 84 +++++++++++++--------------- 1 file changed, 38 insertions(+), 46 deletions(-) diff --git a/drivers/gpu/drm/panel/panel-simple.c b/drivers/gpu/drm/panel/panel-simple.c index bcdc84b20827..c08bc70f7798 100644 --- a/drivers/gpu/drm/panel/panel-simple.c +++ b/drivers/gpu/drm/panel/panel-simple.c @@ -199,7 +199,7 @@ struct panel_simple { ktime_t prepared_time; ktime_t unprepared_time; - const struct panel_desc *desc; + struct panel_desc desc; struct regulator *supply; struct i2c_adapter *ddc; @@ -226,8 +226,8 @@ static unsigned int panel_simple_get_timings_modes(struct panel_simple *panel, struct drm_display_mode *mode; unsigned int i, num = 0; - for (i = 0; i < panel->desc->num_timings; i++) { - const struct display_timing *dt = &panel->desc->timings[i]; + for (i = 0; i < panel->desc.num_timings; i++) { + const struct display_timing *dt = &panel->desc.timings[i]; struct videomode vm; videomode_from_timing(dt, &vm); @@ -242,7 +242,7 @@ static unsigned int panel_simple_get_timings_modes(struct panel_simple *panel, mode->type |= DRM_MODE_TYPE_DRIVER; - if (panel->desc->num_timings == 1) + if (panel->desc.num_timings == 1) mode->type |= DRM_MODE_TYPE_PREFERRED; drm_mode_probed_add(connector, mode); @@ -258,8 +258,8 @@ static unsigned int panel_simple_get_display_modes(struct panel_simple *panel, struct drm_display_mode *mode; unsigned int i, num = 0; - for (i = 0; i < panel->desc->num_modes; i++) { - const struct drm_display_mode *m = &panel->desc->modes[i]; + for (i = 0; i < panel->desc.num_modes; i++) { + const struct drm_display_mode *m = &panel->desc.modes[i]; mode = drm_mode_duplicate(connector->dev, m); if (!mode) { @@ -271,7 +271,7 @@ static unsigned int panel_simple_get_display_modes(struct panel_simple *panel, mode->type |= DRM_MODE_TYPE_DRIVER; - if (panel->desc->num_modes == 1) + if (panel->desc.num_modes == 1) mode->type |= DRM_MODE_TYPE_PREFERRED; drm_mode_set_name(mode); @@ -290,9 +290,6 @@ static int panel_simple_get_non_edid_modes(struct panel_simple *panel, bool has_override = panel->override_mode.type; unsigned int num = 0; - if (!panel->desc) - return 0; - if (has_override) { mode = drm_mode_duplicate(connector->dev, &panel->override_mode); @@ -305,7 +302,7 @@ static int panel_simple_get_non_edid_modes(struct panel_simple *panel, } /* Only add timings if override was not there or failed to validate */ - if (num == 0 && panel->desc->num_timings) + if (num == 0 && panel->desc.num_timings) num = panel_simple_get_timings_modes(panel, connector); /* @@ -314,17 +311,17 @@ static int panel_simple_get_non_edid_modes(struct panel_simple *panel, * We should only ever have either the display timings specified * or a fixed mode. Anything else is rather bogus. */ - WARN_ON(panel->desc->num_timings && panel->desc->num_modes); + WARN_ON(panel->desc.num_timings && panel->desc.num_modes); if (num == 0) num = panel_simple_get_display_modes(panel, connector); - connector->display_info.bpc = panel->desc->bpc; - connector->display_info.width_mm = panel->desc->size.width; - connector->display_info.height_mm = panel->desc->size.height; - if (panel->desc->bus_format) + connector->display_info.bpc = panel->desc.bpc; + connector->display_info.width_mm = panel->desc.size.width; + connector->display_info.height_mm = panel->desc.size.height; + if (panel->desc.bus_format) drm_display_info_set_bus_formats(&connector->display_info, - &panel->desc->bus_format, 1); - connector->display_info.bus_flags = panel->desc->bus_flags; + &panel->desc.bus_format, 1); + connector->display_info.bus_flags = panel->desc.bus_flags; return num; } @@ -350,8 +347,8 @@ static int panel_simple_disable(struct drm_panel *panel) if (!p->enabled) return 0; - if (p->desc->delay.disable) - msleep(p->desc->delay.disable); + if (p->desc.delay.disable) + msleep(p->desc.delay.disable); p->enabled = false; @@ -364,8 +361,8 @@ static int panel_simple_suspend(struct device *dev) gpiod_set_value_cansleep(p->enable_gpio, 0); - if (p->desc->delay.disable_to_power_off) - msleep(p->desc->delay.disable_to_power_off); + if (p->desc.delay.disable_to_power_off) + msleep(p->desc.delay.disable_to_power_off); regulator_disable(p->supply); p->unprepared_time = ktime_get(); @@ -416,7 +413,7 @@ static int panel_simple_prepare_once(struct panel_simple *p) int hpd_asserted; unsigned long hpd_wait_us; - panel_simple_wait(p->unprepared_time, p->desc->delay.unprepare); + panel_simple_wait(p->unprepared_time, p->desc.delay.unprepare); err = regulator_enable(p->supply); if (err < 0) { @@ -424,20 +421,20 @@ static int panel_simple_prepare_once(struct panel_simple *p) return err; } - if (p->desc->delay.power_to_enable) - msleep(p->desc->delay.power_to_enable); + if (p->desc.delay.power_to_enable) + msleep(p->desc.delay.power_to_enable); gpiod_set_value_cansleep(p->enable_gpio, 1); - delay = p->desc->delay.prepare; + delay = p->desc.delay.prepare; if (p->no_hpd) - delay += p->desc->delay.hpd_absent_delay; + delay += p->desc.delay.hpd_absent_delay; if (delay) msleep(delay); if (p->hpd_gpio) { - if (p->desc->delay.hpd_absent_delay) - hpd_wait_us = p->desc->delay.hpd_absent_delay * 1000UL; + if (p->desc.delay.hpd_absent_delay) + hpd_wait_us = p->desc.delay.hpd_absent_delay * 1000UL; else hpd_wait_us = 2000000; @@ -520,10 +517,10 @@ static int panel_simple_enable(struct drm_panel *panel) if (p->enabled) return 0; - if (p->desc->delay.enable) - msleep(p->desc->delay.enable); + if (p->desc.delay.enable) + msleep(p->desc.delay.enable); - panel_simple_wait(p->prepared_time, p->desc->delay.prepare_to_enable); + panel_simple_wait(p->prepared_time, p->desc.delay.prepare_to_enable); p->enabled = true; @@ -566,14 +563,14 @@ static int panel_simple_get_timings(struct drm_panel *panel, struct panel_simple *p = to_panel_simple(panel); unsigned int i; - if (p->desc->num_timings < num_timings) - num_timings = p->desc->num_timings; + if (p->desc.num_timings < num_timings) + num_timings = p->desc.num_timings; if (timings) for (i = 0; i < num_timings; i++) - timings[i] = p->desc->timings[i]; + timings[i] = p->desc.timings[i]; - return p->desc->num_timings; + return p->desc.num_timings; } static const struct drm_panel_funcs panel_simple_funcs = { @@ -592,15 +589,12 @@ static int panel_dpi_probe(struct device *dev, { struct display_timing *timing; const struct device_node *np; - struct panel_desc *desc; + struct panel_desc *desc = &panel->desc; unsigned int bus_flags; struct videomode vm; int ret; np = dev->of_node; - desc = devm_kzalloc(dev, sizeof(*desc), GFP_KERNEL); - if (!desc) - return -ENOMEM; timing = devm_kzalloc(dev, sizeof(*timing), GFP_KERNEL); if (!timing) @@ -628,8 +622,6 @@ static int panel_dpi_probe(struct device *dev, /* We do not know the connector for the DT node, so guess it */ desc->connector_type = DRM_MODE_CONNECTOR_DPI; - panel->desc = desc; - return 0; } @@ -640,7 +632,7 @@ static void panel_simple_parse_panel_timing_node(struct device *dev, struct panel_simple *panel, const struct display_timing *ot) { - const struct panel_desc *desc = panel->desc; + const struct panel_desc *desc = &panel->desc; struct videomode vm; unsigned int i; @@ -653,8 +645,8 @@ static void panel_simple_parse_panel_timing_node(struct device *dev, return; } - for (i = 0; i < panel->desc->num_timings; i++) { - const struct display_timing *dt = &panel->desc->timings[i]; + for (i = 0; i < desc->num_timings; i++) { + const struct display_timing *dt = &desc->timings[i]; if (!PANEL_SIMPLE_BOUNDS_CHECK(ot, dt, hactive) || !PANEL_SIMPLE_BOUNDS_CHECK(ot, dt, hfront_porch) || @@ -696,7 +688,7 @@ static int panel_simple_probe(struct device *dev, const struct panel_desc *desc, panel->enabled = false; panel->prepared_time = 0; - panel->desc = desc; + panel->desc = *desc; panel->aux = aux; panel->no_hpd = of_property_read_bool(dev->of_node, "no-hpd"); -- 2.32.0.432.gabb21c7263-goog
WARNING: multiple messages have this Message-ID (diff)
From: Douglas Anderson <dianders@chromium.org> To: Thierry Reding <thierry.reding@gmail.com>, Rob Herring <robh+dt@kernel.org> Cc: devicetree@vger.kernel.org, Thomas Zimmermann <tzimmermann@suse.de>, David Airlie <airlied@linux.ie>, linux-arm-msm@vger.kernel.org, Douglas Anderson <dianders@chromium.org>, dri-devel@lists.freedesktop.org, Bjorn Andersson <bjorn.andersson@linaro.org>, Steev Klimaszewski <steev@kali.org>, Sam Ravnborg <sam@ravnborg.org>, linux-kernel@vger.kernel.org Subject: [RFC PATCH 5/8] drm/panel-simple: Copy "desc" into driver data; don't store a pointer Date: Thu, 22 Jul 2021 17:21:43 -0700 [thread overview] Message-ID: <20210722172104.RFC.5.I89640bdefeafa3b4a856f16fb7f150dc2c4b14d5@changeid> (raw) In-Reply-To: <20210723002146.1962910-1-dianders@chromium.org> Up until now "desc" has usually pointed to one of the "static const" objects defined in the panel-simple driver. Just storing a pointer to one of these data elements made sense. In a future patch to support probable eDP panels, however, it's convenient to be able to modify the delays that the driver uses as it starts up. One nice way to handle this is to just copy the "desc" into our driver data instead of storing a pointer. This has a nice side effect of simplifying the "DPI" case since we no longer need an extra alloc there. Signed-off-by: Douglas Anderson <dianders@chromium.org> --- drivers/gpu/drm/panel/panel-simple.c | 84 +++++++++++++--------------- 1 file changed, 38 insertions(+), 46 deletions(-) diff --git a/drivers/gpu/drm/panel/panel-simple.c b/drivers/gpu/drm/panel/panel-simple.c index bcdc84b20827..c08bc70f7798 100644 --- a/drivers/gpu/drm/panel/panel-simple.c +++ b/drivers/gpu/drm/panel/panel-simple.c @@ -199,7 +199,7 @@ struct panel_simple { ktime_t prepared_time; ktime_t unprepared_time; - const struct panel_desc *desc; + struct panel_desc desc; struct regulator *supply; struct i2c_adapter *ddc; @@ -226,8 +226,8 @@ static unsigned int panel_simple_get_timings_modes(struct panel_simple *panel, struct drm_display_mode *mode; unsigned int i, num = 0; - for (i = 0; i < panel->desc->num_timings; i++) { - const struct display_timing *dt = &panel->desc->timings[i]; + for (i = 0; i < panel->desc.num_timings; i++) { + const struct display_timing *dt = &panel->desc.timings[i]; struct videomode vm; videomode_from_timing(dt, &vm); @@ -242,7 +242,7 @@ static unsigned int panel_simple_get_timings_modes(struct panel_simple *panel, mode->type |= DRM_MODE_TYPE_DRIVER; - if (panel->desc->num_timings == 1) + if (panel->desc.num_timings == 1) mode->type |= DRM_MODE_TYPE_PREFERRED; drm_mode_probed_add(connector, mode); @@ -258,8 +258,8 @@ static unsigned int panel_simple_get_display_modes(struct panel_simple *panel, struct drm_display_mode *mode; unsigned int i, num = 0; - for (i = 0; i < panel->desc->num_modes; i++) { - const struct drm_display_mode *m = &panel->desc->modes[i]; + for (i = 0; i < panel->desc.num_modes; i++) { + const struct drm_display_mode *m = &panel->desc.modes[i]; mode = drm_mode_duplicate(connector->dev, m); if (!mode) { @@ -271,7 +271,7 @@ static unsigned int panel_simple_get_display_modes(struct panel_simple *panel, mode->type |= DRM_MODE_TYPE_DRIVER; - if (panel->desc->num_modes == 1) + if (panel->desc.num_modes == 1) mode->type |= DRM_MODE_TYPE_PREFERRED; drm_mode_set_name(mode); @@ -290,9 +290,6 @@ static int panel_simple_get_non_edid_modes(struct panel_simple *panel, bool has_override = panel->override_mode.type; unsigned int num = 0; - if (!panel->desc) - return 0; - if (has_override) { mode = drm_mode_duplicate(connector->dev, &panel->override_mode); @@ -305,7 +302,7 @@ static int panel_simple_get_non_edid_modes(struct panel_simple *panel, } /* Only add timings if override was not there or failed to validate */ - if (num == 0 && panel->desc->num_timings) + if (num == 0 && panel->desc.num_timings) num = panel_simple_get_timings_modes(panel, connector); /* @@ -314,17 +311,17 @@ static int panel_simple_get_non_edid_modes(struct panel_simple *panel, * We should only ever have either the display timings specified * or a fixed mode. Anything else is rather bogus. */ - WARN_ON(panel->desc->num_timings && panel->desc->num_modes); + WARN_ON(panel->desc.num_timings && panel->desc.num_modes); if (num == 0) num = panel_simple_get_display_modes(panel, connector); - connector->display_info.bpc = panel->desc->bpc; - connector->display_info.width_mm = panel->desc->size.width; - connector->display_info.height_mm = panel->desc->size.height; - if (panel->desc->bus_format) + connector->display_info.bpc = panel->desc.bpc; + connector->display_info.width_mm = panel->desc.size.width; + connector->display_info.height_mm = panel->desc.size.height; + if (panel->desc.bus_format) drm_display_info_set_bus_formats(&connector->display_info, - &panel->desc->bus_format, 1); - connector->display_info.bus_flags = panel->desc->bus_flags; + &panel->desc.bus_format, 1); + connector->display_info.bus_flags = panel->desc.bus_flags; return num; } @@ -350,8 +347,8 @@ static int panel_simple_disable(struct drm_panel *panel) if (!p->enabled) return 0; - if (p->desc->delay.disable) - msleep(p->desc->delay.disable); + if (p->desc.delay.disable) + msleep(p->desc.delay.disable); p->enabled = false; @@ -364,8 +361,8 @@ static int panel_simple_suspend(struct device *dev) gpiod_set_value_cansleep(p->enable_gpio, 0); - if (p->desc->delay.disable_to_power_off) - msleep(p->desc->delay.disable_to_power_off); + if (p->desc.delay.disable_to_power_off) + msleep(p->desc.delay.disable_to_power_off); regulator_disable(p->supply); p->unprepared_time = ktime_get(); @@ -416,7 +413,7 @@ static int panel_simple_prepare_once(struct panel_simple *p) int hpd_asserted; unsigned long hpd_wait_us; - panel_simple_wait(p->unprepared_time, p->desc->delay.unprepare); + panel_simple_wait(p->unprepared_time, p->desc.delay.unprepare); err = regulator_enable(p->supply); if (err < 0) { @@ -424,20 +421,20 @@ static int panel_simple_prepare_once(struct panel_simple *p) return err; } - if (p->desc->delay.power_to_enable) - msleep(p->desc->delay.power_to_enable); + if (p->desc.delay.power_to_enable) + msleep(p->desc.delay.power_to_enable); gpiod_set_value_cansleep(p->enable_gpio, 1); - delay = p->desc->delay.prepare; + delay = p->desc.delay.prepare; if (p->no_hpd) - delay += p->desc->delay.hpd_absent_delay; + delay += p->desc.delay.hpd_absent_delay; if (delay) msleep(delay); if (p->hpd_gpio) { - if (p->desc->delay.hpd_absent_delay) - hpd_wait_us = p->desc->delay.hpd_absent_delay * 1000UL; + if (p->desc.delay.hpd_absent_delay) + hpd_wait_us = p->desc.delay.hpd_absent_delay * 1000UL; else hpd_wait_us = 2000000; @@ -520,10 +517,10 @@ static int panel_simple_enable(struct drm_panel *panel) if (p->enabled) return 0; - if (p->desc->delay.enable) - msleep(p->desc->delay.enable); + if (p->desc.delay.enable) + msleep(p->desc.delay.enable); - panel_simple_wait(p->prepared_time, p->desc->delay.prepare_to_enable); + panel_simple_wait(p->prepared_time, p->desc.delay.prepare_to_enable); p->enabled = true; @@ -566,14 +563,14 @@ static int panel_simple_get_timings(struct drm_panel *panel, struct panel_simple *p = to_panel_simple(panel); unsigned int i; - if (p->desc->num_timings < num_timings) - num_timings = p->desc->num_timings; + if (p->desc.num_timings < num_timings) + num_timings = p->desc.num_timings; if (timings) for (i = 0; i < num_timings; i++) - timings[i] = p->desc->timings[i]; + timings[i] = p->desc.timings[i]; - return p->desc->num_timings; + return p->desc.num_timings; } static const struct drm_panel_funcs panel_simple_funcs = { @@ -592,15 +589,12 @@ static int panel_dpi_probe(struct device *dev, { struct display_timing *timing; const struct device_node *np; - struct panel_desc *desc; + struct panel_desc *desc = &panel->desc; unsigned int bus_flags; struct videomode vm; int ret; np = dev->of_node; - desc = devm_kzalloc(dev, sizeof(*desc), GFP_KERNEL); - if (!desc) - return -ENOMEM; timing = devm_kzalloc(dev, sizeof(*timing), GFP_KERNEL); if (!timing) @@ -628,8 +622,6 @@ static int panel_dpi_probe(struct device *dev, /* We do not know the connector for the DT node, so guess it */ desc->connector_type = DRM_MODE_CONNECTOR_DPI; - panel->desc = desc; - return 0; } @@ -640,7 +632,7 @@ static void panel_simple_parse_panel_timing_node(struct device *dev, struct panel_simple *panel, const struct display_timing *ot) { - const struct panel_desc *desc = panel->desc; + const struct panel_desc *desc = &panel->desc; struct videomode vm; unsigned int i; @@ -653,8 +645,8 @@ static void panel_simple_parse_panel_timing_node(struct device *dev, return; } - for (i = 0; i < panel->desc->num_timings; i++) { - const struct display_timing *dt = &panel->desc->timings[i]; + for (i = 0; i < desc->num_timings; i++) { + const struct display_timing *dt = &desc->timings[i]; if (!PANEL_SIMPLE_BOUNDS_CHECK(ot, dt, hactive) || !PANEL_SIMPLE_BOUNDS_CHECK(ot, dt, hfront_porch) || @@ -696,7 +688,7 @@ static int panel_simple_probe(struct device *dev, const struct panel_desc *desc, panel->enabled = false; panel->prepared_time = 0; - panel->desc = desc; + panel->desc = *desc; panel->aux = aux; panel->no_hpd = of_property_read_bool(dev->of_node, "no-hpd"); -- 2.32.0.432.gabb21c7263-goog
next prev parent reply other threads:[~2021-07-23 0:22 UTC|newest] Thread overview: 22+ messages / expand[flat|nested] mbox.gz Atom feed top 2021-07-23 0:21 [RFC PATCH 0/8] eDP: Support probing eDP panels dynamically instead of hardcoding Douglas Anderson 2021-07-23 0:21 ` Douglas Anderson 2021-07-23 0:21 ` [RFC PATCH 1/8] dt-bindings: drm/panel-simple: Introduce generic eDP panels Douglas Anderson 2021-07-23 0:21 ` Douglas Anderson 2021-07-29 20:27 ` Rob Herring 2021-07-29 20:27 ` Rob Herring 2021-07-29 21:19 ` Doug Anderson 2021-07-29 21:19 ` Doug Anderson 2021-07-23 0:21 ` [RFC PATCH 2/8] drm/edid: Break out reading block 0 of the EDID Douglas Anderson 2021-07-23 0:21 ` Douglas Anderson 2021-07-23 0:21 ` [RFC PATCH 3/8] drm/edid: Allow the querying/working with the panel ID from " Douglas Anderson 2021-07-23 0:21 ` Douglas Anderson 2021-07-23 0:21 ` [RFC PATCH 4/8] drm/panel-simple: Don't re-read the EDID every time we power off the panel Douglas Anderson 2021-07-23 0:21 ` Douglas Anderson 2021-07-23 0:21 ` Douglas Anderson [this message] 2021-07-23 0:21 ` [RFC PATCH 5/8] drm/panel-simple: Copy "desc" into driver data; don't store a pointer Douglas Anderson 2021-07-23 0:21 ` [RFC PATCH 6/8] drm/panel-simple: Split the delay structure out of the panel description Douglas Anderson 2021-07-23 0:21 ` Douglas Anderson 2021-07-23 0:21 ` [RFC PATCH 7/8] drm/panel-simple: Implement generic "edp-panel"s probed by EDID Douglas Anderson 2021-07-23 0:21 ` Douglas Anderson 2021-07-23 0:21 ` [RFC PATCH 8/8] arm64: dts: qcom: sc7180: trogdor devices can use probable eDP panels Douglas Anderson 2021-07-23 0:21 ` Douglas Anderson
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=20210722172104.RFC.5.I89640bdefeafa3b4a856f16fb7f150dc2c4b14d5@changeid \ --to=dianders@chromium.org \ --cc=airlied@linux.ie \ --cc=bjorn.andersson@linaro.org \ --cc=daniel@ffwll.ch \ --cc=devicetree@vger.kernel.org \ --cc=dri-devel@lists.freedesktop.org \ --cc=linus.walleij@linaro.org \ --cc=linux-arm-msm@vger.kernel.org \ --cc=linux-kernel@vger.kernel.org \ --cc=maarten.lankhorst@linux.intel.com \ --cc=mripard@kernel.org \ --cc=robh+dt@kernel.org \ --cc=sam@ravnborg.org \ --cc=steev@kali.org \ --cc=thierry.reding@gmail.com \ --cc=tzimmermann@suse.de \ /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: linkBe 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.