/* * 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_idvendor_name * device_iddevice_name * subvendorsubdevicesubsystem_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. Once we hit those, we stop all parsing. We currently * don't care about consuming or presenting those. */ #include #include #include #include #include #include #include #include #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 (0); 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 (0); 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); }