intel/display: use drm vblank wait support

Fixes #5415
This commit is contained in:
Alexander Boettcher 2024-12-05 15:54:46 +01:00 committed by Norman Feske
parent d143ed7e47
commit 4ec277058e

View File

@ -26,12 +26,22 @@
#include "lx_emul.h"
enum { MAX_BRIGHTNESS = 100, INVALID_BRIGHTNESS = MAX_BRIGHTNESS + 1 };
enum { MAX_CONNECTORS = 32, CONNECTOR_ID_MIRROR = MAX_CONNECTORS - 1 };
enum { CAPTURE_RATE_MS = 10, ATTEMPTS_BEFORE_STOP = 7 };
/* defined in #include <drm/drm_internal.h> */
int drm_wait_vblank_ioctl(struct drm_device *dev, void *data,
struct drm_file *filp);
enum { MAX_BRIGHTNESS = 100, INVALID_BRIGHTNESS = MAX_BRIGHTNESS + 1 };
enum { MAX_CONNECTORS = 32, CONNECTOR_ID_MIRROR = MAX_CONNECTORS - 1 };
enum { MAX_CRTCS = 4 };
static struct update_task {
struct task_struct * lx_task;
unsigned pipe_id;
unsigned unchanged;
} update_tasks[MAX_CRTCS];
struct task_struct * lx_user_task = NULL;
static struct task_struct * lx_update_task = NULL;
static struct drm_client_dev * dev_client = NULL;
static bool const verbose = false;
@ -43,7 +53,6 @@ static struct state {
struct i915_vma * vma;
unsigned long vma_flags;
uint8_t mode_id;
uint8_t unchanged;
bool mirrored;
bool enabled;
} states [MAX_CONNECTORS] = { };
@ -306,7 +315,6 @@ static void destroy_fb_and_capture(struct drm_client_dev * const dev,
}
state->enabled = false;
state->unchanged = 0;
destroy_fb(dev, &(state->fb_dumb), &(state->fb_cmd));
}
@ -729,6 +737,12 @@ static int do_action_loop(void * data)
status_last_action = probe_and_apply_fbs(dev_client, false)
? ACTION_FAILED : !ACTION_FAILED;
/* wakeup tasks, connector->state->crtc should now be assigned */
for (unsigned pipe_id = 0; pipe_id < MAX_CRTCS; pipe_id++) {
update_tasks[pipe_id].unchanged = 0;
lx_emul_task_unblock(update_tasks[pipe_id].lx_task);
}
break;
default:
lx_emul_task_schedule(true /* block task */);
@ -740,104 +754,164 @@ static int do_action_loop(void * data)
}
static void mark_framebuffer_dirty(struct drm_framebuffer * const fb)
void lx_emul_i915_wakeup(unsigned const connector_id)
{
struct drm_clip_rect *clips = NULL;
struct drm_mode_fb_dirty_cmd r = { };
struct drm_connector_list_iter conn_iter;
struct drm_connector * connector = NULL;
unsigned flags = 0;
int num_clips = 0;
int ret = 0;
unsigned pipe_id = ~0U; /* invalid pipe id */
if (!dev_client)
return;
if (!fb || !fb->funcs || !fb->funcs->dirty)
return;
drm_connector_list_iter_begin(dev_client->dev, &conn_iter);
drm_client_for_each_connector_iter(connector, &conn_iter) {
struct drm_crtc * crtc = NULL;
bool mirrored = false;
ret = fb->funcs->dirty(fb, dev_client->file, flags, r.color, clips,
num_clips);
if (!connector)
continue;
if (ret)
printk("%s failed %d\n", __func__, ret);
}
mirrored = connector_id == CONNECTOR_ID_MIRROR &&
connector->index < MAX_CONNECTORS &&
states[connector->index].mirrored &&
states[connector->index].enabled;
if (!mirrored && connector->index != connector_id)
continue;
void lx_emul_i915_wakeup(unsigned const connector_id)
{
bool const valid_id = connector_id < MAX_CONNECTORS;
if (!connector->state || !connector->state->crtc) {
if (!mirrored && verbose)
printk("unable to look up pipe id of connector %s index=%u\n",
connector->name, connector->index);
continue;
}
if (!valid_id) {
printk("%s: connector id invalid %d\n", __func__, connector_id);
crtc = connector->state->crtc;
if (verbose)
printk("%s:%u %s %u->%u %s %s\n", __func__, __LINE__,
connector->name,
crtc->base.id, drm_crtc_index(crtc), crtc->name,
crtc->enabled ? "enabled" : "not enabled");
pipe_id = drm_crtc_index(crtc);
break;
}
drm_connector_list_iter_end(&conn_iter);
if (pipe_id >= MAX_CRTCS) {
if (verbose)
printk("unknown pipe id for connector %u\n", connector_id);
return;
}
states[connector_id].unchanged = 0;
update_tasks[pipe_id].unchanged = 0;
/* wake potential sleeping update task */
lx_emul_task_unblock(lx_update_task);
lx_emul_task_unblock(update_tasks[pipe_id].lx_task);
}
static int update_content(void *)
static int update_content(void * task_info)
{
struct update_task * const info = (struct update_task *)task_info;
unsigned long last_usec = 0;
unsigned long last_sec = 0;
bool block_task = true;
unsigned const stop_after_ms = 70; /* when nothing changed content wise */
lx_emul_task_schedule(true /* block task */);
while (true) {
struct drm_connector_list_iter conn_iter;
struct drm_connector * connector = NULL;
struct drm_device const * dev = dev_client->dev;
bool block_task = true;
bool mirror_run = false;
struct drm_device * dev = dev_client->dev;
union drm_wait_vblank vblwait = {};
int error = -1;
unsigned hz = 60;
unsigned attempts_before_stop = 4;
drm_connector_list_iter_begin(dev, &conn_iter);
drm_client_for_each_connector_iter(connector, &conn_iter) {
/* adjust to actual current hz value of active hw mode */
if (dev->num_crtcs > info->pipe_id) {
struct drm_vblank_crtc *vblank = &dev->vblank[info->pipe_id];
bool may_sleep = false;
unsigned index = connector->index;
hz = vblank ? drm_mode_vrefresh(&vblank->hwmode) : 60;
if (hz == 0) hz = 60;
}
if (connector->status != connector_status_connected)
continue;
attempts_before_stop = stop_after_ms * hz / 1000;
if (attempts_before_stop < 2) attempts_before_stop = 2;
if (connector->index >= MAX_CONNECTORS) {
printk("%s: connector id invalid %d\n", __func__, index);
index = CONNECTOR_ID_MIRROR; /* should never happen case */
}
/* wait for next vblank sequence */
vblwait.request.sequence = 1;
vblwait.request.type = _DRM_VBLANK_RELATIVE
| info->pipe_id << _DRM_VBLANK_HIGH_CRTC_SHIFT;
if (!states[index].enabled)
continue;
/* wait for next vblank before capturing */
error = drm_wait_vblank_ioctl(dev, &vblwait, dev_client->file);
if (states[index].mirrored) {
if (mirror_run)
if (error) {
if (verbose)
printk("%s:%u pipe=%u error=%d %s\n", __func__, __LINE__,
info->pipe_id, error,
error == -EINVAL ? "EINVAL" : "");
/* block connectors with errors by now */
info->unchanged = attempts_before_stop;
}
/* debugging - statistics */
if (verbose && !block_task && !error) {
uint64_t diff_us = last_usec < vblwait.reply.tval_usec
? vblwait.reply.tval_usec - last_usec
: vblwait.reply.tval_usec + 1000000 - last_usec;
uint64_t warn_min_us = 1000ul * 1000 * 0.95 / hz;
uint64_t warn_max_us = 1000ul * 1000 * 1.05 / hz;
if (diff_us < warn_min_us || diff_us > warn_max_us)
printk("%s:%u pipe=%u %llu us [%llu,%llu] (seq=%u)\n",
__func__, __LINE__, info->pipe_id, diff_us,
warn_min_us, warn_max_us, vblwait.reply.sequence);
}
last_sec = vblwait.reply.tval_sec;
last_usec = vblwait.reply.tval_usec;
block_task = info->unchanged++ >= attempts_before_stop;
{
struct drm_connector_list_iter conn_iter;
struct drm_connector * connector = NULL;
unsigned connector_id = 0;
drm_connector_list_iter_begin(dev, &conn_iter);
drm_client_for_each_connector_iter(connector, &conn_iter) {
if (!connector->state || !connector->state->crtc)
continue;
mirror_run = true;
index = CONNECTOR_ID_MIRROR;
if (info->pipe_id != drm_crtc_index(connector->state->crtc))
continue;
if (connector->index >= MAX_CONNECTORS)
continue;
connector_id = states[connector->index].mirrored
? CONNECTOR_ID_MIRROR : connector->index;
/* hint that capturing may be stopped now, when block_task */
if (lx_emul_i915_blit(connector_id, block_task)) {
info->unchanged = 0;
block_task = false;
}
}
states[index].unchanged ++;
may_sleep = states[index].unchanged >= ATTEMPTS_BEFORE_STOP;
if (!lx_emul_i915_blit(index, may_sleep)) {
if (!may_sleep)
block_task = false;
continue;
}
block_task = false;
states[index].unchanged = 0;
if (states[index].fbs)
mark_framebuffer_dirty(states[index].fbs);
drm_connector_list_iter_end(&conn_iter);
}
drm_connector_list_iter_end(&conn_iter);
if (block_task)
lx_emul_task_schedule(true /* block task */);
else
/* schedule_timeout(jiffes) or hrtimer or msleep */
msleep(CAPTURE_RATE_MS);
}
return 0;
@ -846,13 +920,18 @@ static int update_content(void *)
void lx_user_init(void)
{
int pid = kernel_thread(do_action_loop, NULL, "lx_user",
CLONE_FS | CLONE_FILES);
int pid2 = kernel_thread(update_content, NULL, "lx_update",
CLONE_FS | CLONE_FILES);
int pid = kernel_thread(do_action_loop, NULL, "lx_user",
CLONE_FS | CLONE_FILES);
lx_user_task = find_task_by_pid_ns(pid , NULL);
lx_update_task = find_task_by_pid_ns(pid2, NULL);
lx_user_task = find_task_by_pid_ns(pid , NULL);
for (unsigned i = 0; i < MAX_CRTCS; i++) {
int pid_update;
update_tasks[i].pipe_id = i;
pid_update = kernel_thread(update_content, &update_tasks[i],
"lx_update", CLONE_FS | CLONE_FILES);
update_tasks[i].lx_task = find_task_by_pid_ns(pid_update, NULL);
}
}