diff options
Diffstat (limited to 'arch/ia64/sn/kernel/io_acpi_init.c')
-rw-r--r-- | arch/ia64/sn/kernel/io_acpi_init.c | 314 |
1 files changed, 290 insertions, 24 deletions
diff --git a/arch/ia64/sn/kernel/io_acpi_init.c b/arch/ia64/sn/kernel/io_acpi_init.c index cb96b4ea7df..8c331ca6e5c 100644 --- a/arch/ia64/sn/kernel/io_acpi_init.c +++ b/arch/ia64/sn/kernel/io_acpi_init.c @@ -13,6 +13,7 @@ #include <asm/sn/sn_sal.h> #include "xtalk/hubdev.h" #include <linux/acpi.h> +#include <acpi/acnamesp.h> /* @@ -31,6 +32,12 @@ struct acpi_vendor_uuid sn_uuid = { 0xa2, 0x7c, 0x08, 0x00, 0x69, 0x13, 0xea, 0x51 }, }; +struct sn_pcidev_match { + u8 bus; + unsigned int devfn; + acpi_handle handle; +}; + /* * Perform the early IO init in PROM. */ @@ -119,9 +126,11 @@ sn_get_bussoft_ptr(struct pci_bus *bus) status = acpi_get_vendor_resource(handle, METHOD_NAME__CRS, &sn_uuid, &buffer); if (ACPI_FAILURE(status)) { - printk(KERN_ERR "get_acpi_pcibus_ptr: " - "get_acpi_bussoft_info() failed: %d\n", - status); + printk(KERN_ERR "%s: " + "acpi_get_vendor_resource() failed (0x%x) for: ", + __FUNCTION__, status); + acpi_ns_print_node_pathname(handle, NULL); + printk("\n"); return NULL; } resource = buffer.pointer; @@ -130,8 +139,8 @@ sn_get_bussoft_ptr(struct pci_bus *bus) if ((vendor->byte_length - sizeof(struct acpi_vendor_uuid)) != sizeof(struct pcibus_bussoft *)) { printk(KERN_ERR - "get_acpi_bussoft_ptr: Invalid vendor data " - "length %d\n", vendor->byte_length); + "%s: Invalid vendor data length %d\n", + __FUNCTION__, vendor->byte_length); kfree(buffer.pointer); return NULL; } @@ -143,34 +152,254 @@ sn_get_bussoft_ptr(struct pci_bus *bus) } /* - * sn_acpi_bus_fixup + * sn_extract_device_info - Extract the pcidev_info and the sn_irq_info + * pointers from the vendor resource using the + * provided acpi handle, and copy the structures + * into the argument buffers. */ -void -sn_acpi_bus_fixup(struct pci_bus *bus) +static int +sn_extract_device_info(acpi_handle handle, struct pcidev_info **pcidev_info, + struct sn_irq_info **sn_irq_info) { - struct pci_dev *pci_dev = NULL; - struct pcibus_bussoft *prom_bussoft_ptr; - extern void sn_common_bus_fixup(struct pci_bus *, - struct pcibus_bussoft *); + u64 addr; + struct acpi_buffer buffer = { ACPI_ALLOCATE_BUFFER, NULL }; + struct sn_irq_info *irq_info, *irq_info_prom; + struct pcidev_info *pcidev_ptr, *pcidev_prom_ptr; + struct acpi_resource *resource; + int ret = 0; + acpi_status status; + struct acpi_resource_vendor_typed *vendor; - if (!bus->parent) { /* If root bus */ - prom_bussoft_ptr = sn_get_bussoft_ptr(bus); - if (prom_bussoft_ptr == NULL) { + /* + * The pointer to this device's pcidev_info structure in + * the PROM, is in the vendor resource. + */ + status = acpi_get_vendor_resource(handle, METHOD_NAME__CRS, + &sn_uuid, &buffer); + if (ACPI_FAILURE(status)) { + printk(KERN_ERR + "%s: acpi_get_vendor_resource() failed (0x%x) for: ", + __FUNCTION__, status); + acpi_ns_print_node_pathname(handle, NULL); + printk("\n"); + return 1; + } + + resource = buffer.pointer; + vendor = &resource->data.vendor_typed; + if ((vendor->byte_length - sizeof(struct acpi_vendor_uuid)) != + sizeof(struct pci_devdev_info *)) { + printk(KERN_ERR + "%s: Invalid vendor data length: %d for: ", + __FUNCTION__, vendor->byte_length); + acpi_ns_print_node_pathname(handle, NULL); + printk("\n"); + ret = 1; + goto exit; + } + + pcidev_ptr = kzalloc(sizeof(struct pcidev_info), GFP_KERNEL); + if (!pcidev_ptr) + panic("%s: Unable to alloc memory for pcidev_info", __FUNCTION__); + + memcpy(&addr, vendor->byte_data, sizeof(struct pcidev_info *)); + pcidev_prom_ptr = __va(addr); + memcpy(pcidev_ptr, pcidev_prom_ptr, sizeof(struct pcidev_info)); + + /* Get the IRQ info */ + irq_info = kzalloc(sizeof(struct sn_irq_info), GFP_KERNEL); + if (!irq_info) + panic("%s: Unable to alloc memory for sn_irq_info", __FUNCTION__); + + if (pcidev_ptr->pdi_sn_irq_info) { + irq_info_prom = __va(pcidev_ptr->pdi_sn_irq_info); + memcpy(irq_info, irq_info_prom, sizeof(struct sn_irq_info)); + } + + *pcidev_info = pcidev_ptr; + *sn_irq_info = irq_info; + +exit: + kfree(buffer.pointer); + return ret; +} + +static unsigned int +get_host_devfn(acpi_handle device_handle, acpi_handle rootbus_handle) +{ + unsigned long adr; + acpi_handle child; + unsigned int devfn; + int function; + acpi_handle parent; + int slot; + acpi_status status; + + /* + * Do an upward search to find the root bus device, and + * obtain the host devfn from the previous child device. + */ + child = device_handle; + while (child) { + status = acpi_get_parent(child, &parent); + if (ACPI_FAILURE(status)) { + printk(KERN_ERR "%s: acpi_get_parent() failed " + "(0x%x) for: ", __FUNCTION__, status); + acpi_ns_print_node_pathname(child, NULL); + printk("\n"); + panic("%s: Unable to find host devfn\n", __FUNCTION__); + } + if (parent == rootbus_handle) + break; + child = parent; + } + if (!child) { + printk(KERN_ERR "%s: Unable to find root bus for: ", + __FUNCTION__); + acpi_ns_print_node_pathname(device_handle, NULL); + printk("\n"); + BUG(); + } + + status = acpi_evaluate_integer(child, METHOD_NAME__ADR, NULL, &adr); + if (ACPI_FAILURE(status)) { + printk(KERN_ERR "%s: Unable to get _ADR (0x%x) for: ", + __FUNCTION__, status); + acpi_ns_print_node_pathname(child, NULL); + printk("\n"); + panic("%s: Unable to find host devfn\n", __FUNCTION__); + } + + slot = (adr >> 16) & 0xffff; + function = adr & 0xffff; + devfn = PCI_DEVFN(slot, function); + return devfn; +} + +/* + * find_matching_device - Callback routine to find the ACPI device + * that matches up with our pci_dev device. + * Matching is done on bus number and devfn. + * To find the bus number for a particular + * ACPI device, we must look at the _BBN method + * of its parent. + */ +static acpi_status +find_matching_device(acpi_handle handle, u32 lvl, void *context, void **rv) +{ + unsigned long bbn = -1; + unsigned long adr; + acpi_handle parent = NULL; + acpi_status status; + unsigned int devfn; + int function; + int slot; + struct sn_pcidev_match *info = context; + + status = acpi_evaluate_integer(handle, METHOD_NAME__ADR, NULL, + &adr); + if (ACPI_SUCCESS(status)) { + status = acpi_get_parent(handle, &parent); + if (ACPI_FAILURE(status)) { printk(KERN_ERR - "sn_pci_fixup_bus: 0x%04x:0x%02x Unable to " - "obtain prom_bussoft_ptr\n", - pci_domain_nr(bus), bus->number); - return; + "%s: acpi_get_parent() failed (0x%x) for: ", + __FUNCTION__, status); + acpi_ns_print_node_pathname(handle, NULL); + printk("\n"); + return AE_OK; + } + status = acpi_evaluate_integer(parent, METHOD_NAME__BBN, + NULL, &bbn); + if (ACPI_FAILURE(status)) { + printk(KERN_ERR + "%s: Failed to find _BBN in parent of: ", + __FUNCTION__); + acpi_ns_print_node_pathname(handle, NULL); + printk("\n"); + return AE_OK; + } + + slot = (adr >> 16) & 0xffff; + function = adr & 0xffff; + devfn = PCI_DEVFN(slot, function); + if ((info->devfn == devfn) && (info->bus == bbn)) { + /* We have a match! */ + info->handle = handle; + return 1; } - sn_common_bus_fixup(bus, prom_bussoft_ptr); } - list_for_each_entry(pci_dev, &bus->devices, bus_list) { - sn_pci_fixup_slot(pci_dev); + return AE_OK; +} + +/* + * sn_acpi_get_pcidev_info - Search ACPI namespace for the acpi + * device matching the specified pci_dev, + * and return the pcidev info and irq info. + */ +int +sn_acpi_get_pcidev_info(struct pci_dev *dev, struct pcidev_info **pcidev_info, + struct sn_irq_info **sn_irq_info) +{ + unsigned int host_devfn; + struct sn_pcidev_match pcidev_match; + acpi_handle rootbus_handle; + unsigned long segment; + acpi_status status; + + rootbus_handle = PCI_CONTROLLER(dev)->acpi_handle; + status = acpi_evaluate_integer(rootbus_handle, METHOD_NAME__SEG, NULL, + &segment); + if (ACPI_SUCCESS(status)) { + if (segment != pci_domain_nr(dev)) { + printk(KERN_ERR + "%s: Segment number mismatch, 0x%lx vs 0x%x for: ", + __FUNCTION__, segment, pci_domain_nr(dev)); + acpi_ns_print_node_pathname(rootbus_handle, NULL); + printk("\n"); + return 1; + } + } else { + printk(KERN_ERR "%s: Unable to get __SEG from: ", + __FUNCTION__); + acpi_ns_print_node_pathname(rootbus_handle, NULL); + printk("\n"); + return 1; + } + + /* + * We want to search all devices in this segment/domain + * of the ACPI namespace for the matching ACPI device, + * which holds the pcidev_info pointer in its vendor resource. + */ + pcidev_match.bus = dev->bus->number; + pcidev_match.devfn = dev->devfn; + pcidev_match.handle = NULL; + + acpi_walk_namespace(ACPI_TYPE_DEVICE, rootbus_handle, ACPI_UINT32_MAX, + find_matching_device, &pcidev_match, NULL); + + if (!pcidev_match.handle) { + printk(KERN_ERR + "%s: Could not find matching ACPI device for %s.\n", + __FUNCTION__, pci_name(dev)); + return 1; } + + if (sn_extract_device_info(pcidev_match.handle, pcidev_info, sn_irq_info)) + return 1; + + /* Build up the pcidev_info.pdi_slot_host_handle */ + host_devfn = get_host_devfn(pcidev_match.handle, rootbus_handle); + (*pcidev_info)->pdi_slot_host_handle = + ((unsigned long) pci_domain_nr(dev) << 40) | + /* bus == 0 */ + host_devfn; + return 0; } /* - * sn_acpi_slot_fixup - Perform any SN specific slot fixup. + * sn_acpi_slot_fixup - Obtain the pcidev_info and sn_irq_info. + * Perform any SN specific slot fixup. * At present there does not appear to be * any generic way to handle a ROM image * that has been shadowed by the PROM, so @@ -179,11 +408,18 @@ sn_acpi_bus_fixup(struct pci_bus *bus) */ void -sn_acpi_slot_fixup(struct pci_dev *dev, struct pcidev_info *pcidev_info) +sn_acpi_slot_fixup(struct pci_dev *dev) { void __iomem *addr; + struct pcidev_info *pcidev_info = NULL; + struct sn_irq_info *sn_irq_info = NULL; size_t size; + if (sn_acpi_get_pcidev_info(dev, &pcidev_info, &sn_irq_info)) { + panic("%s: Failure obtaining pcidev_info for %s\n", + __FUNCTION__, pci_name(dev)); + } + if (pcidev_info->pdi_pio_mapped_addr[PCI_ROM_RESOURCE]) { /* * A valid ROM image exists and has been shadowed by the @@ -200,8 +436,11 @@ sn_acpi_slot_fixup(struct pci_dev *dev, struct pcidev_info *pcidev_info) (unsigned long) addr + size; dev->resource[PCI_ROM_RESOURCE].flags |= IORESOURCE_ROM_BIOS_COPY; } + sn_pci_fixup_slot(dev, pcidev_info, sn_irq_info); } +EXPORT_SYMBOL(sn_acpi_slot_fixup); + static struct acpi_driver acpi_sn_hubdev_driver = { .name = "SGI HUBDEV Driver", .ids = "SGIHUB,SGITIO", @@ -212,6 +451,33 @@ static struct acpi_driver acpi_sn_hubdev_driver = { /* + * sn_acpi_bus_fixup - Perform SN specific setup of software structs + * (pcibus_bussoft, pcidev_info) and hardware + * registers, for the specified bus and devices under it. + */ +void +sn_acpi_bus_fixup(struct pci_bus *bus) +{ + struct pci_dev *pci_dev = NULL; + struct pcibus_bussoft *prom_bussoft_ptr; + + if (!bus->parent) { /* If root bus */ + prom_bussoft_ptr = sn_get_bussoft_ptr(bus); + if (prom_bussoft_ptr == NULL) { + printk(KERN_ERR + "%s: 0x%04x:0x%02x Unable to " + "obtain prom_bussoft_ptr\n", + __FUNCTION__, pci_domain_nr(bus), bus->number); + return; + } + sn_common_bus_fixup(bus, prom_bussoft_ptr); + } + list_for_each_entry(pci_dev, &bus->devices, bus_list) { + sn_acpi_slot_fixup(pci_dev); + } +} + +/* * sn_io_acpi_init - PROM has ACPI support for IO, defining at a minimum the * nodes and root buses in the DSDT. As a result, bus scanning * will be initiated by the Linux ACPI code. |