diff options
Diffstat (limited to 'tools')
-rw-r--r-- | tools/.gitignore | 1 | ||||
-rw-r--r-- | tools/Makefile.sources | 1 | ||||
-rw-r--r-- | tools/intel_residency.c | 708 |
3 files changed, 710 insertions, 0 deletions
diff --git a/tools/.gitignore b/tools/.gitignore index 12406527..6ca6fbe0 100644 --- a/tools/.gitignore +++ b/tools/.gitignore @@ -25,6 +25,7 @@ intel_panel_fitter intel_perf_counters intel_reg intel_reg_checker +intel_residency intel_stepping intel_watermark skl_compute_wrpll diff --git a/tools/Makefile.sources b/tools/Makefile.sources index 5c6dc935..5d5958df 100644 --- a/tools/Makefile.sources +++ b/tools/Makefile.sources @@ -29,6 +29,7 @@ bin_PROGRAMS = \ intel_panel_fitter \ intel_perf_counters \ intel_reg_checker \ + intel_residency \ intel_stepping \ intel_watermark diff --git a/tools/intel_residency.c b/tools/intel_residency.c new file mode 100644 index 00000000..7b820c11 --- /dev/null +++ b/tools/intel_residency.c @@ -0,0 +1,708 @@ +/* + * Copyright © 2016 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@intel.com> + * + */ +#include <sys/types.h> +#include <sys/stat.h> +#include <fcntl.h> +#include <unistd.h> +#include <signal.h> +#include <time.h> +#include <getopt.h> +#include "igt.h" + +#define IA32_TIME_STAMP_COUNTER 0x10 + +#define MSR_PKG_CST_CONFIG_CONTROL 0xE2 +#define PKG_CST_LIMIT_MASK 0x7 +#define PKG_CST_LIMIT_C0 0x0 +#define PKG_CST_LIMIT_C2 0x1 +#define PKG_CST_LIMIT_C3 0x2 +#define PKG_CST_LIMIT_C6 0x3 +#define PKG_CST_LIMIT_C7 0x4 +#define PKG_CST_LIMIT_C7s 0x5 +#define PKG_CST_NO_LIMIT 0x7 + +#define MSR_PKG_C2_RESIDENCY 0x60D +#define MSR_PKG_C3_RESIDENCY 0x3F8 +#define MSR_PKG_C6_RESIDENCY 0x3F9 +#define MSR_PKG_C7_RESIDENCY 0x3FA +#define MSR_PKG_C8_RESIDENCY 0x630 +#define MSR_PKG_C9_RESIDENCY 0x631 +#define MSR_PKG_C10_RESIDENCY 0x632 + +#define NUM_PC_STATES 7 + +const char *res_msr_names[] = { + "PC2", "PC3", "PC6", "PC7", "PC8", "PC9", "PC10" +}; + +const uint32_t res_msr_addrs[] = { + MSR_PKG_C2_RESIDENCY, + MSR_PKG_C3_RESIDENCY, + MSR_PKG_C6_RESIDENCY, + MSR_PKG_C7_RESIDENCY, + MSR_PKG_C8_RESIDENCY, + MSR_PKG_C9_RESIDENCY, + MSR_PKG_C10_RESIDENCY, +}; + +int msr_fd; + +uint32_t deepest_pc_state; +uint64_t idle_res; + +#define MAX_CONNECTORS 32 +#define MAX_PLANES 32 +struct { + int fd; + drmModeResPtr res; + drmModeConnectorPtr connectors[MAX_CONNECTORS]; + drm_intel_bufmgr *bufmgr; +} drm; + +struct { + uint32_t crtc_id; + uint32_t connector_id; + drmModeModeInfoPtr mode; +} modeset; + +int vblank_interval_us; +struct igt_fb fbs[2], cursor, *front_fb, *back_fb; + +struct { + int draw_size; + bool do_page_flip; + bool do_draw; + bool do_draw_and_flip; + int res_warm_time; + int res_calc_time; + int loop_inc; + char *test_name; +} opts = { + .draw_size = 0, + .do_page_flip = true, + .do_draw = true, + .do_draw_and_flip = true, + .res_warm_time = 1, + .res_calc_time = 4, + .loop_inc = 2, + .test_name = NULL, +}; + +static uint64_t msr_read(uint32_t addr) +{ + int rc; + uint64_t ret; + + rc = pread(msr_fd, &ret, sizeof(uint64_t), addr); + igt_assert(rc == sizeof(ret)); + + return ret; +} + +static void setup_msr(void) +{ +#if 0 + uint64_t control; + const char *limit; +#endif + + /* Make sure our Kernel supports MSR and the module is loaded. */ + igt_assert(system("modprobe -q msr > /dev/null 2>&1") != -1); + + msr_fd = open("/dev/cpu/0/msr", O_RDONLY); + igt_assert_f(msr_fd >= 0, + "Can't open /dev/cpu/0/msr.\n"); + +#if 0 + /* FIXME: why is this code not printing the truth? */ + control = msr_read(MSR_PKG_CST_CONFIG_CONTROL); + printf("Control: 0x016%" PRIx64 "\n", control); + switch (control & PKG_CST_LIMIT_MASK) { + case PKG_CST_LIMIT_C0: + limit = "C0"; + break; + case PKG_CST_LIMIT_C2: + limit = "C2"; + break; + case PKG_CST_LIMIT_C3: + limit = "C3"; + break; + case PKG_CST_LIMIT_C6: + limit = "C6"; + break; + case PKG_CST_LIMIT_C7: + limit = "C7"; + break; + case PKG_CST_LIMIT_C7s: + limit = "C7s"; + break; + case PKG_CST_NO_LIMIT: + limit = "no limit"; + break; + default: + limit = "unknown"; + break; + } + printf("Package C state limit: %s\n", limit); +#endif +} + +static void teardown_msr(void) +{ + close(msr_fd); +} + +static void setup_drm(void) +{ + int i; + + drm.fd = drm_open_driver_master(DRIVER_INTEL); + + drm.res = drmModeGetResources(drm.fd); + igt_assert(drm.res->count_connectors <= MAX_CONNECTORS); + + for (i = 0; i < drm.res->count_connectors; i++) + drm.connectors[i] = drmModeGetConnector(drm.fd, + drm.res->connectors[i]); + + drm.bufmgr = drm_intel_bufmgr_gem_init(drm.fd, 4096); + igt_assert(drm.bufmgr); + drm_intel_bufmgr_gem_enable_reuse(drm.bufmgr); +} + +static void teardown_drm(void) +{ + int i; + + drm_intel_bufmgr_destroy(drm.bufmgr); + + for (i = 0; i < drm.res->count_connectors; i++) + drmModeFreeConnector(drm.connectors[i]); + + drmModeFreeResources(drm.res); + close(drm.fd); +} + +static void draw_rect(struct igt_fb *fb, enum igt_draw_method method, + uint32_t color) +{ + drmModeClip clip; + int rc; + + switch (opts.draw_size) { + case 0: + clip.x1 = fb->width / 2 - 32; + clip.x2 = fb->width / 2 + 32; + clip.y1 = fb->height / 2 - 32; + clip.y2 = fb->height / 2 + 32; + break; + case 1: + clip.x1 = fb->width / 4; + clip.x2 = fb->width / 4 + fb->width / 2; + clip.y1 = fb->height / 4; + clip.y2 = fb->height / 4 + fb->height / 2; + break; + case 2: + clip.x1 = 0; + clip.x2 = fb->width; + clip.y1 = 0; + clip.y2 = fb->height; + break; + default: + igt_assert(false); + } + + igt_draw_rect_fb(drm.fd, drm.bufmgr, NULL, fb, method, clip.x1, clip.y1, + clip.x2 - clip.x1, clip.y2 - clip.y1, color); + + if (method == IGT_DRAW_MMAP_WC) { + rc = drmModeDirtyFB(drm.fd, fb->fb_id, &clip, 1); + igt_assert(rc == 0 || rc == -ENOSYS); + } +} + +static void setup_modeset(void) +{ + int i; + + for (i = 0; i < drm.res->count_connectors; i++) { + drmModeConnectorPtr c = drm.connectors[i]; + + if (c->connection == DRM_MODE_CONNECTED && + c->count_modes > 0) { + modeset.connector_id = c->connector_id; + modeset.mode = &c->modes[0]; + break; + } + } + igt_assert(i < drm.res->count_connectors); + + modeset.crtc_id = drm.res->crtcs[0]; + + for (i = 0; i < 2; i++) { + igt_create_fb(drm.fd, modeset.mode->hdisplay, + modeset.mode->vdisplay, DRM_FORMAT_XRGB8888, + LOCAL_I915_FORMAT_MOD_X_TILED, &fbs[i]); + igt_draw_fill_fb(drm.fd, &fbs[i], 0x80); + } + draw_rect(&fbs[1], IGT_DRAW_BLT, 0x800000); + + igt_create_fb(drm.fd, 64, 64, DRM_FORMAT_ARGB8888, + LOCAL_DRM_FORMAT_MOD_NONE, &cursor); + igt_draw_fill_fb(drm.fd, &cursor, 0xFF008000); +} + +static void teardown_modeset(void) +{ + igt_remove_fb(drm.fd, &fbs[0]); + igt_remove_fb(drm.fd, &fbs[1]); + igt_remove_fb(drm.fd, &cursor); +} + +static void setup_vblank_interval(void) +{ + uint64_t vrefresh, interval; + + vrefresh = ((uint64_t) modeset.mode->clock * 1000 * 1000) / + (modeset.mode->htotal * modeset.mode->vtotal); + interval = 1000000000 / vrefresh; + + vblank_interval_us = interval; + + printf("Interval between vblanks:\t%dus\n", vblank_interval_us); +} + +bool alarm_received; +static void alarm_handler(int signal) +{ + alarm_received = true; +} + +static void setup_alarm(void) +{ + struct sigaction sa; + + sa.sa_handler = alarm_handler; + sigemptyset(&sa.sa_mask); + sa.sa_flags = 0; + sigaction(SIGALRM, &sa, NULL); +} + +static void unset_mode(void) +{ + int rc; + + kmstest_unset_all_crtcs(drm.fd, drm.res); + rc = drmModeSetCursor(drm.fd, modeset.crtc_id, 0, 0, 0); + igt_assert(rc == 0); +} + +static void set_mode(void) +{ + int rc; + + front_fb = &fbs[0]; + back_fb = &fbs[1]; + rc = drmModeSetCrtc(drm.fd, modeset.crtc_id, front_fb->fb_id, 0, 0, + &modeset.connector_id, 1, modeset.mode); + igt_assert(rc == 0); + + /* TODO: it seems we need a cursor in order to reach PC7 on BDW. Why? */ + rc = drmModeMoveCursor(drm.fd, modeset.crtc_id, 0, 0); + igt_assert(rc == 0); + + rc = drmModeSetCursor(drm.fd, modeset.crtc_id, cursor.gem_handle, + cursor.width, cursor.height); + igt_assert(rc == 0); +} + +static void wait_vblanks(int n_vblanks) +{ + drmVBlank vblank; + + if (!n_vblanks) + return; + + vblank.request.type = DRM_VBLANK_RELATIVE; + vblank.request.sequence = n_vblanks; + vblank.request.signal = 0; + drmWaitVBlank(drm.fd, &vblank); +} + +static void page_flip(void) +{ + struct igt_fb *tmp_fb; + int rc; + + rc = drmModePageFlip(drm.fd, modeset.crtc_id, back_fb->fb_id, 0, NULL); + igt_assert(rc == 0); + + tmp_fb = front_fb; + front_fb = back_fb; + back_fb = tmp_fb; +} + +static void wait_until_idle(void) +{ + uint64_t tsc, pc, res; + + do { + alarm_received = false; + ualarm(500 * 1000, 0); + + tsc = msr_read(IA32_TIME_STAMP_COUNTER); + pc = msr_read(deepest_pc_state); + + while (!alarm_received) + pause(); + + pc = msr_read(deepest_pc_state) - pc; + tsc = msr_read(IA32_TIME_STAMP_COUNTER) - tsc; + + res = pc * 100 / tsc; + + /*printf("res:%02"PRIu64"\n", res);*/ + } while (res < idle_res && idle_res - res > 3); + + if (res > idle_res && res - idle_res > 3) + fprintf(stderr, "The calculated idle residency may be too low " + "(got %02"PRIu64"%%)\n", res); +} + +static uint64_t do_measurement(void (*callback)(void *ptr), void *ptr) +{ + uint64_t tsc, pc; + + wait_until_idle(); + + alarm_received = false; + alarm(opts.res_warm_time); + callback(ptr); + + alarm_received = false; + alarm(opts.res_calc_time); + + tsc = msr_read(IA32_TIME_STAMP_COUNTER); + pc = msr_read(deepest_pc_state); + + callback(ptr); + + pc = msr_read(deepest_pc_state) - pc; + tsc = msr_read(IA32_TIME_STAMP_COUNTER) - tsc; + + return pc * 100 / tsc; +} + +static void setup_idle(void) +{ + uint64_t tsc, pc[NUM_PC_STATES], res, best_res; + int pc_i, best_pc_i = 0, retries, consecutive_not_best; + + for (retries = 0; ; retries++) { + + alarm_received = false; + alarm(opts.res_warm_time); + while (!alarm_received) + pause(); + + alarm_received = false; + alarm(opts.res_calc_time); + + tsc = msr_read(IA32_TIME_STAMP_COUNTER); + for (pc_i = best_pc_i; pc_i < NUM_PC_STATES; pc_i++) + pc[pc_i] = msr_read(res_msr_addrs[pc_i]); + + while (!alarm_received) + pause(); + + for (pc_i = best_pc_i; pc_i < NUM_PC_STATES; pc_i++) + pc[pc_i] = msr_read(res_msr_addrs[pc_i]) - pc[pc_i]; + tsc = msr_read(IA32_TIME_STAMP_COUNTER) - tsc; + + for (pc_i = NUM_PC_STATES -1; pc_i >= best_pc_i; pc_i--) + if (pc[pc_i] != 0) + break; + igt_require_f(pc_i >= 0, "We're not reaching any PC states!\n"); + + res = pc[pc_i] * 100 / tsc; + + if (retries == 0 || pc_i > best_pc_i || res > best_res) { + best_pc_i = pc_i; + best_res = res; + consecutive_not_best = 0; + } else { + consecutive_not_best++; + if (consecutive_not_best > 2) + break; + } + } + + deepest_pc_state = res_msr_addrs[best_pc_i]; + idle_res = best_res; + + printf("Stable idle residency retries:\t%d\n", retries); + printf("Deepest PC state reached when idle:\t%s\n", + res_msr_names[best_pc_i]); + printf("Idle residency for this state:\t%02"PRIu64"%%\n", idle_res); +} + +static void print_result(int ops, int vblanks, uint64_t res) +{ + printf("- %02d ops every %02d vblanks:\t%02"PRIu64"%%\n", + ops, vblanks, res); + fflush(stdout); +} + +struct page_flip_data { + int n_vblanks; +}; + +static void page_flip_cb(void *ptr) +{ + struct page_flip_data *data = ptr; + + while (!alarm_received) { + page_flip(); + wait_vblanks(data->n_vblanks); + } +} + +static void page_flip_test(void) +{ + struct page_flip_data data; + int n_vblanks; + uint64_t res; + + printf("\nPage flip test:\n"); + + for (n_vblanks = 1; n_vblanks <= 64; n_vblanks *= opts.loop_inc) { + data.n_vblanks = n_vblanks; + res = do_measurement(page_flip_cb, &data); + print_result(1, n_vblanks, res); + } +} + +struct draw_data { + enum igt_draw_method method; + int n_vblanks; + int ops_per_vblank; +}; + +static void draw_cb(void *ptr) +{ + struct draw_data *data = ptr; + struct timespec req; + int i, ops; + + req.tv_sec = 0; + req.tv_nsec = vblank_interval_us * 1000 / data->ops_per_vblank; + + for (i = 0; !alarm_received; i++) { + for (ops = 0; ops < data->ops_per_vblank; ops++) { + draw_rect(front_fb, data->method, i << 8); + + /* The code that stops the callbacks relies on SIGALRM, + * so we have to use nanosleep since it doesn't use + * signals. */ + if (data->ops_per_vblank > 1) + nanosleep(&req, NULL); + } + + if (data->n_vblanks) + wait_vblanks(data->n_vblanks); + } +} + +static void draw_test(void) +{ + struct draw_data data; + enum igt_draw_method method; + int i; + uint64_t res; + + for (method = 0; method < IGT_DRAW_METHOD_COUNT; method++) { + data.method = method; + + printf("\nDraw %s test:\n", + igt_draw_get_method_name(method)); + + data.n_vblanks = 0; + for (i = 32; i >= 2; i /= opts.loop_inc) { + data.ops_per_vblank = i; + res = do_measurement(draw_cb, &data); + print_result(i, 1, res); + } + + data.ops_per_vblank = 1; + for (i = 1; i <= 64; i *= opts.loop_inc) { + data.n_vblanks = i ; + res = do_measurement(draw_cb, &data); + print_result(1, i, res); + } + } +} + +static void draw_and_flip_cb(void *ptr) +{ + struct draw_data *data = ptr; + int i, ops; + + for (i = 0; !alarm_received; i++) { + for (ops = 0; ops < data->ops_per_vblank; ops++) + draw_rect(back_fb, data->method, i << 8); + + page_flip(); + wait_vblanks(1); + } +} + +static void draw_and_flip_test(void) +{ + struct draw_data data; + enum igt_draw_method method; + int i; + uint64_t res; + + for (method = 0; method < IGT_DRAW_METHOD_COUNT; method++) { + data.method = method; + + /* Doing everything consumes too much time! */ + if (method != IGT_DRAW_MMAP_CPU && method != IGT_DRAW_BLT) + continue; + + printf("\nDraw and flip %s test:\n", + igt_draw_get_method_name(method)); + + for (i = 16; i >= 1; i /= opts.loop_inc) { + data.ops_per_vblank = 1; + res = do_measurement(draw_and_flip_cb, &data); + print_result(i, 1, res); + } + } +} + +static void parse_opts(int argc, char *argv[]) +{ + int opt; + char short_opts[] = "d:lrbw:c:i:fsn:"; + struct option long_opts[] = { + { "draw-size", required_argument, NULL, 'd'}, + { "no-flip", no_argument, NULL, 'l'}, + { "no-draw", no_argument, NULL, 'r'}, + { "no-draw-and-flip", no_argument, NULL, 'b'}, + { "warm-time", required_argument, NULL, 'w'}, + { "calc-time", required_argument, NULL, 'c'}, + { "loop-increment", required_argument, NULL, 'i'}, + { "fast", no_argument, NULL, 'f'}, + { "slow", no_argument, NULL, 's'}, + { "name", required_argument, NULL, 'n'}, + { 0 }, + }; + + while (1) { + opt = getopt_long(argc, argv, short_opts, long_opts, NULL); + + switch (opt) { + case 'd': + if (strcmp(optarg, "s") == 0) + opts.draw_size = 0; + else if (strcmp(optarg, "m") == 0) + opts.draw_size = 1; + else if (strcmp(optarg, "l") == 0) + opts.draw_size = 2; + else + igt_assert(false); + break; + case 'l': + opts.do_page_flip = false; + break; + case 'r': + opts.do_draw = false; + break; + case 'b': + opts.do_draw_and_flip = false; + break; + case 'w': + opts.res_warm_time = atoi(optarg); + break; + case 'c': + opts.res_calc_time = atoi(optarg); + break; + case 'i': + opts.loop_inc = atoi(optarg); + break; + case 'f': + opts.res_warm_time = 1; + opts.res_calc_time = 2; + opts.loop_inc = 4; + break; + case 's': + opts.res_warm_time = 2; + opts.res_calc_time = 6; + opts.loop_inc = 2; + break; + case 'n': + opts.test_name = optarg; + break; + case -1: + return; + default: + igt_assert(false); + } + } +} + +int main(int argc, char *argv[]) +{ + parse_opts(argc, argv); + + setup_msr(); + setup_drm(); + setup_modeset(); + setup_vblank_interval(); + setup_alarm(); + + printf("Test name:\t%s\n", opts.test_name); + + unset_mode(); + set_mode(); + + setup_idle(); + + if (opts.do_page_flip) + page_flip_test(); + + if (opts.do_draw) + draw_test(); + + if (opts.do_draw_and_flip) + draw_and_flip_test(); + + teardown_modeset(); + teardown_drm(); + teardown_msr(); + return 0; +} |