linux/drivers/gpu/drm/drm_client_modeset.c
Ma Ke 113fd6372a drm/client: fix null pointer dereference in drm_client_modeset_probe
In drm_client_modeset_probe(), the return value of drm_mode_duplicate() is
assigned to modeset->mode, which will lead to a possible NULL pointer
dereference on failure of drm_mode_duplicate(). Add a check to avoid npd.

Cc: stable@vger.kernel.org
Fixes: cf13909aee ("drm/fb-helper: Move out modeset config code")
Signed-off-by: Ma Ke <make24@iscas.ac.cn>
Reviewed-by: Thomas Zimmermann <tzimmermann@suse.de>
Signed-off-by: Thomas Zimmermann <tzimmermann@suse.de>
Link: https://patchwork.freedesktop.org/patch/msgid/20240802044736.1570345-1-make24@iscas.ac.cn
2024-08-02 09:23:54 +02:00

1263 lines
33 KiB
C

// SPDX-License-Identifier: MIT
/*
* Copyright 2018 Noralf Trønnes
* Copyright (c) 2006-2009 Red Hat Inc.
* Copyright (c) 2006-2008 Intel Corporation
* Jesse Barnes <jesse.barnes@intel.com>
* Copyright (c) 2007 Dave Airlie <airlied@linux.ie>
*/
#include "drm/drm_modeset_lock.h"
#include <linux/module.h>
#include <linux/mutex.h>
#include <linux/slab.h>
#include <linux/string_helpers.h>
#include <drm/drm_atomic.h>
#include <drm/drm_client.h>
#include <drm/drm_connector.h>
#include <drm/drm_crtc.h>
#include <drm/drm_device.h>
#include <drm/drm_drv.h>
#include <drm/drm_edid.h>
#include <drm/drm_encoder.h>
#include <drm/drm_print.h>
#include "drm_crtc_internal.h"
#include "drm_internal.h"
#define DRM_CLIENT_MAX_CLONED_CONNECTORS 8
struct drm_client_offset {
int x, y;
};
int drm_client_modeset_create(struct drm_client_dev *client)
{
struct drm_device *dev = client->dev;
unsigned int num_crtc = dev->mode_config.num_crtc;
unsigned int max_connector_count = 1;
struct drm_mode_set *modeset;
struct drm_crtc *crtc;
unsigned int i = 0;
/* Add terminating zero entry to enable index less iteration */
client->modesets = kcalloc(num_crtc + 1, sizeof(*client->modesets), GFP_KERNEL);
if (!client->modesets)
return -ENOMEM;
mutex_init(&client->modeset_mutex);
drm_for_each_crtc(crtc, dev)
client->modesets[i++].crtc = crtc;
/* Cloning is only supported in the single crtc case. */
if (num_crtc == 1)
max_connector_count = DRM_CLIENT_MAX_CLONED_CONNECTORS;
for (modeset = client->modesets; modeset->crtc; modeset++) {
modeset->connectors = kcalloc(max_connector_count,
sizeof(*modeset->connectors), GFP_KERNEL);
if (!modeset->connectors)
goto err_free;
}
return 0;
err_free:
drm_client_modeset_free(client);
return -ENOMEM;
}
static void drm_client_modeset_release(struct drm_client_dev *client)
{
struct drm_mode_set *modeset;
unsigned int i;
drm_client_for_each_modeset(modeset, client) {
drm_mode_destroy(client->dev, modeset->mode);
modeset->mode = NULL;
modeset->fb = NULL;
for (i = 0; i < modeset->num_connectors; i++) {
drm_connector_put(modeset->connectors[i]);
modeset->connectors[i] = NULL;
}
modeset->num_connectors = 0;
}
}
void drm_client_modeset_free(struct drm_client_dev *client)
{
struct drm_mode_set *modeset;
mutex_lock(&client->modeset_mutex);
drm_client_modeset_release(client);
drm_client_for_each_modeset(modeset, client)
kfree(modeset->connectors);
mutex_unlock(&client->modeset_mutex);
mutex_destroy(&client->modeset_mutex);
kfree(client->modesets);
}
static struct drm_mode_set *
drm_client_find_modeset(struct drm_client_dev *client, struct drm_crtc *crtc)
{
struct drm_mode_set *modeset;
drm_client_for_each_modeset(modeset, client)
if (modeset->crtc == crtc)
return modeset;
return NULL;
}
static struct drm_display_mode *
drm_connector_get_tiled_mode(struct drm_connector *connector)
{
struct drm_display_mode *mode;
list_for_each_entry(mode, &connector->modes, head) {
if (mode->hdisplay == connector->tile_h_size &&
mode->vdisplay == connector->tile_v_size)
return mode;
}
return NULL;
}
static struct drm_display_mode *
drm_connector_fallback_non_tiled_mode(struct drm_connector *connector)
{
struct drm_display_mode *mode;
list_for_each_entry(mode, &connector->modes, head) {
if (mode->hdisplay == connector->tile_h_size &&
mode->vdisplay == connector->tile_v_size)
continue;
return mode;
}
return NULL;
}
static struct drm_display_mode *
drm_connector_has_preferred_mode(struct drm_connector *connector, int width, int height)
{
struct drm_display_mode *mode;
list_for_each_entry(mode, &connector->modes, head) {
if (mode->hdisplay > width ||
mode->vdisplay > height)
continue;
if (mode->type & DRM_MODE_TYPE_PREFERRED)
return mode;
}
return NULL;
}
static struct drm_display_mode *drm_connector_pick_cmdline_mode(struct drm_connector *connector)
{
struct drm_cmdline_mode *cmdline_mode;
struct drm_display_mode *mode;
bool prefer_non_interlace;
/*
* Find a user-defined mode. If the user gave us a valid
* mode on the kernel command line, it will show up in this
* list.
*/
list_for_each_entry(mode, &connector->modes, head) {
if (mode->type & DRM_MODE_TYPE_USERDEF)
return mode;
}
cmdline_mode = &connector->cmdline_mode;
if (cmdline_mode->specified == false)
return NULL;
/*
* Attempt to find a matching mode in the list of modes we
* have gotten so far.
*/
prefer_non_interlace = !cmdline_mode->interlace;
again:
list_for_each_entry(mode, &connector->modes, head) {
/* check width/height */
if (mode->hdisplay != cmdline_mode->xres ||
mode->vdisplay != cmdline_mode->yres)
continue;
if (cmdline_mode->refresh_specified) {
if (drm_mode_vrefresh(mode) != cmdline_mode->refresh)
continue;
}
if (cmdline_mode->interlace) {
if (!(mode->flags & DRM_MODE_FLAG_INTERLACE))
continue;
} else if (prefer_non_interlace) {
if (mode->flags & DRM_MODE_FLAG_INTERLACE)
continue;
}
return mode;
}
if (prefer_non_interlace) {
prefer_non_interlace = false;
goto again;
}
return NULL;
}
static bool drm_connector_enabled(struct drm_connector *connector, bool strict)
{
bool enable;
if (connector->display_info.non_desktop)
return false;
if (strict)
enable = connector->status == connector_status_connected;
else
enable = connector->status != connector_status_disconnected;
return enable;
}
static void drm_client_connectors_enabled(struct drm_connector **connectors,
unsigned int connector_count,
bool *enabled)
{
bool any_enabled = false;
struct drm_connector *connector;
int i = 0;
for (i = 0; i < connector_count; i++) {
connector = connectors[i];
enabled[i] = drm_connector_enabled(connector, true);
drm_dbg_kms(connector->dev, "[CONNECTOR:%d:%s] enabled? %s\n",
connector->base.id, connector->name,
connector->display_info.non_desktop ?
"non desktop" : str_yes_no(enabled[i]));
any_enabled |= enabled[i];
}
if (any_enabled)
return;
for (i = 0; i < connector_count; i++)
enabled[i] = drm_connector_enabled(connectors[i], false);
}
static bool drm_client_target_cloned(struct drm_device *dev,
struct drm_connector **connectors,
unsigned int connector_count,
struct drm_display_mode **modes,
struct drm_client_offset *offsets,
bool *enabled, int width, int height)
{
int count, i, j;
bool can_clone = false;
struct drm_display_mode *dmt_mode, *mode;
/* only contemplate cloning in the single crtc case */
if (dev->mode_config.num_crtc > 1)
return false;
count = 0;
for (i = 0; i < connector_count; i++) {
if (enabled[i])
count++;
}
/* only contemplate cloning if more than one connector is enabled */
if (count <= 1)
return false;
/* check the command line or if nothing common pick 1024x768 */
can_clone = true;
for (i = 0; i < connector_count; i++) {
if (!enabled[i])
continue;
modes[i] = drm_connector_pick_cmdline_mode(connectors[i]);
if (!modes[i]) {
can_clone = false;
break;
}
for (j = 0; j < i; j++) {
if (!enabled[j])
continue;
if (!drm_mode_match(modes[j], modes[i],
DRM_MODE_MATCH_TIMINGS |
DRM_MODE_MATCH_CLOCK |
DRM_MODE_MATCH_FLAGS |
DRM_MODE_MATCH_3D_FLAGS))
can_clone = false;
}
}
if (can_clone) {
drm_dbg_kms(dev, "can clone using command line\n");
return true;
}
/* try and find a 1024x768 mode on each connector */
can_clone = true;
dmt_mode = drm_mode_find_dmt(dev, 1024, 768, 60, false);
if (!dmt_mode)
goto fail;
for (i = 0; i < connector_count; i++) {
if (!enabled[i])
continue;
list_for_each_entry(mode, &connectors[i]->modes, head) {
if (drm_mode_match(mode, dmt_mode,
DRM_MODE_MATCH_TIMINGS |
DRM_MODE_MATCH_CLOCK |
DRM_MODE_MATCH_FLAGS |
DRM_MODE_MATCH_3D_FLAGS))
modes[i] = mode;
}
if (!modes[i])
can_clone = false;
}
kfree(dmt_mode);
if (can_clone) {
drm_dbg_kms(dev, "can clone using 1024x768\n");
return true;
}
fail:
drm_info(dev, "kms: can't enable cloning when we probably wanted to.\n");
return false;
}
static int drm_client_get_tile_offsets(struct drm_device *dev,
struct drm_connector **connectors,
unsigned int connector_count,
struct drm_display_mode **modes,
struct drm_client_offset *offsets,
int idx,
int h_idx, int v_idx)
{
struct drm_connector *connector;
int i;
int hoffset = 0, voffset = 0;
for (i = 0; i < connector_count; i++) {
connector = connectors[i];
if (!connector->has_tile)
continue;
if (!modes[i] && (h_idx || v_idx)) {
drm_dbg_kms(dev,
"[CONNECTOR:%d:%s] no modes for connector tiled %d\n",
connector->base.id, connector->name, i);
continue;
}
if (connector->tile_h_loc < h_idx)
hoffset += modes[i]->hdisplay;
if (connector->tile_v_loc < v_idx)
voffset += modes[i]->vdisplay;
}
offsets[idx].x = hoffset;
offsets[idx].y = voffset;
drm_dbg_kms(dev, "returned %d %d for %d %d\n", hoffset, voffset, h_idx, v_idx);
return 0;
}
static bool drm_client_target_preferred(struct drm_device *dev,
struct drm_connector **connectors,
unsigned int connector_count,
struct drm_display_mode **modes,
struct drm_client_offset *offsets,
bool *enabled, int width, int height)
{
const u64 mask = BIT_ULL(connector_count) - 1;
struct drm_connector *connector;
u64 conn_configured = 0;
int tile_pass = 0;
int num_tiled_conns = 0;
int i;
for (i = 0; i < connector_count; i++) {
if (connectors[i]->has_tile &&
connectors[i]->status == connector_status_connected)
num_tiled_conns++;
}
retry:
for (i = 0; i < connector_count; i++) {
connector = connectors[i];
if (conn_configured & BIT_ULL(i))
continue;
if (enabled[i] == false) {
conn_configured |= BIT_ULL(i);
continue;
}
/* first pass over all the untiled connectors */
if (tile_pass == 0 && connector->has_tile)
continue;
if (tile_pass == 1) {
if (connector->tile_h_loc != 0 ||
connector->tile_v_loc != 0)
continue;
} else {
if (connector->tile_h_loc != tile_pass - 1 &&
connector->tile_v_loc != tile_pass - 1)
/* if this tile_pass doesn't cover any of the tiles - keep going */
continue;
/*
* find the tile offsets for this pass - need to find
* all tiles left and above
*/
drm_client_get_tile_offsets(dev, connectors, connector_count,
modes, offsets, i,
connector->tile_h_loc, connector->tile_v_loc);
}
drm_dbg_kms(dev, "[CONNECTOR:%d:%s] looking for cmdline mode\n",
connector->base.id, connector->name);
/* got for command line mode first */
modes[i] = drm_connector_pick_cmdline_mode(connector);
if (!modes[i]) {
drm_dbg_kms(dev, "[CONNECTOR:%d:%s] looking for preferred mode, tile %d\n",
connector->base.id, connector->name,
connector->tile_group ? connector->tile_group->id : 0);
modes[i] = drm_connector_has_preferred_mode(connector, width, height);
}
/* No preferred modes, pick one off the list */
if (!modes[i] && !list_empty(&connector->modes)) {
list_for_each_entry(modes[i], &connector->modes, head)
break;
}
/*
* In case of tiled mode if all tiles not present fallback to
* first available non tiled mode.
* After all tiles are present, try to find the tiled mode
* for all and if tiled mode not present due to fbcon size
* limitations, use first non tiled mode only for
* tile 0,0 and set to no mode for all other tiles.
*/
if (connector->has_tile) {
if (num_tiled_conns <
connector->num_h_tile * connector->num_v_tile ||
(connector->tile_h_loc == 0 &&
connector->tile_v_loc == 0 &&
!drm_connector_get_tiled_mode(connector))) {
drm_dbg_kms(dev,
"[CONNECTOR:%d:%s] Falling back to non-tiled mode\n",
connector->base.id, connector->name);
modes[i] = drm_connector_fallback_non_tiled_mode(connector);
} else {
modes[i] = drm_connector_get_tiled_mode(connector);
}
}
drm_dbg_kms(dev, "[CONNECTOR:%d:%s] Found mode %s\n",
connector->base.id, connector->name,
modes[i] ? modes[i]->name : "none");
conn_configured |= BIT_ULL(i);
}
if ((conn_configured & mask) != mask) {
tile_pass++;
goto retry;
}
return true;
}
static bool connector_has_possible_crtc(struct drm_connector *connector,
struct drm_crtc *crtc)
{
struct drm_encoder *encoder;
drm_connector_for_each_possible_encoder(connector, encoder) {
if (encoder->possible_crtcs & drm_crtc_mask(crtc))
return true;
}
return false;
}
static int drm_client_pick_crtcs(struct drm_client_dev *client,
struct drm_connector **connectors,
unsigned int connector_count,
struct drm_crtc **best_crtcs,
struct drm_display_mode **modes,
int n, int width, int height)
{
struct drm_device *dev = client->dev;
struct drm_connector *connector;
int my_score, best_score, score;
struct drm_crtc **crtcs, *crtc;
struct drm_mode_set *modeset;
int o;
if (n == connector_count)
return 0;
connector = connectors[n];
best_crtcs[n] = NULL;
best_score = drm_client_pick_crtcs(client, connectors, connector_count,
best_crtcs, modes, n + 1, width, height);
if (modes[n] == NULL)
return best_score;
crtcs = kcalloc(connector_count, sizeof(*crtcs), GFP_KERNEL);
if (!crtcs)
return best_score;
my_score = 1;
if (connector->status == connector_status_connected)
my_score++;
if (connector->cmdline_mode.specified)
my_score++;
if (drm_connector_has_preferred_mode(connector, width, height))
my_score++;
/*
* select a crtc for this connector and then attempt to configure
* remaining connectors
*/
drm_client_for_each_modeset(modeset, client) {
crtc = modeset->crtc;
if (!connector_has_possible_crtc(connector, crtc))
continue;
for (o = 0; o < n; o++)
if (best_crtcs[o] == crtc)
break;
if (o < n) {
/* ignore cloning unless only a single crtc */
if (dev->mode_config.num_crtc > 1)
continue;
if (!drm_mode_equal(modes[o], modes[n]))
continue;
}
crtcs[n] = crtc;
memcpy(crtcs, best_crtcs, n * sizeof(*crtcs));
score = my_score + drm_client_pick_crtcs(client, connectors, connector_count,
crtcs, modes, n + 1, width, height);
if (score > best_score) {
best_score = score;
memcpy(best_crtcs, crtcs, connector_count * sizeof(*crtcs));
}
}
kfree(crtcs);
return best_score;
}
/* Try to read the BIOS display configuration and use it for the initial config */
static bool drm_client_firmware_config(struct drm_client_dev *client,
struct drm_connector **connectors,
unsigned int connector_count,
struct drm_crtc **crtcs,
struct drm_display_mode **modes,
struct drm_client_offset *offsets,
bool *enabled, int width, int height)
{
const int count = min_t(unsigned int, connector_count, BITS_PER_LONG);
unsigned long conn_configured, conn_seq, mask;
struct drm_device *dev = client->dev;
int i, j;
bool *save_enabled;
bool fallback = true, ret = true;
int num_connectors_enabled = 0;
int num_connectors_detected = 0;
int num_tiled_conns = 0;
struct drm_modeset_acquire_ctx ctx;
if (!drm_drv_uses_atomic_modeset(dev))
return false;
if (drm_WARN_ON(dev, count <= 0))
return false;
save_enabled = kcalloc(count, sizeof(bool), GFP_KERNEL);
if (!save_enabled)
return false;
drm_modeset_acquire_init(&ctx, 0);
while (drm_modeset_lock_all_ctx(dev, &ctx) != 0)
drm_modeset_backoff(&ctx);
memcpy(save_enabled, enabled, count);
mask = GENMASK(count - 1, 0);
conn_configured = 0;
for (i = 0; i < count; i++) {
if (connectors[i]->has_tile &&
connectors[i]->status == connector_status_connected)
num_tiled_conns++;
}
retry:
conn_seq = conn_configured;
for (i = 0; i < count; i++) {
struct drm_connector *connector;
struct drm_encoder *encoder;
struct drm_crtc *new_crtc;
connector = connectors[i];
if (conn_configured & BIT(i))
continue;
if (conn_seq == 0 && !connector->has_tile)
continue;
if (connector->status == connector_status_connected)
num_connectors_detected++;
if (!enabled[i]) {
drm_dbg_kms(dev, "[CONNECTOR:%d:%s] not enabled, skipping\n",
connector->base.id, connector->name);
conn_configured |= BIT(i);
continue;
}
if (connector->force == DRM_FORCE_OFF) {
drm_dbg_kms(dev, "[CONNECTOR:%d:%s] disabled by user, skipping\n",
connector->base.id, connector->name);
enabled[i] = false;
continue;
}
encoder = connector->state->best_encoder;
if (!encoder || drm_WARN_ON(dev, !connector->state->crtc)) {
if (connector->force > DRM_FORCE_OFF)
goto bail;
drm_dbg_kms(dev, "[CONNECTOR:%d:%s] has no encoder or crtc, skipping\n",
connector->base.id, connector->name);
enabled[i] = false;
conn_configured |= BIT(i);
continue;
}
num_connectors_enabled++;
new_crtc = connector->state->crtc;
/*
* Make sure we're not trying to drive multiple connectors
* with a single CRTC, since our cloning support may not
* match the BIOS.
*/
for (j = 0; j < count; j++) {
if (crtcs[j] == new_crtc) {
drm_dbg_kms(dev, "fallback: cloned configuration\n");
goto bail;
}
}
drm_dbg_kms(dev, "[CONNECTOR:%d:%s] looking for cmdline mode\n",
connector->base.id, connector->name);
/* go for command line mode first */
modes[i] = drm_connector_pick_cmdline_mode(connector);
/* try for preferred next */
if (!modes[i]) {
drm_dbg_kms(dev,
"[CONNECTOR:%d:%s] looking for preferred mode, has tile: %s\n",
connector->base.id, connector->name,
str_yes_no(connector->has_tile));
modes[i] = drm_connector_has_preferred_mode(connector, width, height);
}
/* No preferred mode marked by the EDID? Are there any modes? */
if (!modes[i] && !list_empty(&connector->modes)) {
drm_dbg_kms(dev, "[CONNECTOR:%d:%s] using first listed mode\n",
connector->base.id, connector->name);
modes[i] = list_first_entry(&connector->modes,
struct drm_display_mode,
head);
}
/* last resort: use current mode */
if (!modes[i]) {
/*
* IMPORTANT: We want to use the adjusted mode (i.e.
* after the panel fitter upscaling) as the initial
* config, not the input mode, which is what crtc->mode
* usually contains. But since our current
* code puts a mode derived from the post-pfit timings
* into crtc->mode this works out correctly.
*
* This is crtc->mode and not crtc->state->mode for the
* fastboot check to work correctly.
*/
drm_dbg_kms(dev, "[CONNECTOR:%d:%s] looking for current mode\n",
connector->base.id, connector->name);
modes[i] = &connector->state->crtc->mode;
}
/*
* In case of tiled modes, if all tiles are not present
* then fallback to a non tiled mode.
*/
if (connector->has_tile &&
num_tiled_conns < connector->num_h_tile * connector->num_v_tile) {
drm_dbg_kms(dev, "[CONNECTOR:%d:%s] Falling back to non-tiled mode\n",
connector->base.id, connector->name);
modes[i] = drm_connector_fallback_non_tiled_mode(connector);
}
crtcs[i] = new_crtc;
drm_dbg_kms(dev, "[CONNECTOR:%d:%s] on [CRTC:%d:%s]: %dx%d%s\n",
connector->base.id, connector->name,
connector->state->crtc->base.id,
connector->state->crtc->name,
modes[i]->hdisplay, modes[i]->vdisplay,
modes[i]->flags & DRM_MODE_FLAG_INTERLACE ? "i" : "");
fallback = false;
conn_configured |= BIT(i);
}
if ((conn_configured & mask) != mask && conn_configured != conn_seq)
goto retry;
/*
* If the BIOS didn't enable everything it could, fall back to have the
* same user experiencing of lighting up as much as possible like the
* fbdev helper library.
*/
if (num_connectors_enabled != num_connectors_detected &&
num_connectors_enabled < dev->mode_config.num_crtc) {
drm_dbg_kms(dev, "fallback: Not all outputs enabled\n");
drm_dbg_kms(dev, "Enabled: %i, detected: %i\n",
num_connectors_enabled, num_connectors_detected);
fallback = true;
}
if (fallback) {
bail:
drm_dbg_kms(dev, "Not using firmware configuration\n");
memcpy(enabled, save_enabled, count);
ret = false;
}
drm_modeset_drop_locks(&ctx);
drm_modeset_acquire_fini(&ctx);
kfree(save_enabled);
return ret;
}
/**
* drm_client_modeset_probe() - Probe for displays
* @client: DRM client
* @width: Maximum display mode width (optional)
* @height: Maximum display mode height (optional)
*
* This function sets up display pipelines for enabled connectors and stores the
* config in the client's modeset array.
*
* Returns:
* Zero on success or negative error code on failure.
*/
int drm_client_modeset_probe(struct drm_client_dev *client, unsigned int width, unsigned int height)
{
struct drm_connector *connector, **connectors = NULL;
struct drm_connector_list_iter conn_iter;
struct drm_device *dev = client->dev;
unsigned int total_modes_count = 0;
struct drm_client_offset *offsets;
unsigned int connector_count = 0;
/* points to modes protected by mode_config.mutex */
struct drm_display_mode **modes;
struct drm_crtc **crtcs;
int i, ret = 0;
bool *enabled;
drm_dbg_kms(dev, "\n");
if (!width)
width = dev->mode_config.max_width;
if (!height)
height = dev->mode_config.max_height;
drm_connector_list_iter_begin(dev, &conn_iter);
drm_client_for_each_connector_iter(connector, &conn_iter) {
struct drm_connector **tmp;
tmp = krealloc(connectors, (connector_count + 1) * sizeof(*connectors), GFP_KERNEL);
if (!tmp) {
ret = -ENOMEM;
goto free_connectors;
}
connectors = tmp;
drm_connector_get(connector);
connectors[connector_count++] = connector;
}
drm_connector_list_iter_end(&conn_iter);
if (!connector_count)
return 0;
crtcs = kcalloc(connector_count, sizeof(*crtcs), GFP_KERNEL);
modes = kcalloc(connector_count, sizeof(*modes), GFP_KERNEL);
offsets = kcalloc(connector_count, sizeof(*offsets), GFP_KERNEL);
enabled = kcalloc(connector_count, sizeof(bool), GFP_KERNEL);
if (!crtcs || !modes || !enabled || !offsets) {
ret = -ENOMEM;
goto out;
}
mutex_lock(&client->modeset_mutex);
mutex_lock(&dev->mode_config.mutex);
for (i = 0; i < connector_count; i++)
total_modes_count += connectors[i]->funcs->fill_modes(connectors[i], width, height);
if (!total_modes_count)
drm_dbg_kms(dev, "No connectors reported connected with modes\n");
drm_client_connectors_enabled(connectors, connector_count, enabled);
if (!drm_client_firmware_config(client, connectors, connector_count, crtcs,
modes, offsets, enabled, width, height)) {
memset(modes, 0, connector_count * sizeof(*modes));
memset(crtcs, 0, connector_count * sizeof(*crtcs));
memset(offsets, 0, connector_count * sizeof(*offsets));
if (!drm_client_target_cloned(dev, connectors, connector_count, modes,
offsets, enabled, width, height) &&
!drm_client_target_preferred(dev, connectors, connector_count, modes,
offsets, enabled, width, height))
drm_err(dev, "Unable to find initial modes\n");
drm_dbg_kms(dev, "picking CRTCs for %dx%d config\n",
width, height);
drm_client_pick_crtcs(client, connectors, connector_count,
crtcs, modes, 0, width, height);
}
drm_client_modeset_release(client);
for (i = 0; i < connector_count; i++) {
struct drm_display_mode *mode = modes[i];
struct drm_crtc *crtc = crtcs[i];
struct drm_client_offset *offset = &offsets[i];
if (mode && crtc) {
struct drm_mode_set *modeset = drm_client_find_modeset(client, crtc);
struct drm_connector *connector = connectors[i];
drm_dbg_kms(dev, "[CRTC:%d:%s] desired mode %s set (%d,%d)\n",
crtc->base.id, crtc->name,
mode->name, offset->x, offset->y);
if (drm_WARN_ON_ONCE(dev, modeset->num_connectors == DRM_CLIENT_MAX_CLONED_CONNECTORS ||
(dev->mode_config.num_crtc > 1 && modeset->num_connectors == 1))) {
ret = -EINVAL;
break;
}
kfree(modeset->mode);
modeset->mode = drm_mode_duplicate(dev, mode);
if (!modeset->mode) {
ret = -ENOMEM;
break;
}
drm_connector_get(connector);
modeset->connectors[modeset->num_connectors++] = connector;
modeset->x = offset->x;
modeset->y = offset->y;
}
}
mutex_unlock(&dev->mode_config.mutex);
mutex_unlock(&client->modeset_mutex);
out:
kfree(crtcs);
kfree(modes);
kfree(offsets);
kfree(enabled);
free_connectors:
for (i = 0; i < connector_count; i++)
drm_connector_put(connectors[i]);
kfree(connectors);
return ret;
}
EXPORT_SYMBOL(drm_client_modeset_probe);
/**
* drm_client_rotation() - Check the initial rotation value
* @modeset: DRM modeset
* @rotation: Returned rotation value
*
* This function checks if the primary plane in @modeset can hw rotate
* to match the rotation needed on its connector.
*
* Note: Currently only 0 and 180 degrees are supported.
*
* Return:
* True if the plane can do the rotation, false otherwise.
*/
bool drm_client_rotation(struct drm_mode_set *modeset, unsigned int *rotation)
{
struct drm_connector *connector = modeset->connectors[0];
struct drm_plane *plane = modeset->crtc->primary;
struct drm_cmdline_mode *cmdline;
u64 valid_mask = 0;
unsigned int i;
if (!modeset->num_connectors)
return false;
switch (connector->display_info.panel_orientation) {
case DRM_MODE_PANEL_ORIENTATION_BOTTOM_UP:
*rotation = DRM_MODE_ROTATE_180;
break;
case DRM_MODE_PANEL_ORIENTATION_LEFT_UP:
*rotation = DRM_MODE_ROTATE_90;
break;
case DRM_MODE_PANEL_ORIENTATION_RIGHT_UP:
*rotation = DRM_MODE_ROTATE_270;
break;
default:
*rotation = DRM_MODE_ROTATE_0;
}
/**
* The panel already defined the default rotation
* through its orientation. Whatever has been provided
* on the command line needs to be added to that.
*
* Unfortunately, the rotations are at different bit
* indices, so the math to add them up are not as
* trivial as they could.
*
* Reflections on the other hand are pretty trivial to deal with, a
* simple XOR between the two handle the addition nicely.
*/
cmdline = &connector->cmdline_mode;
if (cmdline->specified && cmdline->rotation_reflection) {
unsigned int cmdline_rest, panel_rest;
unsigned int cmdline_rot, panel_rot;
unsigned int sum_rot, sum_rest;
panel_rot = ilog2(*rotation & DRM_MODE_ROTATE_MASK);
cmdline_rot = ilog2(cmdline->rotation_reflection & DRM_MODE_ROTATE_MASK);
sum_rot = (panel_rot + cmdline_rot) % 4;
panel_rest = *rotation & ~DRM_MODE_ROTATE_MASK;
cmdline_rest = cmdline->rotation_reflection & ~DRM_MODE_ROTATE_MASK;
sum_rest = panel_rest ^ cmdline_rest;
*rotation = (1 << sum_rot) | sum_rest;
}
/*
* TODO: support 90 / 270 degree hardware rotation,
* depending on the hardware this may require the framebuffer
* to be in a specific tiling format.
*/
if (((*rotation & DRM_MODE_ROTATE_MASK) != DRM_MODE_ROTATE_0 &&
(*rotation & DRM_MODE_ROTATE_MASK) != DRM_MODE_ROTATE_180) ||
!plane->rotation_property)
return false;
for (i = 0; i < plane->rotation_property->num_values; i++)
valid_mask |= (1ULL << plane->rotation_property->values[i]);
if (!(*rotation & valid_mask))
return false;
return true;
}
EXPORT_SYMBOL(drm_client_rotation);
static int drm_client_modeset_commit_atomic(struct drm_client_dev *client, bool active, bool check)
{
struct drm_device *dev = client->dev;
struct drm_plane *plane;
struct drm_atomic_state *state;
struct drm_modeset_acquire_ctx ctx;
struct drm_mode_set *mode_set;
int ret;
drm_modeset_acquire_init(&ctx, 0);
state = drm_atomic_state_alloc(dev);
if (!state) {
ret = -ENOMEM;
goto out_ctx;
}
state->acquire_ctx = &ctx;
retry:
drm_for_each_plane(plane, dev) {
struct drm_plane_state *plane_state;
plane_state = drm_atomic_get_plane_state(state, plane);
if (IS_ERR(plane_state)) {
ret = PTR_ERR(plane_state);
goto out_state;
}
plane_state->rotation = DRM_MODE_ROTATE_0;
/* disable non-primary: */
if (plane->type == DRM_PLANE_TYPE_PRIMARY)
continue;
ret = __drm_atomic_helper_disable_plane(plane, plane_state);
if (ret != 0)
goto out_state;
}
drm_client_for_each_modeset(mode_set, client) {
struct drm_plane *primary = mode_set->crtc->primary;
unsigned int rotation;
if (drm_client_rotation(mode_set, &rotation)) {
struct drm_plane_state *plane_state;
/* Cannot fail as we've already gotten the plane state above */
plane_state = drm_atomic_get_new_plane_state(state, primary);
plane_state->rotation = rotation;
}
ret = __drm_atomic_helper_set_config(mode_set, state);
if (ret != 0)
goto out_state;
/*
* __drm_atomic_helper_set_config() sets active when a
* mode is set, unconditionally clear it if we force DPMS off
*/
if (!active) {
struct drm_crtc *crtc = mode_set->crtc;
struct drm_crtc_state *crtc_state = drm_atomic_get_new_crtc_state(state, crtc);
crtc_state->active = false;
}
}
if (check)
ret = drm_atomic_check_only(state);
else
ret = drm_atomic_commit(state);
out_state:
if (ret == -EDEADLK)
goto backoff;
drm_atomic_state_put(state);
out_ctx:
drm_modeset_drop_locks(&ctx);
drm_modeset_acquire_fini(&ctx);
return ret;
backoff:
drm_atomic_state_clear(state);
drm_modeset_backoff(&ctx);
goto retry;
}
static int drm_client_modeset_commit_legacy(struct drm_client_dev *client)
{
struct drm_device *dev = client->dev;
struct drm_mode_set *mode_set;
struct drm_plane *plane;
int ret = 0;
drm_modeset_lock_all(dev);
drm_for_each_plane(plane, dev) {
if (plane->type != DRM_PLANE_TYPE_PRIMARY)
drm_plane_force_disable(plane);
if (plane->rotation_property)
drm_mode_plane_set_obj_prop(plane,
plane->rotation_property,
DRM_MODE_ROTATE_0);
}
drm_client_for_each_modeset(mode_set, client) {
struct drm_crtc *crtc = mode_set->crtc;
if (crtc->funcs->cursor_set2) {
ret = crtc->funcs->cursor_set2(crtc, NULL, 0, 0, 0, 0, 0);
if (ret)
goto out;
} else if (crtc->funcs->cursor_set) {
ret = crtc->funcs->cursor_set(crtc, NULL, 0, 0, 0);
if (ret)
goto out;
}
ret = drm_mode_set_config_internal(mode_set);
if (ret)
goto out;
}
out:
drm_modeset_unlock_all(dev);
return ret;
}
/**
* drm_client_modeset_check() - Check modeset configuration
* @client: DRM client
*
* Check modeset configuration.
*
* Returns:
* Zero on success or negative error code on failure.
*/
int drm_client_modeset_check(struct drm_client_dev *client)
{
int ret;
if (!drm_drv_uses_atomic_modeset(client->dev))
return 0;
mutex_lock(&client->modeset_mutex);
ret = drm_client_modeset_commit_atomic(client, true, true);
mutex_unlock(&client->modeset_mutex);
return ret;
}
EXPORT_SYMBOL(drm_client_modeset_check);
/**
* drm_client_modeset_commit_locked() - Force commit CRTC configuration
* @client: DRM client
*
* Commit modeset configuration to crtcs without checking if there is a DRM
* master. The assumption is that the caller already holds an internal DRM
* master reference acquired with drm_master_internal_acquire().
*
* Returns:
* Zero on success or negative error code on failure.
*/
int drm_client_modeset_commit_locked(struct drm_client_dev *client)
{
struct drm_device *dev = client->dev;
int ret;
mutex_lock(&client->modeset_mutex);
if (drm_drv_uses_atomic_modeset(dev))
ret = drm_client_modeset_commit_atomic(client, true, false);
else
ret = drm_client_modeset_commit_legacy(client);
mutex_unlock(&client->modeset_mutex);
return ret;
}
EXPORT_SYMBOL(drm_client_modeset_commit_locked);
/**
* drm_client_modeset_commit() - Commit CRTC configuration
* @client: DRM client
*
* Commit modeset configuration to crtcs.
*
* Returns:
* Zero on success or negative error code on failure.
*/
int drm_client_modeset_commit(struct drm_client_dev *client)
{
struct drm_device *dev = client->dev;
int ret;
if (!drm_master_internal_acquire(dev))
return -EBUSY;
ret = drm_client_modeset_commit_locked(client);
drm_master_internal_release(dev);
return ret;
}
EXPORT_SYMBOL(drm_client_modeset_commit);
static void drm_client_modeset_dpms_legacy(struct drm_client_dev *client, int dpms_mode)
{
struct drm_device *dev = client->dev;
struct drm_connector *connector;
struct drm_mode_set *modeset;
struct drm_modeset_acquire_ctx ctx;
int j;
int ret;
DRM_MODESET_LOCK_ALL_BEGIN(dev, ctx, 0, ret);
drm_client_for_each_modeset(modeset, client) {
if (!modeset->crtc->enabled)
continue;
for (j = 0; j < modeset->num_connectors; j++) {
connector = modeset->connectors[j];
connector->funcs->dpms(connector, dpms_mode);
drm_object_property_set_value(&connector->base,
dev->mode_config.dpms_property, dpms_mode);
}
}
DRM_MODESET_LOCK_ALL_END(dev, ctx, ret);
}
/**
* drm_client_modeset_dpms() - Set DPMS mode
* @client: DRM client
* @mode: DPMS mode
*
* Note: For atomic drivers @mode is reduced to on/off.
*
* Returns:
* Zero on success or negative error code on failure.
*/
int drm_client_modeset_dpms(struct drm_client_dev *client, int mode)
{
struct drm_device *dev = client->dev;
int ret = 0;
if (!drm_master_internal_acquire(dev))
return -EBUSY;
mutex_lock(&client->modeset_mutex);
if (drm_drv_uses_atomic_modeset(dev))
ret = drm_client_modeset_commit_atomic(client, mode == DRM_MODE_DPMS_ON, false);
else
drm_client_modeset_dpms_legacy(client, mode);
mutex_unlock(&client->modeset_mutex);
drm_master_internal_release(dev);
return ret;
}
EXPORT_SYMBOL(drm_client_modeset_dpms);
#ifdef CONFIG_DRM_KUNIT_TEST
#include "tests/drm_client_modeset_test.c"
#endif