Skip to content
This repository was archived by the owner on Nov 1, 2021. It is now read-only.

backend/drm: add support for hotplug_mode_update #2712

Open
wants to merge 7 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
188 changes: 151 additions & 37 deletions backend/drm/drm.c
Original file line number Diff line number Diff line change
Expand Up @@ -468,6 +468,9 @@ static struct wlr_output_mode *drm_connector_get_pending_mode(
output->pending.custom_mode.height,
(float)output->pending.custom_mode.refresh / 1000, false, false);
mode.type = DRM_MODE_TYPE_USERDEF;
if (conn->hot_plug_mode) {
return &wlr_drm_connector_create_mode(output, &mode)->wlr_mode;
}
return wlr_drm_connector_add_mode(output, &mode);
}
abort();
Expand Down Expand Up @@ -536,6 +539,26 @@ bool drm_connector_supports_vrr(struct wlr_drm_connector *conn) {
return true;
}

bool drm_connector_has_hotplug_mode(struct wlr_drm_connector *conn) {
uint64_t hotplug_mode_update = 0;
struct wlr_drm_backend *drm = conn->backend;
get_drm_prop(drm->fd, conn->id, conn->props.hotplug_mode_update,
&hotplug_mode_update);
return hotplug_mode_update == 1;
}

bool drm_connector_get_preferred_mode(drmModeConnector *drm_conn,
drmModeModeInfo *drm_mode) {
bool found = false;
for (int i = 0; i < drm_conn->count_modes; ++i) {
if (drm_conn->modes[i].type & DRM_MODE_TYPE_PREFERRED) {
found = true;
*drm_mode = drm_conn->modes[i];
}
}
return found;
}

static bool drm_connector_commit(struct wlr_output *output) {
struct wlr_drm_connector *conn = get_drm_connector_from_output(output);
struct wlr_drm_backend *drm = conn->backend;
Expand Down Expand Up @@ -567,6 +590,10 @@ static bool drm_connector_commit(struct wlr_output *output) {
}

if (!drm_connector_set_mode(conn, wlr_mode)) {
if (conn->hot_plug_mode) {
struct wlr_drm_mode *drm_mode = (struct wlr_drm_mode *)wlr_mode;
free(drm_mode);
}
return false;
}
} else if (output->pending.committed & WLR_OUTPUT_STATE_BUFFER) {
Expand Down Expand Up @@ -760,8 +787,16 @@ static void attempt_enable_needs_modeset(struct wlr_drm_backend *drm) {
conn->crtc != NULL && conn->desired_mode != NULL &&
conn->desired_enabled) {
wlr_drm_conn_log(conn, WLR_DEBUG,
"Output has a desired mode and a CRTC, attempting a modeset");
drm_connector_set_mode(conn, conn->desired_mode);
"Output has a desired mode and a CRTC, requesting a modeset");
struct wlr_output_state state = {
.committed = WLR_OUTPUT_STATE_MODE | WLR_OUTPUT_STATE_ENABLED,
.mode_type = conn->hot_plug_mode
? WLR_OUTPUT_STATE_MODE_CUSTOM
: WLR_OUTPUT_STATE_MODE_FIXED,
.mode = conn->desired_mode,
.enabled = true,
};
wlr_output_send_request_state(&conn->output, &state);
}
}
}
Expand Down Expand Up @@ -817,7 +852,12 @@ bool drm_connector_set_mode(struct wlr_drm_connector *conn,

conn->state = WLR_DRM_CONN_CONNECTED;
conn->desired_mode = NULL;
struct wlr_output_mode *previous_mode = conn->output.current_mode;
wlr_output_update_mode(&conn->output, wlr_mode);
if (previous_mode != NULL && conn->hot_plug_mode) {
struct wlr_drm_mode *drm_mode = (struct wlr_drm_mode *)previous_mode;
free(drm_mode);
}
wlr_output_update_enabled(&conn->output, true);
conn->desired_enabled = true;

Expand All @@ -828,6 +868,20 @@ bool drm_connector_set_mode(struct wlr_drm_connector *conn,
return true;
}

struct wlr_drm_mode *wlr_drm_connector_create_mode(struct wlr_output *output,
const drmModeModeInfo *modeinfo) {
struct wlr_drm_mode *mode = calloc(1, sizeof(*mode));
if (!mode) {
return NULL;
}
memcpy(&mode->drm_mode, modeinfo, sizeof(*modeinfo));

mode->wlr_mode.width = mode->drm_mode.hdisplay;
mode->wlr_mode.height = mode->drm_mode.vdisplay;
mode->wlr_mode.refresh = calculate_refresh_rate(modeinfo);
return mode;
}

struct wlr_output_mode *wlr_drm_connector_add_mode(struct wlr_output *output,
const drmModeModeInfo *modeinfo) {
struct wlr_drm_connector *conn = get_drm_connector_from_output(output);
Expand All @@ -844,15 +898,7 @@ struct wlr_output_mode *wlr_drm_connector_add_mode(struct wlr_output *output,
}
}

struct wlr_drm_mode *mode = calloc(1, sizeof(*mode));
if (!mode) {
return NULL;
}
memcpy(&mode->drm_mode, modeinfo, sizeof(*modeinfo));

mode->wlr_mode.width = mode->drm_mode.hdisplay;
mode->wlr_mode.height = mode->drm_mode.vdisplay;
mode->wlr_mode.refresh = calculate_refresh_rate(modeinfo);
struct wlr_drm_mode *mode = wlr_drm_connector_create_mode(output, modeinfo);

wlr_drm_conn_log(conn, WLR_INFO, "Registered custom mode "
"%"PRId32"x%"PRId32"@%"PRId32,
Expand Down Expand Up @@ -1014,7 +1060,15 @@ static void drm_connector_destroy_output(struct wlr_output *output) {

conn->state = WLR_DRM_CONN_DISCONNECTED;
conn->desired_enabled = false;
if (conn->desired_mode != NULL && conn->hot_plug_mode) {
struct wlr_drm_mode *drm_mode = (struct wlr_drm_mode *)conn->desired_mode;
free(drm_mode);
}
conn->desired_mode = NULL;
if (output->current_mode != NULL && conn->hot_plug_mode) {
struct wlr_drm_mode *drm_mode = (struct wlr_drm_mode *)output->current_mode;
free(drm_mode);
}
conn->possible_crtcs = 0;
conn->pending_page_flip_crtc = 0;

Expand Down Expand Up @@ -1180,7 +1234,12 @@ static void realloc_crtcs(struct wlr_drm_backend *drm) {
conn->state = WLR_DRM_CONN_NEEDS_MODESET;
wlr_output_update_enabled(&conn->output, false);
conn->desired_mode = conn->output.current_mode;
struct wlr_output_mode *previous_mode = conn->output.current_mode;
wlr_output_update_mode(&conn->output, NULL);
if (previous_mode != NULL && conn->hot_plug_mode) {
struct wlr_drm_mode *drm_mode = (struct wlr_drm_mode *)previous_mode;
free(drm_mode);
}
}
continue;
}
Expand Down Expand Up @@ -1325,6 +1384,44 @@ void scan_drm_connectors(struct wlr_drm_backend *drm) {
}
}

if ((wlr_conn->state == WLR_DRM_CONN_CONNECTED
|| wlr_conn->state == WLR_DRM_CONN_NEEDS_MODESET) &&
drm_conn->connection == DRM_MODE_CONNECTED &&
wlr_conn->hot_plug_mode) {
wlr_log(WLR_INFO, "Scanning '%s' for hot plugged mode", wlr_conn->name);
drmModeModeInfo drm_mode;
if (!drm_connector_get_preferred_mode(drm_conn, &drm_mode)) {
wlr_log(WLR_ERROR,
"%s supports hotplug_mode_update but has no preferred mode",
wlr_conn->name);
return;
}

int32_t width = drm_mode.hdisplay;
int32_t height = drm_mode.vdisplay;
int32_t refresh = calculate_refresh_rate(&drm_mode);
if (width == wlr_conn->output.width &&
height == wlr_conn->output.height &&
refresh == wlr_conn->output.refresh) {
wlr_log(WLR_INFO, "No changes");
return;
}

wlr_log(WLR_INFO,
"Preferred mode changed to: %"PRId32"x%"PRId32"@%"PRId32"",
width, height, refresh);
struct wlr_output_state state = {
.committed = WLR_OUTPUT_STATE_MODE,
.mode_type = WLR_OUTPUT_STATE_MODE_CUSTOM,
.custom_mode = {
.width = width,
.height = height,
.refresh = refresh,
},
};
wlr_output_send_request_state(&wlr_conn->output, &state);
}

if (wlr_conn->state == WLR_DRM_CONN_DISCONNECTED &&
drm_conn->connection == DRM_MODE_CONNECTED) {
wlr_log(WLR_INFO, "'%s' connected", wlr_conn->name);
Expand All @@ -1345,6 +1442,8 @@ void scan_drm_connectors(struct wlr_drm_backend *drm) {

get_drm_connector_props(drm->fd, wlr_conn->id, &wlr_conn->props);

wlr_conn->hot_plug_mode = drm_connector_has_hotplug_mode(wlr_conn);

size_t edid_len = 0;
uint8_t *edid = get_drm_prop_blob(drm->fd,
wlr_conn->id, wlr_conn->props.edid, &edid_len);
Expand All @@ -1370,34 +1469,49 @@ void scan_drm_connectors(struct wlr_drm_backend *drm) {

free(subconnector);

wlr_log(WLR_INFO, "Detected modes:");

for (int i = 0; i < drm_conn->count_modes; ++i) {
struct wlr_drm_mode *mode = calloc(1, sizeof(*mode));
if (!mode) {
wlr_log_errno(WLR_ERROR, "Allocation failed");
continue;
}

if (drm_conn->modes[i].flags & DRM_MODE_FLAG_INTERLACE) {
free(mode);
continue;
if (wlr_conn->hot_plug_mode) {
drmModeModeInfo drm_mode;
if (!drm_connector_get_preferred_mode(drm_conn, &drm_mode)) {
wlr_log(WLR_ERROR,
"%s supports hotplug_mode_update but has no preferred mode",
wlr_conn->name);
abort();
}

mode->drm_mode = drm_conn->modes[i];
mode->wlr_mode.width = mode->drm_mode.hdisplay;
mode->wlr_mode.height = mode->drm_mode.vdisplay;
mode->wlr_mode.refresh = calculate_refresh_rate(&mode->drm_mode);
if (mode->drm_mode.type & DRM_MODE_TYPE_PREFERRED) {
mode->wlr_mode.preferred = true;
wlr_log(WLR_INFO, "Detected hot pluggable mode");
int32_t width = drm_mode.hdisplay;
int32_t height = drm_mode.vdisplay;
int32_t refresh = calculate_refresh_rate(&drm_mode);
wlr_output_set_custom_mode(output, width, height, refresh);
} else {
wlr_log(WLR_INFO, "Detected modes:");

for (int i = 0; i < drm_conn->count_modes; ++i) {
struct wlr_drm_mode *mode = calloc(1, sizeof(*mode));
if (!mode) {
wlr_log_errno(WLR_ERROR, "Allocation failed");
continue;
}

if (drm_conn->modes[i].flags & DRM_MODE_FLAG_INTERLACE) {
free(mode);
continue;
}

mode->drm_mode = drm_conn->modes[i];
mode->wlr_mode.width = mode->drm_mode.hdisplay;
mode->wlr_mode.height = mode->drm_mode.vdisplay;
mode->wlr_mode.refresh = calculate_refresh_rate(&mode->drm_mode);
if (mode->drm_mode.type & DRM_MODE_TYPE_PREFERRED) {
mode->wlr_mode.preferred = true;
}

wlr_log(WLR_INFO, " %"PRId32"x%"PRId32"@%"PRId32" %s",
mode->wlr_mode.width, mode->wlr_mode.height,
mode->wlr_mode.refresh,
mode->wlr_mode.preferred ? "(preferred)" : "");

wl_list_insert(&wlr_conn->output.modes, &mode->wlr_mode.link);
}

wlr_log(WLR_INFO, " %"PRId32"x%"PRId32"@%"PRId32" %s",
mode->wlr_mode.width, mode->wlr_mode.height,
mode->wlr_mode.refresh,
mode->wlr_mode.preferred ? "(preferred)" : "");

wl_list_insert(&wlr_conn->output.modes, &mode->wlr_mode.link);
}

wlr_conn->possible_crtcs = get_possible_crtcs(drm->fd, res, drm_conn);
Expand Down
1 change: 1 addition & 0 deletions backend/drm/properties.c
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ static const struct prop_info connector_info[] = {
{ "DPMS", INDEX(dpms) },
{ "EDID", INDEX(edid) },
{ "PATH", INDEX(path) },
{ "hotplug_mode_update", INDEX(hotplug_mode_update) },
{ "link-status", INDEX(link_status) },
{ "subconnector", INDEX(subconnector) },
{ "vrr_capable", INDEX(vrr_capable) },
Expand Down
13 changes: 11 additions & 2 deletions backend/wayland/output.c
Original file line number Diff line number Diff line change
Expand Up @@ -550,8 +550,17 @@ static void xdg_toplevel_handle_configure(void *data,
if (width == 0 || height == 0) {
return;
}
// loop over states for maximized etc?
output_set_custom_mode(&output->wlr_output, width, height, 0);
if (width == output->wlr_output.width &&
height == output->wlr_output.height) {
return;
}

struct wlr_output_state state = {
.committed = WLR_OUTPUT_STATE_MODE,
.mode_type = WLR_OUTPUT_STATE_MODE_CUSTOM,
.custom_mode = { .width = width, .height = height },
};
wlr_output_send_request_state(&output->wlr_output, &state);
}

static void xdg_toplevel_handle_close(void *data,
Expand Down
52 changes: 25 additions & 27 deletions backend/x11/output.c
Original file line number Diff line number Diff line change
Expand Up @@ -39,20 +39,11 @@ static struct wlr_x11_output *get_x11_output_from_output(
return (struct wlr_x11_output *)wlr_output;
}

static void output_set_refresh(struct wlr_output *wlr_output, int32_t refresh) {
struct wlr_x11_output *output = get_x11_output_from_output(wlr_output);

wlr_output_update_custom_mode(&output->wlr_output, wlr_output->width,
wlr_output->height, 0);
}

static bool output_set_custom_mode(struct wlr_output *wlr_output,
int32_t width, int32_t height, int32_t refresh) {
struct wlr_x11_output *output = get_x11_output_from_output(wlr_output);
struct wlr_x11_backend *x11 = output->x11;

output_set_refresh(&output->wlr_output, refresh);

const uint32_t values[] = { width, height };
xcb_void_cookie_t cookie = xcb_configure_window_checked(
x11->xcb, output->win,
Expand All @@ -66,6 +57,22 @@ static bool output_set_custom_mode(struct wlr_output *wlr_output,
return false;
}

if (output->swapchain->width != width ||
output->swapchain->height != height) {
struct wlr_swapchain *swapchain = wlr_swapchain_create(
output->x11->allocator, width, height, output->x11->drm_format);
if (!swapchain) {
return false;
}
wlr_swapchain_destroy(output->swapchain);
output->swapchain = swapchain;
}

wlr_output_update_custom_mode(&output->wlr_output, width, height, 0);

// Move the pointer to its new location
update_x11_pointer_position(output, output->x11->time);

return true;
}

Expand Down Expand Up @@ -382,8 +389,6 @@ struct wlr_output *wlr_x11_output_create(struct wlr_backend *backend) {
return NULL;
}

output_set_refresh(&output->wlr_output, 0);

snprintf(wlr_output->name, sizeof(wlr_output->name), "X11-%zd",
++x11->last_output_num);
parse_xcb_setup(wlr_output, x11->xcb);
Expand Down Expand Up @@ -468,31 +473,24 @@ struct wlr_output *wlr_x11_output_create(struct wlr_backend *backend) {

void handle_x11_configure_notify(struct wlr_x11_output *output,
xcb_configure_notify_event_t *ev) {
// ignore events that set an invalid size:
if (ev->width == 0 || ev->height == 0) {
wlr_log(WLR_DEBUG,
"Ignoring X11 configure event for height=%d, width=%d",
ev->width, ev->height);
return;
}

if (output->swapchain->width != ev->width ||
output->swapchain->height != ev->height) {
struct wlr_swapchain *swapchain = wlr_swapchain_create(
output->x11->allocator, ev->width, ev->height,
output->x11->drm_format);
if (!swapchain) {
return;
}
wlr_swapchain_destroy(output->swapchain);
output->swapchain = swapchain;
if (ev->width == output->wlr_output.width &&
ev->height == output->wlr_output.height) {
return;
}

wlr_output_update_custom_mode(&output->wlr_output, ev->width,
ev->height, 0);

// Move the pointer to its new location
update_x11_pointer_position(output, output->x11->time);
struct wlr_output_state state = {
.committed = WLR_OUTPUT_STATE_MODE,
.mode_type = WLR_OUTPUT_STATE_MODE_CUSTOM,
.custom_mode = { .width = ev->width, .height = ev->height },
};
wlr_output_send_request_state(&output->wlr_output, &state);
}

bool wlr_output_is_x11(struct wlr_output *wlr_output) {
Expand Down
Loading