| // 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: |
| case 0x116: |
| case 0x126: |
| // ivy bridge |
| case 0x166: |
| 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__) |