| 1 | // SPDX-License-Identifier: MIT |
| 2 | /* |
| 3 | * Copyright © 2021 Intel Corporation |
| 4 | */ |
| 5 | |
| 6 | #include <linux/string_helpers.h> |
| 7 | |
| 8 | #include <drm/drm_cache.h> |
| 9 | |
| 10 | #include "gt/intel_gt.h" |
| 11 | #include "gt/intel_gt_regs.h" |
| 12 | #include "gt/intel_rps.h" |
| 13 | |
| 14 | #include "i915_drv.h" |
| 15 | #include "i915_reg.h" |
| 16 | #include "i915_wait_util.h" |
| 17 | #include "intel_guc_print.h" |
| 18 | #include "intel_guc_slpc.h" |
| 19 | #include "intel_mchbar_regs.h" |
| 20 | |
| 21 | /** |
| 22 | * DOC: SLPC - Dynamic Frequency management |
| 23 | * |
| 24 | * Single Loop Power Control (SLPC) is a GuC algorithm that manages |
| 25 | * GT frequency based on busyness and how KMD initializes it. SLPC is |
| 26 | * almost completely in control after initialization except for a few |
| 27 | * scenarios mentioned below. |
| 28 | * |
| 29 | * KMD uses the concept of waitboost to ramp frequency to RP0 when there |
| 30 | * are pending submissions for a context. It achieves this by sending GuC a |
| 31 | * request to update the min frequency to RP0. Waitboost is disabled |
| 32 | * when the request retires. |
| 33 | * |
| 34 | * Another form of frequency control happens through per-context hints. |
| 35 | * A context can be marked as low latency during creation. That will ensure |
| 36 | * that SLPC uses an aggressive frequency ramp when that context is active. |
| 37 | * |
| 38 | * Power profiles add another level of control to these mechanisms. |
| 39 | * When power saving profile is chosen, SLPC will use conservative |
| 40 | * thresholds to ramp frequency, thus saving power. KMD will disable |
| 41 | * waitboosts as well, which achieves further power savings. Base profile |
| 42 | * is default and ensures balanced performance for any workload. |
| 43 | * |
| 44 | * Lastly, users have some level of control through sysfs, where min/max |
| 45 | * frequency values can be altered and the use of efficient freq |
| 46 | * can be toggled. |
| 47 | */ |
| 48 | |
| 49 | static inline struct intel_guc *slpc_to_guc(struct intel_guc_slpc *slpc) |
| 50 | { |
| 51 | return container_of(slpc, struct intel_guc, slpc); |
| 52 | } |
| 53 | |
| 54 | static inline struct intel_gt *slpc_to_gt(struct intel_guc_slpc *slpc) |
| 55 | { |
| 56 | return guc_to_gt(guc: slpc_to_guc(slpc)); |
| 57 | } |
| 58 | |
| 59 | static inline struct drm_i915_private *slpc_to_i915(struct intel_guc_slpc *slpc) |
| 60 | { |
| 61 | return slpc_to_gt(slpc)->i915; |
| 62 | } |
| 63 | |
| 64 | static bool __detect_slpc_supported(struct intel_guc *guc) |
| 65 | { |
| 66 | /* GuC SLPC is unavailable for pre-Gen12 */ |
| 67 | return guc->submission_supported && |
| 68 | GRAPHICS_VER(guc_to_i915(guc)) >= 12; |
| 69 | } |
| 70 | |
| 71 | static bool __guc_slpc_selected(struct intel_guc *guc) |
| 72 | { |
| 73 | if (!intel_guc_slpc_is_supported(guc)) |
| 74 | return false; |
| 75 | |
| 76 | return guc->submission_selected; |
| 77 | } |
| 78 | |
| 79 | void intel_guc_slpc_init_early(struct intel_guc_slpc *slpc) |
| 80 | { |
| 81 | struct intel_guc *guc = slpc_to_guc(slpc); |
| 82 | |
| 83 | slpc->supported = __detect_slpc_supported(guc); |
| 84 | slpc->selected = __guc_slpc_selected(guc); |
| 85 | } |
| 86 | |
| 87 | static void slpc_mem_set_param(struct slpc_shared_data *data, |
| 88 | u32 id, u32 value) |
| 89 | { |
| 90 | GEM_BUG_ON(id >= SLPC_MAX_OVERRIDE_PARAMETERS); |
| 91 | /* |
| 92 | * When the flag bit is set, corresponding value will be read |
| 93 | * and applied by SLPC. |
| 94 | */ |
| 95 | data->override_params.bits[id >> 5] |= (1 << (id % 32)); |
| 96 | data->override_params.values[id] = value; |
| 97 | } |
| 98 | |
| 99 | static void slpc_mem_set_enabled(struct slpc_shared_data *data, |
| 100 | u8 enable_id, u8 disable_id) |
| 101 | { |
| 102 | /* |
| 103 | * Enabling a param involves setting the enable_id |
| 104 | * to 1 and disable_id to 0. |
| 105 | */ |
| 106 | slpc_mem_set_param(data, id: enable_id, value: 1); |
| 107 | slpc_mem_set_param(data, id: disable_id, value: 0); |
| 108 | } |
| 109 | |
| 110 | static void slpc_mem_set_disabled(struct slpc_shared_data *data, |
| 111 | u8 enable_id, u8 disable_id) |
| 112 | { |
| 113 | /* |
| 114 | * Disabling a param involves setting the enable_id |
| 115 | * to 0 and disable_id to 1. |
| 116 | */ |
| 117 | slpc_mem_set_param(data, id: disable_id, value: 1); |
| 118 | slpc_mem_set_param(data, id: enable_id, value: 0); |
| 119 | } |
| 120 | |
| 121 | static u32 slpc_get_state(struct intel_guc_slpc *slpc) |
| 122 | { |
| 123 | struct slpc_shared_data *data; |
| 124 | |
| 125 | GEM_BUG_ON(!slpc->vma); |
| 126 | |
| 127 | drm_clflush_virt_range(addr: slpc->vaddr, length: sizeof(u32)); |
| 128 | data = slpc->vaddr; |
| 129 | |
| 130 | return data->header.global_state; |
| 131 | } |
| 132 | |
| 133 | static int guc_action_slpc_set_param_nb(struct intel_guc *guc, u8 id, u32 value) |
| 134 | { |
| 135 | u32 request[] = { |
| 136 | GUC_ACTION_HOST2GUC_PC_SLPC_REQUEST, |
| 137 | SLPC_EVENT(SLPC_EVENT_PARAMETER_SET, 2), |
| 138 | id, |
| 139 | value, |
| 140 | }; |
| 141 | int ret; |
| 142 | |
| 143 | ret = intel_guc_send_nb(guc, action: request, ARRAY_SIZE(request), g2h_len_dw: 0); |
| 144 | |
| 145 | return ret > 0 ? -EPROTO : ret; |
| 146 | } |
| 147 | |
| 148 | static int slpc_set_param_nb(struct intel_guc_slpc *slpc, u8 id, u32 value) |
| 149 | { |
| 150 | struct intel_guc *guc = slpc_to_guc(slpc); |
| 151 | |
| 152 | GEM_BUG_ON(id >= SLPC_MAX_PARAM); |
| 153 | |
| 154 | return guc_action_slpc_set_param_nb(guc, id, value); |
| 155 | } |
| 156 | |
| 157 | static int guc_action_slpc_set_param(struct intel_guc *guc, u8 id, u32 value) |
| 158 | { |
| 159 | u32 request[] = { |
| 160 | GUC_ACTION_HOST2GUC_PC_SLPC_REQUEST, |
| 161 | SLPC_EVENT(SLPC_EVENT_PARAMETER_SET, 2), |
| 162 | id, |
| 163 | value, |
| 164 | }; |
| 165 | int ret; |
| 166 | |
| 167 | ret = intel_guc_send(guc, action: request, ARRAY_SIZE(request)); |
| 168 | |
| 169 | return ret > 0 ? -EPROTO : ret; |
| 170 | } |
| 171 | |
| 172 | static bool slpc_is_running(struct intel_guc_slpc *slpc) |
| 173 | { |
| 174 | return slpc_get_state(slpc) == SLPC_GLOBAL_STATE_RUNNING; |
| 175 | } |
| 176 | |
| 177 | static int guc_action_slpc_query(struct intel_guc *guc, u32 offset) |
| 178 | { |
| 179 | u32 request[] = { |
| 180 | GUC_ACTION_HOST2GUC_PC_SLPC_REQUEST, |
| 181 | SLPC_EVENT(SLPC_EVENT_QUERY_TASK_STATE, 2), |
| 182 | offset, |
| 183 | 0, |
| 184 | }; |
| 185 | int ret; |
| 186 | |
| 187 | ret = intel_guc_send(guc, action: request, ARRAY_SIZE(request)); |
| 188 | |
| 189 | return ret > 0 ? -EPROTO : ret; |
| 190 | } |
| 191 | |
| 192 | static int slpc_query_task_state(struct intel_guc_slpc *slpc) |
| 193 | { |
| 194 | struct intel_guc *guc = slpc_to_guc(slpc); |
| 195 | u32 offset = intel_guc_ggtt_offset(guc, vma: slpc->vma); |
| 196 | int ret; |
| 197 | |
| 198 | ret = guc_action_slpc_query(guc, offset); |
| 199 | if (unlikely(ret)) |
| 200 | guc_probe_error(guc, "Failed to query task state: %pe\n" , ERR_PTR(ret)); |
| 201 | |
| 202 | drm_clflush_virt_range(addr: slpc->vaddr, SLPC_PAGE_SIZE_BYTES); |
| 203 | |
| 204 | return ret; |
| 205 | } |
| 206 | |
| 207 | static int slpc_set_param(struct intel_guc_slpc *slpc, u8 id, u32 value) |
| 208 | { |
| 209 | struct intel_guc *guc = slpc_to_guc(slpc); |
| 210 | int ret; |
| 211 | |
| 212 | GEM_BUG_ON(id >= SLPC_MAX_PARAM); |
| 213 | |
| 214 | ret = guc_action_slpc_set_param(guc, id, value); |
| 215 | if (ret) |
| 216 | guc_probe_error(guc, "Failed to set param %d to %u: %pe\n" , |
| 217 | id, value, ERR_PTR(ret)); |
| 218 | |
| 219 | return ret; |
| 220 | } |
| 221 | |
| 222 | static int slpc_force_min_freq(struct intel_guc_slpc *slpc, u32 freq) |
| 223 | { |
| 224 | struct intel_guc *guc = slpc_to_guc(slpc); |
| 225 | struct drm_i915_private *i915 = slpc_to_i915(slpc); |
| 226 | intel_wakeref_t wakeref; |
| 227 | int ret = 0; |
| 228 | |
| 229 | lockdep_assert_held(&slpc->lock); |
| 230 | |
| 231 | if (!intel_guc_is_ready(guc)) |
| 232 | return -ENODEV; |
| 233 | |
| 234 | /* |
| 235 | * This function is a little different as compared to |
| 236 | * intel_guc_slpc_set_min_freq(). Softlimit will not be updated |
| 237 | * here since this is used to temporarily change min freq, |
| 238 | * for example, during a waitboost. Caller is responsible for |
| 239 | * checking bounds. |
| 240 | */ |
| 241 | |
| 242 | with_intel_runtime_pm(&i915->runtime_pm, wakeref) { |
| 243 | /* Non-blocking request will avoid stalls */ |
| 244 | ret = slpc_set_param_nb(slpc, |
| 245 | id: SLPC_PARAM_GLOBAL_MIN_GT_UNSLICE_FREQ_MHZ, |
| 246 | value: freq); |
| 247 | if (ret) |
| 248 | guc_notice(guc, "Failed to send set_param for min freq(%d): %pe\n" , |
| 249 | freq, ERR_PTR(ret)); |
| 250 | } |
| 251 | |
| 252 | return ret; |
| 253 | } |
| 254 | |
| 255 | static void slpc_boost_work(struct work_struct *work) |
| 256 | { |
| 257 | struct intel_guc_slpc *slpc = container_of(work, typeof(*slpc), boost_work); |
| 258 | int err; |
| 259 | |
| 260 | /* |
| 261 | * Raise min freq to boost. It's possible that |
| 262 | * this is greater than current max. But it will |
| 263 | * certainly be limited by RP0. An error setting |
| 264 | * the min param is not fatal. |
| 265 | */ |
| 266 | mutex_lock(lock: &slpc->lock); |
| 267 | if (atomic_read(v: &slpc->num_waiters)) { |
| 268 | err = slpc_force_min_freq(slpc, freq: slpc->boost_freq); |
| 269 | if (!err) |
| 270 | slpc->num_boosts++; |
| 271 | } |
| 272 | mutex_unlock(lock: &slpc->lock); |
| 273 | } |
| 274 | |
| 275 | int intel_guc_slpc_init(struct intel_guc_slpc *slpc) |
| 276 | { |
| 277 | struct intel_guc *guc = slpc_to_guc(slpc); |
| 278 | u32 size = PAGE_ALIGN(sizeof(struct slpc_shared_data)); |
| 279 | int err; |
| 280 | |
| 281 | GEM_BUG_ON(slpc->vma); |
| 282 | |
| 283 | err = intel_guc_allocate_and_map_vma(guc, size, out_vma: &slpc->vma, out_vaddr: (void **)&slpc->vaddr); |
| 284 | if (unlikely(err)) { |
| 285 | guc_probe_error(guc, "Failed to allocate SLPC struct: %pe\n" , ERR_PTR(err)); |
| 286 | return err; |
| 287 | } |
| 288 | |
| 289 | slpc->max_freq_softlimit = 0; |
| 290 | slpc->min_freq_softlimit = 0; |
| 291 | slpc->ignore_eff_freq = false; |
| 292 | slpc->min_is_rpmax = false; |
| 293 | |
| 294 | slpc->boost_freq = 0; |
| 295 | atomic_set(v: &slpc->num_waiters, i: 0); |
| 296 | slpc->num_boosts = 0; |
| 297 | slpc->media_ratio_mode = SLPC_MEDIA_RATIO_MODE_DYNAMIC_CONTROL; |
| 298 | |
| 299 | slpc->power_profile = SLPC_POWER_PROFILES_BASE; |
| 300 | |
| 301 | mutex_init(&slpc->lock); |
| 302 | INIT_WORK(&slpc->boost_work, slpc_boost_work); |
| 303 | |
| 304 | return err; |
| 305 | } |
| 306 | |
| 307 | static const char *slpc_global_state_to_string(enum slpc_global_state state) |
| 308 | { |
| 309 | switch (state) { |
| 310 | case SLPC_GLOBAL_STATE_NOT_RUNNING: |
| 311 | return "not running" ; |
| 312 | case SLPC_GLOBAL_STATE_INITIALIZING: |
| 313 | return "initializing" ; |
| 314 | case SLPC_GLOBAL_STATE_RESETTING: |
| 315 | return "resetting" ; |
| 316 | case SLPC_GLOBAL_STATE_RUNNING: |
| 317 | return "running" ; |
| 318 | case SLPC_GLOBAL_STATE_SHUTTING_DOWN: |
| 319 | return "shutting down" ; |
| 320 | case SLPC_GLOBAL_STATE_ERROR: |
| 321 | return "error" ; |
| 322 | default: |
| 323 | return "unknown" ; |
| 324 | } |
| 325 | } |
| 326 | |
| 327 | static const char *slpc_get_state_string(struct intel_guc_slpc *slpc) |
| 328 | { |
| 329 | return slpc_global_state_to_string(state: slpc_get_state(slpc)); |
| 330 | } |
| 331 | |
| 332 | static int guc_action_slpc_reset(struct intel_guc *guc, u32 offset) |
| 333 | { |
| 334 | u32 request[] = { |
| 335 | GUC_ACTION_HOST2GUC_PC_SLPC_REQUEST, |
| 336 | SLPC_EVENT(SLPC_EVENT_RESET, 2), |
| 337 | offset, |
| 338 | 0, |
| 339 | }; |
| 340 | int ret; |
| 341 | |
| 342 | ret = intel_guc_send(guc, action: request, ARRAY_SIZE(request)); |
| 343 | |
| 344 | return ret > 0 ? -EPROTO : ret; |
| 345 | } |
| 346 | |
| 347 | static int slpc_reset(struct intel_guc_slpc *slpc) |
| 348 | { |
| 349 | struct intel_guc *guc = slpc_to_guc(slpc); |
| 350 | u32 offset = intel_guc_ggtt_offset(guc, vma: slpc->vma); |
| 351 | int ret; |
| 352 | |
| 353 | ret = guc_action_slpc_reset(guc, offset); |
| 354 | |
| 355 | if (unlikely(ret < 0)) { |
| 356 | guc_probe_error(guc, "SLPC reset action failed: %pe\n" , ERR_PTR(ret)); |
| 357 | return ret; |
| 358 | } |
| 359 | |
| 360 | if (!ret) { |
| 361 | if (wait_for(slpc_is_running(slpc), SLPC_RESET_TIMEOUT_MS)) { |
| 362 | guc_probe_error(guc, "SLPC not enabled! State = %s\n" , |
| 363 | slpc_get_state_string(slpc)); |
| 364 | return -EIO; |
| 365 | } |
| 366 | } |
| 367 | |
| 368 | return 0; |
| 369 | } |
| 370 | |
| 371 | static u32 slpc_decode_min_freq(struct intel_guc_slpc *slpc) |
| 372 | { |
| 373 | struct slpc_shared_data *data = slpc->vaddr; |
| 374 | |
| 375 | GEM_BUG_ON(!slpc->vma); |
| 376 | |
| 377 | return DIV_ROUND_CLOSEST(REG_FIELD_GET(SLPC_MIN_UNSLICE_FREQ_MASK, |
| 378 | data->task_state_data.freq) * |
| 379 | GT_FREQUENCY_MULTIPLIER, GEN9_FREQ_SCALER); |
| 380 | } |
| 381 | |
| 382 | static u32 slpc_decode_max_freq(struct intel_guc_slpc *slpc) |
| 383 | { |
| 384 | struct slpc_shared_data *data = slpc->vaddr; |
| 385 | |
| 386 | GEM_BUG_ON(!slpc->vma); |
| 387 | |
| 388 | return DIV_ROUND_CLOSEST(REG_FIELD_GET(SLPC_MAX_UNSLICE_FREQ_MASK, |
| 389 | data->task_state_data.freq) * |
| 390 | GT_FREQUENCY_MULTIPLIER, GEN9_FREQ_SCALER); |
| 391 | } |
| 392 | |
| 393 | static void slpc_shared_data_reset(struct intel_guc_slpc *slpc) |
| 394 | { |
| 395 | struct drm_i915_private *i915 = slpc_to_i915(slpc); |
| 396 | struct slpc_shared_data *data = slpc->vaddr; |
| 397 | |
| 398 | memset(s: data, c: 0, n: sizeof(struct slpc_shared_data)); |
| 399 | data->header.size = sizeof(struct slpc_shared_data); |
| 400 | |
| 401 | /* Enable only GTPERF task, disable others */ |
| 402 | slpc_mem_set_enabled(data, enable_id: SLPC_PARAM_TASK_ENABLE_GTPERF, |
| 403 | disable_id: SLPC_PARAM_TASK_DISABLE_GTPERF); |
| 404 | |
| 405 | /* |
| 406 | * Don't allow balancer related algorithms on platforms before |
| 407 | * Xe_LPG, where GuC started to restrict it to TDP limited scenarios. |
| 408 | */ |
| 409 | if (GRAPHICS_VER_FULL(i915) < IP_VER(12, 70)) { |
| 410 | slpc_mem_set_disabled(data, enable_id: SLPC_PARAM_TASK_ENABLE_BALANCER, |
| 411 | disable_id: SLPC_PARAM_TASK_DISABLE_BALANCER); |
| 412 | |
| 413 | slpc_mem_set_disabled(data, enable_id: SLPC_PARAM_TASK_ENABLE_DCC, |
| 414 | disable_id: SLPC_PARAM_TASK_DISABLE_DCC); |
| 415 | } |
| 416 | } |
| 417 | |
| 418 | /** |
| 419 | * intel_guc_slpc_set_max_freq() - Set max frequency limit for SLPC. |
| 420 | * @slpc: pointer to intel_guc_slpc. |
| 421 | * @val: frequency (MHz) |
| 422 | * |
| 423 | * This function will invoke GuC SLPC action to update the max frequency |
| 424 | * limit for unslice. |
| 425 | * |
| 426 | * Return: 0 on success, non-zero error code on failure. |
| 427 | */ |
| 428 | int intel_guc_slpc_set_max_freq(struct intel_guc_slpc *slpc, u32 val) |
| 429 | { |
| 430 | struct drm_i915_private *i915 = slpc_to_i915(slpc); |
| 431 | intel_wakeref_t wakeref; |
| 432 | int ret; |
| 433 | |
| 434 | if (val < slpc->min_freq || |
| 435 | val > slpc->rp0_freq || |
| 436 | val < slpc->min_freq_softlimit) |
| 437 | return -EINVAL; |
| 438 | |
| 439 | with_intel_runtime_pm(&i915->runtime_pm, wakeref) { |
| 440 | ret = slpc_set_param(slpc, |
| 441 | id: SLPC_PARAM_GLOBAL_MAX_GT_UNSLICE_FREQ_MHZ, |
| 442 | value: val); |
| 443 | |
| 444 | /* Return standardized err code for sysfs calls */ |
| 445 | if (ret) |
| 446 | ret = -EIO; |
| 447 | } |
| 448 | |
| 449 | if (!ret) |
| 450 | slpc->max_freq_softlimit = val; |
| 451 | |
| 452 | return ret; |
| 453 | } |
| 454 | |
| 455 | /** |
| 456 | * intel_guc_slpc_get_max_freq() - Get max frequency limit for SLPC. |
| 457 | * @slpc: pointer to intel_guc_slpc. |
| 458 | * @val: pointer to val which will hold max frequency (MHz) |
| 459 | * |
| 460 | * This function will invoke GuC SLPC action to read the max frequency |
| 461 | * limit for unslice. |
| 462 | * |
| 463 | * Return: 0 on success, non-zero error code on failure. |
| 464 | */ |
| 465 | int intel_guc_slpc_get_max_freq(struct intel_guc_slpc *slpc, u32 *val) |
| 466 | { |
| 467 | struct drm_i915_private *i915 = slpc_to_i915(slpc); |
| 468 | intel_wakeref_t wakeref; |
| 469 | int ret = 0; |
| 470 | |
| 471 | with_intel_runtime_pm(&i915->runtime_pm, wakeref) { |
| 472 | /* Force GuC to update task data */ |
| 473 | ret = slpc_query_task_state(slpc); |
| 474 | |
| 475 | if (!ret) |
| 476 | *val = slpc_decode_max_freq(slpc); |
| 477 | } |
| 478 | |
| 479 | return ret; |
| 480 | } |
| 481 | |
| 482 | int intel_guc_slpc_set_ignore_eff_freq(struct intel_guc_slpc *slpc, bool val) |
| 483 | { |
| 484 | struct drm_i915_private *i915 = slpc_to_i915(slpc); |
| 485 | intel_wakeref_t wakeref; |
| 486 | int ret; |
| 487 | |
| 488 | mutex_lock(lock: &slpc->lock); |
| 489 | wakeref = intel_runtime_pm_get(rpm: &i915->runtime_pm); |
| 490 | |
| 491 | ret = slpc_set_param(slpc, |
| 492 | id: SLPC_PARAM_IGNORE_EFFICIENT_FREQUENCY, |
| 493 | value: val); |
| 494 | if (ret) { |
| 495 | guc_probe_error(slpc_to_guc(slpc), "Failed to set efficient freq(%d): %pe\n" , |
| 496 | val, ERR_PTR(ret)); |
| 497 | } else { |
| 498 | slpc->ignore_eff_freq = val; |
| 499 | |
| 500 | /* Set min to RPn when we disable efficient freq */ |
| 501 | if (val) |
| 502 | ret = slpc_set_param(slpc, |
| 503 | id: SLPC_PARAM_GLOBAL_MIN_GT_UNSLICE_FREQ_MHZ, |
| 504 | value: slpc->min_freq); |
| 505 | } |
| 506 | |
| 507 | intel_runtime_pm_put(rpm: &i915->runtime_pm, wref: wakeref); |
| 508 | mutex_unlock(lock: &slpc->lock); |
| 509 | return ret; |
| 510 | } |
| 511 | |
| 512 | /** |
| 513 | * intel_guc_slpc_set_min_freq() - Set min frequency limit for SLPC. |
| 514 | * @slpc: pointer to intel_guc_slpc. |
| 515 | * @val: frequency (MHz) |
| 516 | * |
| 517 | * This function will invoke GuC SLPC action to update the min unslice |
| 518 | * frequency. |
| 519 | * |
| 520 | * Return: 0 on success, non-zero error code on failure. |
| 521 | */ |
| 522 | int intel_guc_slpc_set_min_freq(struct intel_guc_slpc *slpc, u32 val) |
| 523 | { |
| 524 | struct drm_i915_private *i915 = slpc_to_i915(slpc); |
| 525 | intel_wakeref_t wakeref; |
| 526 | int ret; |
| 527 | |
| 528 | if (val < slpc->min_freq || |
| 529 | val > slpc->rp0_freq || |
| 530 | val > slpc->max_freq_softlimit) |
| 531 | return -EINVAL; |
| 532 | |
| 533 | /* Need a lock now since waitboost can be modifying min as well */ |
| 534 | mutex_lock(lock: &slpc->lock); |
| 535 | wakeref = intel_runtime_pm_get(rpm: &i915->runtime_pm); |
| 536 | |
| 537 | ret = slpc_set_param(slpc, |
| 538 | id: SLPC_PARAM_GLOBAL_MIN_GT_UNSLICE_FREQ_MHZ, |
| 539 | value: val); |
| 540 | |
| 541 | if (!ret) |
| 542 | slpc->min_freq_softlimit = val; |
| 543 | |
| 544 | intel_runtime_pm_put(rpm: &i915->runtime_pm, wref: wakeref); |
| 545 | mutex_unlock(lock: &slpc->lock); |
| 546 | |
| 547 | /* Return standardized err code for sysfs calls */ |
| 548 | if (ret) |
| 549 | ret = -EIO; |
| 550 | |
| 551 | return ret; |
| 552 | } |
| 553 | |
| 554 | /** |
| 555 | * intel_guc_slpc_get_min_freq() - Get min frequency limit for SLPC. |
| 556 | * @slpc: pointer to intel_guc_slpc. |
| 557 | * @val: pointer to val which will hold min frequency (MHz) |
| 558 | * |
| 559 | * This function will invoke GuC SLPC action to read the min frequency |
| 560 | * limit for unslice. |
| 561 | * |
| 562 | * Return: 0 on success, non-zero error code on failure. |
| 563 | */ |
| 564 | int intel_guc_slpc_get_min_freq(struct intel_guc_slpc *slpc, u32 *val) |
| 565 | { |
| 566 | struct drm_i915_private *i915 = slpc_to_i915(slpc); |
| 567 | intel_wakeref_t wakeref; |
| 568 | int ret = 0; |
| 569 | |
| 570 | with_intel_runtime_pm(&i915->runtime_pm, wakeref) { |
| 571 | /* Force GuC to update task data */ |
| 572 | ret = slpc_query_task_state(slpc); |
| 573 | |
| 574 | if (!ret) |
| 575 | *val = slpc_decode_min_freq(slpc); |
| 576 | } |
| 577 | |
| 578 | return ret; |
| 579 | } |
| 580 | |
| 581 | int intel_guc_slpc_set_strategy(struct intel_guc_slpc *slpc, u32 val) |
| 582 | { |
| 583 | struct drm_i915_private *i915 = slpc_to_i915(slpc); |
| 584 | intel_wakeref_t wakeref; |
| 585 | int ret = 0; |
| 586 | |
| 587 | with_intel_runtime_pm(&i915->runtime_pm, wakeref) |
| 588 | ret = slpc_set_param(slpc, |
| 589 | id: SLPC_PARAM_STRATEGIES, |
| 590 | value: val); |
| 591 | |
| 592 | return ret; |
| 593 | } |
| 594 | |
| 595 | int intel_guc_slpc_set_media_ratio_mode(struct intel_guc_slpc *slpc, u32 val) |
| 596 | { |
| 597 | struct drm_i915_private *i915 = slpc_to_i915(slpc); |
| 598 | intel_wakeref_t wakeref; |
| 599 | int ret = 0; |
| 600 | |
| 601 | if (!HAS_MEDIA_RATIO_MODE(i915)) |
| 602 | return -ENODEV; |
| 603 | |
| 604 | with_intel_runtime_pm(&i915->runtime_pm, wakeref) |
| 605 | ret = slpc_set_param(slpc, |
| 606 | id: SLPC_PARAM_MEDIA_FF_RATIO_MODE, |
| 607 | value: val); |
| 608 | return ret; |
| 609 | } |
| 610 | |
| 611 | int intel_guc_slpc_set_power_profile(struct intel_guc_slpc *slpc, u32 val) |
| 612 | { |
| 613 | struct drm_i915_private *i915 = slpc_to_i915(slpc); |
| 614 | intel_wakeref_t wakeref; |
| 615 | int ret = 0; |
| 616 | |
| 617 | if (val > SLPC_POWER_PROFILES_POWER_SAVING) |
| 618 | return -EINVAL; |
| 619 | |
| 620 | mutex_lock(lock: &slpc->lock); |
| 621 | wakeref = intel_runtime_pm_get(rpm: &i915->runtime_pm); |
| 622 | |
| 623 | ret = slpc_set_param(slpc, |
| 624 | id: SLPC_PARAM_POWER_PROFILE, |
| 625 | value: val); |
| 626 | if (ret) |
| 627 | guc_err(slpc_to_guc(slpc), |
| 628 | "Failed to set power profile to %d: %pe\n" , |
| 629 | val, ERR_PTR(ret)); |
| 630 | else |
| 631 | slpc->power_profile = val; |
| 632 | |
| 633 | intel_runtime_pm_put(rpm: &i915->runtime_pm, wref: wakeref); |
| 634 | mutex_unlock(lock: &slpc->lock); |
| 635 | |
| 636 | return ret; |
| 637 | } |
| 638 | |
| 639 | void intel_guc_pm_intrmsk_enable(struct intel_gt *gt) |
| 640 | { |
| 641 | u32 pm_intrmsk_mbz = 0; |
| 642 | |
| 643 | /* |
| 644 | * Allow GuC to receive ARAT timer expiry event. |
| 645 | * This interrupt register is setup by RPS code |
| 646 | * when host based Turbo is enabled. |
| 647 | */ |
| 648 | pm_intrmsk_mbz |= ARAT_EXPIRED_INTRMSK; |
| 649 | |
| 650 | intel_uncore_rmw(uncore: gt->uncore, |
| 651 | GEN6_PMINTRMSK, clear: pm_intrmsk_mbz, set: 0); |
| 652 | } |
| 653 | |
| 654 | static int slpc_set_softlimits(struct intel_guc_slpc *slpc) |
| 655 | { |
| 656 | int ret = 0; |
| 657 | |
| 658 | /* |
| 659 | * Softlimits are initially equivalent to platform limits |
| 660 | * unless they have deviated from defaults, in which case, |
| 661 | * we retain the values and set min/max accordingly. |
| 662 | */ |
| 663 | if (!slpc->max_freq_softlimit) { |
| 664 | slpc->max_freq_softlimit = slpc->rp0_freq; |
| 665 | slpc_to_gt(slpc)->defaults.max_freq = slpc->max_freq_softlimit; |
| 666 | } else if (slpc->max_freq_softlimit != slpc->rp0_freq) { |
| 667 | ret = intel_guc_slpc_set_max_freq(slpc, |
| 668 | val: slpc->max_freq_softlimit); |
| 669 | } |
| 670 | |
| 671 | if (unlikely(ret)) |
| 672 | return ret; |
| 673 | |
| 674 | if (!slpc->min_freq_softlimit) { |
| 675 | /* Min softlimit is initialized to RPn */ |
| 676 | slpc->min_freq_softlimit = slpc->min_freq; |
| 677 | slpc_to_gt(slpc)->defaults.min_freq = slpc->min_freq_softlimit; |
| 678 | } else { |
| 679 | return intel_guc_slpc_set_min_freq(slpc, |
| 680 | val: slpc->min_freq_softlimit); |
| 681 | } |
| 682 | |
| 683 | return 0; |
| 684 | } |
| 685 | |
| 686 | static bool is_slpc_min_freq_rpmax(struct intel_guc_slpc *slpc) |
| 687 | { |
| 688 | int slpc_min_freq; |
| 689 | int ret; |
| 690 | |
| 691 | ret = intel_guc_slpc_get_min_freq(slpc, val: &slpc_min_freq); |
| 692 | if (ret) { |
| 693 | guc_err(slpc_to_guc(slpc), "Failed to get min freq: %pe\n" , ERR_PTR(ret)); |
| 694 | return false; |
| 695 | } |
| 696 | |
| 697 | if (slpc_min_freq == SLPC_MAX_FREQ_MHZ) |
| 698 | return true; |
| 699 | else |
| 700 | return false; |
| 701 | } |
| 702 | |
| 703 | static void update_server_min_softlimit(struct intel_guc_slpc *slpc) |
| 704 | { |
| 705 | /* For server parts, SLPC min will be at RPMax. |
| 706 | * Use min softlimit to clamp it to RP0 instead. |
| 707 | */ |
| 708 | if (!slpc->min_freq_softlimit && |
| 709 | is_slpc_min_freq_rpmax(slpc)) { |
| 710 | slpc->min_is_rpmax = true; |
| 711 | slpc->min_freq_softlimit = slpc->rp0_freq; |
| 712 | (slpc_to_gt(slpc))->defaults.min_freq = slpc->min_freq_softlimit; |
| 713 | } |
| 714 | } |
| 715 | |
| 716 | static int slpc_use_fused_rp0(struct intel_guc_slpc *slpc) |
| 717 | { |
| 718 | /* Force SLPC to used platform rp0 */ |
| 719 | return slpc_set_param(slpc, |
| 720 | id: SLPC_PARAM_GLOBAL_MAX_GT_UNSLICE_FREQ_MHZ, |
| 721 | value: slpc->rp0_freq); |
| 722 | } |
| 723 | |
| 724 | static void slpc_get_rp_values(struct intel_guc_slpc *slpc) |
| 725 | { |
| 726 | struct intel_rps *rps = &slpc_to_gt(slpc)->rps; |
| 727 | struct intel_rps_freq_caps caps; |
| 728 | |
| 729 | gen6_rps_get_freq_caps(rps, caps: &caps); |
| 730 | slpc->rp0_freq = intel_gpu_freq(rps, val: caps.rp0_freq); |
| 731 | slpc->rp1_freq = intel_gpu_freq(rps, val: caps.rp1_freq); |
| 732 | slpc->min_freq = intel_gpu_freq(rps, val: caps.min_freq); |
| 733 | |
| 734 | if (!slpc->boost_freq) |
| 735 | slpc->boost_freq = slpc->rp0_freq; |
| 736 | } |
| 737 | |
| 738 | /* |
| 739 | * intel_guc_slpc_enable() - Start SLPC |
| 740 | * @slpc: pointer to intel_guc_slpc. |
| 741 | * |
| 742 | * SLPC is enabled by setting up the shared data structure and |
| 743 | * sending reset event to GuC SLPC. Initial data is setup in |
| 744 | * intel_guc_slpc_init. Here we send the reset event. We do |
| 745 | * not currently need a slpc_disable since this is taken care |
| 746 | * of automatically when a reset/suspend occurs and the GuC |
| 747 | * CTB is destroyed. |
| 748 | * |
| 749 | * Return: 0 on success, non-zero error code on failure. |
| 750 | */ |
| 751 | int intel_guc_slpc_enable(struct intel_guc_slpc *slpc) |
| 752 | { |
| 753 | struct intel_guc *guc = slpc_to_guc(slpc); |
| 754 | int ret; |
| 755 | |
| 756 | GEM_BUG_ON(!slpc->vma); |
| 757 | |
| 758 | slpc_shared_data_reset(slpc); |
| 759 | |
| 760 | ret = slpc_reset(slpc); |
| 761 | if (unlikely(ret < 0)) { |
| 762 | guc_probe_error(guc, "SLPC Reset event returned: %pe\n" , ERR_PTR(ret)); |
| 763 | return ret; |
| 764 | } |
| 765 | |
| 766 | ret = slpc_query_task_state(slpc); |
| 767 | if (unlikely(ret < 0)) |
| 768 | return ret; |
| 769 | |
| 770 | intel_guc_pm_intrmsk_enable(gt: slpc_to_gt(slpc)); |
| 771 | |
| 772 | slpc_get_rp_values(slpc); |
| 773 | |
| 774 | /* Handle the case where min=max=RPmax */ |
| 775 | update_server_min_softlimit(slpc); |
| 776 | |
| 777 | /* Set SLPC max limit to RP0 */ |
| 778 | ret = slpc_use_fused_rp0(slpc); |
| 779 | if (unlikely(ret)) { |
| 780 | guc_probe_error(guc, "Failed to set SLPC max to RP0: %pe\n" , ERR_PTR(ret)); |
| 781 | return ret; |
| 782 | } |
| 783 | |
| 784 | /* Set cached value of ignore efficient freq */ |
| 785 | intel_guc_slpc_set_ignore_eff_freq(slpc, val: slpc->ignore_eff_freq); |
| 786 | |
| 787 | /* Revert SLPC min/max to softlimits if necessary */ |
| 788 | ret = slpc_set_softlimits(slpc); |
| 789 | if (unlikely(ret)) { |
| 790 | guc_probe_error(guc, "Failed to set SLPC softlimits: %pe\n" , ERR_PTR(ret)); |
| 791 | return ret; |
| 792 | } |
| 793 | |
| 794 | /* Set cached media freq ratio mode */ |
| 795 | intel_guc_slpc_set_media_ratio_mode(slpc, val: slpc->media_ratio_mode); |
| 796 | |
| 797 | /* Enable SLPC Optimized Strategy for compute */ |
| 798 | intel_guc_slpc_set_strategy(slpc, SLPC_OPTIMIZED_STRATEGY_COMPUTE); |
| 799 | |
| 800 | /* Set cached value of power_profile */ |
| 801 | ret = intel_guc_slpc_set_power_profile(slpc, val: slpc->power_profile); |
| 802 | if (unlikely(ret)) { |
| 803 | guc_probe_error(guc, "Failed to set SLPC power profile: %pe\n" , ERR_PTR(ret)); |
| 804 | return ret; |
| 805 | } |
| 806 | |
| 807 | return 0; |
| 808 | } |
| 809 | |
| 810 | int intel_guc_slpc_set_boost_freq(struct intel_guc_slpc *slpc, u32 val) |
| 811 | { |
| 812 | int ret = 0; |
| 813 | |
| 814 | if (val < slpc->min_freq || val > slpc->rp0_freq) |
| 815 | return -EINVAL; |
| 816 | |
| 817 | mutex_lock(lock: &slpc->lock); |
| 818 | |
| 819 | if (slpc->boost_freq != val) { |
| 820 | /* Apply only if there are active waiters */ |
| 821 | if (atomic_read(v: &slpc->num_waiters)) { |
| 822 | ret = slpc_force_min_freq(slpc, freq: val); |
| 823 | if (ret) { |
| 824 | ret = -EIO; |
| 825 | goto done; |
| 826 | } |
| 827 | } |
| 828 | |
| 829 | slpc->boost_freq = val; |
| 830 | } |
| 831 | |
| 832 | done: |
| 833 | mutex_unlock(lock: &slpc->lock); |
| 834 | return ret; |
| 835 | } |
| 836 | |
| 837 | void intel_guc_slpc_dec_waiters(struct intel_guc_slpc *slpc) |
| 838 | { |
| 839 | /* |
| 840 | * Return min back to the softlimit. |
| 841 | * This is called during request retire, |
| 842 | * so we don't need to fail that if the |
| 843 | * set_param fails. |
| 844 | */ |
| 845 | mutex_lock(lock: &slpc->lock); |
| 846 | if (atomic_dec_and_test(v: &slpc->num_waiters)) |
| 847 | slpc_force_min_freq(slpc, freq: slpc->min_freq_softlimit); |
| 848 | mutex_unlock(lock: &slpc->lock); |
| 849 | } |
| 850 | |
| 851 | int intel_guc_slpc_print_info(struct intel_guc_slpc *slpc, struct drm_printer *p) |
| 852 | { |
| 853 | struct drm_i915_private *i915 = slpc_to_i915(slpc); |
| 854 | struct slpc_shared_data *data = slpc->vaddr; |
| 855 | struct slpc_task_state_data *slpc_tasks; |
| 856 | intel_wakeref_t wakeref; |
| 857 | int ret = 0; |
| 858 | |
| 859 | GEM_BUG_ON(!slpc->vma); |
| 860 | |
| 861 | with_intel_runtime_pm(&i915->runtime_pm, wakeref) { |
| 862 | ret = slpc_query_task_state(slpc); |
| 863 | |
| 864 | if (!ret) { |
| 865 | slpc_tasks = &data->task_state_data; |
| 866 | |
| 867 | drm_printf(p, f: "\tSLPC state: %s\n" , slpc_get_state_string(slpc)); |
| 868 | drm_printf(p, f: "\tGTPERF task active: %s\n" , |
| 869 | str_yes_no(v: slpc_tasks->status & SLPC_GTPERF_TASK_ENABLED)); |
| 870 | drm_printf(p, f: "\tDCC enabled: %s\n" , |
| 871 | str_yes_no(v: slpc_tasks->status & |
| 872 | SLPC_DCC_TASK_ENABLED)); |
| 873 | drm_printf(p, f: "\tDCC in: %s\n" , |
| 874 | str_yes_no(v: slpc_tasks->status & SLPC_IN_DCC)); |
| 875 | drm_printf(p, f: "\tBalancer enabled: %s\n" , |
| 876 | str_yes_no(v: slpc_tasks->status & |
| 877 | SLPC_BALANCER_ENABLED)); |
| 878 | drm_printf(p, f: "\tIBC enabled: %s\n" , |
| 879 | str_yes_no(v: slpc_tasks->status & |
| 880 | SLPC_IBC_TASK_ENABLED)); |
| 881 | drm_printf(p, f: "\tBalancer IA LMT enabled: %s\n" , |
| 882 | str_yes_no(v: slpc_tasks->status & |
| 883 | SLPC_BALANCER_IA_LMT_ENABLED)); |
| 884 | drm_printf(p, f: "\tBalancer IA LMT active: %s\n" , |
| 885 | str_yes_no(v: slpc_tasks->status & |
| 886 | SLPC_BALANCER_IA_LMT_ACTIVE)); |
| 887 | drm_printf(p, f: "\tMax freq: %u MHz\n" , |
| 888 | slpc_decode_max_freq(slpc)); |
| 889 | drm_printf(p, f: "\tMin freq: %u MHz\n" , |
| 890 | slpc_decode_min_freq(slpc)); |
| 891 | drm_printf(p, f: "\twaitboosts: %u\n" , |
| 892 | slpc->num_boosts); |
| 893 | drm_printf(p, f: "\tBoosts outstanding: %u\n" , |
| 894 | atomic_read(v: &slpc->num_waiters)); |
| 895 | } |
| 896 | } |
| 897 | |
| 898 | return ret; |
| 899 | } |
| 900 | |
| 901 | void intel_guc_slpc_fini(struct intel_guc_slpc *slpc) |
| 902 | { |
| 903 | if (!slpc->vma) |
| 904 | return; |
| 905 | |
| 906 | i915_vma_unpin_and_release(p_vma: &slpc->vma, I915_VMA_RELEASE_MAP); |
| 907 | } |
| 908 | |