/* * * (C) COPYRIGHT 2015-2016 ARM Limited. All rights reserved. * * This program is free software and is provided to you under the terms of the * GNU General Public License version 2 as published by the Free Software * Foundation, and any use by you of this program is subject to the terms * of such GNU licence. * * A copy of the licence is included with the program, and can also be obtained * from Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. * */ #include #include #include #define NR_IPA_GROUPS 8 struct kbase_ipa_context; /** * struct ipa_group - represents a single IPA group * @name: name of the IPA group * @capacitance: capacitance constant for IPA group * @calc_power: function to calculate power for IPA group */ struct ipa_group { const char *name; u32 capacitance; u32 (*calc_power)(struct kbase_ipa_context *, struct ipa_group *); }; #include /** * struct kbase_ipa_context - IPA context per device * @kbdev: pointer to kbase device * @groups: array of IPA groups for this context * @vinstr_cli: vinstr client handle * @vinstr_buffer: buffer to dump hardware counters onto * @ipa_lock: protects the entire IPA context */ struct kbase_ipa_context { struct kbase_device *kbdev; struct ipa_group groups[NR_IPA_GROUPS]; struct kbase_vinstr_client *vinstr_cli; void *vinstr_buffer; struct mutex ipa_lock; }; static ssize_t show_ipa_group(struct device *dev, struct device_attribute *attr, char *buf) { struct kbase_device *kbdev = dev_get_drvdata(dev); struct kbase_ipa_context *ctx = kbdev->ipa_ctx; ssize_t count = -EINVAL; size_t i; mutex_lock(&ctx->ipa_lock); for (i = 0; i < ARRAY_SIZE(ctx->groups); i++) { if (!strcmp(ctx->groups[i].name, attr->attr.name)) { count = snprintf(buf, PAGE_SIZE, "%lu\n", (unsigned long)ctx->groups[i].capacitance); break; } } mutex_unlock(&ctx->ipa_lock); return count; } static ssize_t set_ipa_group(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) { struct kbase_device *kbdev = dev_get_drvdata(dev); struct kbase_ipa_context *ctx = kbdev->ipa_ctx; unsigned long capacitance; size_t i; int err; err = kstrtoul(buf, 0, &capacitance); if (err < 0) return err; if (capacitance > U32_MAX) return -ERANGE; mutex_lock(&ctx->ipa_lock); for (i = 0; i < ARRAY_SIZE(ctx->groups); i++) { if (!strcmp(ctx->groups[i].name, attr->attr.name)) { ctx->groups[i].capacitance = capacitance; mutex_unlock(&ctx->ipa_lock); return count; } } mutex_unlock(&ctx->ipa_lock); return -EINVAL; } static DEVICE_ATTR(group0, S_IRUGO | S_IWUSR, show_ipa_group, set_ipa_group); static DEVICE_ATTR(group1, S_IRUGO | S_IWUSR, show_ipa_group, set_ipa_group); static DEVICE_ATTR(group2, S_IRUGO | S_IWUSR, show_ipa_group, set_ipa_group); static DEVICE_ATTR(group3, S_IRUGO | S_IWUSR, show_ipa_group, set_ipa_group); static DEVICE_ATTR(group4, S_IRUGO | S_IWUSR, show_ipa_group, set_ipa_group); static DEVICE_ATTR(group5, S_IRUGO | S_IWUSR, show_ipa_group, set_ipa_group); static DEVICE_ATTR(group6, S_IRUGO | S_IWUSR, show_ipa_group, set_ipa_group); static DEVICE_ATTR(group7, S_IRUGO | S_IWUSR, show_ipa_group, set_ipa_group); static struct attribute *kbase_ipa_attrs[] = { &dev_attr_group0.attr, &dev_attr_group1.attr, &dev_attr_group2.attr, &dev_attr_group3.attr, &dev_attr_group4.attr, &dev_attr_group5.attr, &dev_attr_group6.attr, &dev_attr_group7.attr, NULL, }; static struct attribute_group kbase_ipa_attr_group = { .name = "ipa", .attrs = kbase_ipa_attrs, }; static void init_ipa_groups(struct kbase_ipa_context *ctx) { memcpy(ctx->groups, ipa_groups_def, sizeof(ctx->groups)); } #if defined(CONFIG_OF) && (LINUX_VERSION_CODE >= KERNEL_VERSION(3, 7, 0)) static int update_ipa_groups_from_dt(struct kbase_ipa_context *ctx) { struct kbase_device *kbdev = ctx->kbdev; struct device_node *np, *child; struct ipa_group *group; size_t nr_groups; size_t i; int err; np = of_get_child_by_name(kbdev->dev->of_node, "ipa-groups"); if (!np) return 0; nr_groups = 0; for_each_available_child_of_node(np, child) nr_groups++; if (!nr_groups || nr_groups > ARRAY_SIZE(ctx->groups)) { dev_err(kbdev->dev, "invalid number of IPA groups: %zu", nr_groups); err = -EINVAL; goto err0; } for_each_available_child_of_node(np, child) { const char *name; u32 capacitance; name = of_get_property(child, "label", NULL); if (!name) { dev_err(kbdev->dev, "label missing for IPA group"); err = -EINVAL; goto err0; } err = of_property_read_u32(child, "capacitance", &capacitance); if (err < 0) { dev_err(kbdev->dev, "capacitance missing for IPA group"); goto err0; } for (i = 0; i < ARRAY_SIZE(ctx->groups); i++) { group = &ctx->groups[i]; if (!strcmp(group->name, name)) { group->capacitance = capacitance; break; } } } of_node_put(np); return 0; err0: of_node_put(np); return err; } #else static int update_ipa_groups_from_dt(struct kbase_ipa_context *ctx) { return 0; } #endif static int reset_ipa_groups(struct kbase_ipa_context *ctx) { init_ipa_groups(ctx); return update_ipa_groups_from_dt(ctx); } static inline u32 read_hwcnt(struct kbase_ipa_context *ctx, u32 offset) { u8 *p = ctx->vinstr_buffer; return *(u32 *)&p[offset]; } static inline u32 add_saturate(u32 a, u32 b) { if (U32_MAX - a < b) return U32_MAX; return a + b; } /* * Calculate power estimation based on hardware counter `c' * across all shader cores. */ static u32 calc_power_sc_single(struct kbase_ipa_context *ctx, struct ipa_group *group, u32 c) { struct kbase_device *kbdev = ctx->kbdev; u64 core_mask; u32 base = 0, r = 0; core_mask = kbdev->gpu_props.props.coherency_info.group[0].core_mask; while (core_mask != 0ull) { if ((core_mask & 1ull) != 0ull) { u64 n = read_hwcnt(ctx, base + c); u32 d = read_hwcnt(ctx, GPU_ACTIVE); u32 s = group->capacitance; r = add_saturate(r, div_u64(n * s, d)); } base += NR_CNT_PER_BLOCK * NR_BYTES_PER_CNT; core_mask >>= 1; } return r; } /* * Calculate power estimation based on hardware counter `c1' * and `c2' across all shader cores. */ static u32 calc_power_sc_double(struct kbase_ipa_context *ctx, struct ipa_group *group, u32 c1, u32 c2) { struct kbase_device *kbdev = ctx->kbdev; u64 core_mask; u32 base = 0, r = 0; core_mask = kbdev->gpu_props.props.coherency_info.group[0].core_mask; while (core_mask != 0ull) { if ((core_mask & 1ull) != 0ull) { u64 n = read_hwcnt(ctx, base + c1); u32 d = read_hwcnt(ctx, GPU_ACTIVE); u32 s = group->capacitance; r = add_saturate(r, div_u64(n * s, d)); n = read_hwcnt(ctx, base + c2); r = add_saturate(r, div_u64(n * s, d)); } base += NR_CNT_PER_BLOCK * NR_BYTES_PER_CNT; core_mask >>= 1; } return r; } static u32 calc_power_single(struct kbase_ipa_context *ctx, struct ipa_group *group, u32 c) { u64 n = read_hwcnt(ctx, c); u32 d = read_hwcnt(ctx, GPU_ACTIVE); u32 s = group->capacitance; return div_u64(n * s, d); } static u32 calc_power_group0(struct kbase_ipa_context *ctx, struct ipa_group *group) { return calc_power_single(ctx, group, L2_ANY_LOOKUP); } static u32 calc_power_group1(struct kbase_ipa_context *ctx, struct ipa_group *group) { return calc_power_single(ctx, group, TILER_ACTIVE); } static u32 calc_power_group2(struct kbase_ipa_context *ctx, struct ipa_group *group) { return calc_power_sc_single(ctx, group, FRAG_ACTIVE); } static u32 calc_power_group3(struct kbase_ipa_context *ctx, struct ipa_group *group) { return calc_power_sc_double(ctx, group, VARY_SLOT_32, VARY_SLOT_16); } static u32 calc_power_group4(struct kbase_ipa_context *ctx, struct ipa_group *group) { return calc_power_sc_single(ctx, group, TEX_COORD_ISSUE); } static u32 calc_power_group5(struct kbase_ipa_context *ctx, struct ipa_group *group) { return calc_power_sc_single(ctx, group, EXEC_INSTR_COUNT); } static u32 calc_power_group6(struct kbase_ipa_context *ctx, struct ipa_group *group) { return calc_power_sc_double(ctx, group, BEATS_RD_LSC, BEATS_WR_LSC); } static u32 calc_power_group7(struct kbase_ipa_context *ctx, struct ipa_group *group) { return calc_power_sc_single(ctx, group, EXEC_CORE_ACTIVE); } static int attach_vinstr(struct kbase_ipa_context *ctx) { struct kbase_device *kbdev = ctx->kbdev; struct kbase_uk_hwcnt_reader_setup setup; size_t dump_size; dump_size = kbase_vinstr_dump_size(kbdev); ctx->vinstr_buffer = kzalloc(dump_size, GFP_KERNEL); if (!ctx->vinstr_buffer) { dev_err(kbdev->dev, "Failed to allocate IPA dump buffer"); return -1; } setup.jm_bm = ~0u; setup.shader_bm = ~0u; setup.tiler_bm = ~0u; setup.mmu_l2_bm = ~0u; ctx->vinstr_cli = kbase_vinstr_hwcnt_kernel_setup(kbdev->vinstr_ctx, &setup, ctx->vinstr_buffer); if (!ctx->vinstr_cli) { dev_err(kbdev->dev, "Failed to register IPA with vinstr core"); kfree(ctx->vinstr_buffer); ctx->vinstr_buffer = NULL; return -1; } return 0; } static void detach_vinstr(struct kbase_ipa_context *ctx) { if (ctx->vinstr_cli) kbase_vinstr_detach_client(ctx->vinstr_cli); ctx->vinstr_cli = NULL; kfree(ctx->vinstr_buffer); ctx->vinstr_buffer = NULL; } struct kbase_ipa_context *kbase_ipa_init(struct kbase_device *kbdev) { struct kbase_ipa_context *ctx; int err; ctx = kzalloc(sizeof(*ctx), GFP_KERNEL); if (!ctx) return NULL; mutex_init(&ctx->ipa_lock); ctx->kbdev = kbdev; err = reset_ipa_groups(ctx); if (err < 0) goto err0; err = sysfs_create_group(&kbdev->dev->kobj, &kbase_ipa_attr_group); if (err < 0) goto err0; return ctx; err0: kfree(ctx); return NULL; } void kbase_ipa_term(struct kbase_ipa_context *ctx) { struct kbase_device *kbdev = ctx->kbdev; detach_vinstr(ctx); sysfs_remove_group(&kbdev->dev->kobj, &kbase_ipa_attr_group); kfree(ctx); } u32 kbase_ipa_dynamic_power(struct kbase_ipa_context *ctx, int *err) { struct ipa_group *group; u32 power = 0; size_t i; mutex_lock(&ctx->ipa_lock); if (!ctx->vinstr_cli) { *err = attach_vinstr(ctx); if (*err < 0) goto err0; } *err = kbase_vinstr_hwc_dump(ctx->vinstr_cli, BASE_HWCNT_READER_EVENT_MANUAL); if (*err) goto err0; for (i = 0; i < ARRAY_SIZE(ctx->groups); i++) { group = &ctx->groups[i]; power = add_saturate(power, group->calc_power(ctx, group)); } err0: mutex_unlock(&ctx->ipa_lock); return power; } KBASE_EXPORT_TEST_API(kbase_ipa_dynamic_power);