512 lines
12 KiB
C
512 lines
12 KiB
C
/*
|
|
* (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"
|
|
#include "pciaccess_private.h"
|
|
|
|
#define DO_MATCH(a,b) (((a) == PCI_MATCH_ANY) || ((a) == (b)))
|
|
|
|
#ifdef HAVE_ZLIB
|
|
#include <zlib.h>
|
|
typedef gzFile pci_id_file;
|
|
|
|
static pci_id_file
|
|
pci_id_file_open()
|
|
{
|
|
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
|
|
typedef FILE pci_id_file;
|
|
#define pci_id_file_open() 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 = pci_id_file_open();
|
|
char buf[128];
|
|
unsigned vendor = PCI_MATCH_ANY;
|
|
|
|
|
|
/* If the pci.ids file could not be opened, there's nothing we can do.
|
|
*/
|
|
if (f == NULL) {
|
|
return;
|
|
}
|
|
|
|
|
|
/* 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) {
|
|
pci_id_file_close( f );
|
|
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 ) {
|
|
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 ] );
|
|
}
|
|
}
|
|
}
|
|
|
|
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 );
|
|
}
|