summaryrefslogtreecommitdiffstats
path: root/usb.c
blob: c4e8822da63b3f32bdeeeee0c35b9597e0111da6 (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
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include "libldetect.h"
#include "common.h"
#include "names.h"

static char *proc_usb_path_default = "/sys/kernel/debug/usb/devices";
char *proc_usb_path = NULL;

static void build_text(struct pciusb_entry *e, char *vendor_text, char *product_text) {
	if(e) {
		if(!vendor_text) {
			char const* vendorname;
			vendorname = names_vendor(e->vendor);
			if(vendorname) {
				vendor_text = malloc(strlen(vendorname)+2);
				sprintf(vendor_text, "%s|", vendorname);
			} else 
				vendor_text = strdup("Unknown|");
		}
		if(!product_text) {
			char const* productname;
			productname = names_product(e->vendor, e->device);
			if(productname) {
				product_text = strdup(productname);
			} else 
				product_text = strdup("Unknown");
		}
		vendor_text = realloc(vendor_text, strlen(vendor_text)+strlen(product_text)+1);
		e->text = vendor_text;
		strcat(e->text, product_text);
		free(product_text);
	}
}


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

	names_init();
	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;
		goto exit;
	}

	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); line++) {
		if (r.nb >= allocated) {
			allocated = r.nb*2;
			r.entries = realloc(r.entries, sizeof(struct pciusb_entry) * allocated);
		}

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

			if (sscanf(buf, "T:  Bus=%02hd Lev=%*02d Prnt=%*04d Port=%02hd Cnt=%*02d Dev#=%3hd Spd=%*3s MxCh=%*2d", &pci_bus, &usb_port, &pci_device) == 3) {
				e->pci_bus = pci_bus;
				e->pci_device = pci_device;
				e->usb_port = usb_port;
			} 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 || e->module == NULL) {
			char driver[50];
			int class_id, sub, prot = 0;
			if (sscanf(buf, "I:* If#=%*2d Alt=%*2d #EPs=%*2d Cls=%02x(%*5c) Sub=%02x Prot=%02x Driver=%s", &class_id, &sub, &prot, driver) >= 3) {
				unsigned long cid = (class_id * 0x100 + sub) * 0x100 + prot;
				if (e->class_id == 0)
					e->class_id = cid;
				if (strncmp(driver, "(none)", 6)) {
					char *p;
					/* Get current class if we are on the first one having used by a driver */
					e->class_id = cid;
					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:  If#=%*2d Alt=%*2d #EPs=%*2d Cls=%02x(%*5c) Sub=%02x Prot=%02x Driver=%s", &class_id, &sub, &prot, driver) >= 3) {
				/* Ignore interfaces not active */
			} 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 '|' */
				vendor_text = strdup(buf + offset);
			} else if (sscanf(buf, "S:  Product=%n%c", &offset, &dummy) == 1) {
				buf[length] = 0; /* removing '\n' */
				product_text = strdup(buf + offset);
			}
		}
		}
	}

	build_text(e, vendor_text, product_text);

	fclose(f);

	/* shrink to real size */
	r.entries = realloc(r.entries,  sizeof(struct pciusb_entry) * r.nb);

	pciusb_find_modules(&r, "usbtable", DO_NOT_LOAD, 0);

exit:
	names_exit();
	return r;
}