/*
 * IBM Hot Plug Controller Driver
 *
 * Written By: Chuck Cole, Jyoti Shah, Tong Yu, Irene Zubarev, IBM Corporation
 *
 * Copyright (c) 2001 Greg Kroah-Hartman (greg@kroah.com)
 * Copyright (c) 2001,2002 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 <gregkh@us.ibm.com>
 *
 */

#include <linux/init.h>
#include <linux/module.h>
#include <linux/slab.h>
#include <linux/pci.h>
#include <linux/interrupt.h>
#include <linux/delay.h>
#include <linux/wait.h>
#include <linux/smp_lock.h>
#include "../../arch/i386/kernel/pci-i386.h"	/* for struct irq_routing_table */
#include "ibmphp.h"

#define attn_on(sl)  ibmphp_hpc_writeslot (sl, HPC_SLOT_ATTNON)
#define attn_off(sl) ibmphp_hpc_writeslot (sl, HPC_SLOT_ATTNOFF)
#define attn_LED_blink(sl) ibmphp_hpc_writeslot (sl, HPC_SLOT_BLINKLED)
#define get_ctrl_revision(sl, rev) ibmphp_hpc_readslot (sl, READ_REVLEVEL, rev)
#define get_hpc_options(sl, opt) ibmphp_hpc_readslot (sl, READ_HPCOPTIONS, opt)

#define DRIVER_VERSION	"0.1"
#define DRIVER_DESC	"IBM Hot Plug PCI Controller Driver"

int ibmphp_debug;

static int debug;
MODULE_PARM (debug, "i");
MODULE_PARM_DESC (debug, "Debugging mode enabled or not");
MODULE_LICENSE ("GPL");
MODULE_DESCRIPTION (DRIVER_DESC);

static int *ops[MAX_OPS + 1];
struct pci_ops *ibmphp_pci_root_ops;
static int max_slots;

static int irqs[16];    /* PIC mode IRQ's we're using so far (in case MPS tables don't provide default info for empty slots */

static int init_flag;

/*
static int get_max_adapter_speed_1 (struct hotplug_slot *, u8 *, u8);

static inline int get_max_adapter_speed (struct hotplug_slot *hs, u8 *value)
{
	return get_max_adapter_speed_1 (hs, value, 1);
}
*/
static inline int get_cur_bus_info (struct slot **sl) 
{
	int rc = 1;
	struct slot * slot_cur = *sl;

	debug ("options = %x\n", slot_cur->ctrl->options);
	debug ("revision = %x\n", slot_cur->ctrl->revision);	

	if (READ_BUS_STATUS (slot_cur->ctrl)) 
		rc = ibmphp_hpc_readslot (slot_cur, READ_BUSSTATUS, NULL);
	
	if (rc) 
		return rc;
	  
	slot_cur->bus_on->current_speed = CURRENT_BUS_SPEED (slot_cur->busstatus);
	if (READ_BUS_MODE (slot_cur->ctrl))
		slot_cur->bus_on->current_bus_mode = CURRENT_BUS_MODE (slot_cur->busstatus);

	debug ("busstatus = %x, bus_speed = %x, bus_mode = %x\n", slot_cur->busstatus, slot_cur->bus_on->current_speed, slot_cur->bus_on->current_bus_mode);
	
	*sl = slot_cur;
	return 0;
}

static inline int slot_update (struct slot **sl)
{
	int rc;
 	rc = ibmphp_hpc_readslot (*sl, READ_ALLSTAT, NULL);
	if (rc) 
		return rc;
	if (!init_flag)
		return get_cur_bus_info (sl);
	return rc;
}

static int get_max_slots (void)
{
	struct list_head * tmp;
	int slot_count = 0;

	list_for_each (tmp, &ibmphp_slot_head) 
		++slot_count;
	return slot_count;
}

/* This routine will put the correct slot->device information per slot.  It's
 * called from initialization of the slot structures. It will also assign
 * interrupt numbers per each slot.
 * Parameters: struct slot
 * Returns 0 or errors
 */
int ibmphp_init_devno (struct slot **cur_slot)
{
	struct irq_routing_table *rtable;
	int len;
	int loop;
	int i;

	rtable = pcibios_get_irq_routing_table ();
	if (!rtable) {
		err ("no BIOS routing table...\n");
		return -ENOMEM;
	}

	len = (rtable->size - sizeof (struct irq_routing_table)) / sizeof (struct irq_info);

	if (!len)
		return -1;
	for (loop = 0; loop < len; loop++) {
		if ((*cur_slot)->number == rtable->slots[loop].slot) {
		if ((*cur_slot)->bus == rtable->slots[loop].bus) {
			(*cur_slot)->device = PCI_SLOT (rtable->slots[loop].devfn);
			for (i = 0; i < 4; i++)
				(*cur_slot)->irq[i] = IO_APIC_get_PCI_irq_vector ((int) (*cur_slot)->bus, (int) (*cur_slot)->device, i);

				debug ("(*cur_slot)->irq[0] = %x\n", (*cur_slot)->irq[0]);
				debug ("(*cur_slot)->irq[1] = %x\n", (*cur_slot)->irq[1]);
				debug ("(*cur_slot)->irq[2] = %x\n", (*cur_slot)->irq[2]);
				debug ("(*cur_slot)->irq[3] = %x\n", (*cur_slot)->irq[3]);

				debug ("rtable->exlusive_irqs = %x\n", rtable->exclusive_irqs);
				debug ("rtable->slots[loop].irq[0].bitmap = %x\n", rtable->slots[loop].irq[0].bitmap);
				debug ("rtable->slots[loop].irq[1].bitmap = %x\n", rtable->slots[loop].irq[1].bitmap);
				debug ("rtable->slots[loop].irq[2].bitmap = %x\n", rtable->slots[loop].irq[2].bitmap);
				debug ("rtable->slots[loop].irq[3].bitmap = %x\n", rtable->slots[loop].irq[3].bitmap);

				debug ("rtable->slots[loop].irq[0].link= %x\n", rtable->slots[loop].irq[0].link);
				debug ("rtable->slots[loop].irq[1].link = %x\n", rtable->slots[loop].irq[1].link);
				debug ("rtable->slots[loop].irq[2].link = %x\n", rtable->slots[loop].irq[2].link);
				debug ("rtable->slots[loop].irq[3].link = %x\n", rtable->slots[loop].irq[3].link);
				debug ("end of init_devno\n");
				return 0;
			}
		}
	}

	return -1;
}

static inline int power_on (struct slot *slot_cur)
{
	u8 cmd = HPC_SLOT_ON;
	int retval;

	retval = ibmphp_hpc_writeslot (slot_cur, cmd);
	if (retval) {
		err ("power on failed\n");
		return retval;
	}
	if (CTLR_RESULT (slot_cur->ctrl->status)) {
		err ("command not completed successfully in power_on \n");
		return -EIO;
	}
	long_delay (3 * HZ); /* For ServeRAID cards, and some 66 PCI */
	return 0;
}

static inline int power_off (struct slot *slot_cur)
{
	u8 cmd = HPC_SLOT_OFF;
	int retval;

	retval = ibmphp_hpc_writeslot (slot_cur, cmd);
	if (retval) {
		err ("power off failed \n");
		return retval;
	}
	if (CTLR_RESULT (slot_cur->ctrl->status)) {
		err ("command not completed successfully in power_off \n");
		return -EIO;
	}
	return 0;
}

static int set_attention_status (struct hotplug_slot *hotplug_slot, u8 value)
{
	int rc = 0;
	struct slot *pslot;
	u8 cmd;
	int hpcrc = 0;

	debug ("set_attention_status - Entry hotplug_slot[%lx] value[%x]\n", (ulong) hotplug_slot, value);
	ibmphp_lock_operations ();
	cmd = 0x00;     // avoid compiler warning

	if (hotplug_slot) {
		switch (value) {
		case HPC_SLOT_ATTN_OFF:
			cmd = HPC_SLOT_ATTNOFF;
			break;
		case HPC_SLOT_ATTN_ON:
			cmd = HPC_SLOT_ATTNON;
			break;
		case HPC_SLOT_ATTN_BLINK:
			cmd = HPC_SLOT_BLINKLED;
			break;
		default:
			rc = -ENODEV;
			err ("set_attention_status - Error : invalid input [%x]\n", value);
			break;
		}
		if (rc == 0) {
			pslot = (struct slot *) hotplug_slot->private;
			if (pslot)
				hpcrc = ibmphp_hpc_writeslot (pslot, cmd);
			else
				rc = -ENODEV;
		}
	} else	
		rc = -ENODEV;

	if (hpcrc)
		rc = hpcrc;

	ibmphp_unlock_operations ();

	debug ("set_attention_status - Exit rc[%d]\n", rc);
	return rc;
}

static int get_attention_status (struct hotplug_slot *hotplug_slot, u8 * value)
{
	int rc = -ENODEV;
	struct slot *pslot;
	int hpcrc = 0;
	struct slot myslot;

	debug ("get_attention_status - Entry hotplug_slot[%lx] pvalue[%lx]\n", (ulong) hotplug_slot, (ulong) value);
        
	ibmphp_lock_operations ();
	if (hotplug_slot && value) {
		pslot = (struct slot *) hotplug_slot->private;
		if (pslot) {
			memcpy ((void *) &myslot, (void *) pslot, sizeof (struct slot));
			hpcrc = ibmphp_hpc_readslot (pslot, READ_SLOTSTATUS, &(myslot.status));
			if (!hpcrc)
				hpcrc = ibmphp_hpc_readslot (pslot, READ_EXTSLOTSTATUS, &(myslot.ext_status));
			if (!hpcrc) {
				*value = SLOT_ATTN (myslot.status, myslot.ext_status);
				rc = 0;
			}
		}
	} else
		rc = -ENODEV;

	if (hpcrc)
		rc = hpcrc;

	ibmphp_unlock_operations ();
	debug ("get_attention_status - Exit rc[%d] hpcrc[%x] value[%x]\n", rc, hpcrc, *value);
	return rc;
}

static int get_latch_status (struct hotplug_slot *hotplug_slot, u8 * value)
{
	int rc = -ENODEV;
	struct slot *pslot;
	int hpcrc = 0;
	struct slot myslot;

	debug ("get_latch_status - Entry hotplug_slot[%lx] pvalue[%lx]\n", (ulong) hotplug_slot, (ulong) value);
	ibmphp_lock_operations ();
	if (hotplug_slot && value) {
		pslot = (struct slot *) hotplug_slot->private;
		if (pslot) {
			memcpy ((void *) &myslot, (void *) pslot, sizeof (struct slot));
			hpcrc = ibmphp_hpc_readslot (pslot, READ_SLOTSTATUS, &(myslot.status));
			if (!hpcrc) {
				*value = SLOT_LATCH (myslot.status);
				rc = 0;
			}
		}
	} else
		rc = -ENODEV;

	if (hpcrc)
		rc = hpcrc;

	ibmphp_unlock_operations ();
	debug ("get_latch_status - Exit rc[%d] hpcrc[%x] value[%x]\n", rc, hpcrc, *value);
	return rc;
}


static int get_power_status (struct hotplug_slot *hotplug_slot, u8 * value)
{
	int rc = -ENODEV;
	struct slot *pslot;
	int hpcrc = 0;
	struct slot myslot;

	debug ("get_power_status - Entry hotplug_slot[%lx] pvalue[%lx]\n", (ulong) hotplug_slot, (ulong) value);
	ibmphp_lock_operations ();
	if (hotplug_slot && value) {
		pslot = (struct slot *) hotplug_slot->private;
		if (pslot) {
			memcpy ((void *) &myslot, (void *) pslot, sizeof (struct slot));
			hpcrc = ibmphp_hpc_readslot (pslot, READ_SLOTSTATUS, &(myslot.status));
			if (!hpcrc) {
				*value = SLOT_POWER (myslot.status);
				rc = 0;
			}
		}
	} else
		rc = -ENODEV;

	if (hpcrc)
		rc = hpcrc;

	ibmphp_unlock_operations ();
	debug ("get_power_status - Exit rc[%d] hpcrc[%x] value[%x]\n", rc, hpcrc, *value);
	return rc;
}

static int get_adapter_present (struct hotplug_slot *hotplug_slot, u8 * value)
{
	int rc = -ENODEV;
	struct slot *pslot;
	u8 present;
	int hpcrc = 0;
	struct slot myslot;

	debug ("get_adapter_status - Entry hotplug_slot[%lx] pvalue[%lx]\n", (ulong) hotplug_slot, (ulong) value);
	ibmphp_lock_operations ();
	if (hotplug_slot && value) {
		pslot = (struct slot *) hotplug_slot->private;
		if (pslot) {
			memcpy ((void *) &myslot, (void *) pslot, sizeof (struct slot));
			hpcrc = ibmphp_hpc_readslot (pslot, READ_SLOTSTATUS, &(myslot.status));
			if (!hpcrc) {
				present = SLOT_PRESENT (myslot.status);
				if (present == HPC_SLOT_EMPTY)
					*value = 0;
				else
					*value = 1;
				rc = 0;
			}
		}
	} else
		rc = -ENODEV;
	if (hpcrc)
		rc = hpcrc;

	ibmphp_unlock_operations ();
	debug ("get_adapter_present - Exit rc[%d] hpcrc[%x] value[%x]\n", rc, hpcrc, *value);
	return rc;
}
/*
static int get_max_bus_speed (struct hotplug_slot *hotplug_slot, u8 * value)
{
	int rc = -ENODEV;
	struct slot *pslot;
	u8 mode = 0;

	debug ("get_max_bus_speed - Entry hotplug_slot[%lx] pvalue[%lx]\n", (ulong)hotplug_slot, (ulong) value);

	ibmphp_lock_operations ();

	if (hotplug_slot && value) {
		pslot = (struct slot *) hotplug_slot->private;
		if (pslot) {
			rc = 0;
			mode = pslot->bus_on->supported_bus_mode;
			*value = pslot->bus_on->supported_speed;
			*value &= 0x0f;

			if (mode == BUS_MODE_PCIX)
				*value |= 0x80;
			else if (mode == BUS_MODE_PCI)
				*value |= 0x40;
			else
				*value |= 0x20;
		}
	} else
		rc = -ENODEV;

	ibmphp_unlock_operations ();
	debug ("get_max_bus_speed - Exit rc[%d] value[%x]\n", rc, *value);
	return rc;
}

static int get_cur_bus_speed (struct hotplug_slot *hotplug_slot, u8 * value)
{
	int rc = -ENODEV;
	struct slot *pslot;
	u8 mode = 0;

	debug ("get_cur_bus_speed - Entry hotplug_slot[%lx] pvalue[%lx]\n", (ulong)hotplug_slot, (ulong) value);

	ibmphp_lock_operations ();

	if (hotplug_slot && value) {
		pslot = (struct slot *) hotplug_slot->private;
		if (pslot) {
			rc = get_cur_bus_info (&pslot);
			if (!rc) {
				mode = pslot->bus_on->current_bus_mode;
				*value = pslot->bus_on->current_speed;
				*value &= 0x0f;
				
				if (mode == BUS_MODE_PCIX)
					*value |= 0x80;
				else if (mode == BUS_MODE_PCI)
					*value |= 0x40;
				else
					*value |= 0x20;	
			}
		}
	} else
		rc = -ENODEV;

	ibmphp_unlock_operations ();
	debug ("get_cur_bus_speed - Exit rc[%d] value[%x]\n", rc, *value);
	return rc;
}

static int get_max_adapter_speed_1 (struct hotplug_slot *hotplug_slot, u8 * value, u8 flag)
{
	int rc = -ENODEV;
	struct slot *pslot;
	int hpcrc = 0;
	struct slot myslot;

	debug ("get_max_adapter_speed - Entry hotplug_slot[%lx] pvalue[%lx]\n", (ulong)hotplug_slot, (ulong) value);

	if (flag)
		ibmphp_lock_operations ();

	if (hotplug_slot && value) {
		pslot = (struct slot *) hotplug_slot->private;
		if (pslot) {
			memcpy ((void *) &myslot, (void *) pslot, sizeof (struct slot));
			hpcrc = ibmphp_hpc_readslot (pslot, READ_SLOTSTATUS, &(myslot.status));

			if (!(SLOT_LATCH (myslot.status)) && (SLOT_PRESENT (myslot.status))) {
				hpcrc = ibmphp_hpc_readslot (pslot, READ_EXTSLOTSTATUS, &(myslot.ext_status));
				if (!hpcrc) {
					*value = SLOT_SPEED (myslot.ext_status);
					rc = 0;
				}
			} else {
				*value = MAX_ADAPTER_NONE;
				rc = 0;
			}
                }
        } else
		rc = -ENODEV;

	if (hpcrc)
		rc = hpcrc;

	if (flag)
		ibmphp_unlock_operations ();

	debug ("get_adapter_present - Exit rc[%d] hpcrc[%x] value[%x]\n", rc, hpcrc, *value);
	return rc;
}

static int get_card_bus_names (struct hotplug_slot *hotplug_slot, char * value)
{
	int rc = -ENODEV;
	struct slot *pslot = NULL;
	struct pci_dev * dev = NULL;

	debug ("get_card_bus_names - Entry hotplug_slot[%lx] \n", (ulong)hotplug_slot);

	ibmphp_lock_operations ();

	if (hotplug_slot) {
		pslot = (struct slot *) hotplug_slot->private;
		if (pslot) {
			rc = 0;
			if (pslot->func)
				dev = pslot->func->dev;
			else
                		dev = pci_find_slot (pslot->bus, (pslot->device << 3) | (0x00 & 0x7));
			if (dev) 
				snprintf (value, 100, "Bus %d : %s", pslot->bus,dev->name);
			else
				snprintf (value, 100, "Bus %d", pslot->bus);
			
				
		}
	} else
		rc = -ENODEV;

	ibmphp_unlock_operations ();
	debug ("get_card_bus_names - Exit rc[%d] value[%x]\n", rc, *value);
	return rc;
}

*/
/*******************************************************************************
 * This routine will initialize the ops data structure used in the validate
 * function. It will also power off empty slots that are powered on since BIOS
 * leaves those on, albeit disconnected
 ******************************************************************************/
static int init_ops (void)
{
	struct slot *slot_cur;
	int retval;
	int j;
	int rc;

	for (j = 0; j < MAX_OPS; j++) {
		ops[j] = (int *) kmalloc ((max_slots + 1) * sizeof (int), GFP_KERNEL);
		if (!ops[j]) {
			err ("out of system memory \n");
			return -ENOMEM;
		}
	}

	ops[ADD][0] = 0;
	ops[REMOVE][0] = 0;
	ops[DETAIL][0] = 0;

	for (j = 1; j <= max_slots; j++) {

		slot_cur = ibmphp_get_slot_from_physical_num (j);

		debug ("BEFORE GETTING SLOT STATUS, slot # %x\n", slot_cur->number);

		if (slot_cur->ctrl->revision == 0xFF) 
			if (get_ctrl_revision (slot_cur, &slot_cur->ctrl->revision))
				return -1;

		if (slot_cur->bus_on->current_speed == 0xFF) 
			if (get_cur_bus_info (&slot_cur)) 
				return -1;

		if (slot_cur->ctrl->options == 0xFF)
			if (get_hpc_options (slot_cur, &slot_cur->ctrl->options))
				return -1;

		retval = slot_update (&slot_cur);
		if (retval)
			return retval;

		debug ("status = %x, ext_status = %x\n", slot_cur->status, slot_cur->ext_status);
		debug ("SLOT_POWER = %x, SLOT_PRESENT = %x, SLOT_LATCH = %x\n", SLOT_POWER (slot_cur->status), SLOT_PRESENT (slot_cur->status), SLOT_LATCH (slot_cur->status));

		if (!(SLOT_POWER (slot_cur->status)) && (SLOT_PRESENT (slot_cur->status)) && !(SLOT_LATCH (slot_cur->status)))
			/* No power, adapter, and latch closed */
			ops[ADD][j] = 1;
		else
			ops[ADD][j] = 0;

		ops[DETAIL][j] = 1;

		if ((SLOT_POWER (slot_cur->status)) && (SLOT_PRESENT (slot_cur->status)) && !(SLOT_LATCH (slot_cur->status)))
			/*Power,adapter,latch closed */
			ops[REMOVE][j] = 1;
		else
			ops[REMOVE][j] = 0;

		if ((SLOT_POWER (slot_cur->status)) && !(SLOT_PRESENT (slot_cur->status)) && !(SLOT_LATCH (slot_cur->status))) {
			debug ("BEFORE POWER OFF COMMAND\n");
				rc = power_off (slot_cur);
				if (rc)
					return rc;

	/*		retval = slot_update (&slot_cur);
	 *		if (retval)
	 *			return retval;
	 *		ibmphp_update_slot_info (slot_cur);
	 */
		}
	}
	init_flag = 0;
	return 0;
}

/* This operation will check whether the slot is within the bounds and
 * the operation is valid to perform on that slot
 * Parameters: slot, operation
 * Returns: 0 or error codes
 */
static int validate (struct slot *slot_cur, int opn)
{
	int number;
	int retval;

	if (!slot_cur)
		return -ENODEV;
	number = slot_cur->number;
	if ((number > max_slots) || (number < 0))
		return -EBADSLT;
	debug ("slot_number in validate is %d\n", slot_cur->number);

	retval = slot_update (&slot_cur);
	if (retval)
		return retval;

	if (!(SLOT_POWER (slot_cur->status)) && (SLOT_PRESENT (slot_cur->status))
	    && !(SLOT_LATCH (slot_cur->status)))
		ops[ADD][number] = 1;
	else
		ops[ADD][number] = 0;

	ops[DETAIL][number] = 1;

	if ((SLOT_POWER (slot_cur->status)) && (SLOT_PRESENT (slot_cur->status))
	    && !(SLOT_LATCH (slot_cur->status)))
		ops[REMOVE][number] = 1;
	else
		ops[REMOVE][number] = 0;

	switch (opn) {
		case ENABLE:
			if (ops[ADD][number])
				return 0;
			break;
		case DISABLE:
			if (ops[REMOVE][number])
				return 0;
			break;
		case DETAIL:
			if (ops[DETAIL][number])
				return 0;
			break;
		default:
			return -EINVAL;
			break;
	}
	err ("validate failed....\n");
	return -EINVAL;
}

/********************************************************************************
 * This routine is for updating the data structures in the hotplug core
 * Parameters: struct slot
 * Returns: 0 or error
 *******************************************************************************/
int ibmphp_update_slot_info (struct slot *slot_cur)
{
	struct hotplug_slot_info *info;
	char buffer[10];
	int rc;
//	u8 bus_speed;

	info = kmalloc (sizeof (struct hotplug_slot_info), GFP_KERNEL);
	if (!info) {
		err ("out of system memory \n");
		return -ENOMEM;
	}
        
	snprintf (buffer, 10, "%d", slot_cur->number);
	info->power_status = SLOT_POWER (slot_cur->status);
	info->attention_status = SLOT_ATTN (slot_cur->status, slot_cur->ext_status);
	info->latch_status = SLOT_LATCH (slot_cur->status);
        if (!SLOT_PRESENT (slot_cur->status)) {
                info->adapter_status = 0;
//		info->max_adapter_speed_status = MAX_ADAPTER_NONE;
	} else {
                info->adapter_status = 1;
//		get_max_adapter_speed_1 (slot_cur->hotplug_slot, &info->max_adapter_speed_status, 0);
	}
/*
	bus_speed = slot_cur->bus_on->current_speed;
	bus_speed &= 0x0f;
                        
	if (slot_cur->bus_on->current_bus_mode == BUS_MODE_PCIX)
		bus_speed |= 0x80;
	else if (slot_cur->bus_on->current_bus_mode == BUS_MODE_PCI)
		bus_speed |= 0x40;
	else
		bus_speed |= 0x20;

	info->cur_bus_speed_status = bus_speed;
	info->max_bus_speed_status = slot_cur->hotplug_slot->info->max_bus_speed_status;
	// To do: card_bus_names 
*/	
	rc = pci_hp_change_slot_info (buffer, info);
	kfree (info);
	return rc;
}


/******************************************************************************
 * This function will return the pci_func, given bus and devfunc, or NULL.  It
 * is called from visit routines
 ******************************************************************************/

static struct pci_func *ibm_slot_find (u8 busno, u8 device, u8 function)
{
	struct pci_func *func_cur;
	struct slot *slot_cur;
	struct list_head * tmp;
	list_for_each (tmp, &ibmphp_slot_head) {
		slot_cur = list_entry (tmp, struct slot, ibm_slot_list);
		if (slot_cur->func) {
			func_cur = slot_cur->func;
			while (func_cur) {
				if ((func_cur->busno == busno) && (func_cur->device == device) && (func_cur->function == function))
					return func_cur;
				func_cur = func_cur->next;
			}
		}
	}
	return NULL;
}

/* This routine is to find the pci_bus from kernel structures.
 * Parameters: bus number
 * Returns : pci_bus *  or NULL if not found
 */
static struct pci_bus *find_bus (u8 busno)
{
	const struct list_head *tmp;
	struct pci_bus *bus;
	debug ("inside find_bus, busno = %x \n", busno);

	list_for_each (tmp, &pci_root_buses) {
		bus = (struct pci_bus *) pci_bus_b (tmp);
		if (bus)
			if (bus->number == busno)
				return bus;
	}
	return NULL;
}

/******************************************************************
 * This function is here because we can no longer use pci_root_ops
 ******************************************************************/
static struct pci_ops *get_root_pci_ops (void)
{
	struct pci_bus * bus;

	if ((bus = find_bus (0)))
		return bus->ops;
	return NULL;
}

/*************************************************************
 * This routine frees up memory used by struct slot, including
 * the pointers to pci_func, bus, hotplug_slot, controller,
 * and deregistering from the hotplug core
 *************************************************************/
static void free_slots (void)
{
	struct slot *slot_cur;
	struct list_head * tmp;
	struct list_head * next;

	list_for_each_safe (tmp, next, &ibmphp_slot_head) {

		slot_cur = list_entry (tmp, struct slot, ibm_slot_list);

		pci_hp_deregister (slot_cur->hotplug_slot);

		if (slot_cur->hotplug_slot) {
			kfree (slot_cur->hotplug_slot);
			slot_cur->hotplug_slot = NULL;
		}

		if (slot_cur->ctrl) 
			slot_cur->ctrl = NULL;
		
		if (slot_cur->bus_on) 
			slot_cur->bus_on = NULL;

		ibmphp_unconfigure_card (&slot_cur, -1);  /* we don't want to actually remove the resources, since free_resources will do just that */

		kfree (slot_cur);
	}
}

static int ibm_is_pci_dev_in_use (struct pci_dev *dev)
{
	int i = 0;
	int inuse = 0;

	if (dev->driver)
		return 1;

	for (i = 0; !dev->driver && !inuse && (i < 6); i++) {

		if (!pci_resource_start (dev, i))
			continue;

		if (pci_resource_flags (dev, i) & IORESOURCE_IO)
			inuse = check_region (pci_resource_start (dev, i), pci_resource_len (dev, i));

		else if (pci_resource_flags (dev, i) & IORESOURCE_MEM)
			inuse = check_mem_region (pci_resource_start (dev, i), pci_resource_len (dev, i));
	}

	return inuse;
}

static int ibm_pci_hp_remove_device (struct pci_dev *dev)
{
	if (ibm_is_pci_dev_in_use (dev)) {
		err ("***Cannot safely power down device -- it appears to be in use***\n");
		return -EBUSY;
	}
	pci_remove_device (dev);
	return 0;
}

static int ibm_unconfigure_visit_pci_dev_phase2 (struct pci_dev_wrapped *wrapped_dev, struct pci_bus_wrapped *wrapped_bus)
{
	struct pci_dev *dev = wrapped_dev->dev;
	struct pci_func *temp_func;
	int i = 0;

	do {
		temp_func = ibm_slot_find (dev->bus->number, dev->devfn >> 3, i++);
	} while (temp_func && (temp_func->function != (dev->devfn & 0x07)));

	if (dev) {
		if (ibm_pci_hp_remove_device (dev) == 0)
			kfree (dev);    /* Now, remove */
		else
			return -1;
	}

	if (temp_func)
		temp_func->dev = NULL;
	else
		err ("No pci_func representation for bus, devfn = %d, %x\n", dev->bus->number, dev->devfn);

	return 0;
}

static int ibm_unconfigure_visit_pci_bus_phase2 (struct pci_bus_wrapped *wrapped_bus, struct pci_dev_wrapped *wrapped_dev)
{
	struct pci_bus *bus = wrapped_bus->bus;

	pci_proc_detach_bus (bus);
	/* The cleanup code should live in the kernel... */
	bus->self->subordinate = NULL;
	/* unlink from parent bus */
	list_del (&bus->node);

	/* Now, remove */
	if (bus)
		kfree (bus);

	return 0;
}

static int ibm_unconfigure_visit_pci_dev_phase1 (struct pci_dev_wrapped *wrapped_dev, struct pci_bus_wrapped *wrapped_bus)
{
	struct pci_dev *dev = wrapped_dev->dev;

	debug ("attempting removal of driver for device (%x, %x, %x)\n", dev->bus->number, PCI_SLOT (dev->devfn), PCI_FUNC (dev->devfn));

	/* Now, remove the Linux Driver Representation */
	if (dev->driver) {
		debug ("is there a driver?\n");
		if (dev->driver->remove) {
			dev->driver->remove (dev);
			debug ("driver was properly removed\n");
		}
		dev->driver = NULL;
	}

	return ibm_is_pci_dev_in_use (dev);
}

static struct pci_visit ibm_unconfigure_functions_phase1 = {
	post_visit_pci_dev:	ibm_unconfigure_visit_pci_dev_phase1,
};

static struct pci_visit ibm_unconfigure_functions_phase2 = {
	post_visit_pci_bus:	ibm_unconfigure_visit_pci_bus_phase2,
	post_visit_pci_dev:	ibm_unconfigure_visit_pci_dev_phase2,
};

static int ibm_unconfigure_device (struct pci_func *func)
{
	int rc = 0;
	struct pci_dev_wrapped wrapped_dev;
	struct pci_bus_wrapped wrapped_bus;
	struct pci_dev *temp;
	u8 j;

	memset (&wrapped_dev, 0, sizeof (struct pci_dev_wrapped));
	memset (&wrapped_bus, 0, sizeof (struct pci_bus_wrapped));

	debug ("inside ibm_unconfigure_device\n");
	debug ("func->device = %x, func->function = %x\n", func->device, func->function);
	debug ("func->device << 3 | 0x0  = %x\n", func->device << 3 | 0x0);

	for (j = 0; j < 0x08; j++) {
		temp = pci_find_slot (func->busno, (func->device << 3) | j);
		if (temp) {
			wrapped_dev.dev = temp;
			wrapped_bus.bus = temp->bus;
			rc = pci_visit_dev (&ibm_unconfigure_functions_phase1, &wrapped_dev, &wrapped_bus);
			if (rc)
				break;

			rc = pci_visit_dev (&ibm_unconfigure_functions_phase2, &wrapped_dev, &wrapped_bus);
			if (rc)
				break;
		}
	}
	debug ("rc in ibm_unconfigure_device b4 returning is %d \n", rc);
	return rc;
}

static int configure_visit_pci_dev (struct pci_dev_wrapped *wrapped_dev, struct pci_bus_wrapped *wrapped_bus)
{
	//      struct pci_bus *bus = wrapped_bus->bus; /* We don't need this, since we don't create in the else statement */
	struct pci_dev *dev = wrapped_dev->dev;
	struct pci_func *temp_func;
	int i = 0;

	do {
		temp_func = ibm_slot_find (dev->bus->number, dev->devfn >> 3, i++);
	} while (temp_func && (temp_func->function != (dev->devfn & 0x07)));

	if (temp_func)
		temp_func->dev = dev;
	else {
		/* This should not really happen, since we create functions
		   first and then call to configure */
		debug (" We shouldn't come here \n");
	}

	if (temp_func->dev) {
		pci_proc_attach_device (temp_func->dev);
		pci_announce_device_to_drivers (temp_func->dev);
	}

	return 0;
}

static struct pci_visit configure_functions = {
	visit_pci_dev:	configure_visit_pci_dev,
};

static int ibm_configure_device (struct pci_func *func)
{
	unsigned char bus;
	struct pci_dev dev0;
	struct pci_bus *child;
	struct pci_dev *temp;
	int rc = 0;

	struct pci_dev_wrapped wrapped_dev;
	struct pci_bus_wrapped wrapped_bus;

	memset (&wrapped_dev, 0, sizeof (struct pci_dev_wrapped));
	memset (&wrapped_bus, 0, sizeof (struct pci_bus_wrapped));
	memset (&dev0, 0, sizeof (struct pci_dev));

	if (func->dev == NULL)
		func->dev = pci_find_slot (func->busno, (func->device << 3) | (func->function & 0x7));

	if (func->dev == NULL) {
		dev0.bus = find_bus (func->busno);
		dev0.devfn = ((func->device << 3) + (func->function & 0x7));
		dev0.sysdata = dev0.bus->sysdata;

		func->dev = pci_scan_slot (&dev0);

		if (func->dev == NULL) {
			err ("ERROR... : pci_dev still NULL \n");
			return 0;
		}
	}
	if (func->dev->hdr_type == PCI_HEADER_TYPE_BRIDGE) {
		pci_read_config_byte (func->dev, PCI_SECONDARY_BUS, &bus);
		child = (struct pci_bus *) pci_add_new_bus (func->dev->bus, (func->dev), bus);
		pci_do_scan_bus (child);
	}

	temp = func->dev;
	if (temp) {
		wrapped_dev.dev = temp;
		wrapped_bus.bus = temp->bus;
		rc = pci_visit_dev (&configure_functions, &wrapped_dev, &wrapped_bus);
	}
	return rc;
}
/*******************************************************
 * Returns whether the bus is empty or not 
 *******************************************************/
static int is_bus_empty (struct slot * slot_cur)
{
	int rc;
	struct slot * tmp_slot;
	u8 i = slot_cur->bus_on->slot_min;

	while (i <= slot_cur->bus_on->slot_max) {
		if (i == slot_cur->number) {
			i++;
			continue;
		}
		tmp_slot = ibmphp_get_slot_from_physical_num (i);
		rc = slot_update (&tmp_slot);
		if (rc)
			return 0;
		if (SLOT_PRESENT (tmp_slot->status) && SLOT_POWER (tmp_slot->status))
			return 0;
		i++;
	}
	return 1;
}

/***********************************************************
 * If the HPC permits and the bus currently empty, tries to set the 
 * bus speed and mode at the maximum card capability
 ***********************************************************/
static int set_bus (struct slot * slot_cur)
{
	int rc;
	u8 speed;
	u8 cmd = 0x0;

	debug ("%s - entry slot # %d \n", __FUNCTION__, slot_cur->number);
	if (SET_BUS_STATUS (slot_cur->ctrl) && is_bus_empty (slot_cur)) {
		rc = slot_update (&slot_cur);
		if (rc)
			return rc;
		speed = SLOT_SPEED (slot_cur->ext_status);
		debug ("ext_status = %x, speed = %x\n", slot_cur->ext_status, speed);
		switch (speed) {
		case HPC_SLOT_SPEED_33:
			cmd = HPC_BUS_33CONVMODE;
			break;
		case HPC_SLOT_SPEED_66:
			if (SLOT_PCIX (slot_cur->ext_status))
				cmd = HPC_BUS_66PCIXMODE;
			else
				cmd = HPC_BUS_66CONVMODE;
			break;
		case HPC_SLOT_SPEED_133:
			if (slot_cur->bus_on->slot_count > 1)
				cmd = HPC_BUS_100PCIXMODE;
			else
				cmd = HPC_BUS_133PCIXMODE;
			break;
		default:
			err ("wrong slot speed \n");
			return -ENODEV;
		}
		debug ("setting bus speed for slot %d, cmd %x\n", slot_cur->number, cmd);
		rc = ibmphp_hpc_writeslot (slot_cur, cmd);
		if (rc)
			return rc;
	}
	debug ("%s -Exit \n", __FUNCTION__);
	return 0;
}

static inline void print_card_capability (struct slot *slot_cur)
{
	info ("capability of the card is ");
	if ((slot_cur->ext_status & CARD_INFO) == PCIX133) 
		info ("   133 MHz PCI-X \n");
	else if ((slot_cur->ext_status & CARD_INFO) == PCIX66)
		info ("    66 MHz PCI-X \n");
	else if ((slot_cur->ext_status & CARD_INFO) == PCI66)
		info ("    66 MHz PCI \n");
	else
		info ("    33 MHz PCI \n");

}

/* This routine will power on the slot, configure the device(s) and find the
 * drivers for them.
 * Parameters: hotplug_slot
 * Returns: 0 or failure codes
 */
static int enable_slot (struct hotplug_slot *hs)
{
	int rc, i, rcpr;
	struct slot *slot_cur;
	u8 function;
	u8 faulted = 0;
	struct pci_func *tmp_func;

	ibmphp_lock_operations ();

	debug ("ENABLING SLOT........ \n");
	slot_cur = (struct slot *) hs->private;

	if ((rc = validate (slot_cur, ENABLE))) {
		err ("validate function failed \n");
		attn_off (slot_cur);	/* need to turn off if was blinking b4 */
		attn_on (slot_cur);
		rc = slot_update (&slot_cur);
		if (rc) {
			ibmphp_unlock_operations();
			return rc;
		}
		ibmphp_update_slot_info (slot_cur);
                ibmphp_unlock_operations ();
		return rc;
	}

	attn_LED_blink (slot_cur);
	
	rc = set_bus (slot_cur);
	if (rc) {
		err ("was not able to set the bus \n");
		attn_off (slot_cur);
		attn_on (slot_cur);
		ibmphp_unlock_operations ();
		return -ENODEV;
	}
		
	rc = power_on (slot_cur);

	if (rc) {
		err ("something wrong when powering up... please see below for details\n");
		/* need to turn off before on, otherwise, blinking overwrites */
		attn_off(slot_cur);
		attn_on (slot_cur);
		if (slot_update (&slot_cur)) {
			attn_off (slot_cur);
			attn_on (slot_cur);
			ibmphp_unlock_operations ();
			return -ENODEV;
		}
		/* Check to see the error of why it failed */
		if (!(SLOT_PWRGD (slot_cur->status)))
			err ("power fault occured trying to power up \n");
		else if (SLOT_BUS_SPEED (slot_cur->status)) {
			err ("bus speed mismatch occured.  please check current bus speed and card capability \n");
			print_card_capability (slot_cur);
		} else if (SLOT_BUS_MODE (slot_cur->ext_status)) 
			err ("bus mode mismatch occured.  please check current bus mode and card capability \n");

		ibmphp_update_slot_info (slot_cur);
		ibmphp_unlock_operations (); 
		return rc;
	}
	debug ("after power_on\n");

	rc = slot_update (&slot_cur);
	if (rc) {
		attn_off (slot_cur);
		attn_on (slot_cur);
		rcpr = power_off (slot_cur);
		if (rcpr) {
			ibmphp_unlock_operations ();
			return rcpr;
		}
		ibmphp_unlock_operations ();
		return rc;
	}
	
	if (SLOT_POWER (slot_cur->status) && !(SLOT_PWRGD (slot_cur->status))) {
		faulted = 1;
		err ("power fault occured trying to power up...\n");
	} else if (SLOT_POWER (slot_cur->status) && (SLOT_BUS_SPEED (slot_cur->status))) {
		faulted = 1;
		err ("bus speed mismatch occured.  please check current bus speed and card capability \n");
		print_card_capability (slot_cur);
	} 
	/* Don't think this case will happen after above checks... but just in case, for paranoia sake */
	else if (!(SLOT_POWER (slot_cur->status))) {
		err ("power on failed... \n");
		faulted = 1;
	}
	if (faulted) {
		attn_off (slot_cur);	/* need to turn off b4 on */
		attn_on (slot_cur);
		rcpr = power_off (slot_cur);
		if (rcpr) {
			ibmphp_unlock_operations ();
			return rcpr;
		}
			
		if (slot_update (&slot_cur)) {
			ibmphp_unlock_operations ();
			return -ENODEV;
		}
		ibmphp_update_slot_info (slot_cur);
		ibmphp_unlock_operations ();
		return -EINVAL;
	}

	slot_cur->func = (struct pci_func *) kmalloc (sizeof (struct pci_func), GFP_KERNEL);
	if (!slot_cur->func) { /* We cannot do update_slot_info here, since no memory for kmalloc n.e.ways, and update_slot_info allocates some */
		err ("out of system memory \n");
		attn_off (slot_cur);
		attn_on (slot_cur);
		rcpr = power_off (slot_cur);
		if (rcpr) {
			ibmphp_unlock_operations ();
			return rcpr;
		}
		ibmphp_unlock_operations ();
		return -ENOMEM;
	}
	memset (slot_cur->func, 0, sizeof (struct pci_func));
	slot_cur->func->busno = slot_cur->bus;
	slot_cur->func->device = slot_cur->device;
	for (i = 0; i < 4; i++)
		slot_cur->func->irq[i] = slot_cur->irq[i];

	debug ("b4 configure_card, slot_cur->bus = %x, slot_cur->device = %x\n", slot_cur->bus, slot_cur->device);

	if (ibmphp_configure_card (slot_cur->func, slot_cur->number)) {
		err ("configure_card was unsuccessful... \n");
		ibmphp_unconfigure_card (&slot_cur, 1); /* true because don't need to actually deallocate resources, just remove references */
		debug ("after unconfigure_card\n");
		slot_cur->func = NULL;
		attn_off (slot_cur);	/* need to turn off in case was blinking */
		attn_on (slot_cur);
		rcpr = power_off (slot_cur);
		if (rcpr) {
			ibmphp_unlock_operations ();
			return rcpr;
		}
		if (slot_update (&slot_cur)) {
			ibmphp_unlock_operations();
			return -ENODEV;
		}
		ibmphp_update_slot_info (slot_cur);
		ibmphp_unlock_operations ();
		return -ENOMEM;
	}
	function = 0x00;
	do {
		tmp_func = ibm_slot_find (slot_cur->bus, slot_cur->func->device, function++);
		if (tmp_func && !(tmp_func->dev))
			ibm_configure_device (tmp_func);
	} while (tmp_func);

	attn_off (slot_cur);
	if (slot_update (&slot_cur)) {
		ibmphp_unlock_operations ();
		return -EFAULT;
	}
	ibmphp_print_test ();
	rc = ibmphp_update_slot_info (slot_cur);
	ibmphp_unlock_operations(); 
	return rc;
}

/**************************************************************
* HOT REMOVING ADAPTER CARD                                   *
* INPUT: POINTER TO THE HOTPLUG SLOT STRUCTURE                *
* OUTPUT: SUCCESS 0 ; FAILURE: UNCONFIGURE , VALIDATE         *
          DISABLE POWER ,                                    *
**************************************************************/
int ibmphp_disable_slot (struct hotplug_slot *hotplug_slot)
{
	int rc;
	struct slot *slot_cur = (struct slot *) hotplug_slot->private;
	u8 flag = slot_cur->flag;

	slot_cur->flag = TRUE;
	debug ("DISABLING SLOT... \n"); 
		
	ibmphp_lock_operations (); 
	if (slot_cur == NULL) {
		ibmphp_unlock_operations ();
		return -ENODEV;
	}
	if (slot_cur->ctrl == NULL) {
		ibmphp_unlock_operations ();
		return -ENODEV;
	}
	if (flag == TRUE) {
		rc = validate (slot_cur, DISABLE);	/* checking if powered off already & valid slot # */
		if (rc) {
			/*  Need to turn off if was blinking b4 */
			attn_off (slot_cur);
			attn_on (slot_cur);
			if (slot_update (&slot_cur)) {
				ibmphp_unlock_operations ();
				return -EFAULT;
			}
		
			ibmphp_update_slot_info (slot_cur);
			ibmphp_unlock_operations ();
			return rc;
		}
	}
	attn_LED_blink (slot_cur);

	if (slot_cur->func == NULL) {
		/* We need this for fncs's that were there on bootup */
		slot_cur->func = (struct pci_func *) kmalloc (sizeof (struct pci_func), GFP_KERNEL);
		if (!slot_cur->func) {
			err ("out of system memory \n");
			attn_off (slot_cur);
			attn_on (slot_cur);
			ibmphp_unlock_operations ();
			return -ENOMEM;
		}
		memset (slot_cur->func, 0, sizeof (struct pci_func));
		slot_cur->func->busno = slot_cur->bus;
		slot_cur->func->device = slot_cur->device;
	}

	if ((rc = ibm_unconfigure_device (slot_cur->func))) {
		err ("removing from kernel failed... \n");
		err ("Please check to see if it was statically linked or is in use otherwise. (perhaps the driver is not 'hot-removable')\n");
		attn_off (slot_cur);
		attn_on (slot_cur);
		ibmphp_unlock_operations ();
		return rc;
	}

	rc = ibmphp_unconfigure_card (&slot_cur, 0);
	slot_cur->func = NULL;
	debug ("in disable_slot. after unconfigure_card \n");
	if (rc) {
		err ("could not unconfigure card.\n");
		attn_off (slot_cur);	/* need to turn off if was blinking b4 */
		attn_on (slot_cur);

		if (slot_update (&slot_cur)) {
			ibmphp_unlock_operations ();
			return -EFAULT;
		}

		if (flag) 
			ibmphp_update_slot_info (slot_cur);

		ibmphp_unlock_operations ();
		return -EFAULT;
	}

	rc = ibmphp_hpc_writeslot (hotplug_slot->private, HPC_SLOT_OFF);
	if (rc) {
		attn_off (slot_cur);
		attn_on (slot_cur);
		if (slot_update (&slot_cur)) {
			ibmphp_unlock_operations ();
			return -EFAULT;
		}

		if (flag)
			ibmphp_update_slot_info (slot_cur);

		ibmphp_unlock_operations ();
		return rc;
	}

	attn_off (slot_cur);
	if (slot_update (&slot_cur)) {
		ibmphp_unlock_operations ();
		return -EFAULT;
	}
	if (flag) 
		rc = ibmphp_update_slot_info (slot_cur);
	else
		rc = 0;

	ibmphp_print_test ();
	ibmphp_unlock_operations();
	return rc;
}

struct hotplug_slot_ops ibmphp_hotplug_slot_ops = {
	owner:				THIS_MODULE,
	set_attention_status:		set_attention_status,
	enable_slot:			enable_slot,
	disable_slot:			ibmphp_disable_slot,
	hardware_test:			NULL,
	get_power_status:		get_power_status,
	get_attention_status:		get_attention_status,
	get_latch_status:		get_latch_status,
	get_adapter_status:		get_adapter_present,
/*	get_max_bus_speed_status:	get_max_bus_speed,
	get_max_adapter_speed_status:	get_max_adapter_speed,
	get_cur_bus_speed_status:	get_cur_bus_speed,
	get_card_bus_names_status:	get_card_bus_names,
*/
};

static void ibmphp_unload (void)
{
	free_slots ();
	debug ("after slots \n");
	ibmphp_free_resources ();
	debug ("after resources \n");
	ibmphp_free_bus_info_queue ();
	debug ("after bus info \n");
	ibmphp_free_ebda_hpc_queue ();
	debug ("after ebda hpc \n");
	ibmphp_free_ebda_pci_rsrc_queue ();
	debug ("after ebda pci rsrc \n");
}

static int __init ibmphp_init (void)
{
	int i = 0;
	int rc = 0;

	init_flag = 1;
	ibmphp_pci_root_ops = get_root_pci_ops ();
	if (ibmphp_pci_root_ops == NULL) {
		err ("cannot read bus operations... will not be able to read the cards.  Please check your system \n");
		return -ENODEV;	
	}

	ibmphp_debug = debug;

	ibmphp_hpc_initvars ();

	for (i = 0; i < 16; i++)
		irqs[i] = 0;

	if ((rc = ibmphp_access_ebda ())) {
		ibmphp_unload ();
		return rc;
	}
	debug ("after ibmphp_access_ebda () \n");

	if ((rc = ibmphp_rsrc_init ())) {
		ibmphp_unload ();
		return rc;
	}
	debug ("AFTER Resource & EBDA INITIALIZATIONS \n");

	max_slots = get_max_slots ();

	if (init_ops ()) {
		ibmphp_unload ();
		return -ENODEV;
	}
	ibmphp_print_test ();
	if ((rc = ibmphp_hpc_start_poll_thread ())) {
		ibmphp_unload ();
		return -ENODEV;
	}

	/* lock ourselves into memory with a module count of -1 
	 * so that no one can unload us. */
	MOD_DEC_USE_COUNT;

	info (DRIVER_DESC " version: " DRIVER_VERSION "\n");

	return 0;
}

static void __exit ibmphp_exit (void)
{
	ibmphp_hpc_stop_poll_thread ();
	debug ("after polling \n");
	ibmphp_unload ();
	debug ("done \n");
}

module_init (ibmphp_init);
module_exit (ibmphp_exit);
