qubes-linux-kernel/patches.xen/pci-guestdev
2010-07-07 13:12:45 +02:00

2642 lines
65 KiB
Plaintext

Subject: xen/dom0: Reserve devices for guest use
From: http://xenbits.xensource.com/linux-2.6.18-xen.hg (tip 898:ca12928cdafe)
Patch-mainline: n/a
jb: Added support for reassign_resources=all (bnc#574224).
jb: Used kzalloc() instead of all kmalloc()+memset() pairs.
Acked-by: jbeulich@novell.com
--- head-2010-04-29.orig/Documentation/kernel-parameters.txt 2010-04-29 09:29:51.000000000 +0200
+++ head-2010-04-29/Documentation/kernel-parameters.txt 2010-04-29 09:30:30.000000000 +0200
@@ -834,6 +834,24 @@ and is between 256 and 4096 characters.
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.
+
gvp11= [HW,SCSI]
hashdist= [KNL,NUMA] Large hashes allocated during boot
@@ -2183,6 +2201,10 @@ and is between 256 and 4096 characters.
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-2010-04-29.orig/drivers/acpi/pci_root.c 2010-04-29 09:29:51.000000000 +0200
+++ head-2010-04-29/drivers/acpi/pci_root.c 2010-04-15 09:39:25.000000000 +0200
@@ -419,6 +419,40 @@ 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", root->bus_nr);
+ }
+ 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;
@@ -530,6 +564,13 @@ static int __devinit acpi_pci_root_add(s
if (flags != base_flags)
acpi_pci_osc_support(root, flags);
+#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);
@@ -575,3 +616,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->bus_nr;
+ return TRUE;
+ }
+ return FALSE;
+}
+#endif
--- head-2010-04-29.orig/drivers/acpi/scan.c 2010-04-29 09:29:51.000000000 +0200
+++ head-2010-04-29/drivers/acpi/scan.c 2010-04-15 09:39:27.000000000 +0200
@@ -170,6 +170,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);
@@ -210,6 +220,13 @@ static int acpi_device_setup_files(struc
if (result)
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.
@@ -273,6 +290,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)
@@ -1096,6 +1116,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-2010-04-29.orig/drivers/pci/Kconfig 2010-04-29 09:29:51.000000000 +0200
+++ head-2010-04-29/drivers/pci/Kconfig 2010-03-24 13:55:21.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
+ bool "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-2010-04-29.orig/drivers/pci/Makefile 2010-04-29 09:29:51.000000000 +0200
+++ head-2010-04-29/drivers/pci/Makefile 2010-03-24 13:55:21.000000000 +0100
@@ -7,6 +7,8 @@ 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) += iomulti.o
obj-$(CONFIG_PCI_QUIRKS) += quirks.o
--- /dev/null 1970-01-01 00:00:00.000000000 +0000
+++ head-2010-04-29/drivers/pci/guestdev.c 2010-04-28 15:57:49.000000000 +0200
@@ -0,0 +1,887 @@
+/*
+ * 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 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:
+ printk(KERN_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);
+ printk(KERN_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) {
+ printk(KERN_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:
+ printk(KERN_ERR
+ "PCI: The format of the guestdev parameter is illegal. [%s]\n",
+ path_str);
+ ret_val = -EINVAL;
+ goto end;
+
+allocate_err_end:
+ printk(KERN_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) {
+ printk(KERN_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) {
+ printk(KERN_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;
+}
+
+#ifdef CONFIG_PCI_IOMULTI
+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);
+ printk(KERN_INFO
+ "PCI: Device does not exist. %s\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);
+ printk(KERN_INFO
+ "PCI: Device does not exist. %s\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);
+ printk(KERN_INFO "PCI: Device does not exist. %s\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-2010-04-29/drivers/pci/iomulti.c 2010-03-24 13:55:21.000000000 +0100
@@ -0,0 +1,1415 @@
+/*
+ * 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/fs.h>
+#include <linux/miscdevice.h>
+#include <linux/pci.h>
+#include <linux/sort.h>
+
+#include <asm/setup.h>
+#include <asm/uaccess.h>
+
+#include "pci.h"
+#include "iomulti.h"
+
+#define PCI_NUM_BARS 6
+#define PCI_BUS_MAX 255
+#define PCI_DEV_MAX 31
+#define PCI_FUNC_MAX 7
+#define PCI_NUM_FUNC 8
+
+/* 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;
+}
+
+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;
+};
+
+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 LIST_HEAD(switch_list);
+static DEFINE_MUTEX(switch_list_lock);
+
+/*****************************************************************************/
+static int inline pci_iomul_switch_io_allocated(
+ const struct pci_iomul_switch *sw)
+{
+ return !(sw->io_base == 0 || sw->io_base > sw->io_limit);
+}
+
+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.
+ */
+static 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);
+}
+
+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);
+}
+
+#ifdef CONFIG_HOTPLUG_PCI
+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 void pci_iomul_switch_get(struct pci_iomul_switch *sw)
+{
+ kref_get(&sw->kref);
+}
+
+static void pci_iomul_switch_release(struct kref *kref)
+{
+ struct pci_iomul_switch *sw = container_of(kref,
+ struct pci_iomul_switch,
+ kref);
+ kfree(sw);
+}
+
+static void pci_iomul_switch_put(struct pci_iomul_switch *sw)
+{
+ kref_put(&sw->kref, &pci_iomul_switch_release);
+}
+
+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 */
+ printk(KERN_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:
+ printk(KERN_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:
+ printk(KERN_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);
+}
+
+#ifdef CONFIG_HOTPLUG_PCI
+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 void pci_iomul_slot_get(struct pci_iomul_slot *slot)
+{
+ kref_get(&slot->kref);
+}
+
+static void pci_iomul_slot_release(struct kref *kref)
+{
+ struct pci_iomul_slot *slot = container_of(kref, struct pci_iomul_slot,
+ kref);
+ kfree(slot);
+}
+
+static void pci_iomul_slot_put(struct pci_iomul_slot *slot)
+{
+ kref_put(&slot->kref, &pci_iomul_slot_release);
+}
+
+/*****************************************************************************/
+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;
+
+ 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 guestcev=<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;
+
+ printk(KERN_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);
+ printk(KERN_WARNING
+ "PCI: can't allocate memory "
+ "for sw of IO mulplexing %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);
+ printk(KERN_WARNING "PCI: can't allocate memory "
+ "for IO mulplexing %s", pci_name(pdev));
+ return;
+ }
+ pci_iomul_slot_add_locked(sw, slot);
+ }
+
+ printk(KERN_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);
+ printk(KERN_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;
+ printk(KERN_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;
+
+ printk(KERN_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))
+ printk(KERN_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);
+
+/*****************************************************************************/
+#ifdef CONFIG_HOTPLUG_PCI
+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:
+ printk(KERN_WARNING "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 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
+
+/*****************************************************************************/
+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 0;
+}
+
+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, /* nonseekable_open */
+ .release = pci_iomul_release,
+
+ .unlocked_ioctl = pci_iomul_ioctl,
+};
+
+static struct miscdevice pci_iomul_miscdev = {
+ .minor = MISC_DYNAMIC_MINOR,
+ .name = "pci_iomul",
+ .fops = &pci_iomul_fops,
+};
+
+static int pci_iomul_init(void)
+{
+ int error;
+ error = misc_register(&pci_iomul_miscdev);
+ if (error != 0) {
+ printk(KERN_ALERT "Couldn't register /dev/misc/pci_iomul");
+ return error;
+ }
+ printk("PCI IO multiplexer device installed.\n");
+ return 0;
+}
+
+#if 0
+static void pci_iomul_cleanup(void)
+{
+ misc_deregister(&pci_iomul_miscdev);
+}
+#endif
+
+/*
+ * This must be called after pci fixup final which is called by
+ * device_initcall(pci_init).
+ */
+late_initcall(pci_iomul_init);
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Isaku Yamahata <yamahata@valinux.co.jp>");
+MODULE_DESCRIPTION("PCI IO space multiplexing driver");
--- /dev/null 1970-01-01 00:00:00.000000000 +0000
+++ head-2010-04-29/drivers/pci/iomulti.h 2010-03-24 13:55:21.000000000 +0100
@@ -0,0 +1,51 @@
+#ifndef PCI_IOMULTI_H
+#define PCI_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 /* PCI_IOMULTI_H */
--- head-2010-04-29.orig/drivers/pci/pci.c 2010-04-29 09:29:51.000000000 +0200
+++ head-2010-04-29/drivers/pci/pci.c 2010-04-29 09:30:41.000000000 +0200
@@ -2901,6 +2901,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-2010-04-29.orig/drivers/pci/pci.h 2010-04-29 09:29:51.000000000 +0200
+++ head-2010-04-29/drivers/pci/pci.h 2010-03-24 13:55:21.000000000 +0100
@@ -337,4 +337,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 */
--- head-2010-04-29.orig/include/linux/acpi.h 2010-04-29 09:29:51.000000000 +0200
+++ head-2010-04-29/include/linux/acpi.h 2010-03-24 13:55:21.000000000 +0100
@@ -247,6 +247,8 @@ int acpi_check_region(resource_size_t st
int acpi_check_mem_region(resource_size_t start, resource_size_t n,
const char *name);
+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-2010-04-29.orig/include/linux/pci.h 2010-04-29 09:29:51.000000000 +0200
+++ head-2010-04-29/include/linux/pci.h 2010-03-24 13:55:21.000000000 +0100
@@ -1504,5 +1504,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 */