/* * Intel(R) Matrix Storage Manager hardware and firmware support routines * * Copyright (C) 2008 Intel Corporation * * This program is free software; you can redistribute it and/or modify it * under the terms and conditions of the GNU General Public License, * version 2, as published by the Free Software Foundation. * * This program is distributed in the hope 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. */ #include "mdadm.h" #include "platform-intel.h" #include "probe_roms.h" #include #include #include #include #include #include #include #include #include #include static int devpath_to_ll(const char *dev_path, const char *entry, unsigned long long *val); static __u16 devpath_to_vendor(const char *dev_path); static void free_sys_dev(struct sys_dev **list) { while (*list) { struct sys_dev *next = (*list)->next; if ((*list)->path) free((*list)->path); free(*list); *list = next; } } struct sys_dev *find_driver_devices(const char *bus, const char *driver) { /* search sysfs for devices driven by 'driver' */ char path[292]; char link[256]; char *c; DIR *driver_dir; struct dirent *de; struct sys_dev *head = NULL; struct sys_dev *list = NULL; enum sys_dev_type type; unsigned long long dev_id; if (strcmp(driver, "isci") == 0) type = SYS_DEV_SAS; else if (strcmp(driver, "ahci") == 0) type = SYS_DEV_SATA; else type = SYS_DEV_UNKNOWN; sprintf(path, "/sys/bus/%s/drivers/%s", bus, driver); driver_dir = opendir(path); if (!driver_dir) return NULL; for (de = readdir(driver_dir); de; de = readdir(driver_dir)) { int n; /* is 'de' a device? check that the 'subsystem' link exists and * that its target matches 'bus' */ sprintf(path, "/sys/bus/%s/drivers/%s/%s/subsystem", bus, driver, de->d_name); n = readlink(path, link, sizeof(link)); if (n < 0 || n >= (int)sizeof(link)) continue; link[n] = '\0'; c = strrchr(link, '/'); if (!c) continue; if (strncmp(bus, c+1, strlen(bus)) != 0) continue; sprintf(path, "/sys/bus/%s/drivers/%s/%s", bus, driver, de->d_name); /* if it's not Intel device skip it. */ if (devpath_to_vendor(path) != 0x8086) continue; if (devpath_to_ll(path, "device", &dev_id) != 0) continue; /* start / add list entry */ if (!head) { head = xmalloc(sizeof(*head)); list = head; } else { list->next = xmalloc(sizeof(*head)); list = list->next; } if (!list) { free_sys_dev(&head); break; } list->dev_id = (__u16) dev_id; list->type = type; list->path = realpath(path, NULL); list->next = NULL; if ((list->pci_id = strrchr(list->path, '/')) != NULL) list->pci_id++; } closedir(driver_dir); return head; } static struct sys_dev *intel_devices=NULL; static time_t valid_time = 0; static enum sys_dev_type device_type_by_id(__u16 device_id) { struct sys_dev *iter; for(iter = intel_devices; iter != NULL; iter = iter->next) if (iter->dev_id == device_id) return iter->type; return SYS_DEV_UNKNOWN; } static int devpath_to_ll(const char *dev_path, const char *entry, unsigned long long *val) { char path[strlen(dev_path) + strlen(entry) + 2]; int fd; int n; sprintf(path, "%s/%s", dev_path, entry); fd = open(path, O_RDONLY); if (fd < 0) return -1; n = sysfs_fd_get_ll(fd, val); close(fd); return n; } static __u16 devpath_to_vendor(const char *dev_path) { char path[strlen(dev_path) + strlen("/vendor") + 1]; char vendor[7]; int fd; __u16 id = 0xffff; int n; sprintf(path, "%s/vendor", dev_path); fd = open(path, O_RDONLY); if (fd < 0) return 0xffff; n = read(fd, vendor, sizeof(vendor)); if (n == sizeof(vendor)) { vendor[n - 1] = '\0'; id = strtoul(vendor, NULL, 16); } close(fd); return id; } struct sys_dev *find_intel_devices(void) { struct sys_dev *ahci, *isci; if (valid_time > time(0) - 10) return intel_devices; if (intel_devices) free_sys_dev(&intel_devices); isci = find_driver_devices("pci", "isci"); ahci = find_driver_devices("pci", "ahci"); if (!ahci) { ahci = isci; } else { struct sys_dev *elem = ahci; while (elem->next) elem = elem->next; elem->next = isci; } intel_devices = ahci; valid_time = time(0); return intel_devices; } /* * PCI Expansion ROM Data Structure Format */ struct pciExpDataStructFormat { __u8 ver[4]; __u16 vendorID; __u16 deviceID; } __attribute__ ((packed)); static struct imsm_orom imsm_orom[SYS_DEV_MAX]; static int populated_orom[SYS_DEV_MAX]; static int scan(const void *start, const void *end, const void *data) { int offset; const struct imsm_orom *imsm_mem; int dev; int len = (end - start); struct pciExpDataStructFormat *ptr= (struct pciExpDataStructFormat *)data; if (data + 0x18 > end) { dprintf("cannot find pciExpDataStruct \n"); return 0; } dprintf("ptr->vendorID: %lx __le16_to_cpu(ptr->deviceID): %lx \n", (ulong) __le16_to_cpu(ptr->vendorID), (ulong) __le16_to_cpu(ptr->deviceID)); if (__le16_to_cpu(ptr->vendorID) == 0x8086) { /* serach attached intel devices by device id from OROM */ dev = device_type_by_id(__le16_to_cpu(ptr->deviceID)); if (dev == SYS_DEV_UNKNOWN) return 0; } else return 0; for (offset = 0; offset < len; offset += 4) { imsm_mem = start + offset; if ((memcmp(imsm_mem->signature, "$VER", 4) == 0)) { imsm_orom[dev] = *imsm_mem; populated_orom[dev] = 1; return populated_orom[SYS_DEV_SATA] && populated_orom[SYS_DEV_SAS]; } } return 0; } const struct imsm_orom *imsm_platform_test(enum sys_dev_type hba_id, int *populated, struct imsm_orom *imsm_orom) { memset(imsm_orom, 0, sizeof(*imsm_orom)); imsm_orom->rlc = IMSM_OROM_RLC_RAID0 | IMSM_OROM_RLC_RAID1 | IMSM_OROM_RLC_RAID10 | IMSM_OROM_RLC_RAID5; imsm_orom->sss = IMSM_OROM_SSS_4kB | IMSM_OROM_SSS_8kB | IMSM_OROM_SSS_16kB | IMSM_OROM_SSS_32kB | IMSM_OROM_SSS_64kB | IMSM_OROM_SSS_128kB | IMSM_OROM_SSS_256kB | IMSM_OROM_SSS_512kB | IMSM_OROM_SSS_1MB | IMSM_OROM_SSS_2MB; imsm_orom->dpa = IMSM_OROM_DISKS_PER_ARRAY; imsm_orom->tds = IMSM_OROM_TOTAL_DISKS; imsm_orom->vpa = IMSM_OROM_VOLUMES_PER_ARRAY; imsm_orom->vphba = IMSM_OROM_VOLUMES_PER_HBA; imsm_orom->attr = imsm_orom->rlc | IMSM_OROM_ATTR_ChecksumVerify; *populated = 1; if (check_env("IMSM_TEST_OROM_NORAID5")) { imsm_orom->rlc = IMSM_OROM_RLC_RAID0 | IMSM_OROM_RLC_RAID1 | IMSM_OROM_RLC_RAID10; } if (check_env("IMSM_TEST_AHCI_EFI_NORAID5") && (hba_id == SYS_DEV_SAS)) { imsm_orom->rlc = IMSM_OROM_RLC_RAID0 | IMSM_OROM_RLC_RAID1 | IMSM_OROM_RLC_RAID10; } if (check_env("IMSM_TEST_SCU_EFI_NORAID5") && (hba_id == SYS_DEV_SATA)) { imsm_orom->rlc = IMSM_OROM_RLC_RAID0 | IMSM_OROM_RLC_RAID1 | IMSM_OROM_RLC_RAID10; } return imsm_orom; } static const struct imsm_orom *find_imsm_hba_orom(enum sys_dev_type hba_id) { unsigned long align; if (hba_id >= SYS_DEV_MAX) return NULL; /* it's static data so we only need to read it once */ if (populated_orom[hba_id]) { dprintf("OROM CAP: %p, pid: %d pop: %d\n", &imsm_orom[hba_id], (int) getpid(), populated_orom[hba_id]); return &imsm_orom[hba_id]; } if (check_env("IMSM_TEST_OROM")) { dprintf("OROM CAP: %p, pid: %d pop: %d\n", &imsm_orom[hba_id], (int) getpid(), populated_orom[hba_id]); return imsm_platform_test(hba_id, &populated_orom[hba_id], &imsm_orom[hba_id]); } /* return empty OROM capabilities in EFI test mode */ if (check_env("IMSM_TEST_AHCI_EFI") || check_env("IMSM_TEST_SCU_EFI")) return NULL; find_intel_devices(); if (intel_devices == NULL) return NULL; /* scan option-rom memory looking for an imsm signature */ if (check_env("IMSM_SAFE_OROM_SCAN")) align = 2048; else align = 512; if (probe_roms_init(align) != 0) return NULL; probe_roms(); /* ignore return value - True is returned if both adapater roms are found */ scan_adapter_roms(scan); probe_roms_exit(); if (populated_orom[hba_id]) return &imsm_orom[hba_id]; return NULL; } #define GUID_STR_MAX 37 /* according to GUID format: * xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" */ #define EFI_GUID(a, b, c, d0, d1, d2, d3, d4, d5, d6, d7) \ ((struct efi_guid) \ {{ (a) & 0xff, ((a) >> 8) & 0xff, ((a) >> 16) & 0xff, ((a) >> 24) & 0xff, \ (b) & 0xff, ((b) >> 8) & 0xff, \ (c) & 0xff, ((c) >> 8) & 0xff, \ (d0), (d1), (d2), (d3), (d4), (d5), (d6), (d7) }}) #define SYS_EFI_VAR_PATH "/sys/firmware/efi/vars" #define SCU_PROP "RstScuV" #define AHCI_PROP "RstSataV" #define VENDOR_GUID \ EFI_GUID(0x193dfefa, 0xa445, 0x4302, 0x99, 0xd8, 0xef, 0x3a, 0xad, 0x1a, 0x04, 0xc6) int populated_efi[SYS_DEV_MAX] = { 0, 0 }; static struct imsm_orom imsm_efi[SYS_DEV_MAX]; int read_efi_variable(void *buffer, ssize_t buf_size, char *variable_name, struct efi_guid guid) { char path[PATH_MAX]; char buf[GUID_STR_MAX]; int dfd; ssize_t n, var_data_len; snprintf(path, PATH_MAX, "%s/%s-%s/size", SYS_EFI_VAR_PATH, variable_name, guid_str(buf, guid)); dprintf("EFI VAR: path=%s\n", path); /* get size of variable data */ dfd = open(path, O_RDONLY); if (dfd < 0) return 1; n = read(dfd, &buf, sizeof(buf)); close(dfd); if (n < 0) return 1; buf[n] = '\0'; errno = 0; var_data_len = strtoul(buf, NULL, 16); if ((errno == ERANGE && (var_data_len == LONG_MAX)) || (errno != 0 && var_data_len == 0)) return 1; /* get data */ snprintf(path, PATH_MAX, "%s/%s-%s/data", SYS_EFI_VAR_PATH, variable_name, guid_str(buf, guid)); dprintf("EFI VAR: path=%s\n", path); dfd = open(path, O_RDONLY); if (dfd < 0) return 1; n = read(dfd, buffer, buf_size); close(dfd); if (n != var_data_len || n < buf_size) { return 1; } return 0; } const struct imsm_orom *find_imsm_efi(enum sys_dev_type hba_id) { if (hba_id >= SYS_DEV_MAX) return NULL; dprintf("EFI CAP: %p, pid: %d pop: %d\n", &imsm_efi[hba_id], (int) getpid(), populated_efi[hba_id]); /* it's static data so we only need to read it once */ if (populated_efi[hba_id]) { dprintf("EFI CAP: %p, pid: %d pop: %d\n", &imsm_efi[hba_id], (int) getpid(), populated_efi[hba_id]); return &imsm_efi[hba_id]; } if (check_env("IMSM_TEST_AHCI_EFI") || check_env("IMSM_TEST_SCU_EFI")) { dprintf("OROM CAP: %p, pid: %d pop: %d\n", &imsm_efi[hba_id], (int) getpid(), populated_efi[hba_id]); return imsm_platform_test(hba_id, &populated_efi[hba_id], &imsm_efi[hba_id]); } /* OROM test is set, return that there is no EFI capabilities */ if (check_env("IMSM_TEST_OROM")) return NULL; if (read_efi_variable(&imsm_efi[hba_id], sizeof(imsm_efi[0]), hba_id == SYS_DEV_SAS ? SCU_PROP : AHCI_PROP, VENDOR_GUID)) { populated_efi[hba_id] = 0; return NULL; } populated_efi[hba_id] = 1; return &imsm_efi[hba_id]; } /* * backward interface compatibility */ const struct imsm_orom *find_imsm_orom(void) { return find_imsm_hba_orom(SYS_DEV_SATA); } const struct imsm_orom *find_imsm_capability(enum sys_dev_type hba_id) { const struct imsm_orom *cap=NULL; if ((cap = find_imsm_efi(hba_id)) != NULL) return cap; if ((cap = find_imsm_hba_orom(hba_id)) != NULL) return cap; return NULL; } char *devt_to_devpath(dev_t dev) { char device[46]; sprintf(device, "/sys/dev/block/%d:%d/device", major(dev), minor(dev)); return realpath(device, NULL); } char *diskfd_to_devpath(int fd) { /* return the device path for a disk, return NULL on error or fd * refers to a partition */ struct stat st; if (fstat(fd, &st) != 0) return NULL; if (!S_ISBLK(st.st_mode)) return NULL; return devt_to_devpath(st.st_rdev); } int path_attached_to_hba(const char *disk_path, const char *hba_path) { int rc; if (check_env("IMSM_TEST_AHCI_DEV") || check_env("IMSM_TEST_SCU_DEV")) { return 1; } if (!disk_path || !hba_path) return 0; dprintf("hba: %s - disk: %s\n", hba_path, disk_path); if (strncmp(disk_path, hba_path, strlen(hba_path)) == 0) rc = 1; else rc = 0; return rc; } int devt_attached_to_hba(dev_t dev, const char *hba_path) { char *disk_path = devt_to_devpath(dev); int rc = path_attached_to_hba(disk_path, hba_path); if (disk_path) free(disk_path); return rc; } int disk_attached_to_hba(int fd, const char *hba_path) { char *disk_path = diskfd_to_devpath(fd); int rc = path_attached_to_hba(disk_path, hba_path); if (disk_path) free(disk_path); return rc; }