1 /*
2  * Copyright (c) 2014 Jean-Sebastien Pedron <dumbbell@FreeBSD.org>
3  * Copyright (c) 2016 Koop Mast <kwm@FreeBSD.org>
4  * All rights reserved.
5  *
6  * Redistribution and use in source and binary forms, with or without
7  * modification, are permitted provided that the following conditions
8  * are met:
9  * 1. Redistributions of source code must retain the above copyright
10  *    notice, this list of conditions and the following disclaimer
11  *    in this position and unchanged.
12  * 2. Redistributions in binary form must reproduce the above copyright
13  *    notice, this list of conditions and the following disclaimer in the
14  *    documentation and/or other materials provided with the distribution.
15  *
16  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``AS IS'' AND ANY EXPRESS OR
17  * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
18  * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
19  * IN NO EVENT SHALL THE AUTHOR(S) BE LIABLE FOR ANY DIRECT, INDIRECT,
20  * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
21  * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
22  * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
23  * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
24  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
25  * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26  */
27 
28 #include <sys/types.h>
29 #include <sys/sysctl.h>
30 
31 #include <errno.h>
32 #include <stdio.h>
33 #include <string.h>
34 #include <unistd.h>
35 #include <fcntl.h>
36 #include <sys/pciio.h>
37 
38 #if defined(HAVE_LIBPROCSTAT_H)
39 # include <sys/param.h>
40 # include <sys/queue.h>
41 # include <sys/socket.h>
42 # include <kvm.h>
43 # include <libprocstat.h>
44 #else
45 # include <sys/stat.h>
46 # include <dirent.h>
47 #endif
48 
49 #include "libdevq.h"
50 
51 int
devq_device_get_devpath_from_fd(int fd,char * path,size_t * path_len)52 devq_device_get_devpath_from_fd(int fd,
53     char *path, size_t *path_len)
54 {
55 #if defined(HAVE_LIBPROCSTAT_H)
56 	int ret;
57 	struct procstat *procstat;
58 	struct kinfo_proc *kip;
59 	struct filestat_list *head;
60 	struct filestat *fst;
61 	unsigned int count;
62 	size_t len;
63 
64 	ret = 0;
65 	head = NULL;
66 
67 	procstat = procstat_open_sysctl();
68 	if (procstat == NULL)
69 		return (-1);
70 
71 	count = 0;
72 	kip = procstat_getprocs(procstat, KERN_PROC_PID, getpid(), &count);
73 	if (kip == NULL || count != 1) {
74 		ret = -1;
75 		goto out;
76 	}
77 
78 	head = procstat_getfiles(procstat, kip, 0);
79 	if (head == NULL) {
80 		ret = -1;
81 		goto out;
82 	}
83 
84 	STAILQ_FOREACH(fst, head, next) {
85 		if (fst->fs_uflags != 0 ||
86 		    fst->fs_type != PS_FST_TYPE_VNODE ||
87 		    fst->fs_fd != fd)
88 			continue;
89 
90 		if (fst->fs_path == NULL) {
91 			errno = EBADF;
92 			ret = -1;
93 			break;
94 		}
95 
96 		len = strlen(fst->fs_path);
97 		if (path) {
98 			if (*path_len < len) {
99 				*path_len = len;
100 				errno = ENOMEM;
101 				ret = -1;
102 				break;
103 			}
104 
105 			memcpy(path, fst->fs_path, len);
106 		}
107 		*path_len = len;
108 		break;
109 	}
110 
111 out:
112 	if (head != NULL)
113 		procstat_freefiles(procstat, head);
114 	if (kip != NULL)
115 		procstat_freeprocs(procstat, kip);
116 	procstat_close(procstat);
117 
118 	return (ret);
119 #else /* !defined(HAVE_LIBPROCSTAT_H) */
120 	int ret, found;
121 	DIR *dir;
122 	struct stat st;
123 	struct dirent *dp;
124 	char tmp_path[256];
125 	size_t tmp_path_len;
126 
127 	/*
128 	 * FIXME: This function is specific to DRM devices.
129 	 */
130 #define DEVQ_DRIDEV_DIR "/dev/dri"
131 
132 	ret = fstat(fd, &st);
133 	if (ret != 0)
134 		return (-1);
135 	if (!S_ISCHR(st.st_mode)) {
136 		errno = EBADF;
137 		return (-1);
138 	}
139 
140 	dir = opendir(DEVQ_DRIDEV_DIR);
141 	if (dir == NULL)
142 		return (-1);
143 
144 	found = 0;
145 	while ((dp = readdir(dir)) != NULL) {
146 		struct stat tmp_st;
147 
148 		if (dp->d_name[0] == '.')
149 			continue;
150 
151 		tmp_path_len = strlen(DEVQ_DRIDEV_DIR);
152 		strcpy(tmp_path, DEVQ_DRIDEV_DIR);
153 		tmp_path[tmp_path_len++] = '/';
154 		tmp_path[tmp_path_len] = '\0';
155 
156 		strcpy(tmp_path + tmp_path_len, dp->d_name);
157 		tmp_path_len += dp->d_namlen;
158 		tmp_path[tmp_path_len] = '\0';
159 
160 		ret = stat(tmp_path, &tmp_st);
161 		if (ret != 0)
162 			continue;
163 
164 		if (st.st_dev  == tmp_st.st_dev &&
165 		    st.st_ino  == tmp_st.st_ino) {
166 			found = 1;
167 			break;
168 		}
169 	}
170 
171 	closedir(dir);
172 
173 	if (!found) {
174 		errno = EBADF;
175 		return -(1);
176 	}
177 
178 	if (path) {
179 		if (*path_len < tmp_path_len) {
180 			*path_len = tmp_path_len;
181 			errno = ENOMEM;
182 			return (-1);
183 		}
184 
185 		memcpy(path, tmp_path, tmp_path_len);
186 	}
187 	if (path_len)
188 		*path_len = tmp_path_len;
189 
190 	return (0);
191 #endif /* defined(HAVE_LIBPROCSTAT_H) */
192 }
193 
194 static int
devq_compare_vgapci_busaddr(int i,int * domain,int * bus,int * slot,int * function)195 devq_compare_vgapci_busaddr(int i, int *domain, int *bus, int *slot,
196     int *function)
197 {
198 	int ret;
199 	char sysctl_name[32], sysctl_value[128];
200 	size_t sysctl_value_len;
201 
202 	sprintf(sysctl_name, "dev.vgapci.%d.%%location", i);
203 
204 	sysctl_value_len = sizeof(sysctl_value);
205 	memset(sysctl_value, 0, sysctl_value_len);
206 	ret = sysctlbyname(sysctl_name, sysctl_value,
207 	    &sysctl_value_len, NULL, 0);
208 	if (ret != 0)
209 		return (-1);
210 
211 	/*
212 	 * dev.vgapci.$m.%location can have two formats:
213 	 *     o  "pci0:2:0:0 handle=\_SB_.PCI0.PEG3.MXM3" (FreeBSD 11+)
214 	 *     o  "slot=1 function=0" (up-to FreeBSD 10)
215 	 */
216 
217 	ret = sscanf(sysctl_value, "pci%d:%d:%d:%d %*s",
218 	    domain, bus, slot, function);
219 	if (ret == 4)
220 		return (0);
221 
222 	ret = sscanf(sysctl_value, "slot=%d function=%d %*s",
223 	    slot, function);
224 	if (ret != 2)
225 		return (-1);
226 
227 	sprintf(sysctl_name, "dev.vgapci.%d.%%parent", i);
228 
229 	sysctl_value_len = sizeof(sysctl_value);
230 	memset(sysctl_value, 0, sysctl_value_len);
231 	ret = sysctlbyname(sysctl_name, sysctl_value,
232 	    &sysctl_value_len, NULL, 0);
233 	if (ret != 0)
234 		return (-1);
235 
236 	ret = sscanf(sysctl_value, "pci%d", bus);
237 	if (ret != 1)
238 		return (-1);
239 
240 	/* FIXME: What domain to assume? */
241 	*domain = 0;
242 
243 	return (0);
244 }
245 
246 int
devq_device_get_pcibusaddr(int fd,int * domain,int * bus,int * slot,int * function)247 devq_device_get_pcibusaddr(int fd, int *domain,
248 	int *bus, int *slot, int *function)
249 {
250 	int i, dev, ret;
251 	char sysctl_name[32], sysctl_value[128];
252 	const char *busid_format;
253 	size_t sysctl_value_len;
254 
255 	/*
256 	 * FIXME: This function is specific to DRM devices.
257 	 */
258 
259 	/*
260 	 * We don't need the driver name, but this function already
261 	 * walks the hw.dri.* tree and returns the number in
262 	 * hw.dri.$number.
263 	 */
264 	dev = devq_device_drm_get_drvname_from_fd(fd, NULL, NULL);
265 	if (dev < 0)
266 		return (-1);
267 
268 	/*
269 	 * Read the hw.dri.$n.busid sysctl to get the location of the
270 	 * device on the PCI bus. We can then use this location to find
271 	 * the corresponding dev.vgapci.$m tree.
272 	 */
273 	sprintf(sysctl_name, "hw.dri.%d.busid", dev);
274 
275 	busid_format = "pci:%x:%x:%x.%d";
276 	sysctl_value_len = sizeof(sysctl_value);
277 	memset(sysctl_value, 0, sysctl_value_len);
278 	ret = sysctlbyname(sysctl_name, sysctl_value, &sysctl_value_len,
279 	    NULL, 0);
280 
281 	if (ret != 0) {
282 		/*
283 		 * If hw.dri.$n.busid isn't available, fallback on
284 		 * hw.dri.$n.name.
285 		 */
286 		busid_format = "%*s %*s pci:%d:%d:%d.%d";
287 		sysctl_value_len = sizeof(sysctl_value);
288 		memset(sysctl_value, 0, sysctl_value_len);
289 		sprintf(sysctl_name, "hw.dri.%d.name", dev);
290 		ret = sysctlbyname(sysctl_name, sysctl_value, &sysctl_value_len,
291 		    NULL, 0);
292 	}
293 
294 	if (ret != 0)
295 		return (-1);
296 
297 	ret = sscanf(sysctl_value, busid_format,
298 	    domain, bus, slot, function);
299 	if (ret != 4) {
300 		errno = ENOENT;
301 		return (-1);
302 	}
303 
304 	return (0);
305 }
306 
307 int
devq_device_get_pciid_full_from_fd(int fd,int * vendor_id,int * device_id,int * subvendor_id,int * subdevice_id,int * revision_id)308 devq_device_get_pciid_full_from_fd(int fd,
309     int *vendor_id, int *device_id, int *subvendor_id,
310     int *subdevice_id, int *revision_id)
311 {
312 	int i, ret, dev, domain, bus, slot, function;
313 	char sysctl_name[32], sysctl_value[128];
314 	const char *busid_format;
315 	size_t sysctl_value_len;
316 
317 	/*
318 	 * FIXME: This function is specific to DRM devices.
319 	 */
320 
321 	/*
322 	 * We don't need the driver name, but this function already
323 	 * walks the hw.dri.* tree and returns the number in
324 	 * hw.dri.$number.
325 	 */
326 	dev = devq_device_drm_get_drvname_from_fd(fd, NULL, NULL);
327 	if (dev < 0)
328 		return (-1);
329 
330 	/*
331 	 * Read the hw.dri.$n.busid sysctl to get the location of the
332 	 * device on the PCI bus. We can then use this location to find
333 	 * the corresponding dev.vgapci.$m tree.
334 	 */
335 	sprintf(sysctl_name, "hw.dri.%d.busid", dev);
336 
337 	busid_format = "pci:%x:%x:%x.%d";
338 	sysctl_value_len = sizeof(sysctl_value);
339 	memset(sysctl_value, 0, sysctl_value_len);
340 	ret = sysctlbyname(sysctl_name, sysctl_value, &sysctl_value_len,
341 	    NULL, 0);
342 
343 	if (ret != 0) {
344 		/*
345 		 * If hw.dri.$n.busid isn't available, fallback on
346 		 * hw.dri.$n.name.
347 		 */
348 		busid_format = "%*s %*s pci:%d:%d:%d.%d";
349 		sysctl_value_len = sizeof(sysctl_value);
350 		memset(sysctl_value, 0, sysctl_value_len);
351 		sprintf(sysctl_name, "hw.dri.%d.name", dev);
352 		ret = sysctlbyname(sysctl_name, sysctl_value, &sysctl_value_len,
353 		    NULL, 0);
354 	}
355 
356 	if (ret != 0)
357 		return (-1);
358 
359 	ret = sscanf(sysctl_value, busid_format,
360 	    &domain, &bus, &slot, &function);
361 	if (ret != 4) {
362 		errno = ENOENT;
363 		return (-1);
364 	}
365 
366 	/*
367 	 * Now, look at all dev.vgapci.$m trees until we find the
368 	 * correct device. We specifically look at:
369 	 *     o  dev.vgapci.$m.%location
370 	 *     o  dev.vgapci.$m.%parent
371 	 */
372 	for (i = 0; i < DEVQ_MAX_DEVS; ++i) {
373 		int tmp_domain, tmp_bus, tmp_slot, tmp_function;
374 
375 		ret = devq_compare_vgapci_busaddr(i, &tmp_domain, &tmp_bus,
376 		    &tmp_slot, &tmp_function);
377 
378 		if (ret == 0 &&
379 		    tmp_domain == domain &&
380 		    tmp_bus == bus &&
381 		    tmp_slot == slot &&
382 		    tmp_function == function)
383 			break;
384 	}
385 
386 	if (i == DEVQ_MAX_DEVS) {
387 		errno = ENOENT;
388 		return (-1);
389 	}
390 
391 	/*
392 	 * Ok, we have the right tree. Let's read dev.vgapci.$m.%pnpinfo
393 	 * to gather the PCI ID.
394 	 */
395 	sprintf(sysctl_name, "dev.vgapci.%d.%%pnpinfo", i);
396 
397 	sysctl_value_len = sizeof(sysctl_value);
398 	memset(sysctl_value, 0, sysctl_value_len);
399 	ret = sysctlbyname(sysctl_name, sysctl_value,
400 	    &sysctl_value_len, NULL, 0);
401 	if (ret != 0)
402 		return (-1);
403 
404 	ret = sscanf(sysctl_value, "vendor=0x%04x device=0x%04x subvendor=0x%04x subdevice=0x%04x",
405 	    vendor_id, device_id, subvendor_id, subdevice_id);
406 	if (ret != 4) {
407 		errno = EINVAL;
408 		return (-1);
409 	}
410 
411 	/* XXX: add code to find out revision id */
412 	revision_id = 0;
413 
414 	return (0);
415 }
416 
417 int
devq_device_get_pciid_from_fd(int fd,int * vendor_id,int * device_id)418 devq_device_get_pciid_from_fd(int fd,
419     int *vendor_id, int *device_id)
420 {
421 	int subvendor_id, subdevice_id, revision_id;
422 
423 	return devq_device_get_pciid_full_from_fd(fd,
424 		vendor_id, device_id, &subvendor_id,
425 		&subdevice_id, &revision_id);
426 }
427 
428