/* * Copyright (C) 2006, Intel Corporation * * This file is part of the Linux-ready Firmware Developer Kit * * This program file is free software; you can redistribute it and/or modify it * under the terms of the GNU Lesser General Public License as published by the * Free Software Foundation;version 2.1 of the License. * * This library 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 Lesser General Public License * for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program in a file named COPYING; if not, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301 USA */ #define _GNU_SOURCE #include #include #include #include #include #include #include #include #include #include #include #include extern void load_boot_dmesg_buffer(); static int edd_abort; static char *sigs; struct edd_disk_info { char device[32]; char signature[16]; char edd_name[128]; char pci_device[1024]; char uri[1024]; }; static GList *disks; static void check_line(gpointer data, gpointer user_data) { char *line = (char *)data; if (strstr(line, "EDD information not available.")) { report_result("edd", WARN, "No BIOS provided EDD information for multi-disk OS installs", line, NULL); edd_abort = 1; } } static void get_signature(struct edd_disk_info *disk) { char path[PATH_MAX]; unsigned char sector[512]; FILE *file; unsigned int sig; memset(sector, 0, 512); if (!disk) return; sprintf(path, "/dev/%s", disk->device); file = fopen(path, "r"); if (!file) return; if (fread(sector, 512, 1, file)<=0) return; fclose(file); sig = (sector[443] << 24) + (sector[442] << 16) + (sector[441] << 8) + (sector[440]); sprintf(disk->signature,"0x%x", sig); sigs = scatprintf(sigs, "Device %s - signature %s\n", disk->device, disk->signature); } static void get_pci_dev(struct edd_disk_info *disk) { char path[PATH_MAX]; char link[PATH_MAX]; char *c1, *c2; memset(link, 0, PATH_MAX); sprintf(path, "/sys/block/%s/device", disk->device); if (readlink(path, link, PATH_MAX)<0) return; c1 = strstr(link, "pci"); if (!c1) return; c1 = strchr(c1, '/'); if (!c1) return; c1++; if (strlen(c1)<5) return; c2 = strchr(c1, '/'); if (!c2) return; *c2 = 0; sprintf(disk->pci_device, c1); if (strlen(disk->uri)==0) sprintf(disk->uri, "pci://%s", c1); } void check_for_dupes(void) { DIR *dir; struct dirent *entry; dir = opendir("/sys/firmware/edd"); FILE *file; char path[PATH_MAX]; char lastsig[4096], line[4096]; int count=0; memset(lastsig,0,4096); memset(line,0,4096); if (!dir) return; do { entry = readdir(dir); if (!entry) break; sprintf(path, "/sys/firmware/edd/%s/mbr_signature", entry->d_name); file = fopen(path, "r"); if (!file) continue; if (fgets(line, 4095, file)==0) { fclose(file); continue; }; fclose(file); if (count>0 && strcmp(line, lastsig)==0) { sprintf(line, "A duplicate EDD signature value is detected (\"%s\"). The most likely cause for this is that the BIOS " "cleared CF on an non-existant disk for int $13 function 2 (read sectors).", lastsig); report_result("edd",FAIL,"Duplicate MBR signature", line, NULL); break; } count++; } while (entry); closedir(dir); } static void iterate_block(void) { DIR *dir; struct dirent *entry; struct edd_disk_info *disk; dir = opendir("/sys/block"); if (!dir) return; do { entry = readdir(dir); if (!entry) break; /* since we need the MBR, skip all partitioned devices */ if (strlen(entry->d_name)<3) continue; if (strchr(entry->d_name, '0')) continue; if (strchr(entry->d_name, '1')) continue; if (strchr(entry->d_name, '2')) continue; if (strchr(entry->d_name, '3')) continue; if (strchr(entry->d_name, '4')) continue; if (strchr(entry->d_name, '5')) continue; if (strchr(entry->d_name, '6')) continue; if (strchr(entry->d_name, '7')) continue; if (strchr(entry->d_name, '8')) continue; if (strchr(entry->d_name, '9')) continue; disk = malloc(sizeof(struct edd_disk_info)); if (!disk) continue; memset(disk, 0, sizeof(struct edd_disk_info)); strcpy(disk->device, entry->d_name); get_signature(disk); get_pci_dev(disk); disks = g_list_append(disks, disk); } while (entry); } static struct edd_disk_info *find_matching_disk(char *signature) { GList *item; struct edd_disk_info *disk, *found = NULL; item = g_list_first(disks); while (item) { disk = (struct edd_disk_info*)item->data; if (strcmp(disk->signature, signature)==0) { char buffer[4096]; sprintf(buffer, "Multiple disks match signature %s", signature); if (found) report_result("edd", WARN, buffer, NULL, NULL); found = disk; } item = g_list_next(item); } return found; } static void do_disk(int number) { char *c, *c2; FILE *file; char line[4096]; char uri[4096]; char channel[4096]; char buffer[4096]; char device[4096]; char path[4096]; struct edd_disk_info *disk; memset(line, 0, 4096); sprintf(path, "/sys/firmware/edd/int13_dev%x/host_bus", number); file = fopen(path , "r"); if (!file) { if (number == 0x80) report_result("edd", FAIL, "Boot device 0x80 does not support EDD\n", NULL, NULL); return; } if (fgets(line, 4095, file)==NULL) return; fclose(file); if (strncmp(line,"PCI",3)!=0) return; c=line; c+=4; while (*c==' ') c++; while (*c=='\t') c++; c2 = strchr(c, ' '); if (!c2) c2=strchr(c,'\t'); if (!c2) return; *c2 = 0; c2++; if (strchr(c,':') && (strchr(c,':')-c) < 5) { sprintf(device, "0000:%s", c); } else strcpy(device,c); sprintf(uri,"pci://%s", device); c = c2; while (*c==' ') c++; while (*c=='\t') c++; strcpy(channel, c); memset(line, 0, 4096); sprintf(path, "/sys/firmware/edd/int13_dev%x/interface", number); file = fopen(path, "r"); if (!file) return; if (fgets(line, 4095, file)==NULL) return; fclose(file); c = line; while (*c && *c!='\t' && *c!=' ') c++; while (*c && *c==' ') c++; while (*c && *c=='\t') c++; chop_newline(c); chop_newline(channel); if (number == 0x80) sprintf(buffer, "device %x: The system boots from device %s %s %s \n", number, device, channel, c); else sprintf(buffer, "device %x is provided by device %s %s %s \n", number, device, channel, c); report_result("edd", INFO, buffer, NULL, uri); memset(line, 0, 4096); sprintf(path, "/sys/firmware/edd/int13_dev%x/mbr_signature", number); file = fopen(path, "r"); if (!file) return; if (fgets(line, 4095, file)==NULL) return; fclose(file); chop_newline(line); disk = find_matching_disk(line); if (disk) { sprintf(disk->uri, "block://%s", disk->device); if (number == 0x80) sprintf(buffer, "device %x: The boot disk has Linux device name /dev/%s", number, disk->device); else sprintf(buffer, "device %x: This disk has Linux device name /dev/%s", number, disk->device); report_result("edd", INFO, buffer, NULL, disk->uri); sprintf(disk->edd_name, "int13_dev%x", number); if (strlen(disk->pci_device)>0 && strcmp(disk->pci_device, device)!=0) { sprintf(buffer, "device %x: PCI device %s does not match %s", number, disk->pci_device, device); report_result("edd", WARN, buffer, disk->pci_device, disk->uri); } } else { if (number == 0x80) { sprintf(buffer, "device 80: No matching MBR signature (%s) found for the boot disk", line); report_result("edd", WARN, buffer, sigs, NULL); } else { sprintf(buffer, "device %x: No matching MBR signature (%s) found for this disk", number, line); report_result("edd", WARN, buffer , sigs, NULL); } } } int main(int argc, char **argv) { start_test("edd", "EDD Boot disk hinting", "This test verifies if the BIOS directs the operating system on which storage " "device to use for booting (EDD information). This is important for systems that " "(can) have multiple disks. Linux distributions increasingly depend on this info " "to find out on which device to install the bootloader."); load_boot_dmesg_buffer(); if(boot_dmesg != NULL) g_list_foreach(boot_dmesg, check_line, NULL); else fprintf(stderr, "WARN: No boot dmesg found.\n"); iterate_block(); if (!edd_abort) { int i; for (i=0x80; i<0x90; i++) do_disk(i); } check_for_dupes(); finish_test("edd"); return EXIT_SUCCESS; }