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