The IMSM platform code was based on an assumption that the OROM or UEFI capability structure (represented by struct imsm_orom) always belongs to only one HBA. This assumption is no longer valid, because of newer platforms with dual AHCI HBAs. Each HBA can have a separate OROM, but some versions have a combined OROM for both HBAs. This patch implements this HBA-OROM relationship in struct orom_entry, which matches an OROM with a list of HBA PCI ids. All the detected orom_entries are stored and retrieved using a global array and the functions add_orom(), add_orom_device_id() and get_orom_by_device_id(). This replaces the arrays: imsm_orom, populated_orom, imsm_efi, populated_efi. The scan() function is extended to find all HBAs for an OROM. The list of their device ids is retrieved from the PCI Expansion ROM Data Structure, hence the additional field devListOffset in struct pciExpDataStructFormat. In UEFI mode we can't read the PCI Expansion ROM Data Structure and the imsm_orom structures are stored in UEFI variables. They do not provide a similar device id list, so we also check the HBA PCI class to make sure that the HBA has RAID mode enabled. In super-intel.c there are changes which allow spanning of IMSM containers over HBAs of the same type, but only if the HBAs share the same OROM. This is done by comparing imsm_orom pointers, which (outside of platform-intel.c) always point to the global array containing all the detected oroms. Additional warnings are added to validate_container_imsm() to warn about potentially dangerous operations in all the possible cases, e.g. when an array is assembled using disks attached to HBAs with separate OROMs. Signed-off-by: Artur Paszkiewicz <artur.paszkiewicz@intel.com> Signed-off-by: NeilBrown <neilb@suse.de>
556 lines
12 KiB
C
556 lines
12 KiB
C
/*
|
|
* 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 <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <unistd.h>
|
|
#include <dirent.h>
|
|
#include <fcntl.h>
|
|
#include <sys/mman.h>
|
|
#include <sys/types.h>
|
|
#include <sys/stat.h>
|
|
#include <limits.h>
|
|
|
|
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;
|
|
unsigned long long class;
|
|
|
|
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;
|
|
|
|
if (devpath_to_ll(path, "class", &class) != 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->class = (__u32) class;
|
|
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 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;
|
|
__u16 devListOffset;
|
|
} __attribute__ ((packed));
|
|
|
|
struct devid_list {
|
|
__u16 devid;
|
|
struct devid_list *next;
|
|
};
|
|
|
|
struct orom_entry {
|
|
struct imsm_orom orom;
|
|
struct devid_list *devid_list;
|
|
};
|
|
|
|
static struct orom_entry oroms[SYS_DEV_MAX];
|
|
|
|
const struct imsm_orom *get_orom_by_device_id(__u16 dev_id)
|
|
{
|
|
int i;
|
|
struct devid_list *list;
|
|
|
|
for (i = 0; i < SYS_DEV_MAX; i++) {
|
|
for (list = oroms[i].devid_list; list; list = list->next) {
|
|
if (list->devid == dev_id)
|
|
return &oroms[i].orom;
|
|
}
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
static const struct imsm_orom *add_orom(const struct imsm_orom *orom)
|
|
{
|
|
int i;
|
|
|
|
for (i = 0; i < SYS_DEV_MAX; i++) {
|
|
if (&oroms[i].orom == orom)
|
|
return orom;
|
|
if (oroms[i].orom.signature[0] == 0) {
|
|
oroms[i].orom = *orom;
|
|
return &oroms[i].orom;
|
|
}
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
static void add_orom_device_id(const struct imsm_orom *orom, __u16 dev_id)
|
|
{
|
|
int i;
|
|
struct devid_list *list;
|
|
struct devid_list *prev = NULL;
|
|
|
|
for (i = 0; i < SYS_DEV_MAX; i++) {
|
|
if (&oroms[i].orom == orom) {
|
|
for (list = oroms[i].devid_list; list; prev = list, list = list->next) {
|
|
if (list->devid == dev_id)
|
|
return;
|
|
}
|
|
list = xmalloc(sizeof(struct devid_list));
|
|
list->devid = dev_id;
|
|
list->next = NULL;
|
|
|
|
if (prev == NULL)
|
|
oroms[i].devid_list = list;
|
|
else
|
|
prev->next = list;
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
static int scan(const void *start, const void *end, const void *data)
|
|
{
|
|
int offset;
|
|
const struct imsm_orom *imsm_mem = NULL;
|
|
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)
|
|
return 0;
|
|
|
|
for (offset = 0; offset < len; offset += 4) {
|
|
const void *mem = start + offset;
|
|
|
|
if ((memcmp(mem, IMSM_OROM_SIGNATURE, 4) == 0)) {
|
|
imsm_mem = mem;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!imsm_mem)
|
|
return 0;
|
|
|
|
const struct imsm_orom *orom = add_orom(imsm_mem);
|
|
|
|
if (ptr->devListOffset) {
|
|
const __u16 *dev_list = (void *)ptr + ptr->devListOffset;
|
|
int i;
|
|
|
|
for (i = 0; dev_list[i] != 0; i++)
|
|
add_orom_device_id(orom, dev_list[i]);
|
|
} else {
|
|
add_orom_device_id(orom, __le16_to_cpu(ptr->deviceID));
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
const struct imsm_orom *imsm_platform_test(struct sys_dev *hba)
|
|
{
|
|
struct imsm_orom orom = {
|
|
.signature = IMSM_OROM_SIGNATURE,
|
|
.rlc = IMSM_OROM_RLC_RAID0 | IMSM_OROM_RLC_RAID1 |
|
|
IMSM_OROM_RLC_RAID10 | IMSM_OROM_RLC_RAID5,
|
|
.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,
|
|
.dpa = IMSM_OROM_DISKS_PER_ARRAY,
|
|
.tds = IMSM_OROM_TOTAL_DISKS,
|
|
.vpa = IMSM_OROM_VOLUMES_PER_ARRAY,
|
|
.vphba = IMSM_OROM_VOLUMES_PER_HBA
|
|
};
|
|
orom.attr = orom.rlc | IMSM_OROM_ATTR_ChecksumVerify;
|
|
|
|
if (check_env("IMSM_TEST_OROM_NORAID5")) {
|
|
orom.rlc = IMSM_OROM_RLC_RAID0 | IMSM_OROM_RLC_RAID1 |
|
|
IMSM_OROM_RLC_RAID10;
|
|
}
|
|
if (check_env("IMSM_TEST_AHCI_EFI_NORAID5") && (hba->type == SYS_DEV_SAS)) {
|
|
orom.rlc = IMSM_OROM_RLC_RAID0 | IMSM_OROM_RLC_RAID1 |
|
|
IMSM_OROM_RLC_RAID10;
|
|
}
|
|
if (check_env("IMSM_TEST_SCU_EFI_NORAID5") && (hba->type == SYS_DEV_SATA)) {
|
|
orom.rlc = IMSM_OROM_RLC_RAID0 | IMSM_OROM_RLC_RAID1 |
|
|
IMSM_OROM_RLC_RAID10;
|
|
}
|
|
|
|
const struct imsm_orom *ret = add_orom(&orom);
|
|
|
|
add_orom_device_id(ret, hba->dev_id);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static const struct imsm_orom *find_imsm_hba_orom(struct sys_dev *hba)
|
|
{
|
|
unsigned long align;
|
|
|
|
if (check_env("IMSM_TEST_OROM"))
|
|
return imsm_platform_test(hba);
|
|
|
|
/* 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();
|
|
|
|
return get_orom_by_device_id(hba->dev_id);
|
|
}
|
|
|
|
#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)
|
|
|
|
#define PCI_CLASS_RAID_CNTRL 0x010400
|
|
|
|
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(struct sys_dev *hba)
|
|
{
|
|
struct imsm_orom orom;
|
|
const struct imsm_orom *ret;
|
|
|
|
if (check_env("IMSM_TEST_AHCI_EFI") || check_env("IMSM_TEST_SCU_EFI"))
|
|
return imsm_platform_test(hba);
|
|
|
|
/* OROM test is set, return that there is no EFI capabilities */
|
|
if (check_env("IMSM_TEST_OROM"))
|
|
return NULL;
|
|
|
|
if (hba->type == SYS_DEV_SATA && hba->class != PCI_CLASS_RAID_CNTRL)
|
|
return NULL;
|
|
|
|
if (read_efi_variable(&orom, sizeof(orom), hba->type == SYS_DEV_SAS ? SCU_PROP : AHCI_PROP, VENDOR_GUID))
|
|
return NULL;
|
|
|
|
ret = add_orom(&orom);
|
|
add_orom_device_id(ret, hba->dev_id);
|
|
|
|
return ret;
|
|
}
|
|
|
|
const struct imsm_orom *find_imsm_capability(struct sys_dev *hba)
|
|
{
|
|
const struct imsm_orom *cap = get_orom_by_device_id(hba->dev_id);
|
|
|
|
if (cap)
|
|
return cap;
|
|
|
|
if ((cap = find_imsm_efi(hba)) != NULL)
|
|
return cap;
|
|
if ((cap = find_imsm_hba_orom(hba)) != 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;
|
|
}
|