| // SPDX-License-Identifier: MIT |
| |
| /* |
| * Copyright © 2020 Intel Corporation |
| */ |
| |
| #include <linux/bitmap.h> |
| #include <linux/string_helpers.h> |
| |
| #include "i915_drv.h" |
| #include "intel_gt_debugfs.h" |
| #include "intel_gt_regs.h" |
| #include "intel_sseu_debugfs.h" |
| |
| static void cherryview_sseu_device_status(struct intel_gt *gt, |
| struct sseu_dev_info *sseu) |
| { |
| #define SS_MAX 2 |
| struct intel_uncore *uncore = gt->uncore; |
| const int ss_max = SS_MAX; |
| u32 sig1[SS_MAX], sig2[SS_MAX]; |
| int ss; |
| |
| sig1[0] = intel_uncore_read(uncore, CHV_POWER_SS0_SIG1); |
| sig1[1] = intel_uncore_read(uncore, CHV_POWER_SS1_SIG1); |
| sig2[0] = intel_uncore_read(uncore, CHV_POWER_SS0_SIG2); |
| sig2[1] = intel_uncore_read(uncore, CHV_POWER_SS1_SIG2); |
| |
| for (ss = 0; ss < ss_max; ss++) { |
| unsigned int eu_cnt; |
| |
| if (sig1[ss] & CHV_SS_PG_ENABLE) |
| /* skip disabled subslice */ |
| continue; |
| |
| sseu->slice_mask = BIT(0); |
| sseu->subslice_mask.hsw[0] |= BIT(ss); |
| eu_cnt = ((sig1[ss] & CHV_EU08_PG_ENABLE) ? 0 : 2) + |
| ((sig1[ss] & CHV_EU19_PG_ENABLE) ? 0 : 2) + |
| ((sig1[ss] & CHV_EU210_PG_ENABLE) ? 0 : 2) + |
| ((sig2[ss] & CHV_EU311_PG_ENABLE) ? 0 : 2); |
| sseu->eu_total += eu_cnt; |
| sseu->eu_per_subslice = max_t(unsigned int, |
| sseu->eu_per_subslice, eu_cnt); |
| } |
| #undef SS_MAX |
| } |
| |
| static void gen11_sseu_device_status(struct intel_gt *gt, |
| struct sseu_dev_info *sseu) |
| { |
| #define SS_MAX 8 |
| struct intel_uncore *uncore = gt->uncore; |
| const struct intel_gt_info *info = >->info; |
| u32 s_reg[SS_MAX], eu_reg[2 * SS_MAX], eu_mask[2]; |
| int s, ss; |
| |
| for (s = 0; s < info->sseu.max_slices; s++) { |
| /* |
| * FIXME: Valid SS Mask respects the spec and read |
| * only valid bits for those registers, excluding reserved |
| * although this seems wrong because it would leave many |
| * subslices without ACK. |
| */ |
| s_reg[s] = intel_uncore_read(uncore, GEN10_SLICE_PGCTL_ACK(s)) & |
| GEN10_PGCTL_VALID_SS_MASK(s); |
| eu_reg[2 * s] = intel_uncore_read(uncore, |
| GEN10_SS01_EU_PGCTL_ACK(s)); |
| eu_reg[2 * s + 1] = intel_uncore_read(uncore, |
| GEN10_SS23_EU_PGCTL_ACK(s)); |
| } |
| |
| eu_mask[0] = GEN9_PGCTL_SSA_EU08_ACK | |
| GEN9_PGCTL_SSA_EU19_ACK | |
| GEN9_PGCTL_SSA_EU210_ACK | |
| GEN9_PGCTL_SSA_EU311_ACK; |
| eu_mask[1] = GEN9_PGCTL_SSB_EU08_ACK | |
| GEN9_PGCTL_SSB_EU19_ACK | |
| GEN9_PGCTL_SSB_EU210_ACK | |
| GEN9_PGCTL_SSB_EU311_ACK; |
| |
| for (s = 0; s < info->sseu.max_slices; s++) { |
| if ((s_reg[s] & GEN9_PGCTL_SLICE_ACK) == 0) |
| /* skip disabled slice */ |
| continue; |
| |
| sseu->slice_mask |= BIT(s); |
| sseu->subslice_mask.hsw[s] = info->sseu.subslice_mask.hsw[s]; |
| |
| for (ss = 0; ss < info->sseu.max_subslices; ss++) { |
| unsigned int eu_cnt; |
| |
| if (info->sseu.has_subslice_pg && |
| !(s_reg[s] & (GEN9_PGCTL_SS_ACK(ss)))) |
| /* skip disabled subslice */ |
| continue; |
| |
| eu_cnt = 2 * hweight32(eu_reg[2 * s + ss / 2] & |
| eu_mask[ss % 2]); |
| sseu->eu_total += eu_cnt; |
| sseu->eu_per_subslice = max_t(unsigned int, |
| sseu->eu_per_subslice, |
| eu_cnt); |
| } |
| } |
| #undef SS_MAX |
| } |
| |
| static void gen9_sseu_device_status(struct intel_gt *gt, |
| struct sseu_dev_info *sseu) |
| { |
| #define SS_MAX 3 |
| struct intel_uncore *uncore = gt->uncore; |
| const struct intel_gt_info *info = >->info; |
| u32 s_reg[SS_MAX], eu_reg[2 * SS_MAX], eu_mask[2]; |
| int s, ss; |
| |
| for (s = 0; s < info->sseu.max_slices; s++) { |
| s_reg[s] = intel_uncore_read(uncore, GEN9_SLICE_PGCTL_ACK(s)); |
| eu_reg[2 * s] = |
| intel_uncore_read(uncore, GEN9_SS01_EU_PGCTL_ACK(s)); |
| eu_reg[2 * s + 1] = |
| intel_uncore_read(uncore, GEN9_SS23_EU_PGCTL_ACK(s)); |
| } |
| |
| eu_mask[0] = GEN9_PGCTL_SSA_EU08_ACK | |
| GEN9_PGCTL_SSA_EU19_ACK | |
| GEN9_PGCTL_SSA_EU210_ACK | |
| GEN9_PGCTL_SSA_EU311_ACK; |
| eu_mask[1] = GEN9_PGCTL_SSB_EU08_ACK | |
| GEN9_PGCTL_SSB_EU19_ACK | |
| GEN9_PGCTL_SSB_EU210_ACK | |
| GEN9_PGCTL_SSB_EU311_ACK; |
| |
| for (s = 0; s < info->sseu.max_slices; s++) { |
| if ((s_reg[s] & GEN9_PGCTL_SLICE_ACK) == 0) |
| /* skip disabled slice */ |
| continue; |
| |
| sseu->slice_mask |= BIT(s); |
| |
| if (IS_GEN9_BC(gt->i915)) |
| sseu->subslice_mask.hsw[s] = info->sseu.subslice_mask.hsw[s]; |
| |
| for (ss = 0; ss < info->sseu.max_subslices; ss++) { |
| unsigned int eu_cnt; |
| |
| if (IS_GEN9_LP(gt->i915)) { |
| if (!(s_reg[s] & (GEN9_PGCTL_SS_ACK(ss)))) |
| /* skip disabled subslice */ |
| continue; |
| |
| sseu->subslice_mask.hsw[s] |= BIT(ss); |
| } |
| |
| eu_cnt = eu_reg[2 * s + ss / 2] & eu_mask[ss % 2]; |
| eu_cnt = 2 * hweight32(eu_cnt); |
| |
| sseu->eu_total += eu_cnt; |
| sseu->eu_per_subslice = max_t(unsigned int, |
| sseu->eu_per_subslice, |
| eu_cnt); |
| } |
| } |
| #undef SS_MAX |
| } |
| |
| static void bdw_sseu_device_status(struct intel_gt *gt, |
| struct sseu_dev_info *sseu) |
| { |
| const struct intel_gt_info *info = >->info; |
| u32 slice_info = intel_uncore_read(gt->uncore, GEN8_GT_SLICE_INFO); |
| int s; |
| |
| sseu->slice_mask = slice_info & GEN8_LSLICESTAT_MASK; |
| |
| if (sseu->slice_mask) { |
| sseu->eu_per_subslice = info->sseu.eu_per_subslice; |
| for (s = 0; s < fls(sseu->slice_mask); s++) |
| sseu->subslice_mask.hsw[s] = info->sseu.subslice_mask.hsw[s]; |
| sseu->eu_total = sseu->eu_per_subslice * |
| intel_sseu_subslice_total(sseu); |
| |
| /* subtract fused off EU(s) from enabled slice(s) */ |
| for (s = 0; s < fls(sseu->slice_mask); s++) { |
| u8 subslice_7eu = info->sseu.subslice_7eu[s]; |
| |
| sseu->eu_total -= hweight8(subslice_7eu); |
| } |
| } |
| } |
| |
| static void i915_print_sseu_info(struct seq_file *m, |
| bool is_available_info, |
| bool has_pooled_eu, |
| const struct sseu_dev_info *sseu) |
| { |
| const char *type = is_available_info ? "Available" : "Enabled"; |
| |
| seq_printf(m, " %s Slice Mask: %04x\n", type, |
| sseu->slice_mask); |
| seq_printf(m, " %s Slice Total: %u\n", type, |
| hweight8(sseu->slice_mask)); |
| seq_printf(m, " %s Subslice Total: %u\n", type, |
| intel_sseu_subslice_total(sseu)); |
| intel_sseu_print_ss_info(type, sseu, m); |
| seq_printf(m, " %s EU Total: %u\n", type, |
| sseu->eu_total); |
| seq_printf(m, " %s EU Per Subslice: %u\n", type, |
| sseu->eu_per_subslice); |
| |
| if (!is_available_info) |
| return; |
| |
| seq_printf(m, " Has Pooled EU: %s\n", str_yes_no(has_pooled_eu)); |
| if (has_pooled_eu) |
| seq_printf(m, " Min EU in pool: %u\n", sseu->min_eu_in_pool); |
| |
| seq_printf(m, " Has Slice Power Gating: %s\n", |
| str_yes_no(sseu->has_slice_pg)); |
| seq_printf(m, " Has Subslice Power Gating: %s\n", |
| str_yes_no(sseu->has_subslice_pg)); |
| seq_printf(m, " Has EU Power Gating: %s\n", |
| str_yes_no(sseu->has_eu_pg)); |
| } |
| |
| /* |
| * this is called from top-level debugfs as well, so we can't get the gt from |
| * the seq_file. |
| */ |
| int intel_sseu_status(struct seq_file *m, struct intel_gt *gt) |
| { |
| struct drm_i915_private *i915 = gt->i915; |
| const struct intel_gt_info *info = >->info; |
| struct sseu_dev_info *sseu; |
| intel_wakeref_t wakeref; |
| |
| if (GRAPHICS_VER(i915) < 8) |
| return -ENODEV; |
| |
| seq_puts(m, "SSEU Device Info\n"); |
| i915_print_sseu_info(m, true, HAS_POOLED_EU(i915), &info->sseu); |
| |
| seq_puts(m, "SSEU Device Status\n"); |
| |
| sseu = kzalloc(sizeof(*sseu), GFP_KERNEL); |
| if (!sseu) |
| return -ENOMEM; |
| |
| intel_sseu_set_info(sseu, info->sseu.max_slices, |
| info->sseu.max_subslices, |
| info->sseu.max_eus_per_subslice); |
| |
| with_intel_runtime_pm(&i915->runtime_pm, wakeref) { |
| if (IS_CHERRYVIEW(i915)) |
| cherryview_sseu_device_status(gt, sseu); |
| else if (IS_BROADWELL(i915)) |
| bdw_sseu_device_status(gt, sseu); |
| else if (GRAPHICS_VER(i915) == 9) |
| gen9_sseu_device_status(gt, sseu); |
| else if (GRAPHICS_VER(i915) >= 11) |
| gen11_sseu_device_status(gt, sseu); |
| } |
| |
| i915_print_sseu_info(m, false, HAS_POOLED_EU(i915), sseu); |
| |
| kfree(sseu); |
| |
| return 0; |
| } |
| |
| static int sseu_status_show(struct seq_file *m, void *unused) |
| { |
| struct intel_gt *gt = m->private; |
| |
| return intel_sseu_status(m, gt); |
| } |
| DEFINE_INTEL_GT_DEBUGFS_ATTRIBUTE(sseu_status); |
| |
| static int sseu_topology_show(struct seq_file *m, void *unused) |
| { |
| struct intel_gt *gt = m->private; |
| struct drm_printer p = drm_seq_file_printer(m); |
| |
| intel_sseu_print_topology(gt->i915, >->info.sseu, &p); |
| |
| return 0; |
| } |
| DEFINE_INTEL_GT_DEBUGFS_ATTRIBUTE(sseu_topology); |
| |
| void intel_sseu_debugfs_register(struct intel_gt *gt, struct dentry *root) |
| { |
| static const struct intel_gt_debugfs_file files[] = { |
| { "sseu_status", &sseu_status_fops, NULL }, |
| { "sseu_topology", &sseu_topology_fops, NULL }, |
| }; |
| |
| intel_gt_debugfs_register_files(root, files, ARRAY_SIZE(files), gt); |
| } |