diff options
Diffstat (limited to 'src/common_device_name.c')
-rw-r--r-- | src/common_device_name.c | 435 |
1 files changed, 435 insertions, 0 deletions
diff --git a/src/common_device_name.c b/src/common_device_name.c new file mode 100644 index 0000000..2a0d5bf --- /dev/null +++ b/src/common_device_name.c @@ -0,0 +1,435 @@ +/* + * (C) Copyright IBM Corporation 2006 + * All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * on the rights to use, copy, modify, merge, publish, distribute, sub + * license, and/or sell copies of the Software, and to permit persons to whom + * the Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL + * IBM AND/OR THEIR SUPPLIERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +/** + * \file common_device_name.c + * Support routines used to determine the vendor or device names associated + * with a particular device or vendor. + */ + +#include "config.h" +#include <stdio.h> +#include <stdlib.h> +#include <ctype.h> + +#if defined(HAVE_STRING_H) +# include <string.h> +#elif defined(HAVE_STRINGS_H) +# include <strings.h> +#endif + +#if defined(HAVE_INTTYPES_H) +# include <inttypes.h> +#elif defined(HAVE_STDINT_H) +# include <stdint.h> +#endif + +#include "pciaccess.h" + +#ifndef PCIIDS_PATH +# define PCIIDS_PATH "/usr/share/hwdata" +#endif + +#define DO_MATCH(a,b) (((a) == PCI_MATCH_ANY) || ((a) == (b))) + +/** + * Node for sorting vendor IDs. + * + * Each structure forms an internal node of an n-way tree. Each node selects + * \c pci_id_node::bits number of bits from the vendor ID. Starting from the + * root of the tree, a slice of the low-order bits of the vendor ID are + * selected and used as an index into the \c pci_id_node::children array. + * + * At the leaf nodes (i.e., the node entered when all 16 bits of the vendor ID + * have been used), the \c pci_id_node::children is actually an array of + * pointers to \c pci_id_leaf structures. + * + * \todo + * Determine if there is a cleaner way (in the source code) to have the + * \c children array change type based on whether the node is internal or + * a leaf. + * + * \todo + * Currently \c bits is always 4. Decide if this value can ever change + * (i.e., to pull-up levels of the n-way tree when all the children's children + * are full). If it can, rip it out and hard-code it to 4 everywhere. + */ +struct pci_id_node { + unsigned bits; + struct pci_id_node * children[16]; +}; + +struct pci_id_leaf { + uint16_t vendor; + const char * vendor_name; + + size_t num_devices; + struct pci_device_leaf * devices; +}; + +struct pci_device_leaf { + struct pci_id_match id; + const char * device_name; +}; + +/** + * Root of the PCI vendor ID search tree. + */ +struct pci_id_node * tree = NULL; + +/** + * Name of the file containing the PCI ID information. + */ +static const char pci_id_file[] = PCIIDS_PATH "/pci.ids"; + + +/** + * Get a pointer to the leaf node for a vendor ID. + * + * If the vendor ID does not exist in the tree, it is added. + */ +static struct pci_id_leaf * +insert( uint16_t vendor ) +{ + struct pci_id_node * n; + unsigned bits = 0; + + if ( tree == NULL ) { + tree = calloc( 1, sizeof( struct pci_id_node ) ); + tree->bits = 4; + } + + n = tree; + while ( n != NULL ) { + const unsigned used_bits = n->bits; + const unsigned mask = (1 << used_bits) - 1; + const unsigned idx = (vendor & (mask << bits)) >> bits; + + + if ( bits >= 16 ) { + break; + } + + bits += used_bits; + + if ( n->children[ idx ] == NULL ) { + if ( bits < 16 ) { + struct pci_id_node * child = + calloc( 1, sizeof( struct pci_id_node ) ); + + child->bits = 4; + + n->children[ idx ] = child; + } + else { + struct pci_id_leaf * leaf = + calloc( 1, sizeof( struct pci_id_leaf ) ); + + leaf->vendor = vendor; + + n->children[ idx ] = (struct pci_id_node *) leaf; + } + } + + n = n->children[ idx ]; + } + + return (struct pci_id_leaf *) n; +} + + +/** + * Populate a vendor node with all the devices associated with that vendor + * + * \param vend Vendor node that is to be filled from the pci.ids file. + * + * \todo + * The parsing in this function should be more rhobust. There are some error + * cases (i.e., a 0-tab line followed by a 2-tab line) that aren't handled + * correctly. I don't think there are any security problems with the code, + * but it's not impossible. + */ +static void +populate_vendor( struct pci_id_leaf * vend, int fill_device_data ) +{ + FILE * f = fopen( pci_id_file, "r" ); + char buf[128]; + unsigned vendor = PCI_MATCH_ANY; + + + while( fgets( buf, sizeof( buf ), f ) != NULL ) { + unsigned num_tabs; + char * new_line; + size_t length; + + /* Each line either starts with zero, one, or two tabs followed by + * a series of 4 hex digits. Any lines not matching that are ignored. + */ + + for ( num_tabs = 0 ; num_tabs < 3 ; num_tabs++ ) { + if ( buf[ num_tabs ] != '\t' ) { + break; + } + } + + if ( !isxdigit( buf[ num_tabs + 0 ] ) + || !isxdigit( buf[ num_tabs + 1 ] ) + || !isxdigit( buf[ num_tabs + 2 ] ) + || !isxdigit( buf[ num_tabs + 3 ] ) ) { + continue; + } + + new_line = strchr( buf, '\n' ); + if ( new_line != NULL ) { + *new_line = '\0'; + } + + length = strlen( buf ); + (void) memset( buf + length, 0, sizeof( buf ) - length ); + + + if ( num_tabs == 0 ) { + vendor = (unsigned) strtoul( & buf[ num_tabs ], NULL, 16 ); + if ( vend->vendor == vendor ) { + vend->vendor_name = strdup( & buf[ num_tabs + 6 ] ); + + /* If we're not going to fill in all of the device data as + * well, then bail out now. We have all the information that + * we need. + */ + if ( ! fill_device_data ) { + break; + } + } + } + else if ( vendor == vend->vendor ) { + struct pci_device_leaf * d; + struct pci_device_leaf * dev; + struct pci_device_leaf * last_dev; + + + + d = realloc( vend->devices, (vend->num_devices + 1) + * sizeof( struct pci_device_leaf ) ); + if ( d == NULL ) { + return; + } + + last_dev = & d[ vend->num_devices - 1 ]; + dev = & d[ vend->num_devices ]; + vend->num_devices++; + vend->devices = d; + + if ( num_tabs == 1 ) { + dev->id.vendor_id = vend->vendor; + dev->id.device_id = (unsigned) strtoul( & buf[ num_tabs ], + NULL, 16 ); + dev->id.subvendor_id = PCI_MATCH_ANY; + dev->id.subdevice_id = PCI_MATCH_ANY; + + dev->id.device_class = 0; + dev->id.device_class_mask = 0; + dev->id.match_data = 0; + + dev->device_name = strdup( & buf[ num_tabs + 6 ] ); + } + else { + dev->id = last_dev->id; + + dev->id.subvendor_id= (unsigned) strtoul( & buf[ num_tabs ], + NULL, 16 ); + dev->id.subdevice_id = (unsigned) strtoul( & buf[ num_tabs + 5 ], + NULL, 16 ); + dev->device_name = strdup( & buf[ num_tabs + 5 + 6 ] ); + } + } + } + + fclose( f ); +} + + +static const char * +find_device_name( const struct pci_id_match * m ) +{ + struct pci_id_leaf * vend; + unsigned i; + + + if ( m->vendor_id == PCI_MATCH_ANY ) { + return NULL; + } + + + vend = insert( m->vendor_id ); + if ( vend == NULL ) { + return NULL; + } + + if ( vend->num_devices == 0 ) { + populate_vendor( vend, 1 ); + } + + + for ( i = 0 ; i < vend->num_devices ; i++ ) { + struct pci_device_leaf * d = & vend->devices[ i ]; + + if ( DO_MATCH( m->vendor_id, d->id.vendor_id ) + && DO_MATCH( m->device_id, d->id.device_id ) + && DO_MATCH( m->subvendor_id, d->id.subvendor_id ) + && DO_MATCH( m->subdevice_id, d->id.subdevice_id ) ) { + return d->device_name; + } + } + + return NULL; +} + + +static const char * +find_vendor_name( const struct pci_id_match * m ) +{ + struct pci_id_leaf * vend; + + + if ( m->vendor_id == PCI_MATCH_ANY ) { + return NULL; + } + + + vend = insert( m->vendor_id ); + if ( vend == NULL ) { + return NULL; + } + + if ( vend->vendor_name == NULL ) { + populate_vendor( vend, 0 ); + } + + + return vend->vendor_name; +} + + +/** + * Get a name based on an arbitrary PCI search structure. + */ +const char * +pci_get_name( const struct pci_id_match * m ) +{ + return find_device_name( m ); +} + + +/** + * Get the name associated with the device's primary device ID. + */ +const char * +pci_device_get_device_name( const struct pci_device * dev ) +{ + struct pci_id_match m; + + + m.vendor_id = dev->vendor_id; + m.device_id = dev->device_id; + m.subvendor_id = PCI_MATCH_ANY; + m.subdevice_id = PCI_MATCH_ANY; + m.device_class = 0; + m.device_class_mask = 0; + m.match_data = 0; + + return find_device_name( & m ); +} + + +/** + * Get the name associated with the device's subdevice ID. + */ +const char * +pci_device_get_subdevice_name( const struct pci_device * dev ) +{ + struct pci_id_match m; + + + if ( (dev->subvendor_id == 0) || (dev->subdevice_id == 0) ) { + return NULL; + } + + m.vendor_id = dev->vendor_id; + m.device_id = dev->device_id; + m.subvendor_id = dev->subvendor_id; + m.subdevice_id = dev->subdevice_id; + m.device_class = 0; + m.device_class_mask = 0; + m.match_data = 0; + + return find_device_name( & m ); +} + + +/** + * Get the name associated with the device's primary vendor ID. + */ +const char * +pci_device_get_vendor_name( const struct pci_device * dev ) +{ + struct pci_id_match m; + + + m.vendor_id = dev->vendor_id; + m.device_id = PCI_MATCH_ANY; + m.subvendor_id = PCI_MATCH_ANY; + m.subdevice_id = PCI_MATCH_ANY; + m.device_class = 0; + m.device_class_mask = 0; + m.match_data = 0; + + return find_vendor_name( & m ); +} + + +/** + * Get the name associated with the device's subvendor ID. + */ +const char * +pci_device_get_subvendor_name( const struct pci_device * dev ) +{ + struct pci_id_match m; + + + if ( dev->subvendor_id == 0 ) { + return NULL; + } + + + m.vendor_id = dev->subvendor_id; + m.device_id = PCI_MATCH_ANY; + m.subvendor_id = PCI_MATCH_ANY; + m.subdevice_id = PCI_MATCH_ANY; + m.device_class = 0; + m.device_class_mask = 0; + m.match_data = 0; + + return find_vendor_name( & m ); +} |