701 lines
16 KiB
C
701 lines
16 KiB
C
/*
|
|
* 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);
|
|
}
|