The drm resource being limited is the TTM (Translation Table Manager) buffers. TTM manages different types of memory that a GPU might access. These memory types include dedicated Video RAM (VRAM) and host/system memory accessible through IOMMU (GART/GTT). TTM is currently used by multiple drm drivers (amd, ast, bochs, cirrus, hisilicon, maga200, nouveau, qxl, virtio, vmwgfx.) TTM buffers belonging to drm cgroups under memory pressure will be selected to be evicted first. drm.memory.high A read-write nested-keyed file which exists on all cgroups. Each entry is keyed by the drm device's major:minor. The following nested keys are defined. ==== ============================================= vram Video RAM soft limit for a drm device in byte ==== ============================================= Reading returns the following:: 226:0 vram=0 226:1 vram=17768448 226:2 vram=17768448 drm.memory.default A read-only nested-keyed file which exists on the root cgroup. Each entry is keyed by the drm device's major:minor. The following nested keys are defined. ==== =============================== vram Video RAM default limit in byte ==== =============================== Reading returns the following:: 226:0 vram=0 226:1 vram=17768448 226:2 vram=17768448 Change-Id: I7988e28a453b53140b40a28c176239acbc81d491 Signed-off-by: Kenny Ho <Kenny.Ho@xxxxxxx> --- drivers/gpu/drm/ttm/ttm_bo.c | 7 ++ include/drm/drm_cgroup.h | 17 +++++ include/linux/cgroup_drm.h | 2 + kernel/cgroup/drm.c | 135 +++++++++++++++++++++++++++++++++++ 4 files changed, 161 insertions(+) diff --git a/drivers/gpu/drm/ttm/ttm_bo.c b/drivers/gpu/drm/ttm/ttm_bo.c index 32eee85f3641..d7e3d3128ebb 100644 --- a/drivers/gpu/drm/ttm/ttm_bo.c +++ b/drivers/gpu/drm/ttm/ttm_bo.c @@ -853,14 +853,21 @@ static int ttm_mem_evict_first(struct ttm_bo_device *bdev, struct ttm_bo_global *glob = bdev->glob; struct ttm_mem_type_manager *man = &bdev->man[mem_type]; bool locked = false; + bool check_drmcg; unsigned i; int ret; + check_drmcg = drmcg_mem_pressure_scan(bdev, mem_type); + spin_lock(&glob->lru_lock); for (i = 0; i < TTM_MAX_BO_PRIORITY; ++i) { list_for_each_entry(bo, &man->lru[i], lru) { bool busy; + if (check_drmcg && + !drmcg_mem_should_evict(bo, mem_type)) + continue; + if (!ttm_bo_evict_swapout_allowable(bo, ctx, &locked, &busy)) { if (busy && !busy_bo && diff --git a/include/drm/drm_cgroup.h b/include/drm/drm_cgroup.h index 9ce0d54e6bd8..c11df388fdf2 100644 --- a/include/drm/drm_cgroup.h +++ b/include/drm/drm_cgroup.h @@ -6,6 +6,7 @@ #include <linux/cgroup_drm.h> #include <drm/ttm/ttm_bo_api.h> +#include <drm/ttm/ttm_bo_driver.h> /** * Per DRM device properties for DRM cgroup controller for the purpose @@ -22,6 +23,8 @@ struct drmcg_props { s64 mem_bw_bytes_in_period_default; s64 mem_bw_avg_bytes_per_us_default; + + s64 mem_highs_default[TTM_PL_PRIV+1]; }; #ifdef CONFIG_CGROUP_DRM @@ -38,6 +41,8 @@ void drmcg_mem_track_move(struct ttm_buffer_object *old_bo, bool evict, struct ttm_mem_reg *new_mem); unsigned int drmcg_get_mem_bw_period_in_us(struct ttm_buffer_object *tbo); bool drmcg_mem_can_move(struct ttm_buffer_object *tbo); +bool drmcg_mem_pressure_scan(struct ttm_bo_device *bdev, unsigned int type); +bool drmcg_mem_should_evict(struct ttm_buffer_object *tbo, unsigned int type); #else static inline void drmcg_device_update(struct drm_device *device) @@ -81,5 +86,17 @@ static inline bool drmcg_mem_can_move(struct ttm_buffer_object *tbo) { return true; } + +static inline bool drmcg_mem_pressure_scan(struct ttm_bo_device *bdev, + unsigned int type) +{ + return false; +} + +static inline bool drmcg_mem_should_evict(struct ttm_buffer_object *tbo, + unsigned int type) +{ + return true; +} #endif /* CONFIG_CGROUP_DRM */ #endif /* __DRM_CGROUP_H__ */ diff --git a/include/linux/cgroup_drm.h b/include/linux/cgroup_drm.h index 27809a583bf2..c56cfe74d1a6 100644 --- a/include/linux/cgroup_drm.h +++ b/include/linux/cgroup_drm.h @@ -50,6 +50,8 @@ struct drmcg_device_resource { s64 mem_stats[TTM_PL_PRIV+1]; s64 mem_peaks[TTM_PL_PRIV+1]; + s64 mem_highs[TTM_PL_PRIV+1]; + bool mem_pressure[TTM_PL_PRIV+1]; s64 mem_stats_evict; s64 mem_bw_stats_last_update_us; diff --git a/kernel/cgroup/drm.c b/kernel/cgroup/drm.c index ab962a277e58..04fb9a398740 100644 --- a/kernel/cgroup/drm.c +++ b/kernel/cgroup/drm.c @@ -80,6 +80,7 @@ static inline int init_drmcg_single(struct drmcg *drmcg, struct drm_device *dev) { int minor = dev->primary->index; struct drmcg_device_resource *ddr = drmcg->dev_resources[minor]; + int i; if (ddr == NULL) { ddr = kzalloc(sizeof(struct drmcg_device_resource), @@ -108,6 +109,12 @@ static inline int init_drmcg_single(struct drmcg *drmcg, struct drm_device *dev) ddr->mem_bw_limits_avg_bytes_per_us = dev->drmcg_props.mem_bw_avg_bytes_per_us_default; + ddr->mem_bw_limits_avg_bytes_per_us = + dev->drmcg_props.mem_bw_avg_bytes_per_us_default; + + for (i = 0; i <= TTM_PL_PRIV; i++) + ddr->mem_highs[i] = dev->drmcg_props.mem_highs_default[i]; + mutex_unlock(&dev->drmcg_mutex); return 0; } @@ -257,6 +264,11 @@ static void drmcg_print_limits(struct drmcg_device_resource *ddr, case DRMCG_TYPE_BO_PEAK: seq_printf(sf, "%lld\n", ddr->bo_limits_peak_allocated); break; + case DRMCG_TYPE_MEM: + seq_printf(sf, "%s=%lld\n", + ttm_placement_names[TTM_PL_VRAM], + ddr->mem_highs[TTM_PL_VRAM]); + break; case DRMCG_TYPE_BANDWIDTH_PERIOD_BURST: seq_printf(sf, "%lld\n", dev->drmcg_props.mem_bw_limits_period_in_us); @@ -286,6 +298,11 @@ static void drmcg_print_default(struct drmcg_props *props, seq_printf(sf, "%lld\n", props->bo_limits_peak_allocated_default); break; + case DRMCG_TYPE_MEM: + seq_printf(sf, "%s=%lld\n", + ttm_placement_names[TTM_PL_VRAM], + props->mem_highs_default[TTM_PL_VRAM]); + break; case DRMCG_TYPE_BANDWIDTH_PERIOD_BURST: seq_printf(sf, "%lld\n", props->mem_bw_limits_period_in_us_default); @@ -461,6 +478,29 @@ static void drmcg_nested_limit_parse(struct kernfs_open_file *of, continue; } break; /* DRMCG_TYPE_BANDWIDTH */ + case DRMCG_TYPE_MEM: + if (strncmp(sname, ttm_placement_names[TTM_PL_VRAM], + 256) == 0) { + p_max = parent == NULL ? S64_MAX : + parent->dev_resources[minor]-> + mem_highs[TTM_PL_VRAM]; + + rc = drmcg_process_limit_s64_val(sval, true, + props->mem_highs_default[TTM_PL_VRAM], + p_max, &val); + + if (rc || val < 0) { + drmcg_pr_cft_err(drmcg, rc, cft_name, + minor); + continue; + } + + drmcg_value_apply(dev, + &ddr->mem_highs[TTM_PL_VRAM], + val); + continue; + } + break; /* DRMCG_TYPE_MEM */ default: break; } /* switch (type) */ @@ -565,6 +605,7 @@ static ssize_t drmcg_limit_write(struct kernfs_open_file *of, char *buf, drmcg_mem_burst_bw_stats_reset(dm->dev); break; case DRMCG_TYPE_BANDWIDTH: + case DRMCG_TYPE_MEM: drmcg_nested_limit_parse(of, dm->dev, sattr); break; default: @@ -641,6 +682,20 @@ struct cftype files[] = { .private = DRMCG_CTF_PRIV(DRMCG_TYPE_MEM_PEAK, DRMCG_FTYPE_STATS), }, + { + .name = "memory.default", + .seq_show = drmcg_seq_show, + .flags = CFTYPE_ONLY_ON_ROOT, + .private = DRMCG_CTF_PRIV(DRMCG_TYPE_MEM, + DRMCG_FTYPE_DEFAULT), + }, + { + .name = "memory.high", + .write = drmcg_limit_write, + .seq_show = drmcg_seq_show, + .private = DRMCG_CTF_PRIV(DRMCG_TYPE_MEM, + DRMCG_FTYPE_LIMIT), + }, { .name = "burst_bw_period_in_us", .write = drmcg_limit_write, @@ -731,6 +786,8 @@ EXPORT_SYMBOL(drmcg_device_update); */ void drmcg_device_early_init(struct drm_device *dev) { + int i; + dev->drmcg_props.limit_enforced = false; dev->drmcg_props.bo_limits_total_allocated_default = S64_MAX; @@ -740,6 +797,9 @@ void drmcg_device_early_init(struct drm_device *dev) dev->drmcg_props.mem_bw_bytes_in_period_default = S64_MAX; dev->drmcg_props.mem_bw_avg_bytes_per_us_default = 65536; + for (i = 0; i <= TTM_PL_PRIV; i++) + dev->drmcg_props.mem_highs_default[i] = S64_MAX; + drmcg_update_cg_tree(dev); } EXPORT_SYMBOL(drmcg_device_early_init); @@ -1008,3 +1068,78 @@ bool drmcg_mem_can_move(struct ttm_buffer_object *tbo) return result; } EXPORT_SYMBOL(drmcg_mem_can_move); + +static inline void drmcg_mem_set_pressure(struct drmcg *drmcg, + int devIdx, unsigned int mem_type, bool pressure_val) +{ + struct drmcg_device_resource *ddr; + struct cgroup_subsys_state *pos; + struct drmcg *node; + + css_for_each_descendant_pre(pos, &drmcg->css) { + node = css_to_drmcg(pos); + ddr = node->dev_resources[devIdx]; + ddr->mem_pressure[mem_type] = pressure_val; + } +} + +static inline bool drmcg_mem_check(struct drmcg *drmcg, int devIdx, + unsigned int mem_type) +{ + struct drmcg_device_resource *ddr = drmcg->dev_resources[devIdx]; + + /* already under pressure, no need to check and set */ + if (ddr->mem_pressure[mem_type]) + return true; + + if (ddr->mem_stats[mem_type] >= ddr->mem_highs[mem_type]) { + drmcg_mem_set_pressure(drmcg, devIdx, mem_type, true); + return true; + } + + return false; +} + +bool drmcg_mem_pressure_scan(struct ttm_bo_device *bdev, unsigned int type) +{ + struct drm_device *dev = bdev->ddev; + struct cgroup_subsys_state *pos; + struct drmcg *node; + int devIdx; + bool result = false; + + //TODO replace with BUG_ON + if (dev == NULL || type != TTM_PL_VRAM) /* only vram limit for now */ + return false; + + devIdx = dev->primary->index; + + type = type > TTM_PL_PRIV ? TTM_PL_PRIV : type; + + rcu_read_lock(); + drmcg_mem_set_pressure(root_drmcg, devIdx, type, false); + + css_for_each_descendant_pre(pos, &root_drmcg->css) { + node = css_to_drmcg(pos); + result |= drmcg_mem_check(node, devIdx, type); + } + rcu_read_unlock(); + + return result; +} +EXPORT_SYMBOL(drmcg_mem_pressure_scan); + +bool drmcg_mem_should_evict(struct ttm_buffer_object *tbo, unsigned int type) +{ + struct drm_device *dev = tbo->bdev->ddev; + int devIdx; + + //TODO replace with BUG_ON + if (dev == NULL) + return true; + + devIdx = dev->primary->index; + + return tbo->drmcg->dev_resources[devIdx]->mem_pressure[type]; +} +EXPORT_SYMBOL(drmcg_mem_should_evict); -- 2.22.0