xref: /illumos-gate/usr/src/lib/libpcidb/common/pcidb.c (revision 8a2b682e57a046b828f37bcde1776f131ef4629f)
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 (0);
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 (0);
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 		(void) fclose(f);
344 		pcidb_close(h);
345 		free(h);
346 		return (NULL);
347 	}
348 
349 	(void) fclose(f);
350 
351 	return (h);
352 }
353 
354 void
355 pcidb_close(pcidb_hdl_t *h)
356 {
357 	pcidb_vendor_t *v, *tv;
358 
359 	pcidb_device_t *d, *td;
360 	pcidb_subvd_t *s, *ts;
361 
362 	if (h == NULL)
363 		return;
364 
365 	v = h->ph_vstart;
366 	while (v != NULL) {
367 		d = v->pv_dstart;
368 		while (d != NULL) {
369 			s = d->pd_sstart;
370 			while (s != NULL) {
371 				ts = s;
372 				s = s->ps_next;
373 				free(ts);
374 			}
375 			td = d;
376 			d = d->pd_next;
377 			free(td);
378 		}
379 		tv = v;
380 		v = v->pv_next;
381 		free(tv);
382 	}
383 
384 	free(h);
385 }
386 
387 pcidb_vendor_t *
388 pcidb_lookup_vendor(pcidb_hdl_t *hdl, uint16_t id)
389 {
390 	pcidb_vendor_t *v;
391 
392 	for (v = hdl->ph_vstart; v != NULL; v = v->pv_next) {
393 		if (v->pv_id == id)
394 			return (v);
395 	}
396 
397 	return (NULL);
398 }
399 
400 const char *
401 pcidb_vendor_name(pcidb_vendor_t *v)
402 {
403 	return (v->pv_name);
404 }
405 
406 uint16_t
407 pcidb_vendor_id(pcidb_vendor_t *v)
408 {
409 	return (v->pv_id);
410 }
411 
412 pcidb_vendor_t *
413 pcidb_vendor_iter(pcidb_hdl_t *h)
414 {
415 	return (h->ph_vstart);
416 }
417 
418 pcidb_vendor_t *
419 pcidb_vendor_iter_next(pcidb_vendor_t *v)
420 {
421 	assert(v != NULL);
422 	return (v->pv_next);
423 }
424 
425 pcidb_device_t *
426 pcidb_lookup_device_by_vendor(pcidb_vendor_t *v, uint16_t id)
427 {
428 	pcidb_device_t *d;
429 	assert(v != NULL);
430 
431 	for (d = v->pv_dstart; d != NULL; d = d->pd_next)
432 		if (d->pd_id == id)
433 			return (d);
434 
435 	return (NULL);
436 }
437 
438 pcidb_device_t *
439 pcidb_lookup_device(pcidb_hdl_t *h, uint16_t vid, uint16_t did)
440 {
441 	pcidb_vendor_t *v;
442 
443 	v = pcidb_lookup_vendor(h, vid);
444 	if (v == NULL)
445 		return (NULL);
446 
447 	return (pcidb_lookup_device_by_vendor(v, did));
448 }
449 
450 pcidb_device_t *
451 pcidb_device_iter(pcidb_vendor_t *v)
452 {
453 	return (v->pv_dstart);
454 }
455 
456 pcidb_device_t *
457 pcidb_device_iter_next(pcidb_device_t *d)
458 {
459 	return (d->pd_next);
460 }
461 
462 const char *
463 pcidb_device_name(pcidb_device_t *d)
464 {
465 	return (d->pd_name);
466 }
467 
468 uint16_t
469 pcidb_device_id(pcidb_device_t *d)
470 {
471 	return (d->pd_id);
472 }
473 
474 pcidb_vendor_t *
475 pcidb_device_vendor(pcidb_device_t *d)
476 {
477 	return (d->pd_vend);
478 }
479 
480 pcidb_subvd_t *
481 pcidb_lookup_subvd_by_device(pcidb_device_t *d, uint16_t svid, uint16_t sdid)
482 {
483 	pcidb_subvd_t *s;
484 
485 	assert(d != NULL);
486 
487 	for (s = d->pd_sstart; s != NULL; s = s->ps_next)
488 		if (s->ps_vid == svid && s->ps_did == sdid)
489 			return (s);
490 
491 	return (NULL);
492 }
493 
494 pcidb_subvd_t *
495 pcidb_lookup_subvd_by_vendor(pcidb_vendor_t *v, uint16_t devid, uint16_t svid,
496     uint16_t sdid)
497 {
498 	pcidb_device_t *d;
499 
500 	assert(v != NULL);
501 	d = pcidb_lookup_device_by_vendor(v, devid);
502 	if (d == NULL)
503 		return (NULL);
504 
505 	return (pcidb_lookup_subvd_by_device(d, svid, sdid));
506 }
507 
508 pcidb_subvd_t *
509 pcidb_lookup_subvd(pcidb_hdl_t *h, uint16_t vid, uint16_t did, uint16_t svid,
510     uint16_t sdid)
511 {
512 	pcidb_device_t *d;
513 
514 	assert(h != NULL);
515 	d = pcidb_lookup_device(h, vid, did);
516 	if (d == NULL)
517 		return (NULL);
518 
519 	return (pcidb_lookup_subvd_by_device(d, svid, sdid));
520 }
521 
522 pcidb_subvd_t *
523 pcidb_subvd_iter(pcidb_device_t *d)
524 {
525 	return (d->pd_sstart);
526 }
527 
528 pcidb_subvd_t *
529 pcidb_subvd_iter_next(pcidb_subvd_t *s)
530 {
531 	return (s->ps_next);
532 }
533 
534 const char *
535 pcidb_subvd_name(pcidb_subvd_t *s)
536 {
537 	return (s->ps_name);
538 }
539 
540 uint16_t
541 pcidb_subvd_svid(pcidb_subvd_t *s)
542 {
543 	return (s->ps_vid);
544 }
545 
546 uint16_t
547 pcidb_subvd_sdid(pcidb_subvd_t *s)
548 {
549 	return (s->ps_did);
550 }
551 
552 pcidb_device_t *
553 pcidb_subvd_device(pcidb_subvd_t *s)
554 {
555 	return (s->ps_dev);
556 }
557 
558 pcidb_vendor_t *
559 pcidb_subvd_vendor(pcidb_subvd_t *s)
560 {
561 	return (s->ps_vend);
562 }
563