1 /*
2  * CDDL HEADER START
3  *
4  * The contents of this file are subject to the terms of the
5  * Common Development and Distribution License (the "License").
6  * You may not use this file except in compliance with the License.
7  *
8  * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
9  * or http://www.opensolaris.org/os/licensing.
10  * See the License for the specific language governing permissions
11  * and limitations under the License.
12  *
13  * When distributing Covered Code, include this CDDL HEADER in each
14  * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
15  * If applicable, add the following below this CDDL HEADER, with the
16  * fields enclosed by brackets "[]" replaced with your own identifying
17  * information: Portions Copyright [yyyy] [name of copyright owner]
18  *
19  * CDDL HEADER END
20  */
21 /*
22  * Copyright 2009 Sun Microsystems, Inc.  All rights reserved.
23  * Use is subject to license terms.
24  */
25 /*
26  * Copyright 2017 Gordon W. Ross
27  */
28 
29 /*
30  * Helper functions for sun_xf86drm.c to find DRM device nodes
31  * given a pair of device major/minor numbers.
32  *
33  * Some of this code was cribbed from the "prtconf" command.
34  */
35 
36 /*
37  * Device major numbers on illumos and Solaris are dynamic.
38  *
39  * Device node naming is also different.  See:
40  *	uts/common/io/drm/drm_stub.c drm_get_minor()
41  *	uts/common/io/drm/drm_sysfs.c drm_sysfs_device_add()
42  *
43  * Device "types" (encoded in some of the minor bits)
44  * Typical device setup with intel graphics:
45  *
46  * DRM_MINOR_LEGACY	/devices/pci@0,0/display@2:drm0
47  * DRM_MINOR_CONTROL	/devices/pci@0,0/display@2:controlD0
48  * DRM_MINOR_RENDER	/devices/pci@0,0/display@2:renderD0
49  * DRM_MINOR_VGATEXT	/devices/pci@0,0/display@2:gfx0
50  * DRM_MINOR_AGPMASTER	/devices/pci@0,0/display@2:agpmaster0
51  *
52  * The "devlinks" system also makes links under /dev
53  * which normally look something like this:
54  *
55  *	/dev/fbs/gfx0	-> .../display@2:gfx0
56  *	/dev/fb		-> .../display@2:gfx0
57  *	/dev/fb0	-> fbs/text-0
58  *	/dev/fb1 	-> fbs/gfx0
59  *	/dev/agp/agpmaster0 -> .../display@2:agpmaster0
60  *	/dev/dri/card1	-> .../display@2:controlD0
61  *
62  * (i916 driver)	/devices/pci@0,0/pci1179,1@0:agptarget
63  *	/dev/agp/agptarget0 -> /devices/pci@0,0/pci1179,1@0:agptarget
64  *
65  * (agpgart driver)	/devices/agpgart:agpgart
66  *	/dev/agpgart -> /devices/agpgart:agpgart
67  */
68 
69 #include <string.h>
70 #include <fcntl.h>
71 #include <fnmatch.h>
72 #include <libdevinfo.h>
73 #include <sys/stat.h>
74 #include <sys/ioctl.h>
75 #include <sys/mkdev.h>
76 
77 #include "xf86drm.h"
78 
79 struct search_args {
80 	char *s_path;
81 	int s_minor;
82 };
83 
84 static int _sun_drm_major; /* cache */
85 
86 /*
87  * Search callback function called for each minor node under
88  * some device that was found to be of possible interest.
89  * Return non-zero if match found.
90  */
91 static int
find_minor(di_node_t node,di_minor_t minor,struct search_args * sargs)92 find_minor(di_node_t node, di_minor_t minor, struct search_args *sargs)
93 {
94 	char	*path;
95 	dev_t	devt;
96 	int	ret;
97 
98 	devt = di_minor_devt(minor);
99 
100 	/* Does the caller want a specific minor number? */
101 	if (sargs->s_minor >= 0 &&
102 	    sargs->s_minor != minor(devt))
103 		return (0);
104 
105 	/*
106 	 * get device minor node path
107 	 * Note: allocates path
108 	 */
109 	if ((path = di_devfs_minor_path(minor)) == NULL)
110 		return (0);
111 	ret = asprintf(&sargs->s_path, "/devices%s", path);
112 	di_devfs_path_free(path);
113 
114 	if (ret < 0) {
115 		free(sargs->s_path);
116 		return (0);
117 	}
118 
119 	return (1);
120 }
121 
122 /*
123  * Call back function for di_walk_node, called for every device node.
124  */
125 static int
find_dev(di_node_t node,void * vargs)126 find_dev(di_node_t node, void *vargs)
127 {
128 	struct search_args *sargs = vargs;
129 	const char *node_name;
130 	di_minor_t minor_node;
131 
132 	node_name = di_node_name(node);
133 
134 	if (strcmp(node_name, "pseudo") == 0)
135 		return (DI_WALK_PRUNECHILD);
136 
137 	/*
138 	 * Had: udev_enumerate_add_match_subsystem(e, "drm");
139 	 * so skip anything outside "drm".
140 	 * For illumos or Solaris, I think we want to skip
141 	 * anything that's not named "display".
142 	 */
143 	if (strcmp(node_name, "display") != 0)
144 		return (DI_WALK_CONTINUE);
145 
146 	/*
147 	 * Walk the minor node paths searching...
148 	 */
149 	minor_node = DI_MINOR_NIL;
150 	while ((minor_node = di_minor_next(node, minor_node)) != DI_MINOR_NIL) {
151 		if (find_minor(node, minor_node, sargs)) {
152 			/* Found it! */
153 			return (DI_WALK_TERMINATE);
154 		}
155 	}
156 
157 	return (DI_WALK_CONTINUE);
158 }
159 
160 /*
161  * Helper function for xf86drm.c
162  *	drmGetMinorNameForFD()
163  *	drmParseSubsystemType()
164  *	drmParsePciBusInfo()
165  *	drmParsePciDeviceInfo()
166  *
167  * Given a device minor number, find the /devices path.
168  * Returns malloc'ed memory at *pathp, caller frees.
169  */
170 int
_sun_drm_find_device(int min,char ** pathp)171 _sun_drm_find_device(int min, char **pathp)
172 {
173 	struct search_args sargs;
174 	di_node_t root_node;
175 
176 	root_node = di_init("/", DINFOCPYALL);
177 	if (root_node == DI_NODE_NIL)
178 		return (-errno);
179 
180 	memset(&sargs, 0, sizeof (sargs));
181 
182 	di_walk_node(root_node, DI_WALK_CLDFIRST, &sargs, find_dev);
183 	di_fini(root_node);
184 
185 	if (sargs.s_path == NULL)
186 		return (-ENOENT);
187 
188 	if (pathp != NULL)
189 		*pathp = sargs.s_path;
190 	else
191 		free(sargs.s_path);
192 
193 	return (0);
194 }
195 
196 /*
197  * Helper function for DRM_MAJOR in xf86drm.c
198  * Return the major number assigned to the drm driver.
199  */
200 int
_sun_drm_get_major(void)201 _sun_drm_get_major(void)
202 {
203 	struct stat sbuf;
204 	dev_t dev = 0;
205 	char *path;
206 	int i, ret;
207 
208 	if (_sun_drm_major != 0)
209 		return (_sun_drm_major);
210 
211 	for (i = 0; i < DRM_MAX_MINOR; i++) {
212 		ret = _sun_drm_find_device(i, &path);
213 		if (ret != 0)
214 			continue;
215 		ret = stat(path, &sbuf);
216 		free(path);
217 		if (ret != 0)
218 			continue;
219 		if (!S_ISCHR(sbuf.st_mode))
220 			continue;
221 		dev = major(sbuf.st_rdev);
222 		if (dev != 0) {
223 			_sun_drm_major = dev;
224 			return (dev);
225 		}
226 	}
227 
228 	/*
229 	 * No devices found?  No way to return errors here,
230 	 * so just return an impossible value, and let
231 	 * later calls like open fail.
232 	 */
233 	return (MAXMAJ32);
234 }
235 
236 /*
237  * Helper function for drmParseSubsystemType()
238  * Returns one of: DRM_BUS_PCI, ... or -EINVAL.
239  * Our only driver implementations currently
240  * are on PCI.  Others todo.
241  */
242 int
_sun_drm_get_subsystem(char * path)243 _sun_drm_get_subsystem(char *path)
244 {
245 	char *p;
246 	int err;
247 
248 	p = path;
249 	if (strncmp(p, "/devices/", 9) == 0)
250 		p += 8;
251 	if (strncmp(p, "/pci", 4) == 0)
252 		err = DRM_BUS_PCI;
253 	else
254 		err = -EINVAL;
255 
256 	return (err);
257 }
258 
259 /*
260  * Helper function for drmParsePciBusInfo()
261  *
262  * Get PCI bus info for the give device path.
263  */
264 int
_sun_drm_get_pci_bus_info(char * path,drmPciBusInfo * info)265 _sun_drm_get_pci_bus_info(char *path, drmPciBusInfo *info)
266 {
267 	int n, bus, slot, unit;
268 
269 	/* Skip the /devices prefix, if present. */
270 	if (strncmp(path, "/devices/", 9) == 0)
271 		path += 8; /* the next slash */
272 
273 	n = sscanf(path, "/pci@%d,%d/display@%d:",
274 	    &bus, &slot, &unit);
275 	if (n != 3)
276 		return (-EINVAL);
277 
278 	info->domain = 0;
279 	info->bus = bus;
280 	info->dev = slot;
281 	info->func = unit;
282 
283 	return (0);
284 }
285 
286 /*
287  * Helper function for drmParsePciDeviceInfo()
288  *
289  * Get PCI data for the give device path.
290  *
291  * Note path given is a full minor under /devices i.e.
292  *	/devices/pci@0,0/display@2:drm
293  * and libdevinfo wants just:
294  *	/pci@0,0/display@2
295  */
296 int
_sun_drm_get_pci_dev_info(char * path,drmPciDeviceInfo * pcii)297 _sun_drm_get_pci_dev_info(char *path, drmPciDeviceInfo *pcii)
298 {
299 	char pathbuf[MAXPATHLEN];
300 	di_node_t node;
301 	char *s;
302 	int *propval = NULL;
303 	int ret = -EINVAL;
304 
305 	/* Skip the /devices prefix, if present. */
306 	if (strncmp(path, "/devices/", 9) == 0)
307 		path += 8; /* the next slash */
308 	strlcpy(pathbuf, path, sizeof (pathbuf));
309 
310 	/* Strip :drm0 or whatever */
311 	if ((s = strrchr(pathbuf, ':')) != NULL)
312 		*s = '\0';
313 
314 	/*
315 	 * Ask libdevinfo about this device
316 	 */
317 	node = di_init(pathbuf, DINFOCPYALL);
318 	if (node == DI_NODE_NIL)
319 		return (-EINVAL);
320 
321 	/*
322 	 * Get the various PCI properties.
323 	 * Only the first two are required.
324 	 */
325 	memset(pcii, 0, sizeof (*pcii));
326 	if (di_prop_lookup_ints(DDI_DEV_T_ANY, node,
327 	    "vendor-id", &propval) > 0)
328 		pcii->vendor_id = (uint16_t)*propval;
329 	else
330 		goto out;
331 
332 	if (di_prop_lookup_ints(DDI_DEV_T_ANY, node,
333 	    "device-id", &propval) > 0)
334 		pcii->device_id = (uint16_t)*propval;
335 	else
336 		goto out;
337 
338 	if (di_prop_lookup_ints(DDI_DEV_T_ANY, node,
339 	    "subsystem-vendor-id", &propval) > 0)
340 		pcii->subvendor_id = (uint16_t)*propval;
341 
342 	if (di_prop_lookup_ints(DDI_DEV_T_ANY, node,
343 	    "subsystem-id", &propval) > 0)
344 		pcii->subdevice_id = (uint16_t)*propval;
345 
346 	if (di_prop_lookup_ints(DDI_DEV_T_ANY, node,
347 	    "revision-id", &propval) > 0)
348 		pcii->revision_id = (uint16_t)*propval;
349 
350 	ret = 0;
351 out:
352 	di_fini(node);
353 	return (ret);
354 }
355