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