| // SPDX-License-Identifier: MIT |
| /* |
| * Copyright © 2023-2024 Intel Corporation |
| */ |
| |
| #include <linux/string_choices.h> |
| #include <linux/wordpart.h> |
| |
| #include "abi/guc_actions_sriov_abi.h" |
| #include "abi/guc_klvs_abi.h" |
| |
| #include "regs/xe_guc_regs.h" |
| |
| #include "xe_bo.h" |
| #include "xe_device.h" |
| #include "xe_ggtt.h" |
| #include "xe_gt.h" |
| #include "xe_gt_sriov_pf_config.h" |
| #include "xe_gt_sriov_pf_helpers.h" |
| #include "xe_gt_sriov_pf_policy.h" |
| #include "xe_gt_sriov_printk.h" |
| #include "xe_guc.h" |
| #include "xe_guc_ct.h" |
| #include "xe_guc_db_mgr.h" |
| #include "xe_guc_fwif.h" |
| #include "xe_guc_id_mgr.h" |
| #include "xe_guc_klv_helpers.h" |
| #include "xe_guc_klv_thresholds_set.h" |
| #include "xe_guc_submit.h" |
| #include "xe_lmtt.h" |
| #include "xe_map.h" |
| #include "xe_sriov.h" |
| #include "xe_ttm_vram_mgr.h" |
| #include "xe_wopcm.h" |
| |
| /* |
| * Return: number of KLVs that were successfully parsed and saved, |
| * negative error code on failure. |
| */ |
| static int guc_action_update_vf_cfg(struct xe_guc *guc, u32 vfid, |
| u64 addr, u32 size) |
| { |
| u32 request[] = { |
| GUC_ACTION_PF2GUC_UPDATE_VF_CFG, |
| vfid, |
| lower_32_bits(addr), |
| upper_32_bits(addr), |
| size, |
| }; |
| |
| return xe_guc_ct_send_block(&guc->ct, request, ARRAY_SIZE(request)); |
| } |
| |
| /* |
| * Return: 0 on success, negative error code on failure. |
| */ |
| static int pf_send_vf_cfg_reset(struct xe_gt *gt, u32 vfid) |
| { |
| struct xe_guc *guc = >->uc.guc; |
| int ret; |
| |
| ret = guc_action_update_vf_cfg(guc, vfid, 0, 0); |
| |
| return ret <= 0 ? ret : -EPROTO; |
| } |
| |
| /* |
| * Return: number of KLVs that were successfully parsed and saved, |
| * negative error code on failure. |
| */ |
| static int pf_send_vf_cfg_klvs(struct xe_gt *gt, u32 vfid, const u32 *klvs, u32 num_dwords) |
| { |
| const u32 bytes = num_dwords * sizeof(u32); |
| struct xe_tile *tile = gt_to_tile(gt); |
| struct xe_device *xe = tile_to_xe(tile); |
| struct xe_guc *guc = >->uc.guc; |
| struct xe_bo *bo; |
| int ret; |
| |
| bo = xe_bo_create_pin_map(xe, tile, NULL, |
| ALIGN(bytes, PAGE_SIZE), |
| ttm_bo_type_kernel, |
| XE_BO_FLAG_VRAM_IF_DGFX(tile) | |
| XE_BO_FLAG_GGTT | |
| XE_BO_FLAG_GGTT_INVALIDATE); |
| if (IS_ERR(bo)) |
| return PTR_ERR(bo); |
| |
| xe_map_memcpy_to(xe, &bo->vmap, 0, klvs, bytes); |
| |
| ret = guc_action_update_vf_cfg(guc, vfid, xe_bo_ggtt_addr(bo), num_dwords); |
| |
| xe_bo_unpin_map_no_vm(bo); |
| |
| return ret; |
| } |
| |
| /* |
| * Return: 0 on success, -ENOKEY if some KLVs were not updated, -EPROTO if reply was malformed, |
| * negative error code on failure. |
| */ |
| static int pf_push_vf_cfg_klvs(struct xe_gt *gt, unsigned int vfid, u32 num_klvs, |
| const u32 *klvs, u32 num_dwords) |
| { |
| int ret; |
| |
| xe_gt_assert(gt, num_klvs == xe_guc_klv_count(klvs, num_dwords)); |
| |
| ret = pf_send_vf_cfg_klvs(gt, vfid, klvs, num_dwords); |
| |
| if (ret != num_klvs) { |
| int err = ret < 0 ? ret : ret < num_klvs ? -ENOKEY : -EPROTO; |
| struct drm_printer p = xe_gt_info_printer(gt); |
| char name[8]; |
| |
| xe_gt_sriov_notice(gt, "Failed to push %s %u config KLV%s (%pe)\n", |
| xe_sriov_function_name(vfid, name, sizeof(name)), |
| num_klvs, str_plural(num_klvs), ERR_PTR(err)); |
| xe_guc_klv_print(klvs, num_dwords, &p); |
| return err; |
| } |
| |
| if (IS_ENABLED(CONFIG_DRM_XE_DEBUG_SRIOV)) { |
| struct drm_printer p = xe_gt_info_printer(gt); |
| |
| xe_guc_klv_print(klvs, num_dwords, &p); |
| } |
| |
| return 0; |
| } |
| |
| static int pf_push_vf_cfg_u32(struct xe_gt *gt, unsigned int vfid, u16 key, u32 value) |
| { |
| u32 klv[] = { |
| FIELD_PREP(GUC_KLV_0_KEY, key) | FIELD_PREP(GUC_KLV_0_LEN, 1), |
| value, |
| }; |
| |
| return pf_push_vf_cfg_klvs(gt, vfid, 1, klv, ARRAY_SIZE(klv)); |
| } |
| |
| static int pf_push_vf_cfg_u64(struct xe_gt *gt, unsigned int vfid, u16 key, u64 value) |
| { |
| u32 klv[] = { |
| FIELD_PREP(GUC_KLV_0_KEY, key) | FIELD_PREP(GUC_KLV_0_LEN, 2), |
| lower_32_bits(value), |
| upper_32_bits(value), |
| }; |
| |
| return pf_push_vf_cfg_klvs(gt, vfid, 1, klv, ARRAY_SIZE(klv)); |
| } |
| |
| static int pf_push_vf_cfg_ggtt(struct xe_gt *gt, unsigned int vfid, u64 start, u64 size) |
| { |
| u32 klvs[] = { |
| PREP_GUC_KLV_TAG(VF_CFG_GGTT_START), |
| lower_32_bits(start), |
| upper_32_bits(start), |
| PREP_GUC_KLV_TAG(VF_CFG_GGTT_SIZE), |
| lower_32_bits(size), |
| upper_32_bits(size), |
| }; |
| |
| return pf_push_vf_cfg_klvs(gt, vfid, 2, klvs, ARRAY_SIZE(klvs)); |
| } |
| |
| static int pf_push_vf_cfg_ctxs(struct xe_gt *gt, unsigned int vfid, u32 begin, u32 num) |
| { |
| u32 klvs[] = { |
| PREP_GUC_KLV_TAG(VF_CFG_BEGIN_CONTEXT_ID), |
| begin, |
| PREP_GUC_KLV_TAG(VF_CFG_NUM_CONTEXTS), |
| num, |
| }; |
| |
| return pf_push_vf_cfg_klvs(gt, vfid, 2, klvs, ARRAY_SIZE(klvs)); |
| } |
| |
| static int pf_push_vf_cfg_dbs(struct xe_gt *gt, unsigned int vfid, u32 begin, u32 num) |
| { |
| u32 klvs[] = { |
| PREP_GUC_KLV_TAG(VF_CFG_BEGIN_DOORBELL_ID), |
| begin, |
| PREP_GUC_KLV_TAG(VF_CFG_NUM_DOORBELLS), |
| num, |
| }; |
| |
| return pf_push_vf_cfg_klvs(gt, vfid, 2, klvs, ARRAY_SIZE(klvs)); |
| } |
| |
| static int pf_push_vf_cfg_exec_quantum(struct xe_gt *gt, unsigned int vfid, u32 *exec_quantum) |
| { |
| /* GuC will silently clamp values exceeding max */ |
| *exec_quantum = min_t(u32, *exec_quantum, GUC_KLV_VF_CFG_EXEC_QUANTUM_MAX_VALUE); |
| |
| return pf_push_vf_cfg_u32(gt, vfid, GUC_KLV_VF_CFG_EXEC_QUANTUM_KEY, *exec_quantum); |
| } |
| |
| static int pf_push_vf_cfg_preempt_timeout(struct xe_gt *gt, unsigned int vfid, u32 *preempt_timeout) |
| { |
| /* GuC will silently clamp values exceeding max */ |
| *preempt_timeout = min_t(u32, *preempt_timeout, GUC_KLV_VF_CFG_PREEMPT_TIMEOUT_MAX_VALUE); |
| |
| return pf_push_vf_cfg_u32(gt, vfid, GUC_KLV_VF_CFG_PREEMPT_TIMEOUT_KEY, *preempt_timeout); |
| } |
| |
| static int pf_push_vf_cfg_lmem(struct xe_gt *gt, unsigned int vfid, u64 size) |
| { |
| return pf_push_vf_cfg_u64(gt, vfid, GUC_KLV_VF_CFG_LMEM_SIZE_KEY, size); |
| } |
| |
| static int pf_push_vf_cfg_threshold(struct xe_gt *gt, unsigned int vfid, |
| enum xe_guc_klv_threshold_index index, u32 value) |
| { |
| u32 key = xe_guc_klv_threshold_index_to_key(index); |
| |
| xe_gt_assert(gt, key); |
| return pf_push_vf_cfg_u32(gt, vfid, key, value); |
| } |
| |
| static struct xe_gt_sriov_config *pf_pick_vf_config(struct xe_gt *gt, unsigned int vfid) |
| { |
| xe_gt_assert(gt, IS_SRIOV_PF(gt_to_xe(gt))); |
| xe_gt_assert(gt, vfid <= xe_sriov_pf_get_totalvfs(gt_to_xe(gt))); |
| lockdep_assert_held(xe_gt_sriov_pf_master_mutex(gt)); |
| |
| return >->sriov.pf.vfs[vfid].config; |
| } |
| |
| /* Return: number of configuration dwords written */ |
| static u32 encode_config_ggtt(u32 *cfg, const struct xe_gt_sriov_config *config) |
| { |
| u32 n = 0; |
| |
| if (drm_mm_node_allocated(&config->ggtt_region)) { |
| cfg[n++] = PREP_GUC_KLV_TAG(VF_CFG_GGTT_START); |
| cfg[n++] = lower_32_bits(config->ggtt_region.start); |
| cfg[n++] = upper_32_bits(config->ggtt_region.start); |
| |
| cfg[n++] = PREP_GUC_KLV_TAG(VF_CFG_GGTT_SIZE); |
| cfg[n++] = lower_32_bits(config->ggtt_region.size); |
| cfg[n++] = upper_32_bits(config->ggtt_region.size); |
| } |
| |
| return n; |
| } |
| |
| /* Return: number of configuration dwords written */ |
| static u32 encode_config(u32 *cfg, const struct xe_gt_sriov_config *config) |
| { |
| u32 n = 0; |
| |
| n += encode_config_ggtt(cfg, config); |
| |
| cfg[n++] = PREP_GUC_KLV_TAG(VF_CFG_BEGIN_CONTEXT_ID); |
| cfg[n++] = config->begin_ctx; |
| |
| cfg[n++] = PREP_GUC_KLV_TAG(VF_CFG_NUM_CONTEXTS); |
| cfg[n++] = config->num_ctxs; |
| |
| cfg[n++] = PREP_GUC_KLV_TAG(VF_CFG_BEGIN_DOORBELL_ID); |
| cfg[n++] = config->begin_db; |
| |
| cfg[n++] = PREP_GUC_KLV_TAG(VF_CFG_NUM_DOORBELLS); |
| cfg[n++] = config->num_dbs; |
| |
| if (config->lmem_obj) { |
| cfg[n++] = PREP_GUC_KLV_TAG(VF_CFG_LMEM_SIZE); |
| cfg[n++] = lower_32_bits(config->lmem_obj->size); |
| cfg[n++] = upper_32_bits(config->lmem_obj->size); |
| } |
| |
| cfg[n++] = PREP_GUC_KLV_TAG(VF_CFG_EXEC_QUANTUM); |
| cfg[n++] = config->exec_quantum; |
| |
| cfg[n++] = PREP_GUC_KLV_TAG(VF_CFG_PREEMPT_TIMEOUT); |
| cfg[n++] = config->preempt_timeout; |
| |
| return n; |
| } |
| |
| static int pf_push_full_vf_config(struct xe_gt *gt, unsigned int vfid) |
| { |
| struct xe_gt_sriov_config *config = pf_pick_vf_config(gt, vfid); |
| u32 max_cfg_dwords = SZ_4K / sizeof(u32); |
| u32 num_dwords; |
| int num_klvs; |
| u32 *cfg; |
| int err; |
| |
| cfg = kcalloc(max_cfg_dwords, sizeof(u32), GFP_KERNEL); |
| if (!cfg) |
| return -ENOMEM; |
| |
| num_dwords = encode_config(cfg, config); |
| xe_gt_assert(gt, num_dwords <= max_cfg_dwords); |
| |
| if (xe_gt_is_media_type(gt)) { |
| struct xe_gt *primary = gt->tile->primary_gt; |
| struct xe_gt_sriov_config *other = pf_pick_vf_config(primary, vfid); |
| |
| /* media-GT will never include a GGTT config */ |
| xe_gt_assert(gt, !encode_config_ggtt(cfg + num_dwords, config)); |
| |
| /* the GGTT config must be taken from the primary-GT instead */ |
| num_dwords += encode_config_ggtt(cfg + num_dwords, other); |
| } |
| xe_gt_assert(gt, num_dwords <= max_cfg_dwords); |
| |
| num_klvs = xe_guc_klv_count(cfg, num_dwords); |
| err = pf_push_vf_cfg_klvs(gt, vfid, num_klvs, cfg, num_dwords); |
| |
| kfree(cfg); |
| return err; |
| } |
| |
| static u64 pf_get_ggtt_alignment(struct xe_gt *gt) |
| { |
| struct xe_device *xe = gt_to_xe(gt); |
| |
| return IS_DGFX(xe) && xe->info.vram_flags & XE_VRAM_FLAGS_NEED64K ? SZ_64K : SZ_4K; |
| } |
| |
| static u64 pf_get_min_spare_ggtt(struct xe_gt *gt) |
| { |
| /* XXX: preliminary */ |
| return IS_ENABLED(CONFIG_DRM_XE_DEBUG_SRIOV) ? |
| pf_get_ggtt_alignment(gt) : SZ_64M; |
| } |
| |
| static u64 pf_get_spare_ggtt(struct xe_gt *gt) |
| { |
| u64 spare; |
| |
| xe_gt_assert(gt, !xe_gt_is_media_type(gt)); |
| xe_gt_assert(gt, IS_SRIOV_PF(gt_to_xe(gt))); |
| lockdep_assert_held(xe_gt_sriov_pf_master_mutex(gt)); |
| |
| spare = gt->sriov.pf.spare.ggtt_size; |
| spare = max_t(u64, spare, pf_get_min_spare_ggtt(gt)); |
| |
| return spare; |
| } |
| |
| static int pf_set_spare_ggtt(struct xe_gt *gt, u64 size) |
| { |
| xe_gt_assert(gt, !xe_gt_is_media_type(gt)); |
| xe_gt_assert(gt, IS_SRIOV_PF(gt_to_xe(gt))); |
| lockdep_assert_held(xe_gt_sriov_pf_master_mutex(gt)); |
| |
| if (size && size < pf_get_min_spare_ggtt(gt)) |
| return -EINVAL; |
| |
| size = round_up(size, pf_get_ggtt_alignment(gt)); |
| gt->sriov.pf.spare.ggtt_size = size; |
| |
| return 0; |
| } |
| |
| static int pf_distribute_config_ggtt(struct xe_tile *tile, unsigned int vfid, u64 start, u64 size) |
| { |
| int err, err2 = 0; |
| |
| err = pf_push_vf_cfg_ggtt(tile->primary_gt, vfid, start, size); |
| |
| if (tile->media_gt && !err) |
| err2 = pf_push_vf_cfg_ggtt(tile->media_gt, vfid, start, size); |
| |
| return err ?: err2; |
| } |
| |
| static void pf_release_ggtt(struct xe_tile *tile, struct drm_mm_node *node) |
| { |
| struct xe_ggtt *ggtt = tile->mem.ggtt; |
| |
| if (drm_mm_node_allocated(node)) { |
| /* |
| * explicit GGTT PTE assignment to the PF using xe_ggtt_assign() |
| * is redundant, as PTE will be implicitly re-assigned to PF by |
| * the xe_ggtt_clear() called by below xe_ggtt_remove_node(). |
| */ |
| xe_ggtt_remove_node(ggtt, node, false); |
| } |
| } |
| |
| static void pf_release_vf_config_ggtt(struct xe_gt *gt, struct xe_gt_sriov_config *config) |
| { |
| pf_release_ggtt(gt_to_tile(gt), &config->ggtt_region); |
| } |
| |
| static int pf_provision_vf_ggtt(struct xe_gt *gt, unsigned int vfid, u64 size) |
| { |
| struct xe_gt_sriov_config *config = pf_pick_vf_config(gt, vfid); |
| struct drm_mm_node *node = &config->ggtt_region; |
| struct xe_tile *tile = gt_to_tile(gt); |
| struct xe_ggtt *ggtt = tile->mem.ggtt; |
| u64 alignment = pf_get_ggtt_alignment(gt); |
| int err; |
| |
| xe_gt_assert(gt, vfid); |
| xe_gt_assert(gt, !xe_gt_is_media_type(gt)); |
| xe_gt_assert(gt, IS_SRIOV_PF(gt_to_xe(gt))); |
| |
| size = round_up(size, alignment); |
| |
| if (drm_mm_node_allocated(node)) { |
| err = pf_distribute_config_ggtt(tile, vfid, 0, 0); |
| if (unlikely(err)) |
| return err; |
| |
| pf_release_ggtt(tile, node); |
| } |
| xe_gt_assert(gt, !drm_mm_node_allocated(node)); |
| |
| if (!size) |
| return 0; |
| |
| err = xe_ggtt_insert_special_node(ggtt, node, size, alignment); |
| if (unlikely(err)) |
| return err; |
| |
| xe_ggtt_assign(ggtt, node, vfid); |
| xe_gt_sriov_dbg_verbose(gt, "VF%u assigned GGTT %llx-%llx\n", |
| vfid, node->start, node->start + node->size - 1); |
| |
| err = pf_distribute_config_ggtt(gt->tile, vfid, node->start, node->size); |
| if (unlikely(err)) |
| return err; |
| |
| return 0; |
| } |
| |
| static u64 pf_get_vf_config_ggtt(struct xe_gt *gt, unsigned int vfid) |
| { |
| struct xe_gt_sriov_config *config = pf_pick_vf_config(gt, vfid); |
| struct drm_mm_node *node = &config->ggtt_region; |
| |
| xe_gt_assert(gt, !xe_gt_is_media_type(gt)); |
| return drm_mm_node_allocated(node) ? node->size : 0; |
| } |
| |
| /** |
| * xe_gt_sriov_pf_config_get_ggtt - Query size of GGTT address space of the VF. |
| * @gt: the &xe_gt |
| * @vfid: the VF identifier |
| * |
| * This function can only be called on PF. |
| * |
| * Return: size of the VF's assigned (or PF's spare) GGTT address space. |
| */ |
| u64 xe_gt_sriov_pf_config_get_ggtt(struct xe_gt *gt, unsigned int vfid) |
| { |
| u64 size; |
| |
| mutex_lock(xe_gt_sriov_pf_master_mutex(gt)); |
| if (vfid) |
| size = pf_get_vf_config_ggtt(gt_to_tile(gt)->primary_gt, vfid); |
| else |
| size = pf_get_spare_ggtt(gt_to_tile(gt)->primary_gt); |
| mutex_unlock(xe_gt_sriov_pf_master_mutex(gt)); |
| |
| return size; |
| } |
| |
| static int pf_config_set_u64_done(struct xe_gt *gt, unsigned int vfid, u64 value, |
| u64 actual, const char *what, int err) |
| { |
| char size[10]; |
| char name[8]; |
| |
| xe_sriov_function_name(vfid, name, sizeof(name)); |
| |
| if (unlikely(err)) { |
| string_get_size(value, 1, STRING_UNITS_2, size, sizeof(size)); |
| xe_gt_sriov_notice(gt, "Failed to provision %s with %llu (%s) %s (%pe)\n", |
| name, value, size, what, ERR_PTR(err)); |
| string_get_size(actual, 1, STRING_UNITS_2, size, sizeof(size)); |
| xe_gt_sriov_info(gt, "%s provisioning remains at %llu (%s) %s\n", |
| name, actual, size, what); |
| return err; |
| } |
| |
| /* the actual value may have changed during provisioning */ |
| string_get_size(actual, 1, STRING_UNITS_2, size, sizeof(size)); |
| xe_gt_sriov_info(gt, "%s provisioned with %llu (%s) %s\n", |
| name, actual, size, what); |
| return 0; |
| } |
| |
| /** |
| * xe_gt_sriov_pf_config_set_ggtt - Provision VF with GGTT space. |
| * @gt: the &xe_gt (can't be media) |
| * @vfid: the VF identifier |
| * @size: requested GGTT size |
| * |
| * If &vfid represents PF, then function will change PF's spare GGTT config. |
| * |
| * This function can only be called on PF. |
| * |
| * Return: 0 on success or a negative error code on failure. |
| */ |
| int xe_gt_sriov_pf_config_set_ggtt(struct xe_gt *gt, unsigned int vfid, u64 size) |
| { |
| int err; |
| |
| xe_gt_assert(gt, !xe_gt_is_media_type(gt)); |
| |
| mutex_lock(xe_gt_sriov_pf_master_mutex(gt)); |
| if (vfid) |
| err = pf_provision_vf_ggtt(gt, vfid, size); |
| else |
| err = pf_set_spare_ggtt(gt, size); |
| mutex_unlock(xe_gt_sriov_pf_master_mutex(gt)); |
| |
| return pf_config_set_u64_done(gt, vfid, size, |
| xe_gt_sriov_pf_config_get_ggtt(gt, vfid), |
| vfid ? "GGTT" : "spare GGTT", err); |
| } |
| |
| static int pf_config_bulk_set_u64_done(struct xe_gt *gt, unsigned int first, unsigned int num_vfs, |
| u64 value, u64 (*get)(struct xe_gt*, unsigned int), |
| const char *what, unsigned int last, int err) |
| { |
| char size[10]; |
| |
| xe_gt_assert(gt, first); |
| xe_gt_assert(gt, num_vfs); |
| xe_gt_assert(gt, first <= last); |
| |
| if (num_vfs == 1) |
| return pf_config_set_u64_done(gt, first, value, get(gt, first), what, err); |
| |
| if (unlikely(err)) { |
| xe_gt_sriov_notice(gt, "Failed to bulk provision VF%u..VF%u with %s\n", |
| first, first + num_vfs - 1, what); |
| if (last > first) |
| pf_config_bulk_set_u64_done(gt, first, last - first, value, |
| get, what, last, 0); |
| return pf_config_set_u64_done(gt, last, value, get(gt, last), what, err); |
| } |
| |
| /* pick actual value from first VF - bulk provisioning shall be equal across all VFs */ |
| value = get(gt, first); |
| string_get_size(value, 1, STRING_UNITS_2, size, sizeof(size)); |
| xe_gt_sriov_info(gt, "VF%u..VF%u provisioned with %llu (%s) %s\n", |
| first, first + num_vfs - 1, value, size, what); |
| return 0; |
| } |
| |
| /** |
| * xe_gt_sriov_pf_config_bulk_set_ggtt - Provision many VFs with GGTT. |
| * @gt: the &xe_gt (can't be media) |
| * @vfid: starting VF identifier (can't be 0) |
| * @num_vfs: number of VFs to provision |
| * @size: requested GGTT size |
| * |
| * This function can only be called on PF. |
| * |
| * Return: 0 on success or a negative error code on failure. |
| */ |
| int xe_gt_sriov_pf_config_bulk_set_ggtt(struct xe_gt *gt, unsigned int vfid, |
| unsigned int num_vfs, u64 size) |
| { |
| unsigned int n; |
| int err = 0; |
| |
| xe_gt_assert(gt, vfid); |
| xe_gt_assert(gt, !xe_gt_is_media_type(gt)); |
| |
| if (!num_vfs) |
| return 0; |
| |
| mutex_lock(xe_gt_sriov_pf_master_mutex(gt)); |
| for (n = vfid; n < vfid + num_vfs; n++) { |
| err = pf_provision_vf_ggtt(gt, n, size); |
| if (err) |
| break; |
| } |
| mutex_unlock(xe_gt_sriov_pf_master_mutex(gt)); |
| |
| return pf_config_bulk_set_u64_done(gt, vfid, num_vfs, size, |
| xe_gt_sriov_pf_config_get_ggtt, |
| "GGTT", n, err); |
| } |
| |
| /* Return: size of the largest continuous GGTT region */ |
| static u64 pf_get_max_ggtt(struct xe_gt *gt) |
| { |
| struct xe_ggtt *ggtt = gt_to_tile(gt)->mem.ggtt; |
| const struct drm_mm *mm = &ggtt->mm; |
| const struct drm_mm_node *entry; |
| u64 alignment = pf_get_ggtt_alignment(gt); |
| u64 spare = pf_get_spare_ggtt(gt); |
| u64 hole_min_start = xe_wopcm_size(gt_to_xe(gt)); |
| u64 hole_start, hole_end, hole_size; |
| u64 max_hole = 0; |
| |
| mutex_lock(&ggtt->lock); |
| |
| drm_mm_for_each_hole(entry, mm, hole_start, hole_end) { |
| hole_start = max(hole_start, hole_min_start); |
| hole_start = ALIGN(hole_start, alignment); |
| hole_end = ALIGN_DOWN(hole_end, alignment); |
| if (hole_start >= hole_end) |
| continue; |
| hole_size = hole_end - hole_start; |
| xe_gt_sriov_dbg_verbose(gt, "HOLE start %llx size %lluK\n", |
| hole_start, hole_size / SZ_1K); |
| spare -= min3(spare, hole_size, max_hole); |
| max_hole = max(max_hole, hole_size); |
| } |
| |
| mutex_unlock(&ggtt->lock); |
| |
| xe_gt_sriov_dbg_verbose(gt, "HOLE max %lluK reserved %lluK\n", |
| max_hole / SZ_1K, spare / SZ_1K); |
| return max_hole > spare ? max_hole - spare : 0; |
| } |
| |
| static u64 pf_estimate_fair_ggtt(struct xe_gt *gt, unsigned int num_vfs) |
| { |
| u64 available = pf_get_max_ggtt(gt); |
| u64 alignment = pf_get_ggtt_alignment(gt); |
| u64 fair; |
| |
| /* |
| * To simplify the logic we only look at single largest GGTT region |
| * as that will be always the best fit for 1 VF case, and most likely |
| * will also nicely cover other cases where VFs are provisioned on the |
| * fresh and idle PF driver, without any stale GGTT allocations spread |
| * in the middle of the full GGTT range. |
| */ |
| |
| fair = div_u64(available, num_vfs); |
| fair = ALIGN_DOWN(fair, alignment); |
| xe_gt_sriov_dbg_verbose(gt, "GGTT available(%lluK) fair(%u x %lluK)\n", |
| available / SZ_1K, num_vfs, fair / SZ_1K); |
| return fair; |
| } |
| |
| /** |
| * xe_gt_sriov_pf_config_set_fair_ggtt - Provision many VFs with fair GGTT. |
| * @gt: the &xe_gt (can't be media) |
| * @vfid: starting VF identifier (can't be 0) |
| * @num_vfs: number of VFs to provision |
| * |
| * This function can only be called on PF. |
| * |
| * Return: 0 on success or a negative error code on failure. |
| */ |
| int xe_gt_sriov_pf_config_set_fair_ggtt(struct xe_gt *gt, unsigned int vfid, |
| unsigned int num_vfs) |
| { |
| u64 fair; |
| |
| xe_gt_assert(gt, vfid); |
| xe_gt_assert(gt, num_vfs); |
| xe_gt_assert(gt, !xe_gt_is_media_type(gt)); |
| |
| mutex_lock(xe_gt_sriov_pf_master_mutex(gt)); |
| fair = pf_estimate_fair_ggtt(gt, num_vfs); |
| mutex_unlock(xe_gt_sriov_pf_master_mutex(gt)); |
| |
| if (!fair) |
| return -ENOSPC; |
| |
| return xe_gt_sriov_pf_config_bulk_set_ggtt(gt, vfid, num_vfs, fair); |
| } |
| |
| static u32 pf_get_min_spare_ctxs(struct xe_gt *gt) |
| { |
| /* XXX: preliminary */ |
| return IS_ENABLED(CONFIG_DRM_XE_DEBUG_SRIOV) ? |
| hweight64(gt->info.engine_mask) : SZ_256; |
| } |
| |
| static u32 pf_get_spare_ctxs(struct xe_gt *gt) |
| { |
| u32 spare; |
| |
| xe_gt_assert(gt, IS_SRIOV_PF(gt_to_xe(gt))); |
| lockdep_assert_held(xe_gt_sriov_pf_master_mutex(gt)); |
| |
| spare = gt->sriov.pf.spare.num_ctxs; |
| spare = max_t(u32, spare, pf_get_min_spare_ctxs(gt)); |
| |
| return spare; |
| } |
| |
| static int pf_set_spare_ctxs(struct xe_gt *gt, u32 spare) |
| { |
| xe_gt_assert(gt, IS_SRIOV_PF(gt_to_xe(gt))); |
| lockdep_assert_held(xe_gt_sriov_pf_master_mutex(gt)); |
| |
| if (spare > GUC_ID_MAX) |
| return -EINVAL; |
| |
| if (spare && spare < pf_get_min_spare_ctxs(gt)) |
| return -EINVAL; |
| |
| gt->sriov.pf.spare.num_ctxs = spare; |
| |
| return 0; |
| } |
| |
| /* Return: start ID or negative error code on failure */ |
| static int pf_reserve_ctxs(struct xe_gt *gt, u32 num) |
| { |
| struct xe_guc_id_mgr *idm = >->uc.guc.submission_state.idm; |
| unsigned int spare = pf_get_spare_ctxs(gt); |
| |
| return xe_guc_id_mgr_reserve(idm, num, spare); |
| } |
| |
| static void pf_release_ctxs(struct xe_gt *gt, u32 start, u32 num) |
| { |
| struct xe_guc_id_mgr *idm = >->uc.guc.submission_state.idm; |
| |
| if (num) |
| xe_guc_id_mgr_release(idm, start, num); |
| } |
| |
| static void pf_release_config_ctxs(struct xe_gt *gt, struct xe_gt_sriov_config *config) |
| { |
| lockdep_assert_held(xe_gt_sriov_pf_master_mutex(gt)); |
| |
| pf_release_ctxs(gt, config->begin_ctx, config->num_ctxs); |
| config->begin_ctx = 0; |
| config->num_ctxs = 0; |
| } |
| |
| static int pf_provision_vf_ctxs(struct xe_gt *gt, unsigned int vfid, u32 num_ctxs) |
| { |
| struct xe_gt_sriov_config *config = pf_pick_vf_config(gt, vfid); |
| int ret; |
| |
| xe_gt_assert(gt, vfid); |
| |
| if (num_ctxs > GUC_ID_MAX) |
| return -EINVAL; |
| |
| if (config->num_ctxs) { |
| ret = pf_push_vf_cfg_ctxs(gt, vfid, 0, 0); |
| if (unlikely(ret)) |
| return ret; |
| |
| pf_release_config_ctxs(gt, config); |
| } |
| |
| if (!num_ctxs) |
| return 0; |
| |
| ret = pf_reserve_ctxs(gt, num_ctxs); |
| if (unlikely(ret < 0)) |
| return ret; |
| |
| config->begin_ctx = ret; |
| config->num_ctxs = num_ctxs; |
| |
| ret = pf_push_vf_cfg_ctxs(gt, vfid, config->begin_ctx, config->num_ctxs); |
| if (unlikely(ret)) { |
| pf_release_config_ctxs(gt, config); |
| return ret; |
| } |
| |
| xe_gt_sriov_dbg_verbose(gt, "VF%u contexts %u-%u\n", |
| vfid, config->begin_ctx, config->begin_ctx + config->num_ctxs - 1); |
| return 0; |
| } |
| |
| static u32 pf_get_vf_config_ctxs(struct xe_gt *gt, unsigned int vfid) |
| { |
| struct xe_gt_sriov_config *config = pf_pick_vf_config(gt, vfid); |
| |
| return config->num_ctxs; |
| } |
| |
| /** |
| * xe_gt_sriov_pf_config_get_ctxs - Get VF's GuC contexts IDs quota. |
| * @gt: the &xe_gt |
| * @vfid: the VF identifier |
| * |
| * This function can only be called on PF. |
| * If &vfid represents a PF then number of PF's spare GuC context IDs is returned. |
| * |
| * Return: VF's quota (or PF's spare). |
| */ |
| u32 xe_gt_sriov_pf_config_get_ctxs(struct xe_gt *gt, unsigned int vfid) |
| { |
| u32 num_ctxs; |
| |
| mutex_lock(xe_gt_sriov_pf_master_mutex(gt)); |
| if (vfid) |
| num_ctxs = pf_get_vf_config_ctxs(gt, vfid); |
| else |
| num_ctxs = pf_get_spare_ctxs(gt); |
| mutex_unlock(xe_gt_sriov_pf_master_mutex(gt)); |
| |
| return num_ctxs; |
| } |
| |
| static const char *no_unit(u32 unused) |
| { |
| return ""; |
| } |
| |
| static const char *spare_unit(u32 unused) |
| { |
| return " spare"; |
| } |
| |
| static int pf_config_set_u32_done(struct xe_gt *gt, unsigned int vfid, u32 value, u32 actual, |
| const char *what, const char *(*unit)(u32), int err) |
| { |
| char name[8]; |
| |
| xe_sriov_function_name(vfid, name, sizeof(name)); |
| |
| if (unlikely(err)) { |
| xe_gt_sriov_notice(gt, "Failed to provision %s with %u%s %s (%pe)\n", |
| name, value, unit(value), what, ERR_PTR(err)); |
| xe_gt_sriov_info(gt, "%s provisioning remains at %u%s %s\n", |
| name, actual, unit(actual), what); |
| return err; |
| } |
| |
| /* the actual value may have changed during provisioning */ |
| xe_gt_sriov_info(gt, "%s provisioned with %u%s %s\n", |
| name, actual, unit(actual), what); |
| return 0; |
| } |
| |
| /** |
| * xe_gt_sriov_pf_config_set_ctxs - Configure GuC contexts IDs quota for the VF. |
| * @gt: the &xe_gt |
| * @vfid: the VF identifier |
| * @num_ctxs: requested number of GuC contexts IDs (0 to release) |
| * |
| * This function can only be called on PF. |
| * |
| * Return: 0 on success or a negative error code on failure. |
| */ |
| int xe_gt_sriov_pf_config_set_ctxs(struct xe_gt *gt, unsigned int vfid, u32 num_ctxs) |
| { |
| int err; |
| |
| mutex_lock(xe_gt_sriov_pf_master_mutex(gt)); |
| if (vfid) |
| err = pf_provision_vf_ctxs(gt, vfid, num_ctxs); |
| else |
| err = pf_set_spare_ctxs(gt, num_ctxs); |
| mutex_unlock(xe_gt_sriov_pf_master_mutex(gt)); |
| |
| return pf_config_set_u32_done(gt, vfid, num_ctxs, |
| xe_gt_sriov_pf_config_get_ctxs(gt, vfid), |
| "GuC context IDs", vfid ? no_unit : spare_unit, err); |
| } |
| |
| static int pf_config_bulk_set_u32_done(struct xe_gt *gt, unsigned int first, unsigned int num_vfs, |
| u32 value, u32 (*get)(struct xe_gt*, unsigned int), |
| const char *what, const char *(*unit)(u32), |
| unsigned int last, int err) |
| { |
| xe_gt_assert(gt, first); |
| xe_gt_assert(gt, num_vfs); |
| xe_gt_assert(gt, first <= last); |
| |
| if (num_vfs == 1) |
| return pf_config_set_u32_done(gt, first, value, get(gt, first), what, unit, err); |
| |
| if (unlikely(err)) { |
| xe_gt_sriov_notice(gt, "Failed to bulk provision VF%u..VF%u with %s\n", |
| first, first + num_vfs - 1, what); |
| if (last > first) |
| pf_config_bulk_set_u32_done(gt, first, last - first, value, |
| get, what, unit, last, 0); |
| return pf_config_set_u32_done(gt, last, value, get(gt, last), what, unit, err); |
| } |
| |
| /* pick actual value from first VF - bulk provisioning shall be equal across all VFs */ |
| value = get(gt, first); |
| xe_gt_sriov_info(gt, "VF%u..VF%u provisioned with %u%s %s\n", |
| first, first + num_vfs - 1, value, unit(value), what); |
| return 0; |
| } |
| |
| /** |
| * xe_gt_sriov_pf_config_bulk_set_ctxs - Provision many VFs with GuC context IDs. |
| * @gt: the &xe_gt |
| * @vfid: starting VF identifier |
| * @num_vfs: number of VFs to provision |
| * @num_ctxs: requested number of GuC contexts IDs (0 to release) |
| * |
| * This function can only be called on PF. |
| * |
| * Return: 0 on success or a negative error code on failure. |
| */ |
| int xe_gt_sriov_pf_config_bulk_set_ctxs(struct xe_gt *gt, unsigned int vfid, |
| unsigned int num_vfs, u32 num_ctxs) |
| { |
| unsigned int n; |
| int err = 0; |
| |
| xe_gt_assert(gt, vfid); |
| |
| if (!num_vfs) |
| return 0; |
| |
| mutex_lock(xe_gt_sriov_pf_master_mutex(gt)); |
| for (n = vfid; n < vfid + num_vfs; n++) { |
| err = pf_provision_vf_ctxs(gt, n, num_ctxs); |
| if (err) |
| break; |
| } |
| mutex_unlock(xe_gt_sriov_pf_master_mutex(gt)); |
| |
| return pf_config_bulk_set_u32_done(gt, vfid, num_vfs, num_ctxs, |
| xe_gt_sriov_pf_config_get_ctxs, |
| "GuC context IDs", no_unit, n, err); |
| } |
| |
| static u32 pf_estimate_fair_ctxs(struct xe_gt *gt, unsigned int num_vfs) |
| { |
| struct xe_guc_id_mgr *idm = >->uc.guc.submission_state.idm; |
| u32 spare = pf_get_spare_ctxs(gt); |
| u32 fair = (idm->total - spare) / num_vfs; |
| int ret; |
| |
| for (; fair; --fair) { |
| ret = xe_guc_id_mgr_reserve(idm, fair * num_vfs, spare); |
| if (ret < 0) |
| continue; |
| xe_guc_id_mgr_release(idm, ret, fair * num_vfs); |
| break; |
| } |
| |
| xe_gt_sriov_dbg_verbose(gt, "contexts fair(%u x %u)\n", num_vfs, fair); |
| return fair; |
| } |
| |
| /** |
| * xe_gt_sriov_pf_config_set_fair_ctxs - Provision many VFs with fair GuC context IDs. |
| * @gt: the &xe_gt |
| * @vfid: starting VF identifier (can't be 0) |
| * @num_vfs: number of VFs to provision (can't be 0) |
| * |
| * This function can only be called on PF. |
| * |
| * Return: 0 on success or a negative error code on failure. |
| */ |
| int xe_gt_sriov_pf_config_set_fair_ctxs(struct xe_gt *gt, unsigned int vfid, |
| unsigned int num_vfs) |
| { |
| u32 fair; |
| |
| xe_gt_assert(gt, vfid); |
| xe_gt_assert(gt, num_vfs); |
| |
| mutex_lock(xe_gt_sriov_pf_master_mutex(gt)); |
| fair = pf_estimate_fair_ctxs(gt, num_vfs); |
| mutex_unlock(xe_gt_sriov_pf_master_mutex(gt)); |
| |
| if (!fair) |
| return -ENOSPC; |
| |
| return xe_gt_sriov_pf_config_bulk_set_ctxs(gt, vfid, num_vfs, fair); |
| } |
| |
| static u32 pf_get_min_spare_dbs(struct xe_gt *gt) |
| { |
| /* XXX: preliminary, we don't use doorbells yet! */ |
| return IS_ENABLED(CONFIG_DRM_XE_DEBUG_SRIOV) ? 1 : 0; |
| } |
| |
| static u32 pf_get_spare_dbs(struct xe_gt *gt) |
| { |
| u32 spare; |
| |
| xe_gt_assert(gt, IS_SRIOV_PF(gt_to_xe(gt))); |
| lockdep_assert_held(xe_gt_sriov_pf_master_mutex(gt)); |
| |
| spare = gt->sriov.pf.spare.num_dbs; |
| spare = max_t(u32, spare, pf_get_min_spare_dbs(gt)); |
| |
| return spare; |
| } |
| |
| static int pf_set_spare_dbs(struct xe_gt *gt, u32 spare) |
| { |
| xe_gt_assert(gt, IS_SRIOV_PF(gt_to_xe(gt))); |
| lockdep_assert_held(xe_gt_sriov_pf_master_mutex(gt)); |
| |
| if (spare > GUC_NUM_DOORBELLS) |
| return -EINVAL; |
| |
| if (spare && spare < pf_get_min_spare_dbs(gt)) |
| return -EINVAL; |
| |
| gt->sriov.pf.spare.num_dbs = spare; |
| return 0; |
| } |
| |
| /* Return: start ID or negative error code on failure */ |
| static int pf_reserve_dbs(struct xe_gt *gt, u32 num) |
| { |
| struct xe_guc_db_mgr *dbm = >->uc.guc.dbm; |
| unsigned int spare = pf_get_spare_dbs(gt); |
| |
| return xe_guc_db_mgr_reserve_range(dbm, num, spare); |
| } |
| |
| static void pf_release_dbs(struct xe_gt *gt, u32 start, u32 num) |
| { |
| struct xe_guc_db_mgr *dbm = >->uc.guc.dbm; |
| |
| if (num) |
| xe_guc_db_mgr_release_range(dbm, start, num); |
| } |
| |
| static void pf_release_config_dbs(struct xe_gt *gt, struct xe_gt_sriov_config *config) |
| { |
| lockdep_assert_held(xe_gt_sriov_pf_master_mutex(gt)); |
| |
| pf_release_dbs(gt, config->begin_db, config->num_dbs); |
| config->begin_db = 0; |
| config->num_dbs = 0; |
| } |
| |
| static int pf_provision_vf_dbs(struct xe_gt *gt, unsigned int vfid, u32 num_dbs) |
| { |
| struct xe_gt_sriov_config *config = pf_pick_vf_config(gt, vfid); |
| int ret; |
| |
| xe_gt_assert(gt, vfid); |
| |
| if (num_dbs > GUC_NUM_DOORBELLS) |
| return -EINVAL; |
| |
| if (config->num_dbs) { |
| ret = pf_push_vf_cfg_dbs(gt, vfid, 0, 0); |
| if (unlikely(ret)) |
| return ret; |
| |
| pf_release_config_dbs(gt, config); |
| } |
| |
| if (!num_dbs) |
| return 0; |
| |
| ret = pf_reserve_dbs(gt, num_dbs); |
| if (unlikely(ret < 0)) |
| return ret; |
| |
| config->begin_db = ret; |
| config->num_dbs = num_dbs; |
| |
| ret = pf_push_vf_cfg_dbs(gt, vfid, config->begin_db, config->num_dbs); |
| if (unlikely(ret)) { |
| pf_release_config_dbs(gt, config); |
| return ret; |
| } |
| |
| xe_gt_sriov_dbg_verbose(gt, "VF%u doorbells %u-%u\n", |
| vfid, config->begin_db, config->begin_db + config->num_dbs - 1); |
| return 0; |
| } |
| |
| static u32 pf_get_vf_config_dbs(struct xe_gt *gt, unsigned int vfid) |
| { |
| struct xe_gt_sriov_config *config = pf_pick_vf_config(gt, vfid); |
| |
| return config->num_dbs; |
| } |
| |
| /** |
| * xe_gt_sriov_pf_config_get_dbs - Get VF's GuC doorbells IDs quota. |
| * @gt: the &xe_gt |
| * @vfid: the VF identifier |
| * |
| * This function can only be called on PF. |
| * If &vfid represents a PF then number of PF's spare GuC doorbells IDs is returned. |
| * |
| * Return: VF's quota (or PF's spare). |
| */ |
| u32 xe_gt_sriov_pf_config_get_dbs(struct xe_gt *gt, unsigned int vfid) |
| { |
| u32 num_dbs; |
| |
| xe_gt_assert(gt, IS_SRIOV_PF(gt_to_xe(gt))); |
| xe_gt_assert(gt, vfid <= xe_sriov_pf_get_totalvfs(gt_to_xe(gt))); |
| |
| mutex_lock(xe_gt_sriov_pf_master_mutex(gt)); |
| if (vfid) |
| num_dbs = pf_get_vf_config_dbs(gt, vfid); |
| else |
| num_dbs = pf_get_spare_dbs(gt); |
| mutex_unlock(xe_gt_sriov_pf_master_mutex(gt)); |
| |
| return num_dbs; |
| } |
| |
| /** |
| * xe_gt_sriov_pf_config_set_dbs - Configure GuC doorbells IDs quota for the VF. |
| * @gt: the &xe_gt |
| * @vfid: the VF identifier |
| * @num_dbs: requested number of GuC doorbells IDs (0 to release) |
| * |
| * This function can only be called on PF. |
| * |
| * Return: 0 on success or a negative error code on failure. |
| */ |
| int xe_gt_sriov_pf_config_set_dbs(struct xe_gt *gt, unsigned int vfid, u32 num_dbs) |
| { |
| int err; |
| |
| xe_gt_assert(gt, IS_SRIOV_PF(gt_to_xe(gt))); |
| xe_gt_assert(gt, vfid <= xe_sriov_pf_get_totalvfs(gt_to_xe(gt))); |
| |
| mutex_lock(xe_gt_sriov_pf_master_mutex(gt)); |
| if (vfid) |
| err = pf_provision_vf_dbs(gt, vfid, num_dbs); |
| else |
| err = pf_set_spare_dbs(gt, num_dbs); |
| mutex_unlock(xe_gt_sriov_pf_master_mutex(gt)); |
| |
| return pf_config_set_u32_done(gt, vfid, num_dbs, |
| xe_gt_sriov_pf_config_get_dbs(gt, vfid), |
| "GuC doorbell IDs", vfid ? no_unit : spare_unit, err); |
| } |
| |
| /** |
| * xe_gt_sriov_pf_config_bulk_set_dbs - Provision many VFs with GuC context IDs. |
| * @gt: the &xe_gt |
| * @vfid: starting VF identifier (can't be 0) |
| * @num_vfs: number of VFs to provision |
| * @num_dbs: requested number of GuC doorbell IDs (0 to release) |
| * |
| * This function can only be called on PF. |
| * |
| * Return: 0 on success or a negative error code on failure. |
| */ |
| int xe_gt_sriov_pf_config_bulk_set_dbs(struct xe_gt *gt, unsigned int vfid, |
| unsigned int num_vfs, u32 num_dbs) |
| { |
| unsigned int n; |
| int err = 0; |
| |
| xe_gt_assert(gt, vfid); |
| |
| if (!num_vfs) |
| return 0; |
| |
| mutex_lock(xe_gt_sriov_pf_master_mutex(gt)); |
| for (n = vfid; n < vfid + num_vfs; n++) { |
| err = pf_provision_vf_dbs(gt, n, num_dbs); |
| if (err) |
| break; |
| } |
| mutex_unlock(xe_gt_sriov_pf_master_mutex(gt)); |
| |
| return pf_config_bulk_set_u32_done(gt, vfid, num_vfs, num_dbs, |
| xe_gt_sriov_pf_config_get_dbs, |
| "GuC doorbell IDs", no_unit, n, err); |
| } |
| |
| static u32 pf_estimate_fair_dbs(struct xe_gt *gt, unsigned int num_vfs) |
| { |
| struct xe_guc_db_mgr *dbm = >->uc.guc.dbm; |
| u32 spare = pf_get_spare_dbs(gt); |
| u32 fair = (GUC_NUM_DOORBELLS - spare) / num_vfs; |
| int ret; |
| |
| for (; fair; --fair) { |
| ret = xe_guc_db_mgr_reserve_range(dbm, fair * num_vfs, spare); |
| if (ret < 0) |
| continue; |
| xe_guc_db_mgr_release_range(dbm, ret, fair * num_vfs); |
| break; |
| } |
| |
| xe_gt_sriov_dbg_verbose(gt, "doorbells fair(%u x %u)\n", num_vfs, fair); |
| return fair; |
| } |
| |
| /** |
| * xe_gt_sriov_pf_config_set_fair_dbs - Provision many VFs with fair GuC doorbell IDs. |
| * @gt: the &xe_gt |
| * @vfid: starting VF identifier (can't be 0) |
| * @num_vfs: number of VFs to provision (can't be 0) |
| * |
| * This function can only be called on PF. |
| * |
| * Return: 0 on success or a negative error code on failure. |
| */ |
| int xe_gt_sriov_pf_config_set_fair_dbs(struct xe_gt *gt, unsigned int vfid, |
| unsigned int num_vfs) |
| { |
| u32 fair; |
| |
| xe_gt_assert(gt, vfid); |
| xe_gt_assert(gt, num_vfs); |
| |
| mutex_lock(xe_gt_sriov_pf_master_mutex(gt)); |
| fair = pf_estimate_fair_dbs(gt, num_vfs); |
| mutex_unlock(xe_gt_sriov_pf_master_mutex(gt)); |
| |
| if (!fair) |
| return -ENOSPC; |
| |
| return xe_gt_sriov_pf_config_bulk_set_dbs(gt, vfid, num_vfs, fair); |
| } |
| |
| static u64 pf_get_lmem_alignment(struct xe_gt *gt) |
| { |
| /* this might be platform dependent */ |
| return SZ_2M; |
| } |
| |
| static u64 pf_get_min_spare_lmem(struct xe_gt *gt) |
| { |
| /* this might be platform dependent */ |
| return SZ_128M; /* XXX: preliminary */ |
| } |
| |
| static u64 pf_get_spare_lmem(struct xe_gt *gt) |
| { |
| u64 spare; |
| |
| xe_gt_assert(gt, IS_SRIOV_PF(gt_to_xe(gt))); |
| lockdep_assert_held(xe_gt_sriov_pf_master_mutex(gt)); |
| |
| spare = gt->sriov.pf.spare.lmem_size; |
| spare = max_t(u64, spare, pf_get_min_spare_lmem(gt)); |
| |
| return spare; |
| } |
| |
| static int pf_set_spare_lmem(struct xe_gt *gt, u64 size) |
| { |
| xe_gt_assert(gt, IS_SRIOV_PF(gt_to_xe(gt))); |
| lockdep_assert_held(xe_gt_sriov_pf_master_mutex(gt)); |
| |
| if (size && size < pf_get_min_spare_lmem(gt)) |
| return -EINVAL; |
| |
| gt->sriov.pf.spare.lmem_size = size; |
| return 0; |
| } |
| |
| static u64 pf_get_vf_config_lmem(struct xe_gt *gt, unsigned int vfid) |
| { |
| struct xe_gt_sriov_config *config = pf_pick_vf_config(gt, vfid); |
| struct xe_bo *bo; |
| |
| bo = config->lmem_obj; |
| return bo ? bo->size : 0; |
| } |
| |
| static int pf_distribute_config_lmem(struct xe_gt *gt, unsigned int vfid, u64 size) |
| { |
| struct xe_device *xe = gt_to_xe(gt); |
| struct xe_tile *tile; |
| unsigned int tid; |
| int err; |
| |
| for_each_tile(tile, xe, tid) { |
| if (tile->primary_gt == gt) { |
| err = pf_push_vf_cfg_lmem(gt, vfid, size); |
| } else { |
| u64 lmem = pf_get_vf_config_lmem(tile->primary_gt, vfid); |
| |
| if (!lmem) |
| continue; |
| err = pf_push_vf_cfg_lmem(gt, vfid, lmem); |
| } |
| if (unlikely(err)) |
| return err; |
| } |
| return 0; |
| } |
| |
| static void pf_force_lmtt_invalidate(struct xe_device *xe) |
| { |
| /* TODO */ |
| } |
| |
| static void pf_reset_vf_lmtt(struct xe_device *xe, unsigned int vfid) |
| { |
| struct xe_lmtt *lmtt; |
| struct xe_tile *tile; |
| unsigned int tid; |
| |
| xe_assert(xe, IS_DGFX(xe)); |
| xe_assert(xe, IS_SRIOV_PF(xe)); |
| |
| for_each_tile(tile, xe, tid) { |
| lmtt = &tile->sriov.pf.lmtt; |
| xe_lmtt_drop_pages(lmtt, vfid); |
| } |
| } |
| |
| static int pf_update_vf_lmtt(struct xe_device *xe, unsigned int vfid) |
| { |
| struct xe_gt_sriov_config *config; |
| struct xe_tile *tile; |
| struct xe_lmtt *lmtt; |
| struct xe_bo *bo; |
| struct xe_gt *gt; |
| u64 total, offset; |
| unsigned int gtid; |
| unsigned int tid; |
| int err; |
| |
| xe_assert(xe, IS_DGFX(xe)); |
| xe_assert(xe, IS_SRIOV_PF(xe)); |
| |
| total = 0; |
| for_each_tile(tile, xe, tid) |
| total += pf_get_vf_config_lmem(tile->primary_gt, vfid); |
| |
| for_each_tile(tile, xe, tid) { |
| lmtt = &tile->sriov.pf.lmtt; |
| |
| xe_lmtt_drop_pages(lmtt, vfid); |
| if (!total) |
| continue; |
| |
| err = xe_lmtt_prepare_pages(lmtt, vfid, total); |
| if (err) |
| goto fail; |
| |
| offset = 0; |
| for_each_gt(gt, xe, gtid) { |
| if (xe_gt_is_media_type(gt)) |
| continue; |
| |
| config = pf_pick_vf_config(gt, vfid); |
| bo = config->lmem_obj; |
| if (!bo) |
| continue; |
| |
| err = xe_lmtt_populate_pages(lmtt, vfid, bo, offset); |
| if (err) |
| goto fail; |
| offset += bo->size; |
| } |
| } |
| |
| pf_force_lmtt_invalidate(xe); |
| return 0; |
| |
| fail: |
| for_each_tile(tile, xe, tid) { |
| lmtt = &tile->sriov.pf.lmtt; |
| xe_lmtt_drop_pages(lmtt, vfid); |
| } |
| return err; |
| } |
| |
| static void pf_release_vf_config_lmem(struct xe_gt *gt, struct xe_gt_sriov_config *config) |
| { |
| xe_gt_assert(gt, IS_DGFX(gt_to_xe(gt))); |
| xe_gt_assert(gt, !xe_gt_is_media_type(gt)); |
| lockdep_assert_held(xe_gt_sriov_pf_master_mutex(gt)); |
| |
| if (config->lmem_obj) { |
| xe_bo_unpin_map_no_vm(config->lmem_obj); |
| config->lmem_obj = NULL; |
| } |
| } |
| |
| static int pf_provision_vf_lmem(struct xe_gt *gt, unsigned int vfid, u64 size) |
| { |
| struct xe_gt_sriov_config *config = pf_pick_vf_config(gt, vfid); |
| struct xe_device *xe = gt_to_xe(gt); |
| struct xe_tile *tile = gt_to_tile(gt); |
| struct xe_bo *bo; |
| int err; |
| |
| xe_gt_assert(gt, vfid); |
| xe_gt_assert(gt, IS_DGFX(xe)); |
| xe_gt_assert(gt, !xe_gt_is_media_type(gt)); |
| |
| size = round_up(size, pf_get_lmem_alignment(gt)); |
| |
| if (config->lmem_obj) { |
| err = pf_distribute_config_lmem(gt, vfid, 0); |
| if (unlikely(err)) |
| return err; |
| |
| pf_reset_vf_lmtt(xe, vfid); |
| pf_release_vf_config_lmem(gt, config); |
| } |
| xe_gt_assert(gt, !config->lmem_obj); |
| |
| if (!size) |
| return 0; |
| |
| xe_gt_assert(gt, pf_get_lmem_alignment(gt) == SZ_2M); |
| bo = xe_bo_create_pin_map(xe, tile, NULL, |
| ALIGN(size, PAGE_SIZE), |
| ttm_bo_type_kernel, |
| XE_BO_FLAG_VRAM_IF_DGFX(tile) | |
| XE_BO_FLAG_PINNED); |
| if (IS_ERR(bo)) |
| return PTR_ERR(bo); |
| |
| config->lmem_obj = bo; |
| |
| err = pf_update_vf_lmtt(xe, vfid); |
| if (unlikely(err)) |
| goto release; |
| |
| err = pf_push_vf_cfg_lmem(gt, vfid, bo->size); |
| if (unlikely(err)) |
| goto reset_lmtt; |
| |
| xe_gt_sriov_dbg_verbose(gt, "VF%u LMEM %zu (%zuM)\n", |
| vfid, bo->size, bo->size / SZ_1M); |
| return 0; |
| |
| reset_lmtt: |
| pf_reset_vf_lmtt(xe, vfid); |
| release: |
| pf_release_vf_config_lmem(gt, config); |
| return err; |
| } |
| |
| /** |
| * xe_gt_sriov_pf_config_get_lmem - Get VF's LMEM quota. |
| * @gt: the &xe_gt |
| * @vfid: the VF identifier |
| * |
| * This function can only be called on PF. |
| * |
| * Return: VF's (or PF's spare) LMEM quota. |
| */ |
| u64 xe_gt_sriov_pf_config_get_lmem(struct xe_gt *gt, unsigned int vfid) |
| { |
| u64 size; |
| |
| mutex_lock(xe_gt_sriov_pf_master_mutex(gt)); |
| if (vfid) |
| size = pf_get_vf_config_lmem(gt, vfid); |
| else |
| size = pf_get_spare_lmem(gt); |
| mutex_unlock(xe_gt_sriov_pf_master_mutex(gt)); |
| |
| return size; |
| } |
| |
| /** |
| * xe_gt_sriov_pf_config_set_lmem - Provision VF with LMEM. |
| * @gt: the &xe_gt (can't be media) |
| * @vfid: the VF identifier |
| * @size: requested LMEM size |
| * |
| * This function can only be called on PF. |
| */ |
| int xe_gt_sriov_pf_config_set_lmem(struct xe_gt *gt, unsigned int vfid, u64 size) |
| { |
| int err; |
| |
| mutex_lock(xe_gt_sriov_pf_master_mutex(gt)); |
| if (vfid) |
| err = pf_provision_vf_lmem(gt, vfid, size); |
| else |
| err = pf_set_spare_lmem(gt, size); |
| mutex_unlock(xe_gt_sriov_pf_master_mutex(gt)); |
| |
| return pf_config_set_u64_done(gt, vfid, size, |
| xe_gt_sriov_pf_config_get_lmem(gt, vfid), |
| vfid ? "LMEM" : "spare LMEM", err); |
| } |
| |
| /** |
| * xe_gt_sriov_pf_config_bulk_set_lmem - Provision many VFs with LMEM. |
| * @gt: the &xe_gt (can't be media) |
| * @vfid: starting VF identifier (can't be 0) |
| * @num_vfs: number of VFs to provision |
| * @size: requested LMEM size |
| * |
| * This function can only be called on PF. |
| * |
| * Return: 0 on success or a negative error code on failure. |
| */ |
| int xe_gt_sriov_pf_config_bulk_set_lmem(struct xe_gt *gt, unsigned int vfid, |
| unsigned int num_vfs, u64 size) |
| { |
| unsigned int n; |
| int err = 0; |
| |
| xe_gt_assert(gt, vfid); |
| xe_gt_assert(gt, !xe_gt_is_media_type(gt)); |
| |
| if (!num_vfs) |
| return 0; |
| |
| mutex_lock(xe_gt_sriov_pf_master_mutex(gt)); |
| for (n = vfid; n < vfid + num_vfs; n++) { |
| err = pf_provision_vf_lmem(gt, n, size); |
| if (err) |
| break; |
| } |
| mutex_unlock(xe_gt_sriov_pf_master_mutex(gt)); |
| |
| return pf_config_bulk_set_u64_done(gt, vfid, num_vfs, size, |
| xe_gt_sriov_pf_config_get_lmem, |
| "LMEM", n, err); |
| } |
| |
| static u64 pf_query_free_lmem(struct xe_gt *gt) |
| { |
| struct xe_tile *tile = gt->tile; |
| |
| return xe_ttm_vram_get_avail(&tile->mem.vram_mgr->manager); |
| } |
| |
| static u64 pf_query_max_lmem(struct xe_gt *gt) |
| { |
| u64 alignment = pf_get_lmem_alignment(gt); |
| u64 spare = pf_get_spare_lmem(gt); |
| u64 free = pf_query_free_lmem(gt); |
| u64 avail; |
| |
| /* XXX: need to account for 2MB blocks only */ |
| avail = free > spare ? free - spare : 0; |
| avail = round_down(avail, alignment); |
| |
| return avail; |
| } |
| |
| #ifdef CONFIG_DRM_XE_DEBUG_SRIOV |
| #define MAX_FAIR_LMEM SZ_128M /* XXX: make it small for the driver bringup */ |
| #else |
| #define MAX_FAIR_LMEM SZ_2G /* XXX: known issue with allocating BO over 2GiB */ |
| #endif |
| |
| static u64 pf_estimate_fair_lmem(struct xe_gt *gt, unsigned int num_vfs) |
| { |
| u64 available = pf_query_max_lmem(gt); |
| u64 alignment = pf_get_lmem_alignment(gt); |
| u64 fair; |
| |
| fair = div_u64(available, num_vfs); |
| fair = rounddown_pow_of_two(fair); /* XXX: ttm_vram_mgr & drm_buddy limitation */ |
| fair = ALIGN_DOWN(fair, alignment); |
| #ifdef MAX_FAIR_LMEM |
| fair = min_t(u64, MAX_FAIR_LMEM, fair); |
| #endif |
| xe_gt_sriov_dbg_verbose(gt, "LMEM available(%lluM) fair(%u x %lluM)\n", |
| available / SZ_1M, num_vfs, fair / SZ_1M); |
| return fair; |
| } |
| |
| /** |
| * xe_gt_sriov_pf_config_set_fair_lmem - Provision many VFs with fair LMEM. |
| * @gt: the &xe_gt (can't be media) |
| * @vfid: starting VF identifier (can't be 0) |
| * @num_vfs: number of VFs to provision (can't be 0) |
| * |
| * This function can only be called on PF. |
| * |
| * Return: 0 on success or a negative error code on failure. |
| */ |
| int xe_gt_sriov_pf_config_set_fair_lmem(struct xe_gt *gt, unsigned int vfid, |
| unsigned int num_vfs) |
| { |
| u64 fair; |
| |
| xe_gt_assert(gt, vfid); |
| xe_gt_assert(gt, num_vfs); |
| xe_gt_assert(gt, !xe_gt_is_media_type(gt)); |
| |
| if (!IS_DGFX(gt_to_xe(gt))) |
| return 0; |
| |
| mutex_lock(xe_gt_sriov_pf_master_mutex(gt)); |
| fair = pf_estimate_fair_lmem(gt, num_vfs); |
| mutex_unlock(xe_gt_sriov_pf_master_mutex(gt)); |
| |
| if (!fair) |
| return -ENOSPC; |
| |
| return xe_gt_sriov_pf_config_bulk_set_lmem(gt, vfid, num_vfs, fair); |
| } |
| |
| /** |
| * xe_gt_sriov_pf_config_set_fair - Provision many VFs with fair resources. |
| * @gt: the &xe_gt |
| * @vfid: starting VF identifier (can't be 0) |
| * @num_vfs: number of VFs to provision (can't be 0) |
| * |
| * This function can only be called on PF. |
| * |
| * Return: 0 on success or a negative error code on failure. |
| */ |
| int xe_gt_sriov_pf_config_set_fair(struct xe_gt *gt, unsigned int vfid, |
| unsigned int num_vfs) |
| { |
| int result = 0; |
| int err; |
| |
| xe_gt_assert(gt, vfid); |
| xe_gt_assert(gt, num_vfs); |
| |
| if (!xe_gt_is_media_type(gt)) { |
| err = xe_gt_sriov_pf_config_set_fair_ggtt(gt, vfid, num_vfs); |
| result = result ?: err; |
| err = xe_gt_sriov_pf_config_set_fair_lmem(gt, vfid, num_vfs); |
| result = result ?: err; |
| } |
| err = xe_gt_sriov_pf_config_set_fair_ctxs(gt, vfid, num_vfs); |
| result = result ?: err; |
| err = xe_gt_sriov_pf_config_set_fair_dbs(gt, vfid, num_vfs); |
| result = result ?: err; |
| |
| return result; |
| } |
| |
| static const char *exec_quantum_unit(u32 exec_quantum) |
| { |
| return exec_quantum ? "ms" : "(infinity)"; |
| } |
| |
| static int pf_provision_exec_quantum(struct xe_gt *gt, unsigned int vfid, |
| u32 exec_quantum) |
| { |
| struct xe_gt_sriov_config *config = pf_pick_vf_config(gt, vfid); |
| int err; |
| |
| err = pf_push_vf_cfg_exec_quantum(gt, vfid, &exec_quantum); |
| if (unlikely(err)) |
| return err; |
| |
| config->exec_quantum = exec_quantum; |
| return 0; |
| } |
| |
| static int pf_get_exec_quantum(struct xe_gt *gt, unsigned int vfid) |
| { |
| struct xe_gt_sriov_config *config = pf_pick_vf_config(gt, vfid); |
| |
| return config->exec_quantum; |
| } |
| |
| /** |
| * xe_gt_sriov_pf_config_set_exec_quantum - Configure execution quantum for the VF. |
| * @gt: the &xe_gt |
| * @vfid: the VF identifier |
| * @exec_quantum: requested execution quantum in milliseconds (0 is infinity) |
| * |
| * This function can only be called on PF. |
| * |
| * Return: 0 on success or a negative error code on failure. |
| */ |
| int xe_gt_sriov_pf_config_set_exec_quantum(struct xe_gt *gt, unsigned int vfid, |
| u32 exec_quantum) |
| { |
| int err; |
| |
| mutex_lock(xe_gt_sriov_pf_master_mutex(gt)); |
| err = pf_provision_exec_quantum(gt, vfid, exec_quantum); |
| mutex_unlock(xe_gt_sriov_pf_master_mutex(gt)); |
| |
| return pf_config_set_u32_done(gt, vfid, exec_quantum, |
| xe_gt_sriov_pf_config_get_exec_quantum(gt, vfid), |
| "execution quantum", exec_quantum_unit, err); |
| } |
| |
| /** |
| * xe_gt_sriov_pf_config_get_exec_quantum - Get VF's execution quantum. |
| * @gt: the &xe_gt |
| * @vfid: the VF identifier |
| * |
| * This function can only be called on PF. |
| * |
| * Return: VF's (or PF's) execution quantum in milliseconds. |
| */ |
| u32 xe_gt_sriov_pf_config_get_exec_quantum(struct xe_gt *gt, unsigned int vfid) |
| { |
| u32 exec_quantum; |
| |
| mutex_lock(xe_gt_sriov_pf_master_mutex(gt)); |
| exec_quantum = pf_get_exec_quantum(gt, vfid); |
| mutex_unlock(xe_gt_sriov_pf_master_mutex(gt)); |
| |
| return exec_quantum; |
| } |
| |
| static const char *preempt_timeout_unit(u32 preempt_timeout) |
| { |
| return preempt_timeout ? "us" : "(infinity)"; |
| } |
| |
| static int pf_provision_preempt_timeout(struct xe_gt *gt, unsigned int vfid, |
| u32 preempt_timeout) |
| { |
| struct xe_gt_sriov_config *config = pf_pick_vf_config(gt, vfid); |
| int err; |
| |
| err = pf_push_vf_cfg_preempt_timeout(gt, vfid, &preempt_timeout); |
| if (unlikely(err)) |
| return err; |
| |
| config->preempt_timeout = preempt_timeout; |
| |
| return 0; |
| } |
| |
| static int pf_get_preempt_timeout(struct xe_gt *gt, unsigned int vfid) |
| { |
| struct xe_gt_sriov_config *config = pf_pick_vf_config(gt, vfid); |
| |
| return config->preempt_timeout; |
| } |
| |
| /** |
| * xe_gt_sriov_pf_config_set_preempt_timeout - Configure preemption timeout for the VF. |
| * @gt: the &xe_gt |
| * @vfid: the VF identifier |
| * @preempt_timeout: requested preemption timeout in microseconds (0 is infinity) |
| * |
| * This function can only be called on PF. |
| * |
| * Return: 0 on success or a negative error code on failure. |
| */ |
| int xe_gt_sriov_pf_config_set_preempt_timeout(struct xe_gt *gt, unsigned int vfid, |
| u32 preempt_timeout) |
| { |
| int err; |
| |
| mutex_lock(xe_gt_sriov_pf_master_mutex(gt)); |
| err = pf_provision_preempt_timeout(gt, vfid, preempt_timeout); |
| mutex_unlock(xe_gt_sriov_pf_master_mutex(gt)); |
| |
| return pf_config_set_u32_done(gt, vfid, preempt_timeout, |
| xe_gt_sriov_pf_config_get_preempt_timeout(gt, vfid), |
| "preemption timeout", preempt_timeout_unit, err); |
| } |
| |
| /** |
| * xe_gt_sriov_pf_config_get_preempt_timeout - Get VF's preemption timeout. |
| * @gt: the &xe_gt |
| * @vfid: the VF identifier |
| * |
| * This function can only be called on PF. |
| * |
| * Return: VF's (or PF's) preemption timeout in microseconds. |
| */ |
| u32 xe_gt_sriov_pf_config_get_preempt_timeout(struct xe_gt *gt, unsigned int vfid) |
| { |
| u32 preempt_timeout; |
| |
| mutex_lock(xe_gt_sriov_pf_master_mutex(gt)); |
| preempt_timeout = pf_get_preempt_timeout(gt, vfid); |
| mutex_unlock(xe_gt_sriov_pf_master_mutex(gt)); |
| |
| return preempt_timeout; |
| } |
| |
| static void pf_reset_config_sched(struct xe_gt *gt, struct xe_gt_sriov_config *config) |
| { |
| lockdep_assert_held(xe_gt_sriov_pf_master_mutex(gt)); |
| |
| config->exec_quantum = 0; |
| config->preempt_timeout = 0; |
| } |
| |
| static int pf_provision_threshold(struct xe_gt *gt, unsigned int vfid, |
| enum xe_guc_klv_threshold_index index, u32 value) |
| { |
| struct xe_gt_sriov_config *config = pf_pick_vf_config(gt, vfid); |
| int err; |
| |
| err = pf_push_vf_cfg_threshold(gt, vfid, index, value); |
| if (unlikely(err)) |
| return err; |
| |
| config->thresholds[index] = value; |
| |
| return 0; |
| } |
| |
| static int pf_get_threshold(struct xe_gt *gt, unsigned int vfid, |
| enum xe_guc_klv_threshold_index index) |
| { |
| struct xe_gt_sriov_config *config = pf_pick_vf_config(gt, vfid); |
| |
| return config->thresholds[index]; |
| } |
| |
| static const char *threshold_unit(u32 threshold) |
| { |
| return threshold ? "" : "(disabled)"; |
| } |
| |
| /** |
| * xe_gt_sriov_pf_config_set_threshold - Configure threshold for the VF. |
| * @gt: the &xe_gt |
| * @vfid: the VF identifier |
| * @index: the threshold index |
| * @value: requested value (0 means disabled) |
| * |
| * This function can only be called on PF. |
| * |
| * Return: 0 on success or a negative error code on failure. |
| */ |
| int xe_gt_sriov_pf_config_set_threshold(struct xe_gt *gt, unsigned int vfid, |
| enum xe_guc_klv_threshold_index index, u32 value) |
| { |
| u32 key = xe_guc_klv_threshold_index_to_key(index); |
| const char *name = xe_guc_klv_key_to_string(key); |
| int err; |
| |
| mutex_lock(xe_gt_sriov_pf_master_mutex(gt)); |
| err = pf_provision_threshold(gt, vfid, index, value); |
| mutex_unlock(xe_gt_sriov_pf_master_mutex(gt)); |
| |
| return pf_config_set_u32_done(gt, vfid, value, |
| xe_gt_sriov_pf_config_get_threshold(gt, vfid, index), |
| name, threshold_unit, err); |
| } |
| |
| /** |
| * xe_gt_sriov_pf_config_get_threshold - Get VF's threshold. |
| * @gt: the &xe_gt |
| * @vfid: the VF identifier |
| * @index: the threshold index |
| * |
| * This function can only be called on PF. |
| * |
| * Return: value of VF's (or PF's) threshold. |
| */ |
| u32 xe_gt_sriov_pf_config_get_threshold(struct xe_gt *gt, unsigned int vfid, |
| enum xe_guc_klv_threshold_index index) |
| { |
| u32 value; |
| |
| mutex_lock(xe_gt_sriov_pf_master_mutex(gt)); |
| value = pf_get_threshold(gt, vfid, index); |
| mutex_unlock(xe_gt_sriov_pf_master_mutex(gt)); |
| |
| return value; |
| } |
| |
| static void pf_release_vf_config(struct xe_gt *gt, unsigned int vfid) |
| { |
| struct xe_gt_sriov_config *config = pf_pick_vf_config(gt, vfid); |
| struct xe_device *xe = gt_to_xe(gt); |
| |
| if (!xe_gt_is_media_type(gt)) { |
| pf_release_vf_config_ggtt(gt, config); |
| if (IS_DGFX(xe)) { |
| pf_release_vf_config_lmem(gt, config); |
| pf_update_vf_lmtt(xe, vfid); |
| } |
| } |
| pf_release_config_ctxs(gt, config); |
| pf_release_config_dbs(gt, config); |
| pf_reset_config_sched(gt, config); |
| } |
| |
| /** |
| * xe_gt_sriov_pf_config_release - Release and reset VF configuration. |
| * @gt: the &xe_gt |
| * @vfid: the VF identifier (can't be PF) |
| * @force: force configuration release |
| * |
| * This function can only be called on PF. |
| * |
| * Return: 0 on success or a negative error code on failure. |
| */ |
| int xe_gt_sriov_pf_config_release(struct xe_gt *gt, unsigned int vfid, bool force) |
| { |
| int err; |
| |
| xe_gt_assert(gt, vfid); |
| |
| mutex_lock(xe_gt_sriov_pf_master_mutex(gt)); |
| err = pf_send_vf_cfg_reset(gt, vfid); |
| if (!err || force) |
| pf_release_vf_config(gt, vfid); |
| mutex_unlock(xe_gt_sriov_pf_master_mutex(gt)); |
| |
| if (unlikely(err)) { |
| xe_gt_sriov_notice(gt, "VF%u unprovisioning failed with error (%pe)%s\n", |
| vfid, ERR_PTR(err), |
| force ? " but all resources were released anyway!" : ""); |
| } |
| |
| return force ? 0 : err; |
| } |
| |
| /** |
| * xe_gt_sriov_pf_config_push - Reprovision VF's configuration. |
| * @gt: the &xe_gt |
| * @vfid: the VF identifier (can't be PF) |
| * @refresh: explicit refresh |
| * |
| * This function can only be called on PF. |
| * |
| * Return: 0 on success or a negative error code on failure. |
| */ |
| int xe_gt_sriov_pf_config_push(struct xe_gt *gt, unsigned int vfid, bool refresh) |
| { |
| int err = 0; |
| |
| xe_gt_assert(gt, vfid); |
| |
| mutex_lock(xe_gt_sriov_pf_master_mutex(gt)); |
| if (refresh) |
| err = pf_send_vf_cfg_reset(gt, vfid); |
| if (!err) |
| err = pf_push_full_vf_config(gt, vfid); |
| mutex_unlock(xe_gt_sriov_pf_master_mutex(gt)); |
| |
| if (unlikely(err)) { |
| xe_gt_sriov_notice(gt, "Failed to %s VF%u configuration (%pe)\n", |
| refresh ? "refresh" : "push", vfid, ERR_PTR(err)); |
| } |
| |
| return err; |
| } |
| |
| static int pf_validate_vf_config(struct xe_gt *gt, unsigned int vfid) |
| { |
| struct xe_gt *primary_gt = gt_to_tile(gt)->primary_gt; |
| struct xe_device *xe = gt_to_xe(gt); |
| bool is_primary = !xe_gt_is_media_type(gt); |
| bool valid_ggtt, valid_ctxs, valid_dbs; |
| bool valid_any, valid_all; |
| |
| valid_ggtt = pf_get_vf_config_ggtt(primary_gt, vfid); |
| valid_ctxs = pf_get_vf_config_ctxs(gt, vfid); |
| valid_dbs = pf_get_vf_config_dbs(gt, vfid); |
| |
| /* note that GuC doorbells are optional */ |
| valid_any = valid_ctxs || valid_dbs; |
| valid_all = valid_ctxs; |
| |
| /* and GGTT/LMEM is configured on primary GT only */ |
| valid_all = valid_all && valid_ggtt; |
| valid_any = valid_any || (valid_ggtt && is_primary); |
| |
| if (IS_DGFX(xe)) { |
| bool valid_lmem = pf_get_vf_config_ggtt(primary_gt, vfid); |
| |
| valid_any = valid_any || (valid_lmem && is_primary); |
| valid_all = valid_all && valid_lmem; |
| } |
| |
| return valid_all ? 1 : valid_any ? -ENOKEY : -ENODATA; |
| } |
| |
| /** |
| * xe_gt_sriov_pf_config_is_empty - Check VF's configuration. |
| * @gt: the &xe_gt |
| * @vfid: the VF identifier (can't be PF) |
| * |
| * This function can only be called on PF. |
| * |
| * Return: true if VF mandatory configuration (GGTT, LMEM, ...) is empty. |
| */ |
| bool xe_gt_sriov_pf_config_is_empty(struct xe_gt *gt, unsigned int vfid) |
| { |
| bool empty; |
| |
| xe_gt_assert(gt, IS_SRIOV_PF(gt_to_xe(gt))); |
| xe_gt_assert(gt, vfid); |
| |
| mutex_lock(xe_gt_sriov_pf_master_mutex(gt)); |
| empty = pf_validate_vf_config(gt, vfid) == -ENODATA; |
| mutex_unlock(xe_gt_sriov_pf_master_mutex(gt)); |
| |
| return empty; |
| } |
| |
| /** |
| * xe_gt_sriov_pf_config_restart - Restart SR-IOV configurations after a GT reset. |
| * @gt: the &xe_gt |
| * |
| * Any prior configurations pushed to GuC are lost when the GT is reset. |
| * Push again all non-empty VF configurations to the GuC. |
| * |
| * This function can only be called on PF. |
| */ |
| void xe_gt_sriov_pf_config_restart(struct xe_gt *gt) |
| { |
| unsigned int n, total_vfs = xe_sriov_pf_get_totalvfs(gt_to_xe(gt)); |
| unsigned int fail = 0, skip = 0; |
| |
| for (n = 1; n <= total_vfs; n++) { |
| if (xe_gt_sriov_pf_config_is_empty(gt, n)) |
| skip++; |
| else if (xe_gt_sriov_pf_config_push(gt, n, false)) |
| fail++; |
| } |
| |
| if (fail) |
| xe_gt_sriov_notice(gt, "Failed to push %u of %u VF%s configurations\n", |
| fail, total_vfs - skip, str_plural(total_vfs)); |
| |
| if (fail != total_vfs) |
| xe_gt_sriov_dbg(gt, "pushed %u skip %u of %u VF%s configurations\n", |
| total_vfs - skip - fail, skip, total_vfs, str_plural(total_vfs)); |
| } |
| |
| /** |
| * xe_gt_sriov_pf_config_print_ggtt - Print GGTT configurations. |
| * @gt: the &xe_gt |
| * @p: the &drm_printer |
| * |
| * Print GGTT configuration data for all VFs. |
| * VFs without provisioned GGTT are ignored. |
| * |
| * This function can only be called on PF. |
| */ |
| int xe_gt_sriov_pf_config_print_ggtt(struct xe_gt *gt, struct drm_printer *p) |
| { |
| unsigned int n, total_vfs = xe_sriov_pf_get_totalvfs(gt_to_xe(gt)); |
| const struct xe_gt_sriov_config *config; |
| char buf[10]; |
| |
| for (n = 1; n <= total_vfs; n++) { |
| config = >->sriov.pf.vfs[n].config; |
| if (!drm_mm_node_allocated(&config->ggtt_region)) |
| continue; |
| |
| string_get_size(config->ggtt_region.size, 1, STRING_UNITS_2, buf, sizeof(buf)); |
| drm_printf(p, "VF%u:\t%#0llx-%#llx\t(%s)\n", |
| n, config->ggtt_region.start, |
| config->ggtt_region.start + config->ggtt_region.size - 1, buf); |
| } |
| |
| return 0; |
| } |
| |
| /** |
| * xe_gt_sriov_pf_config_print_ctxs - Print GuC context IDs configurations. |
| * @gt: the &xe_gt |
| * @p: the &drm_printer |
| * |
| * Print GuC context ID allocations across all VFs. |
| * VFs without GuC context IDs are skipped. |
| * |
| * This function can only be called on PF. |
| * Return: 0 on success or a negative error code on failure. |
| */ |
| int xe_gt_sriov_pf_config_print_ctxs(struct xe_gt *gt, struct drm_printer *p) |
| { |
| unsigned int n, total_vfs = xe_sriov_pf_get_totalvfs(gt_to_xe(gt)); |
| const struct xe_gt_sriov_config *config; |
| |
| xe_gt_assert(gt, IS_SRIOV_PF(gt_to_xe(gt))); |
| mutex_lock(xe_gt_sriov_pf_master_mutex(gt)); |
| |
| for (n = 1; n <= total_vfs; n++) { |
| config = >->sriov.pf.vfs[n].config; |
| if (!config->num_ctxs) |
| continue; |
| |
| drm_printf(p, "VF%u:\t%u-%u\t(%u)\n", |
| n, |
| config->begin_ctx, |
| config->begin_ctx + config->num_ctxs - 1, |
| config->num_ctxs); |
| } |
| |
| mutex_unlock(xe_gt_sriov_pf_master_mutex(gt)); |
| return 0; |
| } |
| |
| /** |
| * xe_gt_sriov_pf_config_print_dbs - Print GuC doorbell ID configurations. |
| * @gt: the &xe_gt |
| * @p: the &drm_printer |
| * |
| * Print GuC doorbell IDs allocations across all VFs. |
| * VFs without GuC doorbell IDs are skipped. |
| * |
| * This function can only be called on PF. |
| * Return: 0 on success or a negative error code on failure. |
| */ |
| int xe_gt_sriov_pf_config_print_dbs(struct xe_gt *gt, struct drm_printer *p) |
| { |
| unsigned int n, total_vfs = xe_sriov_pf_get_totalvfs(gt_to_xe(gt)); |
| const struct xe_gt_sriov_config *config; |
| |
| xe_gt_assert(gt, IS_SRIOV_PF(gt_to_xe(gt))); |
| mutex_lock(xe_gt_sriov_pf_master_mutex(gt)); |
| |
| for (n = 1; n <= total_vfs; n++) { |
| config = >->sriov.pf.vfs[n].config; |
| if (!config->num_dbs) |
| continue; |
| |
| drm_printf(p, "VF%u:\t%u-%u\t(%u)\n", |
| n, |
| config->begin_db, |
| config->begin_db + config->num_dbs - 1, |
| config->num_dbs); |
| } |
| |
| mutex_unlock(xe_gt_sriov_pf_master_mutex(gt)); |
| return 0; |
| } |
| |
| /** |
| * xe_gt_sriov_pf_config_print_available_ggtt - Print available GGTT ranges. |
| * @gt: the &xe_gt |
| * @p: the &drm_printer |
| * |
| * Print GGTT ranges that are available for the provisioning. |
| * |
| * This function can only be called on PF. |
| */ |
| int xe_gt_sriov_pf_config_print_available_ggtt(struct xe_gt *gt, struct drm_printer *p) |
| { |
| struct xe_ggtt *ggtt = gt_to_tile(gt)->mem.ggtt; |
| const struct drm_mm *mm = &ggtt->mm; |
| const struct drm_mm_node *entry; |
| u64 alignment = pf_get_ggtt_alignment(gt); |
| u64 hole_min_start = xe_wopcm_size(gt_to_xe(gt)); |
| u64 hole_start, hole_end, hole_size; |
| u64 spare, avail, total = 0; |
| char buf[10]; |
| |
| xe_gt_assert(gt, IS_SRIOV_PF(gt_to_xe(gt))); |
| |
| mutex_lock(xe_gt_sriov_pf_master_mutex(gt)); |
| |
| spare = pf_get_spare_ggtt(gt); |
| |
| mutex_lock(&ggtt->lock); |
| |
| drm_mm_for_each_hole(entry, mm, hole_start, hole_end) { |
| hole_start = max(hole_start, hole_min_start); |
| hole_start = ALIGN(hole_start, alignment); |
| hole_end = ALIGN_DOWN(hole_end, alignment); |
| if (hole_start >= hole_end) |
| continue; |
| hole_size = hole_end - hole_start; |
| total += hole_size; |
| |
| string_get_size(hole_size, 1, STRING_UNITS_2, buf, sizeof(buf)); |
| drm_printf(p, "range:\t%#llx-%#llx\t(%s)\n", |
| hole_start, hole_end - 1, buf); |
| } |
| |
| mutex_unlock(&ggtt->lock); |
| mutex_unlock(xe_gt_sriov_pf_master_mutex(gt)); |
| |
| string_get_size(total, 1, STRING_UNITS_2, buf, sizeof(buf)); |
| drm_printf(p, "total:\t%llu\t(%s)\n", total, buf); |
| |
| string_get_size(spare, 1, STRING_UNITS_2, buf, sizeof(buf)); |
| drm_printf(p, "spare:\t%llu\t(%s)\n", spare, buf); |
| |
| avail = total > spare ? total - spare : 0; |
| |
| string_get_size(avail, 1, STRING_UNITS_2, buf, sizeof(buf)); |
| drm_printf(p, "avail:\t%llu\t(%s)\n", avail, buf); |
| |
| return 0; |
| } |