Imported Debian patch 0.5.0-1
[debian/efibootmgr] / src / lib / efi.c
index 0ab0339b23a852d591dfa38236b4394f99a5da88..26c144a347bd6fcab745051466d5d543b8502021 100644 (file)
@@ -1,5 +1,5 @@
 /*
-  efi.[ch] - Manipulates EFI variables as exported in /proc/efi/vars
+  efivars_proc.[ch] - Manipulates EFI variables as exported in /proc/efi/vars
 
   Copyright (C) 2001,2003 Dell Computer Corporation <Matt_Domsch@dell.com>
 
@@ -20,6 +20,8 @@
 
 #define _FILE_OFFSET_BITS 64
 
+typedef unsigned long long u64;        /* hack to allow include of ethtool.h */
+
 #include <stdio.h>
 #include <stdlib.h>
 #include <string.h>
 #include <sys/ioctl.h>
 #include <linux/sockios.h>
 #include <net/if.h>
-
-typedef unsigned long long u64; /* hack, so we may include kernel's ethtool.h */
-typedef __uint32_t u32;         /* ditto */
-typedef __uint16_t u16;         /* ditto */
-typedef __uint8_t u8;           /* ditto */
-
+#include <pci/pci.h>
 #include <linux/ethtool.h>
 #include "efi.h"
 #include "efichar.h"
 #include "scsi_ioctls.h"
 #include "disk.h"
 #include "efibootmgr.h"
+#include "efivars_procfs.h"
+#include "efivars_sysfs.h"
+#include "list.h"
+
+static struct efivar_kernel_calls *fs_kernel_calls;
 
 EFI_DEVICE_PATH *
 load_option_path(EFI_LOAD_OPTION *option)
@@ -69,36 +71,33 @@ efi_guid_unparse(efi_guid_t *guid, char *out)
         return out;
 }
 
-
-efi_status_t
-read_variable(char *name, efi_variable_t *var)
+void
+set_fs_kernel_calls()
 {
-       int newnamesize;
-       char *newname;
-       int fd;
-       size_t readsize;
-       if (!name || !var) return EFI_INVALID_PARAMETER;
-
-       newnamesize = strlen(PROC_DIR_EFI_VARS) + strlen(name) + 1;
-       newname = malloc(newnamesize);
-       if (!newname) return EFI_OUT_OF_RESOURCES;
-       sprintf(newname, "%s%s", PROC_DIR_EFI_VARS,name);
-       fd = open(newname, O_RDONLY);
-       if (fd == -1) {
-               free(newname);
-               return EFI_NOT_FOUND;
+       char name[PATH_MAX];
+       DIR *dir;
+       snprintf(name, PATH_MAX, "%s", SYSFS_DIR_EFI_VARS);
+       dir = opendir(name);
+       if (dir) {
+               closedir(dir);
+               fs_kernel_calls = &sysfs_kernel_calls;
+               return;
        }
-       readsize = read(fd, var, sizeof(*var));
-       if (readsize != sizeof(*var)) {
-               free(newname);
-               close(fd);
-               return EFI_INVALID_PARAMETER;
+
+       snprintf(name, PATH_MAX, "%s", PROCFS_DIR_EFI_VARS);
+       dir = opendir(name);
+       if (dir) {
+               closedir(dir);
+               fs_kernel_calls = &procfs_kernel_calls;
+               return;
        }
-       close(fd);
-       free(newname);
-       return var->Status;
+       fprintf(stderr, "Fatal: Couldn't open either sysfs or procfs directories for accessing EFI variables.\n");
+       fprintf(stderr, "Try 'modprobe efivars' as root.\n");
+       exit(1);
 }
 
+
+
 static efi_status_t
 write_variable_to_file(efi_variable_t *var)
 {
@@ -120,108 +119,72 @@ write_variable_to_file(efi_variable_t *var)
        close(fd);
        return EFI_SUCCESS;
 }
-/**
- * select_variable_names()
- * @d - dirent to compare against
- *
- * This ignores "." and ".." entries, and selects all others.
- */
 
-static int
-select_variable_names(const struct dirent *d)
+efi_status_t
+read_variable(const char *name, efi_variable_t *var)
 {
-       if (!strcmp(d->d_name, ".") ||
-           !strcmp(d->d_name, ".."))
-               return 0;
-       return 1;
+       if (!name || !var) return EFI_INVALID_PARAMETER;
+       return fs_kernel_calls->read(name, var);
 }
 
-/**
- * find_write_victim()
- * @var - variable to be written
- * @file - name of file to open for writing @var is returned.
- *
- * This ignores "." and ".." entries, and selects all others.
- */
-static char *
-find_write_victim(efi_variable_t *var, char file[PATH_MAX])
-{
-       struct dirent **namelist = NULL;
-       int i, n, found=0;
-       char testname[PATH_MAX], *p;
-
-       memset(testname, 0, sizeof(testname));
-       n = scandir(PROC_DIR_EFI_VARS, &namelist,
-                   select_variable_names, alphasort);
-       if (n < 0) {
-               perror("scandir " PROC_DIR_EFI_VARS);
-               fprintf(stderr, "You must 'modprobe efivars' first.\n");
-               return NULL;
-       }
+efi_status_t
+create_variable(efi_variable_t *var)
+{
+       if (!var) return EFI_INVALID_PARAMETER;
+       if (opts.testfile) return write_variable_to_file(var);
+       return fs_kernel_calls->create(var);
+}
 
-       p = testname;
-       efichar_to_char(p, var->VariableName, PATH_MAX);
-       p += strlen(p);
-       p += sprintf(p, "-");
-       efi_guid_unparse(&var->VendorGuid, p);
+efi_status_t
+delete_variable(efi_variable_t *var)
+{
+       if (!var) return EFI_INVALID_PARAMETER;
+       if (opts.testfile) return write_variable_to_file(var);
+       return fs_kernel_calls->delete(var);
+}
 
-       for (i=0; i<n; i++) {
-               if (namelist[i] &&
-                   strncmp(testname, namelist[i]->d_name, sizeof(testname))) {
-                       found++;
-                       sprintf(file, "%s%s", PROC_DIR_EFI_VARS,
-                               namelist[i]->d_name);
-                       break;
-               }
-       }
 
-       while (n--) {
-               if (namelist[n]) {
-                       free(namelist[n]);
-                       namelist[n] = NULL;
-               }
-       }
-       free(namelist);
+efi_status_t
+edit_variable(efi_variable_t *var)
+{
+       char name[PATH_MAX];
+       if (!var) return EFI_INVALID_PARAMETER;
+       if (opts.testfile) return write_variable_to_file(var);
 
-       if (!found) return NULL;
-       return file;
+       variable_to_name(var, name);
+       return fs_kernel_calls->edit(name, var);
 }
 
-
 efi_status_t
-write_variable(efi_variable_t *var)
+create_or_edit_variable(efi_variable_t *var)
 {
-       int fd;
-       size_t writesize;
-       char buffer[PATH_MAX], name[PATH_MAX], *p = NULL;
+       efi_variable_t testvar;
+       char name[PATH_MAX];
 
-       if (!var) return EFI_INVALID_PARAMETER;
-       if (opts.testfile) return write_variable_to_file(var);
-       memset(buffer, 0, sizeof(buffer));
-       memset(name, 0, sizeof(name));
+       memcpy(&testvar, var, sizeof(*var));
+       variable_to_name(var, name);
 
-       p = find_write_victim(var, name);
-       if (!p) return EFI_INVALID_PARAMETER;
+       if (read_variable(name, &testvar) == EFI_SUCCESS)
+               return edit_variable(var);
+       else
+               return create_variable(var);
+}
 
-       fd = open(name, O_WRONLY);
-       if (fd == -1) {
-               sprintf(buffer, "write_variable():open(%s)", name);
-               perror(buffer);
-               return EFI_INVALID_PARAMETER;
-       }
-       writesize = write(fd, var, sizeof(*var));
-       if (writesize != sizeof(*var)) {
-#if 0
-               sprintf(buffer, "write_variable():write(%s)", name);
-               perror(buffer);
-               dump_raw_data(var, sizeof(*var));
-#endif
-               close(fd);
-               return EFI_INVALID_PARAMETER;
+static int
+select_boot_var_names(const struct dirent *d)
+{
+       int num, rc;
+       rc = sscanf(d->d_name, "Boot0%03x-%*s", &num);
+       return rc;
+}
 
-       }
-       close(fd);
-       return EFI_SUCCESS;
+int
+read_boot_var_names(struct dirent ***namelist)
+{
+       if (!fs_kernel_calls || !namelist) return -1;
+       return scandir(fs_kernel_calls->path,
+                      namelist, select_boot_var_names,
+                      alphasort);
 }
 
 
@@ -333,8 +296,55 @@ make_mac_addr_device_path(void *dest, char *mac, uint8_t iftype)
        return p.length;
 }
 
+struct device
+{
+       struct pci_dev *pci_dev;
+       struct list_head node;
+};
+
+static struct device *
+is_parent_bridge(struct pci_dev *p, unsigned int target_bus)
+{
+       struct device *d;
+       unsigned int primary, secondary;
+
+       if ( (pci_read_word(p, PCI_HEADER_TYPE) & 0x7f) != PCI_HEADER_TYPE_BRIDGE)
+               return NULL;
+
+       primary=pci_read_byte(p, PCI_PRIMARY_BUS);
+       secondary=pci_read_byte(p, PCI_SECONDARY_BUS);
+
+
+       if (secondary != target_bus)
+               return NULL;
+
+       d = malloc(sizeof(struct device));
+       if (!d)
+               return NULL;
+       memset(d, 0, sizeof(*d));
+       INIT_LIST_HEAD(&d->node);
+
+       d->pci_dev = p;
+
+       return d;
+}
+
+static struct device *
+find_parent(struct pci_access *pacc, unsigned int target_bus)
+{
+       struct device *dev;
+       struct pci_dev *p;
+
+       for (p=pacc->devices; p; p=p->next) {
+               dev = is_parent_bridge(p, target_bus);
+               if (dev)
+                       return dev;
+       }
+       return NULL;
+}
+
 static uint16_t
-make_pci_device_path(void *dest, uint8_t device, uint8_t function)
+make_one_pci_device_path(void *dest, uint8_t device, uint8_t function)
 {
        PCI_DEVICE_PATH p;
        memset(&p, 0, sizeof(p));
@@ -347,6 +357,47 @@ make_pci_device_path(void *dest, uint8_t device, uint8_t function)
        return p.length;
 }
 
+static uint16_t
+make_pci_device_path(void *dest, uint8_t bus, uint8_t device, uint8_t function)
+{
+       struct device *dev;
+       struct pci_access *pacc;
+       struct list_head *pos, *n;
+       LIST_HEAD(pci_parent_list);
+       char *p = dest;
+
+       pacc = pci_alloc();
+       if (!pacc)
+               return 0;
+
+       pci_init(pacc);
+       pci_scan_bus(pacc);
+
+       do {
+               dev = find_parent(pacc, bus);
+               if (dev) {
+                       list_add(&pci_parent_list, &dev->node);
+                       bus = dev->pci_dev->bus;
+               }
+       } while (dev && bus);
+
+
+       list_for_each_safe(pos, n, &pci_parent_list) {
+               dev = list_entry(pos, struct device, node);
+               p += make_one_pci_device_path(p,
+                                             dev->pci_dev->dev,
+                                             dev->pci_dev->func);
+               list_del(&dev->node);
+               free(dev);
+       }
+
+       p += make_one_pci_device_path(p, device, function);
+
+       pci_cleanup(pacc);
+
+       return ((void *)p - dest);
+}
+
 static uint16_t
 make_scsi_device_path(void *dest, uint16_t id, uint16_t lun)
 {
@@ -423,7 +474,7 @@ make_edd30_device_path(int fd, void *buffer)
        idlun_to_components(&idlun, &host, &channel, &id, &lun);
 
        p += make_acpi_device_path      (p, EISAID_PNP0A03, bus);
-       p += make_pci_device_path       (p, device, function);
+       p += make_pci_device_path       (p, bus, device, function);
        p += make_scsi_device_path      (p, id, lun);
        return ((void *)p - buffer);
 }
@@ -498,7 +549,7 @@ char *make_disk_load_option(char *p, char *disk)
 char *make_net_load_option(char *p, char *iface)
 {
     /* copied pretty much verbatim from the ethtool source */
-    int fd = 0, err;
+    int fd = 0, err; 
     int bus, slot, func;
     struct ifreq ifr;
     struct ethtool_drvinfo drvinfo;
@@ -517,13 +568,14 @@ char *make_net_load_option(char *p, char *iface)
         perror("Cannot get driver information");
     }
 
+
     err = sscanf(drvinfo.bus_info, "%2x:%2x.%x", &bus, &slot, &func);
     if (err == 0) {
         perror("Couldn't parse device location string.");
     }
 
     p += make_acpi_device_path(p, opts.acpi_hid, opts.acpi_uid);
-    p += make_pci_device_path(p, (uint8_t)slot, (uint8_t)func);
+    p += make_pci_device_path(p, bus, (uint8_t)slot, (uint8_t)func);
 
     err = ioctl(fd, SIOCGIFHWADDR, &ifr);
     if (err < 0) {
@@ -532,7 +584,6 @@ char *make_net_load_option(char *p, char *iface)
 
     p += make_mac_addr_device_path(p, ifr.ifr_ifru.ifru_hwaddr.sa_data, 0);
     p += make_end_device_path       (p);
-
     return(p);
 }
 
@@ -690,3 +741,15 @@ make_linux_efi_variable(efi_variable_t *var,
        var->DataSize = load_option_size + opt_data_size;
        return var->DataSize;
 }
+
+
+int
+variable_to_name(efi_variable_t *var, char *name)
+{
+       char *p = name;
+       efichar_to_char(p, var->VariableName, PATH_MAX);
+       p += strlen(p);
+       p += sprintf(p, "-");
+       efi_guid_unparse(&var->VendorGuid, p);
+       return strlen(name);
+}