diff mbox

ACPI / Processor: add sysfs support for low power idle

Message ID 1495221578-10612-1-git-send-email-pprakash@codeaurora.org (mailing list archive)
State Changes Requested, archived
Headers show

Commit Message

Prakash, Prashanth May 19, 2017, 7:19 p.m. UTC
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.

Signed-off-by: Prashanth Prakash <pprakash@codeaurora.org>
---
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(-)

Comments

Rafael J. Wysocki June 27, 2017, 11:59 p.m. UTC | #1
On Fri, May 19, 2017 at 9:19 PM, Prashanth Prakash
<pprakash@codeaurora.org> 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@codeaurora.org>
> ---
> 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@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Prakash, Prashanth July 10, 2017, 10:47 p.m. UTC | #2
Hi Rafael,

On 6/27/2017 5:59 PM, Rafael J. Wysocki wrote:
> On Fri, May 19, 2017 at 9:19 PM, Prashanth Prakash
> <pprakash@codeaurora.org> 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/.
Sure, I will create a patch updating the ABI documentation.
>> 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?
acpi_read() API required reference to acpi_generic_address (our only use case is to read the
register), so keeping this data in acpi_generic_address makes the code little simpler.

--
Thanks,
Prashanth
--
To unsubscribe from this list: send the line "unsubscribe linux-acpi" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Rafael J. Wysocki July 12, 2017, 9:21 p.m. UTC | #3
On Monday, July 10, 2017 04:47:00 PM Prakash, Prashanth wrote:
> Hi Rafael,
> 
> On 6/27/2017 5:59 PM, Rafael J. Wysocki wrote:
> > On Fri, May 19, 2017 at 9:19 PM, Prashanth Prakash
> > <pprakash@codeaurora.org> 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/.
> Sure, I will create a patch updating the ABI documentation.

No, please update the documentation in this patch.

> >> 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?
> acpi_read() API required reference to acpi_generic_address (our only use case is to read the
> register), so keeping this data in acpi_generic_address makes the code little simpler.

OK, fair enough.

Thanks,
Rafael

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

Patch

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;
+};
+
+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 */