Skip to content
Snippets Groups Projects
cpqphp_ctrl.c 75.1 KiB
Newer Older
  • Learn to ignore specific revisions
  • Linus Torvalds's avatar
    Linus Torvalds committed
    /*
     * Compaq Hot Plug Controller Driver
     *
     * Copyright (C) 1995,2001 Compaq Computer Corporation
     * Copyright (C) 2001 Greg Kroah-Hartman (greg@kroah.com)
     * Copyright (C) 2001 IBM Corp.
     *
     * 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 <greg@kroah.com>
     *
     */
    
    #include <linux/module.h>
    #include <linux/kernel.h>
    #include <linux/types.h>
    #include <linux/slab.h>
    #include <linux/workqueue.h>
    #include <linux/interrupt.h>
    #include <linux/delay.h>
    #include <linux/wait.h>
    #include <linux/pci.h>
    
    #include <linux/pci_hotplug.h>
    
    #include <linux/kthread.h>
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    #include "cpqphp.h"
    
    static u32 configure_new_device(struct controller* ctrl, struct pci_func *func,
    			u8 behind_bridge, struct resource_lists *resources);
    static int configure_new_function(struct controller* ctrl, struct pci_func *func,
    			u8 behind_bridge, struct resource_lists *resources);
    static void interrupt_event_handler(struct controller *ctrl);
    
    
    
    static struct task_struct *cpqhp_event_thread;
    static unsigned long pushbutton_pending;	/* = 0 */
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    
    /* delay is in jiffies to wait for */
    static void long_delay(int delay)
    {
    
    	/*
    	 * XXX(hch): if someone is bored please convert all callers
    	 * to call msleep_interruptible directly.  They really want
    	 * to specify timeouts in natural units and spend a lot of
    	 * effort converting them to jiffies..
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	 */
    	msleep_interruptible(jiffies_to_msecs(delay));
    }
    
    
    /* FIXME: The following line needs to be somewhere else... */
    #define WRONG_BUS_FREQUENCY 0x07
    static u8 handle_switch_change(u8 change, struct controller * ctrl)
    {
    	int hp_slot;
    	u8 rc = 0;
    	u16 temp_word;
    	struct pci_func *func;
    	struct event_info *taskInfo;
    
    	if (!change)
    		return 0;
    
    	/* Switch Change */
    	dbg("cpqsbd:  Switch interrupt received.\n");
    
    	for (hp_slot = 0; hp_slot < 6; hp_slot++) {
    		if (change & (0x1L << hp_slot)) {
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    			 * this one changed.
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    			func = cpqhp_slot_find(ctrl->bus,
    				(hp_slot + ctrl->slot_device_offset), 0);
    
    			/* this is the structure that tells the worker thread
    
    			 * what to do
    			 */
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    			taskInfo = &(ctrl->event_queue[ctrl->next_event]);
    			ctrl->next_event = (ctrl->next_event + 1) % 10;
    			taskInfo->hp_slot = hp_slot;
    
    			rc++;
    
    			temp_word = ctrl->ctrl_int_comp >> 16;
    			func->presence_save = (temp_word >> hp_slot) & 0x01;
    			func->presence_save |= (temp_word >> (hp_slot + 7)) & 0x02;
    
    			if (ctrl->ctrl_int_comp & (0x1L << hp_slot)) {
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    				 * Switch opened
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    
    				func->switch_save = 0;
    
    				taskInfo->event_type = INT_SWITCH_OPEN;
    			} else {
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    				 * Switch closed
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    
    				func->switch_save = 0x10;
    
    				taskInfo->event_type = INT_SWITCH_CLOSE;
    			}
    		}
    	}
    
    	return rc;
    }
    
    /**
    
     * cpqhp_find_slot - find the struct slot of given device
    
    Linus Torvalds's avatar
    Linus Torvalds committed
     * @ctrl: scan lots of this controller
     * @device: the device id to find
     */
    static struct slot *cpqhp_find_slot(struct controller *ctrl, u8 device)
    {
    	struct slot *slot = ctrl->slot;
    
    
    	while (slot && (slot->device != device))
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    		slot = slot->next;
    
    	return slot;
    }
    
    
    static u8 handle_presence_change(u16 change, struct controller * ctrl)
    {
    	int hp_slot;
    	u8 rc = 0;
    	u8 temp_byte;
    	u16 temp_word;
    	struct pci_func *func;
    	struct event_info *taskInfo;
    	struct slot *p_slot;
    
    	if (!change)
    		return 0;
    
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	 * Presence Change
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	dbg("cpqsbd:  Presence/Notify input change.\n");
    	dbg("         Changed bits are 0x%4.4x\n", change );
    
    	for (hp_slot = 0; hp_slot < 6; hp_slot++) {
    		if (change & (0x0101 << hp_slot)) {
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    			 * this one changed.
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    			func = cpqhp_slot_find(ctrl->bus,
    				(hp_slot + ctrl->slot_device_offset), 0);
    
    			taskInfo = &(ctrl->event_queue[ctrl->next_event]);
    			ctrl->next_event = (ctrl->next_event + 1) % 10;
    			taskInfo->hp_slot = hp_slot;
    
    			rc++;
    
    			p_slot = cpqhp_find_slot(ctrl, hp_slot + (readb(ctrl->hpc_reg + SLOT_MASK) >> 4));
    			if (!p_slot)
    				return 0;
    
    			/* If the switch closed, must be a button
    
    			 * If not in button mode, nevermind
    			 */
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    			if (func->switch_save && (ctrl->push_button == 1)) {
    				temp_word = ctrl->ctrl_int_comp >> 16;
    				temp_byte = (temp_word >> hp_slot) & 0x01;
    				temp_byte |= (temp_word >> (hp_slot + 7)) & 0x02;
    
    				if (temp_byte != func->presence_save) {
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    					 * button Pressed (doesn't do anything)
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    					dbg("hp_slot %d button pressed\n", hp_slot);
    					taskInfo->event_type = INT_BUTTON_PRESS;
    				} else {
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    					 * button Released - TAKE ACTION!!!!
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    					dbg("hp_slot %d button released\n", hp_slot);
    					taskInfo->event_type = INT_BUTTON_RELEASE;
    
    					/* Cancel if we are still blinking */
    					if ((p_slot->state == BLINKINGON_STATE)
    					    || (p_slot->state == BLINKINGOFF_STATE)) {
    						taskInfo->event_type = INT_BUTTON_CANCEL;
    						dbg("hp_slot %d button cancel\n", hp_slot);
    					} else if ((p_slot->state == POWERON_STATE)
    						   || (p_slot->state == POWEROFF_STATE)) {
    						/* info(msg_button_ignore, p_slot->number); */
    						taskInfo->event_type = INT_BUTTON_IGNORE;
    						dbg("hp_slot %d button ignore\n", hp_slot);
    					}
    				}
    			} else {
    				/* Switch is open, assume a presence change
    
    				 * Save the presence state
    				 */
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    				temp_word = ctrl->ctrl_int_comp >> 16;
    				func->presence_save = (temp_word >> hp_slot) & 0x01;
    				func->presence_save |= (temp_word >> (hp_slot + 7)) & 0x02;
    
    				if ((!(ctrl->ctrl_int_comp & (0x010000 << hp_slot))) ||
    				    (!(ctrl->ctrl_int_comp & (0x01000000 << hp_slot)))) {
    					/* Present */
    					taskInfo->event_type = INT_PRESENCE_ON;
    				} else {
    					/* Not Present */
    					taskInfo->event_type = INT_PRESENCE_OFF;
    				}
    			}
    		}
    	}
    
    	return rc;
    }
    
    
    static u8 handle_power_fault(u8 change, struct controller * ctrl)
    {
    	int hp_slot;
    	u8 rc = 0;
    	struct pci_func *func;
    	struct event_info *taskInfo;
    
    	if (!change)
    		return 0;
    
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	 * power fault
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    
    	info("power fault interrupt\n");
    
    	for (hp_slot = 0; hp_slot < 6; hp_slot++) {
    		if (change & (0x01 << hp_slot)) {
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    			 * this one changed.
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    			func = cpqhp_slot_find(ctrl->bus,
    				(hp_slot + ctrl->slot_device_offset), 0);
    
    			taskInfo = &(ctrl->event_queue[ctrl->next_event]);
    			ctrl->next_event = (ctrl->next_event + 1) % 10;
    			taskInfo->hp_slot = hp_slot;
    
    			rc++;
    
    			if (ctrl->ctrl_int_comp & (0x00000100 << hp_slot)) {
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    				 * power fault Cleared
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    				func->status = 0x00;
    
    				taskInfo->event_type = INT_POWER_FAULT_CLEAR;
    			} else {
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    				 * power fault
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    				taskInfo->event_type = INT_POWER_FAULT;
    
    				if (ctrl->rev < 4) {
    					amber_LED_on (ctrl, hp_slot);
    					green_LED_off (ctrl, hp_slot);
    					set_SOGO (ctrl);
    
    					/* this is a fatal condition, we want
    					 * to crash the machine to protect from
    					 * data corruption. simulated_NMI
    					 * shouldn't ever return */
    					/* FIXME
    					simulated_NMI(hp_slot, ctrl); */
    
    					/* The following code causes a software
    					 * crash just in case simulated_NMI did
    					 * return */
    					/*FIXME
    					panic(msg_power_fault); */
    				} else {
    					/* set power fault status for this board */
    					func->status = 0xFF;
    					info("power fault bit %x set\n", hp_slot);
    				}
    			}
    		}
    	}
    
    	return rc;
    }
    
    
    /**
    
     * sort_by_size - sort nodes on the list by their length, smallest first.
    
    Linus Torvalds's avatar
    Linus Torvalds committed
     * @head: list to sort
     */
    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;
    }
    
    
    /**
    
     * sort_by_max_size - sort nodes on the list by their length, largest first.
    
    Linus Torvalds's avatar
    Linus Torvalds committed
     * @head: list to sort
     */
    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;
    }
    
    
    /**
    
     * do_pre_bridge_resource_split - find node of resources that are unused
     * @head: new list head
     * @orig_head: original list head
     * @alignment: max node size (?)
    
    Linus Torvalds's avatar
    Linus Torvalds committed
     */
    static struct pci_resource *do_pre_bridge_resource_split(struct pci_resource **head,
    				struct pci_resource **orig_head, u32 alignment)
    {
    	struct pci_resource *prevnode = NULL;
    	struct pci_resource *node;
    	struct pci_resource *split_node;
    	u32 rc;
    	u32 temp_dword;
    	dbg("do_pre_bridge_resource_split\n");
    
    	if (!(*head) || !(*orig_head))
    		return NULL;
    
    	rc = cpqhp_resource_sort_and_combine(head);
    
    	if (rc)
    		return NULL;
    
    	if ((*head)->base != (*orig_head)->base)
    		return NULL;
    
    	if ((*head)->length == (*orig_head)->length)
    		return NULL;
    
    
    	/* If we got here, there the bridge requires some of the resource, but
    
    	 * we may be able to split some off of the front
    	 */
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    
    	node = *head;
    
    	if (node->length & (alignment -1)) {
    		/* this one isn't an aligned length, so we'll make a new entry
    
    		 * and split it up.
    		 */
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    		split_node = kmalloc(sizeof(*split_node), GFP_KERNEL);
    
    		if (!split_node)
    			return NULL;
    
    		temp_dword = (node->length | (alignment-1)) + 1 - alignment;
    
    		split_node->base = node->base;
    		split_node->length = temp_dword;
    
    		node->length -= temp_dword;
    		node->base += split_node->length;
    
    		/* Put it in the list */
    		*head = split_node;
    		split_node->next = node;
    	}
    
    	if (node->length < alignment)
    		return NULL;
    
    	/* Now unlink it */
    	if (*head == node) {
    		*head = node->next;
    	} else {
    		prevnode = *head;
    		while (prevnode->next != node)
    			prevnode = prevnode->next;
    
    		prevnode->next = node->next;
    	}
    	node->next = NULL;
    
    	return node;
    }
    
    
    /**
    
     * do_bridge_resource_split - find one node of resources that aren't in use
     * @head: list head
     * @alignment: max node size (?)
    
    Linus Torvalds's avatar
    Linus Torvalds committed
     */
    static struct pci_resource *do_bridge_resource_split(struct pci_resource **head, u32 alignment)
    {
    	struct pci_resource *prevnode = NULL;
    	struct pci_resource *node;
    	u32 rc;
    	u32 temp_dword;
    
    	rc = cpqhp_resource_sort_and_combine(head);
    
    	if (rc)
    		return NULL;
    
    	node = *head;
    
    	while (node->next) {
    		prevnode = node;
    		node = node->next;
    		kfree(prevnode);
    	}
    
    	if (node->length < alignment)
    		goto error;
    
    	if (node->base & (alignment - 1)) {
    		/* Short circuit if adjusted size is too small */
    		temp_dword = (node->base | (alignment-1)) + 1;
    		if ((node->length - (temp_dword - node->base)) < alignment)
    			goto error;
    
    		node->length -= (temp_dword - node->base);
    		node->base = temp_dword;
    	}
    
    	if (node->length & (alignment - 1))
    		/* There's stuff in use after this node */
    		goto error;
    
    	return node;
    error:
    	kfree(node);
    	return NULL;
    }
    
    
    /**
    
     * get_io_resource - find first node of given size not in ISA aliasing window.
    
    Linus Torvalds's avatar
    Linus Torvalds committed
     * @head: list to search
     * @size: size of node to find, must be a power of two.
     *
    
     * Description: This function sorts the resource list by size and then returns
    
    Linus Torvalds's avatar
    Linus Torvalds committed
     * 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.
     */
    static struct pci_resource *get_io_resource(struct pci_resource **head, u32 size)
    {
    	struct pci_resource *prevnode;
    	struct pci_resource *node;
    	struct pci_resource *split_node;
    	u32 temp_dword;
    
    	if (!(*head))
    		return NULL;
    
    
    	if (cpqhp_resource_sort_and_combine(head))
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    		return NULL;
    
    
    	if (sort_by_size(head))
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    		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
    			 */
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    			temp_dword = (node->base | (size-1)) + 1;
    
    			/* Short circuit if adjusted size is too small */
    			if ((node->length - (temp_dword - node->base)) < size)
    				continue;
    
    			split_node = kmalloc(sizeof(*split_node), GFP_KERNEL);
    
    			if (!split_node)
    				return NULL;
    
    			split_node->base = node->base;
    			split_node->length = temp_dword - node->base;
    			node->base = temp_dword;
    			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
    			 */
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    			split_node = kmalloc(sizeof(*split_node), GFP_KERNEL);
    
    			if (!split_node)
    				return NULL;
    
    			split_node->base = node->base + size;
    			split_node->length = node->length - size;
    			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)
    			continue;
    
    		/* If we got here, then it is the right size
    
    		 * Now take it out of the list and break
    		 */
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    		if (*head == node) {
    			*head = node->next;
    		} else {
    			prevnode = *head;
    			while (prevnode->next != node)
    				prevnode = prevnode->next;
    
    			prevnode->next = node->next;
    		}
    		node->next = NULL;
    		break;
    	}
    
    	return node;
    }
    
    
    /**
    
     * get_max_resource - get largest node which has at least the given size.
    
    Linus Torvalds's avatar
    Linus Torvalds committed
     * @head: the list to search the node in
     * @size: the minimum size of the node to find
     *
     * Description: 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 *get_max_resource(struct pci_resource **head, u32 size)
    {
    	struct pci_resource *max;
    	struct pci_resource *temp;
    	struct pci_resource *split_node;
    	u32 temp_dword;
    
    	if (cpqhp_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.
    		 */
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    		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
    			 */
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    			temp_dword = (max->base | (size-1)) + 1;
    
    			/* Short circuit if adjusted size is too small */
    			if ((max->length - (temp_dword - max->base)) < size)
    				continue;
    
    			split_node = kmalloc(sizeof(*split_node), GFP_KERNEL);
    
    			if (!split_node)
    				return NULL;
    
    			split_node->base = max->base;
    			split_node->length = temp_dword - max->base;
    			max->base = temp_dword;
    			max->length -= split_node->length;
    
    			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
    			 */
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    			split_node = kmalloc(sizeof(*split_node), GFP_KERNEL);
    
    			if (!split_node)
    				return NULL;
    			temp_dword = ((max->base + max->length) & ~(size - 1));
    			split_node->base = temp_dword;
    			split_node->length = max->length + max->base
    					     - split_node->base;
    			max->length -= split_node->length;
    
    			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 = *head;
    		if (temp == max) {
    			*head = max->next;
    		} else {
    			while (temp && temp->next != max) {
    				temp = temp->next;
    			}
    
    			temp->next = max->next;
    		}
    
    		max->next = NULL;
    		break;
    	}
    
    	return max;
    }
    
    
    /**
    
     * get_resource - find resource of given size and split up larger ones.
    
    Linus Torvalds's avatar
    Linus Torvalds committed
     * @head: the list to search for resources
     * @size: the size limit to use
     *
     * Description: 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.
     */
    static struct pci_resource *get_resource(struct pci_resource **head, u32 size)
    {
    	struct pci_resource *prevnode;
    	struct pci_resource *node;
    	struct pci_resource *split_node;
    	u32 temp_dword;
    
    	if (cpqhp_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",
    
    		    __func__, size, node, node->base, node->length);
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    		if (node->length < size)
    			continue;
    
    		if (node->base & (size - 1)) {
    
    			dbg("%s: not aligned\n", __func__);
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    			/* this one isn't base aligned properly
    
    			 * so we'll make a new entry and split it up
    			 */
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    			temp_dword = (node->base | (size-1)) + 1;
    
    			/* Short circuit if adjusted size is too small */
    			if ((node->length - (temp_dword - node->base)) < size)
    				continue;
    
    			split_node = kmalloc(sizeof(*split_node), GFP_KERNEL);
    
    			if (!split_node)
    				return NULL;
    
    			split_node->base = node->base;
    			split_node->length = temp_dword - node->base;
    			node->base = temp_dword;
    			node->length -= split_node->length;
    
    			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", __func__);
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    			/* this one is longer than we need
    
    			 * so we'll make a new entry and split it up
    			 */
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    			split_node = kmalloc(sizeof(*split_node), GFP_KERNEL);
    
    			if (!split_node)
    				return NULL;
    
    			split_node->base = node->base + size;
    			split_node->length = node->length - size;
    			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", __func__);
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    		/* 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;
    		break;
    	}
    	return node;
    }
    
    
    /**
    
     * cpqhp_resource_sort_and_combine - sort nodes by base addresses and clean up
    
    Linus Torvalds's avatar
    Linus Torvalds committed
     * @head: the list to sort and clean up
     *
     * Description: 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.
    
    Linus Torvalds's avatar
    Linus Torvalds committed
     */
    int cpqhp_resource_sort_and_combine(struct pci_resource **head)
    {
    	struct pci_resource *node1;
    	struct pci_resource *node2;
    	int out_of_order = 1;
    
    
    	dbg("%s: head = %p, *head = %p\n", __func__, head, *head);
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    
    	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",(*head)->base);
    	dbg("*head->next->base = 0x%x\n",(*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;
    }
    
    
    
    irqreturn_t cpqhp_ctrl_intr(int IRQ, void *data)
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    {
    	struct controller *ctrl = data;
    	u8 schedule_flag = 0;
    	u8 reset;
    	u16 misc;
    	u32 Diff;
    	u32 temp_dword;
    
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	misc = readw(ctrl->hpc_reg + MISC);
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	 * Check to see if it was our interrupt
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	if (!(misc & 0x000C)) {
    		return IRQ_NONE;
    	}
    
    	if (misc & 0x0004) {
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    		 * Serial Output interrupt Pending
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    
    		/* Clear the interrupt */
    		misc |= 0x0004;
    		writew(misc, ctrl->hpc_reg + MISC);
    
    		/* Read to clear posted writes */
    		misc = readw(ctrl->hpc_reg + MISC);
    
    
    		dbg ("%s - waking up\n", __func__);
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    		wake_up_interruptible(&ctrl->queue);
    	}
    
    	if (misc & 0x0008) {
    		/* General-interrupt-input interrupt Pending */
    		Diff = readl(ctrl->hpc_reg + INT_INPUT_CLEAR) ^ ctrl->ctrl_int_comp;
    
    		ctrl->ctrl_int_comp = readl(ctrl->hpc_reg + INT_INPUT_CLEAR);
    
    		/* Clear the interrupt */
    		writel(Diff, ctrl->hpc_reg + INT_INPUT_CLEAR);
    
    		/* Read it back to clear any posted writes */
    		temp_dword = readl(ctrl->hpc_reg + INT_INPUT_CLEAR);
    
    		if (!Diff)
    			/* Clear all interrupts */
    			writel(0xFFFFFFFF, ctrl->hpc_reg + INT_INPUT_CLEAR);
    
    		schedule_flag += handle_switch_change((u8)(Diff & 0xFFL), ctrl);
    		schedule_flag += handle_presence_change((u16)((Diff & 0xFFFF0000L) >> 16), ctrl);
    		schedule_flag += handle_power_fault((u8)((Diff & 0xFF00L) >> 8), ctrl);
    	}
    
    	reset = readb(ctrl->hpc_reg + RESET_FREQ_MODE);
    	if (reset & 0x40) {
    		/* Bus reset has completed */
    		reset &= 0xCF;
    		writeb(reset, ctrl->hpc_reg + RESET_FREQ_MODE);
    		reset = readb(ctrl->hpc_reg + RESET_FREQ_MODE);
    		wake_up_interruptible(&ctrl->queue);
    	}
    
    	if (schedule_flag) {
    
    		wake_up_process(cpqhp_event_thread);
    		dbg("Waking even thread");
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	}
    	return IRQ_HANDLED;
    }
    
    
    /**
     * cpqhp_slot_create - Creates a node and adds it to the proper bus.
    
     * @busnumber: bus where new node is to be located
    
    Linus Torvalds's avatar
    Linus Torvalds committed
     *
    
     * Returns pointer to the new node or %NULL if unsuccessful.
    
    Linus Torvalds's avatar
    Linus Torvalds committed
     */
    struct pci_func *cpqhp_slot_create(u8 busnumber)
    {
    	struct pci_func *new_slot;
    	struct pci_func *next;
    
    
    	new_slot = kzalloc(sizeof(*new_slot), GFP_KERNEL);
    
    	if (new_slot == NULL)
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    		return new_slot;
    
    	new_slot->next = NULL;
    	new_slot->configured = 1;
    
    	if (cpqhp_slot_list[busnumber] == NULL) {
    		cpqhp_slot_list[busnumber] = new_slot;
    	} else {
    		next = cpqhp_slot_list[busnumber];
    		while (next->next != NULL)
    			next = next->next;
    		next->next = new_slot;
    	}
    	return new_slot;
    }
    
    
    /**
     * slot_remove - Removes a node from the linked list of slots.
     * @old_slot: slot to remove
     *
    
     * Returns %0 if successful, !0 otherwise.
    
    Linus Torvalds's avatar
    Linus Torvalds committed
     */
    static int slot_remove(struct pci_func * old_slot)
    {