• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2    Copyright © 2011 by Mauro Carvalho Chehab
3 
4    The get_media_devices is free software; you can redistribute it and/or
5    modify it under the terms of the GNU Lesser General Public
6    License as published by the Free Software Foundation; either
7    version 2.1 of the License, or (at your option) any later version.
8 
9    The libv4l2util Library is distributed in the hope that it will be useful,
10    but WITHOUT ANY WARRANTY; without even the implied warranty of
11    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12    Lesser General Public License for more details.
13 
14    You should have received a copy of the GNU Lesser General Public
15    License along with the libv4l2util Library; if not, write to the Free
16    Software Foundation, Inc., 51 Franklin Street, Suite 500, Boston, MA
17    02110-1335 USA.
18  */
19 
20 #include <stdio.h>
21 #include <unistd.h>
22 #include <sys/types.h>
23 #include <sys/sysmacros.h>
24 #include <sys/stat.h>
25 #include <string.h>
26 #include <stdlib.h>
27 #include <dirent.h>
28 #include <limits.h>
29 #include "get_media_devices.h"
30 
31 #define ARRAY_SIZE(arr) (sizeof(arr) / sizeof((arr)[0]))
32 
33 /**
34  * struct media_device_entry - Describes one device entry got via sysfs
35  *
36  * @device:		sysfs name for a device.
37  *			PCI devices are like: pci0000:00/0000:00:1b.0
38  *			USB devices are like: pci0000:00/0000:00:1d.7/usb1/1-8
39  * @node:		Device node, in sysfs or alsa hw identifier
40  * @device_type:	Type of the device (V4L_*, DVB_*, SND_*)
41  */
42 struct media_device_entry {
43 	char *device;
44 	char *node;
45 	enum device_type type;
46 	enum bus_type bus;
47 	unsigned major, minor;		/* Device major/minor */
48 };
49 
50 /**
51  * struct media_devices - Describes all devices found
52  *
53  * @device:		sysfs name for a device.
54  *			PCI devices are like: pci0000:00/0000:00:1b.0
55  *			USB devices are like: pci0000:00/0000:00:1d.7/usb1/1-8
56  * @node:		Device node, in sysfs or alsa hw identifier
57  * @device_type:	Type of the device (V4L_*, DVB_*, SND_*)
58  */
59 struct media_devices {
60 	struct media_device_entry *md_entry;
61 	unsigned int md_size;
62 };
63 
64 typedef int (*fill_data_t)(struct media_device_entry *md);
65 
66 #define DEVICE_STR "devices"
67 
get_uevent_info(struct media_device_entry * md_ptr,char * dname)68 static void get_uevent_info(struct media_device_entry *md_ptr, char *dname)
69 {
70 	FILE *fd;
71 	char file[PATH_MAX + 8], *name, *p;
72 	char s[1024];
73 
74 	snprintf(file, sizeof(file), "%s/%s/uevent", dname, md_ptr->node);
75 	fd = fopen(file, "r");
76 	if (!fd)
77 		return;
78 	while (fgets(s, sizeof(s), fd)) {
79 		p = strtok(s, "=");
80 		if (!p)
81 			continue;
82 		name = p;
83 		p = strtok(NULL, "\n");
84 		if (!p)
85 			continue;
86 		if (!strcmp(name, "MAJOR"))
87 			md_ptr->major = atol(p);
88 		else if (!strcmp(name, "MINOR"))
89 			md_ptr->minor = atol(p);
90 	}
91 
92 	fclose(fd);
93 }
94 
get_bus(char * device)95 static enum bus_type get_bus(char *device)
96 {
97 	char file[PATH_MAX + 9];
98 	char s[1024];
99 	FILE *f;
100 
101 	if (!strcmp(device, "/sys/devices/virtual"))
102 		return MEDIA_BUS_VIRTUAL;
103 
104 	snprintf(file, sizeof(file), "%s/modalias", device);
105 	f = fopen(file, "r");
106 	if (!f)
107 		return MEDIA_BUS_UNKNOWN;
108 	if (!fgets(s, sizeof(s), f))
109 		return MEDIA_BUS_UNKNOWN;
110 	fclose(f);
111 
112 	if (!strncmp(s, "pci", 3))
113 		return MEDIA_BUS_PCI;
114 	if (!strncmp(s, "usb", 3))
115 		return MEDIA_BUS_USB;
116 
117 	return MEDIA_BUS_UNKNOWN;
118 }
119 
get_class(char * class,struct media_device_entry ** md,unsigned int * md_size,fill_data_t fill)120 static int get_class(char *class,
121 		     struct media_device_entry **md,
122 		     unsigned int *md_size,
123 		     fill_data_t fill)
124 {
125 	DIR		*dir;
126 	struct dirent	*entry;
127 	char		dname[PATH_MAX];
128 	char		fname[PATH_MAX + sizeof(entry->d_name)];
129 	char		link[PATH_MAX];
130 	char		virt_dev[60];
131 	int		err = -2;
132 	struct		media_device_entry *md_ptr = NULL;
133 	char		*p, *device;
134 	enum bus_type	bus;
135 	static int	virtual = 0;
136 
137 	snprintf(dname, PATH_MAX, "/sys/class/%s", class);
138 	dir = opendir(dname);
139 	if (!dir) {
140 		return 0;
141 	}
142 	for (entry = readdir(dir); entry; entry = readdir(dir)) {
143 		/* Skip . and .. */
144 		if (entry->d_name[0] == '.')
145 			continue;
146 		/* Canonicalize the device name */
147 		snprintf(fname, sizeof(fname), "%s/%s", dname, entry->d_name);
148 		if (realpath(fname, link)) {
149 			device = link;
150 
151 			/* Remove the subsystem/class_name from the string */
152 			p = strstr(device, class);
153 			if (!p)
154 				continue;
155 			*(p - 1) = '\0';
156 
157 			bus = get_bus(device);
158 
159 			/* remove the /sys/devices/ from the name */
160 			device += 13;
161 
162 			switch (bus) {
163 			case MEDIA_BUS_PCI:
164 				/* Remove the device function nr */
165 				p = strrchr(device, '.');
166 				if (!p)
167 					continue;
168 				*p = '\0';
169 				break;
170 			case MEDIA_BUS_USB:
171 				/* Remove USB interface from the path */
172 				p = strrchr(device, '/');
173 				if (!p)
174 					continue;
175 				/* In case we have a device where the driver
176 				   attaches directly to the usb device rather
177 				   then to an interface */
178 				if (!strchr(p, ':'))
179 					break;
180 				*p = '\0';
181 				break;
182 			case MEDIA_BUS_VIRTUAL:
183 				/* Don't group virtual devices */
184 				sprintf(virt_dev, "virtual%d", virtual++);
185 				device = virt_dev;
186 				break;
187 			case MEDIA_BUS_UNKNOWN:
188 				break;
189 			}
190 
191 			/* Add one more element to the devices struct */
192 			*md = realloc(*md, (*md_size + 1) * sizeof(*md_ptr));
193 			if (!*md)
194 				goto error;
195 			md_ptr = (*md) + *md_size;
196 			(*md_size)++;
197 
198 			/* Cleans previous data and fills it with device/node */
199 			memset(md_ptr, 0, sizeof(*md_ptr));
200 			md_ptr->type = UNKNOWN;
201 			md_ptr->device = strdup(device);
202 			md_ptr->node = strdup(entry->d_name);
203 
204 			/* Retrieve major and minor information */
205 			get_uevent_info(md_ptr, dname);
206 
207 			/* Used to identify the type of node */
208 			fill(md_ptr);
209 		}
210 	}
211 	err = 0;
212 error:
213 	closedir(dir);
214 	return err;
215 }
216 
add_v4l_class(struct media_device_entry * md)217 static int add_v4l_class(struct media_device_entry *md)
218 {
219 	if (strstr(md->node, "video"))
220 		md->type = MEDIA_V4L_VIDEO;
221 	else if (strstr(md->node, "vbi"))
222 		md->type = MEDIA_V4L_VBI;
223 	else if (strstr(md->node, "swradio"))
224 		md->type = MEDIA_V4L_SWRADIO;
225 	else if (strstr(md->node, "radio"))
226 		md->type = MEDIA_V4L_RADIO;
227 	else if (strstr(md->node, "v4l-touch"))
228 		md->type = MEDIA_V4L_TOUCH;
229 	else if (strstr(md->node, "v4l-subdev"))
230 		md->type = MEDIA_V4L_SUBDEV;
231 
232 	return 0;
233 }
234 
add_snd_class(struct media_device_entry * md)235 static int add_snd_class(struct media_device_entry *md)
236 {
237 	unsigned c = 65535, d = 65535;
238 	char node[64];
239 
240 	if (strstr(md->node, "timer")) {
241 		md->type = MEDIA_SND_TIMER;
242 		return 0;
243 	}
244 	if (strstr(md->node, "seq")) {
245 		md->type = MEDIA_SND_SEQ;
246 		return 0;
247 	} if (strstr(md->node, "card")) {
248 		sscanf(md->node, "card%u", &c);
249 		md->type = MEDIA_SND_CARD;
250 	} else if (strstr(md->node, "hw")) {
251 		sscanf(md->node, "hwC%uD%u", &c, &d);
252 		md->type = MEDIA_SND_HW;
253 	} else if (strstr(md->node, "control")) {
254 		sscanf(md->node, "controlC%u", &c);
255 		md->type = MEDIA_SND_CONTROL;
256 	} else if (strstr(md->node, "pcm")) {
257 		sscanf(md->node, "pcmC%uD%u", &c, &d);
258 		if (md->node[strlen(md->node) - 1] == 'p')
259 			md->type = MEDIA_SND_OUT;
260 		else if (md->node[strlen(md->node) - 1] == 'c')
261 			md->type = MEDIA_SND_CAP;
262 	}
263 
264 	if (c == 65535)
265 		return 0;
266 
267 	/* Reformat device to be useful for alsa userspace library */
268 	if (d == 65535)
269 		snprintf(node, sizeof(node), "hw:%u", c);
270 	else
271 		snprintf(node, sizeof(node), "hw:%u,%u", c, d);
272 
273 	free(md->node);
274 	md->node = strdup(node);
275 
276 	return 0;
277 }
278 
add_dvb_class(struct media_device_entry * md)279 static int add_dvb_class(struct media_device_entry *md)
280 {
281 	if (strstr(md->node, "video"))
282 		md->type = MEDIA_DVB_VIDEO;
283 	if (strstr(md->node, "audio"))
284 		md->type = MEDIA_DVB_AUDIO;
285 	if (strstr(md->node, "sec"))
286 		md->type = MEDIA_DVB_SEC;
287 	if (strstr(md->node, "frontend"))
288 		md->type = MEDIA_DVB_FRONTEND;
289 	else if (strstr(md->node, "demux"))
290 		md->type = MEDIA_DVB_DEMUX;
291 	else if (strstr(md->node, "dvr"))
292 		md->type = MEDIA_DVB_DVR;
293 	else if (strstr(md->node, "net"))
294 		md->type = MEDIA_DVB_NET;
295 	else if (strstr(md->node, "ca"))
296 		md->type = MEDIA_DVB_CA;
297 	else if (strstr(md->node, "osd"))
298 		md->type = MEDIA_DVB_OSD;
299 
300 	return 0;
301 }
302 
sort_media_device_entry(const void * a,const void * b)303 static int sort_media_device_entry(const void *a, const void *b)
304 {
305 	const struct media_device_entry *md_a = a;
306 	const struct media_device_entry *md_b = b;
307 	int cmp;
308 
309 	cmp = strcmp(md_a->device, md_b->device);
310 	if (cmp)
311 		return cmp;
312 	cmp = (int)md_a->type - (int)md_b->type;
313 	if (cmp)
314 		return cmp;
315 
316 	return strcmp(md_a->node, md_b->node);
317 }
318 
319 
320 /* Public functions */
321 
free_media_devices(void * opaque)322 void free_media_devices(void *opaque)
323 {
324 	struct media_devices *md = opaque;
325 	struct media_device_entry *md_ptr = md->md_entry;
326 	int i;
327 	for (i = 0; i < md->md_size; i++) {
328 		free(md_ptr->node);
329 		free(md_ptr->device);
330 		md_ptr++;
331 	}
332 	free(md->md_entry);
333 	free(md);
334 }
335 
discover_media_devices(void)336 void *discover_media_devices(void)
337 {
338 	struct media_devices *md = NULL;
339 	struct media_device_entry *md_entry = NULL;
340 
341 	md = calloc(1, sizeof(*md));
342 	if (!md)
343 		return NULL;
344 
345 	md->md_size = 0;
346 	if (get_class("video4linux", &md_entry, &md->md_size, add_v4l_class))
347 		goto error;
348 	if (get_class("sound", &md_entry, &md->md_size, add_snd_class))
349 		goto error;
350 	if (get_class("dvb", &md_entry, &md->md_size, add_dvb_class))
351 		goto error;
352 
353 	/* There's no media device */
354 	if (!md_entry)
355 		goto error;
356 
357 	qsort(md_entry, md->md_size, sizeof(*md_entry), sort_media_device_entry);
358 
359 	md->md_entry = md_entry;
360 
361 	return md;
362 
363 error:
364 	free_media_devices(md);
365 	return NULL;
366 }
367 
media_device_type(enum device_type type)368 const char *media_device_type(enum device_type type)
369 {
370 	switch(type) {
371 		/* V4L nodes */
372 	case MEDIA_V4L_VIDEO:
373 		return  "video";
374 	case MEDIA_V4L_VBI:
375 		return  "vbi";
376 	case MEDIA_V4L_RADIO:
377 		return "radio";
378 	case MEDIA_V4L_SWRADIO:
379 		return "swradio";
380 	case MEDIA_V4L_TOUCH:
381 		return "v4l-touch";
382 	case MEDIA_V4L_SUBDEV:
383 		return "v4l subdevice";
384 
385 		/* DVB nodes */
386 	case MEDIA_DVB_VIDEO:
387 		return  "dvb video";
388 	case MEDIA_DVB_AUDIO:
389 		return  "dvb audio";
390 	case MEDIA_DVB_SEC:
391 		return  "dvb sec";
392 	case MEDIA_DVB_FRONTEND:
393 		return  "dvb frontend";
394 	case MEDIA_DVB_DEMUX:
395 		return  "dvb demux";
396 	case MEDIA_DVB_DVR:
397 		return  "dvb dvr";
398 	case MEDIA_DVB_NET:
399 		return  "dvb net";
400 	case MEDIA_DVB_CA:
401 		return  "dvb conditional access";
402 	case MEDIA_DVB_OSD:
403 		return  "dvb OSD";
404 
405 		/* Alsa nodes */
406 	case MEDIA_SND_CARD:
407 		return  "sound card";
408 	case MEDIA_SND_CAP:
409 		return  "pcm capture";
410 	case MEDIA_SND_OUT:
411 		return  "pcm output";
412 	case MEDIA_SND_CONTROL:
413 		return  "mixer";
414 	case MEDIA_SND_HW:
415 		return  "sound hardware";
416 	case MEDIA_SND_TIMER:
417 		return  "sound timer";
418 	case MEDIA_SND_SEQ:
419 		return  "sound sequencer";
420 
421 	default:
422 		return "unknown";
423 	};
424 }
425 
display_media_devices(void * opaque)426 void display_media_devices(void *opaque)
427 {
428 	struct media_devices *md = opaque;
429 	struct media_device_entry *md_ptr = md->md_entry;
430 	int i;
431 	char *prev = "";
432 
433 	for (i = 0; i < md->md_size; i++) {
434 		if (strcmp(prev, md_ptr->device)) {
435 			printf("\nDevice %s:\n\t", md_ptr->device);
436 			prev = md_ptr->device;
437 		}
438 		printf("%s(%s, dev %i:%i) ", md_ptr->node,
439 		       media_device_type(md_ptr->type),
440 		       md_ptr->major, md_ptr->minor);
441 		md_ptr++;
442 	}
443 	printf("\n");
444 }
445 
get_associated_device(void * opaque,const char * last_seek,const enum device_type desired_type,const char * seek_device,const enum device_type seek_type)446 const char *get_associated_device(void *opaque,
447 				  const char *last_seek,
448 				  const enum device_type desired_type,
449 				  const char *seek_device,
450 				  const enum device_type seek_type)
451 {
452 	struct media_devices *md = opaque;
453 	struct media_device_entry *md_ptr = md->md_entry;
454 	int i, found = 0;
455 	char *prev, *p;
456 
457 	if (seek_type != NONE && seek_device[0]) {
458 		/* Get just the device name */
459 		p = strrchr(seek_device, '/');
460 		if (p)
461 			seek_device = p + 1;
462 
463 		/* Step 1: Find the seek node */
464 		for (i = 0; i < md->md_size; i++, md_ptr++) {
465 			if (last_seek && md_ptr->type == seek_type &&
466 			    !strcmp(md_ptr->node, last_seek)) {
467 				found = 1;
468 				continue;
469 			}
470 			if (last_seek && !found)
471 				continue;
472 			if (md_ptr->type == seek_type &&
473 			    !strcmp(seek_device, md_ptr->node))
474 				break;
475 		}
476 		if (i == md->md_size)
477 			return NULL;
478 		i++;
479 		prev = md_ptr->device;
480 		md_ptr++;
481 		/* Step 2: find the associated node */
482 		for (; i < md->md_size && !strcmp(prev, md_ptr->device); i++, md_ptr++) {
483 			if (last_seek && md_ptr->type == seek_type &&
484 			    !strcmp(md_ptr->node, last_seek)) {
485 				found = 1;
486 				continue;
487 			}
488 			if (last_seek && !found)
489 				continue;
490 			if (md_ptr->type == desired_type)
491 				return md_ptr->node;
492 		}
493 	} else {
494 		for (i = 0; i < md->md_size; i++, md_ptr++) {
495 			if (last_seek && !strcmp(md_ptr->node, last_seek)) {
496 				found = 1;
497 				continue;
498 			}
499 			if (last_seek && !found)
500 				continue;
501 			if (md_ptr->type == desired_type)
502 				return md_ptr->node;
503 		}
504 	}
505 
506 	return NULL;
507 }
508 
fget_associated_device(void * opaque,const char * last_seek,const enum device_type desired_type,const int fd_seek_device,const enum device_type seek_type)509 const char *fget_associated_device(void *opaque,
510 				   const char *last_seek,
511 				   const enum device_type desired_type,
512 				   const int fd_seek_device,
513 				   const enum device_type seek_type)
514 {
515 	struct media_devices *md = opaque;
516 	struct media_device_entry *md_ptr = md->md_entry;
517 	struct stat f_status;
518 	unsigned int dev_major, dev_minor;
519 	int i, found = 0;
520 	char *prev;
521 
522 	if (fstat(fd_seek_device, &f_status)) {
523 		perror("Can't get file status");
524 		return NULL;
525 	}
526 	if (!S_ISCHR(f_status.st_mode)) {
527 		fprintf(stderr, "File descriptor is not a char device\n");
528 		return NULL;
529 	}
530 	dev_major = major(f_status.st_rdev);
531 	dev_minor = minor(f_status.st_rdev);
532 
533 	/* Step 1: Find the seek node */
534 	for (i = 0; i < md->md_size; i++, md_ptr++) {
535 		if (last_seek && md_ptr->type == seek_type
536 		    && md_ptr->major == dev_major
537 		    && md_ptr->minor == dev_minor) {
538 			found = 1;
539 			continue;
540 		}
541 		if (last_seek && !found)
542 			continue;
543 		if (md_ptr->type == seek_type
544 		    && md_ptr->major == dev_major
545 		    && md_ptr->minor == dev_minor)
546 			break;
547 	}
548 	if (i == md->md_size)
549 		return NULL;
550 	i++;
551 	prev = md_ptr->device;
552 	md_ptr++;
553 	/* Step 2: find the associated node */
554 	for (; i < md->md_size && !strcmp(prev, md_ptr->device); i++, md_ptr++) {
555 		if (last_seek && md_ptr->type == seek_type
556 		    && md_ptr->major == dev_major
557 		    && md_ptr->minor == dev_minor) {
558 			found = 1;
559 			continue;
560 		}
561 		if (last_seek && !found)
562 			continue;
563 		if (md_ptr->type == desired_type)
564 			return md_ptr->node;
565 	}
566 	return NULL;
567 }
568 
get_not_associated_device(void * opaque,const char * last_seek,const enum device_type desired_type,const enum device_type not_desired_type)569 const char *get_not_associated_device(void *opaque,
570 				      const char *last_seek,
571 				      const enum device_type desired_type,
572 				      const enum device_type not_desired_type)
573 {
574 	struct media_devices *md = opaque;
575 	struct media_device_entry *md_ptr = md->md_entry;
576 	int i, skip = 0, found = 0;
577 	char *prev = "", *result = NULL;
578 
579 	/* Step 1: Find a device without seek_type node */
580 	for (i = 0; i < md->md_size; i++, md_ptr++) {
581 		if (last_seek && !strcmp(md_ptr->node, last_seek)) {
582 			found = 1;
583 			continue;
584 		}
585 		if (last_seek && !found)
586 			continue;
587 		if (strcmp(prev, md_ptr->device)) {
588 			if (!skip && result)
589 				break;
590 			prev = md_ptr->device;
591 			skip = 0;
592 			result = NULL;
593 		}
594 		if (md_ptr->type == not_desired_type)
595 			skip = 1;
596 		else if (!skip && !result && md_ptr->type == desired_type)
597 			result = md_ptr->node;
598 	}
599 	if (skip)
600 		result = NULL;
601 
602 	return result;
603 }
604