blob: f15644bdf0332579bd6be1cfa9d3c93cffcea94a [file] [log] [blame]
/*
* Copyright (c) 2008, 2009 NEC Corporation.
* Copyright (c) 2009 Isaku Yamahata
* VA Linux Systems Japan K.K.
*
* 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., 59 Temple
* Place - Suite 330, Boston, MA 02111-1307 USA.
*/
#include <linux/kernel.h>
#include <linux/list.h>
#include <linux/mm.h>
#include <linux/pci.h>
#include <linux/module.h>
#include <linux/string.h>
#include <linux/acpi.h>
#include <asm/setup.h>
#define HID_LEN 8
#define UID_LEN 8
#define DEV_LEN 2
#define FUNC_LEN 1
#define DEV_NUM_MAX 31
#define FUNC_NUM_MAX 7
#define INVALID_SEG (-1)
#define INVALID_BBN (-1)
#define GUESTDEV_STR_MAX 128
#define GUESTDEV_FLAG_TYPE_MASK 0x3
#define GUESTDEV_FLAG_DEVICEPATH 0x1
#define GUESTDEV_FLAG_SBDF 0x2
#define GUESTDEV_OPT_IOMUL 0x1
struct guestdev {
int flags;
int options;
struct list_head root_list;
union {
struct devicepath {
char hid[HID_LEN + 1];
char uid[UID_LEN + 1];
int seg;
int bbn;
struct devicepath_node *child;
} devicepath;
struct sbdf {
int seg;
int bus;
int dev;
int func;
} sbdf;
} u;
};
struct devicepath_node {
int dev;
int func;
struct devicepath_node *child;
};
struct pcidev_sbdf {
int seg;
int bus;
struct pcidev_sbdf_node *child;
};
struct pcidev_sbdf_node {
int dev;
int func;
struct pcidev_sbdf_node *child;
};
static char __initdata guestdev_param[COMMAND_LINE_SIZE];
static LIST_HEAD(guestdev_list);
/* Get hid and uid */
static int __init pci_get_hid_uid(char *str, char *hid, char *uid)
{
char *sp, *ep;
int len;
sp = str;
ep = strchr(sp, ':');
if (!ep) {
ep = strchr(sp, '-');
if (!ep)
goto format_err_end;
}
/* hid length */
len = ep - sp;
if (len <= 0 || HID_LEN < len)
goto format_err_end;
strlcpy(hid, sp, len);
if (*ep == '-') { /* no uid */
uid[0] = '\0';
return TRUE;
}
sp = ep + 1;
ep = strchr(sp, '-');
if (!ep)
ep = strchr(sp, '\0');
/* uid length */
len = ep - sp;
if (len <= 0 || UID_LEN < len)
goto format_err_end;
strlcpy(uid, sp, len);
return TRUE;
format_err_end:
return FALSE;
}
/* Get device and function */
static int __init pci_get_dev_func(char *str, int *dev, int *func)
{
if (sscanf(str, "%02x.%01x", dev, func) != 2)
goto format_err_end;
if (*dev < 0 || DEV_NUM_MAX < *dev)
goto format_err_end;
if (*func < 0 || FUNC_NUM_MAX < *func)
goto format_err_end;
return TRUE;
format_err_end:
return FALSE;
}
/* Check extended guestdev parameter format error */
static int __init pci_check_extended_guestdev_format(char *str)
{
int flg;
char *p;
/* Check extended format */
if (strpbrk(str, "(|)") == NULL)
return TRUE;
flg = 0;
p = str;
while (*p) {
switch (*p) {
case '(':
/* Check nesting error */
if (flg != 0)
goto format_err_end;
flg = 1;
/* Check position of '(' is head or
previos charactor of '(' is not '-'. */
if (p == str || *(p - 1) != '-')
goto format_err_end;
break;
case ')':
/* Check nesting error */
if (flg != 1)
goto format_err_end;
flg = 0;
/* Check next charactor of ')' is not '\0' */
if (*(p + 1) != '\0')
goto format_err_end;
break;
case '|':
/* Check position of '|' is outside of '(' and ')' */
if (flg != 1)
goto format_err_end;
break;
default:
break;
}
p++;
}
/* Check number of '(' and ')' are not equal */
if (flg != 0)
goto format_err_end;
return TRUE;
format_err_end:
pr_err("PCI: The format of the guestdev parameter is illegal. [%s]\n",
str);
return FALSE;
}
/* Make guestdev strings */
static void pci_make_guestdev_str(struct guestdev *gdev,
char *gdev_str, int buf_size)
{
struct devicepath_node *node;
int count;
switch (gdev->flags & GUESTDEV_FLAG_TYPE_MASK) {
case GUESTDEV_FLAG_DEVICEPATH:
memset(gdev_str, 0, buf_size);
if (strlen(gdev->u.devicepath.uid))
count = snprintf(gdev_str, buf_size, "%s:%s",
gdev->u.devicepath.hid,
gdev->u.devicepath.uid);
else
count = snprintf(gdev_str, buf_size, "%s",
gdev->u.devicepath.hid);
if (count < 0)
return;
node = gdev->u.devicepath.child;
while (node) {
gdev_str += count;
buf_size -= count;
if (buf_size <= 0)
return;
count = snprintf(gdev_str, buf_size, "-%02x.%01x",
node->dev, node->func);
if (count < 0)
return;
node = node->child;
}
break;
case GUESTDEV_FLAG_SBDF:
snprintf(gdev_str, buf_size, "%04x:%02x:%02x.%01x",
gdev->u.sbdf.seg, gdev->u.sbdf.bus,
gdev->u.sbdf.dev, gdev->u.sbdf.func);
break;
default:
BUG();
}
}
/* Free guestdev and nodes */
static void __init pci_free_guestdev(struct guestdev *gdev)
{
struct devicepath_node *node, *next;
if (!gdev)
return;
if (gdev->flags & GUESTDEV_FLAG_DEVICEPATH) {
node = gdev->u.devicepath.child;
while (node) {
next = node->child;
kfree(node);
node = next;
}
}
list_del(&gdev->root_list);
kfree(gdev);
}
/* Copy guestdev and nodes */
struct guestdev __init *pci_copy_guestdev(struct guestdev *gdev_src)
{
struct guestdev *gdev;
struct devicepath_node *node, *node_src, *node_upper;
BUG_ON(!(gdev_src->flags & GUESTDEV_FLAG_DEVICEPATH));
gdev = kzalloc(sizeof(*gdev), GFP_KERNEL);
if (!gdev)
goto allocate_err_end;
INIT_LIST_HEAD(&gdev->root_list);
gdev->flags = gdev_src->flags;
gdev->options = gdev_src->options;
strcpy(gdev->u.devicepath.hid, gdev_src->u.devicepath.hid);
strcpy(gdev->u.devicepath.uid, gdev_src->u.devicepath.uid);
gdev->u.devicepath.seg = gdev_src->u.devicepath.seg;
gdev->u.devicepath.bbn = gdev_src->u.devicepath.bbn;
node_upper = NULL;
node_src = gdev_src->u.devicepath.child;
while (node_src) {
node = kzalloc(sizeof(*node), GFP_KERNEL);
if (!node)
goto allocate_err_end;
node->dev = node_src->dev;
node->func = node_src->func;
if (!node_upper)
gdev->u.devicepath.child = node;
else
node_upper->child = node;
node_upper = node;
node_src = node_src->child;
}
return gdev;
allocate_err_end:
if (gdev)
pci_free_guestdev(gdev);
pr_err("PCI: failed to allocate memory\n");
return NULL;
}
/* Make guestdev from path strings */
static int __init pci_make_devicepath_guestdev(char *path_str, int options)
{
char hid[HID_LEN + 1], uid[UID_LEN + 1];
char *sp, *ep;
struct guestdev *gdev, *gdev_org;
struct devicepath_node *node, *node_tmp;
int dev, func, ret_val;
ret_val = 0;
gdev = gdev_org = NULL;
sp = path_str;
/* Look for end of hid:uid'-' */
ep = strchr(sp, '-');
/* Only hid, uid. (No dev, func) */
if (!ep)
goto format_err_end;
memset(hid, 0 ,sizeof(hid));
memset(uid, 0, sizeof(uid));
if (!pci_get_hid_uid(sp, hid, uid))
goto format_err_end;
gdev_org = kzalloc(sizeof(*gdev_org), GFP_KERNEL);
if (!gdev_org)
goto allocate_err_end;
INIT_LIST_HEAD(&gdev_org->root_list);
gdev_org->flags = GUESTDEV_FLAG_DEVICEPATH;
gdev_org->options = options;
strcpy(gdev_org->u.devicepath.hid, hid);
strcpy(gdev_org->u.devicepath.uid, uid);
gdev_org->u.devicepath.seg = INVALID_SEG;
gdev_org->u.devicepath.bbn = INVALID_BBN;
gdev = gdev_org;
sp = ep + 1;
ep = sp;
do {
if (*sp == '(') {
sp++;
if (strchr(sp, '|')) {
gdev = pci_copy_guestdev(gdev_org);
if (!gdev) {
ret_val = -ENOMEM;
goto end;
}
}
continue;
}
if (gdev && pci_get_dev_func(sp, &dev, &func)) {
node = kzalloc(sizeof(*node), GFP_KERNEL);
if (!node)
goto allocate_err_end;
node->dev = dev;
node->func = func;
/* add node to end of guestdev */
if (gdev->u.devicepath.child) {
node_tmp = gdev->u.devicepath.child;
while (node_tmp->child) {
node_tmp = node_tmp->child;
}
node_tmp->child = node;
} else
gdev->u.devicepath.child = node;
} else if (gdev) {
pr_err("PCI: Can't obtain dev# and #func# from %s.\n",
sp);
ret_val = -EINVAL;
if (gdev == gdev_org)
goto end;
pci_free_guestdev(gdev);
gdev = NULL;
}
ep = strpbrk(sp, "-|)");
if (!ep)
ep = strchr(sp, '\0');
/* Is *ep '|' OR ')' OR '\0' ? */
if (*ep != '-') {
if (gdev)
list_add_tail(&gdev->root_list, &guestdev_list);
if (*ep == '|') {
/* Between '|' and '|' ? */
if (strchr(ep + 1, '|')) {
gdev = pci_copy_guestdev(gdev_org);
if (!gdev) {
ret_val = -ENOMEM;
goto end;
}
} else {
gdev = gdev_org;
gdev_org = NULL;
}
} else {
gdev_org = NULL;
gdev = NULL;
}
}
if (*ep == ')')
ep++;
sp = ep + 1;
} while (*ep != '\0');
goto end;
format_err_end:
pr_err("PCI: The format of the guestdev parameter is illegal. [%s]\n",
path_str);
ret_val = -EINVAL;
goto end;
allocate_err_end:
pr_err("PCI: failed to allocate memory\n");
ret_val = -ENOMEM;
goto end;
end:
if (gdev_org && (gdev_org != gdev))
pci_free_guestdev(gdev_org);
if (gdev)
pci_free_guestdev(gdev);
return ret_val;
}
static int __init pci_make_sbdf_guestdev(char* str, int options)
{
struct guestdev *gdev;
int seg, bus, dev, func;
if (sscanf(str, "%x:%x:%x.%x", &seg, &bus, &dev, &func) != 4) {
seg = 0;
if (sscanf(str, "%x:%x.%x", &bus, &dev, &func) != 3)
return -EINVAL;
}
gdev = kmalloc(sizeof(*gdev), GFP_KERNEL);
if (!gdev) {
pr_err("PCI: failed to allocate memory\n");
return -ENOMEM;
}
INIT_LIST_HEAD(&gdev->root_list);
gdev->flags = GUESTDEV_FLAG_SBDF;
gdev->options = options;
gdev->u.sbdf.seg = seg;
gdev->u.sbdf.bus = bus;
gdev->u.sbdf.dev = dev;
gdev->u.sbdf.func = func;
list_add_tail(&gdev->root_list, &guestdev_list);
return 0;
}
static int __init pci_parse_options(const char *str)
{
int options = 0;
char *ep;
while (str) {
str++;
ep = strchr(str, '+');
if (ep)
ep = '\0'; /* Chop */
if (!strcmp(str, "iomul"))
options |= GUESTDEV_OPT_IOMUL;
str = ep;
}
return options;
}
/* Parse guestdev parameter */
static int __init pci_parse_guestdev(void)
{
int len;
char *sp, *ep, *op;
int options;
struct list_head *head;
struct guestdev *gdev;
char path_str[GUESTDEV_STR_MAX];
int ret_val = 0;
len = strlen(guestdev_param);
if (len == 0)
return 0;
sp = guestdev_param;
do {
ep = strchr(sp, ',');
/* Chop */
if (ep)
*ep = '\0';
options = 0;
op = strchr(sp, '+');
if (op && (!ep || op < ep)) {
options = pci_parse_options(op);
*op = '\0'; /* Chop */
}
ret_val = pci_make_sbdf_guestdev(sp, options);
if (ret_val == -EINVAL) {
if (pci_check_extended_guestdev_format(sp)) {
ret_val = pci_make_devicepath_guestdev(
sp, options);
if (ret_val && ret_val != -EINVAL)
break;
}
} else if (ret_val)
break;
if (ep)
ep++;
sp = ep;
} while (ep);
list_for_each(head, &guestdev_list) {
gdev = list_entry(head, struct guestdev, root_list);
pci_make_guestdev_str(gdev, path_str, GUESTDEV_STR_MAX);
printk(KERN_DEBUG
"PCI: %s has been reserved for guest domain.\n",
path_str);
}
return 0;
}
arch_initcall(pci_parse_guestdev);
/* Get command line */
static int __init pci_guestdev_setup(char *str)
{
if (strlen(str) >= COMMAND_LINE_SIZE)
return 0;
strlcpy(guestdev_param, str, sizeof(guestdev_param));
return 1;
}
__setup("guestdev=", pci_guestdev_setup);
/* Free sbdf and nodes */
static void pci_free_sbdf(struct pcidev_sbdf *sbdf)
{
struct pcidev_sbdf_node *node, *next;
node = sbdf->child;
while (node) {
next = node->child;
kfree(node);
node = next;
}
/* Skip kfree(sbdf) */
}
/* Does PCI device belong to sub tree specified by guestdev with device path? */
typedef int (*pci_node_match_t)(const struct devicepath_node *gdev_node,
const struct pcidev_sbdf_node *sbdf_node,
int options);
static int pci_node_match(const struct devicepath_node *gdev_node,
const struct pcidev_sbdf_node *sbdf_node,
int options_unused)
{
return (gdev_node->dev == sbdf_node->dev &&
gdev_node->func == sbdf_node->func);
}
static int pci_is_in_devicepath_sub_tree(struct guestdev *gdev,
struct pcidev_sbdf *sbdf,
pci_node_match_t match)
{
int seg, bbn;
struct devicepath_node *gdev_node;
struct pcidev_sbdf_node *sbdf_node;
if (!gdev || !sbdf)
return FALSE;
BUG_ON(!(gdev->flags & GUESTDEV_FLAG_DEVICEPATH));
/* Compare seg and bbn */
if (gdev->u.devicepath.seg == INVALID_SEG ||
gdev->u.devicepath.bbn == INVALID_BBN) {
if (acpi_pci_get_root_seg_bbn(gdev->u.devicepath.hid,
gdev->u.devicepath.uid, &seg, &bbn)) {
gdev->u.devicepath.seg = seg;
gdev->u.devicepath.bbn = bbn;
} else
return FALSE;
}
if (gdev->u.devicepath.seg != sbdf->seg ||
gdev->u.devicepath.bbn != sbdf->bus)
return FALSE;
gdev_node = gdev->u.devicepath.child;
sbdf_node = sbdf->child;
/* Compare dev and func */
while (gdev_node) {
if (!sbdf_node)
return FALSE;
if (!match(gdev_node, sbdf_node, gdev->options))
return FALSE;
gdev_node = gdev_node->child;
sbdf_node = sbdf_node->child;
}
return TRUE;
}
/* Get sbdf from device */
static int pci_get_sbdf_from_pcidev(
struct pci_dev *dev, struct pcidev_sbdf *sbdf)
{
struct pcidev_sbdf_node *node;
if (!dev)
return FALSE;
for(;;) {
node = kzalloc(sizeof(*node), GFP_KERNEL);
if (!node) {
pr_err("PCI: failed to allocate memory\n");
goto err_end;
}
node->dev = PCI_SLOT(dev->devfn);
node->func = PCI_FUNC(dev->devfn);
if (!sbdf->child)
sbdf->child = node;
else {
node->child = sbdf->child;
sbdf->child = node;
}
if (!dev->bus)
goto err_end;
if (!dev->bus->self)
break;
dev = dev->bus->self;
}
if (sscanf(dev_name(&dev->dev), "%04x:%02x", &sbdf->seg, &sbdf->bus) != 2)
goto err_end;
return TRUE;
err_end:
pci_free_sbdf(sbdf);
return FALSE;
}
/* Does PCI device belong to sub tree specified by guestdev with sbdf? */
typedef int (*pci_sbdf_match_t)(const struct guestdev *gdev,
const struct pci_dev *dev);
static int pci_sbdf_match(const struct guestdev *gdev,
const struct pci_dev *dev)
{
int seg, bus;
if (sscanf(dev_name(&dev->dev), "%04x:%02x", &seg, &bus) != 2)
return FALSE;
return gdev->u.sbdf.seg == seg &&
gdev->u.sbdf.bus == bus &&
gdev->u.sbdf.dev == PCI_SLOT(dev->devfn) &&
gdev->u.sbdf.func == PCI_FUNC(dev->devfn);
}
static int pci_is_in_sbdf_sub_tree(struct guestdev *gdev, struct pci_dev *dev,
pci_sbdf_match_t match)
{
BUG_ON(!(gdev->flags & GUESTDEV_FLAG_SBDF));
for (;;) {
if (match(gdev, dev))
return TRUE;
if (!dev->bus || !dev->bus->self)
break;
dev = dev->bus->self;
}
return FALSE;
}
/* Does PCI device belong to sub tree specified by guestdev parameter? */
static int __pci_is_guestdev(struct pci_dev *dev, pci_node_match_t node_match,
pci_sbdf_match_t sbdf_match)
{
struct guestdev *gdev;
struct pcidev_sbdf pcidev_sbdf, *sbdf = NULL;
struct list_head *head;
int result = FALSE;
if (!dev)
return FALSE;
list_for_each(head, &guestdev_list) {
gdev = list_entry(head, struct guestdev, root_list);
switch (gdev->flags & GUESTDEV_FLAG_TYPE_MASK) {
case GUESTDEV_FLAG_DEVICEPATH:
if (sbdf == NULL) {
sbdf = &pcidev_sbdf;
memset(sbdf, 0 ,sizeof(*sbdf));
if (!pci_get_sbdf_from_pcidev(dev, sbdf))
goto out;
}
if (pci_is_in_devicepath_sub_tree(gdev, sbdf,
node_match)) {
result = TRUE;
goto out;
}
break;
case GUESTDEV_FLAG_SBDF:
if (pci_is_in_sbdf_sub_tree(gdev, dev, sbdf_match)) {
result = TRUE;
goto out;
}
break;
default:
BUG();
}
}
out:
if (sbdf)
pci_free_sbdf(sbdf);
return result;
}
int pci_is_guestdev(struct pci_dev *dev)
{
return __pci_is_guestdev(dev, pci_node_match, pci_sbdf_match);
}
EXPORT_SYMBOL_GPL(pci_is_guestdev);
static int reassign_resources;
static int __init pci_set_reassign_resources(char *str)
{
if (str && !strcmp(str, "all"))
reassign_resources = -1;
else
reassign_resources = 1;
return 1;
}
__setup("reassign_resources", pci_set_reassign_resources);
int pci_is_guestdev_to_reassign(struct pci_dev *dev)
{
if (reassign_resources < 0)
return TRUE;
if (reassign_resources)
return pci_is_guestdev(dev);
return FALSE;
}
#if defined(CONFIG_PCI_IOMULTI) || defined(CONFIG_PCI_IOMULTI_MODULE)
static int pci_iomul_node_match(const struct devicepath_node *gdev_node,
const struct pcidev_sbdf_node *sbdf_node,
int options)
{
return (options & GUESTDEV_OPT_IOMUL) &&
((gdev_node->child != NULL &&
sbdf_node->child != NULL &&
gdev_node->dev == sbdf_node->dev &&
gdev_node->func == sbdf_node->func) ||
(gdev_node->child == NULL &&
sbdf_node->child == NULL &&
gdev_node->dev == sbdf_node->dev));
}
static int pci_iomul_sbdf_match(const struct guestdev *gdev,
const struct pci_dev *dev)
{
int seg, bus;
if (sscanf(dev_name(&dev->dev), "%04x:%02x", &seg, &bus) != 2)
return FALSE;
return (gdev->options & GUESTDEV_OPT_IOMUL) &&
gdev->u.sbdf.seg == seg &&
gdev->u.sbdf.bus == bus &&
gdev->u.sbdf.dev == PCI_SLOT(dev->devfn);
}
int pci_is_iomuldev(struct pci_dev *dev)
{
return __pci_is_guestdev(dev,
pci_iomul_node_match, pci_iomul_sbdf_match);
}
#endif /* CONFIG_PCI_IOMULTI */
/* Check whether the devicepath exists under the pci root bus */
static int __init pci_check_devicepath_exists(
struct guestdev *gdev, struct pci_bus *bus)
{
struct devicepath_node *node;
struct pci_dev *dev;
BUG_ON(!(gdev->flags & GUESTDEV_FLAG_DEVICEPATH));
node = gdev->u.devicepath.child;
while (node) {
if (!bus)
return FALSE;
dev = pci_get_slot(bus, PCI_DEVFN(node->dev, node->func));
if (!dev)
return FALSE;
bus = dev->subordinate;
node = node->child;
pci_dev_put(dev);
}
return TRUE;
}
/* Check whether the guestdev exists in the PCI device tree */
static int __init pci_check_guestdev_exists(void)
{
struct list_head *head;
struct guestdev *gdev;
int seg, bbn;
struct pci_bus *bus;
struct pci_dev *dev;
char path_str[GUESTDEV_STR_MAX];
list_for_each(head, &guestdev_list) {
gdev = list_entry(head, struct guestdev, root_list);
switch (gdev->flags & GUESTDEV_FLAG_TYPE_MASK) {
case GUESTDEV_FLAG_DEVICEPATH:
if (gdev->u.devicepath.seg == INVALID_SEG ||
gdev->u.devicepath.bbn == INVALID_BBN) {
if (acpi_pci_get_root_seg_bbn(
gdev->u.devicepath.hid,
gdev->u.devicepath.uid, &seg, &bbn)) {
gdev->u.devicepath.seg = seg;
gdev->u.devicepath.bbn = bbn;
} else {
pci_make_guestdev_str(gdev,
path_str, GUESTDEV_STR_MAX);
pr_info("PCI: "
"device %s does not exist\n",
path_str);
continue;
}
}
bus = pci_find_bus(gdev->u.devicepath.seg,
gdev->u.devicepath.bbn);
if (!bus || !pci_check_devicepath_exists(gdev, bus)) {
pci_make_guestdev_str(gdev, path_str,
GUESTDEV_STR_MAX);
pr_info("PCI: device %s does not exist\n",
path_str);
}
break;
case GUESTDEV_FLAG_SBDF:
bus = pci_find_bus(gdev->u.sbdf.seg, gdev->u.sbdf.bus);
if (bus) {
dev = pci_get_slot(bus,
PCI_DEVFN(gdev->u.sbdf.dev,
gdev->u.sbdf.func));
if (dev) {
pci_dev_put(dev);
continue;
}
}
pci_make_guestdev_str(gdev, path_str, GUESTDEV_STR_MAX);
pr_info("PCI: device %s does not exist\n", path_str);
break;
default:
BUG();
}
}
return 0;
}
fs_initcall(pci_check_guestdev_exists);