2697 lines
68 KiB
Plaintext
2697 lines
68 KiB
Plaintext
Subject: xen/dom0: Reserve devices for guest use
|
|
From: http://xenbits.xensource.com/linux-2.6.18-xen.hg (tip 1023:85ca9742b8b9)
|
|
Patch-mainline: n/a
|
|
|
|
jb: Added support for reassign_resources=all (bnc#574224).
|
|
jb: Used kzalloc() instead of all kmalloc()+memset() pairs.
|
|
jb: Added support for guestiomuldev=all.
|
|
jb: split /dev/xen/pci_iomul driver to be separate (so it can be a module)
|
|
Acked-by: jbeulich@novell.com
|
|
|
|
--- head-2011-03-11.orig/Documentation/kernel-parameters.txt 2011-03-11 10:41:54.000000000 +0100
|
|
+++ head-2011-03-11/Documentation/kernel-parameters.txt 2011-03-11 10:49:08.000000000 +0100
|
|
@@ -815,6 +815,24 @@ bytes respectively. Such letter suffixes
|
|
gpt [EFI] Forces disk with valid GPT signature but
|
|
invalid Protective MBR to be treated as GPT.
|
|
|
|
+ guestdev= [PCI,ACPI,XEN]
|
|
+ Format: {<device path>|<sbdf>}][,{<device path>|<sbdf>}[,...]]
|
|
+ Format of device path: <hid>[:<uid>]-<dev>.<func>[-<dev>.<func>[,...]][+iomul]
|
|
+ Format of sbdf: [<segment>:]<bus>:<dev>.<func>[+iomul]
|
|
+ Specifies PCI device for guest domain.
|
|
+ If PCI-PCI bridge is specified, all PCI devices
|
|
+ behind PCI-PCI bridge are reserved.
|
|
+ +iomul means that this PCI function will share
|
|
+ IO ports with other +iomul functions under same
|
|
+ switch. NOTE: if +iomul is specfied, all the functions
|
|
+ of the device will share IO ports.
|
|
+
|
|
+ guestiomuldev= [PCI,ACPI,XEN]
|
|
+ Format: [sbd][,<sbd>][,...]
|
|
+ Format of sbdf: [<segment>:]<bus>:<dev>
|
|
+ Note: function shouldn't be specified.
|
|
+ Specifies PCI device for IO port multiplexing driver.
|
|
+
|
|
hashdist= [KNL,NUMA] Large hashes allocated during boot
|
|
are distributed across NUMA nodes. Defaults on
|
|
for 64bit NUMA, off otherwise.
|
|
@@ -2162,6 +2180,10 @@ bytes respectively. Such letter suffixes
|
|
Run specified binary instead of /init from the ramdisk,
|
|
used for early userspace startup. See initrd.
|
|
|
|
+ reassign_resources [PCI,ACPI,XEN]
|
|
+ Use guestdev= parameter to reassign device's
|
|
+ resources, or specify =all here.
|
|
+
|
|
reboot= [BUGS=X86-32,BUGS=ARM,BUGS=IA-64] Rebooting mode
|
|
Format: <reboot_mode>[,<reboot_mode2>[,...]]
|
|
See arch/*/kernel/reboot.c or arch/*/kernel/process.c
|
|
--- head-2011-03-11.orig/drivers/acpi/pci_root.c 2011-03-11 10:41:54.000000000 +0100
|
|
+++ head-2011-03-11/drivers/acpi/pci_root.c 2011-01-31 14:31:27.000000000 +0100
|
|
@@ -448,6 +448,41 @@ out:
|
|
}
|
|
EXPORT_SYMBOL(acpi_pci_osc_control_set);
|
|
|
|
+#ifdef CONFIG_PCI_GUESTDEV
|
|
+#include <linux/sysfs.h>
|
|
+
|
|
+static ssize_t seg_show(struct device *dev,
|
|
+ struct device_attribute *attr, char *buf)
|
|
+{
|
|
+ struct list_head *entry;
|
|
+
|
|
+ list_for_each(entry, &acpi_pci_roots) {
|
|
+ struct acpi_pci_root *root;
|
|
+ root = list_entry(entry, struct acpi_pci_root, node);
|
|
+ if (&root->device->dev == dev)
|
|
+ return sprintf(buf, "%04x\n", root->segment);
|
|
+ }
|
|
+ return 0;
|
|
+}
|
|
+static DEVICE_ATTR(seg, 0444, seg_show, NULL);
|
|
+
|
|
+static ssize_t bbn_show(struct device *dev,
|
|
+ struct device_attribute *attr, char *buf)
|
|
+{
|
|
+ struct list_head *entry;
|
|
+
|
|
+ list_for_each(entry, &acpi_pci_roots) {
|
|
+ struct acpi_pci_root *root;
|
|
+ root = list_entry(entry, struct acpi_pci_root, node);
|
|
+ if (&root->device->dev == dev)
|
|
+ return sprintf(buf, "%02x\n",
|
|
+ (unsigned int)root->secondary.start);
|
|
+ }
|
|
+ return 0;
|
|
+}
|
|
+static DEVICE_ATTR(bbn, 0444, bbn_show, NULL);
|
|
+#endif
|
|
+
|
|
static int __devinit acpi_pci_root_add(struct acpi_device *device)
|
|
{
|
|
unsigned long long segment, bus;
|
|
@@ -599,6 +634,13 @@ static int __devinit acpi_pci_root_add(s
|
|
"ACPI _OSC request failed (code %d)\n", status);
|
|
}
|
|
|
|
+#ifdef CONFIG_PCI_GUESTDEV
|
|
+ if (device_create_file(&device->dev, &dev_attr_seg))
|
|
+ dev_warn(&device->dev, "could not create seg attr\n");
|
|
+ if (device_create_file(&device->dev, &dev_attr_bbn))
|
|
+ dev_warn(&device->dev, "could not create bbn attr\n");
|
|
+#endif
|
|
+
|
|
pci_acpi_add_bus_pm_notifier(device, root->bus);
|
|
if (device->wakeup.flags.run_wake)
|
|
device_set_run_wake(root->bus->bridge, true);
|
|
@@ -646,3 +688,31 @@ static int __init acpi_pci_root_init(voi
|
|
}
|
|
|
|
subsys_initcall(acpi_pci_root_init);
|
|
+
|
|
+#ifdef CONFIG_PCI_GUESTDEV
|
|
+int acpi_pci_get_root_seg_bbn(char *hid, char *uid, int *seg, int *bbn)
|
|
+{
|
|
+ struct list_head *entry;
|
|
+
|
|
+ list_for_each(entry, &acpi_pci_roots) {
|
|
+ struct acpi_pci_root *root;
|
|
+
|
|
+ root = list_entry(entry, struct acpi_pci_root, node);
|
|
+ if (strcmp(acpi_device_hid(root->device), hid))
|
|
+ continue;
|
|
+
|
|
+ if (!root->device->pnp.unique_id) {
|
|
+ if (strlen(uid))
|
|
+ continue;
|
|
+ } else {
|
|
+ if (strcmp(root->device->pnp.unique_id, uid))
|
|
+ continue;
|
|
+ }
|
|
+
|
|
+ *seg = (int)root->segment;
|
|
+ *bbn = (int)root->secondary.start;
|
|
+ return TRUE;
|
|
+ }
|
|
+ return FALSE;
|
|
+}
|
|
+#endif
|
|
--- head-2011-03-11.orig/drivers/acpi/scan.c 2011-03-11 10:41:54.000000000 +0100
|
|
+++ head-2011-03-11/drivers/acpi/scan.c 2011-01-31 14:31:27.000000000 +0100
|
|
@@ -175,6 +175,16 @@ acpi_device_hid_show(struct device *dev,
|
|
}
|
|
static DEVICE_ATTR(hid, 0444, acpi_device_hid_show, NULL);
|
|
|
|
+#ifdef CONFIG_PCI_GUESTDEV
|
|
+static ssize_t
|
|
+acpi_device_uid_show(struct device *dev, struct device_attribute *attr, char *buf) {
|
|
+ struct acpi_device *acpi_dev = to_acpi_device(dev);
|
|
+
|
|
+ return sprintf(buf, "%s\n", acpi_dev->pnp.unique_id);
|
|
+}
|
|
+static DEVICE_ATTR(uid, 0444, acpi_device_uid_show, NULL);
|
|
+#endif
|
|
+
|
|
static ssize_t
|
|
acpi_device_path_show(struct device *dev, struct device_attribute *attr, char *buf) {
|
|
struct acpi_device *acpi_dev = to_acpi_device(dev);
|
|
@@ -217,6 +227,13 @@ static int acpi_device_setup_files(struc
|
|
goto end;
|
|
}
|
|
|
|
+#ifdef CONFIG_PCI_GUESTDEV
|
|
+ if(dev->pnp.unique_id) {
|
|
+ result = device_create_file(&dev->dev, &dev_attr_uid);
|
|
+ if(result)
|
|
+ goto end;
|
|
+ }
|
|
+#endif
|
|
/*
|
|
* If device has _EJ0, 'eject' file is created that is used to trigger
|
|
* hot-removal function from userland.
|
|
@@ -280,6 +297,9 @@ static void acpi_free_ids(struct acpi_de
|
|
kfree(id->id);
|
|
kfree(id);
|
|
}
|
|
+#ifdef CONFIG_PCI_GUESTDEV
|
|
+ kfree(device->pnp.unique_id);
|
|
+#endif
|
|
}
|
|
|
|
static void acpi_device_release(struct device *dev)
|
|
@@ -1131,6 +1151,11 @@ static void acpi_device_set_id(struct ac
|
|
for (i = 0; i < cid_list->count; i++)
|
|
acpi_add_id(device, cid_list->ids[i].string);
|
|
}
|
|
+#ifdef CONFIG_PCI_GUESTDEV
|
|
+ if (info->valid & ACPI_VALID_UID)
|
|
+ device->pnp.unique_id = kstrdup(info->unique_id.string,
|
|
+ GFP_KERNEL);
|
|
+#endif
|
|
if (info->valid & ACPI_VALID_ADR) {
|
|
device->pnp.bus_address = info->address;
|
|
device->flags.bus_address = 1;
|
|
--- head-2011-03-11.orig/drivers/pci/Kconfig 2011-03-11 10:41:54.000000000 +0100
|
|
+++ head-2011-03-11/drivers/pci/Kconfig 2011-01-31 14:31:27.000000000 +0100
|
|
@@ -31,6 +31,20 @@ config PCI_DEBUG
|
|
|
|
When in doubt, say N.
|
|
|
|
+config PCI_GUESTDEV
|
|
+ bool "PCI Device Reservation for Passthrough"
|
|
+ depends on PCI && ACPI && XEN
|
|
+ default y
|
|
+ help
|
|
+ Say Y here if you want to reserve PCI device for passthrough.
|
|
+
|
|
+config PCI_IOMULTI
|
|
+ tristate "PCI Device IO Multiplex for Passthrough"
|
|
+ depends on PCI && ACPI && XEN
|
|
+ default y
|
|
+ help
|
|
+ Say Y here if you need io multiplexing.
|
|
+
|
|
config PCI_STUB
|
|
tristate "PCI Stub driver"
|
|
depends on PCI
|
|
--- head-2011-03-11.orig/drivers/pci/Makefile 2011-03-11 10:41:54.000000000 +0100
|
|
+++ head-2011-03-11/drivers/pci/Makefile 2011-01-31 14:31:28.000000000 +0100
|
|
@@ -7,6 +7,10 @@ obj-y += access.o bus.o probe.o remove.
|
|
irq.o vpd.o
|
|
obj-$(CONFIG_PROC_FS) += proc.o
|
|
obj-$(CONFIG_SYSFS) += slot.o
|
|
+obj-$(CONFIG_PCI_GUESTDEV) += guestdev.o
|
|
+obj-$(CONFIG_PCI_IOMULTI) += pci-iomul.o
|
|
+iomul-$(CONFIG_PCI_IOMULTI) := iomulti.o
|
|
+obj-y += $(iomul-y) $(iomul-m)
|
|
|
|
obj-$(CONFIG_PCI_QUIRKS) += quirks.o
|
|
|
|
--- /dev/null 1970-01-01 00:00:00.000000000 +0000
|
|
+++ head-2011-03-11/drivers/pci/guestdev.c 2011-01-31 14:31:28.000000000 +0100
|
|
@@ -0,0 +1,880 @@
|
|
+/*
|
|
+ * 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);
|
|
+ pr_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);
|
|
+
|
|
--- /dev/null 1970-01-01 00:00:00.000000000 +0000
|
|
+++ head-2011-03-11/drivers/pci/iomulti.c 2011-01-31 14:31:28.000000000 +0100
|
|
@@ -0,0 +1,898 @@
|
|
+/*
|
|
+ * This program is free software; you can redistribute it and/or modify
|
|
+ * it under the terms of the GNU General Public License as published by
|
|
+ * the Free Software Foundation; either version 2 of the License, or
|
|
+ * (at your option) any later version.
|
|
+ *
|
|
+ * This program 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 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, 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
|
+ *
|
|
+ * Copyright (c) 2009 Isaku Yamahata
|
|
+ * VA Linux Systems Japan K.K.
|
|
+ */
|
|
+
|
|
+#include "iomulti.h"
|
|
+#include "pci.h"
|
|
+#include <linux/module.h>
|
|
+#include <linux/sort.h>
|
|
+#include <asm/setup.h>
|
|
+
|
|
+#define PCI_BUS_MAX 255
|
|
+#define PCI_DEV_MAX 31
|
|
+
|
|
+/* see pci_resource_len */
|
|
+static inline resource_size_t pci_iomul_len(const struct resource* r)
|
|
+{
|
|
+ if (r->start == 0 && r->start == r->end)
|
|
+ return 0;
|
|
+ return r->end - r->start + 1;
|
|
+}
|
|
+
|
|
+#define ROUND_UP(x, a) (((x) + (a) - 1) & ~((a) - 1))
|
|
+/* stolen from pbus_size_io() */
|
|
+static unsigned long pdev_size_io(struct pci_dev *pdev)
|
|
+{
|
|
+ unsigned long size = 0, size1 = 0;
|
|
+ int i;
|
|
+
|
|
+ for (i = 0; i < PCI_NUM_RESOURCES; i++) {
|
|
+ struct resource *r = &pdev->resource[i];
|
|
+ unsigned long r_size;
|
|
+
|
|
+ if (!(r->flags & IORESOURCE_IO))
|
|
+ continue;
|
|
+
|
|
+ r_size = r->end - r->start + 1;
|
|
+
|
|
+ if (r_size < 0x400)
|
|
+ /* Might be re-aligned for ISA */
|
|
+ size += r_size;
|
|
+ else
|
|
+ size1 += r_size;
|
|
+ }
|
|
+
|
|
+/* To be fixed in 2.5: we should have sort of HAVE_ISA
|
|
+ flag in the struct pci_bus. */
|
|
+#if defined(CONFIG_ISA) || defined(CONFIG_EISA)
|
|
+ size = (size & 0xff) + ((size & ~0xffUL) << 2);
|
|
+#endif
|
|
+ size = ROUND_UP(size + size1, 4096);
|
|
+ return size;
|
|
+}
|
|
+
|
|
+/*
|
|
+ * primary bus number of PCI-PCI bridge in switch on which
|
|
+ * this slots sits.
|
|
+ * i.e. the primary bus number of PCI-PCI bridge of downstream port
|
|
+ * or root port in switch.
|
|
+ * the secondary bus number of PCI-PCI bridge of upstream port
|
|
+ * in switch.
|
|
+ */
|
|
+static inline unsigned char pci_dev_switch_busnr(struct pci_dev *pdev)
|
|
+{
|
|
+ if (pci_find_capability(pdev, PCI_CAP_ID_EXP))
|
|
+ return pdev->bus->primary;
|
|
+ return pdev->bus->number;
|
|
+}
|
|
+
|
|
+static LIST_HEAD(switch_list);
|
|
+static DEFINE_MUTEX(switch_list_lock);
|
|
+
|
|
+/*****************************************************************************/
|
|
+int pci_iomul_switch_io_allocated(const struct pci_iomul_switch *sw)
|
|
+{
|
|
+ return !(sw->io_base == 0 || sw->io_base > sw->io_limit);
|
|
+}
|
|
+EXPORT_SYMBOL_GPL(pci_iomul_switch_io_allocated);
|
|
+
|
|
+static struct pci_iomul_switch *pci_iomul_find_switch_locked(int segment,
|
|
+ uint8_t bus)
|
|
+{
|
|
+ struct pci_iomul_switch *sw;
|
|
+
|
|
+ BUG_ON(!mutex_is_locked(&switch_list_lock));
|
|
+ list_for_each_entry(sw, &switch_list, list) {
|
|
+ if (sw->segment == segment && sw->bus == bus)
|
|
+ return sw;
|
|
+ }
|
|
+ return NULL;
|
|
+}
|
|
+
|
|
+static struct pci_iomul_slot *pci_iomul_find_slot_locked(
|
|
+ struct pci_iomul_switch *sw, uint8_t busnr, uint8_t dev)
|
|
+{
|
|
+ struct pci_iomul_slot *slot;
|
|
+
|
|
+ BUG_ON(!mutex_is_locked(&sw->lock));
|
|
+ list_for_each_entry(slot, &sw->slots, sibling) {
|
|
+ if (slot->bus == busnr && slot->dev == dev)
|
|
+ return slot;
|
|
+ }
|
|
+ return NULL;
|
|
+}
|
|
+
|
|
+static void pci_iomul_switch_get(struct pci_iomul_switch *sw);
|
|
+/* on successfull exit, sw->lock is locked for use slot and
|
|
+ * refrence count of sw is incremented.
|
|
+ */
|
|
+void pci_iomul_get_lock_switch(struct pci_dev *pdev,
|
|
+ struct pci_iomul_switch **swp,
|
|
+ struct pci_iomul_slot **slot)
|
|
+{
|
|
+ mutex_lock(&switch_list_lock);
|
|
+
|
|
+ *swp = pci_iomul_find_switch_locked(pci_domain_nr(pdev->bus),
|
|
+ pci_dev_switch_busnr(pdev));
|
|
+ if (*swp == NULL) {
|
|
+ *slot = NULL;
|
|
+ goto out;
|
|
+ }
|
|
+
|
|
+ mutex_lock(&(*swp)->lock);
|
|
+ *slot = pci_iomul_find_slot_locked(*swp, pdev->bus->number,
|
|
+ PCI_SLOT(pdev->devfn));
|
|
+ if (*slot == NULL) {
|
|
+ mutex_unlock(&(*swp)->lock);
|
|
+ *swp = NULL;
|
|
+ } else {
|
|
+ pci_iomul_switch_get(*swp);
|
|
+ }
|
|
+out:
|
|
+ mutex_unlock(&switch_list_lock);
|
|
+}
|
|
+EXPORT_SYMBOL_GPL(pci_iomul_get_lock_switch);
|
|
+
|
|
+static struct pci_iomul_switch *pci_iomul_switch_alloc(int segment,
|
|
+ uint8_t bus)
|
|
+{
|
|
+ struct pci_iomul_switch *sw;
|
|
+
|
|
+ BUG_ON(!mutex_is_locked(&switch_list_lock));
|
|
+
|
|
+ sw = kmalloc(sizeof(*sw), GFP_KERNEL);
|
|
+
|
|
+ mutex_init(&sw->lock);
|
|
+ kref_init(&sw->kref);
|
|
+ sw->io_region = NULL;
|
|
+ sw->count = 0;
|
|
+ sw->current_pdev = NULL;
|
|
+ sw->segment = segment;
|
|
+ sw->bus = bus;
|
|
+ sw->io_base = 0;
|
|
+ sw->io_limit = 0;
|
|
+ sw->func = NULL;
|
|
+ INIT_LIST_HEAD(&sw->slots);
|
|
+
|
|
+ return sw;
|
|
+}
|
|
+
|
|
+static void pci_iomul_switch_add_locked(struct pci_iomul_switch *sw)
|
|
+{
|
|
+ BUG_ON(!mutex_is_locked(&switch_list_lock));
|
|
+ list_add(&sw->list, &switch_list);
|
|
+}
|
|
+
|
|
+#if defined(CONFIG_HOTPLUG_PCI) || defined(CONFIG_HOTPLUG_PCI_MODULE)
|
|
+static void pci_iomul_switch_del_locked(struct pci_iomul_switch *sw)
|
|
+{
|
|
+ BUG_ON(!mutex_is_locked(&switch_list_lock));
|
|
+ list_del(&sw->list);
|
|
+}
|
|
+#endif
|
|
+
|
|
+static int __devinit pci_iomul_slot_init(struct pci_dev *pdev,
|
|
+ struct pci_iomul_slot *slot)
|
|
+{
|
|
+ u16 rpcap;
|
|
+ u16 cap;
|
|
+
|
|
+ rpcap = pci_find_capability(pdev, PCI_CAP_ID_EXP);
|
|
+ if (!rpcap) {
|
|
+ /* pci device isn't supported */
|
|
+ pr_info("PCI: sharing io port of non PCIe device %s "
|
|
+ "isn't supported. ignoring.\n",
|
|
+ pci_name(pdev));
|
|
+ return -ENOSYS;
|
|
+ }
|
|
+
|
|
+ pci_read_config_word(pdev, rpcap + PCI_CAP_FLAGS, &cap);
|
|
+ switch ((cap & PCI_EXP_FLAGS_TYPE) >> 4) {
|
|
+ case PCI_EXP_TYPE_RC_END:
|
|
+ pr_info("PCI: io port sharing of root complex integrated "
|
|
+ "endpoint %s isn't supported. ignoring.\n",
|
|
+ pci_name(pdev));
|
|
+ return -ENOSYS;
|
|
+ case PCI_EXP_TYPE_ENDPOINT:
|
|
+ case PCI_EXP_TYPE_LEG_END:
|
|
+ break;
|
|
+ default:
|
|
+ pr_info("PCI: io port sharing of non endpoint %s "
|
|
+ "doesn't make sense. ignoring.\n",
|
|
+ pci_name(pdev));
|
|
+ return -EINVAL;
|
|
+ }
|
|
+
|
|
+ kref_init(&slot->kref);
|
|
+ slot->switch_busnr = pci_dev_switch_busnr(pdev);
|
|
+ slot->segment = pci_domain_nr(pdev->bus);
|
|
+ slot->bus = pdev->bus->number;
|
|
+ slot->dev = PCI_SLOT(pdev->devfn);
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static struct pci_iomul_slot *__devinit
|
|
+pci_iomul_slot_alloc(struct pci_dev *pdev)
|
|
+{
|
|
+ struct pci_iomul_slot *slot;
|
|
+
|
|
+ slot = kzalloc(sizeof(*slot), GFP_KERNEL);
|
|
+ if (slot == NULL)
|
|
+ return NULL;
|
|
+
|
|
+ if (pci_iomul_slot_init(pdev, slot) != 0) {
|
|
+ kfree(slot);
|
|
+ return NULL;
|
|
+ }
|
|
+ return slot;
|
|
+}
|
|
+
|
|
+static void pci_iomul_slot_add_locked(struct pci_iomul_switch *sw,
|
|
+ struct pci_iomul_slot *slot)
|
|
+{
|
|
+ BUG_ON(!mutex_is_locked(&sw->lock));
|
|
+ list_add(&slot->sibling, &sw->slots);
|
|
+}
|
|
+
|
|
+#if defined(CONFIG_HOTPLUG_PCI) || defined(CONFIG_HOTPLUG_PCI_MODULE)
|
|
+static void pci_iomul_slot_del_locked(struct pci_iomul_switch *sw,
|
|
+ struct pci_iomul_slot *slot)
|
|
+{
|
|
+ BUG_ON(!mutex_is_locked(&sw->lock));
|
|
+ list_del(&slot->sibling);
|
|
+}
|
|
+#endif
|
|
+
|
|
+/*****************************************************************************/
|
|
+static int pci_get_sbd(const char *str,
|
|
+ int *segment__, uint8_t *bus__, uint8_t *dev__)
|
|
+{
|
|
+ int segment;
|
|
+ int bus;
|
|
+ int dev;
|
|
+
|
|
+ if (sscanf(str, "%x:%x:%x", &segment, &bus, &dev) != 3) {
|
|
+ if (sscanf(str, "%x:%x", &bus, &dev) == 2)
|
|
+ segment = 0;
|
|
+ else
|
|
+ return -EINVAL;
|
|
+ }
|
|
+
|
|
+ if (segment < 0 || INT_MAX <= segment)
|
|
+ return -EINVAL;
|
|
+ if (bus < 0 || PCI_BUS_MAX < bus)
|
|
+ return -EINVAL;
|
|
+ if (dev < 0 || PCI_DEV_MAX < dev)
|
|
+ return -EINVAL;
|
|
+
|
|
+ *segment__ = segment;
|
|
+ *bus__ = bus;
|
|
+ *dev__ = dev;
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static char iomul_param[COMMAND_LINE_SIZE];
|
|
+#define TOKEN_MAX 10 /* SSSS:BB:DD length is 10 */
|
|
+static int pci_is_iomul_dev_param(struct pci_dev *pdev)
|
|
+{
|
|
+ int len;
|
|
+ char *p;
|
|
+ char *next_str;
|
|
+
|
|
+ if (!strcmp(iomul_param, "all"))
|
|
+ return 1;
|
|
+ for (p = &iomul_param[0]; *p != '\0'; p = next_str + 1) {
|
|
+ next_str = strchr(p, ',');
|
|
+ if (next_str != NULL)
|
|
+ len = next_str - p;
|
|
+ else
|
|
+ len = strlen(p);
|
|
+
|
|
+ if (len > 0 && len <= TOKEN_MAX) {
|
|
+ char tmp[TOKEN_MAX+1];
|
|
+ int seg;
|
|
+ uint8_t bus;
|
|
+ uint8_t dev;
|
|
+
|
|
+ strlcpy(tmp, p, len);
|
|
+ if (pci_get_sbd(tmp, &seg, &bus, &dev) == 0 &&
|
|
+ pci_domain_nr(pdev->bus) == seg &&
|
|
+ pdev->bus->number == bus &&
|
|
+ PCI_SLOT(pdev->devfn) == dev)
|
|
+ return 1;
|
|
+ }
|
|
+ if (next_str == NULL)
|
|
+ break;
|
|
+ }
|
|
+
|
|
+ /* check guestdev=<device>+iomul option */
|
|
+ return pci_is_iomuldev(pdev);
|
|
+}
|
|
+
|
|
+/*
|
|
+ * Format: [<segment>:]<bus>:<dev>[,[<segment>:]<bus>:<dev>[,...]
|
|
+ */
|
|
+static int __init pci_iomul_param_setup(char *str)
|
|
+{
|
|
+ if (strlen(str) >= COMMAND_LINE_SIZE)
|
|
+ return 0;
|
|
+
|
|
+ /* parse it after pci bus scanning */
|
|
+ strlcpy(iomul_param, str, sizeof(iomul_param));
|
|
+ return 1;
|
|
+}
|
|
+__setup("guestiomuldev=", pci_iomul_param_setup);
|
|
+
|
|
+/*****************************************************************************/
|
|
+static void __devinit pci_iomul_set_bridge_io_window(struct pci_dev *bridge,
|
|
+ uint32_t io_base,
|
|
+ uint32_t io_limit)
|
|
+{
|
|
+ uint16_t l;
|
|
+ uint32_t upper16;
|
|
+
|
|
+ io_base >>= 12;
|
|
+ io_base <<= 4;
|
|
+ io_limit >>= 12;
|
|
+ io_limit <<= 4;
|
|
+ l = (io_base & 0xff) | ((io_limit & 0xff) << 8);
|
|
+ upper16 = ((io_base & 0xffff00) >> 8) |
|
|
+ (((io_limit & 0xffff00) >> 8) << 16);
|
|
+
|
|
+ /* Temporarily disable the I/O range before updating PCI_IO_BASE. */
|
|
+ pci_write_config_dword(bridge, PCI_IO_BASE_UPPER16, 0x0000ffff);
|
|
+ /* Update lower 16 bits of I/O base/limit. */
|
|
+ pci_write_config_word(bridge, PCI_IO_BASE, l);
|
|
+ /* Update upper 16 bits of I/O base/limit. */
|
|
+ pci_write_config_dword(bridge, PCI_IO_BASE_UPPER16, upper16);
|
|
+}
|
|
+
|
|
+static void __devinit pci_disable_bridge_io_window(struct pci_dev *bridge)
|
|
+{
|
|
+ /* set base = 0xffffff limit = 0x0 */
|
|
+ pci_iomul_set_bridge_io_window(bridge, 0xffffff, 0);
|
|
+}
|
|
+
|
|
+static int __devinit pci_iomul_func_scan(struct pci_dev *pdev,
|
|
+ struct pci_iomul_slot *slot,
|
|
+ uint8_t func)
|
|
+{
|
|
+ struct pci_iomul_func *f;
|
|
+ unsigned int i;
|
|
+
|
|
+ f = kzalloc(sizeof(*f), GFP_KERNEL);
|
|
+ if (f == NULL)
|
|
+ return -ENOMEM;
|
|
+
|
|
+ f->segment = slot->segment;
|
|
+ f->bus = slot->bus;
|
|
+ f->devfn = PCI_DEVFN(slot->dev, func);
|
|
+ f->io_size = pdev_size_io(pdev);
|
|
+
|
|
+ for (i = 0; i < PCI_NUM_BARS; i++) {
|
|
+ if (!(pci_resource_flags(pdev, i) & IORESOURCE_IO))
|
|
+ continue;
|
|
+ if (pci_resource_len(pdev, i) == 0)
|
|
+ continue;
|
|
+
|
|
+ f->io_bar |= 1 << i;
|
|
+ f->resource[i] = pdev->resource[i];
|
|
+ }
|
|
+
|
|
+ if (f->io_bar)
|
|
+ slot->func[func] = f;
|
|
+ else
|
|
+ kfree(f);
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+/*
|
|
+ * This is tricky part.
|
|
+ * fake PCI resource assignment routines by setting flags to 0.
|
|
+ * PCI resource allocate routines think the resource should
|
|
+ * be allocated by checking flags. 0 means this resource isn't used.
|
|
+ * See pbus_size_io() and pdev_sort_resources().
|
|
+ *
|
|
+ * After allocated resources, flags (IORESOURCE_IO) is exported
|
|
+ * to other part including user process.
|
|
+ * So we have to set flags to IORESOURCE_IO, but at the same time
|
|
+ * we must prevent those resources from reassigning when pci hot plug.
|
|
+ * To achieve that, set r->parent to dummy resource.
|
|
+ */
|
|
+static void __devinit pci_iomul_disable_resource(struct resource *r)
|
|
+{
|
|
+ /* don't allocate this resource */
|
|
+ r->flags = 0;
|
|
+}
|
|
+
|
|
+static void __devinit pci_iomul_reenable_resource(
|
|
+ struct resource *dummy_parent, struct resource *r)
|
|
+{
|
|
+ int ret;
|
|
+
|
|
+ dummy_parent->start = r->start;
|
|
+ dummy_parent->end = r->end;
|
|
+ dummy_parent->flags = r->flags;
|
|
+ dummy_parent->name = "PCI IOMUL dummy resource";
|
|
+
|
|
+ ret = request_resource(dummy_parent, r);
|
|
+ BUG_ON(ret);
|
|
+}
|
|
+
|
|
+static void __devinit pci_iomul_fixup_ioresource(struct pci_dev *pdev,
|
|
+ struct pci_iomul_func *func,
|
|
+ int reassign, int dealloc)
|
|
+{
|
|
+ uint8_t i;
|
|
+ struct resource *r;
|
|
+
|
|
+ pr_info("PCI: deallocating io resource[%s]. io size 0x%lx\n",
|
|
+ pci_name(pdev), func->io_size);
|
|
+ for (i = 0; i < PCI_NUM_BARS; i++) {
|
|
+ r = &pdev->resource[i];
|
|
+ if (!(func->io_bar & (1 << i)))
|
|
+ continue;
|
|
+
|
|
+ if (reassign) {
|
|
+ r->end -= r->start;
|
|
+ r->start = 0;
|
|
+ pci_update_resource(pdev, i);
|
|
+ func->resource[i] = *r;
|
|
+ }
|
|
+
|
|
+ if (dealloc)
|
|
+ /* don't allocate this resource */
|
|
+ pci_iomul_disable_resource(r);
|
|
+ }
|
|
+
|
|
+ /* parent PCI-PCI bridge */
|
|
+ if (!reassign)
|
|
+ return;
|
|
+ pdev = pdev->bus->self;
|
|
+ if ((pdev->class >> 8) == PCI_CLASS_BRIDGE_HOST)
|
|
+ return;
|
|
+ pci_disable_bridge_io_window(pdev);
|
|
+ for (i = 0; i < PCI_NUM_RESOURCES; i++) {
|
|
+ r = &pdev->resource[i];
|
|
+ if (!(r->flags & IORESOURCE_IO))
|
|
+ continue;
|
|
+
|
|
+ r->end -= r->start;
|
|
+ r->start = 0;
|
|
+ if (i < PCI_BRIDGE_RESOURCES)
|
|
+ pci_update_resource(pdev, i);
|
|
+ }
|
|
+}
|
|
+
|
|
+static void __devinit __quirk_iomul_dealloc_ioresource(
|
|
+ struct pci_iomul_switch *sw,
|
|
+ struct pci_dev *pdev, struct pci_iomul_slot *slot)
|
|
+{
|
|
+ struct pci_iomul_func *f;
|
|
+ struct pci_iomul_func *__f;
|
|
+
|
|
+ if (pci_iomul_func_scan(pdev, slot, PCI_FUNC(pdev->devfn)) != 0)
|
|
+ return;
|
|
+
|
|
+ f = slot->func[PCI_FUNC(pdev->devfn)];
|
|
+ if (f == NULL)
|
|
+ return;
|
|
+
|
|
+ __f = sw->func;
|
|
+ /* sw->io_base == 0 means that we are called at boot time.
|
|
+ * != 0 means that we are called by php after boot. */
|
|
+ if (sw->io_base == 0 &&
|
|
+ (__f == NULL || __f->io_size < f->io_size)) {
|
|
+ if (__f != NULL) {
|
|
+ struct pci_bus *__pbus;
|
|
+ struct pci_dev *__pdev;
|
|
+
|
|
+ __pbus = pci_find_bus(__f->segment, __f->bus);
|
|
+ BUG_ON(__pbus == NULL);
|
|
+ __pdev = pci_get_slot(__pbus, __f->devfn);
|
|
+ BUG_ON(__pdev == NULL);
|
|
+ pci_iomul_fixup_ioresource(__pdev, __f, 0, 1);
|
|
+ pci_dev_put(__pdev);
|
|
+ }
|
|
+
|
|
+ pci_iomul_fixup_ioresource(pdev, f, 1, 0);
|
|
+ sw->func = f;
|
|
+ } else {
|
|
+ pci_iomul_fixup_ioresource(pdev, f, 1, 1);
|
|
+ }
|
|
+}
|
|
+
|
|
+static void __devinit quirk_iomul_dealloc_ioresource(struct pci_dev *pdev)
|
|
+{
|
|
+ struct pci_iomul_switch *sw;
|
|
+ struct pci_iomul_slot *slot;
|
|
+
|
|
+ if (pdev->hdr_type != PCI_HEADER_TYPE_NORMAL)
|
|
+ return;
|
|
+ if ((pdev->class >> 8) == PCI_CLASS_BRIDGE_HOST)
|
|
+ return; /* PCI Host Bridge isn't a target device */
|
|
+ if (!pci_is_iomul_dev_param(pdev))
|
|
+ return;
|
|
+
|
|
+ mutex_lock(&switch_list_lock);
|
|
+ sw = pci_iomul_find_switch_locked(pci_domain_nr(pdev->bus),
|
|
+ pci_dev_switch_busnr(pdev));
|
|
+ if (sw == NULL) {
|
|
+ sw = pci_iomul_switch_alloc(pci_domain_nr(pdev->bus),
|
|
+ pci_dev_switch_busnr(pdev));
|
|
+ if (sw == NULL) {
|
|
+ mutex_unlock(&switch_list_lock);
|
|
+ pr_warn("PCI: can't allocate memory"
|
|
+ "for sw of IO multiplexing %s",
|
|
+ pci_name(pdev));
|
|
+ return;
|
|
+ }
|
|
+ pci_iomul_switch_add_locked(sw);
|
|
+ }
|
|
+ pci_iomul_switch_get(sw);
|
|
+ mutex_unlock(&switch_list_lock);
|
|
+
|
|
+ mutex_lock(&sw->lock);
|
|
+ slot = pci_iomul_find_slot_locked(sw, pdev->bus->number,
|
|
+ PCI_SLOT(pdev->devfn));
|
|
+ if (slot == NULL) {
|
|
+ slot = pci_iomul_slot_alloc(pdev);
|
|
+ if (slot == NULL) {
|
|
+ mutex_unlock(&sw->lock);
|
|
+ pci_iomul_switch_put(sw);
|
|
+ pr_warn("PCI: can't allocate memory "
|
|
+ "for IO multiplexing %s", pci_name(pdev));
|
|
+ return;
|
|
+ }
|
|
+ pci_iomul_slot_add_locked(sw, slot);
|
|
+ }
|
|
+
|
|
+ pr_info("PCI: disable device and release io resource[%s].\n",
|
|
+ pci_name(pdev));
|
|
+ pci_disable_device(pdev);
|
|
+
|
|
+ __quirk_iomul_dealloc_ioresource(sw, pdev, slot);
|
|
+
|
|
+ mutex_unlock(&sw->lock);
|
|
+ pci_iomul_switch_put(sw);
|
|
+}
|
|
+DECLARE_PCI_FIXUP_HEADER(PCI_ANY_ID, PCI_ANY_ID,
|
|
+ quirk_iomul_dealloc_ioresource);
|
|
+
|
|
+static void __devinit pci_iomul_read_bridge_io(struct pci_iomul_switch *sw)
|
|
+{
|
|
+ struct pci_iomul_func *f = sw->func;
|
|
+
|
|
+ struct pci_bus *pbus;
|
|
+ struct pci_dev *pdev;
|
|
+ struct pci_dev *bridge;
|
|
+
|
|
+ uint16_t l;
|
|
+ uint16_t base_upper16;
|
|
+ uint16_t limit_upper16;
|
|
+ uint32_t io_base;
|
|
+ uint32_t io_limit;
|
|
+
|
|
+ pbus = pci_find_bus(f->segment, f->bus);
|
|
+ BUG_ON(pbus == NULL);
|
|
+
|
|
+ pdev = pci_get_slot(pbus, f->devfn);
|
|
+ BUG_ON(pdev == NULL);
|
|
+
|
|
+ bridge = pdev->bus->self;
|
|
+ pci_read_config_word(bridge, PCI_IO_BASE, &l);
|
|
+ pci_read_config_word(bridge, PCI_IO_BASE_UPPER16, &base_upper16);
|
|
+ pci_read_config_word(bridge, PCI_IO_LIMIT_UPPER16, &limit_upper16);
|
|
+
|
|
+ io_base = (l & 0xf0) | ((uint32_t)base_upper16 << 8);
|
|
+ io_base <<= 8;
|
|
+ io_limit = (l >> 8) | ((uint32_t)limit_upper16 << 8);
|
|
+ io_limit <<= 8;
|
|
+ io_limit |= 0xfff;
|
|
+
|
|
+ sw->io_base = io_base;
|
|
+ sw->io_limit = io_limit;
|
|
+
|
|
+ pci_dev_put(pdev);
|
|
+ pr_info("PCI: bridge %s base 0x%x limit 0x%x\n",
|
|
+ pci_name(bridge), sw->io_base, sw->io_limit);
|
|
+}
|
|
+
|
|
+static void __devinit pci_iomul_setup_brige(struct pci_dev *bridge,
|
|
+ uint32_t io_base,
|
|
+ uint32_t io_limit)
|
|
+{
|
|
+ uint16_t cmd;
|
|
+
|
|
+ if ((bridge->class >> 8) == PCI_CLASS_BRIDGE_HOST)
|
|
+ return;
|
|
+
|
|
+ pci_iomul_set_bridge_io_window(bridge, io_base, io_limit);
|
|
+
|
|
+ /* and forcibly enables IO */
|
|
+ pci_read_config_word(bridge, PCI_COMMAND, &cmd);
|
|
+ if (!(cmd & PCI_COMMAND_IO)) {
|
|
+ cmd |= PCI_COMMAND_IO;
|
|
+ pr_info("PCI: forcibly enabling IO %s\n", pci_name(bridge));
|
|
+ pci_write_config_word(bridge, PCI_COMMAND, cmd);
|
|
+ }
|
|
+}
|
|
+
|
|
+struct __bar {
|
|
+ unsigned long size;
|
|
+ uint8_t bar;
|
|
+};
|
|
+
|
|
+/* decending order */
|
|
+static int __devinit pci_iomul_bar_cmp(const void *lhs__, const void *rhs__)
|
|
+{
|
|
+ const struct __bar *lhs = (struct __bar*)lhs__;
|
|
+ const struct __bar *rhs = (struct __bar*)rhs__;
|
|
+ return - (lhs->size - rhs->size);
|
|
+}
|
|
+
|
|
+static void __devinit pci_iomul_setup_dev(struct pci_dev *pdev,
|
|
+ struct pci_iomul_func *f,
|
|
+ uint32_t io_base)
|
|
+{
|
|
+ struct __bar bars[PCI_NUM_BARS];
|
|
+ int i;
|
|
+ uint8_t num_bars = 0;
|
|
+ struct resource *r;
|
|
+
|
|
+ pr_info("PCI: Forcibly assign IO %s from 0x%x\n",
|
|
+ pci_name(pdev), io_base);
|
|
+
|
|
+ for (i = 0; i < PCI_NUM_BARS; i++) {
|
|
+ if (!(f->io_bar & (1 << i)))
|
|
+ continue;
|
|
+
|
|
+ r = &f->resource[i];
|
|
+ bars[num_bars].size = pci_iomul_len(r);
|
|
+ bars[num_bars].bar = i;
|
|
+
|
|
+ num_bars++;
|
|
+ }
|
|
+
|
|
+ sort(bars, num_bars, sizeof(bars[0]), &pci_iomul_bar_cmp, NULL);
|
|
+
|
|
+ for (i = 0; i < num_bars; i++) {
|
|
+ struct resource *fr = &f->resource[bars[i].bar];
|
|
+ r = &pdev->resource[bars[i].bar];
|
|
+
|
|
+ BUG_ON(r->start != 0);
|
|
+ r->start += io_base;
|
|
+ r->end += io_base;
|
|
+
|
|
+ fr->start = r->start;
|
|
+ fr->end = r->end;
|
|
+
|
|
+ /* pci_update_resource() check flags. */
|
|
+ r->flags = fr->flags;
|
|
+ pci_update_resource(pdev, bars[i].bar);
|
|
+ pci_iomul_reenable_resource(&f->dummy_parent, r);
|
|
+
|
|
+ io_base += bars[i].size;
|
|
+ }
|
|
+}
|
|
+
|
|
+static void __devinit pci_iomul_release_io_resource(
|
|
+ struct pci_dev *pdev, struct pci_iomul_switch *sw,
|
|
+ struct pci_iomul_slot *slot, struct pci_iomul_func *f)
|
|
+{
|
|
+ int i;
|
|
+ struct resource *r;
|
|
+
|
|
+ for (i = 0; i < PCI_NUM_BARS; i++) {
|
|
+ if (pci_resource_flags(pdev, i) & IORESOURCE_IO &&
|
|
+ pdev->resource[i].parent != NULL) {
|
|
+ r = &pdev->resource[i];
|
|
+ f->resource[i] = *r;
|
|
+ release_resource(r);
|
|
+ pci_iomul_reenable_resource(&f->dummy_parent, r);
|
|
+ }
|
|
+ }
|
|
+
|
|
+ /* parent PCI-PCI bridge */
|
|
+ pdev = pdev->bus->self;
|
|
+ if ((pdev->class >> 8) != PCI_CLASS_BRIDGE_HOST) {
|
|
+ for (i = PCI_BRIDGE_RESOURCES; i < PCI_NUM_RESOURCES; i++) {
|
|
+ struct resource *parent = pdev->resource[i].parent;
|
|
+
|
|
+ if (pci_resource_flags(pdev, i) & IORESOURCE_IO &&
|
|
+ parent != NULL) {
|
|
+ r = &pdev->resource[i];
|
|
+
|
|
+ sw->io_resource.flags = r->flags;
|
|
+ sw->io_resource.start = sw->io_base;
|
|
+ sw->io_resource.end = sw->io_limit;
|
|
+ sw->io_resource.name = "PCI IO Multiplexer";
|
|
+
|
|
+ release_resource(r);
|
|
+ pci_iomul_reenable_resource(
|
|
+ &slot->dummy_parent[i - PCI_BRIDGE_RESOURCES], r);
|
|
+
|
|
+ if (request_resource(parent,
|
|
+ &sw->io_resource))
|
|
+ pr_err("PCI IOMul: can't allocate "
|
|
+ "resource. [0x%x, 0x%x]",
|
|
+ sw->io_base, sw->io_limit);
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+}
|
|
+
|
|
+static void __devinit quirk_iomul_reassign_ioresource(struct pci_dev *pdev)
|
|
+{
|
|
+ struct pci_iomul_switch *sw;
|
|
+ struct pci_iomul_slot *slot;
|
|
+ struct pci_iomul_func *sf;
|
|
+ struct pci_iomul_func *f;
|
|
+
|
|
+ pci_iomul_get_lock_switch(pdev, &sw, &slot);
|
|
+ if (sw == NULL || slot == NULL)
|
|
+ return;
|
|
+
|
|
+ if (sw->io_base == 0)
|
|
+ pci_iomul_read_bridge_io(sw);
|
|
+ if (!pci_iomul_switch_io_allocated(sw))
|
|
+ goto out;
|
|
+
|
|
+ sf = sw->func;
|
|
+ f = slot->func[PCI_FUNC(pdev->devfn)];
|
|
+ if (f == NULL)
|
|
+ /* (sf == NULL || f == NULL) case
|
|
+ * can happen when all the specified devices
|
|
+ * don't have io space
|
|
+ */
|
|
+ goto out;
|
|
+
|
|
+ if (sf != NULL &&
|
|
+ (pci_domain_nr(pdev->bus) != sf->segment ||
|
|
+ pdev->bus->number != sf->bus ||
|
|
+ PCI_SLOT(pdev->devfn) != PCI_SLOT(sf->devfn)) &&
|
|
+ PCI_FUNC(pdev->devfn) == 0) {
|
|
+ pci_iomul_setup_brige(pdev->bus->self,
|
|
+ sw->io_base, sw->io_limit);
|
|
+ }
|
|
+
|
|
+ BUG_ON(f->io_size > sw->io_limit - sw->io_base + 1);
|
|
+ if (/* f == sf */ sf != NULL &&
|
|
+ pci_domain_nr(pdev->bus) == sf->segment &&
|
|
+ pdev->bus->number == sf->bus &&
|
|
+ pdev->devfn == sf->devfn)
|
|
+ pci_iomul_release_io_resource(pdev, sw, slot, f);
|
|
+ else
|
|
+ pci_iomul_setup_dev(pdev, f, sw->io_base);
|
|
+
|
|
+out:
|
|
+ mutex_unlock(&sw->lock);
|
|
+ pci_iomul_switch_put(sw);
|
|
+}
|
|
+
|
|
+DECLARE_PCI_FIXUP_FINAL(PCI_ANY_ID, PCI_ANY_ID,
|
|
+ quirk_iomul_reassign_ioresource);
|
|
+
|
|
+/*****************************************************************************/
|
|
+#if defined(CONFIG_HOTPLUG_PCI) || defined(CONFIG_HOTPLUG_PCI_MODULE)
|
|
+static int __devinit __pci_iomul_notifier_del_device(struct pci_dev *pdev)
|
|
+{
|
|
+ struct pci_iomul_switch *sw;
|
|
+ struct pci_iomul_slot *slot;
|
|
+ int i;
|
|
+
|
|
+ pci_iomul_get_lock_switch(pdev, &sw, &slot);
|
|
+ if (sw == NULL || slot == NULL)
|
|
+ return 0;
|
|
+
|
|
+ if (sw->func == slot->func[PCI_FUNC(pdev->devfn)])
|
|
+ sw->func = NULL;
|
|
+ kfree(slot->func[PCI_FUNC(pdev->devfn)]);
|
|
+ slot->func[PCI_FUNC(pdev->devfn)] = NULL;
|
|
+ for (i = 0; i < PCI_NUM_FUNC; i++) {
|
|
+ if (slot->func[i] != NULL)
|
|
+ goto out;
|
|
+ }
|
|
+
|
|
+ pci_iomul_slot_del_locked(sw, slot);
|
|
+ pci_iomul_slot_put(slot);
|
|
+
|
|
+out:
|
|
+ mutex_unlock(&sw->lock);
|
|
+ pci_iomul_switch_put(sw);
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static int __devinit __pci_iomul_notifier_del_switch(struct pci_dev *pdev)
|
|
+{
|
|
+ struct pci_iomul_switch *sw;
|
|
+
|
|
+ mutex_lock(&switch_list_lock);
|
|
+ sw = pci_iomul_find_switch_locked(pci_domain_nr(pdev->bus),
|
|
+ pdev->bus->number);
|
|
+ if (sw == NULL)
|
|
+ goto out;
|
|
+
|
|
+ pci_iomul_switch_del_locked(sw);
|
|
+
|
|
+ mutex_lock(&sw->lock);
|
|
+ if (sw->io_resource.parent)
|
|
+ release_resource(&sw->io_resource);
|
|
+ sw->io_base = 0; /* to tell this switch is removed */
|
|
+ sw->io_limit = 0;
|
|
+ BUG_ON(!list_empty(&sw->slots));
|
|
+ mutex_unlock(&sw->lock);
|
|
+
|
|
+out:
|
|
+ mutex_unlock(&switch_list_lock);
|
|
+ pci_iomul_switch_put(sw);
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static int __devinit pci_iomul_notifier_del_device(struct pci_dev *pdev)
|
|
+{
|
|
+ int ret;
|
|
+ switch (pdev->hdr_type) {
|
|
+ case PCI_HEADER_TYPE_NORMAL:
|
|
+ ret = __pci_iomul_notifier_del_device(pdev);
|
|
+ break;
|
|
+ case PCI_HEADER_TYPE_BRIDGE:
|
|
+ ret = __pci_iomul_notifier_del_switch(pdev);
|
|
+ break;
|
|
+ default:
|
|
+ pr_warn("PCI IOMUL: device %s has unknown "
|
|
+ "header type %02x, ignoring.\n",
|
|
+ pci_name(pdev), pdev->hdr_type);
|
|
+ ret = -EIO;
|
|
+ break;
|
|
+ }
|
|
+ return ret;
|
|
+}
|
|
+
|
|
+static int __devinit pci_iomul_notifier(struct notifier_block *nb,
|
|
+ unsigned long action, void *data)
|
|
+{
|
|
+ struct device *dev = data;
|
|
+ struct pci_dev *pdev = to_pci_dev(dev);
|
|
+
|
|
+ switch (action) {
|
|
+ case BUS_NOTIFY_ADD_DEVICE:
|
|
+ quirk_iomul_reassign_ioresource(pdev);
|
|
+ break;
|
|
+ case BUS_NOTIFY_DEL_DEVICE:
|
|
+ return pci_iomul_notifier_del_device(pdev);
|
|
+ default:
|
|
+ /* nothing */
|
|
+ break;
|
|
+ }
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static struct notifier_block __devinitdata pci_iomul_nb = {
|
|
+ .notifier_call = pci_iomul_notifier,
|
|
+};
|
|
+
|
|
+static int __init pci_iomul_hotplug_init(void)
|
|
+{
|
|
+ bus_register_notifier(&pci_bus_type, &pci_iomul_nb);
|
|
+ return 0;
|
|
+}
|
|
+late_initcall(pci_iomul_hotplug_init);
|
|
+#endif
|
|
--- /dev/null 1970-01-01 00:00:00.000000000 +0000
|
|
+++ head-2011-03-11/drivers/pci/iomulti.h 2011-01-31 14:31:28.000000000 +0100
|
|
@@ -0,0 +1,122 @@
|
|
+/*
|
|
+ * This program is free software; you can redistribute it and/or modify
|
|
+ * it under the terms of the GNU General Public License as published by
|
|
+ * the Free Software Foundation; either version 2 of the License, or
|
|
+ * (at your option) any later version.
|
|
+ *
|
|
+ * This program 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 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, 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
|
+ *
|
|
+ * Copyright (c) 2009 Isaku Yamahata
|
|
+ * VA Linux Systems Japan K.K.
|
|
+ */
|
|
+
|
|
+#include <linux/kernel.h>
|
|
+#include <linux/list.h>
|
|
+#include <linux/pci.h>
|
|
+
|
|
+#define PCI_NUM_BARS 6
|
|
+#define PCI_NUM_FUNC 8
|
|
+
|
|
+struct pci_iomul_func {
|
|
+ int segment;
|
|
+ uint8_t bus;
|
|
+ uint8_t devfn;
|
|
+
|
|
+ /* only start and end are used */
|
|
+ unsigned long io_size;
|
|
+ uint8_t io_bar;
|
|
+ struct resource resource[PCI_NUM_BARS];
|
|
+ struct resource dummy_parent;
|
|
+};
|
|
+
|
|
+struct pci_iomul_switch {
|
|
+ struct list_head list; /* bus_list_lock protects */
|
|
+
|
|
+ /*
|
|
+ * This lock the following entry and following
|
|
+ * pci_iomul_slot/pci_iomul_func.
|
|
+ */
|
|
+ struct mutex lock;
|
|
+ struct kref kref;
|
|
+
|
|
+ struct resource io_resource;
|
|
+ struct resource *io_region;
|
|
+ unsigned int count;
|
|
+ struct pci_dev *current_pdev;
|
|
+
|
|
+ int segment;
|
|
+ uint8_t bus;
|
|
+
|
|
+ uint32_t io_base;
|
|
+ uint32_t io_limit;
|
|
+
|
|
+ /* func which has the largeset io size*/
|
|
+ struct pci_iomul_func *func;
|
|
+
|
|
+ struct list_head slots;
|
|
+};
|
|
+
|
|
+static inline void pci_iomul_switch_get(struct pci_iomul_switch *sw)
|
|
+{
|
|
+ kref_get(&sw->kref);
|
|
+}
|
|
+
|
|
+static inline void pci_iomul_switch_release(struct kref *kref)
|
|
+{
|
|
+ struct pci_iomul_switch *sw = container_of(kref,
|
|
+ struct pci_iomul_switch,
|
|
+ kref);
|
|
+ kfree(sw);
|
|
+}
|
|
+
|
|
+static inline void pci_iomul_switch_put(struct pci_iomul_switch *sw)
|
|
+{
|
|
+ kref_put(&sw->kref, &pci_iomul_switch_release);
|
|
+}
|
|
+
|
|
+struct pci_iomul_slot {
|
|
+ struct list_head sibling;
|
|
+ struct kref kref;
|
|
+ /*
|
|
+ * busnr
|
|
+ * when pcie, the primary busnr of the PCI-PCI bridge on which
|
|
+ * this devices sits.
|
|
+ */
|
|
+ uint8_t switch_busnr;
|
|
+ struct resource dummy_parent[PCI_NUM_RESOURCES - PCI_BRIDGE_RESOURCES];
|
|
+
|
|
+ /* device */
|
|
+ int segment;
|
|
+ uint8_t bus;
|
|
+ uint8_t dev;
|
|
+
|
|
+ struct pci_iomul_func *func[PCI_NUM_FUNC];
|
|
+};
|
|
+
|
|
+static inline void pci_iomul_slot_get(struct pci_iomul_slot *slot)
|
|
+{
|
|
+ kref_get(&slot->kref);
|
|
+}
|
|
+
|
|
+static inline void pci_iomul_slot_release(struct kref *kref)
|
|
+{
|
|
+ struct pci_iomul_slot *slot = container_of(kref, struct pci_iomul_slot,
|
|
+ kref);
|
|
+ kfree(slot);
|
|
+}
|
|
+
|
|
+static inline void pci_iomul_slot_put(struct pci_iomul_slot *slot)
|
|
+{
|
|
+ kref_put(&slot->kref, &pci_iomul_slot_release);
|
|
+}
|
|
+
|
|
+int pci_iomul_switch_io_allocated(const struct pci_iomul_switch *);
|
|
+void pci_iomul_get_lock_switch(struct pci_dev *, struct pci_iomul_switch **,
|
|
+ struct pci_iomul_slot **);
|
|
--- head-2011-03-11.orig/drivers/pci/pci.c 2011-03-11 10:41:54.000000000 +0100
|
|
+++ head-2011-03-11/drivers/pci/pci.c 2011-01-31 14:31:28.000000000 +0100
|
|
@@ -2984,6 +2984,13 @@ resource_size_t pci_specified_resource_a
|
|
*/
|
|
int pci_is_reassigndev(struct pci_dev *dev)
|
|
{
|
|
+#ifdef CONFIG_PCI_GUESTDEV
|
|
+ int result;
|
|
+
|
|
+ result = pci_is_guestdev_to_reassign(dev);
|
|
+ if (result)
|
|
+ return result;
|
|
+#endif /* CONFIG_PCI_GUESTDEV */
|
|
return (pci_specified_resource_alignment(dev) != 0);
|
|
}
|
|
|
|
--- head-2011-03-11.orig/drivers/pci/pci.h 2011-03-11 10:41:54.000000000 +0100
|
|
+++ head-2011-03-11/drivers/pci/pci.h 2011-01-31 14:31:28.000000000 +0100
|
|
@@ -350,4 +350,11 @@ static inline int pci_dev_specific_reset
|
|
}
|
|
#endif
|
|
|
|
+#ifdef CONFIG_PCI_GUESTDEV
|
|
+extern int pci_is_guestdev_to_reassign(struct pci_dev *dev);
|
|
+extern int pci_is_iomuldev(struct pci_dev *dev);
|
|
+#else
|
|
+#define pci_is_iomuldev(dev) 0
|
|
+#endif
|
|
+
|
|
#endif /* DRIVERS_PCI_H */
|
|
--- /dev/null 1970-01-01 00:00:00.000000000 +0000
|
|
+++ head-2011-03-11/drivers/pci/pci-iomul.c 2011-01-31 14:31:28.000000000 +0100
|
|
@@ -0,0 +1,437 @@
|
|
+/*
|
|
+ * This program is free software; you can redistribute it and/or modify
|
|
+ * it under the terms of the GNU General Public License as published by
|
|
+ * the Free Software Foundation; either version 2 of the License, or
|
|
+ * (at your option) any later version.
|
|
+ *
|
|
+ * This program 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 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, 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
|
+ *
|
|
+ * Copyright (c) 2009 Isaku Yamahata
|
|
+ * VA Linux Systems Japan K.K.
|
|
+ */
|
|
+
|
|
+#include "iomulti.h"
|
|
+#include <linux/fs.h>
|
|
+#include <linux/miscdevice.h>
|
|
+#include <linux/module.h>
|
|
+#include <asm/uaccess.h>
|
|
+#include <xen/public/iomulti.h>
|
|
+
|
|
+struct pci_iomul_data {
|
|
+ struct mutex lock;
|
|
+
|
|
+ struct pci_dev *pdev;
|
|
+ struct pci_iomul_switch *sw;
|
|
+ struct pci_iomul_slot *slot; /* slot::kref */
|
|
+ struct pci_iomul_func **func; /* when dereferencing,
|
|
+ sw->lock is necessary */
|
|
+};
|
|
+
|
|
+static int pci_iomul_func_ioport(struct pci_iomul_func *func,
|
|
+ uint8_t bar, uint64_t offset, int *port)
|
|
+{
|
|
+ if (!(func->io_bar & (1 << bar)))
|
|
+ return -EINVAL;
|
|
+
|
|
+ *port = func->resource[bar].start + offset;
|
|
+ if (*port < func->resource[bar].start ||
|
|
+ *port > func->resource[bar].end)
|
|
+ return -EINVAL;
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static inline int pci_iomul_valid(struct pci_iomul_data *iomul)
|
|
+{
|
|
+ BUG_ON(!mutex_is_locked(&iomul->lock));
|
|
+ BUG_ON(!mutex_is_locked(&iomul->sw->lock));
|
|
+ return pci_iomul_switch_io_allocated(iomul->sw) &&
|
|
+ *iomul->func != NULL;
|
|
+}
|
|
+
|
|
+static void __pci_iomul_enable_io(struct pci_dev *pdev)
|
|
+{
|
|
+ uint16_t cmd;
|
|
+
|
|
+ pci_dev_get(pdev);
|
|
+ pci_read_config_word(pdev, PCI_COMMAND, &cmd);
|
|
+ cmd |= PCI_COMMAND_IO;
|
|
+ pci_write_config_word(pdev, PCI_COMMAND, cmd);
|
|
+}
|
|
+
|
|
+static void __pci_iomul_disable_io(struct pci_iomul_data *iomul,
|
|
+ struct pci_dev *pdev)
|
|
+{
|
|
+ uint16_t cmd;
|
|
+
|
|
+ if (!pci_iomul_valid(iomul))
|
|
+ return;
|
|
+
|
|
+ pci_read_config_word(pdev, PCI_COMMAND, &cmd);
|
|
+ cmd &= ~PCI_COMMAND_IO;
|
|
+ pci_write_config_word(pdev, PCI_COMMAND, cmd);
|
|
+ pci_dev_put(pdev);
|
|
+}
|
|
+
|
|
+static int pci_iomul_open(struct inode *inode, struct file *filp)
|
|
+{
|
|
+ struct pci_iomul_data *iomul;
|
|
+ iomul = kmalloc(sizeof(*iomul), GFP_KERNEL);
|
|
+ if (iomul == NULL)
|
|
+ return -ENOMEM;
|
|
+
|
|
+ mutex_init(&iomul->lock);
|
|
+ iomul->pdev = NULL;
|
|
+ iomul->sw = NULL;
|
|
+ iomul->slot = NULL;
|
|
+ iomul->func = NULL;
|
|
+ filp->private_data = (void*)iomul;
|
|
+
|
|
+ return nonseekable_open(inode, filp);
|
|
+}
|
|
+
|
|
+static int pci_iomul_release(struct inode *inode, struct file *filp)
|
|
+{
|
|
+ struct pci_iomul_data *iomul =
|
|
+ (struct pci_iomul_data*)filp->private_data;
|
|
+ struct pci_iomul_switch *sw;
|
|
+ struct pci_iomul_slot *slot = NULL;
|
|
+
|
|
+ mutex_lock(&iomul->lock);
|
|
+ sw = iomul->sw;
|
|
+ slot = iomul->slot;
|
|
+ if (iomul->pdev != NULL) {
|
|
+ if (sw != NULL) {
|
|
+ mutex_lock(&sw->lock);
|
|
+ if (sw->current_pdev == iomul->pdev) {
|
|
+ __pci_iomul_disable_io(iomul,
|
|
+ sw->current_pdev);
|
|
+ sw->current_pdev = NULL;
|
|
+ }
|
|
+ sw->count--;
|
|
+ if (sw->count == 0) {
|
|
+ release_region(sw->io_region->start, sw->io_region->end - sw->io_region->start + 1);
|
|
+ sw->io_region = NULL;
|
|
+ }
|
|
+ mutex_unlock(&sw->lock);
|
|
+ }
|
|
+ pci_dev_put(iomul->pdev);
|
|
+ }
|
|
+ mutex_unlock(&iomul->lock);
|
|
+
|
|
+ if (slot != NULL)
|
|
+ pci_iomul_slot_put(slot);
|
|
+ if (sw != NULL)
|
|
+ pci_iomul_switch_put(sw);
|
|
+ kfree(iomul);
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static long pci_iomul_setup(struct pci_iomul_data *iomul,
|
|
+ struct pci_iomul_setup __user *arg)
|
|
+{
|
|
+ long error = 0;
|
|
+ struct pci_iomul_setup setup;
|
|
+ struct pci_iomul_switch *sw = NULL;
|
|
+ struct pci_iomul_slot *slot;
|
|
+ struct pci_bus *pbus;
|
|
+ struct pci_dev *pdev;
|
|
+
|
|
+ if (copy_from_user(&setup, arg, sizeof(setup)))
|
|
+ return -EFAULT;
|
|
+
|
|
+ pbus = pci_find_bus(setup.segment, setup.bus);
|
|
+ if (pbus == NULL)
|
|
+ return -ENODEV;
|
|
+ pdev = pci_get_slot(pbus, setup.dev);
|
|
+ if (pdev == NULL)
|
|
+ return -ENODEV;
|
|
+
|
|
+ mutex_lock(&iomul->lock);
|
|
+ if (iomul->sw != NULL) {
|
|
+ error = -EBUSY;
|
|
+ goto out0;
|
|
+ }
|
|
+
|
|
+ pci_iomul_get_lock_switch(pdev, &sw, &slot);
|
|
+ if (sw == NULL || slot == NULL) {
|
|
+ error = -ENODEV;
|
|
+ goto out0;
|
|
+ }
|
|
+ if (!pci_iomul_switch_io_allocated(sw)) {
|
|
+ error = -ENODEV;
|
|
+ goto out;
|
|
+ }
|
|
+
|
|
+ if (slot->func[setup.func] == NULL) {
|
|
+ error = -ENODEV;
|
|
+ goto out;
|
|
+ }
|
|
+
|
|
+ if (sw->count == 0) {
|
|
+ BUG_ON(sw->io_region != NULL);
|
|
+ sw->io_region =
|
|
+ request_region(sw->io_base,
|
|
+ sw->io_limit - sw->io_base + 1,
|
|
+ "PCI IO Multiplexer driver");
|
|
+ if (sw->io_region == NULL) {
|
|
+ mutex_unlock(&sw->lock);
|
|
+ error = -EBUSY;
|
|
+ goto out;
|
|
+ }
|
|
+ }
|
|
+ sw->count++;
|
|
+ pci_iomul_slot_get(slot);
|
|
+
|
|
+ iomul->pdev = pdev;
|
|
+ iomul->sw = sw;
|
|
+ iomul->slot = slot;
|
|
+ iomul->func = &slot->func[setup.func];
|
|
+
|
|
+out:
|
|
+ mutex_unlock(&sw->lock);
|
|
+out0:
|
|
+ mutex_unlock(&iomul->lock);
|
|
+ if (error != 0) {
|
|
+ if (sw != NULL)
|
|
+ pci_iomul_switch_put(sw);
|
|
+ pci_dev_put(pdev);
|
|
+ }
|
|
+ return error;
|
|
+}
|
|
+
|
|
+static int pci_iomul_lock(struct pci_iomul_data *iomul,
|
|
+ struct pci_iomul_switch **sw,
|
|
+ struct pci_iomul_func **func)
|
|
+{
|
|
+ mutex_lock(&iomul->lock);
|
|
+ *sw = iomul->sw;
|
|
+ if (*sw == NULL) {
|
|
+ mutex_unlock(&iomul->lock);
|
|
+ return -ENODEV;
|
|
+ }
|
|
+ mutex_lock(&(*sw)->lock);
|
|
+ if (!pci_iomul_valid(iomul)) {
|
|
+ mutex_unlock(&(*sw)->lock);
|
|
+ mutex_unlock(&iomul->lock);
|
|
+ return -ENODEV;
|
|
+ }
|
|
+ *func = *iomul->func;
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static long pci_iomul_disable_io(struct pci_iomul_data *iomul)
|
|
+{
|
|
+ long error = 0;
|
|
+ struct pci_iomul_switch *sw;
|
|
+ struct pci_iomul_func *dummy_func;
|
|
+ struct pci_dev *pdev;
|
|
+
|
|
+ if (pci_iomul_lock(iomul, &sw, &dummy_func) < 0)
|
|
+ return -ENODEV;
|
|
+
|
|
+ pdev = iomul->pdev;
|
|
+ if (pdev == NULL)
|
|
+ error = -ENODEV;
|
|
+
|
|
+ if (pdev != NULL && sw->current_pdev == pdev) {
|
|
+ __pci_iomul_disable_io(iomul, pdev);
|
|
+ sw->current_pdev = NULL;
|
|
+ }
|
|
+
|
|
+ mutex_unlock(&sw->lock);
|
|
+ mutex_unlock(&iomul->lock);
|
|
+ return error;
|
|
+}
|
|
+
|
|
+static void pci_iomul_switch_to(
|
|
+ struct pci_iomul_data *iomul, struct pci_iomul_switch *sw,
|
|
+ struct pci_dev *next_pdev)
|
|
+{
|
|
+ if (sw->current_pdev == next_pdev)
|
|
+ /* nothing to do */
|
|
+ return;
|
|
+
|
|
+ if (sw->current_pdev != NULL)
|
|
+ __pci_iomul_disable_io(iomul, sw->current_pdev);
|
|
+
|
|
+ __pci_iomul_enable_io(next_pdev);
|
|
+ sw->current_pdev = next_pdev;
|
|
+}
|
|
+
|
|
+static long pci_iomul_in(struct pci_iomul_data *iomul,
|
|
+ struct pci_iomul_in __user *arg)
|
|
+{
|
|
+ struct pci_iomul_in in;
|
|
+ struct pci_iomul_switch *sw;
|
|
+ struct pci_iomul_func *func;
|
|
+
|
|
+ long error = 0;
|
|
+ int port;
|
|
+ uint32_t value = 0;
|
|
+
|
|
+ if (copy_from_user(&in, arg, sizeof(in)))
|
|
+ return -EFAULT;
|
|
+
|
|
+ if (pci_iomul_lock(iomul, &sw, &func) < 0)
|
|
+ return -ENODEV;
|
|
+
|
|
+ error = pci_iomul_func_ioport(func, in.bar, in.offset, &port);
|
|
+ if (error)
|
|
+ goto out;
|
|
+
|
|
+ pci_iomul_switch_to(iomul, sw, iomul->pdev);
|
|
+ switch (in.size) {
|
|
+ case 4:
|
|
+ value = inl(port);
|
|
+ break;
|
|
+ case 2:
|
|
+ value = inw(port);
|
|
+ break;
|
|
+ case 1:
|
|
+ value = inb(port);
|
|
+ break;
|
|
+ default:
|
|
+ error = -EINVAL;
|
|
+ break;
|
|
+ }
|
|
+
|
|
+out:
|
|
+ mutex_unlock(&sw->lock);
|
|
+ mutex_unlock(&iomul->lock);
|
|
+
|
|
+ if (error == 0 && put_user(value, &arg->value))
|
|
+ return -EFAULT;
|
|
+ return error;
|
|
+}
|
|
+
|
|
+static long pci_iomul_out(struct pci_iomul_data *iomul,
|
|
+ struct pci_iomul_out __user *arg)
|
|
+{
|
|
+ struct pci_iomul_in out;
|
|
+ struct pci_iomul_switch *sw;
|
|
+ struct pci_iomul_func *func;
|
|
+
|
|
+ long error = 0;
|
|
+ int port;
|
|
+
|
|
+ if (copy_from_user(&out, arg, sizeof(out)))
|
|
+ return -EFAULT;
|
|
+
|
|
+ if (pci_iomul_lock(iomul, &sw, &func) < 0)
|
|
+ return -ENODEV;
|
|
+
|
|
+ error = pci_iomul_func_ioport(func, out.bar, out.offset, &port);
|
|
+ if (error)
|
|
+ goto out;
|
|
+
|
|
+ pci_iomul_switch_to(iomul, sw, iomul->pdev);
|
|
+ switch (out.size) {
|
|
+ case 4:
|
|
+ outl(out.value, port);
|
|
+ break;
|
|
+ case 2:
|
|
+ outw(out.value, port);
|
|
+ break;
|
|
+ case 1:
|
|
+ outb(out.value, port);
|
|
+ break;
|
|
+ default:
|
|
+ error = -EINVAL;
|
|
+ break;
|
|
+ }
|
|
+
|
|
+out:
|
|
+ mutex_unlock(&sw->lock);
|
|
+ mutex_unlock(&iomul->lock);
|
|
+ return error;
|
|
+}
|
|
+
|
|
+static long pci_iomul_ioctl(struct file *filp,
|
|
+ unsigned int cmd, unsigned long arg)
|
|
+{
|
|
+ long error;
|
|
+ struct pci_iomul_data *iomul =
|
|
+ (struct pci_iomul_data*)filp->private_data;
|
|
+
|
|
+ if (!capable(CAP_SYS_ADMIN) || !capable(CAP_SYS_RAWIO))
|
|
+ return -EPERM;
|
|
+
|
|
+ switch (cmd) {
|
|
+ case PCI_IOMUL_SETUP:
|
|
+ error = pci_iomul_setup(iomul,
|
|
+ (struct pci_iomul_setup __user *)arg);
|
|
+ break;
|
|
+ case PCI_IOMUL_DISABLE_IO:
|
|
+ error = pci_iomul_disable_io(iomul);
|
|
+ break;
|
|
+ case PCI_IOMUL_IN:
|
|
+ error = pci_iomul_in(iomul, (struct pci_iomul_in __user *)arg);
|
|
+ break;
|
|
+ case PCI_IOMUL_OUT:
|
|
+ error = pci_iomul_out(iomul,
|
|
+ (struct pci_iomul_out __user *)arg);
|
|
+ break;
|
|
+ default:
|
|
+ error = -ENOSYS;
|
|
+ break;
|
|
+ }
|
|
+
|
|
+ return error;
|
|
+}
|
|
+
|
|
+static const struct file_operations pci_iomul_fops = {
|
|
+ .owner = THIS_MODULE,
|
|
+
|
|
+ .open = pci_iomul_open,
|
|
+ .release = pci_iomul_release,
|
|
+
|
|
+ .unlocked_ioctl = pci_iomul_ioctl,
|
|
+};
|
|
+
|
|
+static struct miscdevice pci_iomul_miscdev = {
|
|
+ .minor = MISC_DYNAMIC_MINOR,
|
|
+ .name = "pci_iomul",
|
|
+ .nodename = "xen/pci_iomul",
|
|
+ .fops = &pci_iomul_fops,
|
|
+};
|
|
+
|
|
+static int __init pci_iomul_init(void)
|
|
+{
|
|
+ int error;
|
|
+
|
|
+ error = misc_register(&pci_iomul_miscdev);
|
|
+ if (error != 0) {
|
|
+ pr_alert("Couldn't register /dev/xen/pci_iomul");
|
|
+ return error;
|
|
+ }
|
|
+ pr_info("PCI IO multiplexer device installed\n");
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+#ifdef MODULE
|
|
+static void __exit pci_iomul_cleanup(void)
|
|
+{
|
|
+ misc_deregister(&pci_iomul_miscdev);
|
|
+}
|
|
+module_exit(pci_iomul_cleanup);
|
|
+#endif
|
|
+
|
|
+/*
|
|
+ * This must be called after pci fixup final which is called by
|
|
+ * device_initcall(pci_init).
|
|
+ */
|
|
+late_initcall(pci_iomul_init);
|
|
+
|
|
+MODULE_ALIAS("devname:xen/pci_iomul");
|
|
+MODULE_LICENSE("GPL");
|
|
+MODULE_AUTHOR("Isaku Yamahata <yamahata@valinux.co.jp>");
|
|
+MODULE_DESCRIPTION("PCI IO space multiplexing driver");
|
|
--- head-2011-03-11.orig/include/linux/acpi.h 2011-03-11 10:41:54.000000000 +0100
|
|
+++ head-2011-03-11/include/linux/acpi.h 2011-01-31 14:31:28.000000000 +0100
|
|
@@ -248,6 +248,8 @@ int acpi_check_region(resource_size_t st
|
|
|
|
int acpi_resources_are_enforced(void);
|
|
|
|
+int acpi_pci_get_root_seg_bbn(char *hid, char *uid, int *seg, int *bbn);
|
|
+
|
|
#ifdef CONFIG_PM_SLEEP
|
|
void __init acpi_no_s4_hw_signature(void);
|
|
void __init acpi_old_suspend_ordering(void);
|
|
--- head-2011-03-11.orig/include/linux/pci.h 2011-03-11 10:41:54.000000000 +0100
|
|
+++ head-2011-03-11/include/linux/pci.h 2011-01-31 14:31:28.000000000 +0100
|
|
@@ -1538,5 +1538,11 @@ int pci_vpd_find_tag(const u8 *buf, unsi
|
|
int pci_vpd_find_info_keyword(const u8 *buf, unsigned int off,
|
|
unsigned int len, const char *kw);
|
|
|
|
+#ifdef CONFIG_PCI_GUESTDEV
|
|
+int pci_is_guestdev(struct pci_dev *dev);
|
|
+#else
|
|
+#define pci_is_guestdev(dev) 0
|
|
+#endif
|
|
+
|
|
#endif /* __KERNEL__ */
|
|
#endif /* LINUX_PCI_H */
|
|
--- head-2011-03-11.orig/include/xen/Kbuild 2011-03-11 10:41:54.000000000 +0100
|
|
+++ head-2011-03-11/include/xen/Kbuild 2011-01-31 14:31:28.000000000 +0100
|
|
@@ -1,2 +1,3 @@
|
|
header-y += evtchn.h
|
|
header-y += privcmd.h
|
|
+header-y += public/
|
|
--- /dev/null 1970-01-01 00:00:00.000000000 +0000
|
|
+++ head-2011-03-11/include/xen/public/Kbuild 2011-01-31 14:31:28.000000000 +0100
|
|
@@ -0,0 +1 @@
|
|
+header-y += iomulti.h
|
|
--- /dev/null 1970-01-01 00:00:00.000000000 +0000
|
|
+++ head-2011-03-11/include/xen/public/iomulti.h 2011-01-31 14:31:28.000000000 +0100
|
|
@@ -0,0 +1,50 @@
|
|
+#ifndef __LINUX_PUBLIC_IOMULTI_H__
|
|
+#define __LINUX_PUBLIC_IOMULTI_H__
|
|
+/*
|
|
+ * This program is free software; you can redistribute it and/or modify
|
|
+ * it under the terms of the GNU General Public License as published by
|
|
+ * the Free Software Foundation; either version 2 of the License, or
|
|
+ * (at your option) any later version.
|
|
+ *
|
|
+ * This program 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 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, 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
|
+ *
|
|
+ * Copyright (c) 2009 Isaku Yamahata
|
|
+ * VA Linux Systems Japan K.K.
|
|
+ */
|
|
+
|
|
+struct pci_iomul_setup {
|
|
+ uint16_t segment;
|
|
+ uint8_t bus;
|
|
+ uint8_t dev;
|
|
+ uint8_t func;
|
|
+};
|
|
+
|
|
+struct pci_iomul_in {
|
|
+ uint8_t bar;
|
|
+ uint64_t offset;
|
|
+
|
|
+ uint8_t size;
|
|
+ uint32_t value;
|
|
+};
|
|
+
|
|
+struct pci_iomul_out {
|
|
+ uint8_t bar;
|
|
+ uint64_t offset;
|
|
+
|
|
+ uint8_t size;
|
|
+ uint32_t value;
|
|
+};
|
|
+
|
|
+#define PCI_IOMUL_SETUP _IOW ('P', 0, struct pci_iomul_setup)
|
|
+#define PCI_IOMUL_DISABLE_IO _IO ('P', 1)
|
|
+#define PCI_IOMUL_IN _IOWR('P', 2, struct pci_iomul_in)
|
|
+#define PCI_IOMUL_OUT _IOW ('P', 3, struct pci_iomul_out)
|
|
+
|
|
+#endif /* __LINUX_PUBLIC_IOMULTI_H__ */
|