summaryrefslogtreecommitdiffstats
path: root/usb.c
blob: fe656114e8f94defa1f72b9fe69e00fbece43ae0 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include "libldetect.h"
#include "common.h"

static char *proc_usb_path_default = "/proc/bus/usb/devices";
char *proc_usb_path = NULL;


extern struct pciusb_entries usb_probe(void) {
	FILE *f;
	char buf[BUF_SIZE];
	int line;
	struct pciusb_entries r;
	struct pciusb_entry *e = NULL;
	r.nb = 0;

	if (!(f = fopen(proc_usb_path ? proc_usb_path : proc_usb_path_default, "r"))) {
		if (proc_usb_path) {
		        char *err_msg;
			asprintf(&err_msg, "unable to open \"%s\"\n"
				    "You may have passed a wrong argument to the \"-u\" option.\n"
				    "fopen() sets errno to", proc_usb_path);
			perror(err_msg);
			free(err_msg);
		}
		r.entries = NULL;
		return r;
	}

	r.entries = malloc(sizeof(struct pciusb_entry) * MAX_DEVICES);
	/* for further information on the format parsed by this state machine,
	 * read /usr/share/doc/kernel-doc-X.Y.Z/usb/proc_usb_info.txt */  
	for(line = 1; fgets(buf, sizeof(buf) - 1, f) && r.nb < MAX_DEVICES; line++) {

		switch (buf[0]) {
		case 'T': {
			unsigned short pci_bus, pci_device;
			e = &r.entries[r.nb++];
			pciusb_initialize(e);

			if (sscanf(buf, "T:  Bus=%02hd Lev=%*02d Prnt=%*04d Port=%*02d Cnt=%*02d Dev#=%3hd Spd=%*3s MxCh=%*2d", &pci_bus, &pci_device) == 2) {
				e->pci_bus = pci_bus;
				e->pci_device = pci_device;
			} else fprintf(stderr, "%s %d: unknown ``T'' line\n", proc_usb_path, line);
			break;
		}
		case 'P': {
			unsigned short vendor, device;
			if (sscanf(buf, "P:  Vendor=%hx ProdID=%hx", &vendor, &device) == 2) {
				e->vendor = vendor;
				e->device = device;
			} else fprintf(stderr, "%s %d: unknown ``P'' line\n", proc_usb_path, line);
			break;
		}
		case 'I': if (e->class_id == 0) {
			char driver[50];
			int class_id, sub, prot = 0;
			if (sscanf(buf, "I:%*1c  If#=%*2d Alt=%*2d #EPs=%*2d Cls=%02x(%*5c) Sub=%02x Prot=%02x Driver=%s", &class_id, &sub, &prot, driver) == 4) {
				e->class_id = (class_id * 0x100 + sub) * 0x100 + prot;
				if (strncmp(driver, "(none)", 6)) {
					char *p;
					e->module = strdup(driver);
					/* replace '-' characters with '_' to be compliant with modnames from modaliases */
					p = e->module;
					while (p && *p) {
						if (*p == '-') *p = '_';
						p++;
					}
				}
				/* see linux/sound/usb/usbaudio.c::usb_audio_ids */
				if (e->class_id == (0x1*0x100+ 0x01)) /* USB_AUDIO_CLASS*0x100 + USB_SUBCLASS_AUDIO_CONTROL*/
					e->module = strdup("snd_usb_audio");

			} else if (sscanf(buf, "I:%*1c If#=%*2d Alt=%*2d #EPs=%*2d Cls=%02x(%*5c) Sub=%02x Prot=%02x Driver=", &class_id, &sub, &prot) == 3) {
                    /* prevent spurious warnings for strange USB interfaces */
			} else fprintf(stderr, "%s %d: unknown ``I'' line\n", proc_usb_path, line);
			break;
		}
		case 'S': {
			int offset;
			char dummy;
			size_t length = strlen(buf) -1;
			if (sscanf(buf, "S:  Manufacturer=%n%c", &offset, &dummy) == 1) {
				buf[length] = '|'; /* replacing '\n' by '|' */
				e->text = strdup(buf + offset);
			} else if (sscanf(buf, "S:  Product=%n%c", &offset, &dummy) == 1) {
				if (!e->text) 
					e->text = strdup("Unknown|");
				buf[length] = 0; /* removing '\n' */
				e->text = realloc(e->text, strlen(e->text) + length-offset + 2);
				strcat(e->text, buf + offset);
			}
		}
		}
	}
	fclose(f);
	realloc(r.entries,  sizeof(struct pciusb_entry) * r.nb);

	pciusb_find_modules(&r, "usbtable", LOAD, 0);
	return r;
}