Re: [PATCH] ACPI / Processor: add sysfs support for low power idle

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

 



On Fri, May 19, 2017 at 9:19 PM, Prashanth Prakash
<pprakash@xxxxxxxxxxxxxx> wrote:
> Add support to expose idle statistics maintained by platform to
> userspace via sysfs in addition to other data of interest from
> each LPI(Low Power Idle) state.
>
> LPI described in section 8.4.4 of ACPI spec 6.1 provides different
> methods to obtain idle statistics maintained by the platform. These
> show a granular view of how each of the LPI state is being used at
> different level of hierarchy. sysfs data is exposed at each level in
> the hierarchy by creating a directory named 'lpi' at each level and
> the LPI state information is presented under it. Below is the
> representation of LPI information at one such level in the hierarchy
>
> .../ACPI00XX: XX/lpi
>         |-> state0
>         |       |-> desc
>         |       |-> time
>         |       |-> usage
>         |       |-> latency
>         |       |-> min_residency
>         |
>         <<more states>>
>
> ACPI00XX can be ACPI0007(processor) or ACPI0010(processor container)
>
> stateX contains information related to a specific LPI state defined
> in the LPI ACPI tables.

That needs to be documented under Documentation/ABI/.

>
> Signed-off-by: Prashanth Prakash <pprakash@xxxxxxxxxxxxxx>
> ---
> v1
> * Drop flags, arch_flags and summary_stats field (Sudeep)
> * Create sysfs entries after we know that LPI will be used (Sudeep & Rafael)
>
>  drivers/acpi/processor_idle.c | 343 +++++++++++++++++++++++++++++++++++++++++-
>  include/acpi/processor.h      |  14 ++
>  2 files changed, 355 insertions(+), 2 deletions(-)
>
> diff --git a/drivers/acpi/processor_idle.c b/drivers/acpi/processor_idle.c
> index 5c8aa9c..d5c4d94 100644
> --- a/drivers/acpi/processor_idle.c
> +++ b/drivers/acpi/processor_idle.c
> @@ -940,6 +940,10 @@ struct acpi_lpi_states_array {
>         struct acpi_lpi_state *composite_states[ACPI_PROCESSOR_MAX_POWER];
>  };
>
> +static int acpi_lpi_sysfs_init(acpi_handle h,
> +                       struct acpi_lpi_states_array *info);
> +static int acpi_lpi_sysfs_exit(struct acpi_processor *pr);
> +
>  static int obj_get_integer(union acpi_object *obj, u32 *value)
>  {
>         if (obj->type != ACPI_TYPE_INTEGER)
> @@ -949,6 +953,24 @@ static int obj_get_integer(union acpi_object *obj, u32 *value)
>         return 0;
>  }
>
> +static int obj_get_generic_addr(union acpi_object *obj,
> +                               struct acpi_generic_address *addr)
> +{
> +       struct acpi_power_register *reg;
> +
> +       if (obj->type != ACPI_TYPE_BUFFER)
> +               return -EINVAL;
> +
> +       reg = (struct acpi_power_register *)obj->buffer.pointer;
> +       addr->space_id = reg->space_id;
> +       addr->bit_width = reg->bit_width;
> +       addr->bit_offset = reg->bit_offset;
> +       addr->access_width = reg->access_size;
> +       addr->address = reg->address;
> +
> +       return 0;
> +}
> +
>  static int acpi_processor_evaluate_lpi(acpi_handle handle,
>                                        struct acpi_lpi_states_array *info)
>  {
> @@ -1023,8 +1045,6 @@ static int acpi_processor_evaluate_lpi(acpi_handle handle,
>                         continue;
>                 }
>
> -               /* elements[7,8] skipped for now i.e. Residency/Usage counter*/
> -
>                 obj = pkg_elem + 9;
>                 if (obj->type == ACPI_TYPE_STRING)
>                         strlcpy(lpi_state->desc, obj->string.pointer,
> @@ -1052,9 +1072,16 @@ static int acpi_processor_evaluate_lpi(acpi_handle handle,
>
>                 if (obj_get_integer(pkg_elem + 5, &lpi_state->enable_parent_state))
>                         lpi_state->enable_parent_state = 0;
> +
> +               obj_get_generic_addr(pkg_elem + 7, &lpi_state->res_cntr);
> +
> +               obj_get_generic_addr(pkg_elem + 8, &lpi_state->usage_cntr);
>         }
>
>         acpi_handle_debug(handle, "Found %d power states\n", state_idx);
> +
> +       /* Set up LPI sysfs */
> +       acpi_lpi_sysfs_init(handle, info);
>  end:
>         kfree(buffer.pointer);
>         return ret;
> @@ -1166,6 +1193,10 @@ static int acpi_processor_get_lpi_info(struct acpi_processor *pr)
>         if (!acpi_has_method(handle, "_LPI"))
>                 return -EINVAL;
>
> +       /* If we have already initialized just return */
> +       if (pr->flags.has_lpi == 1)
> +               return 0;
> +
>         flat_state_cnt = 0;
>         prev = &info[0];
>         curr = &info[1];
> @@ -1477,8 +1508,316 @@ int acpi_processor_power_exit(struct acpi_processor *pr)
>                 acpi_processor_registered--;
>                 if (acpi_processor_registered == 0)
>                         cpuidle_unregister_driver(&acpi_idle_driver);
> +
> +               acpi_lpi_sysfs_exit(pr);
>         }
>
>         pr->flags.power_setup_done = 0;
>         return 0;
>  }
> +
> +
> +/*
> + * LPI sysfs support
> + */
> +
> +struct acpi_lpi_attr {
> +       struct attribute attr;
> +       ssize_t (*show)(struct kobject *kobj, struct attribute *attr,
> +                       char *buf);
> +       ssize_t (*store)(struct kobject *kobj, struct attribute *attr,
> +                       const char *c, ssize_t count);
> +};
> +
> +#define define_lpi_ro(_name) static struct acpi_lpi_attr _name =       \
> +               __ATTR(_name, 0444, show_##_name, NULL)
> +
> +#define to_acpi_lpi_sysfs_state(k)                             \
> +       container_of(k, struct acpi_lpi_sysfs_state, kobj)
> +
> +#define to_acpi_lpi_state(k)                           \
> +       (&(to_acpi_lpi_sysfs_state(k)->lpi_state))
> +
> +static ssize_t show_desc(struct kobject *kobj, struct attribute *attr,
> +                       char *buf)
> +{
> +       struct acpi_lpi_state *lpi = to_acpi_lpi_state(kobj);
> +
> +       return scnprintf(buf, PAGE_SIZE, "%s\n", lpi->desc);
> +}
> +define_lpi_ro(desc);
> +
> +static int acpi_lpi_get_time(struct acpi_lpi_state *lpi, u64 *val)
> +{
> +       struct acpi_generic_address *reg;
> +
> +       if (!lpi)
> +               return -EFAULT;
> +
> +       reg = &lpi->res_cntr;
> +
> +       /* Supporting only system memory */
> +       if (reg->space_id != ACPI_ADR_SPACE_SYSTEM_MEMORY ||
> +               !(lpi->flags & ACPI_LPI_STATE_FLAGS_ENABLED) ||
> +               !reg->address || !lpi->res_cnt_freq)
> +               return -EINVAL;
> +
> +       if (ACPI_FAILURE(acpi_read(val, reg)))
> +               return -EFAULT;
> +
> +       *val = div_u64((*val * 1000000), lpi->res_cnt_freq);
> +       return 0;
> +
> +}
> +
> +/* shows residency in us */
> +static ssize_t show_time(struct kobject *kobj, struct attribute *attr,
> +                       char *buf)
> +{
> +       struct acpi_lpi_state *lpi = to_acpi_lpi_state(kobj);
> +       u64 val = 0;
> +       int ret;
> +
> +       ret = acpi_lpi_get_time(lpi, &val);
> +
> +       if (ret == -EINVAL)
> +               return scnprintf(buf, PAGE_SIZE, "<unsupported>\n");
> +
> +       if (ret)
> +               return ret;
> +
> +       return scnprintf(buf, PAGE_SIZE, "%llu\n", val);
> +}
> +define_lpi_ro(time);
> +
> +static int acpi_lpi_get_usage(struct acpi_lpi_state *lpi, u64 *val)
> +{
> +       struct acpi_generic_address *reg;
> +
> +       if (!lpi)
> +               return -EFAULT;
> +
> +       reg = &lpi->usage_cntr;
> +
> +       /* Supporting only system memory now (FFH not supported) */
> +       if (reg->space_id != ACPI_ADR_SPACE_SYSTEM_MEMORY ||
> +               !(lpi->flags & ACPI_LPI_STATE_FLAGS_ENABLED) ||
> +               !reg->address)
> +               return -EINVAL;
> +
> +       if (ACPI_FAILURE(acpi_read(val, reg)))
> +               return -EFAULT;
> +
> +       return 0;
> +}
> +
> +static ssize_t show_usage(struct kobject *kobj, struct attribute *attr,
> +                       char *buf)
> +{
> +       struct acpi_lpi_state *lpi = to_acpi_lpi_state(kobj);
> +       u64 val = 0;
> +       int ret;
> +
> +       ret = acpi_lpi_get_usage(lpi, &val);
> +
> +       if (ret == -EINVAL)
> +               return scnprintf(buf, PAGE_SIZE, "<unsupported>\n");
> +
> +       if (ret)
> +               return ret;
> +
> +       return scnprintf(buf, PAGE_SIZE, "%llu\n", val);
> +}
> +define_lpi_ro(usage);
> +
> +static ssize_t show_min_residency(struct kobject *kobj, struct attribute *attr,
> +                               char *buf)
> +{
> +       struct acpi_lpi_state *lpi = to_acpi_lpi_state(kobj);
> +
> +       return scnprintf(buf, PAGE_SIZE, "%u\n", lpi->min_residency);
> +}
> +define_lpi_ro(min_residency);
> +
> +static ssize_t show_latency(struct kobject *kobj, struct attribute *attr,
> +                       char *buf)
> +{
> +       struct acpi_lpi_state *lpi = to_acpi_lpi_state(kobj);
> +
> +       return scnprintf(buf, PAGE_SIZE, "%u\n", lpi->wake_latency);
> +}
> +define_lpi_ro(latency);
> +
> +static struct attribute *acpi_lpi_state_attrs[] = {
> +       &desc.attr,
> +       &min_residency.attr,
> +       &latency.attr,
> +       &time.attr,
> +       &usage.attr,
> +       NULL
> +};
> +
> +static struct kobj_type lpi_state_ktype = {
> +       .sysfs_ops = &kobj_sysfs_ops,
> +       .default_attrs = acpi_lpi_state_attrs,
> +};
> +
> +static void acpi_lpi_sysfs_release(struct kobject *kobj)
> +{
> +       struct acpi_lpi_sysfs_data *sysfs_data =
> +               container_of(kobj, struct acpi_lpi_sysfs_data, kobj);
> +
> +       kfree(sysfs_data->sysfs_states);
> +       kfree(sysfs_data);
> +}
> +
> +static struct kobj_type lpi_device_ktype = {
> +       .sysfs_ops = &kobj_sysfs_ops,
> +       .release = acpi_lpi_sysfs_release,
> +};
> +
> +static int acpi_lpi_sysfs_get(struct acpi_lpi_sysfs_data *sysfs_data)
> +{
> +       int i;
> +
> +       if (!sysfs_data)
> +               return -EFAULT;
> +
> +       for (i = 0; i < sysfs_data->state_count; i++)
> +               kobject_get(&sysfs_data->sysfs_states[i].kobj);
> +
> +       kobject_get(&sysfs_data->kobj);
> +
> +       return 0;
> +}
> +
> +static int acpi_lpi_sysfs_put(struct acpi_lpi_sysfs_data *sysfs_data)
> +{
> +       int i;
> +
> +       if (!sysfs_data)
> +               return -EFAULT;
> +
> +       for (i = 0; i < sysfs_data->state_count; i++)
> +               kobject_put(&sysfs_data->sysfs_states[i].kobj);
> +
> +       kobject_put(&sysfs_data->kobj);
> +
> +       return 0;
> +}
> +
> +/*
> + * Given parsed LPI info creates sysfs entries to expose differnt LPI attributes
> + * stats for all the "enabled" states
> + */
> +static int acpi_lpi_sysfs_init(acpi_handle h,
> +                       struct acpi_lpi_states_array *info)
> +{
> +       struct acpi_device *d;
> +       struct acpi_lpi_sysfs_state *sysfs_state = NULL;
> +       struct acpi_lpi_sysfs_data **lpi_sysfs_data;
> +       struct acpi_lpi_sysfs_data *data = NULL;
> +       int ret, i, j;
> +
> +       if (!info)
> +               return -EINVAL;
> +
> +       ret = acpi_bus_get_device(h, &d);
> +       if (ret)
> +               return ret;
> +
> +       if (!strcmp(acpi_device_hid(d), ACPI_PROCESSOR_CONTAINER_HID))
> +               lpi_sysfs_data = (struct acpi_lpi_sysfs_data **)&d->driver_data;
> +       else {
> +               struct acpi_processor *pr = acpi_driver_data(d);
> +
> +               lpi_sysfs_data = &pr->power.lpi_sysfs_data;
> +       }
> +
> +       /* Already initialized, get a reference and return */
> +       if (*lpi_sysfs_data) {
> +               acpi_lpi_sysfs_get(*lpi_sysfs_data);
> +               return 0;
> +       }
> +
> +       data = kzalloc(sizeof(struct acpi_lpi_sysfs_data), GFP_KERNEL);
> +       if (!data) {
> +               ret = -ENOMEM;
> +               goto kfree_and_return;
> +       }
> +
> +       /* Count number of enabled states */
> +       for (i = 0; i < info->size; i++)
> +               if (info->entries[i].flags & ACPI_LPI_STATE_FLAGS_ENABLED)
> +                       data->state_count++;
> +
> +       sysfs_state = kcalloc(data->state_count,
> +                       sizeof(struct acpi_lpi_sysfs_state), GFP_KERNEL);
> +       if (!sysfs_state) {
> +               ret = -ENOMEM;
> +               goto kfree_and_return;
> +       }
> +
> +       ret = kobject_init_and_add(&data->kobj, &lpi_device_ktype, &d->dev.kobj,
> +                               "lpi");
> +       if (ret)
> +               goto kfree_and_return;
> +
> +       *lpi_sysfs_data = data;
> +       data->sysfs_states = sysfs_state;
> +
> +       for (i = 0, j = 0; i < info->size; i++) {
> +               if (!(info->entries[i].flags & ACPI_LPI_STATE_FLAGS_ENABLED))
> +                       continue;
> +               sysfs_state = data->sysfs_states + j;
> +               memcpy(&sysfs_state->lpi_state, info->entries + i,
> +                       sizeof(struct acpi_lpi_state));
> +               ret = kobject_init_and_add(&sysfs_state->kobj, &lpi_state_ktype,
> +                                       &data->kobj, "state%d", j);
> +               if (ret)
> +                       break;
> +               j++;
> +       }
> +
> +       if (ret) {
> +               while (j > 0) {
> +                       j--;
> +                       sysfs_state = data->sysfs_states + i;
> +                       kobject_put(&sysfs_state->kobj);
> +               }
> +               kobject_put(&data->kobj);
> +       } else
> +               *lpi_sysfs_data = data;
> +
> +       return ret;
> +
> +kfree_and_return:
> +       kfree(data);
> +       kfree(sysfs_state);
> +       return ret;
> +}
> +
> +static int acpi_lpi_sysfs_exit(struct acpi_processor *pr)
> +{
> +       acpi_handle handle, p_handle;
> +       struct acpi_device *d = NULL;
> +       acpi_status status;
> +
> +       if (!pr)
> +               return -ENODEV;
> +
> +       handle = pr->handle;
> +       acpi_lpi_sysfs_put(pr->power.lpi_sysfs_data);
> +
> +       status = acpi_get_parent(handle, &p_handle);
> +       while (ACPI_SUCCESS(status)) {
> +               acpi_bus_get_device(p_handle, &d);
> +               if (strcmp(acpi_device_hid(d), ACPI_PROCESSOR_CONTAINER_HID))
> +                       break;
> +
> +               acpi_lpi_sysfs_put((struct acpi_lpi_sysfs_data *)d->driver_data);
> +               status = acpi_get_parent(handle, &p_handle);
> +       }
> +
> +       return 0;
> +}
> diff --git a/include/acpi/processor.h b/include/acpi/processor.h
> index c1ba00f..b99b84b 100644
> --- a/include/acpi/processor.h
> +++ b/include/acpi/processor.h
> @@ -79,6 +79,19 @@ struct acpi_lpi_state {
>         u8 index;
>         u8 entry_method;
>         char desc[ACPI_CX_DESC_LEN];
> +       struct acpi_generic_address res_cntr;
> +       struct acpi_generic_address usage_cntr;

Is there any reason why these two cannot be of type struct acpi_power_register?

> +};
> +
> +struct acpi_lpi_sysfs_state {
> +       struct acpi_lpi_state lpi_state;
> +       struct kobject kobj;
> +};
> +
> +struct acpi_lpi_sysfs_data {
> +       u8 state_count;
> +       struct kobject kobj;
> +       struct acpi_lpi_sysfs_state *sysfs_states;
>  };
>
>  struct acpi_processor_power {
> @@ -88,6 +101,7 @@ struct acpi_processor_power {
>                 struct acpi_lpi_state lpi_states[ACPI_PROCESSOR_MAX_POWER];
>         };
>         int timer_broadcast_on_state;
> +       struct acpi_lpi_sysfs_data *lpi_sysfs_data;
>  };
>
>  /* Performance Management */
> --
> Qualcomm Datacenter Technologies on behalf of Qualcomm Technologies, Inc.
> Qualcomm Technologies, Inc. is a member of the
> Code Aurora Forum, a Linux Foundation Collaborative Project.
>
> --

Thanks,
Rafael
--
To unsubscribe from this list: send the line "unsubscribe linux-acpi" in
the body of a message to majordomo@xxxxxxxxxxxxxxx
More majordomo info at  http://vger.kernel.org/majordomo-info.html



[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