// Copyright (c) 2012 The Chromium OS Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. /* * The purpose of this test is to exercise the GPU failure path. * We craft an erroneous GPU command packet and send it to the GPU, * and wait for a udev event notifying us of a GPU hang. * If the event doesn't come back, the test fails. * * This test must run with ui stopped. */ #include <stdio.h> #include <stdlib.h> #include <unistd.h> void OUTPUT_INFO(char *msg) { printf("INFO: %s\n", msg); fflush(0); } void OUTPUT_WARNING(char *msg) { printf("WARNING: %s\n", msg); fflush(0); } void OUTPUT_ERROR(char *msg) { printf("ERROR: %s\n", msg); fflush(0); } void OUTPUT_RUN() { printf("[ RUN ] graphics_GpuReset\n"); fflush(0); } void EXIT(int code) { // Sleep a bit. This is not strictly required but will avoid the case where // we call the test back to back and the kernel thinks the GPU is toast. OUTPUT_INFO("sleep(10) to prevent the kernel from thinking the GPU is completely locked."); sleep(10); exit(code); } void OUTPUT_PASS_AND_EXIT() { printf("[ OK ] graphics_GpuReset\n"); fflush(0); EXIT(0); } void OUTPUT_FAIL_AND_EXIT(char *msg) { printf("[ FAILED ] graphics_GpuReset %s\n", msg); fflush(0); EXIT(-1); } #if !defined(__INTEL_GPU__) #pragma message "Compiling for GPU other than Intel." int main(int argc, char **argv) { OUTPUT_RUN(); OUTPUT_WARNING("The gpureset test is defined for some Intel GPUs only."); OUTPUT_PASS_AND_EXIT(); return 0; } #else #pragma message "Compiling for Intel GPU." #include <assert.h> #include <errno.h> #include <fcntl.h> #include <fnmatch.h> #define LIBUDEV_I_KNOW_THE_API_IS_SUBJECT_TO_CHANGE #include <libudev.h> #include <stdbool.h> #include <string.h> #include <sys/ioctl.h> #include <sys/types.h> #include <sys/select.h> #include <sys/stat.h> #include "xf86drm.h" #include "i915_drm.h" #include "intel_bufmgr.h" #define DRM_TEST_MASTER 0x01 static int is_master(int fd) { drm_client_t client; int ret; /* Check that we're the only opener and authed. */ client.idx = 0; ret = ioctl(fd, DRM_IOCTL_GET_CLIENT, &client); assert (ret == 0); if (!client.auth) return 0; client.idx = 1; ret = ioctl(fd, DRM_IOCTL_GET_CLIENT, &client); if (ret != -1 || errno != EINVAL) return 0; return 1; } /** Open the first DRM device matching the criteria. */ int drm_open_matching(const char *pci_glob, int flags) { struct udev *udev; struct udev_enumerate *e; struct udev_device *device, *parent; struct udev_list_entry *entry; const char *pci_id, *path; const char *usub, *dnode; int fd; udev = udev_new(); if (udev == NULL) return -1; fd = -1; e = udev_enumerate_new(udev); udev_enumerate_add_match_subsystem(e, "drm"); udev_enumerate_scan_devices(e); udev_list_entry_foreach(entry, udev_enumerate_get_list_entry(e)) { path = udev_list_entry_get_name(entry); device = udev_device_new_from_syspath(udev, path); parent = udev_device_get_parent(device); usub = udev_device_get_subsystem(parent); /* Filter out KMS output devices. */ if (!usub || (strcmp(usub, "pci") != 0)) continue; pci_id = udev_device_get_property_value(parent, "PCI_ID"); if (fnmatch(pci_glob, pci_id, 0) != 0) continue; dnode = udev_device_get_devnode(device); if (strstr(dnode, "control")) continue; fd = open(dnode, O_RDWR); if (fd < 0) continue; if ((flags & DRM_TEST_MASTER) && !is_master(fd)) { close(fd); fd = -1; continue; } break; } udev_enumerate_unref(e); udev_unref(udev); return fd; } struct udev_monitor* udev_init() { char* subsystem = "drm"; struct udev* udev; // Create the udev object. udev = udev_new(); if (!udev) { OUTPUT_ERROR("Can't get create udev object."); return NULL; } // Create the udev monitor structure. struct udev_monitor* monitor = udev_monitor_new_from_netlink(udev, "udev"); if (!monitor) { OUTPUT_ERROR("Can't get create udev monitor"); udev_unref(udev); return NULL; } udev_monitor_filter_add_match_subsystem_devtype(monitor, subsystem, NULL); udev_monitor_enable_receiving(monitor); return monitor; } int udev_wait(struct udev_monitor* monitor) { fd_set fds; struct timeval tv; int ret; int fd = udev_monitor_get_fd(monitor); FD_ZERO(&fds); FD_SET(fd, &fds); // Wait for at most 20 seconds for the event to come back. tv.tv_sec = 20; tv.tv_usec = 0; ret = select(fd+1, &fds, NULL, NULL, &tv); if (ret>0) { struct udev_device* dev = udev_monitor_receive_device(monitor); if (dev) { // TODO(ihf): variable args to INFO function. printf("INFO: Event on (%s|%s|%s) Action %s\n", udev_device_get_devnode(dev), udev_device_get_subsystem(dev), udev_device_get_devtype(dev), udev_device_get_action(dev)); udev_device_unref(dev); return 1; } else { OUTPUT_ERROR("Can't get receive_device()."); return 0; } } else { OUTPUT_ERROR("Timed out waiting for udev event to come back."); return 0; } } int main(int argc, char **argv) { int fd; int ret; drmVersionPtr v; OUTPUT_RUN(); OUTPUT_INFO("The GPU reset test *must* be run with 'stop ui'."); OUTPUT_INFO("Otherwise following tests will likely hang/crash the machine."); OUTPUT_INFO("sleep(10) to make sure UI has time to stop."); sleep(10); fd = drm_open_matching("*:*", 0); if (fd < 0) { OUTPUT_FAIL_AND_EXIT("Failed to open any drm device."); } v = drmGetVersion(fd); assert(strlen(v->name) != 0); if (strcmp(v->name, "i915") == 0) { assert(v->version_major >= 1); } else { OUTPUT_WARNING("Can't find Intel GPU."); OUTPUT_PASS_AND_EXIT(); } unsigned int pci_id; struct drm_i915_getparam gp; gp.param = I915_PARAM_CHIPSET_ID; gp.value = (int*)&pci_id; ret = ioctl(fd, DRM_IOCTL_I915_GETPARAM, &gp, sizeof(gp)); if (ret) { OUTPUT_FAIL_AND_EXIT("Can't get the i915 pci_id."); } // TODO(ihf): variable args to INFO function. printf("INFO: i915 pci_id=0x%x.\n", pci_id); switch(pci_id) { // sandy bridge case 0x102: case 0x106: // Butterfly, Lumpy. case 0x116: case 0x126: // ivy bridge case 0x156: // Stout. case 0x166: // Link. // haswell case 0xa06: // GT1, Peppy, Falco. case 0xa16: // GT2. case 0xa26: // GT3. break; default: { OUTPUT_WARNING("Intel GPU detected, but model doesn't support reset."); OUTPUT_PASS_AND_EXIT(); } } struct udev_monitor* monitor = udev_init(); if (!monitor) { OUTPUT_FAIL_AND_EXIT("udev init failed."); } drm_intel_bufmgr* bufmgr = drm_intel_bufmgr_gem_init(fd, 4096); drm_intel_bo* bo; bo = drm_intel_bo_alloc(bufmgr, "bogus cmdbuffer", 4096, 4096); uint32_t invalid_buf[8] = { 0x00000000, // NOOP 0xd00dd00d, // invalid command 0x00000000, // NOOP 0x00000000, // NOOP 0x05000000, // BATCHBUFFER_END 0x05000000, // BATCHBUFFER_END 0x05000000, // BATCHBUFFER_END 0x05000000, // BATCHBUFFER_END }; // Copy our invalid cmd buffer into the bo. ret = drm_intel_bo_subdata(bo, 0, sizeof(invalid_buf), invalid_buf); if (ret != 0) { OUTPUT_FAIL_AND_EXIT("bo_subdata failed."); } // Submit our invalid buffer. ret = drm_intel_bo_exec(bo, sizeof(invalid_buf), NULL, 0, 0); if (ret != 0) { OUTPUT_FAIL_AND_EXIT("bo_exec failed."); } OUTPUT_INFO("Sent bogus buffer, waiting for event."); // Submit our invalid buffer. drm_intel_bo_wait_rendering(bo); int res = udev_wait(monitor); drmFree(v); close(fd); if (res) { OUTPUT_PASS_AND_EXIT(); } else { OUTPUT_FAIL_AND_EXIT("GPU reset event did not come back."); } return 0; } #endif // defined(__arm__) || !defined(__INTEL_GPU__)