[Intel-gfx] [RFC i-g-t] tests: add runtime_pm
Daniel Vetter
daniel at ffwll.ch
Sun Oct 27 14:37:14 CET 2013
On Fri, Oct 25, 2013 at 11:44:05AM -0200, Paulo Zanoni wrote:
> From: Paulo Zanoni <paulo.r.zanoni at intel.com>
>
> This test is based on pc8.c. It copies most of the tests from pc8.c,
> but it depends on runtime PM status changes (parsed from sysfs)
> instead of PC8 residency changes (parsed from the MSR registers).
> There's also a test that checks for PC8 residency.
>
> For now, runtime PM and PC8 are different features, so having 2 test
> suites makes sense. In the future we'll merge both, so we'll only get
> PC8 when runtime PM is enabled, so we'll just kill pc8.c and keep
> using runtime_pm.c.
>
> Changes compared to pc8.c:
> - We now look at the runtime PM status instead of PC8 residencies
> - Added more GEM tests (mmap, pread, execbuf, stress tests)
> - Added LPSP and non-LPSP tests
> - Added tests fro sysfs and debugfs files
> - Added a test specifically for PC8
>
> Signed-off-by: Paulo Zanoni <paulo.r.zanoni at intel.com>
Since the actual tests we're running are so similar I prefer if we merge
all the runtime pm tests in one file. It makes testcase maintaince (and
bufixing, that happens) much easier. I guess a struct per runtime pm
method (pc8, D3, ...) with a few vfuncs should get things going. The
overall test would loop over all the pm methods and try to set things up.
Then loop over all subtests and either skip them all (if that particular
runtime pm method isn't supported) or just run them.
We've had a few other case of massive copy&pasting in i-g-t and in the
past few months I've merged most of them back again.
-Daniel
> ---
> tests/.gitignore | 1 +
> tests/Makefile.am | 1 +
> tests/runtime_pm.c | 1262 ++++++++++++++++++++++++++++++++++++++++++++++++++++
> 3 files changed, 1264 insertions(+)
> create mode 100644 tests/runtime_pm.c
>
> diff --git a/tests/.gitignore b/tests/.gitignore
> index 1c97a04..cda6d09 100644
> --- a/tests/.gitignore
> +++ b/tests/.gitignore
> @@ -105,6 +105,7 @@ prime_nv_pcopy
> prime_nv_test
> prime_self_import
> prime_udl
> +runtime_pm
> sysfs_rc6_residency
> sysfs_rps
> testdisplay
> diff --git a/tests/Makefile.am b/tests/Makefile.am
> index f6ba368..234aaf5 100644
> --- a/tests/Makefile.am
> +++ b/tests/Makefile.am
> @@ -59,6 +59,7 @@ TESTS_progs_M = \
> $(NOUVEAU_TESTS_M) \
> pc8 \
> prime_self_import \
> + runtime_pm \
> $(NULL)
>
> TESTS_progs = \
> diff --git a/tests/runtime_pm.c b/tests/runtime_pm.c
> new file mode 100644
> index 0000000..425e743
> --- /dev/null
> +++ b/tests/runtime_pm.c
> @@ -0,0 +1,1262 @@
> +/*
> + * 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:
> + * Paulo Zanoni <paulo.r.zanoni at intel.com>
> + *
> + */
> +
> +#include <string.h>
> +#include <stdio.h>
> +#include <stdint.h>
> +#include <stdbool.h>
> +#include <stdlib.h>
> +
> +#include <dirent.h>
> +#include <fcntl.h>
> +#include <unistd.h>
> +#include <sys/ioctl.h>
> +#include <sys/mman.h>
> +#include <sys/types.h>
> +#include <sys/stat.h>
> +#include <linux/i2c.h>
> +#include <linux/i2c-dev.h>
> +
> +#include "drmtest.h"
> +#include "intel_gpu_tools.h"
> +#include "igt_display.h"
> +
> +
> +#define MSR_PC8_RES 0x630
> +#define MSR_PC9_RES 0x631
> +#define MSR_PC10_RES 0x632
> +
> +#define POWER_DIR "/sys/devices/pci0000:00/0000:00:02.0/power"
> +
> +#define MAX_CONNECTORS 32
> +#define MAX_ENCODERS 32
> +#define MAX_CRTCS 16
> +
> +
> +enum runtime_pm_status {
> + RUNTIME_PM_STATUS_ACTIVE,
> + RUNTIME_PM_STATUS_SUSPENDED,
> + RUNTIME_PM_STATUS_SUSPENDING,
> + RUNTIME_PM_STATUS_UNKNOWN,
> +};
> +
> +enum screen_type {
> + SCREEN_TYPE_LPSP,
> + SCREEN_TYPE_NON_LPSP,
> + SCREEN_TYPE_ANY,
> +};
> +
> +/* Stuff used when creating FBs and mode setting. */
> +struct mode_set_data {
> + drmModeResPtr res;
> + drmModeConnectorPtr connectors[MAX_CONNECTORS];
> + drmModePropertyBlobPtr edids[MAX_CONNECTORS];
> +
> + uint32_t devid;
> +};
> +
> +/* Stuff we query at different times so we can compare. */
> +struct compare_data {
> + drmModeResPtr res;
> + drmModeEncoderPtr encoders[MAX_ENCODERS];
> + drmModeConnectorPtr connectors[MAX_CONNECTORS];
> + drmModeCrtcPtr crtcs[MAX_CRTCS];
> + drmModePropertyBlobPtr edids[MAX_CONNECTORS];
> +};
> +
> +
> +int drm_fd;
> +int status_fd;
> +int msr_fd = 0;
> +struct mode_set_data ms_data;
> +bool quick = false;
> +
> +
> +static uint64_t get_residency(uint32_t type)
> +{
> + int rc;
> + uint64_t ret;
> +
> + rc = pread(msr_fd, &ret, sizeof(uint64_t), type);
> + igt_assert(rc == sizeof(ret));
> +
> + return ret;
> +}
> +
> +static bool pc8_plus_residency_changed(unsigned int timeout_sec)
> +{
> + unsigned int i;
> + uint64_t res_pc8, res_pc9, res_pc10;
> + int to_sleep = 100 * 1000;
> +
> + res_pc8 = get_residency(MSR_PC8_RES);
> + res_pc9 = get_residency(MSR_PC9_RES);
> + res_pc10 = get_residency(MSR_PC10_RES);
> +
> + for (i = 0; i < timeout_sec * 1000 * 1000; i += to_sleep) {
> + if (res_pc8 != get_residency(MSR_PC8_RES) ||
> + res_pc9 != get_residency(MSR_PC9_RES) ||
> + res_pc10 != get_residency(MSR_PC10_RES)) {
> + return true;
> + }
> + usleep(to_sleep);
> + }
> +
> + return false;
> +}
> +
> +/* Checks not only if PC8+ is allowed, but also if we're reaching it.
> + * We call this when we expect this function to return quickly since PC8 is
> + * actually enabled, so the 30s timeout we use shouldn't matter. */
> +static bool pc8_plus_enabled(void)
> +{
> + return pc8_plus_residency_changed(30);
> +}
> +
> +/* We call this when we expect PC8+ to be actually disabled, so we should not
> + * return until the 5s timeout expires. In other words: in the "happy case",
> + * every time we call this function, it will sleep for 5 seconds. */
> +static bool pc8_plus_disabled(void)
> +{
> + return !pc8_plus_residency_changed(5);
> +}
> +
> +/* If the read fails, then the machine doesn't support PC8+ residencies. */
> +static bool supports_pc8_plus_residencies(void)
> +{
> + int rc;
> + uint64_t val;
> +
> + rc = pread(msr_fd, &val, sizeof(uint64_t), MSR_PC8_RES);
> + if (rc != sizeof(val))
> + return false;
> + rc = pread(msr_fd, &val, sizeof(uint64_t), MSR_PC9_RES);
> + if (rc != sizeof(val))
> + return false;
> + rc = pread(msr_fd, &val, sizeof(uint64_t), MSR_PC10_RES);
> + if (rc != sizeof(val))
> + return false;
> +
> + return true;
> +}
> +
> +static enum runtime_pm_status get_runtime_pm_status(void)
> +{
> + ssize_t n_read;
> + char buf[32];
> +
> + lseek(status_fd, 0, SEEK_SET);
> + n_read = read(status_fd, buf, ARRAY_SIZE(buf));
> + igt_assert(n_read >= 0);
> + buf[n_read] = '\0';
> +
> + if (strncmp(buf, "suspended\n", n_read) == 0)
> + return RUNTIME_PM_STATUS_SUSPENDED;
> + else if (strncmp(buf, "active\n", n_read) == 0)
> + return RUNTIME_PM_STATUS_ACTIVE;
> + else if (strncmp(buf, "suspending\n", n_read) == 0)
> + return RUNTIME_PM_STATUS_SUSPENDING;
> +
> + igt_assert_f(false, "Unknown status %s\n", buf);
> + return RUNTIME_PM_STATUS_UNKNOWN;
> +}
> +
> +static bool wait_for_status(enum runtime_pm_status status)
> +{
> + int i;
> + int hundred_ms = 100 * 1000, ten_s = 10 * 1000 * 1000;
> +
> + for (i = 0; i < ten_s; i += hundred_ms) {
> + if (get_runtime_pm_status() == status)
> + return true;
> +
> + usleep(hundred_ms);
> + }
> +
> + return false;
> +}
> +
> +static bool wait_for_suspended(void)
> +{
> + return wait_for_status(RUNTIME_PM_STATUS_SUSPENDED);
> +}
> +
> +static bool wait_for_active(void)
> +{
> + return wait_for_status(RUNTIME_PM_STATUS_ACTIVE);
> +}
> +
> +static void disable_all_screens(struct mode_set_data *data)
> +{
> + int i, rc;
> +
> + for (i = 0; i < data->res->count_crtcs; i++) {
> + rc = drmModeSetCrtc(drm_fd, data->res->crtcs[i], -1, 0, 0,
> + NULL, 0, NULL);
> + igt_assert(rc == 0);
> + }
> +}
> +
> +static uint32_t create_fb(struct mode_set_data *data, int width, int height)
> +{
> + struct kmstest_fb fb;
> + cairo_t *cr;
> + uint32_t buffer_id;
> +
> + buffer_id = kmstest_create_fb(drm_fd, width, height, 32, 24, false,
> + &fb);
> + cr = kmstest_get_cairo_ctx(drm_fd, &fb);
> + kmstest_paint_test_pattern(cr, width, height);
> + return buffer_id;
> +}
> +
> +static bool enable_one_screen_with_type(struct mode_set_data *data,
> + enum screen_type type)
> +{
> + uint32_t crtc_id = 0, buffer_id = 0, connector_id = 0;
> + drmModeModeInfoPtr mode = NULL;
> + int i, rc;
> +
> + for (i = 0; i < data->res->count_connectors; i++) {
> + drmModeConnectorPtr c = data->connectors[i];
> +
> + if (type == SCREEN_TYPE_LPSP &&
> + c->connector_type != DRM_MODE_CONNECTOR_eDP)
> + continue;
> +
> + if (type == SCREEN_TYPE_NON_LPSP &&
> + c->connector_type == DRM_MODE_CONNECTOR_eDP)
> + continue;
> +
> + if (c->connection == DRM_MODE_CONNECTED && c->count_modes) {
> + connector_id = c->connector_id;
> + mode = &c->modes[0];
> + break;
> + }
> + }
> +
> + if (connector_id == 0)
> + return false;
> +
> + crtc_id = data->res->crtcs[0];
> + buffer_id = create_fb(data, mode->hdisplay, mode->vdisplay);
> +
> + igt_assert(crtc_id);
> + igt_assert(buffer_id);
> + igt_assert(connector_id);
> + igt_assert(mode);
> +
> + rc = drmModeSetCrtc(drm_fd, crtc_id, buffer_id, 0, 0, &connector_id,
> + 1, mode);
> + igt_assert(rc == 0);
> +
> + return true;
> +}
> +
> +static void enable_one_screen(struct mode_set_data *data)
> +{
> + igt_assert(enable_one_screen_with_type(data, SCREEN_TYPE_ANY));
> +}
> +
> +/* We could check the checksum too, but just the header is probably enough. */
> +static bool edid_is_valid(const unsigned char *edid)
> +{
> + char edid_header[] = {
> + 0x0, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x0,
> + };
> +
> + return (memcmp(edid, edid_header, sizeof(edid_header)) == 0);
> +}
> +
> +static int count_drm_valid_edids(struct mode_set_data *data)
> +{
> + int i, ret = 0;
> +
> + for (i = 0; i < data->res->count_connectors; i++)
> + if (data->edids[i] && edid_is_valid(data->edids[i]->data))
> + ret++;
> + return ret;
> +}
> +
> +static bool i2c_edid_is_valid(int fd)
> +{
> + int rc;
> + unsigned char edid[128] = {};
> + struct i2c_msg msgs[] = {
> + { /* Start at 0. */
> + .addr = 0x50,
> + .flags = 0,
> + .len = 1,
> + .buf = edid,
> + }, { /* Now read the EDID. */
> + .addr = 0x50,
> + .flags = I2C_M_RD,
> + .len = 128,
> + .buf = edid,
> + }
> + };
> + struct i2c_rdwr_ioctl_data msgset = {
> + .msgs = msgs,
> + .nmsgs = 2,
> + };
> +
> + rc = ioctl(fd, I2C_RDWR, &msgset);
> + return (rc >= 0) ? edid_is_valid(edid) : false;
> +}
> +
> +static int count_i2c_valid_edids(void)
> +{
> + int fd, ret = 0;
> + DIR *dir;
> +
> + struct dirent *dirent;
> + char full_name[32];
> +
> + dir = opendir("/dev/");
> + igt_assert(dir);
> +
> + while ((dirent = readdir(dir))) {
> + if (strncmp(dirent->d_name, "i2c-", 4) == 0) {
> + snprintf(full_name, 32, "/dev/%s", dirent->d_name);
> + fd = open(full_name, O_RDWR);
> + igt_assert(fd != -1);
> + if (i2c_edid_is_valid(fd))
> + ret++;
> + close(fd);
> + }
> + }
> +
> + closedir(dir);
> +
> + return ret;
> +}
> +
> +static void test_i2c(struct mode_set_data *data)
> +{
> + int i2c_edids = count_i2c_valid_edids();
> + int drm_edids = count_drm_valid_edids(data);
> +
> + igt_assert(i2c_edids == drm_edids);
> +}
> +
> +static void i2c_subtest_check_environment(void)
> +{
> + int i2c_dev_files = 0;
> + DIR *dev_dir;
> + struct dirent *dirent;
> +
> + /* Make sure the /dev/i2c-* files exist. */
> + dev_dir = opendir("/dev");
> + igt_assert(dev_dir);
> + while ((dirent = readdir(dev_dir))) {
> + if (strncmp(dirent->d_name, "i2c-", 4) == 0)
> + i2c_dev_files++;
> + }
> + closedir(dev_dir);
> + igt_require(i2c_dev_files);
> +}
> +
> +
> +#define COMPARE(d1, d2, data) igt_assert(d1->data == d2->data)
> +#define COMPARE_ARRAY(d1, d2, size, data) do { \
> + for (i = 0; i < size; i++) \
> + igt_assert(d1->data[i] == d2->data[i]); \
> +} while (0)
> +
> +static void assert_drm_resources_equal(struct compare_data *d1,
> + struct compare_data *d2)
> +{
> + COMPARE(d1, d2, res->count_connectors);
> + COMPARE(d1, d2, res->count_encoders);
> + COMPARE(d1, d2, res->count_crtcs);
> + COMPARE(d1, d2, res->min_width);
> + COMPARE(d1, d2, res->max_width);
> + COMPARE(d1, d2, res->min_height);
> + COMPARE(d1, d2, res->max_height);
> +}
> +
> +static void assert_modes_equal(drmModeModeInfoPtr m1, drmModeModeInfoPtr m2)
> +{
> + COMPARE(m1, m2, clock);
> + COMPARE(m1, m2, hdisplay);
> + COMPARE(m1, m2, hsync_start);
> + COMPARE(m1, m2, hsync_end);
> + COMPARE(m1, m2, htotal);
> + COMPARE(m1, m2, hskew);
> + COMPARE(m1, m2, vdisplay);
> + COMPARE(m1, m2, vsync_start);
> + COMPARE(m1, m2, vsync_end);
> + COMPARE(m1, m2, vtotal);
> + COMPARE(m1, m2, vscan);
> + COMPARE(m1, m2, vrefresh);
> + COMPARE(m1, m2, flags);
> + COMPARE(m1, m2, type);
> + igt_assert(strcmp(m1->name, m2->name) == 0);
> +}
> +
> +static void assert_drm_connectors_equal(drmModeConnectorPtr c1,
> + drmModeConnectorPtr c2)
> +{
> + int i;
> +
> + COMPARE(c1, c2, connector_id);
> + COMPARE(c1, c2, connector_type);
> + COMPARE(c1, c2, connector_type_id);
> + COMPARE(c1, c2, mmWidth);
> + COMPARE(c1, c2, mmHeight);
> + COMPARE(c1, c2, count_modes);
> + COMPARE(c1, c2, count_props);
> + COMPARE(c1, c2, count_encoders);
> + COMPARE_ARRAY(c1, c2, c1->count_props, props);
> + COMPARE_ARRAY(c1, c2, c1->count_encoders, encoders);
> +
> + for (i = 0; i < c1->count_modes; i++)
> + assert_modes_equal(&c1->modes[0], &c2->modes[0]);
> +}
> +
> +static void assert_drm_encoders_equal(drmModeEncoderPtr e1,
> + drmModeEncoderPtr e2)
> +{
> + COMPARE(e1, e2, encoder_id);
> + COMPARE(e1, e2, encoder_type);
> + COMPARE(e1, e2, possible_crtcs);
> + COMPARE(e1, e2, possible_clones);
> +}
> +
> +static void assert_drm_crtcs_equal(drmModeCrtcPtr c1, drmModeCrtcPtr c2)
> +{
> + COMPARE(c1, c2, crtc_id);
> +}
> +
> +static void assert_drm_edids_equal(drmModePropertyBlobPtr e1,
> + drmModePropertyBlobPtr e2)
> +{
> + if (!e1 && !e2)
> + return;
> + igt_assert(e1 && e2);
> +
> + COMPARE(e1, e2, id);
> + COMPARE(e1, e2, length);
> +
> + igt_assert(memcmp(e1->data, e2->data, e1->length) == 0);
> +}
> +
> +static void assert_drm_infos_equal(struct compare_data *d1,
> + struct compare_data *d2)
> +{
> + int i;
> +
> + assert_drm_resources_equal(d1, d2);
> +
> + for (i = 0; i < d1->res->count_connectors; i++) {
> + assert_drm_connectors_equal(d1->connectors[i],
> + d2->connectors[i]);
> + assert_drm_edids_equal(d1->edids[i], d2->edids[i]);
> + }
> +
> + for (i = 0; i < d1->res->count_encoders; i++)
> + assert_drm_encoders_equal(d1->encoders[i], d2->encoders[i]);
> +
> + for (i = 0; i < d1->res->count_crtcs; i++)
> + assert_drm_crtcs_equal(d1->crtcs[i], d2->crtcs[i]);
> +}
> +
> +static drmModePropertyBlobPtr get_connector_edid(drmModeConnectorPtr connector,
> + int index)
> +{
> + unsigned int i;
> + drmModeObjectPropertiesPtr props;
> + drmModePropertyBlobPtr ret = NULL;
> +
> + props = drmModeObjectGetProperties(drm_fd, connector->connector_id,
> + DRM_MODE_OBJECT_CONNECTOR);
> +
> + for (i = 0; i < props->count_props; i++) {
> + drmModePropertyPtr prop = drmModeGetProperty(drm_fd,
> + props->props[i]);
> +
> + if (strcmp(prop->name, "EDID") == 0) {
> + igt_assert(prop->flags & DRM_MODE_PROP_BLOB);
> + igt_assert(prop->count_blobs == 0);
> + ret = drmModeGetPropertyBlob(drm_fd,
> + props->prop_values[i]);
> + }
> +
> + drmModeFreeProperty(prop);
> + }
> +
> + drmModeFreeObjectProperties(props);
> + return ret;
> +}
> +
> +
> +static void get_drm_info(struct compare_data *data)
> +{
> + int i;
> +
> + data->res = drmModeGetResources(drm_fd);
> + igt_assert(data->res);
> +
> + igt_assert(data->res->count_connectors <= MAX_CONNECTORS);
> + igt_assert(data->res->count_encoders <= MAX_ENCODERS);
> + igt_assert(data->res->count_crtcs <= MAX_CRTCS);
> +
> + for (i = 0; i < data->res->count_connectors; i++) {
> + data->connectors[i] = drmModeGetConnector(drm_fd,
> + data->res->connectors[i]);
> + data->edids[i] = get_connector_edid(data->connectors[i], i);
> + }
> + for (i = 0; i < data->res->count_encoders; i++)
> + data->encoders[i] = drmModeGetEncoder(drm_fd,
> + data->res->encoders[i]);
> + for (i = 0; i < data->res->count_crtcs; i++)
> + data->crtcs[i] = drmModeGetCrtc(drm_fd, data->res->crtcs[i]);
> +}
> +
> +static void free_drm_info(struct compare_data *data)
> +{
> + int i;
> +
> + for (i = 0; i < data->res->count_connectors; i++) {
> + drmModeFreeConnector(data->connectors[i]);
> + drmModeFreePropertyBlob(data->edids[i]);
> + }
> + for (i = 0; i < data->res->count_encoders; i++)
> + drmModeFreeEncoder(data->encoders[i]);
> + for (i = 0; i < data->res->count_crtcs; i++)
> + drmModeFreeCrtc(data->crtcs[i]);
> +
> + drmModeFreeResources(data->res);
> +}
> +
> +static void init_mode_set_data(struct mode_set_data *data)
> +{
> + int i;
> +
> + data->res = drmModeGetResources(drm_fd);
> + igt_assert(data->res);
> + igt_assert(data->res->count_connectors <= MAX_CONNECTORS);
> +
> + for (i = 0; i < data->res->count_connectors; i++) {
> + data->connectors[i] = drmModeGetConnector(drm_fd,
> + data->res->connectors[i]);
> + data->edids[i] = get_connector_edid(data->connectors[i], i);
> + }
> +
> + data->devid = intel_get_drm_devid(drm_fd);
> +
> + do_or_die(igt_set_vt_graphics_mode());
> +}
> +
> +static void fini_mode_set_data(struct mode_set_data *data)
> +{
> + int i;
> +
> + for (i = 0; i < data->res->count_connectors; i++) {
> + drmModeFreeConnector(data->connectors[i]);
> + drmModeFreePropertyBlob(data->edids[i]);
> + }
> + drmModeFreeResources(data->res);
> +}
> +
> +static void pc8_subtest_check_environment(void)
> +{
> + igt_require_f(IS_HASWELL(ms_data.devid),
> + "PC8+ feature only supported on Haswell.\n");
> +
> + /* If this fails, you don't have the Kernel module loaded. */
> + msr_fd = open("/dev/cpu/0/msr", O_RDONLY);
> + igt_assert_f(msr_fd >= 0, "Can't open /dev/cpu/0/msr.\n");
> +
> + /* Non-ULT machines don't support PC8+. */
> + igt_require_f(supports_pc8_plus_residencies(),
> + "Machine doesn't support PC8+ residencies.\n");
> +}
> +
> +static void assert_runtime_pm_is_enabled(void)
> +{
> + int fd;
> + ssize_t n_read;
> + char buf[6];
> + int rc;
> +
> + fd = open(POWER_DIR "/control", O_RDONLY);
> + igt_assert_f(fd >= 0, "Can't open " POWER_DIR "/control\n");
> +
> + n_read = read(fd, buf, ARRAY_SIZE(buf));
> + igt_assert(n_read >= 0);
> + igt_require_f(strncmp(buf, "auto\n", n_read) == 0,
> + "Runtime PM not enabled or supported on this platform\n");
> +
> + rc = close(fd);
> + igt_assert(rc == 0);
> +}
> +
> +/* We set 1ms autosuspend delay here so things start/stop fast and we don't
> + * waste time waiting for runtime PM to kick in. We don't care about restoring
> + * the original value at the end of the test. */
> +static void set_autosuspend_delay(void)
> +{
> + int fd;
> + ssize_t n_write;
> + int rc;
> +
> + fd = open(POWER_DIR "/autosuspend_delay_ms", O_WRONLY);
> + igt_assert_f(fd >= 0,
> + "Can't open " POWER_DIR "/autosuspend_delay_ms\n");
> +
> + n_write = write(fd, "1\n", 2);
> + igt_assert(n_write == 2);
> +
> + rc = close(fd);
> + igt_assert(rc == 0);
> +
> +}
> +
> +static void setup_environment(void)
> +{
> +
> + drm_fd = drm_open_any();
> + igt_assert(drm_fd >= 0);
> +
> + init_mode_set_data(&ms_data);
> +
> + assert_runtime_pm_is_enabled(),
> +
> + status_fd = open(POWER_DIR "/runtime_status", O_RDONLY);
> + igt_assert_f(status_fd >= 0,
> + "Can't open " POWER_DIR "/runtime_status\n");
> +
> + set_autosuspend_delay();
> +}
> +
> +static void teardown_environment(void)
> +{
> + int rc;
> +
> + fini_mode_set_data(&ms_data);
> +
> + drmClose(drm_fd);
> +
> + rc = close(status_fd);
> + igt_assert(rc == 0);
> +
> + if (msr_fd) {
> + rc = close(msr_fd);
> + igt_assert(rc == 0);
> + }
> +}
> +
> +/* Sanity test: make sure it suspends when all screens are disabled and it
> + * resumes when we enable a screen. */
> +static void basic_subtest(void)
> +{
> + disable_all_screens(&ms_data);
> + igt_assert_f(wait_for_suspended(),
> + "Graphics device is not suspending, please check its "
> + "runtime PM configuration.\n");
> +
> + enable_one_screen(&ms_data);
> + igt_assert_f(wait_for_active(),
> + "Graphics device is still suspended even with a screen "
> + "enabled. Something is really wrong.\n");
> +}
> +
> +/* Test if the DRM resources reported by the IOCTLs are still the same. This
> + * ensures we still see the monitors with the same eyes. We get the EDIDs and
> + * compare them, which ensures we use DP AUX or GMBUS depending on what's
> + * connected. */
> +static void drm_resources_subtest(void)
> +{
> + struct compare_data pre_suspend, during_suspend, post_suspend;
> +
> + enable_one_screen(&ms_data);
> + igt_assert(wait_for_active());
> + get_drm_info(&pre_suspend);
> + igt_assert(wait_for_active());
> +
> + disable_all_screens(&ms_data);
> + igt_assert(wait_for_suspended());
> + get_drm_info(&during_suspend);
> + igt_assert(wait_for_suspended());
> +
> + enable_one_screen(&ms_data);
> + igt_assert(wait_for_active());
> + get_drm_info(&post_suspend);
> + igt_assert(wait_for_active());
> +
> + assert_drm_infos_equal(&pre_suspend, &during_suspend);
> + assert_drm_infos_equal(&pre_suspend, &post_suspend);
> +
> + free_drm_info(&pre_suspend);
> + free_drm_info(&during_suspend);
> + free_drm_info(&post_suspend);
> +}
> +
> +/* Try to use raw I2C, which also needs interrupts. */
> +static void i2c_subtest(void)
> +{
> + i2c_subtest_check_environment();
> +
> + enable_one_screen(&ms_data);
> + igt_assert(wait_for_active());
> +
> + disable_all_screens(&ms_data);
> + igt_assert(wait_for_suspended());
> + test_i2c(&ms_data);
> + igt_assert(wait_for_suspended());
> +
> + enable_one_screen(&ms_data);
> + igt_assert(wait_for_active());
> +}
> +
> +/* The LPSP version tests eDP, which doesn't require the power well. The
> + * non-LPSP version will make sure we toggle the power well on/off every time.
> + * We SKIP in case we can't find the appropriate type of output connected, but
> + * that doesn't test as much as we could. */
> +static void modeset_stress(bool lpsp)
> +{
> + int i, max;
> + enum screen_type type;
> +
> + max = quick ? 5 : 50;
> + type = lpsp ? SCREEN_TYPE_LPSP : SCREEN_TYPE_NON_LPSP;
> +
> + for (i = 0; i < max; i++) {
> + disable_all_screens(&ms_data);
> + igt_assert(wait_for_suspended());
> +
> + /* If we skip this line it's because the type of screen we want
> + * is not connected. */
> + igt_require(enable_one_screen_with_type(&ms_data, type));
> + igt_assert(wait_for_active());
> + }
> +}
> +
> +/* Make sure we get to PC8 or deeper states when we're suspended. Notice that
> + * this actually depends on a properly configured machine, where the runtime PM
> + * polices for _all_ devices (not only graphics) are configured correctly. If
> + * you use "powertop" and switch everything to "good", there's a good chance
> + * you'll be fine here. */
> +static void pc8_subtest(void)
> +{
> + pc8_subtest_check_environment();
> +
> + disable_all_screens(&ms_data);
> + igt_assert(wait_for_suspended());
> + igt_assert(pc8_plus_enabled());
> +
> + enable_one_screen(&ms_data);
> + igt_assert(wait_for_active());
> + igt_assert(pc8_plus_disabled());
> +}
> +
> +static void *mmap_gem_buf(uint32_t handle, int size)
> +{
> + struct drm_i915_gem_mmap gem_mmap;
> +
> + gem_mmap.handle = handle;
> + gem_mmap.offset = 0;
> + gem_mmap.size = size;
> + do_ioctl(drm_fd, DRM_IOCTL_I915_GEM_MMAP, &gem_mmap);
> + igt_assert(gem_mmap.addr_ptr);
> +
> + return (void *) gem_mmap.addr_ptr;
> +}
> +
> +/* Make sure even mmap still works when we're in D3. */
> +static void gem_mmap_subtest(void)
> +{
> + int i;
> + uint32_t handle;
> + int buf_size = 8192;
> + uint8_t *gem_buf;
> +
> + /* Create, map and set data while the device is active. */
> + enable_one_screen(&ms_data);
> + igt_assert(wait_for_active());
> +
> + handle = gem_create(drm_fd, buf_size);
> +
> + gem_buf = mmap_gem_buf(handle, buf_size);
> +
> + for (i = 0; i < buf_size; i++)
> + gem_buf[i] = i & 0xFF;
> +
> + for (i = 0; i < buf_size; i++)
> + igt_assert(gem_buf[i] == (i & 0xFF));
> +
> + /* Now suspend, read and modify. */
> + disable_all_screens(&ms_data);
> + igt_assert(wait_for_suspended());
> +
> + for (i = 0; i < buf_size; i++)
> + igt_assert(gem_buf[i] == (i & 0xFF));
> + igt_assert(wait_for_suspended());
> +
> + for (i = 0; i < buf_size; i++)
> + gem_buf[i] = (~i & 0xFF);
> + igt_assert(wait_for_suspended());
> +
> + /* Now resume and see if it's still there. */
> + enable_one_screen(&ms_data);
> + igt_assert(wait_for_active());
> + for (i = 0; i < buf_size; i++)
> + igt_assert(gem_buf[i] == (~i & 0xFF));
> +
> + munmap(gem_buf, buf_size);
> +
> + gem_close(drm_fd, handle);
> +}
> +
> +/* Make sure pread() and pwrite() still work when we're in D3. */
> +static void gem_pread_subtest(void)
> +{
> + int i;
> + uint32_t handle;
> + int buf_size = 8192;
> + uint8_t *cpu_buf, *read_buf;
> +
> + cpu_buf = malloc(buf_size);
> + read_buf = malloc(buf_size);
> + igt_assert(cpu_buf);
> + igt_assert(read_buf);
> + memset(cpu_buf, 0, buf_size);
> + memset(read_buf, 0, buf_size);
> +
> + /* Create and set data while the device is active. */
> + enable_one_screen(&ms_data);
> + igt_assert(wait_for_active());
> +
> + handle = gem_create(drm_fd, buf_size);
> +
> + for (i = 0; i < buf_size; i++)
> + cpu_buf[i] = i & 0xFF;
> +
> + gem_write(drm_fd, handle, 0, cpu_buf, buf_size);
> +
> + gem_read(drm_fd, handle, 0, read_buf, buf_size);
> +
> + for (i = 0; i < buf_size; i++)
> + igt_assert(cpu_buf[i] == read_buf[i]);
> +
> + /* Now suspend, read and modify. */
> + disable_all_screens(&ms_data);
> + igt_assert(wait_for_suspended());
> +
> + memset(read_buf, 0, buf_size);
> + gem_read(drm_fd, handle, 0, read_buf, buf_size);
> +
> + for (i = 0; i < buf_size; i++)
> + igt_assert(cpu_buf[i] == read_buf[i]);
> + igt_assert(wait_for_suspended());
> +
> + for (i = 0; i < buf_size; i++)
> + cpu_buf[i] = (~i & 0xFF);
> + gem_write(drm_fd, handle, 0, cpu_buf, buf_size);
> + igt_assert(wait_for_suspended());
> +
> + /* Now resume and see if it's still there. */
> + enable_one_screen(&ms_data);
> + igt_assert(wait_for_active());
> +
> + memset(read_buf, 0, buf_size);
> + gem_read(drm_fd, handle, 0, read_buf, buf_size);
> +
> + for (i = 0; i < buf_size; i++)
> + igt_assert(cpu_buf[i] == read_buf[i]);
> +
> + gem_close(drm_fd, handle);
> +
> + free(cpu_buf);
> + free(read_buf);
> +}
> +
> +/* Paints a square of color $color, size $width x $height, at position $x x $y
> + * of $dst_handle, which contains pitch $pitch. */
> +static void submit_blt_cmd(uint32_t dst_handle, uint32_t x, uint32_t y,
> + uint32_t width, uint32_t height, uint32_t pitch,
> + uint32_t color, uint32_t *presumed_dst_offset)
> +{
> + int i, reloc_pos;
> + int bpp = 4;
> + uint32_t batch_handle;
> + int batch_size = 8 * sizeof(uint32_t);
> + uint32_t batch_buf[batch_size];
> + uint32_t offset_in_dst = (pitch * y) + (x * bpp);
> + struct drm_i915_gem_execbuffer2 execbuf = {};
> + struct drm_i915_gem_exec_object2 objs[2] = {{}, {}};
> + struct drm_i915_gem_relocation_entry relocs[1] = {{}};
> + struct drm_i915_gem_wait gem_wait;
> +
> + i = 0;
> +
> + batch_buf[i++] = COLOR_BLT_CMD | COLOR_BLT_WRITE_ALPHA |
> + COLOR_BLT_WRITE_RGB;
> + batch_buf[i++] = (3 << 24) | (0xF0 << 16) | pitch;
> + batch_buf[i++] = (height << 16) | width * bpp;
> + reloc_pos = i;
> + batch_buf[i++] = *presumed_dst_offset + offset_in_dst;
> + batch_buf[i++] = color;
> +
> + batch_buf[i++] = MI_NOOP;
> + batch_buf[i++] = MI_BATCH_BUFFER_END;
> + batch_buf[i++] = MI_NOOP;
> +
> + igt_assert(i * sizeof(uint32_t) == batch_size);
> +
> + batch_handle = gem_create(drm_fd, batch_size);
> + gem_write(drm_fd, batch_handle, 0, batch_buf, batch_size);
> +
> + relocs[0].target_handle = dst_handle;
> + relocs[0].delta = offset_in_dst;
> + relocs[0].offset = reloc_pos * sizeof(uint32_t);
> + relocs[0].presumed_offset = *presumed_dst_offset;
> + relocs[0].read_domains = 0;
> + relocs[0].write_domain = I915_GEM_DOMAIN_RENDER;
> +
> + objs[0].handle = dst_handle;
> + objs[0].alignment = 64;
> +
> + objs[1].handle = batch_handle;
> + objs[1].relocation_count = 1;
> + objs[1].relocs_ptr = (uint64_t) relocs;
> +
> + execbuf.buffers_ptr = (uint64_t) objs;
> + execbuf.buffer_count = 2;
> + execbuf.batch_len = batch_size;
> + execbuf.flags = I915_EXEC_BLT;
> + i915_execbuffer2_set_context_id(execbuf, 0);
> +
> + do_ioctl(drm_fd, DRM_IOCTL_I915_GEM_EXECBUFFER2, &execbuf);
> +
> + *presumed_dst_offset = relocs[0].presumed_offset;
> +
> + gem_wait.flags = 0;
> + gem_wait.timeout_ns = 10000000000LL; /* 10s */
> +
> + gem_wait.bo_handle = batch_handle;
> + do_ioctl(drm_fd, DRM_IOCTL_I915_GEM_WAIT, &gem_wait);
> +
> + gem_wait.bo_handle = dst_handle;
> + do_ioctl(drm_fd, DRM_IOCTL_I915_GEM_WAIT, &gem_wait);
> +
> + gem_close(drm_fd, batch_handle);
> +}
> +
> +/* Make sure we can submit a batch buffer and verify its result. */
> +static void gem_execbuf_subtest(void)
> +{
> + int x, y;
> + uint32_t handle;
> + int bpp = 4;
> + int pitch = 128 * bpp;
> + int dst_size = 128 * 128 * bpp; /* 128x128 square */
> + uint32_t *cpu_buf;
> + uint32_t presumed_offset = 0;
> + int sq_x = 5, sq_y = 10, sq_w = 15, sq_h = 20;
> + uint32_t color;
> +
> + /* Create and set data while the device is active. */
> + enable_one_screen(&ms_data);
> + igt_assert(wait_for_active());
> +
> + handle = gem_create(drm_fd, dst_size);
> +
> + cpu_buf = malloc(dst_size);
> + igt_assert(cpu_buf);
> + memset(cpu_buf, 0, dst_size);
> + gem_write(drm_fd, handle, 0, cpu_buf, dst_size);
> +
> + /* Now suspend and try it. */
> + disable_all_screens(&ms_data);
> + igt_assert(wait_for_suspended());
> +
> + color = 0x12345678;
> + submit_blt_cmd(handle, sq_x, sq_y, sq_w, sq_h, pitch, color,
> + &presumed_offset);
> + igt_assert(wait_for_suspended());
> +
> + gem_read(drm_fd, handle, 0, cpu_buf, dst_size);
> + igt_assert(wait_for_suspended());
> + for (y = 0; y < 128; y++) {
> + for (x = 0; x < 128; x++) {
> + uint32_t px = cpu_buf[y * 128 + x];
> +
> + if (y >= sq_y && y < (sq_y + sq_h) &&
> + x >= sq_x && x < (sq_x + sq_w))
> + igt_assert(px == color);
> + else
> + igt_assert(px == 0);
> + }
> + }
> +
> + /* Now resume and check for it again. */
> + enable_one_screen(&ms_data);
> + igt_assert(wait_for_active());
> +
> + memset(cpu_buf, 0, dst_size);
> + gem_read(drm_fd, handle, 0, cpu_buf, dst_size);
> + for (y = 0; y < 128; y++) {
> + for (x = 0; x < 128; x++) {
> + uint32_t px = cpu_buf[y * 128 + x];
> +
> + if (y >= sq_y && y < (sq_y + sq_h) &&
> + x >= sq_x && x < (sq_x + sq_w))
> + igt_assert(px == color);
> + else
> + igt_assert(px == 0);
> + }
> + }
> +
> + /* Now we'll do the opposite: do the blt while active, then read while
> + * suspended. We use the same spot, but a different color. As a bonus,
> + * we're testing the presumed_offset from the previous command. */
> + color = 0x87654321;
> + submit_blt_cmd(handle, sq_x, sq_y, sq_w, sq_h, pitch, color,
> + &presumed_offset);
> +
> + disable_all_screens(&ms_data);
> + igt_assert(wait_for_suspended());
> +
> + memset(cpu_buf, 0, dst_size);
> + gem_read(drm_fd, handle, 0, cpu_buf, dst_size);
> + for (y = 0; y < 128; y++) {
> + for (x = 0; x < 128; x++) {
> + uint32_t px = cpu_buf[y * 128 + x];
> +
> + if (y >= sq_y && y < (sq_y + sq_h) &&
> + x >= sq_x && x < (sq_x + sq_w))
> + igt_assert(px == color);
> + else
> + igt_assert(px == 0);
> + }
> + }
> +
> + gem_close(drm_fd, handle);
> +
> + free(cpu_buf);
> +}
> +
> +/* Assuming execbuf already works, let's see what happens when we force many
> + * suspend/resume cycles with commands. */
> +static void gem_execbuf_stress_subtest(void)
> +{
> + int i;
> + int max = quick ? 5 : 50;
> + int batch_size = 4 * sizeof(uint32_t);
> + uint32_t batch_buf[batch_size];
> + uint32_t handle;
> + struct drm_i915_gem_execbuffer2 execbuf = {};
> + struct drm_i915_gem_exec_object2 objs[1] = {{}};
> +
> + i = 0;
> + batch_buf[i++] = MI_NOOP;
> + batch_buf[i++] = MI_NOOP;
> + batch_buf[i++] = MI_BATCH_BUFFER_END;
> + batch_buf[i++] = MI_NOOP;
> + igt_assert(i * sizeof(uint32_t) == batch_size);
> +
> + disable_all_screens(&ms_data);
> + igt_assert(wait_for_suspended());
> +
> + handle = gem_create(drm_fd, batch_size);
> + gem_write(drm_fd, handle, 0, batch_buf, batch_size);
> +
> + objs[0].handle = handle;
> +
> + execbuf.buffers_ptr = (uint64_t) objs;
> + execbuf.buffer_count = 1;
> + execbuf.batch_len = batch_size;
> + execbuf.flags = I915_EXEC_RENDER;
> + i915_execbuffer2_set_context_id(execbuf, 0);
> +
> + for (i = 0; i < max; i++) {
> + do_ioctl(drm_fd, DRM_IOCTL_I915_GEM_EXECBUFFER2, &execbuf);
> + igt_assert(wait_for_suspended());
> + }
> +
> + gem_close(drm_fd, handle);
> +}
> +
> +static void read_full_file(const char *name)
> +{
> + int rc, fd;
> + char buf[128];
> +
> + /* Make sure we're suspended! */
> + igt_assert(wait_for_suspended());
> +
> + fd = open(name, O_RDONLY);
> + if (fd < 0)
> + return;
> +
> + do {
> + rc = read(fd, buf, ARRAY_SIZE(buf));
> + } while (rc == ARRAY_SIZE(buf));
> +
> + rc = close(fd);
> + igt_assert(rc == 0);
> +}
> +
> +static void read_files_from_dir(const char *name, int level)
> +{
> + DIR *dir;
> + struct dirent *dirent;
> + char *full_name;
> + int rc;
> +
> + dir = opendir(name);
> + igt_assert(dir);
> +
> + full_name = malloc(PATH_MAX);
> +
> + igt_assert(level < 128);
> +
> + while ((dirent = readdir(dir))) {
> + struct stat stat_buf;
> +
> + if (strcmp(dirent->d_name, ".") == 0)
> + continue;
> + if (strcmp(dirent->d_name, "..") == 0)
> + continue;
> +
> + snprintf(full_name, PATH_MAX, "%s/%s", name, dirent->d_name);
> +
> + rc = lstat(full_name, &stat_buf);
> + igt_assert(rc == 0);
> +
> + if (S_ISDIR(stat_buf.st_mode))
> + read_files_from_dir(full_name, level + 1);
> +
> + if (S_ISREG(stat_buf.st_mode))
> + read_full_file(full_name);
> + }
> +
> + free(full_name);
> + closedir(dir);
> +}
> +
> +/* This test will probably pass, with a small chance of hanging the machine in
> + * case of bugs. Many of the bugs exercised by this patch just result in dmesg
> + * errors, so a "pass" here should be confirmed by a check on dmesg. */
> +static void debugfs_read_subtest(void)
> +{
> + const char *path = "/sys/kernel/debug/dri/0";
> + DIR *dir;
> +
> + dir = opendir(path);
> + igt_require_f(dir, "Can't open the debugfs directory\n");
> + closedir(dir);
> +
> + disable_all_screens(&ms_data);
> + igt_assert(wait_for_suspended());
> +
> + read_files_from_dir(path, 0);
> +}
> +
> +/* Read the comment on debugfs_read_subtest(). */
> +static void sysfs_read_subtest(void)
> +{
> + const char *path = "/sys/devices/pci0000:00/0000:00:02.0";
> + DIR *dir;
> +
> + dir = opendir(path);
> + igt_require_f(dir, "Can't open the sysfs directory\n");
> + closedir(dir);
> +
> + disable_all_screens(&ms_data);
> + igt_assert(wait_for_suspended());
> +
> + read_files_from_dir(path, 0);
> +}
> +
> +/* Make sure we don't suspend when we have the i915_forcewake_user file open. */
> +static void debugfs_forcewake_user_subtest(void)
> +{
> + int fd, rc;
> +
> + igt_require(!(IS_GEN2(ms_data.devid) || IS_GEN3(ms_data.devid) ||
> + IS_GEN4(ms_data.devid) || IS_GEN5(ms_data.devid)));
> +
> + disable_all_screens(&ms_data);
> + igt_assert(wait_for_suspended());
> +
> + fd = open("/sys/kernel/debug/dri/0/i915_forcewake_user", O_RDONLY);
> + igt_require(fd);
> +
> + igt_assert(!wait_for_suspended());
> +
> + rc = close(fd);
> + igt_assert(rc == 0);
> +
> + igt_assert(wait_for_suspended());
> +}
> +
> +int main(int argc, char *argv[])
> +{
> + /* The --quick option makes the stress tests not so stressful. */
> + if (argc > 1 && strcmp(argv[1], "--quick") == 0)
> + quick = true;
> +
> + igt_subtest_init(argc, argv);
> +
> + igt_fixture
> + setup_environment();
> +
> + igt_subtest("basic")
> + basic_subtest();
> + igt_subtest("drm-resources")
> + drm_resources_subtest();
> + igt_subtest("i2c")
> + i2c_subtest();
> + igt_subtest("pc8")
> + pc8_subtest();
> + igt_subtest("gem-mmap")
> + gem_mmap_subtest();
> + igt_subtest("gem-pread")
> + gem_pread_subtest();
> + igt_subtest("gem-execbuf")
> + gem_execbuf_subtest();
> + igt_subtest("modeset-stress-lpsp")
> + modeset_stress(true);
> + igt_subtest("modeset-stress-non-lpsp")
> + modeset_stress(false);
> + igt_subtest("gem-execbuf-stress")
> + gem_execbuf_stress_subtest();
> + igt_subtest("debugfs-read")
> + debugfs_read_subtest();
> + igt_subtest("debugfs-forcewake-user")
> + debugfs_forcewake_user_subtest();
> + igt_subtest("sysfs-read")
> + sysfs_read_subtest();
> +
> + igt_fixture
> + teardown_environment();
> +
> + igt_exit();
> +}
> --
> 1.8.3.1
>
> _______________________________________________
> Intel-gfx mailing list
> Intel-gfx at lists.freedesktop.org
> http://lists.freedesktop.org/mailman/listinfo/intel-gfx
--
Daniel Vetter
Software Engineer, Intel Corporation
+41 (0) 79 365 57 48 - http://blog.ffwll.ch
More information about the Intel-gfx
mailing list