root/usr/src/cmd/hal/hald/ids.c
/***************************************************************************
 * CVSID: $Id$
 *
 * ids.c : Lookup names from hardware identifiers
 *
 * Copyright (C) 2004 David Zeuthen, <david@fubar.dk>
 *
 * Licensed under the Academic Free License version 2.1
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
 *
 **************************************************************************/

#ifdef HAVE_CONFIG_H
#  include <config.h>
#endif

#include <ctype.h>
#include <stdint.h>
#include <string.h>
#include <unistd.h>

#include <dbus/dbus.h>
#include <dbus/dbus-glib.h>

#include "logger.h"

#include "ids.h"

/** Pointer to where the pci.ids file is loaded */
static char *pci_ids = NULL;

/** Length of data store at at pci_ids */
static unsigned int pci_ids_len;

/** Iterator position into pci_ids */
static unsigned int pci_ids_iter_pos;

/** Initialize the pci.ids line iterator to the beginning of the file */
static void
pci_ids_line_iter_init ()
{
        pci_ids_iter_pos = 0;
}

/** Maximum length of lines in pci.ids */
#define PCI_IDS_MAX_LINE_LEN 512

/** Get the next line from pci.ids
 *
 *  @param  line_len            Pointer to where number of bytes in line will
 *                              be stored
 *  @return                     Pointer to the line; only valid until the
 *                              next invocation of this function
 */
static char *
pci_ids_line_iter_get_line (unsigned int *line_len)
{
        unsigned int i;
        static char line[PCI_IDS_MAX_LINE_LEN];

        for (i = 0;
             pci_ids_iter_pos < pci_ids_len &&
             i < PCI_IDS_MAX_LINE_LEN - 1 &&
             pci_ids[pci_ids_iter_pos] != '\n'; i++, pci_ids_iter_pos++) {
                line[i] = pci_ids[pci_ids_iter_pos];
        }

        line[i] = '\0';
        if (line_len != NULL)
                *line_len = i;

        pci_ids_iter_pos++;

        return line;
}

/** See if there are more lines to process in pci.ids
 *
 *  @return                     #TRUE iff there are more lines to process
 */
static dbus_bool_t
pci_ids_line_iter_has_more ()
{
        return pci_ids_iter_pos < pci_ids_len;
}


/** Find the names for a PCI device.
 *
 *  The pointers returned are only valid until the next invocation of this
 *  function.
 *
 *  @param  vendor_id           PCI vendor id or 0 if unknown
 *  @param  product_id          PCI product id or 0 if unknown
 *  @param  subsys_vendor_id    PCI subsystem vendor id or 0 if unknown
 *  @param  subsys_product_id   PCI subsystem product id or 0 if unknown
 *  @param  vendor_name         Set to pointer of result or NULL
 *  @param  product_name        Set to pointer of result or NULL
 *  @param  subsys_vendor_name  Set to pointer of result or NULL
 *  @param  subsys_product_name Set to pointer of result or NULL
 */
void
ids_find_pci (int vendor_id, int product_id,
              int subsys_vendor_id, int subsys_product_id,
              char **vendor_name, char **product_name,
              char **subsys_vendor_name, char **subsys_product_name)
{
        char *line;
        unsigned int i;
        unsigned int line_len;
        unsigned int num_tabs;
        char rep_vi[8];
        char rep_pi[8];
        char rep_svi[8];
        char rep_spi[8];
        dbus_bool_t vendor_matched = FALSE;
        dbus_bool_t product_matched = FALSE;
        static char store_vn[PCI_IDS_MAX_LINE_LEN];
        static char store_pn[PCI_IDS_MAX_LINE_LEN];
        static char store_svn[PCI_IDS_MAX_LINE_LEN];
        static char store_spn[PCI_IDS_MAX_LINE_LEN];

        snprintf (rep_vi, 8, "%04x", vendor_id);
        snprintf (rep_pi, 8, "%04x", product_id);
        snprintf (rep_svi, 8, "%04x", subsys_vendor_id);
        snprintf (rep_spi, 8, "%04x", subsys_product_id);

        *vendor_name = NULL;
        *product_name = NULL;
        *subsys_vendor_name = NULL;
        *subsys_product_name = NULL;

        for (pci_ids_line_iter_init (); pci_ids_line_iter_has_more ();) {
                line = pci_ids_line_iter_get_line (&line_len);

                /* skip lines with no content */
                if (line_len < 4)
                        continue;

                /* skip comments */
                if (line[0] == '#')
                        continue;

                /* count number of tabs */
                num_tabs = 0;
                for (i = 0; i < line_len; i++) {
                        if (line[i] != '\t')
                                break;
                        num_tabs++;
                }

                switch (num_tabs) {
                case 0:
                        /* vendor names */
                        vendor_matched = FALSE;

                        /* first check subsys_vendor_id, if haven't done
                         * already */
                        if (*subsys_vendor_name == NULL
                            && subsys_vendor_id != 0) {
                                if ((*((dbus_uint32_t *) line)) ==
                                    (*((dbus_uint32_t *) rep_svi))) {
                                        /* found it */
                                        for (i = 4; i < line_len; i++) {
                                                if (!isspace (line[i]))
                                                        break;
                                        }
                                        strncpy (store_svn, line + i,
                                                 PCI_IDS_MAX_LINE_LEN);
                                        *subsys_vendor_name = store_svn;
                                }
                        }

                        /* check vendor_id */
                        if (vendor_id != 0) {
                                if (memcmp (line, rep_vi, 4) == 0) {
                                        /* found it */
                                        vendor_matched = TRUE;

                                        for (i = 4; i < line_len; i++) {
                                                if (!isspace (line[i]))
                                                        break;
                                        }
                                        strncpy (store_vn, line + i,
                                                 PCI_IDS_MAX_LINE_LEN);
                                        *vendor_name = store_vn;
                                }
                        }

                        break;

                case 1:
                        product_matched = FALSE;

                        /* product names */
                        if (!vendor_matched)
                                continue;

                        /* check product_id */
                        if (product_id != 0) {
                                if (memcmp (line + 1, rep_pi, 4) == 0) {
                                        /* found it */

                                        product_matched = TRUE;

                                        for (i = 5; i < line_len; i++) {
                                                if (!isspace (line[i]))
                                                        break;
                                        }
                                        strncpy (store_pn, line + i,
                                                 PCI_IDS_MAX_LINE_LEN);
                                        *product_name = store_pn;
                                }
                        }
                        break;

                case 2:
                        /* subsystem_vendor subsystem_product */
                        if (!vendor_matched || !product_matched)
                                continue;

                        /* check product_id */
                        if (subsys_vendor_id != 0
                            && subsys_product_id != 0) {
                                if (memcmp (line + 2, rep_svi, 4) == 0
                                    && memcmp (line + 7, rep_spi,
                                               4) == 0) {
                                        /* found it */
                                        for (i = 11; i < line_len; i++) {
                                                if (!isspace (line[i]))
                                                        break;
                                        }
                                        strncpy (store_spn, line + i,
                                                 PCI_IDS_MAX_LINE_LEN);
                                        *subsys_product_name = store_spn;
                                }
                        }

                        break;

                default:
                        break;
                }

        }
}

/** Free resources used by to store the PCI database
 *
 *  @param                      #FALSE if the PCI database wasn't loaded
 */
static dbus_bool_t
pci_ids_free ()
{
        if (pci_ids != NULL) {
                free (pci_ids);
                pci_ids = NULL;
                return TRUE;
        }
        return FALSE;
}

/** Load the PCI database used for mapping vendor, product, subsys_vendor
 *  and subsys_product numbers into names.
 *
 *  @param  path                Path of the pci.ids file, e.g.
 *                              /usr/share/hwdata/pci.ids
 *  @return                     #TRUE if the file was succesfully loaded
 */
static dbus_bool_t
pci_ids_load (const char *path)
{
        FILE *fp;
        unsigned int num_read;

        fp = fopen (path, "r");
        if (fp == NULL) {
                HAL_ERROR (("couldn't open PCI database at %s,", path));
                return FALSE;
        }

        fseek (fp, 0, SEEK_END);
        pci_ids_len = ftell (fp);
        fseek (fp, 0, SEEK_SET);

        pci_ids = malloc (pci_ids_len);
        if (pci_ids == NULL) {
                DIE (("Couldn't allocate %d bytes for PCI database file\n",
                      pci_ids_len));
        }

        num_read = fread (pci_ids, sizeof (char), pci_ids_len, fp);
        if (pci_ids_len != num_read) {
                HAL_ERROR (("Error loading PCI database file"));
                pci_ids_free();
                fclose(fp);
                return FALSE;
        }

        fclose(fp);
        return TRUE;
}

/*==========================================================================*/

/** Pointer to where the usb.ids file is loaded */
static char *usb_ids = NULL;

/** Length of data store at at usb_ids */
static unsigned int usb_ids_len;

/** Iterator position into usb_ids */
static unsigned int usb_ids_iter_pos;

/** Initialize the usb.ids line iterator to the beginning of the file */
static void
usb_ids_line_iter_init ()
{
        usb_ids_iter_pos = 0;
}

/** Maximum length of lines in usb.ids */
#define USB_IDS_MAX_LINE_LEN 512

/** Get the next line from usb.ids
 *
 *  @param  line_len            Pointer to where number of bytes in line will
 *                              be stored
 *  @return                     Pointer to the line; only valid until the
 *                              next invocation of this function
 */
static char *
usb_ids_line_iter_get_line (unsigned int *line_len)
{
        unsigned int i;
        static char line[USB_IDS_MAX_LINE_LEN];

        for (i = 0;
             usb_ids_iter_pos < usb_ids_len &&
             i < USB_IDS_MAX_LINE_LEN - 1 &&
             usb_ids[usb_ids_iter_pos] != '\n'; i++, usb_ids_iter_pos++) {
                line[i] = usb_ids[usb_ids_iter_pos];
        }

        line[i] = '\0';
        if (line_len != NULL)
                *line_len = i;

        usb_ids_iter_pos++;

        return line;
}

/** See if there are more lines to process in usb.ids
 *
 *  @return                     #TRUE iff there are more lines to process
 */
static dbus_bool_t
usb_ids_line_iter_has_more ()
{
        return usb_ids_iter_pos < usb_ids_len;
}

/** Find the names for a USB device.
 *
 *  The pointers returned are only valid until the next invocation of this
 *  function.
 *
 *  @param  vendor_id           USB vendor id or 0 if unknown
 *  @param  product_id          USB product id or 0 if unknown
 *  @param  vendor_name         Set to pointer of result or NULL
 *  @param  product_name        Set to pointer of result or NULL
 */
void
ids_find_usb (int vendor_id, int product_id,
              char **vendor_name, char **product_name)
{
        char *line;
        unsigned int i;
        unsigned int line_len;
        unsigned int num_tabs;
        char rep_vi[8];
        char rep_pi[8];
        static char store_vn[USB_IDS_MAX_LINE_LEN];
        static char store_pn[USB_IDS_MAX_LINE_LEN];
        dbus_bool_t vendor_matched = FALSE;

        snprintf (rep_vi, 8, "%04x", vendor_id);
        snprintf (rep_pi, 8, "%04x", product_id);

        *vendor_name = NULL;
        *product_name = NULL;

        for (usb_ids_line_iter_init (); usb_ids_line_iter_has_more ();) {
                line = usb_ids_line_iter_get_line (&line_len);

                /* skip lines with no content */
                if (line_len < 4)
                        continue;

                /* skip comments */
                if (line[0] == '#')
                        continue;

                /* count number of tabs */
                num_tabs = 0;
                for (i = 0; i < line_len; i++) {
                        if (line[i] != '\t')
                                break;
                        num_tabs++;
                }

                switch (num_tabs) {
                case 0:
                        /* vendor names */
                        vendor_matched = FALSE;

                        /* check vendor_id */
                        if (vendor_id != 0) {
                                if (memcmp (line, rep_vi, 4) == 0) {
                                        /* found it */
                                        vendor_matched = TRUE;

                                        for (i = 4; i < line_len; i++) {
                                                if (!isspace (line[i]))
                                                        break;
                                        }
                                        strncpy (store_vn, line + i,
                                                 USB_IDS_MAX_LINE_LEN);
                                        *vendor_name = store_vn;
                                }
                        }
                        break;

                case 1:
                        /* product names */
                        if (!vendor_matched)
                                continue;

                        /* check product_id */
                        if (product_id != 0) {
                                if (memcmp (line + 1, rep_pi, 4) == 0) {
                                        /* found it */
                                        for (i = 5; i < line_len; i++) {
                                                if (!isspace (line[i]))
                                                        break;
                                        }
                                        strncpy (store_pn, line + i,
                                                 USB_IDS_MAX_LINE_LEN);
                                        *product_name = store_pn;

                                        /* no need to continue the search */
                                        return;
                                }
                        }
                        break;

                default:
                        break;
                }

        }
}

/** Free resources used by to store the USB database
 *
 *  @param                      #FALSE if the USB database wasn't loaded
 */
static dbus_bool_t
usb_ids_free ()
{
        if (usb_ids != NULL) {
                free (usb_ids);
                usb_ids = NULL;
                return TRUE;
        }
        return FALSE;
}

/** Load the USB database used for mapping vendor, product, subsys_vendor
 *  and subsys_product numbers into names.
 *
 *  @param  path                Path of the usb.ids file, e.g.
 *                              /usr/share/hwdata/usb.ids
 *  @return                     #TRUE if the file was succesfully loaded
 */
static dbus_bool_t
usb_ids_load (const char *path)
{
        FILE *fp;
        unsigned int num_read;

        fp = fopen (path, "r");
        if (fp == NULL) {
                printf ("couldn't open USB database at %s,", path);
                return FALSE;
        }

        fseek (fp, 0, SEEK_END);
        usb_ids_len = ftell (fp);
        fseek (fp, 0, SEEK_SET);

        usb_ids = malloc (usb_ids_len);
        if (usb_ids == NULL) {
                printf
                    ("Couldn't allocate %d bytes for USB database file\n",
                     usb_ids_len);
                fclose(fp);
                return FALSE;
        }

        num_read = fread (usb_ids, sizeof (char), usb_ids_len, fp);
        if (usb_ids_len != num_read) {
                printf ("Error loading USB database file\n");
                usb_ids_free ();
                fclose(fp);
                return FALSE;
        }

        fclose(fp);
        return TRUE;
}


void
ids_init (void)
{
        /* Load /usr/share/hwdata/pci.ids */
        pci_ids_load (HWDATA_DIR "/pci.ids");

        /* Load /usr/share/hwdata/usb.ids */
        usb_ids_load (HWDATA_DIR "/usb.ids");
}


/* This, somewhat incomplete, list is from this sources:
 * http://www.plasma-online.de/english/identify/serial/pnp_id_pnp.html
 * http://www-pc.uni-regensburg.de/hardware/TECHNIK/PCI_PNP/pnpid.txt
 *
 * Keep this sorted!
 */
struct pnp_id {
        char *id;
        char *desc;
} static pnp_ids_list[] = {
        /* Crystal Semiconductor devices */
        {"CSC0000", "Crystal Semiconductor CS423x sound -- SB/WSS/OPL3 emulation"},
        {"CSC0001", "Crystal Semiconductor CS423x sound -- joystick"},
        {"CSC0003", "Crystal Semiconductor CS423x sound -- MPU401"},
        {"CSC0010", "Crystal Semiconductor CS423x sound -- control"},
        /* IBM devices */
        {"IBM0071", "IBM infrared communications device"},
        {"IBM3760", "IBM DSP"},
        {"IBM3780", "IBM pointing device"},
        /* FinePoint devices */
        {"FPI2004", "FinePoint Innovations Tablet"},
        /* Fujitsu (Siemens Computers) devices */
        {"FUJ02E5", "Wacom Serial Pen HID Tablet"},
        {"FUJ02E6", "Fujitsu Serial TouchScreen"},
        /* interrupt controllers */
        {"PNP0000", "AT Interrupt Controller"},
        {"PNP0001", "EISA Interrupt Controller"},
        {"PNP0002", "MCA Interrupt Controller"},
        {"PNP0003", "APIC"},
        {"PNP0004", "Cyrix SLiC MP interrupt controller"},
        /* timers */
        {"PNP0100", "AT Timer"},
        {"PNP0101", "EISA Timer"},
        {"PNP0102", "MCA Timer"},
        /* DMA controllers */
        {"PNP0200", "AT DMA Controller"},
        {"PNP0201", "EISA DMA Controller"},
        {"PNP0202", "MCA DMA Controller"},
        /* keyboards */
        {"PNP0300", "IBM PC/XT keyboard controller (83-key)"},
        {"PNP0301", "IBM PC/AT keyboard controller (86-key)"},
        {"PNP0302", "IBM PC/XT keyboard controller (84-key)"},
        {"PNP0303", "IBM Enhanced (101/102-key, PS/2 mouse support)"},
        {"PNP0304", "Olivetti Keyboard (83-key)"},
        {"PNP0305", "Olivetti Keyboard (102-key)"},
        {"PNP0306", "Olivetti Keyboard (86-key)"},
        {"PNP0307", "Microsoft Windows(R) Keyboard"},
        {"PNP0308", "General Input Device Emulation Interface (GIDEI) legacy"},
        {"PNP0309", "Olivetti Keyboard (A101/102 key)"},
        {"PNP030A", "AT&T 302 keyboard"},
        {"PNP030B", "Reserved by Microsoft"},
        {"PNP0320", "Japanese 101-key keyboard"},
        {"PNP0321", "Japanese AX keyboard"},
        {"PNP0322", "Japanese 106-key keyboard A01"},
        {"PNP0323", "Japanese 106-key keyboard 002/003"},
        {"PNP0324", "Japanese 106-key keyboard 001"},
        {"PNP0325", "Japanese Toshiba Desktop keyboard"},
        {"PNP0326", "Japanese Toshiba Laptop keyboard"},
        {"PNP0327", "Japanese Toshiba Notebook keyboard"},
        {"PNP0340", "Korean 84-key keyboard"},
        {"PNP0341", "Korean 86-key keyboard"},
        {"PNP0342", "Korean Enhanced keyboard"},
        {"PNP0343", "Korean Enhanced keyboard 101b"},
        {"PNP0343", "Korean Enhanced keyboard 101c"},
        {"PNP0344", "Korean Enhanced keyboard 103"},
        /* parallel ports */
        {"PNP0400", "Standard LPT printer port"},
        {"PNP0401", "ECP printer port"},
        /* serial ports */
        {"PNP0500", "Standard PC COM port"},
        {"PNP0501", "16550A-compatible COM port"},
        {"PNP0502", "Multiport serial device (non-intelligent 16550)"},
        {"PNP0510", "Generic IRDA-compatible device"},
        {"PNP0511", "Generic IRDA-compatible device"},
        /* IDE controller */
        {"PNP0600", "Generic ESDI/IDE/ATA compatible hard disk controller"},
        {"PNP0601", "Plus Hardcard II"},
        {"PNP0602", "Plus Hardcard IIXL/EZ"},
        {"PNP0603", "Generic IDE supporting Microsoft Device Bay Specification"},
        {"PNP0604", "PC standard floppy disk controller"},
        {"PNP0605", "HP Omnibook floppy disk controller"},
        {"PNP0680", "Bus Master E-IDE controller"},
        {"PNP0700", "PC standard floppy disk controller"},
        {"PNP0701", "Standard floppy controller supporting MS Device Bay Spec"},
        /* system devices */
        {"PNP0800", "AT-style speaker sound"},
        /* obsolete devices */
        {"PNP0802", "Microsoft Sound System compatible device (obsolete, use PNPB0xx instead)"},
        /* display adapters / graphic cards */
        {"PNP0900", "VGA Compatible"},
        {"PNP0901", "Video Seven VRAM/VRAM II/1024i"},
        {"PNP0902", "IBM 8514/A Compatible"},
        {"PNP0903", "Trident VGA"},
        {"PNP0904", "Cirrus Logic Laptop VGA"},
        {"PNP0905", "Cirrus Logic VGA"},
        {"PNP0906", "Tseng Labs ET4000"},
        {"PNP0907", "Western Digital VGA"},
        {"PNP0908", "Western Digital Laptop VGA"},
        {"PNP0909", "S3 Inc. 911/924"},
        {"PNP090A", "ATI Ultra Pro/Plus (Mach 32)"},
        {"PNP090B", "ATI Ultra (Mach 8)"},
        {"PNP090C", "IBM XGA Compatible"},
        {"PNP090D", "ATI VGA Wonder"},
        {"PNP090E", "Weitek P9000 Graphics Adapter"},
        {"PNP090F", "Oak Technology VGA"},
        {"PNP0910", "Compaq QVision"},
        {"PNP0911", "IBM XGA/2"},
        {"PNP0912", "Tseng Labs ET4000 W32/W32i/W32p"},
        {"PNP0913", "S3 Inc. 801/928/964"},
        {"PNP0914", "Cirrus Logic 5429/5434 (memory mapped)"},
        {"PNP0915", "Compaq Advanced VGA (AVGA)"},
        {"PNP0916", "ATI Ultra Pro Turbo (Mach64)"},
        {"PNP0917", "Reserved by Microsoft"},
        {"PNP0918", "Matrox MGA"},
        {"PNP0919", "Compaq QVision 2000"},
        {"PNP091A", "Tseng Labs W128"},
        {"PNP0930", "Chips & Technologies Super VGA"},
        {"PNP0931", "Chips & Technologies Accelerator"},
        {"PNP0940", "NCR 77c22e Super VGA"},
        {"PNP0941", "NCR 77c32blt"},
        {"PNP09FF", "Plug and Play Monitors (VESA DDC)"},
        /* peripheral buses */
        {"PNP0A00", "ISA Bus"},
        {"PNP0A01", "EISA Bus"},
        {"PNP0A02", "MCA Bus"},
        {"PNP0A03", "PCI Bus"},
        {"PNP0A04", "VESA/VL Bus"},
        {"PNP0A05", "Generic ACPI Bus"},
        {"PNP0A06", "Generic ACPI Extended-IO Bus (EIO bus)"},
        /* system devices */
        {"PNP0B00", "AT Real-Time Clock"},
        {"PNP0C00", "Plug and Play BIOS (only created by the root enumerator)"},
        {"PNP0C01", "System Board"},
        {"PNP0C02", "General ID for reserving resources required by PnP motherboard registers. (Not device specific.)"},
        {"PNP0C03", "Plug and Play BIOS Event Notification Interrupt"},
        {"PNP0C04", "Math Coprocessor"},
        {"PNP0C05", "APM BIOS (Version independent)"},
        {"PNP0C06", "Reserved for identification of early Plug and Play BIOS implementation"},
        {"PNP0C07", "Reserved for identification of early Plug and Play BIOS implementation"},
        {"PNP0C08", "ACPI system board hardware"},
        {"PNP0C09", "ACPI Embedded Controller"},
        {"PNP0C0A", "ACPI Control Method Battery"},
        {"PNP0C0B", "ACPI Fan"},
        {"PNP0C0C", "ACPI power button device"},
        {"PNP0C0D", "ACPI lid device"},
        {"PNP0C0E", "ACPI sleep button device"},
        {"PNP0C0F", "PCI interrupt link device"},
        {"PNP0C10", "ACPI system indicator device"},
        {"PNP0C11", "ACPI thermal zone"},
        {"PNP0C12", "Device Bay Controller"},
        {"PNP0C13", "Plug and Play BIOS (used when ACPI mode cannot be used)"},
        {"PNP0CF0", "Compaq LTE Lite Support"},
        {"PNP0CF1", "Compaq LTE Elite Support"},
        /* PCMCIA controllers */
        {"PNP0E00", "Intel 82365-Compatible PCMCIA Controller"},
        {"PNP0E01", "Cirrus Logic CL-PD6720 PCMCIA Controller"},
        {"PNP0E02", "VLSI VL82C146 PCMCIA Controller"},
        {"PNP0E03", "Intel 82365-compatible CardBus controller"},
        /* mice */
        {"PNP0F00", "Microsoft Bus Mouse"},
        {"PNP0F01", "Microsoft Serial Mouse"},
        {"PNP0F02", "Microsoft InPort Mouse"},
        {"PNP0F03", "Microsoft PS/2-style Mouse"},
        {"PNP0F04", "Mouse Systems Mouse"},
        {"PNP0F05", "Mouse Systems 3-Button Mouse (COM2)"},
        {"PNP0F06", "Genius Mouse (COM1)"},
        {"PNP0F07", "Genius Mouse (COM2)"},
        {"PNP0F08", "Logitech Serial Mouse"},
        {"PNP0F09", "Microsoft BallPoint Serial Mouse"},
        {"PNP0F0A", "Microsoft Plug and Play Mouse"},
        {"PNP0F0B", "Microsoft Plug and Play BallPoint Mouse"},
        {"PNP0F0C", "Microsoft-compatible Serial Mouse"},
        {"PNP0F0D", "Microsoft-compatible InPort-compatible Mouse"},
        {"PNP0F0E", "Microsoft-compatible PS/2-style Mouse"},
        {"PNP0F0F", "Microsoft-compatible Serial BallPoint-compatible Mouse"},
        {"PNP0F10", "Texas Instruments QuickPort Mouse"},
        {"PNP0F11", "Microsoft-compatible Bus Mouse"},
        {"PNP0F12", "Logitech PS/2-style Mouse"},
        {"PNP0F13", "PS/2 Port for PS/2-style Mice"},
        {"PNP0F14", "Microsoft Kids Mouse"},
        {"PNP0F15", "Logitech bus mouse"},
        {"PNP0F16", "Logitech SWIFT device"},
        {"PNP0F17", "Logitech-compatible serial mouse"},
        {"PNP0F18", "Logitech-compatible bus mouse"},
        {"PNP0F19", "Logitech-compatible PS/2-style Mouse"},
        {"PNP0F1A", "Logitech-compatible SWIFT Device"},
        {"PNP0F1B", "HP Omnibook Mouse"},
        {"PNP0F1C", "Compaq LTE Trackball PS/2-style Mouse"},
        {"PNP0F1D", "Compaq LTE Trackball Serial Mouse"},
        {"PNP0F1E", "Microsoft Kids Trackball Mouse"},
        {"PNP0F1F", "Reserved by Microsoft Input Device Group"},
        {"PNP0F20", "Reserved by Microsoft Input Device Group"},
        {"PNP0F21", "Reserved by Microsoft Input Device Group"},
        {"PNP0F22", "Reserved by Microsoft Input Device Group"},
        {"PNP0F23", "Reserved by Microsoft Input Device Group"},
        {"PNP0FFF", "Reserved by Microsoft Systems"},
        {"PNP0XXX", "Unknown System Device"},
        /* network cards */
        {"PNP8000", "Network Adapter"},
        {"PNP8001", "Novell/Anthem NE3200"},
        {"PNP8004", "Compaq NE3200"},
        {"PNP8006", "Intel EtherExpress/32"},
        {"PNP8008", "HP EtherTwist EISA LAN Adapter/32 (HP27248A)"},
        {"PNP8065", "Ungermann-Bass NIUps or NIUps/EOTP"},
        {"PNP8072", "DEC (DE211) EtherWorks MC/TP"},
        {"PNP8073", "DEC (DE212) EtherWorks MC/TP_BNC"},
        {"PNP8074", "HP MC LAN Adapter/16 TP (PC27246)"},
        {"PNP8078", "DCA 10 Mb MCA"},
        {"PNP807F", "Racal NI9210"},
        {"PNP8081", "Pure Data Ethernet"},
        {"PNP8096", "Thomas-Conrad TC4046"},
        {"PNP80C9", "IBM Token Ring"},
        {"PNP80CA", "IBM Token Ring II"},
        {"PNP80CB", "IBM Token Ring II/Short"},
        {"PNP80CC", "IBM Token Ring 4/16Mbs"},
        {"PNP80D3", "Novell/Anthem NE1000"},
        {"PNP80D4", "Novell/Anthem NE2000"},
        {"PNP80D5", "NE1000 Compatible"},
        {"PNP80D6", "NE2000 Compatible"},
        {"PNP80D7", "Novell/Anthem NE1500T"},
        {"PNP80D8", "Novell/Anthem NE2100"},
        {"PNP80D9", "NE2000 Plus"},
        {"PNP80DD", "SMC ARCNETPC"},
        {"PNP80DE", "SMC ARCNET PC100, PC200"},
        {"PNP80DF", "SMC ARCNET PC110, PC210, PC250"},
        {"PNP80E0", "SMC ARCNET PC130/E"},
        {"PNP80E1", "SMC ARCNET PC120, PC220, PC260"},
        {"PNP80E2", "SMC ARCNET PC270/E"},
        {"PNP80E5", "SMC ARCNET PC600W, PC650W"},
        {"PNP80E7", "DEC DEPCA"},
        {"PNP80E8", "DEC (DE100) EtherWorks LC"},
        {"PNP80E9", "DEC (DE200) EtherWorks Turbo"},
        {"PNP80EA", "DEC (DE101) EtherWorks LC/TP"},
        {"PNP80EB", "DEC (DE201) EtherWorks Turbo/TP"},
        {"PNP80EC", "DEC (DE202) EtherWorks Turbo/TP_BNC"},
        {"PNP80ED", "DEC (DE102) EtherWorks LC/TP_BNC"},
        {"PNP80EE", "DEC EE101 (Built-In)"},
        {"PNP80EF", "DEC PC 433 WS (Built-In)"},
        {"PNP80F1", "3Com EtherLink Plus"},
        {"PNP80F3", "3Com EtherLink II or IITP (8 or 16-bit)"},
        {"PNP80F4", "3Com TokenLink"},
        {"PNP80F6", "3Com EtherLink 16"},
        {"PNP80F7", "3Com EtherLink III"},
        {"PNP80F8", "3Com Generic Etherlink Plug and Play Device"},
        {"PNP80FB", "Thomas Conrad TC6045"},
        {"PNP80FC", "Thomas Conrad TC6042"},
        {"PNP80FD", "Thomas Conrad TC6142"},
        {"PNP80FE", "Thomas Conrad TC6145"},
        {"PNP80FF", "Thomas Conrad TC6242"},
        {"PNP8100", "Thomas Conrad TC6245"},
        {"PNP8101", "Thomas-Conrad TC4045"},
        {"PNP8104", "Thomas-Conrad TC4035"},
        {"PNP8105", "DCA 10 MB"},
        {"PNP8106", "DCA 10 MB Fiber Optic"},
        {"PNP8107", "DCA 10 MB Twisted Pair"},
        {"PNP8113", "Racal NI6510"},
        {"PNP8114", "Racal NI5210/8 or NI5210/16"},
        {"PNP8119", "Ungermann-Bass pcNIU"},
        {"PNP811A", "Ungermann-Bass pcNIU/ex 128K"},
        {"PNP811B", "Ungermann-Bass pcNIU/ex 512K"},
        {"PNP811C", "Ungermann-Bass NIUpc"},
        {"PNP811D", "Ungermann-Bass NIUpc/3270"},
        {"PNP8120", "Ungermann-Bass NIUpc/EOTP"},
        {"PNP8123", "SMC StarCard PLUS (WD/8003S)"},
        {"PNP8124", "SMC StarCard PLUS With On Board Hub (WD/8003SH)"},
        {"PNP8125", "SMC EtherCard PLUS (WD/8003E)"},
        {"PNP8126", "SMC EtherCard PLUS With Boot ROM Socket (WD/8003EBT)"},
        {"PNP8127", "SMC EtherCard PLUS With Boot ROM Socket (WD/8003EB)"},
        {"PNP8128", "SMC EtherCard PLUS TP (WD/8003WT)"},
        {"PNP812A", "SMC EtherCard PLUS 16 With Boot ROM Socket (WD/8013EBT)"},
        {"PNP812D", "Intel EtherExpress 16 or 16TP"},
        {"PNP812F", "Intel TokenExpress 16/4"},
        {"PNP8130", "Intel TokenExpress MCA 16/4"},
        {"PNP8132", "Intel EtherExpress 16 (MCA)"},
        {"PNP8133", "Compaq Ethernet 16E"},
        {"PNP8137", "Artisoft AE-1"},
        {"PNP8138", "Artisoft AE-2 or AE-3"},
        {"PNP8141", "Amplicard AC 210/XT"},
        {"PNP8142", "Amplicard AC 210/AT"},
        {"PNP814B", "Everex SpeedLink /PC16 (EV2027)"},
        {"PNP8155", "HP PC LAN Adapter/8 TP (HP27245)"},
        {"PNP8156", "HP PC LAN Adapter/16 TP (HP27247A)"},
        {"PNP8157", "HP PC LAN Adapter/8 TL (HP27250)"},
        {"PNP8158", "HP PC LAN Adapter/16 TP Plus (HP27247B)"},
        {"PNP8159", "HP PC LAN Adapter/16 TL Plus (HP27252)"},
        {"PNP815F", "National Semiconductor Ethernode *16AT"},
        {"PNP8160", "National Semiconductor AT/LANTIC EtherNODE 16-AT3"},
        {"PNP8169", "NCR StarCard"},
        {"PNP816A", "NCR Token-Ring 4 Mbs ISA"},
        {"PNP816B", "NCR WaveLAN AT"},
        {"PNP816C", "NCR WaveLan MC"},
        {"PNP816D", "NCR Token-Ring 16/4 Mbs ISA"},
        {"PNP8191", "Olicom 16/4 Token-Ring Adapter"},
        {"PNP81A5", "Research Machines Ethernet"},
        {"PNP81B9", "ToshibaLAN (internal)"},
        {"PNP81C3", "SMC EtherCard PLUS Elite (WD/8003EP)"},
        {"PNP81C4", "SMC EtherCard PLUS 10T (WD/8003W)"},
        {"PNP81C5", "SMC EtherCard PLUS Elite 16 (WD/8013EP)"},
        {"PNP81C6", "SMC EtherCard PLUS Elite 16T (WD/8013W)"},
        {"PNP81C7", "SMC EtherCard PLUS Elite 16 Combo (WD/8013EW or 8013EWC)"},
        {"PNP81C8", "SMC EtherElite Ultra 16"},
        {"PNP81C9", "SMC TigerCard (8216L, 8216LC, 8216LT)"},
        {"PNP81CA", "SMC EtherEZ (8416)"},
        {"PNP81D7", "Madge Smart 16/4 PC Ringnode"},
        {"PNP81D8", "Madge Smart 16/4 Ringnode ISA"},
        {"PNP81E4", "Pure Data PDI9025-32 (Token Ring)"},
        {"PNP81E6", "Pure Data PDI508+ (ArcNet)"},
        {"PNP81E7", "Pure Data PDI516+ (ArcNet)"},
        {"PNP81EB", "Proteon Token Ring (P1390)"},
        {"PNP81EC", "Proteon Token Ring (P1392)"},
        {"PNP81ED", "Proteon Token Ring ISA (P1340)"},
        {"PNP81EE", "Proteon Token Ring ISA (P1342)"},
        {"PNP81EF", "Proteon Token Ring ISA (P1346)"},
        {"PNP81F0", "Proteon Token Ring ISA (P1347)"},
        {"PNP81FF", "Cabletron E2000 Series DNI"},
        {"PNP8200", "Cabletron E2100 Series DNI"},
        {"PNP8201", "Cabletron T2015 4/16 Mbit/s DNI"},
        {"PNP8209", "Zenith Data Systems Z-Note"},
        {"PNP820A", "Zenith Data Systems NE2000-Compatible"},
        {"PNP8213", "Xircom Pocket Ethernet II"},
        {"PNP8214", "Xircom Pocket Ethernet I"},
        {"PNP8215", "Xircom Pocket Ethernet III Adapter"},
        {"PNP821D", "RadiSys EXM-10"},
        {"PNP8227", "SMC 3000 Series"},
        {"PNP8228", "SMC 91C2 controller"},
        {"PNP8231", "AMD AM2100/AM1500T"},
        {"PNP824F", "RCE 10Base-T (16 bit)"},
        {"PNP8250", "RCE 10Base-T (8 bit)"},
        {"PNP8263", "Tulip NCC-16"},
        {"PNP8277", "Exos 105"},
        {"PNP828A", "Intel '595 based Ethernet"},
        {"PNP828B", "TI2000-style Token Ring"},
        {"PNP828C", "AMD PCNet Family cards"},
        {"PNP828D", "AMD PCNet32 (VL version)"},
        {"PNP8294", "IrDA Infrared NDIS driver (Microsoft-supplied)"},
        {"PNP82BD", "IBM PCMCIA-NIC"},
        {"PNP82C0", "Eagle Technology NE200T"},
        {"PNP82C2", "Xircom CE10"},
        {"PNP82C3", "Xircom CEM2"},
        {"PNP82C4", "Xircom CE2"},
        {"PNP8321", "DEC Ethernet (All Types)"},
        {"PNP8323", "SMC EtherCard (All Types except 8013/A)"},
        {"PNP8324", "ARCNET Compatible"},
        {"PNP8325", "SMC TokenCard PLUS (8115T)"},
        {"PNP8326", "Thomas Conrad (All Arcnet Types)"},
        {"PNP8327", "IBM Token Ring (All Types)"},
        {"PNP8328", "Ungermann-Bass NIU"},
        {"PNP8329", "Proteon ProNET-4/16 ISA Token Ring (P1392+,P1392,1390)"},
        {"PNP8385", "Remote Network Access [RNA] Driver"},
        {"PNP8387", "Remote Network Access [RNA] PPP Driver"},
        {"PNP8388", "Reserved for Microsoft Networking components"},
        {"PNP8389", "Peer IrLAN infrared driver (Microsoft-supplied)"},
        {"PNP8390", "Generic network adapter"},
        {"PNP8XXX", "Unknown Network Adapter"},
        /* modems */
        {"PNP9000", "Modem"},
        /* CD controller */
        {"PNPA000", "Adaptec 154x compatible SCSI controller"},
        {"PNPA001", "Adaptec 174x compatible SCSI controller"},
        {"PNPA002", "Future Domain 16-700 compatible controller"},
        {"PNPA003", "Mitsumi CD-ROM adapter (Panasonic spec., used on SBPro/SB16)"},
        {"PNPA01B", "Trantor 128 SCSI Controller"},
        {"PNPA01D", "Trantor T160 SCSI Controller"},
        {"PNPA01E", "Trantor T338 Parallel SCSI controller"},
        {"PNPA01F", "Trantor T348 Parallel SCSI controller"},
        {"PNPA020", "Trantor Media Vision SCSI controller"},
        {"PNPA022", "Always IN-2000 SCSI controller"},
        {"PNPA02B", "Sony proprietary CD-ROM controller"},
        {"PNPA02D", "Trantor T13b 8-bit SCSI controller"},
        {"PNPA02F", "Trantor T358 Parallel SCSI controller"},
        {"PNPA030", "Mitsumi LU-005 Single Speed CD-ROM controller + drive"},
        {"PNPA031", "Mitsumi FX-001 Single Speed CD-ROM controller + drive"},
        {"PNPA032", "Mitsumi FX-001 Double Speed CD-ROM controller + drive"},
        {"PNPAXXX", "Unknown SCSI, Proprietary CD Adapter"},
        /* multimedia devices */
        {"PNPB000", "Creative Labs Sound Blaster 1.5 (or compatible sound device)"},
        {"PNPB001", "Creative Labs Sound Blaster 2.0 (or compatible sound device)"},
        {"PNPB002", "Creative Labs Sound Blaster Pro (or compatible sound device)"},
        {"PNPB003", "Creative Labs Sound Blaster 16 (or compatible sound device)"},
        {"PNPB004", "MediaVision Thunderboard (or compatible sound device)"},
        {"PNPB005", "Adlib-compatible FM synthesizer device"},
        {"PNPB006", "MPU401 compatible"},
        {"PNPB007", "Microsoft Windows Sound System-compatible sound device"},
        {"PNPB008", "Compaq Business Audio"},
        {"PNPB009", "Plug and Play Microsoft Windows Sound System Device"},
        {"PNPB00A", "MediaVision Pro Audio Spectrum (Trantor SCSI enabled, Thunder Chip Disabled)"},
        {"PNPB00B", "MediaVision Pro Audio 3D"},
        {"PNPB00C", "MusicQuest MQX-32M"},
        {"PNPB00D", "MediaVision Pro Audio Spectrum Basic (No Trantor SCSI, Thunder Chip Enabled)"},
        {"PNPB00E", "MediaVision Pro Audio Spectrum (Trantor SCSI enabled, Thunder Chip Disabled)"},
        {"PNPB00F", "MediaVision Jazz-16 chipset (OEM Versions)"},
        {"PNPB010", "Orchid Videola - Auravision VxP500 chipset"},
        {"PNPB018", "MediaVision Pro Audio Spectrum 8-bit"},
        {"PNPB019", "MediaVision Pro Audio Spectrum Basic (No Trantor SCSI, Thunder Chip Enabled)"},
        {"PNPB020", "Yamaha OPL3-compatible FM synthesizer device"},
        {"PNPB02F", "Joystick/Game port"},
        {"PNPB077", "OAK Mozart Sound System"},
        {"PNPB078", "OAK Mozart Sound System MPU-401"},
        {"PNPBXXX", "Unknown Multimedia Device"},
        /* modems */
        {"PNPC000", "Compaq 14400 Modem (TBD)"},
        {"PNPC001", "Compaq 2400/9600 Modem (TBD)"},
        {"PNPCXXX", "Unknown Modem"},
        /* some other network cards */
        {"PNPD300", "SK-NET TR4/16+ Token-Ring"},
        {"PNPE000", "SK-NET G16, G16/TP Ethernet"},
        {"PNPF000", "SK-NET FDDI-FI FDDI LAN"},
        /* Toshiba devices */
        {"TOS6200", "Toshiba Notebook Extra HCI driver"},
        {"TOS6202", "Toshiba Notebook Extra HCI driver"},
        {"TOS6207", "Toshiba Notebook Extra HCI driver"},
        {"TOS7400", "Toshiba AcuPoint"},
        /* Wacom devices */
        {"WACf004", "Wacom Serial Tablet PC Pen Tablet/Digitizer"},
        {"WACf005", "Wacom Serial Tablet PC Pen Tablet/Digitizer"},
        {"WACf006", "Wacom Serial Tablet PC Pen Tablet/Digitizer"}
};

static int
ids_comp_pnp(const void *id1, const void *id2) {
        struct pnp_id *pnp_id1 = (struct pnp_id *) id1;
        struct pnp_id *pnp_id2 = (struct pnp_id *) id2;
        return strcasecmp(pnp_id1->id, pnp_id2->id);
}

void
ids_find_pnp (const char *pnp_id, char **pnp_description)
{
        static gboolean sorted = FALSE;
        struct pnp_id search, *res;

        if (!sorted) {
                /* sort the list, to be sure that all is in correc order */
                qsort(pnp_ids_list, sizeof(pnp_ids_list)/sizeof(pnp_ids_list[0]),
                      sizeof(struct pnp_id), ids_comp_pnp);
                sorted = TRUE;
        }

        search.id = (char *) pnp_id;
        res = bsearch(&search, pnp_ids_list, sizeof(pnp_ids_list)/sizeof(pnp_ids_list[0]),
                      sizeof(struct pnp_id), ids_comp_pnp);

        if (res != NULL)
                *pnp_description = res->desc;
        else
                *pnp_description = NULL;
        return;
}