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