/* ----------------------------------------------------------------------- * * * Copyright 2006-2007 Erwan Velu - 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 the rights to use, * copy, modify, merge, publish, distribute, sublicense, 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 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 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT * HOLDERS 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. * * ----------------------------------------------------------------------- */ /* * pci.c * * A module to extract pci informations */ #include <inttypes.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <console.h> #include <sys/pci.h> #include <com32.h> #include <stdbool.h> #include <ctype.h> #include <syslinux/zio.h> #include <dprintf.h> #define MAX_LINE 512 /* removing any \n found in a string */ static void remove_eol(char *string) { int j = strlen(string); int i = 0; for (i = 0; i < j; i++) if (string[i] == '\n') string[i] = 0; } /* converting a hexa string into its numerical value */ static int hex_to_int(char *hexa) { return strtoul(hexa, NULL, 16); } /* Try to match any pci device to the appropriate kernel module */ /* it uses the modules.pcimap from the boot device */ int get_module_name_from_pcimap(struct pci_domain *domain, char *modules_pcimap_path) { char line[MAX_LINE]; char module_name[21]; // the module name field is 21 char long char delims[]=" "; // colums are separated by spaces char vendor_id[16]; char product_id[16]; char sub_vendor_id[16]; char sub_product_id[16]; FILE *f; struct pci_device *dev=NULL; /* Intializing the linux_kernel_module for each pci device to "unknown" */ /* adding a dev_info member if needed */ for_each_pci_func(dev, domain) { /* initialize the dev_info structure if it doesn't exist yet. */ if (! dev->dev_info) { dev->dev_info = zalloc(sizeof *dev->dev_info); if (!dev->dev_info) return -1; } for (int i=0;i<MAX_KERNEL_MODULES_PER_PCI_DEVICE;i++) { if (strlen(dev->dev_info->linux_kernel_module[i])==0) strlcpy(dev->dev_info->linux_kernel_module[i], "unknown",7); } } /* Opening the modules.pcimap (of a linux kernel) from the boot device */ f=zfopen(modules_pcimap_path, "r"); if (!f) return -ENOMODULESPCIMAP; strcpy(vendor_id,"0000"); strcpy(product_id,"0000"); strcpy(sub_product_id,"0000"); strcpy(sub_vendor_id,"0000"); /* for each line we found in the modules.pcimap */ while ( fgets(line, sizeof line, f) ) { /* skipping unecessary lines */ if ((line[0] == '#') || (line[0] == ' ') || (line[0] == 10)) continue; char *result = NULL; int field=0; /* looking for the next field */ result = strtok(line, delims); while( result != NULL ) { /* if the column is larger than 1 char */ /* multiple spaces generates some empty fields */ if (strlen(result)>1) { switch (field) { /* About case 0, the kernel module name is featuring '_' or '-' * in the module name whereas modules.alias is only using '_'. * To avoid kernel modules duplication, let's rename all '-' in '_' * to match what modules.alias provides */ case 0:chrreplace(result,'-','_');strcpy(module_name,result); break; case 1:strcpy(vendor_id,result); break; case 2:strcpy(product_id,result); break; case 3:strcpy(sub_vendor_id,result); break; case 4:strcpy(sub_product_id,result); break; } field++; } /* Searching the next field */ result = strtok( NULL, delims ); } int int_vendor_id=hex_to_int(vendor_id); int int_sub_vendor_id=hex_to_int(sub_vendor_id); int int_product_id=hex_to_int(product_id); int int_sub_product_id=hex_to_int(sub_product_id); /* if a pci_device matches an entry, fill the linux_kernel_module with the appropriate kernel module */ for_each_pci_func(dev, domain) { if (int_vendor_id == dev->vendor && int_product_id == dev->product && (int_sub_product_id & dev->sub_product) == dev->sub_product && (int_sub_vendor_id & dev->sub_vendor) == dev->sub_vendor) { bool found=false; /* Scan all known kernel modules for this pci device */ for (int i=0; i<dev->dev_info->linux_kernel_module_count; i++) { /* Try to detect if we already knew the same kernel module*/ if (strstr(dev->dev_info->linux_kernel_module[i], module_name)) { found=true; break; } } /* If we don't have this kernel module, let's add it */ if (!found) { strcpy(dev->dev_info->linux_kernel_module[dev->dev_info->linux_kernel_module_count], module_name); dev->dev_info->linux_kernel_module_count++; } } } } fclose(f); return 0; } /* Try to match any pci device to the appropriate class name */ /* it uses the pci.ids from the boot device */ int get_class_name_from_pci_ids(struct pci_domain *domain, char *pciids_path) { char line[MAX_LINE]; char class_name[PCI_CLASS_NAME_SIZE]; char sub_class_name[PCI_CLASS_NAME_SIZE]; char class_id_str[5]; char sub_class_id_str[5]; FILE *f; struct pci_device *dev; bool class_mode = false; /* Intializing the vendor/product name for each pci device to "unknown" */ /* adding a dev_info member if needed */ for_each_pci_func(dev, domain) { /* initialize the dev_info structure if it doesn't exist yet. */ if (!dev->dev_info) { dev->dev_info = zalloc(sizeof *dev->dev_info); if (!dev->dev_info) return -1; } strlcpy(dev->dev_info->class_name, "unknown", 7); } /* Opening the pci.ids from the boot device */ f = zfopen(pciids_path, "r"); if (!f) return -ENOPCIIDS; /* for each line we found in the pci.ids */ while (fgets(line, sizeof line, f)) { /* Skipping uncessary lines */ if ((line[0] == '#') || (line[0] == ' ') || (line[0] == 10)) continue; /* Until we found a line starting with a 'C', we are not parsing classes */ if (line[0] == 'C') class_mode = true; if (class_mode == false) continue; strlcpy(class_name, "unknown", 7); /* If the line doesn't start with a tab, it means that's a class name */ if (line[0] != '\t') { /* ignore the two first char and then copy 2 chars (class id) */ strlcpy(class_id_str, &line[2], 2); class_id_str[2] = 0; /* the class name is the next field */ strlcpy(class_name, skipspace(strstr(line, " ")), PCI_CLASS_NAME_SIZE - 1); remove_eol(class_name); int int_class_id_str = hex_to_int(class_id_str); /* assign the class_name to any matching pci device */ for_each_pci_func(dev, domain) { if (int_class_id_str == dev->class[2]) { strlcpy(dev->dev_info->class_name, class_name, PCI_CLASS_NAME_SIZE - 1); /* This value is usually the main category */ strlcpy(dev->dev_info->category_name, class_name + 4, PCI_CLASS_NAME_SIZE - 1); } } /* if we have a tab + a char, it means this is a sub class name */ } else if ((line[0] == '\t') && (line[1] != '\t')) { /* the sub class name the second field */ strlcpy(sub_class_name, skipspace(strstr(line, " ")), PCI_CLASS_NAME_SIZE - 1); remove_eol(sub_class_name); /* the sub class id is first field */ strlcpy(sub_class_id_str, &line[1], 2); sub_class_id_str[2] = 0; int int_class_id_str = hex_to_int(class_id_str); int int_sub_class_id_str = hex_to_int(sub_class_id_str); /* assign the product_name to any matching pci device */ for_each_pci_func(dev, domain) { if (int_class_id_str == dev->class[2] && int_sub_class_id_str == dev->class[1]) strlcpy(dev->dev_info->class_name, sub_class_name, PCI_CLASS_NAME_SIZE - 1); } } } fclose(f); return 0; } /* Try to match any pci device to the appropriate vendor and product name */ /* it uses the pci.ids from the boot device */ int get_name_from_pci_ids(struct pci_domain *domain, char *pciids_path) { char line[MAX_LINE]; char vendor[PCI_VENDOR_NAME_SIZE]; char vendor_id[5]; char product[PCI_PRODUCT_NAME_SIZE]; char product_id[5]; char sub_product_id[5]; char sub_vendor_id[5]; FILE *f; struct pci_device *dev; bool skip_to_next_vendor = false; uint16_t int_vendor_id; uint16_t int_product_id; uint16_t int_sub_product_id; uint16_t int_sub_vendor_id; /* Intializing the vendor/product name for each pci device to "unknown" */ /* adding a dev_info member if needed */ for_each_pci_func(dev, domain) { /* initialize the dev_info structure if it doesn't exist yet. */ if (!dev->dev_info) { dev->dev_info = zalloc(sizeof *dev->dev_info); if (!dev->dev_info) return -1; } strlcpy(dev->dev_info->vendor_name, "unknown", 7); strlcpy(dev->dev_info->product_name, "unknown", 7); } /* Opening the pci.ids from the boot device */ f = zfopen(pciids_path, "r"); if (!f) return -ENOPCIIDS; strlcpy(vendor_id, "0000", 4); strlcpy(product_id, "0000", 4); strlcpy(sub_product_id, "0000", 4); strlcpy(sub_vendor_id, "0000", 4); /* for each line we found in the pci.ids */ while (fgets(line, sizeof line, f)) { /* Skipping uncessary lines */ if ((line[0] == '#') || (line[0] == ' ') || (line[0] == 'C') || (line[0] == 10)) continue; /* If the line doesn't start with a tab, it means that's a vendor id */ if (line[0] != '\t') { /* the 4 first chars are the vendor_id */ strlcpy(vendor_id, line, 4); /* the vendor name is the next field */ vendor_id[4] = 0; strlcpy(vendor, skipspace(strstr(line, " ")), PCI_VENDOR_NAME_SIZE - 1); remove_eol(vendor); /* init product_id, sub_product and sub_vendor */ strlcpy(product_id, "0000", 4); strlcpy(sub_product_id, "0000", 4); strlcpy(sub_vendor_id, "0000", 4); /* Unless we found a matching device, we have to skip to the next vendor */ skip_to_next_vendor = true; int_vendor_id = hex_to_int(vendor_id); /* Iterate in all pci devices to find a matching vendor */ for_each_pci_func(dev, domain) { /* if one device that match this vendor */ if (int_vendor_id == dev->vendor) { /* copy the vendor name for this device */ strlcpy(dev->dev_info->vendor_name, vendor, PCI_VENDOR_NAME_SIZE - 1); /* Some pci devices match this vendor, so we have to found them */ skip_to_next_vendor = false; /* Let's loop on the other devices as some may have the same vendor */ } } /* if we have a tab + a char, it means this is a product id * but we only look at it if we own some pci devices of the current vendor*/ } else if ((line[0] == '\t') && (line[1] != '\t') && (skip_to_next_vendor == false)) { /* the product name the second field */ strlcpy(product, skipspace(strstr(line, " ")), PCI_PRODUCT_NAME_SIZE - 1); remove_eol(product); /* the product id is first field */ strlcpy(product_id, &line[1], 4); product_id[4] = 0; /* init sub_product and sub_vendor */ strlcpy(sub_product_id, "0000", 4); strlcpy(sub_vendor_id, "0000", 4); int_vendor_id = hex_to_int(vendor_id); int_product_id = hex_to_int(product_id); /* assign the product_name to any matching pci device */ for_each_pci_func(dev, domain) { if (int_vendor_id == dev->vendor && int_product_id == dev->product) { strlcpy(dev->dev_info->vendor_name, vendor, PCI_VENDOR_NAME_SIZE - 1); strlcpy(dev->dev_info->product_name, product, PCI_PRODUCT_NAME_SIZE - 1); } } /* if we have two tabs, it means this is a sub product * but we only look at it if we own some pci devices of the current vendor*/ } else if ((line[0] == '\t') && (line[1] == '\t') && (skip_to_next_vendor == false)) { /* the product name is last field */ strlcpy(product, skipspace(strstr(line, " ")), PCI_PRODUCT_NAME_SIZE - 1); strlcpy(product, skipspace(strstr(product, " ")), PCI_PRODUCT_NAME_SIZE - 1); remove_eol(product); /* the sub_vendor id is first field */ strlcpy(sub_vendor_id, &line[2], 4); sub_vendor_id[4] = 0; /* the sub_vendor id is second field */ strlcpy(sub_product_id, &line[7], 4); sub_product_id[4] = 0; int_vendor_id = hex_to_int(vendor_id); int_sub_vendor_id = hex_to_int(sub_vendor_id); int_product_id = hex_to_int(product_id); int_sub_product_id = hex_to_int(sub_product_id); /* assign the product_name to any matching pci device */ for_each_pci_func(dev, domain) { if (int_vendor_id == dev->vendor && int_product_id == dev->product && int_sub_product_id == dev->sub_product && int_sub_vendor_id == dev->sub_vendor) { strlcpy(dev->dev_info->vendor_name, vendor, PCI_VENDOR_NAME_SIZE - 1); strlcpy(dev->dev_info->product_name, product, PCI_PRODUCT_NAME_SIZE - 1); } } } } fclose(f); return 0; } /* searching if any pcidevice match our query */ struct match *find_pci_device(const struct pci_domain *domain, struct match *list) { uint32_t did, sid; struct match *m; const struct pci_device *dev; /* for all matches we have to search */ for (m = list; m; m = m->next) { /* for each pci device we know */ for_each_pci_func(dev, domain) { /* sid & did are the easiest way to compare devices */ /* they are made of vendor/product subvendor/subproduct ids */ sid = dev->svid_sdid; did = dev->vid_did; /* if the current device match */ if (((did ^ m->did) & m->did_mask) == 0 && ((sid ^ m->sid) & m->sid_mask) == 0 && dev->revision >= m->rid_min && dev->revision <= m->rid_max) { dprintf ("PCI Match: Vendor=%04x Product=%04x Sub_vendor=%04x Sub_Product=%04x Release=%02x\n", dev->vendor, dev->product, dev->sub_vendor, dev->sub_product, dev->revision); /* returning the matched pci device */ return m; } } } return NULL; } /* scanning the pci bus to find pci devices */ struct pci_domain *pci_scan(void) { struct pci_domain *domain = NULL; struct pci_bus *bus = NULL; struct pci_slot *slot = NULL; struct pci_device *func = NULL; unsigned int nbus, ndev, nfunc, maxfunc; uint32_t did, sid, rcid; uint8_t hdrtype; pciaddr_t a; int cfgtype; cfgtype = pci_set_config_type(PCI_CFG_AUTO); dprintf("PCI configuration type %d\n", cfgtype); if (cfgtype == PCI_CFG_NONE) return NULL; dprintf("Scanning PCI Buses\n"); for (nbus = 0; nbus < MAX_PCI_BUSES; nbus++) { dprintf("Probing bus 0x%02x... \n", nbus); bus = NULL; for (ndev = 0; ndev < MAX_PCI_DEVICES; ndev++) { maxfunc = 1; /* Assume a single-function device */ slot = NULL; for (nfunc = 0; nfunc < maxfunc; nfunc++) { a = pci_mkaddr(nbus, ndev, nfunc, 0); did = pci_readl(a); if (did == 0xffffffff || did == 0xffff0000 || did == 0x0000ffff || did == 0x00000000) continue; hdrtype = pci_readb(a + 0x0e); if (hdrtype & 0x80) maxfunc = MAX_PCI_FUNC; /* Multifunction device */ rcid = pci_readl(a + 0x08); sid = pci_readl(a + 0x2c); if (!domain) { domain = zalloc(sizeof *domain); if (!domain) goto bail; } if (!bus) { bus = zalloc(sizeof *bus); if (!bus) goto bail; domain->bus[nbus] = bus; } if (!slot) { slot = zalloc(sizeof *slot); if (!slot) goto bail; bus->slot[ndev] = slot; } func = zalloc(sizeof *func); if (!func) goto bail; slot->func[nfunc] = func; func->vid_did = did; func->svid_sdid = sid; func->rid_class = rcid; dprintf ("Scanning: BUS %02x DID %08x (%04x:%04x) SID %08x RID %02x\n", nbus, did, did >> 16, (did << 16) >> 16, sid, rcid & 0xff); } } } return domain; bail: free_pci_domain(domain); return NULL; } /* gathering additional configuration*/ void gather_additional_pci_config(struct pci_domain *domain) { struct pci_device *dev; pciaddr_t pci_addr; int cfgtype; cfgtype = pci_set_config_type(PCI_CFG_AUTO); if (cfgtype == PCI_CFG_NONE) return; for_each_pci_func3(dev, domain, pci_addr) { if (!dev->dev_info) { dev->dev_info = zalloc(sizeof *dev->dev_info); if (!dev->dev_info) { return; } } dev->dev_info->irq = pci_readb(pci_addr + 0x3c); dev->dev_info->latency = pci_readb(pci_addr + 0x0d); } } void free_pci_domain(struct pci_domain *domain) { struct pci_bus *bus; struct pci_slot *slot; struct pci_device *func; unsigned int nbus, ndev, nfunc; if (domain) { for (nbus = 0; nbus < MAX_PCI_BUSES; nbus++) { bus = domain->bus[nbus]; if (bus) { for (ndev = 0; ndev < MAX_PCI_DEVICES; ndev++) { slot = bus->slot[ndev]; if (slot) { for (nfunc = 0; nfunc < MAX_PCI_FUNC; nfunc++) { func = slot->func[nfunc]; if (func) { if (func->dev_info) free(func->dev_info); free(func); } } free(slot); } } free(bus); } } free(domain); } } /* Try to match any pci device to the appropriate kernel module */ /* it uses the modules.alias from the boot device */ int get_module_name_from_alias(struct pci_domain *domain, char *modules_alias_path) { char line[MAX_LINE]; char module_name[21]; // the module name field is 21 char long char delims[]="*"; // colums are separated by spaces char vendor_id[16]; char product_id[16]; char sub_vendor_id[16]; char sub_product_id[16]; FILE *f; struct pci_device *dev=NULL; /* Intializing the linux_kernel_module for each pci device to "unknown" */ /* adding a dev_info member if needed */ for_each_pci_func(dev, domain) { /* initialize the dev_info structure if it doesn't exist yet. */ if (! dev->dev_info) { dev->dev_info = zalloc(sizeof *dev->dev_info); if (!dev->dev_info) return -1; } for (int i=0;i<MAX_KERNEL_MODULES_PER_PCI_DEVICE;i++) { if (strlen(dev->dev_info->linux_kernel_module[i])==0) strlcpy(dev->dev_info->linux_kernel_module[i], "unknown",7); } } /* Opening the modules.pcimap (of a linux kernel) from the boot device */ f=zfopen(modules_alias_path, "r"); if (!f) return -ENOMODULESALIAS; /* for each line we found in the modules.pcimap */ while ( fgets(line, sizeof line, f) ) { /* skipping unecessary lines */ if ((line[0] == '#') || (strstr(line,"alias pci:v")==NULL)) continue; /* Resetting temp buffer*/ memset(module_name,0,sizeof(module_name)); memset(vendor_id,0,sizeof(vendor_id)); memset(sub_vendor_id,0,sizeof(sub_vendor_id)); memset(product_id,0,sizeof(product_id)); memset(sub_product_id,0,sizeof(sub_product_id)); strcpy(vendor_id,"0000"); strcpy(product_id,"0000"); /* ffff will be used to match any device as in modules.alias * a missing subvendor/product have to be considered as 0xFFFF*/ strcpy(sub_product_id,"ffff"); strcpy(sub_vendor_id,"ffff"); char *result = NULL; int field=0; /* looking for the next field */ result = strtok(line+strlen("alias pci:v"), delims); while( result != NULL ) { if (field==0) { /* Searching for the vendor separator*/ char *temp = strstr(result,"d"); if (temp != NULL) { strlcpy(vendor_id,result,temp-result); result+=strlen(vendor_id)+1; } /* Searching for the product separator*/ temp = strstr(result,"sv"); if (temp != NULL) { strlcpy(product_id,result,temp-result); result+=strlen(product_id)+1; } /* Searching for the sub vendor separator*/ temp = strstr(result,"sd"); if (temp != NULL) { strlcpy(sub_vendor_id,result,temp-result); result+=strlen(sub_vendor_id)+1; } /* Searching for the sub product separator*/ temp = strstr(result,"bc"); if (temp != NULL) { strlcpy(sub_product_id,result,temp-result); result+=strlen(sub_product_id)+1; } /* That's the module name */ } else if ((strlen(result)>2) && (result[0]==0x20)) strcpy(module_name,result+1); /* We have to replace \n by \0*/ module_name[strlen(module_name)-1]='\0'; field++; /* Searching the next field */ result = strtok( NULL, delims ); } /* Now we have extracted informations from the modules.alias * Let's compare it with the devices we know*/ int int_vendor_id=hex_to_int(vendor_id); int int_sub_vendor_id=hex_to_int(sub_vendor_id); int int_product_id=hex_to_int(product_id); int int_sub_product_id=hex_to_int(sub_product_id); /* if a pci_device matches an entry, fill the linux_kernel_module with the appropriate kernel module */ for_each_pci_func(dev, domain) { if (int_vendor_id == dev->vendor && int_product_id == dev->product && (int_sub_product_id & dev->sub_product) == dev->sub_product && (int_sub_vendor_id & dev->sub_vendor) == dev->sub_vendor) { bool found=false; /* Scan all known kernel modules for this pci device */ for (int i=0; i<dev->dev_info->linux_kernel_module_count; i++) { /* Try to detect if we already knew the same kernel module*/ if (strstr(dev->dev_info->linux_kernel_module[i], module_name)) { found=true; break; } } /* If we don't have this kernel module, let's add it */ if (!found) { strcpy(dev->dev_info->linux_kernel_module[dev->dev_info->linux_kernel_module_count], module_name); dev->dev_info->linux_kernel_module_count++; } } } } fclose(f); return 0; }