// SPDX-License-Identifier: MIT /* * Copyright © 2023 Intel Corporation */ #include #include #include #include #include #include #include "intel_atomic.h" #include "intel_crtc.h" #include "intel_display_core.h" #include "intel_display_types.h" #include "intel_dp.h" #include "intel_dp_mst.h" #include "intel_dp_tunnel.h" #include "intel_fdi.h" #include "intel_link_bw.h" static int get_forced_link_bpp_x16(struct intel_atomic_state *state, const struct intel_crtc *crtc) { struct intel_digital_connector_state *conn_state; struct intel_connector *connector; int force_bpp_x16 = INT_MAX; int i; for_each_new_intel_connector_in_state(state, connector, conn_state, i) { if (conn_state->base.crtc != &crtc->base) continue; if (!connector->link.force_bpp_x16) continue; force_bpp_x16 = min(force_bpp_x16, connector->link.force_bpp_x16); } return force_bpp_x16 < INT_MAX ? force_bpp_x16 : 0; } /** * intel_link_bw_init_limits - initialize BW limits * @state: Atomic state * @limits: link BW limits * * Initialize @limits. */ void intel_link_bw_init_limits(struct intel_atomic_state *state, struct intel_link_bw_limits *limits) { struct intel_display *display = to_intel_display(state); enum pipe pipe; limits->force_fec_pipes = 0; limits->bpp_limit_reached_pipes = 0; for_each_pipe(display, pipe) { struct intel_crtc *crtc = intel_crtc_for_pipe(display, pipe); const struct intel_crtc_state *crtc_state = intel_atomic_get_new_crtc_state(state, crtc); int forced_bpp_x16 = get_forced_link_bpp_x16(state, crtc); if (state->base.duplicated && crtc_state) { limits->max_bpp_x16[pipe] = crtc_state->max_link_bpp_x16; if (crtc_state->fec_enable) limits->force_fec_pipes |= BIT(pipe); } else { limits->max_bpp_x16[pipe] = INT_MAX; } if (forced_bpp_x16) limits->max_bpp_x16[pipe] = min(limits->max_bpp_x16[pipe], forced_bpp_x16); } } /** * __intel_link_bw_reduce_bpp - reduce maximum link bpp for a selected pipe * @state: atomic state * @limits: link BW limits * @pipe_mask: mask of pipes to select from * @reason: explanation of why bpp reduction is needed * @reduce_forced_bpp: allow reducing bpps below their forced link bpp * * Select the pipe from @pipe_mask with the biggest link bpp value and set the * maximum of link bpp in @limits below this value. Modeset the selected pipe, * so that its state will get recomputed. * * This function can be called to resolve a link's BW overallocation by reducing * the link bpp of one pipe on the link and hence reducing the total link BW. * * Returns * - 0 in case of success * - %-ENOSPC if no pipe can further reduce its link bpp * - Other negative error, if modesetting the selected pipe failed */ static int __intel_link_bw_reduce_bpp(struct intel_atomic_state *state, struct intel_link_bw_limits *limits, u8 pipe_mask, const char *reason, bool reduce_forced_bpp) { struct intel_display *display = to_intel_display(state); enum pipe max_bpp_pipe = INVALID_PIPE; struct intel_crtc *crtc; int max_bpp_x16 = 0; for_each_intel_crtc_in_pipe_mask(display->drm, crtc, pipe_mask) { struct intel_crtc_state *crtc_state; int link_bpp_x16; if (limits->bpp_limit_reached_pipes & BIT(crtc->pipe)) continue; crtc_state = intel_atomic_get_crtc_state(&state->base, crtc); if (IS_ERR(crtc_state)) return PTR_ERR(crtc_state); if (crtc_state->dsc.compression_enable) link_bpp_x16 = crtc_state->dsc.compressed_bpp_x16; else /* * TODO: for YUV420 the actual link bpp is only half * of the pipe bpp value. The MST encoder's BW allocation * is based on the pipe bpp value, set the actual link bpp * limit here once the MST BW allocation is fixed. */ link_bpp_x16 = fxp_q4_from_int(crtc_state->pipe_bpp); if (!reduce_forced_bpp && link_bpp_x16 <= get_forced_link_bpp_x16(state, crtc)) continue; if (link_bpp_x16 > max_bpp_x16) { max_bpp_x16 = link_bpp_x16; max_bpp_pipe = crtc->pipe; } } if (max_bpp_pipe == INVALID_PIPE) return -ENOSPC; limits->max_bpp_x16[max_bpp_pipe] = max_bpp_x16 - 1; return intel_modeset_pipes_in_mask_early(state, reason, BIT(max_bpp_pipe)); } int intel_link_bw_reduce_bpp(struct intel_atomic_state *state, struct intel_link_bw_limits *limits, u8 pipe_mask, const char *reason) { int ret; /* Try to keep any forced link BPP. */ ret = __intel_link_bw_reduce_bpp(state, limits, pipe_mask, reason, false); if (ret == -ENOSPC) ret = __intel_link_bw_reduce_bpp(state, limits, pipe_mask, reason, true); return ret; } /** * intel_link_bw_set_bpp_limit_for_pipe - set link bpp limit for a pipe to its minimum * @state: atomic state * @old_limits: link BW limits * @new_limits: link BW limits * @pipe: pipe * * Set the link bpp limit for @pipe in @new_limits to its value in * @old_limits and mark this limit as the minimum. This function must be * called after a pipe's compute config function failed, @old_limits * containing the bpp limit with which compute config previously passed. * * The function will fail if setting a minimum is not possible, either * because the old and new limits match (and so would lead to a pipe compute * config failure) or the limit is already at the minimum. * * Returns %true in case of success. */ bool intel_link_bw_set_bpp_limit_for_pipe(struct intel_atomic_state *state, const struct intel_link_bw_limits *old_limits, struct intel_link_bw_limits *new_limits, enum pipe pipe) { struct intel_display *display = to_intel_display(state); if (pipe == INVALID_PIPE) return false; if (new_limits->max_bpp_x16[pipe] == old_limits->max_bpp_x16[pipe]) return false; if (drm_WARN_ON(display->drm, new_limits->bpp_limit_reached_pipes & BIT(pipe))) return false; new_limits->max_bpp_x16[pipe] = old_limits->max_bpp_x16[pipe]; new_limits->bpp_limit_reached_pipes |= BIT(pipe); return true; } static int check_all_link_config(struct intel_atomic_state *state, struct intel_link_bw_limits *limits) { /* TODO: Check additional shared display link configurations like MST */ int ret; ret = intel_dp_mst_atomic_check_link(state, limits); if (ret) return ret; ret = intel_dp_tunnel_atomic_check_link(state, limits); if (ret) return ret; ret = intel_fdi_atomic_check_link(state, limits); if (ret) return ret; return 0; } static bool assert_link_limit_change_valid(struct intel_display *display, const struct intel_link_bw_limits *old_limits, const struct intel_link_bw_limits *new_limits) { bool bpps_changed = false; enum pipe pipe; /* FEC can't be forced off after it was forced on. */ if (drm_WARN_ON(display->drm, (old_limits->force_fec_pipes & new_limits->force_fec_pipes) != old_limits->force_fec_pipes)) return false; for_each_pipe(display, pipe) { /* The bpp limit can only decrease. */ if (drm_WARN_ON(display->drm, new_limits->max_bpp_x16[pipe] > old_limits->max_bpp_x16[pipe])) return false; if (new_limits->max_bpp_x16[pipe] < old_limits->max_bpp_x16[pipe]) bpps_changed = true; } /* At least one limit must change. */ if (drm_WARN_ON(display->drm, !bpps_changed && new_limits->force_fec_pipes == old_limits->force_fec_pipes)) return false; return true; } /** * intel_link_bw_atomic_check - check display link states and set a fallback config if needed * @state: atomic state * @new_limits: link BW limits * * Check the configuration of all shared display links in @state and set new BW * limits in @new_limits if there is a BW limitation. * * Returns: * - 0 if the configuration is valid * - %-EAGAIN, if the configuration is invalid and @new_limits got updated * with fallback values with which the configuration of all CRTCs * in @state must be recomputed * - Other negative error, if the configuration is invalid without a * fallback possibility, or the check failed for another reason */ int intel_link_bw_atomic_check(struct intel_atomic_state *state, struct intel_link_bw_limits *new_limits) { struct intel_display *display = to_intel_display(state); struct intel_link_bw_limits old_limits = *new_limits; int ret; ret = check_all_link_config(state, new_limits); if (ret != -EAGAIN) return ret; if (!assert_link_limit_change_valid(display, &old_limits, new_limits)) return -EINVAL; return -EAGAIN; } static int force_link_bpp_show(struct seq_file *m, void *data) { struct intel_connector *connector = m->private; seq_printf(m, FXP_Q4_FMT "\n", FXP_Q4_ARGS(connector->link.force_bpp_x16)); return 0; } static int str_to_fxp_q4_nonneg_int(const char *str, int *val_x16) { unsigned int val; int err; err = kstrtouint(str, 10, &val); if (err) return err; if (val > INT_MAX >> 4) return -ERANGE; *val_x16 = fxp_q4_from_int(val); return 0; } /* modifies str */ static int str_to_fxp_q4_nonneg(char *str, int *val_x16) { const char *int_str; char *frac_str; int frac_digits; int frac_val; int err; int_str = strim(str); frac_str = strchr(int_str, '.'); if (frac_str) *frac_str++ = '\0'; err = str_to_fxp_q4_nonneg_int(int_str, val_x16); if (err) return err; if (!frac_str) return 0; /* prevent negative number/leading +- sign mark */ if (!isdigit(*frac_str)) return -EINVAL; err = str_to_fxp_q4_nonneg_int(frac_str, &frac_val); if (err) return err; frac_digits = strlen(frac_str); if (frac_digits > intlog10(INT_MAX) >> 24 || frac_val > INT_MAX - int_pow(10, frac_digits) / 2) return -ERANGE; frac_val = DIV_ROUND_CLOSEST(frac_val, (int)int_pow(10, frac_digits)); if (*val_x16 > INT_MAX - frac_val) return -ERANGE; *val_x16 += frac_val; return 0; } static int user_str_to_fxp_q4_nonneg(const char __user *ubuf, size_t len, int *val_x16) { char *kbuf; int err; kbuf = memdup_user_nul(ubuf, len); if (IS_ERR(kbuf)) return PTR_ERR(kbuf); err = str_to_fxp_q4_nonneg(kbuf, val_x16); kfree(kbuf); return err; } static bool connector_supports_dsc(struct intel_connector *connector) { struct intel_display *display = to_intel_display(connector); switch (connector->base.connector_type) { case DRM_MODE_CONNECTOR_eDP: return intel_dp_has_dsc(connector); case DRM_MODE_CONNECTOR_DisplayPort: if (connector->mst.dp) return HAS_DSC_MST(display); return HAS_DSC(display); default: return false; } } static ssize_t force_link_bpp_write(struct file *file, const char __user *ubuf, size_t len, loff_t *offp) { struct seq_file *m = file->private_data; struct intel_connector *connector = m->private; struct intel_display *display = to_intel_display(connector); int min_bpp; int bpp_x16; int err; err = user_str_to_fxp_q4_nonneg(ubuf, len, &bpp_x16); if (err) return err; /* TODO: Make the non-DSC min_bpp value connector specific. */ if (connector_supports_dsc(connector)) min_bpp = intel_dp_dsc_min_src_compressed_bpp(); else min_bpp = intel_display_min_pipe_bpp(); if (bpp_x16 && (bpp_x16 < fxp_q4_from_int(min_bpp) || bpp_x16 > fxp_q4_from_int(intel_display_max_pipe_bpp(display)))) return -EINVAL; err = drm_modeset_lock_single_interruptible(&display->drm->mode_config.connection_mutex); if (err) return err; connector->link.force_bpp_x16 = bpp_x16; drm_modeset_unlock(&display->drm->mode_config.connection_mutex); *offp += len; return len; } DEFINE_SHOW_STORE_ATTRIBUTE(force_link_bpp); void intel_link_bw_connector_debugfs_add(struct intel_connector *connector) { struct intel_display *display = to_intel_display(connector); struct dentry *root = connector->base.debugfs_entry; switch (connector->base.connector_type) { case DRM_MODE_CONNECTOR_DisplayPort: case DRM_MODE_CONNECTOR_eDP: break; case DRM_MODE_CONNECTOR_VGA: case DRM_MODE_CONNECTOR_SVIDEO: case DRM_MODE_CONNECTOR_LVDS: case DRM_MODE_CONNECTOR_DVID: if (HAS_FDI(display)) break; return; case DRM_MODE_CONNECTOR_HDMIA: if (HAS_FDI(display) && !HAS_DDI(display)) break; return; default: return; } debugfs_create_file("intel_force_link_bpp", 0644, root, connector, &force_link_bpp_fops); }