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: {|}][,{|}[,...]] + Format of device path: [:]-.[-.[,...]][+iomul] + Format of sbdf: [:]:.[+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][,][,...] + Format of sbdf: [:]: + 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: [,[,...]] 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 + +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 +#include +#include +#include +#include +#include +#include +#include + +#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 +#include +#include + +#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=+iomul option */ + return pci_is_iomuldev(pdev); +} + +/* + * Format: [:]:[,[:]:[,...] + */ +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 +#include +#include + +#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 +#include +#include +#include +#include + +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 "); +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__ */