1// Copyright (c) 2012 The Chromium OS Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5/*
6 * The purpose of this test is to exercise the GPU failure path.
7 * We craft an erroneous GPU command packet and send it to the GPU,
8 * and wait for a udev event notifying us of a GPU hang.
9 * If the event doesn't come back, the test fails.
10 *
11 * This test must run with ui stopped.
12 */
13
14#include <stdio.h>
15#include <stdlib.h>
16#include <unistd.h>
17
18void OUTPUT_INFO(char *msg) {
19	printf("INFO: %s\n", msg);
20	fflush(0);
21}
22void OUTPUT_WARNING(char *msg) {
23	printf("WARNING: %s\n", msg);
24	fflush(0);
25}
26void OUTPUT_ERROR(char *msg) {
27	printf("ERROR: %s\n", msg);
28	fflush(0);
29}
30void OUTPUT_RUN() {
31	printf("[ RUN      ] graphics_GpuReset\n");
32	fflush(0);
33}
34void EXIT(int code) {
35	// Sleep a bit. This is not strictly required but will avoid the case where
36	// we call the test back to back and the kernel thinks the GPU is toast.
37	OUTPUT_INFO("sleep(10) to prevent the kernel from thinking the GPU is completely locked.");
38	sleep(10);
39	exit(code);
40}
41void OUTPUT_PASS_AND_EXIT() {
42	printf("[       OK ] graphics_GpuReset\n");
43	fflush(0);
44	EXIT(0);
45}
46void OUTPUT_FAIL_AND_EXIT(char *msg) {
47	printf("[  FAILED  ] graphics_GpuReset %s\n", msg);
48	fflush(0);
49	EXIT(-1);
50}
51
52#if !defined(__INTEL_GPU__)
53
54#pragma message "Compiling for GPU other than Intel."
55
56int main(int argc, char **argv)
57{
58	OUTPUT_RUN();
59	OUTPUT_WARNING("The gpureset test is defined for some Intel GPUs only.");
60	OUTPUT_PASS_AND_EXIT();
61	return 0;
62}
63
64#else
65
66#pragma message "Compiling for Intel GPU."
67
68#include <assert.h>
69#include <errno.h>
70#include <fcntl.h>
71#include <fnmatch.h>
72#define LIBUDEV_I_KNOW_THE_API_IS_SUBJECT_TO_CHANGE
73#include <libudev.h>
74#include <stdbool.h>
75#include <string.h>
76#include <sys/ioctl.h>
77#include <sys/types.h>
78#include <sys/select.h>
79#include <sys/stat.h>
80
81#include "xf86drm.h"
82#include "i915_drm.h"
83#include "intel_bufmgr.h"
84
85#define DRM_TEST_MASTER 0x01
86
87
88static int is_master(int fd)
89{
90	drm_client_t client;
91	int ret;
92
93	/* Check that we're the only opener and authed. */
94	client.idx = 0;
95	ret = ioctl(fd, DRM_IOCTL_GET_CLIENT, &client);
96	assert (ret == 0);
97	if (!client.auth)
98		return 0;
99	client.idx = 1;
100	ret = ioctl(fd, DRM_IOCTL_GET_CLIENT, &client);
101	if (ret != -1 || errno != EINVAL)
102		return 0;
103
104	return 1;
105}
106
107/** Open the first DRM device matching the criteria. */
108int drm_open_matching(const char *pci_glob, int flags)
109{
110	struct udev *udev;
111	struct udev_enumerate *e;
112	struct udev_device *device, *parent;
113        struct udev_list_entry *entry;
114	const char *pci_id, *path;
115	const char *usub, *dnode;
116	int fd;
117
118	udev = udev_new();
119	if (udev == NULL)
120		return -1;
121
122	fd = -1;
123	e = udev_enumerate_new(udev);
124	udev_enumerate_add_match_subsystem(e, "drm");
125        udev_enumerate_scan_devices(e);
126        udev_list_entry_foreach(entry, udev_enumerate_get_list_entry(e)) {
127		path = udev_list_entry_get_name(entry);
128		device = udev_device_new_from_syspath(udev, path);
129		parent = udev_device_get_parent(device);
130		usub = udev_device_get_subsystem(parent);
131		/* Filter out KMS output devices. */
132		if (!usub || (strcmp(usub, "pci") != 0))
133			continue;
134		pci_id = udev_device_get_property_value(parent, "PCI_ID");
135		if (fnmatch(pci_glob, pci_id, 0) != 0)
136			continue;
137		dnode = udev_device_get_devnode(device);
138		if (strstr(dnode, "control"))
139			continue;
140		fd = open(dnode, O_RDWR);
141		if (fd < 0)
142			continue;
143		if ((flags & DRM_TEST_MASTER) && !is_master(fd)) {
144			close(fd);
145			fd = -1;
146			continue;
147		}
148
149		break;
150	}
151        udev_enumerate_unref(e);
152	udev_unref(udev);
153
154	return fd;
155}
156
157struct udev_monitor* udev_init()
158{
159	char* subsystem = "drm";
160	struct udev* udev;
161	// Create the udev object.
162	udev = udev_new();
163	if (!udev) {
164		OUTPUT_ERROR("Can't get create udev object.");
165		return NULL;
166	}
167
168	// Create the udev monitor structure.
169	struct udev_monitor* monitor = udev_monitor_new_from_netlink(udev, "udev");
170	if (!monitor) {
171		OUTPUT_ERROR("Can't get create udev monitor");
172		udev_unref(udev);
173		return NULL;
174	}
175
176	udev_monitor_filter_add_match_subsystem_devtype(monitor,
177			subsystem,
178			NULL);
179	udev_monitor_enable_receiving(monitor);
180
181	return monitor;
182}
183
184int udev_wait(struct udev_monitor* monitor)
185{
186	fd_set fds;
187	struct timeval tv;
188	int ret;
189
190	int fd = udev_monitor_get_fd(monitor);
191
192	FD_ZERO(&fds);
193	FD_SET(fd, &fds);
194
195	// Wait for at most 20 seconds for the event to come back.
196	tv.tv_sec = 20;
197	tv.tv_usec = 0;
198
199	ret = select(fd+1, &fds, NULL, NULL, &tv);
200
201	if (ret>0)
202	{
203		struct udev_device* dev = udev_monitor_receive_device(monitor);
204		if (dev) {
205		  // TODO(ihf): variable args to INFO function.
206			printf("INFO: Event on (%s|%s|%s) Action %s\n",
207					udev_device_get_devnode(dev),
208					udev_device_get_subsystem(dev),
209					udev_device_get_devtype(dev),
210					udev_device_get_action(dev));
211			udev_device_unref(dev);
212			return 1;
213		} else {
214			OUTPUT_ERROR("Can't get receive_device().");
215			return 0;
216		}
217	} else {
218		OUTPUT_ERROR("Timed out waiting for udev event to come back.");
219		return 0;
220	}
221}
222
223int main(int argc, char **argv)
224{
225	int fd;
226	int ret;
227	drmVersionPtr v;
228
229	OUTPUT_RUN();
230	OUTPUT_INFO("The GPU reset test *must* be run with 'stop ui'.");
231	OUTPUT_INFO("Otherwise following tests will likely hang/crash the machine.");
232	OUTPUT_INFO("sleep(10) to make sure UI has time to stop.");
233	sleep(10);
234
235	fd = drm_open_matching("*:*", 0);
236
237	if (fd < 0) {
238		OUTPUT_FAIL_AND_EXIT("Failed to open any drm device.");
239	}
240
241	v = drmGetVersion(fd);
242	assert(strlen(v->name) != 0);
243	if (strcmp(v->name, "i915") == 0) {
244		assert(v->version_major >= 1);
245	} else {
246		OUTPUT_WARNING("Can't find Intel GPU.");
247		OUTPUT_PASS_AND_EXIT();
248	}
249
250	unsigned int pci_id;
251	struct drm_i915_getparam gp;
252	gp.param = I915_PARAM_CHIPSET_ID;
253	gp.value = (int*)&pci_id;
254	ret = ioctl(fd, DRM_IOCTL_I915_GETPARAM, &gp, sizeof(gp));
255
256	if (ret) {
257		OUTPUT_FAIL_AND_EXIT("Can't get the i915 pci_id.");
258	}
259
260	// TODO(ihf): variable args to INFO function.
261	printf("INFO: i915 pci_id=0x%x.\n", pci_id);
262	switch(pci_id) {
263		// sandy bridge
264		case 0x102:
265                case 0x106: // Butterfly, Lumpy.
266		case 0x116:
267		case 0x126:
268		// ivy bridge
269                case 0x156: // Stout.
270                case 0x166: // Link.
271                // haswell
272                case 0xa06: // GT1, Peppy, Falco.
273                case 0xa16: // GT2.
274                case 0xa26: // GT3.
275			break;
276		default:
277		{
278			OUTPUT_WARNING("Intel GPU detected, but model doesn't support reset.");
279			OUTPUT_PASS_AND_EXIT();
280		}
281	}
282
283	struct udev_monitor* monitor = udev_init();
284	if (!monitor) {
285		OUTPUT_FAIL_AND_EXIT("udev init failed.");
286	}
287
288	drm_intel_bufmgr* bufmgr = drm_intel_bufmgr_gem_init(fd, 4096);
289
290	drm_intel_bo* bo;
291	bo = drm_intel_bo_alloc(bufmgr, "bogus cmdbuffer", 4096, 4096);
292
293	uint32_t invalid_buf[8] =
294	{
295		0x00000000, // NOOP
296		0xd00dd00d, // invalid command
297		0x00000000, // NOOP
298		0x00000000, // NOOP
299		0x05000000, // BATCHBUFFER_END
300		0x05000000, // BATCHBUFFER_END
301		0x05000000, // BATCHBUFFER_END
302		0x05000000, // BATCHBUFFER_END
303	};
304
305	// Copy our invalid cmd buffer into the bo.
306	ret = drm_intel_bo_subdata(bo, 0, sizeof(invalid_buf), invalid_buf);
307	if (ret != 0) {
308		OUTPUT_FAIL_AND_EXIT("bo_subdata failed.");
309	}
310
311	// Submit our invalid buffer.
312	ret = drm_intel_bo_exec(bo, sizeof(invalid_buf), NULL, 0, 0);
313	if (ret != 0) {
314		OUTPUT_FAIL_AND_EXIT("bo_exec failed.");
315	}
316	OUTPUT_INFO("Sent bogus buffer, waiting for event.");
317	// Submit our invalid buffer.
318	drm_intel_bo_wait_rendering(bo);
319
320	int res = udev_wait(monitor);
321
322	drmFree(v);
323	close(fd);
324
325	if (res) {
326		OUTPUT_PASS_AND_EXIT();
327	}
328	else {
329		OUTPUT_FAIL_AND_EXIT("GPU reset event did not come back.");
330	}
331
332	return 0;
333}
334
335#endif // defined(__arm__) ||  !defined(__INTEL_GPU__)
336