xref: /illumos-gate/usr/src/lib/libpcidb/common/pcidb.c (revision abc79d9d)
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 (c) 2012, Joyent, Inc. All rights reserved.
23  */
24 
25 /*
26  * This library exists to understand and parse the pci.ids database that is
27  * maintained at http://pci-ids.ucw.cz/ and in the gate at cmd/hwdata. This
28  * database provides a way to map the PCI device, vendor, and subsystem ids to
29  * a human understandable name.
30  *
31  * This library exports this data in a similar way to a tree. The handle that
32  * is returned from pcidb_open is the root of the tree. The next level are the
33  * vendors. Each vendor has a unique set of devices and each device has a unique
34  * set of subvendor and subdevice pairs.
35  *
36  * Parsing information:
37  *
38  * The database is formatted in the following basic format:
39  * vendor_id<two spaces>vendor_name
40  * <tab>device_id<two spaces>device_name
41  * <tab><tab>subvendor<space>subdevice<two spaces>subsystem_name
42  *
43  * For any given vendor, there can be multiple devices. And for any given device
44  * there will be multiple subsystems. In addition, there can be comments that
45  * start a line which use the '#' character.
46  *
47  * At the end of the file, there are a series of PCI classes. Those will start
48  * with a single C<space>. Once we hit those, we stop all parsing. We currently
49  * don't care about consuming or presenting those.
50  */
51 
52 #include <sys/types.h>
53 #include <stdint.h>
54 #include <stdio.h>
55 #include <stdlib.h>
56 #include <errno.h>
57 #include <string.h>
58 #include <assert.h>
59 #include <unistd.h>
60 
61 #include "pcidb.h"
62 
63 #define	PCI_NAME_MAX	256
64 #define	PCI_READLINE	1024
65 
66 /* Forward declarations */
67 struct pcidb_vendor;
68 struct pcidb_device;
69 struct pcidb_subvd;
70 
71 struct pcidb_subvd {
72 	uint16_t		ps_vid;
73 	uint16_t		ps_did;
74 	char			ps_name[PCI_NAME_MAX];
75 	struct pcidb_subvd	*ps_prev;
76 	struct pcidb_subvd	*ps_next;
77 	struct pcidb_device	*ps_dev;
78 	struct pcidb_vendor	*ps_vend;
79 };
80 
81 struct pcidb_device {
82 	uint16_t		pd_id;
83 	char			pd_name[PCI_NAME_MAX];
84 	struct pcidb_subvd	*pd_sstart;
85 	struct pcidb_subvd	*pd_send;
86 	struct pcidb_device	*pd_next;
87 	struct pcidb_device	*pd_prev;
88 	struct pcidb_vendor	*pd_vend;
89 };
90 
91 struct pcidb_vendor {
92 	uint16_t		pv_id;
93 	char 			pv_name[PCI_NAME_MAX];
94 	struct pcidb_device	*pv_dstart;
95 	struct pcidb_device	*pv_dend;
96 	struct pcidb_vendor	*pv_prev;
97 	struct pcidb_vendor	*pv_next;
98 };
99 
100 struct pcidb_hdl {
101 	pcidb_vendor_t	*ph_vstart;
102 	pcidb_vendor_t	*ph_vend;
103 };
104 
105 typedef enum pcidb_parse {
106 	PDB_VENDOR,
107 	PDB_DEVICE,
108 	PDB_SUBDEV
109 } pcidb_parse_t;
110 
111 static const char *pci_db = "/usr/share/hwdata/pci.ids";
112 
113 static void
114 pcihdl_add_vendor(pcidb_hdl_t *hdl, pcidb_vendor_t *v)
115 {
116 	if (hdl->ph_vstart == NULL && hdl->ph_vend == NULL) {
117 		hdl->ph_vstart = v;
118 		hdl->ph_vend = v;
119 		v->pv_prev = NULL;
120 		v->pv_next = NULL;
121 	} else {
122 		v->pv_prev = hdl->ph_vend;
123 		v->pv_next = NULL;
124 		hdl->ph_vend->pv_next = v;
125 		hdl->ph_vend = v;
126 	}
127 }
128 
129 static pcidb_vendor_t *
130 parse_vendor(char *buf, pcidb_hdl_t *hdl)
131 {
132 	pcidb_vendor_t *v;
133 	size_t len;
134 
135 	v = malloc(sizeof (pcidb_vendor_t));
136 	if (v == NULL)
137 		return (NULL);
138 
139 	pcihdl_add_vendor(hdl, v);
140 	v->pv_dstart = NULL;
141 	v->pv_dend = NULL;
142 
143 	buf[4] = '\0';
144 	v->pv_id = strtol(buf, NULL, 16);
145 	buf += 6;
146 	len = strlen(buf);
147 	if (buf[len-1] == '\n')
148 		buf[len-1] = '\0';
149 
150 	(void) strlcpy(v->pv_name, buf, PCI_NAME_MAX);
151 
152 	return (v);
153 }
154 
155 static void
156 insert_device(pcidb_vendor_t *v, pcidb_device_t *d)
157 {
158 	d->pd_vend = v;
159 	if (v->pv_dstart == NULL && v->pv_dend == NULL) {
160 		v->pv_dstart = d;
161 		v->pv_dend = d;
162 		d->pd_next = NULL;
163 		d->pd_prev = NULL;
164 	} else {
165 		d->pd_prev = v->pv_dend;
166 		d->pd_next = NULL;
167 		v->pv_dend->pd_next = d;
168 		v->pv_dend = d;
169 	}
170 }
171 
172 static pcidb_device_t *
173 parse_device(char *buf, pcidb_vendor_t *v)
174 {
175 	pcidb_device_t *d;
176 	size_t len;
177 
178 	d = malloc(sizeof (pcidb_device_t));
179 	if (d == NULL)
180 		return (d);
181 
182 	d->pd_sstart = NULL;
183 	d->pd_send = NULL;
184 	insert_device(v, d);
185 
186 	buf++;
187 	buf[4] = '\0';
188 	d->pd_id = strtol(buf, NULL, 16);
189 	buf += 6;
190 	len = strlen(buf);
191 	if (buf[len-1] == '\n')
192 		buf[len-1] = '\0';
193 
194 	(void) strlcpy(d->pd_name, buf, PCI_NAME_MAX);
195 	return (d);
196 }
197 
198 static void
199 insert_subdev(pcidb_device_t *d, pcidb_subvd_t *s)
200 {
201 	s->ps_dev = d;
202 	s->ps_vend = d->pd_vend;
203 	if (d->pd_sstart == NULL) {
204 		d->pd_sstart = s;
205 		d->pd_send = s;
206 		s->ps_prev = NULL;
207 		s->ps_next = NULL;
208 	} else {
209 		s->ps_prev = d->pd_send;
210 		s->ps_next = NULL;
211 		d->pd_send->ps_next = s;
212 		d->pd_send = s;
213 	}
214 }
215 
216 static pcidb_subvd_t *
217 parse_subdev(char *buf, pcidb_device_t *d)
218 {
219 	pcidb_subvd_t *s;
220 	size_t len;
221 
222 	s = malloc(sizeof (pcidb_subvd_t));
223 	if (s == NULL)
224 		return (NULL);
225 	insert_subdev(d, s);
226 
227 	buf += 2;
228 	buf[4] = '\0';
229 	s->ps_vid = strtol(buf, NULL, 16);
230 	buf += 5;
231 	buf[4] = '\0';
232 	s->ps_did = strtol(buf, NULL, 16);
233 	buf += 6;
234 
235 	len = strlen(buf);
236 	if (buf[len-1] == '\n')
237 		buf[len-1] = '\0';
238 
239 	(void) strlcpy(s->ps_name, buf, PCI_NAME_MAX);
240 
241 	return (s);
242 }
243 
244 static int
245 readline(FILE *f, char *buf, size_t len)
246 {
247 	for (;;) {
248 		if (fgets(buf, len, f) == NULL)
249 			return (-1);
250 
251 		if (buf[0] == 'C')
252 			return (-1);
253 
254 		if (buf[0] != '#' && buf[0] != '\n')
255 			return (0);
256 	}
257 }
258 
259 static int
260 parse_db(FILE *f, pcidb_hdl_t *hdl)
261 {
262 	char buf[1024];
263 	pcidb_vendor_t *v = NULL;
264 	pcidb_device_t *d = NULL;
265 	pcidb_parse_t state = PDB_VENDOR;
266 
267 	for (;;) {
268 		errno = 0;
269 		if (readline(f, buf, sizeof (buf)) != 0) {
270 			if (errno != 0)
271 				return (-1);
272 			else
273 				return (0);
274 		}
275 
276 newstate:
277 		switch (state) {
278 		case PDB_VENDOR:
279 			v = parse_vendor(buf, hdl);
280 			if (v == NULL)
281 				return (NULL);
282 			state = PDB_DEVICE;
283 			continue;
284 		case PDB_DEVICE:
285 			if (buf[0] != '\t') {
286 				state = PDB_VENDOR;
287 				goto newstate;
288 			}
289 
290 			if (buf[1] == '\t') {
291 				state = PDB_SUBDEV;
292 				goto newstate;
293 			}
294 
295 			assert(v != NULL);
296 			d = parse_device(buf, v);
297 			if (d == NULL)
298 				return (NULL);
299 			continue;
300 		case PDB_SUBDEV:
301 			if (buf[0] != '\t') {
302 				state = PDB_VENDOR;
303 				goto newstate;
304 			}
305 
306 			if (buf[0] == '\t' && buf[1] != '\t') {
307 				state = PDB_DEVICE;
308 				goto newstate;
309 			}
310 
311 			assert(buf[0] == '\t' && buf[1] == '\t');
312 			assert(d != NULL);
313 			(void) parse_subdev(buf, d);
314 		}
315 	}
316 }
317 
318 pcidb_hdl_t *
319 pcidb_open(int version)
320 {
321 	pcidb_hdl_t *h;
322 	FILE *f;
323 
324 	if (version != PCIDB_VERSION) {
325 		errno = EINVAL;
326 		return (NULL);
327 	}
328 
329 	h = malloc(sizeof (pcidb_hdl_t));
330 	if (h == NULL)
331 		return (NULL);
332 
333 	h->ph_vstart = NULL;
334 	h->ph_vend = NULL;
335 
336 	f = fopen(pci_db, "rF");
337 	if (f == NULL) {
338 		free(h);
339 		return (NULL);
340 	}
341 
342 	if (parse_db(f, h) < 0) {
343 		pcidb_close(h);
344 		free(h);
345 		return (NULL);
346 	}
347 
348 	return (h);
349 }
350 
351 void
352 pcidb_close(pcidb_hdl_t *h)
353 {
354 	pcidb_vendor_t *v, *tv;
355 
356 	pcidb_device_t *d, *td;
357 	pcidb_subvd_t *s, *ts;
358 
359 	if (h == NULL)
360 		return;
361 
362 	v = h->ph_vstart;
363 	while (v != NULL) {
364 		d = v->pv_dstart;
365 		while (d != NULL) {
366 			s = d->pd_sstart;
367 			while (s != NULL) {
368 				ts = s;
369 				s = s->ps_next;
370 				free(ts);
371 			}
372 			td = d;
373 			d = d->pd_next;
374 			free(td);
375 		}
376 		tv = v;
377 		v = v->pv_next;
378 		free(tv);
379 	}
380 
381 	free(h);
382 }
383 
384 pcidb_vendor_t *
385 pcidb_lookup_vendor(pcidb_hdl_t *hdl, uint16_t id)
386 {
387 	pcidb_vendor_t *v;
388 
389 	for (v = hdl->ph_vstart; v != NULL; v = v->pv_next) {
390 		if (v->pv_id == id)
391 			return (v);
392 	}
393 
394 	return (NULL);
395 }
396 
397 const char *
398 pcidb_vendor_name(pcidb_vendor_t *v)
399 {
400 	return (v->pv_name);
401 }
402 
403 uint16_t
404 pcidb_vendor_id(pcidb_vendor_t *v)
405 {
406 	return (v->pv_id);
407 }
408 
409 pcidb_vendor_t *
410 pcidb_vendor_iter(pcidb_hdl_t *h)
411 {
412 	return (h->ph_vstart);
413 }
414 
415 pcidb_vendor_t *
416 pcidb_vendor_iter_next(pcidb_vendor_t *v)
417 {
418 	assert(v != NULL);
419 	return (v->pv_next);
420 }
421 
422 pcidb_device_t *
423 pcidb_lookup_device_by_vendor(pcidb_vendor_t *v, uint16_t id)
424 {
425 	pcidb_device_t *d;
426 	assert(v != NULL);
427 
428 	for (d = v->pv_dstart; d != NULL; d = d->pd_next)
429 		if (d->pd_id == id)
430 			return (d);
431 
432 	return (NULL);
433 }
434 
435 pcidb_device_t *
436 pcidb_lookup_device(pcidb_hdl_t *h, uint16_t vid, uint16_t did)
437 {
438 	pcidb_vendor_t *v;
439 
440 	v = pcidb_lookup_vendor(h, vid);
441 	if (v == NULL)
442 		return (NULL);
443 
444 	return (pcidb_lookup_device_by_vendor(v, did));
445 }
446 
447 pcidb_device_t *
448 pcidb_device_iter(pcidb_vendor_t *v)
449 {
450 	return (v->pv_dstart);
451 }
452 
453 pcidb_device_t *
454 pcidb_device_iter_next(pcidb_device_t *d)
455 {
456 	return (d->pd_next);
457 }
458 
459 const char *
460 pcidb_device_name(pcidb_device_t *d)
461 {
462 	return (d->pd_name);
463 }
464 
465 uint16_t
466 pcidb_device_id(pcidb_device_t *d)
467 {
468 	return (d->pd_id);
469 }
470 
471 pcidb_vendor_t *
472 pcidb_device_vendor(pcidb_device_t *d)
473 {
474 	return (d->pd_vend);
475 }
476 
477 pcidb_subvd_t *
478 pcidb_lookup_subvd_by_device(pcidb_device_t *d, uint16_t svid, uint16_t sdid)
479 {
480 	pcidb_subvd_t *s;
481 
482 	assert(d != NULL);
483 
484 	for (s = d->pd_sstart; s != NULL; s = s->ps_next)
485 		if (s->ps_vid == svid && s->ps_did == sdid)
486 			return (s);
487 
488 	return (NULL);
489 }
490 
491 pcidb_subvd_t *
492 pcidb_lookup_subvd_by_vendor(pcidb_vendor_t *v, uint16_t devid, uint16_t svid,
493     uint16_t sdid)
494 {
495 	pcidb_device_t *d;
496 
497 	assert(v != NULL);
498 	d = pcidb_lookup_device_by_vendor(v, devid);
499 	if (d == NULL)
500 		return (NULL);
501 
502 	return (pcidb_lookup_subvd_by_device(d, svid, sdid));
503 }
504 
505 pcidb_subvd_t *
506 pcidb_lookup_subvd(pcidb_hdl_t *h, uint16_t vid, uint16_t did, uint16_t svid,
507     uint16_t sdid)
508 {
509 	pcidb_device_t *d;
510 
511 	assert(h != NULL);
512 	d = pcidb_lookup_device(h, vid, did);
513 	if (d == NULL)
514 		return (NULL);
515 
516 	return (pcidb_lookup_subvd_by_device(d, svid, sdid));
517 }
518 
519 pcidb_subvd_t *
520 pcidb_subvd_iter(pcidb_device_t *d)
521 {
522 	return (d->pd_sstart);
523 }
524 
525 pcidb_subvd_t *
526 pcidb_subvd_iter_next(pcidb_subvd_t *s)
527 {
528 	return (s->ps_next);
529 }
530 
531 const char *
532 pcidb_subvd_name(pcidb_subvd_t *s)
533 {
534 	return (s->ps_name);
535 }
536 
537 uint16_t
538 pcidb_subvd_svid(pcidb_subvd_t *s)
539 {
540 	return (s->ps_vid);
541 }
542 
543 uint16_t
544 pcidb_subvd_sdid(pcidb_subvd_t *s)
545 {
546 	return (s->ps_did);
547 }
548 
549 pcidb_device_t *
550 pcidb_subvd_device(pcidb_subvd_t *s)
551 {
552 	return (s->ps_dev);
553 }
554 
555 pcidb_vendor_t *
556 pcidb_subvd_vendor(pcidb_subvd_t *s)
557 {
558 	return (s->ps_vend);
559 }
560