Re: [PATCH v3] ACPI / fan: Properly handle fine grain control

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



On Sat, Jan 29, 2022 at 12:51 AM Srinivas Pandruvada
<srinivas.pandruvada@xxxxxxxxxxxxxxx> wrote:
>
> When _FIF object specifies support for fine grain control, then fan speed
> can be set from 0 to 100% with the recommended minimum "step size" via
> _FSL object. Here the control value doesn't need to match any value from
> _FPS object.
>
> Currently we have a simple solution implemented which just pick maximum
> control value from _FPS to display the actual state, but this is not
> optimal when there is a big window between two control values in
> _FPS. Also there is no way to set to any speed which doesn't match
> control values in _FPS. The system firmware can start the fan at speed
> which doesn't match any control value.
>
> To support fine grain control (when supported) via thermal sysfs:
> - cooling device max state is not _FPS state count but it will be
> 100 / _FIF.step_size
> - cooling device current state is 100 / _FIF.step_size

I don't quite understand this.

The max state and the current state should not always be the same, should they?

> - cooling device set state will set the control value
> curr_state * _FIF.step_size plus any adjustment for 100%.
> By the spec, when control value do not sum to 100% because of
> _FIF.step_size, OSPM may select an appropriate ending Level increment
> to reach 100%.
>
> In addition publish attributes, which helps in implementing
> algorithm in the user space to optimize fan control. These
> attributes are presened in the same directory as the performance
> states.
> 1. Support of fine grain control
> Publish support of presence of fine grain control so that fan speed
> can be tuned correctly. This attribute is called "fine_grain_control".
> 2. fan speed
> Publish the actual fan rpm in sysfs. Knowing fan rpm is helpful to
> reduce noise level and use passive control instead. Also fan performance
> may not be same over time, so the same control value may not be enough
> to run the fan at a speed. So a feedback value of speed is helpful. This
> sysfs attribute is called "fan_speed_rpm".
>
> Signed-off-by: Srinivas Pandruvada <srinivas.pandruvada@xxxxxxxxxxxxxxx>
> ---
> v3
> Added fine_grain_control attribute.
> v2-update
> Change log is missed for v2.
> v2
> Fix for build issue as reported by Reported-by: kernel test robot <lkp@xxxxxxxxx>
>
>  .../acpi/fan_performance_states.rst           |  29 +++-
>  drivers/acpi/fan.c                            | 164 +++++++++++++++---
>  2 files changed, 164 insertions(+), 29 deletions(-)
>
> diff --git a/Documentation/admin-guide/acpi/fan_performance_states.rst b/Documentation/admin-guide/acpi/fan_performance_states.rst
> index 98fe5c333121..2aec8e89dbd9 100644
> --- a/Documentation/admin-guide/acpi/fan_performance_states.rst
> +++ b/Documentation/admin-guide/acpi/fan_performance_states.rst
> @@ -58,5 +58,30 @@ For example::
>   $cat /sys/bus/acpi/devices/INT3404:00/state1
>   25:0:3200:12500:1250
>
> -When a given field is not populated or its value provided by the platform
> -firmware is invalid, the "not-defined" string is shown instead of the value.
> +ACPI Fan Fine Grain Control
> +=============================
> +
> +When _FIF object specifies support for fine grain control, then fan speed
> +can be set from 0 to 100% with the recommended minimum "step size" via
> +_FSL object. User can adjust fan speed using thermal sysfs cooling device.
> +
> +Here use can look at fan performance states for a reference speed (speed_rpm)
> +and set it by changing cooling device cur_state. If the fine grain control
> +is supported then user can also adjust to some other speeds which are
> +not defined in the performance states.
> +
> +The support of fine grain control is presented via sysfs attribute
> +"fine_grain_control". If fine grain control is present, this attribute
> +will show "1" otherwise "0".
> +
> +This sysfs attribute is presented in the same directory as performance states.
> +
> +ACPI Fan Performance Feedback
> +=============================
> +
> +The optional _FST object provides status information for the fan device.
> +This includes field to provide current fan speed in revolutions per minute
> +at which the fan is rotating.
> +
> +This speed is presented in the sysfs using the attribute "fan_speed_rpm",
> +in the same directory as performance states.
> diff --git a/drivers/acpi/fan.c b/drivers/acpi/fan.c
> index 5cd0ceb50bc8..ab586be2d4fc 100644
> --- a/drivers/acpi/fan.c
> +++ b/drivers/acpi/fan.c
> @@ -64,12 +64,20 @@ struct acpi_fan_fif {
>         u64 low_speed_notification;
>  };
>
> +struct acpi_fan_fst {
> +       u64 revision;
> +       u64 control;
> +       u64 speed;
> +};
> +
>  struct acpi_fan {
>         bool acpi4;
>         struct acpi_fan_fif fif;
>         struct acpi_fan_fps *fps;
>         int fps_count;
>         struct thermal_cooling_device *cdev;
> +       struct device_attribute fst_speed;
> +       struct device_attribute fine_grain_control;
>  };
>
>  static struct platform_driver acpi_fan_driver = {
> @@ -89,20 +97,23 @@ static int fan_get_max_state(struct thermal_cooling_device *cdev, unsigned long
>         struct acpi_device *device = cdev->devdata;
>         struct acpi_fan *fan = acpi_driver_data(device);
>
> -       if (fan->acpi4)
> -               *state = fan->fps_count - 1;
> -       else
> +       if (fan->acpi4) {
> +               if (fan->fif.fine_grain_ctrl)
> +                       *state = 100 / (int)fan->fif.step_size;

Is it really necessary to explicitly cast fif.step_size to int?

> +               else
> +                       *state = fan->fps_count - 1;
> +       } else {
>                 *state = 1;
> +       }
> +
>         return 0;
>  }
>
> -static int fan_get_state_acpi4(struct acpi_device *device, unsigned long *state)
> +static int fan_get_fps(struct acpi_device *device, struct acpi_fan_fst *fst)

Why is this called fan_get_fps()?  I'd rather call it acpi_fan_get_fst().

>  {
>         struct acpi_buffer buffer = { ACPI_ALLOCATE_BUFFER, NULL };
> -       struct acpi_fan *fan = acpi_driver_data(device);
>         union acpi_object *obj;
>         acpi_status status;
> -       int control, i;
>
>         status = acpi_evaluate_object(device->handle, "_FST", NULL, &buffer);
>         if (ACPI_FAILURE(status)) {
> @@ -119,31 +130,51 @@ static int fan_get_state_acpi4(struct acpi_device *device, unsigned long *state)
>                 goto err;
>         }
>
> -       control = obj->package.elements[1].integer.value;
> +       fst->revision = obj->package.elements[0].integer.value;
> +       fst->control = obj->package.elements[1].integer.value;
> +       fst->speed = obj->package.elements[2].integer.value;
> +
> +       status = 0;
> +err:
> +       kfree(obj);
> +       return status;

There is some confusion regarding the error return values in this
function, would be good to fix it while doing this.

> +}
> +
> +static int fan_get_state_acpi4(struct acpi_device *device, unsigned long *state)
> +{
> +       struct acpi_fan *fan = acpi_driver_data(device);
> +       struct acpi_fan_fst fst;
> +       int status;
> +       int control, i;
> +
> +       status = fan_get_fps(device, &fst);
> +       if (status)
> +               return status;
> +
> +       control = fst.control;
> +
> +       if (fan->fif.fine_grain_ctrl) {
> +               /* This control should be same what we set using _FSL by spec */
> +               if (control > 100) {
> +                       dev_dbg(&device->dev, "Invalid control value returned\n");
> +                       return -EINVAL;

Why don't we fall back to the other method in this case?

> +               }
> +
> +               *state = control / (int)fan->fif.step_size;

Do we care about rounding errors?

Say control is 8 and step_size is 9.  Should this count as 0 or as 1?

> +               return 0;
> +       }
> +
>         for (i = 0; i < fan->fps_count; i++) {
> -               /*
> -                * When Fine Grain Control is set, return the state
> -                * corresponding to maximum fan->fps[i].control
> -                * value compared to the current speed. Here the
> -                * fan->fps[] is sorted array with increasing speed.
> -                */
> -               if (fan->fif.fine_grain_ctrl && control < fan->fps[i].control) {
> -                       i = (i > 0) ? i - 1 : 0;
> +               if (control == fan->fps[i].control)
>                         break;
> -               } else if (control == fan->fps[i].control) {
> -                       break;
> -               }
>         }
>         if (i == fan->fps_count) {
>                 dev_dbg(&device->dev, "Invalid control value returned\n");
> -               status = -EINVAL;
> -               goto err;
> +               return -EINVAL;
>         }
>
>         *state = i;
>
> -err:
> -       kfree(obj);
>         return status;
>  }
>
> @@ -187,12 +218,36 @@ static int fan_set_state_acpi4(struct acpi_device *device, unsigned long state)
>  {
>         struct acpi_fan *fan = acpi_driver_data(device);
>         acpi_status status;
> +       u64 value = state;
> +       int max_state;
> +
> +       if (fan->fif.fine_grain_ctrl)
> +               max_state = 100 / (int)fan->fif.step_size;
> +       else
> +               max_state = fan->fps_count - 1;
>
> -       if (state >= fan->fps_count)
> +       if (state > max_state)

Say step_size is 9, so max_state is 11.  state == 12 would still be valid, no?

>                 return -EINVAL;
>
> -       status = acpi_execute_simple_method(device->handle, "_FSL",
> -                                           fan->fps[state].control);
> +       if (fan->fif.fine_grain_ctrl) {
> +               int rem;
> +
> +               value *= fan->fif.step_size;

And you don't need the max_state computation for this, it's only
necessary to cap value at 100.  In which case you also wouldn't need
rem etc.

> +
> +               /*
> +                * In the event OSPM’s incremental selections of Level
> +                * using the StepSize field value do not sum to 100%,
> +                * OSPM may select an appropriate ending Level
> +                * increment to reach 100%.
> +                */
> +               rem = 100 - value;
> +               if (rem && rem < fan->fif.step_size)
> +                       value = 100;
> +       } else {
> +               value = fan->fps[state].control;
> +       }
> +
> +       status = acpi_execute_simple_method(device->handle, "_FSL", value);
>         if (ACPI_FAILURE(status)) {
>                 dev_dbg(&device->dev, "Failed to set state by _FSL\n");
>                 return status;
> @@ -258,6 +313,12 @@ static int acpi_fan_get_fif(struct acpi_device *device)
>                 status = -EINVAL;
>         }
>
> +       /* If there is a bug in step size and set as 0, change to 1 */
> +       if (!fan->fif.step_size)
> +               fan->fif.step_size = 1;
> +       /* If step size > 9, change to 9 (by spec valid values 1-9) */
> +       if (fan->fif.step_size > 9)

I would do "else if" here, because both the above conditions cannot
hold at the same time.

> +               fan->fif.step_size = 9;
>  err:
>         kfree(obj);
>         return status;
> @@ -303,6 +364,27 @@ static ssize_t show_state(struct device *dev, struct device_attribute *attr, cha
>         return count;
>  }
>
> +static ssize_t show_fan_speed(struct device *dev, struct device_attribute *attr, char *buf)
> +{
> +       struct acpi_device *acpi_dev = container_of(dev, struct acpi_device, dev);
> +       struct acpi_fan_fst fst;
> +       int status;
> +
> +       status = fan_get_fps(acpi_dev, &fst);
> +       if (status)
> +               return status;
> +
> +       return sprintf(buf, "%lld\n", fst.speed);
> +}
> +
> +static ssize_t show_fine_grain_control(struct device *dev, struct device_attribute *attr, char *buf)
> +{
> +       struct acpi_device *acpi_dev = container_of(dev, struct acpi_device, dev);
> +       struct acpi_fan *fan = acpi_driver_data(acpi_dev);
> +
> +       return sprintf(buf, "%lld\n", fan->fif.fine_grain_ctrl);
> +}
> +
>  static int acpi_fan_get_fps(struct acpi_device *device)
>  {
>         struct acpi_fan *fan = acpi_driver_data(device);
> @@ -311,15 +393,35 @@ static int acpi_fan_get_fps(struct acpi_device *device)
>         acpi_status status;
>         int i;
>
> +       sysfs_attr_init(&fan->fine_grain_control.attr);
> +       fan->fine_grain_control.show = show_fine_grain_control;
> +       fan->fine_grain_control.store = NULL;
> +       fan->fine_grain_control.attr.name = "fine_grain_control";
> +       fan->fine_grain_control.attr.mode = 0444;
> +       status = sysfs_create_file(&device->dev.kobj, &fan->fine_grain_control.attr);

I would split the creation of the new attributes into a separate file,
for clarity (and to help the review somewhat).

> +       if (status)
> +               return status;
> +
> +
> +       /* _FST is present if we are here */
> +       sysfs_attr_init(&fan->fst_speed.attr);
> +       fan->fst_speed.show = show_fan_speed;
> +       fan->fst_speed.store = NULL;
> +       fan->fst_speed.attr.name = "fan_speed_rpm";
> +       fan->fst_speed.attr.mode = 0444;
> +       status = sysfs_create_file(&device->dev.kobj, &fan->fst_speed.attr);
> +       if (status)
> +               goto rem_fine_grain_attr;
> +
>         status = acpi_evaluate_object(device->handle, "_FPS", NULL, &buffer);
>         if (ACPI_FAILURE(status))
> -               return status;
> +               goto rem_fst_attr;
>
>         obj = buffer.pointer;
>         if (!obj || obj->type != ACPI_TYPE_PACKAGE || obj->package.count < 2) {
>                 dev_err(&device->dev, "Invalid _FPS data\n");
>                 status = -EINVAL;
> -               goto err;
> +               goto rem_fst_attr;
>         }
>
>         fan->fps_count = obj->package.count - 1; /* minus revision field */
> @@ -368,6 +470,14 @@ static int acpi_fan_get_fps(struct acpi_device *device)
>
>  err:
>         kfree(obj);
> +       if (!status)
> +               return 0;
> +rem_fst_attr:
> +       sysfs_remove_file(&device->dev.kobj, &fan->fst_speed.attr);
> +
> +rem_fine_grain_attr:
> +       sysfs_remove_file(&device->dev.kobj, &fan->fine_grain_control.attr);
> +
>         return status;
>  }
>
> --
> 2.31.1
>



[Index of Archives]     [Linux IBM ACPI]     [Linux Power Management]     [Linux Kernel]     [Linux Laptop]     [Kernel Newbies]     [Share Photos]     [Security]     [Netfilter]     [Bugtraq]     [Yosemite News]     [MIPS Linux]     [ARM Linux]     [Linux Security]     [Linux RAID]     [Samba]     [Video 4 Linux]     [Device Mapper]     [Linux Resources]

  Powered by Linux