4549 lines
118 KiB
C
4549 lines
118 KiB
C
/*
|
|
* Copyright © 2013 Intel Corporation
|
|
*
|
|
* Permission is hereby granted, free of charge, to any person obtaining a
|
|
* copy of this software and associated documentation files (the "Software"),
|
|
* to deal in the Software without restriction, including without limitation
|
|
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
|
|
* and/or sell copies of the Software, and to permit persons to whom the
|
|
* Software is furnished to do so, subject to the following conditions:
|
|
*
|
|
* The above copyright notice and this permission notice (including the next
|
|
* paragraph) shall be included in all copies or substantial portions of the
|
|
* Software.
|
|
*
|
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
|
|
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
|
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
|
* IN THE SOFTWARE.
|
|
*
|
|
* Authors:
|
|
* Daniel Vetter <daniel.vetter@ffwll.ch>
|
|
* Damien Lespiau <damien.lespiau@intel.com>
|
|
*/
|
|
|
|
#include "config.h"
|
|
|
|
#include <assert.h>
|
|
#include <inttypes.h>
|
|
#include <unistd.h>
|
|
#include <stdio.h>
|
|
#include <stdarg.h>
|
|
#include <fcntl.h>
|
|
#include <sys/stat.h>
|
|
#include <string.h>
|
|
#include <strings.h>
|
|
#include <stdlib.h>
|
|
#ifdef HAVE_LINUX_KD_H
|
|
#include <linux/kd.h>
|
|
#elif HAVE_SYS_KD_H
|
|
#include <sys/kd.h>
|
|
#endif
|
|
|
|
#if !defined(ANDROID)
|
|
#include <libudev.h>
|
|
#endif
|
|
|
|
#include <poll.h>
|
|
#include <errno.h>
|
|
#include <time.h>
|
|
|
|
#include <i915_drm.h>
|
|
|
|
#include "drmtest.h"
|
|
#include "igt_kms.h"
|
|
#include "igt_aux.h"
|
|
#include "igt_edid.h"
|
|
#include "intel_chipset.h"
|
|
#include "igt_debugfs.h"
|
|
#include "igt_device.h"
|
|
#include "igt_sysfs.h"
|
|
#include "sw_sync.h"
|
|
|
|
/**
|
|
* SECTION:igt_kms
|
|
* @short_description: Kernel modesetting support library
|
|
* @title: KMS
|
|
* @include: igt.h
|
|
*
|
|
* This library provides support to enumerate and set modeset configurations.
|
|
*
|
|
* There are two parts in this library: First the low level helper function
|
|
* which directly build on top of raw ioctls or the interfaces provided by
|
|
* libdrm. Those functions all have a kmstest_ prefix.
|
|
*
|
|
* The second part is a high-level library to manage modeset configurations
|
|
* which abstracts away some of the low-level details like the difference
|
|
* between legacy and universal plane support for setting cursors or in the
|
|
* future the difference between legacy and atomic commit. These high-level
|
|
* functions have all igt_ prefixes. This part is still very much work in
|
|
* progress and so also lacks a bit documentation for the individual functions.
|
|
*
|
|
* Note that this library's header pulls in the [i-g-t framebuffer](igt-gpu-tools-i-g-t-framebuffer.html)
|
|
* library as a dependency.
|
|
*/
|
|
|
|
/* list of connectors that need resetting on exit */
|
|
#define MAX_CONNECTORS 32
|
|
static char *forced_connectors[MAX_CONNECTORS + 1];
|
|
static int forced_connectors_device[MAX_CONNECTORS + 1];
|
|
|
|
/**
|
|
* igt_kms_get_base_edid:
|
|
*
|
|
* Get the base edid block, which includes the following modes:
|
|
*
|
|
* - 1920x1080 60Hz
|
|
* - 1280x720 60Hz
|
|
* - 1024x768 60Hz
|
|
* - 800x600 60Hz
|
|
* - 640x480 60Hz
|
|
*
|
|
* Returns: a basic edid block
|
|
*/
|
|
const struct edid *igt_kms_get_base_edid(void)
|
|
{
|
|
static struct edid edid;
|
|
drmModeModeInfo mode = {};
|
|
|
|
mode.clock = 148500;
|
|
mode.hdisplay = 1920;
|
|
mode.hsync_start = 2008;
|
|
mode.hsync_end = 2052;
|
|
mode.htotal = 2200;
|
|
mode.vdisplay = 1080;
|
|
mode.vsync_start = 1084;
|
|
mode.vsync_end = 1089;
|
|
mode.vtotal = 1125;
|
|
mode.vrefresh = 60;
|
|
|
|
edid_init_with_mode(&edid, &mode);
|
|
edid_update_checksum(&edid);
|
|
|
|
return &edid;
|
|
}
|
|
|
|
/**
|
|
* igt_kms_get_alt_edid:
|
|
*
|
|
* Get an alternate edid block, which includes the following modes:
|
|
*
|
|
* - 1400x1050 60Hz
|
|
* - 1920x1080 60Hz
|
|
* - 1280x720 60Hz
|
|
* - 1024x768 60Hz
|
|
* - 800x600 60Hz
|
|
* - 640x480 60Hz
|
|
*
|
|
* Returns: an alternate edid block
|
|
*/
|
|
const struct edid *igt_kms_get_alt_edid(void)
|
|
{
|
|
static struct edid edid;
|
|
drmModeModeInfo mode = {};
|
|
|
|
mode.clock = 101000;
|
|
mode.hdisplay = 1400;
|
|
mode.hsync_start = 1448;
|
|
mode.hsync_end = 1480;
|
|
mode.htotal = 1560;
|
|
mode.vdisplay = 1050;
|
|
mode.vsync_start = 1053;
|
|
mode.vsync_end = 1057;
|
|
mode.vtotal = 1080;
|
|
mode.vrefresh = 60;
|
|
|
|
edid_init_with_mode(&edid, &mode);
|
|
edid_update_checksum(&edid);
|
|
|
|
return &edid;
|
|
}
|
|
|
|
#define AUDIO_EDID_SIZE (2 * EDID_BLOCK_SIZE)
|
|
|
|
static const struct edid *
|
|
generate_audio_edid(unsigned char raw_edid[static AUDIO_EDID_SIZE],
|
|
bool with_vsdb, struct cea_sad *sad,
|
|
struct cea_speaker_alloc *speaker_alloc)
|
|
{
|
|
struct edid *edid;
|
|
struct edid_ext *edid_ext;
|
|
struct edid_cea *edid_cea;
|
|
char *cea_data;
|
|
struct edid_cea_data_block *block;
|
|
const struct cea_vsdb *vsdb;
|
|
size_t cea_data_size, vsdb_size;
|
|
|
|
/* Create a new EDID from the base IGT EDID, and add an
|
|
* extension that advertises audio support. */
|
|
edid = (struct edid *) raw_edid;
|
|
memcpy(edid, igt_kms_get_base_edid(), sizeof(struct edid));
|
|
edid->extensions_len = 1;
|
|
edid_ext = &edid->extensions[0];
|
|
edid_cea = &edid_ext->data.cea;
|
|
cea_data = edid_cea->data;
|
|
cea_data_size = 0;
|
|
|
|
/* Short Audio Descriptor block */
|
|
block = (struct edid_cea_data_block *) &cea_data[cea_data_size];
|
|
cea_data_size += edid_cea_data_block_set_sad(block, sad, 1);
|
|
|
|
/* A Vendor Specific Data block is needed for HDMI audio */
|
|
if (with_vsdb) {
|
|
block = (struct edid_cea_data_block *) &cea_data[cea_data_size];
|
|
vsdb = cea_vsdb_get_hdmi_default(&vsdb_size);
|
|
cea_data_size += edid_cea_data_block_set_vsdb(block, vsdb,
|
|
vsdb_size);
|
|
}
|
|
|
|
/* Speaker Allocation Data block */
|
|
block = (struct edid_cea_data_block *) &cea_data[cea_data_size];
|
|
cea_data_size += edid_cea_data_block_set_speaker_alloc(block,
|
|
speaker_alloc);
|
|
|
|
assert(cea_data_size <= sizeof(edid_cea->data));
|
|
|
|
edid_ext_set_cea(edid_ext, cea_data_size, 0, EDID_CEA_BASIC_AUDIO);
|
|
|
|
edid_update_checksum(edid);
|
|
|
|
return edid;
|
|
}
|
|
|
|
const struct edid *igt_kms_get_hdmi_audio_edid(void)
|
|
{
|
|
int channels;
|
|
uint8_t sampling_rates, sample_sizes;
|
|
static unsigned char raw_edid[AUDIO_EDID_SIZE] = {0};
|
|
struct cea_sad sad = {0};
|
|
struct cea_speaker_alloc speaker_alloc = {0};
|
|
|
|
/* Initialize the Short Audio Descriptor for PCM */
|
|
channels = 2;
|
|
sampling_rates = CEA_SAD_SAMPLING_RATE_32KHZ |
|
|
CEA_SAD_SAMPLING_RATE_44KHZ |
|
|
CEA_SAD_SAMPLING_RATE_48KHZ;
|
|
sample_sizes = CEA_SAD_SAMPLE_SIZE_16 |
|
|
CEA_SAD_SAMPLE_SIZE_20 |
|
|
CEA_SAD_SAMPLE_SIZE_24;
|
|
cea_sad_init_pcm(&sad, channels, sampling_rates, sample_sizes);
|
|
|
|
/* Initialize the Speaker Allocation Data */
|
|
speaker_alloc.speakers = CEA_SPEAKER_FRONT_LEFT_RIGHT_CENTER;
|
|
|
|
return generate_audio_edid(raw_edid, true, &sad, &speaker_alloc);
|
|
}
|
|
|
|
const struct edid *igt_kms_get_dp_audio_edid(void)
|
|
{
|
|
int channels;
|
|
uint8_t sampling_rates, sample_sizes;
|
|
static unsigned char raw_edid[AUDIO_EDID_SIZE] = {0};
|
|
struct cea_sad sad = {0};
|
|
struct cea_speaker_alloc speaker_alloc = {0};
|
|
|
|
/* Initialize the Short Audio Descriptor for PCM */
|
|
channels = 2;
|
|
sampling_rates = CEA_SAD_SAMPLING_RATE_32KHZ |
|
|
CEA_SAD_SAMPLING_RATE_44KHZ |
|
|
CEA_SAD_SAMPLING_RATE_48KHZ;
|
|
sample_sizes = CEA_SAD_SAMPLE_SIZE_16 |
|
|
CEA_SAD_SAMPLE_SIZE_20 |
|
|
CEA_SAD_SAMPLE_SIZE_24;
|
|
cea_sad_init_pcm(&sad, channels, sampling_rates, sample_sizes);
|
|
|
|
/* Initialize the Speaker Allocation Data */
|
|
speaker_alloc.speakers = CEA_SPEAKER_FRONT_LEFT_RIGHT_CENTER;
|
|
|
|
return generate_audio_edid(raw_edid, false, &sad, &speaker_alloc);
|
|
}
|
|
|
|
static const uint8_t edid_4k_svds[] = {
|
|
32 | CEA_SVD_NATIVE, /* 1080p @ 24Hz (native) */
|
|
5, /* 1080i @ 60Hz */
|
|
20, /* 1080i @ 50Hz */
|
|
4, /* 720p @ 60Hz */
|
|
19, /* 720p @ 50Hz */
|
|
};
|
|
|
|
const struct edid *igt_kms_get_4k_edid(void)
|
|
{
|
|
static unsigned char raw_edid[256] = {0};
|
|
struct edid *edid;
|
|
struct edid_ext *edid_ext;
|
|
struct edid_cea *edid_cea;
|
|
char *cea_data;
|
|
struct edid_cea_data_block *block;
|
|
/* We'll add 6 extension fields to the HDMI VSDB. */
|
|
char raw_hdmi[HDMI_VSDB_MIN_SIZE + 6] = {0};
|
|
struct hdmi_vsdb *hdmi;
|
|
size_t cea_data_size = 0;
|
|
|
|
/* Create a new EDID from the base IGT EDID, and add an
|
|
* extension that advertises 4K support. */
|
|
edid = (struct edid *) raw_edid;
|
|
memcpy(edid, igt_kms_get_base_edid(), sizeof(struct edid));
|
|
edid->extensions_len = 1;
|
|
edid_ext = &edid->extensions[0];
|
|
edid_cea = &edid_ext->data.cea;
|
|
cea_data = edid_cea->data;
|
|
|
|
/* Short Video Descriptor */
|
|
block = (struct edid_cea_data_block *) &cea_data[cea_data_size];
|
|
cea_data_size += edid_cea_data_block_set_svd(block, edid_4k_svds,
|
|
sizeof(edid_4k_svds));
|
|
|
|
/* Vendor-Specific Data Block */
|
|
hdmi = (struct hdmi_vsdb *) raw_hdmi;
|
|
hdmi->src_phy_addr[0] = 0x10;
|
|
hdmi->src_phy_addr[1] = 0x00;
|
|
/* 6 extension fields */
|
|
hdmi->flags1 = 0;
|
|
hdmi->max_tdms_clock = 0;
|
|
hdmi->flags2 = HDMI_VSDB_VIDEO_PRESENT;
|
|
hdmi->data[0] = 0x00; /* HDMI video flags */
|
|
hdmi->data[1] = 1 << 5; /* 1 VIC entry, 0 3D entries */
|
|
hdmi->data[2] = 0x01; /* 2160p, specified as short descriptor */
|
|
|
|
block = (struct edid_cea_data_block *) &cea_data[cea_data_size];
|
|
cea_data_size += edid_cea_data_block_set_hdmi_vsdb(block, hdmi,
|
|
sizeof(raw_hdmi));
|
|
|
|
assert(cea_data_size <= sizeof(edid_cea->data));
|
|
|
|
edid_ext_set_cea(edid_ext, cea_data_size, 0, 0);
|
|
|
|
edid_update_checksum(edid);
|
|
|
|
return edid;
|
|
}
|
|
|
|
const struct edid *igt_kms_get_3d_edid(void)
|
|
{
|
|
static unsigned char raw_edid[256] = {0};
|
|
struct edid *edid;
|
|
struct edid_ext *edid_ext;
|
|
struct edid_cea *edid_cea;
|
|
char *cea_data;
|
|
struct edid_cea_data_block *block;
|
|
/* We'll add 5 extension fields to the HDMI VSDB. */
|
|
char raw_hdmi[HDMI_VSDB_MIN_SIZE + 5] = {0};
|
|
struct hdmi_vsdb *hdmi;
|
|
size_t cea_data_size = 0;
|
|
|
|
/* Create a new EDID from the base IGT EDID, and add an
|
|
* extension that advertises 3D support. */
|
|
edid = (struct edid *) raw_edid;
|
|
memcpy(edid, igt_kms_get_base_edid(), sizeof(struct edid));
|
|
edid->extensions_len = 1;
|
|
edid_ext = &edid->extensions[0];
|
|
edid_cea = &edid_ext->data.cea;
|
|
cea_data = edid_cea->data;
|
|
|
|
/* Short Video Descriptor */
|
|
block = (struct edid_cea_data_block *) &cea_data[cea_data_size];
|
|
cea_data_size += edid_cea_data_block_set_svd(block, edid_4k_svds,
|
|
sizeof(edid_4k_svds));
|
|
|
|
/* Vendor-Specific Data Block */
|
|
hdmi = (struct hdmi_vsdb *) raw_hdmi;
|
|
hdmi->src_phy_addr[0] = 0x10;
|
|
hdmi->src_phy_addr[1] = 0x00;
|
|
/* 5 extension fields */
|
|
hdmi->flags1 = 0;
|
|
hdmi->max_tdms_clock = 0;
|
|
hdmi->flags2 = HDMI_VSDB_VIDEO_PRESENT;
|
|
hdmi->data[0] = HDMI_VSDB_VIDEO_3D_PRESENT; /* HDMI video flags */
|
|
hdmi->data[1] = 0; /* 0 VIC entries, 0 3D entries */
|
|
|
|
block = (struct edid_cea_data_block *) &cea_data[cea_data_size];
|
|
cea_data_size += edid_cea_data_block_set_hdmi_vsdb(block, hdmi,
|
|
sizeof(raw_hdmi));
|
|
|
|
assert(cea_data_size <= sizeof(edid_cea->data));
|
|
|
|
edid_ext_set_cea(edid_ext, cea_data_size, 0, 0);
|
|
|
|
edid_update_checksum(edid);
|
|
|
|
return edid;
|
|
}
|
|
|
|
const char * const igt_plane_prop_names[IGT_NUM_PLANE_PROPS] = {
|
|
[IGT_PLANE_SRC_X] = "SRC_X",
|
|
[IGT_PLANE_SRC_Y] = "SRC_Y",
|
|
[IGT_PLANE_SRC_W] = "SRC_W",
|
|
[IGT_PLANE_SRC_H] = "SRC_H",
|
|
[IGT_PLANE_CRTC_X] = "CRTC_X",
|
|
[IGT_PLANE_CRTC_Y] = "CRTC_Y",
|
|
[IGT_PLANE_CRTC_W] = "CRTC_W",
|
|
[IGT_PLANE_CRTC_H] = "CRTC_H",
|
|
[IGT_PLANE_FB_ID] = "FB_ID",
|
|
[IGT_PLANE_CRTC_ID] = "CRTC_ID",
|
|
[IGT_PLANE_IN_FENCE_FD] = "IN_FENCE_FD",
|
|
[IGT_PLANE_TYPE] = "type",
|
|
[IGT_PLANE_ROTATION] = "rotation",
|
|
[IGT_PLANE_IN_FORMATS] = "IN_FORMATS",
|
|
[IGT_PLANE_COLOR_ENCODING] = "COLOR_ENCODING",
|
|
[IGT_PLANE_COLOR_RANGE] = "COLOR_RANGE",
|
|
[IGT_PLANE_PIXEL_BLEND_MODE] = "pixel blend mode",
|
|
[IGT_PLANE_ALPHA] = "alpha",
|
|
[IGT_PLANE_ZPOS] = "zpos",
|
|
};
|
|
|
|
const char * const igt_crtc_prop_names[IGT_NUM_CRTC_PROPS] = {
|
|
[IGT_CRTC_BACKGROUND] = "background_color",
|
|
[IGT_CRTC_CTM] = "CTM",
|
|
[IGT_CRTC_GAMMA_LUT] = "GAMMA_LUT",
|
|
[IGT_CRTC_GAMMA_LUT_SIZE] = "GAMMA_LUT_SIZE",
|
|
[IGT_CRTC_DEGAMMA_LUT] = "DEGAMMA_LUT",
|
|
[IGT_CRTC_DEGAMMA_LUT_SIZE] = "DEGAMMA_LUT_SIZE",
|
|
[IGT_CRTC_MODE_ID] = "MODE_ID",
|
|
[IGT_CRTC_ACTIVE] = "ACTIVE",
|
|
[IGT_CRTC_OUT_FENCE_PTR] = "OUT_FENCE_PTR",
|
|
[IGT_CRTC_VRR_ENABLED] = "VRR_ENABLED",
|
|
};
|
|
|
|
const char * const igt_connector_prop_names[IGT_NUM_CONNECTOR_PROPS] = {
|
|
[IGT_CONNECTOR_SCALING_MODE] = "scaling mode",
|
|
[IGT_CONNECTOR_CRTC_ID] = "CRTC_ID",
|
|
[IGT_CONNECTOR_DPMS] = "DPMS",
|
|
[IGT_CONNECTOR_BROADCAST_RGB] = "Broadcast RGB",
|
|
[IGT_CONNECTOR_CONTENT_PROTECTION] = "Content Protection",
|
|
[IGT_CONNECTOR_VRR_CAPABLE] = "vrr_capable",
|
|
[IGT_CONNECTOR_HDCP_CONTENT_TYPE] = "HDCP Content Type",
|
|
[IGT_CONNECTOR_LINK_STATUS] = "link-status",
|
|
};
|
|
|
|
/*
|
|
* Retrieve all the properies specified in props_name and store them into
|
|
* plane->props.
|
|
*/
|
|
static void
|
|
igt_fill_plane_props(igt_display_t *display, igt_plane_t *plane,
|
|
int num_props, const char * const prop_names[])
|
|
{
|
|
drmModeObjectPropertiesPtr props;
|
|
int i, j, fd;
|
|
|
|
fd = display->drm_fd;
|
|
|
|
props = drmModeObjectGetProperties(fd, plane->drm_plane->plane_id, DRM_MODE_OBJECT_PLANE);
|
|
igt_assert(props);
|
|
|
|
for (i = 0; i < props->count_props; i++) {
|
|
drmModePropertyPtr prop =
|
|
drmModeGetProperty(fd, props->props[i]);
|
|
|
|
for (j = 0; j < num_props; j++) {
|
|
if (strcmp(prop->name, prop_names[j]) != 0)
|
|
continue;
|
|
|
|
plane->props[j] = props->props[i];
|
|
break;
|
|
}
|
|
|
|
drmModeFreeProperty(prop);
|
|
}
|
|
|
|
drmModeFreeObjectProperties(props);
|
|
}
|
|
|
|
/*
|
|
* Retrieve all the properies specified in props_name and store them into
|
|
* config->atomic_props_crtc and config->atomic_props_connector.
|
|
*/
|
|
static void
|
|
igt_atomic_fill_connector_props(igt_display_t *display, igt_output_t *output,
|
|
int num_connector_props, const char * const conn_prop_names[])
|
|
{
|
|
drmModeObjectPropertiesPtr props;
|
|
int i, j, fd;
|
|
|
|
fd = display->drm_fd;
|
|
|
|
props = drmModeObjectGetProperties(fd, output->config.connector->connector_id, DRM_MODE_OBJECT_CONNECTOR);
|
|
igt_assert(props);
|
|
|
|
for (i = 0; i < props->count_props; i++) {
|
|
drmModePropertyPtr prop =
|
|
drmModeGetProperty(fd, props->props[i]);
|
|
|
|
for (j = 0; j < num_connector_props; j++) {
|
|
if (strcmp(prop->name, conn_prop_names[j]) != 0)
|
|
continue;
|
|
|
|
output->props[j] = props->props[i];
|
|
break;
|
|
}
|
|
|
|
drmModeFreeProperty(prop);
|
|
}
|
|
|
|
drmModeFreeObjectProperties(props);
|
|
}
|
|
|
|
static void
|
|
igt_fill_pipe_props(igt_display_t *display, igt_pipe_t *pipe,
|
|
int num_crtc_props, const char * const crtc_prop_names[])
|
|
{
|
|
drmModeObjectPropertiesPtr props;
|
|
int i, j, fd;
|
|
|
|
fd = display->drm_fd;
|
|
|
|
props = drmModeObjectGetProperties(fd, pipe->crtc_id, DRM_MODE_OBJECT_CRTC);
|
|
igt_assert(props);
|
|
|
|
for (i = 0; i < props->count_props; i++) {
|
|
drmModePropertyPtr prop =
|
|
drmModeGetProperty(fd, props->props[i]);
|
|
|
|
for (j = 0; j < num_crtc_props; j++) {
|
|
if (strcmp(prop->name, crtc_prop_names[j]) != 0)
|
|
continue;
|
|
|
|
pipe->props[j] = props->props[i];
|
|
break;
|
|
}
|
|
|
|
drmModeFreeProperty(prop);
|
|
}
|
|
|
|
drmModeFreeObjectProperties(props);
|
|
}
|
|
|
|
/**
|
|
* kmstest_pipe_name:
|
|
* @pipe: display pipe
|
|
*
|
|
* Returns: String representing @pipe, e.g. "A".
|
|
*/
|
|
const char *kmstest_pipe_name(enum pipe pipe)
|
|
{
|
|
static const char str[] = "A\0B\0C\0D\0E\0F";
|
|
|
|
_Static_assert(sizeof(str) == IGT_MAX_PIPES * 2,
|
|
"Missing pipe name");
|
|
|
|
if (pipe == PIPE_NONE)
|
|
return "None";
|
|
|
|
if (pipe >= IGT_MAX_PIPES)
|
|
return "invalid";
|
|
|
|
return str + (pipe * 2);
|
|
}
|
|
|
|
/**
|
|
* kmstest_pipe_to_index:
|
|
*@pipe: display pipe in string format
|
|
*
|
|
* Returns: index to corresponding pipe
|
|
*/
|
|
int kmstest_pipe_to_index(char pipe)
|
|
{
|
|
int r = pipe - 'A';
|
|
|
|
if (r < 0 || r >= IGT_MAX_PIPES)
|
|
return -EINVAL;
|
|
|
|
return r;
|
|
}
|
|
|
|
/**
|
|
* kmstest_plane_type_name:
|
|
* @plane_type: display plane type
|
|
*
|
|
* Returns: String representing @plane_type, e.g. "overlay".
|
|
*/
|
|
const char *kmstest_plane_type_name(int plane_type)
|
|
{
|
|
static const char * const names[] = {
|
|
[DRM_PLANE_TYPE_OVERLAY] = "overlay",
|
|
[DRM_PLANE_TYPE_PRIMARY] = "primary",
|
|
[DRM_PLANE_TYPE_CURSOR] = "cursor",
|
|
};
|
|
|
|
igt_assert(plane_type < ARRAY_SIZE(names) && names[plane_type]);
|
|
|
|
return names[plane_type];
|
|
}
|
|
|
|
struct type_name {
|
|
int type;
|
|
const char *name;
|
|
};
|
|
|
|
static const char *find_type_name(const struct type_name *names, int type)
|
|
{
|
|
for (; names->name; names++) {
|
|
if (names->type == type)
|
|
return names->name;
|
|
}
|
|
|
|
return "(invalid)";
|
|
}
|
|
|
|
static const struct type_name encoder_type_names[] = {
|
|
{ DRM_MODE_ENCODER_NONE, "none" },
|
|
{ DRM_MODE_ENCODER_DAC, "DAC" },
|
|
{ DRM_MODE_ENCODER_TMDS, "TMDS" },
|
|
{ DRM_MODE_ENCODER_LVDS, "LVDS" },
|
|
{ DRM_MODE_ENCODER_TVDAC, "TVDAC" },
|
|
{ DRM_MODE_ENCODER_VIRTUAL, "Virtual" },
|
|
{ DRM_MODE_ENCODER_DSI, "DSI" },
|
|
{ DRM_MODE_ENCODER_DPMST, "DP MST" },
|
|
{}
|
|
};
|
|
|
|
/**
|
|
* kmstest_encoder_type_str:
|
|
* @type: DRM_MODE_ENCODER_* enumeration value
|
|
*
|
|
* Returns: A string representing the drm encoder @type.
|
|
*/
|
|
const char *kmstest_encoder_type_str(int type)
|
|
{
|
|
return find_type_name(encoder_type_names, type);
|
|
}
|
|
|
|
static const struct type_name connector_status_names[] = {
|
|
{ DRM_MODE_CONNECTED, "connected" },
|
|
{ DRM_MODE_DISCONNECTED, "disconnected" },
|
|
{ DRM_MODE_UNKNOWNCONNECTION, "unknown" },
|
|
{}
|
|
};
|
|
|
|
/**
|
|
* kmstest_connector_status_str:
|
|
* @status: DRM_MODE_* connector status value
|
|
*
|
|
* Returns: A string representing the drm connector status @status.
|
|
*/
|
|
const char *kmstest_connector_status_str(int status)
|
|
{
|
|
return find_type_name(connector_status_names, status);
|
|
}
|
|
|
|
static const struct type_name connector_type_names[] = {
|
|
{ DRM_MODE_CONNECTOR_Unknown, "Unknown" },
|
|
{ DRM_MODE_CONNECTOR_VGA, "VGA" },
|
|
{ DRM_MODE_CONNECTOR_DVII, "DVI-I" },
|
|
{ DRM_MODE_CONNECTOR_DVID, "DVI-D" },
|
|
{ DRM_MODE_CONNECTOR_DVIA, "DVI-A" },
|
|
{ DRM_MODE_CONNECTOR_Composite, "Composite" },
|
|
{ DRM_MODE_CONNECTOR_SVIDEO, "SVIDEO" },
|
|
{ DRM_MODE_CONNECTOR_LVDS, "LVDS" },
|
|
{ DRM_MODE_CONNECTOR_Component, "Component" },
|
|
{ DRM_MODE_CONNECTOR_9PinDIN, "DIN" },
|
|
{ DRM_MODE_CONNECTOR_DisplayPort, "DP" },
|
|
{ DRM_MODE_CONNECTOR_HDMIA, "HDMI-A" },
|
|
{ DRM_MODE_CONNECTOR_HDMIB, "HDMI-B" },
|
|
{ DRM_MODE_CONNECTOR_TV, "TV" },
|
|
{ DRM_MODE_CONNECTOR_eDP, "eDP" },
|
|
{ DRM_MODE_CONNECTOR_VIRTUAL, "Virtual" },
|
|
{ DRM_MODE_CONNECTOR_DSI, "DSI" },
|
|
{ DRM_MODE_CONNECTOR_DPI, "DPI" },
|
|
{}
|
|
};
|
|
|
|
/**
|
|
* kmstest_connector_type_str:
|
|
* @type: DRM_MODE_CONNECTOR_* enumeration value
|
|
*
|
|
* Returns: A string representing the drm connector @type.
|
|
*/
|
|
const char *kmstest_connector_type_str(int type)
|
|
{
|
|
return find_type_name(connector_type_names, type);
|
|
}
|
|
|
|
static const char *mode_stereo_name(const drmModeModeInfo *mode)
|
|
{
|
|
switch (mode->flags & DRM_MODE_FLAG_3D_MASK) {
|
|
case DRM_MODE_FLAG_3D_FRAME_PACKING:
|
|
return "FP";
|
|
case DRM_MODE_FLAG_3D_FIELD_ALTERNATIVE:
|
|
return "FA";
|
|
case DRM_MODE_FLAG_3D_LINE_ALTERNATIVE:
|
|
return "LA";
|
|
case DRM_MODE_FLAG_3D_SIDE_BY_SIDE_FULL:
|
|
return "SBSF";
|
|
case DRM_MODE_FLAG_3D_L_DEPTH:
|
|
return "LD";
|
|
case DRM_MODE_FLAG_3D_L_DEPTH_GFX_GFX_DEPTH:
|
|
return "LDGFX";
|
|
case DRM_MODE_FLAG_3D_TOP_AND_BOTTOM:
|
|
return "TB";
|
|
case DRM_MODE_FLAG_3D_SIDE_BY_SIDE_HALF:
|
|
return "SBSH";
|
|
default:
|
|
return NULL;
|
|
}
|
|
}
|
|
|
|
static const char *mode_picture_aspect_name(const drmModeModeInfo *mode)
|
|
{
|
|
switch (mode->flags & DRM_MODE_FLAG_PIC_AR_MASK) {
|
|
case DRM_MODE_FLAG_PIC_AR_NONE:
|
|
return NULL;
|
|
case DRM_MODE_FLAG_PIC_AR_4_3:
|
|
return "4:3";
|
|
case DRM_MODE_FLAG_PIC_AR_16_9:
|
|
return "16:9";
|
|
case DRM_MODE_FLAG_PIC_AR_64_27:
|
|
return "64:27";
|
|
case DRM_MODE_FLAG_PIC_AR_256_135:
|
|
return "256:135";
|
|
default:
|
|
return "invalid";
|
|
}
|
|
}
|
|
|
|
/**
|
|
* kmstest_dump_mode:
|
|
* @mode: libdrm mode structure
|
|
*
|
|
* Prints @mode to stdout in a human-readable form.
|
|
*/
|
|
void kmstest_dump_mode(drmModeModeInfo *mode)
|
|
{
|
|
const char *stereo = mode_stereo_name(mode);
|
|
const char *aspect = mode_picture_aspect_name(mode);
|
|
|
|
igt_info(" %s %d %d %d %d %d %d %d %d %d 0x%x 0x%x %d%s%s%s%s%s%s\n",
|
|
mode->name, mode->vrefresh,
|
|
mode->hdisplay, mode->hsync_start,
|
|
mode->hsync_end, mode->htotal,
|
|
mode->vdisplay, mode->vsync_start,
|
|
mode->vsync_end, mode->vtotal,
|
|
mode->flags, mode->type, mode->clock,
|
|
stereo ? " (3D:" : "",
|
|
stereo ? stereo : "", stereo ? ")" : "",
|
|
aspect ? " (PAR:" : "",
|
|
aspect ? aspect : "", aspect ? ")" : "");
|
|
}
|
|
|
|
/**
|
|
* kmstest_get_pipe_from_crtc_id:
|
|
* @fd: DRM fd
|
|
* @crtc_id: DRM CRTC id
|
|
*
|
|
* Returns: The crtc index for the given DRM CRTC ID @crtc_id. The crtc index
|
|
* is the equivalent of the pipe id. This value maps directly to an enum pipe
|
|
* value used in other helper functions. Returns 0 if the index could not be
|
|
* determined.
|
|
*/
|
|
|
|
int kmstest_get_pipe_from_crtc_id(int fd, int crtc_id)
|
|
{
|
|
drmModeRes *res;
|
|
drmModeCrtc *crtc;
|
|
int i, cur_id;
|
|
|
|
res = drmModeGetResources(fd);
|
|
igt_assert(res);
|
|
|
|
for (i = 0; i < res->count_crtcs; i++) {
|
|
crtc = drmModeGetCrtc(fd, res->crtcs[i]);
|
|
igt_assert(crtc);
|
|
cur_id = crtc->crtc_id;
|
|
drmModeFreeCrtc(crtc);
|
|
if (cur_id == crtc_id)
|
|
break;
|
|
}
|
|
|
|
igt_assert(i < res->count_crtcs);
|
|
|
|
drmModeFreeResources(res);
|
|
|
|
return i;
|
|
}
|
|
|
|
/**
|
|
* kmstest_find_crtc_for_connector:
|
|
* @fd: DRM fd
|
|
* @res: libdrm resources pointer
|
|
* @connector: libdrm connector pointer
|
|
* @crtc_blacklist_idx_mask: a mask of CRTC indexes that we can't return
|
|
*
|
|
* Returns: the CRTC ID for a CRTC that fits the connector, otherwise it asserts
|
|
* false and never returns. The blacklist mask can be used in case you have
|
|
* CRTCs that are already in use by other connectors.
|
|
*/
|
|
uint32_t kmstest_find_crtc_for_connector(int fd, drmModeRes *res,
|
|
drmModeConnector *connector,
|
|
uint32_t crtc_blacklist_idx_mask)
|
|
{
|
|
drmModeEncoder *e;
|
|
uint32_t possible_crtcs;
|
|
int i, j;
|
|
|
|
for (i = 0; i < connector->count_encoders; i++) {
|
|
e = drmModeGetEncoder(fd, connector->encoders[i]);
|
|
possible_crtcs = e->possible_crtcs & ~crtc_blacklist_idx_mask;
|
|
drmModeFreeEncoder(e);
|
|
|
|
for (j = 0; possible_crtcs >> j; j++)
|
|
if (possible_crtcs & (1 << j))
|
|
return res->crtcs[j];
|
|
}
|
|
|
|
igt_assert(false);
|
|
}
|
|
|
|
/**
|
|
* kmstest_dumb_create:
|
|
* @fd: open drm file descriptor
|
|
* @width: width of the buffer in pixels
|
|
* @height: height of the buffer in pixels
|
|
* @bpp: bytes per pixel of the buffer
|
|
* @stride: Pointer which receives the dumb bo's stride, can be NULL.
|
|
* @size: Pointer which receives the dumb bo's size, can be NULL.
|
|
*
|
|
* This wraps the CREATE_DUMB ioctl, which allocates a new dumb buffer object
|
|
* for the specified dimensions.
|
|
*
|
|
* Returns: The file-private handle of the created buffer object
|
|
*/
|
|
uint32_t kmstest_dumb_create(int fd, int width, int height, int bpp,
|
|
unsigned *stride, uint64_t *size)
|
|
{
|
|
struct drm_mode_create_dumb create;
|
|
|
|
memset(&create, 0, sizeof(create));
|
|
create.width = width;
|
|
create.height = height;
|
|
create.bpp = bpp;
|
|
|
|
create.handle = 0;
|
|
do_ioctl(fd, DRM_IOCTL_MODE_CREATE_DUMB, &create);
|
|
igt_assert(create.handle);
|
|
igt_assert(create.size >= (uint64_t) width * height * bpp / 8);
|
|
|
|
if (stride)
|
|
*stride = create.pitch;
|
|
|
|
if (size)
|
|
*size = create.size;
|
|
|
|
return create.handle;
|
|
}
|
|
|
|
/**
|
|
* kmstest_dumb_map_buffer:
|
|
* @fd: Opened drm file descriptor
|
|
* @handle: Offset in the file referred to by fd
|
|
* @size: Length of the mapping, must be greater than 0
|
|
* @prot: Describes the memory protection of the mapping
|
|
* Returns: A pointer representing the start of the virtual mapping
|
|
*/
|
|
void *kmstest_dumb_map_buffer(int fd, uint32_t handle, uint64_t size,
|
|
unsigned prot)
|
|
{
|
|
struct drm_mode_map_dumb arg = {};
|
|
void *ptr;
|
|
|
|
arg.handle = handle;
|
|
|
|
do_ioctl(fd, DRM_IOCTL_MODE_MAP_DUMB, &arg);
|
|
|
|
ptr = mmap(NULL, size, prot, MAP_SHARED, fd, arg.offset);
|
|
igt_assert(ptr != MAP_FAILED);
|
|
|
|
return ptr;
|
|
}
|
|
|
|
static int __kmstest_dumb_destroy(int fd, uint32_t handle)
|
|
{
|
|
struct drm_mode_destroy_dumb arg = { handle };
|
|
int err = 0;
|
|
|
|
if (drmIoctl(fd, DRM_IOCTL_MODE_DESTROY_DUMB, &arg))
|
|
err = -errno;
|
|
|
|
errno = 0;
|
|
return err;
|
|
}
|
|
|
|
/**
|
|
* kmstest_dumb_destroy:
|
|
* @fd: Opened drm file descriptor
|
|
* @handle: Offset in the file referred to by fd
|
|
*/
|
|
void kmstest_dumb_destroy(int fd, uint32_t handle)
|
|
{
|
|
igt_assert_eq(__kmstest_dumb_destroy(fd, handle), 0);
|
|
}
|
|
|
|
#if !defined(ANDROID)
|
|
/*
|
|
* Returns: the previous mode, or KD_GRAPHICS if no /dev/tty0 was
|
|
* found and nothing was done.
|
|
*/
|
|
static signed long set_vt_mode(unsigned long mode)
|
|
{
|
|
int fd;
|
|
unsigned long prev_mode;
|
|
static const char TTY0[] = "/dev/tty0";
|
|
|
|
if (access(TTY0, F_OK)) {
|
|
/* errno message should be "No such file". Do not
|
|
hardcode but ask strerror() in the very unlikely
|
|
case something else happened. */
|
|
igt_debug("VT: %s: %s, cannot change its mode\n",
|
|
TTY0, strerror(errno));
|
|
return KD_GRAPHICS;
|
|
}
|
|
|
|
fd = open(TTY0, O_RDONLY);
|
|
if (fd < 0)
|
|
return -errno;
|
|
|
|
prev_mode = 0;
|
|
if (drmIoctl(fd, KDGETMODE, &prev_mode))
|
|
goto err;
|
|
if (drmIoctl(fd, KDSETMODE, (void *)mode))
|
|
goto err;
|
|
|
|
close(fd);
|
|
|
|
return prev_mode;
|
|
err:
|
|
close(fd);
|
|
|
|
return -errno;
|
|
}
|
|
#endif
|
|
|
|
static unsigned long orig_vt_mode = -1UL;
|
|
|
|
/**
|
|
* kmstest_restore_vt_mode:
|
|
*
|
|
* Restore the VT mode in use before #kmstest_set_vt_graphics_mode was called.
|
|
*/
|
|
void kmstest_restore_vt_mode(void)
|
|
{
|
|
#if !defined(ANDROID)
|
|
long ret;
|
|
|
|
if (orig_vt_mode != -1UL) {
|
|
ret = set_vt_mode(orig_vt_mode);
|
|
|
|
igt_assert(ret >= 0);
|
|
igt_debug("VT: original mode 0x%lx restored\n", orig_vt_mode);
|
|
orig_vt_mode = -1UL;
|
|
}
|
|
#endif
|
|
}
|
|
|
|
/**
|
|
* kmstest_set_vt_graphics_mode:
|
|
*
|
|
* Sets the controlling VT (if available) into graphics/raw mode and installs
|
|
* an igt exit handler to set the VT back to text mode on exit. Use
|
|
* #kmstest_restore_vt_mode to restore the previous VT mode manually.
|
|
*
|
|
* All kms tests must call this function to make sure that the fbcon doesn't
|
|
* interfere by e.g. blanking the screen.
|
|
*/
|
|
void kmstest_set_vt_graphics_mode(void)
|
|
{
|
|
#if !defined(ANDROID)
|
|
long ret;
|
|
|
|
igt_install_exit_handler((igt_exit_handler_t) kmstest_restore_vt_mode);
|
|
|
|
ret = set_vt_mode(KD_GRAPHICS);
|
|
|
|
igt_assert(ret >= 0);
|
|
orig_vt_mode = ret;
|
|
|
|
igt_debug("VT: graphics mode set (mode was 0x%lx)\n", ret);
|
|
#endif
|
|
}
|
|
|
|
|
|
static void reset_connectors_at_exit(int sig)
|
|
{
|
|
igt_reset_connectors();
|
|
}
|
|
|
|
/**
|
|
* kmstest_force_connector:
|
|
* @fd: drm file descriptor
|
|
* @connector: connector
|
|
* @state: state to force on @connector
|
|
*
|
|
* Force the specified state on the specified connector.
|
|
*
|
|
* Returns: true on success
|
|
*/
|
|
bool kmstest_force_connector(int drm_fd, drmModeConnector *connector,
|
|
enum kmstest_force_connector_state state)
|
|
{
|
|
char *path, **tmp;
|
|
const char *value;
|
|
drmModeConnector *temp;
|
|
uint32_t devid;
|
|
int len, dir, idx;
|
|
|
|
#if defined(USE_INTEL)
|
|
if (is_i915_device(drm_fd)) {
|
|
devid = intel_get_drm_devid(drm_fd);
|
|
|
|
/*
|
|
* forcing hdmi or dp connectors on HSW and BDW doesn't
|
|
* currently work, so fail early to allow the test to skip if
|
|
* required
|
|
*/
|
|
if ((connector->connector_type == DRM_MODE_CONNECTOR_HDMIA ||
|
|
connector->connector_type == DRM_MODE_CONNECTOR_HDMIB ||
|
|
connector->connector_type == DRM_MODE_CONNECTOR_DisplayPort)
|
|
&& (IS_HASWELL(devid) || IS_BROADWELL(devid)))
|
|
return false;
|
|
}
|
|
#endif
|
|
|
|
switch (state) {
|
|
case FORCE_CONNECTOR_ON:
|
|
value = "on";
|
|
break;
|
|
case FORCE_CONNECTOR_DIGITAL:
|
|
value = "on-digital";
|
|
break;
|
|
case FORCE_CONNECTOR_OFF:
|
|
value = "off";
|
|
break;
|
|
|
|
default:
|
|
case FORCE_CONNECTOR_UNSPECIFIED:
|
|
value = "detect";
|
|
break;
|
|
}
|
|
|
|
dir = igt_sysfs_open(drm_fd);
|
|
if (dir < 0)
|
|
return false;
|
|
|
|
idx = igt_device_get_card_index(drm_fd);
|
|
if (idx < 0 || idx > 63)
|
|
return false;
|
|
|
|
if (asprintf(&path, "card%d-%s-%d/status",
|
|
idx,
|
|
kmstest_connector_type_str(connector->connector_type),
|
|
connector->connector_type_id) < 0) {
|
|
close(dir);
|
|
return false;
|
|
}
|
|
|
|
if (!igt_sysfs_set(dir, path, value)) {
|
|
close(dir);
|
|
return false;
|
|
}
|
|
|
|
for (len = 0, tmp = forced_connectors; *tmp; tmp++) {
|
|
/* check the connector is not already present */
|
|
if (strcmp(*tmp, path) == 0) {
|
|
len = -1;
|
|
break;
|
|
}
|
|
len++;
|
|
}
|
|
|
|
if (len != -1 && len < MAX_CONNECTORS) {
|
|
forced_connectors[len] = path;
|
|
forced_connectors_device[len] = dir;
|
|
}
|
|
|
|
if (len >= MAX_CONNECTORS)
|
|
igt_warn("Connector limit reached, %s will not be reset\n",
|
|
path);
|
|
|
|
igt_debug("Connector %s is now forced %s\n", path, value);
|
|
igt_debug("Current forced connectors:\n");
|
|
tmp = forced_connectors;
|
|
while (*tmp) {
|
|
igt_debug("\t%s\n", *tmp);
|
|
tmp++;
|
|
}
|
|
|
|
igt_install_exit_handler(reset_connectors_at_exit);
|
|
|
|
/* To allow callers to always use GetConnectorCurrent we need to force a
|
|
* redetection here. */
|
|
temp = drmModeGetConnector(drm_fd, connector->connector_id);
|
|
drmModeFreeConnector(temp);
|
|
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* kmstest_force_edid:
|
|
* @drm_fd: drm file descriptor
|
|
* @connector: connector to set @edid on
|
|
* @edid: An EDID data block
|
|
*
|
|
* Set the EDID data on @connector to @edid. See also #igt_kms_get_base_edid.
|
|
*
|
|
* If @edid is NULL, the forced EDID will be removed.
|
|
*/
|
|
void kmstest_force_edid(int drm_fd, drmModeConnector *connector,
|
|
const struct edid *edid)
|
|
{
|
|
char *path;
|
|
int debugfs_fd, ret;
|
|
drmModeConnector *temp;
|
|
|
|
igt_assert_neq(asprintf(&path, "%s-%d/edid_override", kmstest_connector_type_str(connector->connector_type), connector->connector_type_id),
|
|
-1);
|
|
debugfs_fd = igt_debugfs_open(drm_fd, path, O_WRONLY | O_TRUNC);
|
|
free(path);
|
|
|
|
igt_require(debugfs_fd != -1);
|
|
|
|
if (edid == NULL)
|
|
ret = write(debugfs_fd, "reset", 5);
|
|
else
|
|
ret = write(debugfs_fd, edid,
|
|
edid_get_size(edid));
|
|
close(debugfs_fd);
|
|
|
|
/* To allow callers to always use GetConnectorCurrent we need to force a
|
|
* redetection here. */
|
|
temp = drmModeGetConnector(drm_fd, connector->connector_id);
|
|
drmModeFreeConnector(temp);
|
|
|
|
igt_assert(ret != -1);
|
|
}
|
|
|
|
/**
|
|
* kmstest_get_connector_default_mode:
|
|
* @drm_fd: DRM fd
|
|
* @connector: libdrm connector
|
|
* @mode: libdrm mode
|
|
*
|
|
* Retrieves the default mode for @connector and stores it in @mode.
|
|
*
|
|
* Returns: true on success, false on failure
|
|
*/
|
|
bool kmstest_get_connector_default_mode(int drm_fd, drmModeConnector *connector,
|
|
drmModeModeInfo *mode)
|
|
{
|
|
int i;
|
|
|
|
if (!connector->count_modes) {
|
|
igt_warn("no modes for connector %d\n",
|
|
connector->connector_id);
|
|
return false;
|
|
}
|
|
|
|
for (i = 0; i < connector->count_modes; i++) {
|
|
if (i == 0 ||
|
|
connector->modes[i].type & DRM_MODE_TYPE_PREFERRED) {
|
|
*mode = connector->modes[i];
|
|
if (mode->type & DRM_MODE_TYPE_PREFERRED)
|
|
break;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
static void
|
|
_kmstest_connector_config_crtc_mask(int drm_fd,
|
|
drmModeConnector *connector,
|
|
struct kmstest_connector_config *config)
|
|
{
|
|
int i;
|
|
|
|
config->valid_crtc_idx_mask = 0;
|
|
|
|
/* Now get a compatible encoder */
|
|
for (i = 0; i < connector->count_encoders; i++) {
|
|
drmModeEncoder *encoder = drmModeGetEncoder(drm_fd,
|
|
connector->encoders[i]);
|
|
|
|
if (!encoder) {
|
|
igt_warn("could not get encoder %d: %s\n",
|
|
connector->encoders[i],
|
|
strerror(errno));
|
|
|
|
continue;
|
|
}
|
|
|
|
config->valid_crtc_idx_mask |= encoder->possible_crtcs;
|
|
drmModeFreeEncoder(encoder);
|
|
}
|
|
}
|
|
|
|
static drmModeEncoder *
|
|
_kmstest_connector_config_find_encoder(int drm_fd, drmModeConnector *connector, enum pipe pipe)
|
|
{
|
|
int i;
|
|
|
|
for (i = 0; i < connector->count_encoders; i++) {
|
|
drmModeEncoder *encoder = drmModeGetEncoder(drm_fd, connector->encoders[i]);
|
|
|
|
if (!encoder) {
|
|
igt_warn("could not get encoder %d: %s\n",
|
|
connector->encoders[i],
|
|
strerror(errno));
|
|
|
|
continue;
|
|
}
|
|
|
|
if (encoder->possible_crtcs & (1 << pipe))
|
|
return encoder;
|
|
|
|
drmModeFreeEncoder(encoder);
|
|
}
|
|
|
|
igt_assert(false);
|
|
return NULL;
|
|
}
|
|
|
|
/**
|
|
* _kmstest_connector_config:
|
|
* @drm_fd: DRM fd
|
|
* @connector_id: DRM connector id
|
|
* @crtc_idx_mask: mask of allowed DRM CRTC indices
|
|
* @config: structure filled with the possible configuration
|
|
* @probe: whether to fully re-probe mode list or not
|
|
*
|
|
* This tries to find a suitable configuration for the given connector and CRTC
|
|
* constraint and fills it into @config.
|
|
*/
|
|
static bool _kmstest_connector_config(int drm_fd, uint32_t connector_id,
|
|
unsigned long crtc_idx_mask,
|
|
struct kmstest_connector_config *config,
|
|
bool probe)
|
|
{
|
|
drmModeRes *resources;
|
|
drmModeConnector *connector;
|
|
|
|
config->pipe = PIPE_NONE;
|
|
|
|
resources = drmModeGetResources(drm_fd);
|
|
if (!resources) {
|
|
igt_warn("drmModeGetResources failed");
|
|
goto err1;
|
|
}
|
|
|
|
/* First, find the connector & mode */
|
|
if (probe)
|
|
connector = drmModeGetConnector(drm_fd, connector_id);
|
|
else
|
|
connector = drmModeGetConnectorCurrent(drm_fd, connector_id);
|
|
|
|
if (!connector)
|
|
goto err2;
|
|
|
|
if (connector->connector_id != connector_id) {
|
|
igt_warn("connector id doesn't match (%d != %d)\n",
|
|
connector->connector_id, connector_id);
|
|
goto err3;
|
|
}
|
|
|
|
/*
|
|
* Find given CRTC if crtc_id != 0 or else the first CRTC not in use.
|
|
* In both cases find the first compatible encoder and skip the CRTC
|
|
* if there is non such.
|
|
*/
|
|
_kmstest_connector_config_crtc_mask(drm_fd, connector, config);
|
|
|
|
if (!connector->count_modes)
|
|
memset(&config->default_mode, 0, sizeof(config->default_mode));
|
|
else if (!kmstest_get_connector_default_mode(drm_fd, connector,
|
|
&config->default_mode))
|
|
goto err3;
|
|
|
|
config->connector = connector;
|
|
|
|
crtc_idx_mask &= config->valid_crtc_idx_mask;
|
|
if (!crtc_idx_mask)
|
|
/* Keep config->connector */
|
|
goto err2;
|
|
|
|
config->pipe = ffs(crtc_idx_mask) - 1;
|
|
|
|
config->encoder = _kmstest_connector_config_find_encoder(drm_fd, connector, config->pipe);
|
|
config->crtc = drmModeGetCrtc(drm_fd, resources->crtcs[config->pipe]);
|
|
|
|
if (connector->connection != DRM_MODE_CONNECTED)
|
|
goto err2;
|
|
|
|
if (!connector->count_modes) {
|
|
if (probe)
|
|
igt_warn("connector %d/%s-%d has no modes\n", connector_id,
|
|
kmstest_connector_type_str(connector->connector_type),
|
|
connector->connector_type_id);
|
|
goto err2;
|
|
}
|
|
|
|
drmModeFreeResources(resources);
|
|
return true;
|
|
err3:
|
|
drmModeFreeConnector(connector);
|
|
err2:
|
|
drmModeFreeResources(resources);
|
|
err1:
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* kmstest_get_connector_config:
|
|
* @drm_fd: DRM fd
|
|
* @connector_id: DRM connector id
|
|
* @crtc_idx_mask: mask of allowed DRM CRTC indices
|
|
* @config: structure filled with the possible configuration
|
|
*
|
|
* This tries to find a suitable configuration for the given connector and CRTC
|
|
* constraint and fills it into @config.
|
|
*/
|
|
bool kmstest_get_connector_config(int drm_fd, uint32_t connector_id,
|
|
unsigned long crtc_idx_mask,
|
|
struct kmstest_connector_config *config)
|
|
{
|
|
return _kmstest_connector_config(drm_fd, connector_id, crtc_idx_mask,
|
|
config, 0);
|
|
}
|
|
|
|
/**
|
|
* kmstest_probe_connector_config:
|
|
* @drm_fd: DRM fd
|
|
* @connector_id: DRM connector id
|
|
* @crtc_idx_mask: mask of allowed DRM CRTC indices
|
|
* @config: structure filled with the possible configuration
|
|
*
|
|
* This tries to find a suitable configuration for the given connector and CRTC
|
|
* constraint and fills it into @config, fully probing the connector in the
|
|
* process.
|
|
*/
|
|
bool kmstest_probe_connector_config(int drm_fd, uint32_t connector_id,
|
|
unsigned long crtc_idx_mask,
|
|
struct kmstest_connector_config *config)
|
|
{
|
|
return _kmstest_connector_config(drm_fd, connector_id, crtc_idx_mask,
|
|
config, 1);
|
|
}
|
|
|
|
/**
|
|
* kmstest_free_connector_config:
|
|
* @config: connector configuration structure
|
|
*
|
|
* Free any resources in @config allocated in kmstest_get_connector_config().
|
|
*/
|
|
void kmstest_free_connector_config(struct kmstest_connector_config *config)
|
|
{
|
|
drmModeFreeCrtc(config->crtc);
|
|
config->crtc = NULL;
|
|
|
|
drmModeFreeEncoder(config->encoder);
|
|
config->encoder = NULL;
|
|
|
|
drmModeFreeConnector(config->connector);
|
|
config->connector = NULL;
|
|
}
|
|
|
|
/**
|
|
* kmstest_set_connector_dpms:
|
|
* @fd: DRM fd
|
|
* @connector: libdrm connector
|
|
* @mode: DRM DPMS value
|
|
*
|
|
* This function sets the DPMS setting of @connector to @mode.
|
|
*/
|
|
void kmstest_set_connector_dpms(int fd, drmModeConnector *connector, int mode)
|
|
{
|
|
int i, dpms = 0;
|
|
bool found_it = false;
|
|
|
|
for (i = 0; i < connector->count_props; i++) {
|
|
struct drm_mode_get_property prop = {
|
|
.prop_id = connector->props[i],
|
|
};
|
|
|
|
if (drmIoctl(fd, DRM_IOCTL_MODE_GETPROPERTY, &prop))
|
|
continue;
|
|
|
|
if (strcmp(prop.name, "DPMS"))
|
|
continue;
|
|
|
|
dpms = prop.prop_id;
|
|
found_it = true;
|
|
break;
|
|
}
|
|
igt_assert_f(found_it, "DPMS property not found on %d\n",
|
|
connector->connector_id);
|
|
|
|
igt_assert(drmModeConnectorSetProperty(fd, connector->connector_id,
|
|
dpms, mode) == 0);
|
|
}
|
|
|
|
/**
|
|
* kmstest_get_property:
|
|
* @drm_fd: drm file descriptor
|
|
* @object_id: object whose properties we're going to get
|
|
* @object_type: type of obj_id (DRM_MODE_OBJECT_*)
|
|
* @name: name of the property we're going to get
|
|
* @prop_id: if not NULL, returns the property id
|
|
* @value: if not NULL, returns the property value
|
|
* @prop: if not NULL, returns the property, and the caller will have to free
|
|
* it manually.
|
|
*
|
|
* Finds a property with the given name on the given object.
|
|
*
|
|
* Returns: true in case we found something.
|
|
*/
|
|
bool
|
|
kmstest_get_property(int drm_fd, uint32_t object_id, uint32_t object_type,
|
|
const char *name, uint32_t *prop_id /* out */,
|
|
uint64_t *value /* out */,
|
|
drmModePropertyPtr *prop /* out */)
|
|
{
|
|
drmModeObjectPropertiesPtr proplist;
|
|
drmModePropertyPtr _prop;
|
|
bool found = false;
|
|
int i;
|
|
|
|
proplist = drmModeObjectGetProperties(drm_fd, object_id, object_type);
|
|
for (i = 0; i < proplist->count_props; i++) {
|
|
_prop = drmModeGetProperty(drm_fd, proplist->props[i]);
|
|
if (!_prop)
|
|
continue;
|
|
|
|
if (strcmp(_prop->name, name) == 0) {
|
|
found = true;
|
|
if (prop_id)
|
|
*prop_id = proplist->props[i];
|
|
if (value)
|
|
*value = proplist->prop_values[i];
|
|
if (prop)
|
|
*prop = _prop;
|
|
else
|
|
drmModeFreeProperty(_prop);
|
|
|
|
break;
|
|
}
|
|
drmModeFreeProperty(_prop);
|
|
}
|
|
|
|
drmModeFreeObjectProperties(proplist);
|
|
return found;
|
|
}
|
|
|
|
/**
|
|
* kmstest_unset_all_crtcs:
|
|
* @drm_fd: the DRM fd
|
|
* @resources: libdrm resources pointer
|
|
*
|
|
* Disables all the screens.
|
|
*/
|
|
void kmstest_unset_all_crtcs(int drm_fd, drmModeResPtr resources)
|
|
{
|
|
int i, rc;
|
|
|
|
for (i = 0; i < resources->count_crtcs; i++) {
|
|
rc = drmModeSetCrtc(drm_fd, resources->crtcs[i], 0, 0, 0, NULL,
|
|
0, NULL);
|
|
igt_assert(rc == 0);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* kmstest_get_crtc_idx:
|
|
* @res: the libdrm resources
|
|
* @crtc_id: the CRTC id
|
|
*
|
|
* Get the CRTC index based on its ID. This is useful since a few places of
|
|
* libdrm deal with CRTC masks.
|
|
*/
|
|
int kmstest_get_crtc_idx(drmModeRes *res, uint32_t crtc_id)
|
|
{
|
|
int i;
|
|
|
|
for (i = 0; i < res->count_crtcs; i++)
|
|
if (res->crtcs[i] == crtc_id)
|
|
return i;
|
|
|
|
igt_assert(false);
|
|
}
|
|
|
|
static inline uint32_t pipe_select(int pipe)
|
|
{
|
|
if (pipe > 1)
|
|
return pipe << DRM_VBLANK_HIGH_CRTC_SHIFT;
|
|
else if (pipe > 0)
|
|
return DRM_VBLANK_SECONDARY;
|
|
else
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* kmstest_get_vblank:
|
|
* @fd: Opened drm file descriptor
|
|
* @pipe: Display pipe
|
|
* @flags: Flags passed to drm_ioctl_wait_vblank
|
|
*
|
|
* Blocks or request a signal when a specified vblank event occurs
|
|
*
|
|
* Returns 0 on success or non-zero unsigned integer otherwise
|
|
*/
|
|
unsigned int kmstest_get_vblank(int fd, int pipe, unsigned int flags)
|
|
{
|
|
union drm_wait_vblank vbl;
|
|
|
|
memset(&vbl, 0, sizeof(vbl));
|
|
vbl.request.type = DRM_VBLANK_RELATIVE | pipe_select(pipe) | flags;
|
|
if (drmIoctl(fd, DRM_IOCTL_WAIT_VBLANK, &vbl))
|
|
return 0;
|
|
|
|
return vbl.reply.sequence;
|
|
}
|
|
|
|
/**
|
|
* kmstest_wait_for_pageflip:
|
|
* @fd: Opened drm file descriptor
|
|
*
|
|
* Blocks until pageflip is completed
|
|
*
|
|
*/
|
|
void kmstest_wait_for_pageflip(int fd)
|
|
{
|
|
drmEventContext evctx = { .version = 2 };
|
|
struct timeval timeout = { .tv_sec = 0, .tv_usec = 50000 };
|
|
fd_set fds;
|
|
int ret;
|
|
|
|
/* Wait for pageflip completion, then consume event on fd */
|
|
FD_ZERO(&fds);
|
|
FD_SET(fd, &fds);
|
|
do {
|
|
errno = 0;
|
|
ret = select(fd + 1, &fds, NULL, NULL, &timeout);
|
|
} while (ret < 0 && errno == EINTR);
|
|
|
|
igt_fail_on_f(ret == 0,
|
|
"Exceeded timeout (50ms) while waiting for a pageflip\n");
|
|
|
|
igt_assert_f(ret == 1,
|
|
"Waiting for pageflip failed with %d from select(drmfd)\n",
|
|
ret);
|
|
|
|
igt_assert(drmHandleEvent(fd, &evctx) == 0);
|
|
}
|
|
|
|
static void get_plane(char *str, int type, struct kmstest_plane *plane)
|
|
{
|
|
int ret;
|
|
char buf[256];
|
|
|
|
plane->type = type;
|
|
ret = sscanf(str + 12, "%d%*c %*s %[^n]s",
|
|
&plane->id,
|
|
buf);
|
|
igt_assert_eq(ret, 2);
|
|
|
|
ret = sscanf(buf + 9, "%4d%*c%4d%*c", &plane->pos_x, &plane->pos_y);
|
|
igt_assert_eq(ret, 2);
|
|
|
|
ret = sscanf(buf + 30, "%4d%*c%4d%*c", &plane->width, &plane->height);
|
|
igt_assert_eq(ret, 2);
|
|
}
|
|
|
|
static int parse_planes(FILE *fid, struct kmstest_plane *planes)
|
|
{
|
|
char tmp[256];
|
|
int n_planes;
|
|
|
|
n_planes = 0;
|
|
while (fgets(tmp, 256, fid) != NULL) {
|
|
if (strstr(tmp, "type=PRI") != NULL) {
|
|
if (planes) {
|
|
get_plane(tmp, DRM_PLANE_TYPE_PRIMARY, &planes[n_planes]);
|
|
planes[n_planes].index = n_planes;
|
|
}
|
|
n_planes++;
|
|
} else if (strstr(tmp, "type=OVL") != NULL) {
|
|
if (planes) {
|
|
get_plane(tmp, DRM_PLANE_TYPE_OVERLAY, &planes[n_planes]);
|
|
planes[n_planes].index = n_planes;
|
|
}
|
|
n_planes++;
|
|
} else if (strstr(tmp, "type=CUR") != NULL) {
|
|
if (planes) {
|
|
get_plane(tmp, DRM_PLANE_TYPE_CURSOR, &planes[n_planes]);
|
|
planes[n_planes].index = n_planes;
|
|
}
|
|
n_planes++;
|
|
break;
|
|
}
|
|
}
|
|
|
|
return n_planes;
|
|
}
|
|
|
|
static void parse_crtc(char *info, struct kmstest_crtc *crtc)
|
|
{
|
|
char buf[256];
|
|
int ret;
|
|
char pipe;
|
|
|
|
ret = sscanf(info + 4, "%d%*c %*s %c%*c %*s %s%*c",
|
|
&crtc->id, &pipe, buf);
|
|
igt_assert_eq(ret, 3);
|
|
|
|
crtc->pipe = kmstest_pipe_to_index(pipe);
|
|
igt_assert(crtc->pipe >= 0);
|
|
|
|
ret = sscanf(buf + 6, "%d%*c%d%*c",
|
|
&crtc->width, &crtc->height);
|
|
igt_assert_eq(ret, 2);
|
|
}
|
|
|
|
static void kmstest_get_crtc(int device, enum pipe pipe, struct kmstest_crtc *crtc)
|
|
{
|
|
char tmp[256];
|
|
FILE *file;
|
|
int ncrtc;
|
|
int line;
|
|
long int n;
|
|
int fd;
|
|
|
|
fd = igt_debugfs_open(device, "i915_display_info", O_RDONLY);
|
|
file = fdopen(fd, "r");
|
|
igt_skip_on(file == NULL);
|
|
|
|
ncrtc = 0;
|
|
line = 0;
|
|
while (fgets(tmp, 256, file) != NULL) {
|
|
if ((strstr(tmp, "CRTC") != NULL) && (line > 0)) {
|
|
if (strstr(tmp, "active=yes") != NULL) {
|
|
crtc->active = true;
|
|
parse_crtc(tmp, crtc);
|
|
|
|
n = ftell(file);
|
|
crtc->n_planes = parse_planes(file, NULL);
|
|
igt_assert_lt(0, crtc->n_planes);
|
|
crtc->planes = calloc(crtc->n_planes, sizeof(*crtc->planes));
|
|
igt_assert_f(crtc->planes, "Failed to allocate memory for %d planes\n", crtc->n_planes);
|
|
|
|
fseek(file, n, SEEK_SET);
|
|
parse_planes(file, crtc->planes);
|
|
|
|
if (crtc->pipe != pipe) {
|
|
free(crtc->planes);
|
|
} else {
|
|
ncrtc++;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
line++;
|
|
}
|
|
|
|
fclose(file);
|
|
close(fd);
|
|
|
|
igt_assert(ncrtc == 1);
|
|
}
|
|
|
|
/**
|
|
* igt_assert_plane_visible:
|
|
* @fd: Opened file descriptor
|
|
* @pipe: Display pipe
|
|
* @visibility: Boolean parameter to test against the plane's current visibility state
|
|
*
|
|
* Asserts only if the plane's visibility state matches the status being passed by @visibility
|
|
*/
|
|
void igt_assert_plane_visible(int fd, enum pipe pipe, int plane_index, bool visibility)
|
|
{
|
|
struct kmstest_crtc crtc;
|
|
bool visible = true;
|
|
|
|
kmstest_get_crtc(fd, pipe, &crtc);
|
|
|
|
igt_assert(plane_index < crtc.n_planes);
|
|
|
|
if (crtc.planes[plane_index].pos_x > crtc.width ||
|
|
crtc.planes[plane_index].pos_y > crtc.height)
|
|
visible = false;
|
|
|
|
free(crtc.planes);
|
|
igt_assert_eq(visible, visibility);
|
|
}
|
|
|
|
/**
|
|
* kms_has_vblank:
|
|
* @fd: DRM fd
|
|
*
|
|
* Get the VBlank errno after an attempt to call drmWaitVBlank(). This
|
|
* function is useful for checking if a driver has support or not for VBlank.
|
|
*
|
|
* Returns: true if target driver has VBlank support, otherwise return false.
|
|
*/
|
|
bool kms_has_vblank(int fd)
|
|
{
|
|
drmVBlank dummy_vbl;
|
|
|
|
memset(&dummy_vbl, 0, sizeof(drmVBlank));
|
|
dummy_vbl.request.type = DRM_VBLANK_RELATIVE;
|
|
|
|
errno = 0;
|
|
drmWaitVBlank(fd, &dummy_vbl);
|
|
return (errno != EOPNOTSUPP);
|
|
}
|
|
|
|
/*
|
|
* A small modeset API
|
|
*/
|
|
|
|
#define LOG_SPACES " "
|
|
#define LOG_N_SPACES (sizeof(LOG_SPACES) - 1)
|
|
|
|
#define LOG_INDENT(d, section) \
|
|
do { \
|
|
igt_display_log(d, "%s {\n", section); \
|
|
igt_display_log_shift(d, 1); \
|
|
} while (0)
|
|
#define LOG_UNINDENT(d) \
|
|
do { \
|
|
igt_display_log_shift(d, -1); \
|
|
igt_display_log(d, "}\n"); \
|
|
} while (0)
|
|
#define LOG(d, fmt, ...) igt_display_log(d, fmt, ## __VA_ARGS__)
|
|
|
|
static void __attribute__((format(printf, 2, 3)))
|
|
igt_display_log(igt_display_t *display, const char *fmt, ...)
|
|
{
|
|
va_list args;
|
|
int i;
|
|
|
|
va_start(args, fmt);
|
|
igt_debug("display: ");
|
|
for (i = 0; i < display->log_shift; i++)
|
|
igt_debug("%s", LOG_SPACES);
|
|
igt_vlog(IGT_LOG_DOMAIN, IGT_LOG_DEBUG, fmt, args);
|
|
va_end(args);
|
|
}
|
|
|
|
static void igt_display_log_shift(igt_display_t *display, int shift)
|
|
{
|
|
display->log_shift += shift;
|
|
igt_assert(display->log_shift >= 0);
|
|
}
|
|
|
|
static void igt_output_refresh(igt_output_t *output)
|
|
{
|
|
igt_display_t *display = output->display;
|
|
unsigned long crtc_idx_mask = 0;
|
|
|
|
if (output->pending_pipe != PIPE_NONE)
|
|
crtc_idx_mask = 1 << output->pending_pipe;
|
|
|
|
kmstest_free_connector_config(&output->config);
|
|
|
|
_kmstest_connector_config(display->drm_fd, output->id, crtc_idx_mask,
|
|
&output->config, output->force_reprobe);
|
|
output->force_reprobe = false;
|
|
|
|
if (!output->name && output->config.connector) {
|
|
drmModeConnector *c = output->config.connector;
|
|
|
|
igt_assert_neq(asprintf(&output->name, "%s-%d", kmstest_connector_type_str(c->connector_type), c->connector_type_id),
|
|
-1);
|
|
}
|
|
|
|
if (output->config.connector)
|
|
igt_atomic_fill_connector_props(display, output,
|
|
IGT_NUM_CONNECTOR_PROPS, igt_connector_prop_names);
|
|
|
|
LOG(display, "%s: Selecting pipe %s\n", output->name,
|
|
kmstest_pipe_name(output->pending_pipe));
|
|
}
|
|
|
|
static int
|
|
igt_plane_set_property(igt_plane_t *plane, uint32_t prop_id, uint64_t value)
|
|
{
|
|
igt_pipe_t *pipe = plane->pipe;
|
|
igt_display_t *display = pipe->display;
|
|
|
|
return drmModeObjectSetProperty(display->drm_fd, plane->drm_plane->plane_id,
|
|
DRM_MODE_OBJECT_PLANE, prop_id, value);
|
|
}
|
|
|
|
/*
|
|
* Walk a plane's property list to determine its type. If we don't
|
|
* find a type property, then the kernel doesn't support universal
|
|
* planes and we know the plane is an overlay/sprite.
|
|
*/
|
|
static int get_drm_plane_type(int drm_fd, uint32_t plane_id)
|
|
{
|
|
uint64_t value;
|
|
bool has_prop;
|
|
|
|
has_prop = kmstest_get_property(drm_fd, plane_id, DRM_MODE_OBJECT_PLANE,
|
|
"type", NULL, &value, NULL);
|
|
if (has_prop)
|
|
return (int)value;
|
|
|
|
return DRM_PLANE_TYPE_OVERLAY;
|
|
}
|
|
|
|
static void igt_plane_reset(igt_plane_t *plane)
|
|
{
|
|
/* Reset src coordinates. */
|
|
igt_plane_set_prop_value(plane, IGT_PLANE_SRC_X, 0);
|
|
igt_plane_set_prop_value(plane, IGT_PLANE_SRC_Y, 0);
|
|
igt_plane_set_prop_value(plane, IGT_PLANE_SRC_W, 0);
|
|
igt_plane_set_prop_value(plane, IGT_PLANE_SRC_H, 0);
|
|
|
|
/* Reset crtc coordinates. */
|
|
igt_plane_set_prop_value(plane, IGT_PLANE_CRTC_X, 0);
|
|
igt_plane_set_prop_value(plane, IGT_PLANE_CRTC_Y, 0);
|
|
igt_plane_set_prop_value(plane, IGT_PLANE_CRTC_W, 0);
|
|
igt_plane_set_prop_value(plane, IGT_PLANE_CRTC_H, 0);
|
|
|
|
/* Reset binding to fb and crtc. */
|
|
igt_plane_set_prop_value(plane, IGT_PLANE_FB_ID, 0);
|
|
igt_plane_set_prop_value(plane, IGT_PLANE_CRTC_ID, 0);
|
|
|
|
if (igt_plane_has_prop(plane, IGT_PLANE_COLOR_ENCODING))
|
|
igt_plane_set_prop_enum(plane, IGT_PLANE_COLOR_ENCODING,
|
|
igt_color_encoding_to_str(IGT_COLOR_YCBCR_BT601));
|
|
|
|
if (igt_plane_has_prop(plane, IGT_PLANE_COLOR_RANGE))
|
|
igt_plane_set_prop_enum(plane, IGT_PLANE_COLOR_RANGE,
|
|
igt_color_range_to_str(IGT_COLOR_YCBCR_LIMITED_RANGE));
|
|
|
|
/* Use default rotation */
|
|
if (igt_plane_has_prop(plane, IGT_PLANE_ROTATION))
|
|
igt_plane_set_prop_value(plane, IGT_PLANE_ROTATION, IGT_ROTATION_0);
|
|
|
|
if (igt_plane_has_prop(plane, IGT_PLANE_PIXEL_BLEND_MODE))
|
|
igt_plane_set_prop_enum(plane, IGT_PLANE_PIXEL_BLEND_MODE, "Pre-multiplied");
|
|
|
|
if (igt_plane_has_prop(plane, IGT_PLANE_ALPHA))
|
|
{
|
|
uint64_t max_alpha = 0xffff;
|
|
drmModePropertyPtr alpha_prop = drmModeGetProperty(
|
|
plane->pipe->display->drm_fd,
|
|
plane->props[IGT_PLANE_ALPHA]);
|
|
|
|
if (alpha_prop)
|
|
{
|
|
if (alpha_prop->flags & DRM_MODE_PROP_RANGE)
|
|
{
|
|
max_alpha = alpha_prop->values[1];
|
|
}
|
|
|
|
drmModeFreeProperty(alpha_prop);
|
|
}
|
|
|
|
igt_plane_set_prop_value(plane, IGT_PLANE_ALPHA, max_alpha);
|
|
}
|
|
|
|
|
|
igt_plane_clear_prop_changed(plane, IGT_PLANE_IN_FENCE_FD);
|
|
plane->values[IGT_PLANE_IN_FENCE_FD] = ~0ULL;
|
|
plane->gem_handle = 0;
|
|
}
|
|
|
|
static void igt_pipe_reset(igt_pipe_t *pipe)
|
|
{
|
|
igt_pipe_obj_set_prop_value(pipe, IGT_CRTC_MODE_ID, 0);
|
|
igt_pipe_obj_set_prop_value(pipe, IGT_CRTC_ACTIVE, 0);
|
|
igt_pipe_obj_clear_prop_changed(pipe, IGT_CRTC_OUT_FENCE_PTR);
|
|
|
|
if (igt_pipe_obj_has_prop(pipe, IGT_CRTC_CTM))
|
|
igt_pipe_obj_set_prop_value(pipe, IGT_CRTC_CTM, 0);
|
|
|
|
if (igt_pipe_obj_has_prop(pipe, IGT_CRTC_GAMMA_LUT))
|
|
igt_pipe_obj_set_prop_value(pipe, IGT_CRTC_GAMMA_LUT, 0);
|
|
|
|
if (igt_pipe_obj_has_prop(pipe, IGT_CRTC_DEGAMMA_LUT))
|
|
igt_pipe_obj_set_prop_value(pipe, IGT_CRTC_DEGAMMA_LUT, 0);
|
|
|
|
pipe->out_fence_fd = -1;
|
|
}
|
|
|
|
static void igt_output_reset(igt_output_t *output)
|
|
{
|
|
output->pending_pipe = PIPE_NONE;
|
|
output->use_override_mode = false;
|
|
memset(&output->override_mode, 0, sizeof(output->override_mode));
|
|
|
|
igt_output_set_prop_value(output, IGT_CONNECTOR_CRTC_ID, 0);
|
|
|
|
if (igt_output_has_prop(output, IGT_CONNECTOR_BROADCAST_RGB))
|
|
igt_output_set_prop_value(output, IGT_CONNECTOR_BROADCAST_RGB,
|
|
BROADCAST_RGB_FULL);
|
|
|
|
if (igt_output_has_prop(output, IGT_CONNECTOR_CONTENT_PROTECTION))
|
|
igt_output_set_prop_enum(output, IGT_CONNECTOR_CONTENT_PROTECTION,
|
|
"Undesired");
|
|
}
|
|
|
|
/**
|
|
* igt_display_reset:
|
|
* @display: a pointer to an #igt_display_t structure
|
|
*
|
|
* Reset basic pipes, connectors and planes on @display back to default values.
|
|
* In particular, the following properties will be reset:
|
|
*
|
|
* For outputs:
|
|
* - %IGT_CONNECTOR_CRTC_ID
|
|
* - %IGT_CONNECTOR_BROADCAST_RGB (if applicable)
|
|
* %IGT_CONNECTOR_CONTENT_PROTECTION (if applicable)
|
|
* - igt_output_override_mode() to default.
|
|
*
|
|
* For pipes:
|
|
* - %IGT_CRTC_MODE_ID (leaked)
|
|
* - %IGT_CRTC_ACTIVE
|
|
* - %IGT_CRTC_OUT_FENCE_PTR
|
|
*
|
|
* For planes:
|
|
* - %IGT_PLANE_SRC_*
|
|
* - %IGT_PLANE_CRTC_*
|
|
* - %IGT_PLANE_FB_ID
|
|
* - %IGT_PLANE_CRTC_ID
|
|
* - %IGT_PLANE_ROTATION
|
|
* - %IGT_PLANE_IN_FENCE_FD
|
|
*/
|
|
void igt_display_reset(igt_display_t *display)
|
|
{
|
|
enum pipe pipe;
|
|
int i;
|
|
|
|
/*
|
|
* Allow resetting rotation on all planes, which is normally
|
|
* prohibited on the primary and cursor plane for legacy commits.
|
|
*/
|
|
display->first_commit = true;
|
|
|
|
for_each_pipe(display, pipe) {
|
|
igt_pipe_t *pipe_obj = &display->pipes[pipe];
|
|
igt_plane_t *plane;
|
|
|
|
for_each_plane_on_pipe(display, pipe, plane)
|
|
igt_plane_reset(plane);
|
|
|
|
igt_pipe_reset(pipe_obj);
|
|
}
|
|
|
|
for (i = 0; i < display->n_outputs; i++) {
|
|
igt_output_t *output = &display->outputs[i];
|
|
|
|
igt_output_reset(output);
|
|
}
|
|
}
|
|
|
|
static void igt_fill_plane_format_mod(igt_display_t *display, igt_plane_t *plane);
|
|
static void igt_fill_display_format_mod(igt_display_t *display);
|
|
|
|
/**
|
|
* igt_display_require:
|
|
* @display: a pointer to an #igt_display_t structure
|
|
* @drm_fd: a drm file descriptor
|
|
*
|
|
* Initialize @display and allocate the various resources required. Use
|
|
* #igt_display_fini to release the resources when they are no longer required.
|
|
*
|
|
* This function automatically skips if the kernel driver doesn't support any
|
|
* CRTC or outputs.
|
|
*/
|
|
void igt_display_require(igt_display_t *display, int drm_fd)
|
|
{
|
|
drmModeRes *resources;
|
|
drmModePlaneRes *plane_resources;
|
|
int i;
|
|
|
|
memset(display, 0, sizeof(igt_display_t));
|
|
|
|
LOG_INDENT(display, "init");
|
|
|
|
display->drm_fd = drm_fd;
|
|
|
|
resources = drmModeGetResources(display->drm_fd);
|
|
if (!resources)
|
|
goto out;
|
|
|
|
/*
|
|
* We cache the number of pipes, that number is a physical limit of the
|
|
* hardware and cannot change of time (for now, at least).
|
|
*/
|
|
display->n_pipes = resources->count_crtcs;
|
|
display->pipes = calloc(sizeof(igt_pipe_t), display->n_pipes);
|
|
igt_assert_f(display->pipes, "Failed to allocate memory for %d pipes\n", display->n_pipes);
|
|
|
|
drmSetClientCap(drm_fd, DRM_CLIENT_CAP_UNIVERSAL_PLANES, 1);
|
|
if (drmSetClientCap(drm_fd, DRM_CLIENT_CAP_ATOMIC, 1) == 0)
|
|
display->is_atomic = 1;
|
|
|
|
plane_resources = drmModeGetPlaneResources(display->drm_fd);
|
|
igt_assert(plane_resources);
|
|
|
|
display->n_planes = plane_resources->count_planes;
|
|
display->planes = calloc(sizeof(igt_plane_t), display->n_planes);
|
|
igt_assert_f(display->planes, "Failed to allocate memory for %d planes\n", display->n_planes);
|
|
|
|
for (i = 0; i < plane_resources->count_planes; ++i) {
|
|
igt_plane_t *plane = &display->planes[i];
|
|
uint32_t id = plane_resources->planes[i];
|
|
|
|
plane->drm_plane = drmModeGetPlane(display->drm_fd, id);
|
|
igt_assert(plane->drm_plane);
|
|
|
|
plane->type = get_drm_plane_type(display->drm_fd, id);
|
|
|
|
/*
|
|
* TODO: Fill in the rest of the plane properties here and
|
|
* move away from the plane per pipe model to align closer
|
|
* to the DRM KMS model.
|
|
*/
|
|
}
|
|
|
|
for_each_pipe(display, i) {
|
|
igt_pipe_t *pipe = &display->pipes[i];
|
|
igt_plane_t *plane;
|
|
int p = 1;
|
|
int j, type;
|
|
uint8_t last_plane = 0, n_planes = 0;
|
|
|
|
pipe->crtc_id = resources->crtcs[i];
|
|
pipe->display = display;
|
|
pipe->pipe = i;
|
|
pipe->plane_cursor = -1;
|
|
pipe->plane_primary = -1;
|
|
pipe->planes = NULL;
|
|
|
|
igt_fill_pipe_props(display, pipe, IGT_NUM_CRTC_PROPS, igt_crtc_prop_names);
|
|
|
|
/* count number of valid planes */
|
|
for (j = 0; j < display->n_planes; j++) {
|
|
drmModePlane *drm_plane = display->planes[j].drm_plane;
|
|
igt_assert(drm_plane);
|
|
|
|
if (drm_plane->possible_crtcs & (1 << i))
|
|
n_planes++;
|
|
}
|
|
|
|
igt_assert_lt(0, n_planes);
|
|
pipe->planes = calloc(sizeof(igt_plane_t), n_planes);
|
|
igt_assert_f(pipe->planes, "Failed to allocate memory for %d planes\n", n_planes);
|
|
last_plane = n_planes - 1;
|
|
|
|
/* add the planes that can be used with that pipe */
|
|
for (j = 0; j < display->n_planes; j++) {
|
|
igt_plane_t *global_plane = &display->planes[j];
|
|
drmModePlane *drm_plane = global_plane->drm_plane;
|
|
|
|
if (!(drm_plane->possible_crtcs & (1 << i)))
|
|
continue;
|
|
|
|
type = global_plane->type;
|
|
|
|
if (type == DRM_PLANE_TYPE_PRIMARY && pipe->plane_primary == -1) {
|
|
plane = &pipe->planes[0];
|
|
plane->index = 0;
|
|
pipe->plane_primary = 0;
|
|
} else if (type == DRM_PLANE_TYPE_CURSOR && pipe->plane_cursor == -1) {
|
|
plane = &pipe->planes[last_plane];
|
|
plane->index = last_plane;
|
|
pipe->plane_cursor = last_plane;
|
|
display->has_cursor_plane = true;
|
|
} else {
|
|
plane = &pipe->planes[p];
|
|
plane->index = p++;
|
|
}
|
|
|
|
igt_assert_f(plane->index < n_planes, "n_planes < plane->index failed\n");
|
|
plane->type = type;
|
|
plane->pipe = pipe;
|
|
plane->drm_plane = drm_plane;
|
|
plane->values[IGT_PLANE_IN_FENCE_FD] = ~0ULL;
|
|
plane->ref = global_plane;
|
|
|
|
/*
|
|
* HACK: point the global plane to the first pipe that
|
|
* it can go on.
|
|
*/
|
|
if (!global_plane->ref)
|
|
igt_plane_set_pipe(plane, pipe);
|
|
|
|
igt_fill_plane_props(display, plane, IGT_NUM_PLANE_PROPS, igt_plane_prop_names);
|
|
|
|
igt_fill_plane_format_mod(display, plane);
|
|
}
|
|
|
|
/*
|
|
* At the bare minimum, we should expect to have a primary
|
|
* plane, and it must be in slot 0.
|
|
*/
|
|
igt_assert_eq(pipe->plane_primary, 0);
|
|
|
|
/* Check that we filled every slot exactly once */
|
|
if (display->has_cursor_plane)
|
|
igt_assert_eq(p, last_plane);
|
|
else
|
|
igt_assert_eq(p, n_planes);
|
|
|
|
pipe->n_planes = n_planes;
|
|
}
|
|
|
|
igt_fill_display_format_mod(display);
|
|
|
|
/*
|
|
* The number of connectors is set, so we just initialize the outputs
|
|
* array in _init(). This may change when we need dynamic connectors
|
|
* (say DisplayPort MST).
|
|
*/
|
|
display->n_outputs = resources->count_connectors;
|
|
display->outputs = calloc(display->n_outputs, sizeof(igt_output_t));
|
|
igt_assert_f(display->outputs, "Failed to allocate memory for %d outputs\n", display->n_outputs);
|
|
|
|
for (i = 0; i < display->n_outputs; i++) {
|
|
igt_output_t *output = &display->outputs[i];
|
|
drmModeConnector *connector;
|
|
|
|
/*
|
|
* We don't assign each output a pipe unless
|
|
* a pipe is set with igt_output_set_pipe().
|
|
*/
|
|
output->pending_pipe = PIPE_NONE;
|
|
output->id = resources->connectors[i];
|
|
output->display = display;
|
|
|
|
igt_output_refresh(output);
|
|
|
|
connector = output->config.connector;
|
|
if (connector && (!connector->count_modes ||
|
|
connector->connection == DRM_MODE_UNKNOWNCONNECTION)) {
|
|
output->force_reprobe = true;
|
|
igt_output_refresh(output);
|
|
}
|
|
}
|
|
|
|
drmModeFreePlaneResources(plane_resources);
|
|
drmModeFreeResources(resources);
|
|
|
|
/* Set reasonable default values for every object in the display. */
|
|
igt_display_reset(display);
|
|
|
|
out:
|
|
LOG_UNINDENT(display);
|
|
|
|
if (display->n_pipes && display->n_outputs)
|
|
igt_enable_connectors(drm_fd);
|
|
else
|
|
igt_skip("No KMS driver or no outputs, pipes: %d, outputs: %d\n",
|
|
display->n_pipes, display->n_outputs);
|
|
}
|
|
|
|
/**
|
|
* igt_display_get_n_pipes:
|
|
* @display: A pointer to an #igt_display_t structure
|
|
*
|
|
* Returns total number of pipes for the given @display
|
|
*/
|
|
int igt_display_get_n_pipes(igt_display_t *display)
|
|
{
|
|
return display->n_pipes;
|
|
}
|
|
|
|
/**
|
|
* igt_display_require_output:
|
|
* @display: A pointer to an #igt_display_t structure
|
|
*
|
|
* Checks whether there's a valid @pipe/@output combination for the given @display
|
|
* Skips test if a valid combination of @pipe and @output is not found
|
|
*/
|
|
void igt_display_require_output(igt_display_t *display)
|
|
{
|
|
enum pipe pipe;
|
|
igt_output_t *output;
|
|
|
|
for_each_pipe_with_valid_output(display, pipe, output)
|
|
return;
|
|
|
|
igt_skip("No valid crtc/connector combinations found.\n");
|
|
}
|
|
|
|
/**
|
|
* igt_display_require_output_on_pipe:
|
|
* @display: A pointer to an #igt_display_t structure
|
|
* @pipe: Display pipe
|
|
*
|
|
* Checks whether there's a valid @pipe/@output combination for the given @display and @pipe
|
|
* Skips test if a valid @pipe is not found
|
|
*/
|
|
void igt_display_require_output_on_pipe(igt_display_t *display, enum pipe pipe)
|
|
{
|
|
igt_output_t *output;
|
|
|
|
igt_skip_on_f(pipe >= igt_display_get_n_pipes(display),
|
|
"Pipe %s does not exist.\n", kmstest_pipe_name(pipe));
|
|
|
|
for_each_valid_output_on_pipe(display, pipe, output)
|
|
return;
|
|
|
|
igt_skip("No valid connector found on pipe %s\n", kmstest_pipe_name(pipe));
|
|
}
|
|
|
|
/**
|
|
* igt_output_from_connector:
|
|
* @display: a pointer to an #igt_display_t structure
|
|
* @connector: a pointer to a drmModeConnector
|
|
*
|
|
* Finds the output corresponding to the given connector
|
|
*
|
|
* Returns: A #igt_output_t structure configured to use the connector, or NULL
|
|
* if none was found
|
|
*/
|
|
igt_output_t *igt_output_from_connector(igt_display_t *display,
|
|
drmModeConnector *connector)
|
|
{
|
|
igt_output_t *output, *found = NULL;
|
|
int i;
|
|
|
|
for (i = 0; i < display->n_outputs; i++) {
|
|
output = &display->outputs[i];
|
|
|
|
if (output->config.connector &&
|
|
output->config.connector->connector_id ==
|
|
connector->connector_id) {
|
|
found = output;
|
|
break;
|
|
}
|
|
}
|
|
|
|
return found;
|
|
}
|
|
|
|
const drmModeModeInfo *igt_std_1024_mode_get(void)
|
|
{
|
|
static const drmModeModeInfo std_1024_mode = {
|
|
.clock = 65000,
|
|
.hdisplay = 1024,
|
|
.hsync_start = 1048,
|
|
.hsync_end = 1184,
|
|
.htotal = 1344,
|
|
.hskew = 0,
|
|
.vdisplay = 768,
|
|
.vsync_start = 771,
|
|
.vsync_end = 777,
|
|
.vtotal = 806,
|
|
.vscan = 0,
|
|
.vrefresh = 60,
|
|
.flags = 0xA,
|
|
.type = 0x40,
|
|
.name = "Custom 1024x768",
|
|
};
|
|
|
|
return &std_1024_mode;
|
|
}
|
|
|
|
static void igt_pipe_fini(igt_pipe_t *pipe)
|
|
{
|
|
free(pipe->planes);
|
|
pipe->planes = NULL;
|
|
|
|
if (pipe->out_fence_fd != -1)
|
|
close(pipe->out_fence_fd);
|
|
}
|
|
|
|
static void igt_output_fini(igt_output_t *output)
|
|
{
|
|
kmstest_free_connector_config(&output->config);
|
|
free(output->name);
|
|
output->name = NULL;
|
|
}
|
|
|
|
/**
|
|
* igt_display_fini:
|
|
* @display: a pointer to an #igt_display_t structure
|
|
*
|
|
* Release any resources associated with @display. This does not free @display
|
|
* itself.
|
|
*/
|
|
void igt_display_fini(igt_display_t *display)
|
|
{
|
|
int i;
|
|
|
|
for (i = 0; i < display->n_planes; ++i) {
|
|
igt_plane_t *plane = &display->planes[i];
|
|
|
|
if (plane->drm_plane) {
|
|
drmModeFreePlane(plane->drm_plane);
|
|
plane->drm_plane = NULL;
|
|
}
|
|
}
|
|
|
|
for (i = 0; i < display->n_pipes; i++)
|
|
igt_pipe_fini(&display->pipes[i]);
|
|
|
|
for (i = 0; i < display->n_outputs; i++)
|
|
igt_output_fini(&display->outputs[i]);
|
|
free(display->outputs);
|
|
display->outputs = NULL;
|
|
free(display->pipes);
|
|
display->pipes = NULL;
|
|
free(display->planes);
|
|
display->planes = NULL;
|
|
}
|
|
|
|
static void igt_display_refresh(igt_display_t *display)
|
|
{
|
|
igt_output_t *output;
|
|
int i;
|
|
|
|
unsigned long pipes_in_use = 0;
|
|
|
|
/* Check that two outputs aren't trying to use the same pipe */
|
|
for (i = 0; i < display->n_outputs; i++) {
|
|
output = &display->outputs[i];
|
|
|
|
if (output->pending_pipe != PIPE_NONE) {
|
|
if (pipes_in_use & (1 << output->pending_pipe))
|
|
goto report_dup;
|
|
|
|
pipes_in_use |= 1 << output->pending_pipe;
|
|
}
|
|
|
|
if (output->force_reprobe)
|
|
igt_output_refresh(output);
|
|
}
|
|
|
|
return;
|
|
|
|
report_dup:
|
|
for (; i > 0; i--) {
|
|
igt_output_t *b = &display->outputs[i - 1];
|
|
|
|
igt_assert_f(output->pending_pipe !=
|
|
b->pending_pipe,
|
|
"%s and %s are both trying to use pipe %s\n",
|
|
igt_output_name(output), igt_output_name(b),
|
|
kmstest_pipe_name(output->pending_pipe));
|
|
}
|
|
}
|
|
|
|
static igt_pipe_t *igt_output_get_driving_pipe(igt_output_t *output)
|
|
{
|
|
igt_display_t *display = output->display;
|
|
enum pipe pipe;
|
|
|
|
if (output->pending_pipe == PIPE_NONE) {
|
|
/*
|
|
* The user hasn't specified a pipe to use, return none.
|
|
*/
|
|
return NULL;
|
|
} else {
|
|
/*
|
|
* Otherwise, return the pending pipe (ie the pipe that should
|
|
* drive this output after the commit()
|
|
*/
|
|
pipe = output->pending_pipe;
|
|
}
|
|
|
|
igt_assert(pipe >= 0 && pipe < display->n_pipes);
|
|
|
|
return &display->pipes[pipe];
|
|
}
|
|
|
|
static igt_plane_t *igt_pipe_get_plane(igt_pipe_t *pipe, int plane_idx)
|
|
{
|
|
igt_require_f(plane_idx >= 0 && plane_idx < pipe->n_planes,
|
|
"Valid pipe->planes plane_idx not found, plane_idx=%d n_planes=%d",
|
|
plane_idx, pipe->n_planes);
|
|
|
|
return &pipe->planes[plane_idx];
|
|
}
|
|
|
|
/**
|
|
* igt_pipe_get_plane_type:
|
|
* @pipe: Target pipe
|
|
* @plane_type: Cursor, primary or an overlay plane
|
|
*
|
|
* Finds a valid plane type for the given @pipe otherwise
|
|
* it skips the test if the right combination of @pipe/@plane_type is not found
|
|
*
|
|
* Returns: A #igt_plane_t structure that matches the requested plane type
|
|
*/
|
|
igt_plane_t *igt_pipe_get_plane_type(igt_pipe_t *pipe, int plane_type)
|
|
{
|
|
int i, plane_idx = -1;
|
|
|
|
switch(plane_type) {
|
|
case DRM_PLANE_TYPE_CURSOR:
|
|
plane_idx = pipe->plane_cursor;
|
|
break;
|
|
case DRM_PLANE_TYPE_PRIMARY:
|
|
plane_idx = pipe->plane_primary;
|
|
break;
|
|
case DRM_PLANE_TYPE_OVERLAY:
|
|
for(i = 0; i < pipe->n_planes; i++)
|
|
if (pipe->planes[i].type == DRM_PLANE_TYPE_OVERLAY)
|
|
plane_idx = i;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
igt_require_f(plane_idx >= 0 && plane_idx < pipe->n_planes,
|
|
"Valid pipe->planes idx not found. plane_idx=%d plane_type=%d n_planes=%d\n",
|
|
plane_idx, plane_type, pipe->n_planes);
|
|
|
|
return &pipe->planes[plane_idx];
|
|
}
|
|
|
|
/**
|
|
* igt_pipe_count_plane_type:
|
|
* @pipe: Target pipe
|
|
* @plane_type: Cursor, primary or an overlay plane
|
|
*
|
|
* Counts the number of planes of type @plane_type for the provided @pipe.
|
|
*
|
|
* Returns: The number of planes that match the requested plane type
|
|
*/
|
|
int igt_pipe_count_plane_type(igt_pipe_t *pipe, int plane_type)
|
|
{
|
|
int i, count = 0;
|
|
|
|
for(i = 0; i < pipe->n_planes; i++)
|
|
if (pipe->planes[i].type == plane_type)
|
|
count++;
|
|
|
|
return count;
|
|
}
|
|
|
|
/**
|
|
* igt_pipe_get_plane_type_index:
|
|
* @pipe: Target pipe
|
|
* @plane_type: Cursor, primary or an overlay plane
|
|
* @index: the index of the plane among planes of the same type
|
|
*
|
|
* Get the @index th plane of type @plane_type for the provided @pipe.
|
|
*
|
|
* Returns: The @index th plane that matches the requested plane type
|
|
*/
|
|
igt_plane_t *igt_pipe_get_plane_type_index(igt_pipe_t *pipe, int plane_type,
|
|
int index)
|
|
{
|
|
int i, type_index = 0;
|
|
|
|
for(i = 0; i < pipe->n_planes; i++) {
|
|
if (pipe->planes[i].type != plane_type)
|
|
continue;
|
|
|
|
if (type_index == index)
|
|
return &pipe->planes[i];
|
|
|
|
type_index++;
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
static bool output_is_internal_panel(igt_output_t *output)
|
|
{
|
|
switch (output->config.connector->connector_type) {
|
|
case DRM_MODE_CONNECTOR_LVDS:
|
|
case DRM_MODE_CONNECTOR_eDP:
|
|
case DRM_MODE_CONNECTOR_DSI:
|
|
case DRM_MODE_CONNECTOR_DPI:
|
|
return true;
|
|
default:
|
|
return false;
|
|
}
|
|
}
|
|
|
|
igt_output_t **__igt_pipe_populate_outputs(igt_display_t *display, igt_output_t **chosen_outputs)
|
|
{
|
|
unsigned full_pipe_mask = (1 << (display->n_pipes)) - 1, assigned_pipes = 0;
|
|
igt_output_t *output;
|
|
int i, j;
|
|
|
|
memset(chosen_outputs, 0, sizeof(*chosen_outputs) * display->n_pipes);
|
|
|
|
/*
|
|
* Try to assign all outputs to the first available CRTC for
|
|
* it, start with the outputs restricted to 1 pipe, then increase
|
|
* number of pipes until we assign connectors to all pipes.
|
|
*/
|
|
for (i = 0; i <= display->n_pipes; i++) {
|
|
for_each_connected_output(display, output) {
|
|
uint32_t pipe_mask = output->config.valid_crtc_idx_mask & full_pipe_mask;
|
|
bool found = false;
|
|
|
|
if (output_is_internal_panel(output)) {
|
|
/*
|
|
* Internal panel should be assigned to pipe A
|
|
* if possible, so make sure they're enumerated
|
|
* first.
|
|
*/
|
|
|
|
if (i)
|
|
continue;
|
|
} else if (__builtin_popcount(pipe_mask) != i)
|
|
continue;
|
|
|
|
for (j = 0; j < display->n_pipes; j++) {
|
|
bool pipe_assigned = assigned_pipes & (1 << j);
|
|
|
|
if (pipe_assigned || !(pipe_mask & (1 << j)))
|
|
continue;
|
|
|
|
if (!found) {
|
|
/* We found an unassigned pipe, use it! */
|
|
found = true;
|
|
assigned_pipes |= 1 << j;
|
|
chosen_outputs[j] = output;
|
|
} else if (!chosen_outputs[j] ||
|
|
/*
|
|
* Overwrite internal panel if not assigned,
|
|
* external outputs are faster to do modesets
|
|
*/
|
|
output_is_internal_panel(chosen_outputs[j]))
|
|
chosen_outputs[j] = output;
|
|
}
|
|
|
|
if (!found)
|
|
igt_warn("Output %s could not be assigned to a pipe\n",
|
|
igt_output_name(output));
|
|
}
|
|
}
|
|
|
|
return chosen_outputs;
|
|
}
|
|
|
|
/**
|
|
* igt_get_single_output_for_pipe:
|
|
* @display: a pointer to an #igt_display_t structure
|
|
* @pipe: The pipe for which an #igt_output_t must be returned.
|
|
*
|
|
* Get a compatible output for a pipe.
|
|
*
|
|
* Returns: A compatible output for a given pipe, or NULL.
|
|
*/
|
|
igt_output_t *igt_get_single_output_for_pipe(igt_display_t *display, enum pipe pipe)
|
|
{
|
|
igt_output_t *chosen_outputs[display->n_pipes];
|
|
|
|
igt_assert(pipe != PIPE_NONE);
|
|
igt_require(pipe < display->n_pipes);
|
|
|
|
__igt_pipe_populate_outputs(display, chosen_outputs);
|
|
|
|
return chosen_outputs[pipe];
|
|
}
|
|
|
|
static igt_output_t *igt_pipe_get_output(igt_pipe_t *pipe)
|
|
{
|
|
igt_display_t *display = pipe->display;
|
|
int i;
|
|
|
|
for (i = 0; i < display->n_outputs; i++) {
|
|
igt_output_t *output = &display->outputs[i];
|
|
|
|
if (output->pending_pipe == pipe->pipe)
|
|
return output;
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
static uint32_t igt_plane_get_fb_id(igt_plane_t *plane)
|
|
{
|
|
return plane->values[IGT_PLANE_FB_ID];
|
|
}
|
|
|
|
#define CHECK_RETURN(r, fail) { \
|
|
if (r && !fail) \
|
|
return r; \
|
|
igt_assert_eq(r, 0); \
|
|
}
|
|
|
|
/*
|
|
* Add position and fb changes of a plane to the atomic property set
|
|
*/
|
|
static void
|
|
igt_atomic_prepare_plane_commit(igt_plane_t *plane, igt_pipe_t *pipe,
|
|
drmModeAtomicReq *req)
|
|
{
|
|
igt_display_t *display = pipe->display;
|
|
int i;
|
|
|
|
igt_assert(plane->drm_plane);
|
|
|
|
LOG(display,
|
|
"populating plane data: %s.%d, fb %u\n",
|
|
kmstest_pipe_name(pipe->pipe),
|
|
plane->index,
|
|
igt_plane_get_fb_id(plane));
|
|
|
|
for (i = 0; i < IGT_NUM_PLANE_PROPS; i++) {
|
|
if (!igt_plane_is_prop_changed(plane, i))
|
|
continue;
|
|
|
|
/* it's an error to try an unsupported feature */
|
|
igt_assert(plane->props[i]);
|
|
|
|
igt_debug("plane %s.%d: Setting property \"%s\" to 0x%"PRIx64"/%"PRIi64"\n",
|
|
kmstest_pipe_name(pipe->pipe), plane->index, igt_plane_prop_names[i],
|
|
plane->values[i], plane->values[i]);
|
|
|
|
igt_assert_lt(0, drmModeAtomicAddProperty(req, plane->drm_plane->plane_id,
|
|
plane->props[i],
|
|
plane->values[i]));
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Properties that can be changed through legacy SetProperty:
|
|
* - Obviously not the XYWH SRC/CRTC coordinates.
|
|
* - Not CRTC_ID or FENCE_ID, done through SetPlane.
|
|
* - Can't set IN_FENCE_FD, that would be silly.
|
|
*
|
|
* Theoretically the above can all be set through the legacy path
|
|
* with the atomic cap set, but that's not how our legacy plane
|
|
* commit behaves, so blacklist it by default.
|
|
*/
|
|
#define LEGACY_PLANE_COMMIT_MASK \
|
|
(((1ULL << IGT_NUM_PLANE_PROPS) - 1) & \
|
|
~(IGT_PLANE_COORD_CHANGED_MASK | \
|
|
(1ULL << IGT_PLANE_FB_ID) | \
|
|
(1ULL << IGT_PLANE_CRTC_ID) | \
|
|
(1ULL << IGT_PLANE_IN_FENCE_FD)))
|
|
|
|
/*
|
|
* Commit position and fb changes to a DRM plane via the SetPlane ioctl; if the
|
|
* DRM call to program the plane fails, we'll either fail immediately (for
|
|
* tests that expect the commit to succeed) or return the failure code (for
|
|
* tests that expect a specific error code).
|
|
*/
|
|
static int igt_drm_plane_commit(igt_plane_t *plane,
|
|
igt_pipe_t *pipe,
|
|
bool fail_on_error)
|
|
{
|
|
igt_display_t *display = pipe->display;
|
|
uint32_t fb_id, crtc_id;
|
|
int ret, i;
|
|
uint32_t src_x;
|
|
uint32_t src_y;
|
|
uint32_t src_w;
|
|
uint32_t src_h;
|
|
int32_t crtc_x;
|
|
int32_t crtc_y;
|
|
uint32_t crtc_w;
|
|
uint32_t crtc_h;
|
|
uint64_t changed_mask;
|
|
bool setplane =
|
|
igt_plane_is_prop_changed(plane, IGT_PLANE_FB_ID) ||
|
|
plane->changed & IGT_PLANE_COORD_CHANGED_MASK;
|
|
|
|
igt_assert(plane->drm_plane);
|
|
|
|
fb_id = igt_plane_get_fb_id(plane);
|
|
crtc_id = pipe->crtc_id;
|
|
|
|
if (setplane && fb_id == 0) {
|
|
LOG(display,
|
|
"SetPlane pipe %s, plane %d, disabling\n",
|
|
kmstest_pipe_name(pipe->pipe),
|
|
plane->index);
|
|
|
|
ret = drmModeSetPlane(display->drm_fd,
|
|
plane->drm_plane->plane_id,
|
|
crtc_id,
|
|
fb_id,
|
|
0, /* flags */
|
|
0, 0, /* crtc_x, crtc_y */
|
|
0, 0, /* crtc_w, crtc_h */
|
|
IGT_FIXED(0,0), /* src_x */
|
|
IGT_FIXED(0,0), /* src_y */
|
|
IGT_FIXED(0,0), /* src_w */
|
|
IGT_FIXED(0,0) /* src_h */);
|
|
|
|
CHECK_RETURN(ret, fail_on_error);
|
|
} else if (setplane) {
|
|
src_x = plane->values[IGT_PLANE_SRC_X];
|
|
src_y = plane->values[IGT_PLANE_SRC_Y];
|
|
src_w = plane->values[IGT_PLANE_SRC_W];
|
|
src_h = plane->values[IGT_PLANE_SRC_H];
|
|
crtc_x = plane->values[IGT_PLANE_CRTC_X];
|
|
crtc_y = plane->values[IGT_PLANE_CRTC_Y];
|
|
crtc_w = plane->values[IGT_PLANE_CRTC_W];
|
|
crtc_h = plane->values[IGT_PLANE_CRTC_H];
|
|
|
|
LOG(display,
|
|
"SetPlane %s.%d, fb %u, src = (%d, %d) "
|
|
"%ux%u dst = (%u, %u) %ux%u\n",
|
|
kmstest_pipe_name(pipe->pipe),
|
|
plane->index,
|
|
fb_id,
|
|
src_x >> 16, src_y >> 16, src_w >> 16, src_h >> 16,
|
|
crtc_x, crtc_y, crtc_w, crtc_h);
|
|
|
|
ret = drmModeSetPlane(display->drm_fd,
|
|
plane->drm_plane->plane_id,
|
|
crtc_id,
|
|
fb_id,
|
|
0, /* flags */
|
|
crtc_x, crtc_y,
|
|
crtc_w, crtc_h,
|
|
src_x, src_y,
|
|
src_w, src_h);
|
|
|
|
CHECK_RETURN(ret, fail_on_error);
|
|
}
|
|
|
|
changed_mask = plane->changed & LEGACY_PLANE_COMMIT_MASK;
|
|
|
|
for (i = 0; i < IGT_NUM_PLANE_PROPS; i++) {
|
|
if (!(changed_mask & (1 << i)))
|
|
continue;
|
|
|
|
LOG(display, "SetProp plane %s.%d \"%s\" to 0x%"PRIx64"/%"PRIi64"\n",
|
|
kmstest_pipe_name(pipe->pipe), plane->index, igt_plane_prop_names[i],
|
|
plane->values[i], plane->values[i]);
|
|
|
|
igt_assert(plane->props[i]);
|
|
|
|
ret = igt_plane_set_property(plane,
|
|
plane->props[i],
|
|
plane->values[i]);
|
|
|
|
CHECK_RETURN(ret, fail_on_error);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* Commit position and fb changes to a cursor via legacy ioctl's. If commit
|
|
* fails, we'll either fail immediately (for tests that expect the commit to
|
|
* succeed) or return the failure code (for tests that expect a specific error
|
|
* code).
|
|
*/
|
|
static int igt_cursor_commit_legacy(igt_plane_t *cursor,
|
|
igt_pipe_t *pipe,
|
|
bool fail_on_error)
|
|
{
|
|
igt_display_t *display = pipe->display;
|
|
uint32_t crtc_id = pipe->crtc_id;
|
|
int ret;
|
|
|
|
if (igt_plane_is_prop_changed(cursor, IGT_PLANE_FB_ID) ||
|
|
igt_plane_is_prop_changed(cursor, IGT_PLANE_CRTC_W) ||
|
|
igt_plane_is_prop_changed(cursor, IGT_PLANE_CRTC_H)) {
|
|
if (cursor->gem_handle)
|
|
LOG(display,
|
|
"SetCursor pipe %s, fb %u %dx%d\n",
|
|
kmstest_pipe_name(pipe->pipe),
|
|
cursor->gem_handle,
|
|
(unsigned)cursor->values[IGT_PLANE_CRTC_W],
|
|
(unsigned)cursor->values[IGT_PLANE_CRTC_H]);
|
|
else
|
|
LOG(display,
|
|
"SetCursor pipe %s, disabling\n",
|
|
kmstest_pipe_name(pipe->pipe));
|
|
|
|
ret = drmModeSetCursor(display->drm_fd, crtc_id,
|
|
cursor->gem_handle,
|
|
cursor->values[IGT_PLANE_CRTC_W],
|
|
cursor->values[IGT_PLANE_CRTC_H]);
|
|
CHECK_RETURN(ret, fail_on_error);
|
|
}
|
|
|
|
if (igt_plane_is_prop_changed(cursor, IGT_PLANE_CRTC_X) ||
|
|
igt_plane_is_prop_changed(cursor, IGT_PLANE_CRTC_Y)) {
|
|
int x = cursor->values[IGT_PLANE_CRTC_X];
|
|
int y = cursor->values[IGT_PLANE_CRTC_Y];
|
|
|
|
LOG(display,
|
|
"MoveCursor pipe %s, (%d, %d)\n",
|
|
kmstest_pipe_name(pipe->pipe),
|
|
x, y);
|
|
|
|
ret = drmModeMoveCursor(display->drm_fd, crtc_id, x, y);
|
|
CHECK_RETURN(ret, fail_on_error);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* Commit position and fb changes to a primary plane via the legacy interface
|
|
* (setmode).
|
|
*/
|
|
static int igt_primary_plane_commit_legacy(igt_plane_t *primary,
|
|
igt_pipe_t *pipe,
|
|
bool fail_on_error)
|
|
{
|
|
struct igt_display *display = primary->pipe->display;
|
|
igt_output_t *output = igt_pipe_get_output(pipe);
|
|
drmModeModeInfo *mode;
|
|
uint32_t fb_id, crtc_id;
|
|
int ret;
|
|
|
|
/* Primary planes can't be windowed when using a legacy commit */
|
|
igt_assert((primary->values[IGT_PLANE_CRTC_X] == 0 && primary->values[IGT_PLANE_CRTC_Y] == 0));
|
|
|
|
/* nor rotated */
|
|
if (!pipe->display->first_commit)
|
|
igt_assert(!igt_plane_is_prop_changed(primary, IGT_PLANE_ROTATION));
|
|
|
|
if (!igt_plane_is_prop_changed(primary, IGT_PLANE_FB_ID) &&
|
|
!(primary->changed & IGT_PLANE_COORD_CHANGED_MASK) &&
|
|
!igt_pipe_obj_is_prop_changed(primary->pipe, IGT_CRTC_MODE_ID))
|
|
return 0;
|
|
|
|
crtc_id = pipe->crtc_id;
|
|
fb_id = output ? igt_plane_get_fb_id(primary) : 0;
|
|
if (fb_id)
|
|
mode = igt_output_get_mode(output);
|
|
else
|
|
mode = NULL;
|
|
|
|
if (fb_id) {
|
|
uint32_t src_x = primary->values[IGT_PLANE_SRC_X] >> 16;
|
|
uint32_t src_y = primary->values[IGT_PLANE_SRC_Y] >> 16;
|
|
|
|
LOG(display,
|
|
"%s: SetCrtc pipe %s, fb %u, src (%d, %d), "
|
|
"mode %dx%d\n",
|
|
igt_output_name(output),
|
|
kmstest_pipe_name(pipe->pipe),
|
|
fb_id,
|
|
src_x, src_y,
|
|
mode->hdisplay, mode->vdisplay);
|
|
|
|
ret = drmModeSetCrtc(display->drm_fd,
|
|
crtc_id,
|
|
fb_id,
|
|
src_x, src_y,
|
|
&output->id,
|
|
1,
|
|
mode);
|
|
} else {
|
|
LOG(display,
|
|
"SetCrtc pipe %s, disabling\n",
|
|
kmstest_pipe_name(pipe->pipe));
|
|
|
|
ret = drmModeSetCrtc(display->drm_fd,
|
|
crtc_id,
|
|
fb_id,
|
|
0, 0, /* x, y */
|
|
NULL, /* connectors */
|
|
0, /* n_connectors */
|
|
NULL /* mode */);
|
|
}
|
|
|
|
CHECK_RETURN(ret, fail_on_error);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int igt_plane_fixup_rotation(igt_plane_t *plane,
|
|
igt_pipe_t *pipe)
|
|
{
|
|
int ret;
|
|
|
|
if (!igt_plane_has_prop(plane, IGT_PLANE_ROTATION))
|
|
return 0;
|
|
|
|
LOG(pipe->display, "Fixing up initial rotation pipe %s, plane %d\n",
|
|
kmstest_pipe_name(pipe->pipe), plane->index);
|
|
|
|
/* First try the easy case, can we change rotation without problems? */
|
|
ret = igt_plane_set_property(plane, plane->props[IGT_PLANE_ROTATION],
|
|
plane->values[IGT_PLANE_ROTATION]);
|
|
if (!ret)
|
|
return 0;
|
|
|
|
/* Disable the plane, while we tinker with rotation */
|
|
ret = drmModeSetPlane(pipe->display->drm_fd,
|
|
plane->drm_plane->plane_id,
|
|
pipe->crtc_id, 0, /* fb_id */
|
|
0, /* flags */
|
|
0, 0, 0, 0, /* crtc_x, crtc_y, crtc_w, crtc_h */
|
|
IGT_FIXED(0,0), IGT_FIXED(0,0), /* src_x, src_y */
|
|
IGT_FIXED(0,0), IGT_FIXED(0,0)); /* src_w, src_h */
|
|
|
|
if (ret && plane->type != DRM_PLANE_TYPE_PRIMARY)
|
|
return ret;
|
|
|
|
/* For primary plane, fall back to disabling the crtc. */
|
|
if (ret) {
|
|
ret = drmModeSetCrtc(pipe->display->drm_fd,
|
|
pipe->crtc_id, 0, 0, 0, NULL, 0, NULL);
|
|
|
|
if (ret)
|
|
return ret;
|
|
}
|
|
|
|
/* and finally, set rotation property. */
|
|
return igt_plane_set_property(plane, plane->props[IGT_PLANE_ROTATION],
|
|
plane->values[IGT_PLANE_ROTATION]);
|
|
}
|
|
|
|
/*
|
|
* Commit position and fb changes to a plane. The value of @s will determine
|
|
* which API is used to do the programming.
|
|
*/
|
|
static int igt_plane_commit(igt_plane_t *plane,
|
|
igt_pipe_t *pipe,
|
|
enum igt_commit_style s,
|
|
bool fail_on_error)
|
|
{
|
|
if (pipe->display->first_commit || (s == COMMIT_UNIVERSAL &&
|
|
igt_plane_is_prop_changed(plane, IGT_PLANE_ROTATION))) {
|
|
int ret;
|
|
|
|
ret = igt_plane_fixup_rotation(plane, pipe);
|
|
CHECK_RETURN(ret, fail_on_error);
|
|
}
|
|
|
|
if (plane->type == DRM_PLANE_TYPE_CURSOR && s == COMMIT_LEGACY) {
|
|
return igt_cursor_commit_legacy(plane, pipe, fail_on_error);
|
|
} else if (plane->type == DRM_PLANE_TYPE_PRIMARY && s == COMMIT_LEGACY) {
|
|
return igt_primary_plane_commit_legacy(plane, pipe,
|
|
fail_on_error);
|
|
} else {
|
|
return igt_drm_plane_commit(plane, pipe, fail_on_error);
|
|
}
|
|
}
|
|
|
|
static bool is_atomic_prop(enum igt_atomic_crtc_properties prop)
|
|
{
|
|
if (prop == IGT_CRTC_MODE_ID ||
|
|
prop == IGT_CRTC_ACTIVE ||
|
|
prop == IGT_CRTC_OUT_FENCE_PTR)
|
|
return true;
|
|
|
|
return false;
|
|
}
|
|
|
|
/*
|
|
* Commit all plane changes to an output. Note that if @s is COMMIT_LEGACY,
|
|
* enabling/disabling the primary plane will also enable/disable the CRTC.
|
|
*
|
|
* If @fail_on_error is true, any failure to commit plane state will lead
|
|
* to subtest failure in the specific function where the failure occurs.
|
|
* Otherwise, the first error code encountered will be returned and no
|
|
* further programming will take place, which may result in some changes
|
|
* taking effect and others not taking effect.
|
|
*/
|
|
static int igt_pipe_commit(igt_pipe_t *pipe,
|
|
enum igt_commit_style s,
|
|
bool fail_on_error)
|
|
{
|
|
int i;
|
|
int ret;
|
|
|
|
for (i = 0; i < IGT_NUM_CRTC_PROPS; i++)
|
|
if (igt_pipe_obj_is_prop_changed(pipe, i) &&
|
|
!is_atomic_prop(i)) {
|
|
igt_assert(pipe->props[i]);
|
|
|
|
ret = drmModeObjectSetProperty(pipe->display->drm_fd,
|
|
pipe->crtc_id, DRM_MODE_OBJECT_CRTC,
|
|
pipe->props[i], pipe->values[i]);
|
|
|
|
CHECK_RETURN(ret, fail_on_error);
|
|
}
|
|
|
|
for (i = 0; i < pipe->n_planes; i++) {
|
|
igt_plane_t *plane = &pipe->planes[i];
|
|
|
|
/* skip planes that are handled by another pipe */
|
|
if (plane->ref->pipe != pipe)
|
|
continue;
|
|
|
|
ret = igt_plane_commit(plane, pipe, s, fail_on_error);
|
|
CHECK_RETURN(ret, fail_on_error);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int igt_output_commit(igt_output_t *output,
|
|
enum igt_commit_style s,
|
|
bool fail_on_error)
|
|
{
|
|
int i, ret;
|
|
|
|
for (i = 0; i < IGT_NUM_CONNECTOR_PROPS; i++) {
|
|
if (!igt_output_is_prop_changed(output, i))
|
|
continue;
|
|
|
|
/* CRTC_ID is set by calling drmModeSetCrtc in the legacy path. */
|
|
if (i == IGT_CONNECTOR_CRTC_ID)
|
|
continue;
|
|
|
|
igt_assert(output->props[i]);
|
|
|
|
if (s == COMMIT_LEGACY)
|
|
ret = drmModeConnectorSetProperty(output->display->drm_fd, output->id,
|
|
output->props[i], output->values[i]);
|
|
else
|
|
ret = drmModeObjectSetProperty(output->display->drm_fd, output->id,
|
|
DRM_MODE_OBJECT_CONNECTOR,
|
|
output->props[i], output->values[i]);
|
|
|
|
CHECK_RETURN(ret, fail_on_error);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static uint64_t igt_mode_object_get_prop(igt_display_t *display,
|
|
uint32_t object_type,
|
|
uint32_t object_id,
|
|
uint32_t prop)
|
|
{
|
|
drmModeObjectPropertiesPtr proplist;
|
|
bool found = false;
|
|
int i;
|
|
uint64_t ret;
|
|
|
|
proplist = drmModeObjectGetProperties(display->drm_fd, object_id, object_type);
|
|
for (i = 0; i < proplist->count_props; i++) {
|
|
if (proplist->props[i] != prop)
|
|
continue;
|
|
|
|
found = true;
|
|
break;
|
|
}
|
|
|
|
igt_assert(found);
|
|
|
|
ret = proplist->prop_values[i];
|
|
|
|
drmModeFreeObjectProperties(proplist);
|
|
return ret;
|
|
}
|
|
|
|
/**
|
|
* igt_plane_get_prop:
|
|
* @plane: Target plane.
|
|
* @prop: Property to check.
|
|
*
|
|
* Return current value on a plane for a given property.
|
|
*
|
|
* Returns: The value the property is set to, if this
|
|
* is a blob, the blob id is returned. This can be passed
|
|
* to drmModeGetPropertyBlob() to get the contents of the blob.
|
|
*/
|
|
uint64_t igt_plane_get_prop(igt_plane_t *plane, enum igt_atomic_plane_properties prop)
|
|
{
|
|
igt_assert(igt_plane_has_prop(plane, prop));
|
|
|
|
return igt_mode_object_get_prop(plane->pipe->display, DRM_MODE_OBJECT_PLANE,
|
|
plane->drm_plane->plane_id, plane->props[prop]);
|
|
}
|
|
|
|
static bool igt_mode_object_get_prop_enum_value(int drm_fd, uint32_t id, const char *str, uint64_t *val)
|
|
{
|
|
drmModePropertyPtr prop = drmModeGetProperty(drm_fd, id);
|
|
int i;
|
|
|
|
igt_assert(id);
|
|
igt_assert(prop);
|
|
|
|
for (i = 0; i < prop->count_enums; i++)
|
|
if (!strcmp(str, prop->enums[i].name)) {
|
|
*val = prop->enums[i].value;
|
|
drmModeFreeProperty(prop);
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
bool igt_plane_try_prop_enum(igt_plane_t *plane,
|
|
enum igt_atomic_plane_properties prop,
|
|
const char *val)
|
|
{
|
|
igt_display_t *display = plane->pipe->display;
|
|
uint64_t uval;
|
|
|
|
igt_assert(plane->props[prop]);
|
|
|
|
if (!igt_mode_object_get_prop_enum_value(display->drm_fd,
|
|
plane->props[prop], val, &uval))
|
|
return false;
|
|
|
|
igt_plane_set_prop_value(plane, prop, uval);
|
|
return true;
|
|
}
|
|
|
|
void igt_plane_set_prop_enum(igt_plane_t *plane,
|
|
enum igt_atomic_plane_properties prop,
|
|
const char *val)
|
|
{
|
|
igt_assert(igt_plane_try_prop_enum(plane, prop, val));
|
|
}
|
|
|
|
/**
|
|
* igt_plane_replace_prop_blob:
|
|
* @plane: plane to set property on.
|
|
* @prop: property for which the blob will be replaced.
|
|
* @ptr: Pointer to contents for the property.
|
|
* @length: Length of contents.
|
|
*
|
|
* This function will destroy the old property blob for the given property,
|
|
* and will create a new property blob with the values passed to this function.
|
|
*
|
|
* The new property blob will be committed when you call igt_display_commit(),
|
|
* igt_display_commit2() or igt_display_commit_atomic().
|
|
*/
|
|
void
|
|
igt_plane_replace_prop_blob(igt_plane_t *plane, enum igt_atomic_plane_properties prop, const void *ptr, size_t length)
|
|
{
|
|
igt_display_t *display = plane->pipe->display;
|
|
uint64_t *blob = &plane->values[prop];
|
|
uint32_t blob_id = 0;
|
|
|
|
if (*blob != 0)
|
|
igt_assert(drmModeDestroyPropertyBlob(display->drm_fd,
|
|
*blob) == 0);
|
|
|
|
if (length > 0)
|
|
igt_assert(drmModeCreatePropertyBlob(display->drm_fd,
|
|
ptr, length, &blob_id) == 0);
|
|
|
|
*blob = blob_id;
|
|
igt_plane_set_prop_changed(plane, prop);
|
|
}
|
|
|
|
/**
|
|
* igt_output_get_prop:
|
|
* @output: Target output.
|
|
* @prop: Property to return.
|
|
*
|
|
* Return current value on an output for a given property.
|
|
*
|
|
* Returns: The value the property is set to, if this
|
|
* is a blob, the blob id is returned. This can be passed
|
|
* to drmModeGetPropertyBlob() to get the contents of the blob.
|
|
*/
|
|
uint64_t igt_output_get_prop(igt_output_t *output, enum igt_atomic_connector_properties prop)
|
|
{
|
|
igt_assert(igt_output_has_prop(output, prop));
|
|
|
|
return igt_mode_object_get_prop(output->display, DRM_MODE_OBJECT_CONNECTOR,
|
|
output->id, output->props[prop]);
|
|
}
|
|
|
|
bool igt_output_try_prop_enum(igt_output_t *output,
|
|
enum igt_atomic_connector_properties prop,
|
|
const char *val)
|
|
{
|
|
igt_display_t *display = output->display;
|
|
uint64_t uval;
|
|
|
|
igt_assert(output->props[prop]);
|
|
|
|
if (!igt_mode_object_get_prop_enum_value(display->drm_fd,
|
|
output->props[prop], val, &uval))
|
|
return false;
|
|
|
|
igt_output_set_prop_value(output, prop, uval);
|
|
return true;
|
|
}
|
|
|
|
void igt_output_set_prop_enum(igt_output_t *output,
|
|
enum igt_atomic_connector_properties prop,
|
|
const char *val)
|
|
{
|
|
igt_assert(igt_output_try_prop_enum(output, prop, val));
|
|
}
|
|
|
|
/**
|
|
* igt_output_replace_prop_blob:
|
|
* @output: output to set property on.
|
|
* @prop: property for which the blob will be replaced.
|
|
* @ptr: Pointer to contents for the property.
|
|
* @length: Length of contents.
|
|
*
|
|
* This function will destroy the old property blob for the given property,
|
|
* and will create a new property blob with the values passed to this function.
|
|
*
|
|
* The new property blob will be committed when you call igt_display_commit(),
|
|
* igt_display_commit2() or igt_display_commit_atomic().
|
|
*/
|
|
void
|
|
igt_output_replace_prop_blob(igt_output_t *output, enum igt_atomic_connector_properties prop, const void *ptr, size_t length)
|
|
{
|
|
igt_display_t *display = output->display;
|
|
uint64_t *blob = &output->values[prop];
|
|
uint32_t blob_id = 0;
|
|
|
|
if (*blob != 0)
|
|
igt_assert(drmModeDestroyPropertyBlob(display->drm_fd,
|
|
*blob) == 0);
|
|
|
|
if (length > 0)
|
|
igt_assert(drmModeCreatePropertyBlob(display->drm_fd,
|
|
ptr, length, &blob_id) == 0);
|
|
|
|
*blob = blob_id;
|
|
igt_output_set_prop_changed(output, prop);
|
|
}
|
|
|
|
/**
|
|
* igt_pipe_obj_get_prop:
|
|
* @pipe: Target pipe.
|
|
* @prop: Property to return.
|
|
*
|
|
* Return current value on a pipe for a given property.
|
|
*
|
|
* Returns: The value the property is set to, if this
|
|
* is a blob, the blob id is returned. This can be passed
|
|
* to drmModeGetPropertyBlob() to get the contents of the blob.
|
|
*/
|
|
uint64_t igt_pipe_obj_get_prop(igt_pipe_t *pipe, enum igt_atomic_crtc_properties prop)
|
|
{
|
|
igt_assert(igt_pipe_obj_has_prop(pipe, prop));
|
|
|
|
return igt_mode_object_get_prop(pipe->display, DRM_MODE_OBJECT_CRTC,
|
|
pipe->crtc_id, pipe->props[prop]);
|
|
}
|
|
|
|
bool igt_pipe_obj_try_prop_enum(igt_pipe_t *pipe_obj,
|
|
enum igt_atomic_crtc_properties prop,
|
|
const char *val)
|
|
{
|
|
igt_display_t *display = pipe_obj->display;
|
|
uint64_t uval;
|
|
|
|
igt_assert(pipe_obj->props[prop]);
|
|
|
|
if (!igt_mode_object_get_prop_enum_value(display->drm_fd,
|
|
pipe_obj->props[prop], val, &uval))
|
|
return false;
|
|
|
|
igt_pipe_obj_set_prop_value(pipe_obj, prop, uval);
|
|
return true;
|
|
}
|
|
|
|
void igt_pipe_obj_set_prop_enum(igt_pipe_t *pipe_obj,
|
|
enum igt_atomic_crtc_properties prop,
|
|
const char *val)
|
|
{
|
|
igt_assert(igt_pipe_obj_try_prop_enum(pipe_obj, prop, val));
|
|
}
|
|
|
|
/**
|
|
* igt_pipe_obj_replace_prop_blob:
|
|
* @pipe: pipe to set property on.
|
|
* @prop: property for which the blob will be replaced.
|
|
* @ptr: Pointer to contents for the property.
|
|
* @length: Length of contents.
|
|
*
|
|
* This function will destroy the old property blob for the given property,
|
|
* and will create a new property blob with the values passed to this function.
|
|
*
|
|
* The new property blob will be committed when you call igt_display_commit(),
|
|
* igt_display_commit2() or igt_display_commit_atomic().
|
|
*
|
|
* Please use igt_output_override_mode() if you want to set #IGT_CRTC_MODE_ID,
|
|
* it works better with legacy commit.
|
|
*/
|
|
void
|
|
igt_pipe_obj_replace_prop_blob(igt_pipe_t *pipe, enum igt_atomic_crtc_properties prop, const void *ptr, size_t length)
|
|
{
|
|
igt_display_t *display = pipe->display;
|
|
uint64_t *blob = &pipe->values[prop];
|
|
uint32_t blob_id = 0;
|
|
|
|
if (*blob != 0)
|
|
igt_assert(drmModeDestroyPropertyBlob(display->drm_fd,
|
|
*blob) == 0);
|
|
|
|
if (length > 0)
|
|
igt_assert(drmModeCreatePropertyBlob(display->drm_fd,
|
|
ptr, length, &blob_id) == 0);
|
|
|
|
*blob = blob_id;
|
|
igt_pipe_obj_set_prop_changed(pipe, prop);
|
|
}
|
|
|
|
/*
|
|
* Add crtc property changes to the atomic property set
|
|
*/
|
|
static void igt_atomic_prepare_crtc_commit(igt_pipe_t *pipe_obj, drmModeAtomicReq *req)
|
|
{
|
|
int i;
|
|
|
|
for (i = 0; i < IGT_NUM_CRTC_PROPS; i++) {
|
|
if (!igt_pipe_obj_is_prop_changed(pipe_obj, i))
|
|
continue;
|
|
|
|
igt_debug("Pipe %s: Setting property \"%s\" to 0x%"PRIx64"/%"PRIi64"\n",
|
|
kmstest_pipe_name(pipe_obj->pipe), igt_crtc_prop_names[i],
|
|
pipe_obj->values[i], pipe_obj->values[i]);
|
|
|
|
igt_assert_lt(0, drmModeAtomicAddProperty(req, pipe_obj->crtc_id, pipe_obj->props[i], pipe_obj->values[i]));
|
|
}
|
|
|
|
if (pipe_obj->out_fence_fd != -1) {
|
|
close(pipe_obj->out_fence_fd);
|
|
pipe_obj->out_fence_fd = -1;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Add connector property changes to the atomic property set
|
|
*/
|
|
static void igt_atomic_prepare_connector_commit(igt_output_t *output, drmModeAtomicReq *req)
|
|
{
|
|
|
|
int i;
|
|
|
|
for (i = 0; i < IGT_NUM_CONNECTOR_PROPS; i++) {
|
|
if (!igt_output_is_prop_changed(output, i))
|
|
continue;
|
|
|
|
/* it's an error to try an unsupported feature */
|
|
igt_assert(output->props[i]);
|
|
|
|
igt_debug("%s: Setting property \"%s\" to 0x%"PRIx64"/%"PRIi64"\n",
|
|
igt_output_name(output), igt_connector_prop_names[i],
|
|
output->values[i], output->values[i]);
|
|
|
|
igt_assert_lt(0, drmModeAtomicAddProperty(req,
|
|
output->config.connector->connector_id,
|
|
output->props[i],
|
|
output->values[i]));
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Commit all the changes of all the planes,crtcs, connectors
|
|
* atomically using drmModeAtomicCommit()
|
|
*/
|
|
static int igt_atomic_commit(igt_display_t *display, uint32_t flags, void *user_data)
|
|
{
|
|
|
|
int ret = 0, i;
|
|
enum pipe pipe;
|
|
drmModeAtomicReq *req;
|
|
igt_output_t *output;
|
|
|
|
if (display->is_atomic != 1)
|
|
return -1;
|
|
req = drmModeAtomicAlloc();
|
|
|
|
for_each_pipe(display, pipe) {
|
|
igt_pipe_t *pipe_obj = &display->pipes[pipe];
|
|
igt_plane_t *plane;
|
|
|
|
/*
|
|
* Add CRTC Properties to the property set
|
|
*/
|
|
if (pipe_obj->changed)
|
|
igt_atomic_prepare_crtc_commit(pipe_obj, req);
|
|
|
|
for_each_plane_on_pipe(display, pipe, plane) {
|
|
/* skip planes that are handled by another pipe */
|
|
if (plane->ref->pipe != pipe_obj)
|
|
continue;
|
|
|
|
if (plane->changed)
|
|
igt_atomic_prepare_plane_commit(plane, pipe_obj, req);
|
|
}
|
|
|
|
}
|
|
|
|
for (i = 0; i < display->n_outputs; i++) {
|
|
output = &display->outputs[i];
|
|
|
|
if (!output->config.connector || !output->changed)
|
|
continue;
|
|
|
|
LOG(display, "%s: preparing atomic, pipe: %s\n",
|
|
igt_output_name(output),
|
|
kmstest_pipe_name(output->config.pipe));
|
|
|
|
igt_atomic_prepare_connector_commit(output, req);
|
|
}
|
|
|
|
ret = drmModeAtomicCommit(display->drm_fd, req, flags, user_data);
|
|
|
|
drmModeAtomicFree(req);
|
|
return ret;
|
|
|
|
}
|
|
|
|
static void
|
|
display_commit_changed(igt_display_t *display, enum igt_commit_style s)
|
|
{
|
|
int i;
|
|
enum pipe pipe;
|
|
|
|
for_each_pipe(display, pipe) {
|
|
igt_pipe_t *pipe_obj = &display->pipes[pipe];
|
|
igt_plane_t *plane;
|
|
|
|
if (s == COMMIT_ATOMIC) {
|
|
if (igt_pipe_obj_is_prop_changed(pipe_obj, IGT_CRTC_OUT_FENCE_PTR))
|
|
igt_assert(pipe_obj->out_fence_fd >= 0);
|
|
|
|
pipe_obj->values[IGT_CRTC_OUT_FENCE_PTR] = 0;
|
|
pipe_obj->changed = 0;
|
|
} else {
|
|
for (i = 0; i < IGT_NUM_CRTC_PROPS; i++)
|
|
if (!is_atomic_prop(i))
|
|
igt_pipe_obj_clear_prop_changed(pipe_obj, i);
|
|
|
|
if (s != COMMIT_UNIVERSAL) {
|
|
igt_pipe_obj_clear_prop_changed(pipe_obj, IGT_CRTC_MODE_ID);
|
|
igt_pipe_obj_clear_prop_changed(pipe_obj, IGT_CRTC_ACTIVE);
|
|
}
|
|
}
|
|
|
|
for_each_plane_on_pipe(display, pipe, plane) {
|
|
if (s == COMMIT_ATOMIC) {
|
|
int fd;
|
|
plane->changed = 0;
|
|
|
|
fd = plane->values[IGT_PLANE_IN_FENCE_FD];
|
|
if (fd != -1)
|
|
close(fd);
|
|
|
|
/* reset fence_fd to prevent it from being set for the next commit */
|
|
plane->values[IGT_PLANE_IN_FENCE_FD] = -1;
|
|
} else {
|
|
plane->changed &= ~IGT_PLANE_COORD_CHANGED_MASK;
|
|
|
|
igt_plane_clear_prop_changed(plane, IGT_PLANE_CRTC_ID);
|
|
igt_plane_clear_prop_changed(plane, IGT_PLANE_FB_ID);
|
|
|
|
if (s != COMMIT_LEGACY ||
|
|
!(plane->type == DRM_PLANE_TYPE_PRIMARY ||
|
|
plane->type == DRM_PLANE_TYPE_CURSOR))
|
|
plane->changed &= ~LEGACY_PLANE_COMMIT_MASK;
|
|
|
|
if (display->first_commit)
|
|
igt_plane_clear_prop_changed(plane, IGT_PLANE_ROTATION);
|
|
}
|
|
}
|
|
}
|
|
|
|
for (i = 0; i < display->n_outputs; i++) {
|
|
igt_output_t *output = &display->outputs[i];
|
|
|
|
if (s != COMMIT_UNIVERSAL)
|
|
output->changed = 0;
|
|
else
|
|
/* no modeset in universal commit, no change to crtc. */
|
|
output->changed &= 1 << IGT_CONNECTOR_CRTC_ID;
|
|
}
|
|
|
|
if (display->first_commit) {
|
|
igt_reset_fifo_underrun_reporting(display->drm_fd);
|
|
|
|
igt_display_drop_events(display);
|
|
|
|
display->first_commit = false;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Commit all plane changes across all outputs of the display.
|
|
*
|
|
* If @fail_on_error is true, any failure to commit plane state will lead
|
|
* to subtest failure in the specific function where the failure occurs.
|
|
* Otherwise, the first error code encountered will be returned and no
|
|
* further programming will take place, which may result in some changes
|
|
* taking effect and others not taking effect.
|
|
*/
|
|
static int do_display_commit(igt_display_t *display,
|
|
enum igt_commit_style s,
|
|
bool fail_on_error)
|
|
{
|
|
int i, ret = 0;
|
|
enum pipe pipe;
|
|
LOG_INDENT(display, "commit");
|
|
|
|
/* someone managed to bypass igt_display_require, catch them */
|
|
assert(display->n_pipes && display->n_outputs);
|
|
|
|
igt_display_refresh(display);
|
|
|
|
if (s == COMMIT_ATOMIC) {
|
|
ret = igt_atomic_commit(display, DRM_MODE_ATOMIC_ALLOW_MODESET, NULL);
|
|
} else {
|
|
for_each_pipe(display, pipe) {
|
|
igt_pipe_t *pipe_obj = &display->pipes[pipe];
|
|
|
|
ret = igt_pipe_commit(pipe_obj, s, fail_on_error);
|
|
if (ret)
|
|
break;
|
|
}
|
|
|
|
for (i = 0; !ret && i < display->n_outputs; i++)
|
|
ret = igt_output_commit(&display->outputs[i], s, fail_on_error);
|
|
}
|
|
|
|
LOG_UNINDENT(display);
|
|
CHECK_RETURN(ret, fail_on_error);
|
|
|
|
display_commit_changed(display, s);
|
|
|
|
igt_debug_wait_for_keypress("modeset");
|
|
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* igt_display_try_commit_atomic:
|
|
* @display: #igt_display_t to commit.
|
|
* @flags: Flags passed to drmModeAtomicCommit.
|
|
* @user_data: User defined pointer passed to drmModeAtomicCommit.
|
|
*
|
|
* This function is similar to #igt_display_try_commit2, but is
|
|
* used when you want to pass different flags to the actual commit.
|
|
*
|
|
* Useful flags can be DRM_MODE_ATOMIC_ALLOW_MODESET,
|
|
* DRM_MODE_ATOMIC_NONBLOCK, DRM_MODE_PAGE_FLIP_EVENT,
|
|
* or DRM_MODE_ATOMIC_TEST_ONLY.
|
|
*
|
|
* @user_data is returned in the event if you pass
|
|
* DRM_MODE_PAGE_FLIP_EVENT to @flags.
|
|
*
|
|
* This function will return an error if commit fails, instead of
|
|
* aborting the test.
|
|
*/
|
|
int igt_display_try_commit_atomic(igt_display_t *display, uint32_t flags, void *user_data)
|
|
{
|
|
int ret;
|
|
|
|
/* someone managed to bypass igt_display_require, catch them */
|
|
assert(display->n_pipes && display->n_outputs);
|
|
|
|
LOG_INDENT(display, "commit");
|
|
|
|
igt_display_refresh(display);
|
|
|
|
ret = igt_atomic_commit(display, flags, user_data);
|
|
|
|
LOG_UNINDENT(display);
|
|
|
|
if (ret || (flags & DRM_MODE_ATOMIC_TEST_ONLY))
|
|
return ret;
|
|
|
|
if (display->first_commit)
|
|
igt_fail_on_f(flags & (DRM_MODE_PAGE_FLIP_EVENT | DRM_MODE_ATOMIC_NONBLOCK),
|
|
"First commit has to drop all stale events\n");
|
|
|
|
display_commit_changed(display, COMMIT_ATOMIC);
|
|
|
|
igt_debug_wait_for_keypress("modeset");
|
|
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* igt_display_commit_atomic:
|
|
* @display: #igt_display_t to commit.
|
|
* @flags: Flags passed to drmModeAtomicCommit.
|
|
* @user_data: User defined pointer passed to drmModeAtomicCommit.
|
|
*
|
|
* This function is similar to #igt_display_commit2, but is
|
|
* used when you want to pass different flags to the actual commit.
|
|
*
|
|
* Useful flags can be DRM_MODE_ATOMIC_ALLOW_MODESET,
|
|
* DRM_MODE_ATOMIC_NONBLOCK, DRM_MODE_PAGE_FLIP_EVENT,
|
|
* or DRM_MODE_ATOMIC_TEST_ONLY.
|
|
*
|
|
* @user_data is returned in the event if you pass
|
|
* DRM_MODE_PAGE_FLIP_EVENT to @flags.
|
|
*
|
|
* This function will abort the test if commit fails.
|
|
*/
|
|
void igt_display_commit_atomic(igt_display_t *display, uint32_t flags, void *user_data)
|
|
{
|
|
int ret = igt_display_try_commit_atomic(display, flags, user_data);
|
|
|
|
igt_assert_eq(ret, 0);
|
|
}
|
|
|
|
/**
|
|
* igt_display_commit2:
|
|
* @display: DRM device handle
|
|
* @s: Commit style
|
|
*
|
|
* Commits framebuffer and positioning changes to all planes of each display
|
|
* pipe, using a specific API to perform the programming. This function should
|
|
* be used to exercise a specific driver programming API; igt_display_commit
|
|
* should be used instead if the API used is unimportant to the test being run.
|
|
*
|
|
* This function should only be used to commit changes that are expected to
|
|
* succeed, since any failure during the commit process will cause the IGT
|
|
* subtest to fail. To commit changes that are expected to fail, use
|
|
* @igt_try_display_commit2 instead.
|
|
*
|
|
* Returns: 0 upon success. This function will never return upon failure
|
|
* since igt_fail() at lower levels will longjmp out of it.
|
|
*/
|
|
int igt_display_commit2(igt_display_t *display,
|
|
enum igt_commit_style s)
|
|
{
|
|
do_display_commit(display, s, true);
|
|
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* igt_display_try_commit2:
|
|
* @display: DRM device handle
|
|
* @s: Commit style
|
|
*
|
|
* Attempts to commit framebuffer and positioning changes to all planes of each
|
|
* display pipe. This function should be used to commit changes that are
|
|
* expected to fail, so that the error code can be checked for correctness.
|
|
* For changes that are expected to succeed, use @igt_display_commit instead.
|
|
*
|
|
* Note that in non-atomic commit styles, no display programming will be
|
|
* performed after the first failure is encountered, so only some of the
|
|
* operations requested by a test may have been completed. Tests that catch
|
|
* errors returned by this function should take care to restore the display to
|
|
* a sane state after a failure is detected.
|
|
*
|
|
* Returns: 0 upon success, otherwise the error code of the first error
|
|
* encountered.
|
|
*/
|
|
int igt_display_try_commit2(igt_display_t *display, enum igt_commit_style s)
|
|
{
|
|
return do_display_commit(display, s, false);
|
|
}
|
|
|
|
/**
|
|
* igt_display_commit:
|
|
* @display: DRM device handle
|
|
*
|
|
* Commits framebuffer and positioning changes to all planes of each display
|
|
* pipe.
|
|
*
|
|
* Returns: 0 upon success. This function will never return upon failure
|
|
* since igt_fail() at lower levels will longjmp out of it.
|
|
*/
|
|
int igt_display_commit(igt_display_t *display)
|
|
{
|
|
return igt_display_commit2(display, COMMIT_LEGACY);
|
|
}
|
|
|
|
/**
|
|
* igt_display_drop_events:
|
|
* @display: DRM device handle
|
|
*
|
|
* Nonblockingly reads all current events and drops them, for highest
|
|
* reliablility, call igt_display_commit2() first to flush all outstanding
|
|
* events.
|
|
*
|
|
* This will be called on the first commit after igt_display_reset() too,
|
|
* to make sure any stale events are flushed.
|
|
*
|
|
* Returns: Number of dropped events.
|
|
*/
|
|
int igt_display_drop_events(igt_display_t *display)
|
|
{
|
|
int ret = 0;
|
|
|
|
/* Clear all events from drm fd. */
|
|
struct pollfd pfd = {
|
|
.fd = display->drm_fd,
|
|
.events = POLLIN
|
|
};
|
|
|
|
while (poll(&pfd, 1, 0) > 0) {
|
|
struct drm_event *ev;
|
|
char buf[4096];
|
|
ssize_t retval;
|
|
|
|
retval = read(display->drm_fd, &buf, sizeof(buf));
|
|
igt_assert_lt(0, retval);
|
|
|
|
for (int i = 0; i < retval; i += ev->length) {
|
|
ev = (struct drm_event *)&buf[i];
|
|
|
|
igt_info("Dropping event type %u length %u\n", ev->type, ev->length);
|
|
igt_assert(ev->length + i <= sizeof(buf));
|
|
ret++;
|
|
}
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
/**
|
|
* igt_output_name:
|
|
* @output: Target output
|
|
*
|
|
* Returns: String representing a connector's name, e.g. "DP-1".
|
|
*/
|
|
const char *igt_output_name(igt_output_t *output)
|
|
{
|
|
return output->name;
|
|
}
|
|
|
|
/**
|
|
* igt_output_get_mode:
|
|
* @output: Target output
|
|
*
|
|
* Get the current mode of the given connector
|
|
*
|
|
* Returns: A #drmModeModeInfo struct representing the current mode
|
|
*/
|
|
drmModeModeInfo *igt_output_get_mode(igt_output_t *output)
|
|
{
|
|
if (output->use_override_mode)
|
|
return &output->override_mode;
|
|
else
|
|
return &output->config.default_mode;
|
|
}
|
|
|
|
/**
|
|
* igt_output_override_mode:
|
|
* @output: Output of which the mode will be overridden
|
|
* @mode: New mode, or NULL to disable override.
|
|
*
|
|
* Overrides the output's mode with @mode, so that it is used instead of the
|
|
* mode obtained with get connectors. Note that the mode is used without
|
|
* checking if the output supports it, so this might lead to unexpected results.
|
|
*/
|
|
void igt_output_override_mode(igt_output_t *output, const drmModeModeInfo *mode)
|
|
{
|
|
igt_pipe_t *pipe = igt_output_get_driving_pipe(output);
|
|
|
|
if (mode)
|
|
output->override_mode = *mode;
|
|
|
|
output->use_override_mode = !!mode;
|
|
|
|
if (pipe) {
|
|
if (output->display->is_atomic)
|
|
igt_pipe_obj_replace_prop_blob(pipe, IGT_CRTC_MODE_ID, igt_output_get_mode(output), sizeof(*mode));
|
|
else
|
|
igt_pipe_obj_set_prop_changed(pipe, IGT_CRTC_MODE_ID);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* igt_output_set_pipe:
|
|
* @output: Target output for which the pipe is being set to
|
|
* @pipe: Display pipe to set to
|
|
*
|
|
* This function sets a @pipe to a specific @output connector by
|
|
* setting the CRTC_ID property of the @pipe. The pipe
|
|
* is only activated for all pipes except PIPE_NONE.
|
|
*/
|
|
void igt_output_set_pipe(igt_output_t *output, enum pipe pipe)
|
|
{
|
|
igt_display_t *display = output->display;
|
|
igt_pipe_t *old_pipe = NULL, *pipe_obj = NULL;;
|
|
|
|
igt_assert(output->name);
|
|
|
|
if (output->pending_pipe != PIPE_NONE)
|
|
old_pipe = igt_output_get_driving_pipe(output);
|
|
|
|
if (pipe != PIPE_NONE)
|
|
pipe_obj = &display->pipes[pipe];
|
|
|
|
LOG(display, "%s: set_pipe(%s)\n", igt_output_name(output),
|
|
kmstest_pipe_name(pipe));
|
|
output->pending_pipe = pipe;
|
|
|
|
if (old_pipe) {
|
|
igt_output_t *old_output;
|
|
|
|
old_output = igt_pipe_get_output(old_pipe);
|
|
if (!old_output) {
|
|
if (display->is_atomic)
|
|
igt_pipe_obj_replace_prop_blob(old_pipe, IGT_CRTC_MODE_ID, NULL, 0);
|
|
else
|
|
igt_pipe_obj_set_prop_changed(old_pipe, IGT_CRTC_MODE_ID);
|
|
|
|
igt_pipe_obj_set_prop_value(old_pipe, IGT_CRTC_ACTIVE, 0);
|
|
}
|
|
}
|
|
|
|
igt_output_set_prop_value(output, IGT_CONNECTOR_CRTC_ID, pipe == PIPE_NONE ? 0 : display->pipes[pipe].crtc_id);
|
|
|
|
igt_output_refresh(output);
|
|
|
|
if (pipe_obj) {
|
|
if (display->is_atomic)
|
|
igt_pipe_obj_replace_prop_blob(pipe_obj, IGT_CRTC_MODE_ID, igt_output_get_mode(output), sizeof(drmModeModeInfo));
|
|
else
|
|
igt_pipe_obj_set_prop_changed(pipe_obj, IGT_CRTC_MODE_ID);
|
|
|
|
igt_pipe_obj_set_prop_value(pipe_obj, IGT_CRTC_ACTIVE, 1);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* igt_pipe_refresh:
|
|
* @display: a pointer to an #igt_display_t structure
|
|
* @pipe: Pipe to refresh
|
|
* @force: Should be set to true if mode_blob is no longer considered
|
|
* to be valid, for example after doing an atomic commit during fork or closing display fd.
|
|
*
|
|
* Requests the pipe to be part of the state on next update.
|
|
* This is useful when state may have been out of sync after
|
|
* a fork, or we just want to be sure the pipe is included
|
|
* in the next commit.
|
|
*/
|
|
void igt_pipe_refresh(igt_display_t *display, enum pipe pipe, bool force)
|
|
{
|
|
igt_pipe_t *pipe_obj = &display->pipes[pipe];
|
|
|
|
if (force && display->is_atomic) {
|
|
igt_output_t *output = igt_pipe_get_output(pipe_obj);
|
|
|
|
pipe_obj->values[IGT_CRTC_MODE_ID] = 0;
|
|
if (output)
|
|
igt_pipe_obj_replace_prop_blob(pipe_obj, IGT_CRTC_MODE_ID, igt_output_get_mode(output), sizeof(drmModeModeInfo));
|
|
} else
|
|
igt_pipe_obj_set_prop_changed(pipe_obj, IGT_CRTC_MODE_ID);
|
|
}
|
|
|
|
igt_plane_t *igt_output_get_plane(igt_output_t *output, int plane_idx)
|
|
{
|
|
igt_pipe_t *pipe;
|
|
|
|
pipe = igt_output_get_driving_pipe(output);
|
|
igt_assert(pipe);
|
|
|
|
return igt_pipe_get_plane(pipe, plane_idx);
|
|
}
|
|
|
|
/**
|
|
* igt_output_get_plane_type:
|
|
* @output: Target output
|
|
* @plane_type: Cursor, primary or an overlay plane
|
|
*
|
|
* Finds a valid plane type for the given @output otherwise
|
|
* the test is skipped if the right combination of @output/@plane_type is not found
|
|
*
|
|
* Returns: A #igt_plane_t structure that matches the requested plane type
|
|
*/
|
|
igt_plane_t *igt_output_get_plane_type(igt_output_t *output, int plane_type)
|
|
{
|
|
igt_pipe_t *pipe;
|
|
|
|
pipe = igt_output_get_driving_pipe(output);
|
|
igt_assert(pipe);
|
|
|
|
return igt_pipe_get_plane_type(pipe, plane_type);
|
|
}
|
|
|
|
/**
|
|
* igt_output_count_plane_type:
|
|
* @output: Target output
|
|
* @plane_type: Cursor, primary or an overlay plane
|
|
*
|
|
* Counts the number of planes of type @plane_type for the provided @output.
|
|
*
|
|
* Returns: The number of planes that match the requested plane type
|
|
*/
|
|
int igt_output_count_plane_type(igt_output_t *output, int plane_type)
|
|
{
|
|
igt_pipe_t *pipe = igt_output_get_driving_pipe(output);
|
|
igt_assert(pipe);
|
|
|
|
return igt_pipe_count_plane_type(pipe, plane_type);
|
|
}
|
|
|
|
/**
|
|
* igt_output_get_plane_type_index:
|
|
* @output: Target output
|
|
* @plane_type: Cursor, primary or an overlay plane
|
|
* @index: the index of the plane among planes of the same type
|
|
*
|
|
* Get the @index th plane of type @plane_type for the provided @output.
|
|
*
|
|
* Returns: The @index th plane that matches the requested plane type
|
|
*/
|
|
igt_plane_t *igt_output_get_plane_type_index(igt_output_t *output,
|
|
int plane_type, int index)
|
|
{
|
|
igt_pipe_t *pipe = igt_output_get_driving_pipe(output);
|
|
igt_assert(pipe);
|
|
|
|
return igt_pipe_get_plane_type_index(pipe, plane_type, index);
|
|
}
|
|
|
|
/**
|
|
* igt_plane_set_fb:
|
|
* @plane: Plane
|
|
* @fb: Framebuffer pointer
|
|
*
|
|
* Pairs a given @framebuffer to a @plane
|
|
*
|
|
* This function also sets a default size and position for the framebuffer
|
|
* to avoid crashes on applications that ignore to set these.
|
|
*/
|
|
void igt_plane_set_fb(igt_plane_t *plane, struct igt_fb *fb)
|
|
{
|
|
igt_pipe_t *pipe = plane->pipe;
|
|
igt_display_t *display = pipe->display;
|
|
|
|
LOG(display, "%s.%d: plane_set_fb(%d)\n", kmstest_pipe_name(pipe->pipe),
|
|
plane->index, fb ? fb->fb_id : 0);
|
|
|
|
igt_plane_set_prop_value(plane, IGT_PLANE_CRTC_ID, fb ? pipe->crtc_id : 0);
|
|
igt_plane_set_prop_value(plane, IGT_PLANE_FB_ID, fb ? fb->fb_id : 0);
|
|
|
|
if (plane->type == DRM_PLANE_TYPE_CURSOR && fb)
|
|
plane->gem_handle = fb->gem_handle;
|
|
else
|
|
plane->gem_handle = 0;
|
|
|
|
/* hack to keep tests working that don't call igt_plane_set_size() */
|
|
if (fb) {
|
|
/* set default plane size as fb size */
|
|
igt_plane_set_size(plane, fb->width, fb->height);
|
|
|
|
/* set default src pos/size as fb size */
|
|
igt_fb_set_position(fb, plane, 0, 0);
|
|
igt_fb_set_size(fb, plane, fb->width, fb->height);
|
|
|
|
if (igt_plane_has_prop(plane, IGT_PLANE_COLOR_ENCODING))
|
|
igt_plane_set_prop_enum(plane, IGT_PLANE_COLOR_ENCODING,
|
|
igt_color_encoding_to_str(fb->color_encoding));
|
|
if (igt_plane_has_prop(plane, IGT_PLANE_COLOR_RANGE))
|
|
igt_plane_set_prop_enum(plane, IGT_PLANE_COLOR_RANGE,
|
|
igt_color_range_to_str(fb->color_range));
|
|
|
|
/* Hack to prioritize the plane on the pipe that last set fb */
|
|
igt_plane_set_pipe(plane, pipe);
|
|
} else {
|
|
igt_plane_set_size(plane, 0, 0);
|
|
|
|
/* set default src pos/size as fb size */
|
|
igt_fb_set_position(fb, plane, 0, 0);
|
|
igt_fb_set_size(fb, plane, 0, 0);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* igt_plane_set_fence_fd:
|
|
* @plane: plane
|
|
* @fence_fd: fence fd, disable fence_fd by setting it to -1
|
|
*
|
|
* This function sets a fence fd to enable a commit to wait for some event to
|
|
* occur before completing.
|
|
*/
|
|
void igt_plane_set_fence_fd(igt_plane_t *plane, int fence_fd)
|
|
{
|
|
int64_t fd;
|
|
|
|
fd = plane->values[IGT_PLANE_IN_FENCE_FD];
|
|
if (fd != -1)
|
|
close(fd);
|
|
|
|
if (fence_fd != -1) {
|
|
fd = dup(fence_fd);
|
|
igt_fail_on(fd == -1);
|
|
} else
|
|
fd = -1;
|
|
|
|
igt_plane_set_prop_value(plane, IGT_PLANE_IN_FENCE_FD, fd);
|
|
}
|
|
|
|
/**
|
|
* igt_plane_set_pipe:
|
|
* @plane: Target plane pointer
|
|
* @pipe: The pipe to assign the plane to
|
|
*
|
|
*/
|
|
void igt_plane_set_pipe(igt_plane_t *plane, igt_pipe_t *pipe)
|
|
{
|
|
/*
|
|
* HACK: Point the global plane back to the local plane.
|
|
* This is used to help apply the correct atomic state while
|
|
* we're moving away from the single pipe per plane model.
|
|
*/
|
|
plane->ref->ref = plane;
|
|
plane->ref->pipe = pipe;
|
|
}
|
|
|
|
/**
|
|
* igt_plane_set_position:
|
|
* @plane: Plane pointer for which position is to be set
|
|
* @x: X coordinate
|
|
* @y: Y coordinate
|
|
*
|
|
* This function sets a new (x,y) position for the given plane.
|
|
* New position will be committed at plane commit time via drmModeSetPlane().
|
|
*/
|
|
void igt_plane_set_position(igt_plane_t *plane, int x, int y)
|
|
{
|
|
igt_pipe_t *pipe = plane->pipe;
|
|
igt_display_t *display = pipe->display;
|
|
|
|
LOG(display, "%s.%d: plane_set_position(%d,%d)\n",
|
|
kmstest_pipe_name(pipe->pipe), plane->index, x, y);
|
|
|
|
igt_plane_set_prop_value(plane, IGT_PLANE_CRTC_X, x);
|
|
igt_plane_set_prop_value(plane, IGT_PLANE_CRTC_Y, y);
|
|
}
|
|
|
|
/**
|
|
* igt_plane_set_size:
|
|
* @plane: plane pointer for which size to be set
|
|
* @w: width
|
|
* @h: height
|
|
*
|
|
* This function sets width and height for requested plane.
|
|
* New size will be committed at plane commit time via
|
|
* drmModeSetPlane().
|
|
*/
|
|
void igt_plane_set_size(igt_plane_t *plane, int w, int h)
|
|
{
|
|
igt_pipe_t *pipe = plane->pipe;
|
|
igt_display_t *display = pipe->display;
|
|
|
|
LOG(display, "%s.%d: plane_set_size (%dx%d)\n",
|
|
kmstest_pipe_name(pipe->pipe), plane->index, w, h);
|
|
|
|
igt_plane_set_prop_value(plane, IGT_PLANE_CRTC_W, w);
|
|
igt_plane_set_prop_value(plane, IGT_PLANE_CRTC_H, h);
|
|
}
|
|
|
|
/**
|
|
* igt_fb_set_position:
|
|
* @fb: framebuffer pointer
|
|
* @plane: plane
|
|
* @x: X position
|
|
* @y: Y position
|
|
*
|
|
* This function sets position for requested framebuffer as src to plane.
|
|
* New position will be committed at plane commit time via drmModeSetPlane().
|
|
*/
|
|
void igt_fb_set_position(struct igt_fb *fb, igt_plane_t *plane,
|
|
uint32_t x, uint32_t y)
|
|
{
|
|
igt_pipe_t *pipe = plane->pipe;
|
|
igt_display_t *display = pipe->display;
|
|
|
|
LOG(display, "%s.%d: fb_set_position(%d,%d)\n",
|
|
kmstest_pipe_name(pipe->pipe), plane->index, x, y);
|
|
|
|
igt_plane_set_prop_value(plane, IGT_PLANE_SRC_X, IGT_FIXED(x, 0));
|
|
igt_plane_set_prop_value(plane, IGT_PLANE_SRC_Y, IGT_FIXED(y, 0));
|
|
}
|
|
|
|
/**
|
|
* igt_fb_set_size:
|
|
* @fb: framebuffer pointer
|
|
* @plane: plane
|
|
* @w: width
|
|
* @h: height
|
|
*
|
|
* This function sets fetch rect size from requested framebuffer as src
|
|
* to plane. New size will be committed at plane commit time via
|
|
* drmModeSetPlane().
|
|
*/
|
|
void igt_fb_set_size(struct igt_fb *fb, igt_plane_t *plane,
|
|
uint32_t w, uint32_t h)
|
|
{
|
|
igt_pipe_t *pipe = plane->pipe;
|
|
igt_display_t *display = pipe->display;
|
|
|
|
LOG(display, "%s.%d: fb_set_size(%dx%d)\n",
|
|
kmstest_pipe_name(pipe->pipe), plane->index, w, h);
|
|
|
|
igt_plane_set_prop_value(plane, IGT_PLANE_SRC_W, IGT_FIXED(w, 0));
|
|
igt_plane_set_prop_value(plane, IGT_PLANE_SRC_H, IGT_FIXED(h, 0));
|
|
}
|
|
|
|
static const char *rotation_name(igt_rotation_t rotation)
|
|
{
|
|
switch (rotation & IGT_ROTATION_MASK) {
|
|
case IGT_ROTATION_0:
|
|
return "0°";
|
|
case IGT_ROTATION_90:
|
|
return "90°";
|
|
case IGT_ROTATION_180:
|
|
return "180°";
|
|
case IGT_ROTATION_270:
|
|
return "270°";
|
|
default:
|
|
igt_assert(0);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* igt_plane_set_rotation:
|
|
* @plane: Plane pointer for which rotation is to be set
|
|
* @rotation: Plane rotation value (0, 90, 180, 270)
|
|
*
|
|
* This function sets a new rotation for the requested @plane.
|
|
* New @rotation will be committed at plane commit time via
|
|
* drmModeSetPlane().
|
|
*/
|
|
void igt_plane_set_rotation(igt_plane_t *plane, igt_rotation_t rotation)
|
|
{
|
|
igt_pipe_t *pipe = plane->pipe;
|
|
igt_display_t *display = pipe->display;
|
|
|
|
LOG(display, "%s.%d: plane_set_rotation(%s)\n",
|
|
kmstest_pipe_name(pipe->pipe),
|
|
plane->index, rotation_name(rotation));
|
|
|
|
igt_plane_set_prop_value(plane, IGT_PLANE_ROTATION, rotation);
|
|
}
|
|
|
|
/**
|
|
* igt_pipe_request_out_fence:
|
|
* @pipe: pipe which out fence will be requested for
|
|
*
|
|
* Marks this pipe for requesting an out fence at the next atomic commit
|
|
* will contain the fd number of the out fence created by KMS.
|
|
*/
|
|
void igt_pipe_request_out_fence(igt_pipe_t *pipe)
|
|
{
|
|
igt_pipe_obj_set_prop_value(pipe, IGT_CRTC_OUT_FENCE_PTR, (ptrdiff_t)&pipe->out_fence_fd);
|
|
}
|
|
|
|
/**
|
|
* igt_wait_for_vblank_count:
|
|
* @drm_fd: A drm file descriptor
|
|
* @pipe: Pipe to wait_for_vblank on
|
|
* @count: Number of vblanks to wait on
|
|
*
|
|
* Waits for a given number of vertical blank intervals
|
|
*/
|
|
void igt_wait_for_vblank_count(int drm_fd, enum pipe pipe, int count)
|
|
{
|
|
drmVBlank wait_vbl;
|
|
uint32_t pipe_id_flag;
|
|
|
|
memset(&wait_vbl, 0, sizeof(wait_vbl));
|
|
pipe_id_flag = kmstest_get_vbl_flag(pipe);
|
|
|
|
wait_vbl.request.type = DRM_VBLANK_RELATIVE;
|
|
wait_vbl.request.type |= pipe_id_flag;
|
|
wait_vbl.request.sequence = count;
|
|
|
|
igt_assert(drmWaitVBlank(drm_fd, &wait_vbl) == 0);
|
|
}
|
|
|
|
/**
|
|
* igt_wait_for_vblank:
|
|
* @drm_fd: A drm file descriptor
|
|
* @pipe: Pipe to wait_for_vblank on
|
|
*
|
|
* Waits for 1 vertical blank intervals
|
|
*/
|
|
void igt_wait_for_vblank(int drm_fd, enum pipe pipe)
|
|
{
|
|
igt_wait_for_vblank_count(drm_fd, pipe, 1);
|
|
}
|
|
|
|
/**
|
|
* igt_enable_connectors:
|
|
* @drm_fd: A drm file descriptor
|
|
*
|
|
* Force connectors to be enabled where this is known to work well. Use
|
|
* #igt_reset_connectors to revert the changes.
|
|
*
|
|
* An exit handler is installed to ensure connectors are reset when the test
|
|
* exits.
|
|
*/
|
|
void igt_enable_connectors(int drm_fd)
|
|
{
|
|
drmModeRes *res;
|
|
|
|
res = drmModeGetResources(drm_fd);
|
|
if (!res)
|
|
return;
|
|
|
|
for (int i = 0; i < res->count_connectors; i++) {
|
|
drmModeConnector *c;
|
|
|
|
/* Do a probe. This may be the first action after booting */
|
|
c = drmModeGetConnector(drm_fd, res->connectors[i]);
|
|
if (!c) {
|
|
igt_warn("Could not read connector %u: %m\n", res->connectors[i]);
|
|
continue;
|
|
}
|
|
|
|
/* don't attempt to force connectors that are already connected
|
|
*/
|
|
if (c->connection == DRM_MODE_CONNECTED)
|
|
continue;
|
|
|
|
/* just enable VGA for now */
|
|
if (c->connector_type == DRM_MODE_CONNECTOR_VGA) {
|
|
if (!kmstest_force_connector(drm_fd, c, FORCE_CONNECTOR_ON))
|
|
igt_info("Unable to force state on %s-%d\n",
|
|
kmstest_connector_type_str(c->connector_type),
|
|
c->connector_type_id);
|
|
}
|
|
|
|
drmModeFreeConnector(c);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* igt_reset_connectors:
|
|
*
|
|
* Remove any forced state from the connectors.
|
|
*/
|
|
void igt_reset_connectors(void)
|
|
{
|
|
/* reset the connectors stored in forced_connectors, avoiding any
|
|
* functions that are not safe to call in signal handlers */
|
|
for (int i = 0; forced_connectors[i]; i++)
|
|
igt_sysfs_set(forced_connectors_device[i],
|
|
forced_connectors[i],
|
|
"detect");
|
|
}
|
|
|
|
#if !defined(ANDROID)
|
|
/**
|
|
* igt_watch_hotplug:
|
|
*
|
|
* Begin monitoring udev for sysfs hotplug events.
|
|
*
|
|
* Returns: a udev monitor for detecting hotplugs on
|
|
*/
|
|
struct udev_monitor *igt_watch_hotplug(void)
|
|
{
|
|
struct udev *udev;
|
|
struct udev_monitor *mon;
|
|
int ret, flags, fd;
|
|
|
|
udev = udev_new();
|
|
igt_assert(udev != NULL);
|
|
|
|
mon = udev_monitor_new_from_netlink(udev, "udev");
|
|
igt_assert(mon != NULL);
|
|
|
|
ret = udev_monitor_filter_add_match_subsystem_devtype(mon,
|
|
"drm",
|
|
"drm_minor");
|
|
igt_assert_eq(ret, 0);
|
|
ret = udev_monitor_filter_update(mon);
|
|
igt_assert_eq(ret, 0);
|
|
ret = udev_monitor_enable_receiving(mon);
|
|
igt_assert_eq(ret, 0);
|
|
|
|
/* Set the fd for udev as non blocking */
|
|
fd = udev_monitor_get_fd(mon);
|
|
flags = fcntl(fd, F_GETFL, 0);
|
|
igt_assert(flags);
|
|
|
|
flags |= O_NONBLOCK;
|
|
igt_assert_neq(fcntl(fd, F_SETFL, flags), -1);
|
|
|
|
return mon;
|
|
}
|
|
|
|
static bool event_detected(struct udev_monitor *mon, int timeout_secs,
|
|
const char *property)
|
|
{
|
|
struct udev_device *dev;
|
|
const char *hotplug_val;
|
|
struct pollfd fd = {
|
|
.fd = udev_monitor_get_fd(mon),
|
|
.events = POLLIN
|
|
};
|
|
bool hotplug_received = false;
|
|
|
|
/* Go through all of the events pending on the udev monitor. Once we
|
|
* receive a hotplug, we continue going through the rest of the events
|
|
* so that redundant hotplug events don't change the results of future
|
|
* checks
|
|
*/
|
|
while (!hotplug_received && poll(&fd, 1, timeout_secs * 1000)) {
|
|
dev = udev_monitor_receive_device(mon);
|
|
|
|
hotplug_val = udev_device_get_property_value(dev, property);
|
|
if (hotplug_val && atoi(hotplug_val) == 1)
|
|
hotplug_received = true;
|
|
|
|
udev_device_unref(dev);
|
|
}
|
|
|
|
return hotplug_received;
|
|
}
|
|
|
|
/**
|
|
* igt_hotplug_detected:
|
|
* @mon: A udev monitor initialized with #igt_watch_hotplug
|
|
* @timeout_secs: How long to wait for a hotplug event to occur.
|
|
*
|
|
* Assert that a hotplug event was received since we last checked the monitor.
|
|
*
|
|
* Returns: true if a sysfs hotplug event was received, false if we timed out
|
|
*/
|
|
bool igt_hotplug_detected(struct udev_monitor *mon, int timeout_secs)
|
|
{
|
|
return event_detected(mon, timeout_secs, "HOTPLUG");
|
|
}
|
|
|
|
/**
|
|
* igt_lease_change_detected:
|
|
* @mon: A udev monitor initialized with #igt_watch_hotplug
|
|
* @timeout_secs: How long to wait for a lease change event to occur.
|
|
*
|
|
* Assert that a lease change event was received since we last checked the monitor.
|
|
*
|
|
* Returns: true if a sysfs lease change event was received, false if we timed out
|
|
*/
|
|
bool igt_lease_change_detected(struct udev_monitor *mon, int timeout_secs)
|
|
{
|
|
return event_detected(mon, timeout_secs, "LEASE");
|
|
}
|
|
|
|
/**
|
|
* igt_flush_hotplugs:
|
|
* @mon: A udev monitor initialized with #igt_watch_hotplug
|
|
*
|
|
* Get rid of any pending hotplug events
|
|
*/
|
|
void igt_flush_hotplugs(struct udev_monitor *mon)
|
|
{
|
|
struct udev_device *dev;
|
|
|
|
while ((dev = udev_monitor_receive_device(mon)))
|
|
udev_device_unref(dev);
|
|
}
|
|
|
|
/**
|
|
* igt_cleanup_hotplug:
|
|
* @mon: A udev monitor initialized with #igt_watch_hotplug
|
|
*
|
|
* Cleanup the resources allocated by #igt_watch_hotplug
|
|
*/
|
|
void igt_cleanup_hotplug(struct udev_monitor *mon)
|
|
{
|
|
struct udev *udev = udev_monitor_get_udev(mon);
|
|
|
|
udev_monitor_unref(mon);
|
|
mon = NULL;
|
|
udev_unref(udev);
|
|
}
|
|
#endif /*!defined(ANDROID)*/
|
|
|
|
/**
|
|
* kmstest_get_vbl_flag:
|
|
* @pipe_id: Pipe to convert to flag representation.
|
|
*
|
|
* Convert a pipe id into the flag representation
|
|
* expected in DRM while processing DRM_IOCTL_WAIT_VBLANK.
|
|
*/
|
|
uint32_t kmstest_get_vbl_flag(uint32_t pipe_id)
|
|
{
|
|
if (pipe_id == 0)
|
|
return 0;
|
|
else if (pipe_id == 1)
|
|
return _DRM_VBLANK_SECONDARY;
|
|
else {
|
|
uint32_t pipe_flag = pipe_id << 1;
|
|
igt_assert(!(pipe_flag & ~DRM_VBLANK_HIGH_CRTC_MASK));
|
|
return pipe_flag;
|
|
}
|
|
}
|
|
|
|
static inline const uint32_t *
|
|
formats_ptr(const struct drm_format_modifier_blob *blob)
|
|
{
|
|
return (const uint32_t *)((const char *)blob + blob->formats_offset);
|
|
}
|
|
|
|
static inline const struct drm_format_modifier *
|
|
modifiers_ptr(const struct drm_format_modifier_blob *blob)
|
|
{
|
|
return (const struct drm_format_modifier *)((const char *)blob + blob->modifiers_offset);
|
|
}
|
|
|
|
static int igt_count_plane_format_mod(const struct drm_format_modifier_blob *blob_data)
|
|
{
|
|
const struct drm_format_modifier *modifiers;
|
|
int count = 0;
|
|
|
|
modifiers = modifiers_ptr(blob_data);
|
|
|
|
for (int i = 0; i < blob_data->count_modifiers; i++)
|
|
count += igt_hweight(modifiers[i].formats);
|
|
|
|
return count;
|
|
}
|
|
|
|
static void igt_fill_plane_format_mod(igt_display_t *display, igt_plane_t *plane)
|
|
{
|
|
const struct drm_format_modifier_blob *blob_data;
|
|
drmModePropertyBlobPtr blob;
|
|
uint64_t blob_id;
|
|
int idx = 0;
|
|
int count;
|
|
|
|
if (!igt_plane_has_prop(plane, IGT_PLANE_IN_FORMATS)) {
|
|
drmModePlanePtr p = plane->drm_plane;
|
|
|
|
count = p->count_formats;
|
|
|
|
plane->format_mod_count = count;
|
|
plane->formats = calloc(count, sizeof(plane->formats[0]));
|
|
igt_assert(plane->formats);
|
|
plane->modifiers = calloc(count, sizeof(plane->modifiers[0]));
|
|
igt_assert(plane->modifiers);
|
|
|
|
/*
|
|
* We don't know which modifiers are
|
|
* supported, so we'll assume linear only.
|
|
*/
|
|
for (int i = 0; i < count; i++) {
|
|
plane->formats[i] = p->formats[i];
|
|
plane->modifiers[i] = DRM_FORMAT_MOD_LINEAR;
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
blob_id = igt_plane_get_prop(plane, IGT_PLANE_IN_FORMATS);
|
|
|
|
blob = drmModeGetPropertyBlob(display->drm_fd, blob_id);
|
|
if (!blob)
|
|
return;
|
|
|
|
blob_data = (const struct drm_format_modifier_blob *) blob->data;
|
|
|
|
count = igt_count_plane_format_mod(blob_data);
|
|
if (!count)
|
|
return;
|
|
|
|
plane->format_mod_count = count;
|
|
plane->formats = calloc(count, sizeof(plane->formats[0]));
|
|
igt_assert(plane->formats);
|
|
plane->modifiers = calloc(count, sizeof(plane->modifiers[0]));
|
|
igt_assert(plane->modifiers);
|
|
|
|
for (int i = 0; i < blob_data->count_modifiers; i++) {
|
|
for (int j = 0; j < 64; j++) {
|
|
const struct drm_format_modifier *modifiers =
|
|
modifiers_ptr(blob_data);
|
|
const uint32_t *formats = formats_ptr(blob_data);
|
|
|
|
if (!(modifiers[i].formats & (1ULL << j)))
|
|
continue;
|
|
|
|
plane->formats[idx] = formats[modifiers[i].offset + j];
|
|
plane->modifiers[idx] = modifiers[i].modifier;
|
|
idx++;
|
|
igt_assert_lte(idx, plane->format_mod_count);
|
|
}
|
|
}
|
|
|
|
igt_assert_eq(idx, plane->format_mod_count);
|
|
}
|
|
|
|
bool igt_plane_has_format_mod(igt_plane_t *plane, uint32_t format,
|
|
uint64_t modifier)
|
|
{
|
|
int i;
|
|
|
|
for (i = 0; i < plane->format_mod_count; i++) {
|
|
if (plane->formats[i] == format &&
|
|
plane->modifiers[i] == modifier)
|
|
return true;
|
|
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
static int igt_count_display_format_mod(igt_display_t *display)
|
|
{
|
|
enum pipe pipe;
|
|
int count = 0;
|
|
|
|
for_each_pipe(display, pipe) {
|
|
igt_plane_t *plane;
|
|
|
|
for_each_plane_on_pipe(display, pipe, plane) {
|
|
count += plane->format_mod_count;
|
|
}
|
|
}
|
|
|
|
return count;
|
|
}
|
|
|
|
static void
|
|
igt_add_display_format_mod(igt_display_t *display, uint32_t format,
|
|
uint64_t modifier)
|
|
{
|
|
int i;
|
|
|
|
for (i = 0; i < display->format_mod_count; i++) {
|
|
if (display->formats[i] == format &&
|
|
display->modifiers[i] == modifier)
|
|
return;
|
|
|
|
}
|
|
|
|
display->formats[i] = format;
|
|
display->modifiers[i] = modifier;
|
|
display->format_mod_count++;
|
|
}
|
|
|
|
static void igt_fill_display_format_mod(igt_display_t *display)
|
|
{
|
|
int count = igt_count_display_format_mod(display);
|
|
enum pipe pipe;
|
|
|
|
if (!count)
|
|
return;
|
|
|
|
display->formats = calloc(count, sizeof(display->formats[0]));
|
|
igt_assert(display->formats);
|
|
display->modifiers = calloc(count, sizeof(display->modifiers[0]));
|
|
igt_assert(display->modifiers);
|
|
|
|
for_each_pipe(display, pipe) {
|
|
igt_plane_t *plane;
|
|
|
|
for_each_plane_on_pipe(display, pipe, plane) {
|
|
for (int i = 0; i < plane->format_mod_count; i++) {
|
|
igt_add_display_format_mod(display,
|
|
plane->formats[i],
|
|
plane->modifiers[i]);
|
|
igt_assert_lte(display->format_mod_count, count);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
bool igt_display_has_format_mod(igt_display_t *display, uint32_t format,
|
|
uint64_t modifier)
|
|
{
|
|
int i;
|
|
|
|
for (i = 0; i < display->format_mod_count; i++) {
|
|
if (display->formats[i] == format &&
|
|
display->modifiers[i] == modifier)
|
|
return true;
|
|
|
|
}
|
|
|
|
return false;
|
|
}
|