diff options
Diffstat (limited to 'drivers/pci/hotplug/acpiphp_res.c')
-rw-r--r-- | drivers/pci/hotplug/acpiphp_res.c | 700 |
1 files changed, 700 insertions, 0 deletions
diff --git a/drivers/pci/hotplug/acpiphp_res.c b/drivers/pci/hotplug/acpiphp_res.c new file mode 100644 index 00000000000..f54b1fa7b75 --- /dev/null +++ b/drivers/pci/hotplug/acpiphp_res.c @@ -0,0 +1,700 @@ +/* + * ACPI PCI HotPlug Utility functions + * + * Copyright (C) 1995,2001 Compaq Computer Corporation + * Copyright (C) 2001 Greg Kroah-Hartman (greg@kroah.com) + * Copyright (C) 2001 IBM Corp. + * Copyright (C) 2002 Hiroshi Aono (h-aono@ap.jp.nec.com) + * Copyright (C) 2002 Takayoshi Kochi (t-kochi@bq.jp.nec.com) + * Copyright (C) 2002 NEC Corporation + * + * All rights reserved. + * + * 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, GOOD TITLE or + * NON INFRINGEMENT. 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., 675 Mass Ave, Cambridge, MA 02139, USA. + * + * Send feedback to <gregkh@us.ibm.com>, <t-kochi@bq.jp.nec.com> + * + */ + +#include <linux/init.h> +#include <linux/module.h> + +#include <linux/kernel.h> +#include <linux/types.h> +#include <linux/proc_fs.h> +#include <linux/sysctl.h> +#include <linux/pci.h> +#include <linux/smp.h> +#include <linux/smp_lock.h> + +#include <linux/string.h> +#include <linux/mm.h> +#include <linux/errno.h> +#include <linux/ioport.h> +#include <linux/slab.h> +#include <linux/interrupt.h> +#include <linux/timer.h> + +#include <linux/ioctl.h> +#include <linux/fcntl.h> + +#include <linux/list.h> + +#include "pci_hotplug.h" +#include "acpiphp.h" + +#define MY_NAME "acpiphp_res" + + +/* + * sort_by_size - sort nodes by their length, smallest first + */ +static int sort_by_size(struct pci_resource **head) +{ + struct pci_resource *current_res; + struct pci_resource *next_res; + int out_of_order = 1; + + if (!(*head)) + return 1; + + if (!((*head)->next)) + return 0; + + while (out_of_order) { + out_of_order = 0; + + /* Special case for swapping list head */ + if (((*head)->next) && + ((*head)->length > (*head)->next->length)) { + out_of_order++; + current_res = *head; + *head = (*head)->next; + current_res->next = (*head)->next; + (*head)->next = current_res; + } + + current_res = *head; + + while (current_res->next && current_res->next->next) { + if (current_res->next->length > current_res->next->next->length) { + out_of_order++; + next_res = current_res->next; + current_res->next = current_res->next->next; + current_res = current_res->next; + next_res->next = current_res->next; + current_res->next = next_res; + } else + current_res = current_res->next; + } + } /* End of out_of_order loop */ + + return 0; +} + +#if 0 +/* + * sort_by_max_size - sort nodes by their length, largest first + */ +static int sort_by_max_size(struct pci_resource **head) +{ + struct pci_resource *current_res; + struct pci_resource *next_res; + int out_of_order = 1; + + if (!(*head)) + return 1; + + if (!((*head)->next)) + return 0; + + while (out_of_order) { + out_of_order = 0; + + /* Special case for swapping list head */ + if (((*head)->next) && + ((*head)->length < (*head)->next->length)) { + out_of_order++; + current_res = *head; + *head = (*head)->next; + current_res->next = (*head)->next; + (*head)->next = current_res; + } + + current_res = *head; + + while (current_res->next && current_res->next->next) { + if (current_res->next->length < current_res->next->next->length) { + out_of_order++; + next_res = current_res->next; + current_res->next = current_res->next->next; + current_res = current_res->next; + next_res->next = current_res->next; + current_res->next = next_res; + } else + current_res = current_res->next; + } + } /* End of out_of_order loop */ + + return 0; +} +#endif + +/** + * get_io_resource - get resource for I/O ports + * + * this function sorts the resource list by size and then + * returns the first node of "size" length that is not in the + * ISA aliasing window. If it finds a node larger than "size" + * it will split it up. + * + * size must be a power of two. + * + * difference from get_resource is handling of ISA aliasing space. + * + */ +struct pci_resource *acpiphp_get_io_resource (struct pci_resource **head, u32 size) +{ + struct pci_resource *prevnode; + struct pci_resource *node; + struct pci_resource *split_node; + u64 temp_qword; + + if (!(*head)) + return NULL; + + if (acpiphp_resource_sort_and_combine(head)) + return NULL; + + if (sort_by_size(head)) + return NULL; + + for (node = *head; node; node = node->next) { + if (node->length < size) + continue; + + if (node->base & (size - 1)) { + /* this one isn't base aligned properly + so we'll make a new entry and split it up */ + temp_qword = (node->base | (size-1)) + 1; + + /* Short circuit if adjusted size is too small */ + if ((node->length - (temp_qword - node->base)) < size) + continue; + + split_node = acpiphp_make_resource(node->base, temp_qword - node->base); + + if (!split_node) + return NULL; + + node->base = temp_qword; + node->length -= split_node->length; + + /* Put it in the list */ + split_node->next = node->next; + node->next = split_node; + } /* End of non-aligned base */ + + /* Don't need to check if too small since we already did */ + if (node->length > size) { + /* this one is longer than we need + so we'll make a new entry and split it up */ + split_node = acpiphp_make_resource(node->base + size, node->length - size); + + if (!split_node) + return NULL; + + node->length = size; + + /* Put it in the list */ + split_node->next = node->next; + node->next = split_node; + } /* End of too big on top end */ + + /* For IO make sure it's not in the ISA aliasing space */ + if ((node->base & 0x300L) && !(node->base & 0xfffff000)) + continue; + + /* If we got here, then it is the right size + Now take it out of the list */ + if (*head == node) { + *head = node->next; + } else { + prevnode = *head; + while (prevnode->next != node) + prevnode = prevnode->next; + + prevnode->next = node->next; + } + node->next = NULL; + /* Stop looping */ + break; + } + + return node; +} + + +#if 0 +/** + * get_max_resource - get the largest resource + * + * Gets the largest node that is at least "size" big from the + * list pointed to by head. It aligns the node on top and bottom + * to "size" alignment before returning it. + */ +static struct pci_resource *acpiphp_get_max_resource (struct pci_resource **head, u32 size) +{ + struct pci_resource *max; + struct pci_resource *temp; + struct pci_resource *split_node; + u64 temp_qword; + + if (!(*head)) + return NULL; + + if (acpiphp_resource_sort_and_combine(head)) + return NULL; + + if (sort_by_max_size(head)) + return NULL; + + for (max = *head;max; max = max->next) { + + /* If not big enough we could probably just bail, + instead we'll continue to the next. */ + if (max->length < size) + continue; + + if (max->base & (size - 1)) { + /* this one isn't base aligned properly + so we'll make a new entry and split it up */ + temp_qword = (max->base | (size-1)) + 1; + + /* Short circuit if adjusted size is too small */ + if ((max->length - (temp_qword - max->base)) < size) + continue; + + split_node = acpiphp_make_resource(max->base, temp_qword - max->base); + + if (!split_node) + return NULL; + + max->base = temp_qword; + max->length -= split_node->length; + + /* Put it next in the list */ + split_node->next = max->next; + max->next = split_node; + } + + if ((max->base + max->length) & (size - 1)) { + /* this one isn't end aligned properly at the top + so we'll make a new entry and split it up */ + temp_qword = ((max->base + max->length) & ~(size - 1)); + + split_node = acpiphp_make_resource(temp_qword, + max->length + max->base - temp_qword); + + if (!split_node) + return NULL; + + max->length -= split_node->length; + + /* Put it in the list */ + split_node->next = max->next; + max->next = split_node; + } + + /* Make sure it didn't shrink too much when we aligned it */ + if (max->length < size) + continue; + + /* Now take it out of the list */ + temp = (struct pci_resource*) *head; + if (temp == max) { + *head = max->next; + } else { + while (temp && temp->next != max) { + temp = temp->next; + } + + temp->next = max->next; + } + + max->next = NULL; + return max; + } + + /* If we get here, we couldn't find one */ + return NULL; +} +#endif + +/** + * get_resource - get resource (mem, pfmem) + * + * this function sorts the resource list by size and then + * returns the first node of "size" length. If it finds a node + * larger than "size" it will split it up. + * + * size must be a power of two. + * + */ +struct pci_resource *acpiphp_get_resource (struct pci_resource **head, u32 size) +{ + struct pci_resource *prevnode; + struct pci_resource *node; + struct pci_resource *split_node; + u64 temp_qword; + + if (!(*head)) + return NULL; + + if (acpiphp_resource_sort_and_combine(head)) + return NULL; + + if (sort_by_size(head)) + return NULL; + + for (node = *head; node; node = node->next) { + dbg("%s: req_size =%x node=%p, base=%x, length=%x\n", + __FUNCTION__, size, node, (u32)node->base, node->length); + if (node->length < size) + continue; + + if (node->base & (size - 1)) { + dbg("%s: not aligned\n", __FUNCTION__); + /* this one isn't base aligned properly + so we'll make a new entry and split it up */ + temp_qword = (node->base | (size-1)) + 1; + + /* Short circuit if adjusted size is too small */ + if ((node->length - (temp_qword - node->base)) < size) + continue; + + split_node = acpiphp_make_resource(node->base, temp_qword - node->base); + + if (!split_node) + return NULL; + + node->base = temp_qword; + node->length -= split_node->length; + + /* Put it in the list */ + split_node->next = node->next; + node->next = split_node; + } /* End of non-aligned base */ + + /* Don't need to check if too small since we already did */ + if (node->length > size) { + dbg("%s: too big\n", __FUNCTION__); + /* this one is longer than we need + so we'll make a new entry and split it up */ + split_node = acpiphp_make_resource(node->base + size, node->length - size); + + if (!split_node) + return NULL; + + node->length = size; + + /* Put it in the list */ + split_node->next = node->next; + node->next = split_node; + } /* End of too big on top end */ + + dbg("%s: got one!!!\n", __FUNCTION__); + /* If we got here, then it is the right size + Now take it out of the list */ + if (*head == node) { + *head = node->next; + } else { + prevnode = *head; + while (prevnode->next != node) + prevnode = prevnode->next; + + prevnode->next = node->next; + } + node->next = NULL; + /* Stop looping */ + break; + } + return node; +} + +/** + * get_resource_with_base - get resource with specific base address + * + * this function + * returns the first node of "size" length located at specified base address. + * If it finds a node larger than "size" it will split it up. + * + * size must be a power of two. + * + */ +struct pci_resource *acpiphp_get_resource_with_base (struct pci_resource **head, u64 base, u32 size) +{ + struct pci_resource *prevnode; + struct pci_resource *node; + struct pci_resource *split_node; + u64 temp_qword; + + if (!(*head)) + return NULL; + + if (acpiphp_resource_sort_and_combine(head)) + return NULL; + + for (node = *head; node; node = node->next) { + dbg(": 1st req_base=%x req_size =%x node=%p, base=%x, length=%x\n", + (u32)base, size, node, (u32)node->base, node->length); + if (node->base > base) + continue; + + if ((node->base + node->length) < (base + size)) + continue; + + if (node->base < base) { + dbg(": split 1\n"); + /* this one isn't base aligned properly + so we'll make a new entry and split it up */ + temp_qword = base; + + /* Short circuit if adjusted size is too small */ + if ((node->length - (temp_qword - node->base)) < size) + continue; + + split_node = acpiphp_make_resource(node->base, temp_qword - node->base); + + if (!split_node) + return NULL; + + node->base = temp_qword; + node->length -= split_node->length; + + /* Put it in the list */ + split_node->next = node->next; + node->next = split_node; + } + + dbg(": 2nd req_base=%x req_size =%x node=%p, base=%x, length=%x\n", + (u32)base, size, node, (u32)node->base, node->length); + + /* Don't need to check if too small since we already did */ + if (node->length > size) { + dbg(": split 2\n"); + /* this one is longer than we need + so we'll make a new entry and split it up */ + split_node = acpiphp_make_resource(node->base + size, node->length - size); + + if (!split_node) + return NULL; + + node->length = size; + + /* Put it in the list */ + split_node->next = node->next; + node->next = split_node; + } /* End of too big on top end */ + + dbg(": got one!!!\n"); + /* If we got here, then it is the right size + Now take it out of the list */ + if (*head == node) { + *head = node->next; + } else { + prevnode = *head; + while (prevnode->next != node) + prevnode = prevnode->next; + + prevnode->next = node->next; + } + node->next = NULL; + /* Stop looping */ + break; + } + return node; +} + + +/** + * acpiphp_resource_sort_and_combine + * + * Sorts all of the nodes in the list in ascending order by + * their base addresses. Also does garbage collection by + * combining adjacent nodes. + * + * returns 0 if success + */ +int acpiphp_resource_sort_and_combine (struct pci_resource **head) +{ + struct pci_resource *node1; + struct pci_resource *node2; + int out_of_order = 1; + + if (!(*head)) + return 1; + + dbg("*head->next = %p\n",(*head)->next); + + if (!(*head)->next) + return 0; /* only one item on the list, already sorted! */ + + dbg("*head->base = 0x%x\n",(u32)(*head)->base); + dbg("*head->next->base = 0x%x\n", (u32)(*head)->next->base); + while (out_of_order) { + out_of_order = 0; + + /* Special case for swapping list head */ + if (((*head)->next) && + ((*head)->base > (*head)->next->base)) { + node1 = *head; + (*head) = (*head)->next; + node1->next = (*head)->next; + (*head)->next = node1; + out_of_order++; + } + + node1 = (*head); + + while (node1->next && node1->next->next) { + if (node1->next->base > node1->next->next->base) { + out_of_order++; + node2 = node1->next; + node1->next = node1->next->next; + node1 = node1->next; + node2->next = node1->next; + node1->next = node2; + } else + node1 = node1->next; + } + } /* End of out_of_order loop */ + + node1 = *head; + + while (node1 && node1->next) { + if ((node1->base + node1->length) == node1->next->base) { + /* Combine */ + dbg("8..\n"); + node1->length += node1->next->length; + node2 = node1->next; + node1->next = node1->next->next; + kfree(node2); + } else + node1 = node1->next; + } + + return 0; +} + + +/** + * acpiphp_make_resource - make resource structure + * @base: base address of a resource + * @length: length of a resource + */ +struct pci_resource *acpiphp_make_resource (u64 base, u32 length) +{ + struct pci_resource *res; + + res = kmalloc(sizeof(struct pci_resource), GFP_KERNEL); + if (res) { + memset(res, 0, sizeof(struct pci_resource)); + res->base = base; + res->length = length; + } + + return res; +} + + +/** + * acpiphp_move_resource - move linked resources from one to another + * @from: head of linked resource list + * @to: head of linked resource list + */ +void acpiphp_move_resource (struct pci_resource **from, struct pci_resource **to) +{ + struct pci_resource *tmp; + + while (*from) { + tmp = (*from)->next; + (*from)->next = *to; + *to = *from; + *from = tmp; + } + + /* *from = NULL is guaranteed */ +} + + +/** + * acpiphp_free_resource - free all linked resources + * @res: head of linked resource list + */ +void acpiphp_free_resource (struct pci_resource **res) +{ + struct pci_resource *tmp; + + while (*res) { + tmp = (*res)->next; + kfree(*res); + *res = tmp; + } + + /* *res = NULL is guaranteed */ +} + + +/* debug support functions; will go away sometime :) */ +static void dump_resource(struct pci_resource *head) +{ + struct pci_resource *p; + int cnt; + + p = head; + cnt = 0; + + while (p) { + dbg("[%02d] %08x - %08x\n", + cnt++, (u32)p->base, (u32)p->base + p->length - 1); + p = p->next; + } +} + +void acpiphp_dump_resource(struct acpiphp_bridge *bridge) +{ + dbg("I/O resource:\n"); + dump_resource(bridge->io_head); + dbg("MEM resource:\n"); + dump_resource(bridge->mem_head); + dbg("PMEM resource:\n"); + dump_resource(bridge->p_mem_head); + dbg("BUS resource:\n"); + dump_resource(bridge->bus_head); +} + +void acpiphp_dump_func_resource(struct acpiphp_func *func) +{ + dbg("I/O resource:\n"); + dump_resource(func->io_head); + dbg("MEM resource:\n"); + dump_resource(func->mem_head); + dbg("PMEM resource:\n"); + dump_resource(func->p_mem_head); + dbg("BUS resource:\n"); + dump_resource(func->bus_head); +} |