Linux Documentation
 help / color / mirror / Atom feed
From: Zhongqiu Han <zhongqiu.han@oss.qualcomm.com>
To: Christian Loehle <christian.loehle@arm.com>, rafael@kernel.org
Cc: zhenglifeng1@huawei.com, viresh.kumar@linaro.org,
	linux-kernel@vger.kernel.org, linux-acpi@vger.kernel.org,
	linux-arm-kernel@lists.infradead.org, linux-doc@vger.kernel.org,
	Mario Limonciello <mario.limonciello@amd.com>,
	K Prateek Nayak <kprateek.nayak@amd.com>,
	Huang Rui <ray.huang@amd.com>, Perry Yuan <perry.yuan@amd.com>,
	"Gautham R . Shenoy" <gautham.shenoy@amd.com>,
	Vanshidhar Konda <vanshikonda@os.amperecomputing.com>,
	Shubhang Kaushik <sh@gentwo.org>,
	Pierre Gondois <pierre.gondois@arm.com>,
	Beata Michalska <beata.michalska@arm.com>,
	Dietmar Eggemann <dietmar.eggemann@arm.com>,
	Ionela Voinescu <ionela.voinescu@arm.com>,
	Sudeep Holla <sudeep.holla@kernel.org>,
	Lukasz Luba <lukasz.luba@arm.com>,
	Jeremy Linton <jeremy.linton@arm.com>,
	Peter Zijlstra <peterz@infradead.org>,
	jonathanh@nvidia.com, zhanjie9@hisilicon.com,
	Vincent Guittot <vincent.guittot@linaro.org>,
	Jonathan Corbet <corbet@lwn.net>,
	Shuah Khan <skhan@linuxfoundation.org>,
	Randy Dunlap <rdunlap@infradead.org>,
	zhongqiu.han@oss.qualcomm.com
Subject: Re: [PATCH 2/3] cpufreq: CPPC: Resolve frequencies to performance levels
Date: Sun, 4 Oct 2026 21:55:25 +0800	[thread overview]
Message-ID: <c6948769-1050-4211-b93c-b72f51c32dd4@oss.qualcomm.com> (raw)
In-Reply-To: <0a4a28b8-0b26-41df-bd55-c809177a9909@oss.qualcomm.com>

On 10/2/2026 8:29 PM, Zhongqiu Han wrote:
> Hi Christian,
> 
> 
> On 9/29/2026 6:29 PM, Christian Loehle wrote:
>> Different kHz requests can select the same CPPC performance level.
>> Implement ->resolve_freq() so governors such as schedutil can skip
>> redundant writes.
>>
>> Precompute the affine conversion and invert its integer rounding 
>> directly.
>> Share bounded conversions with ->target() and ->fast_switch(), 
>> choosing the
>> first performance level when several share a kHz value. Recompute limits
>> from each policy snapshot to avoid caching sysfs-shared mutable controls.
>>
>> Cap intervals at or below nominal kHz at Nominal Performance, excluding
>> boosted levels that alias it. Unchanged frequency limits then imply
>> unchanged performance limits across boost toggles.
>>
>> After clamping to CPU limits, snap the limits to supported frequencies.
>> If none lies in the interval, collapse both limits to the highest 
>> supported
>> frequency not above its maximum, as frequency-table verification does.
>>
>> Signed-off-by: Christian Loehle <christian.loehle@arm.com>
>> ---
>>   drivers/cpufreq/cppc_cpufreq.c | 257 ++++++++++++++++++++++++++++++---
>>   1 file changed, 239 insertions(+), 18 deletions(-)
>>
>> diff --git a/drivers/cpufreq/cppc_cpufreq.c b/drivers/cpufreq/ 
>> cppc_cpufreq.c
>> index 4ea444ff889e..fa85efb9f701 100644
>> --- a/drivers/cpufreq/cppc_cpufreq.c
>> +++ b/drivers/cpufreq/cppc_cpufreq.c
>> @@ -18,8 +18,10 @@
>>   #include <linux/cpufreq.h>
>>   #include <linux/irq_work.h>
>>   #include <linux/kthread.h>
>> +#include <linux/math64.h>
>>   #include <linux/mutex.h>
>>   #include <linux/time.h>
>> +#include <linux/units.h>
>>   #include <linux/vmalloc.h>
>>   #include <uapi/linux/sched/types.h>
>> @@ -29,6 +31,24 @@
>>   static struct cpufreq_driver cppc_cpufreq_driver;
>> +struct cppc_perf_freq_map {
>> +    s64 offset;
>> +    u64 multiplier;
>> +    u32 divisor;
>> +};
>> +
>> +struct cppc_cpufreq_data {
>> +    struct cppc_cpudata cpu_data;
>> +    struct cppc_perf_freq_map map;
>> +    unsigned int nominal_khz;
>> +};
>> +
>> +static struct cppc_cpufreq_data *
>> +cppc_cpufreq_data(struct cppc_cpudata *cpu_data)
>> +{
>> +    return container_of(cpu_data, struct cppc_cpufreq_data, cpu_data);
>> +}
>> +
>>   #ifdef CONFIG_ACPI_CPPC_CPUFREQ_FIE
>>   static enum {
>>       FIE_UNSET = -1,
>> @@ -302,11 +322,159 @@ static inline void cppc_freq_invariance_exit(void)
>>   }
>>   #endif /* CONFIG_ACPI_CPPC_CPUFREQ_FIE */
>> +/* Precompute the affine mapping used by cppc_perf_to_khz(). */
>> +static void cppc_cpufreq_init_perf_map(struct cppc_perf_caps *caps,
>> +                       struct cppc_perf_freq_map *map)
>> +{
>> +    if (caps->lowest_freq && caps->nominal_freq) {
>> +        if (caps->lowest_freq == caps->nominal_freq) {
>> +            map->multiplier = (u64)caps->nominal_freq * KHZ_PER_MHZ;
> 
> This duplicates the perf<->kHz conversion, but cppc_perf_to_khz() rounds
> its offset at MHz granularity before scaling to kHz while the new map
> scales first, so the two can differ by up to 999 kHz. In current patch
> func cppc_cpufreq_perf_limits() the max_freq operand still derives from
> cpuinfo.max_freq (old conversion) while data->nominal_khz uses the new
> one -- with boost off, could the max_freq <= data->nominal_khz test
> still be relied upon? If it goes false, will policy_max_perf become
> highest_perf and max_perf seems to end up above nominal_perf?

Please ignore this comment. I misread cppc_perf_to_khz(). Sorry about
that.

> 
>> +            map->divisor = caps->nominal_perf;
>> +            map->offset = 0;
>> +        } else {
>> +            map->multiplier = (u64)(caps->nominal_freq -
>> +                       caps->lowest_freq) * KHZ_PER_MHZ;
>> +            map->divisor = caps->nominal_perf - caps->lowest_perf;
>> +            map->offset = (s64)caps->nominal_freq * KHZ_PER_MHZ -
>> +                div64_u64(caps->nominal_perf * map->multiplier,
>> +                      map->divisor);
>> +        }
>> +    } else {
>> +        map->multiplier = cppc_get_dmi_max_khz();
>> +        map->divisor = caps->highest_perf;
>> +        map->offset = 0;
>> +    }
>> +}
>> +
>> +static unsigned int
>> +cppc_cpufreq_perf_to_khz(const struct cppc_perf_freq_map *map, u32 perf)
>> +{
>> +    s64 freq = map->offset + div64_u64(perf * map->multiplier,
>> +                        map->divisor);
>> +
>> +    return freq > 0 ? freq : 0;
>> +}
>> +
>> +/*
>> + * Invert integer-kHz rounding: L finds the first level at or above the
>> + * target frequency, H the last at or below it, within the supplied 
>> bounds.
>> + */
>> +static u32 cppc_cpufreq_perf_for_freq(const struct cppc_perf_freq_map 
>> *map,
>> +                      unsigned int target_freq,
>> +                      u32 min_perf, u32 max_perf,
>> +                      unsigned int relation)
>> +{
>> +    s64 scaled_freq = (s64)target_freq - map->offset;
>> +    u64 perf;
>> +
>> +    switch (relation) {
>> +    case CPUFREQ_RELATION_L:
>> +        if (!target_freq || scaled_freq <= 0)
>> +            perf = 0;
>> +        else
>> +            perf = mul_u64_u64_div_u64_roundup(scaled_freq,
>> +                               map->divisor,
>> +                               map->multiplier);
>> +        break;
>> +
>> +    case CPUFREQ_RELATION_H:
>> +        if (scaled_freq < 0) {
>> +            perf = 0;
>> +        } else {
>> +            perf = mul_u64_u64_div_u64_roundup(scaled_freq + 1,
>> +                               map->divisor,
>> +                               map->multiplier);
>> +            perf--;
>> +        }
>> +        break;
>> +
>> +    default:
>> +        WARN_ON_ONCE(1);
>> +        return min_perf;
>> +    }
>> +
>> +    return clamp_t(u64, perf, min_perf, max_perf);
>> +}
>> +
>> +static void cppc_cpufreq_perf_limits(struct cppc_perf_caps *caps,
>> +                     struct cppc_cpufreq_data *data,
>> +                     unsigned int min_freq,
>> +                     unsigned int max_freq,
>> +                     u32 *min_perf, u32 *max_perf)
>> +{
>> +    u32 policy_max_perf;
>> +
>> +    /* Do not include boosted levels that alias the nominal 
>> frequency. */
>> +    policy_max_perf = max_freq <= data->nominal_khz ?
>> +              caps->nominal_perf : caps->highest_perf;
>> +    *min_perf = cppc_cpufreq_perf_for_freq(&data->map, min_freq,
>> +                           caps->lowest_perf,
>> +                           policy_max_perf,
>> +                           CPUFREQ_RELATION_L);
>> +    *max_perf = cppc_cpufreq_perf_for_freq(&data->map, max_freq,
>> +                           caps->lowest_perf,
>> +                           policy_max_perf,
>> +                           CPUFREQ_RELATION_H);
>> +}
>> +
>> +static unsigned int
>> +cppc_cpufreq_resolve_freq(struct cpufreq_policy *policy,
>> +              unsigned int target_freq,
>> +              unsigned int min_freq,
>> +              unsigned int max_freq,
>> +              unsigned int relation)
>> +{
>> +    struct cppc_cpudata *cpu_data = policy->driver_data;
>> +    struct cppc_perf_caps *caps = &cpu_data->perf_caps;
>> +    struct cppc_cpufreq_data *data = cppc_cpufreq_data(cpu_data);
>> +    const struct cppc_perf_freq_map *map = &data->map;
>> +    u32 min_perf, max_perf, perf;
>> +
>> +    cppc_cpufreq_perf_limits(caps, data, min_freq, max_freq,
>> +                 &min_perf, &max_perf);
>> +    if (WARN_ON_ONCE(min_perf > max_perf))
>> +        return cppc_cpufreq_perf_to_khz(map, max_perf);
>> +
>> +    switch (relation) {
>> +    case CPUFREQ_RELATION_L:
>> +    case CPUFREQ_RELATION_H:
>> +        perf = cppc_cpufreq_perf_for_freq(map, target_freq,
>> +                          min_perf, max_perf, relation);
>> +        break;
>> +    case CPUFREQ_RELATION_C: {
>> +        u32 lower = cppc_cpufreq_perf_for_freq(map, target_freq,
>> +                            min_perf, max_perf,
>> +                            CPUFREQ_RELATION_H);
>> +        u32 upper = cppc_cpufreq_perf_for_freq(map, target_freq,
>> +                            min_perf, max_perf,
>> +                            CPUFREQ_RELATION_L);
>> +        unsigned int lower_freq = cppc_cpufreq_perf_to_khz(map, lower);
>> +        unsigned int upper_freq = cppc_cpufreq_perf_to_khz(map, upper);
>> +
>> +        if (lower_freq >= target_freq)
>> +            perf = lower;
>> +        else if (upper_freq <= target_freq)
>> +            perf = upper;
>> +        else if (target_freq - lower_freq < upper_freq - target_freq)
>> +            perf = lower;
>> +        else
>> +            perf = upper;
>> +        break;
>> +    }
>> +    default:
>> +        WARN_ON_ONCE(1);
>> +        return target_freq;
>> +    }
>> +
>> +    return cppc_cpufreq_perf_to_khz(map, perf);
>> +}
>> +
>>   static void cppc_cpufreq_get_perf_limits(struct cppc_cpudata *cpu_data,
>>                        struct cpufreq_policy *policy,
>>                        u32 *min_perf, u32 *max_perf)
>>   {
>>       struct cppc_perf_caps *caps = &cpu_data->perf_caps;
>> +    struct cppc_cpufreq_data *data = cppc_cpufreq_data(cpu_data);
>>       unsigned int min_freq, max_freq;
>>       u32 min, max;
>> @@ -315,11 +483,11 @@ static void cppc_cpufreq_get_perf_limits(struct 
>> cppc_cpudata *cpu_data,
>>       if (unlikely(min_freq > max_freq))
>>           min_freq = max_freq;
>> -    min = cppc_khz_to_perf(caps, min_freq);
>> -    max = cppc_khz_to_perf(caps, max_freq);
>> +    cppc_cpufreq_perf_limits(caps, data, min_freq, max_freq,
>> +                 &min, &max);
>> -    *min_perf = clamp_t(u32, min, caps->lowest_perf, caps- 
>> >highest_perf);
>> -    *max_perf = clamp_t(u32, max, caps->lowest_perf, caps- 
>> >highest_perf);
>> +    *min_perf = min(min, max);
>> +    *max_perf = max;
>>   }
>>   static void cppc_cpufreq_update_perf_limits(struct cppc_cpudata 
>> *cpu_data,
>> @@ -330,6 +498,26 @@ static void 
>> cppc_cpufreq_update_perf_limits(struct cppc_cpudata *cpu_data,
>>                        &cpu_data->perf_ctrls.max_perf);
>>   }
>> +static unsigned int
>> +cppc_cpufreq_update_perf_ctrls(struct cppc_cpudata *cpu_data,
>> +                   struct cpufreq_policy *policy,
>> +                   unsigned int target_freq)
>> +{
>> +    struct cppc_cpufreq_data *data = cppc_cpufreq_data(cpu_data);
>> +    u32 min_perf, max_perf, desired_perf;
>> +
>> +    cppc_cpufreq_get_perf_limits(cpu_data, policy, &min_perf, 
>> &max_perf);
>> +    /* Use the first level when several share the same integer-kHz 
>> value. */
>> +    desired_perf = cppc_cpufreq_perf_for_freq(&data->map, target_freq,
>> +                          min_perf, max_perf,
>> +                          CPUFREQ_RELATION_L);
>> +    cpu_data->perf_ctrls.min_perf = min_perf;
>> +    cpu_data->perf_ctrls.max_perf = max_perf;
>> +    cpu_data->perf_ctrls.desired_perf = desired_perf;
>> +
>> +    return cppc_cpufreq_perf_to_khz(&data->map, desired_perf);
>> +}
>> +
>>   static int cppc_cpufreq_set_target(struct cpufreq_policy *policy,
>>                      unsigned int target_freq,
>>                      unsigned int relation)
>> @@ -339,12 +527,9 @@ static int cppc_cpufreq_set_target(struct 
>> cpufreq_policy *policy,
>>       struct cpufreq_freqs freqs;
>>       int ret = 0;
>> -    cpu_data->perf_ctrls.desired_perf =
>> -            cppc_khz_to_perf(&cpu_data->perf_caps, target_freq);
>> -    cppc_cpufreq_update_perf_limits(cpu_data, policy);
>> -
>>       freqs.old = policy->cur;
>> -    freqs.new = target_freq;
>> +    freqs.new = cppc_cpufreq_update_perf_ctrls(cpu_data, policy,
>> +                           target_freq);
>>       cpufreq_freq_transition_begin(policy, &freqs);
>>       ret = cppc_set_perf(cpu, &cpu_data->perf_ctrls);
>> @@ -361,13 +546,12 @@ static unsigned int 
>> cppc_cpufreq_fast_switch(struct cpufreq_policy *policy,
>>                             unsigned int target_freq)
>>   {
>>       struct cppc_cpudata *cpu_data = policy->driver_data;
>> +    unsigned int resolved_freq;
>>       unsigned int cpu = policy->cpu;
>> -    u32 desired_perf;
>>       int ret;
>> -    desired_perf = cppc_khz_to_perf(&cpu_data->perf_caps, target_freq);
>> -    cpu_data->perf_ctrls.desired_perf = desired_perf;
>> -    cppc_cpufreq_update_perf_limits(cpu_data, policy);
>> +    resolved_freq = cppc_cpufreq_update_perf_ctrls(cpu_data, policy,
>> +                               target_freq);
>>       ret = cppc_set_perf(cpu, &cpu_data->perf_ctrls);
>>       if (ret) {
>> @@ -376,12 +560,39 @@ static unsigned int 
>> cppc_cpufreq_fast_switch(struct cpufreq_policy *policy,
>>           return 0;
>>       }
>> -    return target_freq;
>> +    return resolved_freq;
>>   }
>>   static int cppc_verify_policy(struct cpufreq_policy_data *policy)
>>   {
>> +    struct cpufreq_policy *cur_policy;
>> +    struct cppc_cpudata *cpu_data;
>> +    struct cppc_cpufreq_data *data;
>> +    struct cppc_perf_caps *caps;
>> +    unsigned int min_freq, max_freq;
>> +    u32 min_perf, max_perf;
>> +
>>       cpufreq_verify_within_cpu_limits(policy);
>> +
>> +    cur_policy = cpufreq_cpu_get_raw(policy->cpu);
>> +    if (WARN_ON_ONCE(!cur_policy || !cur_policy->driver_data))
>> +        return -ENODEV;
>> +
>> +    cpu_data = cur_policy->driver_data;
>> +    data = cppc_cpufreq_data(cpu_data);
>> +    caps = &cpu_data->perf_caps;
>> +    cppc_cpufreq_perf_limits(caps, data, policy->min, policy->max,
>> +                 &min_perf, &max_perf);
>> +    min_freq = cppc_cpufreq_perf_to_khz(&data->map, min_perf);
>> +    max_freq = cppc_cpufreq_perf_to_khz(&data->map, max_perf);
>> +
>> +    /* Favor the maximum if no supported frequency lies in the 
>> interval. */
>> +    if (min_perf > max_perf || min_freq > policy->max ||
>> +        max_freq < policy->min)
>> +        min_freq = max_freq;
>> +
>> +    policy->min = min_freq;
>> +    policy->max = max_freq;
>>       return 0;
>>   }
>> @@ -618,12 +829,14 @@ static void populate_efficiency_class(void)
>>   static struct cppc_cpudata *cppc_cpufreq_get_cpu_data(unsigned int cpu)
>>   {
>> +    struct cppc_cpufreq_data *data;
>>       struct cppc_cpudata *cpu_data;
>>       int ret;
>> -    cpu_data = kzalloc_obj(struct cppc_cpudata);
>> -    if (!cpu_data)
>> +    data = kzalloc_obj(struct cppc_cpufreq_data);
>> +    if (!data)
>>           goto out;
>> +    cpu_data = &data->cpu_data;
>>       if (!zalloc_cpumask_var(&cpu_data->shared_cpu_map, GFP_KERNEL))
>>           goto free_cpu;
>> @@ -640,6 +853,10 @@ static struct cppc_cpudata 
>> *cppc_cpufreq_get_cpu_data(unsigned int cpu)
>>           goto free_mask;
>>       }
>> +    cppc_cpufreq_init_perf_map(&cpu_data->perf_caps, &data->map);
>> +    data->nominal_khz = cppc_cpufreq_perf_to_khz(&data->map,
>> +                             cpu_data->perf_caps.nominal_perf);
>> +
>>       ret = cppc_get_perf(cpu, &cpu_data->perf_ctrls);
>>       if (ret) {
>>           pr_debug("Err reading CPU%d perf ctrls: ret:%d\n", cpu, ret);
>> @@ -651,7 +868,7 @@ static struct cppc_cpudata 
>> *cppc_cpufreq_get_cpu_data(unsigned int cpu)
>>   free_mask:
>>       free_cpumask_var(cpu_data->shared_cpu_map);
>>   free_cpu:
>> -    kfree(cpu_data);
>> +    kfree(data);
>>   out:
>>       return NULL;
>>   }
>> @@ -659,9 +876,12 @@ static struct cppc_cpudata 
>> *cppc_cpufreq_get_cpu_data(unsigned int cpu)
>>   static void cppc_cpufreq_put_cpu_data(struct cpufreq_policy *policy)
>>   {
>>       struct cppc_cpudata *cpu_data = policy->driver_data;
>> +    struct cppc_cpufreq_data *data;
>> +
>> +    data = container_of(cpu_data, struct cppc_cpufreq_data, cpu_data);
>>       free_cpumask_var(cpu_data->shared_cpu_map);
>> -    kfree(cpu_data);
>> +    kfree(data);
>>       policy->driver_data = NULL;
>>   }
>> @@ -1056,6 +1276,7 @@ static struct cpufreq_driver cppc_cpufreq_driver 
>> = {
>>       .flags = CPUFREQ_CONST_LOOPS | CPUFREQ_NEED_UPDATE_LIMITS,
>>       .verify = cppc_verify_policy,
>>       .target = cppc_cpufreq_set_target,
>> +    .resolve_freq = cppc_cpufreq_resolve_freq,
>>       .get = cppc_cpufreq_get_rate,
>>       .fast_switch = cppc_cpufreq_fast_switch,
>>       .init = cppc_cpufreq_cpu_init,
> 
> 


-- 
Thx and BRs,
Zhongqiu Han

  reply	other threads:[~2026-10-04 13:55 UTC|newest]

Thread overview: 12+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-09-29 10:29 [PATCH 0/3] cpufreq: Resolve CPPC frequencies to performance levels Christian Loehle
2026-09-29 10:29 ` [PATCH 1/3] cpufreq: Add a driver frequency resolution callback Christian Loehle
2026-10-02 10:20   ` Zhongqiu Han
2026-09-29 10:29 ` [PATCH 2/3] cpufreq: CPPC: Resolve frequencies to performance levels Christian Loehle
2026-10-02 12:29   ` Zhongqiu Han
2026-10-04 13:55     ` Zhongqiu Han [this message]
2026-10-06  1:14   ` Jeremy Linton
2026-10-07  2:03     ` Vanshidhar Konda
2026-09-29 10:29 ` [PATCH 3/3] cpufreq: Skip updates for unchanged resolved limits Christian Loehle
2026-10-02 12:46   ` Zhongqiu Han
2026-09-30 20:06 ` [PATCH 0/3] cpufreq: Resolve CPPC frequencies to performance levels Mario Limonciello
2026-10-01 10:34 ` Peter Zijlstra

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=c6948769-1050-4211-b93c-b72f51c32dd4@oss.qualcomm.com \
    --to=zhongqiu.han@oss.qualcomm.com \
    --cc=beata.michalska@arm.com \
    --cc=christian.loehle@arm.com \
    --cc=corbet@lwn.net \
    --cc=dietmar.eggemann@arm.com \
    --cc=gautham.shenoy@amd.com \
    --cc=ionela.voinescu@arm.com \
    --cc=jeremy.linton@arm.com \
    --cc=jonathanh@nvidia.com \
    --cc=kprateek.nayak@amd.com \
    --cc=linux-acpi@vger.kernel.org \
    --cc=linux-arm-kernel@lists.infradead.org \
    --cc=linux-doc@vger.kernel.org \
    --cc=linux-kernel@vger.kernel.org \
    --cc=lukasz.luba@arm.com \
    --cc=mario.limonciello@amd.com \
    --cc=perry.yuan@amd.com \
    --cc=peterz@infradead.org \
    --cc=pierre.gondois@arm.com \
    --cc=rafael@kernel.org \
    --cc=ray.huang@amd.com \
    --cc=rdunlap@infradead.org \
    --cc=sh@gentwo.org \
    --cc=skhan@linuxfoundation.org \
    --cc=sudeep.holla@kernel.org \
    --cc=vanshikonda@os.amperecomputing.com \
    --cc=vincent.guittot@linaro.org \
    --cc=viresh.kumar@linaro.org \
    --cc=zhanjie9@hisilicon.com \
    --cc=zhenglifeng1@huawei.com \
    /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