From: Luke Jones <luke@ljones.dev>
To: linux-kernel@vger.kernel.org
Cc: hdegoede@redhat.com, hadess@hadess.net,
platform-driver-x86@vger.kernel.org
Subject: Re: [PATCH v2 1/1] asus-wmi: Add support for custom fan curves
Date: Thu, 19 Aug 2021 19:26:48 +1200 [thread overview]
Message-ID: <OOS2YQ.5V2ZXNMIG4DC3@ljones.dev> (raw)
In-Reply-To: <20210819011733.231756-2-luke@ljones.dev>
Hi all,
This is the largest patch I've written so far I think, and there may be
mistakes. So far in testing it's been good though.
Feedback is absolutely welcomed. Some of my own comments are inline
here, and this version is a WIP - I expect we'll have a round or two of
changes.
Cheers
On Thu, Aug 19 2021 at 13:17:33 +1200, Luke D. Jones <luke@ljones.dev>
wrote:
> Add support for custom fan curves found on some ASUS ROG laptops.
>
> These laptops have the ability to set a custom curve for the CPU
> and GPU fans via an ACPI method call. This patch enables this,
> additionally enabling custom fan curves per-profile, where profile
> here means each of the 3 levels of "throttle_thermal_policy".
>
> Signed-off-by: Luke D. Jones <luke@ljones.dev>
> ---
> drivers/platform/x86/asus-wmi.c | 498
> +++++++++++++++++++++
> include/linux/platform_data/x86/asus-wmi.h | 2 +
> 2 files changed, 500 insertions(+)
>
> diff --git a/drivers/platform/x86/asus-wmi.c
> b/drivers/platform/x86/asus-wmi.c
> index cc5811844012..dd107301cc1e 100644
> --- a/drivers/platform/x86/asus-wmi.c
> +++ b/drivers/platform/x86/asus-wmi.c
> @@ -122,6 +122,7 @@ struct bios_args {
> u32 arg0;
> u32 arg1;
> u32 arg2; /* At least TUF Gaming series uses 3 dword input buffer.
> */
> + u32 arg3;
> u32 arg4;
> u32 arg5;
> } __packed;
> @@ -173,6 +174,12 @@ enum fan_type {
> FAN_TYPE_SPEC83, /* starting in Spec 8.3, use CPU_FAN_CTRL */
> };
>
> +struct fan_curve {
> + char *balanced;
> + char *performance;
> + char *quiet;
> +};
> +
> struct asus_wmi {
> int dsts_id;
> int spec;
> @@ -220,6 +227,12 @@ struct asus_wmi {
> bool throttle_thermal_policy_available;
> u8 throttle_thermal_policy_mode;
>
> + bool cpu_fan_curve_available;
> + struct fan_curve cpu_fan_curve;
> +
> + bool gpu_fan_curve_available;
> + struct fan_curve gpu_fan_curve;
> +
> struct platform_profile_handler platform_profile_handler;
> bool platform_profile_support;
>
> @@ -285,6 +298,90 @@ int asus_wmi_evaluate_method(u32 method_id, u32
> arg0, u32 arg1, u32 *retval)
> }
> EXPORT_SYMBOL_GPL(asus_wmi_evaluate_method);
>
> +static int asus_wmi_evaluate_method5(u32 method_id,
> + u32 arg0, u32 arg1, u32 arg2, u32 arg3, u32 arg4, u32 *retval)
> +{
> + struct bios_args args = {
> + .arg0 = arg0,
> + .arg1 = arg1,
> + .arg2 = arg2,
> + .arg3 = arg3,
> + .arg4 = arg4,
> + };
> + struct acpi_buffer input = { (acpi_size) sizeof(args), &args };
> + struct acpi_buffer output = { ACPI_ALLOCATE_BUFFER, NULL };
> + acpi_status status;
> + union acpi_object *obj;
> + u32 tmp = 0;
> +
> + status = wmi_evaluate_method(ASUS_WMI_MGMT_GUID, 0, method_id,
> + &input, &output);
> +
> + if (ACPI_FAILURE(status))
> + return -EIO;
> +
> + obj = (union acpi_object *)output.pointer;
> + if (obj && obj->type == ACPI_TYPE_INTEGER)
> + tmp = (u32) obj->integer.value;
> +
> + if (retval)
> + *retval = tmp;
> +
> + kfree(obj);
> +
> + if (tmp == ASUS_WMI_UNSUPPORTED_METHOD)
> + return -ENODEV;
> +
> + return 0;
> +}
> +
> +/*
> + * Returns as an error if the method output is not a buffer.
> Typically this
> + * means that the method called is unsupported.
> +*/
> +static int asus_wmi_evaluate_method_buf(u32 method_id,
> + u32 arg0, u32 arg1, u8 **ret_buffer)
> +{
> + struct bios_args args = {
> + .arg0 = arg0,
> + .arg1 = arg1,
> + .arg2 = 0,
> + };
> + struct acpi_buffer input = { (acpi_size) sizeof(args), &args };
> + struct acpi_buffer output = { ACPI_ALLOCATE_BUFFER, NULL };
> + acpi_status status;
> + union acpi_object *obj;
> + u8 *buf_tmp;
> + u32 int_tmp = 0;
> +
> + status = wmi_evaluate_method(ASUS_WMI_MGMT_GUID, 0, method_id,
> + &input, &output);
> +
> + if (ACPI_FAILURE(status))
> + return -EIO;
> +
> + obj = (union acpi_object *)output.pointer;
> +
> + if (obj && obj->type == ACPI_TYPE_INTEGER) {
> + int_tmp = (u32) obj->integer.value;
> + if (int_tmp == ASUS_WMI_UNSUPPORTED_METHOD)
> + return -ENODEV;
> +
> + return int_tmp;
> + }
> +
> + if (obj && obj->type == ACPI_TYPE_BUFFER) {
> + buf_tmp = obj->buffer.pointer;
> + }
> +
> + if (ret_buffer)
> + ret_buffer = &buf_tmp;
> +
> + kfree(obj);
> +
> + return 0;
> +}
> +
> static int asus_wmi_evaluate_method_agfn(const struct acpi_buffer
> args)
> {
> struct acpi_buffer input;
> @@ -2043,6 +2140,311 @@ static ssize_t fan_boost_mode_store(struct
> device *dev,
> // Fan boost mode: 0 - normal, 1 - overboost, 2 - silent
> static DEVICE_ATTR_RW(fan_boost_mode);
>
> +/* Custom fan curves per-profile
> **********************************************/
> +
> +static int custom_fan_check_present(struct asus_wmi *asus,
> + bool *available, u32 dev)
> +{
> + u8 *buffer;
> + int err;
> +
> + *available = false;
> +
> + /*
> + * We don't do anything with the buffer from this call except
> ensure that
> + * it is a buffer. A fail or unsupported returns an int (from ACPI)
> + */
Discovering the buffer content is underway. It looks as if the format
is the same as what we write to the ACPI method (should have been
obvious :| )
> + err = asus_wmi_evaluate_method_buf(asus->dsts_id, dev, 0, &buffer);
> + if (err) {
> + if (err == -ENODEV)
> + return 0;
> + return err;
> + }
> +
> + err = asus_wmi_evaluate_method_buf(asus->dsts_id, dev, 1, &buffer);
> + if (err) {
> + if (err == -ENODEV)
> + return 0;
> + return err;
> + }
> +
> + err = asus_wmi_evaluate_method_buf(asus->dsts_id, dev, 2, &buffer);
> + if (err) {
> + if (err == -ENODEV)
> + return 0;
> + return err;
> + }
> +
> + *available = true;
> + return 0;
> +}
> +
> +/*
> + * The expected input is of the format
> + * "30:1,49:2,59:3,69:4,79:31,89:49,99:56,109:58"
> + * where a pair is 30:1, with 30 = temperature, and 1 = percentage
> +*/
> +static int fan_curve_check_valid(const char *curve)
> +{
> + char * buf, *set, *set_end, *pair_tmp, *pair, *pair_end;
> + int err, ret;
> +
> + char *set_delimiter = ",";
> + char *pair_delimiter = ":";
> + bool pair_start = true;
> + u32 prev_percent = 0;
> + u32 prev_temp = 0;
> + u32 percent = 0;
> + u32 temp = 0;
> +
> + buf = set_end = pair_end = kstrdup(curve, GFP_KERNEL);
> +
> + while( (set = strsep(&set_end, set_delimiter)) != NULL ) {
> + pair_tmp = kstrdup(set, GFP_KERNEL);
> + pair_start = true;
> + while( (pair = strsep(&pair_tmp, pair_delimiter)) != NULL ) {
> + err = kstrtouint(pair, 10, &ret);
> + if (err) {
> + kfree(pair_tmp);
> + kfree(buf);
> + return err;
> + }
> +
> + if (pair_start) {
> + temp = ret;
> + pair_start = false;
> + } else {
> + percent = ret;
> + }
> + }
> + kfree(pair_tmp);
> +
> + if (temp < prev_temp || percent < prev_percent || percent >
> 100) {
> + pr_info("Fan curve invalid");
> + pr_info("A value is sequentially lower or percentage is > 100");
> + kfree(buf);
> + return -EINVAL;
> + }
> +
> + prev_temp = temp;
> + prev_percent = percent;
> + }
> + kfree(buf);
> +
> + return 0;
> +}
> +
> +static int fan_curve_write(struct asus_wmi *asus, u32 dev, char
> *curve)
> +{
> + char * buf, *set, *pair_tmp, *pair, *set_end, *pair_end;
> + int err, ret;
> +
> + char *set_delimiter = ",";
> + char *pair_delimiter = ":";
> + bool half_complete = false;
> + bool pair_start = true;
> + u32 percent = 0;
> + u32 shift = 0;
> + u32 temp = 0;
> + u32 arg1 = 0;
> + u32 arg2 = 0;
> + u32 arg3 = 0;
> + u32 arg4 = 0;
> +
> + buf = set_end = pair_end = kstrdup(curve, GFP_KERNEL);
> +
> + while( (set = strsep(&set_end, set_delimiter)) != NULL ) {
> + pair_tmp = kstrdup(set, GFP_KERNEL);
> + pair_start = true;
> + while( (pair = strsep(&pair_tmp, pair_delimiter)) != NULL ) {
> + err = kstrtouint(pair, 10, &ret);
> + if (err) {
> + kfree(pair_tmp);
> + kfree(buf);
> + return err;
> + }
> +
> + if (pair_start) {
> + temp = ret;
> + pair_start = false;
> + } else {
> + percent = ret;
> + }
> + }
> + kfree(pair_tmp);
> +
> + if (!half_complete) {
> + arg1 += temp << shift;
> + arg3 += percent << shift;
> + } else {
> + arg2 += temp << shift;
> + arg4 += percent << shift;
> + }
> + shift += 8;
> +
> + if (shift == 32) {
> + shift = 0;
> + half_complete = true;
> + }
> + }
> + kfree(buf);
> +
> + return asus_wmi_evaluate_method5(ASUS_WMI_METHODID_DEVS, dev,
> + arg1, arg2, arg3, arg4, &ret);
> +}
> +
> +static ssize_t fan_curve_store(struct asus_wmi *asus, const char
> *buf,
> + size_t count, u32 dev, char **curve,
> + u32 throttle_policy)
> +{
> + int err;
> +
> + /* Allow a user to write "" or " " to erase a curve setting */
> + if (strlen(buf) <= 1 || strcmp(buf, " \n") == 0) {
> + kfree(*curve);
> + *curve = NULL;
I'll be adding a call to throttle_thermal_policy_write() here to ensure
the EC resets the fan defaults for the current mode.
> >
> ---
> drivers/platform/x86/asus-wmi.c | 498
> +++++++++++++++++++++
> include/linux/platform_data/x86/asus-wmi.h | 2 +
> 2 files changed, 500 insertions(+)
>
> diff --git a/drivers/platform/x86/asus-wmi.c
> b/drivers/platform/x86/asus-wmi.c
> index cc5811844012..dd107301cc1e 100644
> --- a/drivers/platform/x86/asus-wmi.c
> +++ b/drivers/platform/x86/asus-wmi.c
> @@ -122,6 +122,7 @@ struct bios_args {
> u32 arg0;
> u32 arg1;
> u32 arg2; /* At least TUF Gaming series uses 3 dword input buffer.
> */
> + u32 arg3;
> u32 arg4;
> u32 arg5;
> } __packed;
> @@ -173,6 +174,12 @@ enum fan_type {
> FAN_TYPE_SPEC83, /* starting in Spec 8.3, use CPU_FAN_CTRL */
> };
>
> +struct fan_curve {
> + char *balanced;
> + char *performance;
> + char *quiet;
> +};
> +
> struct asus_wmi {
> int dsts_id;
> int spec;
> @@ -220,6 +227,12 @@ struct asus_wmi {
> bool throttle_thermal_policy_available;
> u8 throttle_thermal_policy_mode;
>
> + bool cpu_fan_curve_available;
> + struct fan_curve cpu_fan_curve;
> +
> + bool gpu_fan_curve_available;
> + struct fan_curve gpu_fan_curve;
> +
> struct platform_profile_handler platform_profile_handler;
> bool platform_profile_support;
>
> @@ -285,6 +298,90 @@ int asus_wmi_evaluate_method(u32 method_id, u32
> arg0, u32 arg1, u32 *retval)
> }
> EXPORT_SYMBOL_GPL(asus_wmi_evaluate_method);
>
> +static int asus_wmi_evaluate_method5(u32 method_id,
> + u32 arg0, u32 arg1, u32 arg2, u32 arg3, u32 arg4, u32 *retval)
> +{
> + struct bios_args args = {
> + .arg0 = arg0,
> + .arg1 = arg1,
> + .arg2 = arg2,
> + .arg3 = arg3,
> + .arg4 = arg4,
> + };
> + struct acpi_buffer input = { (acpi_size) sizeof(args), &args };
> + struct acpi_buffer output = { ACPI_ALLOCATE_BUFFER, NULL };
> + acpi_status status;
> + union acpi_object *obj;
> + u32 tmp = 0;
> +
> + status = wmi_evaluate_method(ASUS_WMI_MGMT_GUID, 0, method_id,
> + &input, &output);
> +
> + if (ACPI_FAILURE(status))
> + return -EIO;
> +
> + obj = (union acpi_object *)output.pointer;
> + if (obj && obj->type == ACPI_TYPE_INTEGER)
> + tmp = (u32) obj->integer.value;
> +
> + if (retval)
> + *retval = tmp;
> +
> + kfree(obj);
> +
> + if (tmp == ASUS_WMI_UNSUPPORTED_METHOD)
> + return -ENODEV;
> +
> + return 0;
> +}
> +
> +/*
> + * Returns as an error if the method output is not a buffer.
> Typically this
> + * means that the method called is unsupported.
> +*/
> +static int asus_wmi_evaluate_method_buf(u32 method_id,
> + u32 arg0, u32 arg1, u8 **ret_buffer)
> +{
> + struct bios_args args = {
> + .arg0 = arg0,
> + .arg1 = arg1,
> + .arg2 = 0,
> + };
> + struct acpi_buffer input = { (acpi_size) sizeof(args), &args };
> + struct acpi_buffer output = { ACPI_ALLOCATE_BUFFER, NULL };
> + acpi_status status;
> + union acpi_object *obj;
> + u8 *buf_tmp;
> + u32 int_tmp = 0;
> +
> + status = wmi_evaluate_method(ASUS_WMI_MGMT_GUID, 0, method_id,
> + &input, &output);
> +
> + if (ACPI_FAILURE(status))
> + return -EIO;
> +
> + obj = (union acpi_object *)output.pointer;
> +
> + if (obj && obj->type == ACPI_TYPE_INTEGER) {
> + int_tmp = (u32) obj->integer.value;
> + if (int_tmp == ASUS_WMI_UNSUPPORTED_METHOD)
> + return -ENODEV;
> +
> + return int_tmp;
> + }
> +
> + if (obj && obj->type == ACPI_TYPE_BUFFER) {
> + buf_tmp = obj->buffer.pointer;
> + }
> +
> + if (ret_buffer)
> + ret_buffer = &buf_tmp;
> +
> + kfree(obj);
> +
> + return 0;
> +}
> +
> static int asus_wmi_evaluate_method_agfn(const struct acpi_buffer
> args)
> {
> struct acpi_buffer input;
> @@ -2043,6 +2140,311 @@ static ssize_t fan_boost_mode_store(struct
> device *dev,
> // Fan boost mode: 0 - normal, 1 - overboost, 2 - silent
> static DEVICE_ATTR_RW(fan_boost_mode);
>
> +/* Custom fan curves per-profile
> **********************************************/
> +
> +static int custom_fan_check_present(struct asus_wmi *asus,
> + bool *available, u32 dev)
> +{
> + u8 *buffer;
> + int err;
> +
> + *available = false;
> +
> + /*
> + * We don't do anything with the buffer from this call except
> ensure that
> + * it is a buffer. A fail or unsupported returns an int (from ACPI)
> + */
> + err = asus_wmi_evaluate_method_buf(asus->dsts_id, dev, 0, &buffer);
> + if (err) {
> + if (err == -ENODEV)
> + return 0;
> + return err;
> + }
> +
> + err = asus_wmi_evaluate_method_buf(asus->dsts_id, dev, 1, &buffer);
> + if (err) {
> + if (err == -ENODEV)
> + return 0;
> + return err;
> + }
> +
> + err = asus_wmi_evaluate_method_buf(asus->dsts_id, dev, 2, &buffer);
> + if (err) {
> + if (err == -ENODEV)
> + return 0;
> + return err;
> + }
> +
> + *available = true;
> + return 0;
> +}
> +
> +/*
> + * The expected input is of the format
> + * "30:1,49:2,59:3,69:4,79:31,89:49,99:56,109:58"
> + * where a pair is 30:1, with 30 = temperature, and 1 = percentage
> +*/
> +static int fan_curve_check_valid(const char *curve)
> +{
> + char * buf, *set, *set_end, *pair_tmp, *pair, *pair_end;
> + int err, ret;
> +
> + char *set_delimiter = ",";
> + char *pair_delimiter = ":";
> + bool pair_start = true;
> + u32 prev_percent = 0;
> + u32 prev_temp = 0;
> + u32 percent = 0;
> + u32 temp = 0;
> +
> + buf = set_end = pair_end = kstrdup(curve, GFP_KERNEL);
> +
> + while( (set = strsep(&set_end, set_delimiter)) != NULL ) {
> + pair_tmp = kstrdup(set, GFP_KERNEL);
> + pair_start = true;
> + while( (pair = strsep(&pair_tmp, pair_delimiter)) != NULL ) {
> + err = kstrtouint(pair, 10, &ret);
> + if (err) {
> + kfree(pair_tmp);
> + kfree(buf);
> + return err;
> + }
> +
> + if (pair_start) {
> + temp = ret;
> + pair_start = false;
> + } else {
> + percent = ret;
> + }
> + }
> + kfree(pair_tmp);
> +
> + if (temp < prev_temp || percent < prev_percent || percent >
> 100) {
> + pr_info("Fan curve invalid");
> + pr_info("A value is sequentially lower or percentage is > 100");
> + kfree(buf);
> + return -EINVAL;
> + }
> +
> + prev_temp = temp;
> + prev_percent = percent;
> + }
> + kfree(buf);
> +
> + return 0;
> +}
> +
> +static int fan_curve_write(struct asus_wmi *asus, u32 dev, char
> *curve)
> +{
> + char * buf, *set, *pair_tmp, *pair, *set_end, *pair_end;
> + int err, ret;
> +
> + char *set_delimiter = ",";
> + char *pair_delimiter = ":";
> + bool half_complete = false;
> + bool pair_start = true;
> + u32 percent = 0;
> + u32 shift = 0;
> + u32 temp = 0;
> + u32 arg1 = 0;
> + u32 arg2 = 0;
> + u32 arg3 = 0;
> + u32 arg4 = 0;
> +
> + buf = set_end = pair_end = kstrdup(curve, GFP_KERNEL);
> +
> + while( (set = strsep(&set_end, set_delimiter)) != NULL ) {
> + pair_tmp = kstrdup(set, GFP_KERNEL);
> + pair_start = true;
> + while( (pair = strsep(&pair_tmp, pair_delimiter)) != NULL ) {
> + err = kstrtouint(pair, 10, &ret);
> + if (err) {
> + kfree(pair_tmp);
> + kfree(buf);
> + return err;
> + }
> +
> + if (pair_start) {
> + temp = ret;
> + pair_start = false;
> + } else {
> + percent = ret;
> + }
> + }
> + kfree(pair_tmp);
> +
> + if (!half_complete) {
> + arg1 += temp << shift;
> + arg3 += percent << shift;
> + } else {
> + arg2 += temp << shift;
> + arg4 += percent << shift;
> + }
> + shift += 8;
> +
> + if (shift == 32) {
> + shift = 0;
> + half_complete = true;
> + }
> + }
> + kfree(buf);
> +
> + return asus_wmi_evaluate_method5(ASUS_WMI_METHODID_DEVS, dev,
> + arg1, arg2, arg3, arg4, &ret);
> +}
> +
> +static ssize_t fan_curve_store(struct asus_wmi *asus, const char
> *buf,
> + size_t count, u32 dev, char **curve,
> + u32 throttle_policy)
> +{
> + int err;
> +
> + /* Allow a user to write "" or " " to erase a curve setting */
> + if (strlen(buf) <= 1 || strcmp(buf, " \n") == 0) {
> + kfree(*curve);
> + *curve = NULL;
> + return count;
> + }
> if (attr == &dev_attr_cpu_fan_curve_performance.attr)
> + ok = asus->cpu_fan_curve_available;
> + else if (attr == &dev_attr_cpu_fan_curve_quiet.attr)
> + ok = asus->cpu_fan_curve_available;
> + else if (attr == &dev_attr_gpu_fan_curve_balanced.attr)
> + ok = asus->gpu_fan_curve_available;
> + else if (attr == &dev_attr_gpu_fan_curve_performance.attr)
> + ok = asus->gpu_fan_curve_available;
> + else if (attr == &dev_attr_gpu_fan_curve_quiet.attr)
> + ok = asus->gpu_fan_curve_available;
> else if (attr == &dev_attr_panel_od.attr)
> ok = asus->panel_overdrive_available;
>
> @@ -3016,6 +3503,16 @@ static int asus_wmi_add(struct platform_device
> *pdev)
> else
> throttle_thermal_policy_set_default(asus);
>
> + err = custom_fan_check_present(asus, &asus->cpu_fan_curve_available,
> + ASUS_WMI_DEVID_CPU_FAN_CURVE);
> + if (err)
> + goto fail_throttle_fan_curve;
> +
> + err = custom_fan_check_present(asus,
> &asus->gpu_fan_curve_available,
> + ASUS_WMI_DEVID_GPU_FAN_CURVE);
> + if (err)
> + goto fail_throttle_fan_curve;
> +
> err = platform_profile_setup(asus);
> if (err)
> goto fail_platform_profile_setup;
> @@ -3109,6 +3606,7 @@ static int asus_wmi_add(struct platform_device
> *pdev)
> asus_wmi_sysfs_exit(asus->platform_device);
> fail_sysfs:
> fail_throttle_thermal_policy:
> +fail_throttle_fan_curve:
> fail_platform_profile_setup:
> if (asus->platform_profile_support)
> platform_profile_remove();
> diff --git a/include/linux/platform_data/x86/asus-wmi.h
> b/include/linux/platform_data/x86/asus-wmi.h
> index 17dc5cb6f3f2..a571b47ff362 100644
> --- a/include/linux/platform_data/x86/asus-wmi.h
> +++ b/include/linux/platform_data/x86/asus-wmi.h
> @@ -77,6 +77,8 @@
> #define ASUS_WMI_DEVID_THERMAL_CTRL 0x00110011
> #define ASUS_WMI_DEVID_FAN_CTRL 0x00110012 /* deprecated */
> #define ASUS_WMI_DEVID_CPU_FAN_CTRL 0x00110013
> +#define ASUS_WMI_DEVID_CPU_FAN_CURVE 0x00110024
> +#define ASUS_WMI_DEVID_GPU_FAN_CURVE 0x00110025
>
> /* Power */
> #define ASUS_WMI_DEVID_PROCESSOR_STATE 0x00120012
> --
> 2.31.1
>
prev parent reply other threads:[~2021-08-19 7:27 UTC|newest]
Thread overview: 3+ messages / expand[flat|nested] mbox.gz Atom feed top
2021-08-19 1:17 [PATCH v2 0/1] asus-wmi: Add support for custom fan curves Luke D. Jones
2021-08-19 1:17 ` [PATCH v2 1/1] " Luke D. Jones
2021-08-19 7:26 ` Luke Jones [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=OOS2YQ.5V2ZXNMIG4DC3@ljones.dev \
--to=luke@ljones.dev \
--cc=hadess@hadess.net \
--cc=hdegoede@redhat.com \
--cc=linux-kernel@vger.kernel.org \
--cc=platform-driver-x86@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 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.