From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org Received: from gabe.freedesktop.org (gabe.freedesktop.org [131.252.210.177]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.lore.kernel.org (Postfix) with ESMTPS id C1FFFCCD1A2 for ; Wed, 18 Sep 2024 11:31:20 +0000 (UTC) Received: from gabe.freedesktop.org (localhost [127.0.0.1]) by gabe.freedesktop.org (Postfix) with ESMTP id 7BAC610E58A; Wed, 18 Sep 2024 11:31:20 +0000 (UTC) Authentication-Results: gabe.freedesktop.org; dkim=pass (2048-bit key; unprotected) header.d=intel.com header.i=@intel.com header.b="HUd5ShND"; dkim-atps=neutral Received: from mgamail.intel.com (mgamail.intel.com [192.198.163.13]) by gabe.freedesktop.org (Postfix) with ESMTPS id 794B810E58A for ; Wed, 18 Sep 2024 11:31:18 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=intel.com; i=@intel.com; q=dns/txt; s=Intel; t=1726659078; x=1758195078; h=from:to:cc:subject:date:message-id:in-reply-to: references:mime-version:content-transfer-encoding; bh=wQs6DwpQqWlxSwXO+Lv9p9cNt5MjUf6nLACV0YzVKJ8=; b=HUd5ShND5I6YItjmagrd5qjk9jBOd2XQKPINcRbc19GXwzCrmr5EU4TJ DIWN/GQ8fN6A3lQPLCQypStHK02pH9mQz4o5R0wE95kWHl5HOe3Q74kGg 95IXTsQpjhSxp0FoocLIrZWwm5MNjoOsU7ZYWIynDaZ5DaD+WtrXkIflx ziuqq+lPcFyEYZK8+WpEdyrIYNPgeYpywmNevzMrwIKR+W3LvC6iU9VdO V+W8XHDC4zS/p7NppA49YTCGdSofLU9XXAqfN12eYpUARdT5qBi38+JfV IgQ9fGaCz/WZ7ffW2cbTFWSmge6rD0pkL9fOIIOrMoAKqNXfB8C6eI0hV g==; X-CSE-ConnectionGUID: Nz1YwSVpTMeF9fLoaZNf1g== X-CSE-MsgGUID: g/LDmnyoTWaY2Ef14gpVHQ== X-IronPort-AV: E=McAfee;i="6700,10204,11198"; a="28470099" X-IronPort-AV: E=Sophos;i="6.10,238,1719903600"; d="scan'208";a="28470099" Received: from orviesa001.jf.intel.com ([10.64.159.141]) by fmvoesa107.fm.intel.com with ESMTP/TLS/ECDHE-RSA-AES256-GCM-SHA384; 18 Sep 2024 04:31:18 -0700 X-CSE-ConnectionGUID: yHyufwsiSvqpWioJCgLhyQ== X-CSE-MsgGUID: Yg44TiFNR/yNlgoQgYrR+Q== X-ExtLoop1: 1 X-IronPort-AV: E=Sophos;i="6.10,238,1719903600"; d="scan'208";a="106972665" Received: from mjarzebo-mobl1.ger.corp.intel.com (HELO localhost.localdomain) ([10.245.246.218]) by smtpauth.intel.com with ESMTP/TLS/ECDHE-RSA-AES256-GCM-SHA384; 18 Sep 2024 04:31:13 -0700 From: Christoph Manszewski To: igt-dev@lists.freedesktop.org Cc: =?UTF-8?q?Zbigniew=20Kempczy=C5=84ski?= , Kamil Konieczny , Dominik Grzegorzek , Maciej Patelczyk , =?UTF-8?q?Dominik=20Karol=20Pi=C4=85tkowski?= , Pawel Sikora , Andrzej Hajda , Kolanupaka Naveena , Mika Kuoppala , Gwan-gyeong Mun , Jan Sokolowski , Mika Kuoppala , Christoph Manszewski , Karolina Stolarek Subject: [PATCH i-g-t v7 10/16] lib/xe_eudebug: Introduce eu debug testing framework Date: Wed, 18 Sep 2024 13:30:11 +0200 Message-Id: <20240918113017.144687-11-christoph.manszewski@intel.com> X-Mailer: git-send-email 2.34.1 In-Reply-To: <20240918113017.144687-1-christoph.manszewski@intel.com> References: <20240918113017.144687-1-christoph.manszewski@intel.com> MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit X-BeenThere: igt-dev@lists.freedesktop.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: Development mailing list for IGT GPU Tools List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: igt-dev-bounces@lists.freedesktop.org Sender: "igt-dev" From: Dominik Grzegorzek Introduce library which simplifies testing of eu debug capability. The library provides event log helpers together with asynchronous abstraction for client proccess and the debugger itself. xe_eudebug_client creates its own proccess with user's work function, and gives machanisms to synchronize beginning of execution and event logging. xe_eudebug_debugger allows to attach to the given proccess, provides asynchronous thread for event reading and introduces triggers - a callback mechanism triggered every time subscribed event was read. To build the eudebug testing framework 'xe_eudebug' meson build option has to be enabled, as it is disabled by default. Signed-off-by: Dominik Grzegorzek Signed-off-by: Mika Kuoppala Signed-off-by: Christoph Manszewski Signed-off-by: Maciej Patelczyk Signed-off-by: Pawel Sikora Signed-off-by: Karolina Stolarek Acked-by: Zbigniew Kempczyński --- lib/meson.build | 5 + lib/xe/xe_eudebug.c | 2254 +++++++++++++++++++++++++++++++++++++++++++ lib/xe/xe_eudebug.h | 220 +++++ meson.build | 2 + meson_options.txt | 5 + 5 files changed, 2486 insertions(+) create mode 100644 lib/xe/xe_eudebug.c create mode 100644 lib/xe/xe_eudebug.h diff --git a/lib/meson.build b/lib/meson.build index 1c83bc5c3..c3556a921 100644 --- a/lib/meson.build +++ b/lib/meson.build @@ -195,6 +195,11 @@ if chamelium.found() lib_sources += 'monitor_edids/monitor_edids_helper.c' endif +if build_xe_eudebug + build_info += 'Xe EU debugger test framework enabled.' + lib_sources += 'xe/xe_eudebug.c' +endif + if libprocps.found() lib_deps += libprocps else diff --git a/lib/xe/xe_eudebug.c b/lib/xe/xe_eudebug.c new file mode 100644 index 000000000..2c7cd2ae1 --- /dev/null +++ b/lib/xe/xe_eudebug.c @@ -0,0 +1,2254 @@ +// SPDX-License-Identifier: MIT +/* + * Copyright © 2023 Intel Corporation + */ + +#include +#include +#include +#include +#include +#include +#include + +#include "igt.h" +#include "igt_sysfs.h" +#include "intel_pat.h" +#include "xe_eudebug.h" +#include "xe_ioctl.h" + +struct event_trigger { + xe_eudebug_trigger_fn fn; + int type; + struct igt_list_head link; +}; + +struct seqno_list_entry { + struct igt_list_head link; + uint64_t seqno; +}; + +struct match_dto { + struct drm_xe_eudebug_event *target; + struct igt_list_head *seqno_list; + uint64_t client_handle; + uint32_t filter; + + /* store latest 'EVENT_VM_BIND' seqno */ + uint64_t *bind_seqno; + /* latest vm_bind_op seqno matching bind_seqno */ + uint64_t *bind_op_seqno; +}; + +#define CLIENT_PID 1 +#define CLIENT_RUN 2 +#define CLIENT_FINI 3 +#define CLIENT_STOP 4 +#define CLIENT_STAGE 5 +#define DEBUGGER_STAGE 6 + +static const char *type_to_str(unsigned int type) +{ + switch (type) { + case DRM_XE_EUDEBUG_EVENT_NONE: + return "none"; + case DRM_XE_EUDEBUG_EVENT_READ: + return "read"; + case DRM_XE_EUDEBUG_EVENT_OPEN: + return "client"; + case DRM_XE_EUDEBUG_EVENT_VM: + return "vm"; + case DRM_XE_EUDEBUG_EVENT_EXEC_QUEUE: + return "exec_queue"; + case DRM_XE_EUDEBUG_EVENT_EU_ATTENTION: + return "attention"; + case DRM_XE_EUDEBUG_EVENT_VM_BIND: + return "vm_bind"; + case DRM_XE_EUDEBUG_EVENT_VM_BIND_OP: + return "vm_bind_op"; + case DRM_XE_EUDEBUG_EVENT_VM_BIND_UFENCE: + return "vm_bind_ufence"; + case DRM_XE_EUDEBUG_EVENT_METADATA: + return "metadata"; + case DRM_XE_EUDEBUG_EVENT_VM_BIND_OP_METADATA: + return "vm_bind_op_metadata"; + } + + return "UNKNOWN"; +} + +static const char *event_type_to_str(struct drm_xe_eudebug_event *e, char *buf) +{ + sprintf(buf, "%s(%d)", type_to_str(e->type), e->type); + + return buf; +} + +static const char *flags_to_str(unsigned int flags) +{ + if (flags & DRM_XE_EUDEBUG_EVENT_CREATE) { + if (flags & DRM_XE_EUDEBUG_EVENT_NEED_ACK) + return "create|ack"; + else + return "create"; + } + if (flags & DRM_XE_EUDEBUG_EVENT_DESTROY) + return "destroy"; + + if (flags & DRM_XE_EUDEBUG_EVENT_STATE_CHANGE) + return "state-change"; + + igt_assert(!(flags & DRM_XE_EUDEBUG_EVENT_NEED_ACK)); + + return "flags unknown"; +} + +static const char *event_members_to_str(struct drm_xe_eudebug_event *e, char *buf) +{ + switch (e->type) { + case DRM_XE_EUDEBUG_EVENT_OPEN: { + struct drm_xe_eudebug_event_client *ec = (void *)e; + + sprintf(buf, "handle=%llu", ec->client_handle); + break; + } + case DRM_XE_EUDEBUG_EVENT_VM: { + struct drm_xe_eudebug_event_vm *evm = (void *)e; + + sprintf(buf, "client_handle=%llu, handle=%llu", + evm->client_handle, evm->vm_handle); + break; + } + case DRM_XE_EUDEBUG_EVENT_EXEC_QUEUE: { + struct drm_xe_eudebug_event_exec_queue *ee = (void *)e; + + sprintf(buf, "client_handle=%llu, vm_handle=%llu, " + "exec_queue_handle=%llu, engine_class=%d, exec_queue_width=%d", + ee->client_handle, ee->vm_handle, + ee->exec_queue_handle, ee->engine_class, ee->width); + break; + } + case DRM_XE_EUDEBUG_EVENT_EU_ATTENTION: { + struct drm_xe_eudebug_event_eu_attention *ea = (void *)e; + + sprintf(buf, "client_handle=%llu, exec_queue_handle=%llu, " + "lrc_handle=%llu, bitmask_size=%d", + ea->client_handle, ea->exec_queue_handle, + ea->lrc_handle, ea->bitmask_size); + break; + } + case DRM_XE_EUDEBUG_EVENT_VM_BIND: { + struct drm_xe_eudebug_event_vm_bind *evmb = (void *)e; + + sprintf(buf, "client_handle=%llu, vm_handle=%llu, flags=0x%x, num_binds=%u", + evmb->client_handle, evmb->vm_handle, evmb->flags, evmb->num_binds); + break; + } + case DRM_XE_EUDEBUG_EVENT_VM_BIND_OP: { + struct drm_xe_eudebug_event_vm_bind_op *op = (void *)e; + + sprintf(buf, "vm_bind_ref_seqno=%lld, addr=%016llx, range=%llu num_extensions=%llu", + op->vm_bind_ref_seqno, op->addr, op->range, op->num_extensions); + break; + } + case DRM_XE_EUDEBUG_EVENT_VM_BIND_UFENCE: { + struct drm_xe_eudebug_event_vm_bind_ufence *f = (void *)e; + + sprintf(buf, "vm_bind_ref_seqno=%lld", f->vm_bind_ref_seqno); + break; + } + case DRM_XE_EUDEBUG_EVENT_METADATA: { + struct drm_xe_eudebug_event_metadata *em = (void *)e; + + sprintf(buf, "client_handle=%llu, metadata_handle=%llu, type=%llu, len=%llu", + em->client_handle, em->metadata_handle, em->type, em->len); + break; + } + case DRM_XE_EUDEBUG_EVENT_VM_BIND_OP_METADATA: { + struct drm_xe_eudebug_event_vm_bind_op_metadata *op = (void *)e; + + sprintf(buf, "vm_bind_op_ref_seqno=%lld, metadata_handle=%llu, metadata_cookie=%llu", + op->vm_bind_op_ref_seqno, op->metadata_handle, op->metadata_cookie); + break; + } + default: + strcpy(buf, "<...>"); + } + + return buf; +} + +/** + * xe_eudebug_event_to_str: + * @e: pointer to event + * @buf: target to write string representation of @e + * @len: size of target buffer @buf + * + * Creates string representation for given event. + * + * Returns: the written input buffer pointed by @buf. + */ +const char *xe_eudebug_event_to_str(struct drm_xe_eudebug_event *e, char *buf, size_t len) +{ + char a[256]; + char b[256]; + + igt_assert(e); + igt_assert(buf); + + snprintf(buf, len, "(%llu) %15s:%s: %s", + e->seqno, + event_type_to_str(e, a), + flags_to_str(e->flags), + event_members_to_str(e, b)); + + return buf; +} + +static void catch_child_failure(void) +{ + pid_t pid; + int status; + + pid = waitpid(-1, &status, WNOHANG); + + if (pid == 0 || pid == -1) + return; + + if (!WIFEXITED(status)) + return; + + igt_assert_f(WEXITSTATUS(status) == 0, "Client failed!\n"); +} + +static int safe_pipe_read(int pipe[2], void *buf, int nbytes, int timeout_ms) +{ + int ret; + int t = 0; + struct pollfd fd = { + .fd = pipe[0], + .events = POLLIN, + .revents = 0 + }; + + /* When child fails we may get stuck forever. Check whether + * the child process ended with an error. + */ + do { + const int interval_ms = 1000; + + ret = poll(&fd, 1, interval_ms); + + if (!ret) { + catch_child_failure(); + t += interval_ms; + } + } while (!ret && t < timeout_ms); + + if (ret > 0) + return read(pipe[0], buf, nbytes); + + return 0; +} + +static uint64_t pipe_read(int pipe[2], int timeout_ms) +{ + uint64_t in; + uint64_t ret; + + ret = safe_pipe_read(pipe, &in, sizeof(in), timeout_ms); + igt_assert(ret == sizeof(in)); + + return in; +} + +static void pipe_signal(int pipe[2], uint64_t token) +{ + igt_assert(write(pipe[1], &token, sizeof(token)) == sizeof(token)); +} + +static void pipe_close(int pipe[2]) +{ + if (pipe[0] != -1) + close(pipe[0]); + + if (pipe[1] != -1) + close(pipe[1]); +} + +static uint64_t __wait_token(int pipe[2], const uint64_t token, int timeout_ms) +{ + uint64_t in; + + in = pipe_read(pipe, timeout_ms); + + igt_assert_eq(in, token); + + return pipe_read(pipe, timeout_ms); +} + +static uint64_t client_wait_token(struct xe_eudebug_client *c, const uint64_t token) +{ + return __wait_token(c->p_in, token, c->timeout_ms); +} + +static uint64_t wait_from_client(struct xe_eudebug_client *c, const uint64_t token) +{ + return __wait_token(c->p_out, token, c->timeout_ms); +} + +static void token_signal(int pipe[2], const uint64_t token, const uint64_t value) +{ + pipe_signal(pipe, token); + pipe_signal(pipe, value); +} + +static void client_signal(struct xe_eudebug_client *c, + const uint64_t token, + const uint64_t value) +{ + token_signal(c->p_out, token, value); +} + +static int __xe_eudebug_connect(int fd, pid_t pid, uint32_t flags, uint64_t events) +{ + struct drm_xe_eudebug_connect param = { + .pid = pid, + .flags = flags, + }; + int debugfd; + + debugfd = igt_ioctl(fd, DRM_IOCTL_XE_EUDEBUG_CONNECT, ¶m); + + if (debugfd < 0) + return -errno; + + return debugfd; +} + +static void event_log_write_to_fd(struct xe_eudebug_event_log *l, int fd) +{ + igt_assert_eq(write(fd, &l->head, sizeof(l->head)), + sizeof(l->head)); + + igt_assert_eq(write(fd, l->log, l->head), l->head); +} + +static void read_all(int fd, void *buf, size_t nbytes) +{ + ssize_t remaining_size = nbytes; + ssize_t current_size = 0; + ssize_t read_size = 0; + + do { + read_size = read(fd, buf + current_size, remaining_size); + igt_assert_f(read_size >= 0, "read failed: %s\n", strerror(errno)); + + current_size += read_size; + remaining_size -= read_size; + } while (remaining_size > 0 && read_size > 0); + + igt_assert_eq(current_size, nbytes); +} + +static void event_log_read_from_fd(struct xe_eudebug_event_log *l, int fd) +{ + read_all(fd, &l->head, sizeof(l->head)); + igt_assert_lt(l->head, l->max_size); + + read_all(fd, l->log, l->head); +} + +typedef int (*cmp_fn_t)(struct drm_xe_eudebug_event *, void *); + +static struct drm_xe_eudebug_event * +event_cmp(struct xe_eudebug_event_log *l, + struct drm_xe_eudebug_event *current, + cmp_fn_t match, + void *data) +{ + struct drm_xe_eudebug_event *e = current; + + xe_eudebug_for_each_event(e, l) { + if (match(e, data)) + return e; + } + + return NULL; +} + +static int match_type_and_flags(struct drm_xe_eudebug_event *a, void *data) +{ + struct drm_xe_eudebug_event *b = data; + + if (a->type == b->type && + a->flags == b->flags) + return 1; + + return 0; +} + +static int match_fields(struct drm_xe_eudebug_event *a, void *data) +{ + struct drm_xe_eudebug_event *b = data; + int ret = 0; + + ret = match_type_and_flags(a, data); + if (!ret) + return ret; + + ret = 0; + + switch (a->type) { + case DRM_XE_EUDEBUG_EVENT_EXEC_QUEUE: { + struct drm_xe_eudebug_event_exec_queue *ae = (void *)a; + struct drm_xe_eudebug_event_exec_queue *be = (void *)b; + + if (ae->engine_class == be->engine_class && ae->width == be->width) + ret = 1; + break; + } + case DRM_XE_EUDEBUG_EVENT_VM_BIND: { + struct drm_xe_eudebug_event_vm_bind *ea = (void *)a; + struct drm_xe_eudebug_event_vm_bind *eb = (void *)b; + + if (ea->num_binds == eb->num_binds) + ret = 1; + break; + } + case DRM_XE_EUDEBUG_EVENT_VM_BIND_OP: { + struct drm_xe_eudebug_event_vm_bind_op *ea = (void *)a; + struct drm_xe_eudebug_event_vm_bind_op *eb = (void *)b; + + if (ea->addr == eb->addr && ea->range == eb->range && + ea->num_extensions == eb->num_extensions) + ret = 1; + break; + } + case DRM_XE_EUDEBUG_EVENT_VM_BIND_OP_METADATA: { + struct drm_xe_eudebug_event_vm_bind_op_metadata *ea = (void *)a; + struct drm_xe_eudebug_event_vm_bind_op_metadata *eb = (void *)b; + + if (ea->metadata_handle == eb->metadata_handle && + ea->metadata_cookie == eb->metadata_cookie) + ret = 1; + break; + } + + default: + ret = 1; + break; + } + + return ret; +} + +static int match_client_handle(struct drm_xe_eudebug_event *e, void *data) +{ + struct match_dto *md = data; + uint64_t *bind_seqno = md->bind_seqno; + uint64_t *bind_op_seqno = md->bind_op_seqno; + uint64_t h = md->client_handle; + + if (XE_EUDEBUG_EVENT_IS_FILTERED(e->type, md->filter)) + return 0; + + switch (e->type) { + case DRM_XE_EUDEBUG_EVENT_OPEN: { + struct drm_xe_eudebug_event_client *client = (void *)e; + + if (client->client_handle == h) + return 1; + break; + } + case DRM_XE_EUDEBUG_EVENT_VM: { + struct drm_xe_eudebug_event_vm *vm = (void *)e; + + if (vm->client_handle == h) + return 1; + break; + } + case DRM_XE_EUDEBUG_EVENT_EXEC_QUEUE: { + struct drm_xe_eudebug_event_exec_queue *ee = (void *)e; + + if (ee->client_handle == h) + return 1; + break; + } + case DRM_XE_EUDEBUG_EVENT_VM_BIND: { + struct drm_xe_eudebug_event_vm_bind *evmb = (void *)e; + + if (evmb->client_handle == h) { + *bind_seqno = evmb->base.seqno; + return 1; + } + break; + } + case DRM_XE_EUDEBUG_EVENT_VM_BIND_OP: { + struct drm_xe_eudebug_event_vm_bind_op *eo = (void *)e; + + if (eo->vm_bind_ref_seqno == *bind_seqno) { + *bind_op_seqno = eo->base.seqno; + return 1; + } + break; + } + case DRM_XE_EUDEBUG_EVENT_VM_BIND_UFENCE: { + struct drm_xe_eudebug_event_vm_bind_ufence *ef = (void *)e; + + if (ef->vm_bind_ref_seqno == *bind_seqno) + return 1; + + break; + } + case DRM_XE_EUDEBUG_EVENT_METADATA: { + struct drm_xe_eudebug_event_metadata *em = (void *)e; + + if (em->client_handle == h) + return 1; + break; + } + case DRM_XE_EUDEBUG_EVENT_VM_BIND_OP_METADATA: { + struct drm_xe_eudebug_event_vm_bind_op_metadata *eo = (void *)e; + + if (eo->vm_bind_op_ref_seqno == *bind_op_seqno) + return 1; + break; + } + default: + break; + } + + return 0; +} + +static int match_opposite_resource(struct drm_xe_eudebug_event *e, void *data) +{ + struct drm_xe_eudebug_event *d = data; + int ret; + + d->flags ^= DRM_XE_EUDEBUG_EVENT_CREATE | DRM_XE_EUDEBUG_EVENT_DESTROY; + d->flags &= ~(DRM_XE_EUDEBUG_EVENT_NEED_ACK); + ret = match_type_and_flags(e, data); + d->flags ^= DRM_XE_EUDEBUG_EVENT_CREATE | DRM_XE_EUDEBUG_EVENT_DESTROY; + + if (!ret) + return 0; + + switch (e->type) { + case DRM_XE_EUDEBUG_EVENT_OPEN: { + struct drm_xe_eudebug_event_client *client = (void *)e; + struct drm_xe_eudebug_event_client *filter = data; + + if (client->client_handle == filter->client_handle) + return 1; + break; + } + case DRM_XE_EUDEBUG_EVENT_VM: { + struct drm_xe_eudebug_event_vm *vm = (void *)e; + struct drm_xe_eudebug_event_vm *filter = data; + + if (vm->vm_handle == filter->vm_handle) + return 1; + break; + } + case DRM_XE_EUDEBUG_EVENT_EXEC_QUEUE: { + struct drm_xe_eudebug_event_exec_queue *ee = (void *)e; + struct drm_xe_eudebug_event_exec_queue *filter = data; + + if (ee->exec_queue_handle == filter->exec_queue_handle) + return 1; + break; + } + case DRM_XE_EUDEBUG_EVENT_VM_BIND: { + struct drm_xe_eudebug_event_vm_bind *evmb = (void *)e; + struct drm_xe_eudebug_event_vm_bind *filter = data; + + if (evmb->vm_handle == filter->vm_handle && + evmb->num_binds == filter->num_binds) + return 1; + break; + } + case DRM_XE_EUDEBUG_EVENT_VM_BIND_OP: { + struct drm_xe_eudebug_event_vm_bind_op *avmb = (void *)e; + struct drm_xe_eudebug_event_vm_bind_op *filter = data; + + if (avmb->addr == filter->addr && + avmb->range == filter->range) + return 1; + break; + } + case DRM_XE_EUDEBUG_EVENT_METADATA: { + struct drm_xe_eudebug_event_metadata *em = (void *)e; + struct drm_xe_eudebug_event_metadata *filter = data; + + if (em->metadata_handle == filter->metadata_handle) + return 1; + break; + } + case DRM_XE_EUDEBUG_EVENT_VM_BIND_OP_METADATA: { + struct drm_xe_eudebug_event_vm_bind_op_metadata *avmb = (void *)e; + struct drm_xe_eudebug_event_vm_bind_op_metadata *filter = data; + + if (avmb->metadata_handle == filter->metadata_handle && + avmb->metadata_cookie == filter->metadata_cookie) + return 1; + break; + } + + default: + break; + } + return 0; +} + +static int match_full(struct drm_xe_eudebug_event *e, void *data) +{ + struct seqno_list_entry *sl; + + struct match_dto *md = (void *)data; + int ret = 0; + + ret = match_client_handle(e, md); + if (!ret) + return 0; + + ret = match_fields(e, md->target); + if (!ret) + return 0; + + igt_list_for_each_entry(sl, md->seqno_list, link) { + if (sl->seqno == e->seqno) + return 0; + } + + return 1; +} + +static struct drm_xe_eudebug_event * +event_type_match(struct xe_eudebug_event_log *l, + struct drm_xe_eudebug_event *target, + struct drm_xe_eudebug_event *current) +{ + return event_cmp(l, current, match_type_and_flags, target); +} + +static struct drm_xe_eudebug_event * +client_match(struct xe_eudebug_event_log *l, + uint64_t client_handle, + struct drm_xe_eudebug_event *current, + uint32_t filter, + uint64_t *bind_seqno, + uint64_t *bind_op_seqno) +{ + struct match_dto md = { + .client_handle = client_handle, + .filter = filter, + .bind_seqno = bind_seqno, + .bind_op_seqno = bind_op_seqno, + }; + + return event_cmp(l, current, match_client_handle, &md); +} + +static struct drm_xe_eudebug_event * +opposite_event_match(struct xe_eudebug_event_log *l, + struct drm_xe_eudebug_event *target, + struct drm_xe_eudebug_event *current) +{ + return event_cmp(l, current, match_opposite_resource, target); +} + +static struct drm_xe_eudebug_event * +event_match(struct xe_eudebug_event_log *l, + struct drm_xe_eudebug_event *target, + uint64_t client_handle, + struct igt_list_head *seqno_list, + uint64_t *bind_seqno, + uint64_t *bind_op_seqno) +{ + struct match_dto md = { + .target = target, + .client_handle = client_handle, + .seqno_list = seqno_list, + .bind_seqno = bind_seqno, + .bind_op_seqno = bind_op_seqno, + }; + + return event_cmp(l, NULL, match_full, &md); +} + +static void compare_client(struct xe_eudebug_event_log *log1, struct drm_xe_eudebug_event *ev1, + struct xe_eudebug_event_log *log2, struct drm_xe_eudebug_event *ev2, + uint32_t filter) +{ + struct drm_xe_eudebug_event_client *ev1_client = (void *)ev1; + struct drm_xe_eudebug_event_client *ev2_client = (void *)ev2; + uint64_t cbs = 0, dbs = 0, cbso = 0, dbso = 0; + + struct igt_list_head matched_seqno_list; + struct drm_xe_eudebug_event *evptr1, *evptr2; + struct seqno_list_entry *entry, *tmp; + + igt_assert(ev1_client); + igt_assert(ev2_client); + + igt_debug("client: %llu -> %llu\n", ev1_client->client_handle, ev2_client->client_handle); + + evptr1 = NULL; + evptr2 = NULL; + IGT_INIT_LIST_HEAD(&matched_seqno_list); + + do { + evptr1 = client_match(log1, ev1_client->client_handle, evptr1, filter, &cbs, &cbso); + if (!evptr1) + break; + + evptr2 = event_match(log2, evptr1, ev2_client->client_handle, &matched_seqno_list, + &dbs, &dbso); + + igt_assert_f(evptr2, "%s (%llu): no matching event type %u found for client %llu\n", + log1->name, + evptr1->seqno, + evptr1->type, + ev1_client->client_handle); + + igt_debug("comparing %s %llu vs %s %llu\n", + log1->name, evptr1->seqno, log2->name, evptr2->seqno); + + /* + * Store the seqno of the event that was matched above, + * inside 'matched_seqno_list', to avoid it getting matched + * by subsequent 'event_match' calls. + */ + entry = malloc(sizeof(*entry)); + entry->seqno = evptr2->seqno; + igt_list_add(&entry->link, &matched_seqno_list); + } while (evptr1); + + igt_list_for_each_entry_safe(entry, tmp, &matched_seqno_list, link) + free(entry); +} + +/** + * xe_eudebug_event_log_find_seqno: + * @l: event log pointer + * @seqno: seqno of event to be found + * + * Finds the event with given seqno in the event log. + * + * Returns: pointer to the event with given seqno within @l or NULL seqno is + * not present. + */ +struct drm_xe_eudebug_event * +xe_eudebug_event_log_find_seqno(struct xe_eudebug_event_log *l, uint64_t seqno) +{ + struct drm_xe_eudebug_event *e = NULL, *found = NULL; + + igt_assert(l); + igt_assert_neq(seqno, 0); + /* + * Try to catch if seqno is corrupted and prevent too long tests, + * as our post processing of events is not optimized. + */ + igt_assert_lt(seqno, 10 * 1000 * 1000); + + xe_eudebug_for_each_event(e, l) { + if (e->seqno == seqno) { + if (found) { + igt_warn("Found multiple events with the same seqno %lu\n", seqno); + xe_eudebug_event_log_print(l, false); + igt_assert(!found); + } + found = e; + } + } + + return found; +} + +static void event_log_sort(struct xe_eudebug_event_log *l) +{ + struct xe_eudebug_event_log *tmp; + struct drm_xe_eudebug_event *e = NULL; + uint64_t first_seqno = UINT64_MAX; + uint64_t last_seqno = 0; + uint64_t events = 0, added = 0; + uint64_t i; + + xe_eudebug_for_each_event(e, l) { + if (e->seqno > last_seqno) + last_seqno = e->seqno; + + if (e->seqno < first_seqno) + first_seqno = e->seqno; + + events++; + } + + tmp = xe_eudebug_event_log_create("tmp", l->max_size); + + for (i = first_seqno; i <= last_seqno; i++) { + e = xe_eudebug_event_log_find_seqno(l, i); + if (e) { + xe_eudebug_event_log_write(tmp, e); + added++; + } + } + + igt_assert_eq(events, added); + igt_assert_eq(tmp->head, l->head); + + memcpy(l->log, tmp->log, tmp->head); + + xe_eudebug_event_log_destroy(tmp); +} + +/** + * xe_eudebug_connect: + * @fd: Xe file descriptor + * @pid: client PID + * @flags: connection flags + * + * Opens the xe eu debugger connection to the process described by @pid + * + * Returns: 0 if the debugger was successfully attached, -errno otherwise. + */ +int xe_eudebug_connect(int fd, pid_t pid, uint32_t flags) +{ + int ret; + uint64_t events = 0; /* events filtering not supported yet! */ + + ret = __xe_eudebug_connect(fd, pid, flags, events); + + return ret; +} + +/** + * xe_eudebug_event_log_create: + * @name: event log identifier + * @max_size: maximum size of created log + * + * Function creates an Eu Debugger event log with size equal to @max_size. + * + * Returns: pointer to just created log + */ +#define MAX_EVENT_LOG_SIZE (32 * 1024 * 1024) +struct xe_eudebug_event_log *xe_eudebug_event_log_create(const char *name, unsigned int max_size) +{ + struct xe_eudebug_event_log *l; + + igt_assert(name); + + l = calloc(1, sizeof(*l)); + igt_assert(l); + l->log = calloc(1, max_size); + igt_assert(l->log); + l->max_size = max_size; + strncpy(l->name, name, sizeof(l->name) - 1); + pthread_mutex_init(&l->lock, NULL); + + return l; +} + +/** + * xe_eudebug_event_log_destroy: + * @l: event log pointer + * + * Frees given event log @l. + */ +void xe_eudebug_event_log_destroy(struct xe_eudebug_event_log *l) +{ + igt_assert(l); + pthread_mutex_destroy(&l->lock); + free(l->log); + free(l); +} + +/** + * xe_eudebug_event_log_write: + * @l: event log pointer + * @e: event to be written to event log + * + * Writes event @e to the event log, thread-safe. + */ +void xe_eudebug_event_log_write(struct xe_eudebug_event_log *l, struct drm_xe_eudebug_event *e) +{ + igt_assert(l); + igt_assert(e); + igt_assert(e->seqno); + /* + * Try to catch if seqno is corrupted and prevent too long tests, + * as our post processing of events is not optimized. + */ + igt_assert_lt(e->seqno, 10 * 1000 * 1000); + + pthread_mutex_lock(&l->lock); + igt_assert_lt(l->head + e->len, l->max_size); + memcpy(l->log + l->head, e, e->len); + l->head += e->len; + pthread_mutex_unlock(&l->lock); +} + +/** + * xe_eudebug_event_log_print: + * @l: event log pointer + * @debug: when true function uses igt_debug instead of igt_info. + * + * Prints given event log. + */ +void +xe_eudebug_event_log_print(struct xe_eudebug_event_log *l, bool debug) +{ + struct drm_xe_eudebug_event *e = NULL; + int level = debug ? IGT_LOG_DEBUG : IGT_LOG_INFO; + char str[XE_EUDEBUG_EVENT_STRING_MAX_LEN]; + + igt_assert(l); + + igt_log(IGT_LOG_DOMAIN, level, + "event log '%s' (%u bytes):\n", l->name, l->head); + + xe_eudebug_for_each_event(e, l) { + xe_eudebug_event_to_str(e, str, XE_EUDEBUG_EVENT_STRING_MAX_LEN); + igt_log(IGT_LOG_DOMAIN, level, "%s\n", str); + } +} + +/** + * xe_eudebug_event_log_compare: + * @a: event log pointer + * @b: event log pointer + * @filter: mask that represents events to be skipped during comparison, useful + * for events like 'VM_BIND' since they can be asymmetric. Note that + * 'DRM_XE_EUDEBUG_EVENT_OPEN' will always be matched. + * + * Compares and asserts event logs @a, @b if the event + * sequence matches. + */ +void xe_eudebug_event_log_compare(struct xe_eudebug_event_log *log1, + struct xe_eudebug_event_log *log2, + uint32_t filter) +{ + struct drm_xe_eudebug_event *ev1 = NULL; + struct drm_xe_eudebug_event *ev2 = NULL; + + igt_assert(log1); + igt_assert(log2); + + xe_eudebug_for_each_event(ev1, log1) { + if (ev1->type == DRM_XE_EUDEBUG_EVENT_OPEN && + ev1->flags & DRM_XE_EUDEBUG_EVENT_CREATE) { + ev2 = event_type_match(log2, ev1, ev2); + + compare_client(log1, ev1, log2, ev2, filter); + compare_client(log2, ev2, log1, ev1, filter); + } + } +} + +/** + * xe_eudebug_event_log_match_opposite: + * @l: event log pointer + * @filter: mask that represents events to be skipped during comparison, useful + * for events like 'VM_BIND' since they can be asymmetric + * + * Matches and asserts content of all opposite events (create vs destroy). + */ +void +xe_eudebug_event_log_match_opposite(struct xe_eudebug_event_log *l, uint32_t filter) +{ + struct drm_xe_eudebug_event *ev1 = NULL; + struct drm_xe_eudebug_event *ev2 = NULL; + + igt_assert(l); + + xe_eudebug_for_each_event(ev1, l) { + if (ev1->flags & DRM_XE_EUDEBUG_EVENT_CREATE) { + uint8_t offset = sizeof(struct drm_xe_eudebug_event); + int opposite_matching; + + if (XE_EUDEBUG_EVENT_IS_FILTERED(ev1->type, filter)) + continue; + + /* No opposite matching for binds */ + if ((ev1->type >= DRM_XE_EUDEBUG_EVENT_VM_BIND && + ev1->type <= DRM_XE_EUDEBUG_EVENT_VM_BIND_UFENCE) || + ev1->type == DRM_XE_EUDEBUG_EVENT_VM_BIND_OP_METADATA) + continue; + + ev2 = opposite_event_match(l, ev1, ev1); + + igt_assert_f(ev2, "no opposite event of type %u found\n", ev1->type); + + igt_assert_eq(ev1->len, ev2->len); + opposite_matching = memcmp((uint8_t *)ev2 + offset, + (uint8_t *)ev1 + offset, + ev2->len - offset) == 0; + + igt_assert_f(opposite_matching, + "%s: create|destroy event not maching (%llu) vs (%llu)\n", + l->name, ev2->seqno, ev1->seqno); + } + } +} + +static void debugger_run_triggers(struct xe_eudebug_debugger *d, + struct drm_xe_eudebug_event *e) +{ + struct event_trigger *t; + + igt_list_for_each_entry(t, &d->triggers, link) { + if (e->type == t->type) + t->fn(d, e); + } +} + +#define MAX_EVENT_SIZE (32 * 1024) +static int +xe_eudebug_read_event(int fd, struct drm_xe_eudebug_event *event) +{ + int ret; + + event->type = DRM_XE_EUDEBUG_EVENT_READ; + event->flags = 0; + event->len = MAX_EVENT_SIZE; + + ret = igt_ioctl(fd, DRM_XE_EUDEBUG_IOCTL_READ_EVENT, event); + if (ret < 0) + return -errno; + + return ret; +} + +static void *debugger_worker_loop(void *data) +{ + uint8_t buf[MAX_EVENT_SIZE]; + struct drm_xe_eudebug_event *e = (void *)buf; + struct xe_eudebug_debugger *d = data; + struct pollfd p = { + .events = POLLIN, + .revents = 0, + }; + int timeout_ms = 100, ret; + + igt_assert(d->master_fd >= 0); + + do { + p.fd = d->fd; + ret = poll(&p, 1, timeout_ms); + + if (ret == -1) { + igt_info("poll failed with errno %d\n", errno); + break; + } + + if (ret == 1 && (p.revents & POLLIN)) { + int err = xe_eudebug_read_event(d->fd, e); + + if (!err) { + ++d->event_count; + + xe_eudebug_event_log_write(d->log, e); + debugger_run_triggers(d, e); + } else { + igt_info("xe_eudebug_read_event returned %d\n", ret); + } + } + } while ((ret && READ_ONCE(d->worker_state) == DEBUGGER_WORKER_QUITTING) || + READ_ONCE(d->worker_state) == DEBUGGER_WORKER_ACTIVE); + + d->worker_state = DEBUGGER_WORKER_INACTIVE; + + return NULL; +} + +/** + * xe_eudebug_debugger_available: + * @fd: Xe file descriptor + * + * Returns: true it debugger connection is available, false otherwise. + */ +bool xe_eudebug_debugger_available(int fd) +{ + struct drm_xe_eudebug_connect param = { .pid = getpid() }; + int debugfd; + + debugfd = igt_ioctl(fd, DRM_IOCTL_XE_EUDEBUG_CONNECT, ¶m); + if (debugfd >= 0) + close(debugfd); + + return debugfd >= 0; +} + +/** + * xe_eudebug_debugger_create: + * @master_fd: xe client used to open the debugger connection + * @flags: flags stored in a debugger structure, can be used at will + * of the caller, i.e. to be used inside triggers. + * @data: test's private data, allocated with MAP_SHARED | MAP_ANONYMOUS, + * can be shared between client and debugger. Can be NULL. + * + * Returns: newly created xe_eudebug_debugger structure with its + * event log initialized. Note that to open the connection + * you need call @xe_eudebug_debugger_attach. + */ +struct xe_eudebug_debugger * +xe_eudebug_debugger_create(int master_fd, uint64_t flags, void *data) +{ + struct xe_eudebug_debugger *d; + + d = calloc(1, sizeof(*d)); + igt_assert(d); + + d->flags = flags; + IGT_INIT_LIST_HEAD(&d->triggers); + d->log = xe_eudebug_event_log_create("debugger", MAX_EVENT_LOG_SIZE); + d->fd = -1; + d->master_fd = master_fd; + d->ptr = data; + + return d; +} + +static void debugger_destroy_triggers(struct xe_eudebug_debugger *d) +{ + struct event_trigger *t, *tmp; + + igt_list_for_each_entry_safe(t, tmp, &d->triggers, link) + free(t); +} + +/** + * xe_eudebug_debugger_destroy: + * @d: pointer to the debugger + * + * Frees xe_eudebug_debugger structure pointed by @d. If the debugger + * connection was still opened it terminates it. + */ +void xe_eudebug_debugger_destroy(struct xe_eudebug_debugger *d) +{ + if (d->worker_state != DEBUGGER_WORKER_INACTIVE) + xe_eudebug_debugger_stop_worker(d, 1); + + if (d->target_pid) + xe_eudebug_debugger_detach(d); + + xe_eudebug_event_log_destroy(d->log); + debugger_destroy_triggers(d); + free(d); +} + +/** + * xe_eudebug_debugger_attach: + * @d: pointer to the debugger + * @c: pointer to the client + * + * Opens the xe eu debugger connection to the process described by @c (c->pid) + * + * Returns: 0 if the debugger was successfully attached, -errno otherwise. + */ +int xe_eudebug_debugger_attach(struct xe_eudebug_debugger *d, + struct xe_eudebug_client *c) +{ + int ret; + + igt_assert_eq(d->fd, -1); + igt_assert_neq(c->pid, 0); + ret = xe_eudebug_connect(d->master_fd, c->pid, 0); + + if (ret < 0) + return ret; + + d->fd = ret; + d->target_pid = c->pid; + d->p_client[0] = c->p_in[0]; + d->p_client[1] = c->p_in[1]; + + igt_debug("debugger connected to %lu\n", d->target_pid); + + return 0; +} + +/** + * xe_eudebug_debugger_detach: + * @d: pointer to the debugger + * + * Closes previously opened xe eu debugger connection. Asserts if + * the debugger has active session. + */ +void xe_eudebug_debugger_detach(struct xe_eudebug_debugger *d) +{ + igt_assert(d->target_pid); + close(d->fd); + d->target_pid = 0; + d->fd = -1; +} + +/** + * xe_eudebug_debugger_add_trigger: + * @d: pointer to the debugger + * @type: the type of the event which activates the trigger + * @fn: function to be called when event of @type was read by the debugger. + * + * Adds function @fn to the list of triggers activated when event of @type + * has been read by worker. + * Note: Triggers are activated by the worker. + */ +void xe_eudebug_debugger_add_trigger(struct xe_eudebug_debugger *d, + int type, xe_eudebug_trigger_fn fn) +{ + struct event_trigger *t; + + t = calloc(1, sizeof(*t)); + igt_assert(t); + + IGT_INIT_LIST_HEAD(&t->link); + t->type = type; + t->fn = fn; + + igt_list_add_tail(&t->link, &d->triggers); + igt_debug("added trigger %p\n", t); +} + +/** + * xe_eudebug_debugger_start_worker: + * @d: pointer to the debugger + * + * Starts the debugger worker. Worker is resposible for reading all + * incoming events from the debugger, put then into debugger log and + * execute appropriate event triggers. Note that using the debuggers + * event log while worker is running is not safe. + */ +void xe_eudebug_debugger_start_worker(struct xe_eudebug_debugger *d) +{ + int ret; + + d->worker_state = DEBUGGER_WORKER_ACTIVE; + ret = pthread_create(&d->worker_thread, NULL, &debugger_worker_loop, d); + + igt_assert_f(ret == 0, "Debugger worker thread creation failed!"); +} + +/** + * xe_eudebug_debugger_stop_worker: + * @d: pointer to the debugger + * + * Stops the debugger worker. Event log is sorted by seqno after closure. + */ +void xe_eudebug_debugger_stop_worker(struct xe_eudebug_debugger *d, + int timeout_s) +{ + struct timespec t = {}; + int ret; + + igt_assert_neq(d->worker_state, DEBUGGER_WORKER_INACTIVE); + + d->worker_state = DEBUGGER_WORKER_QUITTING; /* First time be polite. */ + igt_assert_eq(clock_gettime(CLOCK_REALTIME, &t), 0); + t.tv_sec += timeout_s; + + ret = pthread_timedjoin_np(d->worker_thread, NULL, &t); + + if (ret == ETIMEDOUT) { + d->worker_state = DEBUGGER_WORKER_INACTIVE; + ret = pthread_join(d->worker_thread, NULL); + } + + igt_assert_f(ret == 0 || ret != ESRCH, + "pthread join failed with error %d!\n", ret); + + event_log_sort(d->log); +} + +/** + * xe_eudebug_debugger_signal_stage: + * @d: pointer to the debugger + * @stage: stage to signal + * + * Signals to client, waiting in xe_eudebug_client_wait_stage(), + * releasing it to proceed. + */ +void xe_eudebug_debugger_signal_stage(struct xe_eudebug_debugger *d, uint64_t stage) +{ + token_signal(d->p_client, CLIENT_STAGE, stage); +} + +/** + * xe_eudebug_debugger_wait_stage: + * @s: pointer to xe_eudebug_debugger structure + * @stage: stage to wait on + * + * Pauses debugger until the client has signalled the corresponding stage with + * xe_eudebug_client_signal_stage. This is only for situations where the actual + * event flow is not enough to coordinate between client/debugger and extra sync + * mechanism is needed. + */ +void xe_eudebug_debugger_wait_stage(struct xe_eudebug_session *s, uint64_t stage) +{ + u64 stage_in; + + igt_debug("debugger xe client fd: %d pausing for stage %lu\n", s->debugger->master_fd, + stage); + + stage_in = wait_from_client(s->client, DEBUGGER_STAGE); + igt_debug("debugger xe client fd: %d stage %lu, expected %lu, stage\n", + s->debugger->master_fd, stage_in, stage); + + igt_assert_eq(stage_in, stage); +} + +/** + * xe_eudebug_client_create: + * @master_fd: xe client used to open the debugger connection + * @work: function that opens xe device and executes arbitrary workload + * @flags: flags stored in a client structure, can be used at will + * of the caller, i.e. to provide the @work function an additional switch. + * @data: test's private data, allocated with MAP_SHARED | MAP_ANONYMOUS, + * can be shared between client and debugger. Accesible via client->ptr. + * Can be NULL. + * + * Forks and creates the debugger process. @work won't be called until + * xe_eudebug_client_start is called. + * + * Returns: newly created xe_eudebug_debugger structure with its + * event log initialized. + */ +struct xe_eudebug_client *xe_eudebug_client_create(int master_fd, xe_eudebug_client_work_fn work, + uint64_t flags, void *data) +{ + struct xe_eudebug_client *c; + + c = calloc(1, sizeof(*c)); + igt_assert(c); + + c->flags = flags; + igt_assert(!pipe(c->p_in)); + igt_assert(!pipe(c->p_out)); + c->seqno = 1; + c->log = xe_eudebug_event_log_create("client", MAX_EVENT_LOG_SIZE); + c->done = 0; + c->ptr = data; + c->master_fd = master_fd; + c->timeout_ms = XE_EUDEBUG_DEFAULT_TIMEOUT_SEC * MSEC_PER_SEC; + pthread_mutex_init(&c->lock, NULL); + + igt_fork(child, 1) { + int mypid; + + igt_assert_eq(c->pid, 0); + + close(c->p_out[0]); + c->p_out[0] = -1; + close(c->p_in[1]); + c->p_in[1] = -1; + + mypid = getpid(); + client_signal(c, CLIENT_PID, mypid); + + c->pid = client_wait_token(c, CLIENT_RUN); + igt_assert_eq(c->pid, mypid); + if (work) + work(c); + + client_signal(c, CLIENT_FINI, c->seqno); + + event_log_write_to_fd(c->log, c->p_out[1]); + + c->pid = client_wait_token(c, CLIENT_STOP); + igt_assert_eq(c->pid, mypid); + } + + close(c->p_out[1]); + c->p_out[1] = -1; + close(c->p_in[0]); + c->p_in[0] = -1; + + c->pid = wait_from_client(c, CLIENT_PID); + + igt_info("client running with pid %d\n", c->pid); + + return c; +} + +/** + * xe_eudebug_client_stop: + * @c: pointer to xe_eudebug_client structure + * + * Waits for the end of client's work and exits the proccess. + */ +void xe_eudebug_client_stop(struct xe_eudebug_client *c) +{ + if (c->pid) { + int waitstatus; + + xe_eudebug_client_wait_done(c); + + token_signal(c->p_in, CLIENT_STOP, c->pid); + igt_assert_eq(waitpid(c->pid, &waitstatus, 0), + c->pid); + c->pid = 0; + } +} + +/** + * xe_eudebug_client_destroy: + * @c: pointer to xe_eudebug_client structure to be freed + * + * Frees the @c client structure. Note that it calls xe_eudebug_client_stop if + * client proccess has not terminated yet. + */ +void xe_eudebug_client_destroy(struct xe_eudebug_client *c) +{ + xe_eudebug_client_stop(c); + pipe_close(c->p_in); + pipe_close(c->p_out); + xe_eudebug_event_log_destroy(c->log); + pthread_mutex_destroy(&c->lock); + free(c); +} + +/** + * xe_eudebug_client_get_seqno: + * @c: pointer to xe_eudebug_client structure + * + * Increments and returns current seqno value of the given client @c + * + * Returns: incremented seqno + */ +uint64_t xe_eudebug_client_get_seqno(struct xe_eudebug_client *c) +{ + uint64_t ret; + pthread_mutex_lock(&c->lock); + ret = c->seqno++; + pthread_mutex_unlock(&c->lock); + return ret; +} + +/** + * xe_eudebug_client_start: + * @c: pointer to xe_eudebug_client structure + * + * Starts execution of client's work function within the client's proccess. + */ +void xe_eudebug_client_start(struct xe_eudebug_client *c) +{ + token_signal(c->p_in, CLIENT_RUN, c->pid); +} + +/** + * xe_eudebug_client_wait_done: + * @c: pointer to xe_eudebug_client structure + * + * Waits for the client work end updates the event log. + * Doesn't terminate the client's proccess yet. + */ +void xe_eudebug_client_wait_done(struct xe_eudebug_client *c) +{ + if (!c->done) { + c->done = 1; + c->seqno = wait_from_client(c, CLIENT_FINI); + event_log_read_from_fd(c->log, c->p_out[0]); + } +} + +/** + * xe_eudebug_client_signal_stage: + * @c: pointer to the client + * @stage: stage to signal + * + * Signals to debugger, waiting in xe_eudebug_debugger_wait_stage(), + * releasing it to proceed. + */ +void xe_eudebug_client_signal_stage(struct xe_eudebug_client *c, uint64_t stage) +{ + token_signal(c->p_out, DEBUGGER_STAGE, stage); +} + +/** + * xe_eudebug_client_wait_stage: + * @c: pointer to xe_eudebug_client structure + * @stage: stage to wait on + * + * Pauses client until the debugger has signalled the corresponding stage with + * xe_eudebug_debugger_signal_stage. This is only for situations where the + * actual event flow is not enough to coordinate between client/debugger and extra + * sync mechanism is needed. + * + */ +void xe_eudebug_client_wait_stage(struct xe_eudebug_client *c, uint64_t stage) +{ + u64 stage_in; + + if (c->done) { + igt_warn("client: %d already done before %lu\n", c->pid, stage); + return; + } + + igt_debug("client: %d pausing for stage %lu\n", c->pid, stage); + + stage_in = client_wait_token(c, CLIENT_STAGE); + igt_debug("client: %d stage %lu, expected %lu, stage\n", c->pid, stage_in, stage); + + igt_assert_eq(stage_in, stage); +} + +/** + * xe_eudebug_session_create: + * @fd: Xe file descriptor + * @work: function passed to the xe_eudebug_client_create + * @flags: flags passed to client and debugger + * @test_private: test's data, allocated with MAP_SHARED | MAP_ANONYMOUS, + * passed to client and debugger. Can be NULL. + * + * Creates session together with client and debugger structures. + */ +struct xe_eudebug_session *xe_eudebug_session_create(int fd, + xe_eudebug_client_work_fn work, + unsigned int flags, + void *test_private) +{ + struct xe_eudebug_session *s; + + s = calloc(1, sizeof(*s)); + igt_assert(s); + + s->client = xe_eudebug_client_create(fd, work, flags, test_private); + s->debugger = xe_eudebug_debugger_create(fd, flags, test_private); + s->flags = flags; + + return s; +} + +/** + * xe_eudebug_session_run: + * @s: pointer to xe_eudebug_session structure + * + * Attaches debugger to client's proccess, starts debugger's + * async event reader, starts client and once client finish + * it stops debugger worker. + */ +void xe_eudebug_session_run(struct xe_eudebug_session *s) +{ + struct xe_eudebug_debugger *debugger = s->debugger; + struct xe_eudebug_client *client = s->client; + + igt_assert_eq(xe_eudebug_debugger_attach(debugger, client), 0); + + xe_eudebug_debugger_start_worker(debugger); + + xe_eudebug_client_start(client); + xe_eudebug_client_wait_done(client); + + xe_eudebug_debugger_stop_worker(debugger, 1); + + xe_eudebug_event_log_print(debugger->log, true); + xe_eudebug_event_log_print(client->log, true); +} + +/** + * xe_eudebug_session_check: + * @s: pointer to xe_eudebug_session structure + * @match_opposite: indicates whether check should match all + * create and destroy events. + * @filter: mask that represents events to be skipped during comparison, useful + * for events like 'VM_BIND' since they can be asymmetric + * + * Validate debugger's log against the log created by the client. + */ +void xe_eudebug_session_check(struct xe_eudebug_session *s, bool match_opposite, uint32_t filter) +{ + xe_eudebug_event_log_compare(s->client->log, s->debugger->log, filter); + + if (match_opposite) + xe_eudebug_event_log_match_opposite(s->debugger->log, filter); +} + +/** + * xe_eudebug_session_destroy: + * @s: pointer to xe_eudebug_session structure + * + * Destroy session together with its debugger and client. + */ +void xe_eudebug_session_destroy(struct xe_eudebug_session *s) +{ + xe_eudebug_debugger_destroy(s->debugger); + xe_eudebug_client_destroy(s->client); + + free(s); +} + +#define to_base(x) ((struct drm_xe_eudebug_event *)&(x)) + +static void base_event(struct xe_eudebug_client *c, + struct drm_xe_eudebug_event *e, + uint32_t type, + uint32_t flags, + uint64_t size) +{ + e->type = type; + e->flags = flags; + e->seqno = xe_eudebug_client_get_seqno(c); + e->len = size; +} + +static void client_event(struct xe_eudebug_client *c, uint32_t flags, int client_fd) +{ + struct drm_xe_eudebug_event_client ec; + + base_event(c, to_base(ec), DRM_XE_EUDEBUG_EVENT_OPEN, flags, sizeof(ec)); + + ec.client_handle = client_fd; + + xe_eudebug_event_log_write(c->log, (void *)&ec); +} + +static void vm_event(struct xe_eudebug_client *c, uint32_t flags, int client_fd, uint32_t vm_id) +{ + struct drm_xe_eudebug_event_vm evm; + + base_event(c, to_base(evm), DRM_XE_EUDEBUG_EVENT_VM, flags, sizeof(evm)); + + evm.client_handle = client_fd; + evm.vm_handle = vm_id; + + xe_eudebug_event_log_write(c->log, (void *)&evm); +} + +static void exec_queue_event(struct xe_eudebug_client *c, uint32_t flags, + int client_fd, uint32_t vm_id, + uint32_t exec_queue_handle, uint16_t class, + uint16_t width) +{ + struct drm_xe_eudebug_event_exec_queue ee; + + base_event(c, to_base(ee), DRM_XE_EUDEBUG_EVENT_EXEC_QUEUE, + flags, sizeof(ee)); + + ee.client_handle = client_fd; + ee.vm_handle = vm_id; + ee.exec_queue_handle = exec_queue_handle; + ee.engine_class = class; + ee.width = width; + + xe_eudebug_event_log_write(c->log, (void *)&ee); +} + +static void metadata_event(struct xe_eudebug_client *c, uint32_t flags, + int client_fd, uint32_t id, uint64_t type, uint64_t len) +{ + struct drm_xe_eudebug_event_metadata em; + + base_event(c, to_base(em), DRM_XE_EUDEBUG_EVENT_METADATA, + flags, sizeof(em)); + + em.client_handle = client_fd; + em.metadata_handle = id; + em.type = type; + em.len = len; + + xe_eudebug_event_log_write(c->log, (void *)&em); +} + +static int enable_getset(int fd, bool *old, bool *new) +{ + static const char * const fname = "enable_eudebug"; + int ret = 0; + int sysfs, device_fd; + bool val_before; + struct stat st; + + igt_assert(new || old); + igt_assert_eq(fstat(fd, &st), 0); + + sysfs = igt_sysfs_open(fd); + if (sysfs < 0) + return -1; + + device_fd = openat(sysfs, "device", O_DIRECTORY | O_RDONLY); + close(sysfs); + if (device_fd < 0) + return -1; + + if (!__igt_sysfs_get_boolean(device_fd, fname, &val_before)) { + ret = -1; + goto out; + } + + igt_debug("enable_eudebug before: %d\n", val_before); + + if (old) + *old = val_before; + + if (new) { + if (__igt_sysfs_set_boolean(device_fd, fname, *new)) + igt_assert_eq(igt_sysfs_get_boolean(device_fd, fname), *new); + else + ret = -1; + } + +out: + close(device_fd); + + return ret; +} + +/** + * xe_eudebug_enable + * @fd: xe client + * @enable: state toggle - true to enable, false to disable + * + * Enables/disables eudebug capability by writing to + * '/sys/class/drm/card/device/enable_eudebug' sysfs entry. + * + * Returns: previous toggle value, i.e. true when eudebugging was enabled, + * false when eudebugging was disabled. + */ +bool xe_eudebug_enable(int fd, bool enable) +{ + bool old = false; + int ret = enable_getset(fd, &old, &enable); + + if (ret) { + igt_skip_on(enable); + old = false; + } + + return old; +} + +/* Eu debugger wrappers around resource creating xe ioctls. */ + +/** + * xe_eudebug_client_open_driver: + * @c: pointer to xe_eudebug_client structure + * + * Calls drm_open_client(DRIVER_XE) and logs the corresponding + * event in client's event log. + * + * Returns: valid DRM file descriptor + */ +int xe_eudebug_client_open_driver(struct xe_eudebug_client *c) +{ + int fd; + + igt_assert(c); + fd = drm_reopen_driver(c->master_fd); + client_event(c, DRM_XE_EUDEBUG_EVENT_CREATE, fd); + + return fd; +} + +/** + * xe_eudebug_client_close_driver: + * @c: pointer to xe_eudebug_client structure + * @fd: xe client + * + * Calls close driver and logs the corresponding event in + * client's event log. + */ +void xe_eudebug_client_close_driver(struct xe_eudebug_client *c, int fd) +{ + igt_assert(c); + client_event(c, DRM_XE_EUDEBUG_EVENT_DESTROY, fd); + drm_close_driver(fd); +} + +/** + * xe_eudebug_client_vm_create: + * @c: pointer to xe_eudebug_client structure + * @fd: xe client + * @flags: vm bind flags + * @ext: pointer to the first user extension + * + * Calls xe_vm_create() and logs corresponding events + * (including vm set metadata events) in client's event log. + * + * Returns: valid vm handle + */ +uint32_t xe_eudebug_client_vm_create(struct xe_eudebug_client *c, int fd, + uint32_t flags, uint64_t ext) +{ + uint32_t vm; + + igt_assert(c); + vm = xe_vm_create(fd, flags, ext); + vm_event(c, DRM_XE_EUDEBUG_EVENT_CREATE, fd, vm); + + return vm; +} + +/** + * xe_eudebug_client_vm_destroy: + * @c: pointer to xe_eudebug_client structure + * fd: xe client + * vm: vm handle + * + * Calls xe_vm_destroy() and logs the corresponding event in + * client's event log. + */ +void xe_eudebug_client_vm_destroy(struct xe_eudebug_client *c, int fd, uint32_t vm) +{ + igt_assert(c); + xe_vm_destroy(fd, vm); + vm_event(c, DRM_XE_EUDEBUG_EVENT_DESTROY, fd, vm); +} + +/** + * xe_eudebug_client_exec_queue_create: + * @c: pointer to xe_eudebug_client structure + * @fd: xe client + * @create: exec_queue create drm struct + * + * Calls xe exec queue create ioctl and logs the corresponding event in + * client's event log. + * + * Returns: valid exec queue handle + */ +uint32_t xe_eudebug_client_exec_queue_create(struct xe_eudebug_client *c, int fd, + struct drm_xe_exec_queue_create *create) +{ + struct drm_xe_ext_set_property *ext; + bool send = false; + uint16_t class; + + igt_assert(c); + igt_assert(create); + + class = ((struct drm_xe_engine_class_instance *)(create->instances))[0].engine_class; + + igt_assert_eq(igt_ioctl(fd, DRM_IOCTL_XE_EXEC_QUEUE_CREATE, create), 0); + + for (ext = from_user_pointer(create->extensions); ext; + ext = from_user_pointer(ext->base.next_extension)) + if (ext->base.name == DRM_XE_EXEC_QUEUE_EXTENSION_SET_PROPERTY && + ext->property == DRM_XE_EXEC_QUEUE_SET_PROPERTY_EUDEBUG && + ext->value & DRM_XE_EXEC_QUEUE_EUDEBUG_FLAG_ENABLE) + send = true; + + if (send) + exec_queue_event(c, DRM_XE_EUDEBUG_EVENT_CREATE, fd, create->vm_id, + create->exec_queue_id, class, create->width); + + return create->exec_queue_id; +} + +/** + * xe_eudebug_client_exec_queue_destroy: + * @c: pointer to xe_eudebug_client structure + * @fd: xe client + * @create: exec_queue create drm struct which was used for creation + * + * Calls xe exec_queue destroy ioctl and logs the corresponding event in + * client's event log. + */ +void xe_eudebug_client_exec_queue_destroy(struct xe_eudebug_client *c, int fd, + struct drm_xe_exec_queue_create *create) +{ + struct drm_xe_exec_queue_destroy destroy = {}; + struct drm_xe_ext_set_property *ext; + bool send = false; + uint16_t class; + + igt_assert(c); + igt_assert(create); + + destroy.exec_queue_id = create->exec_queue_id; + class = ((struct drm_xe_engine_class_instance *)(create->instances))[0].engine_class; + + for (ext = from_user_pointer(create->extensions); ext; + ext = from_user_pointer(ext->base.next_extension)) + if (ext->base.name == DRM_XE_EXEC_QUEUE_EXTENSION_SET_PROPERTY && + ext->property == DRM_XE_EXEC_QUEUE_SET_PROPERTY_EUDEBUG && + ext->value & DRM_XE_EXEC_QUEUE_EUDEBUG_FLAG_ENABLE) + send = true; + + if (send) + exec_queue_event(c, DRM_XE_EUDEBUG_EVENT_DESTROY, fd, create->vm_id, + create->exec_queue_id, class, create->width); + + igt_assert_eq(igt_ioctl(fd, DRM_IOCTL_XE_EXEC_QUEUE_DESTROY, &destroy), 0); +} + +/** + * xe_eudebug_client_vm_bind_event: + * @c: pointer to xe_eudebug_client structure + * @event_flags: base event flags + * @fd: xe client + * @vm: vm handle + * @bind_flags: bind flags of vm_bind_event + * @num_binds: number of bind (operations) for event + * @ref_seqno: base vm bind reference seqno + * Logs vm bind event in client's event log. + */ +void xe_eudebug_client_vm_bind_event(struct xe_eudebug_client *c, + uint32_t event_flags, int fd, + uint32_t vm, uint32_t bind_flags, + uint32_t num_binds, u64 *ref_seqno) +{ + struct drm_xe_eudebug_event_vm_bind evmb; + + igt_assert(c); + igt_assert(ref_seqno); + + base_event(c, to_base(evmb), DRM_XE_EUDEBUG_EVENT_VM_BIND, + event_flags, sizeof(evmb)); + evmb.client_handle = fd; + evmb.vm_handle = vm; + evmb.flags = bind_flags; + evmb.num_binds = num_binds; + + *ref_seqno = evmb.base.seqno; + + xe_eudebug_event_log_write(c->log, (void *)&evmb); +} + +/** + * xe_eudebug_client_vm_bind_op_event: + * @c: pointer to xe_eudebug_client structure + * @event_flags: base event flags + * @bind_ref_seqno: base vm bind reference seqno + * @op_ref_seqno: output, the vm_bind_op event seqno + * @addr: ppgtt address + * @size: size of the binding + * @num_extensions: number of vm bind op extensions + * + * Logs vm bind op event in client's event log. + */ +void xe_eudebug_client_vm_bind_op_event(struct xe_eudebug_client *c, uint32_t event_flags, + uint64_t bind_ref_seqno, uint64_t *op_ref_seqno, + uint64_t addr, uint64_t range, + uint64_t num_extensions) +{ + struct drm_xe_eudebug_event_vm_bind_op op; + + igt_assert(c); + igt_assert(op_ref_seqno); + + base_event(c, to_base(op), DRM_XE_EUDEBUG_EVENT_VM_BIND_OP, + event_flags, sizeof(op)); + op.vm_bind_ref_seqno = bind_ref_seqno; + op.addr = addr; + op.range = range; + op.num_extensions = num_extensions; + + *op_ref_seqno = op.base.seqno; + + xe_eudebug_event_log_write(c->log, (void *)&op); +} + +/** + * xe_eudebug_client_vm_bind_op_metadata_event: + * @c: pointer to xe_eudebug_client structure + * @event_flags: base event flags + * @op_ref_seqno: base vm bind op reference seqno + * @metadata_handle: metadata handle + * @metadata_cookie: metadata cookie + * + * Logs vm bind op metadata event in client's event log. + */ +void xe_eudebug_client_vm_bind_op_metadata_event(struct xe_eudebug_client *c, + uint32_t event_flags, uint64_t op_ref_seqno, + uint64_t metadata_handle, uint64_t metadata_cookie) +{ + struct drm_xe_eudebug_event_vm_bind_op_metadata op; + + igt_assert(c); + + base_event(c, to_base(op), DRM_XE_EUDEBUG_EVENT_VM_BIND_OP_METADATA, + event_flags, sizeof(op)); + op.vm_bind_op_ref_seqno = op_ref_seqno; + op.metadata_handle = metadata_handle; + op.metadata_cookie = metadata_cookie; + + xe_eudebug_event_log_write(c->log, (void *)&op); +} + +/** + * xe_eudebug_client_vm_bind_ufence_event: + * @c: pointer to xe_eudebug_client structure + * @event_flags: base event flags + * @ref_seqno: base vm bind event seqno + * + * Logs vm bind ufence event in client's event log. + */ +void xe_eudebug_client_vm_bind_ufence_event(struct xe_eudebug_client *c, uint32_t event_flags, + uint64_t ref_seqno) +{ + struct drm_xe_eudebug_event_vm_bind_ufence f; + + igt_assert(c); + + base_event(c, to_base(f), DRM_XE_EUDEBUG_EVENT_VM_BIND_UFENCE, + event_flags, sizeof(f)); + f.vm_bind_ref_seqno = ref_seqno; + + xe_eudebug_event_log_write(c->log, (void *)&f); +} + +static bool has_user_fence(const struct drm_xe_sync *sync, uint32_t num_syncs) +{ + while (num_syncs--) + if (sync[num_syncs].type == DRM_XE_SYNC_TYPE_USER_FENCE) + return true; + + return false; +} + +#define for_each_metadata(__m, __ext) \ + for ((__m) = from_user_pointer(__ext); \ + (__m); \ + (__m) = from_user_pointer((__m)->base.next_extension)) \ + if ((__m)->base.name == XE_VM_BIND_OP_EXTENSIONS_ATTACH_DEBUG) + +static int __xe_eudebug_client_vm_bind(struct xe_eudebug_client *c, + int fd, uint32_t vm, uint32_t exec_queue, + uint32_t bo, uint64_t offset, + uint64_t addr, uint64_t size, + uint32_t op, uint32_t flags, + struct drm_xe_sync *sync, + uint32_t num_syncs, + uint32_t prefetch_region, + uint8_t pat_index, uint64_t op_ext) +{ + struct drm_xe_vm_bind_op_ext_attach_debug *metadata; + const bool ufence = has_user_fence(sync, num_syncs); + const uint32_t bind_flags = ufence ? + DRM_XE_EUDEBUG_EVENT_VM_BIND_FLAG_UFENCE : 0; + uint64_t seqno = 0, op_seqno = 0, num_metadata = 0; + uint32_t bind_base_flags = 0; + int ret; + + for_each_metadata(metadata, op_ext) + num_metadata++; + + switch (op) { + case DRM_XE_VM_BIND_OP_MAP: + bind_base_flags = DRM_XE_EUDEBUG_EVENT_CREATE; + break; + case DRM_XE_VM_BIND_OP_UNMAP: + bind_base_flags = DRM_XE_EUDEBUG_EVENT_DESTROY; + igt_assert_eq(num_metadata, 0); + igt_assert_eq(ufence, false); + break; + default: + /* XXX unmap all? */ + igt_assert(op); + break; + } + + ret = ___xe_vm_bind(fd, vm, exec_queue, bo, offset, addr, size, + op, flags, sync, num_syncs, prefetch_region, + pat_index, 0, op_ext); + + if (ret) + return ret; + + if (!bind_base_flags) + return -EINVAL; + + xe_eudebug_client_vm_bind_event(c, DRM_XE_EUDEBUG_EVENT_STATE_CHANGE, + fd, vm, bind_flags, 1, &seqno); + xe_eudebug_client_vm_bind_op_event(c, bind_base_flags, + seqno, &op_seqno, addr, size, + num_metadata); + + for_each_metadata(metadata, op_ext) + xe_eudebug_client_vm_bind_op_metadata_event(c, + DRM_XE_EUDEBUG_EVENT_CREATE, + op_seqno, + metadata->metadata_id, + metadata->cookie); + if (ufence) + xe_eudebug_client_vm_bind_ufence_event(c, DRM_XE_EUDEBUG_EVENT_CREATE | + DRM_XE_EUDEBUG_EVENT_NEED_ACK, + seqno); + return ret; +} + +static void _xe_eudebug_client_vm_bind(struct xe_eudebug_client *c, int fd, + uint32_t vm, uint32_t bo, + uint64_t offset, uint64_t addr, uint64_t size, + uint32_t op, + uint32_t flags, + struct drm_xe_sync *sync, + uint32_t num_syncs, + uint64_t op_ext) +{ + const uint32_t exec_queue_id = 0; + const uint32_t prefetch_region = 0; + + igt_assert_eq(__xe_eudebug_client_vm_bind(c, fd, vm, exec_queue_id, bo, offset, + addr, size, op, flags, + sync, num_syncs, prefetch_region, + DEFAULT_PAT_INDEX, op_ext), + 0); +} + +/** + * xe_eudebug_client_vm_bind_flags + * @c: pointer to xe_eudebug_client structure + * @fd: xe client + * @vm: vm handle + * @bo: buffer object handle + * @offset: offset within buffer object + * @addr: ppgtt address + * @size: size of the binding + * @flags: vm_bind flags + * @sync: sync objects + * @num_syncs: number of sync objects + * @op_ext: BIND_OP extensions + * + * Calls xe vm_bind ioctl and logs the corresponding event in client's event log. + */ +void xe_eudebug_client_vm_bind_flags(struct xe_eudebug_client *c, int fd, uint32_t vm, + uint32_t bo, uint64_t offset, + uint64_t addr, uint64_t size, uint32_t flags, + struct drm_xe_sync *sync, uint32_t num_syncs, + uint64_t op_ext) +{ + igt_assert(c); + _xe_eudebug_client_vm_bind(c, fd, vm, bo, offset, addr, size, + DRM_XE_VM_BIND_OP_MAP, flags, + sync, num_syncs, op_ext); +} + +/** + * xe_eudebug_client_vm_bind + * @c: pointer to xe_eudebug_client structure + * @fd: xe client + * @vm: vm handle + * @bo: buffer object handle + * @offset: offset within buffer object + * @addr: ppgtt address + * @size: size of the binding + * + * Calls xe vm_bind ioctl and logs the corresponding event in client's event log. + */ +void xe_eudebug_client_vm_bind(struct xe_eudebug_client *c, int fd, uint32_t vm, + uint32_t bo, uint64_t offset, + uint64_t addr, uint64_t size) +{ + const uint32_t flags = 0; + struct drm_xe_sync *sync = NULL; + const uint32_t num_syncs = 0; + const uint64_t op_ext = 0; + + xe_eudebug_client_vm_bind_flags(c, fd, vm, bo, offset, addr, size, flags, sync, num_syncs, + op_ext); +} + +/** + * xe_eudebug_client_vm_unbind_flags + * @c: pointer to xe_eudebug_client structure + * @fd: xe client + * @vm: vm handle + * @offset: offset + * @addr: ppgtt address + * @size: size of the binding + * @flags: vm_bind flags + * @sync: sync objects + * @num_syncs: number of sync objects + * + * Calls xe vm_unbind ioctl and logs the corresponding event in client's event log. + */ +void xe_eudebug_client_vm_unbind_flags(struct xe_eudebug_client *c, int fd, + uint32_t vm, uint64_t offset, + uint64_t addr, uint64_t size, uint32_t flags, + struct drm_xe_sync *sync, uint32_t num_syncs) +{ + igt_assert(c); + _xe_eudebug_client_vm_bind(c, fd, vm, 0, offset, addr, size, + DRM_XE_VM_BIND_OP_UNMAP, flags, + sync, num_syncs, 0); +} + +/** + * xe_eudebug_client_vm_unbind + * @c: pointer to xe_eudebug_client structure + * @fd: xe client + * @vm: vm handle + * @offset: offset + * @addr: ppgtt address + * @size: size of the binding + * + * Calls xe vm_unbind ioctl and logs the corresponding event in client's event log. + */ +void xe_eudebug_client_vm_unbind(struct xe_eudebug_client *c, int fd, uint32_t vm, + uint64_t offset, uint64_t addr, uint64_t size) +{ + const uint32_t flags = 0; + struct drm_xe_sync *sync = NULL; + const uint32_t num_syncs = 0; + + xe_eudebug_client_vm_unbind_flags(c, fd, vm, offset, addr, size, + flags, sync, num_syncs); +} + +/** + * xe_eudebug_client_metadata_create: + * @c: pointer to xe_eudebug_client structure + * @fd: xe client + * @type: debug metadata type + * @len: size of @data + * @data: debug metadata paylad + * + * Calls xe metadata create ioctl and logs the corresponding event in + * client's event log. + * + * Return: valid debug metadata id. + */ +uint32_t xe_eudebug_client_metadata_create(struct xe_eudebug_client *c, int fd, + int type, size_t len, void *data) +{ + struct drm_xe_debug_metadata_create create = { + .type = type, + .user_addr = to_user_pointer(data), + .len = len + }; + + igt_assert(c); + igt_assert_eq(igt_ioctl(fd, DRM_IOCTL_XE_DEBUG_METADATA_CREATE, &create), 0); + + metadata_event(c, DRM_XE_EUDEBUG_EVENT_CREATE, fd, create.metadata_id, type, len); + + return create.metadata_id; +} + +/** + * xe_eudebug_client_metadata_destroy: + * @c: pointer to xe_eudebug_client structure + * @fd: xe client + * @id: xe debug metadata handle + * @type: debug metadata type + * @len: size of debug metadata payload + * + * Calls xe metadata destroy ioctl and logs the corresponding event in + * client's event log. + */ +void xe_eudebug_client_metadata_destroy(struct xe_eudebug_client *c, int fd, + uint32_t id, int type, size_t len) +{ + struct drm_xe_debug_metadata_destroy destroy = { .metadata_id = id }; + + igt_assert(c); + igt_assert_eq(igt_ioctl(fd, DRM_IOCTL_XE_DEBUG_METADATA_DESTROY, &destroy), 0); + + metadata_event(c, DRM_XE_EUDEBUG_EVENT_DESTROY, fd, id, type, len); +} + +void xe_eudebug_ack_ufence(int debugfd, + const struct drm_xe_eudebug_event_vm_bind_ufence *f) +{ + struct drm_xe_eudebug_ack_event ack = { 0, }; + char event_str[XE_EUDEBUG_EVENT_STRING_MAX_LEN]; + + igt_assert(f); + + ack.type = f->base.type; + ack.seqno = f->base.seqno; + + xe_eudebug_event_to_str((void *)f, event_str, XE_EUDEBUG_EVENT_STRING_MAX_LEN); + igt_debug("delivering ack for event: %s\n", event_str); + igt_assert_eq(igt_ioctl(debugfd, DRM_XE_EUDEBUG_IOCTL_ACK_EVENT, &ack), 0); +} diff --git a/lib/xe/xe_eudebug.h b/lib/xe/xe_eudebug.h new file mode 100644 index 000000000..29ab68fee --- /dev/null +++ b/lib/xe/xe_eudebug.h @@ -0,0 +1,220 @@ +/* SPDX-License-Identifier: MIT */ +/* + * Copyright © 2023 Intel Corporation + */ +#include +#include +#include +#include +#include + +#include "igt_list.h" + +struct xe_eudebug_event_log { + uint8_t *log; + unsigned int head; + unsigned int max_size; + char name[80]; + pthread_mutex_t lock; +}; + +enum xe_eudebug_debugger_worker_state { + DEBUGGER_WORKER_INACTIVE = 0, + DEBUGGER_WORKER_ACTIVE, + DEBUGGER_WORKER_QUITTING, +}; + +struct xe_eudebug_debugger { + int fd; + uint64_t flags; + + /* Used to smuggle private data */ + void *ptr; + + struct xe_eudebug_event_log *log; + + uint64_t event_count; + + uint64_t target_pid; + + struct igt_list_head triggers; + + int master_fd; + + pthread_t worker_thread; + enum xe_eudebug_debugger_worker_state worker_state; + + int p_client[2]; +}; + +struct xe_eudebug_client { + int pid; + uint64_t seqno; + uint64_t flags; + + /* Used to smuggle private data */ + void *ptr; + + struct xe_eudebug_event_log *log; + + int done; + int p_in[2]; + int p_out[2]; + + /* Used to pickup right device (the one used in debugger) */ + int master_fd; + + int timeout_ms; + + pthread_mutex_t lock; +}; + +struct xe_eudebug_session { + uint64_t flags; + struct xe_eudebug_client *client; + struct xe_eudebug_debugger *debugger; +}; + +typedef void (*xe_eudebug_client_work_fn)(struct xe_eudebug_client *); +typedef void (*xe_eudebug_trigger_fn)(struct xe_eudebug_debugger *, + struct drm_xe_eudebug_event *); + +#define xe_eudebug_for_each_engine(fd__, hwe__) \ + xe_for_each_engine(fd__, hwe__) \ + if (hwe__->engine_class == DRM_XE_ENGINE_CLASS_RENDER || \ + hwe__->engine_class == DRM_XE_ENGINE_CLASS_COMPUTE) + +#define xe_eudebug_for_each_event(_e, _log) \ + for ((_e) = (_e) ? (void *)(uint8_t *)(_e) + (_e)->len : \ + (void *)(_log)->log; \ + (uint8_t *)(_e) < (_log)->log + (_log)->head; \ + (_e) = (void *)(uint8_t *)(_e) + (_e)->len) + +#define xe_eudebug_assert(d, c) \ + do { \ + if (!(c)) { \ + xe_eudebug_event_log_print((d)->log, true); \ + igt_assert(c); \ + } \ + } while (0) + +#define xe_eudebug_assert_f(d, c, f...) \ + do { \ + if (!(c)) { \ + xe_eudebug_event_log_print((d)->log, true); \ + igt_assert_f(c, f); \ + } \ + } while (0) + +#define XE_EUDEBUG_EVENT_STRING_MAX_LEN 4096 + +/* + * Default abort timeout to use across xe_eudebug lib and tests if no specific + * timeout value is required. + */ +#define XE_EUDEBUG_DEFAULT_TIMEOUT_SEC 25ULL + +#define XE_EUDEBUG_FILTER_EVENT_NONE BIT(DRM_XE_EUDEBUG_EVENT_NONE) +#define XE_EUDEBUG_FILTER_EVENT_READ BIT(DRM_XE_EUDEBUG_EVENT_READ) +#define XE_EUDEBUG_FILTER_EVENT_OPEN BIT(DRM_XE_EUDEBUG_EVENT_OPEN) +#define XE_EUDEBUG_FILTER_EVENT_VM BIT(DRM_XE_EUDEBUG_EVENT_VM) +#define XE_EUDEBUG_FILTER_EVENT_EXEC_QUEUE BIT(DRM_XE_EUDEBUG_EVENT_EXEC_QUEUE) +#define XE_EUDEBUG_FILTER_EVENT_EU_ATTENTION BIT(DRM_XE_EUDEBUG_EVENT_EU_ATTENTION) +#define XE_EUDEBUG_FILTER_EVENT_VM_BIND BIT(DRM_XE_EUDEBUG_EVENT_VM_BIND) +#define XE_EUDEBUG_FILTER_EVENT_VM_BIND_OP BIT(DRM_XE_EUDEBUG_EVENT_VM_BIND_OP) +#define XE_EUDEBUG_FILTER_EVENT_VM_BIND_UFENCE BIT(DRM_XE_EUDEBUG_EVENT_VM_BIND_UFENCE) +#define XE_EUDEBUG_FILTER_ALL GENMASK(DRM_XE_EUDEBUG_EVENT_MAX_EVENT, 0) +#define XE_EUDEBUG_EVENT_IS_FILTERED(_e, _f) ((1UL << (_e)) & (_f)) + +int xe_eudebug_connect(int fd, pid_t pid, uint32_t flags); +const char *xe_eudebug_event_to_str(struct drm_xe_eudebug_event *e, char *buf, size_t len); +struct drm_xe_eudebug_event * +xe_eudebug_event_log_find_seqno(struct xe_eudebug_event_log *l, uint64_t seqno); +struct xe_eudebug_event_log * +xe_eudebug_event_log_create(const char *name, unsigned int max_size); +void xe_eudebug_event_log_destroy(struct xe_eudebug_event_log *l); +void xe_eudebug_event_log_print(struct xe_eudebug_event_log *l, bool debug); +void xe_eudebug_event_log_compare(struct xe_eudebug_event_log *c, struct xe_eudebug_event_log *d, + uint32_t filter); +void xe_eudebug_event_log_write(struct xe_eudebug_event_log *l, struct drm_xe_eudebug_event *e); +void xe_eudebug_event_log_match_opposite(struct xe_eudebug_event_log *l, uint32_t filter); + +bool xe_eudebug_debugger_available(int fd); +struct xe_eudebug_debugger * +xe_eudebug_debugger_create(int xe, uint64_t flags, void *data); +void xe_eudebug_debugger_destroy(struct xe_eudebug_debugger *d); +int xe_eudebug_debugger_attach(struct xe_eudebug_debugger *d, struct xe_eudebug_client *c); +void xe_eudebug_debugger_start_worker(struct xe_eudebug_debugger *d); +void xe_eudebug_debugger_stop_worker(struct xe_eudebug_debugger *d, int timeout_s); +void xe_eudebug_debugger_detach(struct xe_eudebug_debugger *d); +void xe_eudebug_debugger_set_data(struct xe_eudebug_debugger *c, void *ptr); +void xe_eudebug_debugger_add_trigger(struct xe_eudebug_debugger *d, int type, + xe_eudebug_trigger_fn fn); +void xe_eudebug_debugger_signal_stage(struct xe_eudebug_debugger *d, uint64_t stage); +void xe_eudebug_debugger_wait_stage(struct xe_eudebug_session *s, uint64_t stage); + +struct xe_eudebug_client * +xe_eudebug_client_create(int xe, xe_eudebug_client_work_fn work, uint64_t flags, void *data); +void xe_eudebug_client_destroy(struct xe_eudebug_client *c); +void xe_eudebug_client_start(struct xe_eudebug_client *c); +void xe_eudebug_client_stop(struct xe_eudebug_client *c); +void xe_eudebug_client_wait_done(struct xe_eudebug_client *c); +void xe_eudebug_client_signal_stage(struct xe_eudebug_client *c, uint64_t stage); +void xe_eudebug_client_wait_stage(struct xe_eudebug_client *c, uint64_t stage); + +uint64_t xe_eudebug_client_get_seqno(struct xe_eudebug_client *c); +void xe_eudebug_client_set_data(struct xe_eudebug_client *c, void *ptr); + +bool xe_eudebug_enable(int fd, bool enable); + +int xe_eudebug_client_open_driver(struct xe_eudebug_client *c); +void xe_eudebug_client_close_driver(struct xe_eudebug_client *c, int fd); +uint32_t xe_eudebug_client_vm_create(struct xe_eudebug_client *c, int fd, + uint32_t flags, uint64_t ext); +void xe_eudebug_client_vm_destroy(struct xe_eudebug_client *c, int fd, uint32_t vm); +uint32_t xe_eudebug_client_exec_queue_create(struct xe_eudebug_client *c, int fd, + struct drm_xe_exec_queue_create *create); +void xe_eudebug_client_exec_queue_destroy(struct xe_eudebug_client *c, int fd, + struct drm_xe_exec_queue_create *create); +void xe_eudebug_client_vm_bind_event(struct xe_eudebug_client *c, uint32_t event_flags, int fd, + uint32_t vm, uint32_t bind_flags, + uint32_t num_ops, uint64_t *ref_seqno); +void xe_eudebug_client_vm_bind_op_event(struct xe_eudebug_client *c, uint32_t event_flags, + uint64_t ref_seqno, uint64_t *op_ref_seqno, + uint64_t addr, uint64_t range, + uint64_t num_extensions); +void xe_eudebug_client_vm_bind_op_metadata_event(struct xe_eudebug_client *c, + uint32_t event_flags, uint64_t op_ref_seqno, + uint64_t metadata_handle, uint64_t metadata_cookie); +void xe_eudebug_client_vm_bind_ufence_event(struct xe_eudebug_client *c, uint32_t event_flags, + uint64_t ref_seqno); +void xe_eudebug_ack_ufence(int debugfd, + const struct drm_xe_eudebug_event_vm_bind_ufence *f); + +void xe_eudebug_client_vm_bind_flags(struct xe_eudebug_client *c, int fd, uint32_t vm, + uint32_t bo, uint64_t offset, + uint64_t addr, uint64_t size, uint32_t flags, + struct drm_xe_sync *sync, uint32_t num_syncs, + uint64_t op_ext); +void xe_eudebug_client_vm_bind(struct xe_eudebug_client *c, int fd, uint32_t vm, + uint32_t bo, uint64_t offset, + uint64_t addr, uint64_t size); +void xe_eudebug_client_vm_unbind_flags(struct xe_eudebug_client *c, int fd, + uint32_t vm, uint64_t offset, + uint64_t addr, uint64_t size, uint32_t flags, + struct drm_xe_sync *sync, uint32_t num_syncs); +void xe_eudebug_client_vm_unbind(struct xe_eudebug_client *c, int fd, uint32_t vm, + uint64_t offset, uint64_t addr, uint64_t size); + +uint32_t xe_eudebug_client_metadata_create(struct xe_eudebug_client *c, int fd, + int type, size_t len, void *data); +void xe_eudebug_client_metadata_destroy(struct xe_eudebug_client *c, int fd, + uint32_t id, int type, size_t len); + +struct xe_eudebug_session *xe_eudebug_session_create(int fd, + xe_eudebug_client_work_fn work, + unsigned int flags, + void *test_private); +void xe_eudebug_session_destroy(struct xe_eudebug_session *s); +void xe_eudebug_session_run(struct xe_eudebug_session *s); +void xe_eudebug_session_check(struct xe_eudebug_session *s, bool match_opposite, uint32_t filter); diff --git a/meson.build b/meson.build index f67655367..0d06721b4 100644 --- a/meson.build +++ b/meson.build @@ -90,9 +90,11 @@ build_chamelium = get_option('chamelium') build_docs = get_option('docs') build_tests = not get_option('tests').disabled() build_xe = not get_option('xe_driver').disabled() +build_xe_eudebug = get_option('xe_eudebug').enabled() with_libdrm = get_option('libdrm_drivers') build_info = ['Build type: ' + get_option('buildtype')] +build_info += 'Build Xe EU debugger test framework: @0@'.format(build_xe_eudebug) inc = include_directories('include', 'include/drm-uapi', 'include/linux-uapi', 'lib', 'lib/stubs/syscalls', '.') diff --git a/meson_options.txt b/meson_options.txt index 6a9493ea6..11922523b 100644 --- a/meson_options.txt +++ b/meson_options.txt @@ -42,6 +42,11 @@ option('xe_driver', value : 'enabled', description : 'Build tests for Xe driver') +option('xe_eudebug', + type : 'feature', + value : 'disabled', + description : 'Build library for Xe EU debugger') + option('libdrm_drivers', type : 'array', value : ['auto'], -- 2.34.1