diff options
-rw-r--r-- | usr/src/lib/libpcidb/common/pcidb.c | 559 | ||||
-rw-r--r-- | usr/src/lib/libpcidb/common/pcidb.h | 84 |
2 files changed, 643 insertions, 0 deletions
diff --git a/usr/src/lib/libpcidb/common/pcidb.c b/usr/src/lib/libpcidb/common/pcidb.c new file mode 100644 index 0000000..2d3babe --- /dev/null +++ b/usr/src/lib/libpcidb/common/pcidb.c @@ -0,0 +1,559 @@ +/* + * CDDL HEADER START + * + * The contents of this file are subject to the terms of the + * Common Development and Distribution License (the "License"). + * You may not use this file except in compliance with the License. + * + * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE + * or http://www.opensolaris.org/os/licensing. + * See the License for the specific language governing permissions + * and limitations under the License. + * + * When distributing Covered Code, include this CDDL HEADER in each + * file and include the License file at usr/src/OPENSOLARIS.LICENSE. + * If applicable, add the following below this CDDL HEADER, with the + * fields enclosed by brackets "[]" replaced with your own identifying + * information: Portions Copyright [yyyy] [name of copyright owner] + * + * CDDL HEADER END + */ +/* + * Copyright (c) 2012, Joyent, Inc. All rights reserved. + */ + +/* + * This library exists to understand and parse the pci.ids database that is + * maintained at http://pci-ids.ucw.cz/ and in the gate at cmd/hwdata. This + * database provides a way to map the PCI device, vendor, and subsystem ids to + * a human understandable name. + * + * This library exports this data in a similar way to a tree. The handle that + * is returned from pcidb_open is the root of the tree. The next level are the + * vendors. Each vendor has a unique set of devices and each device has a unique + * set of subvendor and subdevice pairs. + * + * Parsing information: + * + * The database is formatted in the following basic format: + * vendor_id<two spaces>vendor_name + * <tab>device_id<two spaces>device_name + * <tab><tab>subvendor<space>subdevice<two spaces>subsystem_name + * + * For any given vendor, there can be multiple devices. And for any given device + * there will be multiple subsystems. In addition, there can be comments that + * start a line which use the '#' character. + * + * At the end of the file, there are a series of PCI classes. Those will start + * with a single C<space>. Once we hit those, we stop all parsing. We currently + * don't care about consuming or presenting those. + */ + +#include <sys/types.h> +#include <stdint.h> +#include <stdio.h> +#include <stdlib.h> +#include <errno.h> +#include <string.h> +#include <assert.h> +#include <unistd.h> + +#include "pcidb.h" + +#define PCI_NAME_MAX 256 +#define PCI_READLINE 1024 + +/* Forward declarations */ +struct pcidb_vendor; +struct pcidb_device; +struct pcidb_subvd; + +struct pcidb_subvd { + uint16_t ps_vid; + uint16_t ps_did; + char ps_name[PCI_NAME_MAX]; + struct pcidb_subvd *ps_prev; + struct pcidb_subvd *ps_next; + struct pcidb_device *ps_dev; + struct pcidb_vendor *ps_vend; +}; + +struct pcidb_device { + uint16_t pd_id; + char pd_name[PCI_NAME_MAX]; + struct pcidb_subvd *pd_sstart; + struct pcidb_subvd *pd_send; + struct pcidb_device *pd_next; + struct pcidb_device *pd_prev; + struct pcidb_vendor *pd_vend; +}; + +struct pcidb_vendor { + uint16_t pv_id; + char pv_name[PCI_NAME_MAX]; + struct pcidb_device *pv_dstart; + struct pcidb_device *pv_dend; + struct pcidb_vendor *pv_prev; + struct pcidb_vendor *pv_next; +}; + +struct pcidb_hdl { + pcidb_vendor_t *ph_vstart; + pcidb_vendor_t *ph_vend; +}; + +typedef enum pcidb_parse { + PDB_VENDOR, + PDB_DEVICE, + PDB_SUBDEV +} pcidb_parse_t; + +static const char *pci_db = "/usr/share/hwdata/pci.ids"; + +static void +pcihdl_add_vendor(pcidb_hdl_t *hdl, pcidb_vendor_t *v) +{ + if (hdl->ph_vstart == NULL && hdl->ph_vend == NULL) { + hdl->ph_vstart = v; + hdl->ph_vend = v; + v->pv_prev = NULL; + v->pv_next = NULL; + } else { + v->pv_prev = hdl->ph_vend; + v->pv_next = NULL; + hdl->ph_vend->pv_next = v; + hdl->ph_vend = v; + } +} + +static pcidb_vendor_t * +parse_vendor(char *buf, pcidb_hdl_t *hdl) +{ + pcidb_vendor_t *v; + size_t len; + + v = malloc(sizeof (pcidb_vendor_t)); + if (v == NULL) + return (NULL); + + pcihdl_add_vendor(hdl, v); + v->pv_dstart = NULL; + v->pv_dend = NULL; + + buf[4] = '\0'; + v->pv_id = strtol(buf, NULL, 16); + buf += 6; + len = strlen(buf); + if (buf[len-1] == '\n') + buf[len-1] = '\0'; + + (void) strlcpy(v->pv_name, buf, PCI_NAME_MAX); + + return (v); +} + +static void +insert_device(pcidb_vendor_t *v, pcidb_device_t *d) +{ + d->pd_vend = v; + if (v->pv_dstart == NULL && v->pv_dend == NULL) { + v->pv_dstart = d; + v->pv_dend = d; + d->pd_next = NULL; + d->pd_prev = NULL; + } else { + d->pd_prev = v->pv_dend; + d->pd_next = NULL; + v->pv_dend->pd_next = d; + v->pv_dend = d; + } +} + +static pcidb_device_t * +parse_device(char *buf, pcidb_vendor_t *v) +{ + pcidb_device_t *d; + size_t len; + + d = malloc(sizeof (pcidb_device_t)); + if (d == NULL) + return (d); + + d->pd_sstart = NULL; + d->pd_send = NULL; + insert_device(v, d); + + buf++; + buf[4] = '\0'; + d->pd_id = strtol(buf, NULL, 16); + buf += 6; + len = strlen(buf); + if (buf[len-1] == '\n') + buf[len-1] = '\0'; + + (void) strlcpy(d->pd_name, buf, PCI_NAME_MAX); + return (d); +} + +static void +insert_subdev(pcidb_device_t *d, pcidb_subvd_t *s) +{ + s->ps_dev = d; + s->ps_vend = d->pd_vend; + if (d->pd_sstart == NULL) { + d->pd_sstart = s; + d->pd_send = s; + s->ps_prev = NULL; + s->ps_next = NULL; + } else { + s->ps_prev = d->pd_send; + s->ps_next = NULL; + d->pd_send->ps_next = s; + d->pd_send = s; + } +} + +static pcidb_subvd_t * +parse_subdev(char *buf, pcidb_device_t *d) +{ + pcidb_subvd_t *s; + size_t len; + + s = malloc(sizeof (pcidb_subvd_t)); + if (s == NULL) + return (NULL); + insert_subdev(d, s); + + buf += 2; + buf[4] = '\0'; + s->ps_vid = strtol(buf, NULL, 16); + buf += 5; + buf[4] = '\0'; + s->ps_did = strtol(buf, NULL, 16); + buf += 6; + + len = strlen(buf); + if (buf[len-1] == '\n') + buf[len-1] = '\0'; + + (void) strlcpy(s->ps_name, buf, PCI_NAME_MAX); + + return (s); +} + +static int +readline(FILE *f, char *buf, size_t len) +{ + for (;;) { + if (fgets(buf, len, f) == NULL) + return (-1); + + if (buf[0] == 'C') + return (-1); + + if (buf[0] != '#' && buf[0] != '\n') + return (0); + } +} + +static int +parse_db(FILE *f, pcidb_hdl_t *hdl) +{ + char buf[1024]; + pcidb_vendor_t *v = NULL; + pcidb_device_t *d = NULL; + pcidb_parse_t state = PDB_VENDOR; + + for (;;) { + errno = 0; + if (readline(f, buf, sizeof (buf)) != 0) { + if (errno != 0) + return (-1); + else + return (0); + } + +newstate: + switch (state) { + case PDB_VENDOR: + v = parse_vendor(buf, hdl); + if (v == NULL) + return (NULL); + state = PDB_DEVICE; + continue; + case PDB_DEVICE: + if (buf[0] != '\t') { + state = PDB_VENDOR; + goto newstate; + } + + if (buf[1] == '\t') { + state = PDB_SUBDEV; + goto newstate; + } + + assert(v != NULL); + d = parse_device(buf, v); + if (d == NULL) + return (NULL); + continue; + case PDB_SUBDEV: + if (buf[0] != '\t') { + state = PDB_VENDOR; + goto newstate; + } + + if (buf[0] == '\t' && buf[1] != '\t') { + state = PDB_DEVICE; + goto newstate; + } + + assert(buf[0] == '\t' && buf[1] == '\t'); + assert(d != NULL); + (void) parse_subdev(buf, d); + } + } +} + +pcidb_hdl_t * +pcidb_open(int version) +{ + pcidb_hdl_t *h; + FILE *f; + + if (version != PCIDB_VERSION) { + errno = EINVAL; + return (NULL); + } + + h = malloc(sizeof (pcidb_hdl_t)); + if (h == NULL) + return (NULL); + + h->ph_vstart = NULL; + h->ph_vend = NULL; + + f = fopen(pci_db, "rF"); + if (f == NULL) { + free(h); + return (NULL); + } + + if (parse_db(f, h) < 0) { + pcidb_close(h); + free(h); + return (NULL); + } + + return (h); +} + +void +pcidb_close(pcidb_hdl_t *h) +{ + pcidb_vendor_t *v, *tv; + + pcidb_device_t *d, *td; + pcidb_subvd_t *s, *ts; + + if (h == NULL) + return; + + v = h->ph_vstart; + while (v != NULL) { + d = v->pv_dstart; + while (d != NULL) { + s = d->pd_sstart; + while (s != NULL) { + ts = s; + s = s->ps_next; + free(ts); + } + td = d; + d = d->pd_next; + free(td); + } + tv = v; + v = v->pv_next; + free(tv); + } + + free(h); +} + +pcidb_vendor_t * +pcidb_lookup_vendor(pcidb_hdl_t *hdl, uint16_t id) +{ + pcidb_vendor_t *v; + + for (v = hdl->ph_vstart; v != NULL; v = v->pv_next) { + if (v->pv_id == id) + return (v); + } + + return (NULL); +} + +const char * +pcidb_vendor_name(pcidb_vendor_t *v) +{ + return (v->pv_name); +} + +uint16_t +pcidb_vendor_id(pcidb_vendor_t *v) +{ + return (v->pv_id); +} + +pcidb_vendor_t * +pcidb_vendor_iter(pcidb_hdl_t *h) +{ + return (h->ph_vstart); +} + +pcidb_vendor_t * +pcidb_vendor_iter_next(pcidb_vendor_t *v) +{ + assert(v != NULL); + return (v->pv_next); +} + +pcidb_device_t * +pcidb_lookup_device_by_vendor(pcidb_vendor_t *v, uint16_t id) +{ + pcidb_device_t *d; + assert(v != NULL); + + for (d = v->pv_dstart; d != NULL; d = d->pd_next) + if (d->pd_id == id) + return (d); + + return (NULL); +} + +pcidb_device_t * +pcidb_lookup_device(pcidb_hdl_t *h, uint16_t vid, uint16_t did) +{ + pcidb_vendor_t *v; + + v = pcidb_lookup_vendor(h, vid); + if (v == NULL) + return (NULL); + + return (pcidb_lookup_device_by_vendor(v, did)); +} + +pcidb_device_t * +pcidb_device_iter(pcidb_vendor_t *v) +{ + return (v->pv_dstart); +} + +pcidb_device_t * +pcidb_device_iter_next(pcidb_device_t *d) +{ + return (d->pd_next); +} + +const char * +pcidb_device_name(pcidb_device_t *d) +{ + return (d->pd_name); +} + +uint16_t +pcidb_device_id(pcidb_device_t *d) +{ + return (d->pd_id); +} + +pcidb_vendor_t * +pcidb_device_vendor(pcidb_device_t *d) +{ + return (d->pd_vend); +} + +pcidb_subvd_t * +pcidb_lookup_subvd_by_device(pcidb_device_t *d, uint16_t svid, uint16_t sdid) +{ + pcidb_subvd_t *s; + + assert(d != NULL); + + for (s = d->pd_sstart; s != NULL; s = s->ps_next) + if (s->ps_vid == svid && s->ps_did == sdid) + return (s); + + return (NULL); +} + +pcidb_subvd_t * +pcidb_lookup_subvd_by_vendor(pcidb_vendor_t *v, uint16_t devid, uint16_t svid, + uint16_t sdid) +{ + pcidb_device_t *d; + + assert(v != NULL); + d = pcidb_lookup_device_by_vendor(v, devid); + if (d == NULL) + return (NULL); + + return (pcidb_lookup_subvd_by_device(d, svid, sdid)); +} + +pcidb_subvd_t * +pcidb_lookup_subvd(pcidb_hdl_t *h, uint16_t vid, uint16_t did, uint16_t svid, + uint16_t sdid) +{ + pcidb_device_t *d; + + assert(h != NULL); + d = pcidb_lookup_device(h, vid, did); + if (d == NULL) + return (NULL); + + return (pcidb_lookup_subvd_by_device(d, svid, sdid)); +} + +pcidb_subvd_t * +pcidb_subvd_iter(pcidb_device_t *d) +{ + return (d->pd_sstart); +} + +pcidb_subvd_t * +pcidb_subvd_iter_next(pcidb_subvd_t *s) +{ + return (s->ps_next); +} + +const char * +pcidb_subvd_name(pcidb_subvd_t *s) +{ + return (s->ps_name); +} + +uint16_t +pcidb_subvd_svid(pcidb_subvd_t *s) +{ + return (s->ps_vid); +} + +uint16_t +pcidb_subvd_sdid(pcidb_subvd_t *s) +{ + return (s->ps_did); +} + +pcidb_device_t * +pcidb_subvd_device(pcidb_subvd_t *s) +{ + return (s->ps_dev); +} + +pcidb_vendor_t * +pcidb_subvd_vendor(pcidb_subvd_t *s) +{ + return (s->ps_vend); +} diff --git a/usr/src/lib/libpcidb/common/pcidb.h b/usr/src/lib/libpcidb/common/pcidb.h new file mode 100644 index 0000000..a11e4d9 --- /dev/null +++ b/usr/src/lib/libpcidb/common/pcidb.h @@ -0,0 +1,84 @@ +/* + * CDDL HEADER START + * + * The contents of this file are subject to the terms of the + * Common Development and Distribution License (the "License"). + * You may not use this file except in compliance with the License. + * + * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE + * or http://www.opensolaris.org/os/licensing. + * See the License for the specific language governing permissions + * and limitations under the License. + * + * When distributing Covered Code, include this CDDL HEADER in each + * file and include the License file at usr/src/OPENSOLARIS.LICENSE. + * If applicable, add the following below this CDDL HEADER, with the + * fields enclosed by brackets "[]" replaced with your own identifying + * information: Portions Copyright [yyyy] [name of copyright owner] + * + * CDDL HEADER END + */ +/* + * Copyright (c) 2012, Joyent, Inc. All rights reserved. + */ + +/* + * This header file is private to illumos and should not be shipped. + */ + +#ifndef _PCIDB_H +#define _PCIDB_H + +#include <stdint.h> + +#ifdef __cplusplus +extern "C" { +#endif + +#define PCIDB_VERSION 1 + +typedef struct pcidb_hdl pcidb_hdl_t; +typedef struct pcidb_vendor pcidb_vendor_t; +typedef struct pcidb_device pcidb_device_t; +typedef struct pcidb_subvd pcidb_subvd_t; + +extern pcidb_hdl_t *pcidb_open(int); +extern void pcidb_close(pcidb_hdl_t *); + +extern pcidb_vendor_t *pcidb_lookup_vendor(pcidb_hdl_t *, uint16_t); +extern pcidb_vendor_t *pcidb_vendor_iter(pcidb_hdl_t *); +extern pcidb_vendor_t *pcidb_vendor_iter_next(pcidb_vendor_t *); + +extern const char *pcidb_vendor_name(pcidb_vendor_t *); +extern uint16_t pcidb_vendor_id(pcidb_vendor_t *); + +extern pcidb_device_t *pcidb_lookup_device(pcidb_hdl_t *, uint16_t, uint16_t); +extern pcidb_device_t *pcidb_lookup_device_by_vendor(pcidb_vendor_t *, + uint16_t); +extern pcidb_device_t *pcidb_device_iter(pcidb_vendor_t *); +extern pcidb_device_t *pcidb_device_iter_next(pcidb_device_t *); + +extern const char *pcidb_device_name(pcidb_device_t *); +extern uint16_t pcidb_device_id(pcidb_device_t *); +extern pcidb_vendor_t *pcidb_device_vendor(pcidb_device_t *); + +extern pcidb_subvd_t *pcidb_lookup_subvd(pcidb_hdl_t *, uint16_t, uint16_t, + uint16_t, uint16_t); +extern pcidb_subvd_t *pcidb_lookup_subvd_by_vendor(pcidb_vendor_t *, uint16_t, + uint16_t, uint16_t); +extern pcidb_subvd_t *pcidb_lookup_subvd_by_device(pcidb_device_t *, uint16_t, + uint16_t); +extern pcidb_subvd_t *pcidb_subvd_iter(pcidb_device_t *); +extern pcidb_subvd_t *pcidb_subvd_iter_next(pcidb_subvd_t *); + +extern const char *pcidb_subvd_name(pcidb_subvd_t *); +extern uint16_t pcidb_subvd_svid(pcidb_subvd_t *); +extern uint16_t pcidb_subvd_sdid(pcidb_subvd_t *); +extern pcidb_device_t *pcidb_subvd_device(pcidb_subvd_t *); +extern pcidb_vendor_t *pcidb_subvd_vendor(pcidb_subvd_t *); + +#ifdef __cplusplus +} +#endif + +#endif /* _PCIDB_H */ |