/* * (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. */ #ifdef HAVE_CONFIG_H #include "config.h" #endif #include #include #include #if defined(HAVE_STRING_H) # include #elif defined(HAVE_STRINGS_H) # include #endif #if defined(HAVE_INTTYPES_H) # include #elif defined(HAVE_STDINT_H) # include #endif #include "pciaccess.h" #include "pciaccess_private.h" #define DO_MATCH(a,b) (((a) == PCI_MATCH_ANY) || ((a) == (b))) #ifdef HAVE_ZLIB #include typedef gzFile pci_id_file; static pci_id_file pci_id_file_open(void) { pci_id_file result; result = gzopen(PCIIDS_PATH "/pci.ids.gz", "rb"); if (result) return result; return gzopen(PCIIDS_PATH "/pci.ids", "rb"); } #define pci_id_file_gets(l, s, f) gzgets(f, l, s) #define pci_id_file_close(f) gzclose(f) #else /* not zlib */ typedef FILE * pci_id_file; static pci_id_file pci_id_file_open(void) { #ifndef __sun pci_id_file result; result = fopen(PCIIDS_PATH "/pci.ids", "re"); if (result) return result; #endif return fopen(PCIIDS_PATH "/pci.ids", "r"); } #define pci_id_file_gets(l, s, f) fgets(l, s, f) #define pci_id_file_close(f) fclose(f) #endif /** * 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. */ _pci_hidden struct pci_id_node * tree = NULL; /** * 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 ) { pci_id_file f; char buf[128]; unsigned vendor = PCI_MATCH_ANY; /* If the device tree for this vendor is already populated, don't do * anything. This avoids wasted processing and potential memory leaks. */ if (vend->num_devices != 0) { return; } f = pci_id_file_open(); /* If the pci.ids file could not be opened, there's nothing we can do. */ if (f == NULL) { return; } while( pci_id_file_gets( 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 ) { /* vendor_name may already be set from a previous invocation * of this function with fill_device_data = 0. */ if (vend->vendor_name == NULL) { 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 ) { goto cleanup; } 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 ] ); } } } cleanup: pci_id_file_close( f ); } /** * Find the name of the specified device. * * Finds the actual product name of the specified device. If a subvendor ID * and subdevice ID are specified in \c m, the returned name will be the name * of the subdevice. */ 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; } /** * Find the vendor name of the specified device. * * Finds the actual vendor name of the specified device. If a subvendor ID * and subdevice ID are specified in \c m, the returned name will be the name * associated with the subvendor. */ 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. */ void pci_get_strings( const struct pci_id_match * m, const char ** device_name, const char ** vendor_name, const char ** subdevice_name, const char ** subvendor_name ) { struct pci_id_match temp; temp = *m; temp.subvendor_id = PCI_MATCH_ANY; temp.subdevice_id = PCI_MATCH_ANY; if ( device_name != NULL ) { *device_name = find_device_name( & temp ); } if ( vendor_name != NULL ) { *vendor_name = find_vendor_name( & temp ); } if ( subdevice_name != NULL ) { *subdevice_name = find_device_name( m ); } if ( subvendor_name != NULL ) { *subvendor_name = find_vendor_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 ); }